mailparser 0.5.0.beta1 → 0.5.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,30 @@
1
+ MailParser
2
+ ==========
3
+
4
+ MailParser is a parser for mail message.
5
+
6
+ Installation
7
+ ------------
8
+
9
+ % gem install --pre mailparser
10
+
11
+ Usage
12
+ -----
13
+
14
+ require 'mailparser'
15
+ f = File.open('hoge.eml')
16
+ m = MailParser::Message.new(f, :output_charset=>'utf-8')
17
+ m.subject #=> String
18
+ m.body #=> String
19
+ m.part #=> Array of Mailparser::Message
20
+
21
+ License
22
+ -------
23
+
24
+ Ruby's
25
+
26
+
27
+ Copyright
28
+ ---------
29
+
30
+ Copyright (C) 2009 TOMITA Masahiro <tommy@tmtm.org>
data/lib/mailparser.rb CHANGED
@@ -15,14 +15,6 @@ require "mmapscanner"
15
15
  require "stringio"
16
16
  require "tempfile"
17
17
 
18
- # メールをパースする。
19
- #
20
- # m = MailParser.new
21
- # m.parse(src)
22
- # m.header => #<MailParser::Header>
23
- # m.body => パースされた本文文字列
24
- # m.part => [#<Mailparser>, ...]
25
- #
26
18
  module MailParser
27
19
  include RFC2045, RFC2183, RFC2822
28
20
 
@@ -57,14 +49,14 @@ module MailParser
57
49
  "content-disposition" => RFC2183,
58
50
  }
59
51
 
60
- # 単一のヘッダ
52
+ # Header field
61
53
  class HeaderItem
62
- # name:: ヘッダ名(String)
63
- # raw:: ヘッダ値(String)
64
- # opt:: オプション(Hash)
65
- # :decode_mime_header:: MIMEヘッダをデコードする
66
- # :output_charset:: デコード出力文字コード(デフォルト: UTF-8)
67
- # :strict:: RFC違反時に ParseError 例外を発生する
54
+ # @param [String] name header field name
55
+ # @param [String] raw header field value
56
+ # @param [Hash] opt options
57
+ # @option opt [Boolean] :decode_mime_header (false) decode MIME header
58
+ # @option opt [String] :output_charset (nil) output encoding name
59
+ # @option opt [Boolean] :strict (false) raise ParseError exception when message is invalid
68
60
  def initialize(name, raw, opt={})
69
61
  @name = name
70
62
  @raw = raw
@@ -72,9 +64,10 @@ module MailParser
72
64
  @opt = opt
73
65
  end
74
66
 
67
+ # @return [String] raw field value
75
68
  attr_reader :raw
76
69
 
77
- # パースした結果オブジェクトを返す
70
+ # @return [header field value] parsed header object
78
71
  def parse()
79
72
  return @parsed if @parsed
80
73
  if HEADER_PARSER.key? @name then
@@ -107,8 +100,12 @@ module MailParser
107
100
  end
108
101
  end
109
102
 
110
- # 同じ名前を持つヘッダの集まり
103
+ # Header part
111
104
  class Header
105
+ # @param [Hash] opt options
106
+ # @option opt [Boolean] :decode_mime_header decode MIME header
107
+ # @option opt [String] :output_charset output encoding name
108
+ # @option opt [Boolean] :strict raise ParseError exception when message is invalid
112
109
  def initialize(opt={})
113
110
  @hash = {}
114
111
  @parsed = {}
@@ -116,17 +113,19 @@ module MailParser
116
113
  @opt = opt
117
114
  end
118
115
 
119
- # name ヘッダに body を追加する
120
- # name:: ヘッダ名(String)
121
- # body:: ヘッダ値(String)
116
+ # add header field
117
+ # @param [String] name header field name
118
+ # @param [String] body header field value
119
+ # @return [void]
122
120
  def add(name, body)
123
121
  name = name.downcase
124
122
  @hash[name] = [] unless @hash.key? name
125
123
  @hash[name] << HeaderItem.new(name, body, @opt)
126
124
  end
127
125
 
128
- # パースした結果オブジェクトの配列を返す
129
- # name:: ヘッダ名(String)
126
+ # header field value
127
+ # @param [String] name header field name
128
+ # @return [Array<header field value>]
130
129
  def [](name)
131
130
  return nil unless @hash.key? name
132
131
  return @parsed[name] if @parsed.key? name
@@ -134,8 +133,8 @@ module MailParser
134
133
  return @parsed[name]
135
134
  end
136
135
 
137
- # 生ヘッダ値文字列の配列を返す
138
- # name:: ヘッダ名(String)
136
+ # @param [String] name header field name
137
+ # @return [Array<String>] raw header value
139
138
  def raw(name)
140
139
  return nil unless @hash.key? name
141
140
  return @raw[name] if @raw.key? name
@@ -143,18 +142,22 @@ module MailParser
143
142
  return @raw[name]
144
143
  end
145
144
 
146
- # ヘッダ名の配列を返す
145
+ # @return [Array<String>] header names
147
146
  def keys()
148
147
  return @hash.keys
149
148
  end
150
149
 
151
- # ヘッダが存在するか?
150
+ # @param [String] name header field name
151
+ # @return [Boolean] true if header field exists
152
152
  def key?(name)
153
153
  return @hash.key?(name)
154
154
  end
155
155
 
156
- # 各ヘッダについてブロックを繰り返す
157
- # ブロック引数は、[ヘッダ名, パース結果オブジェクト,...]]
156
+ # repeat block for each header field
157
+ # @yield [key, value]
158
+ # @yieldparam [String] key header field name
159
+ # @yieldparam [header field value] value header field value
160
+ # @return [void]
158
161
  def each()
159
162
  @hash.each do |k, v|
160
163
  yield k, self[k]
@@ -162,17 +165,22 @@ module MailParser
162
165
  end
163
166
  end
164
167
 
