ruby-msg 1.2.17.3 → 1.3.1

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/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
-