diff --git a/src/connection/HttpConnection.ts b/src/connection/HttpConnection.ts index e8baaf7..4859d9f 100644 --- a/src/connection/HttpConnection.ts +++ b/src/connection/HttpConnection.ts @@ -48,6 +48,7 @@ const debug = Debug('elasticsearch') const INVALID_PATH_REGEX = /[^\u0021-\u00ff]/ const MAX_BUFFER_LENGTH = buffer.constants.MAX_LENGTH const MAX_STRING_LENGTH = buffer.constants.MAX_STRING_LENGTH +const noop = (): void => {} export default class HttpConnection extends BaseConnection { agent?: http.Agent | https.Agent | hpagent.HttpProxyAgent | hpagent.HttpsProxyAgent @@ -128,6 +129,7 @@ export default class HttpConnection extends BaseConnection { const onResponse = (response: http.IncomingMessage): void => { cleanListeners() + request.on('error', noop) // There are some edge cases where the request emits an error while processing the response. this._openRequests-- if (options.asStream === true) { @@ -193,6 +195,7 @@ export default class HttpConnection extends BaseConnection { response.removeListener('end', onEnd) response.removeListener('error', onEnd) response.removeListener('aborted', onAbort) + request.removeListener('error', noop) if (err != null) { if (err.name === 'RequestAbortedError') { diff --git a/test/unit/http-connection.test.ts b/test/unit/http-connection.test.ts index 98dbe29..527c1f9 100644 --- a/test/unit/http-connection.test.ts +++ b/test/unit/http-connection.test.ts @@ -29,6 +29,7 @@ import intoStream from 'into-stream' import { AbortController } from 'node-abort-controller' import { buildServer } from '../utils' import { HttpConnection, errors, ConnectionOptions } from '../../' +import net from "net"; const { TimeoutError, @@ -1268,3 +1269,50 @@ test('Cleanup abort listener', async t => { t.equal(controller.signal.eventEmitter.listeners('abort').length, 0) server.stop() }) + +test('Handles malformed HTML responses (HEAD response with body)', async t => { + t.plan(2) + + // Creating a custom TCP server because `http.createServer` handles + // the method accordingly and skips sending the body if the request is HEAD + function createTcpServer() { + const server = net.createServer(); + server.on('connection', (socket) => { + socket.write(`HTTP/1.1 200 OK\r\n`) + socket.write(`Content-Type: text/html\r\n`) + socket.write(`Content-Length: 155\r\n`) + socket.write(`\r\n`) + socket.write(` + +
+ + + +This is a bad implementation of an HTTP server
+`) + + socket.end() + }) + return new Promise<{port: number, server: net.Server}>((resolve) => server.listen(0, () => { + const port = (server.address() as net.AddressInfo).port + resolve({port, server}) + })); + } + + const {port, server} = await createTcpServer() + const connection = new HttpConnection({ + url: new URL(`http://localhost:${port}`) + }) + + const res = await connection.request({ + path: '/hello', + method: 'HEAD', + headers: { + 'X-Custom-Test': 'true' + } + }, options) + t.match(res.headers, { 'content-length': 155 }) + t.equal(res.body, '') + server.close() +})