Details
Description
When using QNAM to send a POST request, if there's an unexpected EOF or an error in the server's reply, QNAM sends the request again.
For instance:
QNetworkAccessManager qnam; QNetworkRequest req(QUrl("http://localhost:12345/service")); auto reply = qnam.post(req, "hello\n");
Run a local faux server with something like nc -l 12345 -k. When the request arrives, type something into nc and press return – QNAM will re-send the POST.
A quick analysis of the problem:
QHttpProtocolHandler::_q_receiveReply will call m_channel->handleUnexpectedEOF(); in a number of cases. The common case seems to be if the server side times out and closes the connection after having sent a partial reply.¹
QHttpNetworkConnectionChannel::handleUnexpectedEOF will then try again, for a maximum of 4 times, before giving up. It will however not check if it is allowed to re-send the request (i.e. the request is idempotent). This will cause a POST to be sent again, causing data losses.
¹ But also in case of garbage, like the demo above shows. For instance if you just type nonsense, QHttpHeaderParser::parseStatus returns false. That causes QHttpNetworkReplyPrivate::readStatus to return -1:
bool ok = parseStatus(fragment); state = ReadingHeaderState; fragment.clear(); if (!ok) { return -1; }
but -1 is for EOF, not for parsing errors! This will also end up in QHttpNetworkConnectionChannel::handleUnexpectedEOF, and eventually cause it to emit the wrong error (RemoteHostClosedError, instead of a protocol-level error – "server sent garbage").