165
- # メール全体またはひとつのパートを表すクラス
168
+ # Mail message
169
+ # @example
170
+ # require 'mailparser'
171
+ # f = File.open('hoge.eml')
172
+ # m = MailParser::Message.new(f, :output_charset=>'utf-8')
173
+ # m.subject #=> String
174
+ # m.body #=> String
175
+ # m.part #=> Array of Mailparser::Message
166
176
  class Message
167
- # src からヘッダ部を読み込み Header オブジェクトに保持する
168
- # src:: String / File / MmapScanner / read メソッドを持つオブジェクト
169
- # opt:: オプション(Hash)
170
- # :extract_message_type:: message/* type を展開する
171
- # :decode_mime_header:: MIMEヘッダをデコードする
172
- # :decode_mime_filename:: ファイル名を MIME デコードする
173
- # :output_charset:: デコード出力文字コード(デフォルト: 変換しない)
174
- # :strict:: RFC違反時に ParseError 例外を発生する
175
- # :charset_converter:: 文字コード変換用 Proc または Method
177
+ # @param [String, File, MmapScanner, #read] src source object
178
+ # @param [Hash] opt options
179
+ # @option opt [Boolean] :decode_mime_header (false) decode MIME header
180
+ # @option opt [Boolean] :decode_mime_filename (false) decode MIME encoded filename
181
+ # @option opt [Boolean] :output_charset (nil) output encoding
182
+ # @option opt [Boolean] :strict (false) raise ParseError exception when message is invalid
183
+ # @option opt [Proc, Method, #call] :charset_converter (nil) charset converter. default is MailParser::ConvCharset.conv_charset
176
184
  def initialize(src, opt={})
177
185
  if src.is_a? String
178
186
  @src = MmapScanner.new src
@@ -206,8 +214,12 @@ module MailParser
206
214
  end
207
215
 
208
216
  attr_reader :header, :part
217
+ # @!attribute [r] header
218
+ # @return [MailParser::Header]
219
+ # @!attribute [r] part
220
+ # @return [Array<MailParser::Message>]
209
221
 
210
- # charset 変換後の本文を返す
222
+ # @return [String] message body decoded and converted charset
211
223
  def body
212
224
  body = body_preconv
213
225
  if type == 'text' and charset and @opt[:output_charset]
@@ -220,9 +232,8 @@ module MailParser
220
232
  body
221
233
  end
222
234
 
223
- # charset 変換前の本文を返す
235
+ # @return [String] message body decoded and not converted charset
224
236
  def body_preconv
225
- return '' if type == 'multipart' or type == 'message'
226
237
  body = @rawbody.to_s
227
238
  ret = case content_transfer_encoding
228
239
  when "quoted-printable" then RFC2045.qp_decode(body)
@@ -236,11 +247,10 @@ module MailParser
236
247
  ret
237
248
  end
238
249
 
239
- # Content-Type message の時 Message を返す。そうでなければ nil を返す。
250
+ # @return [MailParser::Message] body type is message/*
251
+ # @return [nil] when type is not message/*
240
252
  def message
241
- unless @opt[:extract_message_type] and type == "message"
242
- return nil
243
- end
253
+ return nil unless type == "message"
244
254
  if ['7bit', '8bit'].include? content_transfer_encoding
245
255
  @rawbody.pos = 0
246
256
  return Message.new(@rawbody, @opt)
@@ -248,8 +258,8 @@ module MailParser
248
258
  return Message.new(body_preconv, @opt)
249
259
  end
250
260
 
251
- # From ヘッダがあれば Mailbox を返す。
252
- # なければ nil
261
+ # @return [MailParser::RFC2822::Mailbox] From field
262
+ # @return [nil] when From field don't exist
253
263
  def from()
254
264
  return @from if @from
255
265
  if @header.key? "from" then
@@ -260,8 +270,8 @@ module MailParser
260
270
  return @from
261
271
  end
262
272
 
263
- # To ヘッダがあれば Mailbox の配列を返す
264
- # なければ空配列
273
+ # @return [Array<MailParser::RFC2822::Mailbox>] To field
274
+ # @return [nil] when To field don't exist
265
275
  def to()
266
276
  return @to if @to
267
277
  if @header.key? "to" then
@@ -272,8 +282,8 @@ module MailParser
272
282
  return @to
273
283
  end
274
284
 
275
- # Cc ヘッダがあれば Mailbox の配列を返す
276
- # なければ空配列
285
+ # @return [Array<MailParser::RFC2822::Mailbox>] Cc field
286
+ # @return [nil] when Cc field don't exist
277
287
  def cc()
278
288
  return @cc if @cc
279
289
  if @header.key? "cc" then
@@ -284,8 +294,7 @@ module MailParser
284
294
  return @cc
285
295
  end
286
296
 
287
- # Subject ヘッダがあれば文字列を返す
288
- # なければ空文字
297
+ # @return [String] Subject field
289
298
  def subject()
290
299
  return @subject if @subject
291
300
  if @header.key? "subject" then
@@ -296,8 +305,7 @@ module MailParser
296
305
  return @subject
297
306
  end
298
307
 
299
- # Content-Type type を返す。
300
- # Content-Type がない場合は "text"
308
+ # @return [String] Content-Type main type as lower-case
301
309
  def type()
302
310
  return @type if @type
303
311
  if @header.key? "content-type" then
@@ -308,8 +316,7 @@ module MailParser
308
316
  return @type
309
317
  end
310
318
 
311
- # Content-Type subtype を返す。
312
- # Content-Type がない場合は "plain"
319
+ # @return [String] Content-Type sub type as lower-case
313
320
  def subtype()
314
321
  return @subtype if @subtype
315
322
  if @header.key? "content-type" then
@@ -320,8 +327,8 @@ module MailParser
320
327
  return @subtype
321
328
  end
322
329
 
323
- # Content-Type charset 属性の値(小文字)を返す。
324
- # charset 属性がない場合は nil
330
+ # @return [String] Content-Type charset attribute as lower-case
331
+ # @return [nil] when charset attribute don't exist
325
332
  def charset()
326
333
  return @charset if @charset
327
334
  if @header.key? "content-type" then
@@ -333,13 +340,12 @@ module MailParser
333
340
  return @charset
