diff --git a/lib/net/imap.rb b/lib/net/imap.rb index f069ec5149..11d7db960e 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -818,13 +818,13 @@ def uid_search(keys, charset = nil) # #=> "12-Oct-2000 22:40:59 +0900" # p data.attr["UID"] # #=> 98 - def fetch(set, attr) - return fetch_internal("FETCH", set, attr) + def fetch(set, attr, mod = nil) + return fetch_internal("FETCH", set, attr, mod) end # Similar to #fetch(), but +set+ contains unique identifiers. - def uid_fetch(set, attr) - return fetch_internal("UID FETCH", set, attr) + def uid_fetch(set, attr, mod = nil) + return fetch_internal("UID FETCH", set, attr, mod) end # Sends a STORE command to alter data associated with messages @@ -1324,8 +1324,12 @@ def validate_data(data) when Integer NumValidator.ensure_number(data) when Array - data.each do |i| - validate_data(i) + if data[0] == 'CHANGEDSINCE' + NumValidator.ensure_mod_sequence_value(data[1]) + else + data.each do |i| + validate_data(i) + end end when Time when Symbol @@ -1427,7 +1431,7 @@ def search_internal(cmd, keys, charset) end end - def fetch_internal(cmd, set, attr) + def fetch_internal(cmd, set, attr, mod = nil) case attr when String then attr = RawData.new(attr) @@ -1439,7 +1443,11 @@ def fetch_internal(cmd, set, attr) synchronize do @responses.delete("FETCH") - send_command(cmd, MessageSet.new(set), attr) + if mod + send_command(cmd, MessageSet.new(set), attr, mod) + else + send_command(cmd, MessageSet.new(set), attr) + end return @responses.delete("FETCH") end end @@ -1673,6 +1681,15 @@ def valid_nz_number?(num) num != 0 && valid_number?(num) end + # Check is passed argument valid 'mod_sequence_value' in RFC 4551 terminology + def valid_mod_sequence_value?(num) + # mod-sequence-value = 1*DIGIT + # ; Positive unsigned 64-bit integer + # ; (mod-sequence) + # ; (1 <= n < 18,446,744,073,709,551,615) + num >= 1 && num < 18446744073709551615 + end + # Ensure argument is 'number' or raise DataFormatError def ensure_number(num) return if valid_number?(num) @@ -1688,6 +1705,14 @@ def ensure_nz_number(num) msg = "nz_number must be non-zero unsigned 32-bit integer: #{num}" raise DataFormatError, msg end + + # Ensure argument is 'mod_sequence_value' or raise DataFormatError + def ensure_mod_sequence_value(num) + return if valid_mod_sequence_value?(num) + + msg = "mod_sequence_value must be unsigned 64-bit integer: #{num}" + raise DataFormatError, msg + end end end @@ -2349,6 +2374,8 @@ def msg_att(n) name, val = body_data when /\A(?:UID)\z/ni name, val = uid_data + when /\A(?:MODSEQ)\z/ni + name, val = modseq_data else parse_error("unknown attribute `%s' for {%d}", token.value, n) end @@ -2838,6 +2865,16 @@ def uid_data return name, number end + def modseq_data + token = match(T_ATOM) + name = token.value.upcase + match(T_SPACE) + match(T_LPAR) + modseq = number + match(T_RPAR) + return name, modseq + end + def text_response token = match(T_ATOM) name = token.value.upcase diff --git a/test/net/imap/test_imap_response_parser.rb b/test/net/imap/test_imap_response_parser.rb index db07f9b022..ad5399d093 100644 --- a/test/net/imap/test_imap_response_parser.rb +++ b/test/net/imap/test_imap_response_parser.rb @@ -291,4 +291,11 @@ def test_body_ext_mpart_without_lang assert_equal("test.xml", body.parts[1].disposition.param["FILENAME"]) assert_equal(nil, body.parts[1].language) end + + # [Bug #10119] + def test_msg_att_modseq_data + parser = Net::IMAP::ResponseParser.new + response = parser.parse("* 1 FETCH (FLAGS (\Seen) MODSEQ (12345) UID 5)\r\n") + assert_equal(12345, response.data.attr["MODSEQ"]) + end end