1 /*
2  * This file is part of OpenTTD.
3  * 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.
4  * 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.
5  * 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/>.
6  */
7 
8 /** @file network_content.cpp Content sending/receiving part of the network protocol. */
9 
10 #include "../stdafx.h"
11 #include "../rev.h"
12 #include "../ai/ai.hpp"
13 #include "../game/game.hpp"
14 #include "../window_func.h"
15 #include "../error.h"
16 #include "../base_media_base.h"
17 #include "../settings_type.h"
18 #include "network_content.h"
19 
20 #include "table/strings.h"
21 
22 #if defined(WITH_ZLIB)
23 #include <zlib.h>
24 #endif
25 
26 #ifdef __EMSCRIPTEN__
27 #	include <emscripten.h>
28 #endif
29 
30 #include "../safeguards.h"
31 
32 extern bool HasScenario(const ContentInfo *ci, bool md5sum);
33 
34 /** The client we use to connect to the server. */
35 ClientNetworkContentSocketHandler _network_content_client;
36 
37 /** Wrapper function for the HasProc */
HasGRFConfig(const ContentInfo * ci,bool md5sum)38 static bool HasGRFConfig(const ContentInfo *ci, bool md5sum)
39 {
40 	return FindGRFConfig(BSWAP32(ci->unique_id), md5sum ? FGCM_EXACT : FGCM_ANY, md5sum ? ci->md5sum : nullptr) != nullptr;
41 }
42 
43 /**
44  * Check whether a function piece of content is locally known.
45  * Matches on the unique ID and possibly the MD5 checksum.
46  * @param ci     the content info to search for
47  * @param md5sum also match the MD5 checksum?
48  * @return true iff it's known
49  */
50 typedef bool (*HasProc)(const ContentInfo *ci, bool md5sum);
51 
Receive_SERVER_INFO(Packet * p)52 bool ClientNetworkContentSocketHandler::Receive_SERVER_INFO(Packet *p)
53 {
54 	ContentInfo *ci = new ContentInfo();
55 	ci->type     = (ContentType)p->Recv_uint8();
56 	ci->id       = (ContentID)p->Recv_uint32();
57 	ci->filesize = p->Recv_uint32();
58 
59 	ci->name        = p->Recv_string(NETWORK_CONTENT_NAME_LENGTH);
60 	ci->version     = p->Recv_string(NETWORK_CONTENT_VERSION_LENGTH);
61 	ci->url         = p->Recv_string(NETWORK_CONTENT_URL_LENGTH);
62 	ci->description = p->Recv_string(NETWORK_CONTENT_DESC_LENGTH, SVS_REPLACE_WITH_QUESTION_MARK | SVS_ALLOW_NEWLINE);
63 
64 	ci->unique_id = p->Recv_uint32();
65 	for (uint j = 0; j < sizeof(ci->md5sum); j++) {
66 		ci->md5sum[j] = p->Recv_uint8();
67 	}
68 
69 	uint dependency_count = p->Recv_uint8();
70 	ci->dependencies.reserve(dependency_count);
71 	for (uint i = 0; i < dependency_count; i++) {
72 		ContentID dependency_cid = (ContentID)p->Recv_uint32();
73 		ci->dependencies.push_back(dependency_cid);
74 		this->reverse_dependency_map.insert({ dependency_cid, ci->id });
75 	}
76 
77 	uint tag_count = p->Recv_uint8();
78 	ci->tags.reserve(tag_count);
79 	for (uint i = 0; i < tag_count; i++) ci->tags.push_back(p->Recv_string(NETWORK_CONTENT_TAG_LENGTH));
80 
81 	if (!ci->IsValid()) {
82 		delete ci;
83 		this->CloseConnection();
84 		return false;
85 	}
86 
87 	/* Find the appropriate check function */
88 	HasProc proc = nullptr;
89 	switch (ci->type) {
90 		case CONTENT_TYPE_NEWGRF:
91 			proc = HasGRFConfig;
92 			break;
93 
94 		case CONTENT_TYPE_BASE_GRAPHICS:
95 			proc = BaseGraphics::HasSet;
96 			break;
97 
98 		case CONTENT_TYPE_BASE_MUSIC:
99 			proc = BaseMusic::HasSet;
100 			break;
101 
102 		case CONTENT_TYPE_BASE_SOUNDS:
103 			proc = BaseSounds::HasSet;
104 			break;
105 
106 		case CONTENT_TYPE_AI:
107 			proc = AI::HasAI; break;
108 			break;
109 
110 		case CONTENT_TYPE_AI_LIBRARY:
111 			proc = AI::HasAILibrary; break;
112 			break;
113 
114 		case CONTENT_TYPE_GAME:
115 			proc = Game::HasGame; break;
116 			break;
117 
118 		case CONTENT_TYPE_GAME_LIBRARY:
119 			proc = Game::HasGameLibrary; break;
120 			break;
121 
122 		case CONTENT_TYPE_SCENARIO:
123 		case CONTENT_TYPE_HEIGHTMAP:
124 			proc = HasScenario;
125 			break;
126 
127 		default:
128 			break;
129 	}
130 
131 	if (proc != nullptr) {
132 		if (proc(ci, true)) {
133 			ci->state = ContentInfo::ALREADY_HERE;
134 		} else {
135 			ci->state = ContentInfo::UNSELECTED;
136 			if (proc(ci, false)) ci->upgrade = true;
137 		}
138 	} else {
139 		ci->state = ContentInfo::UNSELECTED;
140 	}
141 
142 	/* Something we don't have and has filesize 0 does not exist in the system */
143 	if (ci->state == ContentInfo::UNSELECTED && ci->filesize == 0) ci->state = ContentInfo::DOES_NOT_EXIST;
144 
145 	/* Do we already have a stub for this? */
146 	for (ContentInfo *ici : this->infos) {
147 		if (ici->type == ci->type && ici->unique_id == ci->unique_id &&
148 				memcmp(ci->md5sum, ici->md5sum, sizeof(ci->md5sum)) == 0) {
149 			/* Preserve the name if possible */
150 			if (ci->name.empty()) ci->name = ici->name;
151 			if (ici->IsSelected()) ci->state = ici->state;
152 
153 			/*
154 			 * As ici might be selected by the content window we cannot delete that.
155 			 * However, we want to keep most of the values of ci, except the values
156 			 * we (just) already preserved.
157 			 */
158 			*ici = *ci;
159 			delete ci;
160 
161 			this->OnReceiveContentInfo(ici);
162 			return true;
163 		}
164 	}
165 
166 	/* Missing content info? Don't list it */
167 	if (ci->filesize == 0) {
168 		delete ci;
169 		return true;
170 	}
171 
172 	this->infos.push_back(ci);
173 
174 	/* Incoming data means that we might need to reconsider dependencies */
175 	ConstContentVector parents;
176 	this->ReverseLookupTreeDependency(parents, ci);
177 	for (const ContentInfo *ici : parents) {
178 		this->CheckDependencyState(const_cast<ContentInfo *>(ici));
179 	}
180 
181 	this->OnReceiveContentInfo(ci);
182 
183 	return true;
184 }
185 
186 /**
187  * Request the content list for the given type.
188  * @param type The content type to request the list for.
189  */
RequestContentList(ContentType type)190 void ClientNetworkContentSocketHandler::RequestContentList(ContentType type)
191 {
192 	if (type == CONTENT_TYPE_END) {
193 		this->RequestContentList(CONTENT_TYPE_BASE_GRAPHICS);
194 		this->RequestContentList(CONTENT_TYPE_BASE_MUSIC);
195 		this->RequestContentList(CONTENT_TYPE_BASE_SOUNDS);
196 		this->RequestContentList(CONTENT_TYPE_SCENARIO);
197 		this->RequestContentList(CONTENT_TYPE_HEIGHTMAP);
198 		this->RequestContentList(CONTENT_TYPE_AI);
199 		this->RequestContentList(CONTENT_TYPE_AI_LIBRARY);
200 		this->RequestContentList(CONTENT_TYPE_GAME);
201 		this->RequestContentList(CONTENT_TYPE_GAME_LIBRARY);
202 		this->RequestContentList(CONTENT_TYPE_NEWGRF);
203 		return;
204 	}
205 
206 	this->Connect();
207 
208 	Packet *p = new Packet(PACKET_CONTENT_CLIENT_INFO_LIST);
209 	p->Send_uint8 ((byte)type);
210 	p->Send_uint32(0xffffffff);
211 	p->Send_uint8 (1);
212 	p->Send_string("vanilla");
213 	p->Send_string(_openttd_content_version);
214 
215 	/* Patchpacks can extend the list with one. In BaNaNaS metadata you can
216 	 * add a branch in the 'compatibility' list, to filter on this. If you want
217 	 * your patchpack to be mentioned in the BaNaNaS web-interface, create an
218 	 * issue on https://github.com/OpenTTD/bananas-api asking for this.
219 
220 	p->Send_string("patchpack"); // Or what-ever the name of your patchpack is.
221 	p->Send_string(_openttd_content_version_patchpack);
222 
223 	*/
224 
225 	this->SendPacket(p);
226 }
227 
228 /**
229  * Request the content list for a given number of content IDs.
230  * @param count The number of IDs to request.
231  * @param content_ids The unique identifiers of the content to request information about.
232  */
RequestContentList(uint count,const ContentID * content_ids)233 void ClientNetworkContentSocketHandler::RequestContentList(uint count, const ContentID *content_ids)
234 {
235 	this->Connect();
236 
237 	while (count > 0) {
238 		/* We can "only" send a limited number of IDs in a single packet.
239 		 * A packet begins with the packet size and a byte for the type.
240 		 * Then this packet adds a uint16 for the count in this packet.
241 		 * The rest of the packet can be used for the IDs. */
242 		uint p_count = std::min<uint>(count, (TCP_MTU - sizeof(PacketSize) - sizeof(byte) - sizeof(uint16)) / sizeof(uint32));
243 
244 		Packet *p = new Packet(PACKET_CONTENT_CLIENT_INFO_ID, TCP_MTU);
245 		p->Send_uint16(p_count);
246 
247 		for (uint i = 0; i < p_count; i++) {
248 			p->Send_uint32(content_ids[i]);
249 		}
250 
251 		this->SendPacket(p);
252 		count -= p_count;
253 		content_ids += p_count;
254 	}
255 }
256 
257 /**
258  * Request the content list for a list of content.
259  * @param cv List with unique IDs and MD5 checksums.
260  * @param send_md5sum Whether we want a MD5 checksum matched set of files or not.
261  */
RequestContentList(ContentVector * cv,bool send_md5sum)262 void ClientNetworkContentSocketHandler::RequestContentList(ContentVector *cv, bool send_md5sum)
263 {
264 	if (cv == nullptr) return;
265 
266 	this->Connect();
267 
268 	assert(cv->size() < 255);
269 	assert(cv->size() < (TCP_MTU - sizeof(PacketSize) - sizeof(byte) - sizeof(uint8)) /
270 			(sizeof(uint8) + sizeof(uint32) + (send_md5sum ? /*sizeof(ContentInfo::md5sum)*/16 : 0)));
271 
272 	Packet *p = new Packet(send_md5sum ? PACKET_CONTENT_CLIENT_INFO_EXTID_MD5 : PACKET_CONTENT_CLIENT_INFO_EXTID, TCP_MTU);
273 	p->Send_uint8((uint8)cv->size());
274 
275 	for (const ContentInfo *ci : *cv) {
276 		p->Send_uint8((byte)ci->type);
277 		p->Send_uint32(ci->unique_id);
278 		if (!send_md5sum) continue;
279 
280 		for (uint j = 0; j < sizeof(ci->md5sum); j++) {
281 			p->Send_uint8(ci->md5sum[j]);
282 		}
283 	}
284 
285 	this->SendPacket(p);
286 
287 	for (ContentInfo *ci : *cv) {
288 		bool found = false;
289 		for (ContentInfo *ci2 : this->infos) {
290 			if (ci->type == ci2->type && ci->unique_id == ci2->unique_id &&
291 					(!send_md5sum || memcmp(ci->md5sum, ci2->md5sum, sizeof(ci->md5sum)) == 0)) {
292 				found = true;
293 				break;
294 			}
295 		}
296 		if (!found) {
297 			this->infos.push_back(ci);
298 		} else {
299 			delete ci;
300 		}
301 	}
302 }
303 
304 /**
305  * Actually begin downloading the content we selected.
306  * @param[out] files The number of files we are going to download.
307  * @param[out] bytes The number of bytes we are going to download.
308  * @param fallback Whether to use the fallback or not.
309  */
DownloadSelectedContent(uint & files,uint & bytes,bool fallback)310 void ClientNetworkContentSocketHandler::DownloadSelectedContent(uint &files, uint &bytes, bool fallback)
311 {
312 	bytes = 0;
313 
314 #ifdef __EMSCRIPTEN__
315 	/* Emscripten is loaded via an HTTPS connection. As such, it is very
316 	 * difficult to make HTTP connections. So always use the TCP method of
317 	 * downloading content. */
318 	fallback = true;
319 #endif
320 
321 	ContentIDList content;
322 	for (const ContentInfo *ci : this->infos) {
323 		if (!ci->IsSelected() || ci->state == ContentInfo::ALREADY_HERE) continue;
324 
325 		content.push_back(ci->id);
326 		bytes += ci->filesize;
327 	}
328 
329 	files = (uint)content.size();
330 
331 	/* If there's nothing to download, do nothing. */
332 	if (files == 0) return;
333 
334 	if (_settings_client.network.no_http_content_downloads || fallback) {
335 		this->DownloadSelectedContentFallback(content);
336 	} else {
337 		this->DownloadSelectedContentHTTP(content);
338 	}
339 }
340 
341 /**
342  * Initiate downloading the content over HTTP.
343  * @param content The content to download.
344  */
DownloadSelectedContentHTTP(const ContentIDList & content)345 void ClientNetworkContentSocketHandler::DownloadSelectedContentHTTP(const ContentIDList &content)
346 {
347 	uint count = (uint)content.size();
348 
349 	/* Allocate memory for the whole request.
350 	 * Requests are "id\nid\n..." (as strings), so assume the maximum ID,
351 	 * which is uint32 so 10 characters long. Then the newlines and
352 	 * multiply that all with the count and then add the '\0'. */
353 	uint bytes = (10 + 1) * count + 1;
354 	char *content_request = MallocT<char>(bytes);
355 	const char *lastof = content_request + bytes - 1;
356 
357 	char *p = content_request;
358 	for (const ContentID &id : content) {
359 		p += seprintf(p, lastof, "%d\n", id);
360 	}
361 
362 	this->http_response_index = -1;
363 
364 	new NetworkHTTPContentConnecter(NetworkContentMirrorConnectionString(), this, NETWORK_CONTENT_MIRROR_URL, content_request);
365 	/* NetworkHTTPContentConnecter takes over freeing of content_request! */
366 }
367 
368 /**
369  * Initiate downloading the content over the fallback protocol.
370  * @param content The content to download.
371  */
DownloadSelectedContentFallback(const ContentIDList & content)372 void ClientNetworkContentSocketHandler::DownloadSelectedContentFallback(const ContentIDList &content)
373 {
374 	uint count = (uint)content.size();
375 	const ContentID *content_ids = content.data();
376 	this->Connect();
377 
378 	while (count > 0) {
379 		/* We can "only" send a limited number of IDs in a single packet.
380 		 * A packet begins with the packet size and a byte for the type.
381 		 * Then this packet adds a uint16 for the count in this packet.
382 		 * The rest of the packet can be used for the IDs. */
383 		uint p_count = std::min<uint>(count, (TCP_MTU - sizeof(PacketSize) - sizeof(byte) - sizeof(uint16)) / sizeof(uint32));
384 
385 		Packet *p = new Packet(PACKET_CONTENT_CLIENT_CONTENT, TCP_MTU);
386 		p->Send_uint16(p_count);
387 
388 		for (uint i = 0; i < p_count; i++) {
389 			p->Send_uint32(content_ids[i]);
390 		}
391 
392 		this->SendPacket(p);
393 		count -= p_count;
394 		content_ids += p_count;
395 	}
396 }
397 
398 /**
399  * Determine the full filename of a piece of content information
400  * @param ci         the information to get the filename from
401  * @param compressed should the filename end with .gz?
402  * @return a statically allocated buffer with the filename or
403  *         nullptr when no filename could be made.
404  */
GetFullFilename(const ContentInfo * ci,bool compressed)405 static std::string GetFullFilename(const ContentInfo *ci, bool compressed)
406 {
407 	Subdirectory dir = GetContentInfoSubDir(ci->type);
408 	if (dir == NO_DIRECTORY) return {};
409 
410 	std::string buf = FioGetDirectory(SP_AUTODOWNLOAD_DIR, dir);
411 	buf += ci->filename;
412 	buf += compressed ? ".tar.gz" : ".tar";
413 
414 	return buf;
415 }
416 
417 /**
418  * Gunzip a given file and remove the .gz if successful.
419  * @param ci container with filename
420  * @return true if the gunzip completed
421  */
GunzipFile(const ContentInfo * ci)422 static bool GunzipFile(const ContentInfo *ci)
423 {
424 #if defined(WITH_ZLIB)
425 	bool ret = true;
426 
427 	/* Need to open the file with fopen() to support non-ASCII on Windows. */
428 	FILE *ftmp = fopen(GetFullFilename(ci, true).c_str(), "rb");
429 	if (ftmp == nullptr) return false;
430 	/* Duplicate the handle, and close the FILE*, to avoid double-closing the handle later. */
431 	int fdup = dup(fileno(ftmp));
432 	gzFile fin = gzdopen(fdup, "rb");
433 	fclose(ftmp);
434 
435 	FILE *fout = fopen(GetFullFilename(ci, false).c_str(), "wb");
436 
437 	if (fin == nullptr || fout == nullptr) {
438 		ret = false;
439 	} else {
440 		byte buff[8192];
441 		for (;;) {
442 			int read = gzread(fin, buff, sizeof(buff));
443 			if (read == 0) {
444 				/* If gzread() returns 0, either the end-of-file has been
445 				 * reached or an underlying read error has occurred.
446 				 *
447 				 * gzeof() can't be used, because:
448 				 * 1.2.5 - it is safe, 1 means 'everything was OK'
449 				 * 1.2.3.5, 1.2.4 - 0 or 1 is returned 'randomly'
450 				 * 1.2.3.3 - 1 is returned for truncated archive
451 				 *
452 				 * So we use gzerror(). When proper end of archive
453 				 * has been reached, then:
454 				 * errnum == Z_STREAM_END in 1.2.3.3,
455 				 * errnum == 0 in 1.2.4 and 1.2.5 */
456 				int errnum;
457 				gzerror(fin, &errnum);
458 				if (errnum != 0 && errnum != Z_STREAM_END) ret = false;
459 				break;
460 			}
461 			if (read < 0 || (size_t)read != fwrite(buff, 1, read, fout)) {
462 				/* If gzread() returns -1, there was an error in archive */
463 				ret = false;
464 				break;
465 			}
466 			/* DO NOT DO THIS! It will fail to detect broken archive with 1.2.3.3!
467 			 * if (read < sizeof(buff)) break; */
468 		}
469 	}
470 
471 	if (fin != nullptr) {
472 		gzclose(fin);
473 	} else if (fdup != -1) {
474 		/* Failing gzdopen does not close the passed file descriptor. */
475 		close(fdup);
476 	}
477 	if (fout != nullptr) fclose(fout);
478 
479 	return ret;
480 #else
481 	NOT_REACHED();
482 #endif /* defined(WITH_ZLIB) */
483 }
484 
485 /**
486  * Simple wrapper around fwrite to be able to pass it to Packet's TransferOut.
487  * @param file   The file to write data to.
488  * @param buffer The buffer to write to the file.
489  * @param amount The number of bytes to write.
490  * @return The number of bytes that were written.
491  */
TransferOutFWrite(FILE * file,const char * buffer,size_t amount)492 static inline ssize_t TransferOutFWrite(FILE *file, const char *buffer, size_t amount)
493 {
494 	return fwrite(buffer, 1, amount, file);
495 }
496 
Receive_SERVER_CONTENT(Packet * p)497 bool ClientNetworkContentSocketHandler::Receive_SERVER_CONTENT(Packet *p)
498 {
499 	if (this->curFile == nullptr) {
500 		delete this->curInfo;
501 		/* When we haven't opened a file this must be our first packet with metadata. */
502 		this->curInfo = new ContentInfo;
503 		this->curInfo->type     = (ContentType)p->Recv_uint8();
504 		this->curInfo->id       = (ContentID)p->Recv_uint32();
505 		this->curInfo->filesize = p->Recv_uint32();
506 		this->curInfo->filename = p->Recv_string(NETWORK_CONTENT_FILENAME_LENGTH);
507 
508 		if (!this->BeforeDownload()) {
509 			this->CloseConnection();
510 			return false;
511 		}
512 	} else {
513 		/* We have a file opened, thus are downloading internal content */
514 		size_t toRead = p->RemainingBytesToTransfer();
515 		if (toRead != 0 && (size_t)p->TransferOut(TransferOutFWrite, this->curFile) != toRead) {
516 			CloseWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_CONTENT_DOWNLOAD);
517 			ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD, STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_FILE_NOT_WRITABLE, WL_ERROR);
518 			this->CloseConnection();
519 			fclose(this->curFile);
520 			this->curFile = nullptr;
521 
522 			return false;
523 		}
524 
525 		this->OnDownloadProgress(this->curInfo, (int)toRead);
526 
527 		if (toRead == 0) this->AfterDownload();
528 	}
529 
530 	return true;
531 }
532 
533 /**
534  * Handle the opening of the file before downloading.
535  * @return false on any error.
536  */
BeforeDownload()537 bool ClientNetworkContentSocketHandler::BeforeDownload()
538 {
539 	if (!this->curInfo->IsValid()) {
540 		delete this->curInfo;
541 		this->curInfo = nullptr;
542 		return false;
543 	}
544 
545 	if (this->curInfo->filesize != 0) {
546 		/* The filesize is > 0, so we are going to download it */
547 		std::string filename = GetFullFilename(this->curInfo, true);
548 		if (filename.empty() || (this->curFile = fopen(filename.c_str(), "wb")) == nullptr) {
549 			/* Unless that fails of course... */
550 			CloseWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_CONTENT_DOWNLOAD);
551 			ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD, STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_FILE_NOT_WRITABLE, WL_ERROR);
552 			return false;
553 		}
554 	}
555 	return true;
556 }
557 
558 /**
559  * Handle the closing and extracting of a file after
560  * downloading it has been done.
561  */
AfterDownload()562 void ClientNetworkContentSocketHandler::AfterDownload()
563 {
564 	/* We read nothing; that's our marker for end-of-stream.
565 	 * Now gunzip the tar and make it known. */
566 	fclose(this->curFile);
567 	this->curFile = nullptr;
568 
569 	if (GunzipFile(this->curInfo)) {
570 		unlink(GetFullFilename(this->curInfo, true).c_str());
571 
572 		Subdirectory sd = GetContentInfoSubDir(this->curInfo->type);
573 		if (sd == NO_DIRECTORY) NOT_REACHED();
574 
575 		TarScanner ts;
576 		std::string fname = GetFullFilename(this->curInfo, false);
577 		ts.AddFile(sd, fname);
578 
579 		if (this->curInfo->type == CONTENT_TYPE_BASE_MUSIC) {
580 			/* Music can't be in a tar. So extract the tar! */
581 			ExtractTar(fname, BASESET_DIR);
582 			unlink(fname.c_str());
583 		}
584 
585 #ifdef __EMSCRIPTEN__
586 		EM_ASM(if (window["openttd_syncfs"]) openttd_syncfs());
587 #endif
588 
589 		this->OnDownloadComplete(this->curInfo->id);
590 	} else {
591 		ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_EXTRACT, INVALID_STRING_ID, WL_ERROR);
592 	}
593 }
594 
595 /* Also called to just clean up the mess. */
OnFailure()596 void ClientNetworkContentSocketHandler::OnFailure()
597 {
598 	/* If we fail, download the rest via the 'old' system. */
599 	uint files, bytes;
600 	this->DownloadSelectedContent(files, bytes, true);
601 
602 	this->http_response.clear();
603 	this->http_response.shrink_to_fit();
604 	this->http_response_index = -2;
605 
606 	if (this->curFile != nullptr) {
607 		/* Revert the download progress when we are going for the old system. */
608 		long size = ftell(this->curFile);
609 		if (size > 0) this->OnDownloadProgress(this->curInfo, (int)-size);
610 
611 		fclose(this->curFile);
612 		this->curFile = nullptr;
613 	}
614 }
615 
OnReceiveData(const char * data,size_t length)616 void ClientNetworkContentSocketHandler::OnReceiveData(const char *data, size_t length)
617 {
618 	assert(data == nullptr || length != 0);
619 
620 	/* Ignore any latent data coming from a connection we closed. */
621 	if (this->http_response_index == -2) return;
622 
623 	if (this->http_response_index == -1) {
624 		if (data != nullptr) {
625 			/* Append the rest of the response. */
626 			this->http_response.insert(this->http_response.end(), data, data + length);
627 			return;
628 		} else {
629 			/* Make sure the response is properly terminated. */
630 			this->http_response.push_back('\0');
631 
632 			/* And prepare for receiving the rest of the data. */
633 			this->http_response_index = 0;
634 		}
635 	}
636 
637 	if (data != nullptr) {
638 		/* We have data, so write it to the file. */
639 		if (fwrite(data, 1, length, this->curFile) != length) {
640 			/* Writing failed somehow, let try via the old method. */
641 			this->OnFailure();
642 		} else {
643 			/* Just received the data. */
644 			this->OnDownloadProgress(this->curInfo, (int)length);
645 		}
646 		/* Nothing more to do now. */
647 		return;
648 	}
649 
650 	if (this->curFile != nullptr) {
651 		/* We've finished downloading a file. */
652 		this->AfterDownload();
653 	}
654 
655 	if ((uint)this->http_response_index >= this->http_response.size()) {
656 		/* It's not a real failure, but if there's
657 		 * nothing more to download it helps with
658 		 * cleaning up the stuff we allocated. */
659 		this->OnFailure();
660 		return;
661 	}
662 
663 	delete this->curInfo;
664 	/* When we haven't opened a file this must be our first packet with metadata. */
665 	this->curInfo = new ContentInfo;
666 
667 /** Check p for not being null and return calling OnFailure if that's not the case. */
668 #define check_not_null(p) { if ((p) == nullptr) { this->OnFailure(); return; } }
669 /** Check p for not being null and then terminate, or return calling OnFailure. */
670 #define check_and_terminate(p) { check_not_null(p); *(p) = '\0'; }
671 
672 	for (;;) {
673 		char *str = this->http_response.data() + this->http_response_index;
674 		char *p = strchr(str, '\n');
675 		check_and_terminate(p);
676 
677 		/* Update the index for the next one */
678 		this->http_response_index += (int)strlen(str) + 1;
679 
680 		/* Read the ID */
681 		p = strchr(str, ',');
682 		check_and_terminate(p);
683 		this->curInfo->id = (ContentID)atoi(str);
684 
685 		/* Read the type */
686 		str = p + 1;
687 		p = strchr(str, ',');
688 		check_and_terminate(p);
689 		this->curInfo->type = (ContentType)atoi(str);
690 
691 		/* Read the file size */
692 		str = p + 1;
693 		p = strchr(str, ',');
694 		check_and_terminate(p);
695 		this->curInfo->filesize = atoi(str);
696 
697 		/* Read the URL */
698 		str = p + 1;
699 		/* Is it a fallback URL? If so, just continue with the next one. */
700 		if (strncmp(str, "ottd", 4) == 0) {
701 			if ((uint)this->http_response_index >= this->http_response.size()) {
702 				/* Have we gone through all lines? */
703 				this->OnFailure();
704 				return;
705 			}
706 			continue;
707 		}
708 
709 		p = strrchr(str, '/');
710 		check_not_null(p);
711 		p++; // Start after the '/'
712 
713 		char tmp[MAX_PATH];
714 		if (strecpy(tmp, p, lastof(tmp)) == lastof(tmp)) {
715 			this->OnFailure();
716 			return;
717 		}
718 		/* Remove the extension from the string. */
719 		for (uint i = 0; i < 2; i++) {
720 			p = strrchr(tmp, '.');
721 			check_and_terminate(p);
722 		}
723 
724 		/* Copy the string, without extension, to the filename. */
725 		this->curInfo->filename = tmp;
726 
727 		/* Request the next file. */
728 		if (!this->BeforeDownload()) {
729 			this->OnFailure();
730 			return;
731 		}
732 
733 		NetworkHTTPSocketHandler::Connect(str, this);
734 		return;
735 	}
736 
737 #undef check
738 #undef check_and_terminate
739 }
740 
741 /**
742  * Create a socket handler to handle the connection.
743  */
ClientNetworkContentSocketHandler()744 ClientNetworkContentSocketHandler::ClientNetworkContentSocketHandler() :
745 	NetworkContentSocketHandler(),
746 	http_response_index(-2),
747 	curFile(nullptr),
748 	curInfo(nullptr),
749 	isConnecting(false)
750 {
751 	this->lastActivity = std::chrono::steady_clock::now();
752 }
753 
754 /** Clear up the mess ;) */
~ClientNetworkContentSocketHandler()755 ClientNetworkContentSocketHandler::~ClientNetworkContentSocketHandler()
756 {
757 	delete this->curInfo;
758 	if (this->curFile != nullptr) fclose(this->curFile);
759 
760 	for (ContentInfo *ci : this->infos) delete ci;
761 }
762 
763 /** Connect to the content server. */
764 class NetworkContentConnecter : TCPConnecter {
765 public:
766 	/**
767 	 * Initiate the connecting.
768 	 * @param address The address of the server.
769 	 */
NetworkContentConnecter(const std::string & connection_string)770 	NetworkContentConnecter(const std::string &connection_string) : TCPConnecter(connection_string, NETWORK_CONTENT_SERVER_PORT) {}
771 
OnFailure()772 	void OnFailure() override
773 	{
774 		_network_content_client.isConnecting = false;
775 		_network_content_client.OnConnect(false);
776 	}
777 
OnConnect(SOCKET s)778 	void OnConnect(SOCKET s) override
779 	{
780 		assert(_network_content_client.sock == INVALID_SOCKET);
781 		_network_content_client.lastActivity = std::chrono::steady_clock::now();
782 		_network_content_client.isConnecting = false;
783 		_network_content_client.sock = s;
784 		_network_content_client.Reopen();
785 		_network_content_client.OnConnect(true);
786 	}
787 };
788 
789 /**
790  * Connect with the content server.
791  */
Connect()792 void ClientNetworkContentSocketHandler::Connect()
793 {
794 	if (this->sock != INVALID_SOCKET || this->isConnecting) return;
795 	this->isConnecting = true;
796 	new NetworkContentConnecter(NetworkContentServerConnectionString());
797 }
798 
799 /**
800  * Disconnect from the content server.
801  */
CloseConnection(bool error)802 NetworkRecvStatus ClientNetworkContentSocketHandler::CloseConnection(bool error)
803 {
804 	NetworkContentSocketHandler::CloseConnection();
805 
806 	if (this->sock == INVALID_SOCKET) return NETWORK_RECV_STATUS_OKAY;
807 
808 	this->CloseSocket();
809 	this->OnDisconnect();
810 
811 	return NETWORK_RECV_STATUS_OKAY;
812 }
813 
814 /**
815  * Check whether we received/can send some data from/to the content server and
816  * when that's the case handle it appropriately
817  */
SendReceive()818 void ClientNetworkContentSocketHandler::SendReceive()
819 {
820 	if (this->sock == INVALID_SOCKET || this->isConnecting) return;
821 
822 	if (std::chrono::steady_clock::now() > this->lastActivity + IDLE_TIMEOUT) {
823 		this->CloseConnection();
824 		return;
825 	}
826 
827 	if (this->CanSendReceive()) {
828 		if (this->ReceivePackets()) {
829 			/* Only update activity once a packet is received, instead of every time we try it. */
830 			this->lastActivity = std::chrono::steady_clock::now();
831 		}
832 	}
833 
834 	this->SendPackets();
835 }
836 
837 /**
838  * Download information of a given Content ID if not already tried
839  * @param cid the ID to try
840  */
DownloadContentInfo(ContentID cid)841 void ClientNetworkContentSocketHandler::DownloadContentInfo(ContentID cid)
842 {
843 	/* When we tried to download it already, don't try again */
844 	if (std::find(this->requested.begin(), this->requested.end(), cid) != this->requested.end()) return;
845 
846 	this->requested.push_back(cid);
847 	this->RequestContentList(1, &cid);
848 }
849 
850 /**
851  * Get the content info based on a ContentID
852  * @param cid the ContentID to search for
853  * @return the ContentInfo or nullptr if not found
854  */
GetContent(ContentID cid) const855 ContentInfo *ClientNetworkContentSocketHandler::GetContent(ContentID cid) const
856 {
857 	for (ContentInfo *ci : this->infos) {
858 		if (ci->id == cid) return ci;
859 	}
860 	return nullptr;
861 }
862 
863 
864 /**
865  * Select a specific content id.
866  * @param cid the content ID to select
867  */
Select(ContentID cid)868 void ClientNetworkContentSocketHandler::Select(ContentID cid)
869 {
870 	ContentInfo *ci = this->GetContent(cid);
871 	if (ci == nullptr || ci->state != ContentInfo::UNSELECTED) return;
872 
873 	ci->state = ContentInfo::SELECTED;
874 	this->CheckDependencyState(ci);
875 }
876 
877 /**
878  * Unselect a specific content id.
879  * @param cid the content ID to deselect
880  */
Unselect(ContentID cid)881 void ClientNetworkContentSocketHandler::Unselect(ContentID cid)
882 {
883 	ContentInfo *ci = this->GetContent(cid);
884 	if (ci == nullptr || !ci->IsSelected()) return;
885 
886 	ci->state = ContentInfo::UNSELECTED;
887 	this->CheckDependencyState(ci);
888 }
889 
890 /** Select everything we can select */
SelectAll()891 void ClientNetworkContentSocketHandler::SelectAll()
892 {
893 	for (ContentInfo *ci : this->infos) {
894 		if (ci->state == ContentInfo::UNSELECTED) {
895 			ci->state = ContentInfo::SELECTED;
896 			this->CheckDependencyState(ci);
897 		}
898 	}
899 }
900 
901 /** Select everything that's an update for something we've got */
SelectUpgrade()902 void ClientNetworkContentSocketHandler::SelectUpgrade()
903 {
904 	for (ContentInfo *ci : this->infos) {
905 		if (ci->state == ContentInfo::UNSELECTED && ci->upgrade) {
906 			ci->state = ContentInfo::SELECTED;
907 			this->CheckDependencyState(ci);
908 		}
909 	}
910 }
911 
912 /** Unselect everything that we've not downloaded so far. */
UnselectAll()913 void ClientNetworkContentSocketHandler::UnselectAll()
914 {
915 	for (ContentInfo *ci : this->infos) {
916 		if (ci->IsSelected() && ci->state != ContentInfo::ALREADY_HERE) ci->state = ContentInfo::UNSELECTED;
917 	}
918 }
919 
920 /** Toggle the state of a content info and check its dependencies */
ToggleSelectedState(const ContentInfo * ci)921 void ClientNetworkContentSocketHandler::ToggleSelectedState(const ContentInfo *ci)
922 {
923 	switch (ci->state) {
924 		case ContentInfo::SELECTED:
925 		case ContentInfo::AUTOSELECTED:
926 			this->Unselect(ci->id);
927 			break;
928 
929 		case ContentInfo::UNSELECTED:
930 			this->Select(ci->id);
931 			break;
932 
933 		default:
934 			break;
935 	}
936 }
937 
938 /**
939  * Reverse lookup the dependencies of (direct) parents over a given child.
940  * @param parents list to store all parents in (is not cleared)
941  * @param child   the child to search the parents' dependencies for
942  */
ReverseLookupDependency(ConstContentVector & parents,const ContentInfo * child) const943 void ClientNetworkContentSocketHandler::ReverseLookupDependency(ConstContentVector &parents, const ContentInfo *child) const
944 {
945 	auto range = this->reverse_dependency_map.equal_range(child->id);
946 
947 	for (auto iter = range.first; iter != range.second; ++iter) {
948 		parents.push_back(GetContent(iter->second));
949 	}
950 }
951 
952 /**
953  * Reverse lookup the dependencies of all parents over a given child.
954  * @param tree  list to store all parents in (is not cleared)
955  * @param child the child to search the parents' dependencies for
956  */
ReverseLookupTreeDependency(ConstContentVector & tree,const ContentInfo * child) const957 void ClientNetworkContentSocketHandler::ReverseLookupTreeDependency(ConstContentVector &tree, const ContentInfo *child) const
958 {
959 	tree.push_back(child);
960 
961 	/* First find all direct parents. We can't use the "normal" iterator as
962 	 * we are including stuff into the vector and as such the vector's data
963 	 * store can be reallocated (and thus move), which means out iterating
964 	 * pointer gets invalid. So fall back to the indices. */
965 	for (uint i = 0; i < tree.size(); i++) {
966 		ConstContentVector parents;
967 		this->ReverseLookupDependency(parents, tree[i]);
968 
969 		for (const ContentInfo *ci : parents) {
970 			include(tree, ci);
971 		}
972 	}
973 }
974 
975 /**
976  * Check the dependencies (recursively) of this content info
977  * @param ci the content info to check the dependencies of
978  */
CheckDependencyState(ContentInfo * ci)979 void ClientNetworkContentSocketHandler::CheckDependencyState(ContentInfo *ci)
980 {
981 	if (ci->IsSelected() || ci->state == ContentInfo::ALREADY_HERE) {
982 		/* Selection is easy; just walk all children and set the
983 		 * autoselected state. That way we can see what we automatically
984 		 * selected and thus can unselect when a dependency is removed. */
985 		for (auto &dependency : ci->dependencies) {
986 			ContentInfo *c = this->GetContent(dependency);
987 			if (c == nullptr) {
988 				this->DownloadContentInfo(dependency);
989 			} else if (c->state == ContentInfo::UNSELECTED) {
990 				c->state = ContentInfo::AUTOSELECTED;
991 				this->CheckDependencyState(c);
992 			}
993 		}
994 		return;
995 	}
996 
997 	if (ci->state != ContentInfo::UNSELECTED) return;
998 
999 	/* For unselection we need to find the parents of us. We need to
1000 	 * unselect them. After that we unselect all children that we
1001 	 * depend on and are not used as dependency for us, but only when
1002 	 * we automatically selected them. */
1003 	ConstContentVector parents;
1004 	this->ReverseLookupDependency(parents, ci);
1005 	for (const ContentInfo *c : parents) {
1006 		if (!c->IsSelected()) continue;
1007 
1008 		this->Unselect(c->id);
1009 	}
1010 
1011 	for (auto &dependency : ci->dependencies) {
1012 		const ContentInfo *c = this->GetContent(dependency);
1013 		if (c == nullptr) {
1014 			DownloadContentInfo(dependency);
1015 			continue;
1016 		}
1017 		if (c->state != ContentInfo::AUTOSELECTED) continue;
1018 
1019 		/* Only unselect when WE are the only parent. */
1020 		parents.clear();
1021 		this->ReverseLookupDependency(parents, c);
1022 
1023 		/* First check whether anything depends on us */
1024 		int sel_count = 0;
1025 		bool force_selection = false;
1026 		for (const ContentInfo *parent_ci : parents) {
1027 			if (parent_ci->IsSelected()) sel_count++;
1028 			if (parent_ci->state == ContentInfo::SELECTED) force_selection = true;
1029 		}
1030 		if (sel_count == 0) {
1031 			/* Nothing depends on us */
1032 			this->Unselect(c->id);
1033 			continue;
1034 		}
1035 		/* Something manually selected depends directly on us */
1036 		if (force_selection) continue;
1037 
1038 		/* "Flood" search to find all items in the dependency graph*/
1039 		parents.clear();
1040 		this->ReverseLookupTreeDependency(parents, c);
1041 
1042 		/* Is there anything that is "force" selected?, if so... we're done. */
1043 		for (const ContentInfo *parent_ci : parents) {
1044 			if (parent_ci->state != ContentInfo::SELECTED) continue;
1045 
1046 			force_selection = true;
1047 			break;
1048 		}
1049 
1050 		/* So something depended directly on us */
1051 		if (force_selection) continue;
1052 
1053 		/* Nothing depends on us, mark the whole graph as unselected.
1054 		 * After that's done run over them once again to test their children
1055 		 * to unselect. Don't do it immediately because it'll do exactly what
1056 		 * we're doing now. */
1057 		for (const ContentInfo *c : parents) {
1058 			if (c->state == ContentInfo::AUTOSELECTED) this->Unselect(c->id);
1059 		}
1060 		for (const ContentInfo *c : parents) {
1061 			this->CheckDependencyState(this->GetContent(c->id));
1062 		}
1063 	}
1064 }
1065 
1066 /** Clear all downloaded content information. */
Clear()1067 void ClientNetworkContentSocketHandler::Clear()
1068 {
1069 	for (ContentInfo *c : this->infos) delete c;
1070 
1071 	this->infos.clear();
1072 	this->requested.clear();
1073 	this->reverse_dependency_map.clear();
1074 }
1075 
1076 /*** CALLBACK ***/
1077 
OnConnect(bool success)1078 void ClientNetworkContentSocketHandler::OnConnect(bool success)
1079 {
1080 	for (size_t i = 0; i < this->callbacks.size(); /* nothing */) {
1081 		ContentCallback *cb = this->callbacks[i];
1082 		/* the callback may remove itself from this->callbacks */
1083 		cb->OnConnect(success);
1084 		if (i != this->callbacks.size() && this->callbacks[i] == cb) i++;
1085 	}
1086 }
1087 
OnDisconnect()1088 void ClientNetworkContentSocketHandler::OnDisconnect()
1089 {
1090 	for (size_t i = 0; i < this->callbacks.size(); /* nothing */) {
1091 		ContentCallback *cb = this->callbacks[i];
1092 		cb->OnDisconnect();
1093 		if (i != this->callbacks.size() && this->callbacks[i] == cb) i++;
1094 	}
1095 }
1096 
OnReceiveContentInfo(const ContentInfo * ci)1097 void ClientNetworkContentSocketHandler::OnReceiveContentInfo(const ContentInfo *ci)
1098 {
1099 	for (size_t i = 0; i < this->callbacks.size(); /* nothing */) {
1100 		ContentCallback *cb = this->callbacks[i];
1101 		/* the callback may add items and/or remove itself from this->callbacks */
1102 		cb->OnReceiveContentInfo(ci);
1103 		if (i != this->callbacks.size() && this->callbacks[i] == cb) i++;
1104 	}
1105 }
1106 
OnDownloadProgress(const ContentInfo * ci,int bytes)1107 void ClientNetworkContentSocketHandler::OnDownloadProgress(const ContentInfo *ci, int bytes)
1108 {
1109 	for (size_t i = 0; i < this->callbacks.size(); /* nothing */) {
1110 		ContentCallback *cb = this->callbacks[i];
1111 		cb->OnDownloadProgress(ci, bytes);
1112 		if (i != this->callbacks.size() && this->callbacks[i] == cb) i++;
1113 	}
1114 }
1115 
OnDownloadComplete(ContentID cid)1116 void ClientNetworkContentSocketHandler::OnDownloadComplete(ContentID cid)
1117 {
1118 	ContentInfo *ci = this->GetContent(cid);
1119 	if (ci != nullptr) {
1120 		ci->state = ContentInfo::ALREADY_HERE;
1121 	}
1122 
1123 	for (size_t i = 0; i < this->callbacks.size(); /* nothing */) {
1124 		ContentCallback *cb = this->callbacks[i];
1125 		/* the callback may remove itself from this->callbacks */
1126 		cb->OnDownloadComplete(cid);
1127 		if (i != this->callbacks.size() && this->callbacks[i] == cb) i++;
1128 	}
1129 }
1130