OLD | NEW |
| (Empty) |
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 "net/quic/spdy_utils.h" | |
6 | |
7 #include <memory> | |
8 #include <vector> | |
9 | |
10 #include "base/stl_util.h" | |
11 #include "base/strings/string_number_conversions.h" | |
12 #include "base/strings/string_split.h" | |
13 #include "base/strings/string_util.h" | |
14 #include "base/strings/stringprintf.h" | |
15 #include "net/spdy/spdy_flags.h" | |
16 #include "net/spdy/spdy_frame_builder.h" | |
17 #include "net/spdy/spdy_framer.h" | |
18 #include "net/spdy/spdy_protocol.h" | |
19 #include "url/gurl.h" | |
20 | |
21 using base::StringPiece; | |
22 using std::string; | |
23 using std::vector; | |
24 | |
25 namespace net { | |
26 | |
27 // static | |
28 string SpdyUtils::SerializeUncompressedHeaders(const SpdyHeaderBlock& headers) { | |
29 SpdyMajorVersion spdy_version = HTTP2; | |
30 | |
31 size_t length = SpdyFramer::GetSerializedLength(spdy_version, &headers); | |
32 SpdyFrameBuilder builder(length, spdy_version); | |
33 SpdyFramer framer(spdy_version); | |
34 framer.SerializeHeaderBlockWithoutCompression(&builder, headers); | |
35 SpdySerializedFrame block(builder.take()); | |
36 return string(block.data(), length); | |
37 } | |
38 | |
39 // static | |
40 bool SpdyUtils::ParseHeaders(const char* data, | |
41 uint32_t data_len, | |
42 int64_t* content_length, | |
43 SpdyHeaderBlock* headers) { | |
44 SpdyFramer framer(HTTP2); | |
45 if (!framer.ParseHeaderBlockInBuffer(data, data_len, headers) || | |
46 headers->empty()) { | |
47 return false; // Headers were invalid. | |
48 } | |
49 | |
50 if (ContainsKey(*headers, "content-length")) { | |
51 // Check whether multiple values are consistent. | |
52 base::StringPiece content_length_header = (*headers)["content-length"]; | |
53 vector<string> values = | |
54 base::SplitString(content_length_header, base::StringPiece("\0", 1), | |
55 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); | |
56 for (const string& value : values) { | |
57 int64_t new_value; | |
58 if (!base::StringToInt64(value, &new_value) || new_value < 0) { | |
59 return false; | |
60 } | |
61 if (*content_length < 0) { | |
62 *content_length = new_value; | |
63 continue; | |
64 } | |
65 if (new_value != *content_length) { | |
66 return false; | |
67 } | |
68 } | |
69 } | |
70 | |
71 return true; | |
72 } | |
73 | |
74 // static | |
75 bool SpdyUtils::ParseTrailers(const char* data, | |
76 uint32_t data_len, | |
77 size_t* final_byte_offset, | |
78 SpdyHeaderBlock* trailers) { | |
79 SpdyFramer framer(HTTP2); | |
80 if (!framer.ParseHeaderBlockInBuffer(data, data_len, trailers) || | |
81 trailers->empty()) { | |
82 DVLOG(1) << "Request Trailers are invalid."; | |
83 return false; // Trailers were invalid. | |
84 } | |
85 | |
86 // Pull out the final offset pseudo header which indicates the number of | |
87 // response body bytes expected. | |
88 auto it = trailers->find(kFinalOffsetHeaderKey); | |
89 if (it == trailers->end() || | |
90 !base::StringToSizeT(it->second, final_byte_offset)) { | |
91 DVLOG(1) << "Required key '" << kFinalOffsetHeaderKey << "' not present"; | |
92 return false; | |
93 } | |
94 // The final offset header is no longer needed. | |
95 trailers->erase(it->first); | |
96 | |
97 // Trailers must not have empty keys, and must not contain pseudo headers. | |
98 for (const auto& trailer : *trailers) { | |
99 base::StringPiece key = trailer.first; | |
100 base::StringPiece value = trailer.second; | |
101 if (key.starts_with(":")) { | |
102 DVLOG(1) << "Trailers must not contain pseudo-header: '" << key << "','" | |
103 << value << "'."; | |
104 return false; | |
105 } | |
106 | |
107 // TODO(rjshade): Check for other forbidden keys, following the HTTP/2 spec. | |
108 } | |
109 | |
110 DVLOG(1) << "Successfully parsed Trailers."; | |
111 return true; | |
112 } | |
113 | |
114 bool SpdyUtils::CopyAndValidateHeaders(const QuicHeaderList& header_list, | |
115 int64_t* content_length, | |
116 SpdyHeaderBlock* headers) { | |
117 for (const auto& p : header_list) { | |
118 const string& name = p.first; | |
119 if (name.empty()) { | |
120 DVLOG(1) << "Header name must not be empty."; | |
121 return false; | |
122 } | |
123 | |
124 if (FLAGS_chromium_http2_flag_use_new_spdy_header_block_header_joining) { | |
125 headers->AppendValueOrAddHeader(name, p.second); | |
126 } else { | |
127 auto iter = headers->find(name); | |
128 if (iter == headers->end()) { | |
129 (*headers)[name] = p.second; | |
130 } else { | |
131 // This header had multiple values, so it must be reconstructed. | |
132 StringPiece v = iter->second; | |
133 string s(v.data(), v.length()); | |
134 if (name == "cookie") { | |
135 // Obeys section 8.1.2.5 in RFC 7540 for cookie reconstruction. | |
136 s.append("; "); | |
137 } else { | |
138 StringPiece("\0", 1).AppendToString(&s); | |
139 } | |
140 s.append(p.second); | |
141 headers->ReplaceOrAppendHeader(name, s); | |
142 } | |
143 } | |
144 } | |
145 | |
146 if (ContainsKey(*headers, "content-length")) { | |
147 // Check whether multiple values are consistent. | |
148 StringPiece content_length_header = (*headers)["content-length"]; | |
149 vector<string> values = | |
150 base::SplitString(content_length_header, base::StringPiece("\0", 1), | |
151 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); | |
152 for (const string& value : values) { | |
153 int64_t new_value; | |
154 if (!base::StringToInt64(value, &new_value) || new_value < 0) { | |
155 DLOG(ERROR) << "Content length was either unparseable or negative."; | |
156 return false; | |
157 } | |
158 if (*content_length < 0) { | |
159 *content_length = new_value; | |
160 continue; | |
161 } | |
162 if (new_value != *content_length) { | |
163 DLOG(ERROR) << "Parsed content length " << new_value << " is " | |
164 << "inconsistent with previously detected content length " | |
165 << *content_length; | |
166 return false; | |
167 } | |
168 } | |
169 } | |
170 | |
171 DVLOG(1) << "Successfully parsed headers: " << headers->DebugString(); | |
172 return true; | |
173 } | |
174 | |
175 bool SpdyUtils::CopyAndValidateTrailers(const QuicHeaderList& header_list, | |
176 size_t* final_byte_offset, | |
177 SpdyHeaderBlock* trailers) { | |
178 bool found_final_byte_offset = false; | |
179 for (const auto& p : header_list) { | |
180 const string& name = p.first; | |
181 | |
182 // Pull out the final offset pseudo header which indicates the number of | |
183 // response body bytes expected. | |
184 int offset; | |
185 if (!found_final_byte_offset && name == kFinalOffsetHeaderKey && | |
186 base::StringToInt(p.second, &offset)) { | |
187 *final_byte_offset = offset; | |
188 found_final_byte_offset = true; | |
189 continue; | |
190 } | |
191 | |
192 if (name.empty() || name[0] == ':') { | |
193 DVLOG(1) << "Trailers must not be empty, and must not contain pseudo-" | |
194 << "headers. Found: '" << name << "'"; | |
195 return false; | |
196 } | |
197 | |
198 if (std::any_of(name.begin(), name.end(), base::IsAsciiUpper<char>)) { | |
199 DVLOG(1) << "Malformed header: Header name " << name | |
200 << " contains upper-case characters."; | |
201 return false; | |
202 } | |
203 | |
204 if (trailers->find(name) != trailers->end()) { | |
205 DVLOG(1) << "Duplicate header '" << name << "' found in trailers."; | |
206 return false; | |
207 } | |
208 | |
209 (*trailers)[name] = p.second; | |
210 } | |
211 | |
212 if (!found_final_byte_offset) { | |
213 DVLOG(1) << "Required key '" << kFinalOffsetHeaderKey << "' not present"; | |
214 return false; | |
215 } | |
216 | |
217 // TODO(rjshade): Check for other forbidden keys, following the HTTP/2 spec. | |
218 | |
219 DVLOG(1) << "Successfully parsed Trailers: " << trailers->DebugString(); | |
220 return true; | |
221 } | |
222 | |
223 // static | |
224 string SpdyUtils::GetUrlFromHeaderBlock(const SpdyHeaderBlock& headers) { | |
225 SpdyHeaderBlock::const_iterator it = headers.find(":scheme"); | |
226 if (it == headers.end()) { | |
227 return ""; | |
228 } | |
229 std::string url = it->second.as_string(); | |
230 | |
231 url.append("://"); | |
232 | |
233 it = headers.find(":authority"); | |
234 if (it == headers.end()) { | |
235 return ""; | |
236 } | |
237 url.append(it->second.as_string()); | |
238 | |
239 it = headers.find(":path"); | |
240 if (it == headers.end()) { | |
241 return ""; | |
242 } | |
243 url.append(it->second.as_string()); | |
244 return url; | |
245 } | |
246 | |
247 // static | |
248 string SpdyUtils::GetHostNameFromHeaderBlock(const SpdyHeaderBlock& headers) { | |
249 return GURL(GetUrlFromHeaderBlock(headers)).host(); | |
250 } | |
251 | |
252 // static | |
253 bool SpdyUtils::UrlIsValid(const SpdyHeaderBlock& headers) { | |
254 string url(GetUrlFromHeaderBlock(headers)); | |
255 return url != "" && GURL(url).is_valid(); | |
256 } | |
257 | |
258 } // namespace net | |
OLD | NEW |