[gateway,http] implement proper timeouts

Implement request timeouts in http_response_recv, use TcpConnectTimeout
as default
This commit is contained in:
akallabeth
2024-06-26 22:44:22 +02:00
parent 4bf3eac357
commit 559e770982

View File

@@ -26,6 +26,7 @@
#include <winpr/stream.h>
#include <winpr/string.h>
#include <winpr/rpc.h>
#include <winpr/sysinfo.h>
#include <freerdp/log.h>
#include <freerdp/crypto/crypto.h>
@@ -1219,44 +1220,42 @@ int http_chuncked_read(BIO* bio, BYTE* pBuffer, size_t size,
}
}
HttpResponse* http_response_recv(rdpTls* tls, BOOL readContentLength)
#define sleep_or_timeout(startMS, timeoutMS) \
sleep_or_timeout_((startMS), (timeoutMS), __FILE__, __func__, __LINE__)
static BOOL sleep_or_timeout_(UINT64 startMS, UINT32 timeoutMS, const char* file, const char* fkt,
size_t line)
{
size_t position = 0;
size_t bodyLength = 0;
size_t payloadOffset = 0;
HttpResponse* response = http_response_new();
if (!response)
return NULL;
response->ContentLength = 0;
const UINT32 timeout = freerdp_settings_get_uint32(tls->settings, FreeRDP_TcpConnectTimeout);
while (payloadOffset == 0)
USleep(100);
const UINT64 nowMS = GetTickCount64();
if (nowMS - startMS > timeoutMS)
{
DWORD level = WLOG_ERROR;
wLog* log = WLog_Get(TAG);
if (WLog_IsLevelActive(log, level))
WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, level, line, file, fkt,
"timeout [%" PRIu32 "ms] exceeded", timeoutMS);
return TRUE;
}
return FALSE;
}
static SSIZE_T http_response_recv_line(rdpTls* tls, HttpResponse* response)
{
WINPR_ASSERT(tls);
WINPR_ASSERT(response);
SSIZE_T payloadOffset = -1;
const UINT32 timeoutMS = freerdp_settings_get_uint32(tls->settings, FreeRDP_TcpConnectTimeout);
const UINT64 startMS = GetTickCount64();
while (payloadOffset <= 0)
{
size_t bodyLength = 0;
size_t position = 0;
int status = -1;
size_t s = 0;
char* end = NULL;
/* Read until we encounter \r\n\r\n */
ERR_clear_error();
const long wstatus = BIO_wait_read(tls->bio, timeout);
if (wstatus < 0)
{
if (!BIO_should_retry(tls->bio))
{
WLog_ERR(TAG, "[BIO_wait_read] Retries exceeded");
ERR_print_errors_cb(print_bio_error, NULL);
goto out_error;
}
USleep(100);
continue;
}
else if (wstatus == 0)
{
WLog_ERR(TAG, "[BIO_wait] timeout exceeded");
ERR_print_errors_cb(print_bio_error, NULL);
goto out_error;
}
status = BIO_read(tls->bio, Stream_Pointer(response->data), 1);
if (status <= 0)
@@ -1268,7 +1267,8 @@ HttpResponse* http_response_recv(rdpTls* tls, BOOL readContentLength)
goto out_error;
}
USleep(100);
if (sleep_or_timeout(startMS, timeoutMS))
goto out_error;
continue;
}
@@ -1299,6 +1299,132 @@ HttpResponse* http_response_recv(rdpTls* tls, BOOL readContentLength)
payloadOffset = Stream_GetPosition(response->data);
}
out_error:
return payloadOffset;
}
static BOOL http_response_recv_body(rdpTls* tls, HttpResponse* response, BOOL readContentLength,
size_t payloadOffset, size_t bodyLength)
{
BOOL rc = FALSE;
WINPR_ASSERT(tls);
WINPR_ASSERT(response);
const UINT64 startMS = GetTickCount64();
const UINT32 timeoutMS = freerdp_settings_get_uint32(tls->settings, FreeRDP_TcpConnectTimeout);
if ((response->TransferEncoding == TransferEncodingChunked) && readContentLength)
{
http_encoding_chunked_context ctx = { 0 };
ctx.state = ChunkStateLenghHeader;
ctx.nextOffset = 0;
ctx.headerFooterPos = 0;
int full_len = 0;
do
{
if (!Stream_EnsureRemainingCapacity(response->data, 2048))
goto out_error;
int status = http_chuncked_read(tls->bio, Stream_Pointer(response->data),
Stream_GetRemainingCapacity(response->data), &ctx);
if (status <= 0)
{
if (!BIO_should_retry(tls->bio))
{
WLog_ERR(TAG, "Retries exceeded");
ERR_print_errors_cb(print_bio_error, NULL);
goto out_error;
}
if (sleep_or_timeout(startMS, timeoutMS))
goto out_error;
}
else
{
Stream_Seek(response->data, (size_t)status);
full_len += status;
}
} while (ctx.state != ChunkStateEnd);
response->BodyLength = full_len;
if (response->BodyLength > 0)
response->BodyContent = &(Stream_Buffer(response->data))[payloadOffset];
}
else
{
while (response->BodyLength < bodyLength)
{
int status = 0;
if (!Stream_EnsureRemainingCapacity(response->data, bodyLength - response->BodyLength))
goto out_error;
ERR_clear_error();
status = BIO_read(tls->bio, Stream_Pointer(response->data),
bodyLength - response->BodyLength);
if (status <= 0)
{
if (!BIO_should_retry(tls->bio))
{
WLog_ERR(TAG, "Retries exceeded");
ERR_print_errors_cb(print_bio_error, NULL);
goto out_error;
}
if (sleep_or_timeout(startMS, timeoutMS))
goto out_error;
continue;
}
Stream_Seek(response->data, (size_t)status);
response->BodyLength += (unsigned long)status;
if (response->BodyLength > RESPONSE_SIZE_LIMIT)
{
WLog_ERR(TAG, "Request body too large! (%" PRIdz " bytes) Aborting!",
response->BodyLength);
goto out_error;
}
}
if (response->BodyLength > 0)
response->BodyContent = &(Stream_Buffer(response->data))[payloadOffset];
if (bodyLength != response->BodyLength)
{
WLog_WARN(TAG, "%s unexpected body length: actual: %" PRIuz ", expected: %" PRIuz,
response->ContentType, response->BodyLength, bodyLength);
if (bodyLength > 0)
response->BodyLength = MIN(bodyLength, response->BodyLength);
}
/* '\0' terminate the http body */
if (!Stream_EnsureRemainingCapacity(response->data, sizeof(UINT16)))
goto out_error;
Stream_Write_UINT16(response->data, 0);
}
rc = TRUE;
out_error:
return rc;
}
HttpResponse* http_response_recv(rdpTls* tls, BOOL readContentLength)
{
size_t bodyLength = 0;
HttpResponse* response = http_response_new();
if (!response)
return NULL;
response->ContentLength = 0;
const SSIZE_T payloadOffset = http_response_recv_line(tls, response);
if (payloadOffset < 0)
goto out_error;
if (payloadOffset)
{
size_t count = 0;
@@ -1370,96 +1496,8 @@ HttpResponse* http_response_recv(rdpTls* tls, BOOL readContentLength)
}
/* Fetch remaining body! */
if ((response->TransferEncoding == TransferEncodingChunked) && readContentLength)
{
http_encoding_chunked_context ctx = { 0 };
ctx.state = ChunkStateLenghHeader;
ctx.nextOffset = 0;
ctx.headerFooterPos = 0;
int full_len = 0;
do
{
if (!Stream_EnsureRemainingCapacity(response->data, 2048))
goto out_error;
int status = http_chuncked_read(tls->bio, Stream_Pointer(response->data),
Stream_GetRemainingCapacity(response->data), &ctx);
if (status <= 0)
{
if (!BIO_should_retry(tls->bio))
{
WLog_ERR(TAG, "Retries exceeded");
ERR_print_errors_cb(print_bio_error, NULL);
goto out_error;
}
USleep(100);
}
else
{
Stream_Seek(response->data, (size_t)status);
full_len += status;
}
} while (ctx.state != ChunkStateEnd);
response->BodyLength = full_len;
if (response->BodyLength > 0)
response->BodyContent = &(Stream_Buffer(response->data))[payloadOffset];
}
else
{
while (response->BodyLength < bodyLength)
{
int status = 0;
if (!Stream_EnsureRemainingCapacity(response->data,
bodyLength - response->BodyLength))
goto out_error;
ERR_clear_error();
status = BIO_read(tls->bio, Stream_Pointer(response->data),
bodyLength - response->BodyLength);
if (status <= 0)
{
if (!BIO_should_retry(tls->bio))
{
WLog_ERR(TAG, "Retries exceeded");
ERR_print_errors_cb(print_bio_error, NULL);
goto out_error;
}
USleep(100);
continue;
}
Stream_Seek(response->data, (size_t)status);
response->BodyLength += (unsigned long)status;
if (response->BodyLength > RESPONSE_SIZE_LIMIT)
{
WLog_ERR(TAG, "Request body too large! (%" PRIdz " bytes) Aborting!",
response->BodyLength);
goto out_error;
}
}
if (response->BodyLength > 0)
response->BodyContent = &(Stream_Buffer(response->data))[payloadOffset];
if (bodyLength != response->BodyLength)
{
WLog_WARN(TAG, "%s unexpected body length: actual: %" PRIuz ", expected: %" PRIuz,
response->ContentType, response->BodyLength, bodyLength);
if (bodyLength > 0)
response->BodyLength = MIN(bodyLength, response->BodyLength);
}
/* '\0' terminate the http body */
if (!Stream_EnsureRemainingCapacity(response->data, sizeof(UINT16)))
goto out_error;
Stream_Write_UINT16(response->data, 0);
}
if (!http_response_recv_body(tls, response, readContentLength, payloadOffset, bodyLength))
goto out_error;
}
Stream_SealLength(response->data);