Browse Source

fix: validate response in `protocol.handle()` (#38635)

fix: validate response in protocol.handle()

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <[email protected]>
trop[bot] 1 year ago
parent
commit
1facc6d304
2 changed files with 61 additions and 4 deletions
  1. 25 4
      lib/browser/api/protocol.ts
  2. 36 0
      spec/api-protocol-spec.ts

+ 25 - 4
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<Response>) {
   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<ArrayBufferView>) : 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

+ 36 - 0
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'); });