sisimai 4.25.17-java → 5.0.0-java

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.
Files changed (178) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +3 -3
  3. data/ANALYTICAL-PRECISION +2 -2
  4. data/Benchmarks.mk +3 -3
  5. data/CONTRIBUTING +1 -1
  6. data/ChangeLog.md +406 -407
  7. data/Developers.mk +5 -6
  8. data/Gemfile +1 -1
  9. data/Makefile +12 -12
  10. data/README-JA.md +142 -94
  11. data/README.md +282 -150
  12. data/Rakefile +9 -3
  13. data/Repository.mk +2 -3
  14. data/lib/sisimai/address.rb +118 -74
  15. data/lib/sisimai/arf.rb +84 -82
  16. data/lib/sisimai/datetime.rb +5 -52
  17. data/lib/sisimai/{data → fact}/json.rb +7 -9
  18. data/lib/sisimai/fact/yaml.rb +31 -0
  19. data/lib/sisimai/fact.rb +468 -0
  20. data/lib/sisimai/lhost/activehunter.rb +12 -14
  21. data/lib/sisimai/lhost/amavis.rb +11 -14
  22. data/lib/sisimai/lhost/amazonses.rb +37 -41
  23. data/lib/sisimai/lhost/amazonworkmail.rb +15 -18
  24. data/lib/sisimai/lhost/aol.rb +12 -14
  25. data/lib/sisimai/lhost/apachejames.rb +19 -21
  26. data/lib/sisimai/lhost/barracuda.rb +10 -12
  27. data/lib/sisimai/lhost/bigfoot.rb +21 -21
  28. data/lib/sisimai/lhost/biglobe.rb +15 -16
  29. data/lib/sisimai/lhost/courier.rb +20 -20
  30. data/lib/sisimai/lhost/domino.rb +23 -19
  31. data/lib/sisimai/lhost/einsundeins.rb +20 -16
  32. data/lib/sisimai/lhost/exchange2003.rb +30 -29
  33. data/lib/sisimai/lhost/exchange2007.rb +70 -58
  34. data/lib/sisimai/lhost/exim.rb +175 -161
  35. data/lib/sisimai/lhost/ezweb.rb +31 -56
  36. data/lib/sisimai/lhost/facebook.rb +21 -33
  37. data/lib/sisimai/lhost/fml.rb +43 -48
  38. data/lib/sisimai/lhost/gmail.rb +29 -29
  39. data/lib/sisimai/lhost/gmx.rb +18 -17
  40. data/lib/sisimai/lhost/googlegroups.rb +9 -10
  41. data/lib/sisimai/lhost/gsuite.rb +21 -27
  42. data/lib/sisimai/lhost/imailserver.rb +25 -39
  43. data/lib/sisimai/lhost/interscanmss.rb +28 -31
  44. data/lib/sisimai/lhost/kddi.rb +22 -28
  45. data/lib/sisimai/lhost/mailfoundry.rb +11 -12
  46. data/lib/sisimai/lhost/mailmarshalsmtp.rb +25 -29
  47. data/lib/sisimai/lhost/mailru.rb +33 -27
  48. data/lib/sisimai/lhost/mcafee.rb +21 -31
  49. data/lib/sisimai/lhost/messagelabs.rb +17 -20
  50. data/lib/sisimai/lhost/messagingserver.rb +40 -37
  51. data/lib/sisimai/lhost/mfilter.rb +15 -16
  52. data/lib/sisimai/lhost/mxlogic.rb +24 -23
  53. data/lib/sisimai/lhost/notes.rb +17 -17
  54. data/lib/sisimai/lhost/office365.rb +63 -27
  55. data/lib/sisimai/lhost/opensmtpd.rb +12 -13
  56. data/lib/sisimai/lhost/outlook.rb +12 -15
  57. data/lib/sisimai/lhost/postfix.rb +179 -129
  58. data/lib/sisimai/lhost/powermta.rb +12 -14
  59. data/lib/sisimai/lhost/qmail.rb +44 -47
  60. data/lib/sisimai/lhost/receivingses.rb +15 -20
  61. data/lib/sisimai/lhost/sendgrid.rb +34 -32
  62. data/lib/sisimai/lhost/sendmail.rb +66 -53
  63. data/lib/sisimai/lhost/surfcontrol.rb +19 -19
  64. data/lib/sisimai/lhost/v5sendmail.rb +45 -39
  65. data/lib/sisimai/lhost/verizon.rb +35 -39
  66. data/lib/sisimai/lhost/x1.rb +18 -17
  67. data/lib/sisimai/lhost/x2.rb +17 -14
  68. data/lib/sisimai/lhost/x3.rb +19 -19
  69. data/lib/sisimai/lhost/x4.rb +72 -57
  70. data/lib/sisimai/lhost/x5.rb +17 -19
  71. data/lib/sisimai/lhost/x6.rb +41 -17
  72. data/lib/sisimai/lhost/yahoo.rb +17 -16
  73. data/lib/sisimai/lhost/yandex.rb +16 -20
  74. data/lib/sisimai/lhost/zoho.rb +16 -15
  75. data/lib/sisimai/lhost.rb +8 -10
  76. data/lib/sisimai/mail/maildir.rb +1 -3
  77. data/lib/sisimai/mail/mbox.rb +3 -4
  78. data/lib/sisimai/mail/memory.rb +0 -1
  79. data/lib/sisimai/mail/stdin.rb +1 -3
  80. data/lib/sisimai/mail.rb +3 -7
  81. data/lib/sisimai/mda.rb +28 -42
  82. data/lib/sisimai/message.rb +435 -325
  83. data/lib/sisimai/order.rb +5 -5
  84. data/lib/sisimai/reason/authfailure.rb +64 -0
  85. data/lib/sisimai/reason/badreputation.rb +53 -0
  86. data/lib/sisimai/reason/blocked.rb +94 -160
  87. data/lib/sisimai/reason/contenterror.rb +8 -9
  88. data/lib/sisimai/reason/delivered.rb +4 -6
  89. data/lib/sisimai/reason/exceedlimit.rb +10 -12
  90. data/lib/sisimai/reason/expired.rb +6 -8
  91. data/lib/sisimai/reason/feedback.rb +2 -3
  92. data/lib/sisimai/reason/filtered.rb +17 -19
  93. data/lib/sisimai/reason/hasmoved.rb +9 -10
  94. data/lib/sisimai/reason/hostunknown.rb +15 -15
  95. data/lib/sisimai/reason/mailboxfull.rb +10 -12
  96. data/lib/sisimai/reason/mailererror.rb +18 -20
  97. data/lib/sisimai/reason/mesgtoobig.rb +9 -11
  98. data/lib/sisimai/reason/networkerror.rb +5 -8
  99. data/lib/sisimai/reason/norelaying.rb +8 -11
  100. data/lib/sisimai/reason/notaccept.rb +13 -14
  101. data/lib/sisimai/reason/notcompliantrfc.rb +43 -0
  102. data/lib/sisimai/reason/onhold.rb +6 -9
  103. data/lib/sisimai/reason/policyviolation.rb +14 -12
  104. data/lib/sisimai/reason/rejected.rb +26 -24
  105. data/lib/sisimai/reason/requireptr.rb +69 -0
  106. data/lib/sisimai/reason/securityerror.rb +33 -36
  107. data/lib/sisimai/reason/spamdetected.rb +114 -147
  108. data/lib/sisimai/reason/speeding.rb +49 -0
  109. data/lib/sisimai/reason/suspend.rb +11 -11
  110. data/lib/sisimai/reason/syntaxerror.rb +11 -10
  111. data/lib/sisimai/reason/systemerror.rb +7 -9
  112. data/lib/sisimai/reason/systemfull.rb +7 -8
  113. data/lib/sisimai/reason/toomanyconn.rb +9 -11
  114. data/lib/sisimai/reason/undefined.rb +2 -3
  115. data/lib/sisimai/reason/userunknown.rb +129 -146
  116. data/lib/sisimai/reason/vacation.rb +3 -4
  117. data/lib/sisimai/reason/virusdetected.rb +10 -11
  118. data/lib/sisimai/reason.rb +59 -64
  119. data/lib/sisimai/rfc1894.rb +55 -28
  120. data/lib/sisimai/rfc2045.rb +373 -0
  121. data/lib/sisimai/rfc3464.rb +250 -308
  122. data/lib/sisimai/rfc3834.rb +42 -45
  123. data/lib/sisimai/rfc5322.rb +75 -100
  124. data/lib/sisimai/rfc5965.rb +31 -0
  125. data/lib/sisimai/rhost/cox.rb +5 -6
  126. data/lib/sisimai/rhost/franceptt.rb +6 -8
  127. data/lib/sisimai/rhost/godaddy.rb +12 -12
  128. data/lib/sisimai/rhost/{googleapps.rb → google.rb} +80 -72
  129. data/lib/sisimai/rhost/iua.rb +9 -10
  130. data/lib/sisimai/rhost/kddi.rb +6 -8
  131. data/lib/sisimai/rhost/{exchangeonline.rb → microsoft.rb} +115 -114
  132. data/lib/sisimai/rhost/mimecast.rb +42 -40
  133. data/lib/sisimai/rhost/nttdocomo.rb +13 -18
  134. data/lib/sisimai/rhost/spectrum.rb +10 -12
  135. data/lib/sisimai/rhost/{tencentqq.rb → tencent.rb} +7 -8
  136. data/lib/sisimai/rhost.rb +23 -31
  137. data/lib/sisimai/smtp/command.rb +59 -0
  138. data/lib/sisimai/smtp/error.rb +4 -7
  139. data/lib/sisimai/smtp/reply.rb +161 -74
  140. data/lib/sisimai/smtp/status.rb +504 -393
  141. data/lib/sisimai/smtp/transcript.rb +124 -0
  142. data/lib/sisimai/smtp.rb +0 -1
  143. data/lib/sisimai/string.rb +74 -5
  144. data/lib/sisimai/time.rb +1 -2
  145. data/lib/sisimai/version.rb +1 -1
  146. data/lib/sisimai.rb +35 -21
  147. data/set-of-emails/maildir/bsd/lhost-domino-02.eml +6 -3
  148. data/set-of-emails/maildir/bsd/lhost-googlegroups-15.eml +174 -0
  149. data/set-of-emails/maildir/bsd/lhost-gsuite-15.eml +229 -0
  150. data/set-of-emails/maildir/bsd/lhost-postfix-75.eml +51 -0
  151. data/set-of-emails/maildir/bsd/lhost-postfix-76.eml +101 -0
  152. data/set-of-emails/maildir/bsd/lhost-postfix-77.eml +74 -0
  153. data/set-of-emails/maildir/bsd/lhost-postfix-78.eml +91 -0
  154. data/set-of-emails/maildir/bsd/lhost-receivingses-08.eml +88 -0
  155. data/set-of-emails/maildir/bsd/rfc3464-43.eml +88 -0
  156. data/set-of-emails/maildir/bsd/rhost-google-03.eml +101 -0
  157. data/set-of-emails/maildir/bsd/rhost-google-04.eml +102 -0
  158. data/set-of-emails/maildir/bsd/rhost-google-05.eml +82 -0
  159. data/set-of-emails/maildir/bsd/rhost-google-06.eml +102 -0
  160. data/set-of-emails/maildir/bsd/rhost-google-07.eml +69 -0
  161. data/set-of-emails/maildir/bsd/rhost-google-08.eml +99 -0
  162. data/sisimai-java.gemspec +1 -1
  163. data/sisimai.gemspec +1 -1
  164. metadata +42 -23
  165. data/.rspec +0 -2
  166. data/lib/sisimai/data/yaml.rb +0 -33
  167. data/lib/sisimai/data.rb +0 -411
  168. data/lib/sisimai/mime.rb +0 -456
  169. data/set-of-emails/maildir/mac/reported-from-nick4tech-san-01.eml +0 -6
  170. /data/set-of-emails/maildir/bsd/{rfc3464-41.eml → rfc3834-05.eml} +0 -0
  171. /data/set-of-emails/maildir/bsd/{rhost-googleapps-01.eml → rhost-google-01.eml} +0 -0
  172. /data/set-of-emails/maildir/bsd/{rhost-googleapps-02.eml → rhost-google-02.eml} +0 -0
  173. /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-01.eml → rhost-microsoft-01.eml} +0 -0
  174. /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-02.eml → rhost-microsoft-02.eml} +0 -0
  175. /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-03.eml → rhost-microsoft-03.eml} +0 -0
  176. /data/set-of-emails/maildir/bsd/{rhost-tencentqq-01.eml → rhost-tencent-01.eml} +0 -0
  177. /data/set-of-emails/maildir/bsd/{rhost-tencentqq-02.eml → rhost-tencent-02.eml} +0 -0
  178. /data/set-of-emails/maildir/bsd/{rhost-tencentqq-03.eml → rhost-tencent-03.eml} +0 -0
