20#include <freerdp/config.h>
26#include <winpr/print.h>
27#include <winpr/stream.h>
28#include <winpr/string.h>
30#include <winpr/sysinfo.h>
32#include <freerdp/log.h>
33#include <freerdp/crypto/crypto.h>
36#include <winpr/crypto.h>
38#ifdef FREERDP_HAVE_VALGRIND_MEMCHECK_H
39#include <valgrind/memcheck.h>
46#define TAG FREERDP_TAG("core.gateway.http")
48#define RESPONSE_SIZE_LIMIT (64ULL * 1024ULL * 1024ULL)
50#define WEBSOCKET_MAGIC_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
58 BOOL websocketUpgrade;
59 char* SecWebsocketKey;
60 wListDictionary* cookies;
72 TRANSFER_ENCODING TransferEncoding;
86 TRANSFER_ENCODING TransferEncoding;
87 char* SecWebsocketVersion;
88 char* SecWebsocketAccept;
93 wHashTable* Authenticates;
94 wHashTable* SetCookie;
98static wHashTable* HashTable_New_String(
void);
99#define sleep_or_timeout(bio, context, startMS, timeoutMS) \
100 sleep_or_timeout_((bio), (context), (startMS), (timeoutMS), __FILE__, __func__, __LINE__)
101static BOOL sleep_or_timeout_(BIO* bio, rdpContext* context, UINT64 startMS, UINT32 timeoutMS,
102 const char* file,
const char* fkt,
size_t line);
104static BOOL strings_equals_nocase(
const void* obj1,
const void* obj2)
109 return _stricmp(obj1, obj2) == 0;
112HttpContext* http_context_new(
void)
114 HttpContext* context = (HttpContext*)calloc(1,
sizeof(HttpContext));
118 context->headers = HashTable_New_String();
119 if (!context->headers)
122 context->cookies = ListDictionary_New(FALSE);
123 if (!context->cookies)
127 wObject* key = ListDictionary_KeyObject(context->cookies);
128 wObject* value = ListDictionary_ValueObject(context->cookies);
142 WINPR_PRAGMA_DIAG_PUSH
143 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
144 http_context_free(context);
145 WINPR_PRAGMA_DIAG_POP
149BOOL http_context_set_method(HttpContext* context,
const char* Method)
151 if (!context || !Method)
154 free(context->Method);
155 context->Method = _strdup(Method);
157 return (context->Method !=
nullptr);
160BOOL http_request_set_content_type(HttpRequest* request,
const char* ContentType)
162 if (!request || !ContentType)
165 return http_request_set_header(request,
"Content-Type",
"%s", ContentType);
168const char* http_context_get_uri(HttpContext* context)
176BOOL http_context_set_uri(HttpContext* context,
const char* URI)
178 if (!context || !URI)
182 context->URI = _strdup(URI);
184 return (context->URI !=
nullptr);
187BOOL http_context_set_user_agent(HttpContext* context,
const char* UserAgent)
189 if (!context || !UserAgent)
192 return http_context_set_header(context,
"User-Agent",
"%s", UserAgent);
195BOOL http_context_set_x_ms_user_agent(HttpContext* context,
const char* X_MS_UserAgent)
197 if (!context || !X_MS_UserAgent)
200 return http_context_set_header(context,
"X-MS-User-Agent",
"%s", X_MS_UserAgent);
203BOOL http_context_set_host(HttpContext* context,
const char* Host)
205 if (!context || !Host)
208 return http_context_set_header(context,
"Host",
"%s", Host);
211BOOL http_context_set_accept(HttpContext* context,
const char* Accept)
213 if (!context || !Accept)
216 return http_context_set_header(context,
"Accept",
"%s", Accept);
219BOOL http_context_set_cache_control(HttpContext* context,
const char* CacheControl)
221 if (!context || !CacheControl)
224 return http_context_set_header(context,
"Cache-Control",
"%s", CacheControl);
227BOOL http_context_set_connection(HttpContext* context,
const char* Connection)
229 if (!context || !Connection)
232 free(context->Connection);
233 context->Connection = _strdup(Connection);
235 return (context->Connection !=
nullptr);
238WINPR_ATTR_FORMAT_ARG(2, 0)
239static BOOL list_append(HttpContext* context, WINPR_FORMAT_ARG const
char* str, va_list ap)
242 va_list vat = WINPR_C_ARRAY_INIT;
243 char* Pragma =
nullptr;
244 size_t PragmaSize = 0;
247 const int size = winpr_vasprintf(&Pragma, &PragmaSize, str, ap);
254 char* sstr =
nullptr;
258 winpr_asprintf(&sstr, &slen,
"%s, %s", context->Pragma, Pragma);
265 free(context->Pragma);
266 context->Pragma = sstr;
276WINPR_ATTR_FORMAT_ARG(2, 3)
277BOOL http_context_set_pragma(HttpContext* context, WINPR_FORMAT_ARG const
char* Pragma, ...)
279 if (!context || !Pragma)
282 free(context->Pragma);
283 context->Pragma =
nullptr;
285 va_list ap = WINPR_C_ARRAY_INIT;
286 va_start(ap, Pragma);
287 return list_append(context, Pragma, ap);
290WINPR_ATTR_FORMAT_ARG(2, 3)
291BOOL http_context_append_pragma(HttpContext* context, const
char* Pragma, ...)
293 if (!context || !Pragma)
296 va_list ap = WINPR_C_ARRAY_INIT;
297 va_start(ap, Pragma);
298 return list_append(context, Pragma, ap);
301BOOL http_context_set_rdg_connection_id(HttpContext* context)
306 GUID RdgConnectionId;
307 if (UuidCreate(&RdgConnectionId) != RPC_S_OK)
310 char buffer[64] = WINPR_C_ARRAY_INIT;
311 return http_context_set_header(context,
"RDG-Connection-Id",
"{%s}",
312 guid2str(&RdgConnectionId, buffer,
sizeof(buffer)));
315BOOL http_context_set_rdg_correlation_id(HttpContext* context,
const GUID* RdgCorrelationId)
317 if (!context || !RdgCorrelationId)
320 char buffer[64] = WINPR_C_ARRAY_INIT;
321 return http_context_set_header(context,
"RDG-Correlation-Id",
"{%s}",
322 guid2str(RdgCorrelationId, buffer,
sizeof(buffer)));
325BOOL http_context_enable_websocket_upgrade(HttpContext* context, BOOL enable)
327 WINPR_ASSERT(context);
331 GUID key = WINPR_C_ARRAY_INIT;
332 if (RPC_S_OK != UuidCreate(&key))
335 free(context->SecWebsocketKey);
336 context->SecWebsocketKey = crypto_base64_encode((BYTE*)&key,
sizeof(key));
337 if (!context->SecWebsocketKey)
341 context->websocketUpgrade = enable;
345BOOL http_context_is_websocket_upgrade_enabled(HttpContext* context)
347 return context->websocketUpgrade;
350BOOL http_context_set_rdg_auth_scheme(HttpContext* context,
const char* RdgAuthScheme)
352 if (!context || !RdgAuthScheme)
355 return http_context_set_header(context,
"RDG-Auth-Scheme",
"%s", RdgAuthScheme);
358BOOL http_context_set_cookie(HttpContext* context,
const char* CookieName,
const char* CookieValue)
360 if (!context || !CookieName || !CookieValue)
362 if (ListDictionary_Contains(context->cookies, CookieName))
364 if (!ListDictionary_SetItemValue(context->cookies, CookieName, CookieValue))
369 if (!ListDictionary_Add(context->cookies, CookieName, CookieValue))
375void http_context_free(HttpContext* context)
379 free(context->SecWebsocketKey);
381 free(context->Method);
382 free(context->Connection);
383 free(context->Pragma);
384 HashTable_Free(context->headers);
385 ListDictionary_Free(context->cookies);
390BOOL http_request_set_method(HttpRequest* request,
const char* Method)
392 if (!request || !Method)
395 free(request->Method);
396 request->Method = _strdup(Method);
398 return (request->Method !=
nullptr);
401BOOL http_request_set_uri(HttpRequest* request,
const char* URI)
403 if (!request || !URI)
407 request->URI = _strdup(URI);
409 return (request->URI !=
nullptr);
412BOOL http_request_set_auth_scheme(HttpRequest* request,
const char* AuthScheme)
414 if (!request || !AuthScheme)
417 free(request->AuthScheme);
418 request->AuthScheme = _strdup(AuthScheme);
420 return (request->AuthScheme !=
nullptr);
423BOOL http_request_set_auth_param(HttpRequest* request,
const char* AuthParam)
425 if (!request || !AuthParam)
428 free(request->AuthParam);
429 request->AuthParam = _strdup(AuthParam);
431 return (request->AuthParam !=
nullptr);
434BOOL http_request_set_transfer_encoding(HttpRequest* request, TRANSFER_ENCODING TransferEncoding)
436 if (!request || TransferEncoding == TransferEncodingUnknown)
439 request->TransferEncoding = TransferEncoding;
444WINPR_ATTR_FORMAT_ARG(2, 3)
445static BOOL http_encode_print(
wStream* s, WINPR_FORMAT_ARG const
char* fmt, ...)
448 va_list ap = WINPR_C_ARRAY_INIT;
456 length = vsnprintf(
nullptr, 0, fmt, ap) + 1;
459 if (!Stream_EnsureRemainingCapacity(s, (
size_t)length))
462 str = (
char*)Stream_Pointer(s);
464 used = vsnprintf(str, (
size_t)length, fmt, ap);
468 if ((used + 1) != length)
471 Stream_Seek(s, (
size_t)used);
475static BOOL http_encode_body_line(
wStream* s,
const char* param,
const char* value)
477 if (!s || !param || !value)
480 return http_encode_print(s,
"%s: %s\r\n", param, value);
483static BOOL http_encode_content_length_line(
wStream* s,
size_t ContentLength)
485 return http_encode_print(s,
"Content-Length: %" PRIuz
"\r\n", ContentLength);
488static BOOL http_encode_header_line(
wStream* s,
const char* Method,
const char* URI)
490 if (!s || !Method || !URI)
493 return http_encode_print(s,
"%s %s HTTP/1.1\r\n", Method, URI);
496static BOOL http_encode_authorization_line(
wStream* s,
const char* AuthScheme,
497 const char* AuthParam)
499 if (!s || !AuthScheme || !AuthParam)
502 return http_encode_print(s,
"Authorization: %s %s\r\n", AuthScheme, AuthParam);
505static BOOL http_encode_cookie_line(
wStream* s, wListDictionary* cookies)
507 ULONG_PTR* keys =
nullptr;
513 ListDictionary_Lock(cookies);
514 const size_t count = ListDictionary_GetKeys(cookies, &keys);
519 status = http_encode_print(s,
"Cookie: ");
523 for (
size_t x = 0; status && x < count; x++)
525 char* cur = (
char*)ListDictionary_GetItemValue(cookies, (
void*)keys[x]);
533 status = http_encode_print(s,
"; ");
537 status = http_encode_print(s,
"%s=%s", (
char*)keys[x], cur);
540 status = http_encode_print(s,
"\r\n");
543 ListDictionary_Unlock(cookies);
547static BOOL write_headers(
const void* pkey,
void* pvalue,
void* arg)
549 const char* key = pkey;
550 const char* value = pvalue;
557 return http_encode_body_line(s, key, value);
560wStream* http_request_write(HttpContext* context, HttpRequest* request)
562 if (!context || !request)
565 wStream* s = Stream_New(
nullptr, 1024);
570 if (!http_encode_header_line(s, request->Method, request->URI) ||
572 !http_encode_body_line(s,
"Pragma", context->Pragma))
575 if (!context->websocketUpgrade)
577 if (!http_encode_body_line(s,
"Connection", context->Connection))
582 if (!http_encode_body_line(s,
"Connection",
"Upgrade") ||
583 !http_encode_body_line(s,
"Upgrade",
"websocket") ||
584 !http_encode_body_line(s,
"Sec-Websocket-Version",
"13") ||
585 !http_encode_body_line(s,
"Sec-Websocket-Key", context->SecWebsocketKey))
589 if (request->TransferEncoding != TransferEncodingIdentity)
591 if (request->TransferEncoding == TransferEncodingChunked)
593 if (!http_encode_body_line(s,
"Transfer-Encoding",
"chunked"))
601 if (!http_encode_content_length_line(s, request->ContentLength))
605 if (!utils_str_is_empty(request->Authorization))
607 if (!http_encode_body_line(s,
"Authorization", request->Authorization))
610 else if (!utils_str_is_empty(request->AuthScheme) && !utils_str_is_empty(request->AuthParam))
612 if (!http_encode_authorization_line(s, request->AuthScheme, request->AuthParam))
616 if (!HashTable_Foreach(context->headers, write_headers, s))
619 if (!HashTable_Foreach(request->headers, write_headers, s))
622 if (!http_encode_cookie_line(s, context->cookies))
625 if (!http_encode_print(s,
"\r\n"))
628 Stream_SealLength(s);
631 Stream_Free(s, TRUE);
635HttpRequest* http_request_new(
void)
637 HttpRequest* request = (HttpRequest*)calloc(1,
sizeof(HttpRequest));
641 request->headers = HashTable_New_String();
642 if (!request->headers)
644 request->TransferEncoding = TransferEncodingIdentity;
647 http_request_free(request);
651void http_request_free(HttpRequest* request)
656 free(request->AuthParam);
657 free(request->AuthScheme);
658 free(request->Authorization);
659 free(request->Method);
661 HashTable_Free(request->headers);
665static BOOL http_response_parse_header_status_line(HttpResponse* response,
const char* status_line)
668 char* separator =
nullptr;
669 char* status_code =
nullptr;
675 separator = strchr(status_line,
' ');
680 status_code = separator + 1;
681 separator = strchr(status_code,
' ');
687 const char* reason_phrase = separator + 1;
691 long val = strtol(status_code,
nullptr, 0);
693 if ((errno != 0) || (val < 0) || (val > INT16_MAX))
696 response->StatusCode = (UINT16)val;
698 free(response->ReasonPhrase);
699 response->ReasonPhrase = _strdup(reason_phrase);
702 if (!response->ReasonPhrase)
710 WLog_ERR(TAG,
"http_response_parse_header_status_line failed [%s]", status_line);
715static BOOL http_response_parse_header_field(HttpResponse* response,
const char* name,
718 WINPR_ASSERT(response);
723 if (_stricmp(name,
"Content-Length") == 0)
725 unsigned long long val = 0;
727 val = _strtoui64(value,
nullptr, 0);
729 if ((errno != 0) || (val > INT32_MAX))
732 response->ContentLength = WINPR_ASSERTING_INT_CAST(
size_t, val);
736 if (_stricmp(name,
"Content-Type") == 0)
738 free(response->ContentType);
739 response->ContentType = _strdup(value);
741 return response->ContentType !=
nullptr;
744 if (_stricmp(name,
"Transfer-Encoding") == 0)
746 if (_stricmp(value,
"identity") == 0)
747 response->TransferEncoding = TransferEncodingIdentity;
748 else if (_stricmp(value,
"chunked") == 0)
749 response->TransferEncoding = TransferEncodingChunked;
751 response->TransferEncoding = TransferEncodingUnknown;
756 if (_stricmp(name,
"Sec-WebSocket-Version") == 0)
758 free(response->SecWebsocketVersion);
759 response->SecWebsocketVersion = _strdup(value);
761 return response->SecWebsocketVersion !=
nullptr;
764 if (_stricmp(name,
"Sec-WebSocket-Accept") == 0)
766 free(response->SecWebsocketAccept);
767 response->SecWebsocketAccept = _strdup(value);
769 return response->SecWebsocketAccept !=
nullptr;
772 if (_stricmp(name,
"WWW-Authenticate") == 0)
774 const char* authScheme = value;
775 const char* authValue =
"";
776 char* separator = strchr(value,
' ');
787 authValue = separator + 1;
790 return HashTable_Insert(response->Authenticates, authScheme, authValue);
793 if (_stricmp(name,
"Set-Cookie") == 0)
795 char* separator = strchr(value,
'=');
805 const char* CookieName = value;
806 char* CookieValue = separator + 1;
808 if (*CookieValue ==
'"')
810 char* p = CookieValue;
811 while (*p !=
'"' && *p !=
'\0')
821 char* p = CookieValue;
822 while (*p !=
';' && *p !=
'\0' && *p !=
' ')
828 return HashTable_Insert(response->SetCookie, CookieName, CookieValue);
835static BOOL http_response_parse_header(HttpResponse* response)
839 char* line =
nullptr;
840 char* name =
nullptr;
841 char* colon_pos =
nullptr;
842 char* end_of_header =
nullptr;
843 char end_of_header_char = 0;
848 if (!response->lines)
851 if (!http_response_parse_header_status_line(response, response->lines[0]))
854 for (
size_t count = 1; count < response->count; count++)
856 line = response->lines[count];
868 colon_pos = strchr(line,
':');
872 if ((colon_pos ==
nullptr) || (colon_pos == line))
876 for (end_of_header = colon_pos; end_of_header != line; end_of_header--)
878 c = end_of_header[-1];
880 if (c !=
' ' && c !=
'\t' && c !=
':')
884 if (end_of_header == line)
887 end_of_header_char = *end_of_header;
888 *end_of_header =
'\0';
892 char* value = colon_pos + 1;
893 for (; *value; value++)
895 if ((*value !=
' ') && (*value !=
'\t'))
899 const int res = http_response_parse_header_field(response, name, value);
900 *end_of_header = end_of_header_char;
909 WLog_ERR(TAG,
"parsing failed");
914static void http_response_print(wLog* log, DWORD level,
const HttpResponse* response,
915 const char* file,
size_t line,
const char* fkt)
917 char buffer[64] = WINPR_C_ARRAY_INIT;
920 WINPR_ASSERT(response);
922 if (!WLog_IsLevelActive(log, level))
925 const long status = http_response_get_status_code(response);
926 WLog_PrintTextMessage(log, level, line, file, fkt,
"HTTP status: %s",
927 freerdp_http_status_string_format(status, buffer, ARRAYSIZE(buffer)));
929 if (WLog_IsLevelActive(log, WLOG_DEBUG))
931 for (
size_t i = 0; i < response->count; i++)
932 WLog_PrintTextMessage(log, WLOG_DEBUG, line, file, fkt,
"[%" PRIuz
"] %s", i,
936 if (response->ReasonPhrase)
937 WLog_PrintTextMessage(log, level, line, file, fkt,
"[reason] %s", response->ReasonPhrase);
939 if (WLog_IsLevelActive(log, WLOG_TRACE))
941 WLog_PrintTextMessage(log, WLOG_TRACE, line, file, fkt,
"[body][%" PRIuz
"] %s",
942 response->BodyLength, response->BodyContent);
946static BOOL http_use_content_length(
const char* cur)
953 if (_strnicmp(cur,
"application/rpc", 15) == 0)
955 else if (_strnicmp(cur,
"text/plain", 10) == 0)
957 else if (_strnicmp(cur,
"text/html", 9) == 0)
959 else if (_strnicmp(cur,
"application/json", 16) == 0)
983static int print_bio_error(
const char* str,
size_t len,
void* bp)
988 WLog_Print(log, WLOG_ERROR,
"%s", str);
994int http_chuncked_read(BIO* bio, rdpContext* context, BYTE* pBuffer,
size_t size,
998 int effectiveDataLen = 0;
1000 WINPR_ASSERT(pBuffer);
1001 WINPR_ASSERT(encodingContext !=
nullptr);
1002 WINPR_ASSERT(size <= INT32_MAX);
1004 WINPR_ASSERT(context);
1005 const UINT32 timeoutMS =
1007 const UINT64 startMS = GetTickCount64();
1010 switch (encodingContext->state)
1012 case ChunkStateData:
1015 (size > encodingContext->nextOffset ? encodingContext->nextOffset : size);
1020 status = BIO_read(bio, pBuffer, (
int)rd);
1023 if (sleep_or_timeout(bio, context, startMS, timeoutMS))
1025 return (effectiveDataLen > 0 ? effectiveDataLen : 0);
1028 encodingContext->nextOffset -= WINPR_ASSERTING_INT_CAST(uint32_t, status);
1029 if (encodingContext->nextOffset == 0)
1031 encodingContext->state = ChunkStateFooter;
1032 encodingContext->headerFooterPos = 0;
1034 effectiveDataLen += status;
1036 if ((
size_t)status == size)
1037 return effectiveDataLen;
1040 size -= (size_t)status;
1043 case ChunkStateFooter:
1045 char _dummy[2] = WINPR_C_ARRAY_INIT;
1046 WINPR_ASSERT(encodingContext->nextOffset == 0);
1047 WINPR_ASSERT(encodingContext->headerFooterPos < 2);
1049 status = BIO_read(bio, _dummy, (
int)(2 - encodingContext->headerFooterPos));
1052 if (sleep_or_timeout(bio, context, startMS, timeoutMS))
1054 return (effectiveDataLen > 0 ? effectiveDataLen : status);
1058 encodingContext->headerFooterPos += (size_t)status;
1059 if (encodingContext->headerFooterPos == 2)
1061 encodingContext->state = ChunkStateLenghHeader;
1062 encodingContext->headerFooterPos = 0;
1067 case ChunkStateLenghHeader:
1069 BOOL _haveNewLine = FALSE;
1070 char* dst = &encodingContext->lenBuffer[encodingContext->headerFooterPos];
1071 WINPR_ASSERT(encodingContext->nextOffset == 0);
1072 while (encodingContext->headerFooterPos < 10 && !_haveNewLine)
1075 status = BIO_read(bio, dst, 1);
1078 if (sleep_or_timeout(bio, context, startMS, timeoutMS))
1080 return (effectiveDataLen > 0 ? effectiveDataLen : 0);
1085 _haveNewLine = TRUE;
1086 encodingContext->headerFooterPos += (size_t)status;
1094 size_t tmp = strtoul(encodingContext->lenBuffer,
nullptr, 16);
1095 if ((errno != 0) || (tmp > SIZE_MAX))
1098 encodingContext->nextOffset = 0;
1099 encodingContext->state = ChunkStateEnd;
1102 encodingContext->nextOffset = tmp;
1103 encodingContext->state = ChunkStateData;
1105 if (encodingContext->nextOffset == 0)
1107 WLog_DBG(TAG,
"chunked encoding end of stream received");
1108 encodingContext->headerFooterPos = 0;
1109 encodingContext->state = ChunkStateEnd;
1110 return (effectiveDataLen > 0 ? effectiveDataLen : 0);
1121BOOL sleep_or_timeout_(BIO* bio, rdpContext* context, UINT64 startMS, UINT32 timeoutMS,
1122 const char* file,
const char* fkt,
size_t line)
1125 WINPR_ASSERT(context);
1128 const UINT64 nowMS = GetTickCount64();
1129 if (nowMS - startMS > timeoutMS)
1131 DWORD level = WLOG_ERROR;
1132 wLog* log = WLog_Get(TAG);
1133 if (WLog_IsLevelActive(log, level))
1134 WLog_PrintTextMessage(log, level, line, file, fkt,
"timeout [%" PRIu32
"ms] exceeded",
1138 if (!BIO_should_retry(bio))
1140 DWORD level = WLOG_ERROR;
1141 wLog* log = WLog_Get(TAG);
1142 if (WLog_IsLevelActive(log, level))
1144 WLog_PrintTextMessage(log, level, line, file, fkt,
"Retries exceeded");
1145 ERR_print_errors_cb(print_bio_error, log);
1149 if (freerdp_shall_disconnect_context(context))
1155static SSIZE_T http_response_recv_line(rdpTls* tls, HttpResponse* response)
1158 WINPR_ASSERT(response);
1160 SSIZE_T payloadOffset = -1;
1161 const UINT32 timeoutMS =
1163 const UINT64 startMS = GetTickCount64();
1164 while (payloadOffset <= 0)
1166 size_t bodyLength = 0;
1167 size_t position = 0;
1170 char* end =
nullptr;
1174 status = BIO_read(tls->bio, Stream_Pointer(response->data), 1);
1177 if (sleep_or_timeout(tls->bio, tls->context, startMS, timeoutMS))
1182#ifdef FREERDP_HAVE_VALGRIND_MEMCHECK_H
1183 VALGRIND_MAKE_MEM_DEFINED(Stream_Pointer(response->data), status);
1185 Stream_Seek(response->data, (
size_t)status);
1187 if (!Stream_EnsureRemainingCapacity(response->data, 1024))
1190 position = Stream_GetPosition(response->data);
1194 else if (position > RESPONSE_SIZE_LIMIT)
1196 WLog_ERR(TAG,
"Request header too large! (%" PRIuz
" bytes) Aborting!", bodyLength);
1202 s = (position > 8) ? 8 : position;
1203 end = (
char*)Stream_Pointer(response->data) - s;
1205 if (winpr_strnstr(end,
"\r\n\r\n", s) !=
nullptr)
1206 payloadOffset = WINPR_ASSERTING_INT_CAST(SSIZE_T, Stream_GetPosition(response->data));
1210 return payloadOffset;
1213static BOOL http_response_recv_body(rdpTls* tls, HttpResponse* response, BOOL readContentLength,
1214 size_t payloadOffset,
size_t bodyLength)
1219 WINPR_ASSERT(response);
1221 const UINT64 startMS = GetTickCount64();
1222 const UINT32 timeoutMS =
1225 if ((response->TransferEncoding == TransferEncodingChunked) && readContentLength)
1228 ctx.state = ChunkStateLenghHeader;
1230 ctx.headerFooterPos = 0;
1234 if (!Stream_EnsureRemainingCapacity(response->data, 2048))
1237 int status = http_chuncked_read(tls->bio, tls->context, Stream_Pointer(response->data),
1238 Stream_GetRemainingCapacity(response->data), &ctx);
1241 if (sleep_or_timeout(tls->bio, tls->context, startMS, timeoutMS))
1246 Stream_Seek(response->data, (
size_t)status);
1249 if (Stream_GetPosition(response->data) > RESPONSE_SIZE_LIMIT)
1251 WLog_ERR(TAG,
"http response limit exceeded!");
1254 }
while (ctx.state != ChunkStateEnd);
1255 response->BodyLength = WINPR_ASSERTING_INT_CAST(uint32_t, full_len);
1256 if (response->BodyLength > 0)
1257 response->BodyContent = &(Stream_BufferAs(response->data,
char))[payloadOffset];
1261 while (response->BodyLength < bodyLength)
1265 if (!Stream_EnsureRemainingCapacity(response->data, bodyLength - response->BodyLength))
1269 size_t diff = bodyLength - response->BodyLength;
1270 if (diff > INT32_MAX)
1272 status = BIO_read(tls->bio, Stream_Pointer(response->data), (
int)diff);
1276 if (sleep_or_timeout(tls->bio, tls->context, startMS, timeoutMS))
1281 Stream_Seek(response->data, (
size_t)status);
1282 response->BodyLength += (
unsigned long)status;
1284 if (response->BodyLength > RESPONSE_SIZE_LIMIT)
1286 WLog_ERR(TAG,
"Request body too large! (%" PRIuz
" bytes) Aborting!",
1287 response->BodyLength);
1292 if (response->BodyLength > 0)
1293 response->BodyContent = &(Stream_BufferAs(response->data,
char))[payloadOffset];
1295 if (bodyLength != response->BodyLength)
1297 WLog_WARN(TAG,
"%s unexpected body length: actual: %" PRIuz
", expected: %" PRIuz,
1298 response->ContentType, response->BodyLength, bodyLength);
1301 response->BodyLength = MIN(bodyLength, response->BodyLength);
1305 if (!Stream_EnsureRemainingCapacity(response->data,
sizeof(UINT16)))
1307 Stream_Write_UINT16(response->data, 0);
1315static void clear_lines(HttpResponse* response)
1317 WINPR_ASSERT(response);
1319 for (
size_t x = 0; x < response->count; x++)
1321 WINPR_ASSERT(response->lines);
1322 char* line = response->lines[x];
1326 free((
void*)response->lines);
1327 response->lines =
nullptr;
1328 response->count = 0;
1331HttpResponse* http_response_recv(rdpTls* tls, BOOL readContentLength)
1333 size_t bodyLength = 0;
1334 HttpResponse* response = http_response_new();
1339 response->ContentLength = 0;
1341 const SSIZE_T payloadOffset = http_response_recv_line(tls, response);
1342 if (payloadOffset < 0)
1348 char* buffer = Stream_BufferAs(response->data,
char);
1349 char* line = Stream_BufferAs(response->data,
char);
1350 char* context =
nullptr;
1352 while ((line = winpr_strnstr(line,
"\r\n",
1353 WINPR_ASSERTING_INT_CAST(
size_t, payloadOffset) -
1354 WINPR_ASSERTING_INT_CAST(
size_t, (line - buffer)) - 2UL)))
1360 clear_lines(response);
1361 response->count = count;
1365 response->lines = (
char**)calloc(response->count,
sizeof(
char*));
1367 if (!response->lines)
1371 buffer[payloadOffset - 1] =
'\0';
1372 buffer[payloadOffset - 2] =
'\0';
1374 line = strtok_s(buffer,
"\r\n", &context);
1376 while (line && (response->count > count))
1378 response->lines[count] = _strdup(line);
1379 if (!response->lines[count])
1381 line = strtok_s(
nullptr,
"\r\n", &context);
1385 if (!http_response_parse_header(response))
1388 response->BodyLength =
1389 Stream_GetPosition(response->data) - WINPR_ASSERTING_INT_CAST(
size_t, payloadOffset);
1391 WINPR_ASSERT(response->BodyLength == 0);
1392 bodyLength = response->BodyLength;
1394 if (readContentLength && (response->ContentLength > 0))
1396 const char* cur = response->ContentType;
1398 while (cur !=
nullptr)
1400 if (http_use_content_length(cur))
1402 if (response->ContentLength < RESPONSE_SIZE_LIMIT)
1403 bodyLength = response->ContentLength;
1408 readContentLength = FALSE;
1410 cur = strchr(cur + 1,
';');
1414 if (bodyLength > RESPONSE_SIZE_LIMIT)
1416 WLog_ERR(TAG,
"Expected request body too large! (%" PRIuz
" bytes) Aborting!",
1422 if (!http_response_recv_body(tls, response, readContentLength,
1423 WINPR_ASSERTING_INT_CAST(
size_t, payloadOffset), bodyLength))
1426 Stream_SealLength(response->data);
1429 if (!Stream_EnsureRemainingCapacity(response->data, 2))
1431 Stream_Write_UINT16(response->data, 0);
1435 WLog_ERR(TAG,
"No response");
1436 http_response_free(response);
1440const char* http_response_get_body(
const HttpResponse* response)
1445 return response->BodyContent;
1448wHashTable* HashTable_New_String(
void)
1450 wHashTable* table = HashTable_New(FALSE);
1454 if (!HashTable_SetupForStringData(table, TRUE))
1456 HashTable_Free(table);
1459 HashTable_KeyObject(table)->
fnObjectEquals = strings_equals_nocase;
1460 HashTable_ValueObject(table)->
fnObjectEquals = strings_equals_nocase;
1464HttpResponse* http_response_new(
void)
1466 HttpResponse* response = (HttpResponse*)calloc(1,
sizeof(HttpResponse));
1471 response->Authenticates = HashTable_New_String();
1473 if (!response->Authenticates)
1476 response->SetCookie = HashTable_New_String();
1478 if (!response->SetCookie)
1481 response->data = Stream_New(
nullptr, 2048);
1483 if (!response->data)
1486 response->TransferEncoding = TransferEncodingIdentity;
1489 WINPR_PRAGMA_DIAG_PUSH
1490 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
1491 http_response_free(response);
1492 WINPR_PRAGMA_DIAG_POP
1496void http_response_free(HttpResponse* response)
1501 clear_lines(response);
1502 free(response->ReasonPhrase);
1503 free(response->ContentType);
1504 free(response->SecWebsocketAccept);
1505 free(response->SecWebsocketVersion);
1506 HashTable_Free(response->Authenticates);
1507 HashTable_Free(response->SetCookie);
1508 Stream_Free(response->data, TRUE);
1512const char* http_request_get_uri(HttpRequest* request)
1517 return request->URI;
1520SSIZE_T http_request_get_content_length(HttpRequest* request)
1525 return (SSIZE_T)request->ContentLength;
1528BOOL http_request_set_content_length(HttpRequest* request,
size_t length)
1533 request->ContentLength = length;
1537UINT16 http_response_get_status_code(
const HttpResponse* response)
1539 WINPR_ASSERT(response);
1541 return response->StatusCode;
1544size_t http_response_get_body_length(
const HttpResponse* response)
1546 WINPR_ASSERT(response);
1548 return response->BodyLength;
1551const char* http_response_get_auth_token(
const HttpResponse* response,
const char* method)
1553 if (!response || !method)
1556 return HashTable_GetItemValue(response->Authenticates, method);
1559const char* http_response_get_setcookie(
const HttpResponse* response,
const char* cookie)
1561 if (!response || !cookie)
1564 return HashTable_GetItemValue(response->SetCookie, cookie);
1567TRANSFER_ENCODING http_response_get_transfer_encoding(
const HttpResponse* response)
1570 return TransferEncodingUnknown;
1572 return response->TransferEncoding;
1575BOOL http_response_is_websocket(
const HttpContext* http,
const HttpResponse* response)
1577 BOOL isWebsocket = FALSE;
1578 WINPR_DIGEST_CTX* sha1 =
nullptr;
1579 char* base64accept =
nullptr;
1580 BYTE sha1_digest[WINPR_SHA1_DIGEST_LENGTH];
1582 if (!http || !response)
1585 if (!http->websocketUpgrade || response->StatusCode != HTTP_STATUS_SWITCH_PROTOCOLS)
1588 if (response->SecWebsocketVersion && _stricmp(response->SecWebsocketVersion,
"13") != 0)
1591 if (!response->SecWebsocketAccept)
1596 sha1 = winpr_Digest_New();
1600 if (!winpr_Digest_Init(sha1, WINPR_MD_SHA1))
1603 if (!winpr_Digest_Update(sha1, (BYTE*)http->SecWebsocketKey, strlen(http->SecWebsocketKey)))
1605 if (!winpr_Digest_Update(sha1, (
const BYTE*)WEBSOCKET_MAGIC_GUID, strlen(WEBSOCKET_MAGIC_GUID)))
1608 if (!winpr_Digest_Final(sha1, sha1_digest,
sizeof(sha1_digest)))
1611 base64accept = crypto_base64_encode(sha1_digest, WINPR_SHA1_DIGEST_LENGTH);
1615 if (_stricmp(response->SecWebsocketAccept, base64accept) != 0)
1617 WLog_WARN(TAG,
"Webserver gave Websocket Upgrade response but sanity check failed");
1622 winpr_Digest_Free(sha1);
1627void http_response_log_error_status_(wLog* log, DWORD level,
const HttpResponse* response,
1628 const char* file,
size_t line,
const char* fkt)
1631 WINPR_ASSERT(response);
1633 if (!WLog_IsLevelActive(log, level))
1636 char buffer[64] = WINPR_C_ARRAY_INIT;
1637 const UINT16 status = http_response_get_status_code(response);
1638 WLog_PrintTextMessage(log, level, line, file, fkt,
"Unexpected HTTP status: %s",
1639 freerdp_http_status_string_format(status, buffer, ARRAYSIZE(buffer)));
1640 http_response_print(log, level, response, file, line, fkt);
1643static BOOL extract_cookie(
const void* pkey,
void* pvalue,
void* arg)
1645 const char* key = pkey;
1646 const char* value = pvalue;
1647 HttpContext* context = arg;
1651 WINPR_ASSERT(value);
1653 return http_context_set_cookie(context, key, value);
1656BOOL http_response_extract_cookies(
const HttpResponse* response, HttpContext* context)
1658 WINPR_ASSERT(response);
1659 WINPR_ASSERT(context);
1661 return HashTable_Foreach(response->SetCookie, extract_cookie, context);
1664FREERDP_LOCAL BOOL http_context_set_header(HttpContext* context,
const char* key,
const char* value,
1667 WINPR_ASSERT(context);
1668 va_list ap = WINPR_C_ARRAY_INIT;
1669 va_start(ap, value);
1670 const BOOL rc = http_context_set_header_va(context, key, value, ap);
1675BOOL http_request_set_header(HttpRequest* request,
const char* key,
const char* value, ...)
1677 WINPR_ASSERT(request);
1680 va_list ap = WINPR_C_ARRAY_INIT;
1681 va_start(ap, value);
1682 winpr_vasprintf(&v, &vlen, value, ap);
1684 const BOOL rc = HashTable_Insert(request->headers, key, v);
1689BOOL http_context_set_header_va(HttpContext* context,
const char* key,
const char* value,
1694 winpr_vasprintf(&v, &vlen, value, ap);
1695 const BOOL rc = HashTable_Insert(context->headers, key, v);
WINPR_ATTR_NODISCARD FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.
This struct contains function pointer to initialize/free objects.
OBJECT_FREE_FN fnObjectFree
WINPR_ATTR_NODISCARD OBJECT_EQUALS_FN fnObjectEquals
WINPR_ATTR_NODISCARD OBJECT_NEW_FN fnObjectNew