Subversion Repositories tpanel

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
21 andreas 1
/*
88 andreas 2
 * Copyright (C) 2021, 2022 by Andreas Theofilu <andreas@theosys.at>
21 andreas 3
 *
4
 * This program is free software; you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation; either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program; if not, write to the Free Software Foundation,
16
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
17
 */
18
 
19
#include <vector>
88 andreas 20
#include <thread>
21 andreas 21
 
22
#include <string.h>
23
#include <strings.h>
24
#include <sys/socket.h>
25
#include <sys/types.h>
26
#include <unistd.h>
27
#include <stdlib.h>
28
#include <fcntl.h>
29
#include <netinet/in.h>
30
#include <netdb.h>
31
#include <netinet/tcp.h>
32
#include <openssl/err.h>
33
#include <sys/socket.h>
34
#include <arpa/inet.h>
35
#include <signal.h>
36
#include <poll.h>
37
 
38
#include "thttpclient.h"
39
#include "base64.h"
40
#include "terror.h"
41
#include "tresources.h"
42
#include "tconfig.h"
93 andreas 43
#include "texcept.h"
21 andreas 44
 
45
#define HTTP_DIRECTION_RECEIVE  1
46
#define HTTP_DIRECTION_SEND     2
47
 
48
using std::string;
49
using std::vector;
50
using std::min;
51
 
52
THTTPClient::THTTPClient()
53
{
54
    DECL_TRACER("THTTPClient::THTTPClient()");
55
    mBody.body = nullptr;
56
    mBody.len = 0;
57
    mRequest.code = 0;
58
    mRequest.direction = 0;
59
    mRequest.method = UNSUPPORTED;
60
    mRequest.path = nullptr;
61
    mRequest.status = nullptr;
62
    mRequest.version = nullptr;
63
}
64
 
65
THTTPClient::~THTTPClient()
66
{
67
    DECL_TRACER("THTTPClient::~THTTPClient()");
68
    mBody.clear();
69
    mRequest.clear();
70
}
71
 
72
#define MAX_BUFFER  65535
73
#define MAX_BLOCK   32767
74
 
