Bug 1891234, additional filename filter checks, r=Gijs,extension-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D208659
This commit is contained in:
Neil Deakin 2024-05-14 17:35:05 +00:00
parent 75385837ce
commit e17b53a054
6 changed files with 70 additions and 61 deletions

View file

@ -34,29 +34,29 @@ add_task(async function test_sanitize() {
// Platform-dependent conversion of special characters to spaces. // Platform-dependent conversion of special characters to spaces.
const kSpecialChars = 'A:*?|""<<>>;,+=[]B][=+,;>><<""|?*:C'; const kSpecialChars = 'A:*?|""<<>>;,+=[]B][=+,;>><<""|?*:C';
if (AppConstants.platform == "android") { if (AppConstants.platform == "android") {
testSanitize(kSpecialChars, "A B C"); testSanitize(kSpecialChars, "A________________B________________C");
testSanitize(" :: Website :: ", "Website"); testSanitize(" :: Website :: ", "__ Website __");
testSanitize("* Website!", "Website!"); testSanitize("* Website!", "_ Website!");
testSanitize("Website | Page!", "Website Page!"); testSanitize("Website | Page!", "Website _ Page!");
testSanitize("Directory Listing: /a/b/", "Directory Listing _a_b_"); testSanitize("Directory Listing: /a/b/", "Directory Listing_ _a_b_");
} else if (AppConstants.platform == "win") { } else if (AppConstants.platform == "win") {
testSanitize(kSpecialChars, "A ;,+=[]B][=+,; C"); testSanitize(kSpecialChars, "A__________;,+=[]B][=+,;__________C");
testSanitize(" :: Website :: ", "Website"); testSanitize(" :: Website :: ", "__ Website __");
testSanitize("* Website!", "Website!"); testSanitize("* Website!", "_ Website!");
testSanitize("Website | Page!", "Website Page!"); testSanitize("Website | Page!", "Website _ Page!");
testSanitize("Directory Listing: /a/b/", "Directory Listing _a_b_"); testSanitize("Directory Listing: /a/b/", "Directory Listing_ _a_b_");
} else if (AppConstants.platform == "macosx") { } else if (AppConstants.platform == "macosx") {
testSanitize(kSpecialChars, "A ;,+=[]B][=+,; C"); testSanitize(kSpecialChars, "A__________;,+=[]B][=+,;__________C");
testSanitize(" :: Website :: ", "Website"); testSanitize(" :: Website :: ", "__ Website __");
testSanitize("* Website!", "Website!"); testSanitize("* Website!", "_ Website!");
testSanitize("Website | Page!", "Website Page!"); testSanitize("Website | Page!", "Website _ Page!");
testSanitize("Directory Listing: /a/b/", "Directory Listing _a_b_"); testSanitize("Directory Listing: /a/b/", "Directory Listing_ _a_b_");
} else { } else {
testSanitize(kSpecialChars, "A ;,+=[]B][=+,; C"); testSanitize(kSpecialChars, "A__________;,+=[]B][=+,;__________C");
testSanitize(" :: Website :: ", "Website"); testSanitize(" :: Website :: ", "__ Website __");
testSanitize("* Website!", "Website!"); testSanitize("* Website!", "_ Website!");
testSanitize("Website | Page!", "Website Page!"); testSanitize("Website | Page!", "Website _ Page!");
testSanitize("Directory Listing: /a/b/", "Directory Listing _a_b_"); testSanitize("Directory Listing: /a/b/", "Directory Listing_ _a_b_");
} }
// Conversion of consecutive runs of slashes and backslashes to underscores. // Conversion of consecutive runs of slashes and backslashes to underscores.
@ -64,9 +64,9 @@ add_task(async function test_sanitize() {
// Removal of leading and trailing whitespace and dots after conversion. // Removal of leading and trailing whitespace and dots after conversion.
testSanitize(" Website ", "Website"); testSanitize(" Website ", "Website");
testSanitize(". . Website . Page . .", "Website . Page"); testSanitize(". . Website . Page . .", "Website .Page");
testSanitize(" File . txt ", "File . txt"); testSanitize(" File . txt ", "File .txt");
testSanitize("\f\n\r\t\v\x00\x1f\x7f\x80\x9f\xa0 . txt", "txt"); testSanitize("\f\n\r\t\v\x00\x1f\x7f\x80\x9f\xa0 . txt", "_________ .txt");
testSanitize("\u1680\u180e\u2000\u2008\u200a . txt", "txt"); testSanitize("\u1680\u180e\u2000\u2008\u200a . txt", "txt");
testSanitize("\u2028\u2029\u202f\u205f\u3000\ufeff . txt", "txt"); testSanitize("\u2028\u2029\u202f\u205f\u3000\ufeff . txt", "txt");
@ -77,25 +77,25 @@ add_task(async function test_sanitize() {
testSanitize(" . ", ""); testSanitize(" . ", "");
// Stripping of BIDI formatting characters. // Stripping of BIDI formatting characters.
testSanitize("\u200e \u202b\u202c\u202d\u202etest\x7f\u200f", "_ ____test _"); testSanitize("\u200e \u202b\u202c\u202d\u202etest\x7f\u200f", "_ ____test__");
testSanitize("AB\x7f\u202a\x7f\u202a\x7fCD", "AB _ _ CD"); testSanitize("AB\x7f\u202a\x7f\u202a\x7fCD", "AB_____CD");
// Stripping of colons: // Stripping of colons:
testSanitize("foo:bar", "foo bar"); testSanitize("foo:bar", "foo_bar");
// not compressing whitespaces. // not compressing whitespaces.
testSanitize("foo : bar", "foo bar", { compressWhitespaces: false }); testSanitize("foo : bar", "foo _ bar", { compressWhitespaces: false });
testSanitize("thing.lnk", "thing.lnk.download"); testSanitize("thing.lnk", "thing.lnk.download");
testSanitize("thing.lnk\n", "thing.lnk.download"); testSanitize("thing.lnk\n", "thing.lnk_");
testSanitize("thing.lnk", "thing.lnk", { testSanitize("thing.lnk", "thing.lnk", {
allowInvalidFilenames: true, allowInvalidFilenames: true,
}); });
testSanitize("thing.lnk\n", "thing.lnk", { testSanitize("thing.lnk\n", "thing.lnk_", {
allowInvalidFilenames: true, allowInvalidFilenames: true,
}); });
testSanitize("thing.URl", "thing.URl.download"); testSanitize("thing.URl", "thing.URl.download");
testSanitize("thing.URl \n", "thing.URl", { testSanitize("thing.URl \n", "thing.URl_", {
allowInvalidFilenames: true, allowInvalidFilenames: true,
}); });
@ -107,12 +107,12 @@ add_task(async function test_sanitize() {
allowInvalidFilenames: true, allowInvalidFilenames: true,
}); });
testSanitize("thing.local|", "thing.local.download"); testSanitize("thing.local|", "thing.local_");
testSanitize("thing.lo|cal", "thing.lo cal"); testSanitize("thing.lo|cal", "thing.lo_cal");
testSanitize('thing.local/*"', "thing.local_"); testSanitize('thing.local/*"', "thing.local___");
testSanitize("thing.desktoP", "thing.desktoP.download"); testSanitize("thing.desktoP", "thing.desktoP.download");
testSanitize("thing.desktoP \n", "thing.desktoP", { testSanitize("thing.desktoP \n", "thing.desktoP_", {
allowInvalidFilenames: true, allowInvalidFilenames: true,
}); });
}); });

