Files
CoreUtils/ZString.cpp
T
Brad Arant 694c6b80a4 Fix ZString::operator[] to throw on out-of-range index instead of crashing
Was an unchecked std::vector index (return list[index];). Any
malformed/short line - a port scanner, a bot probing the port, or any
client sending fewer colon-delimited fields than a given handler
indexes via request[N] - reached this with no length check anywhere
upstream, across every server built on this class. Depending on build
flags that was either undefined behavior or, confirmed live (a real
crash log from Zephora's game server), a hard process abort via
libstdc++'s own vector bounds assertion:

  Assertion '__n < this->size()' failed.

Throwing instead lets this be caught: every caller that dispatches a
parsed request already wraps that dispatch in a try/catch (see
CommandList::processRequest()), so a single malformed line is now
cleanly rejected instead of taking down the entire server process for
every connected player.
2026-09-06 19:36:50 +00:00

690 lines
17 KiB
C++

#include "ZString.h"
#include "Log.h"
#include "Exception.h"
#include <stdlib.h>
#include <iostream>
namespace coreutils {
std::ostream &operator<<(std::ostream &os, const ZString &zstring) {
for (char *ix = zstring.data; ix < (zstring.data + zstring.length); ++ix)
os << *ix;
return os;
}
std::ostream &operator<<(std::ostream &os, const std::string &string) {
os << string;
return os;
}
std::ostream &operator+(std::ostream &os, const ZString &zstring) {
os << zstring;
return os;
}
std::ostream &operator+(std::ostream &os, const char *value) {
os << value;
return os;
}
ZString::ZString() {
data = NULL;
length = 0;
cursor = data;
}
ZString::ZString(const char *data) {
if((char *)data != NULL) {
this->data = (char *)data;
this->length = strlen(data);
this->cursor = (char *)data;
} else {
this->data = NULL;
this->length = 0;
this->cursor = NULL;
}
}
ZString::ZString(char *data, size_t length) : data(data), length(length), cursor(data) {}
ZString::ZString(unsigned char *data, size_t length) : data((char *)data), length(length), cursor((char *)data) {}
ZString::ZString(const char *data, size_t length) : data((char *)data), length(length), cursor((char *)data) {}
ZString::ZString(const unsigned char *data, size_t length) : data((char *)data), length(length), cursor((char *)data) {}
ZString::ZString(const ZString &zstring) {
data = zstring.data;
length = zstring.length;
cursor = zstring.cursor;
}
ZString::ZString(std::string &data) {
this->data = (char *)data.c_str();
length = data.length();
cursor = (char *)data.c_str();
}
ZString::~ZString() {
if(cdata != NULL)
free(cdata);
}
// All four relational operators compare from each side's current cursor position to its
// own true buffer end (data+length), not from data alone - this class is a cursor/view
// over a buffer (see parsed()/unparsed()), so "less than" means "the remaining, unparsed
// content compares less than", consistently across <, <=, >=, and >.
bool ZString::operator<(const ZString &valuex) const {
char *end1 = data + length;
char *end2 = valuex.data + valuex.length;
char *cursor1 = cursor;
char *cursor2 = valuex.cursor;
while((cursor1 < end1) && (cursor2 < end2)) {
if(*cursor1 < *cursor2)
return 1;
if(*cursor1 > *cursor2)
return 0;
++cursor1;
++cursor2;
}
if((cursor1 == end1) && (cursor2 != end2))
return 1;
if((cursor2 == end2) && (cursor1 != end1))
return 0;
return 0;
}
bool ZString::operator<=(const ZString &valuex) const {
return !(valuex < *this);
}
bool ZString::operator>=(const ZString &valuex) const {
return !(*this < valuex);
}
bool ZString::operator>(const ZString &valuex) const {
return (valuex < *this);
}
bool ZString::operator==(const ZString valuex) {
if(valuex.getLength() != length)
return false;
return (strncmp(data, valuex.data, length) == 0);
}
bool ZString::operator!=(const ZString valuex) const {
if(length != valuex.getLength())
return true;
return (strncmp(data, valuex.data, length) != 0);
}
bool ZString::operator!=(const char *valuex) const {
if(valuex == NULL) {
if(length == 0)
return false;
else
return true;
} else {
if(strlen(valuex) != length)
return true;
return (strncmp(data, valuex, length) != 0);
}
}
std::vector<ZString> &ZString::getList() {
return list;
}
int ZString::asInteger() {
int value = 0;
bool negative = false;
char *end = data + length;
if(*cursor == '-') {
negative = true;
++cursor;
} else if(*cursor == '+')
++cursor;
if(cursor == end)
return 0;
while((*cursor >= '0') && (*cursor <= '9')) {
value *= 10;
value += *cursor - 48;
++cursor;
if(cursor == end) {
if(negative)
return -value;
else
return value;
}
}
if(negative)
return -value;
return value;
}
double ZString::asDouble() {
double value = 0;
bool negative = false;
char *end = data + length;
if(*cursor == '-') {
negative = true;
++cursor;
} else if(*cursor == '+')
++cursor;
if(cursor == end)
return 0;
while((*cursor >= '0') && (*cursor <= '9')) {
value *= 10;
value += *cursor - 48;
++cursor;
if(cursor == end) {
if(negative)
return -value;
else
return value;
}
}
if(*cursor != '.') {
if(negative)
return -value;
else
return value;
}
++cursor;
if(cursor == end) {
if(negative)
return -value;
else
return value;
}
double div = 10;
while((*cursor >= '0') && (*cursor <= '9')) {
value += (*cursor - 48) / div;
div *= 10;
++cursor;
if(cursor == end) {
if(negative)
return -value;
else
return value;
}
}
if(negative)
return -value;
return value;
}
int ZString::incrementCursor(int length) {
char *temp = cursor;
if((cursor + length) > (data + this->length))
cursor = data + this->length;
else
cursor += length;
return cursor - temp;
}
void ZString::push() {
stack.push(cursor);
}
void ZString::pop() {
cursor = stack.top();
stack.pop();
}
std::string ZString::str() {
return std::string(data, length);
}
std::string ZString::str(int len) {
return std::string(data, len);
}
char* ZString::c_str() {
if(cdata == NULL)
cdata = (char *)malloc(length + 1);
else
cdata = (char *)realloc(cdata, length + 1);
strncpy(cdata, data, length);
cdata[length] = '\0';
return cdata;
}
ZString ZString::substring(int start) {
char *end = data + length;
char *startChar = data + start;
if(startChar < end) {
return ZString(startChar, end - startChar);
}
return ZString();
}
ZString ZString::substring(int start, int len) {
char *end = data + length;
char *startChar = data + start;
char *endChar = startChar + len;
// Clamp to the remaining valid length (end - startChar) when the requested
// length would overrun the buffer - previously computed endChar - end (the
// overrun amount itself) instead, handing back a length far past what's
// actually there. size_t also fixes a latent overflow for strings > 127
// bytes that the old `char newlen` would have silently wrapped.
size_t newlen = endChar > end ? (size_t)(end - startChar) : (size_t)len;
if(startChar < end)
return ZString(startChar, newlen);
return ZString();
}
std::vector<ZString> &ZString::split(std::string delimiter, size_t maxSize) {
coreutils::ZString zDelimiter((char *)delimiter.c_str(), delimiter.size());
return split(zDelimiter, maxSize);
}
std::vector<ZString> &ZString::split(ZString &delimiter, size_t maxSize) {
list.clear();
if (length == 0) {
list.push_back(ZString(""));
return list;
}
maxSize = maxSize == 0 ? 255: maxSize;
char *end = data + length;
char *pos = cursor;
while ((pos + delimiter.getLength()) <= end) {
if ((strncmp(pos, delimiter.getData(), delimiter.getLength()) == 0) && (maxSize != 0)) {
list.push_back(ZString(cursor, pos - cursor));
cursor = pos + delimiter.getLength();
pos = cursor;
--maxSize;
}
else {
++pos;
}
}
list.push_back(ZString(cursor, pos - cursor));
cursor = data;
return list;
}
bool ZString::isCharacter(char ch, ZString include) {
for (int ix = 0; ix < include.getLength(); ++ix) {
if (ch == include.charAt(ix))
return true;
}
return false;
}
bool ZString::ifNextInclude(ZString include) {
if((cursor <= (data + length)) && isCharacter(*cursor, include))
return true;
return false;
}
ZString ZString::getTokenInclude(const char *include) {
char *start = cursor;
while ((cursor <= (data + length)) && isCharacter(*cursor, include))
++cursor;
return ZString(start, cursor - start);
}
ZString ZString::getTokenExclude(const char *exclude) {
char previous = 0;
char *start = cursor;
do {
while ((cursor < (data + length)) && !isCharacter(*cursor, exclude)) {
previous = *cursor;
++cursor;
}
++cursor;
} while(previous == '\\');
--cursor;
return ZString(start, cursor - start);
}
ZString ZString::getTokenExclude(std::string exclude) {
return getTokenExclude(exclude.c_str());
}
ZString ZString::getContainer() {
char term = *cursor == '"' ? '"': *cursor == '(' ? ')': *cursor == '[' ? ']': *cursor == '{' ? '}': 0;
if(!term)
return ZString();
char *start = cursor;
char nest = *cursor;
char *end = cursor + length;
int level = 0;
++cursor;
while(cursor != end) {
if(*cursor == term) {
if(!level) {
++cursor;
return ZString(start, cursor - start);
}
--level;
} else if(*cursor == nest) {
++level;
}
++cursor;
}
return ZString();
}
ZString &ZString::operator[](int index) {
// Previously an unchecked std::vector index - a single
// malformed/short line (a port scanner, a bot probing the port,
// or any client sending fewer colon-delimited fields than a
// given handler indexes) reaches every command handler across
// every server built on this class via request[N] with no
// length check first. Depending on build flags that was either
// undefined behavior or - confirmed live, crashing a real game
// server - a hard process abort (libstdc++'s own vector bounds
// assertion). Throwing here instead lets it be caught: every
// caller that dispatches a parsed request already wraps that
// dispatch in a try/catch (see CommandList::processRequest()),
// so this turns "one bad line takes down the whole server" into
// "that one line is cleanly rejected."
if (index < 0 || (size_t)index >= list.size())
throw coreutils::Exception("ZString::operator[]: index " + std::to_string(index) +
" out of range (have " + std::to_string(list.size()) + ")");
return list[index];
}
bool ZString::eod() {
return cursor >= data + length;
}
int ZString::goeod() {
char *temp = cursor;
cursor = data + length;
return cursor - temp;
}
bool ZString::startsWith(const char *value) {
return strncmp(cursor, value, strlen(value)) == 0;
}
bool ZString::startsWithNumber() {
if(*cursor == '-')
return true;
return ((*cursor >= '0') && (*cursor <= '9'));
}
int ZString::compare(ZString zstring) {
char *end1 = data + length;
char *end2 = zstring.getData() + zstring.getLength();
char *cursor1 = cursor;
char *cursor2 = zstring.getCursor();
while((cursor1 < end1) && (cursor2 < end2)) {
if(*cursor1 < *cursor2)
return -1;
if(*cursor1 > *cursor2)
return 1;
++cursor1;
++cursor2;
}
if((cursor1 == end1) && (cursor2 != end2))
return -1;
if((cursor2 == end2) && (cursor1 != end1))
return 1;
return 0;
}
bool ZString::equals(const char *value) {
if (strlen(value) != length)
return false;
return strncmp(data, value, length) == 0;
}
bool ZString::equals(char *value) {
if (strlen(value) != length)
return false;
return strncmp(data, value, length) == 0;
}
bool ZString::equals(ZString &zstring) {
if (zstring.getLength() != length)
return false;
return strncmp(data, zstring.getData(), length) == 0;
}
bool ZString::equals(std::string &string) {
return string == std::string(data, length);
}
bool ZString::ifNext(ZString value) {
if (((data + length) - cursor) < value.getLength())
return false;
bool test = (strncmp(cursor, value.getCursor(), value.getLength()) == 0);
if (test)
cursor += value.getLength();
return test;
}
bool ZString::ifNextIgnoreCase(ZString value) {
if (((data + length) - cursor) < value.getLength())
return false;
bool test = (strncasecmp(cursor, value.getCursor(), value.getLength()) == 0);
if (test)
cursor += value.getLength();
return test;
}
int ZString::ifEqualsCount(ZString &comparator) {
int count = 0;
while (cursor < (data + length)) {
if (*cursor == *comparator.cursor) {
++count;
++cursor;
++comparator.cursor;
}
else {
return count;
}
}
return count;
}
int ZString::skipWhitespace() {
char *end = data + length;
int len = 0;
while((cursor < end) && ((*cursor == ' ') || (*cursor == '\n') || (*cursor == '\t'))) {
++cursor;
++len;
}
return len;
}
int ZString::trimTrailingWhitespace() {
goeod();
int len = 0;
--cursor;
while((cursor > data) && ((*cursor == ' ') || (*cursor == '\n') || (*cursor == '\t'))) {
--cursor;
--length;
++len;
}
++cursor;
return len;
}
bool ZString::lineIsWhitespace() {
char *end = data + length;
char *temp = cursor;
while(temp < end) {
if(*temp == '\n')
return true;
if((*temp != ' ') && (*temp != '\t'))
return false;
++temp;
}
return true;
}
ZString ZString::goeol() {
bool set = false;
char *temp = cursor;
char *tempend = data + length;
while (cursor <= (data + length)) {
if (*cursor == '\r') {
tempend = cursor++;
set = true;
}
if (*cursor == '\n') {
if (!set)
tempend = cursor;
++cursor;
break;
}
++cursor;
}
return ZString(temp, tempend - temp);
}
ZString ZString::goeolwithContinuation() {
ZString temp = goeol();
while(ifNextInclude(" \t"))
goeol();
return temp;
}
ZString ZString::trim() {
trimTrailingWhitespace();
reset();
skipWhitespace();
return unparsed();
}
int ZString::find(ZString comparator) {
int count = 0;
char *temp = cursor;
char *temp2 = cursor;
while(!ifNext(comparator)) {
++count;
++cursor;
++temp2;
if(cursor > (data + length)) {
cursor = temp;
return -1;
}
}
cursor = temp2;
return count;
}
ZString ZString::readBlock(size_t size) {
char *temp = cursor;
cursor += size;
return ZString(temp, size);
}
char *ZString::getData() const {
return data;
}
char *ZString::getCursor() const {
return cursor;
}
void ZString::setCursor(char *cursor) {
if((cursor < data) || (cursor > (data + length)))
throw Exception("Cursor out of range on setCursor.");
this->cursor = cursor;
}
size_t ZString::getLength() const {
return length - (cursor - data);
}
void ZString::setZString(ZString zstring) {
data = zstring.getData();
length = zstring.getLength();
cursor = data;
}
ZString ZString::parsed() {
return ZString(data, cursor - data);
}
ZString ZString::unparsed() {
return ZString(cursor, data + length - cursor);
}
int ZString::setParsed() {
cursor = data + length;
return length;
}
void ZString::reset() {
cursor = data;
}
char ZString::charAt(int index) {
return *(cursor + index);
}
bool ZString::trimCRLF() {
int len = length;
// Guard both checks against an empty string - previously read *(data - 1)
// (one byte before the buffer) when length was already 0.
if ((length > 0) && (*(data + length - 1) == '\n'))
--length;
if ((length > 0) && (*(data + length - 1) == '\r'))
--length;
if (cursor >= (data + length))
cursor = data + length;
return len != length;
}
bool ZString::boolValue() {
if(this->equals("false"))
return false;
else if(this->equals("0"))
return false;
else if(this->equals(""))
return false;
return true;
}
ZString ZString::removeTrailingZeros() {
if(length > 2) {
while((*(data + length - 1)) == '0')
--length;
if(*(data + length - 1) == '.')
--length;
}
return *this;
}
char ZString::getChar() {
return *cursor;
}
char ZString::nextChar() {
char temp = 0;
if(!eod())
temp = *cursor++;
return temp;
}
int ZString::getLineNumberAtCursor() {
char *temp = cursor;
int line = 1;
while(temp != data)
if(*temp-- == '\n')
++line;
return line;
}
void ZString::moveBackToLineStart() {
while(cursor != data) {
if(*cursor == '\n') {
cursor++;
break;
}
cursor--;
}
return;
}
}