From 95afda8f1044cce230b1b82d7f882a376161eb2a Mon Sep 17 00:00:00 2001 From: nick evans Date: Fri, 1 May 2026 14:41:33 -0400 Subject: [PATCH 1/2] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Allow=20RawData.new=20?= =?UTF-8?q?to=20directly=20set=20parts=20array?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It's convenient that `RawData` can take a string, even though it's composed of parts. But it's surprising, IMO, to _require_ the `data` parameter be a string, when the `data` member is an array. --- lib/net/imap/command_data.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/net/imap/command_data.rb b/lib/net/imap/command_data.rb index bd7299de..47dc6db5 100644 --- a/lib/net/imap/command_data.rb +++ b/lib/net/imap/command_data.rb @@ -198,7 +198,12 @@ def send_data(imap, tag) = imap.__send__(:put_string, data) class RawData < CommandData # :nodoc: def initialize(data:) - data = split_parts(data) + case data + in String then data = split_parts(data) + in Array if data.all? { _1 in RawText | Literal } + else + raise TypeError, "expected String or Array[#{RawText} | #{Literal}]" + end super validate end From 257e51d0a55ea75842b60aa87551df2178b0e04e Mon Sep 17 00:00:00 2001 From: nick evans Date: Fri, 1 May 2026 18:07:31 -0400 Subject: [PATCH 2/2] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Extract=20RawData.spli?= =?UTF-8?q?t(string)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/net/imap/command_data.rb | 13 ++++++++----- test/net/imap/test_command_data.rb | 17 +++++++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/lib/net/imap/command_data.rb b/lib/net/imap/command_data.rb index 47dc6db5..7d817430 100644 --- a/lib/net/imap/command_data.rb +++ b/lib/net/imap/command_data.rb @@ -199,7 +199,7 @@ def send_data(imap, tag) = imap.__send__(:put_string, data) class RawData < CommandData # :nodoc: def initialize(data:) case data - in String then data = split_parts(data) + in String then data = self.class.split(data) in Array if data.all? { _1 in RawText | Literal } else raise TypeError, "expected String or Array[#{RawText} | #{Literal}]" @@ -217,9 +217,11 @@ def validate end end - private - - def split_parts(data) + # Splits an input +string+ into an array of RawText and Literal/Literal8. + # + # NOTE: unlike RawData#validate, this does not prevent the final RawText + # from ending with a literal prefix. + def self.split(data) data = data.b # dups and ensures BINARY encoding parts = [] while data.match(/(~)?\{(0|[1-9]\d*)(\+)?\}\r\n/n) @@ -233,7 +235,7 @@ def split_parts(data) parts end - def extract_literal(data, binary:, bytesize:, non_sync:) + def self.extract_literal(data, binary:, bytesize:, non_sync:) if data.bytesize < bytesize raise DataFormatError, "Too few bytes in string for literal, " \ "expected: %s, remaining: %s" % [bytesize, data.bytesize] @@ -241,6 +243,7 @@ def extract_literal(data, binary:, bytesize:, non_sync:) literal = data.byteslice(0, bytesize) (binary ? Literal8 : Literal).new(data: literal, non_sync:) end + private_class_method :extract_literal end class Atom < CommandData # :nodoc: diff --git a/test/net/imap/test_command_data.rb b/test/net/imap/test_command_data.rb index 66cf592b..2946ee1f 100644 --- a/test/net/imap/test_command_data.rb +++ b/test/net/imap/test_command_data.rb @@ -365,6 +365,23 @@ class RawDataTest < CommandDataTest raw = RawData.new(data: " {123} ") assert_equal [RawText[" {123} "]], raw.data end + + data( + "simple raw text" => 'hello "world"', + "text, literal, text" => "OK {5}\r\nhello {5}\r\nworld", + "empty literals" => "{0}\r\n{0+}\r\n~{0}\r\n~{0+}\r\n", + "binary and regular" => "foo ~{7}\r\n\0bar\r\nbaz {4}\r\nquux", + ) + test ".split" do |string| + assert_equal(RawData[string].data, RawData.split(string)) + end + + test ".split allows final literal prefix" do + assert_equal [RawText["text {123}"]], RawData.split("text {123}") + assert_equal [RawText["text+ {123+}"]], RawData.split("text+ {123+}") + assert_equal [RawText["~text ~{123}"]], RawData.split("~text ~{123}") + assert_equal [RawText["~text+ ~{123+}"]], RawData.split("~text+ ~{123+}") + end end end