@@ -1,365 +1,475 @@
1
1
  module Sisimai
2
- # Sisimai::Message convert bounce email text to data structure. It resolve
3
- # email text into an UNIX From line, the header part of the mail, delivery
4
- # status, and RFC822 header part. When the email given as a argument of "new"
5
- # method is not a bounce email, the method returns nil.
6
- class Message
7
- # Imported from p5-Sisimail/lib/Sisimai/Message.pm
8
- # :from [String] UNIX From line
9
- # :header [Hash] Header part of an email
10
- # :ds [Array] Parsed data by Sisimai::Lhost::* module
11
- # :rfc822 [Hash] Header part of the original message
12
- # :catch [Any] The results returned by hook method
13
- attr_accessor :from, :header, :ds, :rfc822, :catch
14
-
15
- require 'sisimai/mime'
16
- require 'sisimai/order'
17
- require 'sisimai/lhost'
18
- require 'sisimai/string'
19
- require 'sisimai/address'
20
- require 'sisimai/rfc5322'
21
- DefaultSet = Sisimai::Order.another
22
- LhostTable = Sisimai::Lhost.path
23
-
24
- # Constructor of Sisimai::Message
25
- # @param [String] data Email text data
26
- # @param [Hash] argvs Module to be loaded
27
- # @options argvs [String] :data Entire email message
28
- # @options argvs [Array] :load User defined MTA module list
29
- # @options argvs [Array] :order The order of MTA modules
30
- # @options argvs [Code] :hook Reference to callback method
31
- # @return [Sisimai::Message] Structured email data or nil if each
32
- # value of the arguments are missing
33
- def initialize(data: '', **argvs)
34
- return nil if data.empty?
35
- param = {}
36
- email = data.scrub('?').gsub("\r\n", "\n").gsub("\r", "\n")
37
- thing = { 'from' => '','header' => {}, 'rfc822' => '', ds => [], 'catch' => nil }
38
-
39
- # 1. Load specified MTA modules
40
- [:load, :order].each do |e|
41
- # Order of MTA modules
42
- next unless argvs[e]
43
- next unless argvs[e].is_a? Array
44
- next if argvs[e].empty?
45
- param[e.to_s] = argvs[e]
46
- end
47
- tobeloaded = Sisimai::Message.load(param)
48
-
49
- # 2. Split email data to headers and a body part.
50
- return nil unless aftersplit = Sisimai::Message.divideup(email)
51
-
52
- # 3. Convert email headers from text to hash reference
53
- thing['from'] = aftersplit[0]
54
- thing['header'] = Sisimai::Message.makemap(aftersplit[1])
55
-
56
- # 4. Decode and rewrite the "Subject:" header
57
- unless thing['header']['subject'].empty?
58
- # Decode MIME-Encoded "Subject:" header
59
- s = thing['header']['subject']
60
- q = Sisimai::MIME.is_mimeencoded(s) ? Sisimai::MIME.mimedecode(s.split(/[ ]/)) : s
61
-
62
- # Remove "Fwd:" string from the Subject: header
63
- if cv = q.downcase.match(/\A[ \t]*fwd?:[ ]*(.*)\z/)
64
- # Delete quoted strings, quote symbols(>)
65
- q = cv[1]
66
- aftersplit[2] = aftersplit[2].gsub(/^[>]+[ ]/, '').gsub(/^[>]$/, '')
2
+ # Sisimai::Message convert bounce email text to data structure. It resolve email text into an UNIX
3
+ # From line, the header part of the mail, delivery status, and RFC822 header part. When the email
4
+ # given as a argument of "rise" method is not a bounce email, the method returns nil.
5
+ module Message
6
+ class << self
7
+ require 'sisimai/rfc1894'
8
+ require 'sisimai/rfc2045'
9
+ require 'sisimai/rfc5322'
10
+ require 'sisimai/rfc5965'
11
+ require 'sisimai/address'
12
+ require 'sisimai/string'
13
+ require 'sisimai/order'
14
+ require 'sisimai/lhost'
15
+
16
+ DefaultSet = Sisimai::Order.another.freeze
17
+ LhostTable = Sisimai::Lhost.path.freeze
18
+ Fields1894 = Sisimai::RFC1894.FIELDINDEX.freeze
19
+ Fields5322 = Sisimai::RFC5322.FIELDINDEX.freeze
20
+ Fields5965 = Sisimai::RFC5965.FIELDINDEX.freeze
21
+ FieldIndex = [Fields1894.flatten, Fields5322.flatten, Fields5965.flatten].flatten.freeze
22
+ FieldTable = FieldIndex.map { |e| [e.downcase, e] }.to_h.freeze
23
+ ReplacesAs = { 'Content-Type' => [%w[message/xdelivery-status message/delivery-status]] }.freeze
24
+ Boundaries = ['Content-Type: message/rfc822', 'Content-Type: text/rfc822-headers'].freeze
25
+
26
+ # Read an email message and convert to structured format
27
+ # @param [Hash] argvs Module to be loaded
28
+ # @options argvs [String] :data Entire email message
29
+ # @options argvs [Array] :load User defined MTA module list
30
+ # @options argvs [Array] :order The order of MTA modules
31
+ # @options argvs [Code] :hook Reference to callback method
32
+ # @return [Sisimai::Message] Structured email data or nil if each
33
+ # value of the arguments are missing
34
+ def rise(**argvs)
35
+ return nil unless argvs
36
+ email = argvs[:data].scrub('?').gsub("\r\n", "\n")
37
+ thing = { 'from' => '','header' => {}, 'rfc822' => '', 'ds' => [], 'catch' => nil }
38
+ param = {}
39
+
40
+ # 0. Load specified MTA modules
41
+ [:load, :order].each do |e|
42
+ # Order of MTA modules
43
+ next unless argvs[e]
44
+ next unless argvs[e].is_a? Array
45
+ next if argvs[e].empty?
46
+ param[e.to_s] = argvs[e]
47
+ end
48
+ tobeloaded = Sisimai::Message.load(param)
49
+
50
+ aftersplit = nil
51
+ beforefact = nil
52
+ parseagain = 0
53
+
54
+ while parseagain < 2 do
55
+ # 1. Split email data to headers and a body part.
56
+ break unless aftersplit = Sisimai::Message.part(email)
57
+
58
+ # 2. Convert email headers from text to hash reference
59
+ thing['from'] = aftersplit[0]
60
+ thing['header'] = Sisimai::Message.makemap(aftersplit[1])
61
+
62
+ # 3. Decode and rewrite the "Subject:" header
63
+ unless thing['header']['subject'].empty?
64
+ # Decode MIME-Encoded "Subject:" header
65
+ cv = thing['header']['subject']
66
+ cq = Sisimai::RFC2045.is_encoded(cv) ? Sisimai::RFC2045.decodeH(cv.split(/[ ]/)) : cv
67
+ cl = cq.downcase
68
+ p1 = cl.index('fwd:'); p1 = cl.index('fw:') unless p1
69
+
70
+ # Remove "Fwd:" string from the Subject: header
71
+ if p1
72
+ # Delete quoted strings, quote symbols(>)
73
+ cq = Sisimai::String.sweep(cq[cq.index(':') + 1, cq.size])
74
+ aftersplit[2] = aftersplit[2].gsub(/^[>]+[ ]/, '').gsub(/^[>]$/, '')
75
+ end
76
+ thing['header']['subject'] = cq
77
+ end
78
+
79
+ # 4. Rewrite message body for detecting the bounce reason
80
+ param = {
81
+ 'hook' => argvs[:hook] || nil,
82
+ 'mail' => thing,
83
+ 'body' => aftersplit[2],
84
+ 'tobeloaded' => tobeloaded,
85
+ 'tryonfirst' => Sisimai::Order.make(thing['header']['subject'])
86
+ }
87
+ break if beforefact = Sisimai::Message.sift(param)
88
+ break unless Boundaries.any? { |a| aftersplit[2].include?(a) }
89
+
90
+ # 5. Try to sift again
91
+ # There is a bounce message inside of mutipart/*, try to sift the first message/rfc822
92
+ # part as a entire message body again.
93
+ parseagain += 1
94
+ email = Sisimai::RFC5322.part(aftersplit[2], Boundaries, true).pop.sub(/\A[\r\n\s]+/, '')
95
+ break unless email.size > 128
67
96
  end
