diff --git a/lib/browser/api/protocol.ts b/lib/browser/api/protocol.ts index 6a2147343d91..286bb570f73c 100644 --- a/lib/browser/api/protocol.ts +++ b/lib/browser/api/protocol.ts @@ -60,6 +60,26 @@ function convertToRequestBody (uploadData: ProtocolRequest['uploadData']): Reque }) as RequestInit['body']; } +// TODO(codebytere): Use Object.hasOwn() once we update to ECMAScript 2022. +function validateResponse (res: Response) { + if (!res || typeof res !== 'object') return false; + + if (res.type === 'error') return true; + + const exists = (key: string) => Object.prototype.hasOwnProperty.call(res, key); + + if (exists('status') && typeof res.status !== 'number') return false; + if (exists('statusText') && typeof res.statusText !== 'string') return false; + if (exists('headers') && typeof res.headers !== 'object') return false; + + if (exists('body')) { + if (typeof res.body !== 'object') return false; + if (res.body !== null && !(res.body instanceof ReadableStream)) return false; + } + + return true; +} + Protocol.prototype.handle = function (this: Electron.Protocol, scheme: string, handler: (req: Request) => Response | Promise) { const register = isBuiltInScheme(scheme) ? this.interceptProtocol : this.registerProtocol; const success = register.call(this, scheme, async (preq: ProtocolRequest, cb: any) => { @@ -73,13 +93,14 @@ Protocol.prototype.handle = function (this: Electron.Protocol, scheme: string, h duplex: body instanceof ReadableStream ? 'half' : undefined } as any); const res = await handler(req); - if (!res || typeof res !== 'object') { + if (!validateResponse(res)) { return cb({ error: ERR_UNEXPECTED }); - } - if (res.type === 'error') { cb({ error: ERR_FAILED }); } else { + } else if (res.type === 'error') { + cb({ error: ERR_FAILED }); + } else { cb({ data: res.body ? Readable.fromWeb(res.body as ReadableStream) : null, - headers: Object.fromEntries(res.headers), + headers: res.headers ? Object.fromEntries(res.headers) : {}, statusCode: res.status, statusText: res.statusText, mimeType: (res as any).__original_resp?._responseHead?.mimeType diff --git a/spec/api-protocol-spec.ts b/spec/api-protocol-spec.ts index 9fab1598a30d..18fe0ea837ef 100644 --- a/spec/api-protocol-spec.ts +++ b/spec/api-protocol-spec.ts @@ -1211,6 +1211,42 @@ describe('protocol module', () => { await expect(net.fetch('test-scheme://foo')).to.eventually.be.rejectedWith('net::ERR_FAILED'); }); + it('handles invalid protocol response status', async () => { + protocol.handle('test-scheme', () => { + return { status: [] } as any; + }); + + defer(() => { protocol.unhandle('test-scheme'); }); + await expect(net.fetch('test-scheme://foo')).to.be.rejectedWith('net::ERR_UNEXPECTED'); + }); + + it('handles invalid protocol response statusText', async () => { + protocol.handle('test-scheme', () => { + return { statusText: false } as any; + }); + + defer(() => { protocol.unhandle('test-scheme'); }); + await expect(net.fetch('test-scheme://foo')).to.be.rejectedWith('net::ERR_UNEXPECTED'); + }); + + it('handles invalid protocol response header parameters', async () => { + protocol.handle('test-scheme', () => { + return { headers: false } as any; + }); + + defer(() => { protocol.unhandle('test-scheme'); }); + await expect(net.fetch('test-scheme://foo')).to.be.rejectedWith('net::ERR_UNEXPECTED'); + }); + + it('handles invalid protocol response body parameters', async () => { + protocol.handle('test-scheme', () => { + return { body: false } as any; + }); + + defer(() => { protocol.unhandle('test-scheme'); }); + await expect(net.fetch('test-scheme://foo')).to.be.rejectedWith('net::ERR_UNEXPECTED'); + }); + it('handles a synchronous error in the handler', async () => { protocol.handle('test-scheme', () => { throw new Error('test'); }); defer(() => { protocol.unhandle('test-scheme'); });