1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "quiche/quic/core/http/spdy_utils.h"
6
7 #include <memory>
8 #include <optional>
9 #include <string>
10 #include <vector>
11
12 #include "absl/strings/numbers.h"
13 #include "absl/strings/str_cat.h"
14 #include "absl/strings/str_split.h"
15 #include "absl/strings/string_view.h"
16 #include "quiche/quic/core/quic_versions.h"
17 #include "quiche/quic/platform/api/quic_flag_utils.h"
18 #include "quiche/quic/platform/api/quic_flags.h"
19 #include "quiche/quic/platform/api/quic_logging.h"
20 #include "quiche/common/quiche_text_utils.h"
21 #include "quiche/spdy/core/spdy_protocol.h"
22
23 using spdy::Http2HeaderBlock;
24
25 namespace quic {
26
27 // static
ExtractContentLengthFromHeaders(int64_t * content_length,Http2HeaderBlock * headers)28 bool SpdyUtils::ExtractContentLengthFromHeaders(int64_t* content_length,
29 Http2HeaderBlock* headers) {
30 auto it = headers->find("content-length");
31 if (it == headers->end()) {
32 return false;
33 } else {
34 // Check whether multiple values are consistent.
35 absl::string_view content_length_header = it->second;
36 std::vector<absl::string_view> values =
37 absl::StrSplit(content_length_header, '\0');
38 for (const absl::string_view& value : values) {
39 uint64_t new_value;
40 if (!absl::SimpleAtoi(value, &new_value) ||
41 !quiche::QuicheTextUtils::IsAllDigits(value)) {
42 QUIC_DLOG(ERROR)
43 << "Content length was either unparseable or negative.";
44 return false;
45 }
46 if (*content_length < 0) {
47 *content_length = new_value;
48 continue;
49 }
50 if (new_value != static_cast<uint64_t>(*content_length)) {
51 QUIC_DLOG(ERROR)
52 << "Parsed content length " << new_value << " is "
53 << "inconsistent with previously detected content length "
54 << *content_length;
55 return false;
56 }
57 }
58 return true;
59 }
60 }
61
CopyAndValidateHeaders(const QuicHeaderList & header_list,int64_t * content_length,Http2HeaderBlock * headers)62 bool SpdyUtils::CopyAndValidateHeaders(const QuicHeaderList& header_list,
63 int64_t* content_length,
64 Http2HeaderBlock* headers) {
65 for (const auto& p : header_list) {
66 const std::string& name = p.first;
67 if (name.empty()) {
68 QUIC_DLOG(ERROR) << "Header name must not be empty.";
69 return false;
70 }
71
72 if (quiche::QuicheTextUtils::ContainsUpperCase(name)) {
73 QUIC_DLOG(ERROR) << "Malformed header: Header name " << name
74 << " contains upper-case characters.";
75 return false;
76 }
77
78 headers->AppendValueOrAddHeader(name, p.second);
79 }
80
81 if (headers->contains("content-length") &&
82 !ExtractContentLengthFromHeaders(content_length, headers)) {
83 return false;
84 }
85
86 QUIC_DVLOG(1) << "Successfully parsed headers: " << headers->DebugString();
87 return true;
88 }
89
CopyAndValidateTrailers(const QuicHeaderList & header_list,bool expect_final_byte_offset,size_t * final_byte_offset,Http2HeaderBlock * trailers)90 bool SpdyUtils::CopyAndValidateTrailers(const QuicHeaderList& header_list,
91 bool expect_final_byte_offset,
92 size_t* final_byte_offset,
93 Http2HeaderBlock* trailers) {
94 bool found_final_byte_offset = false;
95 for (const auto& p : header_list) {
96 const std::string& name = p.first;
97
98 // Pull out the final offset pseudo header which indicates the number of
99 // response body bytes expected.
100 if (expect_final_byte_offset && !found_final_byte_offset &&
101 name == kFinalOffsetHeaderKey &&
102 absl::SimpleAtoi(p.second, final_byte_offset)) {
103 found_final_byte_offset = true;
104 continue;
105 }
106
107 if (name.empty() || name[0] == ':') {
108 QUIC_DLOG(ERROR)
109 << "Trailers must not be empty, and must not contain pseudo-"
110 << "headers. Found: '" << name << "'";
111 return false;
112 }
113
114 if (quiche::QuicheTextUtils::ContainsUpperCase(name)) {
115 QUIC_DLOG(ERROR) << "Malformed header: Header name " << name
116 << " contains upper-case characters.";
117 return false;
118 }
119
120 trailers->AppendValueOrAddHeader(name, p.second);
121 }
122
123 if (expect_final_byte_offset && !found_final_byte_offset) {
124 QUIC_DLOG(ERROR) << "Required key '" << kFinalOffsetHeaderKey
125 << "' not present";
126 return false;
127 }
128
129 // TODO(rjshade): Check for other forbidden keys, following the HTTP/2 spec.
130
131 QUIC_DVLOG(1) << "Successfully parsed Trailers: " << trailers->DebugString();
132 return true;
133 }
134
135 // static
136 // TODO(danzh): Move it to quic/tools/ and switch to use GURL.
PopulateHeaderBlockFromUrl(const std::string url,Http2HeaderBlock * headers)137 bool SpdyUtils::PopulateHeaderBlockFromUrl(const std::string url,
138 Http2HeaderBlock* headers) {
139 (*headers)[":method"] = "GET";
140 size_t pos = url.find("://");
141 if (pos == std::string::npos) {
142 return false;
143 }
144 (*headers)[":scheme"] = url.substr(0, pos);
145 size_t start = pos + 3;
146 pos = url.find('/', start);
147 if (pos == std::string::npos) {
148 (*headers)[":authority"] = url.substr(start);
149 (*headers)[":path"] = "/";
150 return true;
151 }
152 (*headers)[":authority"] = url.substr(start, pos - start);
153 (*headers)[":path"] = url.substr(pos);
154 return true;
155 }
156
157 // static
ExtractQuicVersionFromAltSvcEntry(const spdy::SpdyAltSvcWireFormat::AlternativeService & alternative_service_entry,const ParsedQuicVersionVector & supported_versions)158 ParsedQuicVersion SpdyUtils::ExtractQuicVersionFromAltSvcEntry(
159 const spdy::SpdyAltSvcWireFormat::AlternativeService&
160 alternative_service_entry,
161 const ParsedQuicVersionVector& supported_versions) {
162 for (const ParsedQuicVersion& version : supported_versions) {
163 if (version.AlpnDeferToRFCv1()) {
164 // Versions with share an ALPN with v1 are currently unable to be
165 // advertised with Alt-Svc.
166 continue;
167 }
168 if (AlpnForVersion(version) == alternative_service_entry.protocol_id) {
169 return version;
170 }
171 }
172
173 return ParsedQuicVersion::Unsupported();
174 }
175
176 } // namespace quic
177