mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-18 07:45:30 +00:00
Bug 1402279 - Part 2 - Unify the usage of the DownloadPaths module. r=mak,aswan
MozReview-Commit-ID: HEhwkyxtYTP --HG-- extra : rebase_source : d17ef98796d5a4fcac899c6319045eec02b0633e
This commit is contained in:
parent
8418fc1ba5
commit
9c4cbe557f
@ -429,7 +429,7 @@ this.downloads = class extends ExtensionAPI {
|
|||||||
return Promise.reject({message: "filename must not contain back-references (..)"});
|
return Promise.reject({message: "filename must not contain back-references (..)"});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AppConstants.platform === "win" && /[|"*?:<>]/.test(filename)) {
|
if (path.components.some(component => component != DownloadPaths.sanitize(component))) {
|
||||||
return Promise.reject({message: "filename must not contain illegal characters"});
|
return Promise.reject({message: "filename must not contain illegal characters"});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -472,19 +472,15 @@ this.downloads = class extends ExtensionAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createTarget(downloadsDir) {
|
async function createTarget(downloadsDir) {
|
||||||
let target;
|
if (!filename) {
|
||||||
if (filename) {
|
|
||||||
target = OS.Path.join(downloadsDir, filename);
|
|
||||||
} else {
|
|
||||||
let uri = Services.io.newURI(options.url);
|
let uri = Services.io.newURI(options.url);
|
||||||
|
|
||||||
let remote;
|
|
||||||
if (uri instanceof Ci.nsIURL) {
|
if (uri instanceof Ci.nsIURL) {
|
||||||
remote = uri.fileName;
|
filename = DownloadPaths.sanitize(uri.fileName);
|
||||||
}
|
}
|
||||||
target = OS.Path.join(downloadsDir, remote || "download");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let target = OS.Path.join(downloadsDir, filename || "download");
|
||||||
|
|
||||||
// Create any needed subdirectories if required by filename.
|
// Create any needed subdirectories if required by filename.
|
||||||
const dir = OS.Path.dirname(target);
|
const dir = OS.Path.dirname(target);
|
||||||
await OS.File.makeDir(dir, {from: downloadsDir});
|
await OS.File.makeDir(dir, {from: downloadsDir});
|
||||||
|
@ -14,7 +14,70 @@ this.EXPORTED_SYMBOLS = [
|
|||||||
|
|
||||||
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
|
||||||
|
"resource://gre/modules/AppConstants.jsm");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Platform-dependent regular expression used by the "sanitize" method.
|
||||||
|
*/
|
||||||
|
XPCOMUtils.defineLazyGetter(this, "gConvertToSpaceRegExp", () => {
|
||||||
|
/* eslint-disable no-control-regex */
|
||||||
|
switch (AppConstants.platform) {
|
||||||
|
// On mobile devices, the file system may be very limited in what it
|
||||||
|
// considers valid characters. To avoid errors, sanitize conservatively.
|
||||||
|
case "android":
|
||||||
|
return /[\x00-\x1f\x7f-\x9f:*?|"<>;,+=\[\]]+/g;
|
||||||
|
case "win":
|
||||||
|
return /[\x00-\x1f\x7f-\x9f:*?|]+/g;
|
||||||
|
case "macosx":
|
||||||
|
return /[\x00-\x1f\x7f-\x9f:]+/g;
|
||||||
|
default:
|
||||||
|
return /[\x00-\x1f\x7f-\x9f]+/g;
|
||||||
|
}
|
||||||
|
/* eslint-enable no-control-regex */
|
||||||
|
});
|
||||||
|
|
||||||
this.DownloadPaths = {
|
this.DownloadPaths = {
|
||||||
|
/**
|
||||||
|
* Sanitizes an arbitrary string for use as the local file name of a download.
|
||||||
|
* The input is often a document title or a manually edited name. The output
|
||||||
|
* can be an empty string if the input does not include any valid character.
|
||||||
|
*
|
||||||
|
* The length of the resulting string is not limited, because restrictions
|
||||||
|
* apply to the full path name after the target folder has been added.
|
||||||
|
*
|
||||||
|
* Splitting the base name and extension to add a counter or to identify the
|
||||||
|
* file type should only be done after the sanitization process, because it
|
||||||
|
* can alter the final part of the string or remove leading dots.
|
||||||
|
*
|
||||||
|
* Runs of slashes and backslashes are replaced with an underscore.
|
||||||
|
*
|
||||||
|
* On Windows, the angular brackets `<` and `>` are replaced with parentheses,
|
||||||
|
* and double quotes are replaced with single quotes.
|
||||||
|
*
|
||||||
|
* Runs of control characters are replaced with a space. On Mac, colons are
|
||||||
|
* also included in this group. On Windows, stars, question marks, and pipes
|
||||||
|
* are additionally included. On Android, semicolons, commas, plus signs,
|
||||||
|
* equal signs, and brackets are additionally included.
|
||||||
|
*
|
||||||
|
* Leading and trailing dots and whitespace are removed on all platforms. This
|
||||||
|
* avoids the accidental creation of hidden files on Unix and invalid or
|
||||||
|
* inaccessible file names on Windows. These characters are not removed when
|
||||||
|
* located at the end of the base name or at the beginning of the extension.
|
||||||
|
*/
|
||||||
|
sanitize(leafName) {
|
||||||
|
if (AppConstants.platform == "win") {
|
||||||
|
leafName = leafName.replace(/</g, "(")
|
||||||
|
.replace(/>/g, ")")
|
||||||
|
.replace(/"/g, "'");
|
||||||
|
}
|
||||||
|
return leafName.replace(/[\\/]+/g, "_")
|
||||||
|
.replace(gConvertToSpaceRegExp, " ")
|
||||||
|
.replace(/^[\s\u180e.]+|[\s\u180e.]+$/g, "");
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a uniquely-named file starting from the name of the provided file.
|
* Creates a uniquely-named file starting from the name of the provided file.
|
||||||
* If a file with the provided name already exists, the function attempts to
|
* If a file with the provided name already exists, the function attempts to
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
* Tests for the "DownloadPaths.jsm" JavaScript module.
|
* Tests for the "DownloadPaths.jsm" JavaScript module.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/AppConstants.jsm");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a temporary save directory.
|
* Provides a temporary save directory.
|
||||||
*
|
*
|
||||||
@ -20,6 +22,10 @@ function createTemporarySaveDirectory() {
|
|||||||
return saveDir;
|
return saveDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function testSanitize(leafName, expectedLeafName) {
|
||||||
|
do_check_eq(DownloadPaths.sanitize(leafName), expectedLeafName);
|
||||||
|
}
|
||||||
|
|
||||||
function testSplitBaseNameAndExtension(aLeafName, [aBase, aExt]) {
|
function testSplitBaseNameAndExtension(aLeafName, [aBase, aExt]) {
|
||||||
var [base, ext] = DownloadPaths.splitBaseNameAndExtension(aLeafName);
|
var [base, ext] = DownloadPaths.splitBaseNameAndExtension(aLeafName);
|
||||||
do_check_eq(base, aBase);
|
do_check_eq(base, aBase);
|
||||||
@ -41,6 +47,53 @@ function testCreateNiceUniqueFile(aTempFile, aExpectedLeafName) {
|
|||||||
do_check_eq(createdFile.leafName, aExpectedLeafName);
|
do_check_eq(createdFile.leafName, aExpectedLeafName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_task(async function test_sanitize() {
|
||||||
|
// Platform-dependent conversion of special characters to spaces.
|
||||||
|
const kSpecialChars = "A:*?|\"\"<<>>;,+=[]B][=+,;>><<\"\"|?*:C";
|
||||||
|
if (AppConstants.platform == "android") {
|
||||||
|
testSanitize(kSpecialChars, "A B C");
|
||||||
|
testSanitize(" :: Website :: ", "Website");
|
||||||
|
testSanitize("* Website!", "Website!");
|
||||||
|
testSanitize("Website | Page!", "Website Page!");
|
||||||
|
testSanitize("Directory Listing: /a/b/", "Directory Listing _a_b_");
|
||||||
|
} else if (AppConstants.platform == "win") {
|
||||||
|
testSanitize(kSpecialChars, "A ''(());,+=[]B][=+,;))(('' C");
|
||||||
|
testSanitize(" :: Website :: ", "Website");
|
||||||
|
testSanitize("* Website!", "Website!");
|
||||||
|
testSanitize("Website | Page!", "Website Page!");
|
||||||
|
testSanitize("Directory Listing: /a/b/", "Directory Listing _a_b_");
|
||||||
|
} else if (AppConstants.platform == "macosx") {
|
||||||
|
testSanitize(kSpecialChars, "A *?|\"\"<<>>;,+=[]B][=+,;>><<\"\"|?* C");
|
||||||
|
testSanitize(" :: Website :: ", "Website");
|
||||||
|
testSanitize("* Website!", "* Website!");
|
||||||
|
testSanitize("Website | Page!", "Website | Page!");
|
||||||
|
testSanitize("Directory Listing: /a/b/", "Directory Listing _a_b_");
|
||||||
|
} else {
|
||||||
|
testSanitize(kSpecialChars, kSpecialChars);
|
||||||
|
testSanitize(" :: Website :: ", ":: Website ::");
|
||||||
|
testSanitize("* Website!", "* Website!");
|
||||||
|
testSanitize("Website | Page!", "Website | Page!");
|
||||||
|
testSanitize("Directory Listing: /a/b/", "Directory Listing: _a_b_");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conversion of consecutive runs of slashes and backslashes to underscores.
|
||||||
|
testSanitize("\\ \\\\Website\\/Page// /", "_ _Website_Page_ _");
|
||||||
|
|
||||||
|
// Removal of leading and trailing whitespace and dots after conversion.
|
||||||
|
testSanitize(" Website ", "Website");
|
||||||
|
testSanitize(". . Website . Page . .", "Website . Page");
|
||||||
|
testSanitize(" File . txt ", "File . txt");
|
||||||
|
testSanitize("\f\n\r\t\v\x00\x1f\x7f\x80\x9f\xa0 . txt", "txt");
|
||||||
|
testSanitize("\u1680\u180e\u2000\u2008\u200a . txt", "txt");
|
||||||
|
testSanitize("\u2028\u2029\u202f\u205f\u3000\ufeff . txt", "txt");
|
||||||
|
|
||||||
|
// Strings with whitespace and dots only.
|
||||||
|
testSanitize(".", "");
|
||||||
|
testSanitize("..", "");
|
||||||
|
testSanitize(" ", "");
|
||||||
|
testSanitize(" . ", "");
|
||||||
|
});
|
||||||
|
|
||||||
add_task(async function test_splitBaseNameAndExtension() {
|
add_task(async function test_splitBaseNameAndExtension() {
|
||||||
// Usual file names.
|
// Usual file names.
|
||||||
testSplitBaseNameAndExtension("base", ["base", ""]);
|
testSplitBaseNameAndExtension("base", ["base", ""]);
|
||||||
|
@ -7,6 +7,7 @@ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
|||||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||||
BrowserUtils: "resource://gre/modules/BrowserUtils.jsm",
|
BrowserUtils: "resource://gre/modules/BrowserUtils.jsm",
|
||||||
Downloads: "resource://gre/modules/Downloads.jsm",
|
Downloads: "resource://gre/modules/Downloads.jsm",
|
||||||
|
DownloadPaths: "resource://gre/modules/DownloadPaths.jsm",
|
||||||
DownloadLastDir: "resource://gre/modules/DownloadLastDir.jsm",
|
DownloadLastDir: "resource://gre/modules/DownloadLastDir.jsm",
|
||||||
FileUtils: "resource://gre/modules/FileUtils.jsm",
|
FileUtils: "resource://gre/modules/FileUtils.jsm",
|
||||||
OS: "resource://gre/modules/osfile.jsm",
|
OS: "resource://gre/modules/osfile.jsm",
|
||||||
@ -1059,29 +1060,8 @@ function getDefaultFileName(aDefaultFileName, aURI, aDocument,
|
|||||||
}
|
}
|
||||||
|
|
||||||
function validateFileName(aFileName) {
|
function validateFileName(aFileName) {
|
||||||
var re = /[\/]+/g;
|
let processed = DownloadPaths.sanitize(aFileName) || "_";
|
||||||
if (navigator.appVersion.indexOf("Windows") != -1) {
|
if (AppConstants.platform == "android") {
|
||||||
re = /[\\\/\|]+/g;
|
|
||||||
aFileName = aFileName.replace(/[\"]+/g, "'");
|
|
||||||
aFileName = aFileName.replace(/[\*\:\?]+/g, " ");
|
|
||||||
aFileName = aFileName.replace(/[\<]+/g, "(");
|
|
||||||
aFileName = aFileName.replace(/[\>]+/g, ")");
|
|
||||||
} else if (navigator.appVersion.indexOf("Macintosh") != -1)
|
|
||||||
re = /[\:\/]+/g;
|
|
||||||
else if (navigator.appVersion.indexOf("Android") != -1) {
|
|
||||||
// On mobile devices, the filesystem may be very limited in what
|
|
||||||
// it considers valid characters. To avoid errors, we sanitize
|
|
||||||
// conservatively.
|
|
||||||
const dangerousChars = "*?<>|\":/\\[];,+=";
|
|
||||||
var processed = "";
|
|
||||||
for (var i = 0; i < aFileName.length; i++)
|
|
||||||
processed += aFileName.charCodeAt(i) >= 32 &&
|
|
||||||
!(dangerousChars.indexOf(aFileName[i]) >= 0) ? aFileName[i]
|
|
||||||
: "_";
|
|
||||||
|
|
||||||
// Last character should not be a space
|
|
||||||
processed = processed.trim();
|
|
||||||
|
|
||||||
// If a large part of the filename has been sanitized, then we
|
// If a large part of the filename has been sanitized, then we
|
||||||
// will use a default filename instead
|
// will use a default filename instead
|
||||||
if (processed.replace(/_/g, "").length <= processed.length / 2) {
|
if (processed.replace(/_/g, "").length <= processed.length / 2) {
|
||||||
@ -1099,10 +1079,8 @@ function validateFileName(aFileName) {
|
|||||||
processed += "." + suffix;
|
processed += "." + suffix;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return processed;
|
|
||||||
}
|
}
|
||||||
|
return processed;
|
||||||
return aFileName.replace(re, "_");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNormalizedLeafName(aFile, aDefaultExtension) {
|
function getNormalizedLeafName(aFile, aDefaultExtension) {
|
||||||
|
@ -370,14 +370,8 @@ nsUnknownContentTypeDialog.prototype = {
|
|||||||
|
|
||||||
getFinalLeafName: function (aLeafName, aFileExt)
|
getFinalLeafName: function (aLeafName, aFileExt)
|
||||||
{
|
{
|
||||||
// Remove any leading periods, since we don't want to save hidden files
|
return DownloadPaths.sanitize(aLeafName) ||
|
||||||
// automatically.
|
"unnamed" + (aFileExt ? "." + aFileExt : "");
|
||||||
aLeafName = aLeafName.replace(/^\.+/, "");
|
|
||||||
|
|
||||||
if (aLeafName == "")
|
|
||||||
aLeafName = "unnamed" + (aFileExt ? "." + aFileExt : "");
|
|
||||||
|
|
||||||
return aLeafName;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user