Skip to content

Commit

Permalink
Fixed a race condition in qhttp response processing
Browse files Browse the repository at this point in the history
  • Loading branch information
iagaponenko committed Jan 26, 2024
1 parent aa774e1 commit 9139e22
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 13 deletions.
29 changes: 17 additions & 12 deletions src/qhttp/Response.cc
Original file line number Diff line number Diff line change
Expand Up @@ -145,32 +145,36 @@ void Response::sendStatus(Status status) {
}

void Response::send(std::string const& content, std::string const& contentType) {
if (_startTransmit()) return;

headers["Content-Type"] = contentType;
headers["Content-Length"] = std::to_string(content.length());

_responseBuf = _headers() + "\r\n" + content;
_startTransmit();
asio::async_write(*_socket, asio::buffer(_responseBuf.data(), _responseBuf.size()),
[self = shared_from_this()](boost::system::error_code const& ec, std::size_t sent) {
self->_finishTransmit(ec, sent);
});
}

void Response::sendFile(fs::path const& path) {
_bytesRemaining = fs::file_size(path);
auto ct = contentTypesByExtension.find(path.extension().string());
headers["Content-Type"] = (ct != contentTypesByExtension.end()) ? ct->second : "text/plain";
headers["Content-Length"] = std::to_string(_bytesRemaining);

// Try to open the file. Throw if we hit a snag; exception expected to be caught by
// top-level handler in Server::_dispatchRequest().
_fileName = path.string();
_inFile.open(_fileName, std::ios::binary);
_inFile.open(path.string(), std::ios::binary);
if (!_inFile.is_open()) {
LOGLS_ERROR(_log, logger(_server) << logger(_socket) << "open failed for " << _fileName << ": "
LOGLS_ERROR(_log, logger(_server) << logger(_socket) << "open failed for " << path << ": "
<< std::strerror(errno));
throw(boost::system::system_error(errno, boost::system::generic_category()));
}
if (_startTransmit()) {
_inFile.close();
return;
}
_fileName = path.string();
_bytesRemaining = fs::file_size(path);
auto ct = contentTypesByExtension.find(path.extension().string());
headers["Content-Type"] = (ct != contentTypesByExtension.end()) ? ct->second : "text/plain";
headers["Content-Length"] = std::to_string(_bytesRemaining);

// Make the initial allocation of the response buffer. For smaller files
// the buffer should be large enough to accomodate both the header and
Expand All @@ -189,7 +193,6 @@ void Response::sendFile(fs::path const& path) {
}

// Start reading the file payload into the buffer and transmitting a series of records.
_startTransmit();
std::string::size_type const pos = hdrSize;
std::size_t const size = std::min(_bytesRemaining, _responseBuf.size() - pos);
_sendFileRecord(pos, size);
Expand All @@ -214,11 +217,13 @@ std::string Response::_headers() const {
return headerStream.str();
}

void Response::_startTransmit() {
if (_transmissionStarted.test_and_set()) {
bool Response::_startTransmit() {
const bool multipleResponses = _transmissionStarted.test_and_set();
if (multipleResponses) {
LOGLS_ERROR(_log, logger(_server)
<< logger(_socket) << "handler logic error: multiple responses sent");
}
return multipleResponses;
}

void Response::_finishTransmit(boost::system::error_code const& ec, std::size_t sent) {
Expand Down
7 changes: 6 additions & 1 deletion src/qhttp/Response.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,12 @@ class Response : public std::enable_shared_from_this<Response> {
DoneCallback const& doneCallback, std::size_t const maxResponseBufSize);

std::string _headers() const;
void _startTransmit();

//----- Mark a start of transmission. Return 'true' if multiple transmission attempts
// were detected. Duplicate transmission requests will be ignored. An error message
// may be logged. The ongoing transmission won't be affected.

bool _startTransmit();
void _finishTransmit(boost::system::error_code const& ec, std::size_t sent);
void _sendFileRecord(std::string::size_type pos, std::size_t size);

Expand Down

0 comments on commit 9139e22

Please sign in to comment.