From e55d73b170eb4a5523a411cdc9bd5cf13121694e Mon Sep 17 00:00:00 2001 From: nick evans Date: Wed, 22 Apr 2026 11:25:06 -0400 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=8D=92=20edit=20ca72ac45:=20=E2=99=BB?= =?UTF-8?q?=EF=B8=8F=20Extract=20superclass=20for=20(internal)=20command?= =?UTF-8?q?=20data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unlike the cherry-picked commit (ca72ac45), this _only_ makes `CommandData` the superclass for `Literal` and `Atom`. Because those are the classes that will be modified by later cherry-picked commits. This allows those other commits to merge more cleanly, and work with fewer modifications. --- lib/net/imap/command_data.rb | 50 +++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/lib/net/imap/command_data.rb b/lib/net/imap/command_data.rb index 180a254..91c7336 100644 --- a/lib/net/imap/command_data.rb +++ b/lib/net/imap/command_data.rb @@ -119,33 +119,44 @@ module Net put_string("\\" + symbol.to_s) end - class RawData # :nodoc: - def send_data(imap, tag) - imap.__send__(:put_string, @data) + # simplistic emulation of CommandData = Data.define(:data) + class CommandData # :nodoc: + class << self + def new(arg = nil, data: arg) super(data: data) end + alias :[] :new end - def validate + def initialize(data:) + @data = data + freeze end - private + attr_reader :data - def initialize(data) - @data = data - end - end + def to_h(&block) block ? to_h.to_h(&block) : { data: data } end + def ==(other) self.class === other && to_h == other.to_h end + def eql?(other) self.class === other && to_h.eql?(other.to_h) end + + # following class definition goes beyond the basic Data.define(:data) + ## - class Atom # :nodoc: def send_data(imap, tag) - imap.__send__(:put_string, @data) + raise NoMethodError, "#{self.class} must implement #{__method__}" end def validate end + end - private + class RawData < CommandData # :nodoc: + def send_data(imap, tag) + imap.__send__(:put_string, @data) + end + end - def initialize(data) - @data = data + class Atom < CommandData # :nodoc: + def send_data(imap, tag) + imap.__send__(:put_string, @data) end end @@ -164,19 +175,10 @@ module Net end end - class Literal # :nodoc: + class Literal < CommandData # :nodoc: def send_data(imap, tag) imap.__send__(:send_literal, @data, tag) end - - def validate - end - - private - - def initialize(data) - @data = data - end end class MessageSet # :nodoc: From d1c362631589b20e59f5e64fe1ee9222b3ea07f9 Mon Sep 17 00:00:00 2001 From: nick evans Date: Thu, 19 Feb 2026 15:06:08 -0500 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=8D=92=20pick=209db3e9d60:=20?= =?UTF-8?q?=F0=9F=A5=85=20Strictly=20validate=20symbol=20(\flag)=20argumen?= =?UTF-8?q?ts=20[backports=20#657]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flags should not allow `atom-specials`. Previously, no validation was done on symbol data. Sending atom or flag args which contain atom specials could lead to various errors. Although this could theoretically include injection attacks, this is not considered to be a critical vulnerability in `net-imap`, for the following reason: Valid "system flag" inputs are restricted to an enumerated set of RFC-defined flag types. User-defined "keyword" flags are sent as atoms, not flags, which use string inputs (strings which can't be sent as an atom will be quoted or sent as a literal). `\Seen` as a flag (symbol argument) is semantically different from `Seen` as a keyword (string argument). So there is no scenario where it is appropriate to call `#to_sym` on unvetted user input. Any code which calls `#to_sym` indiscriminately on user-input is already buggy. Nevertheless, users should reasonably be able to rely on `net-imap` to do very basic input validation on its basic input types. --- lib/net/imap/command_data.rb | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/lib/net/imap/command_data.rb b/lib/net/imap/command_data.rb index 91c7336..499002f 100644 --- a/lib/net/imap/command_data.rb +++ b/lib/net/imap/command_data.rb @@ -25,6 +25,7 @@ module Net end when Time, Date, DateTime when Symbol + Flag.validate(data) else data.validate end @@ -45,7 +46,7 @@ module Net when Date send_date_data(data) when Symbol - send_symbol_data(data) + Flag[data].send_data(self, tag) else data.send_data(self, tag) end @@ -115,10 +116,6 @@ module Net def send_date_data(date) put_string Net::IMAP.encode_date(date) end def send_time_data(time) put_string Net::IMAP.encode_time(time) end - def send_symbol_data(symbol) - put_string("\\" + symbol.to_s) - end - # simplistic emulation of CommandData = Data.define(:data) class CommandData # :nodoc: class << self @@ -140,6 +137,12 @@ module Net # following class definition goes beyond the basic Data.define(:data) ## + def self.validate(...) + data = new(...) + data.validate + data + end + def send_data(imap, tag) raise NoMethodError, "#{self.class} must implement #{__method__}" end @@ -155,8 +158,26 @@ module Net end class Atom < CommandData # :nodoc: + def initialize(**) + super + validate + end + + def validate + data.to_s.ascii_only? \ + or raise DataFormatError, "#{self.class} must be ASCII only" + data.match?(ResponseParser::Patterns::ATOM_SPECIALS) \ + and raise DataFormatError, "#{self.class} must not contain atom-specials" + end + def send_data(imap, tag) - imap.__send__(:put_string, @data) + imap.__send__(:put_string, data.to_s) + end + end + + class Flag < Atom # :nodoc: + def send_data(imap, tag) + imap.__send__(:put_string, "\\#{data}") end end