chore: propagate 'Invalid header' errors (#7020)

Enable developers to handle 'Invalid header' errors instead of hiding them to make sure they can address them properly.
Co-authored-by: Jan Scheffler <janscheffler@chromium.org>
This commit is contained in:
Mikkel Snitker 2021-10-04 08:18:03 +02:00 committed by GitHub
parent 327282e047
commit fd607b1095
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 74 additions and 36 deletions

View File

@ -22,6 +22,7 @@ import { Protocol } from 'devtools-protocol';
import { ProtocolMapping } from 'devtools-protocol/types/protocol-mapping.js'; import { ProtocolMapping } from 'devtools-protocol/types/protocol-mapping.js';
import { ConnectionTransport } from './ConnectionTransport.js'; import { ConnectionTransport } from './ConnectionTransport.js';
import { EventEmitter } from './EventEmitter.js'; import { EventEmitter } from './EventEmitter.js';
import { ProtocolError } from './Errors.js';
/** /**
* @public * @public
@ -34,7 +35,7 @@ export { ConnectionTransport, ProtocolMapping };
export interface ConnectionCallback { export interface ConnectionCallback {
resolve: Function; resolve: Function;
reject: Function; reject: Function;
error: Error; error: ProtocolError;
method: string; method: string;
} }
@ -99,7 +100,12 @@ export class Connection extends EventEmitter {
const params = paramArgs.length ? paramArgs[0] : undefined; const params = paramArgs.length ? paramArgs[0] : undefined;
const id = this._rawSend({ method, params }); const id = this._rawSend({ method, params });
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this._callbacks.set(id, { resolve, reject, error: new Error(), method }); this._callbacks.set(id, {
resolve,
reject,
error: new ProtocolError(),
method,
});
}); });
} }
@ -206,7 +212,7 @@ export interface CDPSessionOnMessageObject {
id?: number; id?: number;
method: string; method: string;
params: Record<string, unknown>; params: Record<string, unknown>;
error: { message: string; data: any }; error: { message: string; data: any; code: number };
result?: any; result?: any;
} }
@ -288,7 +294,12 @@ export class CDPSession extends EventEmitter {
}); });
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this._callbacks.set(id, { resolve, reject, error: new Error(), method }); this._callbacks.set(id, {
resolve,
reject,
error: new ProtocolError(),
method,
});
}); });
} }
@ -348,13 +359,13 @@ export class CDPSession extends EventEmitter {
* @returns {!Error} * @returns {!Error}
*/ */
function createProtocolError( function createProtocolError(
error: Error, error: ProtocolError,
method: string, method: string,
object: { error: { message: string; data: any } } object: { error: { message: string; data: any; code: number } }
): Error { ): Error {
let message = `Protocol error (${method}): ${object.error.message}`; let message = `Protocol error (${method}): ${object.error.message}`;
if ('data' in object.error) message += ` ${object.error.data}`; if ('data' in object.error) message += ` ${object.error.data}`;
return rewriteError(error, message); return rewriteError(error, message, object.error.message);
} }
/** /**
@ -362,7 +373,12 @@ function createProtocolError(
* @param {string} message * @param {string} message
* @returns {!Error} * @returns {!Error}
*/ */
function rewriteError(error: Error, message: string): Error { function rewriteError(
error: ProtocolError,
message: string,
originalMessage?: string
): Error {
error.message = message; error.message = message;
error.originalMessage = originalMessage;
return error; return error;
} }

View File

@ -18,7 +18,7 @@
* @public * @public
*/ */
export class CustomError extends Error { export class CustomError extends Error {
constructor(message: string) { constructor(message?: string) {
super(message); super(message);
this.name = this.constructor.name; this.name = this.constructor.name;
Error.captureStackTrace(this, this.constructor); Error.captureStackTrace(this, this.constructor);
@ -36,6 +36,15 @@ export class CustomError extends Error {
* @public * @public
*/ */
export class TimeoutError extends CustomError {} export class TimeoutError extends CustomError {}
/**
* ProtocolError is emitted whenever there is an error from the protocol.
*
* @public
*/
export class ProtocolError extends CustomError {
public code?: number;
public originalMessage: string;
}
/** /**
* @public * @public
*/ */

View File

@ -19,6 +19,7 @@ import { HTTPResponse } from './HTTPResponse.js';
import { assert } from './assert.js'; import { assert } from './assert.js';
import { helper, debugError } from './helper.js'; import { helper, debugError } from './helper.js';
import { Protocol } from 'devtools-protocol'; import { Protocol } from 'devtools-protocol';
import { ProtocolError } from './Errors.js';
/** /**
* @public * @public
@ -229,11 +230,7 @@ export class HTTPRequest {
async finalizeInterceptions(): Promise<void> { async finalizeInterceptions(): Promise<void> {
await this._interceptActions.reduce( await this._interceptActions.reduce(
(promiseChain, interceptAction) => (promiseChain, interceptAction) =>
promiseChain.then(interceptAction).catch((error) => { promiseChain.then(interceptAction).catch(handleError),
// This is here so cooperative handlers that fail do not stop other handlers
// from running
debugError(error);
}),
Promise.resolve() Promise.resolve()
); );
const [resolution] = this.interceptResolution(); const [resolution] = this.interceptResolution();
@ -443,12 +440,7 @@ export class HTTPRequest {
postData: postDataBinaryBase64, postData: postDataBinaryBase64,
headers: headers ? headersArray(headers) : undefined, headers: headers ? headersArray(headers) : undefined,
}) })
.catch((error) => { .catch(handleError);
// In certain cases, protocol will return error if the request was
// already canceled or the page was closed. We should tolerate these
// errors.
debugError(error);
});
} }
/** /**
@ -540,12 +532,7 @@ export class HTTPRequest {
responseHeaders: headersArray(responseHeaders), responseHeaders: headersArray(responseHeaders),
body: responseBody ? responseBody.toString('base64') : undefined, body: responseBody ? responseBody.toString('base64') : undefined,
}) })
.catch((error) => { .catch(handleError);
// In certain cases, protocol will return error if the request was
// already canceled or the page was closed. We should tolerate these
// errors.
debugError(error);
});
} }
/** /**
@ -594,12 +581,7 @@ export class HTTPRequest {
requestId: this._interceptionId, requestId: this._interceptionId,
errorReason, errorReason,
}) })
.catch((error) => { .catch(handleError);
// In certain cases, protocol will return error if the request was
// already canceled or the page was closed. We should tolerate these
// errors.
debugError(error);
});
} }
} }
@ -666,6 +648,16 @@ function headersArray(
return result; return result;
} }
async function handleError(error: ProtocolError) {
if (['Invalid header'].includes(error.originalMessage)) {
throw error;
}
// In certain cases, protocol will return error if the request was
// already canceled or the page was closed. We should tolerate these
// errors.
debugError(error);
}
// List taken from // List taken from
// https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml // https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
// with extra 306 and 418 codes. // with extra 306 and 418 codes.

View File

@ -388,10 +388,7 @@ export class NetworkManager extends EventEmitter {
); );
this._requestIdToRequest.set(event.requestId, request); this._requestIdToRequest.set(event.requestId, request);
this.emit(NetworkManagerEmittedEvents.Request, request); this.emit(NetworkManagerEmittedEvents.Request, request);
request.finalizeInterceptions().catch((error) => { request.finalizeInterceptions();
// This should never happen, but catch just in case.
debugError(error);
});
} }
_onRequestServedFromCache( _onRequestServedFromCache(

View File

@ -68,6 +68,30 @@ describe('network', function () {
}); });
}); });
describeFailsFirefox('Request.continue', () => {
it('should split a request header at new line characters and add the header multiple times instead', async () => {
const { page, server } = getTestState();
let resolve;
const errorPromise = new Promise((r) => {
resolve = r;
});
await page.setRequestInterception(true);
page.on('request', async (request) => {
await request
.continue({
headers: {
'X-Test-Header': 'a\nb',
},
})
.catch(resolve);
});
page.goto(server.PREFIX + '/empty.html');
const error = await errorPromise;
expect(error).toBeTruthy();
});
});
describe('Request.frame', function () { describe('Request.frame', function () {
it('should work for main frame navigation request', async () => { it('should work for main frame navigation request', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();