diff options
author | Matt A. Tobin <email@mattatobin.com> | 2018-02-10 02:51:36 -0500 |
---|---|---|
committer | Matt A. Tobin <email@mattatobin.com> | 2018-02-10 02:51:36 -0500 |
commit | 37d5300335d81cecbecc99812747a657588c63eb (patch) | |
tree | 765efa3b6a56bb715d9813a8697473e120436278 /toolkit/components/webextensions/test/xpcshell/test_ext_downloads_download.js | |
parent | b2bdac20c02b12f2057b9ef70b0a946113a00e00 (diff) | |
parent | 4fb11cd5966461bccc3ed1599b808237be6b0de9 (diff) | |
download | UXP-37d5300335d81cecbecc99812747a657588c63eb.tar UXP-37d5300335d81cecbecc99812747a657588c63eb.tar.gz UXP-37d5300335d81cecbecc99812747a657588c63eb.tar.lz UXP-37d5300335d81cecbecc99812747a657588c63eb.tar.xz UXP-37d5300335d81cecbecc99812747a657588c63eb.zip |
Merge branch 'ext-work'
Diffstat (limited to 'toolkit/components/webextensions/test/xpcshell/test_ext_downloads_download.js')
-rw-r--r-- | toolkit/components/webextensions/test/xpcshell/test_ext_downloads_download.js | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_downloads_download.js b/toolkit/components/webextensions/test/xpcshell/test_ext_downloads_download.js new file mode 100644 index 000000000..37ddd4d7c --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_downloads_download.js @@ -0,0 +1,354 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +/* global OS */ + +Cu.import("resource://gre/modules/osfile.jsm"); +Cu.import("resource://gre/modules/Downloads.jsm"); + +const gServer = createHttpServer(); +gServer.registerDirectory("/data/", do_get_file("data")); + +const WINDOWS = AppConstants.platform == "win"; + +const BASE = `http://localhost:${gServer.identity.primaryPort}/data`; +const FILE_NAME = "file_download.txt"; +const FILE_URL = BASE + "/" + 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); + do_print(`Using download directory ${downloadDir.path}`); + + Services.prefs.setIntPref("browser.download.folderList", 2); + Services.prefs.setComplexValue("browser.download.dir", Ci.nsIFile, downloadDir); + + do_register_cleanup(() => { + Services.prefs.clearUserPref("browser.download.folderList"); + Services.prefs.clearUserPref("browser.download.dir"); + + let entries = downloadDir.directoryEntries; + while (entries.hasMoreElements()) { + let entry = entries.getNext().QueryInterface(Ci.nsIFile); + 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(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); + } + + yield extension.startup(); + yield extension.awaitMessage("ready"); + do_print("extension started"); + + // Call download() with just the url property. + yield testDownload({url: FILE_URL}, FILE_NAME, FILE_LEN, "just source"); + + // Call download() with a filename property. + yield testDownload({ + url: FILE_URL, + filename: "newpath.txt", + }, "newpath.txt", FILE_LEN, "source and filename"); + + // Call download() with a filename with subdirs. + yield 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. + yield 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. + yield 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. + yield 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); + yield 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); + yield testDownload({ + url: FILE_URL, + conflictAction: "overwrite", + }, FILE_NAME, FILE_LEN, "conflictAction=overwrite"); + + // Try to download in invalid url + yield 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. + yield 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"); + yield 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) { + yield 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 .. + yield 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 .. + yield 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"); + }); + + // Try to download a blob url + const BLOB_STRING = "Hello, world"; + yield 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 + yield testDownload({ + blobme: [BLOB_STRING], + }, "download", BLOB_STRING.length, "blob url with no filename"); + extension.sendMessage("killTheBlob"); + + 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(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}); + 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(); +}); |