/* -*- 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_InterceptedHttpChannel_h
#define mozilla_net_InterceptedHttpChannel_h

#include "HttpBaseChannel.h"
#include "nsINetworkInterceptController.h"
#include "nsIInputStream.h"
#include "nsICacheInfoChannel.h"
#include "nsIChannelWithDivertableParentListener.h"
#include "nsIThreadRetargetableRequest.h"

namespace mozilla {
namespace net {

// This class represents an http channel that is being intercepted by a
// ServiceWorker.  This means that when the channel is opened a FetchEvent
// will be fired on the ServiceWorker thread.  The channel will complete
// depending on what the worker does.  The options are:
//
// 1. If the ServiceWorker does not handle the FetchEvent or does not call
//    FetchEvent.respondWith(), then the channel needs to fall back to a
//    normal request.  When this happens ResetInterception() is called and
//    the channel will perform an internal redirect back to an nsHttpChannel.
//
// 2. If the ServiceWorker provides a Response to FetchEvent.respondWith()
//    then the status, headers, and body must be synthesized.  When
//    FinishSynthesizedResponse() is called the synthesized data must be
//    reported back to the channel listener.  This is handled in a few
//    different ways:
//      a. If a redirect was synthesized, then we perform the redirect to
//         a new nsHttpChannel.  This new channel might trigger yet another
//         interception.
//      b. If a same-origin or CORS Response was synthesized, then we simply
//         crate an nsInputStreamPump to process it and call back to the
//         listener.
//      c. If an opaque Response was synthesized, then we perform an internal
//         redirect to a new InterceptedHttpChannel using the cross-origin URL.
//         When this new channel is opened, it then creates a pump as in case
//         (b).  The extra redirect here is to make sure the various listeners
//         treat the result as unsafe cross-origin data.
//
// 3. If an error occurs, such as the ServiceWorker passing garbage to
//    FetchEvent.respondWith(), then CancelInterception() is called.  This is
//    handled the same as a normal nsIChannel::Cancel() call.  We abort the
//    channel and end up calling OnStopRequest() with an error code.
class InterceptedHttpChannel final : public HttpBaseChannel
                                   , public HttpAsyncAborter<InterceptedHttpChannel>
                                   , public nsIInterceptedChannel
                                   , public nsICacheInfoChannel
                                   , public nsIAsyncVerifyRedirectCallback
                                   , public nsIStreamListener
                                   , public nsIChannelWithDivertableParentListener
                                   , public nsIThreadRetargetableRequest
                                   , public nsIThreadRetargetableStreamListener
{
  NS_DECL_ISUPPORTS_INHERITED
  NS_DECL_NSIINTERCEPTEDCHANNEL
  NS_DECL_NSICACHEINFOCHANNEL
  NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
  NS_DECL_NSIREQUESTOBSERVER
  NS_DECL_NSISTREAMLISTENER
  NS_DECL_NSICHANNELWITHDIVERTABLEPARENTLISTENER
  NS_DECL_NSITHREADRETARGETABLEREQUEST
  NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER

private:
  friend class HttpAsyncAborter<InterceptedHttpChannel>;

  UniquePtr<nsHttpResponseHead> mSynthesizedResponseHead;
  nsCOMPtr<nsIChannel> mRedirectChannel;
  nsCOMPtr<nsIInputStream> mBodyReader;
  nsCOMPtr<nsISupports> mReleaseHandle;
  nsCOMPtr<nsIProgressEventSink> mProgressSink;
  nsCOMPtr<nsIInterceptedBodyCallback> mBodyCallback;
  nsCOMPtr<nsICacheInfoChannel> mSynthesizedCacheInfo;
  RefPtr<nsInputStreamPump> mPump;
  RefPtr<ADivertableParentChannel> mParentChannel;
  TimeStamp mFinishResponseStart;
  TimeStamp mFinishResponseEnd;
  Atomic<int64_t> mProgress;
  int64_t mProgressReported;
  int64_t mSynthesizedStreamLength;
  uint64_t mResumeStartPos;
  nsCString mResumeEntityId;
  nsString mStatusHost;
  enum {
    Invalid = 0,
    Synthesized,
    Reset
  } mSynthesizedOrReset;
  Atomic<bool> mCallingStatusAndProgress;
  bool mDiverting;

  InterceptedHttpChannel(PRTime aCreationTime,
                         const TimeStamp& aCreationTimestamp,
                         const TimeStamp& aAsyncOpenTimestamp);
  ~InterceptedHttpChannel() = default;

  virtual void
  ReleaseListeners() override;

  virtual MOZ_MUST_USE nsresult
  SetupReplacementChannel(nsIURI *aURI, nsIChannel *aChannel,
                          bool aPreserveMethod,
                          uint32_t aRedirectFlags) override;

  void
  AsyncOpenInternal();

  bool
  ShouldRedirect() const;

  nsresult
  FollowSyntheticRedirect();

  // If the response's URL is different from the request's then do a service
  // worker redirect. If Response.redirected is false we do an internal
  // redirect. Otherwise, if Response.redirect is true do a non-internal
  // redirect so end consumers detect the redirected state.
  nsresult
  RedirectForResponseURL(nsIURI* aResponseURI, bool aResponseRedirected);

  nsresult
  StartPump();

  nsresult
  OpenRedirectChannel();

  void
  MaybeCallStatusAndProgress();

  void
  MaybeCallBodyCallback();

public:
  static already_AddRefed<InterceptedHttpChannel>
  CreateForInterception(PRTime aCreationTime, const TimeStamp& aCreationTimestamp,
                        const TimeStamp& aAsyncOpenTimestamp);

  static already_AddRefed<InterceptedHttpChannel>
  CreateForSynthesis(const nsHttpResponseHead* aHead, nsIInputStream* aBody,
                     nsIInterceptedBodyCallback* aBodyCallback,
                     PRTime aCreationTime,
                     const TimeStamp& aCreationTimestamp,
                     const TimeStamp& aAsyncOpenTimestamp);

  NS_IMETHOD
  Cancel(nsresult aStatus) override;

  NS_IMETHOD
  Suspend(void) override;

  NS_IMETHOD
  Resume(void) override;

  NS_IMETHOD
  GetSecurityInfo(nsISupports * *aSecurityInfo) override;

  NS_IMETHOD
  AsyncOpen(nsIStreamListener *aListener, nsISupports *aContext) override;

  NS_IMETHOD
  AsyncOpen2(nsIStreamListener *aListener) override;

  NS_IMETHOD
  LogBlockedCORSRequest(const nsAString & aMessage) override;

  NS_IMETHOD
  SetupFallbackChannel(const char * aFallbackKey) override;

  NS_IMETHOD
  GetResponseSynthesized(bool *aResponseSynthesized) override;

  NS_IMETHOD
  SetPriority(int32_t aPriority) override;

  NS_IMETHOD
  SetClassFlags(uint32_t aClassFlags) override;

  NS_IMETHOD
  ClearClassFlags(uint32_t flags) override;

  NS_IMETHOD
  AddClassFlags(uint32_t flags) override;

  NS_IMETHOD
  ResumeAt(uint64_t startPos, const nsACString & entityID) override;

  void
  DoNotifyListenerCleanup() override;
};

} // namespace net
} // namespace mozilla

#endif // mozilla_net_InterceptedHttpChannel_h
