text: // Copyright (c) 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 "net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.h"
  
  #include <algorithm>
  #include <cctype>
  #include <limits>
  
  #include "net/third_party/quiche/src/spdy/platform/api/spdy_logging.h"
  #include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h"
  
  namespace spdy {
  
  namespace {
  
  template <class T>
  bool ParsePositiveIntegerImpl(SpdyStringPiece::const_iterator c,
                                SpdyStringPiece::const_iterator end,
                                T* value) {
    *value = 0;
    for (; c != end && std::isdigit(*c); ++c) {
      if (*value > std::numeric_limits<T>::max() / 10) {
        return false;
      }
      *value *= 10;
      if (*value > std::numeric_limits<T>::max() - (*c - '0')) {
        return false;
      }
      *value += *c - '0';
    }
    return (c == end && *value > 0);
  }
  
  }  // namespace
  
  SpdyAltSvcWireFormat::AlternativeService::AlternativeService() = default;
  
  SpdyAltSvcWireFormat::AlternativeService::AlternativeService(
      const SpdyString& protocol_id,
      const SpdyString& host,
      uint16_t port,
      uint32_t max_age,
      VersionVector version)
      : protocol_id(protocol_id),
        host(host),
        port(port),
        max_age(max_age),
        version(std::move(version)) {}
  
  SpdyAltSvcWireFormat::AlternativeService::~AlternativeService() = default;
  
  SpdyAltSvcWireFormat::AlternativeService::AlternativeService(
      const AlternativeService& other) = default;
  
  // static
  bool SpdyAltSvcWireFormat::ParseHeaderFieldValue(
      SpdyStringPiece value,
      AlternativeServiceVector* altsvc_vector) {
    // Empty value is invalid according to the specification.
    if (value.empty()) {
      return false;
    }
    altsvc_vector->clear();
    if (value == SpdyStringPiece("clear")) {
      return true;
    }
    SpdyStringPiece::const_iterator c = value.begin();
    while (c != value.end()) {
      // Parse protocol-id.
      SpdyStringPiece::const_iterator percent_encoded_protocol_id_end =
          std::find(c, value.end(), '=');
      SpdyString protocol_id;
      if (percent_encoded_protocol_id_end == c ||
          !PercentDecode(c, percent_encoded_protocol_id_end, &protocol_id)) {
        return false;
      }
      // Check for IETF format for advertising QUIC:
      // hq=":443";quic=51303338;quic=51303334
      const bool is_ietf_format_quic = (protocol_id == "hq");
      c = percent_encoded_protocol_id_end;
      if (c == value.end()) {
        return false;
      }
      // Parse alt-authority.
      DCHECK_EQ('=', *c);
      ++c;
      if (c == value.end() || *c != '"') {
        return false;
      }
      ++c;
      SpdyStringPiece::const_iterator alt_authority_begin = c;
      for (; c != value.end() && *c != '"'; ++c) {
        // Decode backslash encoding.
        if (*c != '\\') {
          continue;
        }
        ++c;
        if (c == value.end()) {
          return false;
        }
      }
      if (c == alt_authority_begin || c == value.end()) {
        return false;
      }
      DCHECK_EQ('"', *c);
      SpdyString host;
      uint16_t port;
      if (!ParseAltAuthority(alt_authority_begin, c, &host, &port)) {
        return false;
      }
      ++c;
      // Parse parameters.
      uint32_t max_age = 86400;
      VersionVector version;
      SpdyStringPiece::const_iterator parameters_end =
          std::find(c, value.end(), ',');
      while (c != parameters_end) {
        SkipWhiteSpace(&c, parameters_end);
        if (c == parameters_end) {
          break;
        }
        if (*c != ';') {
          return false;
        }
        ++c;
        SkipWhiteSpace(&c, parameters_end);
        if (c == parameters_end) {
          break;
        }
        SpdyString parameter_name;
        for (; c != parameters_end && *c != '=' && *c != ' ' && *c != '\t'; ++c) {
          parameter_name.push_back(tolower(*c));
        }
        SkipWhiteSpace(&c, parameters_end);
        if (c == parameters_end || *c != '=') {
          return false;
        }
        ++c;
        SkipWhiteSpace(&c, parameters_end);
        SpdyStringPiece::const_iterator parameter_value_begin = c;
        for (; c != parameters_end && *c != ';' && *c != ' ' && *c != '\t'; ++c) {
        }
        if (c == parameter_value_begin) {
          return false;
        }
        if (parameter_name == "ma") {
          if (!ParsePositiveInteger32(parameter_value_begin, c, &max_age)) {
            return false;
          }
        } else if (!is_ietf_format_quic && parameter_name == "v") {
          // Version is a comma separated list of positive integers enclosed in
          // quotation marks.  Since it can contain commas, which are not
          // delineating alternative service entries, |parameters_end| and |c| can
          // be invalid.
          if (*parameter_value_begin != '"') {
            return false;
          }
          c = std::find(parameter_value_begin + 1, value.end(), '"');
          if (c == value.end()) {
            return false;
          }
          ++c;
          parameters_end = std::find(c, value.end(), ',');
          SpdyStringPiece::const_iterator v_begin = parameter_value_begin + 1;
          while (v_begin < c) {
            SpdyStringPiece::const_iterator v_end = v_begin;
            while (v_end < c - 1 && *v_end != ',') {
              ++v_end;
            }
            uint16_t v;
            if (!ParsePositiveInteger16(v_begin, v_end, &v)) {
              return false;
            }
            version.push_back(v);
            v_begin = v_end + 1;
            if (v_begin == c - 1) {
              // List ends in comma.
              return false;
            }
          }
        } else if (is_ietf_format_quic && parameter_name == "quic") {
          // IETF format for advertising QUIC. Version is hex encoding of QUIC
          // version tag. Hex-encoded string should not include leading "0x" or
          // leading zeros.
          // Example for advertising QUIC versions "Q038" and "Q034":
          // hq=":443";quic=51303338;quic=51303334
          if (*parameter_value_begin == '0') {
            return false;
          }
          // Versions will be stored as the uint32_t hex decoding of the param
          // value string. Example: QUIC version "Q038", which is advertised as:
          // hq=":443";quic=51303338
          // ... will be stored in |versions| as 0x51303338.
          uint32_t quic_version;
          if (!SpdyHexDecodeToUInt32(SpdyStringPiece(parameter_value_begin,
                                                     c - parameter_value_begin),
                                     &quic_version) ||
              quic_version == 0) {
            return false;
          }
          version.push_back(quic_version);
        }
      }
      altsvc_vector->emplace_back(protocol_id, host, port, max_age, version);
      for (; c != value.end() && (*c == ' ' || *c == '\t' || *c == ','); ++c) {
      }
    }
    return true;
  }
  
  // static
  SpdyString SpdyAltSvcWireFormat::SerializeHeaderFieldValue(
      const AlternativeServiceVector& altsvc_vector) {
    if (altsvc_vector.empty()) {
      return SpdyString("clear");
    }
    const char kNibbleToHex[] = "0123456789ABCDEF";
    SpdyString value;
    for (const AlternativeService& altsvc : altsvc_vector) {
      if (!value.empty()) {
        value.push_back(',');
      }
      // Check for IETF format for advertising QUIC.
      const bool is_ietf_format_quic = (altsvc.protocol_id == "hq");
      // Percent escape protocol id according to
      // http://tools.ietf.org/html/rfc7230#section-3.2.6.
      for (char c : altsvc.protocol_id) {
        if (isalnum(c)) {
          value.push_back(c);
          continue;
        }
        switch (c) {
          case '!':
          case '#':
          case '$':
          case '&':
          case '\'':
          case '*':
          case '+':
          case '-':
          case '.':
          case '^':
          case '_':
          case '`':
          case '|':
          case '~':
            value.push_back(c);
            break;
          default:
            value.push_back('%');
            // Network byte order is big-endian.
            value.push_back(kNibbleToHex[c >> 4]);
            value.push_back(kNibbleToHex[c & 0x0f]);
            break;
        }
      }
      value.push_back('=');
      value.push_back('"');
      for (char c : altsvc.host) {
        if (c == '"' || c == '\\') {
          value.push_back('\\');
        }
        value.push_back(c);
      }
      value.append(SpdyStrCat(":", altsvc.port, "\""));
      if (altsvc.max_age != 86400) {
        value.append(SpdyStrCat("; ma=", altsvc.max_age));
      }
      if (!altsvc.version.empty()) {
        if (is_ietf_format_quic) {
          for (uint32_t quic_version : altsvc.version) {
            value.append("; quic=");
            value.append(SpdyHexEncodeUInt32AndTrim(quic_version));
          }
        } else {
          value.append("; v=\"");
          for (auto it = altsvc.version.begin(); it != altsvc.version.end();
               ++it) {
            if (it != altsvc.version.begin()) {
              value.append(",");
            }
            value.append(SpdyStrCat(*it));
          }
          value.append("\"");
        }
      }
    }
    return value;
  }
  
  // static
  void SpdyAltSvcWireFormat::SkipWhiteSpace(SpdyStringPiece::const_iterator* c,
                                            SpdyStringPiece::const_iterator end) {
    for (; *c != end && (**c == ' ' || **c == '\t'); ++*c) {
    }
  }
  
  // static
  bool SpdyAltSvcWireFormat::PercentDecode(SpdyStringPiece::const_iterator c,
                                           SpdyStringPiece::const_iterator end,
                                           SpdyString* output) {
    output->clear();
    for (; c != end; ++c) {
      if (*c != '%') {
        output->push_back(*c);
        continue;
      }
      DCHECK_EQ('%', *c);
      ++c;
      if (c == end || !std::isxdigit(*c)) {
        return false;
      }
      // Network byte order is big-endian.
      char decoded = SpdyHexDigitToInt(*c) << 4;
      ++c;
      if (c == end || !std::isxdigit(*c)) {
        return false;
      }
      decoded += SpdyHexDigitToInt(*c);
      output->push_back(decoded);
    }
    return true;
  }
  
  // static
  bool SpdyAltSvcWireFormat::ParseAltAuthority(
      SpdyStringPiece::const_iterator c,
      SpdyStringPiece::const_iterator end,
      SpdyString* host,
      uint16_t* port) {
    host->clear();
    if (c == end) {
      return false;
    }
    if (*c == '[') {
      for (; c != end && *c != ']'; ++c) {
        if (*c == '"') {
          // Port is mandatory.
          return false;
        }
        host->push_back(*c);
      }
      if (c == end) {
        return false;
      }
      DCHECK_EQ(']', *c);
      host->push_back(*c);
      ++c;
    } else {
      for (; c != end && *c != ':'; ++c) {
        if (*c == '"') {
          // Port is mandatory.
          return false;
        }
        if (*c == '\\') {
          ++c;
          if (c == end) {
            return false;
          }
        }
        host->push_back(*c);
      }
    }
    if (c == end || *c != ':') {
      return false;
    }
    DCHECK_EQ(':', *c);
    ++c;
    return ParsePositiveInteger16(c, end, port);
  }
  
  // static
  bool SpdyAltSvcWireFormat::ParsePositiveInteger16(
      SpdyStringPiece::const_iterator c,
      SpdyStringPiece::const_iterator end,
      uint16_t* value) {
    return ParsePositiveIntegerImpl<uint16_t>(c, end, value);
  }
  
  // static
  bool SpdyAltSvcWireFormat::ParsePositiveInteger32(
      SpdyStringPiece::const_iterator c,
      SpdyStringPiece::const_iterator end,
      uint32_t* value) {
    return ParsePositiveIntegerImpl<uint32_t>(c, end, value);
  }
  
  }  // namespace spdy
  