зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1746052, add tests for filename validation when dragging images, copy/paste of images, saving a document or image, and when content disposition: attachment is used, and add a unit test that verifies nsIMIMEService.validateFileNameForSaving, r=mhowell
Differential Revision: https://phabricator.services.mozilla.com/D135959
This commit is contained in:
Родитель
91d7b0f640
Коммит
af5ca09057
|
@ -108,6 +108,9 @@ support-files =
|
|||
script_redirect.html
|
||||
[browser_protocolhandler_loop.js]
|
||||
[browser_remember_download_option.js]
|
||||
[browser_save_filenames.js]
|
||||
support-files =
|
||||
save_filenames.html
|
||||
[browser_txt_download_save_as.js]
|
||||
support-files =
|
||||
file_txt_attachment_test.txt
|
||||
|
|
|
@ -0,0 +1,737 @@
|
|||
// There are at least seven different ways in a which a file can be saved or downloaded. This
|
||||
// test ensures that the filename is determined correctly when saving in these ways. The seven
|
||||
// ways are:
|
||||
// - save the file individually from the File menu
|
||||
// - save as complete web page (this uses a different codepath than the previous one)
|
||||
// - dragging an image to the local file system
|
||||
// - copy an image and paste it as a file to the local file system (windows only)
|
||||
// - open a link with content-disposition set to attachment
|
||||
// - open a link with the download attribute
|
||||
// - save a link or image from the context menu
|
||||
|
||||
requestLongerTimeout(5);
|
||||
|
||||
let types = {
|
||||
text: "text/plain",
|
||||
png: "image/png",
|
||||
jpeg: "image/jpeg",
|
||||
webp: "image/webp",
|
||||
otherimage: "image/unknown",
|
||||
// Other js types (application/javascript and text/javascript) are handled by the system
|
||||
// inconsistently, but application/x-javascript is handled by the external helper app service,
|
||||
// so it is used here for this test.
|
||||
js: "application/x-javascript",
|
||||
binary: "application/octet-stream",
|
||||
gook: "application/x-gook",
|
||||
};
|
||||
|
||||
const PNG_DATA = atob(
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAA" +
|
||||
"ACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII="
|
||||
);
|
||||
|
||||
const JPEG_DATA = atob(
|
||||
"/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4z" +
|
||||
"NDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAABAAEDASIAAhEB" +
|
||||
"AxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS" +
|
||||
"0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKz" +
|
||||
"tLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgEC" +
|
||||
"BAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpj" +
|
||||
"ZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6" +
|
||||
"/9oADAMBAAIRAxEAPwD3+iiigD//2Q=="
|
||||
);
|
||||
|
||||
const WEBP_DATA = atob(
|
||||
"UklGRiIAAABXRUJQVlA4TBUAAAAvY8AYAAfQ/4j+B4CE8H+/ENH/VCIA"
|
||||
);
|
||||
|
||||
const DEFAULT_INDEX_FILENAME =
|
||||
AppConstants.platform == "win" ? "index.htm" : "index.html";
|
||||
|
||||
const PROMISE_FILENAME_TYPE = "application/x-moz-file-promise-dest-filename";
|
||||
|
||||
let MockFilePicker = SpecialPowers.MockFilePicker;
|
||||
MockFilePicker.init(window);
|
||||
|
||||
let expectedItems;
|
||||
let sendAsAttachment = false;
|
||||
let httpServer = null;
|
||||
|
||||
function handleRequest(aRequest, aResponse) {
|
||||
const queryString = new URLSearchParams(aRequest.queryString);
|
||||
let type = queryString.get("type");
|
||||
let filename = queryString.get("filename");
|
||||
let dispname = queryString.get("dispname");
|
||||
|
||||
aResponse.setStatusLine(aRequest.httpVersion, 200);
|
||||
if (type) {
|
||||
aResponse.setHeader("Content-Type", types[type]);
|
||||
}
|
||||
|
||||
if (dispname) {
|
||||
let dispositionType = sendAsAttachment ? "attachment" : "inline";
|
||||
aResponse.setHeader(
|
||||
"Content-Disposition",
|
||||
dispositionType + ';name="' + dispname + '"'
|
||||
);
|
||||
} else if (filename) {
|
||||
let dispositionType = sendAsAttachment ? "attachment" : "inline";
|
||||
aResponse.setHeader(
|
||||
"Content-Disposition",
|
||||
dispositionType + ';filename="' + filename + '"'
|
||||
);
|
||||
} else if (sendAsAttachment) {
|
||||
aResponse.setHeader("Content-Disposition", "attachment");
|
||||
}
|
||||
|
||||
if (type == "png") {
|
||||
aResponse.write(PNG_DATA);
|
||||
} else if (type == "jpeg") {
|
||||
aResponse.write(JPEG_DATA);
|
||||
} else if (type == "webp") {
|
||||
aResponse.write(WEBP_DATA);
|
||||
} else {
|
||||
aResponse.write("// Some Text");
|
||||
}
|
||||
}
|
||||
|
||||
function handleBasicImageRequest(aRequest, aResponse) {
|
||||
aResponse.setHeader("Content-Type", "image/png");
|
||||
aResponse.write(PNG_DATA);
|
||||
}
|
||||
|
||||
function handleRedirect(aRequest, aResponse) {
|
||||
const queryString = new URLSearchParams(aRequest.queryString);
|
||||
let filename = queryString.get("filename");
|
||||
|
||||
aResponse.setStatusLine(aRequest.httpVersion, 302);
|
||||
aResponse.setHeader("Location", "/bell" + filename[0] + "?" + queryString);
|
||||
}
|
||||
|
||||
function promiseDownloadFinished(list) {
|
||||
return new Promise(resolve => {
|
||||
list.addView({
|
||||
onDownloadChanged(download) {
|
||||
if (download.stopped) {
|
||||
list.removeView(this);
|
||||
resolve(download);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// nsIFile::CreateUnique crops long filenames if the path is too long, but
|
||||
// we don't know exactly how long depending on the full path length, so
|
||||
// for those save methods that use CreateUnique, instead just verify that
|
||||
// the filename starts with the right string and has the correct extension.
|
||||
function checkShortenedFilename(actual, expected) {
|
||||
if (actual.length < expected.length) {
|
||||
let actualDot = actual.lastIndexOf(".");
|
||||
let actualExtension = actual.substring(actualDot);
|
||||
let expectedExtension = expected.substring(expected.lastIndexOf("."));
|
||||
if (
|
||||
actualExtension == expectedExtension &&
|
||||
expected.startsWith(actual.substring(0, actualDot))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
add_task(async function init() {
|
||||
const { HttpServer } = ChromeUtils.import(
|
||||
"resource://testing-common/httpd.js"
|
||||
);
|
||||
httpServer = new HttpServer();
|
||||
httpServer.start(8000);
|
||||
|
||||
// Need to load the page from localhost:8000 as the download attribute
|
||||
// only applies to links from the same domain.
|
||||
let saveFilenamesPage = FileUtils.getFile(
|
||||
"CurWorkD",
|
||||
"/browser/uriloader/exthandler/tests/mochitest/save_filenames.html".split(
|
||||
"/"
|
||||
)
|
||||
);
|
||||
httpServer.registerFile("/save_filenames.html", saveFilenamesPage);
|
||||
|
||||
// A variety of different scripts are set up to better ensure uniqueness.
|
||||
httpServer.registerPathHandler("/save_filename.sjs", handleRequest);
|
||||
httpServer.registerPathHandler("/save_thename.sjs", handleRequest);
|
||||
httpServer.registerPathHandler("/getdata.png", handleRequest);
|
||||
httpServer.registerPathHandler("/base", handleRequest);
|
||||
httpServer.registerPathHandler("/basedata", handleRequest);
|
||||
httpServer.registerPathHandler("/basetext", handleRequest);
|
||||
httpServer.registerPathHandler("/text2.txt", handleRequest);
|
||||
httpServer.registerPathHandler("/text3.gonk", handleRequest);
|
||||
httpServer.registerPathHandler("/basic.png", handleBasicImageRequest);
|
||||
httpServer.registerPathHandler("/aquamarine.jpeg", handleBasicImageRequest);
|
||||
httpServer.registerPathHandler("/lazuli.exe", handleBasicImageRequest);
|
||||
httpServer.registerPathHandler("/redir", handleRedirect);
|
||||
httpServer.registerPathHandler("/bellr", handleRequest);
|
||||
httpServer.registerPathHandler("/bellg", handleRequest);
|
||||
httpServer.registerPathHandler("/bellb", handleRequest);
|
||||
|
||||
await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser,
|
||||
"http://localhost:8000/save_filenames.html"
|
||||
);
|
||||
|
||||
expectedItems = await getItems("items");
|
||||
});
|
||||
|
||||
function getItems(parentid) {
|
||||
return SpecialPowers.spawn(
|
||||
gBrowser.selectedBrowser,
|
||||
[parentid, AppConstants.platform],
|
||||
(id, platform) => {
|
||||
let elements = [];
|
||||
let elem = content.document.getElementById(id).firstElementChild;
|
||||
while (elem) {
|
||||
let filename =
|
||||
elem.dataset["filenamePlatform" + platform] || elem.dataset.filename;
|
||||
let url = elem.getAttribute("src");
|
||||
let draggable =
|
||||
elem.localName == "img" && elem.dataset.nodrag != "true";
|
||||
let unknown = elem.dataset.unknown;
|
||||
let noattach = elem.dataset.noattach;
|
||||
elements.push({ draggable, unknown, filename, url, noattach });
|
||||
elem = elem.nextElementSibling;
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function getDirectoryEntries(dir) {
|
||||
let files = [];
|
||||
let entries = dir.directoryEntries;
|
||||
while (true) {
|
||||
let file = entries.nextFile;
|
||||
if (!file) {
|
||||
break;
|
||||
}
|
||||
files.push(file.leafName);
|
||||
}
|
||||
entries.close();
|
||||
return files;
|
||||
}
|
||||
|
||||
// This test saves the document as a complete web page and verifies
|
||||
// that the resources are saved with the correct filename.
|
||||
add_task(async function save_document() {
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
|
||||
let tmp = SpecialPowers.Services.dirsvc.get("TmpD", Ci.nsIFile);
|
||||
const baseFilename = "test_save_filenames_" + Date.now();
|
||||
|
||||
let tmpFile = tmp.clone();
|
||||
tmpFile.append(baseFilename + "_document.html");
|
||||
let tmpDir = tmp.clone();
|
||||
tmpDir.append(baseFilename + "_document_files");
|
||||
|
||||
MockFilePicker.displayDirectory = tmpDir;
|
||||
MockFilePicker.showCallback = function(fp) {
|
||||
MockFilePicker.setFiles([tmpFile]);
|
||||
MockFilePicker.filterIndex = 0; // kSaveAsType_Complete
|
||||
};
|
||||
|
||||
let downloadsList = await Downloads.getList(Downloads.PUBLIC);
|
||||
let savePromise = new Promise((resolve, reject) => {
|
||||
downloadsList.addView({
|
||||
onDownloadChanged(download) {
|
||||
if (download.succeeded) {
|
||||
downloadsList.removeView(this);
|
||||
downloadsList.removeFinished();
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
saveBrowser(browser);
|
||||
await savePromise;
|
||||
|
||||
let filesSaved = getDirectoryEntries(tmpDir);
|
||||
|
||||
for (let idx = 0; idx < expectedItems.length; idx++) {
|
||||
let filename = expectedItems[idx].filename;
|
||||
if (idx == 66 && AppConstants.platform == "win") {
|
||||
// This is special-cased on Windows. The default filename will be used, since
|
||||
// the filename is invalid, but since the previous test file has the same issue,
|
||||
// this second file will be saved with a number suffix added to it. -->
|
||||
filename = "index_002";
|
||||
}
|
||||
|
||||
let file = tmpDir.clone();
|
||||
file.append(filename);
|
||||
|
||||
let fileIdx = -1;
|
||||
// Use checkShortenedFilename to check long filenames.
|
||||
if (filename.length > 240) {
|
||||
for (let t = 0; t < filesSaved.length; t++) {
|
||||
if (
|
||||
filesSaved[t].length > 60 &&
|
||||
checkShortenedFilename(filesSaved[t], filename)
|
||||
) {
|
||||
fileIdx = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fileIdx = filesSaved.indexOf(filename);
|
||||
}
|
||||
|
||||
ok(
|
||||
fileIdx >= 0,
|
||||
"file i" +
|
||||
idx +
|
||||
" " +
|
||||
filename +
|
||||
" was saved with the correct name using saveDocument"
|
||||
);
|
||||
if (fileIdx >= 0) {
|
||||
// If found, remove it from the list. At end of the test, the
|
||||
// list should be empty.
|
||||
filesSaved.splice(fileIdx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
is(filesSaved.length, 0, "all files accounted for");
|
||||
tmpDir.remove(true);
|
||||
tmpFile.remove(false);
|
||||
});
|
||||
|
||||
// This test simulates dragging the images in the document and ensuring that
|
||||
// the correct filename is used for each one.
|
||||
// On Mac, the data is added in the parent process instead, so we cannot
|
||||
// test dragging directly.
|
||||
if (AppConstants.platform != "macosx") {
|
||||
add_task(async function drag_files() {
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
|
||||
await SpecialPowers.spawn(browser, [PROMISE_FILENAME_TYPE], type => {
|
||||
content.addEventListener("dragstart", event => {
|
||||
content.draggedFile = event.dataTransfer.getData(type);
|
||||
event.preventDefault();
|
||||
});
|
||||
});
|
||||
|
||||
for (let idx = 0; idx < expectedItems.length; idx++) {
|
||||
if (!expectedItems[idx].draggable) {
|
||||
// You can't drag non-images and invalid images.
|
||||
continue;
|
||||
}
|
||||
|
||||
await BrowserTestUtils.synthesizeMouse(
|
||||
"#i" + idx,
|
||||
1,
|
||||
1,
|
||||
{ type: "mousedown" },
|
||||
browser
|
||||
);
|
||||
await BrowserTestUtils.synthesizeMouse(
|
||||
"#i" + idx,
|
||||
11,
|
||||
11,
|
||||
{ type: "mousemove" },
|
||||
browser
|
||||
);
|
||||
await BrowserTestUtils.synthesizeMouse(
|
||||
"#i" + idx,
|
||||
20,
|
||||
20,
|
||||
{ type: "mousemove" },
|
||||
browser
|
||||
);
|
||||
await BrowserTestUtils.synthesizeMouse(
|
||||
"#i" + idx,
|
||||
20,
|
||||
20,
|
||||
{ type: "mouseup" },
|
||||
browser
|
||||
);
|
||||
|
||||
let draggedFile = await SpecialPowers.spawn(browser, [], () => {
|
||||
let file = content.draggedFile;
|
||||
content.draggedFile = null;
|
||||
return file;
|
||||
});
|
||||
|
||||
is(
|
||||
draggedFile,
|
||||
expectedItems[idx].filename,
|
||||
"i" +
|
||||
idx +
|
||||
" " +
|
||||
expectedItems[idx].filename +
|
||||
" was saved with the correct name when dragging"
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// This test checks that copying an image provides the right filename
|
||||
// for pasting to the local file system. This is only implemented on Windows.
|
||||
if (AppConstants.platform == "win") {
|
||||
add_task(async function copy_image() {
|
||||
for (let idx = 0; idx < expectedItems.length; idx++) {
|
||||
if (!expectedItems[idx].draggable) {
|
||||
// You can't context-click on non-images.
|
||||
continue;
|
||||
}
|
||||
|
||||
let data = await SpecialPowers.spawn(
|
||||
gBrowser.selectedBrowser,
|
||||
[idx, PROMISE_FILENAME_TYPE],
|
||||
(imagenum, type) => {
|
||||
// No need to wait for the data to be really on the clipboard, we only
|
||||
// need the promise data added when the command is performed.
|
||||
SpecialPowers.setCommandNode(
|
||||
content,
|
||||
content.document.getElementById("i" + imagenum)
|
||||
);
|
||||
SpecialPowers.doCommand(content, "cmd_copyImageContents");
|
||||
|
||||
return SpecialPowers.getClipboardData(type);
|
||||
}
|
||||
);
|
||||
|
||||
is(
|
||||
data,
|
||||
expectedItems[idx].filename,
|
||||
"i" +
|
||||
idx +
|
||||
" " +
|
||||
expectedItems[idx].filename +
|
||||
" was saved with the correct name when copying"
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// This test checks the default filename selected when selecting to save
|
||||
// a file from either the context menu or what would happen when save page
|
||||
// as was selected from the file menu. Note that this tests a filename assigned
|
||||
// when using content-disposition: inline.
|
||||
add_task(async function saveas_files() {
|
||||
// Iterate over each item and try saving them from the context menu,
|
||||
// and the Save Page As command on the File menu.
|
||||
for (let testname of ["context menu", "save page as"]) {
|
||||
for (let idx = 0; idx < expectedItems.length; idx++) {
|
||||
let menu;
|
||||
if (testname == "context menu") {
|
||||
if (!expectedItems[idx].draggable) {
|
||||
// You can't context-click on non-images.
|
||||
continue;
|
||||
}
|
||||
|
||||
menu = document.getElementById("contentAreaContextMenu");
|
||||
let popupShown = BrowserTestUtils.waitForEvent(menu, "popupshown");
|
||||
BrowserTestUtils.synthesizeMouse(
|
||||
"#i" + idx,
|
||||
5,
|
||||
5,
|
||||
{ type: "contextmenu", button: 2 },
|
||||
gBrowser.selectedBrowser
|
||||
);
|
||||
await popupShown;
|
||||
} else {
|
||||
if (expectedItems[idx].unknown == "typeonly") {
|
||||
// Items marked with unknown="typeonly" have unknown content types and
|
||||
// will be downloaded instead of opened in a tab.
|
||||
let list = await Downloads.getList(Downloads.PUBLIC);
|
||||
let downloadFinishedPromise = promiseDownloadFinished(list);
|
||||
|
||||
await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser,
|
||||
expectedItems[idx].url
|
||||
);
|
||||
|
||||
let download = await downloadFinishedPromise;
|
||||
|
||||
let filename = PathUtils.filename(download.target.path);
|
||||
is(
|
||||
filename,
|
||||
expectedItems[idx].filename,
|
||||
"open link" +
|
||||
idx +
|
||||
" " +
|
||||
expectedItems[idx].filename +
|
||||
" was downloaded with the correct name when opened as a url"
|
||||
);
|
||||
|
||||
try {
|
||||
await IOUtils.remove(download.target.path);
|
||||
} catch (ex) {}
|
||||
|
||||
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
continue;
|
||||
}
|
||||
|
||||
await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser,
|
||||
expectedItems[idx].url
|
||||
);
|
||||
}
|
||||
|
||||
let filename = await new Promise(resolve => {
|
||||
MockFilePicker.showCallback = function(fp) {
|
||||
setTimeout(() => {
|
||||
resolve(fp.defaultString);
|
||||
}, 0);
|
||||
return Ci.nsIFilePicker.returnCancel;
|
||||
};
|
||||
|
||||
if (testname == "context menu") {
|
||||
let menuitem = document.getElementById("context-saveimage");
|
||||
menu.activateItem(menuitem);
|
||||
} else if (testname == "save page as") {
|
||||
document.getElementById("Browser:SavePage").doCommand();
|
||||
}
|
||||
});
|
||||
|
||||
// Trying to open an unknown or binary type will just open a blank
|
||||
// page, so trying to save will just save the blank page with the
|
||||
// filename index.html.
|
||||
let expectedFilename = expectedItems[idx].unknown
|
||||
? DEFAULT_INDEX_FILENAME
|
||||
: expectedItems[idx].filename;
|
||||
|
||||
// When saving via contentAreaUtils.js, the content disposition name
|
||||
// field is used as an alternate.
|
||||
if (expectedFilename == "save_thename.png") {
|
||||
expectedFilename = "withname.png";
|
||||
}
|
||||
|
||||
is(
|
||||
filename,
|
||||
expectedFilename,
|
||||
"i" +
|
||||
idx +
|
||||
" " +
|
||||
expectedFilename +
|
||||
" was saved with the correct name " +
|
||||
testname
|
||||
);
|
||||
|
||||
if (testname == "save page as") {
|
||||
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// This test checks that links that result in files with
|
||||
// content-disposition: attachment are saved with the right filenames.
|
||||
add_task(async function save_links() {
|
||||
sendAsAttachment = true;
|
||||
|
||||
// Create some links based on each image and insert them into the document.
|
||||
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
|
||||
let doc = content.document;
|
||||
let insertPos = doc.getElementById("attachment-links");
|
||||
|
||||
let idx = 0;
|
||||
let elem = doc.getElementById("items").firstElementChild;
|
||||
while (elem) {
|
||||
let attachmentlink = doc.createElement("a");
|
||||
attachmentlink.id = "attachmentlink" + idx;
|
||||
attachmentlink.href = elem.localName == "object" ? elem.data : elem.src;
|
||||
attachmentlink.textContent = elem.dataset.filename;
|
||||
insertPos.appendChild(attachmentlink);
|
||||
insertPos.appendChild(doc.createTextNode(" "));
|
||||
|
||||
elem = elem.nextElementSibling;
|
||||
idx++;
|
||||
}
|
||||
});
|
||||
|
||||
let list = await Downloads.getList(Downloads.PUBLIC);
|
||||
|
||||
for (let idx = 0; idx < expectedItems.length; idx++) {
|
||||
// Skip the items that won't have a content-disposition.
|
||||
if (expectedItems[idx].noattach) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let downloadFinishedPromise = promiseDownloadFinished(list);
|
||||
|
||||
BrowserTestUtils.synthesizeMouse(
|
||||
"#attachmentlink" + idx,
|
||||
5,
|
||||
5,
|
||||
{},
|
||||
gBrowser.selectedBrowser
|
||||
);
|
||||
|
||||
let download = await downloadFinishedPromise;
|
||||
|
||||
let filename = PathUtils.filename(download.target.path);
|
||||
|
||||
let expectedFilename = expectedItems[idx].filename;
|
||||
if (AppConstants.platform == "win" && idx == 54) {
|
||||
// On Windows, .txt is added when saving as an attachment
|
||||
// to avoid this looking like an executable. This
|
||||
// is done in validateLeafName in HelperAppDlg.jsm.
|
||||
// XXXndeakin should we do this for all save mechanisms?
|
||||
expectedFilename += ".txt";
|
||||
}
|
||||
|
||||
// Use checkShortenedFilename to check long filenames.
|
||||
if (expectedItems[idx].filename.length > 240) {
|
||||
ok(
|
||||
checkShortenedFilename(filename, expectedFilename),
|
||||
"attachmentlink" +
|
||||
idx +
|
||||
" " +
|
||||
expectedFilename +
|
||||
" was saved with the correct name when opened as attachment (with long name)"
|
||||
);
|
||||
} else {
|
||||
is(
|
||||
filename,
|
||||
expectedFilename,
|
||||
"attachmentlink" +
|
||||
idx +
|
||||
" " +
|
||||
expectedFilename +
|
||||
" was saved with the correct name when opened as attachment"
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await IOUtils.remove(download.target.path);
|
||||
} catch (ex) {}
|
||||
}
|
||||
|
||||
sendAsAttachment = false;
|
||||
});
|
||||
|
||||
// This test checks some cases where links to images are saved using Save Link As,
|
||||
// and when opening them in a new tab and then using Save Page As.
|
||||
add_task(async function saveas_image_links() {
|
||||
let links = await getItems("links");
|
||||
|
||||
// Iterate over each link and try saving the links from the context menu,
|
||||
// and then after opening a new tab for that link and then selecting
|
||||
// the Save Page As command on the File menu.
|
||||
for (let testname of ["save link as", "save link then save page as"]) {
|
||||
for (let idx = 0; idx < links.length; idx++) {
|
||||
let menu = document.getElementById("contentAreaContextMenu");
|
||||
let popupShown = BrowserTestUtils.waitForEvent(menu, "popupshown");
|
||||
BrowserTestUtils.synthesizeMouse(
|
||||
"#link" + idx,
|
||||
5,
|
||||
5,
|
||||
{ type: "contextmenu", button: 2 },
|
||||
gBrowser.selectedBrowser
|
||||
);
|
||||
await popupShown;
|
||||
|
||||
let promptPromise = new Promise(resolve => {
|
||||
MockFilePicker.showCallback = function(fp) {
|
||||
setTimeout(() => {
|
||||
resolve(fp.defaultString);
|
||||
}, 0);
|
||||
return Ci.nsIFilePicker.returnCancel;
|
||||
};
|
||||
});
|
||||
|
||||
if (testname == "save link as") {
|
||||
let menuitem = document.getElementById("context-savelink");
|
||||
menu.activateItem(menuitem);
|
||||
} else {
|
||||
let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser);
|
||||
|
||||
let menuitem = document.getElementById("context-openlinkintab");
|
||||
menu.activateItem(menuitem);
|
||||
|
||||
let tab = await newTabPromise;
|
||||
await BrowserTestUtils.switchTab(gBrowser, tab);
|
||||
|
||||
document.getElementById("Browser:SavePage").doCommand();
|
||||
}
|
||||
|
||||
let filename = await promptPromise;
|
||||
|
||||
let expectedFilename = links[idx].filename;
|
||||
// Only codepaths that go through contentAreaUtils.js use the
|
||||
// name from the content disposition.
|
||||
if (testname == "save link as" && expectedFilename == "four.png") {
|
||||
expectedFilename = "save_filename.png";
|
||||
}
|
||||
|
||||
is(
|
||||
filename,
|
||||
expectedFilename,
|
||||
"i" +
|
||||
idx +
|
||||
" " +
|
||||
expectedFilename +
|
||||
" link was saved with the correct name " +
|
||||
testname
|
||||
);
|
||||
|
||||
if (testname == "save link then save page as") {
|
||||
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// This test checks that links that with a download attribute
|
||||
// are saved with the right filenames.
|
||||
add_task(async function save_download_links() {
|
||||
let downloads = await getItems("downloads");
|
||||
|
||||
let list = await Downloads.getList(Downloads.PUBLIC);
|
||||
for (let idx = 0; idx < downloads.length; idx++) {
|
||||
let downloadFinishedPromise = promiseDownloadFinished(list);
|
||||
|
||||
BrowserTestUtils.synthesizeMouse(
|
||||
"#download" + idx,
|
||||
2,
|
||||
2,
|
||||
{},
|
||||
gBrowser.selectedBrowser
|
||||
);
|
||||
|
||||
let download = await downloadFinishedPromise;
|
||||
|
||||
let filename = PathUtils.filename(download.target.path);
|
||||
|
||||
if (downloads[idx].filename.length > 240) {
|
||||
ok(
|
||||
checkShortenedFilename(filename, downloads[idx].filename),
|
||||
"download" +
|
||||
idx +
|
||||
" " +
|
||||
downloads[idx].filename +
|
||||
" was saved with the correct name when link has download attribute"
|
||||
);
|
||||
} else {
|
||||
is(
|
||||
filename,
|
||||
downloads[idx].filename,
|
||||
"download" +
|
||||
idx +
|
||||
" " +
|
||||
downloads[idx].filename +
|
||||
" was saved with the correct name when link has download attribute"
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await IOUtils.remove(download.target.path);
|
||||
} catch (ex) {}
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async () => {
|
||||
BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
MockFilePicker.cleanup();
|
||||
await new Promise(resolve => httpServer.stop(resolve));
|
||||
});
|
|
@ -0,0 +1,296 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
</head>
|
||||
<body>
|
||||
<style>
|
||||
img { padding: 10px; border: 1px solid red; }
|
||||
a { padding-left: 10px; }
|
||||
</style>
|
||||
|
||||
<span id="items">
|
||||
|
||||
<!-- simple filename -->
|
||||
<img id="i0" src="http://localhost:8000/basic.png"
|
||||
data-noattach="true" data-filename="basic.png">
|
||||
|
||||
<!-- simple filename with content disposition -->
|
||||
<img id="i1" src="http://localhost:8000/save_filename.sjs?type=png&filename=simple.png" data-filename="simple.png">
|
||||
|
||||
<!-- invalid characters in the filename -->
|
||||
<img id="i2" src="http://localhost:8000/save_filename.sjs?type=png&filename=invalidfilename/a:b*c%63d.png" data-filename="invalidfilename_a b ccd.png">
|
||||
|
||||
<!-- invalid extension for a png image -->
|
||||
<img id="i3" src="http://localhost:8000/save_filename.sjs?type=png&filename=invalidextension.pang" data-filename="invalidextension.png">
|
||||
|
||||
<!-- jpeg extension for a png image -->
|
||||
<img id="i4" src="http://localhost:8000/save_filename.sjs?type=png&filename=reallyapng.jpeg" data-filename="reallyapng.png">
|
||||
|
||||
<!-- txt extension for a png image -->
|
||||
<img id="i5" src="http://localhost:8000/save_filename.sjs?type=png&filename=nottext.txt" data-filename="nottext.png">
|
||||
|
||||
<!-- no extension for a png image -->
|
||||
<img id="i6" src="http://localhost:8000/save_filename.sjs?type=png&filename=noext" data-filename="noext.png">
|
||||
|
||||
<!-- empty extension for a png image -->
|
||||
<img id="i7" src="http://localhost:8000/save_filename.sjs?type=png&filename=noextdot." data-filename="noextdot.png">
|
||||
|
||||
<!-- long filename -->
|
||||
<img id="i8" src="http://localhost:8000/save_filename.sjs?type=png&filename=averylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilename.png"
|
||||
data-filename="averylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfi.png">
|
||||
|
||||
<!-- long filename with invalid extension -->
|
||||
<img id="i9" src="http://localhost:8000/save_filename.sjs?type=png&filename=bverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilename.exe"
|
||||
data-filename="bverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfi.png">
|
||||
|
||||
<!-- long filename with invalid extension -->
|
||||
<img id="i10" src="http://localhost:8000/save_filename.sjs?type=png&filename=cverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilename.exe.jpg"
|
||||
data-filename="cverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfilenameverylongfi.png">
|
||||
|
||||
<!-- jpeg with jpg extension -->
|
||||
<img id="i11" src="http://localhost:8000/save_filename.sjs?type=jpeg&filename=thejpg.jpg" data-filename="thejpg.jpg">
|
||||
|
||||
<!-- jpeg with jpeg extension -->
|
||||
<img id="i12" src="http://localhost:8000/save_filename.sjs?type=jpeg&filename=thejpg.jpeg" data-filename="thejpg.jpeg">
|
||||
|
||||
<!-- jpeg with invalid extension -->
|
||||
<img id="i13" src="http://localhost:8000/save_filename.sjs?type=jpeg&filename=morejpg.exe" data-filename="morejpg.jpg" data-filename-platformlinux="morejpg.jpeg">
|
||||
|
||||
<!-- jpeg with multiple extensions -->
|
||||
<img id="i14" src="http://localhost:8000/save_filename.sjs?type=jpeg&filename=anotherjpg.jpg.exe" data-filename="anotherjpg.jpg.jpg" data-filename-platformlinux="anotherjpg.jpg.jpeg">
|
||||
|
||||
<!-- jpeg with no filename portion -->
|
||||
<img id="i15" src="http://localhost:8000/save_filename.sjs?type=jpeg&filename=.jpg" data-filename="index.jpg">
|
||||
|
||||
<!-- png with no filename portion and invalid extension -->
|
||||
<img id="i16" src="http://localhost:8000/save_filename.sjs?type=png&filename=.exe" data-filename="index.png">
|
||||
|
||||
<!-- png with escaped characters -->
|
||||
<img id="i17" src="http://localhost:8000/save_filename.sjs?type=png&filename=first%20file.png" data-filename="first file.png">
|
||||
|
||||
<!-- png with more escaped characters -->
|
||||
<img id="i18" src="http://localhost:8000/save_filename.sjs?type=png&filename=second%32file%2Eexe" data-filename="second2file.png">
|
||||
|
||||
<!-- unknown type with png extension -->
|
||||
<img id="i19" src="http://localhost:8000/save_filename.sjs?type=gook&filename=gook1.png"
|
||||
data-nodrag="true" data-unknown="typeonly" data-filename="gook1.png">
|
||||
|
||||
<!-- unknown type with exe extension -->
|
||||
<img id="i20" src="http://localhost:8000/save_filename.sjs?type=gook&filename=gook2.exe"
|
||||
data-nodrag="true" data-unknown="typeonly" data-filename="gook2.exe">
|
||||
|
||||
<!-- unknown type with no extension -->
|
||||
<img id="i21" src="http://localhost:8000/save_filename.sjs?type=gook&filename=gook3"
|
||||
data-nodrag="true" data-unknown="typeonly" data-filename="gook3">
|
||||
|
||||
<!-- simple script -->
|
||||
<script id="i22" src="http://localhost:8000/save_filename.sjs?type=js&filename=script1.js" data-filename="script1.js"></script>
|
||||
|
||||
<!-- script with invalid extension. Windows doesn't have an association for application/x-javascript
|
||||
so doesn't handle it. -->
|
||||
<script id="i23" src="http://localhost:8000/save_filename.sjs?type=js&filename=script2.exe"
|
||||
data-filename="script2.exe.js"></script>
|
||||
|
||||
<!-- script with escaped characters -->
|
||||
<script id="i24" src="http://localhost:8000/save_filename.sjs?type=js&filename=script%20%33.exe"
|
||||
data-filename="script 3.exe.js"></script>
|
||||
|
||||
<!-- script with long filename -->
|
||||
<script id="i25" src="http://localhost:8000/save_filename.sjs?type=js&filename=script123456789script123456789script123456789script123456789script123456789script123456789script123456789script123456789script123456789script123456789script123456789script123456789script123456789script123456789script123456789script123456789script123456789script123456789.js"
|
||||
data-filename="script123456789script123456789script123456789script123456789script123456789script123456789script123456789script123456789script123456789script123456789script123456789script123456789script123456789script123456789script123456789script123456789script123456.js"></script>
|
||||
|
||||
<!-- binary with exe extension -->
|
||||
<object id="i26" data="http://localhost:8000/save_filename.sjs?type=binary&filename=download1.exe"
|
||||
data-unknown="true" data-filename="download1.exe"></object>
|
||||
|
||||
<!-- binary with invalid extension -->
|
||||
<object id="i27" data="http://localhost:8000/save_filename.sjs?type=binary&filename=download2.png"
|
||||
data-unknown="true" data-filename="download2.png"></object>
|
||||
|
||||
<!-- binary with no extension -->
|
||||
<object id="i28" data="http://localhost:8000/save_filename.sjs?type=binary&filename=downloadnoext"
|
||||
data-unknown="true" data-filename="downloadnoext"></object>
|
||||
|
||||
<!-- binary with no other invalid characters -->
|
||||
<object id="i29" data="http://localhost:8000/save_filename.sjs?type=binary&filename=binary^%31%20exe.exe"
|
||||
data-unknown="true" data-filename="binary^1 exe.exe"></object>
|
||||
|
||||
<!-- unknown image type with no extension, but ending in png -->
|
||||
<img id="i30" src="http://localhost:8000/save_filename.sjs?type=otherimage&filename=specialpng"
|
||||
data-unknown="typeonly" data-nodrag="true" data-filename="specialpng">
|
||||
|
||||
<!-- unknown image type with no extension, but ending in many dots -->
|
||||
<img id="i31" src="http://localhost:8000/save_filename.sjs?type=otherimage&filename=extrapng..."
|
||||
data-unknown="typeonly" data-nodrag="true" data-filename="extrapng">
|
||||
|
||||
<!-- image type with no content-disposition filename specified -->
|
||||
<img id="i32" src="http://localhost:8000/save_filename.sjs?type=png" data-filename="save_filename.png">
|
||||
|
||||
<!-- binary with no content-disposition filename specified -->
|
||||
<object id="i33" data="http://localhost:8000/save_filename.sjs?type=binary"
|
||||
data-unknown="true" data-filename="save_filename.sjs"></object>
|
||||
|
||||
<!-- image where url has png extension -->
|
||||
<img id="i34" src="http://localhost:8000/getdata.png?type=png&filename=override.png" data-filename="override.png">
|
||||
|
||||
<!-- image where url has png extension but content disposition has incorrect extension -->
|
||||
<img id="i35" src="http://localhost:8000/getdata.png?type=png&filename=flower.jpeg" data-filename="flower.png">
|
||||
|
||||
<!-- image where url has png extension but content disposition does not -->
|
||||
<img id="i36" src="http://localhost:8000/getdata.png?type=png&filename=ruby" data-filename="ruby.png">
|
||||
|
||||
<!-- image where url has png extension but content disposition has invalid characters -->
|
||||
<img id="i37" src="http://localhost:8000/getdata.png?type=png&filename=sapphire/data" data-filename="sapphire_data.png">
|
||||
|
||||
<!-- image where neither content disposition or url have an extension -->
|
||||
<img id="i38" src="http://localhost:8000/base?type=png&filename=emerald" data-filename="emerald.png">
|
||||
|
||||
<!-- image where filename is not specified -->
|
||||
<img id="i39" src="http://localhost:8000/base?type=png" data-filename="base.png">
|
||||
|
||||
<!-- simple script where url filename has no extension -->
|
||||
<script id="i40" src="http://localhost:8000/base?type=js&filename=script4.js" data-filename="script4.js"></script>
|
||||
|
||||
<!-- script where url filename has no extension and invalid extension in content disposition filename -->
|
||||
<script id="i41" src="http://localhost:8000/base?type=js&filename=script5.exe"
|
||||
data-filename="script5.exe.js"></script>
|
||||
|
||||
<!-- script where url filename has no extension and escaped characters in content disposition filename-->
|
||||
<script id="i42" src="http://localhost:8000/base?type=js&filename=script%20%36.exe"
|
||||
data-filename="script 6.exe.js"></script>
|
||||
|
||||
<!-- text where filename is present -->
|
||||
<img id="i43" src="http://localhost:8000/getdata.png?type=text&filename=readme.txt"
|
||||
data-nodrag="true" data-filename="readme.txt">
|
||||
|
||||
<!-- text where filename is present with a different extension -->
|
||||
<img id="i44" src="http://localhost:8000/getdata.png?type=text&filename=main.cpp"
|
||||
data-nodrag="true" data-filename="main.cpp">
|
||||
|
||||
<!-- text where extension is not present -->
|
||||
<img id="i45" src="http://localhost:8000/getdata.png?type=text&filename=readme"
|
||||
data-nodrag="true" data-filename="readme">
|
||||
|
||||
<!-- text where extension is not present and url does not have extension -->
|
||||
<img id="i46" src="http://localhost:8000/base?type=text&filename=info"
|
||||
data-nodrag="true" data-filename="info">
|
||||
|
||||
<!-- text where filename is not present -->
|
||||
<img id="i47" src="http://localhost:8000/basetext?type=text"
|
||||
data-nodrag="true" data-filename="basetext">
|
||||
|
||||
<!-- text where url has extension -->
|
||||
<img id="i48" src="http://localhost:8000/text2.txt?type=text"
|
||||
data-nodrag="true" data-filename="text2.txt">
|
||||
|
||||
<!-- text where url has extension -->
|
||||
<img id="i49" src="http://localhost:8000/text3.gonk?type=text"
|
||||
data-nodrag="true" data-filename="text3.gonk">
|
||||
|
||||
<!-- text with long filename -->
|
||||
<img id="i50" src="http://localhost:8000/text3.gonk?type=text&filename=text0123456789zztext0123456789zztext0123456789zztext0123456789zztext0123456789zztext0123456789zztext0123456789zztext0123456789zztext0123456789zztext0123456789zztext0123456789zztext0123456789zztext0123456789zztext0123456789zztext0123456789zztext0123456789text0123456789zztext0123456789zztext0123456789zztext01234567.exe.txt" data-nodrag="true" data-filename="text0123456789zztext0123456789zztext0123456789zztext0123456789zztext0123456789zztext0123456789zztext0123456789zztext0123456789zztext0123456789zztext0123456789zztext0123456789zztext0123456789zztext0123456789zztext0123456789zztext0123456789zztext0123456.txt">
|
||||
|
||||
<!-- webp image -->
|
||||
<img id="i51" src="http://localhost:8000/save_filename.sjs?type=webp&filename=webpimage.webp"
|
||||
data-filename="webpimage.webp">
|
||||
|
||||
<!-- webp image with jpg extension -->
|
||||
<img id="i52" src="http://localhost:8000/save_filename.sjs?type=webp&filename=realwebpimage.jpg"
|
||||
data-filename="realwebpimage.webp">
|
||||
|
||||
<!-- no content type specified -->
|
||||
<img id="i53" src="http://localhost:8000/save_filename.sjs?&filename=notype.png"
|
||||
data-nodrag="true" data-filename="notype.png">
|
||||
|
||||
<!-- no content type specified. Note that on Windows, .txt is
|
||||
appended when saving as an attachment. This is special-cased
|
||||
in browser_save_filenames.js. -->
|
||||
<img id="i54" src="http://localhost:8000/save_filename.sjs?&filename=notypebin.exe"
|
||||
data-nodrag="true" data-filename="notypebin.exe">
|
||||
|
||||
<!-- extension contains invalid characters -->
|
||||
<img id="i55" src="http://localhost:8000/save_filename.sjs?type=png&filename=extinvalid.a?*"
|
||||
data-filename="extinvalid.png">
|
||||
|
||||
<!-- filename with redirect and content disposition -->
|
||||
<img id="i56" src="http://localhost:8000/redir?type=png&filename=red.png" data-filename="red.png">
|
||||
|
||||
<!-- filename with redirect and different type -->
|
||||
<img id="i57" src="http://localhost:8000/redir?type=jpeg&filename=green.png"
|
||||
data-filename="green.jpg" data-filename-platformlinux="green.jpeg">
|
||||
|
||||
<!-- filename with redirect and binary type -->
|
||||
<object id="i58" data="http://localhost:8000/redir?type=binary&filename=blue.png"
|
||||
data-unknown="true" data-filename="blue.png"></object>
|
||||
|
||||
<!-- filename in url with incorrect extension -->
|
||||
<img id="i59" src="http://localhost:8000/aquamarine.jpeg"
|
||||
data-noattach="true" data-filename="aquamarine.png">
|
||||
|
||||
<!-- filename in url with exe extension, but returns a png image -->
|
||||
<img id="i60" src="http://localhost:8000/lazuli.exe"
|
||||
data-noattach="true" data-filename="lazuli.png">
|
||||
|
||||
<!-- filename with leading, trailing and duplicate spaces -->
|
||||
<img id="i61" src="http://localhost:8000/save_filename.sjs?type=png&filename= with spaces.png "
|
||||
data-filename="with spaces.png">
|
||||
|
||||
<!-- filename with leading and trailing periods -->
|
||||
<img id="i62" src="http://localhost:8000/save_filename.sjs?type=png&filename=..with..dots..png.."
|
||||
data-filename="with..dots..png">
|
||||
|
||||
<!-- filename with non-ascii character -->
|
||||
<img id="i63" src="http://localhost:8000/base?type=png&filename=s%C3%B6meescapes.%C3%B7ng" data-filename="sömeescapes.png">
|
||||
|
||||
<!-- filename with content disposition name assigned. The name is only used
|
||||
when selecting to manually save, otherwise it is ignored. -->
|
||||
<img id="i64" src="http://localhost:8000/save_thename.sjs?type=png&dispname=withname"
|
||||
data-filename="save_thename.png">
|
||||
|
||||
<!-- reserved filename on Windows -->
|
||||
<img id="i65" src="http://localhost:8000/save_filename.sjs?type=text&filename=com1"
|
||||
data-nodrag="true" data-filename="com1" data-filename-platformwin="index">
|
||||
|
||||
<!-- reserved filename with extension on Windows -->
|
||||
<img id="i66" src="http://localhost:8000/save_filename.sjs?type=text&filename=com2.any"
|
||||
data-nodrag="true" data-filename="com2.any" data-filename-platformwin="index">
|
||||
|
||||
</span>
|
||||
|
||||
<!-- This set is used to test the filename specified by the download attribute is validated correctly. -->
|
||||
<span id="downloads">
|
||||
<a id="download0" href="http://localhost:8000/base" download="pearl.png" data-filename="pearl.png">Link</a>
|
||||
<a id="download1" href="http://localhost:8000/save_filename.sjs?type=png" download="opal.jpeg" data-filename="opal.png">Link</a>
|
||||
<a id="download2" href="http://localhost:8000/save_filename.sjs?type=jpeg"
|
||||
download="amethyst.png" data-filename="amethyst.jpg"
|
||||
data-filename-platformlinux="amethyst.jpeg">Link</a>
|
||||
<a id="download3" href="http://localhost:8000/save_filename.sjs?type=text"
|
||||
download="onyx.png" data-filename="onyx.png">Link</a>
|
||||
<!-- The content-disposition overrides the download attribute. -->
|
||||
<a id="download4" href="http://localhost:8000/save_filename.sjs?type=png&filename=fakename.jpeg" download="topaz.jpeg" data-filename="fakename.png">Link</a>
|
||||
<a id="download5" href="http://localhost:8000/save_filename.sjs?type=png"
|
||||
download="amber?.png" data-filename="amber .png">Link</a>
|
||||
<a id="download6" href="http://localhost:8000/save_filename.sjs?type=jpeg"
|
||||
download="jade.:*jpeg" data-filename="jade.jpg"
|
||||
data-filename-platformlinux="jade.jpeg">Link</a>>
|
||||
<a id="download7" href="http://localhost:8000/save_filename.sjs?type=png"
|
||||
download="thisisaverylongfilename-thisisaverylongfilename-thisisaverylongfilename-thisisaverylongfilename-thisisaverylongfilename-thisisaverylongfilename-thisisaverylongfilename-thisisaverylongfilename-thisisaverylongfilename-thisisaverylongfilename-thisisaverylongfilename-thisisaverylongfilename.png"
|
||||
data-filename="thisisaverylongfilename-thisisaverylongfilename-thisisaverylongfilename-thisisaverylongfilename-thisisaverylongfilename-thisisaverylongfilename-thisisaverylongfilename-thisisaverylongfilename-thisisaverylongfilename-thisisaverylongfilename-thisisavery.png">Link</a>
|
||||
<a id="download8" href="http://localhost:8000/base"
|
||||
download="	
 ᠎᠎ spa ced.png 	
 ᠎᠎ "
|
||||
data-filename="spa ced.png">Link</a>
|
||||
</span>
|
||||
|
||||
<span id="links">
|
||||
<a id="link0" href="http://localhost:8000/save_filename.sjs?type=png&filename=one.png" data-filename="one.png">One</a>
|
||||
<a id="link1" href="http://localhost:8000/save_filename.sjs?type=png&filename=two.jpeg" data-filename="two.png">Two</a>
|
||||
<a id="link2" href="http://localhost:8000/save_filename.sjs?type=png&filename=three.con" data-filename="three.png">Three</a>
|
||||
<a id="link3" href="http://localhost:8000/save_filename.sjs?type=png&dispname=four" data-filename="four.png">Four</a>
|
||||
</span>
|
||||
|
||||
<!-- The content-disposition attachment generates links from the images/objects/scripts above
|
||||
and inserts them here. -->
|
||||
<p id="attachment-links">
|
||||
</p>
|
||||
|
||||
</body></html>
|
|
@ -0,0 +1,135 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// This test verifies that
|
||||
// nsIMIMEService.validateFileNameForSaving sanitizes filenames
|
||||
// properly with different flags.
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(async function validate_filename_method() {
|
||||
let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
|
||||
|
||||
function checkFilename(filename, flags) {
|
||||
return mimeService.validateFileNameForSaving(filename, "image/png", flags);
|
||||
}
|
||||
|
||||
Assert.equal(checkFilename("basicfile.png", 0), "basicfile.png");
|
||||
Assert.equal(checkFilename(" whitespace.png ", 0), "whitespace.png");
|
||||
Assert.equal(
|
||||
checkFilename(" .whitespaceanddots.png...", 0),
|
||||
"whitespaceanddots.png"
|
||||
);
|
||||
Assert.equal(
|
||||
checkFilename(" \u00a0 \u00a0 extrawhitespace.png \u00a0 \u00a0 ", 0),
|
||||
"extrawhitespace.png"
|
||||
);
|
||||
Assert.equal(
|
||||
checkFilename(" filename with whitespace.png ", 0),
|
||||
"filename with whitespace.png"
|
||||
);
|
||||
Assert.equal(checkFilename("\\path.png", 0), "_path.png");
|
||||
Assert.equal(
|
||||
checkFilename("\\path*and/$?~file.png", 0),
|
||||
"_path and_$ ~file.png"
|
||||
);
|
||||
Assert.equal(
|
||||
checkFilename(" \u180e whit\u180ee.png \u180e", 0),
|
||||
"whit\u180ee.png"
|
||||
);
|
||||
Assert.equal(checkFilename("簡単簡単簡単", 0), "簡単簡単簡単.png");
|
||||
Assert.equal(checkFilename(" happy\u061c\u2069.png", 0), "happy__.png");
|
||||
Assert.equal(
|
||||
checkFilename("12345678".repeat(31) + "abcdefgh.png", 0),
|
||||
"12345678".repeat(31) + "abc.png"
|
||||
);
|
||||
Assert.equal(
|
||||
checkFilename("簡単".repeat(41) + ".png", 0),
|
||||
"簡単".repeat(41) + ".png"
|
||||
);
|
||||
Assert.equal(
|
||||
checkFilename("簡単".repeat(42) + ".png", 0),
|
||||
"簡単".repeat(41) + "簡.png"
|
||||
);
|
||||
Assert.equal(
|
||||
checkFilename("簡単".repeat(56) + ".png", 0),
|
||||
"簡単".repeat(41) + "簡.png"
|
||||
);
|
||||
Assert.equal(checkFilename("café.png", 0), "café.png");
|
||||
Assert.equal(
|
||||
checkFilename("café".repeat(50) + ".png", 0),
|
||||
"café".repeat(50) + ".png"
|
||||
);
|
||||
Assert.equal(
|
||||
checkFilename("café".repeat(51) + ".png", 0),
|
||||
"café".repeat(50) + "c.png"
|
||||
);
|
||||
|
||||
Assert.equal(
|
||||
checkFilename("\u{100001}\u{100002}.png", 0),
|
||||
"\u{100001}\u{100002}.png"
|
||||
);
|
||||
Assert.equal(
|
||||
checkFilename("\u{100001}\u{100002}".repeat(31) + ".png", 0),
|
||||
"\u{100001}\u{100002}".repeat(31) + ".png"
|
||||
);
|
||||
Assert.equal(
|
||||
checkFilename("\u{100001}\u{100002}".repeat(32) + ".png", 0),
|
||||
"\u{100001}\u{100002}".repeat(31) + ".png"
|
||||
);
|
||||
|
||||
Assert.equal(
|
||||
checkFilename("noextensionfile".repeat(16), 0),
|
||||
"noextensionfile".repeat(16) + ".png"
|
||||
);
|
||||
Assert.equal(
|
||||
checkFilename("noextensionfile".repeat(17), 0),
|
||||
"noextensionfile".repeat(16) + "noextension.png"
|
||||
);
|
||||
Assert.equal(
|
||||
checkFilename("noextensionfile".repeat(16) + "noextensionfil.", 0),
|
||||
"noextensionfile".repeat(16) + "noextension.png"
|
||||
);
|
||||
|
||||
Assert.equal(checkFilename(" first .png ", 0), "first .png");
|
||||
Assert.equal(
|
||||
checkFilename(
|
||||
" second .png ",
|
||||
mimeService.VALIDATE_DONT_COLLAPSE_WHITESPACE
|
||||
),
|
||||
"second .png"
|
||||
);
|
||||
|
||||
// For whatever reason, the Android mime handler accepts the .jpeg
|
||||
// extension for image/png, so skip this test there.
|
||||
if (AppConstants.platform != "android") {
|
||||
Assert.equal(checkFilename("thi/*rd.jpeg", 0), "thi_ rd.png");
|
||||
}
|
||||
|
||||
Assert.equal(
|
||||
checkFilename("f*\\ourth file.jpg", mimeService.VALIDATE_SANITIZE_ONLY),
|
||||
"f _ourth file.jpg"
|
||||
);
|
||||
Assert.equal(
|
||||
checkFilename(
|
||||
"f*\\ift h.jpe*\\g",
|
||||
mimeService.VALIDATE_SANITIZE_ONLY |
|
||||
mimeService.VALIDATE_DONT_COLLAPSE_WHITESPACE
|
||||
),
|
||||
"f _ift h.jpe _g"
|
||||
);
|
||||
Assert.equal(checkFilename("sixth.j pe/*g", 0), "sixth.png");
|
||||
|
||||
let repeatStr = "12345678".repeat(31);
|
||||
Assert.equal(
|
||||
checkFilename(
|
||||
repeatStr + "seventh.png",
|
||||
mimeService.VALIDATE_DONT_TRUNCATE
|
||||
),
|
||||
repeatStr + "seventh.png"
|
||||
);
|
||||
Assert.equal(
|
||||
checkFilename(repeatStr + "seventh.png", 0),
|
||||
repeatStr + "sev.png"
|
||||
);
|
||||
});
|
|
@ -15,6 +15,7 @@ skip-if =
|
|||
skip-if =
|
||||
os == "android"
|
||||
appname == "thunderbird"
|
||||
[test_filename_sanitize.js]
|
||||
[test_getFromTypeAndExtension.js]
|
||||
[test_getMIMEInfo_pdf.js]
|
||||
[test_getMIMEInfo_unknown_mime_type.js]
|
||||
|
|
Загрузка…
Ссылка в новой задаче