75
char *THTTPClient::tcall(size_t *size, const string& URL, const string& user, const string& pw)
76
{
77
    DECL_TRACER("THTTPClient::tcall(size_t size, const string& URL, const string& user, const string& pw)");
78
 
79
    char *buffer = nullptr;
80
    size_t bufsize = 0;
81
    mUser = user;
82
    mPassword = pw;
88 andreas 83
    bool encrypt = false;
21 andreas 84
 
85
    try
86
    {
87
        buffer = new char[MAX_BUFFER];
88
        bufsize = MAX_BUFFER;
89
    }
90
    catch (std::exception& e)
91
    {
92
        MSG_ERROR(e.what());
93
        return nullptr;
94
    }
95
 
96
    string request = makeRequest(URL);
97
 
97 andreas 98
    if (TError::isError() || request.empty())
21 andreas 99
    {
100
        delete[] buffer;
101
        return nullptr;
102
    }
103
 
88 andreas 104
    if (mURL.scheme == "https")
105
        encrypt = true;
21 andreas 106
 
88 andreas 107
    if (!connect(mURL.host, mURL.port, encrypt))
21 andreas 108
    {
109
        delete[] buffer;
110
        return nullptr;
111
    }
112
 
113
    int ret = 0;
88 andreas 114
    bool repeat = false;
21 andreas 115
 
93 andreas 116
    try
21 andreas 117
    {
93 andreas 118
        do
21 andreas 119
        {
93 andreas 120
            if ((ret = send((char *)request.c_str(), request.length())) < 0)
88 andreas 121
            {
93 andreas 122
                if (errno)
123
                {
124
                    MSG_ERROR("[" << mURL.host << "] Write error: " << strerror(errno));
125
                }
126
                else if (encrypt)
127
                {
128
                    int err = retrieveSSLerror(ret);
129
                    repeat = false;
21 andreas 130
 
93 andreas 131
                    switch (err)
132
                    {
133
                        case SSL_ERROR_ZERO_RETURN:     MSG_ERROR("The TLS/SSL peer has closed the connection for writing by sending the close_notify alert."); break;
134
 
135
                        case SSL_ERROR_WANT_READ:
136
                        case SSL_ERROR_WANT_WRITE:      MSG_TRACE("The operation did not complete and can be retried later."); repeat = true; break;
137
                        case SSL_ERROR_WANT_CONNECT:
138
                        case SSL_ERROR_WANT_ACCEPT:     MSG_TRACE("The operation did not complete; the same TLS/SSL I/O function should be called again later."); repeat = true; break;
139
                        case SSL_ERROR_WANT_X509_LOOKUP:MSG_TRACE("The operation did not complete because an application callback set by SSL_CTX_set_client_cert_cb() has asked to be called again."); repeat = true; break;
140
                        case SSL_ERROR_WANT_ASYNC:      MSG_TRACE("The operation did not complete because an asynchronous engine is still processing data."); repeat = true; break;
141
                        case SSL_ERROR_WANT_ASYNC_JOB:  MSG_TRACE("The asynchronous job could not be started because there were no async jobs available in the pool."); repeat = true; break;
142
                        case SSL_ERROR_WANT_CLIENT_HELLO_CB: MSG_TRACE("The operation did not complete because an application callback set by SSL_CTX_set_client_hello_cb() has asked to be called again."); repeat = true; break;
143
 
144
                        case SSL_ERROR_SYSCALL:         MSG_ERROR("Some non-recoverable, fatal I/O error occurred."); break;
145
                        case SSL_ERROR_SSL:             MSG_ERROR("A non-recoverable, fatal error in the SSL library occurred, usually a protocol error."); break;
146
 
147
                        default:
148
                            MSG_ERROR("Unknown error " << err << " occured!");
149
                    }
150
                }
151
                else
88 andreas 152
                {
93 andreas 153
                    MSG_ERROR("[" << mURL.host << "] Write error!");
154
                }
88 andreas 155
 
93 andreas 156
                if (!repeat)
157
                {
158
                    close();
88 andreas 159
 
93 andreas 160
                    if (buffer)
161
                        delete[] buffer;
88 andreas 162
 
93 andreas 163
                    return nullptr;
88 andreas 164
                }
165
            }
21 andreas 166
 
93 andreas 167
            if (repeat)
168
                std::this_thread::sleep_for(std::chrono::microseconds(1000));
21 andreas 169
        }
93 andreas 170
        while (repeat);
171
    }
172
    catch (TXceptNetwork& e)
173
    {
174
        MSG_ERROR("Error writing to " << mURL.host << ":" << mURL.port);
21 andreas 175
 
93 andreas 176
        if (buffer)
177
            delete[] buffer;
178
 
179
        return nullptr;
21 andreas 180
    }
181
 
94 andreas 182
    char buf[8194];
21 andreas 183
    memset(buf, 0, sizeof(buf));
184
    size_t pos = 0, length = 0;
97 andreas 185
    size_t rlen, totalLen = 0;
186
    int loop = 0;
21 andreas 187
 
188
    try
189
    {
97 andreas 190
        std::chrono::steady_clock::time_point timePoint = std::chrono::steady_clock::now();
191
        totalLen = 0;
192
 
94 andreas 193
        while ((rlen = receive(buf, sizeof(buf))) > 0 && rlen != TSocket::npos)
21 andreas 194
        {
93 andreas 195
            size_t len = rlen;
88 andreas 196
 
97 andreas 197
            if (totalLen == 0 && loop < 2)
198
            {
199
                // Let's see if we have already the content length
200
                char *cLenPos = nullptr;
201
 
202
                if ((cLenPos = strnstr(buf, "Content-Length:", rlen)) != nullptr)
203
                {
204
                    cLenPos += 16;  // Start of content length information
205
                    size_t cLen = atol(cLenPos);
206
 
207
                    char *cStart = strstr(buf, "\r\n\r\n"); // Find start of content
208
 
209
                    if (cStart)
210
                        totalLen = cLen + ((cStart + 4) - buf);
211
 
212
                    MSG_DEBUG("Total length: " << totalLen << ", content length: " << cLen);
213
                }
214
            }
215
 
88 andreas 216
            if ((pos + len) >= bufsize)
21 andreas 217
            {
93 andreas 218
                renew(&buffer, bufsize, bufsize + MAX_BLOCK);
219
 
220
                if (!buffer)
94 andreas 221
                {
222
                    MSG_ERROR("[" << mURL.host << "] Memory error: Allocating of " << (bufsize + MAX_BLOCK) << " failed!");
93 andreas 223
                    return nullptr;
94 andreas 224
                }
93 andreas 225
 
88 andreas 226
                bufsize += MAX_BLOCK;
227
            }
21 andreas 228
 
88 andreas 229
            if (len > 0)
230
            {
231
                memcpy(buffer+pos, buf, len);
232
                pos += len;
233
                length += len;
234
            }
21 andreas 235
 
97 andreas 236
            if (length && totalLen && length >= totalLen)
237
                break;
238
 
88 andreas 239
            memset(buf, 0, sizeof(buf));
97 andreas 240
            loop++;
21 andreas 241
        }
97 andreas 242
 
243
        if (TStreamError::checkFilter(HLOG_DEBUG))
244
        {
245
            std::chrono::steady_clock::time_point endPoint = std::chrono::steady_clock::now();
246
            std::chrono::nanoseconds difftime = endPoint - timePoint;
247
            std::chrono::seconds secs = std::chrono::duration_cast<std::chrono::seconds>(difftime);
248
            std::chrono::milliseconds msecs = std::chrono::duration_cast<std::chrono::milliseconds>(difftime) - std::chrono::duration_cast<std::chrono::seconds>(secs);
249
            std::stringstream s;
250
            s << std::chrono::duration_cast<std::chrono::nanoseconds> (difftime).count() << "[ns]" << " --> " << std::chrono::duration_cast<std::chrono::seconds>(secs).count() << "s " << std::chrono::duration_cast<std::chrono::milliseconds>(msecs).count() << "ms";
251
            MSG_DEBUG("[" << mURL.host << "] Elapsed time for receive: " << s.str());
252
        }
21 andreas 253
    }
93 andreas 254
    catch(TXceptNetwork& e)
255
    {
256
        MSG_ERROR("Error reading from " << mURL.host << ": " << e.what());
257
 
258
        if (buffer)
259
            delete[] buffer;
260
 
261
        return nullptr;
262
    }
21 andreas 263
    catch (std::exception& e)
264
    {
93 andreas 265
        MSG_ERROR("Error reading from " << mURL.host << ": " << e.what());
88 andreas 266
        close();
21 andreas 267
 
268
        if (buffer)
269
            delete[] buffer;
270
 
271
        return nullptr;
272
    }
273
 
274
    int myerrno = errno;
88 andreas 275
    close();
21 andreas 276
 
277
    if (pos == 0)
278
    {
279
        if (myerrno)
280
        {
93 andreas 281
            MSG_ERROR("Internal read error [" << mURL.host << "]: " << strerror(myerrno));
21 andreas 282
        }
283
        else
284
        {
93 andreas 285
            MSG_ERROR("Internal read error: Received no data from " << mURL.host);
21 andreas 286
        }
287
 
288
        if (buffer)
289
            delete[] buffer;
290
 
291
        return nullptr;
292
    }
293
 
130 andreas 294
    MSG_DEBUG("[" << mURL.host << "] Read " << length << " bytes.");
21 andreas 295
 
296
    if (parseHeader(buffer, length) != 0)
297
    {
298
        if (buffer)
299
            delete[] buffer;
300
 
301
        return nullptr;
302
    }
303
 
304
    if (mRequest.code >= 300)
305
    {
306
        if (mRequest.status && *mRequest.status)
307
        {
93 andreas 308
            MSG_ERROR("[" << mURL.host << "] " << mRequest.code << ": " << mRequest.status);
21 andreas 309
        }
310
        else
311
        {
93 andreas 312
            MSG_ERROR("[" << mURL.host << "] " << mRequest.code << ": UNKNOWN");
21 andreas 313
        }
314
 
93 andreas 315
        if (buffer)
316
            delete[] buffer;
317
 
21 andreas 318
        return nullptr;
319
    }
320
 
93 andreas 321
    if (buffer)
322
        delete[] buffer;
323
 
21 andreas 324
    *size = mBody.len;
325
    return mBody.body;
326
}
327
 
