• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
7 #pragma allow_unsafe_buffers
8 #endif
9 
10 #include "net/test/embedded_test_server/http_request.h"
11 
12 #include <algorithm>
13 #include <string_view>
14 #include <utility>
15 
16 #include "base/logging.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/string_split.h"
19 #include "base/strings/string_util.h"
20 #include "net/base/host_port_pair.h"
21 #include "net/http/http_chunked_decoder.h"
22 #include "url/gurl.h"
23 
24 namespace net::test_server {
25 
26 namespace {
27 
28 size_t kRequestSizeLimit = 64 * 1024 * 1024;  // 64 mb.
29 
30 // Helper function used to trim tokens in http request headers.
Trim(const std::string & value)31 std::string Trim(const std::string& value) {
32   std::string result;
33   base::TrimString(value, " \t", &result);
34   return result;
35 }
36 
37 }  // namespace
38 
39 HttpRequest::HttpRequest() = default;
40 
41 HttpRequest::HttpRequest(const HttpRequest& other) = default;
42 
43 HttpRequest::~HttpRequest() = default;
44 
GetURL() const45 GURL HttpRequest::GetURL() const {
46   if (base_url.is_valid())
47     return base_url.Resolve(relative_url);
48   return GURL("http://localhost" + relative_url);
49 }
50 
HttpRequestParser()51 HttpRequestParser::HttpRequestParser()
52     : http_request_(std::make_unique<HttpRequest>()) {}
53 
54 HttpRequestParser::~HttpRequestParser() = default;
55 
ProcessChunk(std::string_view data)56 void HttpRequestParser::ProcessChunk(std::string_view data) {
57   buffer_.append(data);
58   DCHECK_LE(buffer_.size() + data.size(), kRequestSizeLimit) <<
59       "The HTTP request is too large.";
60 }
61 
ShiftLine()62 std::string HttpRequestParser::ShiftLine() {
63   size_t eoln_position = buffer_.find("\r\n", buffer_position_);
64   DCHECK_NE(std::string::npos, eoln_position);
65   const int line_length = eoln_position - buffer_position_;
66   std::string result = buffer_.substr(buffer_position_, line_length);
67   buffer_position_ += line_length + 2;
68   return result;
69 }
70 
ParseRequest()71 HttpRequestParser::ParseResult HttpRequestParser::ParseRequest() {
72   DCHECK_NE(STATE_ACCEPTED, state_);
73   // Parse the request from beginning. However, entire request may not be
74   // available in the buffer.
75   if (state_ == STATE_HEADERS) {
76     if (ParseHeaders() == ACCEPTED)
77       return ACCEPTED;
78   }
79   // This should not be 'else if' of the previous block, as |state_| can be
80   // changed in ParseHeaders().
81   if (state_ == STATE_CONTENT) {
82     if (ParseContent() == ACCEPTED)
83       return ACCEPTED;
84   }
85   return WAITING;
86 }
87 
ParseHeaders()88 HttpRequestParser::ParseResult HttpRequestParser::ParseHeaders() {
89   // Check if the all request headers are available.
90   if (buffer_.find("\r\n\r\n", buffer_position_) == std::string::npos)
91     return WAITING;
92 
93   // Parse request's the first header line.
94   // Request main main header, eg. GET /foobar.html HTTP/1.1
95   std::string request_headers;
96   {
97     const std::string header_line = ShiftLine();
98     http_request_->all_headers += header_line + "\r\n";
99     std::vector<std::string> header_line_tokens = base::SplitString(
100         header_line, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
101     DCHECK_EQ(3u, header_line_tokens.size());
102     // Method.
103     http_request_->method_string = header_line_tokens[0];
104     http_request_->method = GetMethodType(http_request_->method_string);
105     // Target resource. See
106     // https://www.rfc-editor.org/rfc/rfc9112#name-request-line
107     // https://www.rfc-editor.org/rfc/rfc9110#name-determining-the-target-reso
108     if (http_request_->method == METHOD_CONNECT) {
109       // CONNECT uses a special authority-form. Just report the value as
110       // `relative_url`.
111       // https://www.rfc-editor.org/rfc/rfc9112#section-3.2.3
112       CHECK(!HostPortPair::FromString(header_line_tokens[1]).IsEmpty());
113       http_request_->relative_url = header_line_tokens[1];
114     } else if (http_request_->method == METHOD_OPTIONS &&
115                header_line_tokens[1] == "*") {
116       // OPTIONS allows a special asterisk-form for the request target.
117       // https://www.rfc-editor.org/rfc/rfc9112#section-3.2.4
118       http_request_->relative_url = "*";
119     } else {
120       // The request target should be origin-form, unless connecting through a
121       // proxy, in which case it is absolute-form.
122       // https://www.rfc-editor.org/rfc/rfc9112#name-origin-form
123       // https://www.rfc-editor.org/rfc/rfc9112#name-absolute-form
124       if (!header_line_tokens[1].empty() &&
125           header_line_tokens[1].front() == '/') {
126         http_request_->relative_url = header_line_tokens[1];
127       } else {
128         GURL url(header_line_tokens[1]);
129         CHECK(url.is_valid());
130         // TODO(crbug.com/40242862): This should retain the entire URL.
131         http_request_->relative_url = url.PathForRequest();
132       }
133     }
134 
135     // Protocol.
136     const std::string protocol = base::ToLowerASCII(header_line_tokens[2]);
137     CHECK(protocol == "http/1.0" || protocol == "http/1.1") <<
138         "Protocol not supported: " << protocol;
139   }
140 
141   // Parse further headers.
142   {
143     std::string header_name;
144     while (true) {
145       std::string header_line = ShiftLine();
146       if (header_line.empty())
147         break;
148 
149       http_request_->all_headers += header_line + "\r\n";
150       if (header_line[0] == ' ' || header_line[0] == '\t') {
151         // Continuation of the previous multi-line header.
152         std::string header_value =
153             Trim(header_line.substr(1, header_line.size() - 1));
154         http_request_->headers[header_name] += " " + header_value;
155       } else {
156         // New header.
157         size_t delimiter_pos = header_line.find(":");
158         DCHECK_NE(std::string::npos, delimiter_pos) << "Syntax error.";
159         header_name = Trim(header_line.substr(0, delimiter_pos));
160         std::string header_value = Trim(header_line.substr(
161             delimiter_pos + 1,
162             header_line.size() - delimiter_pos - 1));
163         http_request_->headers[header_name] = header_value;
164       }
165     }
166   }
167 
168   // Headers done. Is any content data attached to the request?
169   declared_content_length_ = 0;
170   if (http_request_->headers.count("Content-Length") > 0) {
171     http_request_->has_content = true;
172     const bool success = base::StringToSizeT(
173         http_request_->headers["Content-Length"],
174         &declared_content_length_);
175     if (!success) {
176       declared_content_length_ = 0;
177       LOG(WARNING) << "Malformed Content-Length header's value.";
178     }
179   } else if (http_request_->headers.count("Transfer-Encoding") > 0) {
180     if (base::EqualsCaseInsensitiveASCII(
181             http_request_->headers["Transfer-Encoding"], "chunked")) {
182       http_request_->has_content = true;
183       chunked_decoder_ = std::make_unique<HttpChunkedDecoder>();
184       state_ = STATE_CONTENT;
185       return WAITING;
186     }
187   }
188   if (declared_content_length_ == 0) {
189     // No content data, so parsing is finished.
190     state_ = STATE_ACCEPTED;
191     return ACCEPTED;
192   }
193 
194   // The request has not yet been parsed yet, content data is still to be
195   // processed.
196   state_ = STATE_CONTENT;
197   return WAITING;
198 }
199 
ParseContent()200 HttpRequestParser::ParseResult HttpRequestParser::ParseContent() {
201   const size_t available_bytes = buffer_.size() - buffer_position_;
202   if (chunked_decoder_.get()) {
203     int bytes_written = chunked_decoder_->FilterBuf(
204         base::as_writable_byte_span(buffer_).subspan(buffer_position_,
205                                                      available_bytes));
206     http_request_->content.append(buffer_.data() + buffer_position_,
207                                   bytes_written);
208 
209     if (chunked_decoder_->reached_eof()) {
210       buffer_ =
211           buffer_.substr(buffer_.size() - chunked_decoder_->bytes_after_eof());
212       buffer_position_ = 0;
213       state_ = STATE_ACCEPTED;
214       return ACCEPTED;
215     }
216     buffer_ = "";
217     buffer_position_ = 0;
218     state_ = STATE_CONTENT;
219     return WAITING;
220   }
221 
222   const size_t fetch_bytes = std::min(
223       available_bytes,
224       declared_content_length_ - http_request_->content.size());
225   http_request_->content.append(buffer_.data() + buffer_position_,
226                                 fetch_bytes);
227   buffer_position_ += fetch_bytes;
228 
229   if (declared_content_length_ == http_request_->content.size()) {
230     state_ = STATE_ACCEPTED;
231     return ACCEPTED;
232   }
233 
234   state_ = STATE_CONTENT;
235   return WAITING;
236 }
237 
GetRequest()238 std::unique_ptr<HttpRequest> HttpRequestParser::GetRequest() {
239   DCHECK_EQ(STATE_ACCEPTED, state_);
240   std::unique_ptr<HttpRequest> result = std::move(http_request_);
241 
242   // Prepare for parsing a new request.
243   state_ = STATE_HEADERS;
244   http_request_ = std::make_unique<HttpRequest>();
245   buffer_.clear();
246   buffer_position_ = 0;
247   declared_content_length_ = 0;
248 
249   return result;
250 }
251 
252 // static
GetMethodType(std::string_view token)253 HttpMethod HttpRequestParser::GetMethodType(std::string_view token) {
254   if (token == "GET") {
255     return METHOD_GET;
256   } else if (token == "HEAD") {
257     return METHOD_HEAD;
258   } else if (token == "POST") {
259     return METHOD_POST;
260   } else if (token == "PUT") {
261     return METHOD_PUT;
262   } else if (token == "DELETE") {
263     return METHOD_DELETE;
264   } else if (token == "PATCH") {
265     return METHOD_PATCH;
266   } else if (token == "CONNECT") {
267     return METHOD_CONNECT;
268   } else if (token == "OPTIONS") {
269     return METHOD_OPTIONS;
270   }
271   LOG(WARNING) << "Method not implemented: " << token;
272   return METHOD_UNKNOWN;
273 }
274 
275 }  // namespace net::test_server
276