68
- thing['header']['subject'] = q
97
+ return nil unless beforefact
98
+ return nil if beforefact.empty?
99
+
100
+ # 6. Rewrite headers of the original message in the body part
101
+ %w|ds catch rfc822|.each { |e| thing[e] = beforefact[e] }
102
+ p = beforefact['rfc822']
103
+ p = aftersplit[2] if p.empty?
104
+ thing['rfc822'] = p.is_a?(::String) ? Sisimai::Message.makemap(p, true) : p
105
+
106
+ return thing
69
107
  end
70
108
 
71
- # 5. Rewrite message body for detecting the bounce reason
72
- param = {
73
- 'hook' => argvs[:hook] || nil,
74
- 'mail' => thing,
75
- 'body' => aftersplit[2],
76
- 'tobeloaded' => tobeloaded,
77
- 'tryonfirst' => Sisimai::Order.make(thing['header']['subject'])
78
- }
79
- return nil unless bouncedata = Sisimai::Message.parse(param)
80
- return nil if bouncedata.empty?
81
-
82
- # 6. Rewrite headers of the original message in the body part
83
- %w|ds catch rfc822|.each { |e| thing[e] = bouncedata[e] }
84
- p = bouncedata['rfc822']
85
- p = aftersplit[2] if p.empty?
86
- thing['rfc822'] = p.is_a?(::String) ? Sisimai::Message.makemap(p, true) : p
87
-
88
- @from = thing['from']
89
- @header = thing['header']
90
- @ds = thing['ds']
91
- @rfc822 = thing['rfc822']
92
- @catch = thing['catch'] || nil
93
- end
109
+ # Load MTA modules which specified at 'order' and 'load' in the argument
110
+ # @param [Hash] argvs Module information to be loaded
111
+ # @options argvs [Array] load User defined MTA module list
112
+ # @options argvs [Array] order The order of MTA modules
113
+ # @return [Array] Module list
114
+ # @since v4.20.0
115
+ def load(argvs)
116
+ modulelist = []
117
+ tobeloaded = []
118
+
119
+ %w[load order].each do |e|
120
+ # The order of MTA modules specified by user
121
+ next unless argvs[e]
122
+ next unless argvs[e].is_a? Array
123
+ next if argvs[e].empty?
124
+
125
+ modulelist += argvs['order'] if e == 'order'
126
+ next unless e == 'load'
94
127
 
