зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1746052, add methods to the mime service that compute and validate a filename for a given content type, r=Gijs
The code in SanitizeFilename will be expanded upon in the following patch. Differential Revision: https://phabricator.services.mozilla.com/D135951
This commit is contained in:
Родитель
976c5b23db
Коммит
2596b808fa
|
@ -8,6 +8,7 @@
|
|||
interface nsIFile;
|
||||
interface nsIMIMEInfo;
|
||||
interface nsIURI;
|
||||
interface nsIChannel;
|
||||
|
||||
%{C++
|
||||
#define NS_MIMESERVICE_CID \
|
||||
|
@ -97,4 +98,103 @@ 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;
|
||||
|
||||
/**
|
||||
* 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 (' ').
|
||||
* - Unless VALIDATE_DONT_TRUNCATE is specified, the filename is truncated
|
||||
* to a maximum length, preserving the extension if possible.
|
||||
*
|
||||
* 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);
|
||||
};
|
||||
|
|
|
@ -114,6 +114,8 @@ 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"
|
||||
|
@ -179,108 +181,6 @@ 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
|
||||
|
@ -809,8 +709,10 @@ nsresult nsExternalHelperAppService::DoContentContentProcessHelper(
|
|||
|
||||
uint32_t reason = nsIHelperAppLauncherDialog::REASON_CANTHANDLE;
|
||||
|
||||
SanitizeFileName(fileName, EmptyCString(), 0);
|
||||
|
||||
RefPtr<nsExternalAppHandler> handler =
|
||||
new nsExternalAppHandler(nullptr, ""_ns, aContentContext, aWindowContext,
|
||||
new nsExternalAppHandler(nullptr, u""_ns, aContentContext, aWindowContext,
|
||||
this, fileName, reason, aForceSave);
|
||||
if (!handler) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
|
@ -830,98 +732,32 @@ 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) {
|
||||
channel->GetURI(getter_AddRefs(uri));
|
||||
channel->GetContentLength(&contentLength);
|
||||
uint32_t contentDisposition = -1;
|
||||
channel->GetContentDisposition(&contentDisposition);
|
||||
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) {
|
||||
if (contentDisposition == nsIChannel::DISPOSITION_ATTACHMENT) {
|
||||
reason = nsIHelperAppLauncherDialog::REASON_SERVERREQUEST;
|
||||
}
|
||||
}
|
||||
|
||||
LOG(("HelperAppService::DoContent: mime '%s', extension '%s'\n",
|
||||
PromiseFlatCString(aMimeContentType).get(), fileExtension.get()));
|
||||
*aStreamListener = nullptr;
|
||||
|
||||
// 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);
|
||||
// Get the file extension and name that we will need later
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
bool allowURLExtension =
|
||||
GetFileNameFromChannel(channel, fileName, getter_AddRefs(uri));
|
||||
|
||||
// Try to find a mime object by looking at the mime type/extension
|
||||
nsCOMPtr<nsIMIMEInfo> mimeInfo;
|
||||
uint32_t flags = VALIDATE_DEFAULT;
|
||||
if (aMimeContentType.Equals(APPLICATION_GUESS_FROM_EXT,
|
||||
nsCaseInsensitiveCStringComparator)) {
|
||||
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));
|
||||
flags = VALIDATE_GUESS_FROM_EXTENSION;
|
||||
}
|
||||
|
||||
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.
|
||||
|
@ -929,17 +765,30 @@ NS_IMETHODIMP nsExternalHelperAppService::CreateListener(
|
|||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
*aStreamListener = nullptr;
|
||||
// We want the mimeInfo's primary extension to pass it to
|
||||
// nsExternalAppHandler
|
||||
nsAutoCString buf;
|
||||
mimeInfo->GetPrimaryExtension(buf);
|
||||
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);
|
||||
}
|
||||
|
||||
// NB: ExternalHelperAppParent depends on this listener always being an
|
||||
// nsExternalAppHandler. If this changes, make sure to update that code.
|
||||
nsExternalAppHandler* handler =
|
||||
new nsExternalAppHandler(mimeInfo, buf, aContentContext, aWindowContext,
|
||||
this, fileName, reason, aForceSave);
|
||||
nsExternalAppHandler* handler = new nsExternalAppHandler(
|
||||
mimeInfo, extension, aContentContext, aWindowContext, this, fileName,
|
||||
reason, aForceSave);
|
||||
if (!handler) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
@ -1428,14 +1277,14 @@ NS_INTERFACE_MAP_BEGIN(nsExternalAppHandler)
|
|||
NS_INTERFACE_MAP_END
|
||||
|
||||
nsExternalAppHandler::nsExternalAppHandler(
|
||||
nsIMIMEInfo* aMIMEInfo, const nsACString& aTempFileExtension,
|
||||
nsIMIMEInfo* aMIMEInfo, const nsAString& aFileExtension,
|
||||
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),
|
||||
|
@ -1453,53 +1302,10 @@ nsExternalAppHandler::nsExternalAppHandler(
|
|||
mRequest(nullptr),
|
||||
mExtProtSvc(aExtProtSvc) {
|
||||
// make sure the extention includes the '.'
|
||||
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);
|
||||
if (!aFileExtension.IsEmpty() && aFileExtension.First() != '.') {
|
||||
mFileExtension = char16_t('.');
|
||||
}
|
||||
|
||||
// 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);
|
||||
mFileExtension.Append(aFileExtension);
|
||||
|
||||
mBufferSize = Preferences::GetUint("network.buffer.cache.size", 4096);
|
||||
}
|
||||
|
@ -1508,80 +1314,6 @@ 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
|
||||
|
@ -2793,7 +2525,7 @@ NS_IMETHODIMP nsExternalAppHandler::PromptForSaveDestination() {
|
|||
}
|
||||
|
||||
if (mSuggestedFileName.IsEmpty()) {
|
||||
RequestSaveDestination(mTempLeafName, mTempFileExtension);
|
||||
RequestSaveDestination(mTempLeafName, mFileExtension);
|
||||
} else {
|
||||
nsAutoString fileExt;
|
||||
int32_t pos = mSuggestedFileName.RFindChar('.');
|
||||
|
@ -2801,7 +2533,7 @@ NS_IMETHODIMP nsExternalAppHandler::PromptForSaveDestination() {
|
|||
mSuggestedFileName.Right(fileExt, mSuggestedFileName.Length() - pos);
|
||||
}
|
||||
if (fileExt.IsEmpty()) {
|
||||
fileExt = mTempFileExtension;
|
||||
fileExt = mFileExtension;
|
||||
}
|
||||
|
||||
RequestSaveDestination(mSuggestedFileName, fileExt);
|
||||
|
@ -2929,7 +2661,13 @@ NS_IMETHODIMP nsExternalAppHandler::SetDownloadToLaunch(
|
|||
}
|
||||
|
||||
#ifdef XP_WIN
|
||||
fileToUse->Append(mSuggestedFileName + mTempFileExtension);
|
||||
// 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);
|
||||
}
|
||||
#else
|
||||
fileToUse->Append(mSuggestedFileName);
|
||||
#endif
|
||||
|
@ -3484,3 +3222,321 @@ 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;
|
||||
|
||||
// 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.
|
||||
if (aAllowURLExtension) {
|
||||
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 = aMimeType.EqualsLiteral(APPLICATION_OCTET_STREAM) ||
|
||||
aMimeType.EqualsLiteral(BINARY_OCTET_STREAM) ||
|
||||
aMimeType.EqualsLiteral("application/x-msdownload") ||
|
||||
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 (mimeService) {
|
||||
bool isValidExtension;
|
||||
if (extension.IsEmpty() ||
|
||||
NS_FAILED(mimeInfo->ExtensionExists(extension, &isValidExtension)) ||
|
||||
!isValidExtension) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
fileName.ReplaceChar(KNOWN_PATH_SEPARATORS, '_');
|
||||
fileName.ReplaceChar(FILE_ILLEGAL_CHARACTERS, ' ');
|
||||
fileName.StripChar(char16_t(0));
|
||||
|
||||
// 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)};
|
||||
fileName.ReplaceChar(unsafeBidiCharacters, '_');
|
||||
|
||||
// Trim whitespace, periods and vowel separators from the beginning and
|
||||
// end of the filename. Periods are removed to avoid creating hidden files.
|
||||
fileName.Trim(" .\f\n\r\t\v", true, true);
|
||||
|
||||
// Collapse duplicate whitespace.
|
||||
if (!(aFlags & VALIDATE_DONT_COLLAPSE_WHITESPACE)) {
|
||||
fileName.CompressWhitespace();
|
||||
}
|
||||
|
||||
// If the filename is too long, truncate it, but preserve the desired
|
||||
// extension.
|
||||
if (!(aFlags & VALIDATE_DONT_TRUNCATE) &&
|
||||
fileName.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) {
|
||||
fileName.Truncate(kDefaultMaxFileNameLength - 1);
|
||||
} else {
|
||||
fileName.Truncate(kDefaultMaxFileNameLength - aExtension.Length() - 1);
|
||||
if (!fileName.IsEmpty()) {
|
||||
if (fileName.Last() != '.') {
|
||||
fileName.AppendLiteral(".");
|
||||
}
|
||||
|
||||
fileName.Append(NS_ConvertUTF8toUTF16(aExtension));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
aFileName = fileName;
|
||||
}
|
||||
|
||||
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,6 +199,32 @@ 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
|
||||
*/
|
||||
|
@ -251,15 +277,15 @@ class nsExternalAppHandler final : public nsIStreamListener,
|
|||
* in which case dialogs will be parented to
|
||||
* aContentContext.
|
||||
* @param mExtProtSvc nsExternalHelperAppService on creation
|
||||
* @param aFileName The filename to use
|
||||
* @param aSuggestedFileName The filename to use
|
||||
* @param aReason A constant from nsIHelperAppLauncherDialog
|
||||
* indicating why the request is handled by a helper app.
|
||||
*/
|
||||
nsExternalAppHandler(nsIMIMEInfo* aMIMEInfo, const nsACString& aFileExtension,
|
||||
nsExternalAppHandler(nsIMIMEInfo* aMIMEInfo, const nsAString& aFileExtension,
|
||||
mozilla::dom::BrowsingContext* aBrowsingContext,
|
||||
nsIInterfaceRequestor* aWindowContext,
|
||||
nsExternalHelperAppService* aExtProtSvc,
|
||||
const nsAString& aFilename, uint32_t aReason,
|
||||
const nsAString& aSuggestedFileName, uint32_t aReason,
|
||||
bool aForceSave);
|
||||
|
||||
/**
|
||||
|
@ -284,7 +310,7 @@ class nsExternalAppHandler final : public nsIStreamListener,
|
|||
|
||||
nsCOMPtr<nsIFile> mTempFile;
|
||||
nsCOMPtr<nsIURI> mSourceUrl;
|
||||
nsString mTempFileExtension;
|
||||
nsString mFileExtension;
|
||||
nsString mTempLeafName;
|
||||
|
||||
/**
|
||||
|
@ -473,12 +499,6 @@ 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
|
||||
|
@ -486,7 +506,7 @@ class nsExternalAppHandler final : public nsIStreamListener,
|
|||
* extension (image/, video/, and audio/ based mimetypes, and a few specific
|
||||
* document types).
|
||||
*
|
||||
* It also ensure that mTempFileExtension only contains an extension
|
||||
* It also ensure that mFileExtension only contains an extension
|
||||
* when it is different from mSuggestedFileName's extension.
|
||||
*/
|
||||
void EnsureCorrectExtension(const nsString& aFileExt);
|
||||
|
|
Загрузка…
Ссылка в новой задаче