Fix: Data races on cursor state in OpenGL backends
[openttd-github.git] / src / network / core / tcp_http.cpp
blobbe5a2c39a7e43c167b94bfbc31711e1a90ee6486
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 */
8 /**
9 * @file tcp_http.cpp Basic functions to receive and send HTTP TCP packets.
12 #include "../../stdafx.h"
13 #include "../../debug.h"
14 #include "../../rev.h"
15 #include "../network_func.h"
17 #include "tcp_http.h"
19 #include "../../safeguards.h"
21 /** List of open HTTP connections. */
22 static std::vector<NetworkHTTPSocketHandler *> _http_connections;
24 /**
25 * Start the querying
26 * @param s the socket of this connection
27 * @param callback the callback for HTTP retrieval
28 * @param host the hostname of the server to connect to
29 * @param url the url at the server
30 * @param data the data to send
31 * @param depth the depth (redirect recursion) of the queries
33 NetworkHTTPSocketHandler::NetworkHTTPSocketHandler(SOCKET s,
34 HTTPCallback *callback, const char *host, const char *url,
35 const char *data, int depth) :
36 NetworkSocketHandler(),
37 recv_pos(0),
38 recv_length(0),
39 callback(callback),
40 data(data),
41 redirect_depth(depth),
42 sock(s)
44 size_t bufferSize = strlen(url) + strlen(host) + strlen(GetNetworkRevisionString()) + (data == nullptr ? 0 : strlen(data)) + 128;
45 char *buffer = AllocaM(char, bufferSize);
47 DEBUG(net, 7, "[tcp/http] requesting %s%s", host, url);
48 if (data != nullptr) {
49 seprintf(buffer, buffer + bufferSize - 1, "POST %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: OpenTTD/%s\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s\r\n", url, host, GetNetworkRevisionString(), (int)strlen(data), data);
50 } else {
51 seprintf(buffer, buffer + bufferSize - 1, "GET %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: OpenTTD/%s\r\n\r\n", url, host, GetNetworkRevisionString());
54 ssize_t size = strlen(buffer);
55 ssize_t res = send(this->sock, (const char*)buffer, size, 0);
56 if (res != size) {
57 /* Sending all data failed. Socket can't handle this little bit
58 * of information? Just fall back to the old system! */
59 this->callback->OnFailure();
60 delete this;
61 return;
64 _http_connections.push_back(this);
67 /** Free whatever needs to be freed. */
68 NetworkHTTPSocketHandler::~NetworkHTTPSocketHandler()
70 this->CloseConnection();
72 if (this->sock != INVALID_SOCKET) closesocket(this->sock);
73 this->sock = INVALID_SOCKET;
74 free(this->data);
77 NetworkRecvStatus NetworkHTTPSocketHandler::CloseConnection(bool error)
79 NetworkSocketHandler::CloseConnection(error);
80 return NETWORK_RECV_STATUS_OKAY;
83 /**
84 * Helper to simplify the error handling.
85 * @param msg the error message to show.
87 #define return_error(msg) { DEBUG(net, 0, msg); return -1; }
89 static const char * const NEWLINE = "\r\n"; ///< End of line marker
90 static const char * const END_OF_HEADER = "\r\n\r\n"; ///< End of header marker
91 static const char * const HTTP_1_0 = "HTTP/1.0 "; ///< Preamble for HTTP 1.0 servers
92 static const char * const HTTP_1_1 = "HTTP/1.1 "; ///< Preamble for HTTP 1.1 servers
93 static const char * const CONTENT_LENGTH = "Content-Length: "; ///< Header for the length of the content
94 static const char * const LOCATION = "Location: "; ///< Header for location
96 /**
97 * Handle the header of a HTTP reply.
98 * @return amount of data to continue downloading.
99 * > 0: we need to download N bytes.
100 * = 0: we're being redirected.
101 * < 0: an error occurred. Downloading failed.
102 * @note if an error occurred the header might not be in its
103 * original state. No effort is undertaken to bring
104 * the header in its original state.
106 int NetworkHTTPSocketHandler::HandleHeader()
108 assert(strlen(HTTP_1_0) == strlen(HTTP_1_1));
109 assert(strstr(this->recv_buffer, END_OF_HEADER) != nullptr);
111 /* We expect a HTTP/1.[01] reply */
112 if (strncmp(this->recv_buffer, HTTP_1_0, strlen(HTTP_1_0)) != 0 &&
113 strncmp(this->recv_buffer, HTTP_1_1, strlen(HTTP_1_1)) != 0) {
114 return_error("[tcp/http] received invalid HTTP reply");
117 char *status = this->recv_buffer + strlen(HTTP_1_0);
118 if (strncmp(status, "200", 3) == 0) {
119 /* We are going to receive a document. */
121 /* Get the length of the document to receive */
122 char *length = strcasestr(this->recv_buffer, CONTENT_LENGTH);
123 if (length == nullptr) return_error("[tcp/http] missing 'content-length' header");
125 /* Skip the header */
126 length += strlen(CONTENT_LENGTH);
128 /* Search the end of the line. This is safe because the header will
129 * always end with two newlines. */
130 char *end_of_line = strstr(length, NEWLINE);
132 /* Read the length */
133 *end_of_line = '\0';
134 int len = atoi(length);
135 /* Restore the header. */
136 *end_of_line = '\r';
138 /* Make sure we're going to download at least something;
139 * zero sized files are, for OpenTTD's purposes, always
140 * wrong. You can't have gzips of 0 bytes! */
141 if (len == 0) return_error("[tcp/http] refusing to download 0 bytes");
143 DEBUG(net, 7, "[tcp/http] downloading %i bytes", len);
144 return len;
147 if (strncmp(status, "301", 3) != 0 &&
148 strncmp(status, "302", 3) != 0 &&
149 strncmp(status, "303", 3) != 0 &&
150 strncmp(status, "307", 3) != 0) {
151 /* We are not going to be redirected :(. */
153 /* Search the end of the line. This is safe because the header will
154 * always end with two newlines. */
155 *strstr(status, NEWLINE) = '\0';
156 DEBUG(net, 0, "[tcp/http] unhandled status reply %s", status);
157 return -1;
160 if (this->redirect_depth == 5) return_error("[tcp/http] too many redirects, looping redirects?");
162 /* Redirect to other URL */
163 char *uri = strcasestr(this->recv_buffer, LOCATION);
164 if (uri == nullptr) return_error("[tcp/http] missing 'location' header for redirect");
166 uri += strlen(LOCATION);
168 /* Search the end of the line. This is safe because the header will
169 * always end with two newlines. */
170 char *end_of_line = strstr(uri, NEWLINE);
171 *end_of_line = '\0';
173 DEBUG(net, 6, "[tcp/http] redirecting to %s", uri);
175 int ret = NetworkHTTPSocketHandler::Connect(uri, this->callback, this->data, this->redirect_depth + 1);
176 if (ret != 0) return ret;
178 /* We've relinquished control of data now. */
179 this->data = nullptr;
181 /* Restore the header. */
182 *end_of_line = '\r';
183 return 0;
187 * Connect to the given URI.
188 * @param uri the URI to connect to.
189 * @param callback the callback to send data back on.
190 * @param data the data we want to send (as POST).
191 * @param depth the recursion/redirect depth.
193 /* static */ int NetworkHTTPSocketHandler::Connect(char *uri, HTTPCallback *callback, const char *data, int depth)
195 char *hname = strstr(uri, "://");
196 if (hname == nullptr) return_error("[tcp/http] invalid location");
198 hname += 3;
200 char *url = strchr(hname, '/');
201 if (url == nullptr) return_error("[tcp/http] invalid location");
203 *url = '\0';
205 /* Fetch the hostname, and possible port number. */
206 const char *company = nullptr;
207 const char *port = nullptr;
208 ParseConnectionString(&company, &port, hname);
209 if (company != nullptr) return_error("[tcp/http] invalid hostname");
211 NetworkAddress address(hname, port == nullptr ? 80 : atoi(port));
213 /* Restore the URL. */
214 *url = '/';
215 new NetworkHTTPContentConnecter(address, callback, url, data, depth);
216 return 0;
219 #undef return_error
222 * Handle receiving of HTTP data.
223 * @return state of the receival of HTTP data.
224 * > 0: we need more cycles for downloading
225 * = 0: we are done downloading
226 * < 0: we have hit an error
228 int NetworkHTTPSocketHandler::Receive()
230 for (;;) {
231 ssize_t res = recv(this->sock, (char *)this->recv_buffer + this->recv_pos, lengthof(this->recv_buffer) - this->recv_pos, 0);
232 if (res == -1) {
233 int err = GET_LAST_ERROR();
234 if (err != EWOULDBLOCK) {
235 /* Something went wrong... (104 is connection reset by peer) */
236 if (err != 104) DEBUG(net, 0, "recv failed with error %d", err);
237 return -1;
239 /* Connection would block, so stop for now */
240 return 1;
243 /* No more data... did we get everything we wanted? */
244 if (res == 0) {
245 if (this->recv_length != 0) return -1;
247 this->callback->OnReceiveData(nullptr, 0);
248 return 0;
251 /* Wait till we read the end-of-header identifier */
252 if (this->recv_length == 0) {
253 ssize_t read = this->recv_pos + res;
254 ssize_t end = std::min<ssize_t>(read, lengthof(this->recv_buffer) - 1);
256 /* Do a 'safe' search for the end of the header. */
257 char prev = this->recv_buffer[end];
258 this->recv_buffer[end] = '\0';
259 char *end_of_header = strstr(this->recv_buffer, END_OF_HEADER);
260 this->recv_buffer[end] = prev;
262 if (end_of_header == nullptr) {
263 if (read == lengthof(this->recv_buffer)) {
264 DEBUG(net, 0, "[tcp/http] header too big");
265 return -1;
267 this->recv_pos = read;
268 } else {
269 int ret = this->HandleHeader();
270 if (ret <= 0) return ret;
272 this->recv_length = ret;
274 end_of_header += strlen(END_OF_HEADER);
275 int len = std::min(read - (end_of_header - this->recv_buffer), res);
276 if (len != 0) {
277 this->callback->OnReceiveData(end_of_header, len);
278 this->recv_length -= len;
281 this->recv_pos = 0;
283 } else {
284 res = std::min<ssize_t>(this->recv_length, res);
285 /* Receive whatever we're expecting. */
286 this->callback->OnReceiveData(this->recv_buffer, res);
287 this->recv_length -= res;
293 * Do the receiving for all HTTP connections.
295 /* static */ void NetworkHTTPSocketHandler::HTTPReceive()
297 /* No connections, just bail out. */
298 if (_http_connections.size() == 0) return;
300 fd_set read_fd;
301 struct timeval tv;
303 FD_ZERO(&read_fd);
304 for (NetworkHTTPSocketHandler *handler : _http_connections) {
305 FD_SET(handler->sock, &read_fd);
308 tv.tv_sec = tv.tv_usec = 0; // don't block at all.
309 int n = select(FD_SETSIZE, &read_fd, nullptr, nullptr, &tv);
310 if (n == -1) return;
312 for (auto iter = _http_connections.begin(); iter < _http_connections.end(); /* nothing */) {
313 NetworkHTTPSocketHandler *cur = *iter;
315 if (FD_ISSET(cur->sock, &read_fd)) {
316 int ret = cur->Receive();
317 /* First send the failure. */
318 if (ret < 0) cur->callback->OnFailure();
319 if (ret <= 0) {
320 /* Then... the connection can be closed */
321 cur->CloseConnection();
322 iter = _http_connections.erase(iter);
323 delete cur;
324 continue;
327 iter++;