GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/net/download.cpp Lines: 72 257 28.0 %
Date: 2018-05-19 03:07:18 Branches: 31 265 11.7 %

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 2009-2010  The Mana Developers
4
 *  Copyright (C) 2011-2018  The ManaPlus Developers
5
 *
6
 *  This file is part of The ManaPlus Client.
7
 *
8
 *  This program is free software; you can redistribute it and/or modify
9
 *  it under the terms of the GNU General Public License as published by
10
 *  the Free Software Foundation; either version 2 of the License, or
11
 *  any later version.
12
 *
13
 *  This program is distributed in the hope that it will be useful,
14
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 *  GNU General Public License for more details.
17
 *
18
 *  You should have received a copy of the GNU General Public License
19
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
 */
21
22
#include "net/download.h"
23
24
#include "configuration.h"
25
#include "logger.h"
26
#include "settings.h"
27
28
#include "fs/files.h"
29
30
#include "utils/cast.h"
31
#include "utils/sdlhelper.h"
32
33
#include <zlib.h>
34
35
#include <fstream>
36
#include <sstream>
37
38
#include "debug.h"
39
40
const char *DOWNLOAD_ERROR_MESSAGE_THREAD
41
    = "Could not create download thread!";
42
43
extern volatile bool isTerminate;
44
45
enum
46
{
47
    OPTIONS_NONE = 0,
48
    OPTIONS_MEMORY = 1
49
};
50
51
namespace Net
52
{
53
54
2
std::string Download::mUploadResponse;
55
56
3
Download::Download(void *const ptr,
57
                   const std::string &url,
58
                   const DownloadUpdate updateFunction,
59
                   const bool ignoreError,
60
                   const bool isUpload,
61
3
                   const bool isXml) :
62
    mPtr(ptr),
63
    mUrl(url),
64
    mOptions(),
65
    mFileName(),
66
    mUrlQueue(),
67
    mWriteFunction(nullptr),
68
    mAdler(0),
69
    mUpdateFunction(updateFunction),
70
    mThread(nullptr),
71
    mCurl(nullptr),
72
    mHeaders(nullptr),
73
    mFormPost(nullptr),
74
3
    mError(static_cast<char*>(calloc(CURL_ERROR_SIZE + 1, 1))),
75
    mIgnoreError(ignoreError),
76
    mUpload(isUpload),
77
15
    mIsXml(isXml)
78
{
79
3
    if (mError != nullptr)
80
3
        mError[0] = 0;
81
82
3
    mOptions.cancel = 0;
83
3
    mOptions.memoryWrite = 0;
84
3
    mOptions.checkAdler = 1u;
85
3
    if (!mUpload)
86
    {
87
6
        const std::string serverName = settings.serverName;
88
3
        if (!serverName.empty())
89
        {
90
            if (mUrl.find('?') == std::string::npos)
91
                mUrl.append("?host=");
92
            else
93
                mUrl.append("&host=");
94
            mUrl.append(serverName);
95
        }
96
    }
97
6
    mUrlQueue.push(url);
98
3
}
99
100
15
Download::~Download()
101
{
102
3
    if (mFormPost != nullptr)
103
    {
104
        curl_formfree(mFormPost);
105
        mFormPost = nullptr;
106
    }
107
108
3
    if (mHeaders != nullptr)
109
    {
110
2
        curl_slist_free_all(mHeaders);
111
2
        mHeaders = nullptr;
112
    }
113
114
3
    SDL::WaitThread(mThread);
115
3
    mThread = nullptr;
116
3
    free(mError);
117
3
}
118
119
/**
120
 * Calculates the Alder-32 checksum for the given file.
121
 */
122
unsigned long Download::fadler32(FILE *const file)
123
{
124
    if (file == nullptr)
125
        return 0;
126
127
    // Obtain file size
128
    fseek(file, 0, SEEK_END);
129
    const long fileSize = ftell(file);
130
    if (fileSize < 0)
131
    {   // file size error
132
        return 0;
133
    }
134
    rewind(file);
135
136
    // Calculate Adler-32 checksum
137
    char *const buffer = new char[CAST_SIZE(fileSize)];
138
    const uInt read = static_cast<uInt>(fread(buffer, 1, fileSize, file));
139
    unsigned long adler = adler32(0L, nullptr, 0);
140
    adler = adler32(static_cast<uInt>(adler),
141
        reinterpret_cast<Bytef*>(buffer), read);
142
    delete [] buffer;
143
    return adler;
144
}
145
146
unsigned long Download::adlerBuffer(const char *const buffer, int size)
147
{
148
    FUNC_BLOCK("Download::adlerBuffer", 1)
149
    unsigned long adler = adler32(0L, nullptr, 0);
150
    return adler32(static_cast<uInt>(adler),
151
        reinterpret_cast<const Bytef*>(buffer), size);
152
}
153
154
void Download::addHeader(const std::string &header)
155
{
156

4
    mHeaders = curl_slist_append(mHeaders, header.c_str());
157
}
158
159
2
void Download::noCache()
160
{
161
10
    addHeader("pragma: no-cache");
162
10
    addHeader("Cache-Control: no-cache");
163
2
}
164
165
1
void Download::setFile(const std::string &filename, const int64_t adler32)
166
{
167
1
    mOptions.memoryWrite = 0;
168
2
    mFileName = filename;
169
170
1
    if (adler32 > -1)
171
    {
172
        mAdler = static_cast<unsigned long>(adler32);
173
        mOptions.checkAdler = 1u;
174
    }
175
    else
176
    {
177
1
        mOptions.checkAdler = 0;
178
    }
179
1
}
180
181
2
void Download::setWriteFunction(WriteFunction write)
182
{
183
2
    mOptions.memoryWrite = 1u;
184
2
    mWriteFunction = write;
185
2
}
186
187
3
bool Download::start()
188
{
189
3
    if (mUpload)
190
        logger->log("Starting upload: %s", mUrl.c_str());
191
    else
192
6
        logger->log("Starting download: %s", mUrl.c_str());
193
194
3
    mThread = SDL::createThread(&downloadThread, "download", this);
195
3
    if (mThread == nullptr)
196
    {
197
        logger->log1(DOWNLOAD_ERROR_MESSAGE_THREAD);
198
        if (mError != nullptr)
199
            strcpy(mError, DOWNLOAD_ERROR_MESSAGE_THREAD);
200
        mUpdateFunction(mPtr, DownloadStatus::ThreadError, 0, 0);
201
        if (!mIgnoreError)
202
            return false;
203
    }
204
205
    return true;
206
}
207
208
3
void Download::cancel()
209
{
210
6
    logger->log("Canceling download: %s", mUrl.c_str());
211
212
3
    mOptions.cancel = 1u;
213
3
    SDL::WaitThread(mThread);
214
3
    mThread = nullptr;
215
3
}
216
217
const char *Download::getError() const
218
{
219
    return mError;
220
}
221
222
int Download::downloadProgress(void *clientp, double dltotal, double dlnow,
223
                               double ultotal A_UNUSED, double ulnow A_UNUSED)
224
{
225
    Download *const d = reinterpret_cast<Download *>(clientp);
226
227
    if (d == nullptr)
228
        return -5;
229
230
    if (d->mUpload)
231
        return 0;
232
233
    if (d->mOptions.cancel != 0u)
234
    {
235
        return d->mUpdateFunction(d->mPtr, DownloadStatus::Cancelled,
236
                                  CAST_SIZE(dltotal),
237
                                  CAST_SIZE(dlnow));
238
    }
239
240
    return d->mUpdateFunction(d->mPtr, DownloadStatus::Idle,
241
                              CAST_SIZE(dltotal),
242
                              CAST_SIZE(dlnow));
243
}
244
245
3
int Download::downloadThread(void *ptr)
246
{
247
3
    int attempts = 0;
248
3
    bool complete = false;
249
3
    Download *const d = reinterpret_cast<Download*>(ptr);
250
    CURLcode res;
251
252
3
    if (d == nullptr)
253
        return 0;
254
255
3
    std::string outFilename;
256
3
    if (d->mUpload)
257
    {
258
        outFilename = d->mFileName;
259
        prepareForm(&d->mFormPost, outFilename);
260
    }
261
    else
262
    {
263
3
        if (d->mOptions.memoryWrite == 0u)
264
2
            outFilename = d->mFileName + ".part";
265
        else
266
            outFilename.clear();
267
    }
268
269
12
    while (!d->mUrlQueue.empty())
270
    {
271
4
        attempts = 0;
272
4
        complete = false;
273
12
        d->mUrl = d->mUrlQueue.front();
274
8
        d->mUrlQueue.pop();
275
276
8
        logger->log_r("selected url: %s", d->mUrl.c_str());
277
8
        while (attempts < 3 &&
278
8
               !complete &&
279

7
               (d->mOptions.cancel == 0u) &&
280
3
               isTerminate == false)
281
        {
282
            d->mUpdateFunction(d->mPtr, DownloadStatus::Starting, 0, 0);
283
284
            if ((d->mOptions.cancel != 0u) || isTerminate == true)
285
            {
286
                return 0;
287
            }
288
            d->mCurl = curl_easy_init();
289
290
            if (d->mCurl != nullptr &&
291
                d->mOptions.cancel == 0u &&
292
                isTerminate == false)
293
            {
294
                FILE *file = nullptr;
295
296
                if (d->mUpload)
297
                {
298
                    logger->log_r("Uploading: %s", d->mUrl.c_str());
299
                    curl_easy_setopt(d->mCurl, CURLOPT_URL, d->mUrl.c_str());
300
                    curl_easy_setopt(d->mCurl, CURLOPT_HTTPPOST, d->mFormPost);
301
                    curl_easy_setopt(d->mCurl, CURLOPT_WRITEFUNCTION,
302
                        &Download::writeFunction);
303
                    mUploadResponse.clear();
304
                }
305
                else
306
                {
307
                    logger->log_r("Downloading: %s", d->mUrl.c_str());
308
                    curl_easy_setopt(d->mCurl, CURLOPT_FOLLOWLOCATION, 1);
309
                    curl_easy_setopt(d->mCurl, CURLOPT_HTTPHEADER,
310
                        d->mHeaders);
311
                    if (d->mOptions.memoryWrite != 0u)
312
                    {
313
                        curl_easy_setopt(d->mCurl, CURLOPT_FAILONERROR, 1);
314
                        curl_easy_setopt(d->mCurl, CURLOPT_WRITEFUNCTION,
315
                                         d->mWriteFunction);
316
                        curl_easy_setopt(d->mCurl, CURLOPT_WRITEDATA, d->mPtr);
317
                    }
318
                    else
319
                    {
320
                        file = fopen(outFilename.c_str(), "w+b");
321
                        if (file != nullptr)
322
                        {
323
                            curl_easy_setopt(d->mCurl, CURLOPT_WRITEDATA,
324
                                file);
325
                        }
326
                    }
327
                    curl_easy_setopt(d->mCurl,
328
                        CURLOPT_USERAGENT,
329
                        settings.userAgent.c_str());
330
331
                    curl_easy_setopt(d->mCurl, CURLOPT_ERRORBUFFER, d->mError);
332
                    curl_easy_setopt(d->mCurl, CURLOPT_URL, d->mUrl.c_str());
333
                    curl_easy_setopt(d->mCurl, CURLOPT_NOPROGRESS, 0);
334
                    curl_easy_setopt(d->mCurl, CURLOPT_PROGRESSFUNCTION,
335
                        &downloadProgress);
336
                    curl_easy_setopt(d->mCurl, CURLOPT_PROGRESSDATA, ptr);
337
#if LIBCURL_VERSION_NUM >= 0x070a00
338
                    curl_easy_setopt(d->mCurl, CURLOPT_NOSIGNAL, 1);
339
#endif  // LIBCURL_VERSION_NUM >= 0x070a00
340
                    curl_easy_setopt(d->mCurl, CURLOPT_CONNECTTIMEOUT, 30);
341
                    curl_easy_setopt(d->mCurl, CURLOPT_TIMEOUT, 1800);
342
                    addHeaders(d->mCurl);
343
                    addProxy(d->mCurl);
344
                    secureCurl(d->mCurl);
345
                }
346
347
                if ((res = curl_easy_perform(d->mCurl)) != 0 &&
348
                    (d->mOptions.cancel == 0u) &&
349
                    isTerminate == false)
350
                {
351
                    PRAGMA45(GCC diagnostic push)
352
                    PRAGMA45(GCC diagnostic ignored "-Wswitch-enum")
353
                    switch (res)
354
                    {
355
                        case CURLE_ABORTED_BY_CALLBACK:
356
                            d->mOptions.cancel = 1u;
357
                            break;
358
                        case CURLE_COULDNT_CONNECT:
359
                        default:
360
                            break;
361
                    }
362
                    PRAGMA45(GCC diagnostic pop)
363
364
                    if (res != 0u)
365
                    {
366
                        if (d->mError != nullptr)
367
                        {
368
                            logger->log_r("curl error %d: %s host: %s",
369
                                res, d->mError, d->mUrl.c_str());
370
                        }
371
                        attempts++;
372
                        continue;
373
                    }
374
375
                    if ((d->mOptions.cancel != 0u) || isTerminate == true)
376
                        break;
377
378
//                    d->mUpdateFunction(d->mPtr, DownloadStatus::Error, 0, 0);
379
380
                    if (file != nullptr)
381
                    {
382
                        fclose(file);
383
                        file = nullptr;
384
                    }
385
                    if (!d->mUpload && (d->mOptions.memoryWrite == 0u))
386
                        ::remove(outFilename.c_str());
387
                    attempts++;
388
                    continue;
389
                }
390
391
                curl_easy_cleanup(d->mCurl);
392
                d->mCurl = nullptr;
393
394
                if (d->mUpload)
395
                {
396
                    if (file != nullptr)
397
                    {
398
                        fclose(file);
399
                        file = nullptr;
400
                    }
401
                    // need check first if we read data from server
402
                    complete = true;
403
                }
404
                else
405
                {
406
                    if (d->mOptions.memoryWrite == 0u)
407
                    {
408
                        // Don't check resources.xml checksum
409
                        if (d->mOptions.checkAdler != 0u)
410
                        {
411
                            const unsigned long adler = fadler32(file);
412
413
                            if (d->mAdler != adler)
414
                            {
415
                                if (file != nullptr)
416
                                {
417
                                    fclose(file);
418
                                    file = nullptr;
419
                                }
420
421
                                // Remove the corrupted file
422
                                ::remove(d->mFileName.c_str());
423
                                logger->log_r("Checksum for file %s failed:"
424
                                    " (%lx/%lx)",
425
                                    d->mFileName.c_str(),
426
                                    adler, d->mAdler);
427
                                attempts++;
428
                                continue;
429
                            }
430
                        }
431
432
                        if (file != nullptr)
433
                        {
434
                            fclose(file);
435
                            file = nullptr;
436
                        }
437
438
                        // Any existing file with this name is deleted first,
439
                        // otherwise the rename will fail on Windows.
440
                        if ((d->mOptions.cancel == 0u) && isTerminate == false)
441
                        {
442
                            if (d->mIsXml)
443
                            {
444
                                if (!XML::Document::validateXml(outFilename))
445
                                {
446
                                    logger->log_r("Xml file validation error");
447
                                    attempts++;
448
                                    continue;
449
                                }
450
                            }
451
452
                            ::remove(d->mFileName.c_str());
453
                            Files::renameFile(outFilename, d->mFileName);
454
455
                            // Check if we can open it and no errors were
456
                            // encountered during renaming
457
                            file = fopen(d->mFileName.c_str(), "rb");
458
                            if (file != nullptr)
459
                            {
460
                                fclose(file);
461
                                file = nullptr;
462
                                complete = true;
463
                            }
464
                        }
465
                    }
466
                    else
467
                    {
468
                        // It's stored in memory, we're done
469
                        complete = true;
470
                    }
471
                }
472
            }
473
474
            if (d->mCurl != nullptr)
475
            {
476
                curl_easy_cleanup(d->mCurl);
477
                d->mCurl = nullptr;
478
            }
479
480
            if ((d->mOptions.cancel != 0u) || isTerminate == true)
481
            {
482
                return 0;
483
            }
484
            attempts++;
485
        }
486
487

4
        if ((complete && attempts < 3) || (d->mOptions.cancel != 0u))
488
            break;
489
    }
490
491

3
    if ((d->mOptions.cancel != 0u) || isTerminate == true)
492
    {
493
        // Nothing to do...
494
    }
495
    else if (!complete || attempts >= 3)
496
    {
497
        d->mUpdateFunction(d->mPtr, DownloadStatus::Error, 0, 0);
498
    }
499
    else
500
    {
501
        d->mUpdateFunction(d->mPtr, DownloadStatus::Complete, 0, 0);
502
    }
503
504
    return 0;
505
}
506
507
void Download::addProxy(CURL *const curl)
508
{
509
    const int mode = config.getIntValue("downloadProxyType");
510
    if (mode == 0)
511
        return;
512
513
    if (mode > 1)
514
    {
515
        curl_easy_setopt(curl, CURLOPT_PROXY,
516
            config.getStringValue("downloadProxy").c_str());
517
    }
518
519
    switch (mode)
520
    {
521
        case 1:  // direct connection
522
        default:
523
            curl_easy_setopt(curl, CURLOPT_PROXY, "");
524
            break;
525
        case 2:  // HTTP
526
#if LIBCURL_VERSION_NUM >= 0x070300
527
            curl_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL,
528
                config.getIntValue("downloadProxyTunnel"));
529
#endif  // LIBCURL_VERSION_NUM >= 0x070300
530
            break;
531
        case 3:  // HTTP 1.0
532
#if LIBCURL_VERSION_NUM >= 0x071304
533
            curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP_1_0);
534
#endif  // LIBCURL_VERSION_NUM >= 0x071304
535
#if LIBCURL_VERSION_NUM >= 0x070300
536
            curl_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL,
537
                config.getIntValue("downloadProxyTunnel"));