328
string THTTPClient::makeURL(const string& scheme, const string& host, int port, const string& path)
329
{
330
    DECL_TRACER("THTTPClient::makeURL(const string& scheme, const string& host, int port, const string& path)");
331
 
332
    string url = scheme + "://" + host;
333
 
334
    if (port > 0)
335
        url += std::to_string(port);
336
 
337
    url += "/" + path;
338
    MSG_DEBUG("URL: " << url);
339
    return url;
340
}
341
 
94 andreas 342
string THTTPClient::makeURLs(const string& scheme, const string& host, int port, const string& path)
343
{
344
    DECL_TRACER("THTTPClient::makeURLs(const string& scheme, const string& host, int port, const string& path)");
345
 
346
    THTTPClient c;
347
    return c.makeURL(scheme, host, port, path);
348
}
349
 
21 andreas 350
URL_t& THTTPClient::parseURL(const string& URL)
351
{
352
    DECL_TRACER("THTTPClient::parseURL(const string& URL, const string& user, const string& pw)");
353
 
354
    if (URL.empty())
355
    {
356
        MSG_ERROR("Invalid empty URL!");
357
        TError::setError();
358
        mURL.clear();
359
        return mURL;
360
    }
361
 
362
    size_t pos = URL.find("://");
363
 
364
    if (pos == string::npos)
365
    {
366
        MSG_ERROR("Invalid URL: " << URL);
367
        TError::setError();
368
        mURL.clear();
369
        return mURL;
370
    }
371
 
372
    mURL.scheme = URL.substr(0, pos);
373
    pos += 3;
374
    string part = URL.substr(pos);
375
    pos = part.find("/");
376
    string host;
377
 
378
    if (pos == string::npos)
379
    {
380
        host = part;
381
        part.clear();
382
    }
383
    else
384
    {
385
        host = part.substr(0, pos);
386
        part = part.substr(pos + 1);
387
    }
388
 
389
    pos = host.find(":");
390
 
391
    if (pos != string::npos)
392
    {
393
        string sport = host.substr(pos + 1);
394
        mURL.host = host.substr(0, pos);
395
        mURL.port = atoi(sport.c_str());
396
    }
397
    else
398
        mURL.host = host;
399
 
400
    if (!part.empty())
401
        mURL.path = part;
402
 
403
    pos = host.find("@");
404
 
405
    if (pos != string::npos)
406
    {
407
        mURL.user = host.substr(0, pos);
408
        mURL.host = host.substr(pos + 1);
409
    }
410
 
411
    if (mURL.port == 0)
412
    {
413
        if (mURL.scheme.compare("https") == 0)
414
            mURL.port = 443;
415
        else
416
            mURL.port = 80;
417
    }
418
 
419
    MSG_DEBUG("URL components: Scheme: " << mURL.scheme << ", Host: " << mURL.host << ", Port: " << mURL.port << ", Path: " << mURL.path << ", User: " << mURL.user << ", Password: " << ((mURL.pw.empty()) ? "" : "****"));
420
    return mURL;
421
}
422
 
