summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--projects/openttd_vs80.vcproj8
-rw-r--r--projects/openttd_vs90.vcproj8
-rw-r--r--source.list2
-rw-r--r--src/network/core/config.h5
-rw-r--r--src/network/core/tcp_http.cpp317
-rw-r--r--src/network/core/tcp_http.h143
-rw-r--r--src/network/network.cpp1
-rw-r--r--src/network/network_content.cpp181
-rw-r--r--src/network/network_content.h13
-rw-r--r--src/settings_type.h1
-rw-r--r--src/table/settings.h1
11 files changed, 675 insertions, 5 deletions
diff --git a/projects/openttd_vs80.vcproj b/projects/openttd_vs80.vcproj
index 08a238796..e15dedc1d 100644
--- a/projects/openttd_vs80.vcproj
+++ b/projects/openttd_vs80.vcproj
@@ -3416,6 +3416,14 @@
>
</File>
<File
+ RelativePath=".\..\src\network\core\tcp_http.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\..\src\network\core\tcp_http.h"
+ >
+ </File>
+ <File
RelativePath=".\..\src\network\core\udp.cpp"
>
</File>
diff --git a/projects/openttd_vs90.vcproj b/projects/openttd_vs90.vcproj
index 7117cb734..eebb65465 100644
--- a/projects/openttd_vs90.vcproj
+++ b/projects/openttd_vs90.vcproj
@@ -3413,6 +3413,14 @@
>
</File>
<File
+ RelativePath=".\..\src\network\core\tcp_http.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\..\src\network\core\tcp_http.h"
+ >
+ </File>
+ <File
RelativePath=".\..\src\network\core\udp.cpp"
>
</File>
diff --git a/source.list b/source.list
index ccc234ef1..275341994 100644
--- a/source.list
+++ b/source.list
@@ -803,6 +803,8 @@ network/core/tcp_content.cpp
network/core/tcp_content.h
network/core/tcp_game.cpp
network/core/tcp_game.h
+network/core/tcp_http.cpp
+network/core/tcp_http.h
network/core/udp.cpp
network/core/udp.h
diff --git a/src/network/core/config.h b/src/network/core/config.h
index 03f3e3b06..2914245dd 100644
--- a/src/network/core/config.h
+++ b/src/network/core/config.h
@@ -18,12 +18,17 @@
#define NETWORK_MASTER_SERVER_HOST "master.openttd.org"
/** DNS hostname of the content server */
#define NETWORK_CONTENT_SERVER_HOST "content.openttd.org"
+/** DNS hostname of the HTTP-content mirror server */
+#define NETWORK_CONTENT_MIRROR_HOST "binaries.openttd.org"
+/** URL of the HTTP mirror system */
+#define NETWORK_CONTENT_MIRROR_URL "/bananas"
/** Message sent to the masterserver to 'identify' this client as OpenTTD */
#define NETWORK_MASTER_SERVER_WELCOME_MESSAGE "OpenTTDRegister"
enum {
NETWORK_MASTER_SERVER_PORT = 3978, ///< The default port of the master server (UDP)
NETWORK_CONTENT_SERVER_PORT = 3978, ///< The default port of the content server (TCP)
+ NETWORK_CONTENT_MIRROR_PORT = 80, ///< The default port of the content mirror (TCP)
NETWORK_DEFAULT_PORT = 3979, ///< The default port of the game server (TCP & UDP)
NETWORK_DEFAULT_DEBUGLOG_PORT = 3982, ///< The default port debug-log is sent too (TCP)
diff --git a/src/network/core/tcp_http.cpp b/src/network/core/tcp_http.cpp
new file mode 100644
index 000000000..32c04535b
--- /dev/null
+++ b/src/network/core/tcp_http.cpp
@@ -0,0 +1,317 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * 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.
+ * 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.
+ * 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/>.
+ */
+
+/**
+ * @file tcp_http.cpp Basic functions to receive and send HTTP TCP packets.
+ */
+
+#ifdef ENABLE_NETWORK
+
+#include "../../stdafx.h"
+#include "../../debug.h"
+#include "../../rev.h"
+#include "../network_func.h"
+
+#include "tcp.h"
+#include "tcp_http.h"
+
+/** List of open HTTP connections. */
+static SmallVector<NetworkHTTPSocketHandler *, 1> _http_connections;
+
+NetworkHTTPSocketHandler::NetworkHTTPSocketHandler(SOCKET s,
+ HTTPCallback *callback, const char *host, const char *url,
+ const char *data, int depth) :
+ NetworkSocketHandler(),
+ recv_pos(0),
+ recv_length(0),
+ callback(callback),
+ data(data),
+ redirect_depth(depth),
+ sock(s)
+{
+ int bufferSize = strlen(url) + strlen(host) + strlen(_openttd_revision) + (data == NULL ? 0 : strlen(data)) + 128;
+ char *buffer = AllocaM(char, bufferSize);
+
+ DEBUG(net, 7, "[tcp/http] requesting %s%s", host, url);
+ if (data != NULL) {
+ 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);
+ } else {
+ 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);
+ }
+
+ ssize_t size = strlen(buffer);
+ ssize_t res = send(this->sock, (const char*)buffer, size, 0);
+ if (res != size) {
+ /* Sending all data failed. Socket can't handle this little bit
+ * of information? Just fall back to the old system! */
+ this->callback->OnFailure();
+ delete this;
+ }
+
+ *_http_connections.Append() = this;
+}
+
+NetworkHTTPSocketHandler::~NetworkHTTPSocketHandler()
+{
+ this->CloseConnection();
+
+ if (this->sock != INVALID_SOCKET) closesocket(this->sock);
+ this->sock = INVALID_SOCKET;
+ free((void*)this->data);
+}
+
+NetworkRecvStatus NetworkHTTPSocketHandler::CloseConnection(bool error)
+{
+ NetworkSocketHandler::CloseConnection(error);
+ return NETWORK_RECV_STATUS_OKAY;
+}
+
+/**
+ * Helper to simplify the error handling.
+ * @param msg the error message to show.
+ */
+#define return_error(msg) { DEBUG(net, 0, msg); return -1; }
+
+static const char * const NEWLINE = "\r\n"; ///< End of line marker
+static const char * const END_OF_HEADER = "\r\n\r\n"; ///< End of header marker
+static const char * const HTTP_1_0 = "HTTP/1.0 "; ///< Preamble for HTTP 1.0 servers
+static const char * const HTTP_1_1 = "HTTP/1.1 "; ///< Preamble for HTTP 1.1 servers
+static const char * const CONTENT_LENGTH = "Content-Length: "; ///< Header for the length of the content
+static const char * const LOCATION = "Location: "; ///< Header for location
+
+/**
+ * Handle the header of a HTTP reply.
+ * @return amount of data to continue downloading.
+ * > 0: we need to download N bytes.
+ * = 0: we're being redirected.
+ * < 0: an error occured. Downloading failed.
+ * @note if an error occured the header might not be in its
+ * original state. No effort is undertaken to bring
+ * the header in its original state.
+ */
+int NetworkHTTPSocketHandler::HandleHeader()
+{
+ assert(strlen(HTTP_1_0) == strlen(HTTP_1_1));
+ assert(strstr(this->recv_buffer, END_OF_HEADER) != NULL);
+
+ /* We expect a HTTP/1.[01] reply */
+ if (strncmp(this->recv_buffer, HTTP_1_0, strlen(HTTP_1_0)) != 0 &&
+ strncmp(this->recv_buffer, HTTP_1_1, strlen(HTTP_1_1)) != 0) {
+ return_error("[tcp/http] received invalid HTTP reply");
+ }
+
+ char *status = this->recv_buffer + strlen(HTTP_1_0);
+ if (strncmp(status, "200", 3) == 0) {
+ /* We are going to receive a document. */
+
+ /* Get the length of the document to receive */
+ char *length = strcasestr(this->recv_buffer, CONTENT_LENGTH);
+ if (length == NULL) return_error("[tcp/http] missing 'content-length' header");
+
+ /* Skip the header */
+ length += strlen(CONTENT_LENGTH);
+
+ /* Search the end of the line. This is safe because the header will
+ * always end with two newlines. */
+ char *end_of_line = strstr(length, NEWLINE);
+
+ /* Read the length */
+ *end_of_line = '\0';
+ int len = atoi(length);
+ /* Restore the header. */
+ *end_of_line = '\r';
+
+ /* Make sure we're going to download at least something;
+ * zero sized files are, for OpenTTD's purposes, always
+ * wrong. You can't have gzips of 0 bytes! */
+ if (len == 0) return_error("[tcp/http] refusing to download 0 bytes");
+
+ DEBUG(net, 7, "[tcp/http] downloading %i bytes", len);
+ return len;
+ }
+
+ if (strncmp(status, "301", 3) != 0 &&
+ strncmp(status, "302", 3) != 0 &&
+ strncmp(status, "303", 3) != 0 &&
+ strncmp(status, "307", 3) != 0) {
+ /* We are not going to be redirected :(. */
+
+ /* Search the end of the line. This is safe because the header will
+ * always end with two newlines. */
+ *strstr(status, NEWLINE) = '\0';
+ DEBUG(net, 0, "[tcp/http] unhandled status reply %s", status);
+ return -1;
+ }
+
+ if (this->redirect_depth == 5) return_error("[tcp/http] too many redirects, looping redirects?");
+
+ /* Redirect to other URL */
+ char *uri = strcasestr(this->recv_buffer, LOCATION);
+ if (uri == NULL) return_error("[tcp/http] missing 'location' header for redirect");
+
+ uri += strlen(LOCATION);
+
+ /* Search the end of the line. This is safe because the header will
+ * always end with two newlines. */
+ char *end_of_line = strstr(uri, NEWLINE);
+ *end_of_line = '\0';
+
+ DEBUG(net, 6, "[tcp/http] redirecting to %s", uri);
+
+ int ret = NetworkHTTPSocketHandler::Connect(uri, this->callback, this->data, this->redirect_depth + 1);
+ if (ret != 0) return ret;
+
+ /* We've relinguished control of data now. */
+ this->data = NULL;
+
+ /* Restore the header. */
+ *end_of_line = '\r';
+ return 0;
+}
+
+/*static */ int NetworkHTTPSocketHandler::Connect(char *uri, HTTPCallback *callback, const char *data, int depth)
+{
+ char *hname = strstr(uri, "://");
+ if (hname == NULL) return_error("[tcp/http] invalid location");
+
+ hname += 3;
+
+ char *url = strchr(hname, '/');
+ if (url == NULL) return_error("[tcp/http] invalid location");
+
+ *url = '\0';
+
+ /* Fetch the hostname, and possible port number. */
+ const char *company = NULL;
+ const char *port = NULL;
+ ParseConnectionString(&company, &port, hname);
+ if (company != NULL) return_error("[tcp/http] invalid hostname");
+
+ NetworkAddress address(hname, port == NULL ? 80 : atoi(port));
+
+ /* Restore the URL. */
+ *url = '/';
+ new NetworkHTTPContentConnecter(address, callback, url, data, depth);
+ return 0;
+}
+
+#undef return_error
+
+/**
+ * Handle receiving of HTTP data.
+ * @return state of the receival of HTTP data.
+ * > 0: we need more cycles for downloading
+ * = 0: we are done downloading
+ * < 0: we have hit an error
+ */
+int NetworkHTTPSocketHandler::Receive()
+{
+ for (;;) {
+ ssize_t res = recv(this->sock, (char *)this->recv_buffer + this->recv_pos, lengthof(this->recv_buffer) - this->recv_pos, 0);
+ if (res == -1) {
+ int err = GET_LAST_ERROR();
+ if (err != EWOULDBLOCK) {
+ /* Something went wrong... (104 is connection reset by peer) */
+ if (err != 104) DEBUG(net, 0, "recv failed with error %d", err);
+ return -1;
+ }
+ /* Connection would block, so stop for now */
+ return 1;
+ }
+
+ /* No more data... did we get everything we wanted? */
+ if (res == 0) {
+ if (this->recv_length != 0) return -1;
+
+ this->callback->OnReceiveData(NULL, 0);
+ return 0;
+ }
+
+ /* Wait till we read the end-of-header identifier */
+ if (this->recv_length == 0) {
+ int read = this->recv_pos + res;
+ int end = min(read, lengthof(this->recv_buffer) - 1);
+
+ /* Do a 'safe' search for the end of the header. */
+ char prev = this->recv_buffer[end];
+ this->recv_buffer[end] = '\0';
+ char *end_of_header = strstr(this->recv_buffer, END_OF_HEADER);
+ this->recv_buffer[end] = prev;
+
+ if (end_of_header == NULL) {
+ if (read == lengthof(this->recv_buffer)) {
+ DEBUG(net, 0, "[tcp/http] header too big");
+ return -1;
+ }
+ this->recv_pos = read;
+ } else {
+ int ret = this->HandleHeader();
+ if (ret <= 0) return ret;
+
+ this->recv_length = ret;
+
+ end_of_header += strlen(END_OF_HEADER);
+ int len = min(read - (end_of_header - this->recv_buffer), res);
+ if (len != 0) {
+ this->callback->OnReceiveData(end_of_header, len);
+ this->recv_length -= len;
+ }
+
+ this->recv_pos = 0;
+ }
+ } else {
+ res = min(this->recv_length, res);
+ /* Receive whatever we're expecting. */
+ this->callback->OnReceiveData(this->recv_buffer, res);
+ this->recv_length -= res;
+ }
+ }
+}
+
+/* static */ void NetworkHTTPSocketHandler::HTTPReceive()
+{
+ /* No connections, just bail out. */
+ if (_http_connections.Length() == 0) return;
+
+ fd_set read_fd;
+ struct timeval tv;
+
+ FD_ZERO(&read_fd);
+ for (NetworkHTTPSocketHandler **iter = _http_connections.Begin(); iter < _http_connections.End(); iter++) {
+ FD_SET((*iter)->sock, &read_fd);
+ }
+
+ tv.tv_sec = tv.tv_usec = 0; // don't block at all.
+#if !defined(__MORPHOS__) && !defined(__AMIGA__)
+ int n = select(FD_SETSIZE, &read_fd, NULL, NULL, &tv);
+#else
+ int n = WaitSelect(FD_SETSIZE, &read_fd, NULL, NULL, &tv, NULL);
+#endif
+ if (n == -1) return;
+
+ for (NetworkHTTPSocketHandler **iter = _http_connections.Begin(); iter < _http_connections.End(); /* nothing */) {
+ NetworkHTTPSocketHandler *cur = *iter;
+
+ if (FD_ISSET(cur->sock, &read_fd)) {
+ int ret = cur->Receive();
+ /* First send the failure. */
+ if (ret < 0) cur->callback->OnFailure();
+ if (ret <= 0) {
+ /* Then... the connection can be closed */
+ cur->CloseConnection();
+ _http_connections.Erase(iter);
+ delete cur;
+ continue;
+ }
+ }
+ iter++;
+ }
+}
+
+#endif /* ENABLE_NETWORK */
diff --git a/src/network/core/tcp_http.h b/src/network/core/tcp_http.h
new file mode 100644
index 000000000..6939577ad
--- /dev/null
+++ b/src/network/core/tcp_http.h
@@ -0,0 +1,143 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * 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.
+ * 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.
+ * 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/>.
+ */
+
+/**
+ * @file tcp_http.h Basic functions to receive and send HTTP TCP packets.
+ */
+
+#ifndef NETWORK_CORE_TCP_HTTP_H
+#define NETWORK_CORE_TCP_HTTP_H
+
+#include "address.h"
+
+#ifdef ENABLE_NETWORK
+
+/** Callback for when the HTTP handler has something to tell us. */
+struct HTTPCallback {
+ /**
+ * An error has occured and the connection has been closed.
+ * @note HTTP socket handler is closed/freed.
+ */
+ virtual void OnFailure() = 0;
+
+ /**
+ * We're receiving data.
+ * @param data the received data, NULL when all data has been received.
+ * @param length the amount of received data, 0 when all data has been received.
+ * @note When NULL is sent the HTTP socket handler is closed/freed.
+ */
+ virtual void OnReceiveData(const char *data, size_t length) = 0;
+};
+
+/** Base socket handler for HTTP traffic. */
+class NetworkHTTPSocketHandler : public NetworkSocketHandler {
+private:
+ char recv_buffer[4096]; ///< Partially received message.
+ int recv_pos; ///< Current position in buffer.
+ int recv_length; ///< Length of the data still retrieving.
+ HTTPCallback *callback; ///< The callback to call for the incoming data.
+ const char *data; ///< The (POST) data we might want to forward (to a redirect).
+ int redirect_depth; ///< The depth of the redirection.
+
+ int HandleHeader();
+ int Receive();
+public:
+ SOCKET sock; ///< The socket currently connected to
+
+ /**
+ * Whether this socket is currently bound to a socket.
+ * @return true when the socket is bound, false otherwise
+ */
+ bool IsConnected() const
+ {
+ return this->sock != INVALID_SOCKET;
+ }
+
+ virtual NetworkRecvStatus CloseConnection(bool error = true);
+
+ /**
+ * Start the querying
+ * @param sock the socket of this connection
+ * @param callback the callback for HTTP retrieval
+ * @param url the url at the server
+ * @param data the data to send
+ * @param depth the depth (redirect recursion) of the queries
+ */
+ NetworkHTTPSocketHandler(SOCKET sock, HTTPCallback *callback,
+ const char *host, const char *url, const char *data, int depth);
+
+ /** Free whatever needs to be freed. */
+ ~NetworkHTTPSocketHandler();
+
+ /**
+ * Connect to the given URI.
+ * @param uri the URI to connect to.
+ * @param callback the callback to send data back on.
+ * @param data the data we want to send (as POST).
+ * @param depth the recursion/redirect depth.
+ */
+ static int Connect(char *uri, HTTPCallback *callback,
+ const char *data = NULL, int depth = 0);
+
+ /**
+ * Do the receiving for all HTTP connections.
+ */
+ static void HTTPReceive();
+};
+
+/** Connect with a HTTP server and do ONE query. */
+class NetworkHTTPContentConnecter : TCPConnecter {
+ HTTPCallback *callback; ///< Callback to tell that we received some data (or won't).
+ const char *url; ///< The URL we want to get at the server.
+ const char *data; ///< The data to send
+ int depth; ///< How far we have recursed
+
+public:
+ /**
+ * Start the connecting.
+ * @param address the address to connect to
+ * @param callback the callback for HTTP retrieval
+ * @param url the url at the server
+ * @param data the data to send
+ * @param depth the depth (redirect recursion) of the queries
+ */
+ NetworkHTTPContentConnecter(const NetworkAddress &address,
+ HTTPCallback *callback, const char *url,
+ const char *data = NULL, int depth = 0) :
+ TCPConnecter(address),
+ callback(callback),
+ url(strdup(url)),
+ data(data),
+ depth(depth)
+ {
+ }
+
+ /** Free all our allocated data. */
+ ~NetworkHTTPContentConnecter()
+ {
+ free((void*)this->url);
+ }
+
+ virtual void OnFailure()
+ {
+ this->callback->OnFailure();
+ free((void*)this->data);
+ }
+
+ virtual void OnConnect(SOCKET s)
+ {
+ new NetworkHTTPSocketHandler(s, this->callback, this->address.GetHostname(), this->url, this->data, this->depth);
+ /* We've relinguished control of data now. */
+ this->data = NULL;
+ }
+};
+
+#endif /* ENABLE_NETWORK */
+
+#endif /* NETWORK_CORE_HTTP_H */
diff --git a/src/network/network.cpp b/src/network/network.cpp
index 380902d63..6578fcff7 100644
--- a/src/network/network.cpp
+++ b/src/network/network.cpp
@@ -1050,6 +1050,7 @@ void NetworkUDPGameLoop()
{
_network_content_client.SendReceive();
TCPConnecter::CheckCallbacks();
+ NetworkHTTPSocketHandler::HTTPReceive();
if (_network_udp_server) {
_udp_server_socket->ReceivePackets();
diff --git a/src/network/network_content.cpp b/src/network/network_content.cpp
index 576d5ad30..ed61fce19 100644
--- a/src/network/network_content.cpp
+++ b/src/network/network_content.cpp
@@ -18,6 +18,7 @@
#include "../gui.h"
#include "../variables.h"
#include "../base_media_base.h"
+#include "../settings_type.h"
#include "network_content.h"
#include "table/strings.h"
@@ -251,7 +252,7 @@ void ClientNetworkContentSocketHandler::RequestContentList(ContentVector *cv, bo
}
}
-void ClientNetworkContentSocketHandler::DownloadSelectedContent(uint &files, uint &bytes)
+void ClientNetworkContentSocketHandler::DownloadSelectedContent(uint &files, uint &bytes, bool fallback)
{
bytes = 0;
@@ -269,8 +270,41 @@ void ClientNetworkContentSocketHandler::DownloadSelectedContent(uint &files, uin
/* If there's nothing to download, do nothing. */
if (files == 0) return;
- uint count = files;
- ContentID *content_ids = content.Begin();
+ if (_settings_client.network.no_http_content_downloads || fallback) {
+ this->DownloadSelectedContentFallback(content);
+ } else {
+ this->DownloadSelectedContentHTTP(content);
+ }
+}
+
+void ClientNetworkContentSocketHandler::DownloadSelectedContentHTTP(const ContentIDList &content)
+{
+ uint count = content.Length();
+
+ /* Allocate memory for the whole request.
+ * Requests are "id\nid\n..." (as strings), so assume the maximum ID,
+ * which is uint32 so 10 characters long. Then the newlines and
+ * multiply that all with the count and then add the '\0'. */
+ uint bytes = (10 + 1) * count + 1;
+ char *content_request = MallocT<char>(bytes);
+ const char *lastof = content_request + bytes - 1;
+
+ char *p = content_request;
+ for (const ContentID *id = content.Begin(); id != content.End(); id++) {
+ p += seprintf(p, lastof, "%d\n", *id);
+ }
+
+ this->http_response_index = -1;
+
+ NetworkAddress address(NETWORK_CONTENT_MIRROR_HOST, NETWORK_CONTENT_MIRROR_PORT);
+ new NetworkHTTPContentConnecter(address, this, NETWORK_CONTENT_MIRROR_URL, content_request);
+ /* NetworkHTTPContentConnecter takes over freeing of content_request! */
+}
+
+void ClientNetworkContentSocketHandler::DownloadSelectedContentFallback(const ContentIDList &content)
+{
+ uint count = content.Length();
+ const ContentID *content_ids = content.Begin();
this->Connect();
while (count > 0) {
@@ -451,6 +485,146 @@ void ClientNetworkContentSocketHandler::AfterDownload()
}
}
+/* Also called to just clean up the mess. */
+void ClientNetworkContentSocketHandler::OnFailure()
+{
+ /* If we fail, download the rest via the 'old' system. */
+ uint files, bytes;
+ this->DownloadSelectedContent(files, bytes, true);
+
+ this->http_response.Reset();
+ this->http_response_index = -2;
+
+ if (this->curFile != NULL) {
+ fclose(this->curFile);
+ this->curFile = NULL;
+ }
+}
+
+void ClientNetworkContentSocketHandler::OnReceiveData(const char *data, size_t length)
+{
+ assert(data == NULL || length != 0);
+
+ /* Ignore any latent data coming from a connection we closed. */
+ if (this->http_response_index == -2) return;
+
+ if (this->http_response_index == -1) {
+ if (data != NULL) {
+ /* Append the rest of the response. */
+ memcpy(this->http_response.Append(length), data, length);
+ return;
+ } else {
+ /* Make sure the response is properly terminated. */
+ *this->http_response.Append() = '\0';
+
+ /* And prepare for receiving the rest of the data. */
+ this->http_response_index = 0;
+ }
+ }
+
+ if (data != NULL) {
+ /* We have data, so write it to the file. */
+ if (fwrite(data, 1, length, this->curFile) != length) {
+ /* Writing failed somehow, let try via the old method. */
+ this->OnFailure();
+ } else {
+ /* Just received the data. */
+ this->OnDownloadProgress(this->curInfo, (uint)length);
+ }
+ /* Nothing more to do now. */
+ return;
+ }
+
+ if (this->curFile != NULL) {
+ /* We've finished downloading a file. */
+ this->AfterDownload();
+ }
+
+ if ((uint)this->http_response_index >= this->http_response.Length()) {
+ /* It's not a real failure, but if there's
+ * nothing more to download it helps with
+ * cleaning up the stuff we allocated. */
+ this->OnFailure();
+ return;
+ }
+
+ delete this->curInfo;
+ /* When we haven't opened a file this must be our first packet with metadata. */
+ this->curInfo = new ContentInfo;
+
+/** Check p for not being null and return calling OnFailure if that's not the case. */
+#define check(p) { if ((p) == NULL) { this->OnFailure(); return; } }
+/** Check p for not being null and then terminate, or return calling OnFailure. */
+#define check_and_terminate(p) { check(p); *(p) = '\0'; }
+
+ for (;;) {
+ char *str = this->http_response.Begin() + this->http_response_index;
+ char *p = strchr(str, '\n');
+ check_and_terminate(p);
+
+ /* Update the index for the next one */
+ this->http_response_index += strlen(str) + 1;
+
+ /* Read the ID */
+ p = strchr(str, ',');
+ check_and_terminate(p);
+ this->curInfo->id = (ContentID)atoi(str);
+
+ /* Read the type */
+ str = p + 1;
+ p = strchr(str, ',');
+ check_and_terminate(p);
+ this->curInfo->type = (ContentType)atoi(str);
+
+ /* Read the file size */
+ str = p + 1;
+ p = strchr(str, ',');
+ check_and_terminate(p);
+ this->curInfo->filesize = atoi(str);
+
+ /* Read the URL */
+ str = p + 1;
+ /* Is it a fallback URL? If so, just continue with the next one. */
+ if (strncmp(str, "ottd", 4) == 0) {
+ if ((uint)this->http_response_index >= this->http_response.Length()) {
+ /* Have we gone through all lines? */
+ this->OnFailure();
+ return;
+ }
+ continue;
+ }
+
+ p = strrchr(str, '/');
+ check(p);
+
+ char tmp[MAX_PATH];
+ if (strecpy(tmp, p, lastof(tmp)) == lastof(tmp)) {
+ this->OnFailure();
+ return;
+ }
+ /* Remove the extension from the string. */
+ for (uint i = 0; i < 2; i++) {
+ p = strrchr(tmp, '.');
+ check_and_terminate(p);
+ }
+
+ /* Copy the string, without extension, to the filename. */
+ strecpy(this->curInfo->filename, tmp, lastof(this->curInfo->filename));
+
+ /* Request the next file. */
+ if (!this->BeforeDownload()) {
+ this->OnFailure();
+ return;
+ }
+
+ NetworkHTTPSocketHandler::Connect(str, this);
+ return;
+ }
+
+#undef check
+#undef check_and_terminate
+}
+
/**
* Create a socket handler with the given socket and (server) address.
* @param s the socket to communicate over
@@ -458,6 +632,7 @@ void ClientNetworkContentSocketHandler::AfterDownload()
*/
ClientNetworkContentSocketHandler::ClientNetworkContentSocketHandler() :
NetworkContentSocketHandler(),
+ http_response_index(-2),
curFile(NULL),
curInfo(NULL),
isConnecting(false)
diff --git a/src/network/network_content.h b/src/network/network_content.h
index 0be0a80cf..a1fea1fb5 100644
--- a/src/network/network_content.h
+++ b/src/network/network_content.h
@@ -13,6 +13,7 @@
#define NETWORK_CONTENT_H
#include "core/tcp_content.h"
+#include "core/tcp_http.h"
#if defined(ENABLE_NETWORK)
@@ -63,12 +64,14 @@ struct ContentCallback {
/**
* Socket handler for the content server connection
*/
-class ClientNetworkContentSocketHandler : public NetworkContentSocketHandler, ContentCallback {
+class ClientNetworkContentSocketHandler : public NetworkContentSocketHandler, ContentCallback, HTTPCallback {
protected:
typedef SmallVector<ContentID, 4> ContentIDList;
SmallVector<ContentCallback *, 2> callbacks; ///< Callbacks to notify "the world"
ContentIDList requested; ///< ContentIDs we already requested (so we don't do it again)
ContentVector infos; ///< All content info we received
+ SmallVector<char, 1024> http_response; ///< The HTTP response to the requests we've been doing
+ int http_response_index; ///< Where we are, in the response, with handling it
FILE *curFile; ///< Currently downloaded file
ContentInfo *curInfo; ///< Information about the currently downloaded file
@@ -89,8 +92,14 @@ protected:
void OnDownloadProgress(const ContentInfo *ci, uint bytes);
void OnDownloadComplete(ContentID cid);
+ void OnFailure();
+ void OnReceiveData(const char *data, size_t length);
+
bool BeforeDownload();
void AfterDownload();
+
+ void DownloadSelectedContentHTTP(const ContentIDList &content);
+ void DownloadSelectedContentFallback(const ContentIDList &content);
public:
/** The idle timeout; when to close the connection because it's idle. */
static const int IDLE_TIMEOUT = 60 * 1000;
@@ -106,7 +115,7 @@ public:
void RequestContentList(uint count, const ContentID *content_ids);
void RequestContentList(ContentVector *cv, bool send_md5sum = true);
- void DownloadSelectedContent(uint &files, uint &bytes);
+ void DownloadSelectedContent(uint &files, uint &bytes, bool fallback = false);
void Select(ContentID cid);
void Unselect(ContentID cid);
diff --git a/src/settings_type.h b/src/settings_type.h
index fee472165..71b5116f6 100644
--- a/src/settings_type.h
+++ b/src/settings_type.h
@@ -147,6 +147,7 @@ struct NetworkSettings {
bool reload_cfg; ///< reload the config file before restarting
char last_host[NETWORK_HOSTNAME_LENGTH]; ///< IP address of the last joined server
uint16 last_port; ///< port of the last joined server
+ bool no_http_content_downloads; ///< do not do content downloads over HTTP
#else /* ENABLE_NETWORK */
#endif
};
diff --git a/src/table/settings.h b/src/table/settings.h
index 00dd770d6..6607f6396 100644
--- a/src/table/settings.h
+++ b/src/table/settings.h
@@ -631,6 +631,7 @@ const SettingDesc _settings[] = {
SDTC_BOOL(network.reload_cfg, S, NO, false, STR_NULL, NULL),
SDTC_STR(network.last_host, SLE_STRB, S, 0, "", STR_NULL, NULL),
SDTC_VAR(network.last_port, SLE_UINT16, S, 0, 0, 0, UINT16_MAX, 0, STR_NULL, NULL),
+ SDTC_BOOL(network.no_http_content_downloads, S, 0, false, STR_NULL, NULL),
#endif /* ENABLE_NETWORK */
/*