95
- # Check whether the object has valid content or not
96
- # @return [True,False] returns true if the object is void
97
- def void; return @ds ? false : true; end
98
-
99
- # Load MTA modules which specified at 'order' and 'load' in the argument
100
- # @param [Hash] argvs Module information to be loaded
101
- # @options argvs [Array] load User defined MTA module list
102
- # @options argvs [Array] order The order of MTA modules
103
- # @return [Array] Module list
104
- # @since v4.20.0
105
- def self.load(argvs)
106
- modulelist = []
107
- tobeloaded = []
108
-
109
- %w[load order].each do |e|
110
- # The order of MTA modules specified by user
111
- next unless argvs[e]
112
- next unless argvs[e].is_a? Array
113
- next if argvs[e].empty?
114
-
115
- modulelist += argvs['order'] if e == 'order'
116
- next unless e == 'load'
117
-
118
- # Load user defined MTA module
119
- argvs['load'].each do |v|
120
128
  # Load user defined MTA module
121
- begin
122
- require v.to_s.gsub('::', '/').downcase
123
- rescue LoadError
124
- warn ' ***warning: Failed to load ' << v
125
- next
129
+ argvs['load'].each do |v|
130
+ # Load user defined MTA module
131
+ begin
132
+ require v.to_s.gsub('::', '/').downcase
133
+ rescue LoadError
134
+ warn ' ***warning: Failed to load ' << v
135
+ next
136
+ end
137
+ tobeloaded << v
126
138
  end
