// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef NET_COOKIES_PARSED_COOKIE_H_
#define NET_COOKIES_PARSED_COOKIE_H_

#include <stddef.h>

#include <string>
#include <utility>
#include <vector>

#include "net/base/net_export.h"
#include "net/cookies/cookie_constants.h"

namespace net {

class CookieInclusionStatus;

class NET_EXPORT ParsedCookie {
 public:
  typedef std::pair<std::string, std::string> TokenValuePair;
  typedef std::vector<TokenValuePair> PairList;

  // The maximum length of a cookie string we will try to parse.
  // TODO(crbug.com/1243852) Remove this when kExtraCookieValidityChecks
  // gets removed (assuming the associated changes cause no issues).
  static const size_t kMaxCookieSize = 4096;

  // The maximum length allowed for a cookie string's name/value pair.
  static const size_t kMaxCookieNamePlusValueSize = 4096;

  // The maximum length allowed for each attribute value in a cookie string.
  static const size_t kMaxCookieAttributeValueSize = 1024;

  // Construct from a cookie string like "BLAH=1; path=/; domain=.google.com"
  // Format is according to RFC6265bis. Cookies with both name and value empty
  // will be considered invalid.
  // `status_out` is a nullable output param which will be populated with
  // informative exclusion reasons if the resulting ParsedCookie is invalid.
  // The CookieInclusionStatus will not be altered if the resulting ParsedCookie
  // is valid.
  explicit ParsedCookie(const std::string& cookie_line,
                        CookieInclusionStatus* status_out = nullptr);

  ParsedCookie(const ParsedCookie&) = delete;
  ParsedCookie& operator=(const ParsedCookie&) = delete;

  ~ParsedCookie();

  // You should not call any other methods except for SetName/SetValue on the
  // class if !IsValid.
  bool IsValid() const;

  const std::string& Name() const { return pairs_[0].first; }
  const std::string& Token() const { return Name(); }
  const std::string& Value() const { return pairs_[0].second; }

  bool HasPath() const { return path_index_ != 0; }
  const std::string& Path() const {
    DCHECK(HasPath());
    return pairs_[path_index_].second;
  }
  bool HasDomain() const { return domain_index_ != 0; }
  const std::string& Domain() const {
    DCHECK(HasDomain());
    return pairs_[domain_index_].second;
  }
  bool HasExpires() const { return expires_index_ != 0; }
  const std::string& Expires() const {
    DCHECK(HasExpires());
    return pairs_[expires_index_].second;
  }
  bool HasMaxAge() const { return maxage_index_ != 0; }
  const std::string& MaxAge() const {
    DCHECK(HasMaxAge());
    return pairs_[maxage_index_].second;
  }
  bool IsSecure() const { return secure_index_ != 0; }
  bool IsHttpOnly() const { return httponly_index_ != 0; }
  // Also spits out an enum value representing the string given as the SameSite
  // attribute value, if |samesite_string| is non-null.
  CookieSameSite SameSite(
      CookieSameSiteString* samesite_string = nullptr) const;
  CookiePriority Priority() const;
  bool IsSameParty() const { return same_party_index_ != 0; }
  bool IsPartitioned() const { return partitioned_index_ != 0; }
  bool HasTruncatedNameOrValue() const { return truncated_name_or_value_; }
  TruncatingCharacterInCookieStringType
  GetTruncatingCharacterInCookieStringType() const {
    return truncating_char_in_cookie_string_type_;
  }
  // Returns the number of attributes, for example, returning 2 for:
  //   "BLAH=hah; path=/; domain=.google.com"
  size_t NumberOfAttributes() const { return pairs_.size() - 1; }

  // These functions set the respective properties of the cookie. If the
  // parameters are empty, the respective properties are cleared.
  // The functions return false in case an error occurred.
  // The cookie needs to be assigned a name/value before setting the other
  // attributes.
  //
  // These functions should only be used if you need to modify a response's
  // Set-Cookie string. The resulting ParsedCookie and its Set-Cookie string
  // should still go through the regular cookie parsing process before entering
  // the cookie jar.
  bool SetName(const std::string& name);
  bool SetValue(const std::string& value);
  bool SetPath(const std::string& path);
  bool SetDomain(const std::string& domain);
  bool SetExpires(const std::string& expires);
  bool SetMaxAge(const std::string& maxage);
  bool SetIsSecure(bool is_secure);
  bool SetIsHttpOnly(bool is_http_only);
  bool SetSameSite(const std::string& same_site);
  bool SetPriority(const std::string& priority);
  bool SetIsSameParty(bool is_same_party);
  bool SetIsPartitioned(bool is_partitioned);

  // Returns the cookie description as it appears in a HTML response header.
  std::string ToCookieLine() const;

