ruby-msg 1.2.17.3 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/FIXES CHANGED
@@ -32,3 +32,25 @@ recent fixes based on importing results into evolution
32
32
 
33
33
  6. still unsure about how to do my "\r" handling.
34
34
 
35
+ 7. need to join addresses with , instead of ; i think. evolution only shows the
36
+ first one otherwise it appears, but all when they are , separated.
37
+
38
+ 8. need to solve ole storage issues with the very large file using extra bat
39
+ stuff.
40
+
41
+ 9. retest a bit on evolution and thunderbird, and release. tested on a corups
42
+ of >1000 msg files, so should be starting to get pretty good quality.
43
+
44
+ 10. longer term, things fall into a few basic categories:
45
+
46
+ - non mail conversions (look further into vcard, ical et al support for other
47
+ types of msg)
48
+ - further tests and robustness for what i handle now. ie, look into corner
49
+ cases covered so far, and work on the mime code. fix random charset encoding
50
+ issues, in the various weird mime ways, do header wrapping etc etc.
51
+ check fidelity of conversions, and capture some more properties as headers,
52
+ such as importance which i don't do yet.
53
+ - fix that named property bug. tidy up warnings, exceptions.
54
+ - extend conversion to make better html.
55
+ this is longer term. as i don't use the rtf, i need to make my html better.
56
+ emulating some rtf things. harder, not important atm.
data/Rakefile CHANGED
@@ -6,8 +6,9 @@ require 'rake/gempackagetask'
6
6
  require 'rbconfig'
7
7
  require 'fileutils'
8
8
 
9
- $: << './lib'
10
- require 'msg.rb'
9
+ $:.unshift 'lib'
10
+
11
+ require 'msg'
11
12
 
12
13
  PKG_NAME = 'ruby-msg'
13
14
  PKG_VERSION = Msg::VERSION
@@ -23,18 +24,8 @@ end
23
24
  # RDocTask wasn't working for me
24
25
  desc 'Build the rdoc HTML Files'
25
26
  task :rdoc do
26
- system "rdoc -S -N -m Msg -w 2 -t '#{PKG_NAME} documentation' lib"
27
- end
28
-
29
- =begin
30
- Rake::PackageTask.new(PKG_NAME, PKG_VERSION) do |p|
31
- p.need_tar_gz = true
32
- p.package_dir = 'build'
33
- p.package_files.include("Rakefile", "README")
34
- p.package_files.include("contrib/*.c")
35
- p.package_files.include("test/test_*.rb", "test/*.doc", "lib/*.rb", "lib/ole/storage.rb")
27
+ system "rdoc -S -N --main Msg --tab-width 2 --title '#{PKG_NAME} documentation' lib"
36
28
  end
37
- =end
38
29
 
39
30
  spec = Gem::Specification.new do |s|
40
31
  s.name = PKG_NAME