538
#endif  // LIBCURL_VERSION_NUM >= 0x070300
539
            break;
540
        case 4:  // SOCKS4
541
#if LIBCURL_VERSION_NUM >= 0x070a00
542
            curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4);
543
#endif  // LIBCURL_VERSION_NUM >= 0x070a00
544
            break;
545
        case 5:  // SOCKS4A
546
#if LIBCURL_VERSION_NUM >= 0x071200
547
            curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4A);
548
#elif LIBCURL_VERSION_NUM >= 0x071000
549
            curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4);
550
#endif  // LIBCURL_VERSION_NUM >= 0x071200
551
552
            break;
553
        case 6:  // SOCKS5
554
#if LIBCURL_VERSION_NUM >= 0x071200
555
            curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
556
#endif  // LIBCURL_VERSION_NUM >= 0x071200
557
558
            break;
559
        case 7:  // SOCKS5 hostname
560
#if LIBCURL_VERSION_NUM >= 0x071200
561
            curl_easy_setopt(curl, CURLOPT_PROXYTYPE,
562
                CURLPROXY_SOCKS5_HOSTNAME);
563
#endif  // LIBCURL_VERSION_NUM >= 0x071200
564
565
            break;
566
    }
567
}
568
569
#if LIBCURL_VERSION_NUM >= 0x070a08
570
void Download::secureCurl(CURL *const curl)
571
#else  // LIBCURL_VERSION_NUM >= 0x070f01
572
void Download::secureCurl(CURL *const curl A_UNUSED)
573
#endif  // LIBCURL_VERSION_NUM >= 0x070f01
574
{
575
#if LIBCURL_VERSION_NUM >= 0x071304
576
    curl_easy_setopt(curl, CURLOPT_PROTOCOLS,
577
        CURLPROTO_HTTP | CURLPROTO_HTTPS);
578
    curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS,
579
        CURLPROTO_HTTP | CURLPROTO_HTTPS);
