Coverage Report

Created: 2025-07-04 06:51

/src/znc/src/HTTPSock.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (C) 2004-2025 ZNC, see the NOTICE file for details.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
17
#include <znc/HTTPSock.h>
18
#include <znc/FileUtils.h>
19
#include <znc/znc.h>
20
#include <iomanip>
21
22
#ifdef HAVE_ZLIB
23
#include <zlib.h>
24
#endif
25
26
using std::map;
27
using std::set;
28
29
0
#define MAX_POST_SIZE 1024 * 1024
30
31
CHTTPSock::CHTTPSock(CModule* pMod, const CString& sURIPrefix)
32
0
    : CHTTPSock(pMod, sURIPrefix, "", 0) {
33
0
    Init();
34
0
}
35
36
CHTTPSock::CHTTPSock(CModule* pMod, const CString& sURIPrefix,
37
                     const CString& sHostname, unsigned short uPort,
38
                     int iTimeout)
39
0
    : CSocket(pMod, sHostname, uPort, iTimeout),
40
0
      m_bSentHeader(false),
41
0
      m_bGotHeader(false),
42
0
      m_bLoggedIn(false),
43
0
      m_bPost(false),
44
0
      m_bDone(false),
45
0
      m_bBasicAuth(false),
46
0
      m_uPostLen(0),
47
0
      m_sPostData(""),
48
0
      m_sURI(""),
49
0
      m_sUser(""),
50
0
      m_sPass(""),
51
0
      m_sContentType(""),
52
0
      m_sDocRoot(""),
53
0
      m_sForwardedIP(""),
54
0
      m_msvsPOSTParams(),
55
0
      m_msvsGETParams(),
56
0
      m_msHeaders(),
57
0
      m_bHTTP10Client(false),
58
0
      m_sIfNoneMatch(""),
59
0
      m_bAcceptGzip(false),
60
0
      m_msRequestCookies(),
61
0
      m_msResponseCookies(),
