sisimai 5.3.0 → 5.4.0
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.
- checksums.yaml +4 -4
- data/ChangeLog.md +12 -0
- data/Makefile +3 -2
- data/README-JA.md +2 -2
- data/README.md +6 -6
- data/lib/sisimai/address.rb +45 -56
- data/lib/sisimai/arf.rb +11 -16
- data/lib/sisimai/datetime.rb +16 -50
- data/lib/sisimai/fact/json.rb +5 -5
- data/lib/sisimai/fact/yaml.rb +3 -3
- data/lib/sisimai/fact.rb +11 -12
- data/lib/sisimai/lda.rb +3 -3
- data/lib/sisimai/lhost/activehunter.rb +4 -6
- data/lib/sisimai/lhost/amazonses.rb +5 -6
- data/lib/sisimai/lhost/apachejames.rb +7 -9
- data/lib/sisimai/lhost/biglobe.rb +3 -5
- data/lib/sisimai/lhost/courier.rb +4 -6
- data/lib/sisimai/lhost/domino.rb +4 -5
- data/lib/sisimai/lhost/dragonfly.rb +3 -5
- data/lib/sisimai/lhost/einsundeins.rb +6 -8
- data/lib/sisimai/lhost/exchange2003.rb +10 -12
- data/lib/sisimai/lhost/exchange2007.rb +4 -5
- data/lib/sisimai/lhost/exim.rb +6 -8
- data/lib/sisimai/lhost/ezweb.rb +10 -12
- data/lib/sisimai/lhost/fml.rb +2 -3
- data/lib/sisimai/lhost/gmail.rb +4 -6
- data/lib/sisimai/lhost/gmx.rb +6 -8
- data/lib/sisimai/lhost/googlegroups.rb +1 -2
- data/lib/sisimai/lhost/googleworkspace.rb +3 -4
- data/lib/sisimai/lhost/imailserver.rb +6 -7
- data/lib/sisimai/lhost/interscanmss.rb +1 -2
- data/lib/sisimai/lhost/kddi.rb +5 -8
- data/lib/sisimai/lhost/mailfoundry.rb +4 -7
- data/lib/sisimai/lhost/mailmarshalsmtp.rb +4 -6
- data/lib/sisimai/lhost/messagingserver.rb +5 -7
- data/lib/sisimai/lhost/mfilter.rb +4 -6
- data/lib/sisimai/lhost/notes.rb +7 -9
- data/lib/sisimai/lhost/opensmtpd.rb +2 -4
- data/lib/sisimai/lhost/postfix.rb +8 -11
- data/lib/sisimai/lhost/qmail.rb +5 -8
- data/lib/sisimai/lhost/sendmail.rb +7 -10
- data/lib/sisimai/lhost/v5sendmail.rb +15 -17
- data/lib/sisimai/lhost/verizon.rb +9 -14
- data/lib/sisimai/lhost/x1.rb +4 -6
- data/lib/sisimai/lhost/x2.rb +5 -7
- data/lib/sisimai/lhost/x3.rb +3 -4
- data/lib/sisimai/lhost/x6.rb +4 -6
- data/lib/sisimai/lhost/zoho.rb +6 -8
- data/lib/sisimai/lhost.rb +1 -1
- data/lib/sisimai/mail/mbox.rb +1 -1
- data/lib/sisimai/mail/memory.rb +1 -1
- data/lib/sisimai/mail.rb +8 -8
- data/lib/sisimai/message.rb +11 -13
- data/lib/sisimai/reason/authfailure.rb +10 -10
- data/lib/sisimai/reason/badreputation.rb +4 -6
- data/lib/sisimai/reason/blocked.rb +6 -8
- data/lib/sisimai/reason/contenterror.rb +5 -6
- data/lib/sisimai/reason/delivered.rb +2 -2
- data/lib/sisimai/reason/exceedlimit.rb +7 -8
- data/lib/sisimai/reason/expired.rb +6 -7
- data/lib/sisimai/reason/failedstarttls.rb +5 -7
- data/lib/sisimai/reason/feedback.rb +2 -2
- data/lib/sisimai/reason/filtered.rb +7 -10
- data/lib/sisimai/reason/hasmoved.rb +4 -5
- data/lib/sisimai/reason/hostunknown.rb +6 -7
- data/lib/sisimai/reason/mailboxfull.rb +7 -8
- data/lib/sisimai/reason/mailererror.rb +5 -8
- data/lib/sisimai/reason/mesgtoobig.rb +5 -6
- data/lib/sisimai/reason/networkerror.rb +5 -8
- data/lib/sisimai/reason/norelaying.rb +4 -5
- data/lib/sisimai/reason/notaccept.rb +5 -8
- data/lib/sisimai/reason/notcompliantrfc.rb +5 -6
- data/lib/sisimai/reason/onhold.rb +6 -9
- data/lib/sisimai/reason/policyviolation.rb +6 -9
- data/lib/sisimai/reason/rejected.rb +5 -6
- data/lib/sisimai/reason/requireptr.rb +6 -7
- data/lib/sisimai/reason/securityerror.rb +6 -9
- data/lib/sisimai/reason/spamdetected.rb +8 -9
- data/lib/sisimai/reason/speeding.rb +6 -7
- data/lib/sisimai/reason/suppressed.rb +3 -7
- data/lib/sisimai/reason/suspend.rb +5 -7
- data/lib/sisimai/reason/syntaxerror.rb +3 -5
- data/lib/sisimai/reason/systemerror.rb +6 -9
- data/lib/sisimai/reason/systemfull.rb +5 -8
- data/lib/sisimai/reason/toomanyconn.rb +5 -6
- data/lib/sisimai/reason/undefined.rb +2 -2
- data/lib/sisimai/reason/userunknown.rb +8 -9
- data/lib/sisimai/reason/vacation.rb +4 -5
- data/lib/sisimai/reason/virusdetected.rb +4 -5
- data/lib/sisimai/reason.rb +13 -13
- data/lib/sisimai/rfc1123.rb +4 -8
- data/lib/sisimai/rfc1894.rb +5 -6
- data/lib/sisimai/rfc2045.rb +27 -31
- data/lib/sisimai/rfc3464/thirdparty.rb +1 -1
- data/lib/sisimai/rfc3464.rb +7 -9
- data/lib/sisimai/rfc3834.rb +5 -9
- data/lib/sisimai/rfc5322.rb +8 -26
- data/lib/sisimai/rfc791.rb +6 -4
- data/lib/sisimai/rhost/google.rb +8 -0
- data/lib/sisimai/rhost/microsoft.rb +17 -5
- data/lib/sisimai/rhost.rb +2 -2
- data/lib/sisimai/smtp/command.rb +1 -1
- data/lib/sisimai/smtp/failure.rb +5 -12
- data/lib/sisimai/smtp/reply.rb +3 -5
- data/lib/sisimai/smtp/status.rb +14 -24
- data/lib/sisimai/smtp/transcript.rb +1 -10
- data/lib/sisimai/string.rb +20 -30
- data/lib/sisimai/version.rb +1 -1
- data/lib/sisimai.rb +11 -11
- data/set-of-emails/maildir/bsd/rhost-microsoft-06.eml +45 -0
- metadata +3 -2
    
        data/lib/sisimai/rfc1894.rb
    CHANGED
    
    | @@ -57,9 +57,9 @@ module Sisimai | |
