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

#include "nsHttpHeaderArray.h"
#include "nsHttp.h"
#include "nsISupportsImpl.h"
#include "nsString.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/RecursiveMutex.h"

#ifdef Status
/* Xlib headers insist on this for some reason... Nuke it because
   it'll override our member name */
typedef Status __StatusTmp;
#  undef Status
typedef __StatusTmp Status;
#endif

class nsIHttpHeaderVisitor;

// This needs to be forward declared here so we can include only this header
// without also including PHttpChannelParams.h
namespace IPC {
template <typename>
struct ParamTraits;
}  // namespace IPC

namespace mozilla {
namespace net {

//-----------------------------------------------------------------------------
// nsHttpResponseHead represents the status line and headers from an HTTP
// response.
//-----------------------------------------------------------------------------

class nsHttpResponseHead {
 public:
  nsHttpResponseHead() = default;

  nsHttpResponseHead(const nsHttpResponseHead& aOther);
  nsHttpResponseHead(nsHttpResponseHead&& aOther);
  nsHttpResponseHead& operator=(const nsHttpResponseHead& aOther);

  void Enter() const MOZ_CAPABILITY_ACQUIRE(mRecursiveMutex) {
    mRecursiveMutex.Lock();
  }
  void Exit() const MOZ_CAPABILITY_RELEASE(mRecursiveMutex) {
    mRecursiveMutex.Unlock();
  }
  void AssertMutexOwned() const { mRecursiveMutex.AssertCurrentThreadIn(); }

  HttpVersion Version() const;
  uint16_t Status() const;
  void StatusText(nsACString& aStatusText);
  int64_t ContentLength();
  void ContentType(nsACString& aContentType) const;
  void ContentCharset(nsACString& aContentCharset);
  bool Public();
  bool Private();
  bool NoStore();
  bool NoCache();
  bool Immutable();
  /**
   * Full length of the entity. For byte-range requests, this may be larger
   * than ContentLength(), which will only represent the requested part of the
   * entity.
   */
  int64_t TotalEntitySize();

  [[nodiscard]] nsresult SetHeader(const nsACString& h, const nsACString& v,
                                   bool m = false);
  [[nodiscard]] nsresult SetHeaderOverride(const nsHttpAtom& h,
                                           const nsACString& v);
  [[nodiscard]] nsresult SetHeader(const nsHttpAtom& h, const nsACString& v,
                                   bool m = false);
  [[nodiscard]] nsresult GetHeader(const nsHttpAtom& h, nsACString& v) const;
  void ClearHeader(const nsHttpAtom& h);
  void ClearHeaders();
  bool HasHeaderValue(const nsHttpAtom& h, const char* v);
  bool HasHeader(const nsHttpAtom& h) const;

  void SetContentType(const nsACString& s);
  void SetContentCharset(const nsACString& s);
  void SetContentLength(int64_t);

  // write out the response status line and headers as a single text block,
  // optionally pruning out transient headers (ie. headers that only make
  // sense the first time the response is handled).
  // Both functions append to the string supplied string.
  void Flatten(nsACString&, bool pruneTransients);
  void FlattenNetworkOriginalHeaders(nsACString& buf);

  // The next 2 functions parse flattened response head and original net
  // headers. They are used when we are reading an entry from the cache.
  //
  // To keep proper order of the original headers we MUST call
  // ParseCachedOriginalHeaders FIRST and then ParseCachedHead.
  //
  // block must be null terminated.
  [[nodiscard]] nsresult ParseCachedHead(const char* block);
  [[nodiscard]] nsresult ParseCachedOriginalHeaders(char* block);

  // parse the status line.
  nsresult ParseStatusLine(const nsACString& line);

  // parse a header line.
  [[nodiscard]] nsresult ParseHeaderLine(const nsACString& line);

  // cache validation support methods
  [[nodiscard]] nsresult ComputeFreshnessLifetime(uint32_t*);
  [[nodiscard]] nsresult ComputeCurrentAge(uint32_t now, uint32_t requestTime,
                                           uint32_t* result);
  bool MustValidate();
  bool MustValidateIfExpired();

  // return true if the response contains a valid Cache-control:
  // stale-while-revalidate and |now| is less than or equal |expiration +
  // stale-while-revalidate|.  Otherwise false.
  bool StaleWhileRevalidate(uint32_t now, uint32_t expiration);

  // returns true if the server appears to support byte range requests.
  bool IsResumable();

  // returns true if the Expires header has a value in the past relative to the
  // value of the Date header.
  bool ExpiresInPast();

  // update headers...
  void UpdateHeaders(nsHttpResponseHead* aOther);

  // reset the response head to it's initial state
  void Reset();

  [[nodiscard]] nsresult GetLastModifiedValue(uint32_t* result);

  bool operator==(const nsHttpResponseHead& aOther) const;

  // Using this function it is possible to itereate through all headers
  // automatically under one lock.
  [[nodiscard]] nsresult VisitHeaders(nsIHttpHeaderVisitor* visitor,
                                      nsHttpHeaderArray::VisitorFilter filter);
  [[nodiscard]] nsresult GetOriginalHeader(const nsHttpAtom& aHeader,
                                           nsIHttpHeaderVisitor* aVisitor);

  bool HasContentType() const;
  bool HasContentCharset();
  bool GetContentTypeOptionsHeader(nsACString& aOutput);

  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
  }

  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
    RecursiveMutexAutoLock monitor(mRecursiveMutex);
    return mStatusText.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
           mContentType.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
           mContentCharset.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
  }

 private:
  [[nodiscard]] nsresult SetHeader_locked(const nsHttpAtom& atom,
                                          const nsACString& h,
                                          const nsACString& v, bool m = false)
      MOZ_REQUIRES(mRecursiveMutex);
  void AssignDefaultStatusText() MOZ_REQUIRES(mRecursiveMutex);
  void ParseVersion(const char*) MOZ_REQUIRES(mRecursiveMutex);
  void ParseCacheControl(const char*) MOZ_REQUIRES(mRecursiveMutex);
  void ParsePragma(const char*) MOZ_REQUIRES(mRecursiveMutex);
  // Parses a content-length header-value as described in
  // https://fetch.spec.whatwg.org/#content-length-header
  nsresult ParseResponseContentLength(const nsACString& aHeaderStr)
      MOZ_REQUIRES(mRecursiveMutex);
  void ParseContentTypeValue(const nsHttpAtom& aAtom,
                             const nsACString& aContentTypeValue)
      MOZ_REQUIRES(mRecursiveMutex);

  nsresult ParseStatusLine_locked(const nsACString& line)
      MOZ_REQUIRES(mRecursiveMutex);
  [[nodiscard]] nsresult ParseHeaderLine_locked(const nsACString& line,
                                                bool originalFromNetHeaders)
      MOZ_REQUIRES(mRecursiveMutex);

  // these return failure if the header does not exist.
  [[nodiscard]] nsresult ParseDateHeader(const nsHttpAtom& header,
                                         uint32_t* result) const
      MOZ_REQUIRES(mRecursiveMutex);
  [[nodiscard]] nsresult GetAgeValue(uint32_t* result);
  [[nodiscard]] nsresult GetMaxAgeValue(uint32_t* result);
  [[nodiscard]] nsresult GetStaleWhileRevalidateValue(uint32_t* result);
  [[nodiscard]] nsresult GetDateValue(uint32_t* result);
  [[nodiscard]] nsresult GetExpiresValue(uint32_t* result);

  bool ExpiresInPast_locked() const MOZ_REQUIRES(mRecursiveMutex);
  [[nodiscard]] nsresult GetAgeValue_locked(uint32_t* result) const
      MOZ_REQUIRES(mRecursiveMutex);
  [[nodiscard]] nsresult GetExpiresValue_locked(uint32_t* result) const
      MOZ_REQUIRES(mRecursiveMutex);
  [[nodiscard]] nsresult GetMaxAgeValue_locked(uint32_t* result) const
      MOZ_REQUIRES(mRecursiveMutex);
  [[nodiscard]] nsresult GetStaleWhileRevalidateValue_locked(
      uint32_t* result) const MOZ_REQUIRES(mRecursiveMutex);

  [[nodiscard]] nsresult GetDateValue_locked(uint32_t* result) const
      MOZ_REQUIRES(mRecursiveMutex) {
    return ParseDateHeader(nsHttp::Date, result);
  }

  [[nodiscard]] nsresult GetLastModifiedValue_locked(uint32_t* result) const
      MOZ_REQUIRES(mRecursiveMutex) {
    return ParseDateHeader(nsHttp::Last_Modified, result);
  }

  bool NoCache_locked() const MOZ_REQUIRES(mRecursiveMutex) {
    MOZ_ASSERT_IF(mCacheControlNoCache, mHasCacheControl);
    // Normally we would ignore Pragma: no-cache if Cache-Control is set.
    // But since all other browsers treat the existence of Pragma: no-cache
    // as a signal to not-cache even when it conflicts with Cache-Control
    // it is safer just to do the same. Previous behaviour where Pragma
    // was ignored when Cache-Control was present resulted in several
    // web-compat issues (Bug 1937766)
    // However, the presence of cacheControl immutable indicates that
    // pragma: no-cache is incorrectly added to the response.
    return (mPragmaNoCache && !mCacheControlImmutable) || mCacheControlNoCache;
  }

 private:
  // All members must be copy-constructable and assignable
  nsHttpHeaderArray mHeaders MOZ_GUARDED_BY(mRecursiveMutex);
  HttpVersion mVersion MOZ_GUARDED_BY(mRecursiveMutex){HttpVersion::v1_1};
  uint16_t mStatus MOZ_GUARDED_BY(mRecursiveMutex){200};
  nsCString mStatusText MOZ_GUARDED_BY(mRecursiveMutex);
  int64_t mContentLength MOZ_GUARDED_BY(mRecursiveMutex){-1};
  nsCString mContentTypeBuffer MOZ_GUARDED_BY(mRecursiveMutex);
  nsCString mContentType MOZ_GUARDED_BY(mRecursiveMutex);
  nsCString mContentCharset MOZ_GUARDED_BY(mRecursiveMutex);
  bool mHasCacheControl MOZ_GUARDED_BY(mRecursiveMutex){false};
  bool mCacheControlPublic MOZ_GUARDED_BY(mRecursiveMutex){false};
  bool mCacheControlPrivate MOZ_GUARDED_BY(mRecursiveMutex){false};
  bool mCacheControlNoStore MOZ_GUARDED_BY(mRecursiveMutex){false};
  bool mCacheControlNoCache MOZ_GUARDED_BY(mRecursiveMutex){false};
  bool mCacheControlImmutable MOZ_GUARDED_BY(mRecursiveMutex){false};
  bool mCacheControlStaleWhileRevalidateSet MOZ_GUARDED_BY(mRecursiveMutex){
      false};
  uint32_t mCacheControlStaleWhileRevalidate MOZ_GUARDED_BY(mRecursiveMutex){0};
  bool mCacheControlMaxAgeSet MOZ_GUARDED_BY(mRecursiveMutex){false};
  uint32_t mCacheControlMaxAge MOZ_GUARDED_BY(mRecursiveMutex){0};
  bool mPragmaNoCache MOZ_GUARDED_BY(mRecursiveMutex){false};

  // We are using RecursiveMutex instead of a Mutex because VisitHeader
  // function calls nsIHttpHeaderVisitor::VisitHeader while under lock.
  mutable RecursiveMutex mRecursiveMutex{"nsHttpResponseHead.mRecursiveMutex"};
  // During VisitHeader we sould not allow call to SetHeader.
  // Depth counter so nested visits cannot disarm the outer guard.
  uint32_t mInVisitHeaders MOZ_GUARDED_BY(mRecursiveMutex){0};

  friend struct IPC::ParamTraits<nsHttpResponseHead>;
};

// Refcounted, immutable wrapper around the HTTP CONNECT response head. It lets
// the connection, transaction and channel share a single head by pointer
// instead of deep-copying the header array on every request, which used to be
// a measurable per-request cost on proxied loads. See bug 2045419.
class ProxyConnectResponseHead final {
 public:
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ProxyConnectResponseHead)

  explicit ProxyConnectResponseHead(const nsHttpResponseHead& aHead)
      : mHead(aHead) {}
  explicit ProxyConnectResponseHead(nsHttpResponseHead&& aHead)
      : mHead(std::move(aHead)) {}

  const nsHttpResponseHead& Head() const { return mHead; }

 private:
  ~ProxyConnectResponseHead() = default;

  const nsHttpResponseHead mHead;
};

}  // namespace net
}  // namespace mozilla

#endif  // nsHttpResponseHead_h_