334
341
  end
335
342
 
336
- # マルチパートメッセージかどうかを返す
343
+ # @return [Boolean] true if multipart type
337
344
  def multipart?()
338
345
  return type == "multipart"
339
346
  end
340
347
 
341
- # Content-Transfer-Encoding mechanism を返す
342
- # Content-Transfer-Encoding がない場合は "7bit"
348
+ # @return [String] Content-Transfer-Encoding mechanism. default is "7bit"
343
349
  def content_transfer_encoding()
344
350
  return @content_transfer_encoding if @content_transfer_encoding
345
351
  if @header.key? "content-transfer-encoding" then
@@ -350,10 +356,8 @@ module MailParser
350
356
  return @content_transfer_encoding
351
357
  end
352
358
 
353
- # ファイル名を返す。
354
- # Content-Disposition filename パラメータ
355
- # または Content-Type の name パラメータ。
356
- # デフォルトは nil。
359
+ # @return [String] Content-Disposition filename attribute or Content-Type name attribute
360
+ # @return [nil] when filename attribute don't exist
357
361
  def filename()
358
362
  return @filename if @filename
359
363
  if @header.key? "content-disposition" and @header["content-disposition"][0].params.key? "filename" then
@@ -365,19 +369,23 @@ module MailParser
365
369
  return @filename
366
370
  end
367
371
 
368
- # 生メッセージを返す
372
+ # @return [String] raw message
369
373
  def raw
370
374
  return @src.to_s
371
375
  end
372
376
 
373
- # 生ヘッダを返す
377
+ # @return [String] raw header
374
378
  def rawheader
375
379
  @rawheader.to_s
376
380
  end
377
381
 
382
+ # @return [String] raw body
383
+ def rawbody
384
+ @rawbody.to_s
385
+ end
386
+
378
387
  private
379
388
 
380
- # ヘッダ部をパースする
381
389
  def read_header()
382
390
  @rawheader = @src.scan_until(/^(?=\r?\n)|\z/)
383
391
  @header = Header.new(@opt)
@@ -390,11 +398,10 @@ module MailParser
390
398
  @rawheader.skip(/.*\n/) or break
391
399
  end
392
400
  end
393
- @src.scan(/\r?\n/) # 空行スキップ
401
+ @src.scan(/\r?\n/) # skip delimiter line
394
402
  @rawbody = @src.rest
395
403
  end
396
404
 
397
- # 各パートの Message オブジェクトの配列を作成
398
405
  def read_part()
399
406
  return if type != "multipart" or @src.eos?
400
407
  b = @header["content-type"][0].params["boundary"]
@@ -410,7 +417,6 @@ module MailParser
410
417
  end
411
418
  end
412
419
 
413
- # uuencode のデコード
414
420
  def decode_uuencode(str)
415
421
  ret = ""
416
422
  str.each_line do |line|
@@ -423,7 +429,6 @@ module MailParser
423
429
  ret
424
430
  end
425
431
 
426
- # str をそのまま返す
427
432
  def decode_plain(str)
428
433
  str
429
434
  end
@@ -40,10 +40,10 @@ module MailParser
40
40
  }
41
41
 
42
42
  module_function
43
- # ヘッダをパースした結果のオブジェクトを返す
44
- # hname:: ヘッダ名(String)
45
- # hbody:: ヘッダ本文(String)
46
- # opt:: オプション(Hash)
43
+ # @param [String] hname
44
+ # @param [String] hbody
45
+ # @param [Hash] opt options
46
+ # @return [header field value]
47
47
  def parse(hname, hbody, opt={})
48
48
  if HEADER_PARSER.key? hname then
49
49
  return method(HEADER_PARSER[hname]).call(hbody, opt)
@@ -57,7 +57,10 @@ module MailParser
57
57
  end
58
58
  end
59
59
 
60
- # Date ヘッダをパースして、RFC2822::DateTime を返す
60
+ # parse Date field
61
+ # @param [String] str
62
+ # @param [Hash] opt options
63
+ # @return [MailParser::RFC2822::DateTime]
61
64
  def parse_date(str, opt={})
62
65
  begin
63
66
  t = Time.rfc2822(str) rescue Time.parse(str)
@@ -67,27 +70,42 @@ module MailParser
67
70
  return RFC2822::DateTime.new(t.year, t.month, t.day, t.hour, t.min, t.sec, t.zone)
68
71
  end
69
72
 
70
- # From,To,Cc 等のヘッダをパースして RFC2822::Mailbox の配列を返す
73
+ # parse From, To,Cc field
74
+ # @param [String] str
75
+ # @param [Hash] opt options
76
+ # @return [Array<MailParser::RFC2822::Mailbox>]
71
77
  def parse_mailbox_list(str, opt={})
72
78
  mailbox_list(str, opt)
73
79
  end
74
80
 
75
- # Sender,Resent-Sender ヘッダをパースして RFC2822::Mailbox を返す
81
+ # parse Sender,Resent-Sender field
82
+ # @param [String] str
83
+ # @param [Hash] opt options
84
+ # @return [MailParser::RFC2822::Mailbox]
76
85
  def parse_mailbox(str, opt={})
77
86
  mailbox_list(str, opt)[0]
78
87
  end
79
88
 
80
- # Message-Id,Resent-Message-Id ヘッダをパースして RFC2822::MsgId を返す
89
+ # parse Message-Id, Resent-Message-Id field
90
+ # @param [String] str
91
+ # @param [Hash] opt options
92
+ # @return [MailParser::RFC2822::MsgId]
81
93
  def parse_msg_id(str, opt={})
82
94
  msg_id_list(str)[0]
83
95
  end
84
96
 
85
- # In-Reply-To,References 等のヘッダを RFC2822::MsgIdList を返す
97
+ # parse In-Reply-To, References field
98
+ # @param [String] str
99
+ # @param [Hash] opt options
100
+ # @return [MailParser::RFC2822::MsgIdList]
86
101
  def parse_msg_id_list(str, opt={})
87
102
  msg_id_list(str)
88
103
  end
89
104
 
