OpenTTD

- Main Page
- Related Pages
- Modules
- Namespaces
- Data Structures
- Files

- File List
- Globals

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
26 static SmallVector<NetworkHTTPSocketHandler *, 1> _http_connections;
27
37 NetworkHTTPSocketHandler::NetworkHTTPSocketHandler(SOCKET s,
38 HTTPCallback *callback, const char *host, const char *url,
39 const char *data, int depth) :
40 NetworkSocketHandler(),
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
72 NetworkHTTPSocketHandler::~NetworkHTTPSocketHandler()
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
81 NetworkRecvStatus NetworkHTTPSocketHandler::CloseConnection(bool error)
82 {
83 NetworkSocketHandler::CloseConnection(error);
84 return NETWORK_RECV_STATUS_OKAY;
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
110 int NetworkHTTPSocketHandler::HandleHeader()
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
232 int NetworkHTTPSocketHandler::Receive()
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
299 /* static */ void NetworkHTTPSocketHandler::HTTPReceive()
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 */