Quote SEARCH string args, fix LARGER/SMALLER, UTF-8 SEARCH - #117
Quote SEARCH string args, fix LARGER/SMALLER, UTF-8 SEARCH#117mpscholten wants to merge 3 commits into
Conversation
Adds UIDPLUS extension support so callers can recover the UIDs the server assigns on APPEND/COPY and target expunges by UID: - appendFullUID: like appendFull, returns the APPENDUID response code - copyUID / copyUIDs / copyUIDR: UID COPY returning the COPYUID code - uidExpunge / uidExpungeR: UID EXPUNGE over a UID set or range New types AppendUID and CopyUID, the UIDSet alias, and the APPENDUID/COPYUID/UIDNOTSTICKY status codes with their parsers. sendCommandWithResponse exposes the tagged ServerResponse so the response codes can be read. The existing appendFull/copyFull keep their old signatures by discarding the UID result. Covered by new parser cases in baseTest and a dedicated imapUIDPlusTest group exercising the API against scripted server responses.
RFC 3501 keywords, response codes, flag names and status attributes are case-insensitive, but the parser matched them with case-sensitive `string`, so any server replying with non-canonical casing (e.g. `ok`, `* search`, `[uidvalidity ...]`, `\seen`) caused a parse error. Adds `stringCI`/`charCI` and applies them throughout the response parser. Along the way the tagged/fatal response handling is factored into `pWithTaggedOrFatal`, `pDone` is split into `pRespCode`/`pRespText`/`pStatusCode`, and string parsing is unified (`pQuotedString`/`pLiteralString`/`pAString`/`pMailboxName`). This also: - surfaces an untagged `* BYE` as a fatal response instead of failing - accepts an empty `* SEARCH` reply (no matches) - accepts a `NIL` hierarchy separator in LIST/LSUB - stops `atomChar` from running past CR/LF Covered by a new caseInsensitiveTest group.
IMAP SEARCH string keys (BCC/BODY/CC/FROM/HEADER/SUBJECT/TEXT/TO/
X-GM-RAW) were interpolated unquoted, so any value containing a space
or special character produced a malformed command. They are now wrapped
with quoteIMAPString.
LARGER/SMALLER wrongly wrapped their octet count in `{}` (the literal
syntax), e.g. `LARGER {100}`; the size is a plain number (`LARGER 100`).
SEARCH now auto-detects non-ASCII text in a query and prepends
`CHARSET UTF-8`, and command bytes are sent UTF-8 encoded
(sendCommandNoResponse) so the non-ASCII bytes survive instead of being
truncated to Latin-1.
Covered by a new imapSearchApiTest group.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f1a8776336
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| pAString :: Parser RespDerivs String | ||
| pAString = pQuotedString <|> pLiteralString <|> many1 atomChar |
There was a problem hiding this comment.
Accept all valid IMAP astring characters in mailbox atoms
When a LIST, LSUB, or STATUS response contains an unquoted mailbox such as foo]bar, pAString delegates to atomChar, which explicitly rejects ] and backslash. These are valid in IMAP astrings/atoms, and the previous mailbox parser accepted them. The LIST parser consequently falls back to pOtherLine and silently omits the mailbox, so use a dedicated astring-character parser rather than the stricter flag/FETCH atom parser.
Useful? React with 👍 / 👎.
| pAtomValue = do v <- many1 atomChar | ||
| return $ if map toUpper v == "NIL" then "" else v |
There was a problem hiding this comment.
Preserve top-level NIL values in FETCH results
When callers of the exported pFetch parser receive a top-level NIL value, such as BODY[1] NIL, this branch changes it to an empty string. That regresses the previous result, disagrees with the streaming FETCH parser, and conflates NIL with a zero-byte literal, preventing callers from distinguishing absent data from present-but-empty data; preserve the NIL token.
Useful? React with 👍 / 👎.
| <|> (do v <- pQuotedString | ||
| return ("\""++v++"\"")) |
There was a problem hiding this comment.
Re-escape decoded quoted FETCH values
When pFetch parses a valid quoted value containing an escaped backslash, pQuotedString removes the escape and this branch merely surrounds the decoded text with quotes. The returned serialized value therefore loses a backslash and may no longer be valid IMAP syntax, whereas this API otherwise preserves quotes and parentheses and the previous parser preserved escaped backslashes; either retain the original token or re-escape the decoded contents before adding quotes.
Useful? React with 👍 / 👎.
Stacked on #116 → #115. Part of the series bringing the belege.ai fork's IMAP fixes upstream. The diff shows the UIDPLUS (#115) and case-insensitive (#116) commits too until those merge; the commit that belongs to this PR is "Quote SEARCH string args, fix LARGER/SMALLER, add UTF-8 SEARCH". Merge order: #115 → #116 → this.
What
Three SEARCH-command correctness fixes:
BCC/BODY/CC/FROM/HEADER/SUBJECT/TEXT/TO/X-GM-RAWwere interpolated unquoted, so any value with a space or special character produced a malformed command (e.g.SUBJECT hello world→ server sees two tokens). They're now wrapped withquoteIMAPString.{}— the literal syntax — emittingLARGER {100}. The argument is a plain number:LARGER 100.CHARSET UTF-8(RFC 3501 §6.4.4) and the command is sent UTF-8 encoded (sendCommandNoResponse), instead of being truncated to Latin-1 byBS.pack.Tests
New
imapSearchApiTest: quoting of string args, brace-free LARGER/SMALLER, charset omitted for ASCII,CHARSET UTF-8added for non-ASCII.🤖 Generated with Claude Code