| 57 57 | 
             
                   #"text" => ["X-Original-Message-ID", "Final-Log-ID", "Original-Envelope-ID"],
         | 
| 58 58 | 
             
                  }.freeze
         | 
| 59 59 |  | 
| 60 | 
            -
                  SubtypeSet = { | 
| 60 | 
            +
                  SubtypeSet = {"addr" => "RFC822", "cdoe" => "SMTP", "host" => "DNS"}.freeze
         | 
| 61 61 | 
             
                  ActionList = ["failed", "delayed", "delivered", "relayed", "expanded"].freeze
         | 
| 62 | 
            -
                  Correction = { | 
| 62 | 
            +
                  Correction = {'deliverable' => 'delivered', 'expired' => 'delayed', 'failure' => 'failed'}
         | 
| 63 63 | 
             
                  FieldGroup = {
         | 
| 64 64 | 
             
                    'original-recipient'    => 'addr',
         | 
| 65 65 | 
             
                    'final-recipient'       => 'addr',
         | 
| @@ -108,9 +108,8 @@ module Sisimai | |
| 108 108 | 
             
                  #                          2: Matched with per-recipient field
         | 
| 109 109 | 
             
                  # @since v4.25.0
         | 
| 110 110 | 
             
                  def match(argv0 = '')
         | 
| 111 | 
            -
                    return 0  | 
| 112 | 
            -
                    return 0  | 
| 113 | 
            -
                    label = Sisimai::RFC1894.label(argv0); return 0 unless label
         | 
| 111 | 
            +
                    return 0 if argv0.to_s == ""
         | 
| 112 | 
            +
                    label = Sisimai::RFC1894.label(argv0); return 0 if label.empty?
         | 
| 114 113 | 
             
                    match = 0
         | 
| 115 114 |  | 
| 116 115 | 
             
                    FieldNames[0].each_key do |e|
         | 
| @@ -135,7 +134,7 @@ module Sisimai | |
| 135 134 | 
             
                  # @return   [String]       Field name as a label
         | 
| 136 135 | 
             
                  # @since v4.25.15
         | 
| 137 136 | 
             
                  def label(argv0 = '')
         | 
| 138 | 
            -
                    return  | 
| 137 | 
            +
                    return "" if argv0.empty?
         | 
| 139 138 | 
             
                    return argv0.split(':', 2).shift.downcase
         | 
| 140 139 | 
             
                  end
         | 
| 141 140 |  | 
    
        data/lib/sisimai/rfc2045.rb
    CHANGED
    
    | @@ -7,10 +7,9 @@ module Sisimai | |
| 7 7 |  | 
| 8 8 | 
             
                  # Check that the argument is MIME-Encoded string or not
         | 
| 9 9 | 
             
                  # @param    [String] argvs  String to be checked
         | 
| 10 | 
            -
                  # @return   [ | 
| 11 | 
            -
                  #                           true:  MIME encoded string
         | 
| 10 | 
            +
                  # @return   [Boolean]       false: Not MIME encoded string, true: MIME encoded string
         | 
| 12 11 | 
             
                  def is_encoded(argv1)
         | 
| 13 | 
            -
                    return  | 
| 12 | 
            +
                    return false unless argv1
         | 
| 14 13 |  | 
| 15 14 | 
             
                    text1 = argv1.delete('"')
         | 
| 16 15 | 
             
                    mime1 = false
         | 
| @@ -60,13 +59,12 @@ module Sisimai | |
| 60 59 | 
             
                        textblocks[-1].gsub!(/\r\n/, '')
         | 
| 61 60 | 
             
                        textblocks << cv[5]
         | 
| 62 61 | 
             
                      else
         | 
| 63 | 
            -
                        textblocks << if textblocks.empty? then e else  | 
| 62 | 
            +
                        textblocks << if textblocks.empty? then e else " #{e}" end
         | 
| 64 63 | 
             
                      end
         | 
| 65 64 | 
             
                    end
         | 
| 66 | 
            -
             | 
| 67 65 | 
             
                    return '' if textblocks.empty?
         | 
| 68 | 
            -
                    p = textblocks.join('')
         | 
| 69 66 |  | 
| 67 | 
            +
                    p = textblocks.join('')
         | 
| 70 68 | 
             
                    if ctxcharset && qbencoding
         | 
| 71 69 | 
             
                      # utf8 => UTF-8
         | 
| 72 70 | 
             
                      ctxcharset = 'UTF-8' if ctxcharset.casecmp('UTF8') == 0
         | 
| @@ -74,32 +72,32 @@ module Sisimai | |
| 74 72 | 
             
                      unless ctxcharset.casecmp('UTF-8') == 0
         | 
| 75 73 | 
             
                        # Characterset is not UTF-8
         | 
| 76 74 | 
             
                        begin
         | 
| 77 | 
            -
                          p .encode!('UTF-8', ctxcharset)
         | 
| 75 | 
            +
                          p = p.encode!('UTF-8', ctxcharset)
         | 
| 78 76 | 
             
                        rescue
         | 
| 79 77 | 
             
                          p = 'FAILED TO CONVERT THE SUBJECT'
         | 
| 80 78 | 
             
                        end
         | 
| 81 79 | 
             
                      end
         | 
| 82 80 | 
             
                    end
         | 
| 83 | 
            -
             | 
| 84 | 
            -
                    return  | 
| 81 | 
            +
                    q = p.dup
         | 
| 82 | 
            +
                    return q.force_encoding('UTF-8').scrub('?')
         | 
| 85 83 | 
             
                  end
         | 
| 86 84 |  | 
| 87 85 | 
             
                  # Decode MIME BASE64 Encoded string
         | 
| 88 86 | 
             
                  # @param  [String] argv0   MIME Encoded text
         | 
| 89 87 | 
             
                  # @return [String]         MIME-Decoded text
         | 
| 90 88 | 
             
                  def decodeB(argv0 = nil)
         | 
| 91 | 
            -
                    return nil  | 
| 89 | 
            +
                    return "" if argv0.nil? || argv0.empty?
         | 
| 92 90 |  | 
| 93 91 | 
             
                    p = nil
         | 
| 94 92 | 
             
                    if cv = argv0.match(%r|([+/\=0-9A-Za-z\r\n]+)|) then p = Base64.decode64(cv[1]) end
         | 
| 95 | 
            -
                    return p ? p.scrub('?') :  | 
| 93 | 
            +
                    return p ? p.scrub('?') : ""
         | 
| 96 94 | 
             
                  end
         | 
| 97 95 |  | 
| 98 96 | 
             
                  # Decode MIME Quoted-Printable Encoded string
         | 
| 99 97 | 
             
                  # @param  [String] argv0   MIME Encoded text
         | 
| 100 98 | 
             
                  # @return [String]         MIME Decoded text
         | 
| 101 99 | 
             
                  def decodeQ(argv0 = nil)
         | 
| 102 | 
            -
                    return nil  | 
| 100 | 
            +
                    return "" if argv0.nil? || argv0.empty?
         | 
| 103 101 | 
             
                    return argv0.unpack('M').first.scrub('?')
         | 
| 104 102 | 
             
                  end
         | 
| 105 103 |  | 
| @@ -109,7 +107,7 @@ module Sisimai | |
| 109 107 | 
             
                  # @return   [String]        The value of the parameter
         | 
| 110 108 | 
             
                  # @since v5.0.0
         | 
| 111 109 | 
             
                  def parameter(argv0 = '', argv1 = '')
         | 
| 112 | 
            -
                    return  | 
| 110 | 
            +
                    return "" if argv0.empty?
         | 
| 113 111 | 
             
                    parameterq = argv1.size > 0 ? argv1 + '=' : ''
         | 
| 114 112 | 
             
                    paramindex = argv1.size > 0 ? argv0.index(parameterq) : 0
         | 
| 115 113 | 
             
                    return '' unless paramindex
         | 
| @@ -128,7 +126,7 @@ module Sisimai | |
| 128 126 | 
             
                  #                            1: End of boundary
         | 
| 129 127 | 
             
                  # @return   [String] Boundary string
         | 
| 130 128 | 
             
                  def boundary(argv0 = '', start = -1)
         | 
| 131 | 
            -
                    return  | 
| 129 | 
            +
                    return "" if argv0.empty?
         | 
| 132 130 | 
             
                    btext = parameter(argv0, 'boundary')
         | 
| 133 131 | 
             
                    return '' if btext.empty?
         | 
| 134 132 |  | 
| @@ -175,8 +173,8 @@ module Sisimai | |
| 175 173 | 
             
                      elsif e.index('boundary=') || e.index('charset=')
         | 
| 176 174 | 
             
                        # "Content-Type" field has boundary="..." or charset="utf-8"
         | 
| 177 175 | 
             
                        next if headerpart[0].empty?
         | 
| 178 | 
            -
                        headerpart[0]  | 
| 179 | 
            -
                        headerpart[0].gsub | 
| 176 | 
            +
                        headerpart[0] += " #{e}"
         | 
| 177 | 
            +
                        headerpart[0]  = headerpart[0].gsub(/\s\s+/, ' ')
         | 
| 180 178 | 
             
                      end
         | 
| 181 179 | 
             
                    end
         | 
| 182 180 | 
             
                    return headerpart if heads
         | 
| @@ -197,7 +195,7 @@ module Sisimai | |
| 197 195 | 
             
                      break if mediatypev.index('/feedback-report')
         | 
| 198 196 | 
             
                      break if ctencoding.empty?
         | 
| 199 197 |  | 
| 200 | 
            -
                      multipart1[2]  | 
| 198 | 
            +
                      multipart1[2] += sprintf("Content-Transfer-Encoding: %s\n", ctencoding)
         | 
| 201 199 | 
             
                      break
         | 
| 202 200 | 
             
                    end
         | 
| 203 201 |  | 
| @@ -206,10 +204,10 @@ module Sisimai | |
| 206 204 | 
             
                      break if lowerchunk.empty?
         | 
| 207 205 | 
             
                      break if lowerchunk[0, 1] == "\n"
         | 
| 208 206 |  | 
| 209 | 
            -
                      multipart1[2]  | 
| 207 | 
            +
                      multipart1[2] += "\n"
         | 
| 210 208 | 
             
                      break
         | 
| 211 209 | 
             
                    end
         | 
| 212 | 
            -
                    multipart1[2]  | 
| 210 | 
            +
                    multipart1[2] += lowerchunk
         | 
| 213 211 | 
             
                    return multipart1
         | 
| 214 212 | 
             
                  end
         | 
| 215 213 |  | 
| @@ -264,10 +262,8 @@ module Sisimai | |
| 264 262 | 
             
                  # @param    [String] argv1  A pointer to multipart/* message blocks
         | 
| 265 263 | 
             
                  # @return   [String]        Message body
         | 
| 266 264 | 
             
                  def makeflat(argv0 = '', argv1 = '')
         | 
| 267 | 
            -
                    return nil  | 
| 268 | 
            -
                    return  | 
| 269 | 
            -
                    return ''  unless argv0.index('multipart/')
         | 
| 270 | 
            -
                    return ''  unless argv0.index('boundary=')
         | 
| 265 | 
            +
                    return "" if argv0.nil? || argv1.nil?
         | 
| 266 | 
            +
                    return "" if argv0.index('multipart/') == false || argv0.index('boundary=') == false
         | 
| 271 267 |  | 
| 272 268 | 
             
                    # Some bounce messages include lower-cased "content-type:" field such as the followings:
         | 
| 273 269 | 
             
                    #   - content-type: message/delivery-status        => Content-Type: message/delivery-status
         | 
| @@ -284,7 +280,7 @@ module Sisimai | |
| 284 280 | 
             
                      #   - text/plain, text/rfc822-headers
         | 
| 285 281 | 
             
                      #   - message/delivery-status, message/rfc822, message/partial, message/feedback-report
         | 
| 286 282 | 
             
                      istexthtml = false
         | 
| 287 | 
            -
                      mediatypev = parameter(e[0])  | 
| 283 | 
            +
                      mediatypev = parameter(e[0]); mediatypev = "text/plain" if mediatypev.empty?
         | 
| 288 284 | 
             
                      next if mediatypev.start_with?('text/', 'message/') == false
         | 
| 289 285 |  | 
| 290 286 | 
             
                      if mediatypev == 'text/html'
         | 
| @@ -312,7 +308,7 @@ module Sisimai | |
| 312 308 | 
             
                          # Content-Transfer-Encoding: 7bit
         | 
| 313 309 | 
             
                          if cv = e[0].downcase.match(iso2022set)
         | 
| 314 310 | 
             
                            # Content-Type: text/plain; charset=ISO-2022-JP
         | 
| 315 | 
            -
                            bodystring = Sisimai::String.to_utf8(bodyinside, cv[1]) | 
| 311 | 
            +
                            bodystring = Sisimai::String.to_utf8(bodyinside, cv[1])
         | 
| 316 312 | 
             
                          else
         | 
| 317 313 | 
             
                            # No "charset" parameter in the value of Content-Type: header
         | 
| 318 314 | 
             
                            bodystring = bodyinside
         | 
| @@ -324,7 +320,7 @@ module Sisimai | |
| 324 320 |  | 
| 325 321 | 
             
                        if istexthtml
         | 
| 326 322 | 
             
                          # Try to delete HTML tags inside of text/html part whenever possible
         | 
| 327 | 
            -
                          bodystring = Sisimai::String.to_plain(bodystring) | 
| 323 | 
            +
                          bodystring = Sisimai::String.to_plain(bodystring)
         | 
| 328 324 | 
             
                        end
         | 
| 329 325 | 
             
                        next if bodystring.empty?
         | 
| 330 326 |  | 
| @@ -341,16 +337,16 @@ module Sisimai | |
| 341 337 | 
             
                            bodystring.scrub!('?')
         | 
| 342 338 | 
             
                          else
         | 
| 343 339 | 
             
                            # ISO-8859-1, GB2312, and so on
         | 
| 344 | 
            -
                            bodystring = Sisimai::String.to_utf8(bodystring, ctxcharset) | 
| 340 | 
            +
                            bodystring = Sisimai::String.to_utf8(bodystring, ctxcharset)
         | 
| 345 341 | 
             
                          end
         | 
| 346 | 
            -
                          bodystring  | 
| 342 | 
            +
                          bodystring += "\n\n"
         | 
| 347 343 | 
             
                        end
         | 
| 348 344 |  | 
| 349 345 | 
             
                        bodystring.gsub!(/\r\n/, "\n") if bodystring.include?("\r\n") # Convert CRLF to LF
         | 
| 350 346 |  | 
| 351 347 | 
             
                      else
         | 
| 352 348 | 
             
                        # There is no Content-Transfer-Encoding header in the part
         | 
| 353 | 
            -
                        bodystring  | 
| 349 | 
            +
                        bodystring += bodyinside
         | 
| 354 350 | 
             
                      end
         | 
| 355 351 |  | 
| 356 352 | 
             
                      if delimiters.any? { |a| mediatypev.include?(a) }
         | 
| @@ -361,8 +357,8 @@ module Sisimai | |
| 361 357 | 
             
                      end
         | 
| 362 358 |  | 
| 363 359 | 
             
                      # Append "\n" when the last character of $bodystring is not LF
         | 
| 364 | 
            -
                      bodystring  | 
| 365 | 
            -
                      flattenout  | 
| 360 | 
            +
                      bodystring += "\n\n" unless bodystring[-2, 2] == "\n\n"
         | 
| 361 | 
            +
                      flattenout += bodystring
         | 
| 366 362 | 
             
                    end
         | 
| 367 363 |  | 
| 368 364 | 
             
                    return flattenout
         | 
| @@ -39,7 +39,7 @@ module Sisimai | |
| 39 39 | 
             
                    def xfield(argv1 = "")
         | 
| 40 40 | 
             
                      return [] if argv1.nil? || argv1.empty?
         | 
| 41 41 | 
             
                      party = Sisimai::RFC3464::ThirdParty.returnedby(argv1); return [] if party.empty?
         | 
| 42 | 
            -
                      return Module.const_get("Sisimai::RFC3464::ThirdParty | 
| 42 | 
            +
                      return Module.const_get("Sisimai::RFC3464::ThirdParty::#{party}").xfield(argv1)
         | 
| 43 43 | 
             
                    end
         | 
| 44 44 | 
             
                  end
         | 
| 45 45 |  | 
    
        data/lib/sisimai/rfc3464.rb
    CHANGED
    
    | @@ -20,7 +20,7 @@ module Sisimai | |
| 20 20 | 
             
                    "Content-Type: message/partial",
         | 
| 21 21 | 
             
                    "Content-Disposition: inline", # See lhost-amavis-*.eml, lhost-facebook-*.eml
         | 
| 22 22 | 
             
                  ].freeze
         | 
| 23 | 
            -
                  StartingOf = { | 
| 23 | 
            +
                  StartingOf = {message: ["Content-Type: message/delivery-status"]}.freeze
         | 
| 24 24 | 
             
                  FieldTable = Sisimai::RFC1894.FIELDTABLE
         | 
| 25 25 |  | 
| 26 26 | 
             
                  # Decode a bounce mail which have fields defined in RFC3464
         | 
| @@ -37,7 +37,7 @@ module Sisimai | |
| 37 37 | 
             
                    end
         | 
| 38 38 |  | 
| 39 39 | 
             
                    permessage = {}
         | 
| 40 | 
            -
                    dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
         | 
| 40 | 
            +
                    dscontents = [Sisimai::Lhost.DELIVERYSTATUS]; v = nil
         | 
| 41 41 | 
             
                    alternates = Sisimai::Lhost.DELIVERYSTATUS
         | 
| 42 42 | 
             
                    emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
         | 
| 43 43 | 
             
                    readslices = [""]
         | 
| @@ -46,7 +46,6 @@ module Sisimai | |
| 46 46 | 
             
                    beforemesg = ""     # (String) String before StartingOf[:message]
         | 
| 47 47 | 
             
                    goestonext = false  # (Bool) Flag: do not append the line into beforemesg
         | 
| 48 48 | 
             
                    isboundary = [Sisimai::RFC2045.boundary(mhead["content-type"], 0)]; isboundary[0] ||= ""
         | 
| 49 | 
            -
                    v = nil
         | 
| 50 49 |  | 
| 51 50 | 
             
                    while emailparts[0].index('@').nil? do
         | 
| 52 51 | 
             
                      # There is no email address in the first element of emailparts
         | 
| @@ -134,12 +133,11 @@ module Sisimai | |
| 134 133 | 
             
                          break if e.start_with?("--")              # Boundary string
         | 
| 135 134 | 
             
                          break if e.include?("--- The follow")     # ----- The following addresses had delivery problems -----
         | 
| 136 135 | 
             
                          break if e.include?("--- Transcript")     # ----- Transcript of session follows -----
         | 
| 137 | 
            -
                          beforemesg  | 
| 136 | 
            +
                          beforemesg += "#{e} "; break
         | 
| 138 137 | 
             
                        end
         | 
| 139 138 | 
             
                        next
         | 
| 140 139 | 
             
                      end
         | 
| 141 | 
            -
                      next if (readcursor & Indicators[:deliverystatus]) == 0
         | 
| 142 | 
            -
                      next if e.empty?
         | 
| 140 | 
            +
                      next if (readcursor & Indicators[:deliverystatus]) == 0 || e.empty?
         | 
| 143 141 |  | 
| 144 142 | 
             
                      f = Sisimai::RFC1894.match(e)
         | 
| 145 143 | 
             
                      if f > 0
         | 
| @@ -177,7 +175,7 @@ module Sisimai | |
| 177 175 | 
             
                            # There are other error messages as a comment such as the following:
         | 
| 178 176 | 
             
                            # Status: 5.0.0 (permanent failure)
         | 
| 179 177 | 
             
                            # Status: 4.0.0 (cat.example.net: host name lookup failure)
         | 
| 180 | 
            -
                            v["diagnosis"]  | 
| 178 | 
            +
                            v["diagnosis"] += " #{o[4]} "
         | 
| 181 179 | 
             
                          end
         | 
| 182 180 | 
             
                          next unless FieldTable[o[0]]
         | 
| 183 181 | 
             
                          next if o[3] == "host" && Sisimai::RFC1123.is_internethost(o[2]) == false
         | 
| @@ -208,13 +206,13 @@ module Sisimai | |
| 208 206 | 
             
                            # In the case of multiple "message/delivery-status" line
         | 
| 209 207 | 
             
                            next if e.start_with?("Content-") # Content-Disposition:, ...
         | 
| 210 208 | 
             
                            next if e.start_with?("--")       # Boundary string
         | 
| 211 | 
            -
                            beforemesg  | 
| 209 | 
            +
                            beforemesg += "#{e} "; next
         | 
| 212 210 | 
             
                          end
         | 
| 213 211 |  | 
| 214 212 | 
             
                          # Diagnostic-Code: SMTP; 550-5.7.26 The MAIL FROM domain [email.example.jp]
         | 
| 215 213 | 
             
                          #    has an SPF record with a hard fail
         | 
| 216 214 | 
             
                          next unless e.start_with?(" ")
         | 
| 217 | 
            -
                          v["diagnosis"]  | 
| 215 | 
            +
                          v["diagnosis"] += " #{Sisimai::String.sweep(e)}"
         | 
| 218 216 | 
             
                        end
         | 
| 219 217 | 
             
                      end
         | 
| 220 218 | 
             
                    end
         | 
    
        data/lib/sisimai/rfc3834.rb
    CHANGED
    
    | @@ -3,7 +3,7 @@ module Sisimai | |
| 3 3 | 
             
              module RFC3834
         | 
| 4 4 | 
             
                class << self
         | 
| 5 5 | 
             
                  # http://tools.ietf.org/html/rfc3834
         | 
| 6 | 
            -
                  MarkingsOf = { | 
| 6 | 
            +
                  MarkingsOf = {:boundary => '__SISIMAI_PSEUDO_BOUNDARY__'}
         | 
| 7 7 | 
             
                  LowerLabel = %w[from to subject auto-submitted precedence x-apple-action].freeze
         | 
| 8 8 | 
             
                  DoNotParse = {
         | 
| 9 9 | 
             
                    'from'    => ['root@', 'postmaster@', 'mailer-daemon@'],
         | 
| @@ -66,7 +66,7 @@ module Sisimai | |
| 66 66 | 
             
                    return nil if match < 1
         | 
| 67 67 |  | 
| 68 68 | 
             
                    require 'sisimai/lhost'
         | 
| 69 | 
            -
                    dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
         | 
| 69 | 
            +
                    dscontents = [Sisimai::Lhost.DELIVERYSTATUS]; v = dscontents[-1]
         | 
| 70 70 | 
             
                    bodyslices = mbody.scrub('?').split("\n")
         | 
| 71 71 | 
             
                    rfc822part = '' # (String) message/rfc822-headers part
         | 
| 72 72 | 
             
                    recipients = 0  # (Integer) The number of 'Final-Recipient' header
         | 
| @@ -74,13 +74,11 @@ module Sisimai | |
| 74 74 | 
             
                    haveloaded = 0  # (Integer) The number of lines loaded from message body
         | 
| 75 75 | 
             
                    blanklines = 0  # (Integer) Counter for countinuous blank lines
         | 
| 76 76 | 
             
                    countuntil = 1  # (Integer) Maximum value of blank lines in the body part
         | 
| 77 | 
            -
                    v = dscontents[-1]
         | 
| 78 77 |  | 
| 79 78 | 
             
                    # RECIPIENT_ADDRESS
         | 
| 80 79 | 
             
                    %w[from return-path].each do |e|
         | 
| 81 80 | 
             
                      # Try to get the address of the recipient
         | 
| 82 81 | 
             
                      next unless mhead[e]
         | 
| 83 | 
            -
                      next unless mhead[e]
         | 
| 84 82 | 
             
                      v['recipient'] = mhead[e]
         | 
| 85 83 | 
             
                      break
         | 
| 86 84 | 
             
                    end
         | 
| @@ -109,12 +107,10 @@ module Sisimai | |
| 109 107 | 
             
                        break if blanklines > countuntil
         | 
| 110 108 | 
             
                        next
         | 
| 111 109 | 
             
                      end
         | 
| 112 | 
            -
                      next  | 
| 113 | 
            -
                      next if     e.start_with?('Content-Type')
         | 
| 114 | 
            -
                      next if     e.start_with?('Content-Transfer')
         | 
| 110 | 
            +
                      next if !e.include?(' ') || e.start_with?('Content-Type', 'Content-Transfer')
         | 
| 115 111 |  | 
| 116 112 | 
             
                      v['diagnosis'] ||= ''
         | 
| 117 | 
            -
                      v['diagnosis']   | 
| 113 | 
            +
                      v['diagnosis']  += "#{e }"
         | 
| 118 114 | 
             
                      haveloaded += 1
         | 
| 119 115 | 
             
                      break if haveloaded >= maxmsgline
         | 
| 120 116 | 
             
                    end
         | 
| @@ -126,7 +122,7 @@ module Sisimai | |
| 126 122 |  | 
| 127 123 | 
             
                    if cv = lower['subject'].match(SubjectSet)
         | 
| 128 124 | 
             
                      # Get the Subject header from the original message
         | 
| 129 | 
            -
                      rfc822part =  | 
| 125 | 
            +
                      rfc822part = "Subject: #{cv[1]}\n"
         | 
| 130 126 | 
             
                    end
         | 
| 131 127 | 
             
                    return { 'ds' => dscontents, 'rfc822' => rfc822part }
         | 
| 132 128 | 
             
                  end
         | 
    
        data/lib/sisimai/rfc5322.rb
    CHANGED
    
    | @@ -32,19 +32,12 @@ module Sisimai | |
| 32 32 | 
             
                    return []
         | 
| 33 33 | 
             
                  end
         | 
| 34 34 |  | 
| 35 | 
            -
                  # Fields that might be long
         | 
| 36 | 
            -
                  # @return   [Hash] Long filed(email header) list
         | 
| 37 | 
            -
                  def LONGFIELDS
         | 
| 38 | 
            -
                    return { 'to' => true, 'from' => true, 'subject' => true, 'message-id' => true }
         | 
| 39 | 
            -
                  end
         | 
| 40 | 
            -
             | 
| 41 35 | 
             
                  # Convert Received headers to a structured data
         | 
| 42 36 | 
             
                  # @param    [String] argv1  Received header
         | 
| 43 37 | 
             
                  # @return   [Array]         Received header as a structured data
         | 
| 44 38 | 
             
                  def received(argv1)
         | 
| 45 39 | 
             
                    return [] unless argv1.is_a?(::String)
         | 
| 46 | 
            -
                    return [] if argv1.include?(' invoked by uid')
         | 
| 47 | 
            -
                    return [] if argv1.include?(' invoked from network')
         | 
| 40 | 
            +
                    return [] if argv1.include?(' invoked by uid') || argv1.include?(' invoked from network')
         | 
| 48 41 |  | 
| 49 42 | 
             
                    # - https://datatracker.ietf.org/doc/html/rfc5322
         | 
| 50 43 | 
             
                    #   received        =   "Received:" *received-token ";" date-time CRLF
         | 
| @@ -105,41 +98,30 @@ module Sisimai | |
| 105 98 | 
             
                      # Check alternatives in "other", and then delete uninformative values.
         | 
| 106 99 | 
             
                      next if e.nil?
         | 
| 107 100 | 
             
                      next if e.size < 4
         | 
| 108 | 
            -
                      next if e == 'unknown'
         | 
| 109 | 
            -
                      next if e == ' | 
| 110 | 
            -
                      next if e == '[127.0.0.1]'
         | 
| 111 | 
            -
                      next if e == '[IPv6:::1]'
         | 
| 112 | 
            -
                      next unless e.include?('.')
         | 
| 113 | 
            -
                      next if e.include?('=')
         | 
| 101 | 
            +
                      next if e == 'unknown' || e == 'localhost' || e == '[127.0.0.1]' || e == '[IPv6:::1]'
         | 
| 102 | 
            +
                      next if e.include?('.') == false || e.include?('=') == true
         | 
| 114 103 | 
             
                      alter << e
         | 
| 115 104 | 
             
                    end
         | 
| 116 105 |  | 
| 117 106 | 
             
                    %w[from by].each do |e|
         | 
| 118 107 | 
             
                      # Remove square brackets from the IP address such as "[192.0.2.25]"
         | 
| 119 | 
            -
                      next if token[e]. | 
| 120 | 
            -
                      next if token[e].empty?
         | 
| 121 | 
            -
                      next unless token[e].start_with?('[')
         | 
| 108 | 
            +
                      next if token[e].to_s.empty? || token[e].start_with?('[') == false
         | 
| 122 109 | 
             
                      token[e] = Sisimai::RFC791.find(token[e]).shift || ''
         | 
| 123 110 | 
             
                    end
         | 
| 124 111 | 
             
                    token['from'] ||= ''
         | 
| 125 112 |  | 
| 126 113 | 
             
                    while true do
         | 
| 127 114 | 
             
                      # Prefer hostnames over IP addresses, except for localhost.localdomain and similar.
         | 
| 128 | 
            -
                      break if token['from'] == 'localhost'
         | 
| 129 | 
            -
                      break if token['from'] == ' | 
| 130 | 
            -
                      break unless token['from'].include?('.')  # A hostname without a domain name
         | 
| 131 | 
            -
                      break unless Sisimai::RFC791.find(token['from']).empty?
         | 
| 115 | 
            +
                      break if token['from'] == 'localhost' || token['from'] == 'localhost.localdomain'
         | 
| 116 | 
            +
                      break if token['from'].include?('.') == false || Sisimai::RFC791.find(token['from']).empty? == false
         | 
| 132 117 |  | 
| 133 | 
            -
                      # No need to rewrite token['from']
         | 
| 134 | 
            -
                      right = true
         | 
| 118 | 
            +
                      right = true # No need to rewrite token['from']
         | 
| 135 119 | 
             
                      break
         | 
| 136 120 | 
             
                    end
         | 
| 137 121 |  | 
| 138 122 | 
             
                    while true do
         | 
| 139 123 | 
             
                      # Try to rewrite uninformative hostnames and IP addresses in token['from']
         | 
| 140 | 
            -
                      break if right | 
| 141 | 
            -
                      break if alter.empty? # There is no alternative to rewriting
         | 
| 142 | 
            -
                      break if alter[0].include?(token['from'])
         | 
| 124 | 
            +
                      break if right || alter.empty? || alter[0].include?(token['from'])
         | 
| 143 125 |  | 
| 144 126 | 
             
                      if token['from'].start_with?('localhost')
         | 
| 145 127 | 
             
                        # localhost or localhost.localdomain
         | 
    
        data/lib/sisimai/rfc791.rb
    CHANGED
    
    | @@ -18,6 +18,7 @@ module Sisimai | |
| 18 18 | 
             
                    end
         | 
| 19 19 | 
             
                    return true
         | 
| 20 20 | 
             
                  end
         | 
| 21 | 
            +
             | 
| 21 22 | 
             
                  # Find an IPv4 address from the given string
         | 
| 22 23 | 
             
                  # @param    [String] argv1  String including an IPv4 address
         | 
| 23 24 | 
             
                  # @return   [Array]         List of IPv4 addresses
         | 
| @@ -26,14 +27,15 @@ module Sisimai | |
| 26 27 | 
             
                    return nil if argv0.to_s.empty?
         | 
| 27 28 | 
             
                    return []  if argv0.size < 7
         | 
| 28 29 |  | 
| 30 | 
            +
                    given = argv0.dup
         | 
| 29 31 | 
             
                    ipv4a = []
         | 
| 30 32 | 
             
                    %w|( ) [ ] ,|.each do |e|
         | 
| 31 33 | 
             
                      # Rewrite: "mx.example.jp[192.0.2.1]" => "mx.example.jp 192.0.2.1"
         | 
| 32 | 
            -
                      p0 =  | 
| 33 | 
            -
                       | 
| 34 | 
            +
                      p0 = given.index(e); next unless p0
         | 
| 35 | 
            +
                      given[p0, 1] = ' '
         | 
| 34 36 | 
             
                    end
         | 
| 35 37 |  | 
| 36 | 
            -
                     | 
| 38 | 
            +
                    given.split(' ').each do |e|
         | 
| 37 39 | 
             
                      # Find string including an IPv4 address
         | 
| 38 40 | 
             
                      next unless e.index('.')  # IPv4 address must include "." character
         | 
| 39 41 |  | 
| @@ -55,7 +57,7 @@ module Sisimai | |
| 55 57 | 
             
                          eo = ''
         | 
| 56 58 | 
             
                          next
         | 
| 57 59 | 
             
                        end
         | 
| 58 | 
            -
                        eo  | 
| 60 | 
            +
                        eo += as.chr
         | 
| 59 61 | 
             
                        break if eo.to_i > 255
         | 
| 60 62 | 
             
                      end
         | 
| 61 63 | 
             
                      ipv4a << e if eo.size > 0 && eo.to_i < 256
         | 
    
        data/lib/sisimai/rhost/google.rb
    CHANGED
    
    | @@ -82,7 +82,15 @@ module Sisimai | |
| 82 82 | 
             
                        # - 421 4.7.32 Your email has been rate limited because the From: header (RFC5322) in
         | 
| 83 83 | 
             
                        #   this message isn't aligned with either the authenticated SPF or DKIM organizational
         | 
| 84 84 | 
             
                        #   domain.
         | 
| 85 | 
            +
                        # - 421 5.7.32 Your email was blocked because the From: header (RFC5322) in this message
         | 
| 86 | 
            +
                        #   isn't aligned with either the authenticated SPF or DKIM organizational domain.
         | 
| 85 87 | 
             
                        ['421', '4.7.32', 'aligned with either the authenticated spf or dkim'],
         | 
| 88 | 
            +
                        ["421", "5.7.32", "aligned with either the authenticated spf or dkim"],
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                        # - 421 4.7.40 Your email has been rate limited because the sending domain doesn't
         | 
| 91 | 
            +
                        #   have a DMARC record, or the DMARC record doesn’t specify a DMARC policy. Gmail
         | 
| 92 | 
            +
                        #   requires all bulk email senders to add a DMARC record to their sending domain.
         | 
| 93 | 
            +
                        ["421", "4.7.40", "to add a dmarc record to "],
         | 
| 86 94 | 
             
                      ],
         | 
| 87 95 | 
             
                      'badreputation' => [
         | 
| 88 96 | 
             
                        # - 421 4.7.0 This message is suspicious due to the very low reputation of the sending
         | 
| @@ -23,6 +23,13 @@ module Sisimai | |
| 23 23 | 
             
                        # - Access denied, sending domain [$SenderDomain] does not pass DMARC verification
         | 
| 24 24 | 
             
                        # - The sender's domain in the 5322.From address doesn't pass DMARC.
         | 
| 25 25 | 
             
                        ['5.7.509', 0, 0, 'does not pass dmarc verification'],
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                        # - 550 5.7.515 Access denied, sending domain EXAMPLE.JP doesn't meet the required
         | 
| 28 | 
            +
                        #   authentication level. The sender's domain in the 5322.From address doesn't meet
         | 
| 29 | 
            +
                        #   the authentication requirements defined for the sender. To learn how to fix this
         | 
| 30 | 
            +
                        #   see: https://go.microsoft.com/fwlink/p/?linkid=2319303
         | 
| 31 | 
            +
                        #   Spf= Fail , Dkim= Pass , DMARC= Pass ...
         | 
| 32 | 
            +
                        ["5.7.515", 0, 0, "doesn't meet the required authentication level"],
         | 
| 26 33 | 
             
                      ],
         | 
| 27 34 | 
             
                      'badreputation' => [
         | 
| 28 35 | 
             
                        # Undocumented error messages ---------------------------------------------------------
         | 
| @@ -597,11 +604,16 @@ module Sisimai | |
| 597 604 | 
             
                        ['5.2.14', 0, 0, 'misconfigured forwarding address'],
         | 
| 598 605 |  | 
| 599 606 | 
             
                        # Undocumented error messages ---------------------------------------------------------
         | 
| 600 | 
            -
                         | 
| 601 | 
            -
                         | 
| 602 | 
            -
                         | 
| 603 | 
            -
                         | 
| 604 | 
            -
                         | 
| 607 | 
            +
                        # - 451 4.4.22 Message failed to be replicated: no healthy peers found ... (in reply to end of DATA command)
         | 
| 608 | 
            +
                        # - 451 4.4.23 Message failed to be replicated: No healthy secondary server available
         | 
| 609 | 
            +
                        #   to accept replica at this time. ... (in reply to end of DATA command)
         | 
| 610 | 
            +
                        # - 451 4.4.28 Message failed to be replicated:
         | 
| 611 | 
            +
                        #   Microsoft.Exchange.Transport.Net.Http.TransportHttpException(session Id: -1) ...(in reply to end of DATA command)
         | 
| 612 | 
            +
                        # - 451 4.4.28 Message failed to be replicated:
         | 
| 613 | 
            +
                        #   System.Net.Http.HttpRequestException(session Id: ****) ... (in reply to end of DATA command)
         | 
| 614 | 
            +
                        ["4.4.", "22", "28", "message failed to be replicated:"],
         | 
| 615 | 
            +
                        ["4.4.3",  "",   "", "temporary server error. please try again later attr18"],
         | 
| 616 | 
            +
                        ["4.7.0",  "",   "", "temporary server error. please try again later. prx4 nexthop:"],
         | 
| 605 617 |  | 
| 606 618 | 
             
                        # 550 5.4.318 Message expired, connection reset (SuspiciousRemoteServerError)
         | 
| 607 619 | 
             
                        # 450 4.4.318 Connection was closed abruptly (SuspiciousRemoteServerError)
         | 
    
        data/lib/sisimai/rhost.rb
    CHANGED
    
    | @@ -74,8 +74,8 @@ module Sisimai | |
| 74 74 | 
             
                    return "" if argvs.nil?
         | 
| 75 75 |  | 
| 76 76 | 
             
                    rhostclass = name(argvs); return "" if rhostclass.empty?
         | 
| 77 | 
            -
                    modulepath = "sisimai/rhost | 
| 78 | 
            -
                    modulename = "Sisimai::Rhost | 
| 77 | 
            +
                    modulepath = "sisimai/rhost/#{rhostclass.downcase}"; require modulepath
         | 
| 78 | 
            +
                    modulename = "Sisimai::Rhost::#{rhostclass}"
         | 
| 79 79 |  | 
| 80 80 | 
             
                    #rhostclass = "sisimai/rhost/" << modulename.downcase.split("::")[2]; require rhostclass
         | 
| 81 81 | 
             
                    reasontext = Module.const_get(modulename).find(argvs)
         | 
    
        data/lib/sisimai/smtp/command.rb
    CHANGED
    
    | @@ -31,7 +31,7 @@ module Sisimai | |
| 31 31 | 
             
                      return "" unless Sisimai::SMTP::Command.test(argv0)
         | 
| 32 32 |  | 
| 33 33 | 
             
                      issuedcode = " " + argv0.downcase + " "
         | 
| 34 | 
            -
                      commandmap = { | 
| 34 | 
            +
                      commandmap = {"STAR" => "STARTTLS", "XFOR" => "XFORWARD"}
         | 
| 35 35 | 
             
                      commandset = []
         | 
| 36 36 |  | 
| 37 37 | 
             
                      Detectable.each do |e|
         | 
    
        data/lib/sisimai/smtp/failure.rb
    CHANGED
    
    | @@ -12,8 +12,7 @@ module Sisimai | |
| 12 12 | 
             
                    #                           false: Is not a permanent error
         | 
| 13 13 | 
             
                    # @since v4.17.3
         | 
| 14 14 | 
             
                    def is_permanent(argv1 = '')
         | 
| 15 | 
            -
                      return false  | 
| 16 | 
            -
                      return false unless argv1.size > 0
         | 
| 15 | 
            +
                      return false if argv1.to_s == ""
         | 
| 17 16 |  | 
| 18 17 | 
             
                      statuscode = Sisimai::SMTP::Status.find(argv1)
         | 
| 19 18 | 
             
                      statuscode = Sisimai::SMTP::Reply.find(argv1) if statuscode.empty?
         | 
| @@ -28,16 +27,14 @@ module Sisimai | |
| 28 27 | 
             
                    #                           false: is not a temporary error
         | 
| 29 28 | 
             
                    # @since v5.2.0
         | 
| 30 29 | 
             
                    def is_temporary(argv1 = '')
         | 
| 31 | 
            -
                      return false  | 
| 32 | 
            -
                      return false unless argv1.size > 0
         | 
| 30 | 
            +
                      return false if argv1.to_s == ""
         | 
| 33 31 |  | 
| 34 32 | 
             
                      statuscode = Sisimai::SMTP::Status.find(argv1);
         | 
| 35 33 | 
             
                      statuscode = Sisimai::SMTP::Reply.find(argv1) if statuscode.empty?
         | 
| 36 34 | 
             
                      issuedcode = argv1.downcase
         | 
| 37 35 |  | 
| 38 36 | 
             
                      return true if statuscode[0, 1] == "4"
         | 
| 39 | 
            -
                      return true if issuedcode.include?(' temporar')
         | 
| 40 | 
            -
                      return true if issuedcode.include?(' persistent')
         | 
| 37 | 
            +
                      return true if issuedcode.include?(' temporar') || issuedcode.include?(' persistent')
         | 
| 41 38 | 
             
                      return false
         | 
| 42 39 | 
             
                    end
         | 
| 43 40 |  | 
| @@ -46,9 +43,7 @@ module Sisimai | |
| 46 43 | 
             
                    # @param    [String] argv2  String including SMTP Status code
         | 
| 47 44 | 
             
                    # @return   [Boolean]       true: is a hard bounce
         | 
| 48 45 | 
             
                    def is_hardbounce(argv1 = '', argv2 = '')
         | 
| 49 | 
            -
                      return false  | 
| 50 | 
            -
                      return false unless argv1.size > 0
         | 
| 51 | 
            -
             | 
| 46 | 
            +
                      return false if argv1.to_s == ""
         | 
| 52 47 | 
             
                      return false if argv1 == "undefined" || argv1 == "onhold"
         | 
| 53 48 | 
             
                      return false if argv1 == "delivered" || argv1 == "feedback"    || argv1 == "vacation"
         | 
| 54 49 | 
             
                      return true  if argv1 == "hasmoved"  || argv1 == "userunknown" || argv1 == "hostunknown"
         | 
| @@ -76,9 +71,7 @@ module Sisimai | |
| 76 71 | 
             
                    # @param    [String] argv2  String including SMTP Status code
         | 
| 77 72 | 
             
                    # @return   [Boolean]       true: is a soft bounce
         | 
| 78 73 | 
             
                    def is_softbounce(argv1 = '', argv2 = '')
         | 
| 79 | 
            -
                      return false  | 
| 80 | 
            -
                      return false unless argv1.size > 0
         | 
| 81 | 
            -
             | 
| 74 | 
            +
                      return false if argv1.to_s == ""
         | 
| 82 75 | 
             
                      return false if argv1 == "delivered" || argv1 == "feedback"    || argv1 == "vacation"
         | 
| 83 76 | 
             
                      return false if argv1 == "hasmoved"  || argv1 == "userunknown" || argv1 == "hostunknown"
         | 
| 84 77 | 
             
                      return true  if argv1 == "undefined" || argv1 == "onhold"
         | 
    
        data/lib/sisimai/smtp/reply.rb
    CHANGED
    
    | @@ -109,7 +109,7 @@ module Sisimai | |
| 109 109 | 
             
                      "550", "552", "553", "551", "521", "525", "523", "524", "530", "533", "534", "535", "538",
         | 
| 110 110 | 
             
                      "555", "556", "554", "500", "501", "502", "503", "504",
         | 
| 111 111 | 
             
                    ].freeze
         | 
| 112 | 
            -
                    CodeOfSMTP = { | 
| 112 | 
            +
                    CodeOfSMTP = {'2' => ReplyCode2, '4' => ReplyCode4, '5' => ReplyCode5}.freeze
         | 
| 113 113 | 
             
                    Associated = {
         | 
| 114 114 | 
             
                      "422" => ["AUTH",     "4.7.12",  "securityerror"], # RFC5238
         | 
| 115 115 | 
             
                      "432" => ["AUTH",     "4.7.12",  "securityerror"], # RFC4954, RFC5321
         | 
| @@ -163,11 +163,9 @@ module Sisimai | |
| 163 163 | 
             
                    # Get SMTP Reply Code from the given string
         | 
| 164 164 | 
             
                    # @param    [String] argv1  String including SMTP Reply Code like 550
         | 
| 165 165 | 
             
                    # @param    [String] argv2  Status code like 5.1.1 or 2 or 4 or 5
         | 
| 166 | 
            -
                    # @return   [String]        SMTP Reply Code
         | 
| 167 | 
            -
                    #           [Nil]           The first argument did not include SMTP Reply Code value
         | 
| 166 | 
            +
                    # @return   [String]        SMTP Reply Code or an empty string
         | 
| 168 167 | 
             
                    def find(argv1 = '', argv2 = '0')
         | 
| 169 | 
            -
                      return "" if argv1.to_s.size < 3
         | 
| 170 | 
            -
                      return "" if argv1.upcase.include?('X-UNIX')
         | 
| 168 | 
            +
                      return "" if argv1.to_s.size < 3 || argv1.upcase.include?('X-UNIX')
         | 
| 171 169 |  | 
| 172 170 | 
             
                      esmtperror = ' ' + argv1 + ' '
         | 
| 173 171 | 
             
                      esmtpreply = ''
         |