127
- tobeloaded << v
128
139
  end
129
- end
130
140
 
131
- while e = modulelist.shift do
132
- # Append the custom order of MTA modules
133
- next if tobeloaded.index(e)
134
- tobeloaded << e
141
+ while e = modulelist.shift do
142
+ # Append the custom order of MTA modules
143
+ next if tobeloaded.index(e)
144
+ tobeloaded << e
145
+ end
146
+
147
+ return tobeloaded
135
148
  end
136
149
 
137
- return tobeloaded
138
- end
150
+ # Divide email data up headers and a body part.
151
+ # @param [String] email Email data
152
+ # @return [Array] Email data after split
153
+ def part(email)
154
+ return nil if email.empty?
139
155
 
140
- # Divide email data up headers and a body part.
141
- # @param [String] email Email data
142
- # @return [Array] Email data after split
143
- def self.divideup(email)
144
- return nil if email.empty?
145
-
146
- block = ['', '', ''] # 0:From, 1:Header, 2:Body
147
- email.gsub!(/\r\n/, "\n") if email.include?("\r\n")
148
- email.gsub!(/[ \t]+$/, '') if email =~ /[ \t]+$/
149
-
150
- (block[1], block[2]) = email.split(/\n\n/, 2)
151
- return nil unless block[1]
152
- return nil unless block[2]
153
-
154
- if block[1].start_with?('From ')
155
- # From MAILER-DAEMON Tue Feb 11 00:00:00 2014
156
- block[0] = block[1].split(/\n/, 2)[0].delete("\r")
157
- else
158
- # Set pseudo UNIX From line
159
- block[0] = 'MAILER-DAEMON Tue Feb 11 00:00:00 2014'
160
- end
161
- block[1] << "\n" unless block[1].end_with?("\n")
162
-
163
- %w[image/ application/ text/html].each do |e|
164
- # https://github.com/sisimai/p5-sisimai/issues/492, Reduce email size
165
- p0 = 0
166
- p1 = 0
167
- ep = e == 'text/html' ? '</html>' : "--\n"
168
- while true
169
- # Remove each part from "Content-Type: image/..." to "--\n" (the end of each boundary)
170
- p0 = block[2].index('Content-Type: ' + e, p0); break unless p0
171
- p1 = block[2].index(ep, p0 + 32); break unless p1
172
- block[2][p0, p1 - p0] = ''
173
- end
174
- end
175
- block[2] << "\n"
156
+ parts = ['', '', ''] # 0:From, 1:Header, 2:Body
157
+ email.gsub!(/\A\s+/, '')
158
+ email.gsub!(/\r\n/, "\n") if email.include?("\r\n")
176
159
 
177
- return block
178
- end
160
+ (parts[1], parts[2]) = email.split(/\n\n/, 2)
161
+ return nil unless parts[1]
162
+ return nil unless parts[2]
179
163
 
180
- # Convert a text including email headers to a hash reference
181
- # @param [String] argv0 Email header data
182
- # @param [Bool] argv1 Decode "Subject:" header
183
- # @return [Hash] Structured email header data
184
- # @since v4.25.6
185
- def self.makemap(argv0 = '', argv1 = nil)
186
- return {} if argv0.empty?
187
- argv0.gsub!(/^[>]+[ ]/m, '') # Remove '>' indent symbol of forwarded message
188
-
189
- # Select and convert all the headers in $argv0. The following regular expression
190
- # is based on https://gist.github.com/xtetsuji/b080e1f5551d17242f6415aba8a00239
191
- headermaps = { 'subject' => '' }
192
- recvheader = []
193
- argv0.scan(/^([\w-]+):[ ]*(.*?)\n(?![\s\t])/m) { |e| headermaps[e[0].downcase] = e[1] }
194
- headermaps.delete('received')
195
- headermaps.each_key { |e| headermaps[e].gsub!(/\n[\s\t]+/, ' ') }
196
-
197
- if argv0.include?('Received:')
198
- # Capture values of each Received: header
199
- recvheader = argv0.scan(/^Received:[ ]*(.*?)\n(?![\s\t])/m).flatten
200
- recvheader.each { |e| e.gsub!(/\n[\s\t]+/, ' ') }
201
- end
202
- headermaps['received'] = recvheader
203
-
204
- return headermaps unless argv1
205
- return headermaps if headermaps['subject'].empty?
206
-
207
- # Convert MIME-Encoded subject
208
- if Sisimai::String.is_8bit(headermaps['subject'])
209
- # The value of ``Subject'' header is including multibyte character,
210
- # is not MIME-Encoded text.
211
- headermaps['subject'].scrub!('?')
212
- else
213
- # MIME-Encoded subject field or ASCII characters only
214
- r = []
215
- if Sisimai::MIME.is_mimeencoded(headermaps['subject'])
216
- # split the value of Subject by borderline
217
- headermaps['subject'].split(/ /).each do |v|
218
- # Insert value to the array if the string is MIME encoded text
219
- r << v if Sisimai::MIME.is_mimeencoded(v)
220
- end
164
+ if parts[1].start_with?('From ')
165
+ # From MAILER-DAEMON Tue Feb 11 00:00:00 2014
166
+ parts[0] = parts[1].split(/\n/, 2)[0].delete("\r")
221
167
  else
222
- # Subject line is not MIME encoded
223
- r << headermaps['subject']
168
+ # Set pseudo UNIX From line
169
+ parts[0] = 'MAILER-DAEMON Tue Feb 11 00:00:00 2014'
224
170
  end
225
- headermaps['subject'] = Sisimai::MIME.mimedecode(r)
171
+ parts[1] << "\n" unless parts[1].end_with?("\n")
172
+
173
+ %w[image/ application/ text/html].each do |e|
174
+ # https://github.com/sisimai/p5-sisimai/issues/492, Reduce email size
175
+ p0 = 0
176
+ p1 = 0
177
+ ep = e == 'text/html' ? '</html>' : "--\n"
178
+ while true
179
+ # Remove each part from "Content-Type: image/..." to "--\n" (the end of each boundary)
180
+ p0 = parts[2].index('Content-Type: ' + e, p0); break unless p0
181
+ p1 = parts[2].index(ep, p0 + 32); break unless p1
182
+ parts[2][p0, p1 - p0] = ''
183
+ end
184
+ end
185
+ parts[2] << "\n"
186
+ return parts
226
187
  end
