OpenTTD Source  1.11.0-beta2
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_func.h"
16 
17 #include "tcp_http.h"
18 
19 #include "../../safeguards.h"
20 
22 static std::vector<NetworkHTTPSocketHandler *> _http_connections;
23 
34  HTTPCallback *callback, const char *host, const char *url,
35  const char *data, int depth) :
37  recv_pos(0),
38  recv_length(0),
39  callback(callback),
40  data(data),
41  redirect_depth(depth),
42  sock(s)
43 {
44  size_t bufferSize = strlen(url) + strlen(host) + strlen(GetNetworkRevisionString()) + (data == nullptr ? 0 : strlen(data)) + 128;
45  char *buffer = AllocaM(char, bufferSize);
46 
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());
52  }
53 
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;
62  }
63 
64  _http_connections.push_back(this);
65 }
66 
69 {
70  this->CloseConnection();
71 
72  if (this->sock != INVALID_SOCKET) closesocket(this->sock);
73  this->sock = INVALID_SOCKET;
74  free(this->data);
75 }
76 
78 {
81 }
82 
87 #define return_error(msg) { DEBUG(net, 0, msg); return -1; }
88 
89 static const char * const NEWLINE = "\r\n";
90 static const char * const END_OF_HEADER = "\r\n\r\n";
91 static const char * const HTTP_1_0 = "HTTP/1.0 ";
92 static const char * const HTTP_1_1 = "HTTP/1.1 ";
93 static const char * const CONTENT_LENGTH = "Content-Length: ";
94 static const char * const LOCATION = "Location: ";
95 
107 {
108  assert(strlen(HTTP_1_0) == strlen(HTTP_1_1));
109  assert(strstr(this->recv_buffer, END_OF_HEADER) != nullptr);
110 
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");
115  }
116 
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. */
120 
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");
124 
125  /* Skip the header */
126  length += strlen(CONTENT_LENGTH);
127 
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);
131 
132  /* Read the length */
133  *end_of_line = '\0';
134  int len = atoi(length);
135  /* Restore the header. */
136  *end_of_line = '\r';
137 
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");
142 
143  DEBUG(net, 7, "[tcp/http] downloading %i bytes", len);
144  return len;
145  }
146 
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 :(. */
152 
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;
158  }
159 
160  if (this->redirect_depth == 5) return_error("[tcp/http] too many redirects, looping redirects?");
161 
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");
165 
166  uri += strlen(LOCATION);
167 
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';
172 
173  DEBUG(net, 6, "[tcp/http] redirecting to %s", uri);
174 
175  int ret = NetworkHTTPSocketHandler::Connect(uri, this->callback, this->data, this->redirect_depth + 1);
176  if (ret != 0) return ret;
177 
178  /* We've relinquished control of data now. */
179  this->data = nullptr;
180 
181  /* Restore the header. */
182  *end_of_line = '\r';
183  return 0;
184 }
185 
193 /* static */ int NetworkHTTPSocketHandler::Connect(char *uri, HTTPCallback *callback, const char *data, int depth)
194 {
195  char *hname = strstr(uri, "://");
196  if (hname == nullptr) return_error("[tcp/http] invalid location");
197 
198  hname += 3;
199 
200  char *url = strchr(hname, '/');
201  if (url == nullptr) return_error("[tcp/http] invalid location");
202 
203  *url = '\0';
204 
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");
210 
211  NetworkAddress address(hname, port == nullptr ? 80 : atoi(port));
212 
213  /* Restore the URL. */
214  *url = '/';
215  new NetworkHTTPContentConnecter(address, callback, url, data, depth);
216  return 0;
217 }
218 
219 #undef return_error
220 
229 {
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;
238  }
239  /* Connection would block, so stop for now */
240  return 1;
241  }
242 
243  /* No more data... did we get everything we wanted? */
244  if (res == 0) {
245  if (this->recv_length != 0) return -1;
246 
247  this->callback->OnReceiveData(nullptr, 0);
248  return 0;
249  }
250 
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);
255 
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;
261 
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;
266  }
267  this->recv_pos = read;
268  } else {
269  int ret = this->HandleHeader();
270  if (ret <= 0) return ret;
271 
272  this->recv_length = ret;
273 
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;
279  }
280 
281  this->recv_pos = 0;
282  }
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;
288  }
289  }
290 }
291 
296 {
297  /* No connections, just bail out. */
298  if (_http_connections.size() == 0) return;
299 
300  fd_set read_fd;
301  struct timeval tv;
302 
303  FD_ZERO(&read_fd);
305  FD_SET(handler->sock, &read_fd);
306  }
307 
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;
311 
312  for (auto iter = _http_connections.begin(); iter < _http_connections.end(); /* nothing */) {
313  NetworkHTTPSocketHandler *cur = *iter;
314 
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;
325  }
326  }
327  iter++;
328  }
329 }
END_OF_HEADER
static const char *const END_OF_HEADER
End of header marker.
Definition: tcp_http.cpp:90
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:41
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.
NetworkSocketHandler::CloseConnection
virtual NetworkRecvStatus CloseConnection(bool error=true)
Close the current connection; for TCP this will be mostly equivalent to Close(), but for UDP it just ...
Definition: core.h:59
LOCATION
static const char *const LOCATION
Header for location.
Definition: tcp_http.cpp:94
NetworkHTTPSocketHandler::~NetworkHTTPSocketHandler
~NetworkHTTPSocketHandler()
Free whatever needs to be freed.
Definition: tcp_http.cpp:68
NetworkHTTPContentConnecter
Connect with a HTTP server and do ONE query.
Definition: tcp_http.h:75
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
DEBUG
#define DEBUG(name, level,...)
Output a line of debugging information.
Definition: debug.h:35
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:92
ParseConnectionString
void ParseConnectionString(const char **company, const char **port, char *connection_string)
Converts a string to ip/port/company Format: IP:port::company.
Definition: network.cpp:464
_http_connections
static std::vector< NetworkHTTPSocketHandler * > _http_connections
List of open HTTP connections.
Definition: tcp_http.cpp:22
HTTPCallback::OnFailure
virtual void OnFailure()=0
An error has occurred and the connection has been closed.
NetworkHTTPSocketHandler::CloseConnection
NetworkRecvStatus CloseConnection(bool error=true) override
Close the current connection; for TCP this will be mostly equivalent to Close(), but for UDP it just ...
Definition: tcp_http.cpp:77
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:193
NetworkHTTPSocketHandler::Receive
int Receive()
Handle receiving of HTTP data.
Definition: tcp_http.cpp:228
NetworkAddress
Wrapper for (un)resolved network addresses; there's no reason to transform a numeric IP to a string a...
Definition: address.h:29
NetworkRecvStatus
NetworkRecvStatus
Status of a network client; reasons why a client has quit.
Definition: core.h:22
NetworkHTTPSocketHandler::NetworkHTTPSocketHandler
NetworkHTTPSocketHandler(SOCKET sock, HTTPCallback *callback, const char *host, const char *url, const char *data, int depth)
Start the querying.
Definition: tcp_http.cpp:33
HTTP_1_0
static const char *const HTTP_1_0
Preamble for HTTP 1.0 servers.
Definition: tcp_http.cpp:91
NetworkHTTPSocketHandler::HTTPReceive
static void HTTPReceive()
Do the receiving for all HTTP connections.
Definition: tcp_http.cpp:295
seprintf
int CDECL seprintf(char *str, const char *last, const char *format,...)
Safer implementation of snprintf; same as snprintf except:
Definition: string.cpp:442
return_error
#define return_error(msg)
Helper to simplify the error handling.
Definition: tcp_http.cpp:87
NEWLINE
static const char *const NEWLINE
End of line marker.
Definition: tcp_http.cpp:89
error
void CDECL error(const char *s,...)
Error handling for fatal non-user errors.
Definition: openttd.cpp:129
NetworkHTTPSocketHandler::recv_length
int recv_length
Length of the data still retrieving.
Definition: tcp_http.h:42
lengthof
#define lengthof(x)
Return the length of an fixed size array.
Definition: stdafx.h:367
NETWORK_RECV_STATUS_OKAY
@ NETWORK_RECV_STATUS_OKAY
Everything is okay.
Definition: core.h:23
free
static void free(const void *ptr)
Version of the standard free that accepts const pointers.
Definition: stdafx.h:454
GetNetworkRevisionString
const char * GetNetworkRevisionString()
Get the network version string used by this build.
Definition: network.cpp:1098
CONTENT_LENGTH
static const char *const CONTENT_LENGTH
Header for the length of the content.
Definition: tcp_http.cpp:93
NetworkHTTPSocketHandler::sock
SOCKET sock
The socket currently connected to.
Definition: tcp_http.h:50
AllocaM
#define AllocaM(T, num_elements)
alloca() has to be called in the parent function, so define AllocaM() as a macro
Definition: alloc_func.hpp:132
NetworkHTTPSocketHandler::HandleHeader
int HandleHeader()
Handle the header of a HTTP reply.
Definition: tcp_http.cpp:106