Skip to content

Blob: store explicit user type verbatim; typeless slice() has empty type - #33605

Closed
robobun wants to merge 6 commits into
mainfrom
claude/farm/645f031c/blob-type-no-canonicalize
Closed

robobun wants to merge 6 commits into
mainfrom
claude/farm/645f031c/blob-type-no-canonicalize

Conversation

@robobun

@robobun robobun commented Jul 7, 2026 •

Copy link
Copy Markdown
Collaborator

What

new Blob({type}), new File({type}), blob.slice(_, _, type), Bun.file(path, {type}), Bun.file().write(data, {type}), s3file.writer({type}), and the S3 client.file(path, {type}) paths were canonicalizing an explicit user-supplied type through Bun's MIME table and, as a side effect of how the canonicalized value was stored, a typeless slice() of such a blob inherited the parent's type instead of being empty.

// before:
new Blob([], { type: "text/plain" }).type                      // "text/plain;charset=utf-8"   node/spec: "text/plain"
new Blob(["x"], { type: "text/plain" }).slice(0, 1).type       // "text/plain;charset=utf-8"   node/spec: ""
new Blob(["x"], { type: "text/plain" }).slice(0, 1, "").type   // "text/plain;charset=utf-8"   node/spec: ""
new Blob(["x"], { type: "a/b"        }).slice(0, 1).type       // ""  (correct, because "a/b" is not in the table)

The inheritance was inconsistent: whether blob.slice(0, 1).type was empty depended on whether the parent's type string happened to be in Bun's static MIME table.

Why

When a user's type matched a table entry, vm.mime_type() returned the canonical MimeType (e.g. text/plain → text/plain;charset=utf-8) and the blob stored it as BlobContentType::Static. get_slice_from() then copied the parent's content_type onto a typeless slice specifically when it was Static, so a table-matched parent bled its type into the slice while an unrecognised parent did not.

Fix

  • Every site that accepts an explicit user-supplied type now calls BlobContentType::from_lowercased(slice) directly instead of going through the MIME table, matching the File API spec and Node.
  • get_slice_from() no longer inherits the parent's content_type; a slice's type is exactly the caller-supplied contentType (or "" if omitted/empty/invalid) per the slice algorithm. The size == 0 early return is dropped so new Blob([]).slice(0, 0, type) also honors the caller's type.
  • Consumers of the now-empty slice content_type: headers_ref.rs filters empty so a typeless slice body omits the Content-Type header rather than sending an empty value; RequestContext::get_content_type falls back to the File/S3 store's extension-sniffed mime_type so new Response(Bun.file(p).slice(a, b)) from a dynamic handler keeps the same Content-Type as the unsliced file (no spurious Content-Disposition); S3File::write_format emits the offset: line correctly when the type line is absent.

Bun-inferred defaults are unchanged: Bun.file("x.txt").type is still "text/plain;charset=utf-8" (extension-sniffed), new Response("s") still defaults to text/plain;charset=utf-8, and data-URL handling still canonicalizes.

Rebased onto #33599 (the BlobContentType enum refactor), which reduced the source change to dropping the vm.mime_type() match at eight call sites plus the slice-inheritance block.

Verification

bun bd test test/js/web/fetch/blob.test.ts -t "Blob type"            # 17 pass (8 fail on released bun)
bun bd test test/js/web/fetch/blob.test.ts                            # 45 pass
bun bd test test/js/web/structured-clone-blob-file.test.ts            # 36 pass
bun bd test test/js/bun/util/inspect.test.js                          # 73 pass
bun bd test test/js/bun/http/bun-serve-static.test.ts                 # 41 pass
bun bd test test/js/web/html/FormData-multipart-serialization.test.ts # 6 pass
bun bd test test/js/bun/util/file-type.test.ts test/js/bun/globals.test.js  # 22 pass