227
- return headermaps
228
- end
229
188
 
230
- # @abstract Parse bounce mail with each MTA module
231
- # @param [Hash] argvs Processing message entity.
232
- # @param options argvs [Hash] mail Email message entity
233
- # @param options mail [String] from From line of mbox
234
- # @param options mail [Hash] header Email header data
235
- # @param options mail [String] rfc822 Original message part
236
- # @param options mail [Array] ds Delivery status list(parsed data)
237
- # @param options argvs [String] body Email message body
238
- # @param options argvs [Array] tryonfirst MTA module list to load on first
239
- # @param options argvs [Array] tobeloaded User defined MTA module list
240
- # @return [Hash] Parsed and structured bounce mails
241
- def self.parse(argvs)
242
- return nil unless argvs['mail']
243
- return nil unless argvs['body']
244
-
245
- mailheader = argvs['mail']['header']
246
- bodystring = argvs['body']
247
- hookmethod = argvs['hook'] || nil
248
- havecaught = nil
249
- return nil unless mailheader
250
-
251
- # PRECHECK_EACH_HEADER:
252
- # Set empty string if the value is nil
253
- mailheader['from'] ||= ''
254
- mailheader['subject'] ||= ''
255
- mailheader['content-type'] ||= ''
256
-
257
- # Decode BASE64 Encoded message body, rewrite.
258
- mesgformat = (mailheader['content-type'] || '').downcase
259
- ctencoding = (mailheader['content-transfer-encoding'] || '').downcase
260
- if mesgformat.start_with?('text/')
261
- # Content-Type: text/plain; charset=UTF-8
262
- if ctencoding == 'base64'
263
- # Content-Transfer-Encoding: base64
264
- bodystring = Sisimai::MIME.base64d(bodystring)
265
-
266
- elsif ctencoding == 'quoted-printable'
267
- # Content-Transfer-Encoding: quoted-printable
268
- bodystring = Sisimai::MIME.qprintd(bodystring)
189
+ # Convert a text including email headers to a hash reference
190
+ # @param [String] argv0 Email header data
191
+ # @param [Bool] argv1 Decode "Subject:" header
192
+ # @return [Hash] Structured email header data
193
+ # @since v4.25.6
194
+ def makemap(argv0 = '', argv1 = nil)
195
+ return {} if argv0.empty?
196
+ argv0.gsub!(/^[>]+[ ]/m, '') # Remove '>' indent symbol of forwarded message
197
+
198
+ # Select and convert all the headers in $argv0. The following regular expression is based on
199
+ # https://gist.github.com/xtetsuji/b080e1f5551d17242f6415aba8a00239
200
+ headermaps = { 'subject' => '' }
201
+ recvheader = []
202
+ argv0.scan(/^([\w-]+):[ ]*(.*?)\n(?![\s\t])/m) { |e| headermaps[e[0].downcase] = e[1] }
203
+ headermaps.delete('received')
204
+ headermaps.each_key { |e| headermaps[e].gsub!(/\n[\s\t]+/, ' ') }
205
+
206
+ if argv0.include?('Received:')
207
+ # Capture values of each Received: header
208
+ recvheader = argv0.scan(/^Received:[ ]*(.*?)\n(?![\s\t])/m).flatten
209
+ recvheader.each { |e| e.gsub!(/\n[\s\t]+/, ' ') }
269
210
  end
211
+ headermaps['received'] = recvheader
270
212
 
271
- if mesgformat.start_with?('text/html;')
272
- # Content-Type: text/html;...
273
- bodystring = Sisimai::String.to_plain(bodystring, true)
274
- end
275
- else
276
- # NOT text/plain
277
- if mesgformat.start_with?('multipart/')
278
- # In case of Content-Type: multipart/*
279
- p = Sisimai::MIME.makeflat(mailheader['content-type'], bodystring)
280
- bodystring = p unless p.empty?
281
- end
282
- end
283
- bodystring = bodystring.scrub('?').delete("\r")
284
-
285
- haveloaded = {}
286
- parseddata = nil
287
- modulename = ''
288
- if hookmethod.is_a? Proc
289
- # Call the hook method
290
- begin
291
- p = { 'headers' => mailheader, 'message' => bodystring }
292
- havecaught = hookmethod.call(p)
293
- rescue StandardError => ce
294
- warn ' ***warning: Something is wrong in hook method :' << ce.to_s
213
+ return headermaps unless argv1
214
+ return headermaps if headermaps['subject'].empty?
215
+
216
+ # Convert MIME-Encoded subject
217
+ if Sisimai::String.is_8bit(headermaps['subject'])
218
+ # The value of ``Subject'' header is including multibyte character, is not MIME-Encoded text.
219
+ headermaps['subject'].scrub!('?')
220
+ else
221
+ # MIME-Encoded subject field or ASCII characters only
222
+ r = []
223
+ if Sisimai::RFC2045.is_encoded(headermaps['subject'])
224
+ # split the value of Subject by borderline
225
+ headermaps['subject'].split(/ /).each do |v|
226
+ # Insert value to the array if the string is MIME encoded text
227
+ r << v if Sisimai::RFC2045.is_encoded(v)
228
+ end
229
+ else
230
+ # Subject line is not MIME encoded
231
+ r << headermaps['subject']
232
+ end
233
+ headermaps['subject'] = Sisimai::RFC2045.decodeH(r)
295
234
  end
235
+ return headermaps
296
236
  end
297
237
 
