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