- MString::read() now gives up after repeated I/O errors instead of looping forever and crashing via realloc() failure (root cause of a recurring barant.com production crash). - HTTPRequest::parse() no longer leaks lineParser/headerParser on a malformed request. - Log gained size-based rotation (config-driven) and a non-static-mutex fix; a separate fix stopped rotation from destroying the logs symlink on its first trigger. - File.cpp: symlink-preserving rotation support. - JString: fixed an array-index out-of-bounds and a setString issue. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
552 lines
14 KiB
C++
552 lines
14 KiB
C++
#include "MString.h"
|
|
#include "Log.h"
|
|
#include "uuid/uuid.h"
|
|
#include "Exception.h"
|
|
#include "Base64.h"
|
|
#include <cstring>
|
|
#include <string>
|
|
#include <format>
|
|
#include <iomanip>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <cctype>
|
|
#include <fstream>
|
|
|
|
namespace coreutils {
|
|
|
|
MString::MString() {
|
|
data = NULL;
|
|
length = 0;
|
|
cursor = NULL;
|
|
setSize(0);
|
|
}
|
|
|
|
MString::MString(const char *data) {
|
|
setSize(strlen(data));
|
|
memcpy(this->data, data, length);
|
|
cursor = this->data;
|
|
}
|
|
|
|
// Declared alongside MString(const char *data) but never implemented -
|
|
// invisible until a real executable actually needed this symbol
|
|
// resolved (a static library archive never checks for unresolved
|
|
// references, only the final link of a real binary does). Stores the
|
|
// raw pointer VALUE itself (not a dereference of it) as the string's
|
|
// bytes, matching how void* aliases are compared everywhere else in
|
|
// this codebase - by pointer identity, not by string content (e.g.
|
|
// TCPServer::getSessionByAlias() and TCPSession::compareAlias() both
|
|
// compare void* aliases directly) - so two MStrings built from the
|
|
// same alias value compare equal, and one built from an arbitrary
|
|
// (possibly non-string, possibly not even null-terminated) alias
|
|
// pointer never risks reading past it.
|
|
MString::MString(void *alias) {
|
|
setSize(sizeof(void *));
|
|
memcpy(this->data, &alias, sizeof(void *));
|
|
cursor = this->data;
|
|
}
|
|
|
|
MString::MString(char *data, size_t length) {
|
|
setSize(length);
|
|
memcpy(this->data, data, length);
|
|
cursor = this->data;
|
|
}
|
|
|
|
MString::MString(unsigned char *data, size_t length) {
|
|
setSize(length);
|
|
memcpy(this->data, data, length);
|
|
cursor = this->data;
|
|
}
|
|
|
|
MString::MString(const char *data, size_t length) {
|
|
setSize(length);
|
|
memcpy(this->data, data, length);
|
|
cursor = this->data;
|
|
}
|
|
|
|
MString::MString(const ZString &zstring) {
|
|
setSize(zstring.getLength());
|
|
memcpy(data, zstring.getCursor(), zstring.getLength());
|
|
cursor = data;
|
|
}
|
|
|
|
MString::MString(ZString &zstring) {
|
|
setSize(zstring.getLength());
|
|
memcpy(data, zstring.getCursor(), zstring.getLength());
|
|
cursor = data;
|
|
}
|
|
|
|
MString::MString(const MString &mstring) {
|
|
setSize(mstring.length);
|
|
memcpy(data, mstring.data, mstring.length);
|
|
cursor = data;
|
|
}
|
|
|
|
MString::MString(MString &mstring) {
|
|
setSize(mstring.getLength());
|
|
memcpy(data, mstring.getCursor(), mstring.getLength());
|
|
cursor = data;
|
|
}
|
|
|
|
// MString::MString(MString mstring) {
|
|
// setSize(mstring.getLength());
|
|
// memcpy(data, mstring.getData(), mstring.getLength());
|
|
// cursor = data;
|
|
// printf("MString Copy Constructor %p\n", this);
|
|
// }
|
|
|
|
MString::MString(std::string data) {
|
|
setSize(data.length());
|
|
memcpy(this->data, (char *)data.c_str(), data.length());
|
|
cursor = this->data;
|
|
}
|
|
|
|
MString::MString(std::FILE *file) {
|
|
setSize(1000000);
|
|
this->length = fread(this->data, 1000000, 1000000, file);
|
|
cursor = this->data;
|
|
}
|
|
|
|
MString::MString(double value) {
|
|
std::string temp = std::format("{:.12f}", value);
|
|
temp.erase(temp.find_last_not_of('0') + 1, std::string::npos);
|
|
if (temp.back() == '.')
|
|
temp.pop_back();
|
|
setSize(temp.length());
|
|
memcpy(this->data, (char *)temp.data(), temp.length());
|
|
cursor = this->data;
|
|
}
|
|
|
|
MString::~MString() {
|
|
if (data)
|
|
free(data);
|
|
}
|
|
|
|
MString &MString::operator=(coreutils::MString value) {
|
|
setSize(value.getLength());
|
|
memcpy(data, value.getCursor(), value.getLength());
|
|
length = value.getLength();
|
|
cursor = data;
|
|
onChange();
|
|
return *this;
|
|
}
|
|
|
|
MString &MString::operator=(coreutils::ZString value) {
|
|
setSize(value.getLength());
|
|
memcpy(data, value.getCursor(), value.getLength());
|
|
length = value.getLength();
|
|
cursor = data;
|
|
onChange();
|
|
return *this;
|
|
}
|
|
|
|
MString &MString::operator=(std::string value) {
|
|
setSize(value.length());
|
|
memcpy(data, value.c_str(), value.length());
|
|
length = value.length();
|
|
cursor = data;
|
|
onChange();
|
|
return *this;
|
|
}
|
|
|
|
MString &MString::operator=(const char *value) {
|
|
int len = strlen(value);
|
|
setSize(len);
|
|
memcpy(data, value, len);
|
|
cursor = data;
|
|
onChange();
|
|
return *this;
|
|
}
|
|
|
|
MString &MString::operator=(char value) {
|
|
int len = 1;
|
|
setSize(1);
|
|
*data = value;
|
|
cursor = data;
|
|
onChange();
|
|
return *this;
|
|
}
|
|
|
|
MString &MString::operator=(double value) {
|
|
std::string temp = std::format("{:.12f}", value);
|
|
temp.erase(temp.find_last_not_of('0') + 1, std::string::npos);
|
|
if (temp.back() == '.')
|
|
temp.pop_back();
|
|
setSize(temp.length());
|
|
memcpy(this->data, (char *)temp.data(), temp.length());
|
|
cursor = this->data;
|
|
onChange();
|
|
return *this;
|
|
}
|
|
|
|
MString &MString::operator=(int value) {
|
|
std::string temp = std::format("{}", value);
|
|
setSize(temp.length());
|
|
memcpy(this->data, (char *)temp.data(), temp.length());
|
|
cursor = this->data;
|
|
onChange();
|
|
return *this;
|
|
}
|
|
|
|
MString &MString::operator<<(const char *value) {
|
|
int temp = length;
|
|
int len = strlen(value);
|
|
setSize(len + length);
|
|
memcpy(data + temp, value, len);
|
|
onChange();
|
|
return *this;
|
|
}
|
|
|
|
MString &MString::operator<<(const int value) {
|
|
std::stringstream temp;
|
|
temp << value;
|
|
MString temp2 = temp.str();
|
|
write(temp2);
|
|
onChange();
|
|
return *this;
|
|
}
|
|
|
|
// MString& MString::operator<<(coreutils::ZString &zstring) {
|
|
// int temp = length;
|
|
// int len = length + zstring.getLength();
|
|
// setSize(len);
|
|
// memcpy(data + temp, zstring.getData(), zstring.getLength());
|
|
// return *this;
|
|
// }
|
|
|
|
MString &MString::operator<<(coreutils::ZString zstring) {
|
|
int temp = length;
|
|
int len = length + zstring.getLength();
|
|
setSize(len);
|
|
memcpy(data + temp, zstring.getCursor(), zstring.getLength());
|
|
onChange();
|
|
return *this;
|
|
}
|
|
|
|
MString &MString::operator<<(char value) {
|
|
setSize(length + 1);
|
|
data[length - 1] = value;
|
|
onChange();
|
|
return *this;
|
|
}
|
|
|
|
// MString &MString::operator<<(std::ostream (*f)(std::ostream&)) {
|
|
MString &MString::operator<<(std::basic_ostream<char> (*pf)(std::basic_ostream<char>&)) {
|
|
// int temp = length;
|
|
// int len = length + value.length();
|
|
// setSize(len);
|
|
// memcpy(data + temp, value.c_str(), value.length());
|
|
std::cout << "xxx" << std::endl;
|
|
return *this;
|
|
}
|
|
|
|
MString &MString::operator<<(std::ifstream &pf) {
|
|
std::string line;
|
|
while(std::getline(pf, line))
|
|
*this << line << '\n';
|
|
return *this;
|
|
}
|
|
|
|
// MString &operator<<(std::ostream &out) {
|
|
// *this << out;
|
|
// }
|
|
|
|
void MString::insert(char ch, int offset) {
|
|
setSize(length + 1);
|
|
for (int ix = length; ix >= offset; ix--)
|
|
getData()[ix + 1] = getData()[ix];
|
|
data[offset] = ch;
|
|
onChange();
|
|
}
|
|
|
|
void MString::insert(ZString value, int offset) {
|
|
setSize(length + value.getLength());
|
|
for (int ix = length; ix >= offset; ix--)
|
|
getData()[ix] = getData()[ix - value.getLength()];
|
|
std::memcpy(data + offset, value.getCursor(), value.getLength());
|
|
onChange();
|
|
}
|
|
|
|
// void MString::insert(std::string value, int offset) {
|
|
// setSize(length + value.size());
|
|
// for (int ix = length; ix >= offset; ix--)
|
|
// getData()[ix + value.size()] = getData()[ix];
|
|
// std::memcpy(data + offset, value.c_str(), value.size());
|
|
// }
|
|
|
|
void MString::replace(char ch, int offset) {
|
|
}
|
|
|
|
void MString::replace(ZString &value, int offset) {
|
|
}
|
|
|
|
void MString::replace(std::string value, int offset) {
|
|
}
|
|
|
|
void MString::remove(int length) {
|
|
// Shifts the tail (everything after the removed range) down to close
|
|
// the gap. The loop must stop once ix + length reaches the end of the
|
|
// valid data - reading cursor[ix + length] past that point is a
|
|
// heap-buffer-overflow (confirmed via AddressSanitizer under
|
|
// concurrent APIServer load: a completely full MString hit exactly
|
|
// this on the final iteration, reading 1 byte past the allocation).
|
|
// The old condition `ix < len` let the loop run `length` iterations
|
|
// too far.
|
|
int len = (data + this->length - cursor);
|
|
for (int ix = 0; ix < len - length; ix++)
|
|
cursor[ix] = cursor[ix + length];
|
|
setSize(this->length - length);
|
|
onChange();
|
|
}
|
|
|
|
void MString::remove(int offset, int length) {
|
|
// Same off-by-one as the single-argument overload above - the loop
|
|
// must stop once ix + length reaches this->length, not run all the
|
|
// way to this->length itself.
|
|
for (int ix = offset; ix < this->length - length; ix++)
|
|
getData()[ix] = getData()[ix + length];
|
|
setSize(this->length - length);
|
|
onChange();
|
|
}
|
|
|
|
MString &MString::write(char ch) {
|
|
setSize(length + 1);
|
|
*(data + length - 1) = ch;
|
|
onChange();
|
|
return *this;
|
|
}
|
|
|
|
MString &MString::write(ZString value) {
|
|
int len = length;
|
|
setSize(length + value.getLength());
|
|
memcpy(data + len, value.getCursor(), value.getLength());
|
|
onChange();
|
|
return *this;
|
|
}
|
|
|
|
void MString::setSize(int size) {
|
|
int cursorOffset = cursor - data;
|
|
int newBufferSize = ((size / 256) + 1) * 256;
|
|
if (bufferSize != newBufferSize) {
|
|
bufferSize = newBufferSize;
|
|
data = (char *)realloc(data, bufferSize);
|
|
cursor = data + cursorOffset;
|
|
}
|
|
length = size;
|
|
}
|
|
|
|
MString &MString::read(int fd) {
|
|
// ::read() returns 0 on EOF, a positive byte count on success, or
|
|
// -1 on error (EINTR - a signal interrupting the call, most
|
|
// plausibly, but any read() error) - the old `while(::read(...))`
|
|
// treated -1 as truthy exactly like a successful read, so a
|
|
// single interrupted read turned this into an infinite loop:
|
|
// every iteration from then on called write() with whatever
|
|
// garbage was left in the uninitialized `ch`, growing this
|
|
// MString without bound. Confirmed via a real production core
|
|
// dump - the eventual crash was a SIGSEGV in write() itself,
|
|
// because unbounded growth made realloc() in setSize() finally
|
|
// fail (returning NULL) under memory pressure, and the next
|
|
// *(data + length - 1) = ch wrote through that null pointer. The
|
|
// actual trigger was <call>'s own out.read(fdo[0]), reading a
|
|
// subprocess's output pipe, but this bug lives in a core utility
|
|
// used throughout the codebase - anything reading a fd through
|
|
// MString was exposed to it whenever that read() call errored.
|
|
char ch;
|
|
ssize_t n;
|
|
while((n = ::read(fd, &ch, 1)) > 0)
|
|
write(ch);
|
|
onChange();
|
|
return *this;
|
|
}
|
|
|
|
MString &MString::read(int fd, int count) {
|
|
char ch;
|
|
ssize_t n;
|
|
while(count-- && (n = ::read(fd, &ch, 1)) > 0)
|
|
write(ch);
|
|
onChange();
|
|
return *this;
|
|
}
|
|
|
|
int MString::offset() {
|
|
return cursor - data;
|
|
}
|
|
|
|
MString MString::toBinary() {
|
|
push();
|
|
reset();
|
|
MString target;
|
|
char temp;
|
|
while(!eod()) {
|
|
temp = nextChar();
|
|
if(strchr("\\'\".\0\r\n", temp))
|
|
target.write('\\');
|
|
target.write(temp);
|
|
}
|
|
pop();
|
|
return target;
|
|
}
|
|
|
|
MString MString::fromBinary() {
|
|
push();
|
|
reset();
|
|
MString target;
|
|
while(!eod()) {
|
|
if(ifNext("\\r"))
|
|
target.write(13);
|
|
else if(ifNext("\\n"))
|
|
target.write(10);
|
|
else if(ifNext("\\0"))
|
|
target.write(0);
|
|
else if(ifNext("\\\\"))
|
|
target.write("\\");
|
|
else if(ifNext("\\."))
|
|
target.write(".");
|
|
else if(ifNext("\\\""))
|
|
target.write("\"");
|
|
else if(ifNext("\\\'"))
|
|
target.write("'");
|
|
else
|
|
target.write(nextChar());
|
|
}
|
|
pop();
|
|
return target;
|
|
}
|
|
|
|
MString MString::toHex() {
|
|
push();
|
|
reset();
|
|
MString target;
|
|
char temp;
|
|
while(!eod()) {
|
|
temp = nextChar();
|
|
char temp2 = temp;
|
|
temp >>= 4;
|
|
target.write(hexChar(temp));
|
|
target.write(hexChar(temp2));
|
|
}
|
|
pop();
|
|
return target;
|
|
}
|
|
|
|
MString MString::fromHex() {
|
|
push();
|
|
reset();
|
|
MString target;
|
|
while(!eod()) {
|
|
char ch1 = nextChar();
|
|
ch1 -= 48;
|
|
if(ch1 > 9)
|
|
ch1 -= 7;
|
|
ch1 <<= 4;
|
|
ch1 &= 240;
|
|
if(eod())
|
|
throw coreutils::Exception("conversion from hex requires even number of characters.");
|
|
char ch2 = nextChar();
|
|
ch2 -= 48;
|
|
if(ch2 > 9)
|
|
ch2 -= 7;
|
|
ch2 &= 15;
|
|
ch1 |= ch2;
|
|
target.write(ch1);
|
|
}
|
|
pop();
|
|
return target;
|
|
}
|
|
|
|
MString MString::toBase64() {
|
|
Base64 b64;
|
|
return b64.encode(*this);
|
|
}
|
|
|
|
|
|
MString MString::fromBase64() {
|
|
Base64 b64;
|
|
return b64.decode(*this);
|
|
}
|
|
|
|
|
|
MString MString::toUpper() {
|
|
MString target;
|
|
for(int ix = 0; ix < length; ++ix)
|
|
target.write(std::toupper(data[ix]));
|
|
return target;
|
|
}
|
|
|
|
|
|
MString MString::toLower() {
|
|
MString target;
|
|
for(int ix = 0; ix < length; ++ix)
|
|
target.write(std::tolower(data[ix]));
|
|
return target;
|
|
}
|
|
|
|
|
|
MString MString::toCGI() {
|
|
push();
|
|
reset();
|
|
MString target;
|
|
while(!eod()) {
|
|
char ch = nextChar();
|
|
if(ch == ' ') {
|
|
target.write('+');
|
|
} else if(isalnum((unsigned char)ch) || ch == '-' || ch == '_' || ch == '.' || ch == '~') {
|
|
target.write(ch);
|
|
} else {
|
|
target.write('%');
|
|
target.write(hexChar((ch >> 4) & 15));
|
|
target.write(hexChar(ch & 15));
|
|
}
|
|
}
|
|
pop();
|
|
return target;
|
|
}
|
|
|
|
|
|
MString MString::fromCGI() {
|
|
push();
|
|
reset();
|
|
MString target;
|
|
while(!eod()) {
|
|
char ch = nextChar();
|
|
if(ch == '+') {
|
|
target.write(' ');
|
|
} else if(ch == '%' && !eod()) {
|
|
char ch1 = nextChar();
|
|
ch1 -= 48;
|
|
if(ch1 > 9)
|
|
ch1 -= 7;
|
|
ch1 <<= 4;
|
|
ch1 &= 240;
|
|
if(!eod()) {
|
|
char ch2 = nextChar();
|
|
ch2 -= 48;
|
|
if(ch2 > 9)
|
|
ch2 -= 7;
|
|
ch2 &= 15;
|
|
ch1 |= ch2;
|
|
target.write(ch1);
|
|
} else {
|
|
target.write(ch1);
|
|
}
|
|
} else {
|
|
target.write(ch);
|
|
}
|
|
}
|
|
pop();
|
|
return target;
|
|
}
|
|
|
|
char MString::hexChar(char c) {
|
|
c &= 15;
|
|
c += 48;
|
|
if(c > 57)
|
|
c += 7;
|
|
return c;
|
|
}
|
|
|
|
void MString::onChange() {}
|
|
|
|
}
|