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:
Neil Deakin 2022-05-03 19:44:28 +00:00
Родитель 435f7665b7
Коммит 2be09b9192
5 изменённых файлов: 1172 добавлений и 0 удалений

Просмотреть файл

@ -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(2);
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="&Tab;&NewLine;&nbsp;&#11&#x180e;&#x180e;&#12 spa ced.png &Tab;&NewLine;&nbsp;&#x180e;&#x180e;&#11&#12 "
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]