mailparser 0.5.0.beta1 → 0.5.0.beta2

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/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
- メールアドレスのドメイン部文字列を返す。