fix(mime): fall back gracefully on malformed base64 instead of aborting the parse
ober
3148912301087c3644f02db97de6d2943325ca2a
--- a/jerboa-mail/encoding.ss +++ b/jerboa-mail/encoding.ss @@ -68,7 +68,15 @@ "base64 payload" s mail-max-transfer-encoded-chars) - (u8vector->utf8-string (base64-string->u8vector (strip-ascii-whitespace s)))) + (let ([stripped (strip-ascii-whitespace s)]) + ;; base64-string->u8vector raises on malformed input (an invalid + ;; character or a length that is 1 mod 4). A hostile mail must not abort + ;; the whole parse -- which would also discard every sibling part of a + ;; multipart message -- so fall back to the undecoded payload, mirroring + ;; the lossy fallbacks already used for quoted-printable and UTF-8, + ;; instead of propagating the error. + (guard (_ [#t stripped]) + (u8vector->utf8-string (base64-string->u8vector stripped))))) (define (hex-value ch) (cond --- a/test/test-all.ss +++ b/test/test-all.ss @@ -89,6 +89,34 @@ (check "base64 invalid UTF-8 returns a string" (string? (mail-base64-decode-string "w0E="))) +;; Malformed base64 (bad char / bad length) must fall back to the undecoded +;; payload instead of raising, mirroring the QP and UTF-8 lossy fallbacks. +(check "base64 malformed chars fall back instead of raising" + (string? (mail-base64-decode-string "!!!!not-base64!!!!"))) + +(check "base64 malformed length falls back instead of raising" + (string? (mail-base64-decode-string "A"))) + +;; A malformed base64 body must not abort the whole parse: the message still +;; parses and the undecoded payload is retained as the body. +(let* ([raw "Content-Type: text/plain\r\nContent-Transfer-Encoding: base64\r\n\r\n@@@@"] + [msg (mail-parse-message raw)]) + (check "parse survives malformed base64 body" + (string=? "@@@@" (mail-best-text-body msg)))) + +;; A single malformed base64 part must not discard its sibling parts. +(let* ([raw (string-append + "Content-Type: multipart/mixed; boundary=\"b\"\r\n\r\n" + "--b\r\nContent-Type: text/plain\r\n\r\ngood part\r\n" + "--b\r\nContent-Type: text/plain\r\nContent-Transfer-Encoding: base64\r\n\r\n@@@@\r\n" + "--b--\r\n")] + [msg (mail-parse-message raw)]) + (check "multipart keeps all parts despite one malformed base64 part" + (= 2 (length (mail-message-parts msg)))) + (check "multipart good sibling part still readable" + (string=? "good part" + (mail-message-body (car (mail-message-parts msg)))))) + (check "encoded-word drops whitespace between adjacent words" (string=? "HelloWorld" (mail-decode-encoded-words "=?UTF-8?Q?Hello?= =?UTF-8?Q?World?=")))