Browse Source

fix: pass postData to new-window event (#28513)

Jeremy Rose 4 years ago
parent
commit
77dcf1020a

+ 1 - 1
docs/api/structures/upload-file.md

@@ -1,6 +1,6 @@
 # UploadFile Object
 
-* `type` String - `file`.
+* `type` 'file' - `file`.
 * `filePath` String - Path of file to be uploaded.
 * `offset` Integer - Defaults to `0`.
 * `length` Integer - Number of bytes to read from `offset`.

+ 1 - 1
docs/api/structures/upload-raw-data.md

@@ -1,4 +1,4 @@
 # UploadRawData Object
 
-* `type` String - `rawData`.
+* `type` 'rawData' - `rawData`.
 * `bytes` Buffer - Data to be uploaded.

+ 34 - 15
lib/browser/guest-window-manager.ts

@@ -56,6 +56,7 @@ export function openGuestWindow ({ event, embedder, guest, referrer, disposition
     windowOpenArgs,
     additionalFeatures,
     disposition,
+    postData,
     referrer
   });
   if (didCancelEvent) return;
@@ -148,7 +149,7 @@ function emitDeprecatedNewWindowEvent ({ event, embedder, guest, windowOpenArgs,
   const isWebViewWithPopupsDisabled = embedder.getType() === 'webview' && embedder.getLastWebPreferences().disablePopups;
   const postBody = postData ? {
     data: postData,
-    headers: formatPostDataHeaders(postData as Electron.UploadRawData[])
+    ...parseContentTypeFormat(postData)
   } : null;
 
   embedder.emit(
@@ -277,22 +278,40 @@ function getDeprecatedInheritedOptions (embedder: WebContents) {
   return inheritableOptions;
 }
 
-function formatPostDataHeaders (postData: Electron.UploadRawData[]) {
+function formatPostDataHeaders (postData: PostData) {
   if (!postData) return;
 
-  let extraHeaders = 'content-type: application/x-www-form-urlencoded';
+  const { contentType, boundary } = parseContentTypeFormat(postData);
+  if (boundary != null) { return `content-type: ${contentType}; boundary=${boundary}`; }
 
-  if (postData.length > 0) {
-    const postDataFront = postData[0].bytes.toString();
-    const boundary = /^--.*[^-\r\n]/.exec(
-      postDataFront
-    );
-    if (boundary != null) {
-      extraHeaders = `content-type: multipart/form-data; boundary=${boundary[0].substr(
-        2
-      )}`;
+  return `content-type: ${contentType}`;
+}
+
+const MULTIPART_CONTENT_TYPE = 'multipart/form-data';
+const URL_ENCODED_CONTENT_TYPE = 'application/x-www-form-urlencoded';
+
+// Figure out appropriate headers for post data.
+const parseContentTypeFormat = function (postData: Exclude<PostData, undefined>) {
+  if (postData.length) {
+    if (postData[0].type === 'rawData') {
+      // For multipart forms, the first element will start with the boundary
+      // notice, which looks something like `------WebKitFormBoundary12345678`
+      // Note, this regex would fail when submitting a urlencoded form with an
+      // input attribute of name="--theKey", but, uhh, don't do that?
+      const postDataFront = postData[0].bytes.toString();
+      const boundary = /^--.*[^-\r\n]/.exec(postDataFront);
+      if (boundary) {
+        return {
+          boundary: boundary[0].substr(2),
+          contentType: MULTIPART_CONTENT_TYPE
+        };
+      }
     }
   }
-
-  return extraHeaders;
-}
+  // Either the form submission didn't contain any inputs (the postData array
+  // was empty), or we couldn't find the boundary and thus we can assume this is
+  // a key=value style form.
+  return {
+    contentType: URL_ENCODED_CONTENT_TYPE
+  };
+};

+ 1 - 1
lib/common/parse-features-string.ts

@@ -62,7 +62,7 @@ export function parseCommaSeparatedKeyValue (source: string, useSoonToBeDeprecat
   for (const keyValuePair of source.split(',')) {
     const [key, value] = keyValuePair.split('=').map(str => str.trim());
     if (useSoonToBeDeprecatedBehaviorForBareKeys && value === undefined) {
-      bareKeys.push(key);
+      if (key) { bareKeys.push(key); }
       continue;
     }
     parsed[key] = coerce(key, value);

+ 50 - 1
spec-main/api-browser-window-spec.ts

@@ -6,7 +6,7 @@ import * as os from 'os';
 import * as qs from 'querystring';
 import * as http from 'http';
 import { AddressInfo } from 'net';
-import { app, BrowserWindow, BrowserView, ipcMain, OnBeforeSendHeadersListenerDetails, protocol, screen, webContents, session, WebContents } from 'electron/main';
+import { app, BrowserWindow, BrowserView, ipcMain, OnBeforeSendHeadersListenerDetails, protocol, screen, webContents, session, WebContents, BrowserWindowConstructorOptions } from 'electron/main';
 
 import { emittedOnce, emittedUntil } from './events-helpers';
 import { ifit, ifdescribe, defer, delay } from './spec-helpers';
@@ -3047,6 +3047,55 @@ describe('BrowserWindow module', () => {
       });
       w.loadFile(path.join(fixtures, 'pages', 'target-name.html'));
     });
+
+    it('includes all properties', async () => {
+      const w = new BrowserWindow({ show: false });
+
+      const p = new Promise<{
+        url: string,
+        frameName: string,
+        disposition: string,
+        options: BrowserWindowConstructorOptions,
+        additionalFeatures: string[],
+        referrer: Electron.Referrer,
+        postBody: Electron.PostBody
+      }>((resolve) => {
+        w.webContents.once('new-window', (e, url, frameName, disposition, options, additionalFeatures, referrer, postBody) => {
+          e.preventDefault();
+          resolve({ url, frameName, disposition, options, additionalFeatures, referrer, postBody });
+        });
+      });
+      w.loadURL(`data:text/html,${encodeURIComponent(`
+        <form target="_blank" method="POST" id="form" action="http://example.com/test">
+          <input type="text" name="post-test-key" value="post-test-value"></input>
+        </form>
+        <script>form.submit()</script>
+      `)}`);
+      const { url, frameName, disposition, options, additionalFeatures, referrer, postBody } = await p;
+      expect(url).to.equal('http://example.com/test');
+      expect(frameName).to.equal('');
+      expect(disposition).to.equal('foreground-tab');
+      expect(options).to.deep.equal({
+        show: true,
+        width: 800,
+        height: 600,
+        webPreferences: {
+          contextIsolation: true,
+          nodeIntegration: false,
+          webviewTag: false,
+          nodeIntegrationInSubFrames: false,
+          openerId: options.webPreferences!.openerId
+        },
+        webContents: undefined
+      });
+      expect(referrer.policy).to.equal('strict-origin-when-cross-origin');
+      expect(referrer.url).to.equal('');
+      expect(additionalFeatures).to.deep.equal([]);
+      expect(postBody.data).to.have.length(1);
+      expect(postBody.data[0].type).to.equal('rawData');
+      expect(postBody.data[0].bytes).to.deep.equal(Buffer.from('post-test-key=post-test-value'));
+      expect(postBody.contentType).to.equal('application/x-www-form-urlencoded');
+    });
   });
 
   ifdescribe(process.platform !== 'linux')('max/minimize events', () => {