OpenTTD
tcp_http.cpp
Go to the documentation of this file.
1 /* $Id$ */
2 
3 /*
4  * This file is part of OpenTTD.
5  * 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.
6  * 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.
7  * 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/>.
8  */
9 
14 #include "../../stdafx.h"
15 #include "../../debug.h"
16 #include "../../rev.h"
17 #include "../network_func.h"
18 
19 #include "tcp_http.h"
20 
21 #include "../../safeguards.h"
22 
24 static std::vector<NetworkHTTPSocketHandler *> _http_connections;
25 
36  HTTPCallback *callback, const char *host, const char *url,
37  const char *data, int depth) :
39  recv_pos(0),
40  recv_length(0),
41  callback(callback),
42  data(data),
43  redirect_depth(depth),
44  sock(s)
45 {
46  size_t bufferSize = strlen(url) + strlen(host) + strlen(GetNetworkRevisionString()) + (data == nullptr ? 0 : strlen(data)) + 128;
47  char *buffer = AllocaM(char, bufferSize);
48 
49  DEBUG(net, 7, "[tcp/http] requesting %s%s", host, url);
50  if (data != nullptr) {
51  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);
52  } else {
53  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  }
55 
56  ssize_t size = strlen(buffer);
57  ssize_t res = send(this->sock, (const char*)buffer, size, 0);
58  if (res != size) {
59  /* Sending all data failed. Socket can't handle this little bit
60  * of information? Just fall back to the old system! */
61  this->callback->OnFailure();
62  delete this;
63  return;
64  }
65 
66  _http_connections.push_back(this);
67 }
68 
71 {
72  this->CloseConnection();
73 
74  if (this->sock != INVALID_SOCKET) closesocket(this->sock);
75  this->sock = INVALID_SOCKET;
76  free(this->data);
77 }
78 
80 {
83 }
84 
89 #define return_error(msg) { DEBUG(net, 0, msg); return -1; }
90 
91 static const char * const NEWLINE = "\r\n";
92 static const char * const END_OF_HEADER = "\r\n\r\n";
93 static const char * const HTTP_1_0 = "HTTP/1.0 ";
94 static const char * const HTTP_1_1 = "HTTP/1.1 ";
95 static const char * const CONTENT_LENGTH = "Content-Length: ";
96 static const char * const LOCATION = "Location: ";
97 
109 {
110  assert(strlen(HTTP_1_0) == strlen(HTTP_1_1));
111  assert(strstr(this->recv_buffer, END_OF_HEADER) != nullptr);
112 
113  /* We expect a HTTP/1.[01] reply */
114  if (strncmp(this->recv_buffer, HTTP_1_0, strlen(HTTP_1_0)) != 0 &&
115  strncmp(this->recv_buffer, HTTP_1_1, strlen(HTTP_1_1)) != 0) {
116  return_error("[tcp/http] received invalid HTTP reply");
117  }
118 
119  char *status = this->recv_buffer + strlen(HTTP_1_0);
120  if (strncmp(status, "200", 3) == 0) {
121  /* We are going to receive a document. */
122 
123  /* Get the length of the document to receive */
124  char *length = strcasestr(this->recv_buffer, CONTENT_LENGTH);
125  if (length == nullptr) return_error("[tcp/http] missing 'content-length' header");
126 
127  /* Skip the header */
128  length += strlen(CONTENT_LENGTH);
129 
130  /* Search the end of the line. This is safe because the header will
131  * always end with two newlines. */
132  char *end_of_line = strstr(length, NEWLINE);
133 
134  /* Read the length */
135  *end_of_line = '\0';
136  int len = atoi(length);
137  /* Restore the header. */
138  *end_of_line = '\r';
139 
140  /* Make sure we're going to download at least something;
141  * zero sized files are, for OpenTTD's purposes, always
142  * wrong. You can't have gzips of 0 bytes! */
143  if (len == 0) return_error("[tcp/http] refusing to download 0 bytes");
144 
145  DEBUG(net, 7, "[tcp/http] downloading %i bytes", len);
146  return len;
147  }
148 
149  if (strncmp(status, "301", 3) != 0 &&
150  strncmp(status, "302", 3) != 0 &&
151  strncmp(status, "303", 3) != 0 &&
152  strncmp(status, "307", 3) != 0) {
153  /* We are not going to be redirected :(. */
154 
155  /* Search the end of the line. This is safe because the header will
156  * always end with two newlines. */
157  *strstr(status, NEWLINE) = '\0';
158  DEBUG(net, 0, "[tcp/http] unhandled status reply %s", status);
159  return -1;
160  }
161 
162  if (this->redirect_depth == 5) return_error("[tcp/http] too many redirects, looping redirects?");
163 
164  /* Redirect to other URL */
165  char *uri = strcasestr(this->recv_buffer, LOCATION);
166  if (uri == nullptr) return_error("[tcp/http] missing 'location' header for redirect");
167 
168  uri += strlen(LOCATION);
169 
170  /* Search the end of the line. This is safe because the header will
171  * always end with two newlines. */
172  char *end_of_line = strstr(uri, NEWLINE);
173  *end_of_line = '\0';
174 
175  DEBUG(net, 6, "[tcp/http] redirecting to %s", uri);
176 
177  int ret = NetworkHTTPSocketHandler::Connect(uri, this->callback, this->data, this->redirect_depth + 1);
178  if (ret != 0) return ret;
179 
180  /* We've relinquished control of data now. */
181  this->data = nullptr;
182 
183  /* Restore the header. */
184  *end_of_line = '\r';
185  return 0;
186 }
187 
195 /* static */ int NetworkHTTPSocketHandler::Connect(char *uri, HTTPCallback *callback, const char *data, int depth)
196 {
197  char *hname = strstr(uri, "://");
198  if (hname == nullptr) return_error("[tcp/http] invalid location");
199 
200  hname += 3;
201 
202  char *url = strchr(hname, '/');
203  if (url == nullptr) return_error("[tcp/http] invalid location");
204 
205  *url = '\0';
206 
207  /* Fetch the hostname, and possible port number. */
208  const char *company = nullptr;
209  const char *port = nullptr;
210  ParseConnectionString(&company, &port, hname);
211  if (company != nullptr) return_error("[tcp/http] invalid hostname");
212 
213  NetworkAddress address(hname, port == nullptr ? 80 : atoi(port));
214 
215  /* Restore the URL. */
216  *url = '/';
217  new NetworkHTTPContentConnecter(address, callback, url, data, depth);
218  return 0;
219 }
220 
221 #undef return_error
222 
231 {
232  for (;;) {
233  ssize_t res = recv(this->sock, (char *)this->recv_buffer + this->recv_pos, lengthof(this->recv_buffer) - this->recv_pos, 0);
234  if (res == -1) {
235  int err = GET_LAST_ERROR();
236  if (err != EWOULDBLOCK) {
237  /* Something went wrong... (104 is connection reset by peer) */
238  if (err != 104) DEBUG(net, 0, "recv failed with error %d", err);
239  return -1;
240  }
241  /* Connection would block, so stop for now */
242  return 1;
243  }
244 
245  /* No more data... did we get everything we wanted? */
246  if (res == 0) {
247  if (this->recv_length != 0) return -1;
248 
249  this->callback->OnReceiveData(nullptr, 0);
250  return 0;
251  }
252 
253  /* Wait till we read the end-of-header identifier */
254  if (this->recv_length == 0) {
255  int read = this->recv_pos + res;
256  int end = min(read, lengthof(this->recv_buffer) - 1);
257 
258  /* Do a 'safe' search for the end of the header. */
259  char prev = this->recv_buffer[end];
260  this->recv_buffer[end] = '\0';
261  char *end_of_header = strstr(this->recv_buffer, END_OF_HEADER);
262  this->recv_buffer[end] = prev;
263 
264  if (end_of_header == nullptr) {
265  if (read == lengthof(this->recv_buffer)) {
266  DEBUG(net, 0, "[tcp/http] header too big");
267  return -1;
268  }
269  this->recv_pos = read;
270  } else {
271  int ret = this->HandleHeader();
272  if (ret <= 0) return ret;
273 
274  this->recv_length = ret;
275 
276  end_of_header += strlen(END_OF_HEADER);
277  int len = min(read - (end_of_header - this->recv_buffer), res);
278  if (len != 0) {
279  this->callback->OnReceiveData(end_of_header, len);
280  this->recv_length -= len;
281  }
282 
283  this->recv_pos = 0;
284  }
285  } else {
286  res = min(this->recv_length, res);
287  /* Receive whatever we're expecting. */
288  this->callback->OnReceiveData(this->recv_buffer, res);
289  this->recv_length -= res;
290  }
291  }
292 }
293 
298 {
299  /* No connections, just bail out. */
300  if (_http_connections.size() == 0) return;
301 
302  fd_set read_fd;
303  struct timeval tv;
304 
305  FD_ZERO(&read_fd);
307  FD_SET(handler->sock, &read_fd);
308  }
309 
310  tv.tv_sec = tv.tv_usec = 0; // don't block at all.
311  int n = select(FD_SETSIZE, &read_fd, nullptr, nullptr, &tv);
312  if (n == -1) return;
313 
314  for (auto iter = _http_connections.begin(); iter < _http_connections.end(); /* nothing */) {
315  NetworkHTTPSocketHandler *cur = *iter;
316 
317  if (FD_ISSET(cur->sock, &read_fd)) {
318  int ret = cur->Receive();
319  /* First send the failure. */
320  if (ret < 0) cur->callback->OnFailure();
321  if (ret <= 0) {
322  /* Then... the connection can be closed */
323  cur->CloseConnection();
324  iter = _http_connections.erase(iter);
325  delete cur;
326  continue;
327  }
328  }
329  iter++;
330  }
331 }
Everything is okay.
Definition: core.h:25
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:472
Connect with a HTTP server and do ONE query.
Definition: tcp_http.h:77
SOCKET sock
The socket currently connected to.
Definition: tcp_http.h:52
Basic functions to receive and send HTTP TCP packets.
int CDECL seprintf(char *str, const char *last, const char *format,...)
Safer implementation of snprintf; same as snprintf except:
Definition: string.cpp:409
int Receive()
Handle receiving of HTTP data.
Definition: tcp_http.cpp:230
char recv_buffer[4096]
Partially received message.
Definition: tcp_http.h:42
NetworkHTTPSocketHandler(SOCKET sock, HTTPCallback *callback, const char *host, const char *url, const char *data, int depth)
Start the querying.
Definition: tcp_http.cpp:35
static const char *const HTTP_1_0
Preamble for HTTP 1.0 servers.
Definition: tcp_http.cpp:93
static const char *const LOCATION
Header for location.
Definition: tcp_http.cpp:96
Wrapper for (un)resolved network addresses; there&#39;s no reason to transform a numeric IP to a string a...
Definition: address.h:29
const char * GetNetworkRevisionString()
Get the network version string used by this build.
Definition: network.cpp:1111
#define AllocaM(T, num_elements)
alloca() has to be called in the parent function, so define AllocaM() as a macro
Definition: alloc_func.hpp:134
static void HTTPReceive()
Do the receiving for all HTTP connections.
Definition: tcp_http.cpp:297
int redirect_depth
The depth of the redirection.
Definition: tcp_http.h:47
static const char *const CONTENT_LENGTH
Header for the length of the content.
Definition: tcp_http.cpp:95
static int Connect(char *uri, HTTPCallback *callback, const char *data=nullptr, int depth=0)
Connect to the given URI.
Definition: tcp_http.cpp:195
static const char *const HTTP_1_1
Preamble for HTTP 1.1 servers.
Definition: tcp_http.cpp:94
virtual void OnReceiveData(const char *data, size_t length)=0
We&#39;re receiving data.
~NetworkHTTPSocketHandler()
Free whatever needs to be freed.
Definition: tcp_http.cpp:70
int recv_pos
Current position in buffer.
Definition: tcp_http.h:43
virtual void OnFailure()=0
An error has occurred and the connection has been closed.
NetworkRecvStatus
Status of a network client; reasons why a client has quit.
Definition: core.h:24
const char * data
The (POST) data we might want to forward (to a redirect).
Definition: tcp_http.h:46
#define lengthof(x)
Return the length of an fixed size array.
Definition: depend.cpp:42
static T min(const T a, const T b)
Returns the minimum of two values.
Definition: math_func.hpp:42
int HandleHeader()
Handle the header of a HTTP reply.
Definition: tcp_http.cpp:108
#define DEBUG(name, level,...)
Output a line of debugging information.
Definition: debug.h:37
Base socket handler for HTTP traffic.
Definition: tcp_http.h:40
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:79
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:61
void CDECL error(const char *s,...)
Error handling for fatal non-user errors.
Definition: openttd.cpp:114
Callback for when the HTTP handler has something to tell us.
Definition: tcp_http.h:20
static void free(const void *ptr)
Version of the standard free that accepts const pointers.
Definition: depend.cpp:131
static std::vector< NetworkHTTPSocketHandler * > _http_connections
List of open HTTP connections.
Definition: tcp_http.cpp:24
HTTPCallback * callback
The callback to call for the incoming data.
Definition: tcp_http.h:45
static const char *const NEWLINE
End of line marker.
Definition: tcp_http.cpp:91
int recv_length
Length of the data still retrieving.
Definition: tcp_http.h:44
#define return_error(msg)
Helper to simplify the error handling.
Definition: tcp_http.cpp:89
static const char *const END_OF_HEADER
End of header marker.
Definition: tcp_http.cpp:92
SocketHandler for all network sockets in OpenTTD.
Definition: core.h:43