12a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved. 22a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 32a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// found in the LICENSE file. 42a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 5b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)#include "net/test/embedded_test_server/http_request.h" 62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 72a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include <algorithm> 82a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 92a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "base/logging.h" 10868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "base/strings/string_util.h" 112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "base/strings/string_number_conversions.h" 122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "base/strings/string_split.h" 132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 14b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)namespace net { 152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)namespace test_server { 162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)namespace { 182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)size_t kRequestSizeLimit = 64 * 1024 * 1024; // 64 mb. 202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Helper function used to trim tokens in http request headers. 222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)std::string Trim(const std::string& value) { 232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) std::string result; 24a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) base::TrimString(value, " \t", &result); 252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return result; 262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} // namespace 292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)HttpRequest::HttpRequest() : method(METHOD_UNKNOWN), 312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) has_content(false) { 322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)HttpRequest::~HttpRequest() { 352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)HttpRequestParser::HttpRequestParser() 382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) : http_request_(new HttpRequest()), 392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) buffer_position_(0), 402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) state_(STATE_HEADERS), 412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) declared_content_length_(0) { 422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)HttpRequestParser::~HttpRequestParser() { 452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)void HttpRequestParser::ProcessChunk(const base::StringPiece& data) { 482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) data.AppendToString(&buffer_); 492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) DCHECK_LE(buffer_.size() + data.size(), kRequestSizeLimit) << 502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) "The HTTP request is too large."; 512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)std::string HttpRequestParser::ShiftLine() { 542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) size_t eoln_position = buffer_.find("\r\n", buffer_position_); 552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) DCHECK_NE(std::string::npos, eoln_position); 562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) const int line_length = eoln_position - buffer_position_; 572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) std::string result = buffer_.substr(buffer_position_, line_length); 582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) buffer_position_ += line_length + 2; 592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return result; 602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)HttpRequestParser::ParseResult HttpRequestParser::ParseRequest() { 632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) DCHECK_NE(STATE_ACCEPTED, state_); 642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // Parse the request from beginning. However, entire request may not be 652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // available in the buffer. 662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (state_ == STATE_HEADERS) { 672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (ParseHeaders() == ACCEPTED) 682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return ACCEPTED; 692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // This should not be 'else if' of the previous block, as |state_| can be 712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // changed in ParseHeaders(). 722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (state_ == STATE_CONTENT) { 732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (ParseContent() == ACCEPTED) 742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return ACCEPTED; 752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return WAITING; 772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)HttpRequestParser::ParseResult HttpRequestParser::ParseHeaders() { 802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // Check if the all request headers are available. 812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (buffer_.find("\r\n\r\n", buffer_position_) == std::string::npos) 822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return WAITING; 832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // Parse request's the first header line. 852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // Request main main header, eg. GET /foobar.html HTTP/1.1 862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) { 872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) const std::string header_line = ShiftLine(); 882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) std::vector<std::string> header_line_tokens; 892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) base::SplitString(header_line, ' ', &header_line_tokens); 902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) DCHECK_EQ(3u, header_line_tokens.size()); 912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // Method. 926e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) http_request_->method = GetMethodType(base::StringToLowerASCII( 932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) header_line_tokens[0])); 942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // Address. 952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // Don't build an absolute URL as the parser does not know (should not 962a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // know) anything about the server address. 972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) http_request_->relative_url = header_line_tokens[1]; 982a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // Protocol. 996e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) const std::string protocol = 1006e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) base::StringToLowerASCII(header_line_tokens[2]); 1012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) CHECK(protocol == "http/1.0" || protocol == "http/1.1") << 1022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) "Protocol not supported: " << protocol; 1032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 1042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // Parse further headers. 1062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) { 1072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) std::string header_name; 1082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) while (true) { 1092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) std::string header_line = ShiftLine(); 1102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (header_line.empty()) 1112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) break; 1122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (header_line[0] == ' ' || header_line[0] == '\t') { 1142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // Continuation of the previous multi-line header. 1152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) std::string header_value = 1162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) Trim(header_line.substr(1, header_line.size() - 1)); 1172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) http_request_->headers[header_name] += " " + header_value; 1182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } else { 1192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // New header. 1202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) size_t delimiter_pos = header_line.find(":"); 1212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) DCHECK_NE(std::string::npos, delimiter_pos) << "Syntax error."; 1222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) header_name = Trim(header_line.substr(0, delimiter_pos)); 1232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) std::string header_value = Trim(header_line.substr( 1242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) delimiter_pos + 1, 1252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) header_line.size() - delimiter_pos - 1)); 1262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) http_request_->headers[header_name] = header_value; 1272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 1282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 1292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 1302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // Headers done. Is any content data attached to the request? 1322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) declared_content_length_ = 0; 1332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (http_request_->headers.count("Content-Length") > 0) { 1342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) http_request_->has_content = true; 1352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) const bool success = base::StringToSizeT( 1362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) http_request_->headers["Content-Length"], 1372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) &declared_content_length_); 1382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) DCHECK(success) << "Malformed Content-Length header's value."; 1392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 1402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (declared_content_length_ == 0) { 1412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // No content data, so parsing is finished. 1422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) state_ = STATE_ACCEPTED; 1432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return ACCEPTED; 1442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 1452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // The request has not yet been parsed yet, content data is still to be 1472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // processed. 1482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) state_ = STATE_CONTENT; 1492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return WAITING; 1502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 1512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)HttpRequestParser::ParseResult HttpRequestParser::ParseContent() { 1532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) const size_t available_bytes = buffer_.size() - buffer_position_; 1542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) const size_t fetch_bytes = std::min( 1552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) available_bytes, 1562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) declared_content_length_ - http_request_->content.size()); 1572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) http_request_->content.append(buffer_.data() + buffer_position_, 1582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) fetch_bytes); 1592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) buffer_position_ += fetch_bytes; 1602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (declared_content_length_ == http_request_->content.size()) { 1622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) state_ = STATE_ACCEPTED; 1632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return ACCEPTED; 1642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 1652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) state_ = STATE_CONTENT; 1672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return WAITING; 1682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 1692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)scoped_ptr<HttpRequest> HttpRequestParser::GetRequest() { 1712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) DCHECK_EQ(STATE_ACCEPTED, state_); 1722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) scoped_ptr<HttpRequest> result = http_request_.Pass(); 1732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // Prepare for parsing a new request. 1752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) state_ = STATE_HEADERS; 1762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) http_request_.reset(new HttpRequest()); 1772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) buffer_.clear(); 1782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) buffer_position_ = 0; 1792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) declared_content_length_ = 0; 1802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return result.Pass(); 1822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 1832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)HttpMethod HttpRequestParser::GetMethodType(const std::string& token) const { 1852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (token == "get") { 1862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return METHOD_GET; 1872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } else if (token == "head") { 1882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return METHOD_HEAD; 1892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } else if (token == "post") { 1902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return METHOD_POST; 1912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } else if (token == "put") { 1922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return METHOD_PUT; 1932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } else if (token == "delete") { 1942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return METHOD_DELETE; 1952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } else if (token == "patch") { 1962a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return METHOD_PATCH; 1972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 1982a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) NOTREACHED() << "Method not implemented: " << token; 1992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return METHOD_UNKNOWN; 2002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 2012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} // namespace test_server 203b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)} // namespace net 204