| // Copyright 2015 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. | 
 |  | 
 | #include "components/domain_reliability/header.h" | 
 |  | 
 | #include <stdint.h> | 
 |  | 
 | #include <memory> | 
 | #include <string> | 
 |  | 
 | #include "base/memory/ptr_util.h" | 
 | #include "base/strings/string_number_conversions.h" | 
 | #include "base/strings/string_tokenizer.h" | 
 | #include "components/domain_reliability/config.h" | 
 |  | 
 | namespace { | 
 |  | 
 | // Parses directives in the format ("foo; bar=value for bar; baz; quux=123") | 
 | // used by NEL. | 
 | class DirectiveHeaderValueParser { | 
 |  public: | 
 |   enum State { | 
 |     BEFORE_NAME, | 
 |     AFTER_NAME, | 
 |     BEFORE_VALUE, | 
 |     AFTER_DIRECTIVE, | 
 |     SYNTAX_ERROR | 
 |   }; | 
 |  | 
 |   DirectiveHeaderValueParser(base::StringPiece value) | 
 |       : value_(value.data()), | 
 |         tokenizer_(value_.begin(), value_.end(), ";= "), | 
 |         stopped_with_error_(false) { | 
 |     tokenizer_.set_options(base::StringTokenizer::RETURN_DELIMS); | 
 |     tokenizer_.set_quote_chars("\"'"); | 
 |   } | 
 |  | 
 |   // Gets the next directive, if there is one. Returns whether there was one. | 
 |   bool GetNext() { | 
 |     if (stopped_with_error_) | 
 |       return false; | 
 |  | 
 |     directive_name_ = base::StringPiece(); | 
 |     directive_has_value_ = false; | 
 |     directive_values_.clear(); | 
 |  | 
 |     State state = BEFORE_NAME; | 
 |     while (state != AFTER_DIRECTIVE && state != SYNTAX_ERROR | 
 |            && tokenizer_.GetNext()) { | 
 |       if (*tokenizer_.token_begin() == ' ') | 
 |         continue; | 
 |  | 
 |       switch (state) { | 
 |         case BEFORE_NAME: | 
 |           state = DoBeforeName(); | 
 |           break; | 
 |         case AFTER_NAME: | 
 |           state = DoAfterName(); | 
 |           break; | 
 |         case BEFORE_VALUE: | 
 |           state = DoBeforeValue(); | 
 |           break; | 
 |         case AFTER_DIRECTIVE: | 
 |         case SYNTAX_ERROR: | 
 |           NOTREACHED(); | 
 |           break; | 
 |       } | 
 |     } | 
 |  | 
 |     switch (state) { | 
 |       // If the parser just read the last directive, it may be in one of these | 
 |       // states, so return true to yield that directive. | 
 |       case AFTER_NAME: | 
 |       case BEFORE_VALUE: | 
 |       case AFTER_DIRECTIVE: | 
 |         return true; | 
 |  | 
 |       // If the parser never found a name, return false, since it doesn't have | 
 |       // a new directive for the caller. | 
 |       case BEFORE_NAME: | 
 |         return false; | 
 |  | 
 |       case SYNTAX_ERROR: | 
 |         stopped_with_error_ = true; | 
 |         return false; | 
 |  | 
 |       default: | 
 |         NOTREACHED(); | 
 |         return false; | 
 |     } | 
 |   } | 
 |  | 
 |  | 
 |   base::StringPiece directive_name() const { return directive_name_; } | 
 |   bool directive_has_value() const { return directive_has_value_; } | 
 |   const std::vector<base::StringPiece>& directive_values() const { | 
 |     return directive_values_; | 
 |   } | 
 |   bool stopped_with_error() const { return stopped_with_error_; } | 
 |  | 
 |  private: | 
 |   State DoBeforeName() { | 
 |     if (tokenizer_.token_is_delim()) | 
 |       return SYNTAX_ERROR; | 
 |  | 
 |     directive_name_ = tokenizer_.token_piece(); | 
 |     return AFTER_NAME; | 
 |   } | 
 |  | 
 |   State DoAfterName() { | 
 |     if (tokenizer_.token_is_delim()) { | 
 |       char token_begin = *tokenizer_.token_begin(); | 
 |       // Name can be followed by =value, ;, or just EOF. | 
 |       if (token_begin == '=') { | 
 |         directive_has_value_ = true; | 
 |         return BEFORE_VALUE; | 
 |       } | 
 |       if (token_begin == ';') | 
 |         return AFTER_DIRECTIVE; | 
 |     } | 
 |     return SYNTAX_ERROR; | 
 |   } | 
 |  | 
 |   State DoBeforeValue() { | 
 |     if (tokenizer_.token_is_delim()) { | 
 |       char token_begin = *tokenizer_.token_begin(); | 
 |       if (token_begin == ';') | 
 |         return AFTER_DIRECTIVE; | 
 |       return SYNTAX_ERROR; | 
 |     } | 
 |  | 
 |     directive_values_.push_back(tokenizer_.token_piece()); | 
 |     return BEFORE_VALUE; | 
 |   } | 
 |  | 
 |   std::string value_; | 
 |   base::StringTokenizer tokenizer_; | 
 |  | 
 |   base::StringPiece directive_name_; | 
 |   bool directive_has_value_; | 
 |   std::vector<base::StringPiece> directive_values_; | 
 |   bool stopped_with_error_; | 
 | }; | 
 |  | 
 | bool Unquote(const std::string& in, std::string* out) { | 
 |   char first = in[0]; | 
 |   char last = in[in.length() - 1]; | 
 |  | 
 |   if (((first == '"') ^ (last == '"')) || ((first == '<') ^ (last == '>'))) | 
 |     return false; | 
 |  | 
 |   if ((first == '"') || (first == '<')) | 
 |     *out = in.substr(1, in.length() - 2); | 
 |   else | 
 |     *out = in; | 
 |   return true; | 
 | } | 
 |  | 
 | bool ParseReportUri(const std::vector<base::StringPiece> in, | 
 |                     std::vector<std::unique_ptr<GURL>>* out) { | 
 |   if (in.size() < 1u) | 
 |     return false; | 
 |  | 
 |   out->clear(); | 
 |   for (const auto& in_token : in) { | 
 |     std::string unquoted; | 
 |     if (!Unquote(in_token.as_string(), &unquoted)) | 
 |       return false; | 
 |     GURL url(unquoted); | 
 |     if (!url.is_valid() || !url.SchemeIsCryptographic()) | 
 |       return false; | 
 |     out->push_back(std::make_unique<GURL>(url)); | 
 |   } | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | bool ParseMaxAge(const std::vector<base::StringPiece> in, | 
 |                  base::TimeDelta* out) { | 
 |   if (in.size() != 1u) | 
 |     return false; | 
 |  | 
 |   int64_t seconds; | 
 |   if (!base::StringToInt64(in[0], &seconds)) | 
 |     return false; | 
 |  | 
 |   if (seconds < 0) | 
 |     return false; | 
 |  | 
 |   *out = base::TimeDelta::FromSeconds(seconds); | 
 |   return true; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | namespace domain_reliability { | 
 |  | 
 | DomainReliabilityHeader::~DomainReliabilityHeader() {} | 
 |  | 
 | // static | 
 | std::unique_ptr<DomainReliabilityHeader> DomainReliabilityHeader::Parse( | 
 |     base::StringPiece value) { | 
 |   std::vector<std::unique_ptr<GURL>> report_uri; | 
 |   base::TimeDelta max_age; | 
 |   bool include_subdomains = false; | 
 |  | 
 |   bool got_report_uri = false; | 
 |   bool got_max_age = false; | 
 |   bool got_include_subdomains = false; | 
 |  | 
 |   DirectiveHeaderValueParser parser(value); | 
 |   while (parser.GetNext()) { | 
 |     base::StringPiece name = parser.directive_name(); | 
 |     if (name == "report-uri") { | 
 |       if (got_report_uri | 
 |           || !parser.directive_has_value() | 
 |           || !ParseReportUri(parser.directive_values(), &report_uri)) { | 
 |         return base::WrapUnique(new DomainReliabilityHeader(PARSE_ERROR)); | 
 |       } | 
 |       got_report_uri = true; | 
 |     } else if (name == "max-age") { | 
 |       if (got_max_age | 
 |           || !parser.directive_has_value() | 
 |           || !ParseMaxAge(parser.directive_values(), &max_age)) { | 
 |         return base::WrapUnique(new DomainReliabilityHeader(PARSE_ERROR)); | 
 |       } | 
 |       got_max_age = true; | 
 |     } else if (name == "includeSubdomains") { | 
 |       if (got_include_subdomains || | 
 |           parser.directive_has_value()) { | 
 |         return base::WrapUnique(new DomainReliabilityHeader(PARSE_ERROR)); | 
 |       } | 
 |       include_subdomains = true; | 
 |       got_include_subdomains = true; | 
 |     } else { | 
 |       DLOG(WARNING) << "Ignoring unknown NEL header directive " << name << "."; | 
 |     } | 
 |   } | 
 |  | 
 |   if (parser.stopped_with_error() || !got_max_age) | 
 |     return base::WrapUnique(new DomainReliabilityHeader(PARSE_ERROR)); | 
 |  | 
 |   if (max_age == base::TimeDelta::FromMicroseconds(0)) | 
 |     return base::WrapUnique(new DomainReliabilityHeader(PARSE_CLEAR_CONFIG)); | 
 |  | 
 |   if (!got_report_uri) | 
 |     return base::WrapUnique(new DomainReliabilityHeader(PARSE_ERROR)); | 
 |  | 
 |   std::unique_ptr<DomainReliabilityConfig> config( | 
 |       new DomainReliabilityConfig()); | 
 |   config->include_subdomains = include_subdomains; | 
 |   config->collectors.clear(); | 
 |   config->collectors.swap(report_uri); | 
 |   config->success_sample_rate = 0.0; | 
 |   config->failure_sample_rate = 1.0; | 
 |   config->path_prefixes.clear(); | 
 |   return base::WrapUnique(new DomainReliabilityHeader( | 
 |       PARSE_SET_CONFIG, std::move(config), max_age)); | 
 | } | 
 |  | 
 | const DomainReliabilityConfig& DomainReliabilityHeader::config() const { | 
 |   DCHECK_EQ(PARSE_SET_CONFIG, status_); | 
 |   return *config_; | 
 | } | 
 |  | 
 | base::TimeDelta DomainReliabilityHeader::max_age() const { | 
 |   DCHECK_EQ(PARSE_SET_CONFIG, status_); | 
 |   return max_age_; | 
 | } | 
 |  | 
 | std::unique_ptr<DomainReliabilityConfig> | 
 | DomainReliabilityHeader::ReleaseConfig() { | 
 |   DCHECK_EQ(PARSE_SET_CONFIG, status_); | 
 |   status_ = PARSE_ERROR; | 
 |   return std::move(config_); | 
 | } | 
 |  | 
 | std::string DomainReliabilityHeader::ToString() const { | 
 |   std::string string; | 
 |   int64_t max_age_s = max_age_.InSeconds(); | 
 |  | 
 |   if (config_->collectors.empty()) { | 
 |     DCHECK_EQ(0, max_age_s); | 
 |   } else { | 
 |     string += "report-uri="; | 
 |     for (const auto& uri : config_->collectors) | 
 |       string += uri->spec() + " "; | 
 |     // Remove trailing space. | 
 |     string.erase(string.length() - 1, 1); | 
 |     string += "; "; | 
 |   } | 
 |  | 
 |   string += "max-age=" + base::Int64ToString(max_age_s) + "; "; | 
 |  | 
 |   if (config_->include_subdomains) | 
 |     string += "includeSubdomains; "; | 
 |  | 
 |   // Remove trailing "; ". | 
 |   string.erase(string.length() - 2, 2); | 
 |  | 
 |   return string; | 
 | } | 
 |  | 
 | DomainReliabilityHeader::DomainReliabilityHeader(ParseStatus status) | 
 |     : status_(status) { | 
 |   DCHECK_NE(PARSE_SET_CONFIG, status_); | 
 | } | 
 |  | 
 | DomainReliabilityHeader::DomainReliabilityHeader( | 
 |     ParseStatus status, | 
 |     std::unique_ptr<DomainReliabilityConfig> config, | 
 |     base::TimeDelta max_age) | 
 |     : status_(status), config_(std::move(config)), max_age_(max_age) { | 
 |   DCHECK_EQ(PARSE_SET_CONFIG, status_); | 
 |   DCHECK(config_); | 
 |   DCHECK_NE(0, max_age_.InMicroseconds()); | 
 | } | 
 |  | 
 | }  // namespace domain_reliability |