298
- catch :PARSER do
299
- while true
300
- # 1. User-Defined Module
301
- # 2. MTA Module Candidates to be tried on first
302
- # 3. Sisimai::Lhost::*
303
- # 4. Sisimai::RFC3464
304
- # 5. Sisimai::ARF
305
- # 6. Sisimai::RFC3834
306
- while r = argvs['tobeloaded'].shift do
307
- # Call user defined MTA modules
308
- next if haveloaded[r]
309
- parseddata = Module.const_get(r).make(mailheader, bodystring)
310
- haveloaded[r] = true
311
- modulename = r
312
- throw :PARSER if parseddata
238
+ # @abstract Tidy up each field name and format
239
+ # @param [String] argv0 Strings including field and value used at an email
240
+ # @return [String] Strings tidied up
241
+ # @since v5.0.0
242
+ def tidy(argv0 = '')
243
+ return '' if argv0.empty?
244
+
245
+ email = ''
246
+ argv0.split("\n").each do |e|
247
+ # Find and tidy up fields defined in RFC5322, RFC1894, and RFC5965
248
+ # 1. Find a field label defined in RFC5322, RFC1894, or RFC5965 from this line
249
+ p0 = e.index(':') || 0
250
+ cf = e.downcase[0, p0]
251
+
252
+ unless FieldTable.has_key?(cf)
253
+ email << e + "\n"
254
+ next
313
255
  end
314
256
 
315
- [argvs['tryonfirst'], DefaultSet].flatten.each do |r|
316
- # Try MTA module candidates
317
- next if haveloaded[r]
318
- require LhostTable[r]
319
- parseddata = Module.const_get(r).make(mailheader, bodystring)
320
- haveloaded[r] = true
321
- modulename = r
322
- throw :PARSER if parseddata
257
+ # 2. There is a field label defined in RFC5322, RFC1894, or RFC5965 from this line.
258
+ # Code below replaces the field name with a valid name listed in @fieldindex when
259
+ # the field name does not match with a valid name.
260
+ # - Before: Message-id: <...>
261
+ # - After: Message-Id: <...>
262
+ fieldlabel = FieldTable[cf]
263
+ substring0 = e[0, p0]
264
+ e[0, p0] = fieldlabel unless substring0.empty?
265
+
266
+ # 3. There is no " " (space character) immediately after ":"
267
+ # - before: Content-Type:text/plain
268
+ # - After: Content-Type: text/plain
269
+ substring0 = e[p0 + 1, 1]
270
+ e[p0, 1] = ': ' if substring0 != ' '
271
+
272
+ # 4. Remove redundant space characters after ":"
273
+ while true
274
+ # - Before: Message-Id: <...>
275
+ # - After: Message-Id: <...>
276
+ break unless p0 + 2 < e.size
277
+ break unless e[p0 + 2, 1] == ' '
278
+ e[p0 + 2, 1] = ''
323
279
  end
324
280
 
325
- unless haveloaded['Sisimai::RFC3464']
326
- # When the all of Sisimai::Lhost::* modules did not return bounce
327
- # data, call Sisimai::RFC3464;
328
- require 'sisimai/rfc3464'
329
- parseddata = Sisimai::RFC3464.make(mailheader, bodystring)
330
- modulename = 'RFC3464'
331
- throw :PARSER if parseddata
281
+ # 5. Tidy up a sub type of each field defined in RFC1894 such as Reporting-MTA: DNS;...
282
+ p1 = e.index(';') || -1
283
+ while true
284
+ # Such as Diagnostic-Code, Remote-MTA, and so on
285
+ # - Before: Diagnostic-Code: SMTP;550 User unknown
286
+ # - After: Diagnostic-Code: smtp; 550 User unknown
287
+ break unless p1 > p0
288
+ break unless ['Content-Type'].concat(Fields1894).any? { |a| a.start_with?(fieldlabel) }
289
+
290
+ substring0 = e[p0 + 2, p1 - p0 - 1]
291
+ e[p0 + 2, substring0.size] = substring0.downcase + ' '
292
+ break
332
293
  end
333
294
 
334
- unless haveloaded['Sisimai::ARF']
335
- # Feedback Loop message
336
- require 'sisimai/arf'
337
- parseddata = Sisimai::ARF.make(mailheader, bodystring) if Sisimai::ARF.is_arf(mailheader)
338
- throw :PARSER if parseddata
295
+ # 6. Remove redundant space characters after ";"
296
+ while true
297
+ # - Before: Diagnostic-Code: SMTP; 550 User unknown
298
+ # - After: Diagnostic-Code: SMTP; 550 User unknown
299
+ break unless p1 + 2 < e.size
300
+ break unless e[p1 + 2, 1] == ' '
301
+ e[p1 + 2, 1] = ''
339
302
  end
340
303
 
341
- unless haveloaded['Sisimai::RFC3834']
342
- # Try to parse the message as auto reply message defined in RFC3834
343
- require 'sisimai/rfc3834'
344
- parseddata = Sisimai::RFC3834.make(mailheader, bodystring)
345
- modulename = 'RFC3834'
346
- throw :PARSER if parseddata
304
+ # 7. Tidy up a value, and a parameter of Content-Type: field
305
+ while true
306
+ # Replace the value of "Content-Type" field
307
+ break unless ReplacesAs.has_key?(fieldlabel)
308
+ p2 = 0
309
+
310
+ ReplacesAs[fieldlabel].each do |f|
311
+ # Content-Type: message/xdelivery-status
312
+ p2 = e.index(f[0]) || -1
313
+ next unless p2 > 1
314
+
315
+ e[p2, f[0].size] = f[1]
316
+ p1 = e.index(';')
317
+ break
318
+ end
319
+
320
+ # A parameter name of Content-Type field should be a lower-cased string
321
+ # - Before: Content-Type: text/plain; CharSet=ascii; Boundary=...
322
+ # - After: Content-Type: text/plain; charset=ascii; boundary=...
323
+ break unless fieldlabel == 'Content-Type'
324
+ p2 = e.index('=') || -1
325
+ break unless p2 > 0
326
+ break unless p2 > p1
327
+
328
+ substring0 = e[p1 + 2, p2 - p1 - 2]
329
+ e[p1 + 2, p2 - p1 - 2] = substring0.downcase
330
+ break
347
331
  end
348
-
349
- break # as of now, we have no sample email for coding this block
332
+ email << e + "\n"
350
333
  end
334
+
335
+ email << "\n" unless email.end_with?("\n\n")
336
+ return email
351
337
  end
352
- return nil unless parseddata
353
338
 
