prod/native/libcommon/code/CommonUtils.cpp (252 lines of code) (raw):
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you 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 "CommonUtils.h"
#include "CiCharTraits.h"
#include "LogLevel.h"
#include "LogFeature.h"
#include "LoggerInterface.h"
#include <algorithm>
#include <array>
#include <charconv>
#include <chrono>
#include <cstdarg>
#include <memory>
#include <optional>
#include <ranges>
#include <string_view>
#include <signal.h>
#include <stddef.h>
#include <sys/types.h>
#include <unistd.h>
#include <boost/regex.hpp>
namespace elasticapm::utils {
using namespace std::literals;
[[maybe_unused]] bool blockSignal(int signo) {
sigset_t currentSigset;
if (pthread_sigmask(SIG_BLOCK, NULL, ¤tSigset) != 0) {
sigemptyset(¤tSigset);
}
if (sigismember(¤tSigset, signo) == 1) {
return true;
}
sigaddset(¤tSigset, signo);
return pthread_sigmask(SIG_BLOCK, ¤tSigset, NULL) == 0;
}
void blockApacheAndPHPSignals() {
// block signals for this thread to be handled by main Apache/PHP thread
// list of signals from Apaches mpm handlers
elasticapm::utils::blockSignal(SIGTERM);
elasticapm::utils::blockSignal(SIGHUP);
elasticapm::utils::blockSignal(SIGINT);
elasticapm::utils::blockSignal(SIGWINCH);
elasticapm::utils::blockSignal(SIGUSR1);
elasticapm::utils::blockSignal(SIGPROF); // php timeout signal
}
std::size_t parseByteUnits(std::string bytesWithUnit) {
auto endWithoutSpaces = std::remove_if(bytesWithUnit.begin(), bytesWithUnit.end(), [](unsigned char c) { return std::isspace(c); });
bytesWithUnit.erase(endWithoutSpaces, bytesWithUnit.end());
std::size_t value = std::stoul(bytesWithUnit.data());
auto unitPos = bytesWithUnit.find_first_not_of("0123456789"sv);
if (unitPos == std::string_view::npos) {
return value;
}
auto unitBuf = bytesWithUnit.substr(unitPos);
istring_view unit{unitBuf.data(), unitBuf.length()};
if (unit == "b"_cisv) {
return value;
} else if (unit == "kb"_cisv) {
return value * 1024;
} else if (unit == "mb"_cisv) {
return value * 1024 * 1024;
} else if (unit == "gb"_cisv) {
return value * 1024 * 1024 * 1024;
}
throw std::invalid_argument("Invalid byte unit.");
}
//TODO handle other string types
std::chrono::milliseconds convertDurationWithUnit(std::string timeWithUnit) {
auto endWithoutSpaces = std::remove_if(timeWithUnit.begin(), timeWithUnit.end(), [](unsigned char c) { return std::isspace(c); });
timeWithUnit.erase(endWithoutSpaces, timeWithUnit.end());
double timeValue = std::stod(timeWithUnit.data());
auto unitPos = timeWithUnit.find_first_not_of("0123456789."sv);
if (unitPos == std::string_view::npos) {
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::duration<double, std::milli>(timeValue)) ;
}
std::string unit{timeWithUnit.substr(unitPos)};
if (unit == "ms") {
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::duration<double, std::milli>(timeValue)) ;
} else if (unit == "s") {
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::duration<double>(timeValue)) ;
} else if (unit == "m") {
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::duration<double, std::ratio<60>>(timeValue)) ;
}
throw std::invalid_argument("Invalid time unit.");
}
bool parseBoolean(std::string_view val) {
constexpr std::array<istring_view, 3> trueValues = {"true"_cisv, "yes"_cisv, "on"_cisv}; // same in zend_ini_parse_bool
auto value = traits_cast<CiCharTraits>(utils::trim(val));
if (!value.length()) {
return false;
}
auto res = std::find(std::begin(trueValues), std::end(trueValues), value);
if (res != std::end(trueValues)) {
return true;
} else {
int iVal{};
if (std::from_chars(value.data(), value.data() + value.length(), iVal).ec == std::errc{}) {
return iVal;
}
}
return false;
}
LogLevel parseLogLevel(std::string_view val) { // throws std::invalid_argument
constexpr std::array<istring_view, LogLevel::last - LogLevel::first + 1> levels = {"OFF"_cisv, "CRITICAL"_cisv, "ERROR"_cisv, "WARNING"_cisv, "INFO"_cisv, "DEBUG"_cisv, "TRACE"_cisv};
auto value = traits_cast<CiCharTraits>(utils::trim(val));
auto found = std::find(std::begin(levels), std::end(levels), value);
if (found != std::end(levels)) {
auto index = std::distance(levels.begin(), found);
return static_cast<LogLevel>(index);
}
throw std::invalid_argument("Unknown log level: "s + std::string(val));
}
std::string getParameterizedString(std::string_view format) {
std::string out;
for (auto c = format.begin(); c < format.end(); ++c) {
if (*c == '%') {
c++;
if (c == format.end()) {
out.append(1, '%');
break;
}
switch (*c) {
case 'p':
out.append(std::to_string(getpid()));
break;
case 't':
out.append(std::to_string(std::chrono::milliseconds(std::time(NULL)).count()));
break;
default:
out.append(1, '%');
out.append(1, *c);
}
} else {
out.append({*c});
}
}
return out;
}
std::string stringPrintf(const char *format, ...) {
va_list args;
va_start(args, format);
auto result = stringVPrintf(format, args);
va_end(args);
return result;
}
std::string stringVPrintf(const char *format, va_list args) {
std::va_list argsCopy;
va_copy(argsCopy, args);
auto reqSpace = std::vsnprintf(nullptr, 0, format, argsCopy);
va_end(argsCopy);
if (reqSpace < 0) {
return {};
}
std::string buffer;
buffer.reserve(reqSpace + 1);
buffer.resize(reqSpace);
auto allocated = std::vsnprintf(buffer.data(), buffer.capacity(), format, args);
buffer.resize(allocated);
return buffer;
}
std::string getIniName(std::string_view optionName) {
auto name = "elastic_otel."s;
return name.append(optionName);
}
std::string getEnvName(std::string_view optionName) {
std::string envName = "ELASTIC_OTEL_"s;
std::transform(optionName.begin(), optionName.end(), std::back_inserter(envName), ::toupper);
return envName;
}
std::string sanitizeKeyValueString(std::string const &tokenName, std::string const &text) {
std::regex regex(tokenName + R"(=("[^"]*"|[^,\s]*))"s, std::regex::icase);
return std::regex_replace(text, regex, tokenName + "=***"s);
}
std::optional<ParsedURL> parseUrl(std::string const &url) {
std::regex url_regex(R"((http|https)://([\w.-]+)(?::(\d+))?(?:/(.*))?)");
std::smatch match;
if (std::regex_match(url, match, url_regex)) {
ParsedURL parsed_url;
parsed_url.protocol = match[1];
parsed_url.host = match[2];
if (match[3].matched) {
parsed_url.port = match[3];
}
if (match[4].matched) {
parsed_url.query = match[4];
}
return parsed_url;
}
return std::nullopt;
}
std::optional<std::string> getConnectionDetailsFromURL(std::string const &url) {
boost::regex urlPattern(R"(^((https?):\/\/([^\/:]+))(?::(\d+))?)");
boost::smatch match;
if (boost::regex_search(url, match, urlPattern)) {
return match[4].matched ? match[1].str() + ":" + match[4].str() : match[1].str();
}
return std::nullopt;
}
// FEATURENAME=LEVEL,ANOTHERFEATURE=LOGLEVEL
std::unordered_map<elasticapm::php::LogFeature, LogLevel> parseLogFeatures(std::shared_ptr<elasticapm::php::LoggerInterface> logger, std::string_view logFeatures) {
std::unordered_map<elasticapm::php::LogFeature, LogLevel> features;
if (!logFeatures.empty()) {
using namespace std::string_view_literals;
for (const auto split : std::views::split(logFeatures, ","sv)) {
std::string_view featureAndValue(split);
auto pos = featureAndValue.find('=');
if (pos != std::string_view::npos) {
std::string_view option = featureAndValue.substr(0, pos);
std::string_view value = featureAndValue.substr(pos + 1);
try {
auto level = parseLogLevel(value);
auto feature = elasticapm::php::parseLogFeature(option);
features.emplace(feature, level);
} catch (std::invalid_argument const &e) {
ELOGF_NF_WARNING(logger, "Error while parsing LogFeature " PRsv ", exception: %s ", PRsvArg(featureAndValue), e.what());
}
} else {
ELOGF_NF_WARNING(logger, "Error while parsing LogFeature " PRsv, PRsvArg(featureAndValue));
}
}
}
return features;
}
bool isUtf8(std::string_view input) {
const uint8_t *p = reinterpret_cast<const uint8_t *>(input.data());
size_t length = input.size();
static constexpr uint8_t utf8_table[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3};
while (length > 0) {
uint32_t d;
uint8_t c = *p++;
length--;
if (c < 0x80)
continue;
if (c < 0xC0 || c >= 0xF5)
return false;
uint8_t ab = utf8_table[c & 0x3F];
if (length < ab)
return false;
length -= ab;
if (((d = *p++) & 0xC0) != 0x80)
return false;
switch (ab) {
case 1:
if ((c & 0x3E) == 0)
return false;
break;
case 2:
if ((*p++ & 0xC0) != 0x80 || (c == 0xE0 && (d & 0x20) == 0) || (c == 0xED && d >= 0xA0))
return false;
break;
case 3:
if ((*p++ & 0xC0) != 0x80 || (*p++ & 0xC0) != 0x80 || (c == 0xF0 && (d & 0x30) == 0) || (c > 0xF4 || (c == 0xF4 && d > 0x8F)))
return false;
break;
}
}
return true;
}
}