580
#endif  // LIBCURL_VERSION_NUM >= 0x071304
581
#if LIBCURL_VERSION_NUM >= 0x071500
582
    curl_easy_setopt(curl, CURLOPT_WILDCARDMATCH, 0);
583
#endif  // LIBCURL_VERSION_NUM >= 0x071500
584
#if LIBCURL_VERSION_NUM >= 0x070f01
585
    curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 3);
586
#endif  // LIBCURL_VERSION_NUM >= 0x070f01
587
#if LIBCURL_VERSION_NUM >= 0x070a08
588
    curl_easy_setopt(curl, CURLOPT_MAXFILESIZE, 536870912);
589
#endif  // LIBCURL_VERSION_NUM >= 0x070a08
590
591
#if LIBCURL_VERSION_NUM >= 0x073100
592
    curl_easy_setopt(curl, CURLOPT_TCP_FASTOPEN, 1L);
593
#endif  // LIBCURL_VERSION_NUM >= 0x073100
594
595
    curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1L);
596
    curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 60L);
597
}
598
599
#if LIBCURL_VERSION_NUM >= 0x071507
600
void Download::addHeaders(CURL *const curl)
601
{
602
    curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");
603
}
604
#else  // LIBCURL_VERSION_NUM >= 0x071507
605
606
void Download::addHeaders(CURL *const curl A_UNUSED)
607
{
608
}
609
#endif  // LIBCURL_VERSION_NUM >= 0x071507
610
611
void Download::prepareForm(curl_httppost **form, const std::string &fileName)
612
{
613
    curl_httppost *lastPtr = nullptr;
614
615
    std::ifstream file;
616
    file.open(fileName.c_str(), std::ios::in);
617
    if (!file.is_open())
618
        return;
619
620
    char *line = new char[10001];
621
    std::ostringstream str;
622
    while (file.getline(line, 10000))
623
        str << line << "\n";
624
625
    delete [] line;
626
627
    curl_formadd(form, &lastPtr,
628
        CURLFORM_COPYNAME, "f:1",
629
        CURLFORM_COPYCONTENTS, str.str().c_str(),
630
        CURLFORM_END);
631
}
632
633
size_t Download::writeFunction(void *ptr,
634
                               size_t size,
635
                               size_t nmemb,
636
                               void *stream A_UNUSED)
637
{
638
    const size_t totalMem = size * nmemb;
639
    char *buf = new char[totalMem + 1];
640
    memcpy(buf, ptr, totalMem);
641
    buf[totalMem] = 0;
642
    mUploadResponse.append(buf);
643
    delete [] buf;
644
    return totalMem;
645
}
646
647
4
}  // namespace Net