/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim:expandtab:shiftwidth=2:tabstop=2:cin: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include"base/basictypes.h"
/* This must occur *after* base/basictypes.h to avoid typedefs conflicts. */ #include"mozilla/ArrayUtils.h" #include"mozilla/Base64.h" #include"mozilla/ResultExtensions.h"
// used to access our datastore of user-configured helper applications #include"nsIHandlerService.h" #include"nsIMIMEInfo.h" #include"nsIHelperAppLauncherDialog.h" #include"nsIContentDispatchChooser.h" #include"nsNetUtil.h" #include"nsIPrivateBrowsingChannel.h" #include"nsIIOService.h" #include"nsNetCID.h"
#include"nsIApplicationReputation.h"
#include"nsDSURIContentListener.h" #include"nsMimeTypes.h" #include"nsMIMEInfoImpl.h" // used for header disposition information. #include"nsIHttpChannel.h" #include"nsIHttpChannelInternal.h" #include"nsIEncodedChannel.h" #include"nsIMultiPartChannel.h" #include"nsIFileChannel.h" #include"nsIObserverService.h"// so we can be a profile change observer #include"nsIPropertyBag2.h"// for the 64-bit content length
#ifdef XP_MACOSX # include "nsILocalFileMac.h" #endif
#include"nsEscape.h"
#include"nsIStringBundle.h"// XXX needed to localize error msgs #include"nsIPrompt.h"
#include"nsITextToSubURI.h"// to unescape the filename
// Using level 3 here because the OSHelperAppServices use a log level // of LogLevel::Debug (4), and we want less detailed output here // Using 3 instead of LogLevel::Warning because we don't output warnings #undef LOG #define LOG(...) \
MOZ_LOG(nsExternalHelperAppService::sLog, mozilla::LogLevel::Info, \
(__VA_ARGS__)) #define LOG_ENABLED() \
MOZ_LOG_TEST(nsExternalHelperAppService::sLog, mozilla::LogLevel::Info)
// Helper functions for Content-Disposition headers
/** * Given a URI fragment, unescape it * @param aFragment The string to unescape * @param aURI The URI from which this fragment is taken. Only its character set * will be used. * @param aResult [out] Unescaped string.
*/ static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI,
nsAString& aResult) { // We need the unescaper
nsresult rv;
nsCOMPtr<nsITextToSubURI> textToSubURI =
do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
/** * UTF-8 version of UnescapeFragment. * @param aFragment The string to unescape * @param aURI The URI from which this fragment is taken. Only its character set * will be used. * @param aResult [out] Unescaped string, UTF-8 encoded. * @note It is safe to pass the same string for aFragment and aResult. * @note When this function fails, aResult will not be modified.
*/ static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI,
nsACString& aResult) {
nsAutoString result;
nsresult rv = UnescapeFragment(aFragment, aURI, result); if (NS_SUCCEEDED(rv)) CopyUTF16toUTF8(result, aResult); return rv;
}
#if !defined(XP_MACOSX) && defined(XP_UNIX) // Ensuring that only the current user can read the file names we end up // creating. Note that creating directories with a specified permission is // only supported on Unix platform right now. That's why the above check // exists.
int counter = 0; bool pathExists;
nsCOMPtr<nsIFile> finalPath;
while (true) {
nsAutoString countedUserDir(userDir);
countedUserDir.AppendInt(counter, 10);
dir->Clone(getter_AddRefs(finalPath));
finalPath->Append(countedUserDir);
MOZ_TRY(finalPath->Exists(&pathExists));
if (pathExists) { // If this path has the right permissions, use it.
MOZ_TRY(finalPath->GetPermissions(&permissions));
// Ensuring the path is writable by the current user. bool isWritable;
MOZ_TRY(finalPath->IsWritable(&isWritable));
if (permissions == PR_IRWXU && isWritable) {
dir = finalPath; break;
}
}
nsresult const rv = finalPath->Create(nsIFile::DIRECTORY_TYPE, PR_IRWXU); if (NS_SUCCEEDED(rv)) {
dir = finalPath; break;
} if (rv != NS_ERROR_FILE_ALREADY_EXISTS) { // Unexpected error. return Err(rv);
}
counter++;
}
}
#endif
NS_ASSERTION(dir, "Somehow we didn't get a download directory!"); return dir;
}
/** * Given an alleged download directory, either create it, or confirm that it * already exists and is usable.
*/ static nsresult EnsureDirectoryExists(nsIFile* aDir) {
nsresult const rv = aDir->Create(nsIFile::DIRECTORY_TYPE, 0755); if (rv == NS_ERROR_FILE_ALREADY_EXISTS || NS_SUCCEEDED(rv)) { return NS_OK;
} return rv;
};
/** * Obtains the final directory to save downloads to. This tends to vary per * platform, and needs to be consistent throughout our codepaths. For platforms * where helper apps use the downloads directory, this should be kept in sync * with the function of the same name in DownloadIntegration.sys.mjs. * * Optionally skip availability of the directory and storage.
*/ static Result<nsCOMPtr<nsIFile>, nsresult> GetPreferredDownloadsDirectory( bool aSkipChecks = false) { #ifdefined(ANDROID) return Err(NS_ERROR_FAILURE); #endif
nsresult rv; // Try to get the users download location, if it's set. switch (Preferences::GetInt(NS_PREF_DOWNLOAD_FOLDERLIST, -1)) { case NS_FOLDER_VALUE_DESKTOP: {
nsCOMPtr<nsIFile> dir; if (NS_SUCCEEDED(
NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(dir)))) { return dir;
}
} break;
case NS_FOLDER_VALUE_CUSTOM: {
nsCOMPtr<nsIFile> dir;
Preferences::GetComplex(NS_PREF_DOWNLOAD_DIR, NS_GET_IID(nsIFile),
getter_AddRefs(dir)); if (!dir) break;
// Check for availability if requested. if (!aSkipChecks && NS_FAILED(EnsureDirectoryExists(dir))) { break;
}
return dir;
} break;
default: case NS_FOLDER_VALUE_DOWNLOADS: // This is just the OS default location, so fall out break;
}
// On some OSes, there is no guarantee that `NS_OS_DEFAULT_DOWNLOAD_DIR` // exists. Fall back to $HOME + Downloads.
// If we've done this before, use the cached value: if (sFallbackDownloadDir) {
MOZ_TRY(sFallbackDownloadDir->Clone(getter_AddRefs(dir))); return dir;
}
return downloadBundle->GetStringFromName("downloadsFolder",
downloadLocalized);
}(); // ... or, failing that, just use "Downloads". if (NS_FAILED(rv)) {
downloadLocalized.AssignLiteral("Downloads");
}
MOZ_TRY(dir->Append(downloadLocalized));
// Cache the result for next time.
{ // Can't getter_AddRefs on StaticRefPtr, so do some copying.
nsCOMPtr<nsIFile> copy;
dir->Clone(getter_AddRefs(copy));
sFallbackDownloadDir = copy.forget();
ClearOnShutdown(&sFallbackDownloadDir);
}
// Check for availability if requested. if (!aSkipChecks) {
MOZ_TRY(EnsureDirectoryExists(dir));
}
return dir;
}
NS_IMETHODIMP nsExternalHelperAppService::GetPreferredDownloadsDirectory(
nsIFile** aOutFile) { auto res = ::GetPreferredDownloadsDirectory(); if (res.isErr()) return res.unwrapErr();
res.unwrap().forget(aOutFile); return NS_OK;
}
/** * Obtains the initial directory to save downloads to. (This may differ from the * actual download directory if "browser.download.start_downloads_in_tmp_dir" is * set.) * * Optionally, skip availability of the directory and storage.
*/ static Result<nsCOMPtr<nsIFile>, nsresult> GetInitialDownloadDirectory( bool aSkipChecks = false) { #ifdefined(ANDROID) return Err(NS_ERROR_FAILURE); #endif
if (StaticPrefs::browser_download_start_downloads_in_tmp_dir()) { return GetOsTmpDownloadDirectory();
}
/** * Helper for random bytes for the filename of downloaded part files.
*/
nsresult GenerateRandomName(nsACString& result) { // We will request raw random bytes, and transform that to a base64 string, // using url-based base64 encoding so that all characters from the base64 // result will be acceptable for filenames. // For each three bytes of random data, we will get four bytes of ASCII. // Request a bit more, to be safe, then truncate in the end.
uint8_t buffer[requiredBytesLength]; if (!mozilla::GenerateRandomBytesFromOS(buffer, requiredBytesLength)) { return NS_ERROR_FAILURE;
}
nsAutoCString tempLeafName; // We're forced to specify a padding policy, though this is guaranteed // not to need padding due to requiredBytesLength being a multiple of 3.
rv = Base64URLEncode(requiredBytesLength, buffer,
Base64URLEncodePaddingPolicy::Omit, tempLeafName);
NS_ENSURE_SUCCESS(rv, rv);
/** * Default extension->mimetype mappings. These are not overridable. * If you add types here, make sure they are lowercase, or you'll regret it.
*/ staticconst nsDefaultMimeTypeEntry defaultMimeEntries[] = { // The following are those extensions that we're asked about during startup, // sorted by order used
{TEXT_XML, "xml"},
{IMAGE_PNG, "png"}, // -- end extensions used during startup
{TEXT_CSS, "css"},
{IMAGE_JPEG, "jpeg"},
{IMAGE_JPEG, "jpg"},
{IMAGE_SVG_XML, "svg"},
{TEXT_HTML, "html"},
{TEXT_HTML, "htm"},
{IMAGE_GIF, "gif"},
{IMAGE_WEBP, "webp"},
{APPLICATION_XPINSTALL, "xpi"},
{APPLICATION_XHTML_XML, "xhtml"},
{APPLICATION_XHTML_XML, "xht"},
{TEXT_PLAIN, "txt"},
{APPLICATION_JSON, "json"},
{APPLICATION_RDF, "rdf"},
{APPLICATION_XJAVASCRIPT, "mjs"},
{APPLICATION_XJAVASCRIPT, "js"},
{APPLICATION_XJAVASCRIPT, "jsm"},
{VIDEO_OGG, "ogv"},
{APPLICATION_OGG, "ogg"},
{AUDIO_OGG, "oga"},
{AUDIO_OGG, "opus"},
{APPLICATION_PDF, "pdf"},
{VIDEO_WEBM, "webm"},
{AUDIO_WEBM, "webm"},
{IMAGE_ICO, "ico"},
{TEXT_PLAIN, "properties"},
{TEXT_PLAIN, "locale"},
{TEXT_PLAIN, "ftl"}, #ifdefined(MOZ_WMF)
{VIDEO_MP4, "mp4"},
{AUDIO_MP4, "m4a"},
{AUDIO_MP3, "mp3"}, #endif #ifdef MOZ_RAW
{VIDEO_RAW, "yuv"} #endif
};
/** * This is a small private struct used to help us initialize some * default mime types.
*/ struct nsExtraMimeTypeEntry { constchar* mMimeType; constchar* mFileExtensions; constchar* mDescription;
};
/** * This table lists all of the 'extra' content types that we can deduce from * particular file extensions. These entries also ensure that we provide a good * descriptive name when we encounter files with these content types and/or * extensions. These can be overridden by user helper app prefs. If you add * types here, make sure they are lowercase, or you'll regret it.
*/ staticconst nsExtraMimeTypeEntry extraMimeEntries[] = { #ifdefined(XP_MACOSX) // don't define .bin on the mac...use internet config to // look that up...
{APPLICATION_OCTET_STREAM, "exe,com", "Binary File"}, #else
{APPLICATION_OCTET_STREAM, "exe,com,bin", "Binary File"}, #endif
{APPLICATION_GZIP2, "gz", "gzip"},
{"application/x-arj", "arj", "ARJ file"},
{"application/rtf", "rtf", "Rich Text Format File"},
{APPLICATION_ZIP, "zip", "ZIP Archive"},
{APPLICATION_XPINSTALL, "xpi", "XPInstall Install"},
{APPLICATION_PDF, "pdf", "Portable Document Format"},
{APPLICATION_POSTSCRIPT, "ps,eps,ai", "Postscript File"},
{APPLICATION_XJAVASCRIPT, "js", "Javascript Source File"},
{APPLICATION_XJAVASCRIPT, "jsm,mjs", "Javascript Module Source File"}, #ifdef MOZ_WIDGET_ANDROID
{"application/vnd.android.package-archive", "apk", "Android Package"}, #endif
/** * File extensions for which decoding should be disabled. * NOTE: These MUST be lower-case and ASCII.
*/ staticconst nsDefaultMimeTypeEntry nonDecodableExtensions[] = {
{APPLICATION_GZIP, "gz"},
{APPLICATION_GZIP, "tgz"},
{APPLICATION_ZIP, "zip"},
{APPLICATION_COMPRESS, "z"},
{APPLICATION_GZIP, "svgz"}};
/** * Mimetypes for which we enforce using a known extension. * * In addition to this list, we do this for all audio/, video/ and * image/ mimetypes.
*/ staticconstchar* forcedExtensionMimetypes[] = {
APPLICATION_PDF, APPLICATION_OGG, APPLICATION_WASM,
TEXT_CALENDAR, TEXT_CSS, TEXT_VCARD};
/** * Primary extensions of types whose descriptions should be overwritten. * This extension is concatenated with "ExtHandlerDescription" to look up the * description in unknownContentType.properties. * NOTE: These MUST be lower-case and ASCII.
*/ staticconstchar* descriptionOverwriteExtensions[] = { "avif", "jxl", "pdf", "svg", "webp", "xml",
};
/** * In child processes, return an nsOSHelperAppServiceChild for remoting * OS calls to the parent process. In the parent process itself, use * nsOSHelperAppService.
*/ /* static */
already_AddRefed<nsExternalHelperAppService>
nsExternalHelperAppService::GetSingleton() { if (!sExtHelperAppSvcSingleton) { if (XRE_IsParentProcess()) {
sExtHelperAppSvcSingleton = new nsOSHelperAppService();
} else {
sExtHelperAppSvcSingleton = new nsOSHelperAppServiceChild();
}
ClearOnShutdown(&sExtHelperAppSvcSingleton);
}
// We need to get a hold of a ContentChild so that we can begin forwarding // this data to the parent. In the HTTP case, this is unfortunate, since // we're actually passing data from parent->child->parent wastefully, but // the Right Fix will eventually be to short-circuit those channels on the // parent side based on some sort of subscription concept. using mozilla::dom::ContentChild; using mozilla::dom::ExternalHelperAppChild;
ContentChild* child = ContentChild::GetSingleton(); if (!child) { return NS_ERROR_FAILURE;
}
// Now we build a protocol for forwarding our data to the parent. The // protocol will act as a listener on the child-side and create a "real" // helperAppService listener on the parent-side, via another call to // DoContent.
RefPtr<ExternalHelperAppChild> childListener = new ExternalHelperAppChild();
MOZ_ALWAYS_TRUE(child->SendPExternalHelperAppConstructor(
childListener, uri, loadInfoArgs, nsCString(aMimeContentType), disp,
contentDisposition, fileName, aForceSave, contentLength, wasFileChannel,
referrer, aContentContext));
// Get the file extension and name that we will need later
nsCOMPtr<nsIURI> uri; bool allowURLExtension =
GetFileNameFromChannel(aChannel, fileName, getter_AddRefs(uri));
LOG("Type/Ext lookup found 0x%p\n", mimeInfo.get());
// No mimeinfo -> we can't continue. probably OOM. if (!mimeInfo) { return NS_ERROR_OUT_OF_MEMORY;
}
if (flags & VALIDATE_GUESS_FROM_EXTENSION) { // Replace the content type with what was guessed.
nsAutoCString mimeType;
mimeInfo->GetMIMEType(mimeType);
aChannel->SetContentType(mimeType);
if (reason == nsIHelperAppLauncherDialog::REASON_CANTHANDLE) {
reason = nsIHelperAppLauncherDialog::REASON_TYPESNIFFED;
}
}
// 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); if (!handler) { return NS_ERROR_OUT_OF_MEMORY;
}
NS_IMETHODIMP nsExternalHelperAppService::DoContent( const nsACString& aMimeContentType, nsIChannel* aChannel,
nsIInterfaceRequestor* aContentContext, bool aForceSave,
nsIInterfaceRequestor* aWindowContext,
nsIStreamListener** aStreamListener) { // Scripted interface requestors cannot return an instance of the // (non-scriptable) nsPIDOMWindowOuter or nsPIDOMWindowInner interfaces, so // get to the window via `nsIDOMWindow`. Unfortunately, at that point we // don't know whether the thing we got is an inner or outer window, so have to // work with either one.
RefPtr<BrowsingContext> bc;
nsCOMPtr<nsIDOMWindow> domWindow = do_GetInterface(aContentContext); if (nsCOMPtr<nsPIDOMWindowOuter> outerWindow = do_QueryInterface(domWindow)) {
bc = outerWindow->GetBrowsingContext();
} elseif (nsCOMPtr<nsPIDOMWindowInner> innerWindow =
do_QueryInterface(domWindow)) {
bc = innerWindow->GetBrowsingContext();
}
nsresult nsExternalHelperAppService::GetFileTokenForPath( const char16_t* aPlatformAppPath, nsIFile** aFile) {
nsDependentString platformAppPath(aPlatformAppPath); // First, check if we have an absolute path
nsIFile* localFile = nullptr;
nsresult rv = NS_NewLocalFile(platformAppPath, &localFile); if (NS_SUCCEEDED(rv)) {
*aFile = localFile; bool exists; if (NS_FAILED((*aFile)->Exists(&exists)) || !exists) {
NS_RELEASE(*aFile); return NS_ERROR_FILE_NOT_FOUND;
} return NS_OK;
}
// Second, check if file exists in mozilla program directory
rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR, aFile); if (NS_SUCCEEDED(rv)) {
rv = (*aFile)->Append(platformAppPath); if (NS_SUCCEEDED(rv)) { bool exists = false;
rv = (*aFile)->Exists(&exists); if (NS_SUCCEEDED(rv) && exists) return NS_OK;
}
NS_RELEASE(*aFile);
}
return NS_ERROR_NOT_AVAILABLE;
}
////////////////////////////////////////////////////////////////////////////////////////////////////// // begin external protocol service default implementation... //////////////////////////////////////////////////////////////////////////////////////////////////////
NS_IMETHODIMP nsExternalHelperAppService::ExternalProtocolHandlerExists( constchar* aProtocolScheme, bool* aHandlerExists) {
nsCOMPtr<nsIHandlerInfo> handlerInfo;
nsresult rv = GetProtocolHandlerInfo(nsDependentCString(aProtocolScheme),
getter_AddRefs(handlerInfo)); if (NS_SUCCEEDED(rv)) { // See if we have any known possible handler apps for this
nsCOMPtr<nsIMutableArray> possibleHandlers;
handlerInfo->GetPossibleApplicationHandlers(
getter_AddRefs(possibleHandlers));
// if not, fall back on an os-based handler return OSProtocolHandlerExists(aProtocolScheme, aHandlerExists);
}
NS_IMETHODIMP nsExternalHelperAppService::IsExposedProtocol( constchar* aProtocolScheme, bool* aResult) { // check the per protocol setting first. it always takes precedence. // if not set, then use the global setting.
// by default, no protocol is exposed. i.e., by default all link clicks must // go through the external protocol service. most applications override this // default behavior.
*aResult = Preferences::GetBool("network.protocol-handler.expose-all", false);
// Prevent sandboxed BrowsingContexts from navigating to external protocols. // This only uses the sandbox flags of the target BrowsingContext of the // load. The navigating document's CSP sandbox flags do not apply. if (aBrowsingContext &&
ExternalProtocolIsBlockedBySandbox(aBrowsingContext,
aHasValidUserGestureActivation)) { // Log an error to the web console of the sandboxed BrowsingContext.
nsAutoString localizedMsg;
nsAutoCString spec;
aURI->GetSpec(spec);
// Log to the the parent window of the iframe. If there is no parent, fall // back to the iframe window itself.
WindowContext* windowContext = aBrowsingContext->GetParentWindowContext(); if (!windowContext) {
windowContext = aBrowsingContext->GetCurrentWindowContext();
}
// Skip logging if we still don't have a WindowContext.
NS_ENSURE_TRUE(windowContext, NS_ERROR_FAILURE);
nsAutoCString scheme;
escapedURI->GetScheme(scheme); if (scheme.IsEmpty()) return NS_OK; // must have a scheme
// Deny load if the prefs say to do so
nsAutoCString externalPref(kExternalProtocolPrefPrefix);
externalPref += scheme; bool allowLoad = false; if (NS_FAILED(Preferences::GetBool(externalPref.get(), &allowLoad))) { // no scheme-specific value, check the default if (NS_FAILED(
Preferences::GetBool(kExternalProtocolDefaultPref, &allowLoad))) { return NS_OK; // missing default pref
}
}
if (!allowLoad) { return NS_OK; // explicitly denied
}
// Now check if the principal is allowed to access the navigated context. // We allow navigating subframes, even if not same-origin - non-external // links can always navigate everywhere, so this is a minor additional // restriction, only aiming to prevent some types of spoofing attacks // from otherwise disjoint browsingcontext trees. if (aBrowsingContext && aTriggeringPrincipal && // Add-on principals are always allowed:
!BasePrincipal::Cast(aTriggeringPrincipal)->AddonPolicy() && // As is chrome code:
!aTriggeringPrincipal->IsSystemPrincipal()) {
RefPtr<BrowsingContext> bc = aBrowsingContext;
WindowGlobalParent* wgp = bc->Canonical()->GetCurrentWindowGlobal(); bool foundAccessibleFrame = false;
// Don't block the load if it is the first load in a new window (e.g. due to // a call to window.open, or a target=_blank link click). if (aNewWindowTarget) {
MOZ_ASSERT(bc->IsTop());
foundAccessibleFrame = true;
}
// Also allow this load if the target is a toplevel BC which contains a // non-web-controlled about:blank document. // NOTE: This catches cases like shift-clicking a link which do not set // `newWindowTarget`, but do open a link in a new window on behalf of web // content. if (!foundAccessibleFrame && bc->IsTop() &&
!bc->GetTopLevelCreatedByWebContent() && wgp) {
nsIURI* uri = wgp->GetDocumentURI();
foundAccessibleFrame = uri && NS_IsAboutBlank(uri);
}
while (!foundAccessibleFrame) { if (wgp) {
foundAccessibleFrame =
aTriggeringPrincipal->Subsumes(wgp->DocumentPrincipal());
} // We have to get the parent via the bc, because there may not // be a window global for the innermost bc; see bug 1650162.
BrowsingContext* parent = bc->GetParent(); if (!parent) { break;
}
bc = parent;
wgp = parent->Canonical()->GetCurrentWindowGlobal();
}
if (!foundAccessibleFrame) { // See if this navigation could have come from a subframe.
nsTArray<RefPtr<BrowsingContext>> contexts;
aBrowsingContext->GetAllBrowsingContextsInSubtree(contexts); for (constauto& kid : contexts) {
wgp = kid->Canonical()->GetCurrentWindowGlobal(); if (wgp && aTriggeringPrincipal->Subsumes(wgp->DocumentPrincipal())) {
foundAccessibleFrame = true; break;
}
}
}
if (!foundAccessibleFrame) { return NS_OK; // deny the load.
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////// // Methods related to deleting temporary files on exit //////////////////////////////////////////////////////////////////////////////////////////////////////
// as a safety measure, make sure the nsIFile is really a file and not a // directory object.
aTemporaryFile->IsFile(&isFile); if (!isFile) return NS_OK;
void nsExternalHelperAppService::ExpungeTemporaryFilesHelper(
nsCOMArray<nsIFile>& fileList) {
int32_t numEntries = fileList.Count();
nsIFile* localFile; for (int32_t index = 0; index < numEntries; index++) {
localFile = fileList[index]; if (localFile) { // First make the file writable, since the temp file is probably readonly.
localFile->SetPermissions(0600);
localFile->Remove(false);
}
}
NS_IMETHODIMP
nsExternalHelperAppService::GetProtocolHandlerInfo( const nsACString& aScheme, nsIHandlerInfo** aHandlerInfo) { // XXX enterprise customers should be able to turn this support off with a // single master pref (maybe use one of the "exposed" prefs here?)
bool exists;
nsresult rv = GetProtocolHandlerInfoFromOS(aScheme, &exists, aHandlerInfo); if (NS_FAILED(rv)) { // Either it knows nothing, or we ran out of memory return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIHandlerService> handlerSvc =
do_GetService(NS_HANDLERSERVICE_CONTRACTID); if (handlerSvc) { bool hasHandler = false;
(void)handlerSvc->Exists(*aHandlerInfo, &hasHandler); if (hasHandler) {
rv = handlerSvc->FillHandlerInfo(*aHandlerInfo, ""_ns); if (NS_SUCCEEDED(rv)) return NS_OK;
}
}
NS_IMETHODIMP
nsExternalHelperAppService::SetProtocolHandlerDefaults(
nsIHandlerInfo* aHandlerInfo, bool aOSHandlerExists) { // this type isn't in our database, so we've only got an OS default handler, // if one exists
if (aOSHandlerExists) { // we've got a default, so use it
aHandlerInfo->SetPreferredAction(nsIHandlerInfo::useSystemDefault);
// whether or not to ask the user depends on the warning preference
nsAutoCString scheme;
aHandlerInfo->GetType(scheme);
nsAutoCString warningPref(kExternalWarningPrefPrefix);
warningPref += scheme; bool warn; if (NS_FAILED(Preferences::GetBool(warningPref.get(), &warn))) { // no scheme-specific value, check the default
warn = Preferences::GetBool(kExternalWarningDefaultPref, true);
}
aHandlerInfo->SetAlwaysAskBeforeHandling(warn);
} else { // If no OS default existed, we set the preferred action to alwaysAsk. // This really means not initialized (i.e. there's no available handler) // to all the code...
aHandlerInfo->SetPreferredAction(nsIHandlerInfo::alwaysAsk);
}
nsExternalAppHandler::~nsExternalAppHandler() {
MOZ_ASSERT(!mSaver, "Saver should hold a reference to us until deleted");
}
void nsExternalAppHandler::DidDivertRequest(nsIRequest* request) {
MOZ_ASSERT(XRE_IsContentProcess(), "in child process"); // Remove our request from the child loadGroup
RetargetLoadNotifications(request);
}
NS_IMETHODIMP nsExternalAppHandler::SetWebProgressListener(
nsIWebProgressListener2* aWebProgressListener) { // This is always called by nsHelperDlg.js. Go ahead and register the // progress listener. At this point, we don't have mTransfer.
mDialogProgressListener = aWebProgressListener; return NS_OK;
}
NS_IMETHODIMP nsExternalAppHandler::GetTargetFileIsExecutable(bool* aExec) { // Use the real target if it's been set if (mFinalFileDestination) return mFinalFileDestination->IsExecutable(aExec);
// Otherwise, use the stored executable-ness of the temporary
*aExec = mTempFileIsExecutable; return NS_OK;
}
void nsExternalAppHandler::RetargetLoadNotifications(nsIRequest* request) { // we are going to run the downloading of the helper app in our own little // docloader / load group context. so go ahead and force the creation of a // load group and doc loader for us to use...
nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request); if (!aChannel) return;
nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(aChannel); if (pbChannel) {
pbChannel->SetPrivate(isPrivate);
}
}
nsresult nsExternalAppHandler::SetUpTempFile(nsIChannel* aChannel) { // First we need to try to get the destination directory for the temporary // file. auto res = GetInitialDownloadDirectory(); if (res.isErr()) return res.unwrapErr();
mTempFile = res.unwrap();
// At this point, we do not have a filename for the temp file. For security // purposes, this cannot be predictable, so we must use a cryptographic // quality PRNG to generate one.
nsAutoCString tempLeafName;
nsresult rv = GenerateRandomName(tempLeafName);
NS_ENSURE_SUCCESS(rv, rv);
// now append our extension.
nsAutoCString ext;
mMimeInfo->GetPrimaryExtension(ext); if (!ext.IsEmpty()) {
ext.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_'); if (ext.First() != '.') tempLeafName.Append('.');
tempLeafName.Append(ext);
}
// We need to temporarily create a dummy file with the correct // file extension to determine the executable-ness, so do this before adding // the extra .part extension.
nsCOMPtr<nsIFile> dummyFile;
rv = mTempFile->Clone(getter_AddRefs(dummyFile));
NS_ENSURE_SUCCESS(rv, rv);
// Set the file name without .part
rv = dummyFile->Append(NS_ConvertUTF8toUTF16(tempLeafName));
NS_ENSURE_SUCCESS(rv, rv);
rv = dummyFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
NS_ENSURE_SUCCESS(rv, rv);
// Store executable-ness then delete
dummyFile->IsExecutable(&mTempFileIsExecutable);
dummyFile->Remove(false);
// Add an additional .part to prevent the OS from running this file in the // default application.
tempLeafName.AppendLiteral(".part");
rv = mTempFile->Append(NS_ConvertUTF8toUTF16(tempLeafName)); // make this file unique!!!
NS_ENSURE_SUCCESS(rv, rv);
rv = mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
NS_ENSURE_SUCCESS(rv, rv);
// Now save the temp leaf name, minus the ".part" bit, so we can use it later. // This is a bit broken in the case when createUnique actually had to append // some numbers, because then we now have a filename like foo.bar-1.part and // we'll end up with foo.bar-1.bar as our final filename if we end up using // this. But the other options are all bad too.... Ideally we'd have a way // to tell createUnique to put its unique marker before the extension that // comes before ".part" or something.
rv = mTempFile->GetLeafName(mTempLeafName);
NS_ENSURE_SUCCESS(rv, rv);
// Strip off the ".part" from mTempLeafName
mTempLeafName.Truncate(mTempLeafName.Length() - std::size(".part") + 1);
MOZ_ASSERT(!mSaver, "Output file initialization called more than once!");
mSaver =
do_CreateInstance(NS_BACKGROUNDFILESAVERSTREAMLISTENER_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIEncodedChannel> encChannel = do_QueryInterface(aRequest); if (!encChannel) { return;
}
// Turn off content encoding conversions if needed bool applyConversion = true;
// First, check to see if conversion is already disabled. If so, we // have nothing to do here.
encChannel->GetApplyConversion(&applyConversion); if (!applyConversion) { return;
}
nsCOMPtr<nsIURL> sourceURL(do_QueryInterface(mSourceUrl)); if (sourceURL) {
nsAutoCString extension;
sourceURL->GetFileExtension(extension); if (!extension.IsEmpty()) {
nsCOMPtr<nsIUTF8StringEnumerator> encEnum;
encChannel->GetContentEncodings(getter_AddRefs(encEnum)); if (encEnum) { bool hasMore;
nsresult rv = encEnum->HasMore(&hasMore); if (NS_SUCCEEDED(rv) && hasMore) {
nsAutoCString encType;
rv = encEnum->GetNext(encType); if (NS_SUCCEEDED(rv) && !encType.IsEmpty()) {
MOZ_ASSERT(mExtProtSvc);
mExtProtSvc->ApplyDecodingForExtension(extension, encType,
&applyConversion);
}
}
}
}
}
if (!dialogParent && mBrowsingContext) {
dialogParent = do_QueryInterface(mBrowsingContext->GetDOMWindow());
} if (!dialogParent && mBrowsingContext && XRE_IsParentProcess()) {
RefPtr<Element> element = mBrowsingContext->Top()->GetEmbedderElement(); if (element) {
dialogParent = do_QueryInterface(element->OwnerDoc()->GetWindow());
}
} return dialogParent.forget();
}
NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
MOZ_ASSERT(request, "OnStartRequest without request?");
// Set mTimeDownloadStarted here as the download has already started and // we want to record the start time before showing the filepicker.
mTimeDownloadStarted = PR_Now();
nsresult rv;
nsAutoCString MIMEType; if (mMimeInfo) {
mMimeInfo->GetMIMEType(MIMEType);
} // Now get the URI if (aChannel) {
aChannel->GetURI(getter_AddRefs(mSourceUrl)); // HTTPS-Only/HTTPS-FirstMode tries to upgrade connections to https. Once // the download is in progress we set that flag so that timeout counter // measures do not kick in.
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); bool isPrivateWin = loadInfo->GetOriginAttributes().IsPrivateBrowsing(); if (nsHTTPSOnlyUtils::IsHttpsOnlyModeEnabled(isPrivateWin) ||
nsHTTPSOnlyUtils::IsHttpsFirstModeEnabled(isPrivateWin)) {
uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_DOWNLOAD_IN_PROGRESS;
loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
}
}
if (!mForceSave && StaticPrefs::browser_download_enable_spam_prevention() &&
IsDownloadSpam(aChannel)) { return NS_OK;
}
if (mDownloadClassification == nsITransfer::DOWNLOAD_FORBIDDEN) { // If the download is rated as forbidden, // cancel the request so no ui knows about this.
mCanceled = true;
request->Cancel(NS_ERROR_ABORT); return NS_OK;
}
nsCOMPtr<nsIFileChannel> fileChan(do_QueryInterface(request));
mIsFileChannel = fileChan != nullptr; if (!mIsFileChannel) { // It's possible that this request came from the child process and the // file channel actually lives there. If this returns true, then our // mSourceUrl will be an nsIFileURL anyway.
nsCOMPtr<dom::nsIExternalHelperAppParent> parent(
do_QueryInterface(request));
mIsFileChannel = parent && parent->WasFileChannel();
}
// Get content length if (aChannel) {
aChannel->GetContentLength(&mContentLength);
}
if (mBrowsingContext) {
mMaybeCloseWindowHelper = new MaybeCloseWindowHelper(mBrowsingContext);
// Determine whether a new window was opened specifically for this request if (aChannel) {
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
mMaybeCloseWindowHelper->SetShouldCloseWindow(
loadInfo->GetIsNewWindowTarget());
}
}
// retarget all load notifications to our docloader instead of the original // window's docloader...
RetargetLoadNotifications(request);
// Close the underlying DOMWindow if it was opened specifically for the // download. We don't run this in the content process, since we have // an instance running in the parent as well, which will handle this // if needed. if (!XRE_IsContentProcess() && mMaybeCloseWindowHelper) {
mBrowsingContext = mMaybeCloseWindowHelper->MaybeCloseWindow();
}
// In an IPC setting, we're allowing the child process, here, to make // decisions about decoding the channel (e.g. decompression). It will // still forward the decoded (uncompressed) data back to the parent. // Con: Uncompressed data means more IPC overhead. // Pros: ExternalHelperAppParent doesn't need to implement nsIEncodedChannel. // Parent process doesn't need to expect CPU time on decompression.
MaybeApplyDecodingForExtension(aChannel);
// At this point, the child process has done everything it can usefully do // for OnStartRequest. if (XRE_IsContentProcess()) { return NS_OK;
}
rv = SetUpTempFile(aChannel); if (NS_FAILED(rv)) {
nsresult transferError = rv;
rv = CreateFailedTransfer(); if (NS_FAILED(rv)) {
LOG("Failed to create transfer to report failure." "Will fallback to prompter!");
}
mCanceled = true;
request->Cancel(transferError);
auto res = GetInitialDownloadDirectory(true); if (res.isErr()) { // Just send the file name as we can't get a download path. // TODO: evaluate adding a more specific error here.
SendStatusChange(kWriteError, transferError, request, mSuggestedFileName); return res.unwrapErr();
}
// Inform channel it is open on behalf of a download to throttle it during // page loads and prevent its caching.
nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(aChannel); if (httpInternal) {
rv = httpInternal->SetChannelIsForDownload(true);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
if (mSourceUrl->SchemeIs("data")) { // In case we're downloading a data:// uri // we don't want to apply AllowTopLevelNavigationToDataURI.
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
loadInfo->SetForceAllowDataURI(true);
}
// now that the temp file is set up, find out if we need to invoke a dialog // asking the user what they want us to do with this content...
// We can get here for three reasons: "can't handle", "sniffed type", or // "server sent content-disposition:attachment". In the first case we want // to honor the user's "always ask" pref; in the other two cases we want to // honor it only if the default action is "save". Opening attachments in // helper apps by default breaks some websites (especially if the attachment // is one part of a multipart document). Opening sniffed content in helper // apps by default introduces security holes that we'd rather not have.
// So let's find out whether the user wants to be prompted. If he does not, // check mReason and the preferred action to see what we should do.
bool alwaysAsk = true;
mMimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk); if (alwaysAsk) { // But we *don't* ask if this mimeInfo didn't come from // our user configuration datastore and the user has said // at some point in the distant past that they don't // want to be asked. The latter fact would have been // stored in pref strings back in the old days.
bool mimeTypeIsInDatastore = false;
nsCOMPtr<nsIHandlerService> handlerSvc =
do_GetService(NS_HANDLERSERVICE_CONTRACTID); if (handlerSvc) {
handlerSvc->Exists(mMimeInfo, &mimeTypeIsInDatastore);
} if (!handlerSvc || !mimeTypeIsInDatastore) { if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_SAVE_TO_DISK_PREF,
MIMEType.get())) { // Don't need to ask after all.
alwaysAsk = false; // Make sure action matches pref (save to disk).
mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
} elseif (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_OPEN_FILE_PREF,
MIMEType.get())) { // Don't need to ask after all.
alwaysAsk = false;
}
}
} elseif (MIMEType.EqualsLiteral("text/plain")) {
nsAutoCString ext;
mMimeInfo->GetPrimaryExtension(ext); // If people are sending us ApplicationReputation-eligible files with // text/plain mimetypes, enforce asking the user what to do. if (!ext.IsEmpty()) {
nsAutoCString dummyFileName("f"); if (ext.First() != '.') {
dummyFileName.Append(".");
}
ext.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
nsCOMPtr<nsIApplicationReputationService> appRep =
components::ApplicationReputation::Service();
appRep->IsBinary(dummyFileName + ext, &alwaysAsk);
}
}
// OK, now check why we're here if (!alwaysAsk && forcePrompt) { // Force asking if we're not saving. See comment back when we fetched the // alwaysAsk boolean for details.
alwaysAsk = (action != nsIMIMEInfo::saveToDisk);
}
// If we're not asking, check we actually know what to do: if (!alwaysAsk) {
alwaysAsk = action != nsIMIMEInfo::saveToDisk &&
action != nsIMIMEInfo::useHelperApp &&
action != nsIMIMEInfo::useSystemDefault &&
!shouldAutomaticallyHandleInternally;
}
// If we're handling with the OS default and we are that default, force // asking, so we don't end up in an infinite loop: if (!alwaysAsk && action == nsIMIMEInfo::useSystemDefault) { bool areOSDefault = false;
alwaysAsk = NS_SUCCEEDED(mMimeInfo->IsCurrentAppOSDefault(&areOSDefault)) &&
areOSDefault;
} elseif (!alwaysAsk && action == nsIMIMEInfo::useHelperApp) {
nsCOMPtr<nsIHandlerApp> preferredApp;
mMimeInfo->GetPreferredApplicationHandler(getter_AddRefs(preferredApp));
nsCOMPtr<nsILocalHandlerApp> handlerApp = do_QueryInterface(preferredApp); if (handlerApp) {
nsCOMPtr<nsIFile> executable;
handlerApp->GetExecutable(getter_AddRefs(executable));
nsCOMPtr<nsIFile> ourselves; if (executable && // Despite the name, this really just fetches an nsIFile...
NS_SUCCEEDED(NS_GetSpecialDirectory(XRE_EXECUTABLE_FILE,
getter_AddRefs(ourselves)))) {
ourselves = nsMIMEInfoBase::GetCanonicalExecutable(ourselves);
executable = nsMIMEInfoBase::GetCanonicalExecutable(executable); bool isSameApp = false;
alwaysAsk =
NS_FAILED(executable->Equals(ourselves, &isSameApp)) || isSameApp;
}
}
}
// if we were told that we _must_ save to disk without asking, all the stuff // before this is irrelevant; override it if (mForceSave || mForceSaveInternallyHandled) {
alwaysAsk = false;
action = nsIMIMEInfo::saveToDisk;
shouldAutomaticallyHandleInternally = false;
} // Additionally, if we are asked by the OS to open a local file, // automatically downloading it to create a second copy of that file doesn't // really make sense. We should ask the user what they want to do. if (mSourceUrl->SchemeIs("file") && !alwaysAsk &&
action == nsIMIMEInfo::saveToDisk) {
alwaysAsk = true;
}
// If adding new checks, make sure this is the last check before telemetry // and going ahead with opening the file! #ifdef XP_WIN /* We need to see whether the file we've got here could be * executable. If it could, we had better not try to open it! * We can skip this check, though, if we have a setting to open in a * helper app.
*/ if (!alwaysAsk && action != nsIMIMEInfo::saveToDisk &&
!shouldAutomaticallyHandleInternally) {
nsCOMPtr<nsIHandlerApp> prefApp;
mMimeInfo->GetPreferredApplicationHandler(getter_AddRefs(prefApp)); if (action != nsIMIMEInfo::useHelperApp || !prefApp) {
nsCOMPtr<nsIFile> fileToTest;
GetTargetFile(getter_AddRefs(fileToTest)); if (fileToTest) { bool isExecutable;
rv = fileToTest->IsExecutable(&isExecutable); if (NS_FAILED(rv) || mTempFileIsExecutable ||
isExecutable) { // checking NS_FAILED, because paranoia is good
alwaysAsk = true;
}
--> --------------------
--> maximum size reached
--> --------------------
¤ Dauer der Verarbeitung: 0.29 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.