|  | // 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 <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" | 
|  | #include "content/public/common/origin_util.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, | 
|  | ScopedVector<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() || !content::IsOriginSecure(url)) | 
|  | return false; | 
|  | out->push_back(new 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) { | 
|  | ScopedVector<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 { | 
|  | LOG(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_.get()); | 
|  | DCHECK_NE(0, max_age_.InMicroseconds()); | 
|  | } | 
|  |  | 
|  | }  // namespace domain_reliability |