blob: 66987dd1a2993153b224f72528e24445dba60b9e [file] [log] [blame]
Andreea Costinas054fbb52020-06-12 20:46:22 +02001// Copyright 2020 The Chromium OS 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 "system-proxy/test_http_server.h"
6
Andreea Costinas8c436b02020-09-18 14:46:54 +02007#include <utility>
8#include <vector>
9
Andreea Costinas054fbb52020-06-12 20:46:22 +020010#include <netinet/in.h>
11#include <sys/socket.h>
12#include <sys/types.h>
13
14#include <base/bind.h>
15#include <base/bind_helpers.h>
16#include <base/callback_helpers.h>
17#include <base/strings/stringprintf.h>
18#include <chromeos/patchpanel/net_util.h>
19#include <chromeos/patchpanel/socket.h>
20
21namespace {
22constexpr int kMaxConn = 10;
Andreea Costinas8c436b02020-09-18 14:46:54 +020023constexpr int kBufSize = 1024;
Andreea Costinas054fbb52020-06-12 20:46:22 +020024
25const std::string_view kConnectionEstablished =
26 "HTTP/1.1 200 Connection established\r\n\r\n";
27
28const std::string_view kProxyAuthenticationRequiredBasic =
29 "HTTP/1.1 407 Proxy Authentication Required\r\n"
30 "Proxy-Authenticate: Basic realm=\"My Proxy\"\r\n"
31 "\r\n";
32
33const std::string_view kProxyAuthenticationRequiredNegotiate =
34 "HTTP/1.1 407 Proxy Authentication Required\r\n"
35 "Proxy-Authenticate: Negotiate realm=\"My Proxy\"\r\n"
36 "\r\n";
37
Andreea Costinasf90a4c02020-06-12 22:30:51 +020038const std::string_view kHttpBadGateway =
39 "HTTP/1.1 502 Bad Gateway\r\n\r\nBag gateway message from the server";
Andreea Costinas054fbb52020-06-12 20:46:22 +020040
41} // namespace
42namespace system_proxy {
43
44HttpTestServer::HttpTestServer()
45 : base::SimpleThread("HttpTestServer"),
46 listening_addr_(htonl(INADDR_LOOPBACK)),
47 listening_port_(0) {}
48
49HttpTestServer::~HttpTestServer() {
50 if (!HasBeenStarted()) {
51 return;
52 }
Andreea Costinas8c436b02020-09-18 14:46:54 +020053 CloseClientSocket();
Andreea Costinas054fbb52020-06-12 20:46:22 +020054 int fd = listening_socket_->release();
55 if (close(fd) != 0) {
Andreea Costinas8c436b02020-09-18 14:46:54 +020056 PLOG(ERROR) << "Failed to close the listening socket";
Andreea Costinas054fbb52020-06-12 20:46:22 +020057 }
58 Join();
59}
60
61void HttpTestServer::Run() {
62 struct sockaddr_storage client_src = {};
63 socklen_t sockaddr_len = sizeof(client_src);
64 while (!expected_responses_.empty()) {
65 if (auto client_conn = listening_socket_->Accept(
66 (struct sockaddr*)&client_src, &sockaddr_len)) {
Andreea Costinas8c436b02020-09-18 14:46:54 +020067 HttpConnectReply response = expected_responses_.front();
Andreea Costinas054fbb52020-06-12 20:46:22 +020068 expected_responses_.pop();
Andreea Costinas8c436b02020-09-18 14:46:54 +020069 std::string_view server_reply = GetConnectReplyString(response);
Andreea Costinas054fbb52020-06-12 20:46:22 +020070 client_conn->SendTo(server_reply.data(), server_reply.size());
Andreea Costinas8c436b02020-09-18 14:46:54 +020071 // Keep the socket to validate data sent by the client after a successful
72 // connection.
73 if (response == HttpConnectReply::kOk) {
74 client_socket_ = std::move(client_conn);
75 }
Andreea Costinas054fbb52020-06-12 20:46:22 +020076 }
77 }
78}
79
Andreea Costinas8c436b02020-09-18 14:46:54 +020080void HttpTestServer::CloseClientSocket() {
81 if (!client_socket_)
82 return;
83 int fd = client_socket_->release();
84 if (close(fd) != 0) {
85 PLOG(ERROR) << "Failed to close the client socket";
86 }
87 client_socket_.reset();
88}
89
Andreea Costinas054fbb52020-06-12 20:46:22 +020090void HttpTestServer::BeforeStart() {
91 listening_socket_ =
92 std::make_unique<patchpanel::Socket>(AF_INET, SOCK_STREAM);
93
94 struct sockaddr_in addr = {0};
95 addr.sin_family = AF_INET;
96 addr.sin_port = htons(listening_port_);
97 addr.sin_addr.s_addr = listening_addr_;
98 if (!listening_socket_->Bind((const struct sockaddr*)&addr, sizeof(addr))) {
99 LOG(ERROR) << "Cannot bind source socket" << std::endl;
100 return;
101 }
102
103 if (!listening_socket_->Listen(kMaxConn)) {
104 LOG(ERROR) << "Cannot listen on source socket." << std::endl;
105 return;
106 }
107
108 socklen_t len = sizeof(addr);
109 if (getsockname(listening_socket_->fd(), (struct sockaddr*)&addr, &len)) {
110 LOG(ERROR) << "Cannot get the listening port " << std::endl;
111 return;
112 }
113 listening_port_ = ntohs(addr.sin_port);
114}
115
116std::string HttpTestServer::GetUrl() {
117 return base::StringPrintf(
118 "http://%s:%d", patchpanel::IPv4AddressToString(listening_addr_).c_str(),
119 listening_port_);
120}
121
Andreea Costinas8c436b02020-09-18 14:46:54 +0200122patchpanel::Socket* HttpTestServer::GetClientSocket() {
123 return client_socket_.get();
124}
125
126// This method is designed for consuming incoming CONNECT requests, which are
127// smaller than |kBufSize| bytes. If larger data is expected, consider doing
128// RecvFrom() in a loop until all data is read.
129void HttpTestServer::ConsumeOutstandingData() {
130 if (!client_socket_)
131 return;
132 std::vector<char> buf(kBufSize);
133 if (client_socket_->RecvFrom(buf.data(), buf.size()) < 0)
134 PLOG(ERROR) << "Error reading from client socket";
135}
136
Andreea Costinas054fbb52020-06-12 20:46:22 +0200137void HttpTestServer::AddHttpConnectReply(HttpConnectReply reply) {
138 expected_responses_.push(reply);
139}
140
141std::string_view HttpTestServer::GetConnectReplyString(HttpConnectReply reply) {
142 switch (reply) {
143 case HttpConnectReply::kOk:
144 return kConnectionEstablished;
145 case HttpConnectReply::kAuthRequiredBasic:
146 return kProxyAuthenticationRequiredBasic;
147 case HttpConnectReply::kAuthRequiredKerberos:
148 return kProxyAuthenticationRequiredNegotiate;
149 case HttpConnectReply::kBadGateway:
150 return kHttpBadGateway;
151 default:
152 return kConnectionEstablished;
153 }
154}
155
156} // namespace system_proxy