Skip to content

Commit 37a1344

Browse files
committed
⚡ Simplify header-fld-name parser
This speeds up my (new) fetch tests/benchmarks by ~10% over 0.4.3, and by 15%-30% over earlier versions.
1 parent 9ccc31e commit 37a1344

File tree

3 files changed

+82
-34
lines changed

3 files changed

+82
-34
lines changed

benchmarks/parser.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,16 @@ benchmark:
9696
response = load_response("../test/net/imap/fixtures/response_parser/continuation_requests.yml",
9797
"test_continuation_request_without_response_text")
9898
script: parser.parse(response)
99+
- name: fetch_msg_att_HEADER.FIELDS
100+
prelude: |2
101+
response = load_response("../test/net/imap/fixtures/response_parser/fetch_responses.yml",
102+
"test_fetch_msg_att_HEADER.FIELDS")
103+
script: parser.parse(response)
104+
- name: fetch_msg_att_HEADER.FIELDS.NOT
105+
prelude: |2
106+
response = load_response("../test/net/imap/fixtures/response_parser/fetch_responses.yml",
107+
"test_fetch_msg_att_HEADER.FIELDS.NOT")
108+
script: parser.parse(response)
99109
- name: fetch_msg_att_flags_and_uid
100110
prelude: |2
101111
response = load_response("../test/net/imap/fixtures/response_parser/fetch_responses.yml",
@@ -171,6 +181,11 @@ benchmark:
171181
response = load_response("../test/net/imap/fixtures/response_parser/quirky_behaviors.yml",
172182
"test_invalid_noop_response_with_numeric_prefix")
173183
script: parser.parse(response)
184+
- name: invalid_noop_response_with_numeric_prefix_and_unparseable_data
185+
prelude: |2
186+
response = load_response("../test/net/imap/fixtures/response_parser/quirky_behaviors.yml",
187+
"test_invalid_noop_response_with_numeric_prefix_and_unparseable_data")
188+
script: parser.parse(response)
174189
- name: invalid_noop_response_with_unparseable_data
175190
prelude: |2
176191
response = load_response("../test/net/imap/fixtures/response_parser/quirky_behaviors.yml",

lib/net/imap/response_parser.rb

Lines changed: 19 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,47 +1101,32 @@ def header_list
11011101
end
11021102

11031103
# RFC3501 & RFC9051:
1104-
# header-fld-name = astring
1104+
# header-fld-name = astring
1105+
#
1106+
# NOTE: Previously, Net::IMAP recreated the raw original source string.
1107+
# Now, it grabs the raw encoded value using @str and @pos. A future
1108+
# version may simply return the decoded astring value. Although that is
1109+
# technically incompatible, it should almost never make a difference: all
1110+
# standard header field names are valid atoms:
1111+
#
1112+
# https://www.iana.org/assignments/message-headers/message-headers.xhtml
11051113
#
11061114
# Although RFC3501 allows any astring, RFC5322-valid header names are one
11071115
# or more of the printable US-ASCII characters, except SP and colon. So
11081116
# empty string isn't valid, and literals aren't needed and should not be
1109-
# used. This syntax is unchanged by [I18N-HDRS] (RFC6532).
1117+
# used. This is explicitly unchanged by [I18N-HDRS] (RFC6532).
11101118
#
11111119
# RFC5233:
1112-
# optional-field = field-name ":" unstructured CRLF
1113-
# field-name = 1*ftext
1114-
# ftext = %d33-57 / ; Printable US-ASCII
1115-
# %d59-126 ; characters not including
1116-
# ; ":".
1117-
#
1118-
# Atom and quoted should be sufficient.
1119-
#
1120-
# TODO: Use original source string, rather than decode and re-encode.
1121-
# TODO: or at least, DRY up this code with the send_command formatting.
1120+
# optional-field = field-name ":" unstructured CRLF
1121+
# field-name = 1*ftext
1122+
# ftext = %d33-57 / ; Printable US-ASCII
1123+
# %d59-126 ; characters not including
1124+
# ; ":".
11221125
def header_fld_name
1123-
case (str = astring)
1124-
when ""
1125-
warn '%s header-fld-name is an invalid RFC5322 field-name: ""' %
1126-
[self.class]
1127-
return '""'
1128-
when /[\x80-\xff\r\n]/n
1129-
warn "%s header-fld-name %p has invalid RFC5322 field-name char: %p" %
1130-
[self.class, str, $&]
1131-
# literal
1132-
return "{" + str.bytesize.to_s + "}" + CRLF + str
1133-
when /[^\x21-\x39\x3b-\xfe]/n
1134-
warn "%s header-fld-name %p has invalid RFC5322 field-name char: %p" %
1135-
[self.class, str, $&]
1136-
# invalid quoted string
1137-
return '"' + str.gsub(/["\\]/n, "\\\\\\&") + '"'
1138-
when /[(){ \x00-\x1f\x7f%*"\\]/n
1139-
# quoted string
1140-
return '"' + str.gsub(/["\\]/n, "\\\\\\&") + '"'
1141-
else
1142-
# atom
1143-
return str
1144-
end
1126+
assert_no_lookahead
1127+
start = @pos
1128+
astring
1129+
@str[start...@pos - 1]
11451130
end
11461131

11471132
# mailbox-data = "FLAGS" SP flag-list / "LIST" SP mailbox-list /

test/net/imap/fixtures/response_parser/fetch_responses.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,54 @@
4040
UID: 5
4141
raw_data: "* 1 FETCH (FLAGS (\\Seen $MDNSent \\Flagged Custom) UID 5)\r\n"
4242

43+
test_fetch_msg_att_HEADER.FIELDS:
44+
:response: &test_fetch_msg_att_HEADER_FIELDS
45+
"* 20367 FETCH (BODY[HEADER.FIELDS (List-ID List-Unsubscribe
46+
List-Unsubscribe-Post List-Owner List-Archive)] {291}\r\nList-Unsubscribe:
47+
manage-example-lists
48+
xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
49+
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n
50+
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n
51+
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n
52+
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n\r\n)\r\n"
53+
:expected: !ruby/struct:Net::IMAP::UntaggedResponse
54+
name: FETCH
55+
data: !ruby/struct:Net::IMAP::FetchData
56+
seqno: 20367
57+
attr:
58+
BODY[HEADER.FIELDS (List-ID List-Unsubscribe List-Unsubscribe-Post List-Owner List-Archive)]: "List-Unsubscribe:
59+
manage-example-lists xxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n
60+
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n
61+
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n\r\n"
62+
raw_data: *test_fetch_msg_att_HEADER_FIELDS
63+
64+
test_fetch_msg_att_HEADER.FIELDS.NOT:
65+
:response: &test_fetch_msg_att_HEADER_FIELDS_NOT
66+
"* 20368 FETCH (BODY[HEADER.FIELDS.NOT (Received DKIM-Signature List-Unsubscribe
67+
ARC-Seal ARC-Authentication-Results Authentication-Results ARC-Message-Signature
68+
Received-SPF X-Received Mime-Version Content-Type Content-Transfer-Encoding
69+
X-AUTO-Response-Suppress X-Google-Smtp-Source)]
70+
{307}\r\nDelivered-To: testy.mctester@mail.test\r\nReturn-Path:
71+
<noreply@example.test>\r\nDate: Thu, 02 Nov 2023 10:10:17 -0700\r\nFrom:
72+
Example <support@example.test>\r\nMessage-ID:
73+
<xxxxxxxxxxxxxxxxxxxx@yyyyyyyyy-zzzzzzzz-example.mail>\r\nSubject:
74+
[Example] You've hit 75% of your spending limit for the Tester\r\n
75+
account\r\n\r\n)\r\n"
76+
:expected: !ruby/struct:Net::IMAP::UntaggedResponse
77+
name: FETCH
78+
data: !ruby/struct:Net::IMAP::FetchData
79+
seqno: 20368
80+
attr:
81+
? BODY[HEADER.FIELDS.NOT (Received DKIM-Signature List-Unsubscribe ARC-Seal
82+
ARC-Authentication-Results Authentication-Results ARC-Message-Signature
83+
Received-SPF X-Received Mime-Version Content-Type Content-Transfer-Encoding
84+
X-AUTO-Response-Suppress X-Google-Smtp-Source)]
85+
: "Delivered-To: testy.mctester@mail.test\r\nReturn-Path: <noreply@example.test>\r\nDate:
86+
Thu, 02 Nov 2023 10:10:17 -0700\r\nFrom: Example <support@example.test>\r\nMessage-ID:
87+
<xxxxxxxxxxxxxxxxxxxx@yyyyyyyyy-zzzzzzzz-example.mail>\r\nSubject: [Example]
88+
You've hit 75% of your spending limit for the Tester\r\n account\r\n\r\n"
89+
raw_data: *test_fetch_msg_att_HEADER_FIELDS_NOT
90+
4391
test_invalid_fetch_msg_att_extra_space:
4492
:response: "* 1 FETCH (UID 92285 )\r\n"
4593
:expected: !ruby/struct:Net::IMAP::UntaggedResponse

0 commit comments

Comments
 (0)