View file

@ -83,7 +83,7 @@ add_task(async function test_decoded_filename_download() {
const FILE_NAME_DECODED_2 = "file\u{0001F6B2}encoded.txt"; const FILE_NAME_DECODED_2 = "file\u{0001F6B2}encoded.txt";
const FILE_NAME_ENCODED_URL_2 = BASE + "/" + FILE_NAME_ENCODED_2; const FILE_NAME_ENCODED_URL_2 = BASE + "/" + FILE_NAME_ENCODED_2;
const FILE_NAME_ENCODED_3 = "file%X%20encode.txt"; const FILE_NAME_ENCODED_3 = "file%X%20encode.txt";
const FILE_NAME_DECODED_3 = "file%X encode.txt"; const FILE_NAME_DECODED_3 = "file_X encode.txt";
const FILE_NAME_ENCODED_URL_3 = BASE + "/" + FILE_NAME_ENCODED_3; const FILE_NAME_ENCODED_URL_3 = BASE + "/" + FILE_NAME_ENCODED_3;
const FILE_NAME_ENCODED_4 = "file%E3%80%82encode.txt"; const FILE_NAME_ENCODED_4 = "file%E3%80%82encode.txt";
const FILE_NAME_DECODED_4 = "file\u3002encode.txt"; const FILE_NAME_DECODED_4 = "file\u3002encode.txt";

View file

@ -3485,8 +3485,8 @@ void nsExternalHelperAppService::SanitizeFileName(nsAString& aFileName,
nsAutoString fileName(aFileName); nsAutoString fileName(aFileName);
// Replace known invalid characters. // Replace known invalid characters.
fileName.ReplaceChar(u"" KNOWN_PATH_SEPARATORS, u'_'); fileName.ReplaceChar(u"" KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS "%",
fileName.ReplaceChar(u"" FILE_ILLEGAL_CHARACTERS, u' '); u'_');
fileName.StripChar(char16_t(0)); fileName.StripChar(char16_t(0));
const char16_t *startStr, *endStr; const char16_t *startStr, *endStr;
@ -3668,6 +3668,14 @@ void nsExternalHelperAppService::SanitizeFileName(nsAString& aFileName,
outFileName.Truncate(lastNonTrimmable); outFileName.Truncate(lastNonTrimmable);
} }
nsAutoString extension;
int32_t dotidx = outFileName.RFind(u".");
if (dotidx != -1) {
extension = Substring(outFileName, dotidx + 1);
extension.StripWhitespace();
outFileName = Substring(outFileName, 0, dotidx + 1) + extension;
}
#ifdef XP_WIN #ifdef XP_WIN
if (nsLocalFile::CheckForReservedFileName(outFileName)) { if (nsLocalFile::CheckForReservedFileName(outFileName)) {
outFileName.Truncate(); outFileName.Truncate();

View file

@ -18,7 +18,7 @@
<img id="i1" src="http://localhost:8000/save_filename.sjs?type=png&filename=simple.png" data-filename="simple.png"> <img id="i1" src="http://localhost:8000/save_filename.sjs?type=png&filename=simple.png" data-filename="simple.png">
<!-- invalid characters in the filename --> <!-- 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"> <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 --> <!-- 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"> <img id="i3" src="http://localhost:8000/save_filename.sjs?type=png&filename=invalidextension.pang" data-filename="invalidextension.png">
@ -329,11 +329,11 @@
<!-- filename which is changed to an invalid filename within the file picker --> <!-- filename which is changed to an invalid filename within the file picker -->
<a id="mod1" href="http://localhost:8000/save_filename.sjs?type=png&filename=orange.png" <a id="mod1" href="http://localhost:8000/save_filename.sjs?type=png&filename=orange.png"
data-pickedfilename='"peach".png' data-filename='peach .png'> data-pickedfilename='"peach".png' data-filename='_peach_.png'>
<!-- an invalid filename which is changed to another invalid filename within the file picker --> <!-- an invalid filename which is changed to another invalid filename within the file picker -->
<a id="mod2" href="http://localhost:8000/save_filename.sjs?type=png&filename=%maroon%34.png" <a id="mod2" href="http://localhost:8000/save_filename.sjs?type=png&filename=%maroon%34.png"
data-pickedfilename='"violet".png' data-filename="violet .png"> data-pickedfilename='"violet".png' data-filename="_violet_.png">
</span> </span>

View file

@ -11,7 +11,7 @@
var tests = [ var tests = [
["test.png:large", "test.png"], ["test.png:large", "test.png"],
["test.png/large", "test.png"], ["test.png/large", "test.png"],
[":test.png::large:", "test.png"], [":test.png::large:", "_test.png"],
]; ];
add_task(async function() { add_task(async function() {

View file

@ -31,8 +31,9 @@ add_task(async function validate_filename_method() {
Assert.equal(checkFilename("\\path.png", 0), "_path.png"); Assert.equal(checkFilename("\\path.png", 0), "_path.png");
Assert.equal( Assert.equal(
checkFilename("\\path*and/$?~file.png", 0), checkFilename("\\path*and/$?~file.png", 0),
"_path and_$ ~file.png" "_path_and_$_~file.png"
); );
Assert.equal( Assert.equal(
checkFilename(" \u180e whit\u180ee.png \u180e", 0), checkFilename(" \u180e whit\u180ee.png \u180e", 0),
"whit\u180ee.png" "whit\u180ee.png"
@ -103,12 +104,12 @@ add_task(async function validate_filename_method() {
// For whatever reason, the Android mime handler accepts the .jpeg // For whatever reason, the Android mime handler accepts the .jpeg
// extension for image/png, so skip this test there. // extension for image/png, so skip this test there.
if (AppConstants.platform != "android") { if (AppConstants.platform != "android") {
Assert.equal(checkFilename("thi/*rd.jpeg", 0), "thi_ rd.png"); Assert.equal(checkFilename("thi/*rd.jpeg", 0), "thi__rd.png");
} }
Assert.equal( Assert.equal(
checkFilename("f*\\ourth file.jpg", mimeService.VALIDATE_SANITIZE_ONLY), checkFilename("f*\\ourth file.jpg", mimeService.VALIDATE_SANITIZE_ONLY),
"f _ourth file.jpg" "f__ourth file.jpg"
); );
Assert.equal( Assert.equal(
checkFilename( checkFilename(
@ -116,7 +117,7 @@ add_task(async function validate_filename_method() {
mimeService.VALIDATE_SANITIZE_ONLY | mimeService.VALIDATE_SANITIZE_ONLY |
mimeService.VALIDATE_DONT_COLLAPSE_WHITESPACE mimeService.VALIDATE_DONT_COLLAPSE_WHITESPACE
), ),
"f _ift h.jpe _g" "f__ift h.jpe__g"
); );
Assert.equal(checkFilename("sixth.j pe/*g", 0), "sixth.png"); Assert.equal(checkFilename("sixth.j pe/*g", 0), "sixth.png");
@ -157,25 +158,25 @@ add_task(async function validate_filename_method() {
repeatStr.substring(0, 254 - ext.length) + ext repeatStr.substring(0, 254 - ext.length) + ext
); );
ext = "lo%?n/ginvalid? ch\\ars"; ext = "lo#?n/ginvalid? ch\\ars";
Assert.equal( Assert.equal(
checkFilename(repeatStr + ext, mimeService.VALIDATE_SANITIZE_ONLY), checkFilename(repeatStr + ext, mimeService.VALIDATE_SANITIZE_ONLY),
repeatStr + "lo% n_" repeatStr + "lo#_n_"
); );
ext = ".long/invalid%? ch\\ars"; ext = ".long/invalid#? ch\\ars";
Assert.equal( Assert.equal(
checkFilename(repeatStr + ext, mimeService.VALIDATE_SANITIZE_ONLY), checkFilename(repeatStr + ext, mimeService.VALIDATE_SANITIZE_ONLY),
repeatStr.substring(0, 233) + ".long_invalid% ch_ars" repeatStr.substring(0, 232) + ".long_invalid#_ch_ars"
); );
Assert.equal( Assert.equal(
checkFilename("test_テスト_T\x83E\\S\x83T.png", 0), checkFilename("test_テスト_T\x83E\\S\x83T.png", 0),
"test_テスト_T E_S T.png" "test_テスト_T_E_S_T.png"
); );
Assert.equal( Assert.equal(
checkFilename("test_テスト_T\x83E\\S\x83T.pテ\x83ng", 0), checkFilename("test_テスト_T\x83E\\S\x83T.pテ\x83ng", 0),
"test_テスト_T E_S T.png" "test_テスト_T_E_S_T.png"
); );
// Check we don't invalidate surrogate pairs when trimming. // Check we don't invalidate surrogate pairs when trimming.
@ -248,11 +249,11 @@ add_task(async function validate_filename_method() {
// cropped to fit into 255 bytes. // cropped to fit into 255 bytes.
Assert.equal( Assert.equal(
mimeService.validateFileNameForSaving( mimeService.validateFileNameForSaving(
"라이브9.9만 시청컬처렐 다이제스티브 3박스 - 3박스 더 (뚱랑이 굿즈 증정) - 선물용 쇼핑백 2장컬처렐 다이제스티브 3박스 - 3박스 더 (뚱랑이 굿즈 증정) - 선물용 쇼핑백 2장24%102 000원 브랜드데이 앵콜 🎁 1.등 유산균 컬처렐 특가!", "라이브9.9만 시청컬처렐 다이제스티브 3박스 - 3박스 더 (뚱랑이 굿즈 증정) - 선물용 쇼핑백 2장컬처렐 다이제스티브 3박스 - 3박스 더 (뚱랑이 굿즈 증정) - 선물용 쇼핑백 2장24%102 000원 브랜드데이 앵콜 🎁 1.등-유산균-컬처렐-특가!",
"text/unknown", "text/unknown",
mimeService.VALIDATE_SANITIZE_ONLY mimeService.VALIDATE_SANITIZE_ONLY
), ),
"라이브9.9만 시청컬처렐 다이제스티브 3박스 - 3박스 더 (뚱랑이 굿즈 증정) - 선물용 쇼핑백 2장컬처렐 다이제스티브 3박스 - 3박스 더 (뚱랑이 굿즈 .등 유산균 컬처렐 특가!", "라이브9.9만 시청컬처렐 다이제스티브 3박스 - 3박스 더 (뚱랑이 굿즈 증정) - 선물용 쇼핑백 2장컬처렐 다이제스티브 3박스 - 3박스 더 (뚱랑이 굿즈 .등-유산균-컬처렐-특가!",
"very long filename with extension" "very long filename with extension"
); );
@ -270,11 +271,11 @@ add_task(async function validate_filename_method() {
// This filename is cropped at 254 bytes. // This filename is cropped at 254 bytes.
Assert.equal( Assert.equal(
mimeService.validateFileNameForSaving( mimeService.validateFileNameForSaving(
".라이브99만 시청컬처렐 다이제스티브 3박스 - 3박스 더 (뚱랑이 굿즈 증정) - 선물용 쇼핑백 2장컬처렐 다이제스티브 3박스 - 3박스 더 (뚱랑이 굿즈 증정) - 선물용 쇼핑백 2장24%102 000원 브랜드데이 앵콜 🎁 1등 유산균 컬처렐 특가!", ".라이브99만 시청컬처렐 다이제스티브 3박스 - 3박스 더 (뚱랑이 굿즈 증정) - 선물용 쇼핑백 2장컬처렐 다이제스티브 3박스 - 3박스 더 (뚱랑이 굿즈 증정) - 선물용 쇼핑백 2장24_102 000원 브랜드데이 앵콜 🎁 1등 유산균 컬처렐 특가!",
"text/unknown", "text/unknown",
mimeService.VALIDATE_SANITIZE_ONLY mimeService.VALIDATE_SANITIZE_ONLY
), ),
"라이브99만 시청컬처렐 다이제스티브 3박스 - 3박스 더 (뚱랑이 굿즈 증정) - 선물용 쇼핑백 2장컬처렐 다이제스티브 3박스 - 3박스 더 (뚱랑이 굿즈 증정) - 선물용 쇼핑백 2장24%102 000원 브랜드데", "라이브99만 시청컬처렐 다이제스티브 3박스 - 3박스 더 (뚱랑이 굿즈 증정) - 선물용 쇼핑백 2장컬처렐 다이제스티브 3박스 - 3박스 더 (뚱랑이 굿즈 증정) - 선물용 쇼핑백 2장24_102 000원 브랜드데",
"very filename with extension only" "very filename with extension only"
); );
@ -311,7 +312,7 @@ add_task(async function validate_filename_method() {
Assert.equal( Assert.equal(
mimeService.validateFileNameForSaving("filename.lnk\n", "text/unknown", 0), mimeService.validateFileNameForSaving("filename.lnk\n", "text/unknown", 0),
"filename.lnk.download", "filename.lnk_",
"filename.lnk with newline" "filename.lnk with newline"
); );
@ -321,7 +322,7 @@ add_task(async function validate_filename_method() {
"text/unknown", "text/unknown",
0 0
), ),
"filename.lnk.download", "filename.lnk_",
"filename.lnk with newline" "filename.lnk with newline"
); );
@ -331,7 +332,7 @@ add_task(async function validate_filename_method() {
"text/unknown", "text/unknown",
0 0
), ),
"filename. lnk", "filename.__lnk",
"filename.lnk with space and newline" "filename.lnk with space and newline"
); );
@ -361,7 +362,7 @@ add_task(async function validate_filename_method() {
"text/unknown", "text/unknown",
mimeService.VALIDATE_ALLOW_INVALID_FILENAMES mimeService.VALIDATE_ALLOW_INVALID_FILENAMES
), ),
"filename.LNK", "filename.LNK_",
"filename.LNK allow invalid" "filename.LNK allow invalid"
); );
@ -372,7 +373,7 @@ add_task(async function validate_filename_method() {
mimeService.VALIDATE_SANITIZE_ONLY | mimeService.VALIDATE_SANITIZE_ONLY |
mimeService.VALIDATE_ALLOW_INVALID_FILENAMES mimeService.VALIDATE_ALLOW_INVALID_FILENAMES
), ),
"filename.URL", "filename.URL_",
"filename.URL allow invalid, sanitize only" "filename.URL allow invalid, sanitize only"
); );
@ -392,7 +393,7 @@ add_task(async function validate_filename_method() {
mimeService.VALIDATE_SANITIZE_ONLY | mimeService.VALIDATE_SANITIZE_ONLY |
mimeService.VALIDATE_ALLOW_INVALID_FILENAMES mimeService.VALIDATE_ALLOW_INVALID_FILENAMES
), ),
"filename.DESKTOP", "filename.DESKTOP_",
"filename.DESKTOP allow invalid, sanitize only" "filename.DESKTOP allow invalid, sanitize only"
); );
}); });