/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */

/* 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_net_HttpChannelChild_h
#define mozilla_net_HttpChannelChild_h

#include "mozilla/Mutex.h"
#include "mozilla/StaticPrefsBase.h"
#include "mozilla/extensions/StreamFilterParent.h"
#include "mozilla/net/HttpBaseChannel.h"
#include "mozilla/net/NeckoTargetHolder.h"
#include "mozilla/net/PHttpChannelChild.h"
#include "mozilla/net/ChannelEventQueue.h"

#include "nsIStreamListener.h"
#include "nsIInterfaceRequestor.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIProgressEventSink.h"
#include "nsICacheEntry.h"
#include "nsICacheInfoChannel.h"
#include "nsIResumableChannel.h"
#include "nsIProxiedChannel.h"
#include "nsIAsyncVerifyRedirectCallback.h"
#include "nsIChildChannel.h"
#include "nsIHttpChannelChild.h"
#include "nsIMultiPartChannel.h"
#include "nsIThreadRetargetableRequest.h"
#include "mozilla/net/DNS.h"

class nsIEventTarget;
class nsIInterceptedBodyCallback;
class nsISerialEventTarget;
class nsITransportSecurityInfo;
class nsInputStreamPump;

#define HTTP_CHANNEL_CHILD_IID \
  {0x321bd99e, 0x2242, 0x4dc6, {0xbb, 0xec, 0xd5, 0x06, 0x29, 0x7c, 0x39, 0x83}}

namespace mozilla::net {

class HttpBackgroundChannelChild;

class HttpChannelChild final : public PHttpChannelChild,
                               public HttpBaseChannel,
                               public HttpAsyncAborter<HttpChannelChild>,
                               public nsICacheInfoChannel,
                               public nsIProxiedChannel,
                               public nsIAsyncVerifyRedirectCallback,
                               public nsIChildChannel,
                               public nsIHttpChannelChild,
                               public nsIMultiPartChannel,
                               public nsIThreadRetargetableRequest,
                               public NeckoTargetHolder {
  virtual ~HttpChannelChild();

 public:
  NS_DECL_ISUPPORTS_INHERITED
  NS_DECL_NSICACHEINFOCHANNEL
  NS_DECL_NSIPROXIEDCHANNEL
  NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
  NS_DECL_NSICHILDCHANNEL
  NS_DECL_NSIHTTPCHANNELCHILD
  NS_DECL_NSIMULTIPARTCHANNEL
  NS_DECL_NSITHREADRETARGETABLEREQUEST
  NS_INLINE_DECL_STATIC_IID(HTTP_CHANNEL_CHILD_IID)

  HttpChannelChild();

  // Methods HttpBaseChannel didn't implement for us or that we override.
  //
  // nsIRequest
  NS_IMETHOD SetCanceledReason(const nsACString& aReason) override;
  NS_IMETHOD GetCanceledReason(nsACString& aReason) override;
  NS_IMETHOD CancelWithReason(nsresult status,
                              const nsACString& reason) override;
  NS_IMETHOD Cancel(nsresult status) override;
  NS_IMETHOD Suspend() override;
  NS_IMETHOD Resume() override;
  // nsIChannel
  NS_IMETHOD GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) override;
  NS_IMETHOD AsyncOpen(nsIStreamListener* aListener) override;
  NS_IMETHOD GetDecompressDictionary(
      DictionaryCacheEntry** aDictionary) override {
    *aDictionary = nullptr;
    return NS_OK;
  }
  NS_IMETHOD SetDecompressDictionary(
      DictionaryCacheEntry* aDictionary) override {
    return NS_OK;
  }

  // HttpBaseChannel::nsIHttpChannel
  NS_IMETHOD SetRequestHeader(const nsACString& aHeader,
                              const nsACString& aValue, bool aMerge) override;
  NS_IMETHOD SetEmptyRequestHeader(const nsACString& aHeader) override;
  NS_IMETHOD RedirectTo(nsIURI* newURI) override;
  NS_IMETHOD TransparentRedirectTo(nsIURI* newURI) override;
  NS_IMETHOD UpgradeToSecure() override;
  NS_IMETHOD GetProtocolVersion(nsACString& aProtocolVersion) override;
  void DoDiagnosticAssertWhenOnStopNotCalledOnDestroy() override;
  // nsIHttpChannelInternal
  NS_IMETHOD GetIsAuthChannel(bool* aIsAuthChannel) override;
  NS_IMETHOD SetEarlyHintObserver(nsIEarlyHintObserver* aObserver) override;
  NS_IMETHOD SetWebTransportSessionEventListener(
      WebTransportSessionEventListener* aListener) override;

  NS_IMETHOD SetResponseOverride(
      nsIReplacedHttpResponse* aReplacedHttpResponse) override {
    return NS_OK;
  }

  NS_IMETHOD SetResponseStatus(uint32_t aStatus,
                               const nsACString& aStatusText) override {
    return NS_OK;
  }
  // nsISupportsPriority
  NS_IMETHOD SetPriority(int32_t value) override;
  // nsIClassOfService
  NS_IMETHOD SetClassFlags(uint32_t inFlags) override;
  NS_IMETHOD AddClassFlags(uint32_t inFlags) override;
  NS_IMETHOD ClearClassFlags(uint32_t inFlags) override;
  NS_IMETHOD SetClassOfService(ClassOfService inCos) override;
  NS_IMETHOD SetIncremental(bool inIncremental) override;
  // nsIResumableChannel
  NS_IMETHOD ResumeAt(uint64_t startPos, const nsACString& entityID) override;

  nsresult SetReferrerHeader(const nsACString& aReferrer,
                             bool aRespectBeforeConnect) override;

  [[nodiscard]] bool IsSuspended();

  // Callback while background channel is ready.
  void OnBackgroundChildReady(HttpBackgroundChannelChild* aBgChild);
  // Callback while background channel is destroyed.
  void OnBackgroundChildDestroyed(HttpBackgroundChannelChild* aBgChild);

  nsresult CrossProcessRedirectFinished(nsresult aStatus);

  void RegisterStreamFilter(
      RefPtr<extensions::StreamFilterParent>& aStreamFilter);

  // Get the captured JavaScript call stack for LNA console logging
  const char* GetCallStack() const {
    return mCallStack ? mCallStack.get() : nullptr;
  }

 protected:
  mozilla::ipc::IPCResult RecvOnStartRequestSent() override;
  mozilla::ipc::IPCResult RecvFailedAsyncOpen(const nsresult& status) override;
  mozilla::ipc::IPCResult RecvRedirect1Begin(
      const uint32_t& registrarId, nsIURI* newOriginalURI,
      const uint32_t& newLoadFlags, const uint32_t& redirectFlags,
      const ParentLoadInfoForwarderArgs& loadInfoForwarder,
      nsHttpResponseHead&& responseHead, nsITransportSecurityInfo* securityInfo,
      const uint64_t& channelId, const NetAddr& oldPeerAddr,
      const ResourceTimingStructArgs& aTiming) override;
  mozilla::ipc::IPCResult RecvRedirect3Complete() override;
  mozilla::ipc::IPCResult RecvRedirectFailed(const nsresult& status) override;
  mozilla::ipc::IPCResult RecvDeleteSelf() override;

  mozilla::ipc::IPCResult RecvReportSecurityMessage(
      const nsAString& messageTag, const nsAString& messageCategory) override;

  mozilla::ipc::IPCResult RecvReportLNAToConsole(
      const NetAddr& aPeerAddr, const nsACString& aMessageType,
      const nsACString& aPromptAction,
      const nsACString& aTopLevelSite) override;

  mozilla::ipc::IPCResult RecvSetPriority(const int16_t& aPriority) override;

  mozilla::ipc::IPCResult RecvOriginalCacheInputStreamAvailable(
      const Maybe<IPCStream>& aStream) override;

  virtual void ActorDestroy(ActorDestroyReason aWhy) override;

  virtual void DoNotifyListenerCleanup() override;

  virtual void DoAsyncAbort(nsresult aStatus) override;

  nsresult AsyncCall(
      void (HttpChannelChild::*funcPtr)(),
      nsRunnableMethod<HttpChannelChild>** retval = nullptr) override {
    // Normally, this method would just be implemented directly, but clang
    // miscompiles the corresponding non-virtual thunk on linux x86.
    // It however doesn't when going though a non-virtual method.
    // https://bugs.llvm.org/show_bug.cgi?id=38466
    return AsyncCallImpl(funcPtr, retval);
  };

  // Get event target for processing network events.
  already_AddRefed<nsISerialEventTarget> GetNeckoTarget() override;

  virtual mozilla::ipc::IPCResult RecvLogBlockedCORSRequest(
      const nsAString& aMessage, const nsACString& aCategory,
      const bool& aIsWarning) override;
  NS_IMETHOD LogBlockedCORSRequest(const nsAString& aMessage,
                                   const nsACString& aCategory,
                                   bool aIsWarning = false) override;

  virtual mozilla::ipc::IPCResult RecvLogMimeTypeMismatch(
      const nsACString& aMessageName, const bool& aWarning,
      const nsAString& aURL, const nsAString& aContentType) override;
  NS_IMETHOD LogMimeTypeMismatch(const nsACString& aMessageName, bool aWarning,
                                 const nsAString& aURL,
                                 const nsAString& aContentType) override;

  virtual void ExplicitSetUploadStreamLength(
      uint64_t aContentLength, bool aSetContentLengthHeader) override;

 private:
  // We want to handle failure result of AsyncOpen, hence AsyncOpen calls the
  // Internal method
  nsresult AsyncOpenInternal(nsIStreamListener* aListener);

  nsresult AsyncCallImpl(void (HttpChannelChild::*funcPtr)(),
                         nsRunnableMethod<HttpChannelChild>** retval);

  // Sets the event target for future IPC messages. Messages will either be
  // directed to the TabGroup or DocGroup, depending on the LoadInfo associated
  // with the channel. Should be called when a new channel is being set up,
  // before the constructor message is sent to the parent.
  void SetEventTarget();

  // Get event target for ODA.
  already_AddRefed<nsIEventTarget> GetODATarget();

  [[nodiscard]] nsresult ContinueAsyncOpen();
  void ProcessOnStartRequest(const nsHttpResponseHead& aResponseHead,
                             const bool& aUseResponseHead,
                             const nsHttpHeaderArray& aRequestHeaders,
                             const HttpChannelOnStartRequestArgs& aArgs,
                             const HttpChannelAltDataStream& aAltData,
                             const TimeStamp& aOnStartRequestStartTime);

  // Callbacks while receiving OnTransportAndData/OnStopRequest/OnProgress/
  // OnStatus/FlushedForDiversion/DivertMessages on background IPC channel.
  void ProcessOnTransportAndData(const nsresult& aChannelStatus,
                                 const nsresult& aTransportStatus,
                                 const uint64_t& aOffset,
                                 const uint32_t& aCount,
                                 const nsACString& aData,
                                 const TimeStamp& aOnDataAvailableStartTime);
  void ProcessOnStopRequest(const nsresult& aChannelStatus,
                            const ResourceTimingStructArgs& aTiming,
                            const nsHttpHeaderArray& aResponseTrailers,
                            nsTArray<ConsoleReportCollected>&& aConsoleReports,
                            bool aFromSocketProcess,
                            const TimeStamp& aOnStopRequestStartTime);
  void ProcessOnConsoleReport(
      nsTArray<ConsoleReportCollected>&& aConsoleReports);

  void ProcessNotifyClassificationFlags(uint32_t aClassificationFlags,
                                        bool aIsThirdParty);
  void ProcessSetClassifierMatchedInfo(const nsACString& aList,
                                       const nsACString& aProvider,
                                       const nsACString& aFullHash);
  void ProcessSetClassifierMatchedTrackingInfo(const nsACString& aLists,
                                               const nsACString& aFullHashes);
  void ProcessOnAfterLastPart(const nsresult& aStatus);
  void ProcessOnProgress(const int64_t& aProgress, const int64_t& aProgressMax);

  void ProcessOnStatus(const nsresult& aStatus);

  void ProcessAttachStreamFilter(
      Endpoint<extensions::PStreamFilterParent>&& aEndpoint);
  void ProcessDetachStreamFilters();

  // Return true if we need to tell the parent the size of unreported received
  // data
  bool NeedToReportBytesRead();
  int32_t mUnreportBytesRead = 0;

  void DoOnConsoleReport(nsTArray<ConsoleReportCollected>&& aConsoleReports);
  void DoOnStartRequest(nsIRequest* aRequest);
  void DoOnStatus(nsIRequest* aRequest, nsresult status);
  void DoOnProgress(nsIRequest* aRequest, int64_t progress,
                    int64_t progressMax);
  void DoOnDataAvailable(nsIRequest* aRequest, nsIInputStream* aStream,
                         uint64_t offset, uint32_t count);
  void DoPreOnStopRequest(nsresult aStatus);
  void DoOnStopRequest(nsIRequest* aRequest, nsresult aChannelStatus);
  void ContinueOnStopRequest();

  // Try send DeletingChannel message to parent side. Dispatch an async task to
  // main thread if invoking on non-main thread.
  void TrySendDeletingChannel();

  // Try invoke Cancel if on main thread, or prepend a CancelEvent in mEventQ to
  // ensure Cacnel is processed before any other channel events.
  void CancelOnMainThread(nsresult aRv, const nsACString& aReason);

  nsresult MaybeLogCOEPError(nsresult aStatus);

  void RetargetDeliveryToImpl(nsISerialEventTarget* aNewTarget,
                              MutexAutoLock& aLockRef);

 private:
  // this section is for main-thread-only object
  // all the references need to be proxy released on main thread.
  nsCOMPtr<nsIChildChannel> mRedirectChannelChild;

  // Proxy release all members above on main thread.
  void ReleaseMainThreadOnlyReferences();

 private:
  nsCString mProtocolVersion;

  RequestHeaderTuples mClientSetRequestHeaders;
  RefPtr<ChannelEventQueue> mEventQ;

  nsCOMPtr<nsIInputStreamReceiver> mOriginalInputStreamReceiver;
  nsCOMPtr<nsIInputStream> mAltDataInputStream;

  // Used to ensure atomicity of mBgChild and mBgInitFailCallback
  Mutex mBgChildMutex{"HttpChannelChild::BgChildMutex"};

  // Associated HTTP background channel
  RefPtr<HttpBackgroundChannelChild> mBgChild MOZ_GUARDED_BY(mBgChildMutex);

  // Error handling procedure if failed to establish PBackground IPC
  nsCOMPtr<nsIRunnable> mBgInitFailCallback MOZ_GUARDED_BY(mBgChildMutex);

  // Remove the association with background channel after OnStopRequest
  // or AsyncAbort.
  void CleanupBackgroundChannel();

  // Target thread for delivering ODA.
  nsCOMPtr<nsISerialEventTarget> mODATarget MOZ_GUARDED_BY(mEventTargetMutex);
  Atomic<bool, mozilla::Relaxed> mGotDataAvailable{false};
  // Used to ensure atomicity of mNeckoTarget / mODATarget;
  Mutex mEventTargetMutex{"HttpChannelChild::EventTargetMutex"};

  TimeStamp mLastStatusReported;

  uint64_t mCacheEntryId{0};
  nsICacheInfoChannel::CacheDisposition mCacheDisposition{
      nsICacheInfoChannel::kCacheUnknown};

  uint32_t mCacheKey{0};
  int32_t mCacheFetchCount{0};
  uint32_t mCacheExpirationTime{
      static_cast<uint32_t>(nsICacheEntry::NO_EXPIRATION_TIME)};

  // If we're handling a multi-part response, then this is set to the current
  // part ID during OnStartRequest.
  Maybe<uint32_t> mMultiPartID;

  // To ensure only one SendDeletingChannel is triggered.
  Atomic<bool> mDeletingChannelSent{false};

  Atomic<bool, SequentiallyConsistent> mIsFromCache{false};
  Atomic<bool, SequentiallyConsistent> mIsRacing{false};
  // Set if we get the result and cache |mNeedToReportBytesRead|
  Atomic<bool, SequentiallyConsistent> mCacheNeedToReportBytesReadInitialized{
      false};
  // True if we need to tell the parent the size of unreported received data
  Atomic<bool, SequentiallyConsistent> mNeedToReportBytesRead{true};
  Atomic<uint32_t, mozilla::Relaxed> mOnProgressEventSent{false};
  // Attached StreamFilterParents
  // Using raw pointer here since StreamFilterParent owns the channel.
  // Should be only accessed on the main thread.
  using StreamFilters = nsTArray<extensions::StreamFilterParent*>;
  StreamFilters mStreamFilters;

#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
  bool mDoDiagnosticAssertWhenOnStopNotCalledOnDestroy = false;
  bool mAsyncOpenSucceeded = false;
  bool mSuccesfullyRedirected = false;
  bool mRemoteChannelExistedAtCancel = false;
  bool mEverHadBgChildAtAsyncOpen = false;
  bool mEverHadBgChildAtConnectParent = false;
  bool mCreateBackgroundChannelFailed = false;
  bool mBgInitFailCallbackTriggered = false;
  bool mCanSendAtCancel = false;
  // State of the HttpBackgroundChannelChild's event queue during destruction.
  enum BckChildQueueStatus {
    // BckChild never told us
    BCKCHILD_UNKNOWN,
    // BckChild was empty at the time of destruction
    BCKCHILD_EMPTY,
    // BckChild was keeping events in the queue at the destruction time!
    BCKCHILD_NON_EMPTY
  };
  Atomic<BckChildQueueStatus> mBackgroundChildQueueFinalState{BCKCHILD_UNKNOWN};
  Maybe<ActorDestroyReason> mActorDestroyReason;
#endif

  uint8_t mCacheEntryAvailable : 1;
  uint8_t mAltDataCacheEntryAvailable : 1;

  // If ResumeAt is called before AsyncOpen, we need to send extra data upstream
  uint8_t mSendResumeAt : 1;

  uint8_t mKeptAlive : 1;  // IPC kept open, but only for security info

  // Set when ActorDestroy(ActorDestroyReason::Deletion) is called
  // The channel must ignore any following OnStart/Stop/DataAvailable messages
  uint8_t mIPCActorDeleted : 1;

  // Set if SendSuspend is called. Determines if SendResume is needed when
  // diverting callbacks to parent.
  uint8_t mSuspendSent : 1;

  // True if this channel is a multi-part channel, and the first part
  // is currently being processed.
  uint8_t mIsFirstPartOfMultiPart : 1;

  // True if this channel is a multi-part channel, and the last part
  // is currently being processed.
  uint8_t mIsLastPartOfMultiPart : 1;

  // True if this channel is suspended by ConnectParent and not resumed by
  // CompleteRedirectSetup/RecvDeleteSelf.
  uint8_t mSuspendForWaitCompleteRedirectSetup : 1;

  // True if RecvOnStartRequestSent was received.
  uint8_t mRecvOnStartRequestSentCalled : 1;

  // True if this channel is for a document and suspended by waiting for
  // permission or cookie. That is, RecvOnStartRequestSent is received.
  uint8_t mSuspendedByWaitingForPermissionCookie : 1;

  // HttpChannelChild::Release has some special logic that makes sure
  // OnStart/OnStop are always called when releasing the channel.
  // But we have to make sure we only do this once - otherwise we could
  // get stuck in a loop.
  uint8_t mAlreadyReleased : 1;

  // JavaScript stack captured during AsyncOpen for LNA console logging
  mozilla::UniquePtr<char[]> mCallStack;

  void CleanupRedirectingChannel(nsresult rv);

  // Calls OnStartRequest and/or OnStopRequest on our listener in case we didn't
  // do that so far.  If we already did, it will just release references to
  // cleanup.
  void NotifyOrReleaseListeners(nsresult rv);

  // true after successful AsyncOpen until OnStopRequest completes.
  bool RemoteChannelExists() { return CanSend() && !mKeptAlive; }

  void OnStartRequest(const nsHttpResponseHead& aResponseHead,
                      const bool& aUseResponseHead,
                      const nsHttpHeaderArray& aRequestHeaders,
                      const HttpChannelOnStartRequestArgs& aArgs);
  void OnTransportAndData(const nsresult& channelStatus, const nsresult& status,
                          const uint64_t& offset, const uint32_t& count,
                          const nsACString& data);
  void OnStopRequest(const nsresult& channelStatus,
                     const ResourceTimingStructArgs& timing,
                     const nsHttpHeaderArray& aResponseTrailers);
  void FailedAsyncOpen(const nsresult& status);
  void HandleAsyncAbort();
  void Redirect1Begin(const uint32_t& registrarId, nsIURI* newOriginalURI,
                      const uint32_t& newLoadFlags,
                      const uint32_t& redirectFlags,
                      const ParentLoadInfoForwarderArgs& loadInfoForwarder,
                      const nsHttpResponseHead& responseHead,
                      nsITransportSecurityInfo* securityInfo,
                      const uint64_t& channelId,
                      const ResourceTimingStructArgs& timing);
  void Redirect3Complete();
  void DeleteSelf();
  // aUseEventQueue should only be false when called from
  // HttpChannelChild::Release to make sure OnStopRequest is called syncly.
  void DoNotifyListener(bool aUseEventQueue = true);
  void ContinueDoNotifyListener();
  void OnAfterLastPart(const nsresult& aStatus);
  void MaybeConnectToSocketProcess();
  void OnDetachStreamFilters();
  void SendOnDataFinished(const nsresult& aChannelStatus);

  // Create a a new channel to be used in a redirection, based on the provided
  // response headers.
  [[nodiscard]] nsresult SetupRedirect(nsIURI* uri,
                                       const nsHttpResponseHead* responseHead,
                                       const uint32_t& redirectFlags,
                                       nsIChannel** outChannel);

  // Collect telemetry for mixed content.
  void CollectMixedContentTelemetry();

  friend class HttpAsyncAborter<HttpChannelChild>;
  friend class InterceptStreamListener;
  friend class InterceptedChannelContent;
  friend class HttpBackgroundChannelChild;
  friend class NeckoTargetChannelFunctionEvent;
};

//-----------------------------------------------------------------------------
// inline functions
//-----------------------------------------------------------------------------

inline bool HttpChannelChild::IsSuspended() { return mSuspendCount != 0; }

}  // namespace mozilla::net

#endif  // mozilla_net_HttpChannelChild_h
