SUMMARY
In the Nylas Java SDK, Attachments.download() and Attachments.find()
percent-encode the attachment ID twice, producing an invalid path.
For Gmail attachments - whose IDs contain reserved characters (':' and
'=', e.g. "v0:::") - the download request returns
HTTP 404 "not_found_error" even though the ID is valid and the message
is readable.
AFFECTED VERSIONS
2.16.0, 2.16.1, and 2.17.0 (latest).
2.15.0 and earlier are NOT affected.
ROOT CAUSE
The ID is encoded once by PathEncoder, then encoded again by OkHttp.
// resources/Attachments.kt
val path = String.format("v3/grants/%s/attachments/%s/download",
identifier, PathEncoder.encode(attachmentId)) // ':' -> '%3A'
// util/PathEncoder.kt
fun encode(segment: String) =
URLEncoder.encode(segment, "UTF-8").replace("+", "%20")
// NylasClient.kt -> buildUrl()
newUrlBuilder().addPathSegments(path) // re-encodes '%' -> '%25', so '%3A' becomes '%253A'
addPathSegments() treats its input as DECODED and percent-encodes it
again, so the '%' produced by PathEncoder becomes '%25'. The introduction
of PathEncoder (commit fd75345, "fix(threads): URL-encode thread id in
path") is incompatible with buildUrl() already encoding via
addPathSegments().
REPRODUCTION
Attachment ID (valid, returned by GET /v3/grants/{grant}/messages/{id}):
v0:UHJvZ3JhbW1lIHPDqW1pbmFpcmUucGRm:YXBwbGljYXRpb24vcGRmOyBuYW1lPSJQcm9ncmFtbWUgc8OpbWluYWlyZS5wZGYi:15204
nylasClient.attachments().download(
grantId, attachmentId,
new FindAttachmentQueryParams.Builder(messageId).build());
Request actually sent on the wire (path segment, as recorded in the
Nylas API logs):
/v3/grants/{grant}/attachments/v0%253AUHJvZ3JhbW1l...%253A...%253A15204/download?message_id=...
Note "%253A" (= "%25" + "3A" = double-encoded ':'). Your API decodes it
once to "v0%3A...", which no longer matches the real ID "v0:...", so it
returns:
{ "message": "requested attachment not found, please check if message id
and attachment id are correct", "type": "not_found_error" }
The exact same attachment downloads successfully with SDK 2.5.2 (no
PathEncoder; the raw ':' is left untouched by OkHttp inside a path segment).
EVIDENCE (our account, EU region)
Grant ID: cda457f6-b4d4-4fcd-a09c-cc5d7fa23074
Message ID: 19e3f7f50c95af7b
Request ID: 1628332985-cddec274-85b5-45c6-b1a4-c9464b3070e0
User-Agent: Nylas Java SDK 2.16.1
IMPACT
All attachment downloads whose ID contains characters PathEncoder
escapes - i.e. effectively ALL Gmail attachments (their "v0:" IDs
contain ':' and '='). Likely also affects other resources that route IDs
through PathEncoder (threads, drafts, webhooks) when those IDs contain
reserved characters.
SUGGESTED FIX
Encode the path segment ONCE, not twice. Either:
- have buildUrl() use addEncodedPathSegments() (the segments are already
encoded by PathEncoder), or
- remove PathEncoder.encode(...) from the resource methods and rely
solely on OkHttp's addPathSegments() to encode.
SUMMARY
In the Nylas Java SDK, Attachments.download() and Attachments.find()
percent-encode the attachment ID twice, producing an invalid path.
For Gmail attachments - whose IDs contain reserved characters (':' and
'=', e.g. "v0:::") - the download request returns
HTTP 404 "not_found_error" even though the ID is valid and the message
is readable.
AFFECTED VERSIONS
2.16.0, 2.16.1, and 2.17.0 (latest).
2.15.0 and earlier are NOT affected.
ROOT CAUSE
The ID is encoded once by PathEncoder, then encoded again by OkHttp.
// resources/Attachments.kt
val path = String.format("v3/grants/%s/attachments/%s/download",
identifier, PathEncoder.encode(attachmentId)) // ':' -> '%3A'
// util/PathEncoder.kt
fun encode(segment: String) =
URLEncoder.encode(segment, "UTF-8").replace("+", "%20")
// NylasClient.kt -> buildUrl()
newUrlBuilder().addPathSegments(path) // re-encodes '%' -> '%25', so '%3A' becomes '%253A'
addPathSegments() treats its input as DECODED and percent-encodes it
again, so the '%' produced by PathEncoder becomes '%25'. The introduction
of PathEncoder (commit fd75345, "fix(threads): URL-encode thread id in
path") is incompatible with buildUrl() already encoding via
addPathSegments().
REPRODUCTION
Attachment ID (valid, returned by GET /v3/grants/{grant}/messages/{id}):
v0:UHJvZ3JhbW1lIHPDqW1pbmFpcmUucGRm:YXBwbGljYXRpb24vcGRmOyBuYW1lPSJQcm9ncmFtbWUgc8OpbWluYWlyZS5wZGYi:15204
nylasClient.attachments().download(
grantId, attachmentId,
new FindAttachmentQueryParams.Builder(messageId).build());
Request actually sent on the wire (path segment, as recorded in the
Nylas API logs):
/v3/grants/{grant}/attachments/v0%253AUHJvZ3JhbW1l...%253A...%253A15204/download?message_id=...
Note "%253A" (= "%25" + "3A" = double-encoded ':'). Your API decodes it
once to "v0%3A...", which no longer matches the real ID "v0:...", so it
returns:
{ "message": "requested attachment not found, please check if message id
and attachment id are correct", "type": "not_found_error" }
The exact same attachment downloads successfully with SDK 2.5.2 (no
PathEncoder; the raw ':' is left untouched by OkHttp inside a path segment).
EVIDENCE (our account, EU region)
Grant ID: cda457f6-b4d4-4fcd-a09c-cc5d7fa23074
Message ID: 19e3f7f50c95af7b
Request ID: 1628332985-cddec274-85b5-45c6-b1a4-c9464b3070e0
User-Agent: Nylas Java SDK 2.16.1
IMPACT
All attachment downloads whose ID contains characters PathEncoder
escapes - i.e. effectively ALL Gmail attachments (their "v0:" IDs
contain ':' and '='). Likely also affects other resources that route IDs
through PathEncoder (threads, drafts, webhooks) when those IDs contain
reserved characters.
SUGGESTED FIX
Encode the path segment ONCE, not twice. Either:
encoded by PathEncoder), or
solely on OkHttp's addPathSegments() to encode.