354
- parseddata['catch'] = havecaught
355
- modulename = modulename.sub(/\A.+::/, '')
356
- parseddata['ds'].each do |e|
357
- e['agent'] = modulename unless e['agent']
358
- e.each_key { |a| e[a] ||= '' } # Replace nil with ""
339
+ # @abstract Sift bounce mail with each MTA module
340
+ # @param [Hash] argvs Processing message entity.
341
+ # @param options argvs [Hash] mail Email message entity
342
+ # @param options mail [String] from From line of mbox
343
+ # @param options mail [Hash] header Email header data
344
+ # @param options mail [String] rfc822 Original message part
345
+ # @param options mail [Array] ds Delivery status list(parsed data)
346
+ # @param options argvs [String] body Email message body
347
+ # @param options argvs [Array] tryonfirst MTA module list to load on first
348
+ # @param options argvs [Array] tobeloaded User defined MTA module list
349
+ # @return [Hash] Parsed and structured bounce mails
350
+ def sift(argvs)
351
+ return nil unless argvs['mail']
352
+ return nil unless argvs['body']
353
+
354
+ mailheader = argvs['mail']['header']
355
+ bodystring = argvs['body']
356
+ hookmethod = argvs['hook'] || nil
357
+ havecaught = nil
358
+ return nil unless mailheader
359
+
360
+ # PRECHECK_EACH_HEADER:
361
+ # Set empty string if the value is nil
362
+ mailheader['from'] ||= ''
363
+ mailheader['subject'] ||= ''
364
+ mailheader['content-type'] ||= ''
365
+
366
+ # Tidy up each field name and value in the entire message body
367
+ bodystring = Sisimai::Message.tidy(bodystring)
368
+
369
+ # Decode BASE64 Encoded message body, rewrite.
370
+ mesgformat = (mailheader['content-type'] || '').downcase
371
+ ctencoding = (mailheader['content-transfer-encoding'] || '').downcase
372
+ if mesgformat.start_with?('text/plain', 'text/html')
373
+ # Content-Type: text/plain; charset=UTF-8
374
+ if ctencoding == 'base64'
375
+ # Content-Transfer-Encoding: base64
376
+ bodystring = Sisimai::RFC2045.decodeB(bodystring)
377
+
378
+ elsif ctencoding == 'quoted-printable'
379
+ # Content-Transfer-Encoding: quoted-printable
380
+ bodystring = Sisimai::RFC2045.decodeQ(bodystring)
381
+ end
382
+
383
+ if mesgformat.start_with?('text/html;')
384
+ # Content-Type: text/html;...
385
+ bodystring = Sisimai::String.to_plain(bodystring, true)
386
+ end
387
+ elsif mesgformat.start_with?('multipart/')
388
+ # NOT text/plain
389
+ # In case of Content-Type: multipart/*
390
+ p = Sisimai::RFC2045.makeflat(mailheader['content-type'], bodystring)
391
+ bodystring = p unless p.empty?
392
+ end
393
+ bodystring = bodystring.scrub('?').delete("\r").gsub("\t", " ")
394
+
395
+ haveloaded = {}
396
+ havesifted = nil
397
+ modulename = ''
398
+ if hookmethod.is_a? Proc
399
+ # Call the hook method
400
+ begin
401
+ p = { 'headers' => mailheader, 'message' => bodystring }
402
+ havecaught = hookmethod.call(p)
403
+ rescue StandardError => ce
404
+ warn ' ***warning: Something is wrong in hook method ":hook":' << ce.to_s
405
+ end
406
+ end
407
+
408
+ catch :PARSER do
409
+ while true
410
+ # 1. User-Defined Module
411
+ # 2. MTA Module Candidates to be tried on first
412
+ # 3. Sisimai::Lhost::*
413
+ # 4. Sisimai::RFC3464
414
+ # 5. Sisimai::ARF
415
+ # 6. Sisimai::RFC3834
416
+ while r = argvs['tobeloaded'].shift do
417
+ # Call user defined MTA modules
418
+ next if haveloaded[r]
419
+ havesifted = Module.const_get(r).inquire(mailheader, bodystring)
420
+ haveloaded[r] = true
421
+ modulename = r
422
+ throw :PARSER if havesifted
423
+ end
424
+
425
+ [argvs['tryonfirst'], DefaultSet].flatten.each do |r|
426
+ # Try MTA module candidates
427
+ next if haveloaded[r]
428
+ require LhostTable[r]
429
+ havesifted = Module.const_get(r).inquire(mailheader, bodystring)
430
+ haveloaded[r] = true
431
+ modulename = r
432
+ throw :PARSER if havesifted
433
+ end
434
+
435
+ unless haveloaded['Sisimai::RFC3464']
436
+ # When the all of Sisimai::Lhost::* modules did not return bounce data, call Sisimai::RFC3464;
437
+ require 'sisimai/rfc3464'
438
+ havesifted = Sisimai::RFC3464.inquire(mailheader, bodystring)
439
+ modulename = 'RFC3464'
440
+ throw :PARSER if havesifted
441
+ end
442
+
443
+ unless haveloaded['Sisimai::ARF']
444
+ # Feedback Loop message
445
+ require 'sisimai/arf'
446
+ havesifted = Sisimai::ARF.inquire(mailheader, bodystring) if Sisimai::ARF.is_arf(mailheader)
447
+ throw :PARSER if havesifted
448
+ end
449
+
450
+ unless haveloaded['Sisimai::RFC3834']
451
+ # Try to sift the message as auto reply message defined in RFC3834
452
+ require 'sisimai/rfc3834'
453
+ havesifted = Sisimai::RFC3834.inquire(mailheader, bodystring)
454
+ modulename = 'RFC3834'
455
+ throw :PARSER if havesifted
456
+ end
457
+
458
+ break # as of now, we have no sample email for coding this block
459
+ end
460
+ end
461
+ return nil unless havesifted
462
+
463
+ havesifted['catch'] = havecaught
464
+ modulename = modulename.sub(/\A.+::/, '')
465
+ havesifted['ds'].each do |e|
466
+ e['agent'] = modulename unless e['agent']
467
+ e.each_key { |a| e[a] ||= '' } # Replace nil with ""
468
+ end
469
+ return havesifted
359
470
  end
360
- return parseddata
361
- end
362
471
 
472
+ end
363
473
  end
364
474
  end
365
475