  // Returns an iterator pointing to the first terminator character found in
  // the given string.
  static std::string::const_iterator FindFirstTerminator(const std::string& s);

  // Given iterators pointing to the beginning and end of a string segment,
  // returns as output arguments token_start and token_end to the start and end
  // positions of a cookie attribute token name parsed from the segment, and
  // updates the segment iterator to point to the next segment to be parsed.
  // If no token is found, the function returns false and the segment iterator
  // is set to end.
  static bool ParseToken(std::string::const_iterator* it,
                         const std::string::const_iterator& end,
                         std::string::const_iterator* token_start,
                         std::string::const_iterator* token_end);

  // Given iterators pointing to the beginning and end of a string segment,
  // returns as output arguments value_start and value_end to the start and end
  // positions of a cookie attribute value parsed from the segment, and updates
  // the segment iterator to point to the next segment to be parsed.
  static void ParseValue(std::string::const_iterator* it,
                         const std::string::const_iterator& end,
                         std::string::const_iterator* value_start,
                         std::string::const_iterator* value_end);

  // Same as the above functions, except the input is assumed to contain the
  // desired token/value and nothing else.
  static std::string ParseTokenString(const std::string& token);
  static std::string ParseValueString(const std::string& value);

  // Returns |true| if the parsed version of |value| matches |value|.
  static bool ValueMatchesParsedValue(const std::string& value);

  // Is the string valid as the name of the cookie or as an attribute name?
  static bool IsValidCookieName(const std::string& name);

  // Is the string valid as the value of the cookie?
  static bool IsValidCookieValue(const std::string& value);

  // Is the string free of any characters not allowed in attribute values?
  static bool CookieAttributeValueHasValidCharSet(const std::string& value);

  // Is the string less than the size limits set for attribute values?
  static bool CookieAttributeValueHasValidSize(const std::string& value);

  // Is the string valid as a cookie attribute value? (only checks the character
  // set - no length checks performed)
  static bool IsValidCookieAttributeValueLegacy(const std::string& value);

  // Returns `true` if the name and value combination are valid. Calls
  // IsValidCookieName() and IsValidCookieValue() on `name` and `value`
  // respectively, in addition to checking that the sum of the two doesn't
  // exceed size limits specified in RFC6265bis.
  static bool IsValidCookieNameValuePair(
      const std::string& name,
      const std::string& value,
      CookieInclusionStatus* status_out = nullptr);

 private:
  void ParseTokenValuePairs(const std::string& cookie_line,
                            CookieInclusionStatus& status_out);
  void SetupAttributes();

  // Sets a key/value pair for a cookie. |index| has to point to one of the
  // |*_index_| fields in ParsedCookie and is updated to the position where
  // the key/value pair is set in |pairs_|. Accordingly, |key| has to correspond
  // to the token matching |index|. If |value| contains invalid characters, the
  // cookie parameter is not changed and the function returns false.
  // If |value| is empty/false the key/value pair is removed.
  bool SetString(size_t* index,
                 const std::string& key,
                 const std::string& value);
  bool SetBool(size_t* index, const std::string& key, bool value);

  // Helper function for SetString and SetBool handling the case that the
  // key/value pair shall not be removed.
  bool SetAttributePair(size_t* index,
                        const std::string& key,
                        const std::string& value);

  // Removes the key/value pair from a cookie that is identified by |index|.
  // |index| refers to a position in |pairs_|.
  void ClearAttributePair(size_t index);

  // Records metrics on cookie name+value and attribute value lengths.
  // This is being recorded to evaluate whether to change length limits for
  // cookies, such that limits are applied to name+value, and individual
  // attribute lengths, rather than to the whole set-cookie line.
  void RecordCookieAttributeValueLengthHistograms() const;

  PairList pairs_;
  // These will default to 0, but that should never be valid since the
  // 0th index is the user supplied cookie name/value, not an attribute.
  size_t path_index_ = 0;
  size_t domain_index_ = 0;
  size_t expires_index_ = 0;
  size_t maxage_index_ = 0;
  size_t secure_index_ = 0;
  size_t httponly_index_ = 0;
  size_t same_site_index_ = 0;
  size_t priority_index_ = 0;
  size_t same_party_index_ = 0;
  size_t partitioned_index_ = 0;
  // For metrics on cookie name/value truncation. See usage at the bottom of
  // `ParseTokenValuePairs()` for more details.
  bool truncated_name_or_value_ = false;
  TruncatingCharacterInCookieStringType truncating_char_in_cookie_string_type_ =
      TruncatingCharacterInCookieStringType::kTruncatingCharNone;
};

}  // namespace net

#endif  // NET_COOKIES_PARSED_COOKIE_H_
