Ctrl K
ComunidadGuías…

src/zm_utils.cpp

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
//
// 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 Go2RTCApiEndpoint(std::string path) {
  while (!path.empty() && path.back() == '/') path.pop_back();
  if (path.empty()) return path;
  const std::string api = "/api";
  if (path.size() < api.size() || path.compare(path.size() - api.size(), api.size(), api) != 0)
    path += api;
  return path;
}

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);
}

// Where the userinfo of url ends: the last '@' before the path, query or
// fragment, or npos if there is none. ffmpeg and cameras split the authority
// at its last '@', so a password with a raw '@' in it works; ending at the
// first one would leave the rest of that password in the masked url. An '@'
// in the path or query is not userinfo.
static std::size_t userinfo_end(const std::string &url) {
  std::size_t scheme_at = url.find("://");
  std::size_t start = (scheme_at == std::string::npos) ? 0 : scheme_at + 3;
  std::size_t end = url.find_first_of("/?#", start);
  if (end == std::string::npos) end = url.size();
  if (end == start) return std::string::npos;
  std::size_t at_at = url.rfind('@', end - 1);
  if (at_at == std::string::npos or at_at < start) return std::string::npos;
  return at_at;
}

std::string mask_authentication(const std::string &url) {
  std::string masked_url = url;
  std::size_t at_at = userinfo_end(masked_url);
  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 = userinfo_end(url);
  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 url_for_log(const std::string &url) {
  std::size_t query_at = url.find('?');
  if (query_at == std::string::npos) return remove_authentication(url);
  return remove_authentication(url.substr(0, query_at)) + "?...";
}

std::string request_for_log(const std::string &request) {
  std::string out;
  out.reserve(request.size());
  std::size_t start = 0;
  bool first = true;
  while (start < request.size()) {
    std::size_t eol = request.find('\n', start);
    std::size_t next = (eol == std::string::npos) ? request.size() : eol + 1;
    std::size_t end = next;
    while (end > start and (request[end-1] == '\n' or request[end-1] == '\r')) end--;
    std::string line = request.substr(start, end - start);
    if (first) {
      // METHOD SP target SP version
      std::size_t sp1 = line.find(' ');
      std::size_t sp2 = (sp1 == std::string::npos) ? sp1 : line.find(' ', sp1 + 1);
      if (sp2 != std::string::npos)
        line = line.substr(0, sp1 + 1) + url_for_log(line.substr(sp1 + 1, sp2 - sp1 - 1)) + line.substr(sp2);
      first = false;
    } else {
      std::size_t colon = line.find(':');
      if (colon != std::string::npos) {
        std::string name = StringToLower(line.substr(0, colon));
        if (name == "authorization" or name == "proxy-authorization")
          line = line.substr(0, colon) + ": <not logged>";
      }
    }
    out += line + request.substr(end, next - end);
    start = next;
  }
  return out;
}

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;
}

size_t zm_parse_leb128(const unsigned char *data, size_t max_size, uint32_t &value) {
  uint64_t result = 0;
  size_t bytes_read = 0;

  for (size_t i = 0; i < max_size && i < 8; i++) {
    uint8_t byte = data[i];
    result |= static_cast<uint64_t>(byte & 0x7F) << (7 * i);
    bytes_read++;

    if ((byte & 0x80) == 0) {
      break;
    }
  }

  value = static_cast<uint32_t>(result);
  return bytes_read;
}