/* 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 mozilla_contentanalysis_h #define mozilla_contentanalysis_h #include "mozilla/MozPromise.h" #include "mozilla/dom/BrowsingContext.h" #include "mozilla/dom/MaybeDiscarded.h" #include "mozilla/dom/Promise.h" #include "mozilla/glean/ContentanalysisMetrics.h" #include "mozilla/media/MediaUtils.h" #include "mozilla/WeakPtr.h" #include "nsIClipboard.h" #include "nsIContentAnalysis.h" #include "nsITransferable.h" #include "nsString.h" #include "nsTHashMap.h" #include "nsTHashSet.h" #include "nsTStringHasher.h" #include class nsBaseClipboard; class nsIPrincipal; class nsIPrintSettings; class ContentAnalysisTest; class ContentAnalysisTelemetryTest; namespace mozilla::dom { class CanonicalBrowsingContext; class DataTransfer; class WindowGlobalParent; } // namespace mozilla::dom namespace mozilla::contentanalysis { class ContentAnalysisBackend; class ContentAnalysisCallback; enum class DefaultResult : uint8_t { eBlock = 0, eWarn = 1, eAllow = 2, eLastValue = 2 }; class ContentAnalysisDiagnosticInfo final : public nsIContentAnalysisDiagnosticInfo { public: NS_DECL_ISUPPORTS NS_DECL_NSICONTENTANALYSISDIAGNOSTICINFO ContentAnalysisDiagnosticInfo(bool aConnectedToAgent, nsString aAgentPath, bool aFailedSignatureVerification, int64_t aRequestCount) : mConnectedToAgent(aConnectedToAgent), mAgentPath(std::move(aAgentPath)), mFailedSignatureVerification(aFailedSignatureVerification), mRequestCount(aRequestCount) {} private: virtual ~ContentAnalysisDiagnosticInfo() = default; bool mConnectedToAgent; nsString mAgentPath; bool mFailedSignatureVerification; int64_t mRequestCount; }; class ContentAnalysisRequest final : public nsIContentAnalysisRequest { public: NS_DECL_ISUPPORTS NS_DECL_NSICONTENTANALYSISREQUEST ContentAnalysisRequest(AnalysisType aAnalysisType, Reason aReason, nsString aString, bool aStringIsFilePath, nsCString aSha256Digest, nsCOMPtr aUrl, OperationType aOperationType, dom::WindowGlobalParent* aWindowGlobalParent, dom::WindowGlobalParent* aSourceWindowGlobal = nullptr, nsCString&& aUserActionId = nsCString()); ContentAnalysisRequest(AnalysisType aAnalysisType, Reason aReason, nsITransferable* aTransferable, dom::WindowGlobalParent* aWindowGlobal, dom::WindowGlobalParent* aSourceWindowGlobal); ContentAnalysisRequest(nsTArray aPrintData, nsCOMPtr aUrl, nsString aPrinterName, Reason aReason, dom::WindowGlobalParent* aWindowGlobalParent); ContentAnalysisRequest(const ContentAnalysisRequest&) = delete; ContentAnalysisRequest& operator=(ContentAnalysisRequest&) = delete; static nsresult GetFileDigest(const nsAString& aFilePath, nsCString& aDigestString); static RefPtr Clone( nsIContentAnalysisRequest* aRequest); private: virtual ~ContentAnalysisRequest(); ContentAnalysisRequest() = default; // See nsIContentAnalysisRequest for values AnalysisType mAnalysisType = AnalysisType::eUnspecified; // See nsIContentAnalysisRequest for values Reason mReason = Reason::eUnknown; nsCOMPtr mTransferable; RefPtr mDataTransfer; // Text content to analyze. Only one of textContent or filePath is defined. nsString mTextContent; // Name of file to analyze. Only one of textContent or filePath is defined. nsString mFilePath; // The URL containing the file download/upload or to which web content is // being uploaded. nsCOMPtr mUrl; // Sha256 digest of file. nsCString mSha256Digest; // URLs involved in the download. nsTArray> mResources; // Email address of user. nsString mEmail; // Unique identifier for this request nsCString mRequestToken; // Unique identifier for this user action. // For example, all requests that come from uploading multiple files // or one clipboard operation should have the same value. nsCString mUserActionId; // The number of requests associated with this mUserActionId. int64_t mUserActionRequestsCount = 1; // Type of text to display, see nsIContentAnalysisRequest for values OperationType mOperationTypeForDisplay = OperationType::eClipboard; // File name to display if mOperationTypeForDisplay is // eUpload or eDownload. nsString mFileNameForDisplay; // The name of the printer being printed to nsString mPrinterName; RefPtr mWindowGlobalParent; // The printed data to analyze, in PDF format. nsTArray mPrintData; // WindowGlobalParent that is the origin of the data in the request, if known. RefPtr mSourceWindowGlobal; // What to multiply the timeout for this request by. Only needed if there are // requests with multiple userActionIds that are logically grouped together. uint32_t mTimeoutMultiplier = 1; // Submit request to agent, even if it was already canceled. Always false // if not in tests. bool mTestOnlyAlwaysSubmitToAgent = false; friend class ::ContentAnalysisTest; template friend RefPtr mozilla::MakeRefPtr(Args&&...); }; #define CONTENTANALYSIS_IID \ {0xa37bed74, 0x4b50, 0x443a, {0xbf, 0x58, 0xf4, 0xeb, 0xbd, 0x30, 0x67, 0xb4}} class ContentAnalysisResponse; class ContentAnalysis final : public nsIContentAnalysis, public nsIObserver, public SupportsWeakPtr { public: NS_INLINE_DECL_STATIC_IID(CONTENTANALYSIS_IID) NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSICONTENTANALYSIS NS_DECL_NSIOBSERVER ContentAnalysis(); ContentAnalysis(const ContentAnalysis&) = delete; ContentAnalysis& operator=(ContentAnalysis&) = delete; struct PrintAllowedResult final { bool mAllowed; dom::MaybeDiscarded mCachedStaticDocumentBrowsingContext; PrintAllowedResult(bool aAllowed, dom::MaybeDiscarded aCachedStaticDocumentBrowsingContext) : mAllowed(aAllowed), mCachedStaticDocumentBrowsingContext( aCachedStaticDocumentBrowsingContext) {} explicit PrintAllowedResult(bool aAllowed) : PrintAllowedResult(aAllowed, dom::MaybeDiscardedBrowsingContext()) {} }; struct PrintAllowedError final { nsresult mError; dom::MaybeDiscarded mCachedStaticDocumentBrowsingContext; PrintAllowedError(nsresult aError, dom::MaybeDiscarded aCachedStaticDocumentBrowsingContext) : mError(aError), mCachedStaticDocumentBrowsingContext( aCachedStaticDocumentBrowsingContext) {} explicit PrintAllowedError(nsresult aError) : PrintAllowedError(aError, dom::MaybeDiscardedBrowsingContext()) {} }; using PrintAllowedPromise = MozPromise; MOZ_CAN_RUN_SCRIPT static RefPtr PrintToPDFToDetermineIfPrintAllowed( dom::CanonicalBrowsingContext* aBrowsingContext, nsIPrintSettings* aPrintSettings); // Find the outermost browsing context that has same-origin access to // aBrowsingContext, and this is the URL we will pass to the Content Analysis // agent. static nsCOMPtr GetURIForBrowsingContext( dom::CanonicalBrowsingContext* aBrowsingContext); static bool CheckClipboardContentAnalysisSync( nsBaseClipboard* aClipboard, mozilla::dom::WindowGlobalParent* aWindow, const nsCOMPtr& trans, nsIClipboard::ClipboardType aClipboardType); static void CheckClipboardContentAnalysis( nsBaseClipboard* aClipboard, mozilla::dom::WindowGlobalParent* aWindow, nsITransferable* aTransferable, nsIClipboard::ClipboardType aClipboardType, ContentAnalysisCallback* aResolver, bool aForFullClipboard = false); using FilesAllowedPromise = MozPromise, nsresult, true>; // Checks the passed in files in "batch mode", meaning that all requests will // be done even if some of them are BLOCKED. Unlike the other Check // methods, "batch mode" requests do not all share a user action ID. // This also consolidates the busy dialogs for the files into one that is // associated with the "primary" request's user action ID -- that is, the // user action ID of the first request generated. // Note that aURI is only necessary to pass in in gtests; otherwise we'll // get the URI from aWindow. static RefPtr CheckUploadsInBatchMode( nsCOMArray&& aFiles, bool aAutoAcknowledge, mozilla::dom::WindowGlobalParent* aWindow, nsIContentAnalysisRequest::Reason aReason, nsIURI* aURI = nullptr); static RefPtr GetContentAnalysisFromService(); void HandleResponseFromAgent(ContentAnalysisResponse* aResponse, bool aAutoAcknowledge); // Cancel all outstanding requests for the given user action ID. // aResult is used to determine what kind of cancellation this is // (user-initiated, timeout, blocked user action, internal error, etc). // The cancellation behavior is dependent on that value. In particular, // some causes lead to programmable default behaviors -- see e.g. // browser.contentanalysis.default_result and // browser.contentanalysis.timeout_result. Oothers, like user-initiated // and shutdown cancellations, have fixed behavior. void CancelWithError(nsCString&& aUserActionId, nsresult aResult); // These are the MIME types that Content Analysis can analyze. static constexpr const char* kKnownClipboardTypes[] = { kTextMime, kHTMLMime, kCustomTypesMime, kFileMime}; // Returns whether the backend is currently creating its agent connection. // Only to be called from tests. bool GetCreatingClientForTest(); // Thread-safe check whether the service is being destroyed. bool IsShutDown(); // Thread-safe check whether the given user action has already been // canceled (either by user action or because a request earlier in the same // user action was canceled). bool WasUserActionCanceled(const nsACString& aUserActionId); // Check whether the given request token is waiting for a WARN dialog to be // responded to. bool IsRequestWaitingForWarnDialog(const nsACString& aRequestToken); private: virtual ~ContentAnalysis(); nsresult RunAnalyzeRequestTask( const nsCOMPtr& aRequest, bool aAutoAcknowledge, const nsCOMPtr& aCallback); nsresult RunAcknowledgeTask( nsIContentAnalysisAcknowledgement* aAcknowledgement, const nsACString& aRequestToken); void IssueResponse(ContentAnalysisResponse* response, nsCString&& aUserActionId, bool aAcknowledge, bool aIsTooLate); void NotifyResponseObservers(ContentAnalysisResponse* aResponse, nsCString&& aUserActionId, bool aAutoAcknowledge, bool aIsTimeout); void NotifyObserversAndMaybeIssueResponseFromAgent( ContentAnalysisResponse* aResponse, nsCString&& aUserActionId, bool aAutoAcknowledge); // Destroy the service. Happens during xpcom-shutdown-threads. void Close(); // Did the URL filter completely handle the request or do we need to check // with the agent. enum UrlFilterResult { eCheck, eDeny, eAllow }; UrlFilterResult FilterByUrlLists(nsIContentAnalysisRequest* aRequest, nsIURI* aUri); void EnsureParsedUrlFilters(); using ContentAnalysisRequestArray = CopyableTArray>; using RequestsPromise = MozPromise; // Counts the number of times it receives an "allow content" and (1) calls // ContentResult on mCallback when all requests are approved, (2) calls // ContentResult and cancels outstanding scans when any one request is // rejected, or (3) calls Error and cancels outstanding scans when any one // fails. // Once constructed, this object is required to eventually issue a response to // the given callback. // This class doesn't care if it receives more calls than there are requests. // Canceling issues callback calls with no initiating request. This class // relays the verdicts on a first-come-first-served basis, so a cancel // that comes before an allow overrides that allow, and vice-versa. class MultipartRequestCallback : public nsIContentAnalysisCallback { public: NS_INLINE_DECL_REFCOUNTING(MultipartRequestCallback, override) NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; NS_DECL_NSICONTENTANALYSISCALLBACK static RefPtr Create( ContentAnalysis* aContentAnalysis, const nsTArray& aRequests, nsIContentAnalysisCallback* aCallback, bool aAutoAcknowledge); bool HasResponded() const { return mResponded; } private: MultipartRequestCallback() = default; virtual ~MultipartRequestCallback(); template friend RefPtr mozilla::MakeRefPtr(Args&&...); void Initialize( ContentAnalysis* aContentAnalysis, const nsTArray& aRequests, nsIContentAnalysisCallback* aCallback, bool aAutoAcknowledge); void CancelRequests(); void RemoveFromUserActionMap(); WeakPtr mWeakContentAnalysis; nsCOMPtr mCallback; nsCString mUserActionId; // Number of CA requests remaining for this transaction. size_t mNumCARequestsRemaining = 0; // True if we have issued a response for these requests. bool mResponded = false; }; Result, nsCOMPtr> GetFinalRequestList(const ContentAnalysisRequestArray& aRequests); Result, nsresult> ExpandFolderRequest( nsIContentAnalysisRequest* aRequest, nsIFile* file); bool mSetByEnterprise; // Backend engine that produces analysis verdicts. RefPtr mBackend; struct UserActionData final { nsCOMPtr mCallback; nsTHashSet mRequestTokens; RefPtr mTimeoutRunnable; bool mAutoAcknowledge; bool mIsHandlingTimeout = false; }; // This map is stored so that requests can be canceled while they are // still being checked. It is maintained by our inner class // MultipartRequestCallback. nsTHashMap mUserActionMap; void RemoveFromUserActionMap(nsCString&& aUserActionId); // The agent may respond to actions that we have canceled and we need to // remember how we handled them, whether it was to cancel (block) them, // or to issue a default response. struct CanceledResponse { nsIContentAnalysisAcknowledgement::FinalAction mAction; size_t mNumExpectedResponses; }; using UserActionIdToCanceledResponseMap = nsTHashMap; DataMutex mUserActionIdToCanceledResponseMap{ "ContentAnalysis::UserActionIdToCanceledResponseMap"}; class CachedClipboardResponse { public: CachedClipboardResponse() = default; Maybe GetCachedResponse( nsIURI* aURI, int32_t aClipboardSequenceNumber); void SetCachedResponse(const nsCOMPtr& aURI, int32_t aClipboardSequenceNumber, nsIContentAnalysisResponse::Action aAction); private: Maybe mClipboardSequenceNumber; nsTArray, nsIContentAnalysisResponse::Action>> mData; }; CachedClipboardResponse mCachedClipboardResponse; struct WarnResponseData { RefPtr mResponse; nsCString mUserActionId; bool mAutoAcknowledge; bool mWasTimeout; }; // Request token to warn response map. nsTHashMap mWarnResponseDataMap; std::vector mAllowUrlList; std::vector mDenyUrlList; bool mParsedUrlLists = false; bool mForbidFutureRequests = false; DataMutex mIsShutDown{false, "ContentAnalysis::IsShutDown"}; // Set of sets of user action IDs. Each set of IDs defines one compound // action. using UserActionSet = media::Refcountable>; using UserActionSets = mozilla::HashSet, PointerHasher>; UserActionSets mCompoundUserActions; friend class ContentAnalysisResponse; friend class ::ContentAnalysisTest; friend class ::ContentAnalysisTelemetryTest; }; class ContentAnalysisResponse final : public nsIContentAnalysisResponse, public nsIClassInfo { public: NS_DECL_ISUPPORTS NS_DECL_NSICLASSINFO NS_DECL_NSICONTENTANALYSISRESULT NS_DECL_NSICONTENTANALYSISRESPONSE void SetOwner(ContentAnalysis* aOwner); void DoNotAcknowledge() { mDoNotAcknowledge = true; } void SetCancelError(CancelError aCancelError); void SetIsCachedResponse() { mIsCachedResponse = true; } // Remove unneeded copy constructor/assignment ContentAnalysisResponse(const ContentAnalysisResponse&) = delete; ContentAnalysisResponse& operator=(ContentAnalysisResponse&) = delete; private: virtual ~ContentAnalysisResponse() = default; ContentAnalysisResponse(Action aAction, const nsACString& aRequestToken, const nsACString& aUserActionId, bool aIsSynthetic = false); // Use MakeRefPtr as factory. template friend RefPtr mozilla::MakeRefPtr(Args&&...); void ResolveWarnAction(bool aAllowContent); // Action requested by the agent Action mAction; // Identifiers for the corresponding nsIContentAnalysisRequest nsCString mRequestToken; nsCString mUserActionId; // If mAction is eCanceled, this is the error explaining why the request was // canceled, or eUserInitiated if the user canceled it. CancelError mCancelError = CancelError::eUserInitiated; // ContentAnalysis (or, more precisely, its Client object) must outlive // the transaction. RefPtr mOwner; // Whether the response has been acknowledged bool mHasAcknowledged = false; // If true, the request was completely handled by URL filter lists, so it // was not sent to the agent and should not send an Acknowledge. bool mDoNotAcknowledge = false; // Whether this is a cached result that wasn't actually sent to the DLP agent. // This indicates that the request was a duplicate of a previously sent one, // so any dialogs (for block/warn) should not be shown. bool mIsCachedResponse = false; // Whether this is a synthesizic response from Firefox (as opposed to a // response from a DLP agent). // Synthetic responses ignore browser.contentanalysis.show_blocked_result and // always show a blocked result for blocked content, since there is no agent // that could have shown one for us. bool mIsSyntheticResponse = false; friend class ContentAnalysis; }; class ContentAnalysisAcknowledgement final : public nsIContentAnalysisAcknowledgement { public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSICONTENTANALYSISACKNOWLEDGEMENT ContentAnalysisAcknowledgement(Result aResult, FinalAction aFinalAction); private: virtual ~ContentAnalysisAcknowledgement() = default; Result mResult; FinalAction mFinalAction; }; /** * This class: * 1. Asserts if the callback is not called before destruction. * 2. Takes a strong reference to the nsIContentAnalysisResult when * calling the callback, which guarantees that someone does. Otherwise, * if neither the caller nor the callback did, then the result would leak. */ class ContentAnalysisCallback final : public nsIContentAnalysisCallback { public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSICONTENTANALYSISCALLBACK ContentAnalysisCallback( std::function&& aContentResponseCallback, std::function&& aErrorCallback) : mContentResponseCallback(std::move(aContentResponseCallback)), mErrorCallback(std::move(aErrorCallback)) {} explicit ContentAnalysisCallback( std::function&& aContentResponseCallback); // Wrap a given callback, in case it doesn't provide the guarantees that // this one does (such as checking that it is eventually called). explicit ContentAnalysisCallback(nsIContentAnalysisCallback* aDecoratedCB) { mContentResponseCallback = [decoratedCB = RefPtr{aDecoratedCB}]( nsIContentAnalysisResult* aResult) { decoratedCB->ContentResult(aResult); }; mErrorCallback = [decoratedCB = RefPtr{aDecoratedCB}](nsresult aRv) { decoratedCB->Error(aRv); }; } private: virtual ~ContentAnalysisCallback() { MOZ_ASSERT(!mContentResponseCallback && !mErrorCallback && !mPromise, "ContentAnalysisCallback never called!"); } // Called after callbacks are called. void ClearCallbacks() { mContentResponseCallback = nullptr; mErrorCallback = nullptr; mPromise = nullptr; } explicit ContentAnalysisCallback(dom::Promise* aPromise); std::function mContentResponseCallback; std::function mErrorCallback; RefPtr mPromise; friend class ContentAnalysis; }; } // namespace mozilla::contentanalysis #endif // mozilla_contentanalysis_h