| Index: net/spdy/spdy_alt_svc_wire_format.cc
|
| diff --git a/net/spdy/spdy_alt_svc_wire_format.cc b/net/spdy/spdy_alt_svc_wire_format.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..a1e7361771c64d2d42153918e46d15c2ef57b5ec
|
| --- /dev/null
|
| +++ b/net/spdy/spdy_alt_svc_wire_format.cc
|
| @@ -0,0 +1,310 @@
|
| +// 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/spdy/spdy_alt_svc_wire_format.h"
|
| +
|
| +#include <limits>
|
| +#include <string>
|
| +
|
| +#include "base/logging.h"
|
| +#include "base/strings/stringprintf.h"
|
| +
|
| +namespace net {
|
| +
|
| +namespace {
|
| +
|
| +template <class T>
|
| +bool ParsePositiveIntegerImpl(StringPiece::const_iterator c,
|
| + StringPiece::const_iterator end,
|
| + T* value) {
|
| + *value = 0;
|
| + for (; c != end && 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
|
| +
|
| +// static
|
| +bool SpdyAltSvcWireFormat::ParseHeaderFieldValue(StringPiece value,
|
| + std::string* protocol_id,
|
| + std::string* host,
|
| + uint16* port,
|
| + uint32* max_age,
|
| + double* p) {
|
| + *max_age = 86400;
|
| + *p = 1.0;
|
| +
|
| + StringPiece::const_iterator c = value.begin();
|
| + StringPiece::const_iterator percent_encoded_protocol_id_end =
|
| + std::find(c, value.end(), '=');
|
| + if (percent_encoded_protocol_id_end == c ||
|
| + !PercentDecode(c, percent_encoded_protocol_id_end, protocol_id)) {
|
| + return false;
|
| + }
|
| + c = percent_encoded_protocol_id_end;
|
| + if (c == value.end()) {
|
| + return false;
|
| + }
|
| + DCHECK_EQ('=', *c);
|
| + ++c;
|
| + if (c == value.end() || *c != '"') {
|
| + return false;
|
| + }
|
| + ++c;
|
| + StringPiece::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);
|
| + if (!ParseAltAuthority(alt_authority_begin, c, host, port)) {
|
| + return false;
|
| + }
|
| + ++c;
|
| + StringPiece::const_iterator parameters_end = std::find(c, value.end(), ',');
|
| + while (c != parameters_end) {
|
| + SkipWhiteSpace(&c, parameters_end);
|
| + if (c == parameters_end) {
|
| + return true;
|
| + }
|
| + if (*c != ';') {
|
| + return false;
|
| + }
|
| + ++c;
|
| + SkipWhiteSpace(&c, parameters_end);
|
| + if (c == parameters_end) {
|
| + return true;
|
| + }
|
| + std::string 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);
|
| + StringPiece::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.compare("ma") == 0) {
|
| + if (!ParsePositiveInteger32(parameter_value_begin, c, max_age)) {
|
| + return false;
|
| + }
|
| + } else if (parameter_name.compare("p") == 0) {
|
| + if (!ParseProbability(parameter_value_begin, c, p)) {
|
| + return false;
|
| + }
|
| + }
|
| + SkipWhiteSpace(&c, parameters_end);
|
| + }
|
| + // TODO(bnc): Parse additional alternative services delimited by ','.
|
| + return true;
|
| +}
|
| +
|
| +// static
|
| +std::string SpdyAltSvcWireFormat::SerializeHeaderFieldValue(
|
| + const std::string& protocol_id,
|
| + const std::string& host,
|
| + uint16 port,
|
| + uint32 max_age,
|
| + double p) {
|
| + const char kNibbleToHex[] = "0123456789ABCDEF";
|
| + std::string value;
|
| + // Percent escape protocol id according to
|
| + // http://tools.ietf.org/html/rfc7230#section-3.2.6.
|
| + for (char c : 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 : host) {
|
| + if (c == '"' || c == '\\') {
|
| + value.push_back('\\');
|
| + }
|
| + value.push_back(c);
|
| + }
|
| + base::StringAppendF(&value, ":%d\"", port);
|
| + if (max_age != 86400) {
|
| + base::StringAppendF(&value, "; ma=%d", max_age);
|
| + }
|
| + if (p != 1.0) {
|
| + base::StringAppendF(&value, "; p=%.2f", p);
|
| + }
|
| + return value;
|
| +}
|
| +
|
| +// static
|
| +void SpdyAltSvcWireFormat::SkipWhiteSpace(StringPiece::const_iterator* c,
|
| + StringPiece::const_iterator end) {
|
| + for (; *c != end && (**c == ' ' || **c == '\t'); ++*c) {
|
| + }
|
| +}
|
| +
|
| +// static
|
| +bool SpdyAltSvcWireFormat::PercentDecode(StringPiece::const_iterator c,
|
| + StringPiece::const_iterator end,
|
| + std::string* output) {
|
| + output->clear();
|
| + for (; c != end; ++c) {
|
| + if (*c != '%') {
|
| + output->push_back(*c);
|
| + continue;
|
| + }
|
| + DCHECK_EQ('%', *c);
|
| + ++c;
|
| + if (c == end || !isxdigit(*c)) {
|
| + return false;
|
| + }
|
| + char decoded = tolower(*c);
|
| + // '0' is 0, 'a' is 10.
|
| + decoded += isdigit(*c) ? (0 - '0') : (10 - 'a');
|
| + // Network byte order is big-endian.
|
| + decoded <<= 4;
|
| + ++c;
|
| + if (c == end || !isxdigit(*c)) {
|
| + return false;
|
| + }
|
| + decoded += tolower(*c);
|
| + // '0' is 0, 'a' is 10.
|
| + decoded += isdigit(*c) ? (0 - '0') : (10 - 'a');
|
| + output->push_back(decoded);
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +// static
|
| +bool SpdyAltSvcWireFormat::ParseAltAuthority(StringPiece::const_iterator c,
|
| + StringPiece::const_iterator end,
|
| + std::string* host,
|
| + uint16* port) {
|
| + host->clear();
|
| + 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) {
|
| + return false;
|
| + }
|
| + DCHECK_EQ(':', *c);
|
| + ++c;
|
| + return ParsePositiveInteger16(c, end, port);
|
| +}
|
| +
|
| +// static
|
| +bool SpdyAltSvcWireFormat::ParsePositiveInteger16(
|
| + StringPiece::const_iterator c,
|
| + StringPiece::const_iterator end,
|
| + uint16* value) {
|
| + return ParsePositiveIntegerImpl<uint16>(c, end, value);
|
| +}
|
| +
|
| +// static
|
| +bool SpdyAltSvcWireFormat::ParsePositiveInteger32(
|
| + StringPiece::const_iterator c,
|
| + StringPiece::const_iterator end,
|
| + uint32* value) {
|
| + return ParsePositiveIntegerImpl<uint32>(c, end, value);
|
| +}
|
| +
|
| +// Probability is a decimal fraction between 0.0 and 1.0, inclusive, with
|
| +// optional leading zero, optional decimal point, and optional digits following
|
| +// the decimal point, with the restriction that there has to be at least one
|
| +// digit (that is, "" and "." are not valid).
|
| +// static
|
| +bool SpdyAltSvcWireFormat::ParseProbability(StringPiece::const_iterator c,
|
| + StringPiece::const_iterator end,
|
| + double* p) {
|
| + // "" is invalid.
|
| + if (c == end) {
|
| + return false;
|
| + }
|
| + // "." is invalid.
|
| + if (end - c == 1 && *c == '.') {
|
| + return false;
|
| + }
|
| + if (*c == '1') {
|
| + *p = 1.0;
|
| + ++c;
|
| + } else {
|
| + *p = 0.0;
|
| + if (*c == '0') {
|
| + ++c;
|
| + }
|
| + }
|
| + if (c == end) {
|
| + return true;
|
| + }
|
| + if (*c != '.') {
|
| + return false;
|
| + }
|
| + // So far we could have had ".", "0.", or "1.".
|
| + ++c;
|
| + double place_value = 0.1;
|
| + for (; c != end && isdigit(*c); ++c) {
|
| + *p += place_value * (*c - '0');
|
| + place_value *= 0.1;
|
| + }
|
| + return (c == end && *p <= 1.0);
|
| +}
|
| +
|
| +} // namespace net
|
|
|