mirror of
https://github.com/znc/znc.git
synced 2026-03-28 17:42:41 +01:00
792 lines
24 KiB
C++
792 lines
24 KiB
C++
/*
|
|
* Copyright (C) 2004-2017 ZNC, see the NOTICE file for details.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <znc/HTTPSock.h>
|
|
#include <znc/FileUtils.h>
|
|
#include <znc/znc.h>
|
|
#include <iomanip>
|
|
|
|
#ifdef HAVE_ZLIB
|
|
#include <zlib.h>
|
|
#endif
|
|
|
|
using std::map;
|
|
using std::set;
|
|
|
|
#define MAX_POST_SIZE 1024 * 1024
|
|
|
|
CHTTPSock::CHTTPSock(CModule* pMod, const CString& sURIPrefix)
|
|
: CHTTPSock(pMod, sURIPrefix, "", 0) {
|
|
Init();
|
|
}
|
|
|
|
CHTTPSock::CHTTPSock(CModule* pMod, const CString& sURIPrefix,
|
|
const CString& sHostname, unsigned short uPort,
|
|
int iTimeout)
|
|
: CSocket(pMod, sHostname, uPort, iTimeout),
|
|
m_bSentHeader(false),
|
|
m_bGotHeader(false),
|
|
m_bLoggedIn(false),
|
|
m_bPost(false),
|
|
m_bDone(false),
|
|
m_bBasicAuth(false),
|
|
m_uPostLen(0),
|
|
m_sPostData(""),
|
|
m_sURI(""),
|
|
m_sUser(""),
|
|
m_sPass(""),
|
|
m_sContentType(""),
|
|
m_sDocRoot(""),
|
|
m_sForwardedIP(""),
|
|
m_msvsPOSTParams(),
|
|
m_msvsGETParams(),
|
|
m_msHeaders(),
|
|
m_bHTTP10Client(false),
|
|
m_sIfNoneMatch(""),
|
|
m_bAcceptGzip(false),
|
|
m_msRequestCookies(),
|
|
m_msResponseCookies(),
|
|
m_sURIPrefix(sURIPrefix) {
|
|
Init();
|
|
}
|
|
|
|
void CHTTPSock::Init() {
|
|
EnableReadLine();
|
|
SetMaxBufferThreshold(10240);
|
|
}
|
|
|
|
CHTTPSock::~CHTTPSock() {}
|
|
|
|
void CHTTPSock::ReadData(const char* data, size_t len) {
|
|
if (!m_bDone && m_bGotHeader && m_bPost) {
|
|
m_sPostData.append(data, len);
|
|
CheckPost();
|
|
}
|
|
}
|
|
|
|
bool CHTTPSock::SendCookie(const CString& sKey, const CString& sValue) {
|
|
if (!sKey.empty() && !sValue.empty()) {
|
|
if (m_msRequestCookies.find(sKey) == m_msRequestCookies.end() ||
|
|
m_msRequestCookies[sKey].StrCmp(sValue) != 0) {
|
|
// only queue a Set-Cookie to be sent if the client didn't send a
|
|
// Cookie header of the same name+value.
|
|
m_msResponseCookies[sKey] = sValue;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
CString CHTTPSock::GetRequestCookie(const CString& sKey) const {
|
|
MCString::const_iterator it = m_msRequestCookies.find(sKey);
|
|
|
|
return it != m_msRequestCookies.end() ? it->second : "";
|
|
}
|
|
|
|
void CHTTPSock::CheckPost() {
|
|
if (m_sPostData.size() >= m_uPostLen) {
|
|
ParseParams(m_sPostData.Left(m_uPostLen), m_msvsPOSTParams);
|
|
GetPage();
|
|
m_sPostData.clear();
|
|
m_bDone = true;
|
|
}
|
|
}
|
|
|
|
void CHTTPSock::ReadLine(const CString& sData) {
|
|
if (m_bGotHeader) {
|
|
return;
|
|
}
|
|
|
|
CString sLine = sData;
|
|
sLine.TrimRight("\r\n");
|
|
|
|
CString sName = sLine.Token(0);
|
|
|
|
if (sName.Equals("GET")) {
|
|
m_bPost = false;
|
|
m_sURI = sLine.Token(1);
|
|
m_bHTTP10Client = sLine.Token(2).Equals("HTTP/1.0");
|
|
ParseURI();
|
|
} else if (sName.Equals("POST")) {
|
|
m_bPost = true;
|
|
m_sURI = sLine.Token(1);
|
|
ParseURI();
|
|
} else if (sName.Equals("Cookie:")) {
|
|
VCString vsNV;
|
|
|
|
sLine.Token(1, true).Split(";", vsNV, false, "", "", true, true);
|
|
|
|
for (const CString& s : vsNV) {
|
|
m_msRequestCookies[s.Token(0, false, "=")
|
|
.Escape_n(CString::EURL, CString::EASCII)] =
|
|
s.Token(1, true, "=").Escape_n(CString::EURL, CString::EASCII);
|
|
}
|
|
} else if (sName.Equals("Authorization:")) {
|
|
CString sUnhashed;
|
|
sLine.Token(2).Base64Decode(sUnhashed);
|
|
m_sUser = sUnhashed.Token(0, false, ":");
|
|
m_sPass = sUnhashed.Token(1, true, ":");
|
|
m_bBasicAuth = true;
|
|
// Postpone authorization attempt until end of headers, because cookies
|
|
// should be read before that, otherwise session id will be overwritten
|
|
// in GetSession()
|
|
} else if (sName.Equals("Content-Length:")) {
|
|
m_uPostLen = sLine.Token(1).ToULong();
|
|
if (m_uPostLen > MAX_POST_SIZE)
|
|
PrintErrorPage(413, "Request Entity Too Large",
|
|
"The request you sent was too large.");
|
|
} else if (sName.Equals("X-Forwarded-For:")) {
|
|
// X-Forwarded-For: client, proxy1, proxy2
|
|
if (m_sForwardedIP.empty()) {
|
|
const VCString& vsTrustedProxies = CZNC::Get().GetTrustedProxies();
|
|
CString sIP = GetRemoteIP();
|
|
|
|
VCString vsIPs;
|
|
sLine.Token(1, true).Split(",", vsIPs, false, "", "", false, true);
|
|
|
|
while (!vsIPs.empty()) {
|
|
// sIP told us that it got connection from vsIPs.back()
|
|
// check if sIP is trusted proxy
|
|
bool bTrusted = false;
|
|
for (const CString& sTrustedProxy : vsTrustedProxies) {
|
|
if (CUtils::CheckCIDR(sIP, sTrustedProxy)) {
|
|
bTrusted = true;
|
|
break;
|
|
}
|
|
}
|
|
if (bTrusted) {
|
|
// sIP is trusted proxy, so use vsIPs.back() as new sIP
|
|
sIP = vsIPs.back();
|
|
vsIPs.pop_back();
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// either sIP is not trusted proxy, or it's in the beginning of the
|
|
// X-Forwarded-For list in both cases use it as the endpoind
|
|
m_sForwardedIP = sIP;
|
|
}
|
|
} else if (sName.Equals("If-None-Match:")) {
|
|
// this is for proper client cache support (HTTP 304) on static files:
|
|
m_sIfNoneMatch = sLine.Token(1, true);
|
|
} else if (sName.Equals("Accept-Encoding:") && !m_bHTTP10Client) {
|
|
SCString ssEncodings;
|
|
// trimming whitespace from the tokens is important:
|
|
sLine.Token(1, true)
|
|
.Split(",", ssEncodings, false, "", "", false, true);
|
|
m_bAcceptGzip = (ssEncodings.find("gzip") != ssEncodings.end());
|
|
} else if (sLine.empty()) {
|
|
if (m_bBasicAuth && !m_bLoggedIn) {
|
|
m_bLoggedIn = OnLogin(m_sUser, m_sPass, true);
|
|
// After successful login ReadLine("") will be called again to
|
|
// trigger "else" block Failed login sends error and closes socket,
|
|
// so no infinite loop here
|
|
} else {
|
|
m_bGotHeader = true;
|
|
|
|
if (m_bPost) {
|
|
m_sPostData = GetInternalReadBuffer();
|
|
CheckPost();
|
|
} else {
|
|
GetPage();
|
|
}
|
|
|
|
DisableReadLine();
|
|
}
|
|
}
|
|
}
|
|
|
|
CString CHTTPSock::GetRemoteIP() const {
|
|
if (!m_sForwardedIP.empty()) {
|
|
return m_sForwardedIP;
|
|
}
|
|
|
|
return CSocket::GetRemoteIP();
|
|
}
|
|
|
|
CString CHTTPSock::GetDate(time_t stamp) {
|
|
struct tm tm;
|
|
std::stringstream stream;
|
|
const char* wkday[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
|
|
const char* month[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
|
|
|
|
if (stamp == 0) time(&stamp);
|
|
gmtime_r(&stamp, &tm);
|
|
|
|
stream << wkday[tm.tm_wday] << ", ";
|
|
stream << std::setfill('0') << std::setw(2) << tm.tm_mday << " ";
|
|
stream << month[tm.tm_mon] << " ";
|
|
stream << std::setfill('0') << std::setw(4) << tm.tm_year + 1900 << " ";
|
|
stream << std::setfill('0') << std::setw(2) << tm.tm_hour << ":";
|
|
stream << std::setfill('0') << std::setw(2) << tm.tm_min << ":";
|
|
stream << std::setfill('0') << std::setw(2) << tm.tm_sec << " GMT";
|
|
|
|
return stream.str();
|
|
}
|
|
|
|
void CHTTPSock::GetPage() {
|
|
DEBUG("Page Request [" << m_sURI << "] ");
|
|
|
|
// Check that the requested path starts with the prefix. Strip it if so.
|
|
if (!m_sURI.TrimPrefix(m_sURIPrefix)) {
|
|
DEBUG("INVALID path => Does not start with prefix [" + m_sURIPrefix +
|
|
"]");
|
|
DEBUG("Expected prefix: " << m_sURIPrefix);
|
|
DEBUG("Requested path: " << m_sURI);
|
|
Redirect("/");
|
|
} else if (m_sURI.empty()) {
|
|
// This can happen if prefix was /foo, and the requested page is /foo
|
|
Redirect("/");
|
|
} else {
|
|
OnPageRequest(m_sURI);
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_ZLIB
|
|
static bool InitZlibStream(z_stream* zStrm, const char* buf) {
|
|
memset(zStrm, 0, sizeof(z_stream));
|
|
zStrm->next_in = (Bytef*)buf;
|
|
|
|
// "15" is the default value for good compression,
|
|
// the weird "+ 16" means "please generate a gzip header and trailer".
|
|
const int WINDOW_BITS = 15 + 16;
|
|
const int MEMLEVEL = 8;
|
|
|
|
return (deflateInit2(zStrm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, WINDOW_BITS,
|
|
MEMLEVEL, Z_DEFAULT_STRATEGY) == Z_OK);
|
|
}
|
|
#endif
|
|
|
|
void CHTTPSock::PrintPage(const CString& sPage) {
|
|
#ifdef HAVE_ZLIB
|
|
if (m_bAcceptGzip && !SentHeader()) {
|
|
char szBuf[4096];
|
|
z_stream zStrm;
|
|
int zStatus, zFlush = Z_NO_FLUSH;
|
|
|
|
if (InitZlibStream(&zStrm, sPage.c_str())) {
|
|
DEBUG("- Sending gzip-compressed.");
|
|
AddHeader("Content-Encoding", "gzip");
|
|
PrintHeader(0); // we do not know the compressed data's length
|
|
|
|
zStrm.avail_in = sPage.size();
|
|
do {
|
|
if (zStrm.avail_in == 0) {
|
|
zFlush = Z_FINISH;
|
|
}
|
|
|
|
zStrm.next_out = (Bytef*)szBuf;
|
|
zStrm.avail_out = sizeof(szBuf);
|
|
|
|
zStatus = deflate(&zStrm, zFlush);
|
|
|
|
if ((zStatus == Z_OK || zStatus == Z_STREAM_END) &&
|
|
zStrm.avail_out < sizeof(szBuf)) {
|
|
Write(szBuf, sizeof(szBuf) - zStrm.avail_out);
|
|
}
|
|
} while (zStatus == Z_OK);
|
|
|
|
Close(Csock::CLT_AFTERWRITE);
|
|
deflateEnd(&zStrm);
|
|
return;
|
|
}
|
|
|
|
} // else: fall through
|
|
#endif
|
|
if (!SentHeader()) {
|
|
PrintHeader(sPage.length());
|
|
} else {
|
|
DEBUG("PrintPage(): Header was already sent");
|
|
}
|
|
|
|
Write(sPage);
|
|
Close(Csock::CLT_AFTERWRITE);
|
|
}
|
|
|
|
bool CHTTPSock::PrintFile(const CString& sFileName, CString sContentType) {
|
|
CString sFilePath = sFileName;
|
|
|
|
if (!m_sDocRoot.empty()) {
|
|
sFilePath.TrimLeft("/");
|
|
|
|
sFilePath = CDir::CheckPathPrefix(m_sDocRoot, sFilePath, m_sDocRoot);
|
|
|
|
if (sFilePath.empty()) {
|
|
PrintErrorPage(403, "Forbidden",
|
|
"You don't have permission to access that file on "
|
|
"this server.");
|
|
DEBUG("THIS FILE: [" << sFilePath << "] does not live in ...");
|
|
DEBUG("DOCUMENT ROOT: [" << m_sDocRoot << "]");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
CFile File(sFilePath);
|
|
|
|
if (!File.Open()) {
|
|
PrintNotFound();
|
|
return false;
|
|
}
|
|
|
|
if (sContentType.empty()) {
|
|
if (sFileName.EndsWith(".html") || sFileName.EndsWith(".htm")) {
|
|
sContentType = "text/html; charset=utf-8";
|
|
} else if (sFileName.EndsWith(".css")) {
|
|
sContentType = "text/css; charset=utf-8";
|
|
} else if (sFileName.EndsWith(".js")) {
|
|
sContentType = "application/x-javascript; charset=utf-8";
|
|
} else if (sFileName.EndsWith(".jpg")) {
|
|
sContentType = "image/jpeg";
|
|
} else if (sFileName.EndsWith(".gif")) {
|
|
sContentType = "image/gif";
|
|
} else if (sFileName.EndsWith(".ico")) {
|
|
sContentType = "image/x-icon";
|
|
} else if (sFileName.EndsWith(".png")) {
|
|
sContentType = "image/png";
|
|
} else if (sFileName.EndsWith(".bmp")) {
|
|
sContentType = "image/bmp";
|
|
} else {
|
|
sContentType = "text/plain; charset=utf-8";
|
|
}
|
|
}
|
|
|
|
const time_t iMTime = File.GetMTime();
|
|
bool bNotModified = false;
|
|
CString sETag;
|
|
|
|
if (iMTime > 0 && !m_bHTTP10Client) {
|
|
sETag = "-" + CString(iMTime); // lighttpd style ETag
|
|
|
|
AddHeader("Last-Modified", GetDate(iMTime));
|
|
AddHeader("ETag", "\"" + sETag + "\"");
|
|
AddHeader("Cache-Control", "public");
|
|
|
|
if (!m_sIfNoneMatch.empty()) {
|
|
m_sIfNoneMatch.Trim("\\\"'");
|
|
bNotModified =
|
|
(m_sIfNoneMatch.Equals(sETag, CString::CaseSensitive));
|
|
}
|
|
}
|
|
|
|
if (bNotModified) {
|
|
PrintHeader(0, sContentType, 304, "Not Modified");
|
|
} else {
|
|
off_t iSize = File.GetSize();
|
|
|
|
// Don't try to send files over 16 MiB, because it might block
|
|
// the whole process and use huge amounts of memory.
|
|
if (iSize > 16 * 1024 * 1024) {
|
|
DEBUG("- Abort: File is over 16 MiB big: " << iSize);
|
|
PrintErrorPage(500, "Internal Server Error", "File too big");
|
|
return true;
|
|
}
|
|
|
|
#ifdef HAVE_ZLIB
|
|
bool bGzip = m_bAcceptGzip && (sContentType.StartsWith("text/") ||
|
|
sFileName.EndsWith(".js"));
|
|
|
|
if (bGzip) {
|
|
DEBUG("- Sending gzip-compressed.");
|
|
AddHeader("Content-Encoding", "gzip");
|
|
PrintHeader(
|
|
0,
|
|
sContentType); // we do not know the compressed data's length
|
|
WriteFileGzipped(File);
|
|
} else
|
|
#endif
|
|
{
|
|
PrintHeader(iSize, sContentType);
|
|
WriteFileUncompressed(File);
|
|
}
|
|
}
|
|
|
|
DEBUG("- ETag: [" << sETag << "] / If-None-Match [" << m_sIfNoneMatch
|
|
<< "]");
|
|
|
|
Close(Csock::CLT_AFTERWRITE);
|
|
|
|
return true;
|
|
}
|
|
|
|
void CHTTPSock::WriteFileUncompressed(CFile& File) {
|
|
char szBuf[4096];
|
|
off_t iLen = 0;
|
|
ssize_t i = 0;
|
|
off_t iSize = File.GetSize();
|
|
|
|
// while we haven't reached iSize and read() succeeds...
|
|
while (iLen < iSize && (i = File.Read(szBuf, sizeof(szBuf))) > 0) {
|
|
Write(szBuf, i);
|
|
iLen += i;
|
|
}
|
|
|
|
if (i < 0) {
|
|
DEBUG("- Error while reading file: " << strerror(errno));
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_ZLIB
|
|
void CHTTPSock::WriteFileGzipped(CFile& File) {
|
|
char szBufIn[8192];
|
|
char szBufOut[8192];
|
|
off_t iFileSize = File.GetSize(), iFileReadTotal = 0;
|
|
z_stream zStrm;
|
|
int zFlush = Z_NO_FLUSH;
|
|
int zStatus;
|
|
|
|
if (!InitZlibStream(&zStrm, szBufIn)) {
|
|
DEBUG("- Error initializing zlib!");
|
|
return;
|
|
}
|
|
|
|
do {
|
|
ssize_t iFileRead = 0;
|
|
|
|
if (zStrm.avail_in == 0) {
|
|
// input buffer is empty, try to read more data from file.
|
|
// if there is no more data, finish the stream.
|
|
|
|
if (iFileReadTotal < iFileSize) {
|
|
iFileRead = File.Read(szBufIn, sizeof(szBufIn));
|
|
|
|
if (iFileRead < 1) {
|
|
// wtf happened? better quit compressing.
|
|
iFileReadTotal = iFileSize;
|
|
zFlush = Z_FINISH;
|
|
} else {
|
|
iFileReadTotal += iFileRead;
|
|
|
|
zStrm.next_in = (Bytef*)szBufIn;
|
|
zStrm.avail_in = iFileRead;
|
|
}
|
|
} else {
|
|
zFlush = Z_FINISH;
|
|
}
|
|
}
|
|
|
|
zStrm.next_out = (Bytef*)szBufOut;
|
|
zStrm.avail_out = sizeof(szBufOut);
|
|
|
|
zStatus = deflate(&zStrm, zFlush);
|
|
|
|
if ((zStatus == Z_OK || zStatus == Z_STREAM_END) &&
|
|
zStrm.avail_out < sizeof(szBufOut)) {
|
|
// there's data in the buffer:
|
|
Write(szBufOut, sizeof(szBufOut) - zStrm.avail_out);
|
|
}
|
|
|
|
} while (zStatus == Z_OK);
|
|
|
|
deflateEnd(&zStrm);
|
|
}
|
|
#endif
|
|
|
|
void CHTTPSock::ParseURI() {
|
|
ParseParams(m_sURI.Token(1, true, "?"), m_msvsGETParams);
|
|
m_sURI = m_sURI.Token(0, false, "?");
|
|
}
|
|
|
|
CString CHTTPSock::GetPath() const { return m_sURI.Token(0, false, "?"); }
|
|
|
|
void CHTTPSock::ParseParams(const CString& sParams,
|
|
map<CString, VCString>& msvsParams) {
|
|
msvsParams.clear();
|
|
|
|
VCString vsPairs;
|
|
sParams.Split("&", vsPairs, true);
|
|
|
|
for (const CString& sPair : vsPairs) {
|
|
CString sName =
|
|
sPair.Token(0, false, "=").Escape_n(CString::EURL, CString::EASCII);
|
|
CString sValue =
|
|
sPair.Token(1, true, "=").Escape_n(CString::EURL, CString::EASCII);
|
|
|
|
msvsParams[sName].push_back(sValue);
|
|
}
|
|
}
|
|
|
|
void CHTTPSock::SetDocRoot(const CString& s) {
|
|
m_sDocRoot = s + "/";
|
|
m_sDocRoot.Replace("//", "/");
|
|
}
|
|
|
|
const CString& CHTTPSock::GetDocRoot() const { return m_sDocRoot; }
|
|
|
|
const CString& CHTTPSock::GetUser() const { return m_sUser; }
|
|
|
|
const CString& CHTTPSock::GetPass() const { return m_sPass; }
|
|
|
|
const CString& CHTTPSock::GetContentType() const { return m_sContentType; }
|
|
|
|
const CString& CHTTPSock::GetParamString() const { return m_sPostData; }
|
|
|
|
const CString& CHTTPSock::GetURI() const { return m_sURI; }
|
|
|
|
const CString& CHTTPSock::GetURIPrefix() const { return m_sURIPrefix; }
|
|
|
|
bool CHTTPSock::HasParam(const CString& sName, bool bPost) const {
|
|
if (bPost) return (m_msvsPOSTParams.find(sName) != m_msvsPOSTParams.end());
|
|
return (m_msvsGETParams.find(sName) != m_msvsGETParams.end());
|
|
}
|
|
|
|
CString CHTTPSock::GetRawParam(const CString& sName, bool bPost) const {
|
|
if (bPost) return GetRawParam(sName, m_msvsPOSTParams);
|
|
return GetRawParam(sName, m_msvsGETParams);
|
|
}
|
|
|
|
CString CHTTPSock::GetRawParam(const CString& sName,
|
|
const map<CString, VCString>& msvsParams) {
|
|
CString sRet;
|
|
|
|
map<CString, VCString>::const_iterator it = msvsParams.find(sName);
|
|
|
|
if (it != msvsParams.end() && it->second.size() > 0) {
|
|
sRet = it->second[0];
|
|
}
|
|
|
|
return sRet;
|
|
}
|
|
|
|
CString CHTTPSock::GetParam(const CString& sName, bool bPost,
|
|
const CString& sFilter) const {
|
|
if (bPost) return GetParam(sName, m_msvsPOSTParams, sFilter);
|
|
return GetParam(sName, m_msvsGETParams, sFilter);
|
|
}
|
|
|
|
CString CHTTPSock::GetParam(const CString& sName,
|
|
const map<CString, VCString>& msvsParams,
|
|
const CString& sFilter) {
|
|
CString sRet = GetRawParam(sName, msvsParams);
|
|
sRet.Trim();
|
|
|
|
for (size_t i = 0; i < sFilter.length(); i++) {
|
|
sRet.Replace(CString(sFilter.at(i)), "");
|
|
}
|
|
|
|
return sRet;
|
|
}
|
|
|
|
size_t CHTTPSock::GetParamValues(const CString& sName, set<CString>& ssRet,
|
|
bool bPost, const CString& sFilter) const {
|
|
if (bPost) return GetParamValues(sName, ssRet, m_msvsPOSTParams, sFilter);
|
|
return GetParamValues(sName, ssRet, m_msvsGETParams, sFilter);
|
|
}
|
|
|
|
size_t CHTTPSock::GetParamValues(const CString& sName, set<CString>& ssRet,
|
|
const map<CString, VCString>& msvsParams,
|
|
const CString& sFilter) {
|
|
ssRet.clear();
|
|
|
|
map<CString, VCString>::const_iterator it = msvsParams.find(sName);
|
|
|
|
if (it != msvsParams.end()) {
|
|
for (CString sParam : it->second) {
|
|
sParam.Trim();
|
|
|
|
for (size_t i = 0; i < sFilter.length(); i++) {
|
|
sParam.Replace(CString(sFilter.at(i)), "");
|
|
}
|
|
ssRet.insert(sParam);
|
|
}
|
|
}
|
|
|
|
return ssRet.size();
|
|
}
|
|
|
|
size_t CHTTPSock::GetParamValues(const CString& sName, VCString& vsRet,
|
|
bool bPost, const CString& sFilter) const {
|
|
if (bPost) return GetParamValues(sName, vsRet, m_msvsPOSTParams, sFilter);
|
|
return GetParamValues(sName, vsRet, m_msvsGETParams, sFilter);
|
|
}
|
|
|
|
size_t CHTTPSock::GetParamValues(const CString& sName, VCString& vsRet,
|
|
const map<CString, VCString>& msvsParams,
|
|
const CString& sFilter) {
|
|
vsRet.clear();
|
|
|
|
map<CString, VCString>::const_iterator it = msvsParams.find(sName);
|
|
|
|
if (it != msvsParams.end()) {
|
|
for (CString sParam : it->second) {
|
|
sParam.Trim();
|
|
|
|
for (size_t i = 0; i < sFilter.length(); i++) {
|
|
sParam.Replace(CString(sFilter.at(i)), "");
|
|
}
|
|
vsRet.push_back(sParam);
|
|
}
|
|
}
|
|
|
|
return vsRet.size();
|
|
}
|
|
|
|
const map<CString, VCString>& CHTTPSock::GetParams(bool bPost) const {
|
|
if (bPost) return m_msvsPOSTParams;
|
|
return m_msvsGETParams;
|
|
}
|
|
|
|
bool CHTTPSock::IsPost() const { return m_bPost; }
|
|
|
|
bool CHTTPSock::PrintNotFound() {
|
|
return PrintErrorPage(404, "Not Found",
|
|
"The requested URL was not found on this server.");
|
|
}
|
|
|
|
bool CHTTPSock::PrintErrorPage(unsigned int uStatusId,
|
|
const CString& sStatusMsg,
|
|
const CString& sMessage) {
|
|
if (SentHeader()) {
|
|
DEBUG("PrintErrorPage(): Header was already sent");
|
|
return false;
|
|
}
|
|
|
|
CString sPage =
|
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
|
|
"<!DOCTYPE html>\r\n"
|
|
"<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\" "
|
|
"xml:lang=\"en\">\r\n"
|
|
"<head>\r\n"
|
|
"<meta charset=\"UTF-8\"/>\r\n"
|
|
"<title>" +
|
|
CString(uStatusId) + " " + sStatusMsg.Escape_n(CString::EHTML) +
|
|
"</title>\r\n"
|
|
"</head>\r\n"
|
|
"<body>\r\n"
|
|
"<h1>" +
|
|
sStatusMsg.Escape_n(CString::EHTML) +
|
|
"</h1>\r\n"
|
|
"<p>" +
|
|
sMessage.Escape_n(CString::EHTML) +
|
|
"</p>\r\n"
|
|
"<hr/>\r\n"
|
|
"<p>" +
|
|
CZNC::GetTag(false, /* bHTML = */ true) +
|
|
"</p>\r\n"
|
|
"</body>\r\n"
|
|
"</html>\r\n";
|
|
|
|
PrintHeader(sPage.length(), "text/html; charset=utf-8", uStatusId,
|
|
sStatusMsg);
|
|
Write(sPage);
|
|
Close(Csock::CLT_AFTERWRITE);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CHTTPSock::ForceLogin() {
|
|
if (m_bLoggedIn) {
|
|
return true;
|
|
}
|
|
|
|
if (SentHeader()) {
|
|
DEBUG("ForceLogin(): Header was already sent!");
|
|
return false;
|
|
}
|
|
|
|
AddHeader("WWW-Authenticate",
|
|
"Basic realm=\"" + CZNC::GetTag(false) + "\"");
|
|
PrintErrorPage(401, "Unauthorized", "You need to login to view this page.");
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CHTTPSock::OnLogin(const CString& sUser, const CString& sPass,
|
|
bool bBasic) {
|
|
return false;
|
|
}
|
|
|
|
bool CHTTPSock::SentHeader() const { return m_bSentHeader; }
|
|
|
|
bool CHTTPSock::PrintHeader(off_t uContentLength, const CString& sContentType,
|
|
unsigned int uStatusId, const CString& sStatusMsg) {
|
|
if (SentHeader()) {
|
|
DEBUG("PrintHeader(): Header was already sent!");
|
|
return false;
|
|
}
|
|
|
|
if (!sContentType.empty()) {
|
|
m_sContentType = sContentType;
|
|
}
|
|
|
|
if (m_sContentType.empty()) {
|
|
m_sContentType = "text/html; charset=utf-8";
|
|
}
|
|
|
|
DEBUG("- " << uStatusId << " (" << sStatusMsg << ") [" << m_sContentType
|
|
<< "]");
|
|
|
|
Write("HTTP/" + CString(m_bHTTP10Client ? "1.0 " : "1.1 ") +
|
|
CString(uStatusId) + " " + sStatusMsg + "\r\n");
|
|
Write("Date: " + GetDate() + "\r\n");
|
|
Write("Server: " + CZNC::GetTag(false) + "\r\n");
|
|
if (uContentLength > 0) {
|
|
Write("Content-Length: " + CString(uContentLength) + "\r\n");
|
|
}
|
|
Write("Content-Type: " + m_sContentType + "\r\n");
|
|
|
|
for (const auto& it : m_msResponseCookies) {
|
|
Write("Set-Cookie: " + it.first.Escape_n(CString::EURL) + "=" +
|
|
it.second.Escape_n(CString::EURL) + "; HttpOnly; path=/;" +
|
|
(GetSSL() ? "Secure;" : "") + " SameSite=Strict;\r\n");
|
|
}
|
|
|
|
for (const auto& it : m_msHeaders) {
|
|
Write(it.first + ": " + it.second + "\r\n");
|
|
}
|
|
|
|
Write("Connection: Close\r\n");
|
|
|
|
Write("\r\n");
|
|
m_bSentHeader = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
void CHTTPSock::SetContentType(const CString& sContentType) {
|
|
m_sContentType = sContentType;
|
|
}
|
|
|
|
void CHTTPSock::AddHeader(const CString& sName, const CString& sValue) {
|
|
m_msHeaders[sName] = sValue;
|
|
}
|
|
|
|
bool CHTTPSock::Redirect(const CString& sURL) {
|
|
if (SentHeader()) {
|
|
DEBUG("Redirect() - Header was already sent");
|
|
return false;
|
|
} else if (!sURL.StartsWith("/")) {
|
|
// HTTP/1.1 only admits absolute URIs for the Location header.
|
|
DEBUG("Redirect to relative URI [" + sURL + "] is not allowed.");
|
|
return false;
|
|
} else {
|
|
CString location = m_sURIPrefix + sURL;
|
|
|
|
DEBUG("- Redirect to [" << location
|
|
<< "] with prefix [" + m_sURIPrefix + "]");
|
|
AddHeader("Location", location);
|
|
PrintErrorPage(302, "Found", "The document has moved <a href=\"" +
|
|
location.Escape_n(CString::EHTML) +
|
|
"\">here</a>.");
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void CHTTPSock::Connected() { SetTimeout(120); }
|