62
0
      m_sURIPrefix(sURIPrefix) {
63
0
    Init();
64
0
}
65
66
0
void CHTTPSock::Init() {
67
0
    EnableReadLine();
68
0
    SetMaxBufferThreshold(10240);
69
0
}
70
71
0
CHTTPSock::~CHTTPSock() {}
72
73
0
void CHTTPSock::ReadData(const char* data, size_t len) {
74
0
    if (!m_bDone && m_bGotHeader && m_bPost) {
75
0
        m_sPostData.append(data, len);
76
0
        CheckPost();
77
0
    }
78
0
}
79
80
0
bool CHTTPSock::SendCookie(const CString& sKey, const CString& sValue) {
81
0
    if (!sKey.empty() && !sValue.empty()) {
82
0
        if (m_msRequestCookies.find(sKey) == m_msRequestCookies.end() ||
83
0
            m_msRequestCookies[sKey].StrCmp(sValue) != 0) {
84
            // only queue a Set-Cookie to be sent if the client didn't send a
85
            // Cookie header of the same name+value.
86
0
            m_msResponseCookies[sKey] = sValue;
87
0
        }
88
0
        return true;
89
0
    }
90
91
0
    return false;
92
0
}
93
94
0
CString CHTTPSock::GetRequestCookie(const CString& sKey) const {
95
0
    MCString::const_iterator it = m_msRequestCookies.find(sKey);
96
97
0
    return it != m_msRequestCookies.end() ? it->second : "";
98
0
}
99
100
0
void CHTTPSock::CheckPost() {
101
0
    if (m_sPostData.size() >= m_uPostLen) {
102
0
        ParseParams(m_sPostData.Left(m_uPostLen), m_msvsPOSTParams);
103
0
        GetPage();
104
0
        m_sPostData.clear();
105
0
        m_bDone = true;
106
0
    }
107
0
}
108
109
0
void CHTTPSock::ReadLine(const CString& sData) {
110
0
    if (m_bGotHeader) {
111
0
        return;
112
0
    }
113
114
0
    CString sLine = sData;
115
0
    sLine.TrimRight("\r\n");
116
117
0
    CString sName = sLine.Token(0);
118
119
0
    if (sName.Equals("GET")) {
120
0
        m_bPost = false;
121
0
        m_sURI = sLine.Token(1);
122
0
        m_bHTTP10Client = sLine.Token(2).Equals("HTTP/1.0");
123
0
        ParseURI();
124
0
    } else if (sName.Equals("POST")) {
125
0
        m_bPost = true;
126
0
        m_sURI = sLine.Token(1);
127
0
        ParseURI();
128
0
    } else if (sName.Equals("Cookie:")) {
129
0
        VCString vsNV;
130
131
0
        sLine.Token(1, true).Split(";", vsNV, false, "", "", true, true);
132
133
0
        for (const CString& s : vsNV) {
134
0
            m_msRequestCookies[s.Token(0, false, "=")
135
0
                                   .Escape_n(CString::EURL, CString::EASCII)] =
136
0
                s.Token(1, true, "=").Escape_n(CString::EURL, CString::EASCII);
137
0
        }
138
0
    } else if (sName.Equals("Authorization:")) {
139
0
        CString sUnhashed;
140
0
        sLine.Token(2).Base64Decode(sUnhashed);
141
0
        m_sUser = sUnhashed.Token(0, false, ":");
142
0
        m_sPass = sUnhashed.Token(1, true, ":");
143
0
        m_bBasicAuth = true;
144
        // Postpone authorization attempt until end of headers, because cookies
145
        // should be read before that, otherwise session id will be overwritten
146
        // in GetSession()
147
0
    } else if (sName.Equals("Content-Length:")) {
148
0
        m_uPostLen = sLine.Token(1).ToULong();
149
0
        if (m_uPostLen > MAX_POST_SIZE)
150
0
            PrintErrorPage(413, "Request Entity Too Large",
151
0
                           "The request you sent was too large.");
152
0
    } else if (sName.Equals("X-Forwarded-For:")) {
153
        // X-Forwarded-For: client, proxy1, proxy2
154
0
        if (m_sForwardedIP.empty()) {
155
0
            const VCString& vsTrustedProxies = CZNC::Get().GetTrustedProxies();
156
0
            CString sIP = GetRemoteIP();
157
158
0
            VCString vsIPs;
159
0
            sLine.Token(1, true).Split(",", vsIPs, false, "", "", false, true);
160
161
0
            while (!vsIPs.empty()) {
162
                // sIP told us that it got connection from vsIPs.back()
163
                // check if sIP is trusted proxy
164
0
                bool bTrusted = false;
165
0
                for (const CString& sTrustedProxy : vsTrustedProxies) {
166
0
                    if (CUtils::CheckCIDR(sIP, sTrustedProxy)) {
167
0
                        bTrusted = true;
168
0
                        break;
169
0
                    }
170
0
                }
171
0
                if (bTrusted) {
172
                    // sIP is trusted proxy, so use vsIPs.back() as new sIP
173
0
                    sIP = vsIPs.back();
174
0
                    vsIPs.pop_back();
175
0
                } else {
176
0
                    break;
177
0
                }
178
0
            }
179
180
            // either sIP is not trusted proxy, or it's in the beginning of the
181
            // X-Forwarded-For list in both cases use it as the endpoind
182
0
            m_sForwardedIP = sIP;
183
0
        }
184
0
    } else if (sName.Equals("If-None-Match:")) {
185
        // this is for proper client cache support (HTTP 304) on static files:
186
0
        m_sIfNoneMatch = sLine.Token(1, true);
187
0
    } else if (sName.Equals("Accept-Encoding:") && !m_bHTTP10Client) {
188
0
        SCString ssEncodings;
189
        // trimming whitespace from the tokens is important:
190
0
        sLine.Token(1, true)
191
0
            .Split(",", ssEncodings, false, "", "", false, true);
192
0
        m_bAcceptGzip = (ssEncodings.find("gzip") != ssEncodings.end());
193
0
    } else if (sLine.empty()) {
194
0
        if (m_bBasicAuth && !m_bLoggedIn) {
195
0
            m_bLoggedIn = OnLogin(m_sUser, m_sPass, true);
196
            // After successful login ReadLine("") will be called again to
197
            // trigger "else" block Failed login sends error and closes socket,
198
            // so no infinite loop here
199
0
        } else {
200
0
            m_bGotHeader = true;
201
202
0
            if (m_bPost) {
203
0
                m_sPostData = GetInternalReadBuffer();
204
0
                CheckPost();
205
0
            } else {
206
0
                GetPage();
207
0
            }
208
209
0
            DisableReadLine();
210
0
        }
211
0
    }
212
0
}
213
214
0
CString CHTTPSock::GetRemoteIP() const {
215
0
    if (!m_sForwardedIP.empty()) {
216
0
        return m_sForwardedIP;
217
0
    }
218
219
0
    return CSocket::GetRemoteIP();
220
0
}
221
222
0
CString CHTTPSock::GetDate(time_t stamp) {
223
0
    struct tm tm;
224
0
    std::stringstream stream;
225
0
    const char* wkday[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
226
0
    const char* month[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
227
0
                           "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
228
229
0
    if (stamp == 0) time(&stamp);
230
0
    gmtime_r(&stamp, &tm);
231
232
0
    stream << wkday[tm.tm_wday] << ", ";
233
0
    stream << std::setfill('0') << std::setw(2) << tm.tm_mday << " ";
234
0
    stream << month[tm.tm_mon] << " ";
235
0
    stream << std::setfill('0') << std::setw(4) << tm.tm_year + 1900 << " ";
236
0
    stream << std::setfill('0') << std::setw(2) << tm.tm_hour << ":";
237
0
    stream << std::setfill('0') << std::setw(2) << tm.tm_min << ":";
238
0
    stream << std::setfill('0') << std::setw(2) << tm.tm_sec << " GMT";
239
240
0
    return stream.str();
241
0
}
242
243
0
void CHTTPSock::GetPage() {
244
0
    DEBUG("Page Request [" << m_sURI << "] ");
245
246
    // Check that the requested path starts with the prefix. Strip it if so.
247
0
    if (!m_sURI.TrimPrefix(m_sURIPrefix)) {
248
0
        DEBUG("INVALID path => Does not start with prefix [" + m_sURIPrefix +
249
0
              "]");
250
0
        DEBUG("Expected prefix:   " << m_sURIPrefix);
251
0
        DEBUG("Requested path:    " << m_sURI);
252
0
        Redirect("/");
253
0
    } else if (m_sURI.empty()) {
254
        // This can happen if prefix was /foo, and the requested page is /foo
255
0
        Redirect("/");
256
0
    } else {
257
0
        OnPageRequest(m_sURI);
258
0
    }
259
0
}
260
261
#ifdef HAVE_ZLIB
262
static bool InitZlibStream(z_stream* zStrm, const char* buf) {
263
    memset(zStrm, 0, sizeof(z_stream));
264
    zStrm->next_in = (Bytef*)buf;
265
266
    // "15" is the default value for good compression,
267
    // the weird "+ 16" means "please generate a gzip header and trailer".
268
    const int WINDOW_BITS = 15 + 16;
269
    const int MEMLEVEL = 8;
270
271
    return (deflateInit2(zStrm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, WINDOW_BITS,
272
                         MEMLEVEL, Z_DEFAULT_STRATEGY) == Z_OK);
273
}
274
#endif
275
276
0
void CHTTPSock::PrintPage(const CString& sPage) {
277
#ifdef HAVE_ZLIB
278
    if (m_bAcceptGzip && !SentHeader()) {
279
        char szBuf[4096];
280
        z_stream zStrm;
281
        int zStatus, zFlush = Z_NO_FLUSH;
282
283
        if (InitZlibStream(&zStrm, sPage.c_str())) {
284
            DEBUG("- Sending gzip-compressed.");
285
            AddHeader("Content-Encoding", "gzip");
286
            PrintHeader(0);  // we do not know the compressed data's length
287
288
            zStrm.avail_in = sPage.size();
289
            do {
290
                if (zStrm.avail_in == 0) {
291
                    zFlush = Z_FINISH;
292
                }
293
294
                zStrm.next_out = (Bytef*)szBuf;
295
                zStrm.avail_out = sizeof(szBuf);
296
297
                zStatus = deflate(&zStrm, zFlush);
298
299
                if ((zStatus == Z_OK || zStatus == Z_STREAM_END) &&
300
                    zStrm.avail_out < sizeof(szBuf)) {
301
                    Write(szBuf, sizeof(szBuf) - zStrm.avail_out);
302
                }
303
            } while (zStatus == Z_OK);
304
305
            Close(Csock::CLT_AFTERWRITE);
306
            deflateEnd(&zStrm);
307
            return;
308
        }
309
310
    }  // else: fall through
311
#endif
312
0
    if (!SentHeader()) {
313
0
        PrintHeader(sPage.length());
314
0
    } else {
315
0
        DEBUG("PrintPage(): Header was already sent");
316
0
    }
317
318
0
    Write(sPage);
319
0
    Close(Csock::CLT_AFTERWRITE);
320
0
}
321
322
0
bool CHTTPSock::PrintFile(const CString& sFileName, CString sContentType) {
323
0
    CString sFilePath = sFileName;
324
325
0
    if (!m_sDocRoot.empty()) {
326
0
        sFilePath.TrimLeft("/");
327
328
0
        sFilePath = CDir::CheckPathPrefix(m_sDocRoot, sFilePath, m_sDocRoot);
329
330
0
        if (sFilePath.empty()) {
331
0
            PrintErrorPage(403, "Forbidden",
332
0
                           "You don't have permission to access that file on "
333
0
                           "this server.");
334
0
            DEBUG("THIS FILE:     [" << sFilePath << "] does not live in ...");
335
0
            DEBUG("DOCUMENT ROOT: [" << m_sDocRoot << "]");
336
0
            return false;
337
0
        }
338
0
    }
339
340
0
    CFile File(sFilePath);
341
342
0
    if (!File.Open()) {
343
0
        PrintNotFound();
344
0
        return false;
345
0
    }
346
347
0
    if (sContentType.empty()) {
348
0
        if (sFileName.EndsWith(".html") || sFileName.EndsWith(".htm")) {
349
0
            sContentType = "text/html; charset=utf-8";
350
0
        } else if (sFileName.EndsWith(".css")) {
351
0
            sContentType = "text/css; charset=utf-8";
352
0
        } else if (sFileName.EndsWith(".js")) {
353
0
            sContentType = "application/x-javascript; charset=utf-8";
354
0
        } else if (sFileName.EndsWith(".jpg")) {
355
0
            sContentType = "image/jpeg";
356
0
        } else if (sFileName.EndsWith(".gif")) {
357
0
            sContentType = "image/gif";
358
0
        } else if (sFileName.EndsWith(".ico")) {
359
0
            sContentType = "image/x-icon";
360
0
        } else if (sFileName.EndsWith(".png")) {
361
0
            sContentType = "image/png";
362
0
        } else if (sFileName.EndsWith(".bmp")) {
363
0
            sContentType = "image/bmp";
364
0
        } else {
365
0
            sContentType = "text/plain; charset=utf-8";
366
0
        }
367
0
    }
368
369
0
    const time_t iMTime = File.GetMTime();
370
0
    bool bNotModified = false;
371
0
    CString sETag;
372
373
0
    if (iMTime > 0 && !m_bHTTP10Client) {
374
0
        sETag = "-" + CString(iMTime);  // lighttpd style ETag
375
376
0
        AddHeader("Last-Modified", GetDate(iMTime));
377
0
        AddHeader("ETag", "\"" + sETag + "\"");
378
0
        AddHeader("Cache-Control", "public");
379
380
0
        if (!m_sIfNoneMatch.empty()) {
381
0
            m_sIfNoneMatch.Trim("\\\"'");
382
0
            bNotModified =
383
0
                (m_sIfNoneMatch.Equals(sETag, CString::CaseSensitive));
384
0
        }
385
0
    }
386
387
0
    if (bNotModified) {
388
0
        PrintHeader(0, sContentType, 304, "Not Modified");
389
0
    } else {
390
0
        off_t iSize = File.GetSize();
391
392
        // Don't try to send files over 16 MiB, because it might block
393
        // the whole process and use huge amounts of memory.
394
0
        if (iSize > 16 * 1024 * 1024) {
395
0
            DEBUG("- Abort: File is over 16 MiB big: " << iSize);
396
0
            PrintErrorPage(500, "Internal Server Error", "File too big");
397
0
            return true;
398
0
        }
399
400
#ifdef HAVE_ZLIB
401
        bool bGzip = m_bAcceptGzip && (sContentType.StartsWith("text/") ||
402
                                       sFileName.EndsWith(".js"));
403
404
        if (bGzip) {
405
            DEBUG("- Sending gzip-compressed.");
406
            AddHeader("Content-Encoding", "gzip");
407
            PrintHeader(
408
                0,
409
                sContentType);  // we do not know the compressed data's length
410
            WriteFileGzipped(File);
411
        } else
412
#endif
413
0
        {
414
0
            PrintHeader(iSize, sContentType);
415
0
            WriteFileUncompressed(File);
416
0
        }
417
0
    }
418
419
0
    DEBUG("- ETag: [" << sETag << "] / If-None-Match [" << m_sIfNoneMatch
420
0
                      << "]");
421
422
0
    Close(Csock::CLT_AFTERWRITE);
423
424
0
    return true;
425
0
}
426
427
0
void CHTTPSock::WriteFileUncompressed(CFile& File) {
428
0
    char szBuf[4096];
429
0
    off_t iLen = 0;
430
0
    ssize_t i = 0;
431
0
    off_t iSize = File.GetSize();
432
433
    // while we haven't reached iSize and read() succeeds...
434
0
    while (iLen < iSize && (i = File.Read(szBuf, sizeof(szBuf))) > 0) {
435
0
        Write(szBuf, i);
436
0
        iLen += i;
437
0
    }
438
439
0
    if (i < 0) {
440
0
        DEBUG("- Error while reading file: " << strerror(errno));
441
0
    }
442
0
}
443
444
#ifdef HAVE_ZLIB
445
void CHTTPSock::WriteFileGzipped(CFile& File) {
446
    char szBufIn[8192];
447
    char szBufOut[8192];
448
    off_t iFileSize = File.GetSize(), iFileReadTotal = 0;
449
    z_stream zStrm;
450
    int zFlush = Z_NO_FLUSH;
451
    int zStatus;
452
453
    if (!InitZlibStream(&zStrm, szBufIn)) {
454
        DEBUG("- Error initializing zlib!");
455
        return;
456
    }
457
458
    do {
459
        ssize_t iFileRead = 0;
460
461
        if (zStrm.avail_in == 0) {
462
            // input buffer is empty, try to read more data from file.
463
            // if there is no more data, finish the stream.
464
465
            if (iFileReadTotal < iFileSize) {
466
                iFileRead = File.Read(szBufIn, sizeof(szBufIn));
467
468
                if (iFileRead < 1) {
469
                    // wtf happened? better quit compressing.
470
                    iFileReadTotal = iFileSize;
471
                    zFlush = Z_FINISH;
472
                } else {
473
                    iFileReadTotal += iFileRead;
474
475
                    zStrm.next_in = (Bytef*)szBufIn;
476
                    zStrm.avail_in = iFileRead;
477
                }
478
            } else {
479
                zFlush = Z_FINISH;
480
            }
481
        }
482
483
        zStrm.next_out = (Bytef*)szBufOut;
484
        zStrm.avail_out = sizeof(szBufOut);
485
486
        zStatus = deflate(&zStrm, zFlush);
487
488
        if ((zStatus == Z_OK || zStatus == Z_STREAM_END) &&
489
            zStrm.avail_out < sizeof(szBufOut)) {
490
            // there's data in the buffer:
491
            Write(szBufOut, sizeof(szBufOut) - zStrm.avail_out);
492
        }
493
494
    } while (zStatus == Z_OK);
495
496
    deflateEnd(&zStrm);
497
}
498
#endif
499
500
0
void CHTTPSock::ParseURI() {
501
0
    ParseParams(m_sURI.Token(1, true, "?"), m_msvsGETParams);
502
0
    m_sURI = m_sURI.Token(0, false, "?");
503
0
}
504
505
0
CString CHTTPSock::GetPath() const { return m_sURI.Token(0, false, "?"); }
506
507
void CHTTPSock::ParseParams(const CString& sParams,
508
0
                            map<CString, VCString>& msvsParams) {
509
0
    msvsParams.clear();
510
511
0
    VCString vsPairs;
512
0
    sParams.Split("&", vsPairs, true);
513
514
0
    for (const CString& sPair : vsPairs) {
515
0
        CString sName =
516
0
            sPair.Token(0, false, "=").Escape_n(CString::EURL, CString::EASCII);
517
0
        CString sValue =
518
0
            sPair.Token(1, true, "=").Escape_n(CString::EURL, CString::EASCII);
519
520
0
        msvsParams[sName].push_back(sValue);
521
0
    }
522
0
}
523
524
0
void CHTTPSock::SetDocRoot(const CString& s) {
525
0
    m_sDocRoot = s + "/";
526
0
    m_sDocRoot.Replace("//", "/");
527
0
}
528
529
0
const CString& CHTTPSock::GetDocRoot() const { return m_sDocRoot; }
530
531
0
const CString& CHTTPSock::GetUser() const { return m_sUser; }
532
533
0
const CString& CHTTPSock::GetPass() const { return m_sPass; }
534
535
0
const CString& CHTTPSock::GetContentType() const { return m_sContentType; }
536
537
0
const CString& CHTTPSock::GetParamString() const { return m_sPostData; }
538
539
0
const CString& CHTTPSock::GetURI() const { return m_sURI; }
540
541
0
const CString& CHTTPSock::GetURIPrefix() const { return m_sURIPrefix; }
542
543
0
bool CHTTPSock::HasParam(const CString& sName, bool bPost) const {
544
0
    if (bPost) return (m_msvsPOSTParams.find(sName) != m_msvsPOSTParams.end());
545
0
    return (m_msvsGETParams.find(sName) != m_msvsGETParams.end());
546
0
}
547
548
0
CString CHTTPSock::GetRawParam(const CString& sName, bool bPost) const {
549
0
    if (bPost) return GetRawParam(sName, m_msvsPOSTParams);
550
0
    return GetRawParam(sName, m_msvsGETParams);
551
0
}
552
553
CString CHTTPSock::GetRawParam(const CString& sName,
554
0
                               const map<CString, VCString>& msvsParams) {
555
0
    CString sRet;
556
557
0
    map<CString, VCString>::const_iterator it = msvsParams.find(sName);
558
559
0
    if (it != msvsParams.end() && it->second.size() > 0) {
560
0
        sRet = it->second[0];
561
0
    }
562
563
0
    return sRet;
564
0
}
565
566
CString CHTTPSock::GetParam(const CString& sName, bool bPost,
567
0
                            const CString& sFilter) const {
568
0
    if (bPost) return GetParam(sName, m_msvsPOSTParams, sFilter);
569
0
    return GetParam(sName, m_msvsGETParams, sFilter);
570
0
}
571
572
CString CHTTPSock::GetParam(const CString& sName,
573
                            const map<CString, VCString>& msvsParams,
574
0
                            const CString& sFilter) {
575
0
    CString sRet = GetRawParam(sName, msvsParams);
576
0
    sRet.Trim();
577
578
0
    for (size_t i = 0; i < sFilter.length(); i++) {
579
0
        sRet.Replace(CString(sFilter.at(i)), "");
580
0
    }
581
582
0
    return sRet;
583
0
}
584
585
size_t CHTTPSock::GetParamValues(const CString& sName, set<CString>& ssRet,
586
0
                                 bool bPost, const CString& sFilter) const {
587
0
    if (bPost) return GetParamValues(sName, ssRet, m_msvsPOSTParams, sFilter);
588
0
    return GetParamValues(sName, ssRet, m_msvsGETParams, sFilter);
589
0
}
590
591
size_t CHTTPSock::GetParamValues(const CString& sName, set<CString>& ssRet,
592
                                 const map<CString, VCString>& msvsParams,
593
0
                                 const CString& sFilter) {
594
0
    ssRet.clear();
595
596
0
    map<CString, VCString>::const_iterator it = msvsParams.find(sName);
597
598
0
    if (it != msvsParams.end()) {
599
0
        for (CString sParam : it->second) {
600
0
            sParam.Trim();
601
602
0
            for (size_t i = 0; i < sFilter.length(); i++) {
603
0
                sParam.Replace(CString(sFilter.at(i)), "");
604
0
            }
605
0
            ssRet.insert(sParam);
606
0
        }
607
0
    }
608
609
0
    return ssRet.size();
610
0
}
611
612
size_t CHTTPSock::GetParamValues(const CString& sName, VCString& vsRet,
613
0
                                 bool bPost, const CString& sFilter) const {
614
0
    if (bPost) return GetParamValues(sName, vsRet, m_msvsPOSTParams, sFilter);
615
0
    return GetParamValues(sName, vsRet, m_msvsGETParams, sFilter);
616
0
}
617
618
size_t CHTTPSock::GetParamValues(const CString& sName, VCString& vsRet,
619
                                 const map<CString, VCString>& msvsParams,
620
0
                                 const CString& sFilter) {
621
0
    vsRet.clear();
622
623
0
    map<CString, VCString>::const_iterator it = msvsParams.find(sName);
624
625
0
    if (it != msvsParams.end()) {
626
0
        for (CString sParam : it->second) {
627
0
            sParam.Trim();
628
629
0
            for (size_t i = 0; i < sFilter.length(); i++) {
630
0
                sParam.Replace(CString(sFilter.at(i)), "");
631
0
            }
632
0
            vsRet.push_back(sParam);
633
0
        }
634
0
    }
635
636
0
    return vsRet.size();
637
0
}
638
639
0
const map<CString, VCString>& CHTTPSock::GetParams(bool bPost) const {
640
0
    if (bPost) return m_msvsPOSTParams;
641
0
    return m_msvsGETParams;
642
0
}
643
644
0
bool CHTTPSock::IsPost() const { return m_bPost; }
645
646
0
bool CHTTPSock::PrintNotFound() {
647
0
    return PrintErrorPage(404, "Not Found",
648
0
                          "The requested URL was not found on this server.");
649
0
}
650
651
bool CHTTPSock::PrintErrorPage(unsigned int uStatusId,
652
                               const CString& sStatusMsg,
653
0
                               const CString& sMessage) {
654
0
    if (SentHeader()) {
655
0
        DEBUG("PrintErrorPage(): Header was already sent");
656
0
        return false;
657
0
    }
658
659
0
    CString sPage =
660
0
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
661
0
        "<!DOCTYPE html>\r\n"
662
0
        "<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\" "
663
0
        "xml:lang=\"en\">\r\n"
664
0
        "<head>\r\n"
665
0
        "<meta charset=\"UTF-8\"/>\r\n"
666
0
        "<title>" +
667
0
        CString(uStatusId) + " " + sStatusMsg.Escape_n(CString::EHTML) +
668
0
        "</title>\r\n"
669
0
        "</head>\r\n"
670
0
        "<body>\r\n"
671
0
        "<h1>" +
672
0
        sStatusMsg.Escape_n(CString::EHTML) +
673
0
        "</h1>\r\n"
674
0
        "<p>" +
675
0
        sMessage.Escape_n(CString::EHTML) +
676
0
        "</p>\r\n"
677
0
        "<hr/>\r\n"
678
0
        "<p>" +
679
0
        CZNC::GetTag(false, /* bHTML = */ true) +
680
0
        "</p>\r\n"
681
0
        "</body>\r\n"
682
0
        "</html>\r\n";
683
684
0
    PrintHeader(sPage.length(), "text/html; charset=utf-8", uStatusId,
685
0
                sStatusMsg);
686
0
    Write(sPage);
687
0
    Close(Csock::CLT_AFTERWRITE);
688
689
0
    return true;
690
0
}
691
692
0
bool CHTTPSock::ForceLogin() {
693
0
    if (m_bLoggedIn) {
694
0
        return true;
695
0
    }
696
697
0
    if (SentHeader()) {
698
0
        DEBUG("ForceLogin(): Header was already sent!");
699
0
        return false;
700
0
    }
701
702
0
    AddHeader("WWW-Authenticate",
703
0
              "Basic realm=\"" + CZNC::GetTag(false) + "\"");
704
0
    PrintErrorPage(401, "Unauthorized", "You need to login to view this page.");
705
706
0
    return false;
707
0
}
708
709
bool CHTTPSock::OnLogin(const CString& sUser, const CString& sPass,
710
0
                        bool bBasic) {
711
0
    return false;
712
0
}
713
714
0
bool CHTTPSock::SentHeader() const { return m_bSentHeader; }
715
716
bool CHTTPSock::PrintHeader(off_t uContentLength, const CString& sContentType,
717
0
                            unsigned int uStatusId, const CString& sStatusMsg) {
718
0
    if (SentHeader()) {
719
0
        DEBUG("PrintHeader(): Header was already sent!");
720
0
        return false;
721
0
    }
722
723
0
    if (!sContentType.empty()) {
724
0
        m_sContentType = sContentType;
725
0
    }
726
727
0
    if (m_sContentType.empty()) {
728
0
        m_sContentType = "text/html; charset=utf-8";
729
0
    }
730
731
0
    DEBUG("- " << uStatusId << " (" << sStatusMsg << ") [" << m_sContentType
732
0
               << "]");
733
734
0
    Write("HTTP/" + CString(m_bHTTP10Client ? "1.0 " : "1.1 ") +
735
0
          CString(uStatusId) + " " + sStatusMsg + "\r\n");
736
0
    Write("Date: " + GetDate() + "\r\n");
737
0
    Write("Server: " + CZNC::GetTag(false) + "\r\n");
738
0
    if (uContentLength > 0) {
739
0
        Write("Content-Length: " + CString(uContentLength) + "\r\n");
740
0
    }
741
0
    Write("Content-Type: " + m_sContentType + "\r\n");
742
743
0
    for (const auto& it : m_msResponseCookies) {
744
0
        Write("Set-Cookie: " + it.first.Escape_n(CString::EURL) + "=" +
745
0
              it.second.Escape_n(CString::EURL) + "; HttpOnly; path=/;" +
746
0
              (GetSSL() ? "Secure;" : "") + " SameSite=Strict;\r\n");
747
0
    }
748
749
0
    for (const auto& it : m_msHeaders) {
750
0
        Write(it.first + ": " + it.second + "\r\n");
751
0
    }
752
753
0
    Write("Connection: Close\r\n");
754
755
0
    Write("\r\n");
756
0
    m_bSentHeader = true;
757
758
0
    return true;
759
0
}
760
761
0
void CHTTPSock::SetContentType(const CString& sContentType) {
762
0
    m_sContentType = sContentType;
763
0
}
764
765
0
void CHTTPSock::AddHeader(const CString& sName, const CString& sValue) {
766
0
    m_msHeaders[sName] = sValue;
767
0
}
768
769
0
bool CHTTPSock::Redirect(const CString& sURL) {
770
0
    if (SentHeader()) {
771
0
        DEBUG("Redirect() - Header was already sent");
772
0
        return false;
773
0
    } else if (!sURL.StartsWith("/")) {
774
        // HTTP/1.1 only admits absolute URIs for the Location header.
775
0
        DEBUG("Redirect to relative URI [" + sURL + "] is not allowed.");
776
0
        return false;
777
0
    } else {
778
0
        CString location = m_sURIPrefix + sURL;
779
780
0
        DEBUG("- Redirect to [" << location
781
0
                                << "] with prefix [" + m_sURIPrefix + "]");
782
0
        AddHeader("Location", location);
783
0
        PrintErrorPage(302, "Found", "The document has moved <a href=\"" +
784
0
                                         location.Escape_n(CString::EHTML) +
785
0
                                         "\">here</a>.");
786
787
0
        return true;
788
0
    }
789
0
}
790
791
0
void CHTTPSock::Connected() { SetTimeout(120); }