Skip to content

WrapHandler translates headers using utf-8 instead of latin1 #4797

Description

@domenic

Bug Description

When WrapHandler gets involved to translate from the new handler API calls to the old handler API, it incorrectly translates headers using utf-8 instead of isomorphic encoding, due to calling Buffer.from(x) instead of Buffer.from(x, 'latin1').

This affects all APIs that use WrapHandler such as request().

Reproducible By

const WrapHandler = require("undici/lib/handler/wrap-handler.js");

const oldAPIHandler = {
  onHeaders(statusCode, rawHeaders) {
    const value = rawHeaders[1].toString("latin1");
    console.log("Expected:", JSON.stringify("\xE2\x80\xA6"));
    console.log("Got:     ", JSON.stringify(value));
  }
};

const wrapped = WrapHandler.wrap(oldAPIHandler);
wrapped.onResponseStart({ resume() {} }, 200, { "x-test": "\xE2\x80\xA6" }, "OK");
A repro case using request() around a custom dispatcher that uses WrapHandler
const http = require("http");
const { Dispatcher, request } = require("undici");
const WrapHandler = require("undici/lib/handler/wrap-handler.js");

const server = http.createServer((req, res) => {
  res.writeHead(200, { "x-test": "\xE2\x80\xA6" });
  res.end();
});

// Dispatcher that does its own HTTP and calls onResponseStart with object headers
class MyDispatcher extends Dispatcher {
  dispatch(opts, handler) {
    const wrapped = WrapHandler.wrap(handler);

    http.get(opts.origin + opts.path, res => {
      wrapped.onRequestStart?.({ abort() {}, pause() {}, resume() {} }, {});
      wrapped.onResponseStart?.({ resume() {}, pause() {} }, res.statusCode, res.headers, res.statusMessage);
      res.on("data", chunk => wrapped.onResponseData?.({}, chunk));
      res.on("end", () => wrapped.onResponseEnd?.({}, {}));
    }).on("error", err => wrapped.onResponseError?.({}, err));

    return true;
  }
  close() { return Promise.resolve(); }
  destroy() { return Promise.resolve(); }
}

(async () => {
  await new Promise(r => server.listen(0, r));
  const url = `http://localhost:${server.address().port}`;

  const response = await request(url, { dispatcher: new MyDispatcher() });
  await response.body.dump();

  console.log("Expected:", JSON.stringify("\xE2\x80\xA6"));
  console.log("Got:     ", JSON.stringify(response.headers["x-test"]));

  server.close();
})();

Expected Behavior

Expected: "â¦"
Got:      "â¦"

Environment

  • Node v25.3.0
  • Undici 7.20.0

Additional context

I don't believe #4785 will fix this.

#4786 might fix this.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions