From bd481ebd4450f04c9cbfc62cab8b1a7c628d057f Mon Sep 17 00:00:00 2001
From: uzlopak <aras.abbasi@googlemail.com>
Date: Mon, 15 Apr 2024 05:40:37 +0200
Subject: [PATCH 1/2] test: increase coverage

---
 lib/core/request.js    |   6 +-
 lib/core/util.js       |   4 +-
 test/client-request.js | 159 +++++++++++++++++++++++++++++++++++------
 test/request.js        |  14 ++++
 4 files changed, 156 insertions(+), 27 deletions(-)

diff --git a/lib/core/request.js b/lib/core/request.js
index 37839d3c949..d77d07670fa 100644
--- a/lib/core/request.js
+++ b/lib/core/request.js
@@ -7,7 +7,7 @@ const {
 const assert = require('node:assert')
 const {
   isValidHTTPToken,
-  isValidHeaderChar,
+  isValidHeaderValue,
   isStream,
   destroy,
   isBuffer,
@@ -336,7 +336,7 @@ function processHeader (request, key, val) {
     const arr = []
     for (let i = 0; i < val.length; i++) {
       if (typeof val[i] === 'string') {
-        if (!isValidHeaderChar(val[i])) {
+        if (!isValidHeaderValue(val[i])) {
           throw new InvalidArgumentError(`invalid ${key} header`)
         }
         arr.push(val[i])
@@ -350,7 +350,7 @@ function processHeader (request, key, val) {
     }
     val = arr
   } else if (typeof val === 'string') {
-    if (!isValidHeaderChar(val)) {
+    if (!isValidHeaderValue(val)) {
       throw new InvalidArgumentError(`invalid ${key} header`)
     }
   } else if (val === null) {
diff --git a/lib/core/util.js b/lib/core/util.js
index 2051f5fad2d..47ad0485202 100644
--- a/lib/core/util.js
+++ b/lib/core/util.js
@@ -547,7 +547,7 @@ const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/
 /**
  * @param {string} characters
  */
-function isValidHeaderChar (characters) {
+function isValidHeaderValue (characters) {
   return !headerCharRegex.test(characters)
 }
 
@@ -627,7 +627,7 @@ module.exports = {
   buildURL,
   addAbortListener,
   isValidHTTPToken,
-  isValidHeaderChar,
+  isValidHeaderValue,
   isTokenCharCode,
   parseRangeHeader,
   isValidPort,
diff --git a/test/client-request.js b/test/client-request.js
index 8e0111de09c..96694748501 100644
--- a/test/client-request.js
+++ b/test/client-request.js
@@ -3,7 +3,7 @@
 'use strict'
 
 const { tspl } = require('@matteo.collina/tspl')
-const { test, after } = require('node:test')
+const { test, after, describe, before } = require('node:test')
 const { Client, errors } = require('..')
 const { createServer } = require('node:http')
 const EE = require('node:events')
@@ -11,7 +11,7 @@ const { kConnect } = require('../lib/core/symbols')
 const { Readable } = require('node:stream')
 const net = require('node:net')
 const { promisify } = require('node:util')
-const { NotSupportedError } = require('../lib/core/errors')
+const { NotSupportedError, InvalidArgumentError } = require('../lib/core/errors')
 const { parseFormDataString } = require('./utils/formdata')
 
 test('request dump big', async (t) => {
@@ -53,6 +53,7 @@ test('request dump', async (t) => {
   t = tspl(t, { plan: 3 })
 
   const server = createServer((req, res) => {
+    res.setHeader('content-length', 5)
     res.end('hello')
   })
   after(() => server.close())
@@ -391,34 +392,148 @@ test('request text', async (t) => {
   await t.completed
 })
 
-test('empty host header', async (t) => {
-  t = tspl(t, { plan: 3 })
+describe('headers', () => {
+  describe('invalid headers', () => {
+    test('invalid header value - array with string with invalid character', async (t) => {
+      t = tspl(t, { plan: 1 })
 
-  const server = createServer((req, res) => {
-    res.end(req.headers.host)
-  })
-  after(() => server.close())
+      const client = new Client('http://localhost:8080')
+      after(() => client.destroy())
 
-  server.listen(0, async () => {
-    const serverAddress = `localhost:${server.address().port}`
-    const client = new Client(`http://${serverAddress}`)
-    after(() => client.destroy())
+      t.rejects(client.request({
+        path: '/',
+        method: 'GET',
+        headers: { name: ['test\0'] }
+      }), new InvalidArgumentError('invalid name header'))
+
+      await t.completed
+    })
+    test('invalid header value - array with POJO', async (t) => {
+      t = tspl(t, { plan: 1 })
+
+      const client = new Client('http://localhost:8080')
+      after(() => client.destroy())
 
-    const getWithHost = async (host, wanted) => {
-      const { body } = await client.request({
+      t.rejects(client.request({
         path: '/',
         method: 'GET',
-        headers: { host }
-      })
-      t.strictEqual(await body.text(), wanted)
-    }
+        headers: { name: [{}] }
+      }), new InvalidArgumentError('invalid name header'))
+
+      await t.completed
+    })
+
+    test('invalid header value - string with invalid character', async (t) => {
+      t = tspl(t, { plan: 1 })
 
-    await getWithHost('test', 'test')
-    await getWithHost(undefined, serverAddress)
-    await getWithHost('', '')
+      const client = new Client('http://localhost:8080')
+      after(() => client.destroy())
+
+      t.rejects(client.request({
+        path: '/',
+        method: 'GET',
+        headers: { name: 'test\0' }
+      }), new InvalidArgumentError('invalid name header'))
+
+      await t.completed
+    })
   })
 
-  await t.completed
+  describe('array', () => {
+    let serverAddress
+    const server = createServer((req, res) => {
+      res.end(JSON.stringify(req.headers))
+    })
+
+    before(async () => {
+      server.listen(0)
+      await EE.once(server, 'listening')
+      serverAddress = `localhost:${server.address().port}`
+    })
+
+    after(() => server.close())
+
+    test('empty host header', async (t) => {
+      t = tspl(t, { plan: 4 })
+
+      const client = new Client(`http://${serverAddress}`)
+      after(() => client.destroy())
+
+      const testCase = async (expected, actual) => {
+        const { body } = await client.request({
+          path: '/',
+          method: 'GET',
+          headers: expected
+        })
+
+        const result = await body.json()
+        t.deepStrictEqual(result, { ...result, ...actual })
+      }
+
+      await testCase({ key: [null] }, { key: '' })
+      await testCase({ key: ['test'] }, { key: 'test' })
+      await testCase({ key: ['test', 'true'] }, { key: 'test, true' })
+      await testCase({ key: ['test', true] }, { key: 'test, true' })
+
+      await t.completed
+    })
+  })
+
+  describe('host', () => {
+    let serverAddress
+    const server = createServer((req, res) => {
+      res.end(req.headers.host)
+    })
+
+    before(async () => {
+      server.listen(0)
+      await EE.once(server, 'listening')
+      serverAddress = `localhost:${server.address().port}`
+    })
+
+    after(() => server.close())
+
+    test('invalid host header', async (t) => {
+      t = tspl(t, { plan: 1 })
+
+      const client = new Client(`http://${serverAddress}`)
+      after(() => client.destroy())
+
+      t.rejects(client.request({
+        path: '/',
+        method: 'GET',
+        headers: {
+          host: [
+            'www.example.com'
+          ]
+        }
+      }), new InvalidArgumentError('invalid host header'))
+
+      await t.completed
+    })
+
+    test('empty host header', async (t) => {
+      t = tspl(t, { plan: 3 })
+
+      const client = new Client(`http://${serverAddress}`)
+      after(() => client.destroy())
+
+      const getWithHost = async (host, wanted) => {
+        const { body } = await client.request({
+          path: '/',
+          method: 'GET',
+          headers: { host }
+        })
+        t.strictEqual(await body.text(), wanted)
+      }
+
+      await getWithHost('test', 'test')
+      await getWithHost(undefined, serverAddress)
+      await getWithHost('', '')
+
+      await t.completed
+    })
+  })
 })
 
 test('request long multibyte text', async (t) => {
diff --git a/test/request.js b/test/request.js
index 628bed0d415..d515133e0a4 100644
--- a/test/request.js
+++ b/test/request.js
@@ -163,6 +163,20 @@ test('Absolute URL as pathname should be included in req.path', async (t) => {
   t.end()
 })
 
+describe('DispatchOptions#expectContinue', () => {
+  test('Should throw if invalid expectContinue option', async t => {
+    t = tspl(t, { plan: 1 })
+
+    await t.rejects(request({
+      method: 'GET',
+      origin: 'http://somehost.xyz',
+      expectContinue: 0
+    }), /invalid expectContinue/)
+
+    await t.completed
+  })
+})
+
 describe('DispatchOptions#reset', () => {
   test('Should throw if invalid reset option', async t => {
     t = tspl(t, { plan: 1 })

From 4c59f4dffb341dc51df71832051aa1eb79578105 Mon Sep 17 00:00:00 2001
From: uzlopak <aras.abbasi@googlemail.com>
Date: Mon, 15 Apr 2024 05:58:39 +0200
Subject: [PATCH 2/2] remove redundant line

---
 lib/core/request.js    |  2 --
 test/client-request.js | 15 +++++++++++++++
 2 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/lib/core/request.js b/lib/core/request.js
index d77d07670fa..c44cd0891f5 100644
--- a/lib/core/request.js
+++ b/lib/core/request.js
@@ -355,8 +355,6 @@ function processHeader (request, key, val) {
     }
   } else if (val === null) {
     val = ''
-  } else if (typeof val === 'object') {
-    throw new InvalidArgumentError(`invalid ${key} header`)
   } else {
     val = `${val}`
   }
diff --git a/test/client-request.js b/test/client-request.js
index 96694748501..e71a034408d 100644
--- a/test/client-request.js
+++ b/test/client-request.js
@@ -437,6 +437,21 @@ describe('headers', () => {
 
       await t.completed
     })
+
+    test('invalid header value - object', 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: { name: new Date() }
+      }), new InvalidArgumentError('invalid name header'))
+
+      await t.completed
+    })
   })
 
   describe('array', () => {