feat: allow headers to be sent with session.downloadURL()
(#38785)
This commit is contained in:
parent
74d73166d9
commit
e73edb5481
4 changed files with 120 additions and 4 deletions
|
@ -1284,9 +1284,11 @@ reused for new connections.
|
||||||
|
|
||||||
Returns `Promise<Buffer>` - resolves with blob data.
|
Returns `Promise<Buffer>` - resolves with blob data.
|
||||||
|
|
||||||
#### `ses.downloadURL(url)`
|
#### `ses.downloadURL(url[, options])`
|
||||||
|
|
||||||
* `url` string
|
* `url` string
|
||||||
|
* `options` Object (optional)
|
||||||
|
* `headers` Record<string, string> (optional) - HTTP request headers.
|
||||||
|
|
||||||
Initiates a download of the resource at `url`.
|
Initiates a download of the resource at `url`.
|
||||||
The API will generate a [DownloadItem](download-item.md) that can be accessed
|
The API will generate a [DownloadItem](download-item.md) that can be accessed
|
||||||
|
|
|
@ -804,10 +804,24 @@ v8::Local<v8::Promise> Session::GetBlobData(v8::Isolate* isolate,
|
||||||
return holder->ReadAll(isolate);
|
return holder->ReadAll(isolate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::DownloadURL(const GURL& url) {
|
void Session::DownloadURL(const GURL& url, gin::Arguments* args) {
|
||||||
auto* download_manager = browser_context()->GetDownloadManager();
|
std::map<std::string, std::string> headers;
|
||||||
|
gin_helper::Dictionary options;
|
||||||
|
if (args->GetNext(&options)) {
|
||||||
|
if (options.Has("headers") && !options.Get("headers", &headers)) {
|
||||||
|
args->ThrowTypeError("Invalid value for headers - must be an object");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto download_params = std::make_unique<download::DownloadUrlParameters>(
|
auto download_params = std::make_unique<download::DownloadUrlParameters>(
|
||||||
url, MISSING_TRAFFIC_ANNOTATION);
|
url, MISSING_TRAFFIC_ANNOTATION);
|
||||||
|
|
||||||
|
for (const auto& [name, value] : headers) {
|
||||||
|
download_params->add_request_header(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* download_manager = browser_context()->GetDownloadManager();
|
||||||
download_manager->DownloadUrl(std::move(download_params));
|
download_manager->DownloadUrl(std::move(download_params));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -131,7 +131,7 @@ class Session : public gin::Wrappable<Session>,
|
||||||
bool IsPersistent();
|
bool IsPersistent();
|
||||||
v8::Local<v8::Promise> GetBlobData(v8::Isolate* isolate,
|
v8::Local<v8::Promise> GetBlobData(v8::Isolate* isolate,
|
||||||
const std::string& uuid);
|
const std::string& uuid);
|
||||||
void DownloadURL(const GURL& url);
|
void DownloadURL(const GURL& url, gin::Arguments* args);
|
||||||
void CreateInterruptedDownload(const gin_helper::Dictionary& options);
|
void CreateInterruptedDownload(const gin_helper::Dictionary& options);
|
||||||
void SetPreloads(const std::vector<base::FilePath>& preloads);
|
void SetPreloads(const std::vector<base::FilePath>& preloads);
|
||||||
std::vector<base::FilePath> GetPreloads() const;
|
std::vector<base::FilePath> GetPreloads() const;
|
||||||
|
|
|
@ -788,6 +788,7 @@ describe('session module', () => {
|
||||||
const contentDisposition = 'inline; filename="mock.pdf"';
|
const contentDisposition = 'inline; filename="mock.pdf"';
|
||||||
let port: number;
|
let port: number;
|
||||||
let downloadServer: http.Server;
|
let downloadServer: http.Server;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
downloadServer = http.createServer((req, res) => {
|
downloadServer = http.createServer((req, res) => {
|
||||||
res.writeHead(200, {
|
res.writeHead(200, {
|
||||||
|
@ -799,9 +800,11 @@ describe('session module', () => {
|
||||||
});
|
});
|
||||||
port = (await listen(downloadServer)).port;
|
port = (await listen(downloadServer)).port;
|
||||||
});
|
});
|
||||||
|
|
||||||
after(async () => {
|
after(async () => {
|
||||||
await new Promise(resolve => downloadServer.close(resolve));
|
await new Promise(resolve => downloadServer.close(resolve));
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(closeAllWindows);
|
afterEach(closeAllWindows);
|
||||||
|
|
||||||
const isPathEqual = (path1: string, path2: string) => {
|
const isPathEqual = (path1: string, path2: string) => {
|
||||||
|
@ -840,6 +843,103 @@ describe('session module', () => {
|
||||||
session.defaultSession.downloadURL(`${url}:${port}`);
|
session.defaultSession.downloadURL(`${url}:${port}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can download using session.downloadURL with a valid auth header', async () => {
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
const { authorization } = req.headers;
|
||||||
|
if (!authorization || authorization !== 'Basic i-am-an-auth-header') {
|
||||||
|
res.statusCode = 401;
|
||||||
|
res.setHeader('WWW-Authenticate', 'Basic realm="Restricted"');
|
||||||
|
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 downloadDone: Promise<Electron.DownloadItem> = new Promise((resolve) => {
|
||||||
|
session.defaultSession.once('will-download', (e, item) => {
|
||||||
|
item.savePath = downloadFilePath;
|
||||||
|
item.on('done', () => {
|
||||||
|
try {
|
||||||
|
resolve(item);
|
||||||
|
} catch {}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
session.defaultSession.downloadURL(`${url}:${port}`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Basic i-am-an-auth-header'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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 session.downloadURL is called with invalid headers', () => {
|
||||||
|
expect(() => {
|
||||||
|
session.defaultSession.downloadURL(`${url}:${port}`, {
|
||||||
|
// @ts-ignore this line is intentionally incorrect
|
||||||
|
headers: 'i-am-a-bad-header'
|
||||||
|
});
|
||||||
|
}).to.throw(/Invalid value for headers - must be an object/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can download using session.downloadURL with an invalid auth header', async () => {
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
const { authorization } = req.headers;
|
||||||
|
if (!authorization || authorization !== 'Basic i-am-an-auth-header') {
|
||||||
|
res.statusCode = 401;
|
||||||
|
res.setHeader('WWW-Authenticate', 'Basic realm="Restricted"');
|
||||||
|
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 downloadFailed: Promise<Electron.DownloadItem> = new Promise((resolve) => {
|
||||||
|
session.defaultSession.once('will-download', (_, item) => {
|
||||||
|
item.savePath = downloadFilePath;
|
||||||
|
item.on('done', (e, state) => {
|
||||||
|
console.log(state);
|
||||||
|
try {
|
||||||
|
resolve(item);
|
||||||
|
} catch {}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
session.defaultSession.downloadURL(`${url}:${port}`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: 'wtf-is-this'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const item = await downloadFailed;
|
||||||
|
expect(item.getState()).to.equal('interrupted');
|
||||||
|
expect(item.getReceivedBytes()).to.equal(0);
|
||||||
|
expect(item.getTotalBytes()).to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
it('can download using WebContents.downloadURL', (done) => {
|
it('can download using WebContents.downloadURL', (done) => {
|
||||||
const w = new BrowserWindow({ show: false });
|
const w = new BrowserWindow({ show: false });
|
||||||
w.webContents.session.once('will-download', function (e, item) {
|
w.webContents.session.once('will-download', function (e, item) {
|
||||||
|
|
Loading…
Reference in a new issue