зеркало из https://github.com/mozilla/gecko-dev.git
bug 1247793 - Implement download() options: method, headers and body, r=aswan
MozReview-Commit-ID: 2muhcweY8Fo --HG-- extra : rebase_source : 30f0aabce48717caa963610c2fcf831a0b890702
This commit is contained in:
Родитель
3b68400cf3
Коммит
204e680596
|
@ -38,6 +38,14 @@ const DOWNLOAD_ITEM_FIELDS = ["id", "url", "referrer", "filename", "incognito",
|
|||
const DOWNLOAD_ITEM_CHANGE_FIELDS = ["endTime", "state", "paused", "canResume",
|
||||
"error", "exists"];
|
||||
|
||||
// From https://fetch.spec.whatwg.org/#forbidden-header-name
|
||||
const FORBIDDEN_HEADERS = ["ACCEPT-CHARSET", "ACCEPT-ENCODING",
|
||||
"ACCESS-CONTROL-REQUEST-HEADERS", "ACCESS-CONTROL-REQUEST-METHOD",
|
||||
"CONNECTION", "CONTENT-LENGTH", "COOKIE", "COOKIE2", "DATE", "DNT",
|
||||
"EXPECT", "HOST", "KEEP-ALIVE", "ORIGIN", "REFERER", "TE", "TRAILER",
|
||||
"TRANSFER-ENCODING", "UPGRADE", "VIA"];
|
||||
|
||||
const FORBIDDEN_PREFIXES = /^PROXY-|^SEC-/i;
|
||||
|
||||
class DownloadItem {
|
||||
constructor(id, download, extension) {
|
||||
|
@ -418,6 +426,38 @@ extensions.registerSchemaAPI("downloads", "addon_parent", context => {
|
|||
return Promise.reject({message: "conflictAction prompt not yet implemented"});
|
||||
}
|
||||
|
||||
if (options.headers) {
|
||||
for (let {name} of options.headers) {
|
||||
if (FORBIDDEN_HEADERS.includes(name.toUpperCase()) || name.match(FORBIDDEN_PREFIXES)) {
|
||||
return Promise.reject({message: "Forbidden request header name"});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle method, headers and body options.
|
||||
function adjustChannel(channel) {
|
||||
if (channel instanceof Ci.nsIHttpChannel) {
|
||||
const method = options.method || "GET";
|
||||
channel.requestMethod = method;
|
||||
|
||||
if (options.headers) {
|
||||
for (let {name, value} of options.headers) {
|
||||
channel.setRequestHeader(name, value, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.body != null) {
|
||||
const stream = Cc["@mozilla.org/io/string-input-stream;1"]
|
||||
.createInstance(Ci.nsIStringInputStream);
|
||||
stream.setData(options.body, options.body.length);
|
||||
|
||||
channel.QueryInterface(Ci.nsIUploadChannel2);
|
||||
channel.explicitSetUploadStream(stream, null, -1, method, false);
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
function createTarget(downloadsDir) {
|
||||
let target;
|
||||
if (filename) {
|
||||
|
@ -481,13 +521,23 @@ extensions.registerSchemaAPI("downloads", "addon_parent", context => {
|
|||
let download;
|
||||
return Downloads.getPreferredDownloadsDirectory()
|
||||
.then(downloadsDir => createTarget(downloadsDir))
|
||||
.then(target => Downloads.createDownload({
|
||||
source: options.url,
|
||||
target: {
|
||||
path: target,
|
||||
partFilePath: target + ".part",
|
||||
},
|
||||
})).then(dl => {
|
||||
.then(target => {
|
||||
const source = {
|
||||
url: options.url,
|
||||
};
|
||||
|
||||
if (options.method || options.headers || options.body) {
|
||||
source.adjustChannel = adjustChannel;
|
||||
}
|
||||
|
||||
return Downloads.createDownload({
|
||||
source,
|
||||
target: {
|
||||
path: target,
|
||||
partFilePath: target + ".part",
|
||||
},
|
||||
});
|
||||
}).then(dl => {
|
||||
download = dl;
|
||||
return DownloadMap.getDownloadList();
|
||||
}).then(list => {
|
||||
|
|
|
@ -386,7 +386,6 @@
|
|||
"type": "boolean"
|
||||
},
|
||||
"method": {
|
||||
"unsupported": true,
|
||||
"description": "The HTTP method to use if the URL uses the HTTP[S] protocol.",
|
||||
"enum": [
|
||||
"GET",
|
||||
|
@ -396,7 +395,6 @@
|
|||
"type": "string"
|
||||
},
|
||||
"headers": {
|
||||
"unsupported": true,
|
||||
"optional": true,
|
||||
"type": "array",
|
||||
"description": "Extra HTTP headers to send with the request if the URL uses the HTTP[s] protocol. Each header is represented as a dictionary containing the keys <code>name</code> and either <code>value</code> or <code>binaryValue</code>, restricted to those allowed by XMLHttpRequest.",
|
||||
|
@ -415,7 +413,6 @@
|
|||
}
|
||||
},
|
||||
"body": {
|
||||
"unsupported": true,
|
||||
"description": "Post body.",
|
||||
"optional": true,
|
||||
"type": "string"
|
||||
|
|
|
@ -255,3 +255,101 @@ add_task(function* test_downloads() {
|
|||
|
||||
yield extension.unload();
|
||||
});
|
||||
|
||||
add_task(function* test_download_post() {
|
||||
const server = createHttpServer();
|
||||
const url = `http://localhost:${server.identity.primaryPort}/post-log`;
|
||||
|
||||
let received;
|
||||
server.registerPathHandler("/post-log", request => {
|
||||
received = request;
|
||||
});
|
||||
|
||||
// Confirm received vs. expected values.
|
||||
function confirm(method, headers = {}, body) {
|
||||
equal(received.method, method, "method is correct");
|
||||
|
||||
for (let name in headers) {
|
||||
ok(received.hasHeader(name), `header ${name} received`);
|
||||
equal(received.getHeader(name), headers[name], `header ${name} is correct`);
|
||||
}
|
||||
|
||||
if (body) {
|
||||
const str = NetUtil.readInputStreamToString(received.bodyInputStream,
|
||||
received.bodyInputStream.available());
|
||||
equal(str, body, "body is correct");
|
||||
}
|
||||
}
|
||||
|
||||
function background() {
|
||||
browser.test.onMessage.addListener(options => {
|
||||
Promise.resolve()
|
||||
.then(() => browser.downloads.download(options))
|
||||
.catch(err => browser.test.sendMessage("done", {err: err.message}));
|
||||
});
|
||||
browser.downloads.onChanged.addListener(({state}) => {
|
||||
if (state && state.current === "complete") {
|
||||
browser.test.sendMessage("done", {ok: true});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const manifest = {permissions: ["downloads"]};
|
||||
const extension = ExtensionTestUtils.loadExtension({background, manifest});
|
||||
yield extension.startup();
|
||||
|
||||
function download(options) {
|
||||
options.url = url;
|
||||
options.conflictAction = "overwrite";
|
||||
|
||||
extension.sendMessage(options);
|
||||
return extension.awaitMessage("done");
|
||||
}
|
||||
|
||||
// Test method option.
|
||||
let result = yield download({});
|
||||
ok(result.ok, "download works without the method option, defaults to GET");
|
||||
confirm("GET");
|
||||
|
||||
result = yield download({method: "PUT"});
|
||||
ok(!result.ok, "download rejected with PUT method");
|
||||
ok(/method: Invalid enumeration/.test(result.err), "descriptive error message");
|
||||
|
||||
result = yield download({method: "POST"});
|
||||
ok(result.ok, "download works with POST method");
|
||||
confirm("POST");
|
||||
|
||||
// Test body option values.
|
||||
result = yield download({body: []});
|
||||
ok(!result.ok, "download rejected because of non-string body");
|
||||
ok(/body: Expected string/.test(result.err), "descriptive error message");
|
||||
|
||||
result = yield download({method: "POST", body: "of work"});
|
||||
ok(result.ok, "download works with POST method and body");
|
||||
confirm("POST", {"Content-Length": 7}, "of work");
|
||||
|
||||
// Test custom headers.
|
||||
result = yield download({headers: [{name: "X-Custom"}]});
|
||||
ok(!result.ok, "download rejected because of missing header value");
|
||||
ok(/"value" is required/.test(result.err), "descriptive error message");
|
||||
|
||||
result = yield download({headers: [{name: "X-Custom", value: "13"}]});
|
||||
ok(result.ok, "download works with a custom header");
|
||||
confirm("GET", {"X-Custom": "13"});
|
||||
|
||||
// Test forbidden headers.
|
||||
result = yield download({headers: [{name: "DNT", value: "1"}]});
|
||||
ok(!result.ok, "download rejected because of forbidden header name DNT");
|
||||
ok(/Forbidden request header/.test(result.err), "descriptive error message");
|
||||
|
||||
result = yield download({headers: [{name: "Proxy-Connection", value: "keep"}]});
|
||||
ok(!result.ok, "download rejected because of forbidden header name prefix Proxy-");
|
||||
ok(/Forbidden request header/.test(result.err), "descriptive error message");
|
||||
|
||||
result = yield download({headers: [{name: "Sec-ret", value: "13"}]});
|
||||
ok(!result.ok, "download rejected because of forbidden header name prefix Sec-");
|
||||
ok(/Forbidden request header/.test(result.err), "descriptive error message");
|
||||
|
||||
remove("post-log");
|
||||
yield extension.unload();
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче