// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef NET_COOKIES_SITE_FOR_COOKIES_H_
#define NET_COOKIES_SITE_FOR_COOKIES_H_

#include <string>

#include "base/gtest_prod_util.h"
#include "base/strings/string_piece.h"
#include "net/base/net_export.h"
#include "net/base/schemeful_site.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace net {

// Represents which origins are to be considered same-site for a given
// context (e.g. frame). There may be none.
//
// The currently implemented policy ("schemeful") is that
// two valid URLs would be considered same-site if either:
// 1) They both have compatible schemes along with non-empty and equal
//    registrable domains or hostnames/IPs. Two schemes are compatible if they
//    are either equal, or are both in {http, ws}, or are both in {https, wss}.
//    E.x. "https://example.com" and "wss://example.com"
// 2) They both have empty hostnames and exactly equal schemes. E.x. "file://"
// and "file://"
//
// With the SchemefulSameSite feature disabled the policy ("schemeless") is that
// two valid URLs would be considered the same site if either: 1) They both have
// non-empty and equal registrable domains or hostnames/IPs. E.x. "example.com"
// and "example.com" 2) They both have empty hostnames and equal schemes. E.x.
// "file://" and "file://"
//
//
// Invalid URLs are never same-site to anything.
class NET_EXPORT SiteForCookies {
 public:
  // Matches nothing.
  SiteForCookies();

  SiteForCookies(const SiteForCookies& other);
  SiteForCookies(SiteForCookies&& other);

  explicit SiteForCookies(const SchemefulSite& schemeful_site);

  ~SiteForCookies();

  SiteForCookies& operator=(const SiteForCookies& other);
  SiteForCookies& operator=(SiteForCookies&& other);

  // Tries to construct an instance from (potentially untrusted) values of
  // site() and schemefully_same() that got received over an RPC.
  //
  // Returns whether successful or not. Doesn't touch `*out` if false is
  // returned.  This returning `true` does not mean that whoever sent the values
  // did not lie, merely that they are well-formed.
  static bool FromWire(const SchemefulSite& site,
                       bool schemefully_same,
                       SiteForCookies* out);

  // If the origin is opaque, returns SiteForCookies that matches nothing.
  //
  // If it's not opaque, returns one that matches URLs which are considered to
  // be same-party as URLs from `origin`.
  static SiteForCookies FromOrigin(const url::Origin& origin);

  // Equivalent to FromOrigin(url::Origin::Create(url)).
  static SiteForCookies FromUrl(const GURL& url);

  // Returns a string with the values of the member variables.
  // `schemefully_same` being false does not change the output.
  std::string ToDebugString() const;

  // Returns true if `url` should be considered first-party to the context
  // `this` represents.
  bool IsFirstParty(const GURL& url) const;

  // Don't use this function unless you know what you're doing, if you're unsure
  // you probably want IsFirstParty().
  //
  // If `compute_schemefully` is true this function will return true if `url`
  // should be considered first-party to the context `this` represents when the
  // compatibility of the schemes are taken into account.
  //
  // If `compute_schemefully` is false this function will return true if `url`
  // should be considered first-party to the context `this` represents when the
  // compatibility of the scheme are not taken into account. Note that schemes
  // are still compared for exact equality if neither `this` nor `url` have a
  // registered domain.
  bool IsFirstPartyWithSchemefulMode(const GURL& url,
                                     bool compute_schemefully) const;

  // Returns true if `other.IsFirstParty()` is true for exactly the same URLs
  // as `this->IsFirstParty` (potentially none). Two SFCs are also considered
  // equivalent if neither ever returns true for `IsFirstParty()`. I.e., both
  // are null.
  bool IsEquivalent(const SiteForCookies& other) const;

  // Compares a "candidate" SFC, `this`, with an origin (represented as a
  // SchemefulSite) from the frame tree and revises `this` to be null as
  // required.
  //
  // This method is used when a sub-frame needs to have its SiteForCookies
  // computed, which is dependent on all of its ancestors' origins (`other`). If
  // its or any of its ancestors' frame's origins are not first-party with the
  // top-level origin then this frame's SFC should be null. Otherwise, if it and
  // all its ancestors are first-party with the top-level frame, the frame's
  // SFC is the same as the top-level.
  //
  // This computation gets a bit tricky when considering "Schemeful Same-Site"
  // as we don't know ahead of time if `this` is going to be used for a
  // schemeful or schemeless computation later down the line, so we need to
  // be careful to not completely nullify the entire SFC just because an `other`
  // isn't schemefully first-party. If the computation is schemelessly
  // first-party but *not schemefully* first-party then only the
  // `schemefully_same_` flag will be cleared (which informs other functions to
  // treat `this` as null for schemeful purposes). If the computation is not
  // schemelessly first-party then `this` will have an opaque `site_` which
  // completely nullifies it.
  //
  // This function should be called on all ancestors up to the top-level frame
  // unless it returns false in which case `this` has been completely nullified
  // and the caller may stop early.
  //
  // (This function's return value is the same as IsEquivalent() when "Schemeful
  // Same-Site" is disabled.)
  bool CompareWithFrameTreeSiteAndRevise(const SchemefulSite& other);

  // Converts an Origin into a SchemefulSite and then calls
  //`CompareWithFrameTreeSiteAndRevise(SchemefulSite)`
  //
  // If possible, prefer `CompareWithFrameTreeSiteAndRevise(SchemefulSite)`
  // for performance reasons.
  bool CompareWithFrameTreeOriginAndRevise(const url::Origin& other);

  // Returns a URL that's first party to this SiteForCookies (an empty URL if
  // none) --- that is, it has the property that
  // site_for_cookies.IsEquivalent(
  //     SiteForCookies::FromUrl(site_for_cookies.RepresentativeUrl()));
  //
  // The convention used here (empty for nothing) is equivalent to that
  // used before SiteForCookies existed as a type; this method is mostly
  // meant to help incrementally migrate towards the type. New code probably
  // should not need this.
  GURL RepresentativeUrl() const;

  const SchemefulSite& site() const { return site_; }

  // Guaranteed to be lowercase.
  const std::string& scheme() const { return site_.site_as_origin_.scheme(); }

  const std::string& registrable_domain() const {
    return site_.site_as_origin_.host();
  }

  // Used for serialization/deserialization. This value is irrelevant if
  // site().opaque() is true.
  bool schemefully_same() const { return schemefully_same_; }

  void SetSchemefullySameForTesting(bool schemefully_same) {
    schemefully_same_ = schemefully_same;
  }

  // Returns true if this SiteForCookies matches nothing.
  // If the SchemefulSameSite feature is enabled then !schemefully_same_ causes
  // this function to return true.
  bool IsNull() const;

  // Allows SiteForCookies to be used as a key in STL (for example, a std::set
  // or std::map).
  NET_EXPORT friend bool operator<(const SiteForCookies& lhs,
                                   const SiteForCookies& rhs);

 private:
  FRIEND_TEST_ALL_PREFIXES(SiteForCookiesTest, SameScheme);
  FRIEND_TEST_ALL_PREFIXES(SiteForCookiesTest, SameSchemeOpaque);

  bool IsSchemefullyFirstParty(const GURL& url) const;

  bool IsSchemelesslyFirstParty(const GURL& url) const;

  // Sets the schemefully_same_ flag to false if other`'s scheme is
  // cross-scheme to `this`. Schemes are considered cross-scheme if they're not
  // compatible. Two schemes are compatible if they are either equal, or are
  // both in {http, ws}, or are both in {https, wss}.
  void MarkIfCrossScheme(const SchemefulSite& other);

  // Represents the scheme and registrable domain of the site. The scheme should
  // not be a WebSocket scheme; instead, ws is normalized to http, and
  // wss is normalized to https upon construction.
  SchemefulSite site_;

  // Used to indicate if the SiteForCookies would be the same if computed
  // schemefully. A schemeful computation means to take the scheme as well as
  // the registrable_domain into account when determining same-siteness.
  // See the class-level comment for more details on schemeless vs schemeful.
  //
  // True means to treat `this` as-is while false means that `this` should be
  // treated as if it matches nothing i.e. IsNull() returns true.
  //
  // This value is important in the case where the SiteForCookies is being used
  // to assess the first-partyness of a sub-frame in a document.
  //
  // For a SiteForCookies with !site_.opaque() this value starts as true and
  // will only go false via MarkIfCrossScheme(), otherwise this value is
  // irrelevant (For tests this value can also be modified by
  // SetSchemefullySameForTesting()).
  bool schemefully_same_ = false;
};

}  // namespace net

#endif  // NET_COOKIES_SITE_FOR_COOKIES_H_
