Skip to content

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

@domenic

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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions