current commit
260725.5 · src: fix crash when writing odd-length hex string via Writev
'use strict';

const common = require('../common');
const assert = require('node:assert');
const http = require('node:http');
const net = require('node:net');

// Regression test for https://github.com/nodejs/node/issues/45150
// Writing an odd-length hex string to a stream that batches writes via
// Writev (e.g. HTTP requests that are automatically corked) used to
// fatal-assert in StringBytes::StorageSize. The trailing incomplete nibble
// should be silently dropped, consistent with non-Writev paths.

// Test 1: HTTP POST with a single odd-length hex write.
// "1" has no complete bytes in hex encoding, so the request body is empty.
{
  const server = http.createServer(common.mustCall((req, res) => {
    const chunks = [];
    req.on('data', (chunk) => chunks.push(chunk));
    req.on('end', common.mustCall(() => {
      assert.strictEqual(Buffer.concat(chunks).length, 0);
      res.end();
      server.close();
    }));
  }));

  server.listen(0, common.mustCall(() => {
    const req = http.request({
      port: server.address().port,
      method: 'POST',
    }, common.mustCall((res) => {
      res.resume();
    }));
    req.write('1', 'hex');
    req.end();
  }));
}

// Test 2: HTTP POST with cork/uncork and mixed odd-length hex writes.
// "ff1" (3 hex chars) decodes to 1 byte (0xff); the trailing "1" nibble is
// dropped.  "1" (1 hex char) decodes to 0 bytes.
{
  const server = http.createServer(common.mustCall((req, res) => {
    const chunks = [];
    req.on('data', (chunk) => chunks.push(chunk));
    req.on('end', common.mustCall(() => {
      assert.deepStrictEqual(Buffer.concat(chunks), Buffer.from([0xff]));
      res.end();
      server.close();
    }));
  }));

  server.listen(0, common.mustCall(() => {
    const req = http.request({
      port: server.address().port,
      method: 'POST',
    }, common.mustCall((res) => {
      res.resume();
    }));
    req.cork();
    req.write('ff1', 'hex');
    req.write('1', 'hex');
    req.uncork();
    req.end();
  }));
}

// Test 3: net socket with cork/uncork and an odd-length hex write.
// Exercises the Writev path directly at the net layer.
{
  const server = net.createServer(common.mustCall((socket) => {
    const chunks = [];
    socket.on('data', (chunk) => chunks.push(chunk));
    socket.on('end', common.mustCall(() => {
      assert.deepStrictEqual(Buffer.concat(chunks), Buffer.from([0xff]));
      server.close();
    }));
    socket.resume();
  }));

  server.listen(0, common.mustCall(() => {
    const conn = net.createConnection(server.address().port);
    conn.on('connect', common.mustCall(() => {
      conn.cork();
      conn.write('ff', 'hex');
      conn.write('1', 'hex');
      conn.uncork();
      conn.end();
    }));
  }));
}