ruby-msg 1.2.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/ole/types.rb ADDED
@@ -0,0 +1,36 @@
1
+ require 'iconv'
2
+ require 'date'
3
+
4
+ require 'ole/base'
5
+
6
+ module Ole # :nodoc:
7
+ # FIXME
8
+ module Types
9
+ FROM_UTF16 = Iconv.new 'utf-8', 'utf-16le'
10
+ TO_UTF16 = Iconv.new 'utf-16le', 'utf-8'
11
+ EPOCH = DateTime.parse '1601-01-01'
12
+ # Create a +DateTime+ object from a struct +FILETIME+
13
+ # (http://msdn2.microsoft.com/en-us/library/ms724284.aspx).
14
+ #
15
+ # Converts +str+ to two 32 bit time values, comprising the high and low 32 bits of
16
+ # the 100's of nanoseconds since 1st january 1601 (Epoch).
17
+ def self.load_time str
18
+ low, high = str.unpack 'L2'
19
+ # we ignore these, without even warning about it
20
+ return nil if low == 0 and high == 0
21
+ time = EPOCH + (high * (1 << 32) + low) * 1e-7 / 86400 rescue return
22
+ # extra sanity check...
23
+ unless (1800...2100) === time.year
24
+ Log.warn "ignoring unlikely time value #{time.to_s}"
25
+ return nil
26
+ end
27
+ time
28
+ end
29
+
30
+ # Convert a binary guid into a plain string (will move to proper class later).
31
+ def self.load_guid str
32
+ "{%08x-%04x-%04x-%02x%02x-#{'%02x' * 6}}" % str.unpack('L S S CC C6')
33
+ end
34
+ end
35
+ end
36
+
@@ -0,0 +1,218 @@
1
+ # = OrderedHash
2
+ #
3
+ # == Version
4
+ # 1.2006.07.13 (change of the first number means Big Change)
5
+ #
6
+ # == Description
7
+ # Hash which preserves order of added items (like PHP array).
8
+ #
9
+ # == Usage
10
+ #
11
+ # (see examples directory under the ruby gems root directory)
12
+ #
13
+ # require 'rubygems'
14
+ # require 'ordered_hash'
15
+ #
16
+ # hsh = OrderedHash.new
17
+ # hsh['z'] = 1
18
+ # hsh['a'] = 2
19
+ # hsh['c'] = 3
20
+ # p hsh.keys # ['z','a','c']
21
+ #
22
+ # == Source
23
+ # http://simplypowerful.1984.cz/goodlibs/1.2006.07.13
24
+ #
25
+ # == Author
26
+ # jan molic (/mig/at_sign/1984/dot/cz/)
27
+ #
28
+ # == Thanks to
29
+ # Andrew Johnson for his suggestions and fixes of Hash[], merge, to_a, inspect and shift
30
+ # Desmond Dsouza for == fixes
31
+ #
32
+ # == Licence
33
+ # You can redistribute it and/or modify it under the same terms of Ruby's license;
34
+ # either the dual license version in 2003, or any later version.
35
+ #
36
+
37
+ class OrderedHash < Hash
38
+
39
+ attr_accessor :order
40
+
41
+ class << self
42
+
43
+ def [] *args
44
+ hsh = OrderedHash.new
45
+ if Hash === args[0]
46
+ hsh.replace args[0]
47
+ elsif (args.size % 2) != 0
48
+ raise ArgumentError, "odd number of elements for Hash"
49
+ else
50
+ hsh[args.shift] = args.shift while args.size > 0
51
+ end
52
+ hsh
53
+ end
54
+
55
+ end
56
+
57
+ def initialize(*a, &b)
58
+ super
59
+ @order = []
60
+ end
61
+
62
+ def store_only a,b
63
+ store a,b
64
+ end
65
+
66
+ alias orig_store store
67
+
68
+ def store a,b
69
+ @order.push a unless has_key? a
70
+ super a,b
71
+ end
72
+
73
+ alias []= store
74
+
75
+ def == hsh2
76
+ return hsh2==self if !hsh2.is_a?(OrderedHash)
77
+ return false if @order != hsh2.order
78
+ super hsh2
79
+ end
80
+
81
+ def clear
82
+ @order = []
83
+ super
84
+ end
85
+
86
+ def delete key
87
+ @order.delete key
88
+ super
89
+ end
90
+
91
+ def each_key
92
+ @order.each { |k| yield k }
93
+ self
94
+ end
95
+
96
+ def each_value
97
+ @order.each { |k| yield self[k] }
98
+ self
99
+ end
100
+
101
+ def each
102
+ @order.each { |k| yield k,self[k] }
103
+ self
104
+ end
105
+
106
+ alias each_pair each
107
+
108
+ def delete_if
109
+ @order.clone.each { |k|
110
+ delete k if yield
111
+ }
112
+ self
113
+ end
114
+
115
+ def values
116
+ ary = []
117
+ @order.each { |k| ary.push self[k] }
118
+ ary
119
+ end
120
+
121
+ def keys
122
+ @order
123
+ end
124
+
125
+ def invert
126
+ hsh2 = Hash.new
127
+ @order.each { |k| hsh2[self[k]] = k }
128
+ hsh2
129
+ end
130
+
131
+ def reject &block
132
+ self.dup.delete_if( &block )
133
+ end
134
+
135
+ def reject! &block
136
+ hsh2 = reject( &block )
137
+ self == hsh2 ? nil : hsh2
138
+ end
139
+
140
+ def replace hsh2
141
+ @order = hsh2.keys
142
+ super hsh2
143
+ end
144
+
145
+ def shift
146
+ key = @order.first
147
+ key ? [key,delete(key)] : super
148
+ end
149
+
150
+ def unshift k,v
151
+ unless self.include? k
152
+ @order.unshift k
153
+ orig_store(k,v)
154
+ true
155
+ else
156
+ false
157
+ end
158
+ end
159
+
160
+ def push k,v
161
+ unless self.include? k
162
+ @order.push k
163
+ orig_store(k,v)
164
+ true
165
+ else
166
+ false
167
+ end
168
+ end
169
+
170
+ def pop
171
+ key = @order.last
172
+ key ? [key,delete(key)] : nil
173
+ end
174
+
175
+ def first
176
+ self[@order.first]
177
+ end
178
+
179
+ def last
180
+ self[@order.last]
181
+ end
182
+
183
+ def to_a
184
+ ary = []
185
+ each { |k,v| ary << [k,v] }
186
+ ary
187
+ end
188
+
189
+ def to_s
190
+ self.to_a.to_s
191
+ end
192
+
193
+ def inspect
194
+ ary = []
195
+ each {|k,v| ary << k.inspect + "=>" + v.inspect}
196
+ '{' + ary.join(", ") + '}'
197
+ end
198
+
199
+ def update hsh2
200
+ hsh2.each { |k,v| self[k] = v }
201
+ self
202
+ end
203
+
204
+ alias :merge! update
205
+
206
+ def merge hsh2
207
+ self.dup update(hsh2)
208
+ end
209
+
210
+ def select
211
+ ary = []
212
+ each { |k,v| ary << [k,v] if yield k,v }
213
+ ary
214
+ end
215
+
216
+ end
217
+
218
+ #=end
data/lib/rtf.rb ADDED
@@ -0,0 +1,118 @@
1
+ #! /usr/bin/ruby -w
2
+
3
+ require 'stringio'
4
+
5
+ # this file is pretty crap, its just to ensure there is always something readable if
6
+ # there is an rtf only body, with no html encapsulation.
7
+
8
+ module RTF
9
+ class Tokenizer
10
+ def self.process io
11
+ while true do
12
+ case c = io.getc
13
+ when ?{; yield :open_group
14
+ when ?}; yield :close_group
15
+ when ?\\
16
+ case c = io.getc
17
+ when ?{, ?}, ?\\; yield :text, c.chr
18
+ when ?'; yield :text, [io.read(2)].pack('H*')
19
+ when ?a..?z, ?A..?Z
20
+ # read control word
21
+ str = c.chr
22
+ str << c while c = io.read(1) and c =~ /[a-zA-Z]/
23
+ neg = 1
24
+ neg = -1 and c = io.read(1) if c == '-'
25
+ num = if c =~ /[0-9]/
26
+ num = c
27
+ num << c while c = io.read(1) and c =~ /[0-9]/
28
+ num.to_i * neg
29
+ end
30
+ raise "invalid rtf stream" if neg == -1 and !num # ???? \blahblah- some text
31
+ io.seek(-1, IO::SEEK_CUR) if c != ' '
32
+ yield :control_word, str, num
33
+ when nil
34
+ raise "invalid rtf stream" # \EOF
35
+ else
36
+ # other kind of control symbol
37
+ yield :control_symbol, c.chr
38
+ end
39
+ when nil
40
+ return
41
+ when ?\r, ?\n
42
+ # ignore
43
+ else yield :text, c.chr
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ class Converter
50
+ # crappy
51
+ def self.rtf2text str, format=:text
52
+ group = 0
53
+ text = ''
54
+ text << "<html>\n<body>" if format == :html
55
+ group_type = []
56
+ group_tags = []
57
+ RTF::Tokenizer.process(StringIO.new(str)) do |a, b, c|
58
+ add_text = ''
59
+ case a
60
+ when :open_group; group += 1; group_type[group] = nil; group_tags[group] = []
61
+ when :close_group; group_tags[group].reverse.each { |t| text << "</#{t}>" }; group -= 1;
62
+ when :control_word; # ignore
63
+ group_type[group] ||= b
64
+ # maybe change this to use utf8 where possible
65
+ add_text = if b == 'par' || b == 'line' || b == 'page'; "\n"
66
+ elsif b == 'tab' || b == 'cell'; "\t"
67
+ elsif b == 'endash' || b == 'emdash'; "-"
68
+ elsif b == 'emspace' || b == 'enspace' || b == 'qmspace'; " "
69
+ elsif b == 'ldblquote'; '"'
70
+ else ''
71
+ end
72
+ if b == 'b' || b == 'i' and format == :html
73
+ close = c == 0 ? '/' : ''
74
+ text << "<#{close}#{b}>"
75
+ if c == 0
76
+ group_tags[group].delete b
77
+ else
78
+ group_tags[group] << b
79
+ end
80
+ end
81
+ # lot of other ones belong in here.\
82
+ =begin
83
+ \bullet Bullet character.
84
+ \lquote Left single quotation mark.
85
+ \rquote Right single quotation mark.
86
+ \ldblquote Left double quotation mark.
87
+ \rdblquote
88
+ =end
89
+ when :control_symbol; # ignore
90
+ group_type[group] ||= b
91
+ add_text = ' ' if b == '~' # non-breakable space
92
+ add_text = '-' if b == '_' # non-breakable hypen
93
+ when :text
94
+ add_text = b if group <= 1 or group_type[group] == 'rtlch' && !group_type[0...group].include?('*')
95
+ end
96
+ if format == :html
97
+ text << add_text.gsub(/([<>&"'])/) do
98
+ ent = { '<' => 'lt', '>' => 'gt', '&' => 'amp', '"' => 'quot', "'" => 'apos' }[$1]
99
+ "&#{ent};"
100
+ end
101
+ text << '<br>' if add_text == "\n"
102
+ else
103
+ text << add_text
104
+ end
105
+ end
106
+ text << "</body>\n</html>\n" if format == :html
107
+ text
108
+ end
109
+ end
110
+ end
111
+
112
+ if $0 == __FILE__
113
+ #str = File.read('test.rtf')
114
+ str = YAML.load(open('rtfs.yaml'))[2]
115
+ #puts str
116
+ puts text
117
+ end
118
+
data/lib/support.rb ADDED
@@ -0,0 +1,51 @@
1
+
2
+ #
3
+ # A file with general support functions used by most files in the project.
4
+ #
5
+
6
+ require 'logger'
7
+
8
+ class File # :nodoc:
9
+ # for consistency with StringIO and others. makes more sense than forcing
10
+ # them to provide a #stat
11
+ def size
12
+ stat.size
13
+ end
14
+ end
15
+
16
+ class Symbol # :nodoc:
17
+ def to_proc
18
+ proc { |a| a.send self }
19
+ end
20
+ end
21
+
22
+ module Enumerable # :nodoc:
23
+ # 1.9 backport
24
+ def group_by
25
+ hash = Hash.new { |hash, key| hash[key] = [] }
26
+ each { |item| hash[yield(item)] << item }
27
+ hash
28
+ end
29
+
30
+ def sum initial=0
31
+ inject(initial) { |a, b| a + b }
32
+ end
33
+ end
34
+
35
+ class Logger # :nodoc:
36
+ # A helper method for creating <tt>Logger</tt>s which produce call stack
37
+ # in their output
38
+ def self.new_with_callstack logdev=STDERR
39
+ log = Logger.new logdev
40
+ log.level = WARN
41
+ log.formatter = proc do |severity, time, progname, msg|
42
+ # find where we were called from, in our code
43
+ callstack = caller.dup
44
+ callstack.shift while callstack.first =~ /\/logger\.rb:\d+:in/
45
+ from = callstack.first.sub(/:in `(.*?)'/, ":\\1")
46
+ "[%s %s]\n%-7s%s\n" % [time.strftime('%H:%M:%S'), from, severity, msg.to_s]
47
+ end
48
+ log
49
+ end
50
+ end
51
+
data/test/test_mime.rb ADDED
@@ -0,0 +1,22 @@
1
+ #! /usr/bin/ruby -w
2
+
3
+ TEST_DIR = File.dirname __FILE__
4
+ $: << "#{TEST_DIR}/../lib"
5
+
6
+ require 'test/unit'
7
+ require 'mime'
8
+
9
+ class TestMime < Test::Unit::TestCase
10
+ # test out the way it partitions a message into parts
11
+ def test_parsing_no_multipart
12
+ mime = Mime.new "Header1: Value1\r\nHeader2: Value2\r\n\r\nBody text."
13
+ assert_equal ['Value1'], mime.headers['Header1']
14
+ assert_equal 'Body text.', mime.body
15
+ assert_equal false, mime.multipart?
16
+ assert_equal nil, mime.parts
17
+ # we get round trip conversion. this is mostly fluke, as orderedhash hasn't been
18
+ # added yet
19
+ assert_equal "Header1: Value1\r\nHeader2: Value2\r\n\r\nBody text.", mime.to_s
20
+ end
21
+ end
22
+
@@ -0,0 +1,139 @@
1
+ #! /usr/bin/ruby -w
2
+
3
+ TEST_DIR = File.dirname __FILE__
4
+ $: << "#{TEST_DIR}/../lib"
5
+
6
+ require 'test/unit'
7
+ require 'ole/storage'
8
+ require 'digest/sha1'
9
+ require 'stringio'
10
+
11
+ #
12
+ # = TODO
13
+ #
14
+ # These tests could be a lot more complete.
15
+ #
16
+
17
+ class TestRangesIO < Test::Unit::TestCase
18
+ def setup
19
+ # why not :) ?
20
+ # repeats too
21
+ ranges = [100..200, 0..10, 100..150]
22
+ @io = RangesIO.new open("#{TEST_DIR}/test_storage.rb"), ranges, :close_parent => true
23
+ end
24
+
25
+ def teardown
26
+ @io.close
27
+ end
28
+
29
+ def test_basic
30
+ assert_equal 160, @io.size
31
+ # this will map to the start of the file:
32
+ @io.pos = 100
33
+ assert_equal '#! /usr/bi', @io.read(10)
34
+ end
35
+
36
+ # should test range_and_offset specifically
37
+
38
+ def test_reading
39
+ # test selection of initial range, offset within that range
40
+ pos = 100
41
+ @io.seek pos
42
+ # test advancing of pos properly, by...
43
+ chunked = (0...10).map { @io.read 10 }.join
44
+ # given the file is 160 long:
45
+ assert_equal 60, chunked.length
46
+ @io.seek pos
47
+ # comparing with a flat read
48
+ assert_equal chunked, @io.read(60)
49
+ end
50
+ end
51
+
52
+ # should test resizeable and migrateable IO.
53
+
54
+ class TestStorageRead < Test::Unit::TestCase
55
+ def setup
56
+ @ole = Ole::Storage.open "#{TEST_DIR}/test_word_6.doc", 'rb'
57
+ end
58
+
59
+ def teardown
60
+ @ole.close
61
+ end
62
+
63
+ def test_header
64
+ # should have further header tests, testing the validation etc.
65
+ assert_equal 17, @ole.header.to_a.length
66
+ assert_equal 117, @ole.header.dirent_start
67
+ assert_equal 1, @ole.header.num_bat
68
+ assert_equal 1, @ole.header.num_sbat
69
+ assert_equal 0, @ole.header.num_mbat
70
+ end
71
+
72
+ def test_fat
73
+ # the fat block has all the numbers from 5..118 bar 117
74
+ bbat_table = [112] + ((5..118).to_a - [112, 117])
75
+ assert_equal bbat_table, @ole.bbat.table.reject { |i| i >= (1 << 32) - 3 }, 'bbat'
76
+ sbat_table = (1..43).to_a - [2, 3]
77
+ assert_equal sbat_table, @ole.sbat.table.reject { |i| i >= (1 << 32) - 3 }, 'sbat'
78
+ end
79
+
80
+ def test_directories
81
+ assert_equal 5, @ole.dirents.length, 'have all directories'
82
+ # a more complicated one would be good for this
83
+ assert_equal 4, @ole.root.children.length, 'properly nested directories'
84
+ end
85
+
86
+ def test_utf16_conversion
87
+ assert_equal 'Root Entry', @ole.root.name
88
+ assert_equal 'WordDocument', @ole.root.children[2].name
89
+ end
90
+
91
+ def test_data
92
+ # test the ole storage type
93
+ type = 'Microsoft Word 6.0-Dokument'
94
+ assert_equal type, @ole.root["\001CompObj"].read[/^.{32}([^\x00]+)/m, 1]
95
+ # i was actually not loading data correctly before, so carefully check everything here
96
+ hashes = [-482597081, 285782478, 134862598, -863988921]
97
+ assert_equal hashes, @ole.root.children.map { |child| child.read.hash }
98
+ end
99
+ end
100
+
101
+ class TestStorageWrite < Test::Unit::TestCase
102
+ def sha1 str
103
+ Digest::SHA1.hexdigest str
104
+ end
105
+
106
+ # FIXME
107
+ # don't really want to lock down the actual internal api's yet. this will just
108
+ # ensure for the time being that #flush continues to work properly. need a host
109
+ # of checks involving writes that resize their file bigger/smaller, that resize
110
+ # the bats to more blocks, that resizes the sb_blocks, that has migration etc.
111
+ def test_write_hash
112
+ io = StringIO.open File.read("#{TEST_DIR}/test_word_6.doc")
113
+ assert_equal '9974e354def8471225f548f82b8d81c701221af7', sha1(io.string)
114
+ Ole::Storage.open(io) { }
115
+ assert_equal 'efa8cfaf833b30b1d1d9381771ddaafdfc95305c', sha1(io.string)
116
+ # add a repack test here
117
+ Ole::Storage.open io, &:repack
118
+ assert_equal 'c8bb9ccacf0aaad33677e1b2a661ee6e66a48b5a', sha1(io.string)
119
+ end
120
+
121
+ def test_plain_repack
122
+ io = StringIO.open File.read("#{TEST_DIR}/test_word_6.doc")
123
+ assert_equal '9974e354def8471225f548f82b8d81c701221af7', sha1(io.string)
124
+ Ole::Storage.open io, &:repack
125
+ # note equivalence to the above flush, repack, flush
126
+ assert_equal 'c8bb9ccacf0aaad33677e1b2a661ee6e66a48b5a', sha1(io.string)
127
+ end
128
+
129
+ def test_create_from_scratch_hash
130
+ io = StringIO.new
131
+ Ole::Storage.new(io) { }
132
+ assert_equal '6bb9d6c1cdf1656375e30991948d70c5fff63d57', sha1(io.string)
133
+ # more repack test, note invariance
134
+ Ole::Storage.open io, &:repack
135
+ assert_equal '6bb9d6c1cdf1656375e30991948d70c5fff63d57', sha1(io.string)
136
+ end
137
+ end
138
+
139
+
Binary file
Binary file
Binary file
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.0
3
+ specification_version: 1
4
+ name: ruby-msg
5
+ version: !ruby/object:Gem::Version
6
+ version: 1.2.17
7
+ date: 2007-05-13 00:00:00 +10:00
8
+ summary: Ruby Msg library.
9
+ require_paths:
10
+ - lib
11
+ email: aquasync@gmail.com
12
+ homepage: http://code.google.com/p/ruby-msg
13
+ rubyforge_project:
14
+ description: A library for reading Outlook msg files, and for converting them to RFC2822 emails.
15
+ autorequire: msg
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Charles Lowe
31
+ files:
32
+ - data/named_map.yaml
33
+ - data/types.yaml
34
+ - data/mapitags.yaml
35
+ - Rakefile
36
+ - README
37
+ - FIXES
38
+ - bin/msgtool
39
+ - bin/oletool
40
+ - lib/orderedhash.rb
41
+ - lib/blah.rb
42
+ - lib/mime-new.rb
43
+ - lib/rtf.rb
44
+ - lib/support.rb
45
+ - lib/mime.rb
46
+ - lib/msg.rb
47
+ - lib/ole/types.rb
48
+ - lib/ole/file_system.rb
49
+ - lib/ole/storage.rb
50
+ - lib/ole/io_helpers.rb
51
+ - lib/ole/base.rb
52
+ - lib/msg/rtf.rb
53
+ - lib/msg/properties.rb
54
+ - test/test_mime.rb
55
+ - test/test_storage.rb
56
+ - test/test_word_6.doc
57
+ - test/test_word_95.doc
58
+ - test/test_word_97.doc
59
+ test_files: []
60
+
61
+ rdoc_options: []
62
+
63
+ extra_rdoc_files: []
64
+
65
+ executables:
66
+ - msgtool
67
+ - oletool
68
+ extensions: []
69
+
70
+ requirements: []
71
+
72
+ dependencies: []
73
+