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/>.
11 * @file tcp_http.cpp Basic functions to receive and send HTTP TCP packets.
16 #include "../../stdafx.h"
17 #include "../../debug.h"
18 #include "../../rev.h"
19 #include "../network_func.h"
23 #include "../../safeguards.h"
25 /** List of open HTTP connections. */
26 static SmallVector
<NetworkHTTPSocketHandler
*, 1> _http_connections
;
30 * @param s the socket of this connection
31 * @param callback the callback for HTTP retrieval
32 * @param host the hostname of the server to connect to
33 * @param url the url at the server
34 * @param data the data to send
35 * @param depth the depth (redirect recursion) of the queries
37 NetworkHTTPSocketHandler::NetworkHTTPSocketHandler(SOCKET s
,
38 HTTPCallback
*callback
, const char *host
, const char *url
,
39 const char *data
, int depth
) :
40 NetworkSocketHandler(),
45 redirect_depth(depth
),
48 size_t bufferSize
= strlen(url
) + strlen(host
) + strlen(_openttd_revision
) + (data
== NULL
? 0 : strlen(data
)) + 128;
49 char *buffer
= AllocaM(char, bufferSize
);
51 DEBUG(net
, 7, "[tcp/http] requesting %s%s", host
, url
);
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
);
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
);
58 ssize_t size
= strlen(buffer
);
59 ssize_t res
= send(this->sock
, (const char*)buffer
, size
, 0);
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();
68 *_http_connections
.Append() = this;
71 /** Free whatever needs to be freed. */
72 NetworkHTTPSocketHandler::~NetworkHTTPSocketHandler()
74 this->CloseConnection();
76 if (this->sock
!= INVALID_SOCKET
) closesocket(this->sock
);
77 this->sock
= INVALID_SOCKET
;
81 NetworkRecvStatus
NetworkHTTPSocketHandler::CloseConnection(bool error
)
83 NetworkSocketHandler::CloseConnection(error
);
84 return NETWORK_RECV_STATUS_OKAY
;
88 * Helper to simplify the error handling.
89 * @param msg the error message to show.
91 #define return_error(msg) { DEBUG(net, 0, msg); return -1; }
93 static const char * const NEWLINE
= "\r\n"; ///< End of line marker
94 static const char * const END_OF_HEADER
= "\r\n\r\n"; ///< End of header marker
95 static const char * const HTTP_1_0
= "HTTP/1.0 "; ///< Preamble for HTTP 1.0 servers
96 static const char * const HTTP_1_1
= "HTTP/1.1 "; ///< Preamble for HTTP 1.1 servers
97 static const char * const CONTENT_LENGTH
= "Content-Length: "; ///< Header for the length of the content
98 static const char * const LOCATION
= "Location: "; ///< Header for location
101 * Handle the header of a HTTP reply.
102 * @return amount of data to continue downloading.
103 * > 0: we need to download N bytes.
104 * = 0: we're being redirected.
105 * < 0: an error occurred. Downloading failed.
106 * @note if an error occurred the header might not be in its
107 * original state. No effort is undertaken to bring
108 * the header in its original state.
110 int NetworkHTTPSocketHandler::HandleHeader()
112 assert(strlen(HTTP_1_0
) == strlen(HTTP_1_1
));
113 assert(strstr(this->recv_buffer
, END_OF_HEADER
) != NULL
);
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");
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. */
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");
129 /* Skip the header */
130 length
+= strlen(CONTENT_LENGTH
);
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
);
136 /* Read the length */
138 int len
= atoi(length
);
139 /* Restore the header. */
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");
147 DEBUG(net
, 7, "[tcp/http] downloading %i bytes", len
);
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 :(. */
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
);
164 if (this->redirect_depth
== 5) return_error("[tcp/http] too many redirects, looping redirects?");
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");
170 uri
+= strlen(LOCATION
);
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
);
177 DEBUG(net
, 6, "[tcp/http] redirecting to %s", uri
);
179 int ret
= NetworkHTTPSocketHandler::Connect(uri
, this->callback
, this->data
, this->redirect_depth
+ 1);
180 if (ret
!= 0) return ret
;
182 /* We've relinquished control of data now. */
185 /* Restore the header. */
191 * Connect to the given URI.
192 * @param uri the URI to connect to.
193 * @param callback the callback to send data back on.
194 * @param data the data we want to send (as POST).
195 * @param depth the recursion/redirect depth.
197 /* static */ int NetworkHTTPSocketHandler::Connect(char *uri
, HTTPCallback
*callback
, const char *data
, int depth
)
199 char *hname
= strstr(uri
, "://");
200 if (hname
== NULL
) return_error("[tcp/http] invalid location");
204 char *url
= strchr(hname
, '/');
205 if (url
== NULL
) return_error("[tcp/http] invalid location");
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");
215 NetworkAddress
address(hname
, port
== NULL
? 80 : atoi(port
));
217 /* Restore the URL. */
219 new NetworkHTTPContentConnecter(address
, callback
, url
, data
, depth
);
226 * Handle receiving of HTTP data.
227 * @return state of the receival of HTTP data.
228 * > 0: we need more cycles for downloading
229 * = 0: we are done downloading
230 * < 0: we have hit an error
232 int NetworkHTTPSocketHandler::Receive()
235 ssize_t res
= recv(this->sock
, (char *)this->recv_buffer
+ this->recv_pos
, lengthof(this->recv_buffer
) - this->recv_pos
, 0);
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
);
243 /* Connection would block, so stop for now */
247 /* No more data... did we get everything we wanted? */
249 if (this->recv_length
!= 0) return -1;
251 this->callback
->OnReceiveData(NULL
, 0);
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);
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
;
266 if (end_of_header
== NULL
) {
267 if (read
== lengthof(this->recv_buffer
)) {
268 DEBUG(net
, 0, "[tcp/http] header too big");
271 this->recv_pos
= read
;
273 int ret
= this->HandleHeader();
274 if (ret
<= 0) return ret
;
276 this->recv_length
= ret
;
278 end_of_header
+= strlen(END_OF_HEADER
);
279 int len
= min(read
- (end_of_header
- this->recv_buffer
), res
);
281 this->callback
->OnReceiveData(end_of_header
, len
);
282 this->recv_length
-= len
;
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
;
297 * Do the receiving for all HTTP connections.
299 /* static */ void NetworkHTTPSocketHandler::HTTPReceive()
301 /* No connections, just bail out. */
302 if (_http_connections
.Length() == 0) return;
308 for (NetworkHTTPSocketHandler
**iter
= _http_connections
.Begin(); iter
< _http_connections
.End(); iter
++) {
309 FD_SET((*iter
)->sock
, &read_fd
);
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
);
316 int n
= WaitSelect(FD_SETSIZE
, &read_fd
, NULL
, NULL
, &tv
, NULL
);
320 for (NetworkHTTPSocketHandler
**iter
= _http_connections
.Begin(); iter
< _http_connections
.End(); /* nothing */) {
321 NetworkHTTPSocketHandler
*cur
= *iter
;
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();
328 /* Then... the connection can be closed */
329 cur
->CloseConnection();
330 _http_connections
.Erase(iter
);
339 #endif /* ENABLE_NETWORK */