Files
CoreUtils/File.cpp
T
Brad Arant 78c45d378c Fix File stat-before-open crash risk, JSON string-quoting bug, and MString buffer overflow
- File: stat() only after a successful open() (previously ran first with
  its result ignored, feeding a missing file's garbage st_size into a
  realloc - could crash the whole APIServer on a request for a
  nonexistent static asset)
- JString::setString(): always quote+escape the value instead of going
  through set()'s literal-value guess, which misclassified any string
  starting with digits (e.g. a UUID) as a bare numeric literal and wrote
  it unquoted, corrupting later edits to the same JSON document
- MString::remove() (both overloads): fixed an off-by-one that read one
  byte past the buffer on a completely full string (found via ASan under
  concurrent load)
- CGIFormData: added fields() so callers can iterate every parsed field
  without knowing names in advance (used to merge POST body into
  jet::Global::variables the same way query-string params already are)
2026-09-03 05:14:24 +00:00

105 lines
2.8 KiB
C++

#include "File.h"
#include "Exception.h"
#include <sys/stat.h>
#include <iostream>
#include <sstream>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
namespace coreutils {
File::File(coreutils::ZString fileName, int mode, int authority) {
open(fileName, mode, authority);
}
void File::open(coreutils::ZString fileName, int mode, int authority) {
this->fileName = fileName;
buffer = NULL;
fd = ::open(fileName.c_str(), mode, authority);
if(fd < 0) {
std::stringstream temp;
temp << "Error opening file " << fileName << ".";
throw Exception(temp.str(), __FILE__, __LINE__);
}
// stat() only after a successful open(), and only used to size the
// read buffer - previously this ran BEFORE the open()/fd check with
// its return value ignored, so a missing file left the stat struct fully
// uninitialized and fed that garbage st_size straight into
// setBufferSize()'s realloc(), before the open-failure path ever
// got a chance to throw its clean Exception. Found while adding a
// static-asset route (GET /assets/{filename}) that legitimately
// hits this path on every request for a nonexistent file - a
// garbage-sized realloc there could abort/crash the whole
// long-running APIServer process (shared with every other live
// route) instead of cleanly 500ing just that one request.
struct stat status;
if (fstat(fd, &status) != 0) {
std::stringstream temp;
temp << "Error statting file " << fileName << ".";
throw Exception(temp.str(), __FILE__, __LINE__);
}
setBufferSize(status.st_size);
}
File::~File() {
close(fd);
free(buffer);
}
void File::setBufferSize(size_t size) {
buffer = (char *)realloc(buffer, size);
this->size = size;
}
int File::read() {
size = ::read(fd, buffer, size);
setBufferSize(size);
return size;
}
int File::readLine() {
setBufferSize(4096);
size = 0;
int len = ::read(fd, buffer + size, 1);
if(len == 0)
mEof = true;
while(!mEof && (*(buffer + size) != '\n')) {
if(*(buffer + size) != '\r') {
// setBufferSize(size);
++size;
}
len = ::read(fd, buffer + size, 1);
if(len == 0)
mEof = true;
}
return size;
}
void File::write(std::string data) {
::write(fd, data.c_str(), data.length());
}
void File::write(coreutils::ZString &data) {
int len = ::write(fd, data.getData(), data.getLength());
}
std::string File::asString() {
return std::string(buffer, size);
}
coreutils::ZString File::asZString() {
return ZString(buffer, size);
}
bool File::eof() {
return mEof;
}
}