@ 261107.5
current
'use strict'

const { tspl } = require('@matteo.collina/tspl')
const { test, after } = require('node:test')
const { createServer } = require('node:http')
const { once } = require('node:events')
const { Client, errors } = require('..')

function makeInjectingFn (payload) {
  const fn = function () {}
  fn.toString = () => payload
  return fn
}

function makeToPrimitiveFn (payload) {
  const fn = function () {}
  fn[Symbol.toPrimitive] = () => payload
  return fn
}

test('rejects function header value with CRLF via toString', async (t) => {
  t = tspl(t, { plan: 1 })

  const client = new Client('http://localhost:8080')
  after(() => client.destroy())

  t.rejects(client.request({
    path: '/',
    method: 'GET',
    headers: {
      'x-safe': makeInjectingFn('legit\r\ntransfer-encoding: chunked')
    }
  }), new errors.InvalidArgumentError('invalid x-safe header'))

  await t.completed
})

test('rejects function header value with CRLF via Symbol.toPrimitive', async (t) => {
  t = tspl(t, { plan: 1 })

  const client = new Client('http://localhost:8080')
  after(() => client.destroy())

  t.rejects(client.request({
    path: '/',
    method: 'GET',
    headers: {
      'x-safe': makeToPrimitiveFn('legit\r\nx-injected: yes')
    }
  }), new errors.InvalidArgumentError('invalid x-safe header'))

  await t.completed
})

test('rejects function header value in array element with CRLF via toString', async (t) => {
  t = tspl(t, { plan: 1 })

  const client = new Client('http://localhost:8080')
  after(() => client.destroy())

  t.rejects(client.request({
    path: '/',
    method: 'GET',
    headers: {
      'x-safe': [makeInjectingFn('legit\r\nx-injected: via-array')]
    }
  }), new errors.InvalidArgumentError('invalid x-safe header'))

  await t.completed
})

test('rejects function header value in array element with CRLF via Symbol.toPrimitive', async (t) => {
  t = tspl(t, { plan: 1 })

  const client = new Client('http://localhost:8080')
  after(() => client.destroy())

  t.rejects(client.request({
    path: '/',
    method: 'GET',
    headers: {
      'x-safe': [makeToPrimitiveFn('legit\r\nx-injected: via-array-primitive')]
    }
  }), new errors.InvalidArgumentError('invalid x-safe header'))

  await t.completed
})

test('still accepts number and boolean header values', async (t) => {
  t = tspl(t, { plan: 2 })

  const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
    t.strictEqual(req.headers['x-num'], '123')
    t.strictEqual(req.headers['x-bool'], 'true')
    res.end('ok')
  })
  after(() => server.close())

  server.listen(0)
  await once(server, 'listening')

  const client = new Client(`http://localhost:${server.address().port}`)
  after(() => client.close())

  const { body } = await client.request({
    path: '/',
    method: 'GET',
    headers: {
      'x-num': 123,
      'x-bool': true
    }
  })
  await body.dump()

  await t.completed
})