423
int THTTPClient::parseHeader(const char *buffer, size_t len)
424
{
425
    DECL_TRACER("THTTPClient::parseHeader(const char *buffer, size_t len)");
426
 
427
    if (buffer == nullptr || len == 0)
428
    {
94 andreas 429
        MSG_ERROR("[" << mURL.host << "] Empty receive buffer!");
21 andreas 430
        return -1;
431
    }
432
 
433
    int blen = 0, receive = 0, code = 0;
434
    char *raw = (char *)buffer;
435
    char *path, *version, *status;
436
    int direction = HTTP_DIRECTION_SEND;
437
    METHOD_t method;
438
    mHeader.clear();
439
    mBody.clear();
440
    mRequest.clear();
441
 
442
    // Method
443
    size_t meth_len = strcspn(raw, " ");
444
 
445
    if (meth_len >= len)
446
    {
93 andreas 447
        MSG_ERROR("[" << mURL.host << "] Buffer contains no valid HTTP response!");
21 andreas 448
        return -1;
449
    }
450
 
451
    if (memcmp(raw, "GET", 3) == 0)
452
        method = GET;
453
    else if (memcmp(raw, "PUT", 3) == 0)
454
        method = PUT;
455
    else if (memcmp(raw, "POST", 4) == 0)
456
        method = POST;
457
    else if (memcmp(raw, "HEAD", 4) == 0)
458
        method = HEAD;
459
    else
460
    {
461
        method = UNSUPPORTED;
462
        direction = HTTP_DIRECTION_RECEIVE;
463
        receive = 1;
93 andreas 464
        MSG_DEBUG("[" << mURL.host << "] Detected a receive buffer");
21 andreas 465
    }
466
 
467
    mRequest.method = method;
468
    mRequest.direction = direction;
469
    raw += meth_len + 1; // move past <SP>
470
    status = path = version = nullptr;
471
 
472
    if (!receive)
473
    {
474
        // Request-URI
475
        size_t path_len = strcspn(raw, " ");
476
 
477
        try
478
        {
479
            path = new char[path_len+1];
480
            memcpy(path, raw, path_len);
481
            path[path_len] = '\0';
482
            raw += path_len + 1; // move past <SP>
483
 
484
            // HTTP-Version
485
            size_t ver_len = strcspn(raw, "\r\n");
486
            version = new char[ver_len + 1];
487
            memcpy(version, raw, ver_len);
488
            version[ver_len] = '\0';
489
            raw += ver_len + 2; // move past <CR><LF>
490
            mRequest.path = path;
491
            mRequest.version = version;
492
        }
493
        catch (std::exception& e)
494
        {
93 andreas 495
            MSG_ERROR("[" << mURL.host << "] Error allocating memory: " << e.what());
21 andreas 496
 
497
            if (path)
498
                delete[] path;
499
 
500
            if (version)
501
                delete[] version;
502
 
503
            mRequest.path = nullptr;
504
            mRequest.version = nullptr;
505
            return -1;
506
        }
507
    }
508
    else
509
    {
510
        char scode[16];
511
        memset(scode, 0, sizeof(scode));
512
        size_t code_len = strcspn(raw, " ");
513
        strncpy(scode, raw, min(code_len, sizeof(scode)));
514
        scode[sizeof(scode)-1] = 0;
515
        code = atoi(scode);
516
 
93 andreas 517
        MSG_DEBUG("[" << mURL.host << "] Received code " << code);
21 andreas 518
 
519
        if (strstr(buffer, "\r\n\r\n") == NULL)
520
        {
93 andreas 521
            MSG_ERROR("[" << mURL.host << "] Received no content!");
21 andreas 522
            return -1;
523
        }
524
 
525
        if (code_len >= len)
526
        {
93 andreas 527
            MSG_ERROR("[" << mURL.host << "] Buffer contains no valid HTTP response!");
21 andreas 528
            return -1;
529
        }
530
 
531
        raw += code_len + 1;
532
        size_t stat_len = strcspn(raw, "\r\n");
533
 
534
        try
535
        {
536
            status = new char[stat_len + 1];
537
            memcpy(status, raw, stat_len);
538
            status[stat_len] = '\0';
539
            raw += stat_len + 2;
540
            mRequest.status = status;
541
        }
542
        catch (std::exception& e)
543
        {
93 andreas 544
            MSG_ERROR("[" << mURL.host << "] Error allocating memory: " << e.what());
21 andreas 545
            return -1;
546
        }
547
    }
548
 
549
    char *end = strstr(raw, "\r\n\r\n");
550
 
551
    while (raw < end)
552
    {
553
        HTTPHEAD_t head;
554
        char hv0[1024];
555
        // name
556
        size_t name_len = strcspn(raw, ":");
557
 
558
        size_t mylen = min(name_len, sizeof(hv0));
559
        memcpy(hv0, raw, mylen);
560
        hv0[mylen] = '\0';
561
        head.name = hv0;
562
        raw += name_len + 1; // move past :
563
 
564
        while (*raw == ' ')
565
            raw++;
566
 
567
        // value
568
        size_t value_len = strcspn(raw, "\r\n");
569
        mylen = min(value_len, sizeof(hv0));
570
        memcpy(hv0, raw, mylen);
571
        hv0[mylen] = '\0';
572
        head.content = hv0;
94 andreas 573
        MSG_DEBUG("[" << mURL.host << "] Found header: " << head.name << ": " << head.content);
21 andreas 574
        raw += value_len + 2; // move past <CR><LF>
575
 
576
        if (head.name.compare("Content-Length") == 0)
577
            blen = atoi(head.content.c_str());
578
 
579
        // next
580
        mHeader.push_back(head);
581
    }
582
 
583
    if (blen == 0)
584
    {
585
        size_t head_len = strcspn(buffer, "\r\n\r\n");
586
 
587
        if (head_len < len)
588
            blen = len - head_len + 4;
589
    }
590
 
93 andreas 591
    MSG_DEBUG("[" << mURL.host << "] Content length: " << blen);
21 andreas 592
 
593
    if (blen > 0)
594
    {
595
        raw = end + 4;
596
 
597
        try
598
        {
599
            mBody.body = new char[blen +1];
600
            memcpy(mBody.body, raw, blen);
601
            mBody.body[blen] = '\0';
602
            mBody.len = blen;
603
        }
604
        catch(std::exception& e)
605
        {
606
            MSG_ERROR(e.what());
607
 
608
            if (status)
609
                delete[] status;
610
 
611
            if (path)
612
                delete[] path;
613
 
614
            if (version)
615
                delete[] version;
616
 
617
            mRequest.status = nullptr;
618
            mRequest.path = nullptr;
619
            mRequest.version = nullptr;
620
            return -1;
621
        }
622
    }
623
 
624
    return 0;
625
}
626
 
