Repository: electron/electron
Pull request: fix: `webContents.downloadURL()` did not support referer header

Description of Change

This PR fixes an issue where the `Referer` HTTP header was not being correctly applied when using the `webContents.downloadURL()` API.

The previous implementation added all headers, including `Referer`, through the generic `add_request_header` method. However, `Referer` is a special header that cannot be set via the generic `add_request_header` method. Instead, the download system requires the referrer to be set specifically via `download_params->set_referrer()`.

This change adds logic to specifically detect the `Referer` header (case-insensitively) from the options and sets it using the correct method, ensuring it is properly handled during the download request. Other headers continue to be processed as before.

Before this PR:

- webContents.downloadURL('https://example.com/file', { headers: { 'Referer': 'https://my-referer.com' } }) => ❌ The Referer header was not sent with the download request.

After this PR:

- webContents.downloadURL('https://example.com/file', { headers: { 'Referer': 'https://my-referer.com' } }) => ✅ The Referer header is correctly sent as https://my-referer.com .
- webContents.downloadURL('https://example.com/file', { headers: { 'referer': 'https://my-referer.com' } }) => ✅ The Referer header is correctly sent (header name is case-insensitive).

Checklist
- [x] PR description included and stakeholders cc'd
- [x] `npm test` passes
- [x] tests are [added](https://github.com/electron/electron/blob/main/docs/development/testing.md)
- [x] [PR release notes](https://github.com/electron/clerk/blob/main/README.md) describe the change in a way relevant to app developers, and are [capitalized, punctuated, and past tense](https://github.com/electron/clerk/blob/main/README.md#examples).


Release Notes

Notes: Fixed a bug where the `Referer` header was not being set correctly when using `webContents.downloadURL()`.

Changed files:
filename: shell/browser/api/electron_api_web_contents.cc
patch: @@ -2398,6 +2398,13 @@ void WebContents::DownloadURL(const GURL& url, gin::Arguments* args) {
       content::DownloadRequestUtils::CreateDownloadForWebContentsMainFrame(
           web_contents(), url, MISSING_TRAFFIC_ANNOTATION));
   for (const auto& [name, value] : headers) {
+    if (base::ToLowerASCII(name) ==
+        base::ToLowerASCII(net::HttpRequestHeaders::kReferer)) {
+      // Setting a Referer header with HTTPS scheme while the download URL's
+      // scheme is HTTP might lead to download failure.
+      download_params->set_referrer(GURL(value));
+      continue;
+    }
     download_params->add_request_header(name, value);
   }
 

filename: spec/api-session-spec.ts
patch: @@ -1289,6 +1289,53 @@ describe('session module', () => {
         expect(item.getContentDisposition()).to.equal(contentDisposition);
       });
 
+      it('can perform a download with referer header', async () => {
+        const server = http.createServer((req, res) => {
+          const { referer } = req.headers;
+          if (!referer || !referer.startsWith('http://www.electronjs.org')) {
+            res.statusCode = 403;
+            res.end();
+          } else {
+            res.writeHead(200, {
+              'Content-Length': mockPDF.length,
+              'Content-Type': 'application/pdf',
+              'Content-Disposition': req.url === '/?testFilename' ? 'inline' : contentDisposition
+            });
+            res.end(mockPDF);
+          }
+        });
+
+        const { port } = await listen(server);
+
+        const w = new BrowserWindow({ show: false });
+        const downloadDone: Promise<Electron.DownloadItem> = new Promise((resolve) => {
+          w.webContents.session.once('will-download', (e, item) => {
+            item.savePath = downloadFilePath;
+            item.on('done', () => {
+              try {
+                resolve(item);
+              } catch { }
+            });
+          });
+        });
+
+        w.webContents.downloadURL(`${url}:${port}`, {
+          headers: {
+            // Setting a Referer header with HTTPS scheme while the download URL's
+            // scheme is HTTP might lead to download failure.
+            referer: 'http://www.electronjs.org'
+          }
+        });
+
+        const item = await downloadDone;
+        expect(item.getState()).to.equal('completed');
+        expect(item.getFilename()).to.equal('mock.pdf');
+        expect(item.getMimeType()).to.equal('application/pdf');
+        expect(item.getReceivedBytes()).to.equal(mockPDF.length);
+        expect(item.getTotalBytes()).to.equal(mockPDF.length);
+        expect(item.getContentDisposition()).to.equal(contentDisposition);
+      });
+
       it('throws when called with invalid headers', () => {
         const w = new BrowserWindow({ show: false });
         expect(() => {