fix(res.send): send the bytes covered by a DataView by chiragjangiir · Pull Request #7513 · expressjs/express · GitHub
Skip to content

fix(res.send): send the bytes covered by a DataView - #7513

Closed
chiragjangiir wants to merge 2 commits into
expressjs:masterfrom
chiragjangiir:fix/send-dataview-bytes
Closed

chiragjangiir wants to merge 2 commits into
expressjs:masterfrom
chiragjangiir:fix/send-dataview-bytes

Conversation

@chiragjangiir

Copy link
Copy Markdown

fix(res.send): send the bytes covered by a DataView

res.send() recognized a DataView as binary (ArrayBuffer.isView() is
true), but later called Buffer.from(dataView), which produces an empty
Buffer. A non-empty view therefore returned HTTP 200 with
Content-Length: 0 and an empty body.

Root cause

lib/response.js in the object case:

} else if (ArrayBuffer.isView(chunk)) {
  if (!this.get('Content-Type')) {
    this.type('bin');
  }
} else {
  return this.json(chunk);
}

When chunk is a DataView, this sets Content-Type: application/octet-stream
but leaves chunk as the DataView. The length/body handling then falls into:

} else {
  chunk = Buffer.from(chunk, encoding)  // DataView -> empty Buffer
  ...
}

Buffer.from(dataView) yields an empty buffer, so the view's bytes are
silently dropped. The same Buffer.from(typedArray) behavior is correct for
Uint8Array (it materializes the view correctly), but a DataView is not a
typed array — it only references a slice of a parent ArrayBuffer.

Fix

After the existing this.type('bin') guard (which preserves a manually-set
Content-Type), materialise a DataView by its exact byte range instead of
by the DataView itself:

} else if (util.types.isDataView(chunk)) {
  // A DataView references a slice of a parent ArrayBuffer, and
  // Buffer.from(dataView) yields an empty Buffer. Materialise the exact byte
  // range it covers so the response carries the same bytes a Uint8Array would.
  chunk = Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);
}

This preserves:

  • the byte order and the exact range (byteOffset .. byteOffset + byteLength),
  • the existing Content-Type when the user set one,
  • the ETag when one is configured,
  • the Uint8Array path (which already worked via Buffer.from(typedArray)).

Regression tests

New describe block res.send(DataView) in test/res.send.js with 6 cases, all
passing:

  • sends the exact byte range covered by the view
  • does not override an existing Content-Type
  • preserves the ETag when one is set
  • sends the bytes of a view that does not cover its whole buffer
  • sends cross-realm DataViews unchanged
  • does not affect plain ArrayBuffer behaviour

Validation

  • npx mocha test/res.send.js → 82 passing (the 6 new DataView tests included)
  • npm test → 1267 passing
  • npm run lint → clean (no errors)

Related

chiragjangiir and others added 2 commits October 9, 2026 20:09
DataView objects reference a slice of a parent ArrayBuffer, and
Buffer.from(dataView) returns an empty Buffer, so res.send() on a
DataView produced HTTP 200 with Content-Length: 0 and no body.

Materialise the exact byte range the view covers so the response
carries the same bytes a Uint8Array would, including the byteOffset
and byteLength.

Closes expressjs#7510
Co-authored-by: Buffy <buffy@upstageai.com>
@krzysdz

krzysdz commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

@chiragjangiir

Copy link
Copy Markdown
Author

Thanks for the pointer — I checked rather than guessed, and you're right.

I ran master (a419aa7), this branch and #7511 side by side through a real server, on five body types:

body master #7511 this PR
DataView (offset 1, length 3) 0 bytes 3 bytes 010203 3 bytes 010203
full DataView 0 bytes 3 bytes 010203 3 bytes 010203
Uint8Array, Uint16Array, Buffer unchanged unchanged unchanged

The DataView handling here is the same expression as #7511's — Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength) — and the two branches this PR adds around it are behaviour-preserving: Buffer.isBuffer(chunk) leaves the chunk untouched, and Buffer.from(chunk) for a TypedArray produces the same bytes master already produces, because a non-Buffer view already reaches chunk = Buffer.from(chunk, encoding) in the length/ETag path further down. For every non-DataView body I measured, all three trees put identical bytes, Content-Length and Content-Type on the wire.

So there isn't anything this PR does that #7511 doesn't, and #7511 is both earlier and smaller. Closing this as a duplicate — #7511 is the one to land. Sorry for the duplicate.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

res.send(DataView) silently drops the view’s bytes

2 participants