00001
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034 #include "client.h"
00035 #include "cl_http.h"
00036 #include "battlescape/cl_parse.h"
00037
00038 static cvar_t *cl_http_downloads;
00039 static cvar_t *cl_http_filelists;
00040 static cvar_t *cl_http_max_connections;
00041
00042 enum {
00043 HTTPDL_ABORT_NONE,
00044 HTTPDL_ABORT_SOFT,
00045 HTTPDL_ABORT_HARD
00046 };
00047
00048 static CURLM *multi = NULL;
00049 static int handleCount = 0;
00050 static int pendingCount = 0;
00051 static int abortDownloads = HTTPDL_ABORT_NONE;
00052 static qboolean downloadingPK3 = qfalse;
00053
00054 static void StripHighBits (char *string)
00055 {
00056 char *p = string;
00057
00058 while (string[0]) {
00059 const unsigned char c = *(string++);
00060
00061 if (c >= 32 && c <= 127)
00062 *p++ = c;
00063 }
00064
00065 p[0] = '\0';
00066 }
00067
00068 static inline qboolean isvalidchar (int c)
00069 {
00070 if (!isalnum(c) && c != '_' && c != '-')
00071 return qfalse;
00072 return qtrue;
00073 }
00074
00079 static int CL_HTTP_Progress (void *clientp, double dltotal, double dlnow, double ultotal, double ulnow)
00080 {
00081 dlhandle_t *dl;
00082
00083 dl = (dlhandle_t *)clientp;
00084
00085 dl->position = (unsigned)dlnow;
00086
00087
00088 if (!abortDownloads) {
00089 Q_strncpyz(cls.downloadName, dl->queueEntry->ufoPath, sizeof(cls.downloadName));
00090 cls.downloadPosition = dl->position;
00091
00092 if (dltotal)
00093 cls.downloadPercent = (int)((dlnow / dltotal) * 100.0f);
00094 else
00095 cls.downloadPercent = 0;
00096 }
00097
00098 return abortDownloads;
00099 }
00100
00106 static void CL_EscapeHTTPPath (const char *filePath, char *escaped)
00107 {
00108 int i;
00109 size_t len;
00110 char *p;
00111
00112 p = escaped;
00113
00114 len = strlen(filePath);
00115 for (i = 0; i < len; i++) {
00116 if (!isalnum(filePath[i]) && filePath[i] != ';' && filePath[i] != '/' &&
00117 filePath[i] != '?' && filePath[i] != ':' && filePath[i] != '@' && filePath[i] != '&' &&
00118 filePath[i] != '=' && filePath[i] != '+' && filePath[i] != '$' && filePath[i] != ',' &&
00119 filePath[i] != '[' && filePath[i] != ']' && filePath[i] != '-' && filePath[i] != '_' &&
00120 filePath[i] != '.' && filePath[i] != '!' && filePath[i] != '~' && filePath[i] != '*' &&
00121 filePath[i] != '\'' && filePath[i] != '(' && filePath[i] != ')') {
00122 sprintf(p, "%%%02x", filePath[i]);
00123 p += 3;
00124 } else {
00125 *p = filePath[i];
00126 p++;
00127 }
00128 }
00129 p[0] = 0;
00130
00131
00132
00133 len = strlen(escaped);
00134 p = escaped;
00135 while ((p = strstr (p, "./"))) {
00136 memmove(p, p + 2, len - (p - escaped) - 1);
00137 len -= 2;
00138 }
00139 }
00140
00144 static void CL_StartHTTPDownload (dlqueue_t *entry, dlhandle_t *dl)
00145 {
00146 char tempFile[MAX_OSPATH];
00147 char escapedFilePath[MAX_QPATH * 4];
00148 const char *extension = Com_GetExtension(entry->ufoPath);
00149
00150
00151
00152 if (extension != NULL && !strcmp(extension, "filelist")) {
00153 dl->file = NULL;
00154 CL_EscapeHTTPPath(entry->ufoPath, escapedFilePath);
00155 } else {
00157 Com_sprintf(dl->filePath, sizeof(dl->filePath), "%s/%s", FS_Gamedir(), entry->ufoPath);
00158
00159 Com_sprintf(tempFile, sizeof(tempFile), BASEDIRNAME"/%s", entry->ufoPath);
00160 CL_EscapeHTTPPath(tempFile, escapedFilePath);
00161
00162 strcat(dl->filePath, ".tmp");
00163
00164 FS_CreatePath(dl->filePath);
00165
00166
00167 dl->file = fopen(dl->filePath, "wb");
00168 if (!dl->file) {
00169 Com_Printf("CL_StartHTTPDownload: Couldn't open %s for writing.\n", dl->filePath);
00170 entry->state = DLQ_STATE_DONE;
00171
00172 return;
00173 }
00174 }
00175
00176 dl->tempBuffer = NULL;
00177 dl->speed = 0;
00178 dl->fileSize = 0;
00179 dl->position = 0;
00180 dl->queueEntry = entry;
00181
00182 if (!dl->curl)
00183 dl->curl = curl_easy_init();
00184
00185 Com_sprintf(dl->URL, sizeof(dl->URL), "%s%s", cls.downloadServer, escapedFilePath);
00186
00187 curl_easy_setopt(dl->curl, CURLOPT_ENCODING, "");
00188 #ifdef PARANOID
00189 curl_easy_setopt(dl->curl, CURLOPT_VERBOSE, 1);
00190 #endif
00191 curl_easy_setopt(dl->curl, CURLOPT_NOPROGRESS, 0);
00192 if (dl->file) {
00193 curl_easy_setopt(dl->curl, CURLOPT_WRITEDATA, dl->file);
00194 curl_easy_setopt(dl->curl, CURLOPT_WRITEFUNCTION, NULL);
00195 } else {
00196 curl_easy_setopt(dl->curl, CURLOPT_WRITEDATA, dl);
00197 curl_easy_setopt(dl->curl, CURLOPT_WRITEFUNCTION, HTTP_Recv);
00198 }
00199 curl_easy_setopt(dl->curl, CURLOPT_PROXY, http_proxy->string);
00200 curl_easy_setopt(dl->curl, CURLOPT_FOLLOWLOCATION, 1);
00201 curl_easy_setopt(dl->curl, CURLOPT_MAXREDIRS, 5);
00202 curl_easy_setopt(dl->curl, CURLOPT_WRITEHEADER, dl);
00203 curl_easy_setopt(dl->curl, CURLOPT_HEADERFUNCTION, HTTP_Header);
00204 curl_easy_setopt(dl->curl, CURLOPT_PROGRESSFUNCTION, CL_HTTP_Progress);
00205 curl_easy_setopt(dl->curl, CURLOPT_PROGRESSDATA, dl);
00206 curl_easy_setopt(dl->curl, CURLOPT_USERAGENT, Cvar_GetString("version"));
00207 curl_easy_setopt(dl->curl, CURLOPT_REFERER, cls.downloadReferer);
00208 curl_easy_setopt(dl->curl, CURLOPT_URL, dl->URL);
00209
00210 if (curl_multi_add_handle(multi, dl->curl) != CURLM_OK) {
00211 Com_Printf("curl_multi_add_handle: error\n");
00212 dl->queueEntry->state = DLQ_STATE_DONE;
00213 return;
00214 }
00215
00216 handleCount++;
00217
00218 Com_Printf("CL_StartHTTPDownload: Fetching %s...\n", dl->URL);
00219 dl->queueEntry->state = DLQ_STATE_RUNNING;
00220 }
00221
00225 void CL_SetHTTPServer (const char *URL)
00226 {
00227 dlqueue_t *q, *last;
00228
00229 CL_HTTP_Cleanup();
00230
00231 q = &cls.downloadQueue;
00232
00233 last = NULL;
00234
00235 while (q->next) {
00236 q = q->next;
00237
00238 if (last)
00239 Mem_Free(last);
00240
00241 last = q;
00242 }
00243
00244 if (last)
00245 Mem_Free(last);
00246
00247 if (multi)
00248 Com_Error(ERR_DROP, "CL_SetHTTPServer: Still have old handle");
00249
00250 multi = curl_multi_init();
00251
00252 memset(&cls.downloadQueue, 0, sizeof(cls.downloadQueue));
00253
00254 abortDownloads = HTTPDL_ABORT_NONE;
00255 handleCount = pendingCount = 0;
00256
00257 Q_strncpyz(cls.downloadServer, URL, sizeof(cls.downloadServer));
00258 }
00259
00260 void CL_ResetPrecacheCheck(void);
00261
00265 void CL_CancelHTTPDownloads (qboolean permKill)
00266 {
00267 dlqueue_t *q;
00268
00269 if (permKill)
00270 abortDownloads = HTTPDL_ABORT_HARD;
00271 else
00272 abortDownloads = HTTPDL_ABORT_SOFT;
00273
00274 q = &cls.downloadQueue;
00275
00276 while (q->next) {
00277 q = q->next;
00278 if (q->state == DLQ_STATE_NOT_STARTED)
00279 q->state = DLQ_STATE_DONE;
00280 }
00281
00282 if (!pendingCount && !handleCount && abortDownloads == HTTPDL_ABORT_HARD)
00283 cls.downloadServer[0] = 0;
00284
00285 pendingCount = 0;
00286 }
00287
00291 static dlhandle_t *CL_GetFreeDLHandle (void)
00292 {
00293 int i;
00294
00295 for (i = 0; i < 4; i++) {
00296 dlhandle_t *dl = &cls.HTTPHandles[i];
00297 if (!dl->queueEntry || dl->queueEntry->state == DLQ_STATE_DONE)
00298 return dl;
00299 }
00300
00301 return NULL;
00302 }
00303
00308 qboolean CL_QueueHTTPDownload (const char *ufoPath)
00309 {
00310 dlqueue_t *q;
00311
00312
00313 if (!cls.downloadServer[0] || abortDownloads || !cl_http_downloads->integer)
00314 return qfalse;
00315
00316 q = &cls.downloadQueue;
00317
00318 while (q->next) {
00319 q = q->next;
00320
00321
00322 if (!strcmp(ufoPath, q->ufoPath))
00323 return qtrue;
00324 }
00325
00326 q->next = Mem_Alloc(sizeof(*q));
00327 q = q->next;
00328
00329 q->next = NULL;
00330 q->state = DLQ_STATE_NOT_STARTED;
00331 Q_strncpyz(q->ufoPath, ufoPath, sizeof(q->ufoPath));
00332
00333
00334 if (cl_http_filelists->integer) {
00335 const char *extension = Com_GetExtension(ufoPath);
00336 if (extension != NULL && !Q_strcasecmp(extension, "bsp")) {
00337 char listPath[MAX_OSPATH];
00338 const size_t len = strlen(ufoPath);
00339 Com_sprintf(listPath, sizeof(listPath), BASEDIRNAME"/%.*s.filelist", (int)(len - 4), ufoPath);
00340 CL_QueueHTTPDownload(listPath);
00341 }
00342 }
00343
00344
00345 pendingCount++;
00346
00347 return qtrue;
00348 }
00349
00356 qboolean CL_PendingHTTPDownloads (void)
00357 {
00358 if (cls.downloadServer[0] == '\0')
00359 return qfalse;
00360
00361 return pendingCount + handleCount;
00362 }
00363
00369 qboolean CL_CheckOrDownloadFile (const char *filename)
00370 {
00371 static char lastfilename[MAX_OSPATH] = "";
00372
00373 if (!filename || filename[0] == '\0')
00374 return qtrue;
00375
00376
00377 if (!strcmp(filename, lastfilename))
00378 return qtrue;
00379
00380 Q_strncpyz(lastfilename, filename, sizeof(lastfilename));
00381
00382 if (strstr(filename, "..")) {
00383 Com_Printf("Refusing to check a path with .. (%s)\n", filename);
00384 return qtrue;
00385 }
00386
00387 if (strchr(filename, ' ')) {
00388 Com_Printf("Refusing to check a path containing spaces (%s)\n", filename);
00389 return qtrue;
00390 }
00391
00392 if (strchr(filename, ':')) {
00393 Com_Printf("Refusing to check a path containing a colon (%s)\n", filename);
00394 return qtrue;
00395 }
00396
00397 if (filename[0] == '/') {
00398 Com_Printf("Refusing to check a path starting with / (%s)\n", filename);
00399 return qtrue;
00400 }
00401
00402 if (FS_LoadFile(filename, NULL) != -1) {
00403
00404 return qtrue;
00405 }
00406
00407 if (CL_QueueHTTPDownload(filename))
00408 return qfalse;
00409
00410 return qtrue;
00411 }
00412
00419 static void CL_CheckAndQueueDownload (char *path)
00420 {
00421 size_t length;
00422 const char *ext;
00423 qboolean pak;
00424 qboolean gameLocal;
00425
00426 StripHighBits(path);
00427
00428 length = strlen(path);
00429
00430 if (length >= MAX_QPATH)
00431 return;
00432
00433 ext = Com_GetExtension(path);
00434 if (ext == NULL)
00435 return;
00436
00437 if (!strcmp(ext, "pk3")) {
00438 Com_Printf("NOTICE: Filelist is requesting a .pk3 file (%s)\n", path);
00439 pak = qtrue;
00440 } else
00441 pak = qfalse;
00442
00443 if (!pak && strcmp(ext, "bsp") && strcmp(ext, "wav") && strcmp(ext, "md2") && strcmp(ext, "ogg") &&
00444 strcmp(ext, "md3") && strcmp(ext, "tga") && strcmp(ext, "png") && strcmp(ext, "jpg") &&
00445 strcmp(ext, "dpm") && strcmp(ext, "obj") && strcmp(ext, "mat") && strcmp(ext, "ump")) {
00446 Com_Printf("WARNING: Illegal file type '%s' in filelist.\n", path);
00447 return;
00448 }
00449
00450 if (path[0] == '@') {
00451 if (pak) {
00452 Com_Printf("WARNING: @ prefix used on a pk3 file (%s) in filelist.\n", path);
00453 return;
00454 }
00455 gameLocal = qtrue;
00456 path++;
00457 length--;
00458 } else
00459 gameLocal = qfalse;
00460
00461 if (strstr(path, "..") || !isvalidchar(path[0]) || !isvalidchar(path[length - 1]) || strstr(path, "//") ||
00462 strchr(path, '\\') || (!pak && !strchr(path, '/')) || (pak && strchr(path, '/'))) {
00463 Com_Printf("WARNING: Illegal path '%s' in filelist.\n", path);
00464 return;
00465 }
00466
00467
00468 if (gameLocal || pak) {
00469 qboolean exists;
00470
00471
00472 if (pak) {
00473 char gamePath[MAX_OSPATH];
00474 FILE *f;
00475 Com_sprintf(gamePath, sizeof(gamePath), "%s/%s", FS_Gamedir(), path);
00476 f = fopen(gamePath, "rb");
00477 if (!f)
00478 exists = qfalse;
00479 else {
00480 exists = qtrue;
00481 fclose(f);
00482 }
00483 } else
00484 exists = FS_CheckFile("%s", path);
00485
00486 if (!exists) {
00487 if (CL_QueueHTTPDownload(path)) {
00488
00489
00490
00491 if (pak) {
00492 dlqueue_t *q, *last;
00493
00494 last = q = &cls.downloadQueue;
00495
00496 while (q->next) {
00497 last = q;
00498 q = q->next;
00499 }
00500
00501 last->next = NULL;
00502 q->next = cls.downloadQueue.next;
00503 cls.downloadQueue.next = q;
00504 }
00505 }
00506 }
00507 } else
00508 CL_CheckOrDownloadFile(path);
00509 }
00510
00514 static void CL_ParseFileList (dlhandle_t *dl)
00515 {
00516 char *list;
00517
00518 if (!cl_http_filelists->integer)
00519 return;
00520
00521 list = dl->tempBuffer;
00522
00523 for (;;) {
00524 char *p = strchr(list, '\n');
00525 if (p) {
00526 p[0] = 0;
00527 if (list[0])
00528 CL_CheckAndQueueDownload(list);
00529 list = p + 1;
00530 } else {
00531 if (list[0])
00532 CL_CheckAndQueueDownload(list);
00533 break;
00534 }
00535 }
00536
00537 Mem_Free(dl->tempBuffer);
00538 dl->tempBuffer = NULL;
00539 }
00540
00545 static void CL_ReVerifyHTTPQueue (void)
00546 {
00547 dlqueue_t *q = &cls.downloadQueue;
00548
00549 pendingCount = 0;
00550
00551 while (q->next) {
00552 q = q->next;
00553 if (q->state == DLQ_STATE_NOT_STARTED) {
00554 if (FS_LoadFile(q->ufoPath, NULL) != -1)
00555 q->state = DLQ_STATE_DONE;
00556 else
00557 pendingCount++;
00558 }
00559 }
00560 }
00561
00565 void CL_HTTP_Cleanup (void)
00566 {
00567 int i;
00568
00569 for (i = 0; i < 4; i++) {
00570 dlhandle_t *dl = &cls.HTTPHandles[i];
00571
00572 if (dl->file) {
00573 fclose(dl->file);
00574 remove(dl->filePath);
00575 dl->file = NULL;
00576 }
00577
00578 if (dl->tempBuffer) {
00579 Mem_Free(dl->tempBuffer);
00580 dl->tempBuffer = NULL;
00581 }
00582
00583 if (dl->curl) {
00584 if (multi)
00585 curl_multi_remove_handle(multi, dl->curl);
00586 curl_easy_cleanup(dl->curl);
00587 dl->curl = NULL;
00588 }
00589 }
00590
00591 if (multi) {
00592 curl_multi_cleanup(multi);
00593 multi = NULL;
00594 }
00595 }
00596
00601 static void CL_FinishHTTPDownload (void)
00602 {
00603 int messagesInQueue, i;
00604 CURLcode result;
00605 CURL *curl;
00606 long responseCode;
00607 double timeTaken, fileSize;
00608 char tempName[MAX_OSPATH];
00609 qboolean isFile;
00610
00611 do {
00612 CURLMsg *msg = curl_multi_info_read(multi, &messagesInQueue);
00613 dlhandle_t *dl = NULL;
00614
00615 if (!msg) {
00616 Com_Printf("CL_FinishHTTPDownload: Odd, no message for us...\n");
00617 return;
00618 }
00619
00620 if (msg->msg != CURLMSG_DONE) {
00621 Com_Printf("CL_FinishHTTPDownload: Got some weird message...\n");
00622 continue;
00623 }
00624
00625 curl = msg->easy_handle;
00626
00627
00628 for (i = 0; i < 4; i++) {
00629 if (cls.HTTPHandles[i].curl == curl) {
00630 dl = &cls.HTTPHandles[i];
00631 break;
00632 }
00633 }
00634
00635 if (!dl)
00636 Com_Error(ERR_DROP, "CL_FinishHTTPDownload: Handle not found");
00637
00638
00639 dl->queueEntry->state = DLQ_STATE_DONE;
00640
00641
00642 if (dl->file)
00643 isFile = qtrue;
00644 else
00645 isFile = qfalse;
00646
00647 if (isFile) {
00648 fclose(dl->file);
00649 dl->file = NULL;
00650 }
00651
00652
00653 if (pendingCount)
00654 pendingCount--;
00655 handleCount--;
00656
00657 cls.downloadName[0] = 0;
00658 cls.downloadPosition = 0;
00659
00660 result = msg->data.result;
00661
00662 switch (result) {
00663
00664 case CURLE_HTTP_RETURNED_ERROR:
00665 case CURLE_OK:
00666 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode);
00667 if (responseCode == 404) {
00668 const char *extension = Com_GetExtension(dl->queueEntry->ufoPath);
00669 if (extension != NULL && !strcmp(extension, "pk3"))
00670 downloadingPK3 = qfalse;
00671
00672 if (isFile)
00673 FS_RemoveFile(dl->filePath);
00674 Com_Printf("HTTP(%s): 404 File Not Found [%d remaining files]\n", dl->queueEntry->ufoPath, pendingCount);
00675 curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &fileSize);
00676 if (fileSize > 512) {
00677
00678 isFile = qfalse;
00679 result = CURLE_FILESIZE_EXCEEDED;
00680 Com_Printf("Oversized 404 body received (%d bytes), aborting HTTP downloading.\n", (int)fileSize);
00681 } else {
00682 curl_multi_remove_handle(multi, dl->curl);
00683 continue;
00684 }
00685 } else if (responseCode == 200) {
00686 if (!isFile && !abortDownloads)
00687 CL_ParseFileList(dl);
00688 break;
00689 }
00690
00691
00692
00693
00694 case CURLE_COULDNT_RESOLVE_HOST:
00695 case CURLE_COULDNT_CONNECT:
00696 case CURLE_COULDNT_RESOLVE_PROXY:
00697 if (isFile)
00698 FS_RemoveFile(dl->filePath);
00699 Com_Printf("Fatal HTTP error: %s\n", curl_easy_strerror(result));
00700 curl_multi_remove_handle(multi, dl->curl);
00701 if (abortDownloads)
00702 continue;
00703 CL_CancelHTTPDownloads(qtrue);
00704 continue;
00705 default:
00706 i = strlen(dl->queueEntry->ufoPath);
00707 if (!strcmp(dl->queueEntry->ufoPath + i - 4, ".pk3"))
00708 downloadingPK3 = qfalse;
00709 if (isFile)
00710 FS_RemoveFile(dl->filePath);
00711 Com_Printf("HTTP download failed: %s\n", curl_easy_strerror(result));
00712 curl_multi_remove_handle(multi, dl->curl);
00713 continue;
00714 }
00715
00716 if (isFile) {
00717
00718 Com_sprintf(tempName, sizeof(tempName), "%s/%s", FS_Gamedir(), dl->queueEntry->ufoPath);
00719
00720 if (!FS_RenameFile(dl->filePath, tempName, qfalse))
00721 Com_Printf("Failed to rename %s for some odd reason...", dl->filePath);
00722
00723
00724 i = strlen(tempName);
00725 if (!strcmp(tempName + i - 4, ".pk3")) {
00726 FS_RestartFilesystem();
00727 CL_ReVerifyHTTPQueue();
00728 downloadingPK3 = qfalse;
00729 }
00730 }
00731
00732
00733 curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &timeTaken);
00734 curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &fileSize);
00735
00741 curl_multi_remove_handle(multi, dl->curl);
00742
00743 Com_Printf("HTTP(%s): %.f bytes, %.2fkB/sec [%d remaining files]\n",
00744 dl->queueEntry->ufoPath, fileSize, (fileSize / 1024.0) / timeTaken, pendingCount);
00745 } while (messagesInQueue > 0);
00746
00747 if (handleCount == 0) {
00748 if (abortDownloads == HTTPDL_ABORT_SOFT)
00749 abortDownloads = HTTPDL_ABORT_NONE;
00750 else if (abortDownloads == HTTPDL_ABORT_HARD)
00751 cls.downloadServer[0] = 0;
00752 }
00753
00754
00755 if (cls.state == ca_connected && !CL_PendingHTTPDownloads())
00756 CL_RequestNextDownload();
00757 }
00758
00763 static void CL_StartNextHTTPDownload (void)
00764 {
00765 dlqueue_t *q = &cls.downloadQueue;
00766
00767 while (q->next) {
00768 q = q->next;
00769 if (q->state == DLQ_STATE_NOT_STARTED) {
00770 size_t len;
00771 dlhandle_t *dl = CL_GetFreeDLHandle();
00772 if (!dl)
00773 return;
00774
00775 CL_StartHTTPDownload(q, dl);
00776
00777
00778 len = strlen(q->ufoPath);
00779 if (len > 4 && !Q_strcasecmp(q->ufoPath + len - 4, ".pk3"))
00780 downloadingPK3 = qtrue;
00781
00782 break;
00783 }
00784 }
00785 }
00786
00793 void CL_RunHTTPDownloads (void)
00794 {
00795 CURLMcode ret;
00796
00797 if (!cls.downloadServer[0])
00798 return;
00799
00800
00801
00802
00803 if (pendingCount && abortDownloads == HTTPDL_ABORT_NONE &&
00804 !downloadingPK3 && handleCount < cl_http_max_connections->integer)
00805 CL_StartNextHTTPDownload();
00806
00807 do {
00808 int newHandleCount;
00809 ret = curl_multi_perform(multi, &newHandleCount);
00810 if (newHandleCount < handleCount) {
00811
00812
00813 CL_FinishHTTPDownload();
00814 handleCount = newHandleCount;
00815 }
00816 } while (ret == CURLM_CALL_MULTI_PERFORM);
00817
00818 if (ret != CURLM_OK) {
00819 Com_Printf("curl_multi_perform error. Aborting HTTP downloads.\n");
00820 CL_CancelHTTPDownloads(qtrue);
00821 }
00822
00823
00824 if (pendingCount && abortDownloads == HTTPDL_ABORT_NONE &&
00825 !downloadingPK3 && handleCount < cl_http_max_connections->integer)
00826 CL_StartNextHTTPDownload();
00827 }
00828
00829 void HTTP_InitStartup (void)
00830 {
00831 cl_http_filelists = Cvar_Get("cl_http_filelists", "1", 0, NULL);
00832 cl_http_downloads = Cvar_Get("cl_http_downloads", "1", 0, "Try to download files via http");
00833 cl_http_max_connections = Cvar_Get("cl_http_max_connections", "1", 0, NULL);
00834 }