90
- # Keywords ヘッダをパースして文字列の配列を返す
105
+ # parse Keywords field
106
+ # @param [String] str
107
+ # @param [Hash] opt options
108
+ # @return [Array<String>]
91
109
  def parse_phrase_list(str, opt={})
92
110
  s = split_by(Tokenizer.token(str), ",")
93
111
  s.map!{|i| i.join(" ")}
@@ -97,12 +115,18 @@ module MailParser
97
115
  s
98
116
  end
99
117
 
100
- # Return-Path ヘッダをパースして RFC2822:ReturnPath を返す
118
+ # parse Return-Path field
119
+ # @param [String] str
120
+ # @param [Hash] opt options
121
+ # @return [Array<MailParser::RFC2822::ReturnPath>]
101
122
  def parse_return_path(str, opt={})
102
123
  mailbox_list(str, opt)[0]
103
124
  end
104
125
 
105
- # Received ヘッダをパースして RFC2822::Received を返す
126
+ # parse Received field
127
+ # @param [String] str
128
+ # @param [Hash] opt options
129
+ # @return [MailParser::RFC2822::Received]
106
130
  def parse_received(str, opt={})
107
131
  a = split_by(Tokenizer.token_received(str), ";")
108
132
  date = a.length > 1 ? parse_date(a.last.join(" ")) : RFC2822::DateTime.now
@@ -124,7 +148,10 @@ module MailParser
124
148
  RFC2822::Received.new(name_val, date)
125
149
  end
126
150
 
127
- # Content-Type ヘッダをパースして RFC2045::ContentType を返す
151
+ # parse Content-Type field
152
+ # @param [String] str
153
+ # @param [Hash] opt options
154
+ # @return [MailParser::RFC2045::ContentType]
128
155
  def parse_content_type(str, opt={})
129
156
  token = split_by(Tokenizer.token(str), ";")
130
157
  type, subtype = token.empty? ? nil : token.shift.join.split("/", 2)
@@ -140,17 +167,26 @@ module MailParser
140
167
  RFC2045::ContentType.new(type, subtype, params)
141
168
  end
142
169
 
143
- # Content-Transfer-Encoding ヘッダをパースして RFC2045::ContentTransferEncoding を返す
170
+ # parse Content-Transfer-Encoding field
171
+ # @param [String] str
172
+ # @param [Hash] opt options
173
+ # @return [MailParser::RFC2045::ContentTransferEncoding]
144
174
  def parse_content_transfer_encoding(str, opt={})
145
175
  RFC2045::ContentTransferEncoding.new(Tokenizer.token(str).first.to_s)
146
176
  end
147
177
 
148
- # Mime-Version ヘッダをパースして文字列を返す
178
+ # parse Mime-Version field
179
+ # @param [String] str
180
+ # @param [Hash] opt options
181
+ # @return [String]
149
182
  def parse_mime_version(str, opt={})
150
183
  Tokenizer.token(str).join
151
184
  end
152
185
 
153
- # Content-Disposition ヘッダをパースして RFC2183::ContentDisposition を返す
186
+ # parse Content-Disposition field
187
+ # @param [String] str
188
+ # @param [Hash] opt options
189
+ # @return [MailParser::RFC2183::ContentDispositoin]
154
190
  def parse_content_disposition(str, opt={})
155
191
  token = split_by(Tokenizer.token(str), ";")
156
192
  type = token.empty? ? '' : token.shift.join
@@ -162,7 +198,10 @@ module MailParser
162
198
  RFC2183::ContentDisposition.new(type, params)
163
199
  end
164
200
 
165
- # array delim で分割した配列(要素は配列)を返す
201
+ # split arry by delim
202
+ # @param [Array] array
203
+ # @param [Object] delim
204
+ # @return [Array<Array>]
166
205
  def split_by(array, delim)
167
206
  ret = []
168
207
  a = []
@@ -178,7 +217,10 @@ module MailParser
178
217
  return ret
179
218
  end
180
219
 
181
- # Mailbox のリストを返す
220
+ # parse Mailbox type field
221
+ # @param [String] str
222
+ # @param [Hash] opt options
223
+ # @return [Array<MailParser::RFC2822::Mailbox>]
182
224
  def mailbox_list(str, opt)
183
225
  ret = []
184
226
  split_by(Tokenizer.token(str), ",").each do |m|
@@ -198,7 +240,9 @@ module MailParser
198
240
  return ret
199
241
  end
200
242
 
201
- # MsgId のリストを返す
243
+ # parse MsgId type field
244
+ # @param [String] str
245
+ # @return [Array<MailParser::RFC2822::MsgId>]
202
246
  def msg_id_list(str)
203
247
  ret = []
204
248
  flag = false
@@ -226,12 +270,14 @@ module MailParser
226
270
  end
227
271
 
228
272
  class Tokenizer < RFC2822::Scanner
273
+ # @return [String] str source string
229
274
  def initialize(str)
230
275
  @comments = []
231
276
  @ss = StringScanner.new(str)
232
277
  end
233
278
 
234
- # トークンに分割(コメント部は削除)
279
+ # tokenize
280
+ # @return [Array<String>] tokens
235
281
  def token()
236
282
  token = []
237
283
  while @ss.rest? do
@@ -256,7 +302,8 @@ module MailParser
256
302
  return token
257
303
  end
258
304
 
259
- # Received 用に分割
305
+ # tokenize for Received field
306
+ # @return [Array<String>] tokens
260
307
  def token_received()
261
308
  ret = []
262
309
  while @ss.rest? do
@@ -68,7 +68,8 @@ class MailParser::RFC2822::Scanner
68
68
  @ss.rest
69
69
  end
70
70
 
71
- # (」の直後からコメント部の終わりまでスキャン
71
+ # scan from after "(" to end of comment part
72
+ # @return [String] comment
72
73
  def cfws(ss)
73
74
  comments = []
74
75
  while true
@@ -81,17 +82,17 @@ class MailParser::RFC2822::Scanner
81
82
  return comments.join
82
83
  end
83
84
 
