Browse Source

fix: data corruption in `protocol.handle` (#41894)

Jeremy Rose 1 year ago
parent
commit
4e8c28fdf0
3 changed files with 45 additions and 1 deletions
  1. 1 1
      lib/browser/api/protocol.ts
  2. 43 0
      spec/api-protocol-spec.ts
  3. 1 0
      spec/index.js

+ 1 - 1
lib/browser/api/protocol.ts

@@ -18,7 +18,7 @@ function makeStreamFromPipe (pipe: any): ReadableStream {
       try {
         const rv = await pipe.read(buf);
         if (rv > 0) {
-          controller.enqueue(buf.subarray(0, rv));
+          controller.enqueue(buf.slice(0, rv));
         } else {
           controller.close();
         }

+ 43 - 0
spec/api-protocol-spec.ts

@@ -1381,6 +1381,49 @@ describe('protocol module', () => {
       expect(body).to.equal(text);
     });
 
+    it('can receive stream request body asynchronously', async () => {
+      let done: any;
+      const requestReceived: Promise<Buffer[]> = new Promise(resolve => { done = resolve; });
+      protocol.handle('http-like', async (req) => {
+        const chunks = [];
+        for await (const chunk of (req.body as any)) {
+          chunks.push(chunk);
+        }
+        done(chunks);
+        return new Response('ok');
+      });
+      defer(() => { protocol.unhandle('http-like'); });
+      const w = new BrowserWindow({ show: false });
+      w.loadURL('about:blank');
+      const expectedHashChunks = await w.webContents.executeJavaScript(`
+        const dataStream = () =>
+          new ReadableStream({
+            async start(controller) {
+              for (let i = 0; i < 10; i++) { controller.enqueue(Array(1024 * 128).fill(+i).join("\\n")); }
+              controller.close();
+            },
+          }).pipeThrough(new TextEncoderStream());
+        fetch(
+          new Request("http-like://host", {
+            method: "POST",
+            body: dataStream(),
+            duplex: "half",
+          })
+        );
+        (async () => {
+          const chunks = []
+          for await (const chunk of dataStream()) {
+            chunks.push(chunk);
+          }
+          return chunks;
+        })()
+      `);
+      const expectedHash = Buffer.from(await crypto.subtle.digest('SHA-256', Buffer.concat(expectedHashChunks))).toString('hex');
+      const body = Buffer.concat(await requestReceived);
+      const actualHash = Buffer.from(await crypto.subtle.digest('SHA-256', Buffer.from(body))).toString('hex');
+      expect(actualHash).to.equal(expectedHash);
+    });
+
     it('can receive multi-part postData from loadURL', async () => {
       protocol.handle('test-scheme', (req) => new Response(req.body));
       defer(() => { protocol.unhandle('test-scheme'); });

+ 1 - 0
spec/index.js

@@ -40,6 +40,7 @@ protocol.registerSchemesAsPrivileged([
   { scheme: global.standardScheme, privileges: { standard: true, secure: true, stream: false } },
   { scheme: global.zoomScheme, privileges: { standard: true, secure: true } },
   { scheme: global.serviceWorkerScheme, privileges: { allowServiceWorkers: true, standard: true, secure: true } },
+  { scheme: 'http-like', privileges: { standard: true, secure: true, corsEnabled: true, supportFetchAPI: true } },
   { scheme: 'cors-blob', privileges: { corsEnabled: true, supportFetchAPI: true } },
   { scheme: 'cors', privileges: { corsEnabled: true, supportFetchAPI: true } },
   { scheme: 'no-cors', privileges: { supportFetchAPI: true } },