mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-10-02 15:35:09 -04:00
With ZM_AUTH_HASH_IPS on, the client address bound into the auth hash was taken from the left-most X-Forwarded-For value whenever the header was present, both when PHP generated the hash (getRemoteAddr()) and when PHP or zms validated it (getAuthUser(), zmLoadAuthUser()). The header is client controlled, so anyone holding a leaked hash could replay it from anywhere by sending the address it was bound to. Add ZM_AUTH_TRUSTED_PROXIES, a list of exact reverse proxy addresses. X-Forwarded-For is now used only when REMOTE_ADDR is one of them, and is read from the right, skipping hops that are themselves listed proxies, so values a client prepends are never chosen. With the option empty, the default, the header is ignored and REMOTE_ADDR is used. PHP (web/includes/Network.php getRemoteAddr()) and C++ (ClientAddress() in zm_utils, used by zmLoadAuthUser()) implement the same rule so generation and validation continue to agree. Every PHP caller already routes through getRemoteAddr(), so session.php and auth.php need no change. Reverse proxy users who enable ZM_AUTH_HASH_IPS must list their proxy in the new option; until they do, hashes bind to the proxy address, which still validates but no longer distinguishes clients. This is the behaviour change that the #4921 work avoided by trusting the header. The option is added through ConfigData only, like other recent options; zmupdate.pl --freshen inserts it, zms falls back to the compiled-in default and PHP treats an undefined constant as empty, so no schema migration is needed. Tests: ClientAddress Catch2 case; tests/php/test_remote_addr.php updated for the trusted-proxy rule. refs GHSA-72rf-54rm-798c Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit acea5889dec0826f595cb736147a5fdc9c94a2a9)
614 lines
19 KiB
C++
614 lines
19 KiB
C++
//
|
|
// ZoneMinder General Utility Functions, $Date$, $Revision$
|
|
// Copyright (C) 2001-2008 Philip Coombes
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License
|
|
// as published by the Free Software Foundation; either version 2
|
|
// of the License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
//
|
|
|
|
#include "zm_utils.h"
|
|
|
|
#include "zm_config.h"
|
|
#include "zm_logger.h"
|
|
#include <array>
|
|
#include <cctype>
|
|
#include <cstdarg>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <fcntl.h> /* Definition of AT_* constants */
|
|
#include <unistd.h>
|
|
#include <regex>
|
|
#include <sstream>
|
|
#include <sys/stat.h>
|
|
|
|
#if defined(__arm__)
|
|
#include <sys/auxv.h>
|
|
#endif
|
|
|
|
unsigned int sse_version = 0;
|
|
unsigned int neonversion = 0;
|
|
|
|
// Trim Both leading and trailing sets
|
|
std::string Trim(const std::string &str, const std::string &char_set) {
|
|
size_t start_pos = str.find_first_not_of(char_set);
|
|
size_t end_pos = str.find_last_not_of(char_set);
|
|
|
|
// if all spaces or empty return an empty string
|
|
if ((start_pos == std::string::npos) || (end_pos == std::string::npos))
|
|
return "";
|
|
return str.substr(start_pos, end_pos - start_pos + 1);
|
|
}
|
|
|
|
std::string ReplaceAll(std::string str, const std::string &old_value, const std::string &new_value) {
|
|
if (old_value.empty())
|
|
return str;
|
|
size_t start_pos = 0;
|
|
while ((start_pos = str.find(old_value, start_pos)) != std::string::npos) {
|
|
str.replace(start_pos, old_value.length(), new_value);
|
|
start_pos += new_value.length(); // In case 'new_value' contains 'old_value', like replacing 'x' with 'yx'
|
|
}
|
|
return str;
|
|
}
|
|
|
|
StringVector Split(const std::string &str, char delim) {
|
|
std::vector<std::string> tokens;
|
|
|
|
size_t start = 0;
|
|
for (size_t end = str.find(delim); end != std::string::npos; end = str.find(delim, start)) {
|
|
tokens.push_back(str.substr(start, end - start));
|
|
start = end + 1;
|
|
}
|
|
|
|
tokens.push_back(str.substr(start));
|
|
|
|
return tokens;
|
|
}
|
|
|
|
StringVector Split(const std::string &str, const std::string &delim, size_t limit) {
|
|
StringVector tokens;
|
|
size_t start = 0;
|
|
|
|
do {
|
|
size_t end = str.find_first_of(delim, start);
|
|
if (end > 0) {
|
|
tokens.push_back(str.substr(start, end - start));
|
|
}
|
|
if (end == std::string::npos) {
|
|
break;
|
|
}
|
|
// Find non-delimiters
|
|
start = str.find_first_not_of(delim, end);
|
|
if (limit && (tokens.size() == limit - 1)) {
|
|
tokens.push_back(str.substr(start));
|
|
break;
|
|
}
|
|
} while (start != std::string::npos);
|
|
|
|
return tokens;
|
|
}
|
|
|
|
std::pair<std::string, std::string> PairSplit(const std::string &str, char delim) {
|
|
if (str.empty())
|
|
return std::make_pair("", "");
|
|
|
|
size_t pos = str.find(delim);
|
|
|
|
if (pos == std::string::npos)
|
|
return std::make_pair("", "");
|
|
|
|
return std::make_pair(str.substr(0, pos), str.substr(pos + 1, std::string::npos));
|
|
}
|
|
|
|
std::string Join(const StringVector &values, const std::string &delim) {
|
|
std::stringstream ss;
|
|
|
|
for (size_t i = 0; i < values.size(); ++i) {
|
|
if (i != 0)
|
|
ss << delim;
|
|
ss << values[i];
|
|
}
|
|
return ss.str();
|
|
}
|
|
|
|
std::string ClientAddress(const std::string &remote_addr,
|
|
const std::string &forwarded_for,
|
|
const std::string &trusted_proxies) {
|
|
const StringVector trusted = Split(trusted_proxies, " \t\r\n,");
|
|
auto is_trusted = [&trusted](const std::string &addr) {
|
|
return !addr.empty() && std::find(trusted.begin(), trusted.end(), addr) != trusted.end();
|
|
};
|
|
if (forwarded_for.empty() || !is_trusted(remote_addr))
|
|
return remote_addr;
|
|
|
|
StringVector hops;
|
|
for (const std::string &hop : Split(forwarded_for, ',')) {
|
|
std::string trimmed = TrimSpaces(hop);
|
|
if (!trimmed.empty()) hops.push_back(trimmed);
|
|
}
|
|
for (auto it = hops.rbegin(); it != hops.rend(); ++it) {
|
|
if (!is_trusted(*it)) return *it;
|
|
}
|
|
// Every hop is a trusted proxy; the left-most is the origin.
|
|
return hops.empty() ? remote_addr : hops.front();
|
|
}
|
|
|
|
std::string stringtf(const char* format, ...) {
|
|
va_list args;
|
|
va_start(args, format);
|
|
va_list args2;
|
|
va_copy(args2, args);
|
|
int size = vsnprintf(nullptr, 0, format, args);
|
|
va_end(args);
|
|
|
|
if (size < 0) {
|
|
va_end(args2);
|
|
throw std::runtime_error("Error during formatting.");
|
|
}
|
|
size += 1; // Extra space for '\0'
|
|
|
|
std::unique_ptr<char[]> buf(new char[size]);
|
|
vsnprintf(buf.get(), size, format, args2);
|
|
va_end(args2);
|
|
|
|
return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside
|
|
}
|
|
|
|
std::string ByteArrayToHexString(nonstd::span<const uint8> bytes) {
|
|
static constexpr char lowercase_table[] = "0123456789abcdef";
|
|
std::string buf;
|
|
buf.resize(2 * bytes.size());
|
|
|
|
const uint8 *srcPtr = bytes.data();
|
|
char *dstPtr = &buf[0];
|
|
|
|
for (size_t i = 0; i < bytes.size(); ++i) {
|
|
uint8 c = *srcPtr++;
|
|
*dstPtr++ = lowercase_table[c >> 4];
|
|
*dstPtr++ = lowercase_table[c & 0x0f];
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
std::string Base64Encode(const std::string &str) {
|
|
static char base64_table[64] = {'\0'};
|
|
|
|
if (!base64_table[0]) {
|
|
int i = 0;
|
|
for (char c = 'A'; c <= 'Z'; c++)
|
|
base64_table[i++] = c;
|
|
for (char c = 'a'; c <= 'z'; c++)
|
|
base64_table[i++] = c;
|
|
for (char c = '0'; c <= '9'; c++)
|
|
base64_table[i++] = c;
|
|
base64_table[i++] = '+';
|
|
base64_table[i++] = '/';
|
|
}
|
|
|
|
std::string outString;
|
|
outString.reserve(2 * str.size());
|
|
|
|
const char *inPtr = str.c_str();
|
|
while (*inPtr) {
|
|
unsigned char selection = *inPtr >> 2;
|
|
unsigned char remainder = (*inPtr++ & 0x03) << 4;
|
|
outString += base64_table[selection];
|
|
|
|
if (*inPtr) {
|
|
selection = remainder | (*inPtr >> 4);
|
|
remainder = (*inPtr++ & 0x0f) << 2;
|
|
outString += base64_table[selection];
|
|
|
|
if (*inPtr) {
|
|
selection = remainder | (*inPtr >> 6);
|
|
outString += base64_table[selection];
|
|
selection = (*inPtr++ & 0x3f);
|
|
outString += base64_table[selection];
|
|
} else {
|
|
outString += base64_table[remainder];
|
|
outString += '=';
|
|
}
|
|
} else {
|
|
outString += base64_table[remainder];
|
|
outString += '=';
|
|
outString += '=';
|
|
}
|
|
}
|
|
return outString;
|
|
}
|
|
|
|
std::string TimevalToString(timeval tv) {
|
|
tm now = {};
|
|
std::array<char, 26> tm_buf = {};
|
|
|
|
localtime_r(&tv.tv_sec, &now);
|
|
size_t tm_buf_len = strftime(tm_buf.data(), tm_buf.size(), "%Y-%m-%d %H:%M:%S", &now);
|
|
if (tm_buf_len == 0) {
|
|
return "";
|
|
}
|
|
|
|
// %jd/intmax_t, not %ld: under glibc's 64 bit time ABI (Debian trixie on
|
|
// armhf, for one) tv_usec is __suseconds64_t while long is still 32 bits, so
|
|
// %ld reads half the argument and misaligns the rest of the varargs. Harmless
|
|
// enough on stdout, but this one lands in a buffer that gets used. refs #4580
|
|
return stringtf("%s.%06jd", tm_buf.data(), static_cast<intmax_t>(tv.tv_usec));
|
|
}
|
|
|
|
/* Detect special hardware features, such as SIMD instruction sets */
|
|
void HwCapsDetect() {
|
|
neonversion = 0;
|
|
sse_version = 0;
|
|
#if (defined(__i386__) || defined(__x86_64__))
|
|
__builtin_cpu_init();
|
|
|
|
if (__builtin_cpu_supports("avx2")) {
|
|
sse_version = 52; /* AVX2 */
|
|
Debug(1, "Detected a x86\\x86-64 processor with AVX2");
|
|
} else if (__builtin_cpu_supports("avx")) {
|
|
sse_version = 51; /* AVX */
|
|
Debug(1, "Detected a x86\\x86-64 processor with AVX");
|
|
} else if (__builtin_cpu_supports("sse4.2")) {
|
|
sse_version = 42; /* SSE4.2 */
|
|
Debug(1, "Detected a x86\\x86-64 processor with SSE4.2");
|
|
} else if (__builtin_cpu_supports("sse4.1")) {
|
|
sse_version = 41; /* SSE4.1 */
|
|
Debug(1, "Detected a x86\\x86-64 processor with SSE4.1");
|
|
} else if (__builtin_cpu_supports("ssse3")) {
|
|
sse_version = 35; /* SSSE3 */
|
|
Debug(1, "Detected a x86\\x86-64 processor with SSSE3");
|
|
} else if (__builtin_cpu_supports("sse3")) {
|
|
sse_version = 30; /* SSE3 */
|
|
Debug(1, "Detected a x86\\x86-64 processor with SSE3");
|
|
} else if (__builtin_cpu_supports("sse2")) {
|
|
sse_version = 20; /* SSE2 */
|
|
Debug(1, "Detected a x86\\x86-64 processor with SSE2");
|
|
} else if (__builtin_cpu_supports("sse")) {
|
|
sse_version = 10; /* SSE */
|
|
Debug(1, "Detected a x86\\x86-64 processor with SSE");
|
|
} else {
|
|
sse_version = 0;
|
|
Debug(1, "Detected a x86\\x86-64 processor");
|
|
}
|
|
#elif defined(__arm__)
|
|
// ARM processor in 32bit mode
|
|
// To see if it supports NEON, we need to get that information from the kernel
|
|
#ifdef __linux__
|
|
unsigned long auxval = getauxval(AT_HWCAP);
|
|
if (auxval & HWCAP_ARM_NEON) {
|
|
#elif defined(__FreeBSD__)
|
|
unsigned long auxval = 0;
|
|
elf_aux_info(AT_HWCAP, &auxval, sizeof(auxval));
|
|
if (auxval & HWCAP_NEON) {
|
|
#else
|
|
{
|
|
#error Unsupported OS.
|
|
#endif
|
|
Debug(1,"Detected ARM (AArch32) processor with Neon");
|
|
neonversion = 1;
|
|
} else {
|
|
Debug(1,"Detected ARM (AArch32) processor");
|
|
}
|
|
#elif defined(__aarch64__)
|
|
// ARM processor in 64bit mode
|
|
// Neon is mandatory, no need to check for it
|
|
neonversion = 1;
|
|
Debug(1,"Detected ARM (AArch64) processor with Neon");
|
|
#else
|
|
// Unknown processor
|
|
Debug(1,"Detected unknown processor architecture");
|
|
#endif
|
|
}
|
|
|
|
/* SSE2 aligned memory copy. Useful for big copying of aligned memory like image buffers in ZM */
|
|
/* For platforms without SSE2 we will use standard x86 asm memcpy or glibc's memcpy() */
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
__attribute__((noinline, __target__("sse2")))
|
|
#endif
|
|
void *sse2_aligned_memcpy(void *dest, const void *src, size_t bytes) {
|
|
#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE))
|
|
if (bytes > 128) {
|
|
unsigned int remainder = bytes % 128;
|
|
const uint8_t *lastsrc = (uint8_t *) src + (bytes - remainder);
|
|
|
|
__asm__ __volatile__(
|
|
"sse2_copy_iter:\n\t"
|
|
"movdqa (%0),%%xmm0\n\t"
|
|
"movdqa 0x10(%0),%%xmm1\n\t"
|
|
"movdqa 0x20(%0),%%xmm2\n\t"
|
|
"movdqa 0x30(%0),%%xmm3\n\t"
|
|
"movdqa 0x40(%0),%%xmm4\n\t"
|
|
"movdqa 0x50(%0),%%xmm5\n\t"
|
|
"movdqa 0x60(%0),%%xmm6\n\t"
|
|
"movdqa 0x70(%0),%%xmm7\n\t"
|
|
"movntdq %%xmm0,(%1)\n\t"
|
|
"movntdq %%xmm1,0x10(%1)\n\t"
|
|
"movntdq %%xmm2,0x20(%1)\n\t"
|
|
"movntdq %%xmm3,0x30(%1)\n\t"
|
|
"movntdq %%xmm4,0x40(%1)\n\t"
|
|
"movntdq %%xmm5,0x50(%1)\n\t"
|
|
"movntdq %%xmm6,0x60(%1)\n\t"
|
|
"movntdq %%xmm7,0x70(%1)\n\t"
|
|
"add $0x80, %0\n\t"
|
|
"add $0x80, %1\n\t"
|
|
"cmp %2, %0\n\t"
|
|
"jb sse2_copy_iter\n\t"
|
|
"test %3, %3\n\t"
|
|
"jz sse2_copy_finish\n\t"
|
|
"cld\n\t"
|
|
"rep movsb\n\t"
|
|
"sse2_copy_finish:\n\t"
|
|
:
|
|
: "S" (src), "D" (dest), "r" (lastsrc), "c" (remainder)
|
|
: "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "cc", "memory"
|
|
);
|
|
|
|
} else {
|
|
/* Standard memcpy */
|
|
__asm__ __volatile__("cld; rep movsb"::"S"(src), "D"(dest), "c"(bytes) : "cc", "memory");
|
|
}
|
|
#else
|
|
/* Non x86\x86-64 platform, use memcpy */
|
|
memcpy(dest,src,bytes);
|
|
#endif
|
|
return dest;
|
|
}
|
|
|
|
void touch(const char *pathname) {
|
|
int fd = open(pathname, O_WRONLY | O_CREAT | O_NOCTTY | O_NONBLOCK, 0666);
|
|
if (fd < 0) {
|
|
// Couldn't open that path.
|
|
Error("Couldn't open() path %s in touch", pathname);
|
|
return;
|
|
}
|
|
int rc = utimensat(AT_FDCWD, pathname, nullptr, 0);
|
|
if (rc) {
|
|
Error("Couldn't utimensat() path %s in touch", pathname);
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
size_t zm_get_rss_kb() {
|
|
// /proc/self/statm reports memory in pages: field 2 is resident set size.
|
|
FILE *fp = fopen("/proc/self/statm", "r");
|
|
if (!fp) return 0;
|
|
long rss_pages = 0;
|
|
// First field (total program size) is skipped, second is resident pages.
|
|
if (fscanf(fp, "%*s %ld", &rss_pages) != 1) {
|
|
fclose(fp);
|
|
return 0;
|
|
}
|
|
fclose(fp);
|
|
long page_kb = sysconf(_SC_PAGESIZE) / 1024;
|
|
if (page_kb <= 0) page_kb = 4; // sane default if sysconf fails
|
|
return static_cast<size_t>(rss_pages) * static_cast<size_t>(page_kb);
|
|
}
|
|
|
|
std::string UriDecode(const std::string &encoded) {
|
|
const char *src = encoded.c_str();
|
|
std::string retbuf;
|
|
retbuf.reserve(encoded.length());
|
|
while (*src) {
|
|
char a, b;
|
|
if ((*src == '%') && ((a = src[1]) && (b = src[2])) && (isxdigit(a) && isxdigit(b))) {
|
|
if (a >= 'a')
|
|
a -= 'a' - 'A';
|
|
if (a >= 'A')
|
|
a -= ('A' - 10);
|
|
else
|
|
a -= '0';
|
|
if (b >= 'a')
|
|
b -= 'a' - 'A';
|
|
if (b >= 'A')
|
|
b -= ('A' - 10);
|
|
else
|
|
b -= '0';
|
|
retbuf.push_back(16 * a + b);
|
|
src += 3;
|
|
} else if (*src == '+') {
|
|
retbuf.push_back(' ');
|
|
src++;
|
|
} else {
|
|
retbuf.push_back(*src++);
|
|
}
|
|
}
|
|
return retbuf;
|
|
}
|
|
|
|
std::string UriEncode(const std::string &value) {
|
|
const char *src = value.c_str();
|
|
std::string retbuf;
|
|
retbuf.reserve(value.length() * 3); // at most all characters get replaced with the escape
|
|
|
|
char tmp[4] = "";
|
|
while (*src) {
|
|
// Must be unsigned: a signed char sign-extends bytes >= 0x80 when promoted
|
|
// for isalnum()/snprintf(), which mangles every non-ASCII UTF-8 byte.
|
|
const unsigned char c = static_cast<unsigned char>(*src);
|
|
if (c == ' ') {
|
|
retbuf.append("%20");
|
|
} else if ((c < 0x80) && (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~')) {
|
|
// The unreserved set is ASCII-only, so bytes >= 0x80 are always escaped
|
|
// rather than left to a locale-dependent isalnum().
|
|
retbuf.push_back(static_cast<char>(c));
|
|
} else {
|
|
snprintf(tmp, sizeof(tmp), "%%%02X", c);
|
|
retbuf.append(tmp);
|
|
}
|
|
src++;
|
|
}
|
|
return retbuf;
|
|
}
|
|
|
|
QueryString::QueryString(std::istream &input) {
|
|
while (!input.eof() && input.peek() > 0) {
|
|
//Should eat "param1="
|
|
auto name = parseName(input);
|
|
//Should eat value1&
|
|
std::string value = parseValue(input);
|
|
|
|
auto foundItr = parameters_.find(name);
|
|
if (foundItr == parameters_.end()) {
|
|
std::unique_ptr<QueryParameter> newParam = zm::make_unique<QueryParameter>(name);
|
|
if (!value.empty()) {
|
|
newParam->addValue(value);
|
|
}
|
|
parameters_.emplace(name, std::move(newParam));
|
|
} else {
|
|
foundItr->second->addValue(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<std::string> QueryString::names() const {
|
|
std::vector<std::string> names;
|
|
for (auto const &pair : parameters_)
|
|
names.push_back(pair.second->name());
|
|
|
|
return names;
|
|
}
|
|
|
|
const QueryParameter *QueryString::get(const std::string &name) const {
|
|
auto itr = parameters_.find(name);
|
|
return itr == parameters_.end() ? nullptr : itr->second.get();
|
|
}
|
|
|
|
std::string QueryString::parseName(std::istream &input) {
|
|
std::string name;
|
|
|
|
while (!input.eof() && input.peek() != '=') {
|
|
name.push_back(input.get());
|
|
}
|
|
|
|
//Eat the '='
|
|
if (!input.eof()) {
|
|
input.get();
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
std::string QueryString::parseValue(std::istream &input) {
|
|
std::string url_encoded_value;
|
|
|
|
int c = input.get();
|
|
while (c > 0 && c != '&') {
|
|
url_encoded_value.push_back(c);
|
|
c = input.get();
|
|
}
|
|
|
|
if (url_encoded_value.empty()) {
|
|
return "";
|
|
}
|
|
|
|
return UriDecode(url_encoded_value);
|
|
}
|
|
|
|
std::string mask_authentication(const std::string &url) {
|
|
std::string masked_url = url;
|
|
std::size_t at_at = masked_url.find("@");
|
|
if (at_at == std::string::npos) {
|
|
return masked_url;
|
|
}
|
|
std::size_t password_at = masked_url.rfind(":", at_at);
|
|
|
|
if (password_at == std::string::npos) {
|
|
// no : means no http:// either so something like username@192.168.1.1
|
|
masked_url.replace(0, at_at, at_at, '*');
|
|
} else if (masked_url[password_at+1] == '/') {
|
|
// no password, something like http://username@192.168.1.1
|
|
masked_url.replace(password_at+3, at_at-(password_at+3), at_at-(password_at+3), '*');
|
|
} else {
|
|
// have username and password, something like http://username:password@192.168.1.1/
|
|
masked_url.replace(password_at+1, at_at - (password_at+1), at_at - (password_at+1), '*');
|
|
std::size_t username_at = masked_url.rfind("/", password_at);
|
|
if (username_at == std::string::npos) {
|
|
// Something like username:password@192.168.1.1
|
|
masked_url.replace(0, password_at, password_at, '*');
|
|
} else {
|
|
masked_url.replace(username_at+1, password_at-(username_at+1), password_at-(username_at+1), '*');
|
|
// something like http://username:password@192.168.1.1/
|
|
}
|
|
}
|
|
return masked_url;
|
|
}
|
|
|
|
std::string remove_authentication(const std::string &url) {
|
|
std::size_t at_at = url.find("@");
|
|
if (at_at == std::string::npos) {
|
|
return url;
|
|
}
|
|
std::string result;
|
|
std::size_t password_at = url.rfind(":", at_at);
|
|
|
|
if (password_at == std::string::npos) {
|
|
// no : means no http:// either so something like username@192.168.1.1
|
|
result = url.substr(at_at+1);
|
|
} else if (url[password_at+1] == '/') {
|
|
// no password, something like http://username@192.168.1.1
|
|
result = url.substr(0, password_at+3) + url.substr(at_at+1);
|
|
} else {
|
|
std::size_t username_at = url.rfind("/", password_at);
|
|
if (username_at == std::string::npos) {
|
|
// Something like username:password@192.168.1.1
|
|
result = url.substr(at_at+1);
|
|
} else {
|
|
// have username and password, something like http://username:password@192.168.1.1/
|
|
result = url.substr(0, username_at+1) + url.substr(at_at+1);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string remove_newlines( std::string str ) {
|
|
while (!str.empty() && str.find("\n") != std::string::npos)
|
|
str.erase(std::remove(str.begin(), str.end(), '\n'), str.cend());
|
|
return str;
|
|
}
|
|
|
|
std::string escape_json_string(std::string input) {
|
|
// One pass over the input, so each byte produces its escape exactly once.
|
|
// The previous implementation ran a chain of regex_replace calls with the
|
|
// backslash pass last, which re-escaped the backslashes the earlier passes
|
|
// had just introduced: a quote came out as \\" , an escaped backslash
|
|
// followed by a bare quote, which terminates the JSON string it is sitting
|
|
// in. It also left control characters other than \b \f \n \r \t unescaped,
|
|
// which RFC 8259 does not allow.
|
|
std::string output;
|
|
output.reserve(input.size());
|
|
|
|
for (unsigned char c : input) {
|
|
switch (c) {
|
|
case '"': output += "\\\""; break;
|
|
case '\\': output += "\\\\"; break;
|
|
case '\b': output += "\\b"; break;
|
|
case '\f': output += "\\f"; break;
|
|
case '\n': output += "\\n"; break;
|
|
case '\r': output += "\\r"; break;
|
|
case '\t': output += "\\t"; break;
|
|
default:
|
|
if (c < 0x20) {
|
|
// Control characters with no short form have to go out as \u00XX.
|
|
char buffer[7];
|
|
snprintf(buffer, sizeof(buffer), "\\u%04x", c);
|
|
output += buffer;
|
|
} else {
|
|
// Bytes >= 0x80 are passed through so UTF-8 stays intact.
|
|
output += static_cast<char>(c);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return output;
|
|
}
|