diff options
Diffstat (limited to 'uriloader/exthandler/nsExternalHelperAppService.h')
-rw-r--r-- | uriloader/exthandler/nsExternalHelperAppService.h | 561 |
1 files changed, 561 insertions, 0 deletions
diff --git a/uriloader/exthandler/nsExternalHelperAppService.h b/uriloader/exthandler/nsExternalHelperAppService.h new file mode 100644 index 0000000000..62f9d60abc --- /dev/null +++ b/uriloader/exthandler/nsExternalHelperAppService.h @@ -0,0 +1,561 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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/. */ + +#ifndef nsExternalHelperAppService_h__ +#define nsExternalHelperAppService_h__ + +#include "mozilla/Logging.h" +#include "prtime.h" + +#include "nsIExternalHelperAppService.h" +#include "nsIExternalProtocolService.h" +#include "nsIWebProgressListener2.h" +#include "nsIHelperAppLauncherDialog.h" + +#include "nsILoadInfo.h" +#include "nsIMIMEInfo.h" +#include "nsIMIMEService.h" +#include "nsINamed.h" +#include "nsIStreamListener.h" +#include "nsIFile.h" +#include "nsIPermission.h" +#include "nsString.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIChannel.h" +#include "nsIBackgroundFileSaver.h" + +#include "nsCOMPtr.h" +#include "nsIObserver.h" +#include "nsCOMArray.h" +#include "nsWeakReference.h" +#include "mozilla/Attributes.h" + +class nsExternalAppHandler; +class nsIMIMEInfo; +class nsITransfer; +class nsIPrincipal; +class MaybeCloseWindowHelper; + +#define EXTERNAL_APP_HANDLER_IID \ + { \ + 0x50eb7479, 0x71ff, 0x4ef8, { \ + 0xb3, 0x1e, 0x3b, 0x59, 0xc8, 0xab, 0xb9, 0x24 \ + } \ + } + +/** + * The helper app service. Responsible for handling content that Mozilla + * itself can not handle + * Note that this is an abstract class - we depend on appropriate subclassing + * on a per-OS basis to implement some methods. + */ +class nsExternalHelperAppService : public nsIExternalHelperAppService, + public nsPIExternalAppLauncher, + public nsIExternalProtocolService, + public nsIMIMEService, + public nsIObserver, + public nsSupportsWeakReference { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIEXTERNALHELPERAPPSERVICE + NS_DECL_NSPIEXTERNALAPPLAUNCHER + NS_DECL_NSIMIMESERVICE + NS_DECL_NSIOBSERVER + + nsExternalHelperAppService(); + + /** + * Initializes internal state. Will be called automatically when + * this service is first instantiated. + */ + [[nodiscard]] nsresult Init(); + + /** + * nsIExternalProtocolService methods that we provide in this class. Other + * methods should be implemented by per-OS subclasses. + */ + NS_IMETHOD ExternalProtocolHandlerExists(const char* aProtocolScheme, + bool* aHandlerExists) override; + NS_IMETHOD IsExposedProtocol(const char* aProtocolScheme, + bool* aResult) override; + NS_IMETHOD GetProtocolHandlerInfo(const nsACString& aScheme, + nsIHandlerInfo** aHandlerInfo) override; + + NS_IMETHOD LoadURI(nsIURI* aURI, nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aRedirectPrincipal, + mozilla::dom::BrowsingContext* aBrowsingContext, + bool aWasTriggeredExternally, + bool aHasValidUserGestureActivation) override; + NS_IMETHOD SetProtocolHandlerDefaults(nsIHandlerInfo* aHandlerInfo, + bool aOSHandlerExists) override; + + /** + * Given a string identifying an application, create an nsIFile representing + * it. This function should look in $PATH for the application. + * The base class implementation will first try to interpret platformAppPath + * as an absolute path, and if that fails it will look for a file next to the + * mozilla executable. Subclasses can override this method if they want a + * different behaviour. + * @param platformAppPath A platform specific path to an application that we + * got out of the rdf data source. This can be a mac + * file spec, a unix path or a windows path depending + * on the platform + * @param aFile [out] An nsIFile representation of that platform + * application path. + */ + virtual nsresult GetFileTokenForPath(const char16_t* platformAppPath, + nsIFile** aFile); + + NS_IMETHOD OSProtocolHandlerExists(const char* aScheme, bool* aExists) = 0; + + /** + * Given an extension, get a MIME type string from the builtin list of + * mime types. + * @return true if we successfully found a mimetype. + */ + virtual bool GetMIMETypeFromDefaultForExtension(const nsACString& aExtension, + nsACString& aMIMEType); + + /** + * Given an extension, get a MIME type string. If not overridden by + * the OS-specific nsOSHelperAppService, will call into GetMIMEInfoFromOS + * with an empty mimetype. + * @return true if we successfully found a mimetype. + */ + virtual bool GetMIMETypeFromOSForExtension(const nsACString& aExtension, + nsACString& aMIMEType); + + static already_AddRefed<nsExternalHelperAppService> GetSingleton(); + + // Internal method. Only called directly from tests. + static nsresult EscapeURI(nsIURI* aURI, nsIURI** aResult); + + /** + * Logging Module. Usage: set MOZ_LOG=HelperAppService:level, where level + * should be 2 for errors, 3 for debug messages from the cross- platform + * nsExternalHelperAppService, and 4 for os-specific debug messages. + */ + static mozilla::LazyLogModule sLog; + + protected: + virtual ~nsExternalHelperAppService(); + + /** + * Searches the "extra" array of MIMEInfo objects for an object + * with a specific type. If found, it will modify the passed-in + * MIMEInfo. Otherwise, it will return an error and the MIMEInfo + * will be untouched. + * @param aContentType The type to search for. + * @param aOverwriteDescription Whether to overwrite the description + * @param aMIMEInfo [inout] The mime info, if found + */ + nsresult FillMIMEInfoForMimeTypeFromExtras(const nsACString& aContentType, + bool aOverwriteDescription, + nsIMIMEInfo* aMIMEInfo); + /** + * Searches the "extra" array of MIMEInfo objects for an object + * with a specific extension. + * + * Does not change the MIME Type of the MIME Info. + * + * @see FillMIMEInfoForMimeTypeFromExtras + */ + nsresult FillMIMEInfoForExtensionFromExtras(const nsACString& aExtension, + nsIMIMEInfo* aMIMEInfo); + + /** + * Replace the primary extension of the mimeinfo object if it's in our + * list of forbidden extensions. This fixes up broken information + * provided to us by the OS. + */ + bool MaybeReplacePrimaryExtension(const nsACString& aPrimaryExtension, + nsIMIMEInfo* aMIMEInfo); + + /** + * Searches the "extra" array for a MIME type, and gets its extension. + * @param aExtension The extension to search for + * @param aMIMEType [out] The found MIME type. + * @return true if the extension was found, false otherwise. + */ + bool GetTypeFromExtras(const nsACString& aExtension, nsACString& aMIMEType); + + // friend, so that it can access the nspr log module. + friend class nsExternalAppHandler; + + /** + * Helper function for ExpungeTemporaryFiles and ExpungeTemporaryPrivateFiles + */ + static void ExpungeTemporaryFilesHelper(nsCOMArray<nsIFile>& fileList); + /** + * Helper function for DeleteTemporaryFileOnExit and + * DeleteTemporaryPrivateFileWhenPossible + */ + static nsresult DeleteTemporaryFileHelper(nsIFile* aTemporaryFile, + nsCOMArray<nsIFile>& aFileList); + /** + * Functions related to the tempory file cleanup service provided by + * nsExternalHelperAppService + */ + void ExpungeTemporaryFiles(); + /** + * Functions related to the tempory file cleanup service provided by + * nsExternalHelperAppService (for the temporary files added during + * the private browsing mode) + */ + 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); + + // Ensure that the filename is safe for the file system. This will remove or + // replace any invalid characters and trim extra whitespace as needed. If the + // filename is too long, it will be truncated but the existing period and + // extension, if any, will be preserved. + void SanitizeFileName(nsAString& aFileName, 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, + bool aForceAppend, + const nsCString& aFileExt); + + /** + * Array for the files that should be deleted + */ + nsCOMArray<nsIFile> mTemporaryFilesList; + /** + * Array for the files that should be deleted (for the temporary files + * added during the private browsing mode) + */ + nsCOMArray<nsIFile> mTemporaryPrivateFilesList; + + private: + nsresult DoContentContentProcessHelper( + const nsACString& aMimeContentType, nsIRequest* aRequest, + mozilla::dom::BrowsingContext* aContentContext, bool aForceSave, + nsIInterfaceRequestor* aWindowContext, + nsIStreamListener** aStreamListener); +}; + +/** + * An external app handler is just a small little class that presents itself as + * a nsIStreamListener. It saves the incoming data into a temp file. The handler + * is bound to an application when it is created. When it receives an + * OnStopRequest it launches the application using the temp file it has + * stored the data into. We create a handler every time we have to process + * data using a helper app. + */ +class nsExternalAppHandler final : public nsIStreamListener, + public nsIHelperAppLauncher, + public nsIBackgroundFileSaverObserver, + public nsINamed { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSIHELPERAPPLAUNCHER + NS_DECL_NSICANCELABLE + NS_DECL_NSIBACKGROUNDFILESAVEROBSERVER + NS_DECL_NSINAMED + + NS_DECLARE_STATIC_IID_ACCESSOR(EXTERNAL_APP_HANDLER_IID) + + /** + * @param aMIMEInfo MIMEInfo object, representing the type of the + * content that should be handled + * @param aFileExtension The extension we need to append to our temp file, + * INCLUDING the ".". e.g. .mp3 + * @param aContentContext dom Window context, as passed to DoContent. + * @param aWindowContext Top level window context used in dialog parenting, + * as passed to DoContent. This parameter may be null, + * in which case dialogs will be parented to + * aContentContext. + * @param mExtProtSvc nsExternalHelperAppService on creation + * @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 nsAString& aFileExtension, + mozilla::dom::BrowsingContext* aBrowsingContext, + nsIInterfaceRequestor* aWindowContext, + nsExternalHelperAppService* aExtProtSvc, + const nsAString& aSuggestedFileName, uint32_t aReason, + bool aForceSave); + + /** + * Clean up after the request was diverted to the parent process. + */ + void DidDivertRequest(nsIRequest* request); + + /** + * Apply content conversions if needed. + */ + void MaybeApplyDecodingForExtension(nsIRequest* request); + + void SetShouldCloseWindow() { mShouldCloseWindow = true; } + + protected: + bool IsDownloadSpam(nsIChannel* aChannel); + + ~nsExternalAppHandler(); + + nsCOMPtr<nsIFile> mTempFile; + nsCOMPtr<nsIURI> mSourceUrl; + nsString mFileExtension; + nsString mTempLeafName; + + /** + * The MIME Info for this load. Will never be null. + */ + nsCOMPtr<nsIMIMEInfo> mMimeInfo; + + /** + * The BrowsingContext associated with this request to handle content. + */ + RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext; + + /** + * If set, the parent window helper app dialogs and file pickers + * should use in parenting. If null, we use mContentContext. + */ + nsCOMPtr<nsIInterfaceRequestor> mWindowContext; + + /** + * Used to close the window on a timer, to avoid any exceptions that are + * thrown if we try to close the window before it's fully loaded. + */ + RefPtr<MaybeCloseWindowHelper> mMaybeCloseWindowHelper; + + /** + * The following field is set if we were processing an http channel that had + * a content disposition header which specified the SUGGESTED file name we + * should present to the user in the save to disk dialog. + */ + nsString mSuggestedFileName; + + /** + * If set, this handler should forcibly save the file to disk regardless of + * MIME info settings or anything else, without ever popping up the + * unknown content type handling dialog. + */ + bool mForceSave; + + /** + * The canceled flag is set if the user canceled the launching of this + * application before we finished saving the data to a temp file. + */ + bool mCanceled; + + /** + * True if a stop request has been issued. + */ + bool mStopRequestIssued; + + bool mIsFileChannel; + + /** + * True if the ExternalHelperAppChild told us that we should close the window + * if we handle the content as a download. + */ + bool mShouldCloseWindow; + + /** + * True if the file should be handled internally. + */ + bool mHandleInternally; + + /** + * True if any dialog (e.g. unknown content type or file picker) is shown — + * can stop downloads panel from opening, to avoid redundant interruptions. + */ + bool mDialogShowing; + + /** + * One of the REASON_ constants from nsIHelperAppLauncherDialog. Indicates the + * reason the dialog was shown (unknown content type, server requested it, + * etc). + */ + uint32_t mReason; + + /** + * Indicates if the nsContentSecurityUtils rate this download as + * acceptable, potentialy unwanted or illigal request. + * + */ + int32_t mDownloadClassification; + + /** + * Track the executable-ness of the temporary file. + */ + bool mTempFileIsExecutable; + + PRTime mTimeDownloadStarted; + int64_t mContentLength; + int64_t mProgress; /**< Number of bytes received (for sending progress + notifications). */ + + /** + * When we are told to save the temp file to disk (in a more permament + * location) before we are done writing the content to a temp file, then + * we need to remember the final destination until we are ready to use it. + */ + nsCOMPtr<nsIFile> mFinalFileDestination; + + uint32_t mBufferSize; + + /** + * This object handles saving the data received from the network to a + * temporary location first, and then move the file to its final location, + * doing all the input/output on a background thread. + */ + nsCOMPtr<nsIBackgroundFileSaver> mSaver; + + /** + * Stores the SHA-256 hash associated with the file that we downloaded. + */ + nsAutoCString mHash; + /** + * Stores the signature information of the downloaded file in an nsTArray of + * nsTArray of Array of bytes. If the file is unsigned this will be + * empty. + */ + nsTArray<nsTArray<nsTArray<uint8_t>>> mSignatureInfo; + /** + * Stores the redirect information associated with the channel. + */ + nsCOMPtr<nsIArray> mRedirects; + /** + * Get the dialog parent: the parent window that we can attach + * a dialog to when prompting the user for a download. + */ + already_AddRefed<nsIInterfaceRequestor> GetDialogParent(); + /** + * Creates the temporary file for the download and an output stream for it. + * Upon successful return, both mTempFile and mSaver will be valid. + */ + nsresult SetUpTempFile(nsIChannel* aChannel); + /** + * When we download a helper app, we are going to retarget all load + * notifications into our own docloader and load group instead of + * using the window which initiated the load....RetargetLoadNotifications + * contains that information... + */ + void RetargetLoadNotifications(nsIRequest* request); + /** + * Once the user tells us how they want to dispose of the content + * create an nsITransfer so they know what's going on. If this fails, the + * caller MUST call Cancel. + */ + nsresult CreateTransfer(); + + /** + * If we fail to create the necessary temporary file to initiate a transfer + * we will report the failure by creating a failed nsITransfer. + */ + nsresult CreateFailedTransfer(); + + /* + * The following two functions are part of the split of SaveToDisk + * to make it async, and works as following: + * + * SaveToDisk -------> RequestSaveDestination + * . + * . + * v + * ContinueSave <------- SaveDestinationAvailable + */ + + /** + * This is called by SaveToDisk to decide what's the final + * file destination chosen by the user or by auto-download settings. + */ + void RequestSaveDestination(const nsString& aDefaultFile, + const nsString& aDefaultFileExt); + + /** + * When SaveToDisk is called, it possibly delegates to RequestSaveDestination + * to decide the file destination. ContinueSave must then be called when + * the final destination is finally known. + * @param aFile The file that was chosen as the final destination. + * Must not be null. + */ + nsresult ContinueSave(nsIFile* aFile); + + /** + * Notify our nsITransfer object that we are done with the download. This is + * always called after the target file has been closed. + * + * @param aStatus + * NS_OK for success, or a failure code if the download failed. + * A partially downloaded file may still be available in this case. + */ + void NotifyTransfer(nsresult aStatus); + + /** + * Helper routine that searches a pref string for a given mime type + */ + bool GetNeverAskFlagFromPref(const char* prefName, const char* aContentType); + + /** + * Helper routine to ensure that mSuggestedFileName ends in the correct + * extension, in case the original extension contains invalid characters + * or if this download is for a mimetype where we enforce using a specific + * extension (image/, video/, and audio/ based mimetypes, and a few specific + * document types). + * + * It also ensure that mFileExtension only contains an extension + * when it is different from mSuggestedFileName's extension. + */ + void EnsureCorrectExtension(const nsString& aFileExt); + + typedef enum { kReadError, kWriteError, kLaunchError } ErrorType; + /** + * Utility function to send proper error notification to web progress listener + */ + void SendStatusChange(ErrorType type, nsresult aStatus, nsIRequest* aRequest, + const nsString& path); + + /** + * Set in HelperAppDlg.jsm. This is always null after the user has chosen an + * action. + */ + nsCOMPtr<nsIWebProgressListener2> mDialogProgressListener; + /** + * Set once the user has chosen an action. This is null after the download + * has been canceled or completes. + */ + nsCOMPtr<nsITransfer> mTransfer; + + nsCOMPtr<nsIHelperAppLauncherDialog> mDialog; + + /** + + * The request that's being loaded. Initialized in OnStartRequest. + * Nulled out in OnStopRequest or once we know what we're doing + * with the data, whichever happens later. + */ + nsCOMPtr<nsIRequest> mRequest; + + RefPtr<nsExternalHelperAppService> mExtProtSvc; +}; +NS_DEFINE_STATIC_IID_ACCESSOR(nsExternalAppHandler, EXTERNAL_APP_HANDLER_IID) + +#endif // nsExternalHelperAppService_h__ |