OpenTTD Source: src/network/core/tcp_http.cpp Source File

OpenTTD Source 13.1

tcp_http.cpp
Go to the documentation of this file.
1 /*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
7
12 #include "../../stdafx.h"
13 #include "../../debug.h"
14 #include "../../rev.h"
15 #include "../network_internal.h"
16 #include "game_info.h"
17
18 #include "tcp_http.h"
19
20 #include "../../safeguards.h"
21
23 static std::vector<NetworkHTTPSocketHandler *> _http_connections;
24
34 NetworkHTTPSocketHandler::NetworkHTTPSocketHandler(SOCKET s,
35 HTTPCallback *callback, const std::string &host, const char *url,
36 const char *data, int depth) :
37 NetworkSocketHandler(),
38 recv_pos(0),
39 recv_length(0),
40 callback(callback),
41 data(data),
42 redirect_depth(depth),
43 sock(s)
44 {
45 Debug(net, 5, "[tcp/http] Requesting {}{}", host, url);
46 std::string request;
47 if (data != nullptr) {
48 request = fmt::format("POST {} HTTP/1.0\r\nHost: {}\r\nUser-Agent: OpenTTD/{}\r\nContent-Type: text/plain\r\nContent-Length: {}\r\n\r\n{}\r\n", url, host, GetNetworkRevisionString(), strlen(data), data);
49 } else {
50 request = fmt::format("GET {} HTTP/1.0\r\nHost: {}\r\nUser-Agent: OpenTTD/{}\r\n\r\n", url, host, GetNetworkRevisionString());
51 }
52
53 ssize_t res = send(this->sock, request.data(), (int)request.size(), 0);
54 if (res != (ssize_t)request.size()) {
55 /* Sending all data failed. Socket can't handle this little bit
56 * of information? Just fall back to the old system! */
57 this->callback->OnFailure();
58 delete this;
59 return;
60 }
61
62 _http_connections.push_back(this);
63 }
64
66 NetworkHTTPSocketHandler::~NetworkHTTPSocketHandler()
67 {
68 this->CloseSocket();
69
70 free(this->data);
71 }
72
76 void NetworkHTTPSocketHandler::CloseSocket()
77 {
78 if (this->sock != INVALID_SOCKET) closesocket(this->sock);
79 this->sock = INVALID_SOCKET;
80 }
81
86 #define return_error(msg) { Debug(net, 1, msg); return -1; }
87
88 static const char * const NEWLINE = "\r\n";
89 static const char * const END_OF_HEADER = "\r\n\r\n";
90 static const char * const HTTP_1_0 = "HTTP/1.0 ";
91 static const char * const HTTP_1_1 = "HTTP/1.1 ";
92 static const char * const CONTENT_LENGTH = "Content-Length: ";
93 static const char * const LOCATION = "Location: ";
94
105 int NetworkHTTPSocketHandler::HandleHeader()
106 {
107 assert(strlen(HTTP_1_0) == strlen(HTTP_1_1));
108 assert(strstr(this->recv_buffer, END_OF_HEADER) != nullptr);
109
110 /* We expect a HTTP/1.[01] reply */
111 if (strncmp(this->recv_buffer, HTTP_1_0, strlen(HTTP_1_0)) != 0 &&
112 strncmp(this->recv_buffer, HTTP_1_1, strlen(HTTP_1_1)) != 0) {
113 return_error("[tcp/http] Received invalid HTTP reply");
114 }
115
116 char *status = this->recv_buffer + strlen(HTTP_1_0);
117 if (strncmp(status, "200", 3) == 0) {
118 /* We are going to receive a document. */
119
120 /* Get the length of the document to receive */
121 char *length = strcasestr(this->recv_buffer, CONTENT_LENGTH);
122 if (length == nullptr) return_error("[tcp/http] Missing 'content-length' header");
123
124 /* Skip the header */
125 length += strlen(CONTENT_LENGTH);
126
127 /* Search the end of the line. This is safe because the header will
128 * always end with two newlines. */
129 char *end_of_line = strstr(length, NEWLINE);
130
131 /* Read the length */
132 *end_of_line = '\0';
133 int len = atoi(length);
134 /* Restore the header. */
135 *end_of_line = '\r';
136
137 /* Make sure we're going to download at least something;
138 * zero sized files are, for OpenTTD's purposes, always
139 * wrong. You can't have gzips of 0 bytes! */
140 if (len == 0) return_error("[tcp/http] Refusing to download 0 bytes");
141
142 Debug(net, 7, "[tcp/http] Downloading {} bytes", len);
143 return len;
144 }
145
146 if (strncmp(status, "301", 3) != 0 &&
147 strncmp(status, "302", 3) != 0 &&
148 strncmp(status, "303", 3) != 0 &&
149 strncmp(status, "307", 3) != 0) {
150 /* We are not going to be redirected :(. */
151
152 /* Search the end of the line. This is safe because the header will
153 * always end with two newlines. */
154 *strstr(status, NEWLINE) = '\0';
155 Debug(net, 1, "[tcp/http] Unhandled status reply {}", status);
156 return -1;
157 }
158
159 if (this->redirect_depth == 5) return_error("[tcp/http] Too many redirects, looping redirects?");
160
161 /* Redirect to other URL */
162 char *uri = strcasestr(this->recv_buffer, LOCATION);
163 if (uri == nullptr) return_error("[tcp/http] Missing 'location' header for redirect");
164
165 uri += strlen(LOCATION);
166
167 /* Search the end of the line. This is safe because the header will
168 * always end with two newlines. */
169 char *end_of_line = strstr(uri, NEWLINE);
170 *end_of_line = '\0';
171
172 Debug(net, 7, "[tcp/http] Redirecting to {}", uri);
173
174 int ret = NetworkHTTPSocketHandler::Connect(uri, this->callback, this->data, this->redirect_depth + 1);
175 if (ret != 0) return ret;
176
177 /* We've relinquished control of data now. */
178 this->data = nullptr;
179
180 /* Restore the header. */
181 *end_of_line = '\r';
182 return 0;
183 }
184
192 /* static */ int NetworkHTTPSocketHandler::Connect(char *uri, HTTPCallback *callback, const char *data, int depth)
193 {
194 char *hname = strstr(uri, "://");
195 if (hname == nullptr) return_error("[tcp/http] Invalid location");
196
197 hname += 3;
198
199 char *url = strchr(hname, '/');
200 if (url == nullptr) return_error("[tcp/http] Invalid location");
201
202 *url = '\0';
203
204 std::string hostname = std::string(hname);
205
206 /* Restore the URL. */
207 *url = '/';
208 new NetworkHTTPContentConnecter(hostname, callback, url, data, depth);
209 return 0;
210 }
211
212 #undef return_error
213
221 int NetworkHTTPSocketHandler::Receive()
222 {
223 for (;;) {
224 ssize_t res = recv(this->sock, (char *)this->recv_buffer + this->recv_pos, lengthof(this->recv_buffer) - this->recv_pos, 0);
225 if (res == -1) {
226 NetworkError err = NetworkError::GetLast();
227 if (!err.WouldBlock()) {
228 /* Something went wrong... */
229 if (!err.IsConnectionReset()) Debug(net, 0, "Recv failed: {}", err.AsString());
230 return -1;
231 }
232 /* Connection would block, so stop for now */
233 return 1;
234 }
235
236 /* No more data... did we get everything we wanted? */
237 if (res == 0) {
238 if (this->recv_length != 0) return -1;
239
240 this->callback->OnReceiveData(nullptr, 0);
241 return 0;
242 }
243
244 /* Wait till we read the end-of-header identifier */
245 if (this->recv_length == 0) {
246 ssize_t read = this->recv_pos + res;
247 ssize_t end = std::min<ssize_t>(read, lengthof(this->recv_buffer) - 1);
248
249 /* Do a 'safe' search for the end of the header. */
250 char prev = this->recv_buffer[end];
251 this->recv_buffer[end] = '\0';
252 char *end_of_header = strstr(this->recv_buffer, END_OF_HEADER);
253 this->recv_buffer[end] = prev;
254
255 if (end_of_header == nullptr) {
256 if (read == lengthof(this->recv_buffer)) {
257 Debug(net, 1, "[tcp/http] Header too big");
258 return -1;
259 }
260 this->recv_pos = read;
261 } else {
262 int ret = this->HandleHeader();
263 if (ret <= 0) return ret;
264
265 this->recv_length = ret;
266
267 end_of_header += strlen(END_OF_HEADER);
268 int len = std::min(read - (end_of_header - this->recv_buffer), res);
269 if (len != 0) {
270 this->callback->OnReceiveData(end_of_header, len);
271 this->recv_length -= len;
272 }
273
274 this->recv_pos = 0;
275 }
276 } else {
277 res = std::min<ssize_t>(this->recv_length, res);
278 /* Receive whatever we're expecting. */
279 this->callback->OnReceiveData(this->recv_buffer, res);
280 this->recv_length -= res;
281 }
282 }
283 }
284
288 /* static */ void NetworkHTTPSocketHandler::HTTPReceive()
289 {
290 /* No connections, just bail out. */
291 if (_http_connections.size() == 0) return;
292
293 fd_set read_fd;
294 struct timeval tv;
295
296 FD_ZERO(&read_fd);
297 for (NetworkHTTPSocketHandler *handler : _http_connections) {
298 FD_SET(handler->sock, &read_fd);
299 }
300
301 tv.tv_sec = tv.tv_usec = 0; // don't block at all.
302 int n = select(FD_SETSIZE, &read_fd, nullptr, nullptr, &tv);
303 if (n == -1) return;
304
305 for (auto iter = _http_connections.begin(); iter < _http_connections.end(); /* nothing */) {
306 NetworkHTTPSocketHandler *cur = *iter;
307
308 if (FD_ISSET(cur->sock, &read_fd)) {
309 int ret = cur->Receive();
310 /* First send the failure. */
311 if (ret < 0) cur->callback->OnFailure();
312 if (ret <= 0) {
313 /* Then... the connection can be closed */
314 cur->CloseSocket();
315 iter = _http_connections.erase(iter);
316 delete cur;
317 continue;
318 }
319 }
320 iter++;
321 }
322 }
END_OF_HEADER
static const char *const END_OF_HEADER
End of header marker.
Definition: tcp_http.cpp:89
NetworkHTTPSocketHandler
Base socket handler for HTTP traffic.
Definition: tcp_http.h:38
NetworkHTTPSocketHandler::callback
HTTPCallback * callback
The callback to call for the incoming data.
Definition: tcp_http.h:43
HTTPCallback
Callback for when the HTTP handler has something to tell us.
Definition: tcp_http.h:18
tcp_http.h
NetworkSocketHandler
SocketHandler for all network sockets in OpenTTD.
Definition: core.h:42
NetworkHTTPSocketHandler::data
const char * data
The (POST) data we might want to forward (to a redirect).
Definition: tcp_http.h:44
HTTPCallback::OnReceiveData
virtual void OnReceiveData(const char *data, size_t length)=0
We're receiving data.
GetNetworkRevisionString
std::string_view GetNetworkRevisionString()
Get the network version string used by this build.
Definition: game_info.cpp:43
LOCATION
static const char *const LOCATION
Header for location.
Definition: tcp_http.cpp:93
NetworkHTTPSocketHandler::~NetworkHTTPSocketHandler
~NetworkHTTPSocketHandler()
Free whatever needs to be freed.
Definition: tcp_http.cpp:66
NetworkHTTPContentConnecter
Connect with a HTTP server and do ONE query.
Definition: tcp_http.h:75
NetworkHTTPSocketHandler::NetworkHTTPSocketHandler
NetworkHTTPSocketHandler(SOCKET sock, HTTPCallback *callback, const std::string &host, const char *url, const char *data, int depth)
Start the querying.
Definition: tcp_http.cpp:34
NetworkHTTPSocketHandler::redirect_depth
int redirect_depth
The depth of the redirection.
Definition: tcp_http.h:45
NetworkHTTPSocketHandler::recv_pos
int recv_pos
Current position in buffer.
Definition: tcp_http.h:41
NetworkHTTPSocketHandler::recv_buffer
char recv_buffer[4096]
Partially received message.
Definition: tcp_http.h:40
HTTP_1_1
static const char *const HTTP_1_1
Preamble for HTTP 1.1 servers.
Definition: tcp_http.cpp:91
NetworkError::WouldBlock
bool WouldBlock() const
Check whether this error describes that the operation would block.
Definition: os_abstraction.cpp:38
NetworkError::GetLast
static NetworkError GetLast()
Get the last network error.
Definition: os_abstraction.cpp:116
NetworkHTTPSocketHandler::CloseSocket
void CloseSocket()
Close the actual socket of the connection.
Definition: tcp_http.cpp:76
_http_connections
static std::vector< NetworkHTTPSocketHandler * > _http_connections
List of open HTTP connections.
Definition: tcp_http.cpp:23
HTTPCallback::OnFailure
virtual void OnFailure()=0
An error has occurred and the connection has been closed.
NetworkHTTPSocketHandler::Connect
static int Connect(char *uri, HTTPCallback *callback, const char *data=nullptr, int depth=0)
Connect to the given URI.
Definition: tcp_http.cpp:192
NetworkHTTPSocketHandler::Receive
int Receive()
Handle receiving of HTTP data.
Definition: tcp_http.cpp:221
HTTP_1_0
static const char *const HTTP_1_0
Preamble for HTTP 1.0 servers.
Definition: tcp_http.cpp:90
NetworkHTTPSocketHandler::HTTPReceive
static void HTTPReceive()
Do the receiving for all HTTP connections.
Definition: tcp_http.cpp:288
NetworkError::IsConnectionReset
bool IsConnectionReset() const
Check whether this error describes a connection reset.
Definition: os_abstraction.cpp:53
NetworkError::AsString
const std::string & AsString() const
Get the string representation of the error message.
Definition: os_abstraction.cpp:79
game_info.h
NetworkError
Abstraction of a network error where all implementation details of the error codes are encapsulated i...
Definition: os_abstraction.h:21
return_error
#define return_error(msg)
Helper to simplify the error handling.
Definition: tcp_http.cpp:86
NEWLINE
static const char *const NEWLINE
End of line marker.
Definition: tcp_http.cpp:88
NetworkHTTPSocketHandler::recv_length
int recv_length
Length of the data still retrieving.
Definition: tcp_http.h:42
Debug
#define Debug(name, level, format_string,...)
Ouptut a line of debugging information.
Definition: debug.h:37
lengthof
#define lengthof(x)
Return the length of an fixed size array.
Definition: stdafx.h:386
free
static void free(const void *ptr)
Version of the standard free that accepts const pointers.
Definition: stdafx.h:470
CONTENT_LENGTH
static const char *const CONTENT_LENGTH
Header for the length of the content.
Definition: tcp_http.cpp:92
NetworkHTTPSocketHandler::sock
SOCKET sock
The socket currently connected to.
Definition: tcp_http.h:50
NetworkHTTPSocketHandler::HandleHeader
int HandleHeader()
Handle the header of a HTTP reply.
Definition: tcp_http.cpp:105