Backed out 13 changesets (bug 1746052) for causing failures on uriloader/exthandler/tests/. CLOSED TREE

Backed out changeset d19cc58e3cab (bug 1746052)
Backed out changeset 229edc158a2b (bug 1746052)
Backed out changeset b0ef7c68abcf (bug 1746052)
Backed out changeset 30de4b77f242 (bug 1746052)
Backed out changeset ebc6720fdab3 (bug 1746052)
Backed out changeset daccb796a093 (bug 1746052)
Backed out changeset 5b76d8d76b2b (bug 1746052)
Backed out changeset a698068d078f (bug 1746052)
Backed out changeset 73d17535d8d1 (bug 1746052)
Backed out changeset 94c95f004221 (bug 1746052)
Backed out changeset 1a389759585a (bug 1746052)
Backed out changeset c91230a8ea90 (bug 1746052)
Backed out changeset 7665f02c114e (bug 1746052)
This commit is contained in:
Csoregi Natalia 2022-05-04 01:15:12 +03:00
Родитель a3e1d0e570
Коммит 7896e7e124
24 изменённых файлов: 793 добавлений и 2052 удалений

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

@ -100,7 +100,7 @@ function test() {
aCallback();
};
launcherDialog.promptForSaveToFileAsync(launcher, aWin, "", "", false);
launcherDialog.promptForSaveToFileAsync(launcher, aWin, null, null, null);
}
testOnWindow(false, function(win, downloadDir) {

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

@ -261,23 +261,42 @@ nsContentAreaDragDropDataProvider::GetFlavorData(nsITransferable* aTransferable,
supportsString = do_QueryInterface(tmp);
if (!supportsString) return NS_ERROR_FAILURE;
nsAutoString contentType;
supportsString->GetData(contentType);
nsAutoString imageRequestMime;
supportsString->GetData(imageRequestMime);
nsCOMPtr<nsIMIMEService> mimeService =
do_GetService("@mozilla.org/mime;1");
if (NS_WARN_IF(!mimeService)) {
return NS_ERROR_FAILURE;
// If we have a MIME type, check the extension is compatible
if (!imageRequestMime.IsEmpty()) {
// Build a URL to get the filename extension
nsCOMPtr<nsIURL> imageURL = do_QueryInterface(sourceURI, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString extension;
rv = imageURL->GetFileExtension(extension);
NS_ENSURE_SUCCESS(rv, rv);
NS_ConvertUTF16toUTF8 mimeCString(imageRequestMime);
bool isValidExtension;
nsAutoCString primaryExtension;
rv = CheckAndGetExtensionForMime(extension, mimeCString,
&isValidExtension, &primaryExtension);
NS_ENSURE_SUCCESS(rv, rv);
if (!isValidExtension && !primaryExtension.IsEmpty()) {
// The filename extension is missing or incompatible
// with the MIME type, replace it with the primary
// extension.
nsAutoCString newFileName;
rv = imageURL->GetFileBaseName(newFileName);
NS_ENSURE_SUCCESS(rv, rv);
newFileName.Append(".");
newFileName.Append(primaryExtension);
CopyUTF8toUTF16(newFileName, targetFilename);
}
}
mimeService->ValidateFileNameForSaving(
targetFilename, NS_ConvertUTF16toUTF8(contentType),
nsIMIMEService::VALIDATE_DEFAULT, targetFilename);
} else {
// make the filename safe for the filesystem
targetFilename.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS,
'-');
}
// make the filename safe for the filesystem
targetFilename.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS,
'-');
#endif /* defined(XP_MACOSX) */
// get the target directory from the kFilePromiseDirectoryMime
@ -417,28 +436,55 @@ nsresult DragDataProducer::GetImageData(imgIContainer* aImage,
nsCString mimeType;
aRequest->GetMimeType(getter_Copies(mimeType));
nsAutoCString fileName;
aRequest->GetFileName(fileName);
#if defined(XP_MACOSX)
// Save the MIME type so we can make sure the extension
// is compatible (and replace it if it isn't) when the
// image is dropped. On Mac, we need to get the OS MIME
// handler information in the parent due to sandboxing.
CopyUTF8toUTF16(mimeType, mImageRequestMime);
CopyUTF8toUTF16(fileName, mImageDestFileName);
#else
nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1");
if (NS_WARN_IF(!mimeService)) {
return NS_ERROR_FAILURE;
}
CopyUTF8toUTF16(fileName, mImageDestFileName);
mimeService->ValidateFileNameForSaving(mImageDestFileName, mimeType,
nsIMIMEService::VALIDATE_DEFAULT,
mImageDestFileName);
nsCOMPtr<nsIMIMEInfo> mimeInfo;
mimeService->GetFromTypeAndExtension(mimeType, ""_ns,
getter_AddRefs(mimeInfo));
if (mimeInfo) {
nsAutoCString extension;
imgUrl->GetFileExtension(extension);
bool validExtension;
if (extension.IsEmpty() ||
NS_FAILED(mimeInfo->ExtensionExists(extension, &validExtension)) ||
!validExtension) {
// Fix the file extension in the URL
nsAutoCString primaryExtension;
mimeInfo->GetPrimaryExtension(primaryExtension);
if (!primaryExtension.IsEmpty()) {
rv = NS_MutateURI(imgUrl)
.Apply(&nsIURLMutator::SetFileExtension, primaryExtension,
nullptr)
.Finalize(imgUrl);
NS_ENSURE_SUCCESS(rv, rv);
}
}
}
#endif /* defined(XP_MACOSX) */
nsAutoCString fileName;
imgUrl->GetFileName(fileName);
NS_UnescapeURL(fileName);
#if !defined(XP_MACOSX)
// make the filename safe for the filesystem
fileName.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '-');
#endif
CopyUTF8toUTF16(fileName, mImageDestFileName);
// and the image object
mImage = aImage;
}

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

@ -32,6 +32,8 @@
#include "nsHTMLDocument.h"
#include "nsGkAtoms.h"
#include "nsIFrame.h"
#include "nsIURI.h"
#include "nsGenericHTMLElement.h"
// image copy stuff
#include "nsIImageLoadingContent.h"
@ -602,39 +604,70 @@ static nsresult AppendImagePromise(nsITransferable* aTransferable,
nsCOMPtr<nsINode> node = do_QueryInterface(aImageElement, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1");
if (NS_WARN_IF(!mimeService)) {
return NS_ERROR_FAILURE;
}
// Fix the file extension in the URL if necessary
nsCOMPtr<nsIMIMEService> mimeService =
do_GetService(NS_MIMESERVICE_CONTRACTID);
NS_ENSURE_TRUE(mimeService, NS_OK);
nsCOMPtr<nsIURI> imgUri;
rv = aImgRequest->GetFinalURI(getter_AddRefs(imgUri));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURL> imgUrl = do_QueryInterface(imgUri);
NS_ENSURE_TRUE(imgUrl, NS_OK);
nsAutoCString extension;
rv = imgUrl->GetFileExtension(extension);
NS_ENSURE_SUCCESS(rv, rv);
nsCString mimeType;
rv = aImgRequest->GetMimeType(getter_Copies(mimeType));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIMIMEInfo> mimeInfo;
mimeService->GetFromTypeAndExtension(mimeType, ""_ns,
getter_AddRefs(mimeInfo));
NS_ENSURE_TRUE(mimeInfo, NS_OK);
nsAutoCString spec;
rv = imgUri->GetSpec(spec);
rv = imgUrl->GetSpec(spec);
NS_ENSURE_SUCCESS(rv, rv);
// pass out the image source string
nsString imageSourceString;
CopyUTF8toUTF16(spec, imageSourceString);
nsCString mimeType;
rv = aImgRequest->GetMimeType(getter_Copies(mimeType));
NS_ENSURE_SUCCESS(rv, rv);
bool validExtension;
if (extension.IsEmpty() ||
NS_FAILED(mimeInfo->ExtensionExists(extension, &validExtension)) ||
!validExtension) {
// Fix the file extension in the URL
nsAutoCString primaryExtension;
mimeInfo->GetPrimaryExtension(primaryExtension);
if (!primaryExtension.IsEmpty()) {
rv = NS_MutateURI(imgUri)
.Apply(&nsIURLMutator::SetFileExtension, primaryExtension,
nullptr)
.Finalize(imgUrl);
NS_ENSURE_SUCCESS(rv, rv);
}
}
nsAutoCString fileName;
rv = aImgRequest->GetFileName(fileName);
NS_ENSURE_SUCCESS(rv, rv);
imgUrl->GetFileName(fileName);
nsAutoString validFileName = NS_ConvertUTF8toUTF16(fileName);
mimeService->ValidateFileNameForSaving(
validFileName, mimeType, nsIMIMEService::VALIDATE_DEFAULT, validFileName);
NS_UnescapeURL(fileName);
// make the filename safe for the filesystem
fileName.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '-');
nsString imageDestFileName;
CopyUTF8toUTF16(fileName, imageDestFileName);
rv = AppendString(aTransferable, imageSourceString, kFilePromiseURLMime);
NS_ENSURE_SUCCESS(rv, rv);
rv = AppendString(aTransferable, validFileName, kFilePromiseDestFilename);
rv = AppendString(aTransferable, imageDestFileName, kFilePromiseDestFilename);
NS_ENSURE_SUCCESS(rv, rv);
aTransferable->SetRequestingPrincipal(node->NodePrincipal());

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

@ -2152,26 +2152,78 @@ nsresult nsWebBrowserPersist::CalculateAndAppendFileExt(
mMIMEService->GetTypeFromURI(uri, contentType);
}
// Validate the filename
// Append the extension onto the file
if (!contentType.IsEmpty()) {
nsAutoString newFileName;
if (NS_SUCCEEDED(mMIMEService->GetValidFileName(
aChannel, contentType, aOriginalURIWithExtension,
nsIMIMEService::VALIDATE_DEFAULT, newFileName))) {
nsCOMPtr<nsIFile> localFile;
GetLocalFileFromURI(aURI, getter_AddRefs(localFile));
if (localFile) {
localFile->SetLeafName(newFileName);
nsCOMPtr<nsIMIMEInfo> mimeInfo;
mMIMEService->GetFromTypeAndExtension(contentType, ""_ns,
getter_AddRefs(mimeInfo));
// Resync the URI with the file after the extension has been appended
return NS_MutateURI(aURI)
.Apply(&nsIFileURLMutator::SetFile, localFile)
nsCOMPtr<nsIFile> localFile;
GetLocalFileFromURI(aURI, getter_AddRefs(localFile));
if (mimeInfo) {
nsCOMPtr<nsIURL> url(do_QueryInterface(aURI));
NS_ENSURE_TRUE(url, NS_ERROR_FAILURE);
nsAutoCString newFileName;
url->GetFileName(newFileName);
// Test if the current extension is current for the mime type
bool hasExtension = false;
int32_t ext = newFileName.RFind(".");
if (ext != -1) {
mimeInfo->ExtensionExists(Substring(newFileName, ext + 1),
&hasExtension);
}
// Append the mime file extension
nsAutoCString fileExt;
if (!hasExtension) {
// Test if previous extension is acceptable
nsCOMPtr<nsIURL> oldurl(do_QueryInterface(aOriginalURIWithExtension));
NS_ENSURE_TRUE(oldurl, NS_ERROR_FAILURE);
oldurl->GetFileExtension(fileExt);
bool useOldExt = false;
if (!fileExt.IsEmpty()) {
mimeInfo->ExtensionExists(fileExt, &useOldExt);
}
// If the url doesn't have an extension, or we don't know the extension,
// try to use the primary extension for the type. If we don't know the
// primary extension for the type, just continue with the url extension.
if (!useOldExt) {
nsAutoCString primaryExt;
mimeInfo->GetPrimaryExtension(primaryExt);
if (!primaryExt.IsEmpty()) {
fileExt = primaryExt;
}
}
if (!fileExt.IsEmpty()) {
uint32_t newLength = newFileName.Length() + fileExt.Length() + 1;
if (newLength > kDefaultMaxFilenameLength) {
if (fileExt.Length() > kDefaultMaxFilenameLength / 2)
fileExt.Truncate(kDefaultMaxFilenameLength / 2);
uint32_t diff = kDefaultMaxFilenameLength - 1 - fileExt.Length();
if (newFileName.Length() > diff) newFileName.Truncate(diff);
}
newFileName.Append('.');
newFileName.Append(fileExt);
}
if (localFile) {
localFile->SetLeafName(NS_ConvertUTF8toUTF16(newFileName));
// Resync the URI with the file after the extension has been appended
return NS_MutateURI(url)
.Apply(&nsIFileURLMutator::SetFile, localFile)
.Finalize(aOutURI);
}
return NS_MutateURI(url)
.Apply(&nsIURLMutator::SetFileName, newFileName, nullptr)
.Finalize(aOutURI);
}
return NS_MutateURI(aURI)
.Apply(&nsIURLMutator::SetFileName,
NS_ConvertUTF16toUTF8(newFileName), nullptr)
.Finalize(aOutURI);
}
}

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

@ -100,14 +100,6 @@ interface imgIRequest : nsIRequest
readonly attribute string mimeType;
/**
* The filename that should be used when saving the image. This is determined
* from the Content-Disposition, if present, or the uri of the image. This
* filename should be validated using nsIMIMEService::GetValidFilenameForSaving
* before creating the file.
*/
readonly attribute ACString fileName;
/**
* Clone this request; the returned request will have aObserver as the
* observer. aObserver will be notified synchronously (before the clone()

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

@ -31,7 +31,6 @@
#include "nsIScriptSecurityManager.h"
#include "nsComponentManagerUtils.h"
#include "nsContentUtils.h"
#include "nsEscape.h"
#include "plstr.h" // PL_strcasestr(...)
#include "prtime.h" // for PR_Now
@ -39,7 +38,6 @@
#include "nsIProtocolHandler.h"
#include "imgIRequest.h"
#include "nsProperties.h"
#include "nsIURL.h"
#include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/SizeOfState.h"
@ -463,30 +461,6 @@ already_AddRefed<image::Image> imgRequest::GetImage() const {
return image.forget();
}
void imgRequest::GetFileName(nsACString& aFileName) {
nsAutoString fileName;
nsCOMPtr<nsISupportsCString> supportscstr;
if (NS_SUCCEEDED(mProperties->Get("content-disposition",
NS_GET_IID(nsISupportsCString),
getter_AddRefs(supportscstr))) &&
supportscstr) {
nsAutoCString cdHeader;
supportscstr->GetData(cdHeader);
NS_GetFilenameFromDisposition(fileName, cdHeader);
}
if (fileName.IsEmpty()) {
nsCOMPtr<nsIURL> imgUrl(do_QueryInterface(mURI));
if (imgUrl) {
imgUrl->GetFileName(aFileName);
NS_UnescapeURL(aFileName);
}
} else {
aFileName = NS_ConvertUTF16toUTF8(fileName);
}
}
int32_t imgRequest::Priority() const {
int32_t priority = nsISupportsPriority::PRIORITY_NORMAL;
nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mRequest);

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

@ -151,8 +151,6 @@ class imgRequest final : public nsIStreamListener,
/// Returns a non-owning pointer to this imgRequest's MIME type.
const char* GetMimeType() const { return mContentType.get(); }
void GetFileName(nsACString& aFileName);
/// @return the priority of the underlying network request, or
/// PRIORITY_NORMAL if it doesn't support nsISupportsPriority.
int32_t Priority() const;

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

@ -747,16 +747,6 @@ imgRequestProxy::GetMimeType(char** aMimeType) {
return NS_OK;
}
NS_IMETHODIMP
imgRequestProxy::GetFileName(nsACString& aFileName) {
if (!GetOwner()) {
return NS_ERROR_FAILURE;
}
GetOwner()->GetFileName(aFileName);
return NS_OK;
}
imgRequestProxy* imgRequestProxy::NewClonedProxy() {
return new imgRequestProxy();
}

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

@ -8,7 +8,6 @@
interface nsIFile;
interface nsIMIMEInfo;
interface nsIURI;
interface nsIChannel;
%{C++
#define NS_MIMESERVICE_CID \
@ -98,124 +97,4 @@ interface nsIMIMEService : nsISupports {
nsIMIMEInfo getMIMEInfoFromOS(in ACString aType,
in ACString aFileExtension,
out boolean aFound);
/**
* Default filename validation for getValidFileName and
* validateFileNameForSaving where other flags are not true.
* That is, the extension is modified to fit the content type,
* duplicate whitespace is collapsed, and long filenames are
* truncated. A valid content type must be supplied. See the
* description of getValidFileName for more details about how
* the flags are used.
*/
const long VALIDATE_DEFAULT = 0;
/**
* If true, then the filename is only validated to ensure that it is
* acceptable for the file system. If false, then the extension is also
* checked to ensure that it is valid for the content type. If the
* extension is not valid, the filename is modified to have the proper
* extension.
*/
const long VALIDATE_SANITIZE_ONLY = 1;
/**
* Don't collapse strings of duplicate whitespace into a single string.
*/
const long VALIDATE_DONT_COLLAPSE_WHITESPACE = 2;
/**
* Don't truncate long filenames.
*/
const long VALIDATE_DONT_TRUNCATE = 4;
/**
* True to ignore the content type and guess the type from any existing
* extension instead. "application/octet-stream" is used as the default
* if there is no extension or there is no information available for
* the extension.
*/
const long VALIDATE_GUESS_FROM_EXTENSION = 8;
/**
* If the filename is empty, return the empty filename
* without modification.
*/
const long VALIDATE_ALLOW_EMPTY = 16;
/**
* Don't apply a default filename if the non-extension portion of the
* filename is empty.
*/
const long VALIDATE_NO_DEFAULT_FILENAME = 32;
/**
* Generate a valid filename from the channel that can be used to save
* the content of the channel to the local disk.
*
* The filename is determined from the content disposition, the filename
* of the uri, or a default filename. The following modifications are
* applied:
* - If the VALIDATE_SANITIZE_ONLY flag is not specified, then the
* extension of the filename is modified to suit the supplied content type.
* - Path separators (typically / and \) are replaced by underscores (_)
* - Characters that are not valid or would be confusing in filenames are
* replaced by spaces (*, :, etc)
* - Bidi related marks are replaced by underscores (_)
* - Whitespace and periods are removed from the beginning and end.
* - Unless VALIDATE_DONT_COLLAPSE_WHITESPACE is specified, multiple
* consecutive whitespace characters are collapsed to a single space
* character, either ' ' or an ideographic space 0x3000 if present.
* - Unless VALIDATE_DONT_TRUNCATE is specified, the filename is truncated
* to a maximum length, preserving the extension if possible.
* - Some filenames are invalid on certain platforms. These are replaced if
* possible.
*
* If the VALIDATE_NO_DEFAULT_FILENAME flag is not specified, and after the
* rules above are applied, the resulting filename is empty, a default
* filename is used.
*
* If the VALIDATE_ALLOW_EMPTY flag is specified, an empty string may be
* returned only if the filename could not be determined or was blank.
*
* If either the VALIDATE_SANITIZE_ONLY or VALIDATE_GUESS_FROM_EXTENSION flags
* are specified, then the content type may be empty. Otherwise, the type must
* not be empty.
*
* The aOriginalURI would be specified if the channel is for a local file but
* it was originally sourced from a different uri.
*
* When saving an image, use validateFileNameForSaving instead and
* pass the result of imgIRequest::GetFileName() as the filename to
* check.
*
* @param aChannel The channel of the content to save.
* @param aType The MIME type to use, which would usually be the
* same as the content type of the channel.
* @param aOriginalURL the source url of the file, but may be null.
* @param aFlags one or more of the flags above.
* @returns The resulting filename.
*/
AString getValidFileName(in nsIChannel aChannel,
in ACString aType,
in nsIURI aOriginalURI,
in unsigned long aFlags);
/**
* Similar to getValidFileName, but used when a specific filename needs
* to be validated. The filename is modified as needed based on the
* content type in the same manner as getValidFileName.
*
* If the filename came from a uri, it should not be escaped, that is,
* any needed unescaping of the filename should happen before calling
* this method.
*
* @param aType The MIME type to use.
* @param aFlags one or more of the flags above.
* @param aFileName The filename to validate.
* @returns The validated filename.
*/
AString validateFileNameForSaving(in AString aFileName,
in ACString aType,
in unsigned long aFlags);
};

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

@ -10,22 +10,83 @@
var EXPORTED_SYMBOLS = ["DownloadPaths"];
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"AppConstants",
"resource://gre/modules/AppConstants.jsm"
);
/**
* Platform-dependent regular expression used by the "sanitize" method.
*/
XPCOMUtils.defineLazyGetter(this, "gConvertToSpaceRegExp", () => {
// Note: we remove colons everywhere to avoid issues in subresource URL
// parsing, as well as filename restrictions on some OSes (see bug 1562176).
/* eslint-disable no-control-regex */
switch (AppConstants.platform) {
// On mobile devices, the file system may be very limited in what it
// considers valid characters. To avoid errors, sanitize conservatively.
case "android":
return /[\x00-\x1f\x7f-\x9f:*?|"<>;,+=\[\]]+/g;
case "win":
return /[\x00-\x1f\x7f-\x9f:*?|]+/g;
default:
return /[\x00-\x1f\x7f-\x9f:]+/g;
}
/* eslint-enable no-control-regex */
});
var DownloadPaths = {
/**
* Sanitizes an arbitrary string via mimeSvc.validateFileNameForSaving.
* Sanitizes an arbitrary string for use as the local file name of a download.
* The input is often a document title or a manually edited name. The output
* can be an empty string if the input does not include any valid character.
*
* The length of the resulting string is not limited, because restrictions
* apply to the full path name after the target folder has been added.
*
* Splitting the base name and extension to add a counter or to identify the
* file type should only be done after the sanitization process, because it
* can alter the final part of the string or remove leading dots.
*
* Runs of slashes and backslashes are replaced with an underscore.
*
* On Windows, the angular brackets `<` and `>` are replaced with parentheses,
* and double quotes are replaced with single quotes.
*
* Runs of control characters are replaced with a space. On Mac, colons are
* also included in this group. On Windows, stars, question marks, and pipes
* are additionally included. On Android, semicolons, commas, plus signs,
* equal signs, and brackets are additionally included.
*
* Leading and trailing dots and whitespace are removed on all platforms. This
* avoids the accidental creation of hidden files on Unix and invalid or
* inaccessible file names on Windows. These characters are not removed when
* located at the end of the base name or at the beginning of the extension.
*
* @param {string} leafName The full leaf name to sanitize
* @param {boolean} [compressWhitespaces] Whether consecutive whitespaces
* should be compressed.
*/
sanitize(leafName, { compressWhitespaces = true } = {}) {
const mimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
let flags = mimeSvc.VALIDATE_SANITIZE_ONLY | mimeSvc.VALIDATE_DONT_TRUNCATE;
if (!compressWhitespaces) {
flags |= mimeSvc.VALIDATE_DONT_COLLAPSE_WHITESPACE;
if (AppConstants.platform == "win") {
leafName = leafName
.replace(/</g, "(")
.replace(/>/g, ")")
.replace(/"/g, "'");
}
return mimeSvc.validateFileNameForSaving(leafName, "", flags);
leafName = leafName
.replace(/[\\/]+/g, "_")
.replace(/[\u200e\u200f\u202a-\u202e]/g, "")
.replace(gConvertToSpaceRegExp, " ");
if (compressWhitespaces) {
leafName = leafName.replace(/\s{2,}/g, " ");
}
return leafName.replace(/^[\s\u180e.]+|[\s\u180e.]+$/g, "");
},
/**

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

@ -40,27 +40,27 @@ add_task(async function test_sanitize() {
testSanitize("Website | Page!", "Website Page!");
testSanitize("Directory Listing: /a/b/", "Directory Listing _a_b_");
} else if (AppConstants.platform == "win") {
testSanitize(kSpecialChars, "A ;,+=[]B][=+,; C");
testSanitize(kSpecialChars, "A ''(());,+=[]B][=+,;))(('' C");
testSanitize(" :: Website :: ", "Website");
testSanitize("* Website!", "Website!");
testSanitize("Website | Page!", "Website Page!");
testSanitize("Directory Listing: /a/b/", "Directory Listing _a_b_");
} else if (AppConstants.platform == "macosx") {
testSanitize(kSpecialChars, "A ;,+=[]B][=+,; C");
testSanitize(kSpecialChars, 'A *?|""<<>>;,+=[]B][=+,;>><<""|?* C');
testSanitize(" :: Website :: ", "Website");
testSanitize("* Website!", "Website!");
testSanitize("Website | Page!", "Website Page!");
testSanitize("* Website!", "* Website!");
testSanitize("Website | Page!", "Website | Page!");
testSanitize("Directory Listing: /a/b/", "Directory Listing _a_b_");
} else {
testSanitize(kSpecialChars, "A ;,+=[]B][=+,; C");
testSanitize(kSpecialChars, kSpecialChars.replace(/[:]/g, " "));
testSanitize(" :: Website :: ", "Website");
testSanitize("* Website!", "Website!");
testSanitize("Website | Page!", "Website Page!");
testSanitize("* Website!", "* Website!");
testSanitize("Website | Page!", "Website | Page!");
testSanitize("Directory Listing: /a/b/", "Directory Listing _a_b_");
}
// Conversion of consecutive runs of slashes and backslashes to underscores.
testSanitize("\\ \\\\Website\\/Page// /", "_ __Website__Page__ _");
testSanitize("\\ \\\\Website\\/Page// /", "_ _Website_Page_ _");
// Removal of leading and trailing whitespace and dots after conversion.
testSanitize(" Website ", "Website");
@ -77,8 +77,8 @@ add_task(async function test_sanitize() {
testSanitize(" . ", "");
// Stripping of BIDI formatting characters.
testSanitize("\u200e \u202b\u202c\u202d\u202etest\x7f\u200f", "_ ____test _");
testSanitize("AB\x7f\u202a\x7f\u202a\x7fCD", "AB _ _ CD");
testSanitize("\u200e \u202b\u202c\u202d\u202etest\x7f\u200f", "test");
testSanitize("AB\x7f\u202a\x7f\u202a\x7fCD", "AB CD");
// Stripping of colons:
testSanitize("foo:bar", "foo bar");

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

@ -568,36 +568,28 @@ function initFileInfo(
aContentDisposition
) {
try {
let uriExt = null;
// Get an nsIURI object from aURL if possible:
try {
aFI.uri = makeURI(aURL, aURLCharset);
// Assuming nsiUri is valid, calling QueryInterface(...) on it will
// populate extra object fields (eg filename and file extension).
uriExt = aFI.uri.QueryInterface(Ci.nsIURL).fileExtension;
var url = aFI.uri.QueryInterface(Ci.nsIURL);
aFI.fileExt = url.fileExtension;
} catch (e) {}
// Get the default filename:
let fileName = getDefaultFileName(
aFI.fileName = getDefaultFileName(
aFI.suggestedFileName || aFI.fileName,
aFI.uri,
aDocument,
aContentDisposition
);
let mimeService = this.getMIMEService();
aFI.fileName = mimeService.validateFileNameForSaving(
fileName,
aContentType,
mimeService.VALIDATE_DEFAULT
);
// If uriExt is blank, consider: aFI.suggestedFileName is supplied if
// saveURL(...) was the original caller (hence both aContentType and
// If aFI.fileExt is still blank, consider: aFI.suggestedFileName is supplied
// if saveURL(...) was the original caller (hence both aContentType and
// aDocument are blank). If they were saving a link to a website then make
// the extension .htm .
if (
!uriExt &&
!aFI.fileExt &&
!aDocument &&
!aContentType &&
/^http(s?):\/\//i.test(aURL)
@ -605,10 +597,8 @@ function initFileInfo(
aFI.fileExt = "htm";
aFI.fileBaseName = aFI.fileName;
} else {
let idx = aFI.fileName.lastIndexOf(".");
aFI.fileBaseName =
idx >= 0 ? aFI.fileName.substring(0, idx) : aFI.fileName;
aFI.fileExt = idx >= 0 ? aFI.fileName.substring(idx + 1) : null;
aFI.fileExt = getDefaultExtension(aFI.fileName, aFI.uri, aContentType);
aFI.fileBaseName = getFileBaseName(aFI.fileName);
}
} catch (e) {}
}
@ -655,7 +645,9 @@ function promiseTargetFile(
let dir = new FileUtils.File(dirPath);
if (useDownloadDir && dirExists) {
dir.append(aFpP.fileInfo.fileName);
dir.append(
getNormalizedLeafName(aFpP.fileInfo.fileName, aFpP.fileInfo.fileExt)
);
aFpP.file = uniqueFile(dir);
return true;
}
@ -696,7 +688,10 @@ function promiseTargetFile(
fp.displayDirectory = dir;
fp.defaultExtension = aFpP.fileInfo.fileExt;
fp.defaultString = aFpP.fileInfo.fileName;
fp.defaultString = getNormalizedLeafName(
aFpP.fileInfo.fileName,
aFpP.fileInfo.fileExt
);
appendFiltersForContentType(
fp,
aFpP.contentType,
@ -1026,9 +1021,11 @@ function getDefaultFileName(
} catch (e) {}
}
if (fileName) {
return Services.textToSubURI.unEscapeURIForUI(
fileName,
/* dontEscape = */ true
return validateFileName(
Services.textToSubURI.unEscapeURIForUI(
fileName,
/* dontEscape = */ true
)
);
}
}
@ -1036,6 +1033,7 @@ function getDefaultFileName(
let docTitle;
if (aDocument && aDocument.title && aDocument.title.trim()) {
// If the document looks like HTML or XML, try to use its original title.
docTitle = validateFileName(aDocument.title);
let contentType = aDocument.contentType;
if (
contentType == "application/xhtml+xml" ||
@ -1045,7 +1043,7 @@ function getDefaultFileName(
contentType == "text/xml"
) {
// 2) Use the document title
return aDocument.title;
return docTitle;
}
}
@ -1053,9 +1051,11 @@ function getDefaultFileName(
var url = aURI.QueryInterface(Ci.nsIURL);
if (url.fileName != "") {
// 3) Use the actual file name, if present
return Services.textToSubURI.unEscapeURIForUI(
url.fileName,
/* dontEscape = */ true
return validateFileName(
Services.textToSubURI.unEscapeURIForUI(
url.fileName,
/* dontEscape = */ true
)
);
}
} catch (e) {
@ -1070,22 +1070,35 @@ function getDefaultFileName(
if (aDefaultFileName) {
// 5) Use the caller-provided name, if any
return aDefaultFileName;
return validateFileName(aDefaultFileName);
}
// 6) If this is a directory, use the last directory name
var path = aURI.pathQueryRef.match(/\/([^\/]+)\/$/);
if (path && path.length > 1) {
return validateFileName(path[1]);
}
try {
if (aURI.host) {
// 6) Use the host.
return aURI.host;
// 7) Use the host.
return validateFileName(aURI.host);
}
} catch (e) {
// Some files have no information at all, like Javascript generated pages
}
return "";
try {
// 8) Use the default file name
return ContentAreaUtils.stringBundle.GetStringFromName(
"DefaultSaveFileName"
);
} catch (e) {
// in case localized string cannot be found
}
// 9) If all else fails, use "index"
return "index";
}
// This is only used after the user has entered a filename.
function validateFileName(aFileName) {
let processed = DownloadPaths.sanitize(aFileName) || "_";
if (AppConstants.platform == "android") {
@ -1111,6 +1124,120 @@ function validateFileName(aFileName) {
return processed;
}
// This is the set of image extensions supported by extraMimeEntries in
// nsExternalHelperAppService.
const kImageExtensions = new Set([
"art",
"bmp",
"gif",
"ico",
"cur",
"jpeg",
"jpg",
"jfif",
"pjpeg",
"pjp",
"png",
"apng",
"tiff",
"tif",
"xbm",
"svg",
"webp",
"avif",
"jxl",
]);
function getNormalizedLeafName(aFile, aDefaultExtension) {
if (!aDefaultExtension) {
return aFile;
}
if (AppConstants.platform == "win") {
// Remove trailing dots and spaces on windows
aFile = aFile.replace(/[\s.]+$/, "");
}
// Remove leading dots
aFile = aFile.replace(/^\.+/, "");
// Include the default extension in the file name to which we're saving.
var i = aFile.lastIndexOf(".");
let previousExtension = aFile.substr(i + 1).toLowerCase();
if (previousExtension != aDefaultExtension.toLowerCase()) {
// Suffixing the extension is the safe bet - it preserves the previous
// extension in case that's meaningful (e.g. various text files served
// with text/plain will end up as `foo.cpp.txt`, in the worst case).
// However, for images, the extension derived from the URL should be
// replaced if the content type indicates a different filetype - this makes
// sure that we treat e.g. feature-tested webp/avif images correctly.
if (kImageExtensions.has(previousExtension)) {
return aFile.substr(0, i + 1) + aDefaultExtension;
}
return aFile + "." + aDefaultExtension;
}
return aFile;
}
function getDefaultExtension(aFilename, aURI, aContentType) {
if (
aContentType == "text/plain" ||
aContentType == "application/octet-stream" ||
aURI.scheme == "ftp"
) {
return "";
} // temporary fix for bug 120327
// First try the extension from the filename
var url = Cc["@mozilla.org/network/standard-url-mutator;1"]
.createInstance(Ci.nsIURIMutator)
.setSpec("http://example.com") // construct the URL
.setFilePath(aFilename)
.finalize()
.QueryInterface(Ci.nsIURL);
var ext = url.fileExtension;
// This mirrors some code in nsExternalHelperAppService::DoContent
// Use the filename first and then the URI if that fails
// For images, rely solely on the mime type if known.
// All the extension is going to do is lie to us.
var lookupExt = ext;
if (aContentType?.startsWith("image/")) {
lookupExt = "";
}
var mimeInfo = getMIMEInfoForType(aContentType, lookupExt);
if (ext && mimeInfo && mimeInfo.extensionExists(ext)) {
return ext;
}
// Well, that failed. Now try the extension from the URI
var urlext;
try {
url = aURI.QueryInterface(Ci.nsIURL);
urlext = url.fileExtension;
} catch (e) {}
if (urlext && mimeInfo && mimeInfo.extensionExists(urlext)) {
return urlext;
}
// That failed as well. If we could lookup the MIME use the primary
// extension for that type.
try {
if (mimeInfo) {
return mimeInfo.primaryExtension;
}
} catch (e) {}
// Fall back on the extensions in the filename and URI for lack
// of anything better.
return ext || urlext;
}
function GetSaveModeForContentType(aContentType, aDocument) {
// We can only save a complete page if we have a loaded document,
if (!aDocument) {

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

@ -294,13 +294,11 @@ nsUnknownContentTypeDialog.prototype = {
let defaultFolder = new FileUtils.File(preferredDir);
try {
if (aDefaultFileName) {
result = this.validateLeafName(
defaultFolder,
aDefaultFileName,
aSuggestedFileExtension
);
}
result = this.validateLeafName(
defaultFolder,
aDefaultFileName,
aSuggestedFileExtension
);
} catch (ex) {
// When the default download directory is write-protected,
// prompt the user for a different target file.

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

@ -51,8 +51,6 @@
#include "nsOSHelperAppService.h"
#include "nsOSHelperAppServiceChild.h"
#include "nsContentSecurityUtils.h"
#include "nsUTF8Utils.h"
#include "nsUnicodeProperties.h"
// used to access our datastore of user-configured helper applications
#include "nsIHandlerService.h"
@ -105,7 +103,6 @@
#ifdef XP_WIN
# include "nsWindowsHelpers.h"
# include "nsLocalFile.h"
#endif
#include "mozilla/Components.h"
@ -117,8 +114,6 @@ using namespace mozilla;
using namespace mozilla::ipc;
using namespace mozilla::dom;
#define kDefaultMaxFileNameLength 255
// Download Folder location constants
#define NS_PREF_DOWNLOAD_DIR "browser.download.dir"
#define NS_PREF_DOWNLOAD_FOLDERLIST "browser.download.folderList"
@ -184,6 +179,108 @@ static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI,
return rv;
}
/**
* Given a channel, returns the filename and extension the channel has.
* This uses the URL and other sources (nsIMultiPartChannel).
* Also gives back whether the channel requested external handling (i.e.
* whether Content-Disposition: attachment was sent)
* @param aChannel The channel to extract the filename/extension from
* @param aFileName [out] Reference to the string where the filename should be
* stored. Empty if it could not be retrieved.
* WARNING - this filename may contain characters which the OS does not
* allow as part of filenames!
* @param aExtension [out] Reference to the string where the extension should
* be stored. Empty if it could not be retrieved. Stored in UTF-8.
* @param aAllowURLExtension (optional) Get the extension from the URL if no
* Content-Disposition header is present. Default is true.
* @retval true The server sent Content-Disposition:attachment or equivalent
* @retval false Content-Disposition: inline or no content-disposition header
* was sent.
*/
static bool GetFilenameAndExtensionFromChannel(nsIChannel* aChannel,
nsString& aFileName,
nsCString& aExtension,
bool aAllowURLExtension = true) {
aExtension.Truncate();
/*
* If the channel is an http or part of a multipart channel and we
* have a content disposition header set, then use the file name
* suggested there as the preferred file name to SUGGEST to the
* user. we shouldn't actually use that without their
* permission... otherwise just use our temp file
*/
bool handleExternally = false;
uint32_t disp;
nsresult rv = aChannel->GetContentDisposition(&disp);
bool gotFileNameFromURI = false;
if (NS_SUCCEEDED(rv)) {
aChannel->GetContentDispositionFilename(aFileName);
if (disp == nsIChannel::DISPOSITION_ATTACHMENT) handleExternally = true;
}
// If the disposition header didn't work, try the filename from nsIURL
nsCOMPtr<nsIURI> uri;
aChannel->GetURI(getter_AddRefs(uri));
nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
if (url && aFileName.IsEmpty()) {
if (aAllowURLExtension) {
url->GetFileExtension(aExtension);
UnescapeFragment(aExtension, url, aExtension);
// Windows ignores terminating dots. So we have to as well, so
// that our security checks do "the right thing"
// In case the aExtension consisted only of the dot, the code below will
// extract an aExtension from the filename
aExtension.Trim(".", false);
}
// try to extract the file name from the url and use that as a first pass as
// the leaf name of our temp file...
nsAutoCString leafName;
url->GetFileName(leafName);
if (!leafName.IsEmpty()) {
gotFileNameFromURI = true;
rv = UnescapeFragment(leafName, url, aFileName);
if (NS_FAILED(rv)) {
CopyUTF8toUTF16(leafName, aFileName); // use escaped name
}
}
}
// If we have a filename and no extension, remove trailing dots from the
// filename and extract the extension if that is possible.
if (aExtension.IsEmpty() && !aFileName.IsEmpty()) {
// Windows ignores terminating dots. So we have to as well, so
// that our security checks do "the right thing"
aFileName.Trim(".", false);
// We can get an extension if the filename is from a header, or if getting
// it from the URL was allowed.
bool canGetExtensionFromFilename =
!gotFileNameFromURI || aAllowURLExtension;
// ... , or if the mimetype is meaningless and we have nothing to go on:
if (!canGetExtensionFromFilename) {
nsAutoCString contentType;
if (NS_SUCCEEDED(aChannel->GetContentType(contentType))) {
canGetExtensionFromFilename =
contentType.EqualsIgnoreCase(APPLICATION_OCTET_STREAM) ||
contentType.EqualsIgnoreCase("binary/octet-stream") ||
contentType.EqualsIgnoreCase("application/x-msdownload");
}
}
if (canGetExtensionFromFilename) {
// XXX RFindCharInReadable!!
nsAutoString fileNameStr(aFileName);
int32_t idx = fileNameStr.RFindChar(char16_t('.'));
if (idx != kNotFound)
CopyUTF16toUTF8(StringTail(fileNameStr, fileNameStr.Length() - idx - 1),
aExtension);
}
}
return handleExternally;
}
/**
* Obtains the directory to use. This tends to vary per platform, and
* needs to be consistent throughout our codepaths. For platforms where
@ -515,6 +612,8 @@ static const nsExtraMimeTypeEntry extraMimeEntries[] = {
{"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"xlsx", "Microsoft Excel (Open XML)"},
// Note: if you add new image types, please also update the list in
// contentAreaUtils.js to match.
{IMAGE_ART, "art", "ART Image"},
{IMAGE_BMP, "bmp", "BMP Image"},
{IMAGE_GIF, "gif", "GIF Image"},
@ -710,10 +809,8 @@ nsresult nsExternalHelperAppService::DoContentContentProcessHelper(
uint32_t reason = nsIHelperAppLauncherDialog::REASON_CANTHANDLE;
SanitizeFileName(fileName, EmptyCString(), 0);
RefPtr<nsExternalAppHandler> handler =
new nsExternalAppHandler(nullptr, u""_ns, aContentContext, aWindowContext,
new nsExternalAppHandler(nullptr, ""_ns, aContentContext, aWindowContext,
this, fileName, reason, aForceSave);
if (!handler) {
return NS_ERROR_OUT_OF_MEMORY;
@ -733,32 +830,98 @@ NS_IMETHODIMP nsExternalHelperAppService::CreateListener(
nsAutoString fileName;
nsAutoCString fileExtension;
uint32_t reason = nsIHelperAppLauncherDialog::REASON_CANTHANDLE;
uint32_t contentDisposition = -1;
// Get the file extension and name that we will need later
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
nsCOMPtr<nsIURI> uri;
int64_t contentLength = -1;
if (channel) {
uint32_t contentDisposition = -1;
channel->GetURI(getter_AddRefs(uri));
channel->GetContentLength(&contentLength);
channel->GetContentDisposition(&contentDisposition);
if (contentDisposition == nsIChannel::DISPOSITION_ATTACHMENT) {
channel->GetContentDispositionFilename(fileName);
// Check if we have a POST request, in which case we don't want to use
// the url's extension
bool allowURLExt = !net::ChannelIsPost(channel);
// Check if we had a query string - we don't want to check the URL
// extension if a query is present in the URI
// If we already know we don't want to check the URL extension, don't
// bother checking the query
if (uri && allowURLExt) {
nsCOMPtr<nsIURL> url = do_QueryInterface(uri);
if (url) {
nsAutoCString query;
// We only care about the query for HTTP and HTTPS URLs
if (uri->SchemeIs("http") || uri->SchemeIs("https")) {
url->GetQuery(query);
}
// Only get the extension if the query is empty; if it isn't, then the
// extension likely belongs to a cgi script and isn't helpful
allowURLExt = query.IsEmpty();
}
}
// Extract name & extension
bool isAttachment = GetFilenameAndExtensionFromChannel(
channel, fileName, fileExtension, allowURLExt);
LOG(("Found extension '%s' (filename is '%s', handling attachment: %i)",
fileExtension.get(), NS_ConvertUTF16toUTF8(fileName).get(),
isAttachment));
if (isAttachment) {
reason = nsIHelperAppLauncherDialog::REASON_SERVERREQUEST;
}
}
*aStreamListener = nullptr;
LOG(("HelperAppService::DoContent: mime '%s', extension '%s'\n",
PromiseFlatCString(aMimeContentType).get(), fileExtension.get()));
// Get the file extension and name that we will need later
nsCOMPtr<nsIURI> uri;
bool allowURLExtension =
GetFileNameFromChannel(channel, fileName, getter_AddRefs(uri));
// We get the mime service here even though we're the default implementation
// of it, so it's possible to override only the mime service and not need to
// reimplement the whole external helper app service itself.
nsCOMPtr<nsIMIMEService> mimeSvc(do_GetService(NS_MIMESERVICE_CONTRACTID));
NS_ENSURE_TRUE(mimeSvc, NS_ERROR_FAILURE);
uint32_t flags = VALIDATE_ALLOW_EMPTY;
// Try to find a mime object by looking at the mime type/extension
nsCOMPtr<nsIMIMEInfo> mimeInfo;
if (aMimeContentType.Equals(APPLICATION_GUESS_FROM_EXT,
nsCaseInsensitiveCStringComparator)) {
flags |= VALIDATE_GUESS_FROM_EXTENSION;
nsAutoCString mimeType;
if (!fileExtension.IsEmpty()) {
mimeSvc->GetFromTypeAndExtension(""_ns, fileExtension,
getter_AddRefs(mimeInfo));
if (mimeInfo) {
mimeInfo->GetMIMEType(mimeType);
LOG(("OS-Provided mime type '%s' for extension '%s'\n", mimeType.get(),
fileExtension.get()));
}
}
if (fileExtension.IsEmpty() || mimeType.IsEmpty()) {
// Extension lookup gave us no useful match
mimeSvc->GetFromTypeAndExtension(
nsLiteralCString(APPLICATION_OCTET_STREAM), fileExtension,
getter_AddRefs(mimeInfo));
mimeType.AssignLiteral(APPLICATION_OCTET_STREAM);
}
if (channel) {
channel->SetContentType(mimeType);
}
// Don't overwrite SERVERREQUEST
if (reason == nsIHelperAppLauncherDialog::REASON_CANTHANDLE) {
reason = nsIHelperAppLauncherDialog::REASON_TYPESNIFFED;
}
} else {
mimeSvc->GetFromTypeAndExtension(aMimeContentType, fileExtension,
getter_AddRefs(mimeInfo));
}
nsCOMPtr<nsIMIMEInfo> mimeInfo = ValidateFileNameForSaving(
fileName, aMimeContentType, uri, nullptr, flags, allowURLExtension);
LOG(("Type/Ext lookup found 0x%p\n", mimeInfo.get()));
// No mimeinfo -> we can't continue. probably OOM.
@ -766,30 +929,17 @@ NS_IMETHODIMP nsExternalHelperAppService::CreateListener(
return NS_ERROR_OUT_OF_MEMORY;
}
if (flags & VALIDATE_GUESS_FROM_EXTENSION) {
if (channel) {
// Replace the content type with what was guessed.
nsAutoCString mimeType;
mimeInfo->GetMIMEType(mimeType);
channel->SetContentType(mimeType);
}
if (reason == nsIHelperAppLauncherDialog::REASON_CANTHANDLE) {
reason = nsIHelperAppLauncherDialog::REASON_TYPESNIFFED;
}
}
nsAutoString extension;
int32_t dotidx = fileName.RFind(".");
if (dotidx != -1) {
extension = Substring(fileName, dotidx + 1);
}
*aStreamListener = nullptr;
// We want the mimeInfo's primary extension to pass it to
// nsExternalAppHandler
nsAutoCString buf;
mimeInfo->GetPrimaryExtension(buf);
// NB: ExternalHelperAppParent depends on this listener always being an
// nsExternalAppHandler. If this changes, make sure to update that code.
nsExternalAppHandler* handler = new nsExternalAppHandler(
mimeInfo, extension, aContentContext, aWindowContext, this, fileName,
reason, aForceSave);
nsExternalAppHandler* handler =
new nsExternalAppHandler(mimeInfo, buf, aContentContext, aWindowContext,
this, fileName, reason, aForceSave);
if (!handler) {
return NS_ERROR_OUT_OF_MEMORY;
}
@ -1278,14 +1428,14 @@ NS_INTERFACE_MAP_BEGIN(nsExternalAppHandler)
NS_INTERFACE_MAP_END
nsExternalAppHandler::nsExternalAppHandler(
nsIMIMEInfo* aMIMEInfo, const nsAString& aFileExtension,
nsIMIMEInfo* aMIMEInfo, const nsACString& aTempFileExtension,
BrowsingContext* aBrowsingContext, nsIInterfaceRequestor* aWindowContext,
nsExternalHelperAppService* aExtProtSvc,
const nsAString& aSuggestedFileName, uint32_t aReason, bool aForceSave)
const nsAString& aSuggestedFilename, uint32_t aReason, bool aForceSave)
: mMimeInfo(aMIMEInfo),
mBrowsingContext(aBrowsingContext),
mWindowContext(aWindowContext),
mSuggestedFileName(aSuggestedFileName),
mSuggestedFileName(aSuggestedFilename),
mForceSave(aForceSave),
mCanceled(false),
mStopRequestIssued(false),
@ -1303,10 +1453,53 @@ nsExternalAppHandler::nsExternalAppHandler(
mRequest(nullptr),
mExtProtSvc(aExtProtSvc) {
// make sure the extention includes the '.'
if (!aFileExtension.IsEmpty() && aFileExtension.First() != '.') {
mFileExtension = char16_t('.');
if (!aTempFileExtension.IsEmpty() && aTempFileExtension.First() != '.')
mTempFileExtension = char16_t('.');
AppendUTF8toUTF16(aTempFileExtension, mTempFileExtension);
// Get mSuggestedFileName's current file extension.
nsAutoString originalFileExt;
int32_t pos = mSuggestedFileName.RFindChar('.');
if (pos != kNotFound) {
mSuggestedFileName.Right(originalFileExt,
mSuggestedFileName.Length() - pos);
}
mFileExtension.Append(aFileExtension);
// replace platform specific path separator and illegal characters to avoid
// any confusion.
// Try to keep the use of spaces or underscores in sync with the Downloads
// code sanitization in DownloadPaths.jsm
mSuggestedFileName.ReplaceChar(KNOWN_PATH_SEPARATORS, '_');
mSuggestedFileName.ReplaceChar(FILE_ILLEGAL_CHARACTERS, ' ');
mSuggestedFileName.ReplaceChar(char16_t(0), '_');
mTempFileExtension.ReplaceChar(KNOWN_PATH_SEPARATORS, '_');
mTempFileExtension.ReplaceChar(FILE_ILLEGAL_CHARACTERS, ' ');
// Remove unsafe bidi characters which might have spoofing implications (bug
// 511521).
const char16_t unsafeBidiCharacters[] = {
char16_t(0x061c), // Arabic Letter Mark
char16_t(0x200e), // Left-to-Right Mark
char16_t(0x200f), // Right-to-Left Mark
char16_t(0x202a), // Left-to-Right Embedding
char16_t(0x202b), // Right-to-Left Embedding
char16_t(0x202c), // Pop Directional Formatting
char16_t(0x202d), // Left-to-Right Override
char16_t(0x202e), // Right-to-Left Override
char16_t(0x2066), // Left-to-Right Isolate
char16_t(0x2067), // Right-to-Left Isolate
char16_t(0x2068), // First Strong Isolate
char16_t(0x2069), // Pop Directional Isolate
char16_t(0)};
mSuggestedFileName.ReplaceChar(unsafeBidiCharacters, '_');
mTempFileExtension.ReplaceChar(unsafeBidiCharacters, '_');
// Remove trailing or leading spaces that we may have generated while
// sanitizing.
mSuggestedFileName.CompressWhitespace();
mTempFileExtension.CompressWhitespace();
EnsureCorrectExtension(originalFileExt);
mBufferSize = Preferences::GetUint("network.buffer.cache.size", 4096);
}
@ -1315,6 +1508,80 @@ nsExternalAppHandler::~nsExternalAppHandler() {
MOZ_ASSERT(!mSaver, "Saver should hold a reference to us until deleted");
}
bool nsExternalAppHandler::ShouldForceExtension(const nsString& aFileExt) {
nsAutoCString MIMEType;
if (!mMimeInfo || NS_FAILED(mMimeInfo->GetMIMEType(MIMEType))) {
return false;
}
bool canForce = StringBeginsWith(MIMEType, "image/"_ns) ||
StringBeginsWith(MIMEType, "audio/"_ns) ||
StringBeginsWith(MIMEType, "video/"_ns);
if (!canForce &&
StaticPrefs::browser_download_sanitize_non_media_extensions()) {
for (const char* mime : forcedExtensionMimetypes) {
if (MIMEType.Equals(mime)) {
canForce = true;
break;
}
}
}
if (!canForce) {
return false;
}
// If we get here, we know for sure the mimetype allows us to overwrite the
// existing extension, if it's wrong. Return whether the extension is wrong:
bool knownExtension = false;
// Note that aFileExt is either empty or consists of an extension
// *including the dot* which we remove for ExtensionExists().
return (
aFileExt.IsEmpty() || aFileExt.EqualsLiteral(".") ||
(NS_SUCCEEDED(mMimeInfo->ExtensionExists(
Substring(NS_ConvertUTF16toUTF8(aFileExt), 1), &knownExtension)) &&
!knownExtension));
}
void nsExternalAppHandler::EnsureCorrectExtension(const nsString& aFileExt) {
// If we don't have an extension (which will include the .),
// just short-circuit.
if (mTempFileExtension.Length() <= 1) {
return;
}
// After removing trailing whitespaces from the name, if we have a
// temp file extension, there are broadly 2 cases where we want to
// replace the extension.
// First, if the file extension contains invalid characters.
// Second, for document type mimetypes, if the extension is either
// missing or not valid for this mimetype.
bool replaceExtension =
(aFileExt.FindCharInSet(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS) !=
kNotFound) ||
ShouldForceExtension(aFileExt);
if (replaceExtension) {
int32_t pos = mSuggestedFileName.RFindChar('.');
if (pos != kNotFound) {
mSuggestedFileName =
Substring(mSuggestedFileName, 0, pos) + mTempFileExtension;
} else {
mSuggestedFileName.Append(mTempFileExtension);
}
}
/*
* Ensure we don't double-append the file extension if it matches:
*/
if (replaceExtension ||
aFileExt.Equals(mTempFileExtension, nsCaseInsensitiveStringComparator)) {
// Matches -> mTempFileExtension can be empty
mTempFileExtension.Truncate();
}
}
void nsExternalAppHandler::DidDivertRequest(nsIRequest* request) {
MOZ_ASSERT(XRE_IsContentProcess(), "in child process");
// Remove our request from the child loadGroup
@ -2526,7 +2793,7 @@ NS_IMETHODIMP nsExternalAppHandler::PromptForSaveDestination() {
}
if (mSuggestedFileName.IsEmpty()) {
RequestSaveDestination(mTempLeafName, mFileExtension);
RequestSaveDestination(mTempLeafName, mTempFileExtension);
} else {
nsAutoString fileExt;
int32_t pos = mSuggestedFileName.RFindChar('.');
@ -2534,7 +2801,7 @@ NS_IMETHODIMP nsExternalAppHandler::PromptForSaveDestination() {
mSuggestedFileName.Right(fileExt, mSuggestedFileName.Length() - pos);
}
if (fileExt.IsEmpty()) {
fileExt = mFileExtension;
fileExt = mTempFileExtension;
}
RequestSaveDestination(mSuggestedFileName, fileExt);
@ -2662,13 +2929,7 @@ NS_IMETHODIMP nsExternalAppHandler::SetDownloadToLaunch(
}
#ifdef XP_WIN
// Ensure we don't double-append the file extension if it matches:
if (StringEndsWith(mSuggestedFileName, mFileExtension,
nsCaseInsensitiveStringComparator)) {
fileToUse->Append(mSuggestedFileName);
} else {
fileToUse->Append(mSuggestedFileName + mFileExtension);
}
fileToUse->Append(mSuggestedFileName + mTempFileExtension);
#else
fileToUse->Append(mSuggestedFileName);
#endif
@ -3223,474 +3484,3 @@ nsresult nsExternalHelperAppService::GetMIMEInfoFromOS(
*aFound = false;
return NS_ERROR_NOT_IMPLEMENTED;
}
bool nsExternalHelperAppService::GetFileNameFromChannel(nsIChannel* aChannel,
nsAString& aFileName,
nsIURI** aURI) {
if (!aChannel) {
return false;
}
aChannel->GetURI(aURI);
nsCOMPtr<nsIURL> url = do_QueryInterface(*aURI);
// Check if we have a POST request, in which case we don't want to use
// the url's extension
bool allowURLExt = !net::ChannelIsPost(aChannel);
// Check if we had a query string - we don't want to check the URL
// extension if a query is present in the URI
// If we already know we don't want to check the URL extension, don't
// bother checking the query
if (url && allowURLExt) {
nsAutoCString query;
// We only care about the query for HTTP and HTTPS URLs
if (url->SchemeIs("http") || url->SchemeIs("https")) {
url->GetQuery(query);
}
// Only get the extension if the query is empty; if it isn't, then the
// extension likely belongs to a cgi script and isn't helpful
allowURLExt = query.IsEmpty();
}
aChannel->GetContentDispositionFilename(aFileName);
return allowURLExt;
}
NS_IMETHODIMP
nsExternalHelperAppService::GetValidFileName(nsIChannel* aChannel,
const nsACString& aType,
nsIURI* aOriginalURI,
uint32_t aFlags,
nsAString& aOutFileName) {
nsCOMPtr<nsIURI> uri;
bool allowURLExtension =
GetFileNameFromChannel(aChannel, aOutFileName, getter_AddRefs(uri));
nsCOMPtr<nsIMIMEInfo> mimeInfo = ValidateFileNameForSaving(
aOutFileName, aType, uri, aOriginalURI, aFlags, allowURLExtension);
return NS_OK;
}
NS_IMETHODIMP
nsExternalHelperAppService::ValidateFileNameForSaving(
const nsAString& aFileName, const nsACString& aType, uint32_t aFlags,
nsAString& aOutFileName) {
nsAutoString fileName(aFileName);
// Just sanitize the filename only.
if (aFlags & VALIDATE_SANITIZE_ONLY) {
nsAutoString extension;
int32_t dotidx = fileName.RFind(".");
if (dotidx != -1) {
extension = Substring(fileName, dotidx + 1);
}
SanitizeFileName(fileName, NS_ConvertUTF16toUTF8(extension), aFlags);
} else {
nsCOMPtr<nsIMIMEInfo> mimeInfo = ValidateFileNameForSaving(
fileName, aType, nullptr, nullptr, aFlags, true);
}
aOutFileName = fileName;
return NS_OK;
}
already_AddRefed<nsIMIMEInfo>
nsExternalHelperAppService::ValidateFileNameForSaving(
nsAString& aFileName, const nsACString& aMimeType, nsIURI* aURI,
nsIURI* aOriginalURI, uint32_t aFlags, bool aAllowURLExtension) {
nsAutoString fileName(aFileName);
nsAutoCString extension;
nsCOMPtr<nsIMIMEInfo> mimeInfo;
bool isBinaryType = aMimeType.EqualsLiteral(APPLICATION_OCTET_STREAM) ||
aMimeType.EqualsLiteral(BINARY_OCTET_STREAM) ||
aMimeType.EqualsLiteral("application/x-msdownload");
// We get the mime service here even though we're the default implementation
// of it, so it's possible to override only the mime service and not need to
// reimplement the whole external helper app service itself.
nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1");
if (mimeService) {
if (fileName.IsEmpty()) {
nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
// Try to extract the file name from the url and use that as a first
// pass as the leaf name of our temp file...
if (url) {
nsAutoCString leafName;
url->GetFileName(leafName);
if (!leafName.IsEmpty()) {
if (NS_SUCCEEDED(UnescapeFragment(leafName, url, fileName))) {
CopyUTF8toUTF16(leafName, aFileName); // use escaped name
}
}
// Only get the extension from the URL if allowed, or if this
// is a binary type in which case the type might not be valid
// anyway.
if (aAllowURLExtension || isBinaryType) {
url->GetFileExtension(extension);
}
}
} else {
// Determine the current extension for the filename.
int32_t dotidx = fileName.RFind(".");
if (dotidx != -1) {
CopyUTF16toUTF8(Substring(fileName, dotidx + 1), extension);
}
}
if (aFlags & VALIDATE_GUESS_FROM_EXTENSION) {
nsAutoCString mimeType;
if (!extension.IsEmpty()) {
mimeService->GetFromTypeAndExtension(EmptyCString(), extension,
getter_AddRefs(mimeInfo));
if (mimeInfo) {
mimeInfo->GetMIMEType(mimeType);
}
}
if (mimeType.IsEmpty()) {
// Extension lookup gave us no useful match, so use octet-stream
// instead.
mimeService->GetFromTypeAndExtension(
nsLiteralCString(APPLICATION_OCTET_STREAM), extension,
getter_AddRefs(mimeInfo));
}
} else if (!aMimeType.IsEmpty()) {
// If this is a binary type, include the extension as a hint to get
// the mime info. For other types, the mime type itself should be
// sufficient.
// The special case for application/ogg is because that type could
// actually be used for a video which can better be determined by the
// extension. This is tested by browser_save_video.js.
bool useExtension =
isBinaryType || aMimeType.EqualsLiteral(APPLICATION_OGG);
mimeService->GetFromTypeAndExtension(
aMimeType, useExtension ? extension : EmptyCString(),
getter_AddRefs(mimeInfo));
if (mimeInfo) {
// But if no primary extension was returned, this mime type is probably
// an unknown type. Look it up again but this time supply the extension.
nsAutoCString primaryExtension;
mimeInfo->GetPrimaryExtension(primaryExtension);
if (primaryExtension.IsEmpty()) {
mimeService->GetFromTypeAndExtension(aMimeType, extension,
getter_AddRefs(mimeInfo));
}
}
}
}
// Windows ignores terminating dots. So we have to as well, so
// that our security checks do "the right thing"
fileName.Trim(".", false);
// If an empty filename is allowed, then return early. It will be saved
// using the filename of the temporary file that was created for the download.
if (aFlags & VALIDATE_ALLOW_EMPTY && fileName.IsEmpty()) {
aFileName.Truncate();
return mimeInfo.forget();
}
if (mimeInfo) {
bool isValidExtension;
if (extension.IsEmpty() ||
NS_FAILED(mimeInfo->ExtensionExists(extension, &isValidExtension)) ||
!isValidExtension) {
// Skip these checks for text and binary, so we don't append the unneeded
// .txt or other extension.
if (aMimeType.EqualsLiteral(TEXT_PLAIN) || isBinaryType) {
extension.Truncate();
} else {
nsAutoCString originalExtension(extension);
// If an original url was supplied, see if it has a valid extension.
bool useOldExtension = false;
if (aOriginalURI) {
nsCOMPtr<nsIURL> originalURL(do_QueryInterface(aOriginalURI));
if (originalURL) {
originalURL->GetFileExtension(extension);
if (!extension.IsEmpty()) {
mimeInfo->ExtensionExists(extension, &useOldExtension);
}
}
}
if (!useOldExtension) {
// If the filename doesn't have a valid extension, or we don't know
// the extension, try to use the primary extension for the type. If we
// don't know the primary extension for the type, just continue with
// the existing extension, or leave the filename with no extension.
mimeInfo->GetPrimaryExtension(extension);
}
ModifyExtensionType modify =
ShouldModifyExtension(mimeInfo, originalExtension);
if (modify == ModifyExtension_Replace) {
int32_t dotidx = fileName.RFind(".");
if (dotidx != -1) {
// Remove the existing extension and replace it.
fileName.Truncate(dotidx);
}
}
// Otherwise, just append the proper extension to the end of the
// filename, adding to the invalid extension that might already be
// there.
if (modify != ModifyExtension_Ignore && !extension.IsEmpty()) {
fileName.AppendLiteral(".");
fileName.Append(NS_ConvertUTF8toUTF16(extension));
}
}
}
}
#ifdef XP_WIN
nsLocalFile::CheckForReservedFileName(fileName);
#endif
// If no filename is present, use a default filename.
if (!(aFlags & VALIDATE_NO_DEFAULT_FILENAME) &&
(fileName.Length() == 0 || fileName.RFind(".") == 0)) {
nsCOMPtr<nsIStringBundleService> stringService =
mozilla::components::StringBundle::Service();
if (stringService) {
nsCOMPtr<nsIStringBundle> bundle;
if (NS_SUCCEEDED(stringService->CreateBundle(
"chrome://global/locale/contentAreaCommands.properties",
getter_AddRefs(bundle)))) {
nsAutoString defaultFileName;
bundle->GetStringFromName("DefaultSaveFileName", defaultFileName);
// Append any existing extension to the default filename.
fileName = defaultFileName + fileName;
}
}
// Use 'index' as a last resort.
if (!fileName.Length()) {
fileName.AssignLiteral("index");
}
}
// Make the filename safe for the filesystem
SanitizeFileName(fileName, extension, aFlags);
aFileName = fileName;
return mimeInfo.forget();
}
void nsExternalHelperAppService::SanitizeFileName(nsAString& aFileName,
const nsACString& aExtension,
uint32_t aFlags) {
nsAutoString fileName(aFileName);
// Replace characters
fileName.ReplaceChar(KNOWN_PATH_SEPARATORS, '_');
fileName.ReplaceChar(FILE_ILLEGAL_CHARACTERS, ' ');
fileName.StripChar(char16_t(0));
const char16_t *startStr, *endStr;
fileName.BeginReading(startStr);
fileName.EndReading(endStr);
// True if multiple consecutive whitespace characters should
// be replaced by single space ' '.
bool collapseWhitespace = !(aFlags & VALIDATE_DONT_COLLAPSE_WHITESPACE);
// The maximum filename length differs based on the platform:
// Windows (FAT/NTFS) stores filenames as a maximum of 255 UTF-16 code units.
// Mac (APFS) stores filenames with a maximum 255 of UTF-8 code units.
// Linux (ext3/ext4...) stores filenames with a maximum 255 bytes.
// So here we just use the maximum of 255 bytes.
uint32_t maxBytes = 0; // 0 means don't truncate at a maximum size.
if (!(aFlags & VALIDATE_DONT_TRUNCATE)) {
maxBytes = 255 - aExtension.Length() - 1;
}
// True if the last character added was whitespace.
bool lastWasWhitespace = false;
// True if the filename is too long and must be truncated.
bool longFileName = false;
// Length of the filename that fits into the maximum size excluding the
// extension and period.
int32_t longFileNameEnd = -1;
// Index of the last character added that was not a character that can be
// trimmed off of the end of the string. Trimmable characters are whitespace,
// periods and the vowel separator u'\u180e'. If all the characters after this
// point are trimmable characters, truncate the string to this point after
// iterating over the filename.
int32_t lastNonTrimmable = -1;
// The number of bytes that the string would occupy if encoded in UTF-8.
uint32_t bytesLength = 0;
// This algorithm iterates over each character in the string and appends it
// or a replacement character if needed to outFileName.
nsAutoString outFileName;
while (startStr < endStr) {
bool err = false;
char32_t nextChar = UTF16CharEnumerator::NextChar(&startStr, endStr, &err);
if (err) {
break;
}
if (nextChar == char16_t(0)) {
continue;
}
auto unicodeCategory = unicode::GetGeneralCategory(nextChar);
if (unicodeCategory == HB_UNICODE_GENERAL_CATEGORY_CONTROL ||
unicodeCategory == HB_UNICODE_GENERAL_CATEGORY_LINE_SEPARATOR ||
unicodeCategory == HB_UNICODE_GENERAL_CATEGORY_PARAGRAPH_SEPARATOR) {
// Skip over any control characters and separators.
continue;
}
if (maxBytes) {
// UTF16CharEnumerator already converts surrogate pairs, so we can use
// a simple computation of byte length here.
bytesLength += nextChar < 0x80 ? 1
: nextChar < 0x800 ? 2
: nextChar < 0x10000 ? 3
: 4;
if (bytesLength > maxBytes) {
if (longFileNameEnd == -1) {
longFileNameEnd = int32_t(outFileName.Length());
}
if (bytesLength > 255) {
longFileName = true;
break;
}
}
}
if (unicodeCategory == HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR ||
nextChar == u'\ufeff') {
// Trim out any whitespace characters at the beginning of the filename,
// and only add whitespace in the middle of the filename if the last
// character was not whitespace or if we are not collapsing whitespace.
if (!outFileName.IsEmpty() &&
(!lastWasWhitespace || !collapseWhitespace)) {
// Allow the ideographic space if it is present, otherwise replace with
// ' '.
if (nextChar != u'\u3000') {
nextChar = ' ';
}
lastWasWhitespace = true;
} else {
lastWasWhitespace = true;
continue;
}
} else {
lastWasWhitespace = false;
if (nextChar == '.' || nextChar == u'\u180e') {
// Don't add any periods or vowel separators at the beginning of the
// string. Note also that lastNonTrimmable is not adjusted in this
// case, because periods and vowel separators are included in the
// set of characters to trim at the end of the filename.
if (outFileName.IsEmpty()) {
continue;
}
} else {
if (unicodeCategory == HB_UNICODE_GENERAL_CATEGORY_FORMAT) {
// Replace formatting characters with an underscore.
nextChar = '_';
}
lastNonTrimmable = int32_t(outFileName.Length()) + 1;
}
}
AppendUCS4ToUTF16(nextChar, outFileName);
}
// There are two ways in which the filename should be truncated:
// - If the filename was too long, truncate the name at the length
// of the filename minus the space needed for the extension and period.
// This position is indicated by longFileNameEnd.
// - lastNonTrimmable will indicate the last character that was not
// whitespace, a period, or a vowel separator at the end of the
// the string, so the string should be truncated there as well.
// If both apply, use the earliest position.
if (lastNonTrimmable >= 0) {
outFileName.Truncate(longFileName
? std::min(longFileNameEnd, lastNonTrimmable)
: lastNonTrimmable);
}
// If the filename is too long, truncate it, but preserve the desired
// extension.
if (!maxBytes && !(aFlags & VALIDATE_DONT_TRUNCATE) &&
outFileName.Length() > kDefaultMaxFileNameLength) {
// This is extremely unlikely, but if the extension is larger than the
// maximum size, just get rid of it.
if (aExtension.Length() >= kDefaultMaxFileNameLength) {
outFileName.Truncate(kDefaultMaxFileNameLength - 1);
} else {
outFileName.Truncate(kDefaultMaxFileNameLength - aExtension.Length() - 1);
longFileName = true;
}
}
if (longFileName && !outFileName.IsEmpty()) {
if (outFileName.Last() != '.') {
outFileName.AppendLiteral(".");
}
outFileName.Append(NS_ConvertUTF8toUTF16(aExtension));
}
aFileName = outFileName;
}
nsExternalHelperAppService::ModifyExtensionType
nsExternalHelperAppService::ShouldModifyExtension(nsIMIMEInfo* aMimeInfo,
const nsCString& aFileExt) {
nsAutoCString MIMEType;
if (!aMimeInfo || NS_FAILED(aMimeInfo->GetMIMEType(MIMEType))) {
return ModifyExtension_Append;
}
// Determine whether the extensions should be appended or replaced depending
// on the content type.
bool canForce = StringBeginsWith(MIMEType, "image/"_ns) ||
StringBeginsWith(MIMEType, "audio/"_ns) ||
StringBeginsWith(MIMEType, "video/"_ns);
if (!canForce) {
for (const char* mime : forcedExtensionMimetypes) {
if (MIMEType.Equals(mime)) {
if (!StaticPrefs::browser_download_sanitize_non_media_extensions()) {
return ModifyExtension_Ignore;
}
canForce = true;
break;
}
}
if (!canForce) {
return ModifyExtension_Append;
}
}
// If we get here, we know for sure the mimetype allows us to modify the
// existing extension, if it's wrong. Return whether we should replace it
// or append it.
bool knownExtension = false;
// Note that aFileExt is either empty or consists of an extension
// excluding the dot.
if (aFileExt.IsEmpty() ||
(NS_SUCCEEDED(aMimeInfo->ExtensionExists(aFileExt, &knownExtension)) &&
!knownExtension)) {
return ModifyExtension_Replace;
}
return ModifyExtension_Append;
}

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

@ -199,32 +199,6 @@ class nsExternalHelperAppService : public nsIExternalHelperAppService,
*/
void ExpungeTemporaryPrivateFiles();
bool GetFileNameFromChannel(nsIChannel* aChannel, nsAString& aFileName,
nsIURI** aURI);
// Internal version of the method from nsIMIMEService.
already_AddRefed<nsIMIMEInfo> ValidateFileNameForSaving(
nsAString& aFileName, const nsACString& aMimeType, nsIURI* aURI,
nsIURI* aOriginalURI, uint32_t aFlags, bool aAllowURLExtension);
void SanitizeFileName(nsAString& aFileName, const nsACString& aExtension,
uint32_t aFlags);
/**
* Helper routine that checks how we should modify an extension
* for this file.
*/
enum ModifyExtensionType {
// Replace an invalid extension with the preferred one.
ModifyExtension_Replace = 0,
// Append the preferred extension after any existing one.
ModifyExtension_Append = 1,
// Don't modify the extension.
ModifyExtension_Ignore = 2
};
ModifyExtensionType ShouldModifyExtension(nsIMIMEInfo* aMimeInfo,
const nsCString& aFileExt);
/**
* Array for the files that should be deleted
*/
@ -277,15 +251,15 @@ class nsExternalAppHandler final : public nsIStreamListener,
* in which case dialogs will be parented to
* aContentContext.
* @param mExtProtSvc nsExternalHelperAppService on creation
* @param aSuggestedFileName The filename to use
* @param aFileName The filename to use
* @param aReason A constant from nsIHelperAppLauncherDialog
* indicating why the request is handled by a helper app.
*/
nsExternalAppHandler(nsIMIMEInfo* aMIMEInfo, const nsAString& aFileExtension,
nsExternalAppHandler(nsIMIMEInfo* aMIMEInfo, const nsACString& aFileExtension,
mozilla::dom::BrowsingContext* aBrowsingContext,
nsIInterfaceRequestor* aWindowContext,
nsExternalHelperAppService* aExtProtSvc,
const nsAString& aSuggestedFileName, uint32_t aReason,
const nsAString& aFilename, uint32_t aReason,
bool aForceSave);
/**
@ -310,7 +284,7 @@ class nsExternalAppHandler final : public nsIStreamListener,
nsCOMPtr<nsIFile> mTempFile;
nsCOMPtr<nsIURI> mSourceUrl;
nsString mFileExtension;
nsString mTempFileExtension;
nsString mTempLeafName;
/**
@ -499,6 +473,12 @@ class nsExternalAppHandler final : public nsIStreamListener,
*/
bool GetNeverAskFlagFromPref(const char* prefName, const char* aContentType);
/**
* Helper routine that checks whether we should enforce an extension
* for this file.
*/
bool ShouldForceExtension(const nsString& aFileExt);
/**
* Helper routine to ensure that mSuggestedFileName ends in the correct
* extension, in case the original extension contains invalid characters
@ -506,7 +486,7 @@ class nsExternalAppHandler final : public nsIStreamListener,
* extension (image/, video/, and audio/ based mimetypes, and a few specific
* document types).
*
* It also ensure that mFileExtension only contains an extension
* It also ensure that mTempFileExtension only contains an extension
* when it is different from mSuggestedFileName's extension.
*/
void EnsureCorrectExtension(const nsString& aFileExt);

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

@ -108,9 +108,6 @@ 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

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

@ -1,737 +0,0 @@
// 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));
});

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

@ -1,296 +0,0 @@
<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>

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

@ -1,135 +0,0 @@
/* 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,7 +15,6 @@ 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]

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

@ -44,7 +44,6 @@
#include "imgIEncoder.h"
#include "imgITools.h"
#include "WinUtils.h"
#include "nsLocalFile.h"
#include "mozilla/LazyIdleThread.h"
#include <algorithm>
@ -1088,6 +1087,36 @@ nsDataObj ::GetFileContents(FORMATETC& aFE, STGMEDIUM& aSTG) {
} // GetFileContents
//
// Given a unicode string, we ensure that it contains only characters which are
// valid within the file system. Remove all forbidden characters from the name,
// and completely disallow any title that starts with a forbidden name and
// extension (e.g. "nul" is invalid, but "nul." and "nul.txt" are also invalid
// and will cause problems).
//
// It would seem that this is more functionality suited to being in nsIFile.
//
static void MangleTextToValidFilename(nsString& aText) {
static const char* forbiddenNames[] = {
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8",
"COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7",
"LPT8", "LPT9", "CON", "PRN", "AUX", "NUL", "CLOCK$"};
aText.StripChars(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS);
aText.CompressWhitespace(true, true);
uint32_t nameLen;
for (size_t n = 0; n < ArrayLength(forbiddenNames); ++n) {
nameLen = (uint32_t)strlen(forbiddenNames[n]);
if (aText.EqualsIgnoreCase(forbiddenNames[n], nameLen)) {
// invalid name is either the entire string, or a prefix with a period
if (aText.Length() == nameLen || aText.CharAt(nameLen) == char16_t('.')) {
aText.Truncate();
break;
}
}
}
}
//
// Given a unicode string, convert it down to a valid local charset filename
// with the supplied extension. This ensures that we do not cut MBCS characters
@ -1100,7 +1129,7 @@ static bool CreateFilenameFromTextA(nsString& aText, const char* aExtension,
// ensure that the supplied name doesn't have invalid characters. If
// a valid mangled filename couldn't be created then it will leave the
// text empty.
nsLocalFile::CheckForReservedFileName(aText);
MangleTextToValidFilename(aText);
if (aText.IsEmpty()) return false;
// repeatably call WideCharToMultiByte as long as the title doesn't fit in the
@ -1132,7 +1161,7 @@ static bool CreateFilenameFromTextW(nsString& aText, const wchar_t* aExtension,
// ensure that the supplied name doesn't have invalid characters. If
// a valid mangled filename couldn't be created then it will leave the
// text empty.
nsLocalFile::CheckForReservedFileName(aText);
MangleTextToValidFilename(aText);
if (aText.IsEmpty()) return false;
const int extensionLen = wcslen(aExtension);
@ -2154,7 +2183,7 @@ HRESULT nsDataObj::GetDownloadDetails(nsIURI** aSourceURI,
if (srcFileName.IsEmpty()) return E_FAIL;
// make the name safe for the filesystem
nsLocalFile::CheckForReservedFileName(srcFileName);
MangleTextToValidFilename(srcFileName);
sourceURI.swap(*aSourceURI);
aFilename = srcFileName;

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

@ -129,16 +129,9 @@ void NS_MakeRandomString(char* aBuf, int32_t aBufLen);
#define LFSTR "\012"
#define CRLF "\015\012" /* A CR LF equivalent string */
#if defined(ANDROID)
// On mobile devices, the file system may be very limited in what it
// considers valid characters. To avoid errors, sanitize conservatively.
# define OS_FILE_ILLEGAL_CHARACTERS "/:*?\"<>|;,+=[]"
#else
// Otherwise, we use the most restrictive filesystem as our default set of
// illegal filename characters. This is currently Windows.
# define OS_FILE_ILLEGAL_CHARACTERS "/:*?\"<>|"
#endif
// We use the most restrictive filesystem as our default set of illegal filename
// characters. This is currently Windows.
#define OS_FILE_ILLEGAL_CHARACTERS "/:*?\"<>|"
// We also provide a list of all known file path separators for all filesystems.
// This can be used in replacement of FILE_PATH_SEPARATOR when you need to
// identify or replace all known path separators.
@ -160,12 +153,7 @@ void NS_MakeRandomString(char* aBuf, int32_t aBufLen);
"\001\002\003\004\005\006\007" \
"\010\011\012\013\014\015\016\017" \
"\020\021\022\023\024\025\026\027" \
"\030\031\032\033\034\035\036\037" \
"\177" \
"\200\201\202\203\204\205\206\207" \
"\210\211\212\213\214\215\216\217" \
"\220\221\222\223\224\225\226\227" \
"\230\231\232\233\234\235\236\237"
"\030\031\032\033\034\035\036\037"
#define FILE_ILLEGAL_CHARACTERS CONTROL_CHARACTERS OS_FILE_ILLEGAL_CHARACTERS

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

@ -191,26 +191,6 @@ nsresult nsLocalFile::RevealFile(const nsString& aResolvedPath) {
return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
}
// static
void nsLocalFile::CheckForReservedFileName(nsString& aFileName) {
static const char* forbiddenNames[] = {
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8",
"COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7",
"LPT8", "LPT9", "CON", "PRN", "AUX", "NUL", "CLOCK$"};
uint32_t nameLen;
for (size_t n = 0; n < ArrayLength(forbiddenNames); ++n) {
nameLen = (uint32_t)strlen(forbiddenNames[n]);
if (aFileName.EqualsIgnoreCase(forbiddenNames[n], nameLen)) {
// invalid name is either the entire string, or a prefix with a period
if (aFileName.Length() == nameLen ||
aFileName.CharAt(nameLen) == char16_t('.')) {
aFileName.Truncate();
}
}
}
}
class nsDriveEnumerator : public nsSimpleEnumerator,
public nsIDirectoryEnumerator {
public:

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

@ -49,10 +49,6 @@ class nsLocalFile final : public nsILocalFileWin {
// Called off the main thread to open the window revealing the file
static nsresult RevealFile(const nsString& aResolvedPath);
// Checks if the filename is one of the windows reserved filenames
// (com1, com2, etc...) and truncates the string if so.
static void CheckForReservedFileName(nsString& aFileName);
private:
// CopyMove and CopySingleFile constants for |options| parameter:
enum CopyFileOption {