super-productivity/src
Parman Mohammadalizadeh 7fba6f55e2
fix(tasks): decode multipart and transfer-encoded eml bodies #8975 (#8999)
* fix(tasks): decode multipart and transfer-encoded eml bodies #8975

Dropping a real-world .eml (e.g. saved from Outlook) onto the add-task
button created a title-only task: the parser only accepted a single,
unencoded text/plain body, but most clients send multipart/alternative
(plain + HTML) with quoted-printable or base64 transfer encoding on the
plain part.

eml-parser.ts now walks multipart/* structures (bounded recursion depth)
for the first supported text/plain leaf, and decodes quoted-printable/
base64 transfer encodings on it. HTML bodies and non-UTF-8/ASCII
charsets are still never decoded, matching the existing threat model
for this untrusted, inert note content.

* fix(tasks): keep unsupported-encoding eml test accurate after decode support

The service-level test for "unsupported body encoding" used a base64
fixture, which the eml-parser fix now legitimately decodes. Switch it
to a genuinely unsupported transfer-encoding token, and add a
dedicated test asserting base64 bodies decode and wrap in the notes
code fence as expected.

* fix(tasks): skip Content-Disposition: attachment parts in eml import

_extractPlainText() picked the first supported text/plain leaf in wire
order regardless of Content-Disposition, so a text/plain attachment
could be imported as the note when the real body was HTML, or shadow
the real body if it appeared first in the multipart structure. Skip
any part (leaf or multipart container) marked as an attachment before
inspecting it further.

Addresses review feedback from @johannesjo on PR #8999.

* fix(tasks): treat non-inline disposition and legacy name= as attachment

Content-Disposition is optional (legacy mail marks a filename via
Content-Type's name= parameter instead), and RFC 2183 §2.8 requires
any disposition type other than inline — recognized or not — to be
treated as attachment. The previous fix only matched the literal
token "attachment", so a text/plain part identified solely by a
legacy name= parameter, or one with an unrecognized disposition type
(e.g. x-download), still shadowed the real body.

_isAttachmentPart() now treats any present disposition other than
inline as an attachment, and falls back to the Content-Type name=
hint only when Content-Disposition is absent entirely.

Addresses further review feedback from @johannesjo on PR #8999.

* fix(tasks): recognize RFC 2231 name*/name*0 attachment filename hints

The legacy Content-Type name= fallback (used when Content-Disposition
is absent) only matched the literal key "name", missing RFC 2231's
encoded (name*) and continuation (name*0, name*0*, name*1, ...)
spellings of the same parameter. A part identified solely by a
continued name*0/name*1 filename hint slipped through undetected and
could be imported as the note instead of producing a title-only task.

_parseContentType() now matches any RFC 2231 spelling of name via a
presence-only regex; the value is never decoded or reassembled since
only the filename hint's existence matters.

Addresses further review feedback from @johannesjo on PR #8999.

* fix(tasks): recognize RFC 2231 filename*/filename*0 disposition hints

Content-Disposition's hasFilename fallback (used when the disposition
type token fails to parse, e.g. a type-less "; filename=...") only
matched the literal key "filename=", missing RFC 2231's encoded
(filename*) and continuation (filename*0, filename*0*, ...) spellings
of the same parameter -- the mirror of the name*/name*0 gap fixed for
Content-Type.

_parseContentDisposition() now matches any RFC 2231 spelling of
filename via a presence-only regex, symmetric to _NAME_PARAM_KEY_RE.

Addresses further review feedback from @johannesjo on PR #8999.
2026-07-15 09:55:39 +02:00
..
app
assets
environments
static
styles
test-helpers
typings
_common.scss
favicon.ico
hammer-config.class.ts
index.html
karma.conf.js
main.ts
manifest.json
polyfills.ts
styles.scss
test.ts
tsconfig.app.json
tsconfig.spec.json
tsconfig.worker.json