@@ -46,20 +37,26 @@ spec = Gem::Specification.new do |s|
46
37
  s.homepage = %q{http://code.google.com/p/ruby-msg}
47
38
  #s.rubyforge_project = %q{ruby-msg}
48
39
 
49
- s.executables = ['msgtool', 'oletool']
40
+ s.executables = ['msgtool']
50
41
  s.files = Dir.glob('data/*.yaml') + ['Rakefile', 'README', 'FIXES']
51
42
  s.files += Dir.glob("lib/**/*.rb")
52
- s.files += Dir.glob("test/test_*.rb") + Dir.glob("test/*.doc")
43
+ s.files += Dir.glob("test/test_*.rb")
53
44
  s.files += Dir.glob("bin/*")
54
45
 
55
46
  s.has_rdoc = true
47
+ s.rdoc_options += ['--main', 'Msg',
48
+ '--title', "#{PKG_NAME} documentation",
49
+ '--tab-width', '2']
50
+
56
51
 
57
52
  s.autorequire = 'msg'
53
+
54
+ s.add_dependency 'ruby-ole', '>=1.2.1'
58
55
  end
59
56
 
60
57
  Rake::GemPackageTask.new(spec) do |p|
61
58
  p.gem_spec = spec
62
- # p.need_tar = true
59
+ p.need_tar = true
63
60
  p.need_zip = false
64
61
  p.package_dir = 'build'
65
62
  end
@@ -32,7 +32,7 @@ def msgtool
32
32
  end
33
33
  # just shut up and convert a message to eml
34
34
  Msg::Log.level = Ole::Log.level = opts[:verbose] ? Logger::WARN : Logger::FATAL
35
- # fix for windows
35
+ # for windows. see issue #2
36
36
  STDOUT.binmode
37
37
  case opts[:action]
38
38
  when :convert
data/lib/msg.rb CHANGED
@@ -5,7 +5,7 @@ $: << File.dirname(__FILE__)
5
5
  require 'yaml'
6
6
  require 'base64'
7
7
 
8
- require 'support'
8
+ require 'rubygems'
9
9
  require 'ole/storage'
10
10
  require 'msg/properties'
11
11
  require 'msg/rtf'
@@ -20,7 +20,7 @@ require 'mime'
20
20
  #
21
21
 
22
22
  class Msg
23
- VERSION = '1.2.17.3'
23
+ VERSION = '1.3.1'
24
24
  # we look here for the yaml files in data/, and the exe files for support
25
25
  # decoding at the moment.
26
26
  SUPPORT_DIR = File.dirname(__FILE__) + '/..'
@@ -144,9 +144,9 @@ class Msg
144
144
  # of the ole name, or just leave it if we can't
145
145
  recips = recips_by_type[type]
146
146
  recips = (recips.sort_by { |r| r.obj.name[/\d{8}$/].hex } rescue recips)
147
- # are you supposed to use ; or , to separate?
147
+ # switched to using , for separation, not ;. see issue #4
148
148
  # recips.empty? is strange. i wouldn't have thought it possible, but it was right?
149
- headers[type.to_s.sub(/^(.)/) { $1.upcase }] = [recips.join('; ')] unless recips.empty?
149
+ headers[type.to_s.sub(/^(.)/) { $1.upcase }] = [recips.join(', ')] unless recips.empty?
150
150
  end
151
151
  headers['Subject'] = [props.subject] if props.subject
152
152
 
@@ -171,11 +171,28 @@ class Msg
171
171
  headers['Date'] = [Time.iso8601(time.to_s).rfc2822] if time
172
172
  end
173
173
 
174
- if !headers.has_key?('Message-ID') and props.internet_message_id
175
- headers['Message-ID'] = [props.internet_message_id]
176
- end
177
- if !headers.has_key?('In-Reply-To') and props.in_reply_to_id
178
- headers['In-Reply-To'] = [props.in_reply_to_id]
174
+ # some very simplistic mapping between internet message headers and the
175
+ # mapi properties
176
+ # any of these could be causing duplicates due to case issues. the hack in #to_mime
177
+ # just stops re-duplication at that point. need to move some smarts into the mime
178
+ # code to handle it.
179
+ mapi_header_map = [
180
+ [:internet_message_id, 'Message-ID'],
181
+ [:in_reply_to_id, 'In-Reply-To'],
182
+ # don't set these values if they're equal to the defaults anyway
183
+ [:importance, 'Importance', proc { |val| val.to_s == '1' ? nil : val }],
184
+ [:priority, 'Priority', proc { |val| val.to_s == '1' ? nil : val }],
185
+ [:sensitivity, 'Sensitivity', proc { |val| val.to_s == '0' ? nil : val }],
186
+ # yeah?
187
+ [:conversation_topic, 'Thread-Topic'],
188
+ # not sure of the distinction here
189
+ # :originator_delivery_report_requested ??
190
+ [:read_receipt_requested, 'Disposition-Notification-To', proc { |val| from }]
191
+ ]
192
+ mapi_header_map.each do |mapi, mime, *f|
193
+ next unless q = val = props.send(mapi) or headers.has_key?(mime)
194
+ next if f[0] and !(val = f[0].call(val))
195
+ headers[mime] = [val.to_s]
179
196
  end
180
197
  end
181
198
 
@@ -48,24 +48,28 @@ class Msg
48
48
  # There also needs to be a way to look up properties more specifically:
49
49
  #
50
50
  # properties[0x0037] # => gets the subject
51
- # properties[PS_MAPI, 0x0037] # => still gets the subject
52
- # properties[PS_PUBLIC_STRINGS, 'Keywords'] # => gets the above categories
51
+ # properties[0x0037, PS_MAPI] # => still gets the subject
52
+ # properties['Keywords', PS_PUBLIC_STRINGS] # => gets outlook's categories array
53
53
  #
54
- # The abbreviate versions work by "resolving" the symbols to full keys:
54
+ # The abbreviated versions work by "resolving" the symbols to full keys:
55
55
  #
56
- # properties.resolve :keywords # => [PS_OUTLOOK, 'Keywords']
57
- # properties.resolve :subject # => [PS_MAPI, 0x0037]
56
+ # # the guid here is just PS_PUBLIC_STRINGS
57
+ # properties.resolve :keywords # => #<Key {00020329-0000-0000-c000-000000000046}/"Keywords">
58
+ # # the result here is actually also a key
59
+ # k = properties.resolve :subject # => 0x0037
60
+ # # it has a guid
61
+ # k.guid == Msg::Properties::PS_MAPI # => true
58
62
  #
59
63
  # = Parsing
60
64
  #
61
65
  # There are three objects that need to be parsed to load a +Msg+ property store:
62
66
  #
63
- # 1. The +nameid+ directory (<tt>Properties.parse_nameid</tt>)
67
+ # 1. The +nameid+ directory (<tt>Properties.parse_nameid</tt>)
64
68
  # 2. The many +substg+ objects, whose names should match <tt>Properties::SUBSTG_RX</tt>
65
69
  # (<tt>Properties#parse_substg</tt>)
66
70
  # 3. The +properties+ file (<tt>Properties#parse_properties</tt>)
67
71
  #
68
- # Understanding of the formats is by no means perfect
72
+ # Understanding of the formats is by no means perfect.
69
73
  #
70
74
  # = TODO
71
75
  #
@@ -79,7 +83,7 @@ class Msg
79
83
  # current greedy-loading approach. still want strings to work nicely:
80
84
  # props.subject
81
85
  # but don't want to be loading up large binary blobs, typically attachments, eg
82
- # props.attach_data.
86
+ # props.attach_data
83
87
  # probably the easiest solution is that the binary "encoding", be to return an io
84
88
  # object instead. and you must read it if you want it as a string
85
89
  # maybe i can avoid the greedy model anyway? rather than parsing the properties completely,
@@ -133,9 +137,13 @@ class Msg
133
137
  attr_reader :unused
134
138
  attr_reader :nameid
135
139
 
140
+ # +nameid+ is to provide a way to inherit from parent (needed for property sets for
141
+ # attachments and recipients, which inherit from the msg itself. what about nested
142
+ # msg??)
136
143
  def initialize
137
144
  @raw = {}
138
145
  @unused = []
146
+ @nameid = nil
139
147
  # FIXME
140
148
  @body_rtf = @body_html = @body = false
141
149
  end
@@ -144,7 +152,7 @@ class Msg
144
152
  # The parsing methods
145
153
  #++
146
154
 
147
- def self.load obj
155
+ def self.load obj, ignore=nil
148
156
  prop = Properties.new
149
157
  prop.load obj
150
158
  prop
@@ -154,9 +162,16 @@ class Msg
154
162
  def load obj
155
163
  # we need to do the nameid first, as it provides the map for later user defined properties
156
164
  children = obj.children.dup
157
- @nameid = if nameid_obj = children.find { |child| child.name == '__nameid_version1.0' }
165
+ if nameid_obj = children.find { |child| child.name == '__nameid_version1.0' }
158
166
  children.delete nameid_obj
159
- Properties.parse_nameid nameid_obj
167
+ @nameid = Properties.parse_nameid nameid_obj
168
+ # hack to make it available to all msg files from the same ole storage object
169
+ class << obj.ole
170
+ attr_accessor :msg_nameid
171
+ end
172
+ obj.ole.msg_nameid = @nameid
173
+ elsif obj.ole
174
+ @nameid = obj.ole.msg_nameid rescue nil
160
175
  end
161
176
  # now parse the actual properties. i think dirs that match the substg should be decoded
162
177
  # as properties to. 0x000d is just another encoding, the dir encoding. it should match
@@ -310,6 +325,8 @@ class Msg
310
325
  elsif real_key = @nameid[key]
311
326
  key = real_key
312
327
  else
328
+ # i think i hit these when i have a named property, in the PS_MAPI
329
+ # guid
313
330
  Log.warn "property in named range not in nameid #{key.inspect}"
314
331
  key = Key.new key
315
332
  end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: ruby-msg
5
5
  version: !ruby/object:Gem::Version
6
- version: 1.2.17.3
7
- date: 2007-07-12 00:00:00 +10:00
6
+ version: 1.3.1
7
+ date: 2007-08-21 00:00:00 +10:00
8
8
  summary: Ruby Msg library.
9
9
  require_paths:
10
10
  - lib
@@ -36,36 +36,37 @@ files:
36
36
  - README
37
37
  - FIXES
38
38
  - bin/msgtool
39
- - bin/oletool
40
39
  - lib/orderedhash.rb
41
40
  - lib/rtf.rb
42
- - lib/support.rb
43
41
  - lib/mime.rb
44
42
  - lib/msg.rb
45
- - lib/ole/types.rb
46
- - lib/ole/file_system.rb
47
- - lib/ole/storage.rb
48
- - lib/ole/io_helpers.rb
49
- - lib/ole/base.rb
50
43
  - lib/msg/rtf.rb
51
44
  - lib/msg/properties.rb
52
45
  - test/test_mime.rb
53
- - test/test_storage.rb
54
- - test/test_word_6.doc
55
- - test/test_word_95.doc
56
- - test/test_word_97.doc
57
46
  test_files: []
58
47
 
59
- rdoc_options: []
60
-
48
+ rdoc_options:
49
+ - --main
50
+ - Msg
51
+ - --title
52
+ - ruby-msg documentation
53
+ - --tab-width
54
+ - "2"
61
55
  extra_rdoc_files: []
62
56
 
63
57
  executables:
64
58
  - msgtool
65
- - oletool
66
59
  extensions: []
67
60
 
68
61
  requirements: []
69
62
 
70
- dependencies: []
71
-
63
+ dependencies:
64
+ - !ruby/object:Gem::Dependency
65
+ name: ruby-ole
66
+ version_requirement:
67
+ version_requirements: !ruby/object:Gem::Version::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: 1.2.1
72
+ version:
@@ -1,35 +0,0 @@
1
- #! /usr/bin/ruby
2
-
3
- require 'optparse'
4
- require 'rubygems'
5
- require 'ole/storage'
6
-
7
- def oletool
8
- opts = {:verbose => false, :action => :tree}
9
- op = OptionParser.new do |op|
10
- op.banner = "Usage: oletool [options] [files]"
11
- op.separator ''
12
- op.on('-t', '--tree', 'Dump ole trees for files (default)') { opts[:action] = :tree }
13
- op.on('-r', '--repack', 'Repack the ole files in canonical form') { opts[:action] = :repack }
14
- op.separator ''
15
- op.on('-v', '--[no-]verbose', 'Run verbosely') { |v| opts[:verbose] = v }
16
- op.on_tail('-h', '--help', 'Show this message') { puts op; exit }
17
- end
18
- files = op.parse ARGV
19
- if files.empty?
20
- puts 'Must specify 1 or more msg files.'
21
- puts op
22
- exit 1
23
- end
24
- Ole::Log.level = opts[:verbose] ? Logger::WARN : Logger::FATAL
25
- files.each do |file|
26
- case opts[:action]
27
- when :tree
28
- Ole::Storage.open(file) { |ole| puts ole.root.to_tree }
29
- when :repack
30
- Ole::Storage.open(file, &:repack)
31
- end
32
- end
33
- end
34
-
35
- oletool
@@ -1,5 +0,0 @@
1
-
2
- module Ole # :nodoc:
3
- Log = Logger.new_with_callstack
4
- end
5
-
@@ -1,181 +0,0 @@
1
- #
2
- # = Introduction
3
- #
4
- # This file intends to provide file system-like api support, a la <tt>zip/zipfilesystem</tt>.
5
- #
6
- # Ideally, this will be the recommended interface, allowing Ole::Storage, Dir, and
7
- # Zip::ZipFile to be used exchangablyk. It should be possible to write recursive copy using
8
- # the plain api, such that you can copy dirs/files agnostically between any of ole docs, dirs,
9
- # and zip files.
10
- #
11
- # = Usage
12
- #
13
- # Currently you can do something like the following:
14
- #
15
- # Ole::Storage.open 'test.doc' do |ole|
16
- # ole.dir.entries '/' # => [".", "..", "\001Ole", "1Table", "\001CompObj", ...]
17
- # ole.file.read "\001CompObj" # => "\001\000\376\377\003\n\000\000\377\377..."
18
- # end
19
- #
20
- # = Notes
21
- #
22
- # *** This file is very incomplete
23
- #
24
- # i think its okay to have an api like this on top, but there are certain things that ole
25
- # does that aren't captured.
26
- # <tt>Ole::Storage</tt> can have multiple files with the same name, for example, or with
27
- # / in the name, and other things that are probably invalid anyway.
28
- # i think this should remain an addon, built on top of my core api.
29
- # but still the ideas can be reflected in the core, ie, changing the read/write semantics.
30
- #
31
- # once the core changes are complete, this will be a pretty straight forward file to complete.
32
- #
33
-
34
- require 'ole/base'
35
-
36
- module Ole # :nodoc:
37
- class Storage
38
- def file
39
- @file ||= FileParent.new self
40
- end
41
-
42
- def dir
43
- @dir ||= DirParent.new self
44
- end
45
-
46
- def dirent_from_path path_str
47
- path = path_str.sub(/^\/*/, '').sub(/\/*$/, '')
48
- dirent = @root
49
- return dirent if path.empty?
50
- path = path.split /\/+/
51
- until path.empty?
52
- raise "invalid path #{path_str.inspect}" if dirent.file?
53
- if tmp = dirent[path.shift]
54
- dirent = tmp
55
- else
56
- # allow write etc later.
57
- raise "invalid path #{path_str.inspect}"
58
- end
59
- end
60
- dirent
61
- end
62
-
63
- class FileParent
64
- def initialize ole
65
- @ole = ole
66
- end
67
-
68
- def open path_str, mode='r', &block
69
- dirent = @ole.dirent_from_path path_str
70
- # like Errno::EISDIR
71
- raise "#{path_str.inspect} is a directory" unless dirent.file?
72
- dirent.open(&block)
73
- end
74
-
75
- alias new :open
76
-
77
- def read path
78
- open(path) { |f| f.read }
79
- end
80
-
81
- # crappy copy from Dir.
82
- def unlink path
83
- dirent = @ole.dirent_from_path path
84
- # EPERM
85
- raise "operation not permitted #{path.inspect}" unless dirent.file?
86
- # i think we should free all of our blocks. i think the best way to do that would be
87
- # like:
88
- # open(path) { |f| f.truncate 0 }. which should free all our blocks from the
89
- # allocation table. then if we remove ourself from our parent, we won't be part of
90
- # the bat at save time.
91
- # i think if you run repack, all free blocks should get zeroed.
92
- open(path) { |f| f.truncate 0 }
93
- parent = @ole.dirent_from_path(('/' + path).sub(/\/[^\/]+$/, ''))
94
- parent.children.delete dirent
95
- 1 # hmmm. as per ::File ?
96
- end
97
- end
98
-
99
- class DirParent
100
- def initialize ole
101
- @ole = ole
102
- end
103
-
104
- def open path_str
105
- dirent = @ole.dirent_from_path path_str
106
- # like Errno::ENOTDIR
107
- raise "#{path_str.inspect} is not a directory" unless dirent.dir?
108
- dir = Dir.new dirent, path_str
109
- if block_given?
110
- yield dir
111
- else
112
- dir
113
- end
114
- end
115
-
116
- # certain Dir class methods proxy in this fashion:
117
- def entries path
118
- open(path) { |dir| dir.entries }
119
- end
120
-
121
- # there are some other important ones, like:
122
- # chroot (!), mkdir, chdir, rmdir, glob etc etc. for now, i think
123
- # mkdir, and rmdir are the main ones we'd need to support
124
- def rmdir path
125
- dirent = @ole.dirent_from_path path
126
- # repeating myself
127
- raise "#{path.inspect} is not a directory" unless dirent.dir?
128
- # ENOTEMPTY:
129
- raise "directory not empty #{path.inspect}" unless dirent.children.empty?
130
- # now delete it, how to do that? the canonical representation that is
131
- # maintained is the root tree, and the children array. we must remove it
132
- # from the children array.
133
- # we need the parent then. this sucks but anyway:
134
- parent = @ole.dirent_from_path path.sub(/\/[^\/]+$/, '') || '/'
135
- # note that the way this currently works, on save and repack time this will get
136
- # reflected. to work properly, ie to make a difference now it would have to re-write
137
- # the dirent. i think that Ole::Storage#close will handle that. and maybe include a
138
- # #repack.
139
- parent.children.delete dirent
140
- 0 # hmmm. as per ::Dir ?
141
- end
142
-
143
- class Dir
144
- include Enumerable
145
- attr_reader :dirent, :path, :entries, :pos
146
-
147
- def initialize dirent, path
148
- @dirent, @path = dirent, path
149
- @pos = 0
150
- # FIXME: hack, and probably not really desired
151
- @entries = %w[. ..] + @dirent.children.map(&:name)
152
- end
153
-
154
- def each(&block)
155
- @entries.each(&block)
156
- end
157
-
158
- def close
159
- end
160
-
161
- def read
162
- @entries[@pos]
163
- ensure
164
- @pos += 1 if @pos < @entries.length
165
- end
166
-
167
- def pos= pos
168
- @pos = [[0, pos].max, @entries.length].min
169
- end
170
-
171
- def rewind
172
- @pos = 0
173
- end
174
-
175
- alias tell :pos
176
- alias seek :pos=
177
- end
178
- end
179
- end
180
- end
181
-