mailparser 0.4.22a → 0.5.0.beta1

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/HISTORY DELETED
@@ -1,141 +0,0 @@
1
- == 0.4.22 2010-06-11 ==
2
- * 添付ファイルの末尾の改行コードを削除してしまっていたバグを修正
3
-
4
- == 0.4.21 2010-06-03 ==
5
- * :use_file オプション追加
6
- * 省メモリ化
7
- * Content-Type が text の時にだけ charset 変換するように修正
8
-
9
- == 0.4.20 2010-05-04 ==
10
- * ヘッダが8bit文字を含む場合に MailParser::Header#each が落ちることがあるバグを修正
11
-
12
- == 0.4.19 2009-05-12 ==
13
- * Received に \v が含まれていると無限ループになっていたバグを修正
14
- cf. http://redmine.ruby-lang.org/issues/show/1196
15
-
16
- == 0.4.18 2009-01-27 ==
17
- * トークンが非常に多い文字列のパースに時間がかかっていた
18
-
19
- == 0.4.17 2008-11-26 ==
20
- * 閏秒が 23:59:60 しか考慮されていなかったバグを修正
21
-
22
- == 0.4.16 2008-07-28 ==
23
- * uuencode エンコーディングに対応
24
-
25
- == 0.4.15 2008-06-25 ==
26
- * 「group:, hoge@example.com;」の形式で落ちていたバグを修正
27
-
28
- == 0.4.14 2008-04-03 ==
29
- * 値がない Received ヘッダで落ちるバグを修正
30
- * Header#[] で nil 値は返さないように修正
31
- * Content-Type の subtype がない場合、type が text 以外でも plain になっていた。"" を返すように修正
32
- * 値がない Keywords ヘッダが [nil] を返していた。[] を返すように修正
33
-
34
- == 0.4.13 2008-03-09 ==
35
- * Quoted-Printable のデコード時に、= の後に改行が連続していると行が結合してしまっていたバグを修正
36
-
37
- == 0.4.12 2008-01-24 ==
38
- * Message-Id が不正な形式の場合、空白文字を含んだIDを返していた
39
-
40
- == 0.4.11 2008-01-15 ==
41
- * 日付の判定を厳密にした
42
-
43
- == 0.4.10 2007-11-06 ==
44
- * Content-Transfer-Encoding が空の場合に落ちるバグを修正
45
-
46
- == 0.4.9 2007-10-04 ==
47
- * :charset_converter 追加
48
-
49
- == 0.4.8 2007-09-17 ==
50
- * < > がない Message-Id で落ちていた
51
- * Message#body_preconv 追加
52
-
53
- == 0.4.7 2007-08-29 ==
54
- * boundary 文字列の直前の行の改行コードを無視しないといけなかった (RFC2046)
55
- * Message.new の第一引数に渡すオブジェクトに必要なメソッドを each_line から gets に変更
56
- * Content-Type ヘッダがない場合/charset パラメータがない場合、Message#charset が nil を返すように変更
57
- * racc コンパイル済み .rb ファイルを同梱
58
- * MailSuite::Message が String も受け付けるようにした
59
-
60
- == 0.4.6 2007-08-09 ==
61
- * :keep_raw 指定時、ヘッダの継続行が二重になっていた
62
-
63
- == 0.4.5 2007-08-07 ==
64
- * :decode_mime_filename 指定時、Content-Type も Content-Disposition もない場合に、落ちていたバグを修正
65
- * 空の Content-Type, Content-Disposition でエラーになっていた
66
- * ヘッダ行のみで区切りの空行がないパートを正しく扱えてなかった
67
- * Message#raw の効率化
68
- * Obsolete: RFC2231形式の添付ファイル名が正しく取得できないことがあった
69
-
70
- == 0.4.4 2007-08-06 ==
71
- * :keep_raw オプション追加。
72
- * Message#raw メソッド追加。
73
-
74
- == 0.4.3 2007-06-01 ==
75
- * ヘッダと本文の間の区切りの空行が無い場合にエラーになっていたバグを修正
76
- * RFC2231 パラメータが不正な時、strict=false でも ParseError になっていた
77
- * In-Reply-To, References ヘッダを正しくパースできなかったバグを修正
78
-
79
- == 0.4.2 2007-03-20 ==
80
- * 添付ファイルがネストされていた場合、その次の添付ファイルを取り出せなかったバグを修正。
81
-
82
- == 0.4.1a 2007-03-06 ==
83
- * 64bit環境や非JST環境でもテストが通るようにテストコードを変更。
84
-
85
- == 0.4.1 2007-03-03 ==
86
- * ドキュメントの誤記修正。
87
- * パース結果オブジェクトに raw メソッド追加。
88
- * :extract_message_type を :text_body_only, :skip_body よりも優先するように変更。
89
- * :output_charset 指定時にRFC2232形式のファイル名の charset が変換されていなかった
90
- * Content-Type, Content-Disposition のパラメータが未知の charset でエンコーディングされていた場合に落ちていたバグを修正。
91
-
92
- == 0.4 2007-01-16 ==
93
- * イチから作りなおした。0.3 とは互換なし。
94
-
95
- == 0.3.9 2006-07-10 ==
96
- * text_body_only が true で Content-Type ヘッダがない場合に、メールの本文をないものとして扱っていたバグを修正。
97
-
98
- == 0.3.8 2006-03-30 ==
99
- * RFC 2231 に対応。
100
-
101
- == 0.3.7 2006-03-11 ==
102
- * From, To, Cc の行末が「\」の時に無限ループしていたバグを修正。
103
-
104
- == 0.3.6 2005-10-14 ==
105
- * phrase内に「&lt;」「&gt;」「(」「)」があった時にメールアドレスの取得に失敗するバグを修正。
106
- * 「(」「)」「\(」「\)」が多く存在する行のパースに長時間かかるバグを修正。
107
- * quoted-string内の「(~)」を除去してしまうバグを修正。
108
-
109
- == 0.3.5 2005-06-08 ==
110
- * 本文がHTMLで添付ファイルがついている場合に、添付ファイルが認識されないバグを修正。
111
-
112
- == 0.3.4 2005-05-02 ==
113
- * Date へッダの日付が UNIX 時刻の範囲外の時に落ちるバグを修正。
114
-
115
- == 0.3.3 2005-03-31 ==
116
- * output_charset に nil を指定した時にコード変換しないようにした。
117
-
118
- == 0.3.2 2005-03-01 ==
119
- * From,To,Ccへッダに奇数個の「"」があると無限ループになるバグを修正。
120
-
121
- == 0.3.1 2005-02-21 ==
122
- * extract_message_type=() を追加。
123
-
124
- == 0.3 2005-01-28 ==
125
- * 最初の text/* を :body にするのをやめた。
126
- * :header を Hash に変更。
127
- * :rawheader 追加。
128
- * Uconv ではなく NKF を使用するようにした。
129
-
130
- == 0.2.1 2005-01-28 ==
131
- * 添付ファイルの Content-Type: が multipart/* の時に処理していなかったバグを修正。
132
- * Content-Type が message/* の時、:body が nil ではなく "" になっていたバグを修正。
133
- * 最初の行が空白で始まっていると落ちるバグを修正。
134
-
135
- == 0.2 2005-01-06 ==
136
- * UTF-8対応
137
- * output_charset=(), text_body_only=() を追加
138
- * Test::Unit を使用
139
-
140
- == 0.1 2004/11/02 ==
141
- * 公開
@@ -1,403 +0,0 @@
1
- # Copyright (C) 2003-2010 TOMITA Masahiro
2
- # mailto:tommy@tmtm.org
3
-
4
- require "nkf"
5
- require "date"
6
-
7
- module MailParser
8
-
9
- @@output_charset = "euc-jp"
10
- @@text_body_only = false
11
- @@extract_message_type = true
12
-
13
- ConvertMethods = {
14
- "JE" => :jistoeuc,
15
- "SE" => :sjistoeuc,
16
- "UE" => :utf8toeuc,
17
- "EU" => :euctoutf8,
18
- "SU" => :sjistoutf8,
19
- "JU" => :jistoutf8,
20
- }
21
-
22
- Charsets = {
23
- "iso-2022-jp" => "J",
24
- "euc-jp" => "E",
25
- "shift_jis" => "S",
26
- "sjis" => "S",
27
- "x-sjis" => "S",
28
- "utf-8" => "U",
29
- "us-ascii" => "N",
30
- }
31
-
32
- module_function
33
-
34
- def euctoutf8(s)
35
- NKF.nkf("-m0Ewx", s)
36
- end
37
-
38
- def sjistoutf8(s)
39
- NKF.nkf("-m0Swx", s)
40
- end
41
-
42
- def jistoutf8(s)
43
- NKF.nkf("-m0Jwx", s)
44
- end
45
-
46
- def sjistoeuc(s)
47
- NKF.nkf("-m0Sex", s)
48
- end
49
-
50
- def jistoeuc(s)
51
- NKF.nkf("-m0Jex", s)
52
- end
53
-
54
- def utf8toeuc(s)
55
- NKF.nkf("-m0Wex", s)
56
- end
57
-
58
- def output_charset=(c)
59
- @@output_charset = c
60
- end
61
-
62
- def text_body_only=(f)
63
- @@text_body_only = f
64
- end
65
-
66
- def extract_message_type=(f)
67
- @@extract_message_type = f
68
- end
69
-
70
- def b64_hdecode(str)
71
- str.unpack("m")[0]
72
- end
73
-
74
- def b64_decode(str)
75
- str.unpack("m")[0]
76
- end
77
-
78
- def qp_hdecode(str)
79
- str.gsub("_", " ").gsub(/=([0-9A-F][0-9A-F])/no) do $1.hex.chr end
80
- end
81
-
82
- def qp_decode(str)
83
- str.gsub(/[ \t]+$/no, "").gsub(/=\r?\n/no, "").
84
- gsub(/=([0-9A-F][0-9A-F])/no) do $1.hex.chr end
85
- end
86
-
87
- def mdecode_token(s)
88
- if s !~ /\A=\?([a-z0-9_-]+)\?(Q|B)\?([^?]+)\?=\Z/nio then
89
- s
90
- else
91
- charset, encoding, text = $1, $2, $3
92
- fc = MailParser::Charsets[charset.downcase]
93
- if fc == nil then return s end
94
- if encoding.downcase == 'q' then
95
- s2 = qp_hdecode(text)
96
- else
97
- s2 = b64_hdecode(text)
98
- end
99
- tc = @@output_charset && MailParser::Charsets[@@output_charset.downcase]
100
- if fc == "N" or tc.nil? or fc == tc then return s2 end
101
- MailParser.send(MailParser::ConvertMethods[fc+tc], s2)
102
- end
103
- end
104
-
105
- def mime_header_decode(str)
106
- return str.gsub(/\s+/no, " ").gsub(/\?=\s+=\?/no, "?==?").gsub(/=\?[a-z0-9_-]+\?(Q|B)\?[^?]+\?=/nio){mdecode_token $&}
107
- end
108
-
109
- def trunc_comment(v)
110
- ret = ""
111
- after = v
112
- while not after.empty? and after =~ /^(\\.|\"(\\.|[^\\\"])*\"|[^\\\(])*/no do
113
- ret << $&
114
- after = $'
115
- if after =~ /^\(/no then
116
- a = trunc_comment_sub(after[1..-1])
117
- if a == nil then
118
- return ret+after
119
- end
120
- after = a
121
- end
122
- if after == "\\" then
123
- break
124
- end
125
- end
126
- ret+after
127
- end
128
-
129
- def trunc_comment_sub(orig)
130
- after = orig
131
- loop do
132
- if after =~ /^(\\.|[^\\\(\)])*/no then
133
- after = $'
134
- end
135
- if after =~ /^\)/no then
136
- return after[1..-1]
137
- end
138
- if after =~ /^\(/no then
139
- after = trunc_comment_sub(after[1..-1])
140
- if after == nil then
141
- return nil
142
- end
143
- next
144
- end
145
- return nil
146
- end
147
- end
148
-
149
- def split_address(v)
150
- a = []
151
- r = ""
152
- while not v.empty? do
153
- if v =~ /^(\s+|[0-9A-Za-z\!\#\$\%\&\'\*\+\-\/\=\?\^\_\`\{\|\}\~]+|\"(\\.|[^\\\"])*\")/ then
154
- r << $&
155
- v = $'
156
- elsif v[0] == ?, then
157
- a << r.strip
158
- r = ""
159
- v.slice!(0,1)
160
- else
161
- r << v.slice!(0,1)
162
- end
163
- end
164
- a << r.strip
165
- return a
166
- end
167
-
168
- def get_mail_address(v)
169
- v = trunc_comment(v)
170
- a = split_address(v)
171
- return a.map{|i| i.strip =~ /<([^<>]*)>$/ ? $1 : i.strip}
172
- end
173
-
174
- def get_date(s)
175
- if s =~ /^[A-Z][A-Z][A-Z]\s*,\s*/i then
176
- s = $'
177
- end
178
- d = ::DateTime._strptime(s, "%d %b %Y %X")
179
- return unless d
180
- Time.mktime(d[:year], d[:mon], d[:mday], d[:hour], d[:min], d[:sec]) rescue nil
181
- end
182
-
183
- def parse_content_type(str)
184
- hash = {}
185
- hash[:parameter] = {}
186
- if str.strip =~ /^([a-z0-9_-]+)(?:\/([a-z0-9_-]+))?\s*/nio then
187
- hash[:type] = $1.downcase
188
- hash[:subtype] = $2.downcase if $2
189
- params = $' #'
190
- pending = {}
191
- while true do
192
- if params =~ /\A\s*;\s*([a-z0-9_-]+)(?:\*(\d+))?\s*=\s*(?:\"((?:\\\"|[^\"])*)\"|([^\s\(\)\<\>\@\,\;\:\\\"\/\[\]\?\=]*))\s*/nio then
193
- pn, ord, pv = $1, $2, $3||$4
194
- params = $'
195
- if ord then
196
- pending[pn] = [] unless pending.key? pn
197
- pending[pn] << [ord.to_i, pv]
198
- else
199
- hash[:parameter][pn.downcase] = pv
200
- end
201
- elsif params =~ /\A\s*;\s*([a-z0-9_-]+)\*\s*=\s*([a-z0-9_-]+)?\'(?:[a-z0-9_-]+)?\'(?:\"((?:\\\"|[^\"])*)\"|([^\s\(\)\<\>\@\,\;\:\\\"\/\[\]\?\=]*))\s*/nio then
202
- pn, charset, pv = $1, $2, $3||$4
203
- params = $'
204
- pending[pn] = [[0, pv, charset, true]]
205
- elsif params =~ /\A\s*;\s*([a-z0-9_-]+)\*0\*\s*=\s*([a-z0-9_-]+)?\'(?:[a-z0-9_-]+)?\'(?:\"((?:\\\"|[^\"])*)\"|([^\s\(\)\<\>\@\,\;\:\\\"\/\[\]\?\=]*))\s*/nio then
206
- pn, charset, pv = $1, $2, $3||$4
207
- params = $'
208
- pending[pn] = [[0, pv, charset, true]]
209
- elsif params =~ /\A\s*;\s*([a-z0-9_-]+)\*(\d+)\*\s*=\s*(?:\"((?:\\\"|[^\"])*)\"|([^\s\(\)\<\>\@\,\;\:\\\"\/\[\]\?\=]*))\s*/nio then
210
- pn, ord, pv = $1, $2, $3||$4
211
- params = $'
212
- pending[pn] = [] unless pending.key? pn
213
- pending[pn] << [ord.to_i, pv, nil, true]
214
- else
215
- break
216
- end
217
- end
218
- pending.each do |pn, pv|
219
- pv = pv.sort{|a,b| a[0]<=>b[0]}
220
- charset = pv[0][2]
221
- v = pv.map{|a|a[3] ? a[1].gsub(/%([0-9A-F][0-9A-F])/nio){$1.hex.chr} : a[1]}.join
222
- fc = MailParser::Charsets[charset.downcase] if charset
223
- tc = @@output_charset && MailParser::Charsets[@@output_charset.downcase]
224
- if fc and fc != "N" and fc != tc then
225
- v = MailParser.send(MailParser::ConvertMethods[fc+tc], v)
226
- end
227
- hash[:parameter][pn.downcase] = v
228
- end
229
- end
230
- return hash
231
- end
232
-
233
- def parse_content_disposition(str)
234
- return parse_content_type(str)
235
- end
236
-
237
- def parse_message(msg)
238
- class << msg
239
- def _each_with_multiple_delimiter(delim=[])
240
- @found_boundary = false
241
- loop do
242
- @l = gets
243
- if @l == nil then
244
- return
245
- end
246
- ll = @l.chomp
247
- if delim.include? ll then
248
- @found_boundary = true
249
- return
250
- end
251
- yield @l
252
- end
253
- end
254
- def last_line()
255
- @l && @l.chomp
256
- end
257
- attr_reader :found_boundary
258
- end
259
-
260
- m = parse_message2(msg)
261
- class << m
262
- def to_s()
263
- return <<EOS
264
- From: #{self[:from].join(",")}
265
- To: #{self[:to].join(",")}
266
- Subject:#{self[:subject]}
267
- Date: #{self[:date]}
268
-
269
- #{self[:body]}
270
-
271
- #{if self[:parts] then self[:parts].map{|p| "[#{p[:type]}/#{p[:subtype]}]<#{p[:filename]}>"}.join("\n") end}
272
- EOS
273
- end
274
- end
275
- return m
276
- end
277
-
278
- def parse_message2(msg, boundary=[])
279
- ret = parse_header(msg, boundary)
280
- return ret if msg.found_boundary
281
-
282
- if ret[:type] == "message" and @@extract_message_type then
283
- m = parse_message2(msg, boundary)
284
- ret[:message] = m
285
- elsif ret[:multipart] and ret[:boundary] then
286
- parts = []
287
- b = ret[:boundary]
288
- bd = boundary + ["--"+b+"--", "--"+b]
289
- msg._each_with_multiple_delimiter(bd) do end # skip preamble
290
- while msg.last_line == bd[-1] do
291
- m = parse_message2(msg, bd)
292
- parts << m
293
- end
294
- if msg.last_line == bd[-2] then
295
- msg._each_with_multiple_delimiter(boundary) do end
296
- end
297
- ret[:parts] = parts
298
- else
299
- if not @@text_body_only or ret[:type] == "text" or ret[:type].nil? then
300
- body = ""
301
- msg._each_with_multiple_delimiter(boundary) do |l|
302
- body << l
303
- end
304
- ret[:body] = decode_body(body, ret[:encoding], ret[:charset])
305
- else
306
- msg._each_with_multiple_delimiter(boundary) do end
307
- end
308
- end
309
- return ret
310
- end
311
-
312
- def parse_header(msg, boundary=[])
313
- ret = {}
314
- raw = ""
315
- header = []
316
- msg._each_with_multiple_delimiter(boundary) do |l|
317
- l.chomp!
318
- break if l.empty?
319
- raw << l+"\n"
320
- if l =~ /^\s/no and not header.empty? then
321
- header[-1] << l
322
- elsif not l.include? ":"
323
- next # skip garbage
324
- else
325
- header << l
326
- end
327
- end
328
-
329
- from = []
330
- to = []
331
- cc = []
332
- date = nil
333
- subject = ""
334
- encoding = ct = charset = multipart = body = filename = bd = nil
335
- h = {}
336
-
337
- header.each do |str|
338
- hn, hb = str.split(/:\s*/no, 2)
339
- hn.downcase!
340
- h[hn] = [] unless h.key? hn
341
- h[hn] << mime_header_decode(hb)
342
- case hn.downcase
343
- when "from"
344
- from.concat get_mail_address(hb)
345
- when "to"
346
- to.concat get_mail_address(hb)
347
- when "cc"
348
- cc.concat get_mail_address(hb)
349
- when "date"
350
- date = get_date(hb)
351
- when "subject"
352
- subject.concat hb
353
- when "content-type"
354
- ct = parse_content_type(hb)
355
- if ct[:type] == "text" then
356
- charset = ct[:parameter]["charset"]
357
- elsif ct[:type] == "multipart" then
358
- multipart = true
359
- bd = ct[:parameter]["boundary"]
360
- end
361
- filename = mime_header_decode(ct[:parameter]["name"]) if ct[:parameter]["name"]
362
- when "content-disposition"
363
- cd = parse_content_disposition(hb)
364
- filename = mime_header_decode(cd[:parameter]["filename"]) if cd[:parameter]["filename"]
365
- when "content-transfer-encoding"
366
- encoding = hb.strip.downcase
367
- end
368
- end
369
-
370
- ret[:from] = from
371
- ret[:to] = to
372
- ret[:cc] = cc
373
- ret[:date] = date
374
- ret[:subject] = mime_header_decode subject
375
- if ct then
376
- ret[:type] = ct[:type].downcase if ct[:type]
377
- ret[:subtype] = ct[:subtype].downcase if ct[:subtype]
378
- ret[:charset] = charset.downcase if charset
379
- end
380
- ret[:encoding] = encoding if encoding
381
- ret[:multipart] = multipart
382
- ret[:boundary] = bd
383
- ret[:filename] = filename if filename
384
- ret[:header] = h
385
- ret[:rawheader] = raw
386
- return ret
387
- end
388
-
389
- def decode_body(body, encoding, charset)
390
- case encoding
391
- when "base64"
392
- body = b64_decode body
393
- when "quoted-printable"
394
- body = qp_decode body
395
- end
396
- if charset == nil then return body end
397
- fc = MailParser::Charsets[charset.downcase]
398
- if fc == nil then return body end
399
- tc = @@output_charset && MailParser::Charsets[@@output_charset.downcase]
400
- if fc == "N" or tc.nil? or fc == tc then return body end
401
- MailParser.send(MailParser::ConvertMethods[fc+tc], body)
402
- end
403
- end