627
string THTTPClient::getHeadParameter(const string& name)
628
{
629
    DECL_TRACER("THTTPClient::getHeadParameter(const string& name)");
630
 
631
    if (mHeader.empty())
632
        return string();
633
 
634
    vector<HTTPHEAD_t>::iterator iter;
635
 
118 andreas 636
    for (iter = mHeader.begin(); iter != mHeader.end(); ++iter)
21 andreas 637
    {
638
        if (iter->name.compare(name) == 0)
639
            return iter->content;
640
    }
641
 
642
    return string();
643
}
93 andreas 644
/*
21 andreas 645
char *THTTPClient::getContent(const char* buffer)
646
{
647
    DECL_TRACER("THTTPClient::getContent(const char* buffer)");
648
 
93 andreas 649
    if (!buffer)
650
        return nullptr;
651
 
21 andreas 652
    char *ctnt = strstr((char *)buffer, "\r\n\r\n");
653
    return ctnt;
654
}
93 andreas 655
*/
21 andreas 656
string THTTPClient::makeRequest(const string& url)
657
{
658
    DECL_TRACER("THTTPClient::makeRequest(const string& url)");
659
 
660
    URL_t uparts = parseURL(url);
97 andreas 661
 
662
    if (uparts.host == "0.0.0.0" || uparts.host == "8.8.8.8")
663
    {
664
        MSG_WARNING("Refusing to connect to host " << uparts.host << "!");
665
        return string();
666
    }
667
 
21 andreas 668
    string request = "GET " + uparts.path + " HTTP/1.1\r\n";
669
    request += "Host: " + uparts.host;
670
 
671
    if (uparts.port > 0 && uparts.port != 80 && uparts.port != 443)
672
        request += ":" + std::to_string(uparts.port);
673
 
674
    request += "\r\n";
675
 
676
    if (!mUser.empty())
677
    {
678
        string clearname = mUser + ":" + mPassword;
679
        string enc = Base64::encode((BYTE *)clearname.c_str(), clearname.size());
680
        request += "Authorization: Basic " + enc + "\r\n";
681
    }
682
 
93 andreas 683
    request += "User-Agent: tpanel/" + std::to_string(V_MAJOR) + "." + std::to_string(V_MINOR) + "." + std::to_string(V_PATCH) + "\r\n";
21 andreas 684
    request += "Accept: image/*\r\n";
685
    request += "\r\n";
686
    MSG_DEBUG("Requesting: " << std::endl << request << "------------------------------------------");
687
    return request;
688
}