tcp_http.cpp

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

Generated on Sun May 15 19:20:09 2011 for OpenTTD by  doxygen 1.6.1