From d4034e4947e94dce9f522e2f05a6ecb999c243d2 Mon Sep 17 00:00:00 2001 From: Oskar Eichler Date: Fri, 28 Aug 2026 00:25:46 +0300 Subject: [PATCH 1/2] Scan raw command literals without copying the remaining input --- lib/net/imap/command_data.rb | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/net/imap/command_data.rb b/lib/net/imap/command_data.rb index 1fd0dc30..d98ac2d2 100644 --- a/lib/net/imap/command_data.rb +++ b/lib/net/imap/command_data.rb @@ -265,23 +265,27 @@ def validate def self.split(data) data = data.b # dups and ensures BINARY encoding parts = [] - while data.match(/(~)?\{(0|[1-9]\d*)(\+)?\}\r\n/n) - text, binary, bytesize, non_sync, data = $`, !!$1, $2, !!$3, $' + offset = 0 + while (match = /(~)?\{(0|[1-9]\d*)(\+)?\}\r\n/n.match(data, offset)) + text = data.byteslice(offset...match.begin(0)) + binary, bytesize, non_sync = !!match[1], match[2], !!match[3] bytesize = NumValidator.coerce_number64 bytesize parts << RawText[text] unless text.empty? - parts << extract_literal(data, binary:, bytesize:, non_sync:) - data.bytesplice(0, bytesize, "") + offset = match.end(0) + parts << extract_literal(data, offset:, binary:, bytesize:, non_sync:) + offset += bytesize end - parts << RawText[data] unless data.empty? + parts << RawText[data.byteslice(offset..)] if offset < data.bytesize parts end - def self.extract_literal(data, binary:, bytesize:, non_sync:) - if data.bytesize < bytesize + def self.extract_literal(data, offset:, binary:, bytesize:, non_sync:) + remaining = data.bytesize - offset + if remaining < bytesize raise DataFormatError, "Too few bytes in string for literal, " \ - "expected: %s, remaining: %s" % [bytesize, data.bytesize] + "expected: %s, remaining: %s" % [bytesize, remaining] end - literal = data.byteslice(0, bytesize) + literal = data.byteslice(offset, bytesize) (binary ? Literal8 : Literal).new(data: literal, non_sync:) end private_class_method :extract_literal From e7e91f9941b631f24ba23c4149a42aeb34d182fd Mon Sep 17 00:00:00 2001 From: nick evans Date: Mon, 31 Aug 2026 10:02:22 -0400 Subject: [PATCH 2/2] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Style=20tweaks=20to=20?= =?UTF-8?q?RawDate.split?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is an addendum to #745. This keeps it more in the original style, without sacrificing performance, as far as I can tell. While many style guides (and people) may avoid the regexp globals, this code base uses them more often than not. And their function here should be obvious from the local variable names. I find this version _(subjectively)_ easier to read. Also, using two offsets rather (`text_start` and `literal_start`) and assigning all of the regexp vars at the start of the loop is arguably simpler. And anyway, both versions are `O(n)`, and optimizing `RawData` beyond that isn't very important. So, while it's nice for this code to be faster, keeping the structure easier to understand is higher priority. --- lib/net/imap/command_data.rb | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/net/imap/command_data.rb b/lib/net/imap/command_data.rb index 59026ca4..630ffc58 100644 --- a/lib/net/imap/command_data.rb +++ b/lib/net/imap/command_data.rb @@ -265,21 +265,20 @@ def validate def self.split(data) data = data.b # dups and ensures BINARY encoding parts = [] - offset = 0 - while (match = /(~)?\{(0|[1-9]\d*)(\+)?\}\r\n/n.match(data, offset)) - text = data.byteslice(offset...match.begin(0)) - binary, bytesize, non_sync = !!match[1], match[2], !!match[3] + text_start = 0 + while data.match(/(~)?\{(0|[1-9]\d*)(\+)?\}\r\n/n, text_start) + text, binary, bytesize, non_sync, literal_start = + data.byteslice(text_start...$~.begin(0)), !!$1, $2, !!$3, $~.end(0) bytesize = NumValidator.coerce_number64 bytesize + text_start = literal_start + bytesize parts << RawText[text] unless text.empty? - offset = match.end(0) - parts << extract_literal(data, offset:, binary:, bytesize:, non_sync:) - offset += bytesize + parts << extract_literal(data, literal_start, bytesize, binary:, non_sync:) end - parts << RawText[data.byteslice(offset..)] if offset < data.bytesize + parts << RawText[data.byteslice(text_start..)] if text_start < data.bytesize parts end - def self.extract_literal(data, offset:, binary:, bytesize:, non_sync:) + def self.extract_literal(data, offset, bytesize, binary:, non_sync:) remaining = data.bytesize - offset if remaining < bytesize raise DataFormatError, "Too few bytes in string for literal, " \