Skip to content

Commit e86c340

Browse files
committed
🩹 Workaround servers that don't send req'd SP
In particular, iCloud doesn't send a `SP` after `* BYE`. Although this is technically a violation of the RFCs, RFC9051 did make `text` optional. So, in the spirit of RFC9051 Appx E 23, we don't require a final SP when the `resp-text` is empty. * See also https://www.rfc-editor.org/errata/eid7343 * Fixes #229
1 parent d7cdfeb commit e86c340

File tree

2 files changed

+60
-14
lines changed

2 files changed

+60
-14
lines changed

lib/net/imap/response_parser.rb

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -777,34 +777,47 @@ def response_data__ignored; response_data__unhandled(IgnoredResponse) end
777777

778778
# RFC3501 & RFC9051:
779779
# response-tagged = tag SP resp-cond-state CRLF
780-
#
781-
# resp-cond-state = ("OK" / "NO" / "BAD") SP resp-text
782-
# ; Status condition
783-
#
784-
# tag = 1*<any ASTRING-CHAR except "+">
785780
def response_tagged
786-
tag = tag(); SP!
787-
name = resp_cond_state__name; SP!
788-
TaggedResponse.new(tag, name, resp_text, @str)
781+
TaggedResponse.new(tag, *(SP!; resp_cond_state), @str)
789782
end
790783

791784
# RFC3501 & RFC9051:
792785
# resp-cond-state = ("OK" / "NO" / "BAD") SP resp-text
786+
#
787+
# NOTE: In the spirit of RFC9051 Appx E 23 (and to workaround existing
788+
# servers), we don't require a final SP and instead parse this as:
789+
#
790+
# resp-cond-state = ("OK" / "NO" / "BAD") [SP resp-text]
791+
def resp_cond_state
792+
[resp_cond_state__name, SP? ? resp_text : ResponseText::EMPTY]
793+
end
794+
793795
def resp_cond_state__untagged
794-
name = resp_cond_state__name; SP!
795-
UntaggedResponse.new(name, resp_text, @str)
796+
UntaggedResponse.new(*resp_cond_state, @str)
796797
end
797798

798799
# resp-cond-auth = ("OK" / "PREAUTH") SP resp-text
800+
#
801+
# NOTE: In the spirit of RFC9051 Appx E 23 (and to workaround existing
802+
# servers), we don't require a final SP and instead parse this as:
803+
#
804+
# resp-cond-auth = ("OK" / "PREAUTH") [SP resp-text]
799805
def resp_cond_auth
800-
name = resp_cond_auth__name; SP!
801-
UntaggedResponse.new(name, resp_text, @str)
806+
UntaggedResponse.new(resp_cond_auth__name,
807+
SP? ? resp_text : ResponseText::EMPTY,
808+
@str)
802809
end
803810

804811
# resp-cond-bye = "BYE" SP resp-text
812+
#
813+
# NOTE: In the spirit of RFC9051 Appx E 23 (and to workaround existing
814+
# servers), we don't require a final SP and instead parse this as:
815+
#
816+
# resp-cond-bye = "BYE" [SP resp-text]
805817
def resp_cond_bye
806-
name = label(BYE); SP!
807-
UntaggedResponse.new(name, resp_text, @str)
818+
UntaggedResponse.new(label(BYE),
819+
SP? ? resp_text : ResponseText::EMPTY,
820+
@str)
808821
end
809822

810823
# message-data = nz-number SP ("EXPUNGE" / ("FETCH" SP msg-att))

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,36 @@
117117
code:
118118
text: "Autologout; idle for too long"
119119
raw_data: "* BYE Autologout; idle for too long\r\n"
120+
121+
response-tagged_without_SP_resp-text:
122+
:response: "tag0001 OK\r\n"
123+
:expected: !ruby/struct:Net::IMAP::TaggedResponse
124+
tag: tag0001
125+
name: OK
126+
data: !ruby/struct:Net::IMAP::ResponseText
127+
text: ''
128+
raw_data: "tag0001 OK\r\n"
129+
130+
resp-cond-state_without_SP_resp-text:
131+
:response: "* BAD\r\n"
132+
:expected: !ruby/struct:Net::IMAP::UntaggedResponse
133+
name: BAD
134+
data: !ruby/struct:Net::IMAP::ResponseText
135+
text: ''
136+
raw_data: "* BAD\r\n"
137+
138+
resp-cond-auth_without_SP_resp-text:
139+
:response: "* PREAUTH\r\n"
140+
:expected: !ruby/struct:Net::IMAP::UntaggedResponse
141+
name: PREAUTH
142+
data: !ruby/struct:Net::IMAP::ResponseText
143+
text: ''
144+
raw_data: "* PREAUTH\r\n"
145+
146+
resp-cond-bye_without_SP_resp-text:
147+
:response: "* BYE\r\n"
148+
:expected: !ruby/struct:Net::IMAP::UntaggedResponse
149+
name: BYE
150+
data: !ruby/struct:Net::IMAP::ResponseText
151+
text: ''
152+
raw_data: "* BYE\r\n"

0 commit comments

Comments
 (0)