Six existing test files that asserted the canonicalized type are updated to expect the verbatim type; in each case the test's actual invariant (clone preserves type, serve is stable across registrations, multipart round-trips, S3 inspect shows the slice's own type) is preserved. One vendored Elysia test that asserts the old behavior is skipped via test/vendor.json.

Fixes #15078

Related: #19603 is the same family but goes through MimeType::init / by_name, which this PR does not touch. Remaining sites that still canonicalize for a #19603 follow-up:

  • Body.rs res.blob() path (MimeType::init copying a response Content-Type onto the returned blob), and its !content_type_was_set && store.is_some() heuristic at line 2173 that stamps text/plain;charset=utf-8 onto .blob() of an untyped in-memory body (now also reachable from new Response(typedBlob.slice()), where it was previously masked by the inherited flag).
  • RequestContext::get_content_type's by_name call, so a dynamic fetch handler returning new Response(new Blob([...], {type: "text/html"})) still serves text/html;charset=utf-8 while the static route and in-process .headers now report text/html verbatim. The dynamic-serve wire behavior is unchanged from before this PR.
  • fetch.rs::data_url_response (MimeType::init on the data URL's media type).

@github-actions github-actions Bot added the claude label Jul 7, 2026
@robobun

robobun commented Jul 7, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 12:37 AM PT - Jul 8th, 2026

❌ @robobun, your commit 2a64bde has 2 failures in Build #70275 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 33605

That installs a local version of the PR into your bun-33605 executable, so you can run:

bun-33605 --bun

@robobun

robobun commented Jul 7, 2026 •

Copy link
Copy Markdown
Collaborator Author

Reproduced with:

bun -e 'console.log(new Blob([], {type:"text/plain"}).type)'                       # text/plain;charset=utf-8
bun -e 'console.log(new Blob(["x"], {type:"text/plain"}).slice(0,1).type)'         # text/plain;charset=utf-8
bun -e 'console.log(new Blob(["x"], {type:"a/b"}).slice(0,1).type)'                # ""  (inconsistent)

All three now match Node. Tests in test/js/web/fetch/blob.test.ts ("Blob type" block) fail on released bun, pass with this change. All review threads resolved; remaining MimeType::init/by_name canonicalization sites are listed in the PR body as #19603 follow-up.

CI: the diff is green. Build #70275 fails only on postgres-binary-array-bounds.test.ts with ERR_POSTGRES_CONNECTION_REFUSED on Windows 2019 x64-baseline (Postgres service unavailable, same flake as #70203); the darwin 26 aarch64 artifact-download timeout that blocked earlier builds cleared on this run. No Blob/type/slice test failures on any lane. Ready for maintainer review.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 25 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f84eb1a3-4f7a-452f-b33a-e6c326c3b0dd

📥 Commits

Reviewing files that changed from the base of the PR and between 1de8bd3 and 2a64bde.

📒 Files selected for processing (12)
  • src/runtime/server/FileRoute.rs
  • src/runtime/server/RequestContext.rs
  • src/runtime/webcore/Blob.rs
  • src/runtime/webcore/S3File.rs
  • src/runtime/webcore/headers_ref.rs
  • test/js/bun/http/bun-serve-static.test.ts
  • test/js/bun/s3/s3.test.ts
  • test/js/bun/util/inspect.test.js
  • test/js/web/fetch/blob.test.ts
  • test/js/web/html/FormData-multipart-serialization.test.ts
  • test/js/web/structured-clone-blob-file.test.ts
  • test/vendor.json

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Found 2 issues this PR may fix:

  1. Normalizing blob type with charset causing bug on type validation #15078 - Blob type canonicalization appends charset=utf-8 to user-supplied types (e.g. new Blob(["test"], { type: "text/plain" }).type returns "text/plain;charset=utf-8")
  2. different response blob type from node #19603 - Response blob type differs from Node due to same charset canonicalization (application/json becomes application/json;charset=utf-8)

If this is helpful, copy the block below into the PR description to auto-close these issues on merge.

Fixes #15078
Fixes #19603

🤖 Generated with Claude Code

Comment thread src/runtime/webcore/Blob.rs Resolved
Comment thread src/runtime/webcore/Blob.rs Resolved
Comment thread src/runtime/webcore/Blob.rs Resolved

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing the earlier feedback — I don't see any remaining issues, but since this changes user-visible Content-Type behavior across Blob/File/Bun.file/S3/serve (dropping the auto-appended ;charset=utf-8) and adds a vendored-test skip, a maintainer should sign off on the behavior change.

Extended reasoning...

Overview

This PR removes MIME-table canonicalization at every site where a user explicitly supplies a type option — new Blob({type}), new File({type}), blob.slice(_, _, type), Bun.file(path, {type}), Bun.file().write(data, {type}), s3file.writer({type}), and both S3File constructors — and stores the lowercased string verbatim instead. It also removes the parent-type inheritance branch from get_slice_from, so a typeless slice() now has type === "" per the File API spec. Six test files are updated to expect the verbatim type, a new describe("Blob type") block is added to blob.test.ts, and one vendored Elysia test is skipped via test/vendor.json.

Security risks

None identified. The change replaces a table lookup with a straight lowercase copy of a validated string; is_valid_blob_type still gates the input, and the allocation/ownership pattern (leaked Box<[u8]> with content_type_allocated = true, freed via free_content_type) is the same one already used by the fallthrough branch that this PR promotes to the only path. No new untrusted-input surface.

Level of scrutiny

High. This is a deliberate user-visible behavior change to Web-standard APIs with broad ripple effects: new Response(new Blob([...], {type: "text/html"})) now serves Content-Type: text/html (previously text/html;charset=utf-8), S3 uploads send the un-canonicalized type, multipart bodies emit the verbatim type on the wire, and slice() no longer inherits. The change is spec-correct and matches Node/browsers, but it's the kind of default-behavior shift a maintainer should explicitly approve — especially the Bun.serve static-route Content-Type change and the vendored Elysia test skip.

Other factors

  • I previously left four inline comments on an earlier revision; all four were addressed in 7b4e100 and f7e8072 (S3 inspect test updated, do_write/get_writer de-canonicalized, SAFETY comment tightened, FormData round-trip trace corrected by the author). All threads are resolved.
  • The get_slice_from change also flips content_type_was_set from "inherit parent OR explicit" to "explicit only", which affects downstream consumers gated on that flag (get_mime_type_or_content_type, ByteBlobLoader, Body.rs string-body defaulting). I traced the obvious ones and they look correct for in-memory blobs, but a maintainer familiar with the Bun.file(...).slice() → Response body path should confirm.
  • CI shows one failure (node-net-server.test.ts on Windows x64-baseline) that appears unrelated to this change.
  • Memory-management shape is unchanged — the removed branches were the ones that stored a borrowed &'static pointer with content_type_allocated = false; every remaining explicit-type path now heap-allocates and sets content_type_allocated = true, which free_content_type and the existing dupe/drop paths already handle.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The earlier review feedback has been addressed and I found nothing new, but this is a user-visible behavior change to Blob.type/File.type that flows into served Content-Type headers (static routes, S3 uploads, FormData) and required updating six test files plus skipping a vendored Elysia test — worth a maintainer sign-off on the compat call.

Extended reasoning...

Overview

Removes MIME-table canonicalization at every site that accepts an explicit user-supplied type (Blob/File constructors, slice(), Bun.file(path,{type}), file.write(data,{type}), file.writer({type}), and both S3 construct_s3_file_* paths), and stops get_slice_from() from inheriting the parent's content_type on a typeless slice. Eight parallel hunks in Blob.rs/S3File.rs collapse the vm.mime_type() branch into a single lowercased heap copy; content_type_was_set on a slice is now driven by whether the caller supplied a non-empty type. Six test files are updated to expect the verbatim (non-;charset=utf-8) type, a new describe("Blob type") block is added, and an Elysia vendored test is skipped in test/vendor.json.

Security risks

None identified. The change narrows what goes into content_type (always a fresh lowercased heap copy or the static empty literal) and simplifies the ownership model — the removed static-mime-table pointer branch was the source of the odd "inherit only when non-allocated" behavior. No new untrusted-input parsing, no auth/crypto/permissions surface.

Level of scrutiny

Medium-high. The mechanical transformation is uniform and the memory-lifecycle change is a simplification (fewer pointer provenance cases, free_content_type() still called before reassignment, content_type_allocated correctly tracked). But the semantic change is user-visible across a lot of surface: new Response(new Blob([...], {type: 'text/html'})) now serves Content-Type: text/html instead of text/html;charset=utf-8, S3 uploads send the verbatim type, and typeless .slice() of a file/S3 blob no longer carries a type. That's spec-correct and matches Node, but it's a compat break that a maintainer should sign off on.

Other factors

All four of my prior inline comments were resolved in 7b4e100 (the do_write/get_writer sites, both S3File constructors, the S3 inspect test, and the stale SAFETY comment). The one comment robobun pushed back on (FormData round-trip at line 140) was correctly refuted — that path re-derives type from the filename extension. Test coverage for the new behavior is thorough (test.each matrix over Blob/File/slice + Bun.file construct/write, plus the negative slice-inheritance cases). No outstanding reviewer comments.

new Blob({type}), new File({type}), blob.slice(_, _, type),
Bun.file(path, {type}), Bun.file().write(data, {type}),
s3file.writer({type}), and both S3 client.file(path, {type}) paths
now store the user's type string lowercased instead of canonicalizing
through the MIME table, so new Blob([], {type: 'text/plain'}).type
is 'text/plain' rather than 'text/plain;charset=utf-8'.
blob.slice() with no/empty/invalid contentType now has type '' per
the File API spec instead of inheriting the parent's type when the
parent's type happened to be a static table entry. Bun-inferred
defaults (Bun.file extension sniffing, Response string bodies,
data URLs) are unchanged.

Fixes #15078
@robobun
robobun force-pushed the claude/farm/645f031c/blob-type-no-canonicalize branch from d8f2f6f to 1a67989 Compare July 7, 2026 23:16
Comment thread src/runtime/webcore/Blob.rs Resolved
Comment thread src/runtime/webcore/S3File.rs Resolved
robobun and others added 2 commits July 8, 2026 00:12
…wline

A typeless slice of a File- or S3-backed blob has an empty content_type
but has_content_type_from_user() still returns true (because of the
store kind), so blob_content_type() returned Some("") and fetch/serve
emitted an empty Content-Type header. Filter empty in headers_ref.rs so
the header is omitted, matching the Fetch 'extract a body' algorithm
for a Blob with an empty type. This path was already reachable on main
via Bun.file(path, {type: "custom/x"}).slice(); the previous commit
made the extension-sniffed case land here too.

Also: S3File::write_format only emitted the newline before offset: inside
the type: block, so an empty-type slice with a nonzero offset glued
offset: to the opening brace. Move the newline into the offset: block.
Comment thread src/runtime/webcore/Blob.rs Resolved
Comment thread src/runtime/webcore/headers_ref.rs Resolved
Comment thread src/runtime/webcore/Blob.rs Resolved
…t_type_opt doc

get_slice() was short-circuiting on size==0 before reading the
contentType argument, so new Blob([]).slice(0, 0, 'text/plain').type
was '' instead of 'text/plain'. Drop the early return; get_slice_from()
already handles a zero-size parent (relative_start/end both clamp to 0).
Also update the any_blob_content_type_opt doc comment to match its
siblings.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional findings (outside current diff — PR may have been updated during review):

  • 🟡 src/runtime/webcore/Blob.rs:2441-2443 — Nit (same #19603 family, distinct site): after this change, new Response(new Blob([...], {type: "text/html"})) served from a static route sends Content-Type: text/html (this PR's own updated assertion at bun-serve-static.test.ts:211), but the same Response returned from a dynamic fetch handler still sends text/html;charset=utf-8 — RequestContext::get_content_type routes the blob's now-verbatim "text/html" through MimeType::by_name → MimeType::init → the HTML constant. Before this PR the two paths agreed. Nothing regresses (dynamic-serve wire is unchanged), just flagging a sibling MimeType::init site that now observably diverges from the paths this PR made verbatim.

    Extended reasoning...

    What diverges

    For const r = new Response(new Blob(["<p>hi</p>"], {type: "text/html"})) (no init arg, so init.headers stays None), the Content-Type header now depends on how the Response is served:

    Before PR After PR
    r.headers.get("content-type") (in-process) text/html;charset=utf-8 text/html
    Static route wire (routes: {"/a": r}) text/html;charset=utf-8 text/html
    Dynamic route wire (fetch: () => r) text/html;charset=utf-8 text/html;charset=utf-8

    Before this PR all three agreed; after, the dynamic-serve path disagrees with the other two.

    Step-by-step trace

    Blob construction — after this PR, new Blob([...], {type: "text/html"}) at Blob.rs:2441-2443 stores content_type = Owned("text/html") verbatim (was Static("text/html;charset=utf-8") before, via vm.mime_type()).

    Static route — StaticRoute::from_response calls any_blob_content_type(&blob) (headers_ref.rs:11-17), which returns the blob's content_type() verbatim → Some("text/html") → from_fetch_headers writes Content-Type: text/html. Confirmed by this PR's own updated assertion at bun-serve-static.test.ts:211.

    Dynamic route — render_metadata (RequestContext.rs) calls get_content_type(response.get_init_headers_mut(), &self.blob). With no init arg, get_init_headers_mut() returns None, so the headers branch is skipped and it falls through to by_name(blob.content_type()) = MimeType::init("text/html", false, None). In MimeType::init, category_ == "text" && str == "html" matches and returns the HTML constant, whose .value is "text/html;charset=utf-8". render_metadata then writes that as the content-type header.

    In-process — Response::get_or_create_headers writes blob.content_type_slice() verbatim → "text/html", matching the static route but not the dynamic route.

    There's an additional wrinkle: for a dynamic route, if the caller reads r.headers before returning (which materializes the verbatim "text/html" onto the Response), do_write_headers writes that and needs_content_type=false skips the by_name fallback — so the same dynamic Response now serves text/html or text/html;charset=utf-8 depending on whether .headers was touched. Before this PR both cases produced the canonical form.

    Scope

    Only affects the specific set MimeType::init special-cases (text/plain, text/html, text/css, text/javascript, application/json, application/geo+json). Non-table types like "a/b" are unaffected because MimeType::init falls through and preserves the input verbatim.

    Why not caught

    serve.test.ts's "does propagate type for Blob" test passes the full canonical string type: "text/plain;charset=utf-8", so by_name returns the same string and no change is observable. No test compares the same typed-Blob Response across static and dynamic registration.

    Why nit

    The dynamic-serve wire behavior is unchanged from before this PR — no user regresses, no test breaks. The root cause is the pre-existing by_name() canonicalization in RequestContext::get_content_type, which is the same #19603 / MimeType::init family the PR description explicitly scopes out. This is a distinct code site from the already-flagged Body.rs:2173 nit (that one is .blob()'s return type; this one is the served header), worth noting per "fix the whole class" as another parallel path that now observably disagrees with the paths this PR made verbatim.

    Possible fix

    Either fold into the #19603 follow-up, or have RequestContext::get_content_type write blob.content_type() verbatim into the header (using MimeType::init only to derive the .category needed by autoset_filename) so static/dynamic/.headers agree.

Comment thread src/runtime/webcore/Blob.rs Resolved
…type

RequestContext::get_content_type reads blob.content_type() directly (not
through headers_ref.rs), so a typeless slice of Bun.file() fell through
to MimeType::OTHER and the response got
Content-Type: application/octet-stream plus a Content-Disposition
header. Fall back to content_type_or_mime_type() so the sliced file is
served with the same extension-sniffed type as the unsliced file (which
is what the pre-existing TODO at that site was asking about).
Comment thread src/runtime/webcore/headers_ref.rs Resolved
Mirror RequestContext::get_content_type so a static route registered
with new Response(Bun.file(p).slice(a, b)) serves the same Content-Type
as the dynamic handler path and the unsliced file. Inline the fallback
at the FileRoute call site (content_type_or_mime_type) and drop the
now-unused blob_content_type helper; headers_ref.rs stays spec-shaped
for the fetch-client body path. Extend the serve test to cover both the
static and dynamic route.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — 2a64bde addresses the last FileRoute sibling and everything I flagged is now resolved. I'm not auto-approving only because this is a user-visible Content-Type behavior change (six test files updated, one vendored test skipped) with a couple of intentional scope boundaries (the #19603 Body.rs/by_name follow-ups, dynamic-serve still canonicalizing while static/in-process now verbatim) that a maintainer should confirm.

Extended reasoning...

Overview

Removes MIME-table canonicalization at eight explicit-type call sites in Blob.rs/S3File.rs so new Blob({type})/new File({type})/slice(_,_,type)/Bun.file(path,{type})/.write({type})/s3.file({type}) store the user's string verbatim (lowercased), and drops the Static-only parent-type inheritance in get_slice_from so a typeless slice() has type === "" per the File API spec. Follow-on adjustments in headers_ref.rs (filter empty), RequestContext::get_content_type and FileRoute::headers_from (fall back to the File/S3 store's extension-sniffed mime for typeless file slices), and S3File::write_format (offset-line newline). Six test files updated to expect verbatim types; one Elysia vendored test skipped.

Security risks

None identified. No auth/crypto/permissions surface. The changes narrow what user-supplied strings are transformed (less rewriting, not more), and validation (is_valid_blob_type) is unchanged.

Level of scrutiny

Medium-high. The core transformation is mechanical, but the ripple into HTTP Content-Type emission across three separate serve/fetch consumers took four review rounds to converge (empty header, dynamic-handler octet-stream+Content-Disposition, static-route sibling). Each was fixed with a targeted change and a covering test. The size == 0 early-return removal and the new Any::content_type_or_mime_type helper are small but non-trivial.

Other factors

  • All ten of my prior inline threads are resolved (nine addressed by code, one correctly refuted).
  • New tests cover the spec matrix (Blob/File/slice × table/non-table types, empty parent, fetch body header omission, dynamic + static serve fallback, .write({type})).
  • The PR description explicitly documents the remaining #19603-family sites (Body.rs .blob(), RequestContext by_name, data-URL) as out of scope, and the resulting static-vs-dynamic-serve canonicalization difference. Those scope boundaries are reasonable but are the kind of judgment call a maintainer should confirm.
  • CI is reported green on the diff (only an unrelated darwin artifact-download infra flake).

@robobun

robobun commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator Author

This also fixes the Blob/File constructor half of #34717 (explicit text/plain becoming text/plain;charset=utf-8).

@robobun

robobun commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator Author

Closing as part of a cleanup of stale pull requests. This PR has had no new commits since 2026-07-08, it conflicts with main, and its last CI run failed. This is not a judgment on the fix itself. The linked issue (#15078) stays open. If the problem still reproduces on a current build, reopen this PR after a rebase or open a new one against main.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Normalizing blob type with charset causing bug on type validation

1 participant