changeset 14437:a4c177e14a79 draft

(svn r18994) -Change: content mirroring support (based on work by TrueBrain).
author rubidium <rubidium@openttd.org>
date Wed, 03 Feb 2010 18:42:23 +0000
parents ce2db37fe0f4
children fd78717af07a
files projects/openttd_vs80.vcproj projects/openttd_vs90.vcproj source.list src/network/core/config.h src/network/core/tcp_http.cpp src/network/core/tcp_http.h src/network/network.cpp src/network/network_content.cpp src/network/network_content.h src/settings_type.h src/table/settings.h
diffstat 11 files changed, 675 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- 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>
--- 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>
--- a/source.list
+++ b/source.list
@@ -803,6 +803,8 @@
 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
 
--- 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)
 
new file mode 100644
--- /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 */
new file mode 100644
--- /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 */
--- a/src/network/network.cpp
+++ b/src/network/network.cpp
@@ -1050,6 +1050,7 @@
 {
 	_network_content_client.SendReceive();
 	TCPConnecter::CheckCallbacks();
+	NetworkHTTPSocketHandler::HTTPReceive();
 
 	if (_network_udp_server) {
 		_udp_server_socket->ReceivePackets();
--- 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::DownloadSelectedContent(uint &files, uint &bytes)
+void ClientNetworkContentSocketHandler::DownloadSelectedContent(uint &files, uint &bytes, bool fallback)
 {
 	bytes = 0;
 
@@ -269,8 +270,41 @@
 	/* 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 @@
 	}
 }
 
+/* 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 @@
  */
 ClientNetworkContentSocketHandler::ClientNetworkContentSocketHandler() :
 	NetworkContentSocketHandler(),
+	http_response_index(-2),
 	curFile(NULL),
 	curInfo(NULL),
 	isConnecting(false)
--- 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 @@
 /**
  * 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 @@
 	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 @@
 	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);
--- a/src/settings_type.h
+++ b/src/settings_type.h
@@ -147,6 +147,7 @@
 	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
 };
--- a/src/table/settings.h
+++ b/src/table/settings.h
@@ -631,6 +631,7 @@
 	 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 */
 
 	/*