GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/net/download.cpp Lines: 71 255 27.8 %
Date: 2017-11-29 Branches: 27 263 10.3 %

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 2009-2010  The Mana Developers
4
 *  Copyright (C) 2011-2017  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 <sstream>
36
37
#include "debug.h"
38
39
const char *DOWNLOAD_ERROR_MESSAGE_THREAD
40
    = "Could not create download thread!";
41
42
extern volatile bool isTerminate;
43
44
enum
45
{
46
    OPTIONS_NONE = 0,
47
    OPTIONS_MEMORY = 1
48
};
49
50
namespace Net
51
{
52
53
2
std::string Download::mUploadResponse;
54
55
3
Download::Download(void *const ptr,
56
                   const std::string &url,
57
                   const DownloadUpdate updateFunction,
58
                   const bool ignoreError,
59
                   const bool isUpload,
60
3
                   const bool isXml) :
61
    mPtr(ptr),
62
    mUrl(url),
63
    mOptions(),
64
    mFileName(),
65
    mUrlQueue(),
66
    mWriteFunction(nullptr),
67
    mAdler(0),
68
    mUpdateFunction(updateFunction),
69
    mThread(nullptr),
70
    mCurl(nullptr),
71
    mHeaders(nullptr),
72
    mFormPost(nullptr),
73
3
    mError(static_cast<char*>(calloc(CURL_ERROR_SIZE + 1, 1))),
74
    mIgnoreError(ignoreError),
75
    mUpload(isUpload),
76
15
    mIsXml(isXml)
77
{
78
3
    if (mError != nullptr)
79
3
        mError[0] = 0;
80
81
3
    mOptions.cancel = 0;
82
3
    mOptions.memoryWrite = 0;
83
3
    mOptions.checkAdler = 1u;
84
3
    if (!mUpload)
85
    {
86
6
        const std::string serverName = settings.serverName;
87
3
        if (!serverName.empty())
88
        {
89
            if (mUrl.find('?') == std::string::npos)
90
                mUrl.append("?host=");
91
            else
92
                mUrl.append("&host=");
93
            mUrl.append(serverName);
94
        }
95
    }
96
6
    mUrlQueue.push(url);
97
3
}
98
99
15
Download::~Download()
100
{
101
3
    if (mFormPost != nullptr)
102
    {
103
        curl_formfree(mFormPost);
104
        mFormPost = nullptr;
105
    }
106
107
3
    if (mHeaders != nullptr)
108
    {
109
2
        curl_slist_free_all(mHeaders);
110
2
        mHeaders = nullptr;
111
    }
112
113
3
    SDL::WaitThread(mThread);
114
3
    mThread = nullptr;
115
3
    free(mError);
116
3
}
117
118
/**
119
 * Calculates the Alder-32 checksum for the given file.
120
 */
121
unsigned long Download::fadler32(FILE *const file)
122
{
123
    if (file == nullptr)
124
        return 0;
125
126
    // Obtain file size
127
    fseek(file, 0, SEEK_END);
128
    const long fileSize = ftell(file);
129
    if (fileSize < 0)
130
    {   // file size error
131
        return 0;
132
    }
133
    rewind(file);
134
135
    // Calculate Adler-32 checksum
136
    char *const buffer = new char[CAST_SIZE(fileSize)];
137
    const uInt read = static_cast<uInt>(fread(buffer, 1, fileSize, file));
138
    unsigned long adler = adler32(0L, nullptr, 0);
139
    adler = adler32(static_cast<uInt>(adler),
140
        reinterpret_cast<Bytef*>(buffer), read);
141
    delete [] buffer;
142
    return adler;
143
}
144
145
unsigned long Download::adlerBuffer(const char *const buffer, int size)
146
{
147
    FUNC_BLOCK("Download::adlerBuffer", 1)
148
    unsigned long adler = adler32(0L, nullptr, 0);
149
    return adler32(static_cast<uInt>(adler),
150
        reinterpret_cast<const Bytef*>(buffer), size);
151
}
152
153
void Download::addHeader(const std::string &header)
154
{
155

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

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

4
        if ((complete && attempts < 3) || (d->mOptions.cancel != 0u))
484
            break;
485
    }
486
487

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