Skip to content

Stop emitting non-standard "; charset=binary" suffix - #1383

Closed
silverbucket wants to merge 3 commits into
remotestorage:masterfrom
silverbucket:bugfix/1379-no_charset_binary_suffix
Closed

Stop emitting non-standard "; charset=binary" suffix#1383
silverbucket wants to merge 3 commits into
remotestorage:masterfrom
silverbucket:bugfix/1379-no_charset_binary_suffix

Conversation

@silverbucket

@silverbucket silverbucket commented Apr 27, 2026

Copy link
Copy Markdown
Member

Summary

Closes #1379. Supersedes #1380 (closed because the head branch was renamed to follow the project naming convention; no review comments to lose).

WireClient.put and Dropbox.put append ; charset=binary to the Content-Type header for ArrayBuffer / binary uploads. The string binary is not a registered IANA character set (RFC 6657), and the suffix has no effect on transport — XHR/fetch sends the body bytes verbatim regardless of the charset= parameter.

The suffix is sticky: servers (5apps, Armadietto, anyone storing the request Content-Type verbatim) write it into stored metadata and echo it back on GET. Some browsers — Chrome among them — refuse to render an <img> whose Blob type carries the suffix, producing a valid-looking blob: URL that never paints. Apps that pass getFile()'s contentType straight to a Blob constructor hit this.

Approach

Two commits, two halves of the fix:

1. Stop emitting the suffix

File Change
src/wireclient.ts Drop the suffix-append in put() (and the now-orphaned isArrayBufferView import).
src/dropbox.ts Drop the matching suffix-append in put().
src/util.ts Keep the charset=binary branch in shouldBeTreatedAsBinary with a comment explaining it's there for backward compat with files uploaded by <=2.0.0-beta.9. The existing control-char heuristic handles new uploads.
src/baseclient.ts Update example folder listings in the JSDoc to show clean Content-Type values.
test/unit/wireclient.test.mjs Rename the two PUT charset tests to verify the new behavior (no suffix added; caller-supplied charset preserved).

2. Strip the suffix on the read side too

Stops the suffix from leaking through getFile().contentType for files that already exist on servers with the legacy shape baked into their stored metadata. Without this half, apps would still see broken <img> rendering for old files even after upgrading to the fixed library version.

File Change
src/util.ts Add stripLegacyCharsetBinary helper. Removes only the ; charset=binary parameter — legitimate ones like charset=UTF-8 on text/html are preserved.
src/wireclient.ts, src/dropbox.ts, src/googledrive.ts Apply the strip at the response boundary in each remote's GET handler. Internal binary-vs-text detection still uses the original (pre-strip) header so shouldBeTreatedAsBinary keeps firing correctly.
test/unit/wireclient.test.mjs Update the existing assertion to expect the cleaned contentType; add a regression test that legitimate charsets are not stripped.
test/unit/node-wireclient-suite.js Same assertion update for the legacy Jaribu suite.

GET-side mock-server responses in tests still send the suffix (tolerance input) — the assertions verify the library sanitizes it before returning to the caller.

Backward compatibility

  • Existing files on servers: still served with ; charset=binary by the storage backend, but the library now strips it before exposing contentType to callers.
  • Apps reading getFile().contentType: previously got ; charset=binary for legacy files; now get clean mime. If anyone wrote code that expects the suffix, that breaks — but the library already exposes body as ArrayBuffer vs string for the binary distinction, so the suffix as a signal would be redundant. Risk is low.
  • shouldBeTreatedAsBinary: still public API, still recognizes the suffix when called with the raw header value. Only what's returned from get() is sanitized.
  • Cachinglayer suite tests: put items locally with same/type; charset=binary and read them back. These exercise the local cache, not the WireClient response path — unaffected.

WireClient.put and Dropbox.put appended "; charset=binary" to the
Content-Type header for ArrayBuffer / binary uploads. The string "binary"
is not a registered IANA character set (RFC 6657), and the suffix has no
effect on transport — XHR/fetch sends the body bytes verbatim regardless.

The suffix is, however, sticky: servers (5apps, Armadietto, anyone storing
the request Content-Type verbatim) write it into stored metadata and echo
it back on GET. Some browsers, including Chrome, refuse to render an
<img> whose Blob type carries the "; charset=binary" suffix, producing a
valid-looking blob: URL that never paints. Apps that pass getFile()'s
contentType straight to a Blob constructor hit this.

Fixes the emit side; leaves the read-side detection alone:

  - WireClient.put: drop the suffix-append (and its now-orphaned
    isArrayBufferView import).
  - Dropbox.put: same removal.
  - shouldBeTreatedAsBinary: keep the charset=binary check with a
    backward-compat note. Files uploaded with releases <=2.0.0-beta.9
    still carry the suffix in their stored Content-Type and need to be
    detected as binary on read; the existing control-char heuristic
    handles new uploads.
  - BaseClient docstring: update example folder listings to show clean
    Content-Type values (image/png, not image/png;charset=binary).
  - Tests: rename the two PUT charset assertions to verify the new
    behavior (no suffix added; caller-supplied charset preserved).

Closes remotestorage#1379
…tType

Stops the suffix from leaking through getFile().contentType to consumers
when the server still has it baked into legacy file metadata.

Files uploaded with releases <=2.0.0-beta.9 carry "; charset=binary" in
their stored Content-Type forever — servers (5apps, Armadietto) write the
request header verbatim and echo it back on GET. Apps that pass that
contentType straight to a Blob constructor hit the same Chrome <img>
rendering issue described in remotestorage#1379, even after upgrading to a fixed
library version, because the data on disk is the legacy shape.

Sanitize on read so callers see clean Content-Type values regardless of
when the file was written. Internal binary-vs-text detection still uses
the original (pre-strip) header, so shouldBeTreatedAsBinary keeps firing
correctly on legacy responses.

  - util.ts: add stripLegacyCharsetBinary helper. Removes only the
    `; charset=binary` parameter — legitimate ones like `charset=UTF-8`
    on `text/html` are preserved.
  - wireclient.ts, dropbox.ts, googledrive.ts: apply the strip at the
    response boundary in each remote's GET handler.
  - tests: update the existing wireclient/Jaribu assertions to expect
    the cleaned contentType, add a regression test that legitimate
    charsets are not stripped.

Refs remotestorage#1379
@silverbucket silverbucket changed the title fix(wireclient): stop emitting non-standard "; charset=binary" suffix Stop emitting non-standard "; charset=binary" suffix Apr 27, 2026
@silverbucket silverbucket self-assigned this Apr 27, 2026
@raucao

raucao commented Jun 2, 2026

Copy link
Copy Markdown
Member

@silverbucket Could you reopen the PR from this repo, please? I have added the same fix for cached documents to CachingLayer#get(), but I'm unable to push to your fork.

@silverbucket

Copy link
Copy Markdown
Member Author

@raucao #1390

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Development

Successfully merging this pull request may close these issues.

WireClient appends non-standard ; charset=binary to binary uploads, breaks Chrome <img> rendering

2 participants