ruby-msg 1.2.17

Sign up to get free protection for your applications and to get access to all the features.
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
+