# tcp_http.cpp

Go to the documentation of this file.

    00001 /* $Id: tcp_http.cpp 18996 2010-02-03 21:45:48Z rubidium $ */
    00002
    00003 /*
    00004  * This file is part of OpenTTD.
    00005  * 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.
    00006  * 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.
    00007  * 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/>.
    00008  */
    00009
    00014 #ifdef ENABLE_NETWORK
    00015
    00016 #include "../../stdafx.h"
    00017 #include "../../debug.h"
    00018 #include "../../rev.h"
    00019 #include "../network_func.h"
    00020
    00021 #include "tcp.h"
    00022 #include "tcp_http.h"
    00023
    00025 static SmallVector<NetworkHTTPSocketHandler *, 1> _http_connections;
    00026
    00027 NetworkHTTPSocketHandler::NetworkHTTPSocketHandler(SOCKET s,
    00028     HTTPCallback *callback, const char *host, const char *url,
    00029     const char *data, int depth) :
    00030   NetworkSocketHandler(),
    00031   recv_pos(0),
    00032   recv_length(0),
    00033   callback(callback),
    00034   data(data),
    00035   redirect_depth(depth),
    00036   sock(s)
    00037 {
    00038   size_t bufferSize = strlen(url) + strlen(host) + strlen(_openttd_revision) + (data == NULL ? 0 : strlen(data)) + 128;
    00039   char *buffer = AllocaM(char, bufferSize);
    00040
    00041   DEBUG(net, 7, "[tcp/http] requesting %s%s", host, url);
    00042   if (data != NULL) {
    00043     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);
    00044   } else {
    00045     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);
    00046   }
    00047
    00048   ssize_t size = strlen(buffer);
    00049   ssize_t res = send(this->sock, (const char*)buffer, size, 0);
    00050   if (res != size) {
    00051     /* Sending all data failed. Socket can't handle this little bit
    00052      * of information? Just fall back to the old system! */
    00053     this->callback->OnFailure();
    00054     delete this;
    00055   }
    00056
    00057   *_http_connections.Append() = this;
    00058 }
    00059
    00060 NetworkHTTPSocketHandler::~NetworkHTTPSocketHandler()
    00061 {
    00062   this->CloseConnection();
    00063
    00064   if (this->sock != INVALID_SOCKET) closesocket(this->sock);
    00065   this->sock = INVALID_SOCKET;
    00066   free((void*)this->data);
    00067 }
    00068
    00069 NetworkRecvStatus NetworkHTTPSocketHandler::CloseConnection(bool error)
    00070 {
    00071   NetworkSocketHandler::CloseConnection(error);
    00072   return NETWORK_RECV_STATUS_OKAY;
    00073 }
    00074
    00079 #define return_error(msg) { DEBUG(net, 0, msg); return -1; }
    00080
    00081 static const char * const NEWLINE        = "\r\n";
    00082 static const char * const END_OF_HEADER  = "\r\n\r\n";
    00083 static const char * const HTTP_1_0       = "HTTP/1.0 ";
    00084 static const char * const HTTP_1_1       = "HTTP/1.1 ";
    00085 static const char * const CONTENT_LENGTH = "Content-Length: ";
    00086 static const char * const LOCATION       = "Location: ";
    00087
    00098 int NetworkHTTPSocketHandler::HandleHeader()
    00099 {
    00100   assert(strlen(HTTP_1_0) == strlen(HTTP_1_1));
    00101   assert(strstr(this->recv_buffer, END_OF_HEADER) != NULL);
    00102
    00103   /* We expect a HTTP/1.[01] reply */
    00104   if (strncmp(this->recv_buffer, HTTP_1_0, strlen(HTTP_1_0)) != 0 &&
    00105       strncmp(this->recv_buffer, HTTP_1_1, strlen(HTTP_1_1)) != 0) {
    00106     return_error("[tcp/http] received invalid HTTP reply");
    00107   }
    00108
    00109   char *status = this->recv_buffer + strlen(HTTP_1_0);
    00110   if (strncmp(status, "200", 3) == 0) {
    00111     /* We are going to receive a document. */
    00112
    00113     /* Get the length of the document to receive */
    00114     char *length = strcasestr(this->recv_buffer, CONTENT_LENGTH);
    00115     if (length == NULL) return_error("[tcp/http] missing 'content-length' header");
    00116
    00117     /* Skip the header */
    00118     length += strlen(CONTENT_LENGTH);
    00119
    00120     /* Search the end of the line. This is safe because the header will
    00121      * always end with two newlines. */
    00122     char *end_of_line = strstr(length, NEWLINE);
    00123
    00124     /* Read the length */
    00125     *end_of_line = '\0';
    00126     int len = atoi(length);
    00127     /* Restore the header. */
    00128     *end_of_line = '\r';
    00129
    00130     /* Make sure we're going to download at least something;
    00131      * zero sized files are, for OpenTTD's purposes, always
    00132      * wrong. You can't have gzips of 0 bytes! */
    00133     if (len == 0) return_error("[tcp/http] refusing to download 0 bytes");
    00134
    00135     DEBUG(net, 7, "[tcp/http] downloading %i bytes", len);
    00136     return len;
    00137   }
    00138
    00139   if (strncmp(status, "301", 3) != 0 &&
    00140       strncmp(status, "302", 3) != 0 &&
    00141       strncmp(status, "303", 3) != 0 &&
    00142       strncmp(status, "307", 3) != 0) {
    00143     /* We are not going to be redirected :(. */
    00144
    00145     /* Search the end of the line. This is safe because the header will
    00146      * always end with two newlines. */
    00147     *strstr(status, NEWLINE) = '\0';
    00148     DEBUG(net, 0, "[tcp/http] unhandled status reply %s", status);
    00149     return -1;
    00150   }
    00151
    00152   if (this->redirect_depth == 5) return_error("[tcp/http] too many redirects, looping redirects?");
    00153
    00154   /* Redirect to other URL */
    00155   char *uri = strcasestr(this->recv_buffer, LOCATION);
    00156   if (uri == NULL) return_error("[tcp/http] missing 'location' header for redirect");
    00157
    00158   uri += strlen(LOCATION);
    00159
    00160   /* Search the end of the line. This is safe because the header will
    00161    * always end with two newlines. */
    00162   char *end_of_line = strstr(uri, NEWLINE);
    00163   *end_of_line = '\0';
    00164
    00165   DEBUG(net, 6, "[tcp/http] redirecting to %s", uri);
    00166
    00167   int ret = NetworkHTTPSocketHandler::Connect(uri, this->callback, this->data, this->redirect_depth + 1);
    00168   if (ret != 0) return ret;
    00169
    00170   /* We've relinguished control of data now. */
    00171   this->data = NULL;
    00172
    00173   /* Restore the header. */
    00174   *end_of_line = '\r';
    00175   return 0;
    00176 }
    00177
    00178 /*static */ int NetworkHTTPSocketHandler::Connect(char *uri, HTTPCallback *callback, const char *data, int depth)
    00179 {
    00180   char *hname = strstr(uri, "://");
    00181   if (hname == NULL) return_error("[tcp/http] invalid location");
    00182
    00183   hname += 3;
    00184
    00185   char *url = strchr(hname, '/');
    00186   if (url == NULL) return_error("[tcp/http] invalid location");
    00187
    00188   *url = '\0';
    00189
    00190   /* Fetch the hostname, and possible port number. */
    00191   const char *company = NULL;
    00192   const char *port = NULL;
    00193   ParseConnectionString(&company, &port, hname);
    00194   if (company != NULL) return_error("[tcp/http] invalid hostname");
    00195
    00196   NetworkAddress address(hname, port == NULL ? 80 : atoi(port));
    00197
    00198   /* Restore the URL. */
    00199   *url = '/';
    00200   new NetworkHTTPContentConnecter(address, callback, url, data, depth);
    00201   return 0;
    00202 }
    00203
    00204 #undef return_error
    00205
    00213 int NetworkHTTPSocketHandler::Receive()
    00214 {
    00215   for (;;) {
    00216     ssize_t res = recv(this->sock, (char *)this->recv_buffer + this->recv_pos, lengthof(this->recv_buffer) - this->recv_pos, 0);
    00217     if (res == -1) {
    00218       int err = GET_LAST_ERROR();
    00219       if (err != EWOULDBLOCK) {
    00220         /* Something went wrong... (104 is connection reset by peer) */
    00221         if (err != 104) DEBUG(net, 0, "recv failed with error %d", err);
    00222         return -1;
    00223       }
    00224       /* Connection would block, so stop for now */
    00225       return 1;
    00226     }
    00227
    00228     /* No more data... did we get everything we wanted? */
    00229     if (res == 0) {
    00230       if (this->recv_length != 0) return -1;
    00231
    00232       this->callback->OnReceiveData(NULL, 0);
    00233       return 0;
    00234     }
    00235
    00236     /* Wait till we read the end-of-header identifier */
    00237     if (this->recv_length == 0) {
    00238       int read = this->recv_pos + res;
    00239       int end = min(read, lengthof(this->recv_buffer) - 1);
    00240
    00241       /* Do a 'safe' search for the end of the header. */
    00242       char prev = this->recv_buffer[end];
    00243       this->recv_buffer[end] = '\0';
    00244       char *end_of_header = strstr(this->recv_buffer, END_OF_HEADER);
    00245       this->recv_buffer[end] = prev;
    00246
    00247       if (end_of_header == NULL) {
    00248         if (read == lengthof(this->recv_buffer)) {
    00249           DEBUG(net, 0, "[tcp/http] header too big");
    00250           return -1;
    00251         }
    00252         this->recv_pos = read;
    00253       } else {
    00254         int ret = this->HandleHeader();
    00255         if (ret <= 0) return ret;
    00256
    00257         this->recv_length = ret;
    00258
    00259         end_of_header += strlen(END_OF_HEADER);
    00260         int len = min(read - (end_of_header - this->recv_buffer), res);
    00261         if (len != 0) {
    00262           this->callback->OnReceiveData(end_of_header, len);
    00263           this->recv_length -= len;
    00264         }
    00265
    00266         this->recv_pos = 0;
    00267       }
    00268     } else {
    00269       res = min(this->recv_length, res);
    00270       /* Receive whatever we're expecting. */
    00271       this->callback->OnReceiveData(this->recv_buffer, res);
    00272       this->recv_length -= res;
    00273     }
    00274   }
    00275 }
    00276
    00277 /* static */ void NetworkHTTPSocketHandler::HTTPReceive()
    00278 {
    00279   /* No connections, just bail out. */
    00280   if (_http_connections.Length() == 0) return;
    00281
    00282   fd_set read_fd;
    00283   struct timeval tv;
    00284
    00285   FD_ZERO(&read_fd);
    00286   for (NetworkHTTPSocketHandler **iter = _http_connections.Begin(); iter < _http_connections.End(); iter++) {
    00287     FD_SET((*iter)->sock, &read_fd);
    00288   }
    00289
    00290   tv.tv_sec = tv.tv_usec = 0; // don't block at all.
    00291 #if !defined(__MORPHOS__) && !defined(__AMIGA__)
    00292   int n = select(FD_SETSIZE, &read_fd, NULL, NULL, &tv);
    00293 #else
    00294   int n = WaitSelect(FD_SETSIZE, &read_fd, NULL, NULL, &tv, NULL);
    00295 #endif
    00296   if (n == -1) return;
    00297
    00298   for (NetworkHTTPSocketHandler **iter = _http_connections.Begin(); iter < _http_connections.End(); /* nothing */) {
    00299     NetworkHTTPSocketHandler *cur = *iter;
    00300
    00301     if (FD_ISSET(cur->sock, &read_fd)) {
    00302       int ret = cur->Receive();
    00303       /* First send the failure. */
    00304       if (ret < 0) cur->callback->OnFailure();
    00305       if (ret <= 0) {
    00306         /* Then... the connection can be closed */
    00307         cur->CloseConnection();
    00308         _http_connections.Erase(iter);
    00309         delete cur;
    00310         continue;
    00311       }
    00312     }
    00313     iter++;
    00314   }
    00315 }
    00316
    00317 #endif /* ENABLE_NETWORK */