fune/toolkit/components/extensions/test/xpcshell/test_ext_downloads_download.js
Kris Maglione 6b12d08f7d Bug 1462937: Update callers to use nsIFile::GetDirectoryEntries as a nsIDirectoryEnumerator. r=froydnj
MozReview-Commit-ID: Iv4T1MVAF5

--HG--
extra : rebase_source : 1c518883d082884db7f9323a5acc20361228c26b
extra : histedit_source : 70a73c23d1199d3bfbb5379c78930401166c094b
2018-05-19 20:17:45 -07:00

382 lines
13 KiB
JavaScript

/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
ChromeUtils.import("resource://gre/modules/osfile.jsm");
ChromeUtils.import("resource://gre/modules/Downloads.jsm");
const gServer = createHttpServer();
gServer.registerDirectory("/data/", do_get_file("data"));
gServer.registerPathHandler("/dir/", (_, res) => res.write("length=8"));
const WINDOWS = AppConstants.platform == "win";
const BASE = `http://localhost:${gServer.identity.primaryPort}/`;
const FILE_NAME = "file_download.txt";
const FILE_URL = BASE + "data/" + FILE_NAME;
const FILE_NAME_UNIQUE = "file_download(1).txt";
const FILE_LEN = 46;
let downloadDir;
function setup() {
downloadDir = FileUtils.getDir("TmpD", ["downloads"]);
downloadDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
info(`Using download directory ${downloadDir.path}`);
Services.prefs.setIntPref("browser.download.folderList", 2);
Services.prefs.setComplexValue("browser.download.dir", Ci.nsIFile, downloadDir);
registerCleanupFunction(() => {
Services.prefs.clearUserPref("browser.download.folderList");
Services.prefs.clearUserPref("browser.download.dir");
let entries = downloadDir.directoryEntries;
while (entries.hasMoreElements()) {
let entry = entries.nextFile;
ok(false, `Leftover file ${entry.path} in download directory`);
entry.remove(false);
}
downloadDir.remove(false);
});
}
function backgroundScript() {
let blobUrl;
browser.test.onMessage.addListener(async (msg, ...args) => {
if (msg == "download.request") {
let options = args[0];
if (options.blobme) {
let blob = new Blob(options.blobme);
delete options.blobme;
blobUrl = options.url = window.URL.createObjectURL(blob);
}
try {
let id = await browser.downloads.download(options);
browser.test.sendMessage("download.done", {status: "success", id});
} catch (error) {
browser.test.sendMessage("download.done", {status: "error", errmsg: error.message});
}
} else if (msg == "killTheBlob") {
window.URL.revokeObjectURL(blobUrl);
blobUrl = null;
}
});
browser.test.sendMessage("ready");
}
// This function is a bit of a sledgehammer, it looks at every download
// the browser knows about and waits for all active downloads to complete.
// But we only start one at a time and only do a handful in total, so
// this lets us test download() without depending on anything else.
async function waitForDownloads() {
let list = await Downloads.getList(Downloads.ALL);
let downloads = await list.getAll();
let inprogress = downloads.filter(dl => !dl.stopped);
return Promise.all(inprogress.map(dl => dl.whenSucceeded()));
}
// Create a file in the downloads directory.
function touch(filename) {
let file = downloadDir.clone();
file.append(filename);
file.create(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
}
// Remove a file in the downloads directory.
function remove(filename, recursive = false) {
let file = downloadDir.clone();
file.append(filename);
file.remove(recursive);
}
add_task(async function test_downloads() {
setup();
let extension = ExtensionTestUtils.loadExtension({
background: `(${backgroundScript})()`,
manifest: {
permissions: ["downloads"],
},
});
function download(options) {
extension.sendMessage("download.request", options);
return extension.awaitMessage("download.done");
}
async function testDownload(options, localFile, expectedSize, description) {
let msg = await download(options);
equal(msg.status, "success", `downloads.download() works with ${description}`);
await waitForDownloads();
let localPath = downloadDir.clone();
let parts = Array.isArray(localFile) ? localFile : [localFile];
parts.map(p => localPath.append(p));
equal(localPath.fileSize, expectedSize, "Downloaded file has expected size");
localPath.remove(false);
}
await extension.startup();
await extension.awaitMessage("ready");
info("extension started");
// Call download() with just the url property.
await testDownload({url: FILE_URL}, FILE_NAME, FILE_LEN, "just source");
// Call download() with a filename property.
await testDownload({
url: FILE_URL,
filename: "newpath.txt",
}, "newpath.txt", FILE_LEN, "source and filename");
// Call download() with a filename with subdirs.
await testDownload({
url: FILE_URL,
filename: "sub/dir/file",
}, ["sub", "dir", "file"], FILE_LEN, "source and filename with subdirs");
// Call download() with a filename with existing subdirs.
await testDownload({
url: FILE_URL,
filename: "sub/dir/file2",
}, ["sub", "dir", "file2"], FILE_LEN, "source and filename with existing subdirs");
// Only run Windows path separator test on Windows.
if (WINDOWS) {
// Call download() with a filename with Windows path separator.
await testDownload({
url: FILE_URL,
filename: "sub\\dir\\file3",
}, ["sub", "dir", "file3"], FILE_LEN, "filename with Windows path separator");
}
remove("sub", true);
// Call download(), filename with subdir, skipping parts.
await testDownload({
url: FILE_URL,
filename: "skip//part",
}, ["skip", "part"], FILE_LEN, "source, filename, with subdir, skipping parts");
remove("skip", true);
// Check conflictAction of "uniquify".
touch(FILE_NAME);
await testDownload({
url: FILE_URL,
conflictAction: "uniquify",
}, FILE_NAME_UNIQUE, FILE_LEN, "conflictAction=uniquify");
// todo check that preexisting file was not modified?
remove(FILE_NAME);
// Check conflictAction of "overwrite".
touch(FILE_NAME);
await testDownload({
url: FILE_URL,
conflictAction: "overwrite",
}, FILE_NAME, FILE_LEN, "conflictAction=overwrite");
// Try to download in invalid url
await download({url: "this is not a valid URL"}).then(msg => {
equal(msg.status, "error", "downloads.download() fails with invalid url");
ok(/not a valid URL/.test(msg.errmsg), "error message for invalid url is correct");
});
// Try to download to an empty path.
await download({
url: FILE_URL,
filename: "",
}).then(msg => {
equal(msg.status, "error", "downloads.download() fails with empty filename");
equal(msg.errmsg, "filename must not be empty", "error message for empty filename is correct");
});
// Try to download to an absolute path.
const absolutePath = OS.Path.join(WINDOWS ? "\\tmp" : "/tmp", "file_download.txt");
await download({
url: FILE_URL,
filename: absolutePath,
}).then(msg => {
equal(msg.status, "error", "downloads.download() fails with absolute filename");
equal(msg.errmsg, "filename must not be an absolute path", `error message for absolute path (${absolutePath}) is correct`);
});
if (WINDOWS) {
await download({
url: FILE_URL,
filename: "C:\\file_download.txt",
}).then(msg => {
equal(msg.status, "error", "downloads.download() fails with absolute filename");
equal(msg.errmsg, "filename must not be an absolute path", "error message for absolute path with drive letter is correct");
});
}
// Try to download to a relative path containing ..
await download({
url: FILE_URL,
filename: OS.Path.join("..", "file_download.txt"),
}).then(msg => {
equal(msg.status, "error", "downloads.download() fails with back-references");
equal(msg.errmsg, "filename must not contain back-references (..)", "error message for back-references is correct");
});
// Try to download to a long relative path containing ..
await download({
url: FILE_URL,
filename: OS.Path.join("foo", "..", "..", "file_download.txt"),
}).then(msg => {
equal(msg.status, "error", "downloads.download() fails with back-references");
equal(msg.errmsg, "filename must not contain back-references (..)", "error message for back-references is correct");
});
// Test illegal characters on Windows.
if (WINDOWS) {
await download({
url: FILE_URL,
filename: "like:this",
}).then(msg => {
equal(msg.status, "error", "downloads.download() fails with illegal chars");
equal(msg.errmsg, "filename must not contain illegal characters", "error message correct");
});
}
// Try to download a blob url
const BLOB_STRING = "Hello, world";
await testDownload({
blobme: [BLOB_STRING],
filename: FILE_NAME,
}, FILE_NAME, BLOB_STRING.length, "blob url");
extension.sendMessage("killTheBlob");
// Try to download a blob url without a given filename
await testDownload({
blobme: [BLOB_STRING],
}, "download", BLOB_STRING.length, "blob url with no filename");
extension.sendMessage("killTheBlob");
// Download a normal URL with an empty filename part.
await testDownload({
url: BASE + "dir/",
}, "download", 8, "normal url with empty filename");
// Check that the "incognito" property is supported.
await testDownload({
url: FILE_URL,
incognito: false,
}, FILE_NAME, FILE_LEN, "incognito=false");
await testDownload({
url: FILE_URL,
incognito: true,
}, FILE_NAME, FILE_LEN, "incognito=true");
await extension.unload();
});
add_task(async 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(async options => {
try {
await 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});
await extension.startup();
function download(options) {
options.url = url;
options.conflictAction = "overwrite";
extension.sendMessage(options);
return extension.awaitMessage("done");
}
// Test method option.
let result = await download({});
ok(result.ok, "download works without the method option, defaults to GET");
confirm("GET");
result = await download({method: "PUT"});
ok(!result.ok, "download rejected with PUT method");
ok(/method: Invalid enumeration/.test(result.err), "descriptive error message");
result = await download({method: "POST"});
ok(result.ok, "download works with POST method");
confirm("POST");
// Test body option values.
result = await download({body: []});
ok(!result.ok, "download rejected because of non-string body");
ok(/body: Expected string/.test(result.err), "descriptive error message");
result = await 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 = await 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 = await 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 = await 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 = await 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 = await 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");
await extension.unload();
});