84
- # コメント部の処理
85
- # return: コメント部の文字列
85
+ # process comment part
86
+ # @return [String] comment part
86
87
  def cfws_sub(ss)
87
88
  ret = ""
88
89
  until ss.eos? do
89
90
  if ss.scan(/(\s*(\\[#{TEXT_RE}]|[#{CTEXT_RE}]))*\s*/o) then
90
91
  ret << ss.matched
91
92
  end
92
- if ss.scan(/\)/) then # )」が来たら復帰
93
+ if ss.scan(/\)/) then # return when ")"
93
94
  return ret
94
- elsif ss.scan(/\(/) then # (」が来たら再帰
95
+ elsif ss.scan(/\(/) then # recursive when "("
95
96
  c = cfws_sub(ss)
96
97
  break if c.nil?
97
98
  ret << "(" << c << ")"
@@ -99,17 +100,19 @@ class MailParser::RFC2822::Scanner
99
100
  raise MailParser::ParseError, ss.rest
100
101
  end
101
102
  end
102
- # )」がなかったら例外
103
+ # error when ")" don't exist
103
104
  raise MailParser::ParseError, ss.rest
104
105
  end
105
106
 
106
- # @token中の位置が s から e までの間のコメント文字列の配列を得る
107
+ # @param [Integer] s start index
108
+ # @param [Integer] e end index
107
109
  def get_comment(s, e)
108
110
  a = @token[s..e].select{|i| i =~ /^\s*\(/}.map{|i| i.strip}
109
111
  return a
110
112
  end
111
113
 
112
- # @token中の object_id s_id から e_id までの間のコメント文字列の配列を得る
114
+ # @param [Integer] s_id start object id
115
+ # @param [Integer] e_id end object id
113
116
  def get_comment_by_id(s_id, e_id)
114
117
  s = s_id ? @token_idx[s_id] : 0
115
118
  e = e_id ? @token_idx[e_id] : -1
@@ -604,7 +604,7 @@ EOS
604
604
  assert_equal("body2", m.part[1].body)
605
605
  end
606
606
 
607
- def test_extract_message_type()
607
+ def test_message_type()
608
608
  msg = StringIO.new(<<EOS)
609
609
  From: from1@example.com
610
610
  Content-Type: multipart/mixed; boundary="xxxx"
@@ -623,7 +623,7 @@ Content-Type: text/plain
623
623
  body2
624
624
  --xxxx--
625
625
  EOS
626
- m = MailParser::Message.new(msg, :extract_message_type=>true)
626
+ m = MailParser::Message.new(msg)
627
627
  assert_equal("<from1@example.com>", m.from.to_s)
628
628
  assert_equal(2, m.part.size)
629
629
  assert_equal("text", m.part[0].type)
@@ -631,9 +631,10 @@ EOS
631
631
  assert_equal("message", m.part[1].type)
632
632
  assert_equal("<from2@example.com>", m.part[1].message.from.to_s)
633
633
  assert_equal("body2", m.part[1].message.body)
634
+ assert_equal("From: from2@example.com\nContent-Type: text/plain\n\nbody2", m.part[1].body)
634
635
  end
635
636
 
636
- def test_extract_message_type_header_only
637
+ def test_message_type_header_only
637
638
  msg = StringIO.new(<<EOS)
638
639
  From: from1@example.com
639
640
  Content-Type: multipart/mixed; boundary="xxxx"
@@ -650,7 +651,7 @@ From: from2@example.com
650
651
  Content-Type: text/plain
651
652
  --xxxx--
652
653
  EOS
653
- m = MailParser::Message.new(msg, :extract_message_type=>true)
654
+ m = MailParser::Message.new(msg)
654
655
  assert_equal("<from1@example.com>", m.from.to_s)
655
656
  assert_equal(2, m.part.size)
656
657
  assert_equal("text", m.part[0].type)
@@ -710,7 +711,7 @@ hoge
710
711
  hoge
711
712
  EOS
712
713
  m = MailParser::Message.new(msg)
713
- assert_equal("", m.body)
714
+ assert_equal("hoge\nhoge\n", m.body)
714
715
  assert_equal([], m.part)
715
716
  end
716
717
 
@@ -858,6 +859,17 @@ EOS
858
859
  assert_equal "From: from@example.com\r\nContent-Type: text/plain\r\n", m.rawheader
859
860
  end
860
861
 
862
+ def test_rawbody
863
+ msg = StringIO.new(<<EOS)
864
+ From: from@example.com\r
865
+ Content-Type: text/plain\r
866
+ \r
867
+ hogehoge\r
868
+ EOS
869
+ m = MailParser::Message.new msg
870
+ assert_equal "hogehoge\r\n", m.rawbody
871
+ end
872
+
861
873
  def test_raw_single_part
862
874
  msg = StringIO.new(<<EOS)
863
875
  From: from@example.com
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mailparser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0.beta1
4
+ version: 0.5.0.beta2
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-17 00:00:00.000000000 Z
12
+ date: 2012-09-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: mmapscanner
@@ -33,7 +33,7 @@ executables: []
33
33
  extensions: []
34
34
  extra_rdoc_files: []
35
35
  files:
36
- - README.txt
36
+ - README.md
37
37
  - lib/mailparser.rb
38
38
  - lib/mailparser/rfc2183/scanner.rb
39
39
  - lib/mailparser/rfc2183/parser.rb
data/README.txt DELETED
@@ -1,497 +0,0 @@
1
- = MailParser =
2
-
3
- メールメッセージを解析する。
4
-
5
- == 作者 ==
6
-
7
- とみたまさひろ <tommy@tmtm.org>
8
-
9
- == ライセンス ==
10
-
11
- Ruby ライセンス http://www.ruby-lang.org/ja/LICENSE.txt と同等。
12
-
13
- == 機能 ==
14
-
15
- * メールファイルをパースした結果を返す。
16
- * メール構造による例外は発生しない(例外を発生させることも可能)。
17
- 不正な構造のメッセージがあった場合は適当に処理する。
18
-
19
- * 0.4 でイチから作りなおしたので、0.3 とは互換がない。
20
- require "mailparser/obsolete" すれば 0.3 と同じ機能が使用可能。
21
-
22
- == ダウンロード ==
23
-
24
- * http://github.com/tmtm/mailparser
25
-
26
- == インストール ==
27
-
28
- インストールには racc が必要。
29
-
30
- {{{
31
- $ make
32
- $ make test
33
- # make install
34
- }}}
35
-
36
- == 使用例 ==
37
-
38
- {{{
39
- require "mailparser"
40
- File.open("/tmp/hoge.eml") do |f|
41
- m = MailParser::Message.new(f, :decode_mime_header=>true)
42
- m.from # => From ヘッダ (MailParser::RFC2822::Mailbox)
43
- m.to # => To ヘッダ (MailParser::RFC2822::Mailbox の配列)
44
- m.subject # => Subject 文字列 (String)
45
- m.body # => 本文文字列 (String)
46
- m.charset # => 本文文字コード (String)
47
- m.part # => 添付ファイル (MailParser::Message の配列)
48
- m.part[0].filename # => 1つめの添付ファイルのファイル名 (String)
49
- m.part[0].body # => 1つめの添付ファイルの本文 (String)
50
- end
51
- }}}
52
-
53
- == MailParser::Message ==
54
-
55
- === self.new(io, [opt]) ===
56
-
57
- io から MailParser::Message オブジェクトを生成する。
58
-
59
- io には IO または StringIO オブジェクトを指定する。実際には1行毎の文
60
- 字列を返す gets イテレータを持ち、処理した位置を覚えていて次回実
61
- 行時に続きから実行できるオブジェクトであれば何でも良い。
62
- String オブジェクトも指定可能。内部で StringIO が生成されて使用される。
63
-
64
- opt は Hash オブジェクトで次の値を指定できる。
65
-
66
- :skip_body => true
67
- 本文をスキップする。デフォルトは false。
68
-
69
- :text_body_only => true
70
- text/* type 以外の本文をスキップする。デフォルトは false。
71
-
72
- :extract_message_type => true
73
- message/* type を展開する。デフォルトは false。
74
-
75
- :decode_mime_header => true
76
- MIMEヘッダをデコードする。デフォルトは false。
77
-
78
- :decode_mime_filename => true
79
- MIME エンコードされたファイル名をデコードする。デフォルトは false。
80
-
81
- :output_charset => charsetname
82
- 出力文字コード。デフォルトは nil で無変換。
83
-
84
- :charset_converter => proc{|f,t,s| ...}
85
- 文字コード変換時に呼び出される Proc オブジェクト。引数は、元charset,新charset,元文字列。デフォルトは MailParser::ConvCharset.conv_charset が呼ばれる。
86
-
87
- :strict => true
88
- RFC違反時に ParseError 例外を発生する。デフォルトは false。
89
-
90
- :use_file => bytes
91
- raw が指定したサイズを超えたら、メモリではなくファイルを使う。nil 指定時は無制限にメモリを使う。デフォルトは nil。
92
-
93
- === from ===
94
-
95
- From ヘッダのパース結果の最初のアドレスを MailParser::Mailbox オブジェクトで返す。
96
- {{{
97
- m = MailParser::Message.new(StringIO.new(<<EOS))
98
- From: TOMITA Masahiro <tommy@tmtm.org>
99
- EOS
100
- m.from.display_name # => "TOMITA Masahiro"
101
- m.from.addr_spec.to_s # => "tommy@tmtm.org"
102
- }}}
103
-
104
- === to ===
105
-
106
- To ヘッダのパース結果を MailParser::Mailbox オブジェクトの配列で返す。
107
- {{{
108
- m = MailParser::Message.new(StringIO.new(<<EOS))
109
- To: TOMITA Masahiro <tommy@tmtm.org>, foo@example.com
110
- EOS
111
- m.to[0].to_s # => "TOMITA Masahiro <tommy@tmt.morg>"
112
- m.to[1].to_s # => "<foo@example.com>"
113
- }}}
114
-
115
- === cc ===
116
-
117
- Cc ヘッダのパース結果を MailParser::Mailbox オブジェクトの配列で返す。
118
-
119
- === subject ===
120
-
121
- Subject ヘッダの値を文字列で返す。
122
- {{{
123
- m = MailParser::Message.new(StringIO.new(<<EOS), :decode_mime_header=>true, :output_charset=>"utf-8")
124
- Subject: =?iso-2022-jp?b?GyRCJEgkXyQ/JEckORsoQg==?=
125
- EOS
126
- m.subject # => "とみたです"
127
- }}}
128
-
129
- === type ===
130
-
131
- Content-Type ヘッダのタイプを小文字の文字列で返す。
132
- Content-Type がない場合は "text" を返す。
133
- {{{
134
- m = MailParser::Message.new(StringIO.new(<<EOS), :decode_mime_header=>true, :output_charset=>"utf-8")
135
- Content-Type: Text/Plain; charset=ISO-2022-JP
136
- EOS
137
- m.type # => "text"
138
- m.subtype # => "plain"
139
- m.charset # => "iso-2022-jp"
140
- }}}
141
-
142
- === subtype ===
143
-
144
- Content-Type ヘッダのサブタイプを小文字の文字列で返す。
145
- Content-Type がない場合またはサブタイプがない場合は "plain" を返す。
146
-
147
- === charset ===
148
-
149
- Content-Type ヘッダの charset 属性を小文字の文字列で返す。
150
- Content-Type がない場合または charset がない場合は nil を返す。
151
-
152
- === multipart? ===
153
-
154
- マルチパートメッセージの場合 true を返す。
155
-
156
- === filename ===
157
-
158
- ファイル名を返す。
159
- ファイル名は、Content-Disposition ヘッダの filename 属性または Content-Type ヘッダの name 属性から取得する(Content-Disposition ヘッダが優先)。
160
- {{{
161
- m = MailParser::Message.new(StringIO.new(<<EOS))
162
- Content-Disposition: attachment; filename="hogehoge.txt"
163
- EOS
164
- m.filename # => "hogehoge.txt"
165
- }}}
166
-
167
- {{{
168
- m = MailParser::Message.new(StringIO.new(<<EOS))
169
- Content-Disposition: attachment; filename*=utf-8''%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E5%90%8D.txt
170
- EOS
171
- m.filename # => "ファイル名.txt"
172
- }}}
173
-
174
- RFC的には正しくない(でも一般に使われている)ダブルクォートで括られてMIMEエンコードされた文字列を MIMEデコードしたい場合は、:decode_mime_filename=>true を指定する必要がある。
175
-
176
- {{{
177
- m = MailParser::Message.new(StringIO.new(<<EOS), :decode_mime_filename=>true)
178
- Content-Disposition: attachment; filename="=?utf-8?b?44OV44Kh44Kk44Or5ZCNLnR4dA==?="
179
- EOS
180
- m.filename # => "ファイル名"
181
- }}}
182
-
183
- === header ===
184
-
185
- ヘッダを表す MailParser::Header オブジェクトを返す。
186
- MailParser::Header はハッシュのように使用できる。
187
- キーは小文字のヘッダ名文字列で、値はヘッダオブジェクトの配列(複数存在する場合があるので配列)。
188
- {{{
189
- m = MailParser::Message.new(StringIO.new(<<EOS))
190
- From: TOMITA Masahiro <tommy@tmtm.org>
191
- To: foo@example.com
192
- Subject: test subject
193
- EOS
194
- m.header["from"][0] # => MailParser::Mailbox の配列
195
- m.header["to"][0] # => MailParser::Mailbox の配列
196
- m.header["subject"][0] # => String
197
- }}}
198
-
199
- ヘッダとパース結果のクラスの対応は次の通り。
200
-
201
- || Date || MailParser::DateTime
202
- || From || MailParser::Mailbox の配列
203
- || Sender || MailParser::Mailbox
204
- || Reply-To || MailParser::Mailbox または MailParser::Group の配列
205
- || To || MailParser::Mailbox または MailParser::Group の配列
206
- || Cc || MailParser::Mailbox または MailParser::Group の配列
207
- || Bcc || MailParser::Mailbox または MailParser::Group の配列
208
- || Message-Id || MailParser::MsgId
209
- || In-Reply-To || 文字列または MailParser::MsgId の配列
210
- || References || 文字列または MailParser::MsgId の配列
211
- || Keywords || 文字列の配列
212
- || Resent-Date || MailParser::DateTime
213
- || Resent-From || MailParser::Mailbox の配列
214
- || Resent-To || MailParser::Mailbox または MailParser::Group の配列
215
- || Resent-Cc || MailParser::Mailbox または MailParser::Group の配列
216
- || Resent-Bcc || MailParser::Mailbox または MailParser::Group の配列
217
- || Resent-Message-Id || MailParser::MsgId
218
- || Return-Path || MailParser::Mailbox または nil (<> の場合)
219
- || Received || MailParser::Received
220
- || Content-Type || MailParser::ContentType
221
- || Content-Transfer-Encoding || MailParser::ContentTransferEncoding
222
- || Content-Id || MailParser::MsgId
223
- || Mime-Version || 文字列
224
- || Content-Disposition || MailParser::ContentDisposition
225
-
226
- === body ===
227
-
228
- 本文文字列を返す。
229
- {{{
230
- m = MailParser::Message.new(StringIO.new(<<EOS))
231
- From: TOMITA Masahiro <tommy@tmtm.org>
232
- Content-Type: text/plain; charset=utf-8
233
-
234
- これは本文です。
235
- EOS
236
- m.body # => "これは本文です。\n"
237
- }}}
238
-
239
- === body_preconv ===
240
-
241
- 本文の charset変換前文字列を返す。
242
- {{{
243
- m = MailParser::Message.new(<<EOS)
244
- From: TOMITA Masahiro <tommy@tmtm.org>
245
- Content-Type: text/plain; charset=iso-2022-jp
246
-
247
- \e$B$3$l$OK\\J8$G$9\e(B
248
- EOS
249
- m.body # => "これは本文です\n"
250
- m.body_preconv # => "\e$B$3$l$OK\\J8$G$9\e(B\n"
251
- }}}
252
-
253
- === part ===
254
-
255
- マルチパートメッセージの場合、各パートを表す MailParser::Message オブジェクトの配列を返す。
256
- マルチパートメッセージでない場合は空配列を返す。
257
- {{{
258
- m = MailParser::Message.new(StringIO.new(<<EOS, :output_charset=>"utf8"))
259
- Content-Type: multipart/mixed; boundary="abcdefg"
260
-
261
- --abcdefg
262
- Content-Type: text/plain
263
-
264
- first part.
265
- --abcdefg
266
- Content-Type: text/plain
267
-
268
- second part.
269
- --abcdefg--
270
- EOS
271
- m.part.size # => 2
272
- m.part[0] # => 最初のパートの MailParser::Message オブジェクト
273
- m.part[1] # => 2番目のパートの MailParser::Message オブジェクト
274
- }}}
275
-
276
- === message ===
277
-
278
- :extract_message_type=>true で message タイプのメッセージの場合、本文が示すメッセージの Mailparser::Message オブジェクトを返す。
279
- :extract_message_type=>false または message タイプでない場合は nil を返す。
280
- {{{
281
- m = MailParser::Message.new(StringIO.new(<<EOS), :extract_message_type=>true)
282
- Content-Type: message/rfc822
283
-
284
- Subject: message subject
285
-
286
- message body
287
- EOS
288
- m.message.subject # => "message subject"
289
- }}}
290
-
291
- === rawheader ===
292
-
293
- ヘッダ部文字列をパースせずにそのまま返す。
294
- {{{
295
- m = MailParser::Message.new(StringIO.new(<<EOS))
296
- From: TOMITA Masahiro <tommy@tmtm.org>
297
- To: foo@example.com
298
- Subject: subject
299
-
300
- body message
301
- EOS
302
- m.rawheader # => "From: TOMITA Masahiro <tommy@tmtm.org>\nTo: foo@example.com\nSubject: subject\n"
303
- }}}
304
-
305
- === raw ===
306
-
307
- 生メッセージ文字列を返す。
308
- {{{
309
- m = MailParser::Message.new(StringIO.new(<<EOS))
310
- From: TOMITA Masahiro <tommy@tmtm.org>
311
- To: foo@example.com
312
- Subject: subject
313
-
314
- body message
315
- EOS
316
- m.rawheader # => "From: TOMITA Masahiro <tommy@tmtm.org>\nTo: foo@example.com\nSubject: subject\n"
317
- }}}
318
-
319
- == MailParser::Header ==
320
-
321
- 同じ名前を持つヘッダを表すクラス。
322
-
323
- === add(name, body) ===
324
- name ヘッダの値として body を追加する。
325
-
326
- === [](name) ===
327
- name ヘッダの値をパースした結果オブジェクトの配列を返す。
328
- パース結果オブジェクトは raw メソッドを持ち、パース前文字列を取り出すことができる。
329
-
330
- === raw(name) ===
331
- name ヘッダの値のパース前の文字列の配列を返す。
332
-
333
- === keys ===
334
- ヘッダ名文字列の一覧を返す。
335
-
336
- === key?(name) ===
337
- name ヘッダがあれば真。
338
-
339
- === each {|n,v| } ===
340
- 各ヘッダについてブロックを繰り返す。
341
- ブロック引数は、1番目がヘッダ名文字列、2番目がパース結果オブジェクトの配列。
342
-
343
- == MailParser::DateTime ==
344
-
345
- Date ヘッダを表すクラス。
346
-
347
- === year ===
348
-
349
- 年を表す整数。
350
-
351
- === month ===
352
-
353
- 月を表す整数。
354
-
355
- === day ===
356
-
357
- 日を表す整数。
358
-
359
- === hour ===
360
-
361
- 時を表す整数。
362
-
363
- === min ===
364
-
365
- 分を表す整数。
366
-
367
- === sec ===
368
-
369
- 秒を表す整数。
370
-
371
- === zone ===
372
-
373
- タイムゾーンを表す文字列。「+9999」または「-9999」の形式。
374
-
375
- === time ===
376
-
377
- Time オブジェクトを返す。範囲外の日付の場合は :strict が false でも ArgumentError 例外が発生するので注意。
378
-
379
- == MailParser::Mailbox ==
380
-
381
- メールアドレスを表すクラス。
382
-
383
- === addr_spec ===
384
-
385
- メールアドレスを表す MailParser::AddrSpec を返す。
386
-
387
- === local_part ===
388
-
389
- ローカルパートを表す文字列。
390
- MailParser::Mailbox#addr_spec.local_part と同じ。
391
-
392
- === domain ===
393
-
394
- ドメインを表す文字列。
395
- addr_spec.domain と同じ。
396
-
397
- === display_name ===
398
-
399
- 表示名を表す文字列。
400
-
401
- === phrase ===
402
-
403
- display_name と同じ。
404
-
405
- == MailParser::Group ==
406
-
407
- グループアドレスを表すクラス。
408
-
409
- === mailbox_list ===
410
-
411
- MailParser::Mailbox の配列。
412
-
413
- === display_name ===
414
-
415
- 表示名を表す文字列。
416
-
417
- === phrase ===
418
-
419
- display_name と同じ。
420
-
421
- == MailParser::MsgId ==
422
-
423
- === msg_id ===
424
-
425
- メッセージID文字列。先頭と末尾の < > は含まない。
426
-
427
- == MailParser::Received ==
428
-
429
- Received ヘッダを表すクラス。
430
-
431
- === name_val ===
432
-
433
- Received ヘッダ中の名前(小文字の文字列)と値(文字列)の組を表す Hash を返す。
434
- {{{
435
- m = MailParser::Message.new(StringIO.new(<<EOS))
436
- Received: from mx1.tmtm.org (localhost [127.0.0.1])
437
- by mx2.tmtm.org (Postfix) with ESMTP id 3ED69108383
438
- for <tommy@tmtm.org>; Tue, 16 Jan 2007 14:20:23 +0900 (JST)
439
- EOS
440
- r = m.header["received"][0]
441
- r.name_val["from"] # => "mx1.tmtm.org"
442
- r.name_val["by"] # => "mx2.tmtm.org"
443
- r.name_val["with"] # => "ESMTP"
444
- r.name_val["for"] # => "tommy@tmtm.org"
445
- }}}
446
-
447
- === date_time ===
448
-
449
- Received ヘッダ中の日時を表す MailParser::DateTime オブジェクトを返す。
450
-
451
- == MailParser::ContentType ==
452
-
453
- Content-Type ヘッダのパラメータを表すクラス。
454
-
455
- === type ===
456
-
457
- Content-Type ヘッダのタイプを表す小文字の文字列を返す。
458
-
459
- === subtype ===
460
-
461
- Content-Type ヘッダのサブタイプを表す小文字の文字列を返す。
462
-
463
- === params ===
464
-
465
- Content-Type ヘッダのパラメータを表す Hash を返す。
466
- Hash のキーは小文字の文字列。値は文字列。
467
-
468
- == MailParser::ContentTransferEncoding ==
469
-
470
- Content-Transfer-Encoding ヘッダを表すクラス。
471
-
472
- === mechanism ===
473
-
474
- Content-Transfer-Encoding ヘッダの値を小文字の文字列で返す。
475
-
476
- == MailParser::ContentDisposition ==
477
-
478
- === type ===
479
-
480
- Content-Disposition ヘッダのタイプを表す小文字の文字列を返す。
481
-
482
- === params ===
483
-
484
- Content-Disposition ヘッダのパラメータを表す Hash を返す。
485
- Hash のキーは小文字の文字列。値は文字列。
486
-
487
- == MailParser::AddrSpec ==
488
-
489
- メールアドレスを表すクラス。
490
-
491
- === local_part ===
492
-
493
- メールアドレスのローカルパート文字列を返す。
494
-
495
- === domain ===
496
-
497
- メールアドレスのドメイン部文字列を返す。