Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion src/rpcserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,17 @@ QJsonObject CRpcServer::CreateJsonRpcErrorReply ( int code, QString message )
return object;
}

// Maximum size of a single JSON-RPC request line. An unauthenticated client that

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shorter comment, please. This declaration only needs a comment saying what it is, not where it's used.

The reasoning is repeated later anyway.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you'd like to save tokens, use the Add a suggestion button, which might shortcut to Ctrl+g. The AI will still learn your preference.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The size should definitely allow about a welcome message/chat message + some slack. That's how I would define the maximum.

// sends data without a terminating newline is only ever consumed on a complete line
// (canReadLine()), so without a bound the received bytes accumulate in the socket read
// buffer without limit until the process is killed by the allocator. Requests larger
// than this, or unterminated data that fills the buffer, are rejected instead of held.
// The largest single request is a welcome or chat message, which CServer truncates to
// MAX_LEN_CHAT_TEXT (1600) characters and which is 9698 bytes as a compact JSON line
// when every character is escaped as \uXXXX; 16 KiB leaves 1.7x that, or room for a
// batch of 221 ordinary calls.
static constexpr int MAX_JSON_RPC_REQUEST_BYTES = 16 * 1024;

void CRpcServer::OnNewConnection()
{
QTcpSocket* pSocket = pTransportServer->nextPendingConnection();
Expand All @@ -120,16 +131,36 @@ void CRpcServer::OnNewConnection()

qDebug() << "- JSON-RPC: received connection from:" << pSocket->peerAddress().toString();
vecClients.append ( pSocket );
isAuthenticated[pSocket] = false;
isAuthenticated[pSocket] = false;
isDiscardingLine[pSocket] = false;

// Bound the per-connection read buffer so unterminated input cannot exhaust memory.
pSocket->setReadBufferSize ( MAX_JSON_RPC_REQUEST_BYTES );

connect ( pSocket, &QTcpSocket::disconnected, [this, pSocket]() {
qDebug() << "- JSON-RPC: connection from:" << pSocket->peerAddress().toString() << "closed";
vecClients.removeAll ( pSocket );
isAuthenticated.remove ( pSocket );
isDiscardingLine.remove ( pSocket );
pSocket->deleteLater();
} );

connect ( pSocket, &QTcpSocket::readyRead, [this, pSocket]() {
// An oversized request was already answered with an error; the rest of its line
// is discarded here so that the connection, and the authentication bound to it,
// survive and the next request is read normally.
if ( isDiscardingLine[pSocket] )
{
const QByteArray sPending = pSocket->peek ( pSocket->bytesAvailable() );
const int iEndOfLine = sPending.indexOf ( '\n' );
pSocket->read ( iEndOfLine < 0 ? sPending.size() : iEndOfLine + 1 );
if ( iEndOfLine < 0 )
{
return;
}
isDiscardingLine[pSocket] = false;
}

while ( pSocket->canReadLine() )
{
QByteArray line = pSocket->readLine();
Expand Down Expand Up @@ -197,6 +228,18 @@ void CRpcServer::OnNewConnection()
pSocket->disconnectFromHost();
return;
}

// A full buffer with no complete line is an oversized or unterminated request:
// answer it, then drop the bytes instead of holding them.
if ( !pSocket->canReadLine() && pSocket->bytesAvailable() >= MAX_JSON_RPC_REQUEST_BYTES )
{
Send ( pSocket,
QJsonDocument ( CreateJsonRpcErrorReply (
iErrParseError,
QString ( "Parse error: Request exceeds maximum size of %1 bytes" ).arg ( MAX_JSON_RPC_REQUEST_BYTES ) ) ) );
isDiscardingLine[pSocket] = true;
pSocket->read ( pSocket->bytesAvailable() );
}
} );
}

Expand Down
1 change: 1 addition & 0 deletions src/rpcserver.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ class CRpcServer : public QObject
// A map from method name to handler functions
QMap<QString, CRpcHandler> mapMethodHandlers;
QMap<QTcpSocket*, bool> isAuthenticated;
QMap<QTcpSocket*, bool> isDiscardingLine;
QVector<QTcpSocket*> vecClients;

void HandleApiAuth ( QTcpSocket* pSocket, const QJsonObject& params, QJsonObject& response );
Expand Down
Loading