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;
82 const char* ReasonPhrase;
85 const char* ContentType;
86 TRANSFER_ENCODING TransferEncoding;
87 const char* SecWebsocketVersion;
88 const char* SecWebsocketAccept;
93 wHashTable* Authenticates;
94 wHashTable* SetCookie;
98static wHashTable* HashTable_New_String(
void);
100static char* string_strnstr(
char* str1,
const char* str2,
size_t slen)
106 if ((c = *str2++) !=
'\0')
108 len = strnlen(str2, slen + 1);
114 if (slen-- < 1 || (sc = *str1++) ==
'\0')
120 }
while (strncmp(str1, str2, len) != 0);
128static BOOL strings_equals_nocase(
const void* obj1,
const void* obj2)
133 return _stricmp(obj1, obj2) == 0;
136HttpContext* http_context_new(
void)
138 HttpContext* context = (HttpContext*)calloc(1,
sizeof(HttpContext));
142 context->headers = HashTable_New_String();
143 if (!context->headers)
146 context->cookies = ListDictionary_New(FALSE);
147 if (!context->cookies)
150 wObject* key = ListDictionary_KeyObject(context->cookies);
151 wObject* value = ListDictionary_ValueObject(context->cookies);
155 key->fnObjectFree = winpr_ObjectStringFree;
156 key->fnObjectNew = winpr_ObjectStringClone;
157 value->fnObjectFree = winpr_ObjectStringFree;
158 value->fnObjectNew = winpr_ObjectStringClone;
163 WINPR_PRAGMA_DIAG_PUSH
164 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
165 http_context_free(context);
166 WINPR_PRAGMA_DIAG_POP
170BOOL http_context_set_method(HttpContext* context,
const char* Method)
172 if (!context || !Method)
175 free(context->Method);
176 context->Method = _strdup(Method);
178 if (!context->Method)
184BOOL http_request_set_content_type(HttpRequest* request,
const char* ContentType)
186 if (!request || !ContentType)
189 return http_request_set_header(request,
"Content-Type",
"%s", ContentType);
192const char* http_context_get_uri(HttpContext* context)
200BOOL http_context_set_uri(HttpContext* context,
const char* URI)
202 if (!context || !URI)
206 context->URI = _strdup(URI);
214BOOL http_context_set_user_agent(HttpContext* context,
const char* UserAgent)
216 if (!context || !UserAgent)
219 return http_context_set_header(context,
"User-Agent",
"%s", UserAgent);
222BOOL http_context_set_x_ms_user_agent(HttpContext* context,
const char* X_MS_UserAgent)
224 if (!context || !X_MS_UserAgent)
227 return http_context_set_header(context,
"X-MS-User-Agent",
"%s", X_MS_UserAgent);
230BOOL http_context_set_host(HttpContext* context,
const char* Host)
232 if (!context || !Host)
235 return http_context_set_header(context,
"Host",
"%s", Host);
238BOOL http_context_set_accept(HttpContext* context,
const char* Accept)
240 if (!context || !Accept)
243 return http_context_set_header(context,
"Accept",
"%s", Accept);
246BOOL http_context_set_cache_control(HttpContext* context,
const char* CacheControl)
248 if (!context || !CacheControl)
251 return http_context_set_header(context,
"Cache-Control",
"%s", CacheControl);
254BOOL http_context_set_connection(HttpContext* context,
const char* Connection)
256 if (!context || !Connection)
259 free(context->Connection);
260 context->Connection = _strdup(Connection);
262 if (!context->Connection)
268WINPR_ATTR_FORMAT_ARG(2, 0)
269static BOOL list_append(HttpContext* context, WINPR_FORMAT_ARG const
char* str, va_list ap)
274 size_t PragmaSize = 0;
277 const int size = winpr_vasprintf(&Pragma, &PragmaSize, str, ap);
287 winpr_asprintf(&sstr, &slen,
"%s, %s", context->Pragma, Pragma);
294 free(context->Pragma);
295 context->Pragma = sstr;
304WINPR_ATTR_FORMAT_ARG(2, 3)
305BOOL http_context_set_pragma(HttpContext* context, WINPR_FORMAT_ARG const
char* Pragma, ...)
307 if (!context || !Pragma)
310 free(context->Pragma);
311 context->Pragma = NULL;
314 va_start(ap, Pragma);
315 return list_append(context, Pragma, ap);
318WINPR_ATTR_FORMAT_ARG(2, 3)
319BOOL http_context_append_pragma(HttpContext* context, const
char* Pragma, ...)
321 if (!context || !Pragma)
325 va_start(ap, Pragma);
326 return list_append(context, Pragma, ap);
329static char* guid2str(
const GUID* guid,
char* buffer,
size_t len)
333 char* strguid = NULL;
335 RPC_STATUS rpcStatus = UuidToStringA(guid, &strguid);
337 if (rpcStatus != RPC_S_OK)
340 (void)sprintf_s(buffer, len,
"{%s}", strguid);
341 RpcStringFreeA(&strguid);
345BOOL http_context_set_rdg_connection_id(HttpContext* context,
const GUID* RdgConnectionId)
347 if (!context || !RdgConnectionId)
350 char buffer[64] = { 0 };
351 return http_context_set_header(context,
"RDG-Connection-Id",
"%s",
352 guid2str(RdgConnectionId, buffer,
sizeof(buffer)));
355BOOL http_context_set_rdg_correlation_id(HttpContext* context,
const GUID* RdgCorrelationId)
357 if (!context || !RdgCorrelationId)
360 char buffer[64] = { 0 };
361 return http_context_set_header(context,
"RDG-Correlation-Id",
"%s",
362 guid2str(RdgCorrelationId, buffer,
sizeof(buffer)));
365BOOL http_context_enable_websocket_upgrade(HttpContext* context, BOOL enable)
373 if (RPC_S_OK != UuidCreate(&key))
376 free(context->SecWebsocketKey);
377 context->SecWebsocketKey = crypto_base64_encode((BYTE*)&key,
sizeof(key));
378 if (!context->SecWebsocketKey)
382 context->websocketUpgrade = enable;
386BOOL http_context_is_websocket_upgrade_enabled(HttpContext* context)
388 return context->websocketUpgrade;
391BOOL http_context_set_rdg_auth_scheme(HttpContext* context,
const char* RdgAuthScheme)
393 if (!context || !RdgAuthScheme)
396 return http_context_set_header(context,
"RDG-Auth-Scheme",
"%s", RdgAuthScheme);
399BOOL http_context_set_cookie(HttpContext* context,
const char* CookieName,
const char* CookieValue)
401 if (!context || !CookieName || !CookieValue)
403 if (ListDictionary_Contains(context->cookies, CookieName))
405 if (!ListDictionary_SetItemValue(context->cookies, CookieName, CookieValue))
410 if (!ListDictionary_Add(context->cookies, CookieName, CookieValue))
416void http_context_free(HttpContext* context)
420 free(context->SecWebsocketKey);
422 free(context->Method);
423 free(context->Connection);
424 free(context->Pragma);
425 HashTable_Free(context->headers);
426 ListDictionary_Free(context->cookies);
431BOOL http_request_set_method(HttpRequest* request,
const char* Method)
433 if (!request || !Method)
436 free(request->Method);
437 request->Method = _strdup(Method);
439 if (!request->Method)
445BOOL http_request_set_uri(HttpRequest* request,
const char* URI)
447 if (!request || !URI)
451 request->URI = _strdup(URI);
459BOOL http_request_set_auth_scheme(HttpRequest* request,
const char* AuthScheme)
461 if (!request || !AuthScheme)
464 free(request->AuthScheme);
465 request->AuthScheme = _strdup(AuthScheme);
467 if (!request->AuthScheme)
473BOOL http_request_set_auth_param(HttpRequest* request,
const char* AuthParam)
475 if (!request || !AuthParam)
478 free(request->AuthParam);
479 request->AuthParam = _strdup(AuthParam);
481 if (!request->AuthParam)
487BOOL http_request_set_transfer_encoding(HttpRequest* request, TRANSFER_ENCODING TransferEncoding)
489 if (!request || TransferEncoding == TransferEncodingUnknown)
492 request->TransferEncoding = TransferEncoding;
497WINPR_ATTR_FORMAT_ARG(2, 3)
498static BOOL http_encode_print(
wStream* s, WINPR_FORMAT_ARG const
char* fmt, ...)
509 length = vsnprintf(NULL, 0, fmt, ap) + 1;
512 if (!Stream_EnsureRemainingCapacity(s, (
size_t)length))
515 str = (
char*)Stream_Pointer(s);
517 used = vsnprintf(str, (
size_t)length, fmt, ap);
521 if ((used + 1) != length)
524 Stream_Seek(s, (
size_t)used);
528static BOOL http_encode_body_line(
wStream* s,
const char* param,
const char* value)
530 if (!s || !param || !value)
533 return http_encode_print(s,
"%s: %s\r\n", param, value);
536static BOOL http_encode_content_length_line(
wStream* s,
size_t ContentLength)
538 return http_encode_print(s,
"Content-Length: %" PRIuz
"\r\n", ContentLength);
541static BOOL http_encode_header_line(
wStream* s,
const char* Method,
const char* URI)
543 if (!s || !Method || !URI)
546 return http_encode_print(s,
"%s %s HTTP/1.1\r\n", Method, URI);
549static BOOL http_encode_authorization_line(
wStream* s,
const char* AuthScheme,
550 const char* AuthParam)
552 if (!s || !AuthScheme || !AuthParam)
555 return http_encode_print(s,
"Authorization: %s %s\r\n", AuthScheme, AuthParam);
558static BOOL http_encode_cookie_line(
wStream* s, wListDictionary* cookies)
560 ULONG_PTR* keys = NULL;
566 ListDictionary_Lock(cookies);
567 const size_t count = ListDictionary_GetKeys(cookies, &keys);
572 status = http_encode_print(s,
"Cookie: ");
576 for (
size_t x = 0; status && x < count; x++)
578 char* cur = (
char*)ListDictionary_GetItemValue(cookies, (
void*)keys[x]);
586 status = http_encode_print(s,
"; ");
590 status = http_encode_print(s,
"%s=%s", (
char*)keys[x], cur);
593 status = http_encode_print(s,
"\r\n");
596 ListDictionary_Unlock(cookies);
600static BOOL write_headers(
const void* pkey,
void* pvalue,
void* arg)
602 const char* key = pkey;
603 const char* value = pvalue;
610 return http_encode_body_line(s, key, value);
613wStream* http_request_write(HttpContext* context, HttpRequest* request)
617 if (!context || !request)
620 s = Stream_New(NULL, 1024);
625 if (!http_encode_header_line(s, request->Method, request->URI) ||
627 !http_encode_body_line(s,
"Pragma", context->Pragma))
630 if (!context->websocketUpgrade)
632 if (!http_encode_body_line(s,
"Connection", context->Connection))
637 if (!http_encode_body_line(s,
"Connection",
"Upgrade") ||
638 !http_encode_body_line(s,
"Upgrade",
"websocket") ||
639 !http_encode_body_line(s,
"Sec-Websocket-Version",
"13") ||
640 !http_encode_body_line(s,
"Sec-Websocket-Key", context->SecWebsocketKey))
644 if (request->TransferEncoding != TransferEncodingIdentity)
646 if (request->TransferEncoding == TransferEncodingChunked)
648 if (!http_encode_body_line(s,
"Transfer-Encoding",
"chunked"))
656 if (!http_encode_content_length_line(s, request->ContentLength))
660 if (!utils_str_is_empty(request->Authorization))
662 if (!http_encode_body_line(s,
"Authorization", request->Authorization))
665 else if (!utils_str_is_empty(request->AuthScheme) && !utils_str_is_empty(request->AuthParam))
667 if (!http_encode_authorization_line(s, request->AuthScheme, request->AuthParam))
671 if (!HashTable_Foreach(context->headers, write_headers, s))
674 if (!HashTable_Foreach(request->headers, write_headers, s))
677 if (!http_encode_cookie_line(s, context->cookies))
680 if (!http_encode_print(s,
"\r\n"))
683 Stream_SealLength(s);
686 Stream_Free(s, TRUE);
690HttpRequest* http_request_new(
void)
692 HttpRequest* request = (HttpRequest*)calloc(1,
sizeof(HttpRequest));
696 request->headers = HashTable_New_String();
697 if (!request->headers)
699 request->TransferEncoding = TransferEncodingIdentity;
702 http_request_free(request);
706void http_request_free(HttpRequest* request)
711 free(request->AuthParam);
712 free(request->AuthScheme);
713 free(request->Authorization);
714 free(request->Method);
716 HashTable_Free(request->headers);
720static BOOL http_response_parse_header_status_line(HttpResponse* response,
const char* status_line)
723 char* separator = NULL;
724 char* status_code = NULL;
725 char* reason_phrase = NULL;
731 separator = strchr(status_line,
' ');
736 status_code = separator + 1;
737 separator = strchr(status_code,
' ');
742 reason_phrase = separator + 1;
746 long val = strtol(status_code, NULL, 0);
748 if ((errno != 0) || (val < 0) || (val > INT16_MAX))
751 response->StatusCode = (UINT16)val;
753 response->ReasonPhrase = reason_phrase;
755 if (!response->ReasonPhrase)
763 WLog_ERR(TAG,
"http_response_parse_header_status_line failed [%s]", status_line);
768static BOOL http_response_parse_header_field(HttpResponse* response,
const char* name,
773 WINPR_ASSERT(response);
778 if (_stricmp(name,
"Content-Length") == 0)
780 unsigned long long val = 0;
782 val = _strtoui64(value, NULL, 0);
784 if ((errno != 0) || (val > INT32_MAX))
787 response->ContentLength = WINPR_ASSERTING_INT_CAST(
size_t, val);
789 else if (_stricmp(name,
"Content-Type") == 0)
791 response->ContentType = value;
793 if (!response->ContentType)
796 else if (_stricmp(name,
"Transfer-Encoding") == 0)
798 if (_stricmp(value,
"identity") == 0)
799 response->TransferEncoding = TransferEncodingIdentity;
800 else if (_stricmp(value,
"chunked") == 0)
801 response->TransferEncoding = TransferEncodingChunked;
803 response->TransferEncoding = TransferEncodingUnknown;
805 else if (_stricmp(name,
"Sec-WebSocket-Version") == 0)
807 response->SecWebsocketVersion = value;
809 if (!response->SecWebsocketVersion)
812 else if (_stricmp(name,
"Sec-WebSocket-Accept") == 0)
814 response->SecWebsocketAccept = value;
816 if (!response->SecWebsocketAccept)
819 else if (_stricmp(name,
"WWW-Authenticate") == 0)
821 char* separator = NULL;
822 const char* authScheme = NULL;
823 char* authValue = NULL;
824 separator = strchr(value,
' ');
836 authValue = separator + 1;
838 if (!authScheme || !authValue)
851 status = HashTable_Insert(response->Authenticates, authScheme, authValue);
853 else if (_stricmp(name,
"Set-Cookie") == 0)
855 char* separator = NULL;
856 const char* CookieName = NULL;
857 char* CookieValue = NULL;
858 separator = strchr(value,
'=');
868 CookieValue = separator + 1;
870 if (!CookieName || !CookieValue)
873 if (*CookieValue ==
'"')
875 char* p = CookieValue;
876 while (*p !=
'"' && *p !=
'\0')
886 char* p = CookieValue;
887 while (*p !=
';' && *p !=
'\0' && *p !=
' ')
899 status = HashTable_Insert(response->SetCookie, CookieName, CookieValue);
905static BOOL http_response_parse_header(HttpResponse* response)
911 char* colon_pos = NULL;
912 char* end_of_header = NULL;
913 char end_of_header_char = 0;
918 if (!response->lines)
921 if (!http_response_parse_header_status_line(response, response->lines[0]))
924 for (
size_t count = 1; count < response->count; count++)
926 line = response->lines[count];
938 colon_pos = strchr(line,
':');
942 if ((colon_pos == NULL) || (colon_pos == line))
946 for (end_of_header = colon_pos; end_of_header != line; end_of_header--)
948 c = end_of_header[-1];
950 if (c !=
' ' && c !=
'\t' && c !=
':')
954 if (end_of_header == line)
957 end_of_header_char = *end_of_header;
958 *end_of_header =
'\0';
962 char* value = colon_pos + 1;
963 for (; *value; value++)
965 if ((*value !=
' ') && (*value !=
'\t'))
969 const int res = http_response_parse_header_field(response, name, value);
970 *end_of_header = end_of_header_char;
979 WLog_ERR(TAG,
"parsing failed");
984static void http_response_print(wLog* log, DWORD level,
const HttpResponse* response,
985 const char* file,
size_t line,
const char* fkt)
987 char buffer[64] = { 0 };
990 WINPR_ASSERT(response);
992 if (!WLog_IsLevelActive(log, level))
995 const long status = http_response_get_status_code(response);
996 WLog_PrintTextMessage(log, level, line, file, fkt,
"HTTP status: %s",
997 freerdp_http_status_string_format(status, buffer, ARRAYSIZE(buffer)));
999 if (WLog_IsLevelActive(log, WLOG_DEBUG))
1001 for (
size_t i = 0; i < response->count; i++)
1002 WLog_PrintTextMessage(log, WLOG_DEBUG, line, file, fkt,
"[%" PRIuz
"] %s", i,
1003 response->lines[i]);
1006 if (response->ReasonPhrase)
1007 WLog_PrintTextMessage(log, level, line, file, fkt,
"[reason] %s", response->ReasonPhrase);
1009 if (WLog_IsLevelActive(log, WLOG_TRACE))
1011 WLog_PrintTextMessage(log, WLOG_TRACE, line, file, fkt,
"[body][%" PRIuz
"] %s",
1012 response->BodyLength, response->BodyContent);
1016static BOOL http_use_content_length(
const char* cur)
1023 if (_strnicmp(cur,
"application/rpc", 15) == 0)
1025 else if (_strnicmp(cur,
"text/plain", 10) == 0)
1027 else if (_strnicmp(cur,
"text/html", 9) == 0)
1029 else if (_strnicmp(cur,
"application/json", 16) == 0)
1034 char end = cur[pos];
1053static int print_bio_error(
const char* str,
size_t len,
void* bp)
1058 WLog_Print(log, WLOG_ERROR,
"%s", str);
1059 if (len > INT32_MAX)
1064int http_chuncked_read(BIO* bio, BYTE* pBuffer,
size_t size,
1068 int effectiveDataLen = 0;
1070 WINPR_ASSERT(pBuffer);
1071 WINPR_ASSERT(encodingContext != NULL);
1072 WINPR_ASSERT(size <= INT32_MAX);
1075 switch (encodingContext->state)
1077 case ChunkStateData:
1080 (size > encodingContext->nextOffset ? encodingContext->nextOffset : size);
1085 status = BIO_read(bio, pBuffer, (
int)rd);
1087 return (effectiveDataLen > 0 ? effectiveDataLen : status);
1089 encodingContext->nextOffset -= WINPR_ASSERTING_INT_CAST(uint32_t, status);
1090 if (encodingContext->nextOffset == 0)
1092 encodingContext->state = ChunkStateFooter;
1093 encodingContext->headerFooterPos = 0;
1095 effectiveDataLen += status;
1097 if ((
size_t)status == size)
1098 return effectiveDataLen;
1101 size -= (size_t)status;
1104 case ChunkStateFooter:
1106 char _dummy[2] = { 0 };
1107 WINPR_ASSERT(encodingContext->nextOffset == 0);
1108 WINPR_ASSERT(encodingContext->headerFooterPos < 2);
1110 status = BIO_read(bio, _dummy, (
int)(2 - encodingContext->headerFooterPos));
1113 encodingContext->headerFooterPos += (size_t)status;
1114 if (encodingContext->headerFooterPos == 2)
1116 encodingContext->state = ChunkStateLenghHeader;
1117 encodingContext->headerFooterPos = 0;
1121 return (effectiveDataLen > 0 ? effectiveDataLen : status);
1124 case ChunkStateLenghHeader:
1126 BOOL _haveNewLine = FALSE;
1127 char* dst = &encodingContext->lenBuffer[encodingContext->headerFooterPos];
1128 WINPR_ASSERT(encodingContext->nextOffset == 0);
1129 while (encodingContext->headerFooterPos < 10 && !_haveNewLine)
1132 status = BIO_read(bio, dst, 1);
1136 _haveNewLine = TRUE;
1137 encodingContext->headerFooterPos += (size_t)status;
1141 return (effectiveDataLen > 0 ? effectiveDataLen : status);
1147 size_t tmp = strtoul(encodingContext->lenBuffer, NULL, 16);
1148 if ((errno != 0) || (tmp > SIZE_MAX))
1151 encodingContext->nextOffset = 0;
1152 encodingContext->state = ChunkStateEnd;
1155 encodingContext->nextOffset = tmp;
1156 encodingContext->state = ChunkStateData;
1158 if (encodingContext->nextOffset == 0)
1160 WLog_DBG(TAG,
"chunked encoding end of stream received");
1161 encodingContext->headerFooterPos = 0;
1162 encodingContext->state = ChunkStateEnd;
1163 return (effectiveDataLen > 0 ? effectiveDataLen : 0);
1174#define sleep_or_timeout(tls, startMS, timeoutMS) \
1175 sleep_or_timeout_((tls), (startMS), (timeoutMS), __FILE__, __func__, __LINE__)
1176static BOOL sleep_or_timeout_(rdpTls* tls, UINT64 startMS, UINT32 timeoutMS,
const char* file,
1177 const char* fkt,
size_t line)
1182 const UINT64 nowMS = GetTickCount64();
1183 if (nowMS - startMS > timeoutMS)
1185 DWORD level = WLOG_ERROR;
1186 wLog* log = WLog_Get(TAG);
1187 if (WLog_IsLevelActive(log, level))
1188 WLog_PrintTextMessage(log, level, line, file, fkt,
"timeout [%" PRIu32
"ms] exceeded",
1192 if (!BIO_should_retry(tls->bio))
1194 DWORD level = WLOG_ERROR;
1195 wLog* log = WLog_Get(TAG);
1196 if (WLog_IsLevelActive(log, level))
1198 WLog_PrintTextMessage(log, level, line, file, fkt,
"Retries exceeded");
1199 ERR_print_errors_cb(print_bio_error, log);
1203 if (freerdp_shall_disconnect_context(tls->context))
1209static SSIZE_T http_response_recv_line(rdpTls* tls, HttpResponse* response)
1212 WINPR_ASSERT(response);
1214 SSIZE_T payloadOffset = -1;
1215 const UINT32 timeoutMS =
1217 const UINT64 startMS = GetTickCount64();
1218 while (payloadOffset <= 0)
1220 size_t bodyLength = 0;
1221 size_t position = 0;
1228 status = BIO_read(tls->bio, Stream_Pointer(response->data), 1);
1231 if (sleep_or_timeout(tls, startMS, timeoutMS))
1236#ifdef FREERDP_HAVE_VALGRIND_MEMCHECK_H
1237 VALGRIND_MAKE_MEM_DEFINED(Stream_Pointer(response->data), status);
1239 Stream_Seek(response->data, (
size_t)status);
1241 if (!Stream_EnsureRemainingCapacity(response->data, 1024))
1244 position = Stream_GetPosition(response->data);
1248 else if (position > RESPONSE_SIZE_LIMIT)
1250 WLog_ERR(TAG,
"Request header too large! (%" PRIdz
" bytes) Aborting!", bodyLength);
1256 s = (position > 8) ? 8 : position;
1257 end = (
char*)Stream_Pointer(response->data) - s;
1259 if (string_strnstr(end,
"\r\n\r\n", s) != NULL)
1260 payloadOffset = WINPR_ASSERTING_INT_CAST(SSIZE_T, Stream_GetPosition(response->data));
1264 return payloadOffset;
1267static BOOL http_response_recv_body(rdpTls* tls, HttpResponse* response, BOOL readContentLength,
1268 size_t payloadOffset,
size_t bodyLength)
1273 WINPR_ASSERT(response);
1275 const UINT64 startMS = GetTickCount64();
1276 const UINT32 timeoutMS =
1279 if ((response->TransferEncoding == TransferEncodingChunked) && readContentLength)
1282 ctx.state = ChunkStateLenghHeader;
1284 ctx.headerFooterPos = 0;
1288 if (!Stream_EnsureRemainingCapacity(response->data, 2048))
1291 int status = http_chuncked_read(tls->bio, Stream_Pointer(response->data),
1292 Stream_GetRemainingCapacity(response->data), &ctx);
1295 if (sleep_or_timeout(tls, startMS, timeoutMS))
1300 Stream_Seek(response->data, (
size_t)status);
1303 }
while (ctx.state != ChunkStateEnd);
1304 response->BodyLength = WINPR_ASSERTING_INT_CAST(uint32_t, full_len);
1305 if (response->BodyLength > 0)
1306 response->BodyContent = &(Stream_BufferAs(response->data,
char))[payloadOffset];
1310 while (response->BodyLength < bodyLength)
1314 if (!Stream_EnsureRemainingCapacity(response->data, bodyLength - response->BodyLength))
1318 size_t diff = bodyLength - response->BodyLength;
1319 if (diff > INT32_MAX)
1321 status = BIO_read(tls->bio, Stream_Pointer(response->data), (
int)diff);
1325 if (sleep_or_timeout(tls, startMS, timeoutMS))
1330 Stream_Seek(response->data, (
size_t)status);
1331 response->BodyLength += (
unsigned long)status;
1333 if (response->BodyLength > RESPONSE_SIZE_LIMIT)
1335 WLog_ERR(TAG,
"Request body too large! (%" PRIdz
" bytes) Aborting!",
1336 response->BodyLength);
1341 if (response->BodyLength > 0)
1342 response->BodyContent = &(Stream_BufferAs(response->data,
char))[payloadOffset];
1344 if (bodyLength != response->BodyLength)
1346 WLog_WARN(TAG,
"%s unexpected body length: actual: %" PRIuz
", expected: %" PRIuz,
1347 response->ContentType, response->BodyLength, bodyLength);
1350 response->BodyLength = MIN(bodyLength, response->BodyLength);
1354 if (!Stream_EnsureRemainingCapacity(response->data,
sizeof(UINT16)))
1356 Stream_Write_UINT16(response->data, 0);
1364HttpResponse* http_response_recv(rdpTls* tls, BOOL readContentLength)
1366 size_t bodyLength = 0;
1367 HttpResponse* response = http_response_new();
1372 response->ContentLength = 0;
1374 const SSIZE_T payloadOffset = http_response_recv_line(tls, response);
1375 if (payloadOffset < 0)
1381 char* buffer = Stream_BufferAs(response->data,
char);
1382 char* line = Stream_BufferAs(response->data,
char);
1383 char* context = NULL;
1385 while ((line = string_strnstr(line,
"\r\n",
1386 WINPR_ASSERTING_INT_CAST(
size_t, payloadOffset) -
1387 WINPR_ASSERTING_INT_CAST(
size_t, (line - buffer)) - 2UL)))
1393 response->count = count;
1397 response->lines = (
char**)calloc(response->count,
sizeof(
char*));
1399 if (!response->lines)
1403 buffer[payloadOffset - 1] =
'\0';
1404 buffer[payloadOffset - 2] =
'\0';
1406 line = strtok_s(buffer,
"\r\n", &context);
1408 while (line && (response->count > count))
1410 response->lines[count] = line;
1411 line = strtok_s(NULL,
"\r\n", &context);
1415 if (!http_response_parse_header(response))
1418 response->BodyLength =
1419 Stream_GetPosition(response->data) - WINPR_ASSERTING_INT_CAST(
size_t, payloadOffset);
1421 WINPR_ASSERT(response->BodyLength == 0);
1422 bodyLength = response->BodyLength;
1424 if (readContentLength && (response->BodyLength > 0))
1426 const char* cur = response->ContentType;
1430 if (http_use_content_length(cur))
1432 if (response->ContentLength < RESPONSE_SIZE_LIMIT)
1433 bodyLength = response->ContentLength;
1438 readContentLength = FALSE;
1440 cur = strchr(cur,
';');
1444 if (bodyLength > RESPONSE_SIZE_LIMIT)
1446 WLog_ERR(TAG,
"Expected request body too large! (%" PRIdz
" bytes) Aborting!",
1452 if (!http_response_recv_body(tls, response, readContentLength,
1453 WINPR_ASSERTING_INT_CAST(
size_t, payloadOffset), bodyLength))
1456 Stream_SealLength(response->data);
1459 if (!Stream_EnsureRemainingCapacity(response->data, 2))
1461 Stream_Write_UINT16(response->data, 0);
1465 WLog_ERR(TAG,
"No response");
1466 http_response_free(response);
1470const char* http_response_get_body(
const HttpResponse* response)
1475 return response->BodyContent;
1478wHashTable* HashTable_New_String(
void)
1480 wHashTable* table = HashTable_New(FALSE);
1484 if (!HashTable_SetupForStringData(table, TRUE))
1486 HashTable_Free(table);
1489 HashTable_KeyObject(table)->fnObjectEquals = strings_equals_nocase;
1490 HashTable_ValueObject(table)->fnObjectEquals = strings_equals_nocase;
1494HttpResponse* http_response_new(
void)
1496 HttpResponse* response = (HttpResponse*)calloc(1,
sizeof(HttpResponse));
1501 response->Authenticates = HashTable_New_String();
1503 if (!response->Authenticates)
1506 response->SetCookie = HashTable_New_String();
1508 if (!response->SetCookie)
1511 response->data = Stream_New(NULL, 2048);
1513 if (!response->data)
1516 response->TransferEncoding = TransferEncodingIdentity;
1519 WINPR_PRAGMA_DIAG_PUSH
1520 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
1521 http_response_free(response);
1522 WINPR_PRAGMA_DIAG_POP
1526void http_response_free(HttpResponse* response)
1531 free((
void*)response->lines);
1532 HashTable_Free(response->Authenticates);
1533 HashTable_Free(response->SetCookie);
1534 Stream_Free(response->data, TRUE);
1538const char* http_request_get_uri(HttpRequest* request)
1543 return request->URI;
1546SSIZE_T http_request_get_content_length(HttpRequest* request)
1551 return (SSIZE_T)request->ContentLength;
1554BOOL http_request_set_content_length(HttpRequest* request,
size_t length)
1559 request->ContentLength = length;
1563UINT16 http_response_get_status_code(
const HttpResponse* response)
1565 WINPR_ASSERT(response);
1567 return response->StatusCode;
1570size_t http_response_get_body_length(
const HttpResponse* response)
1572 WINPR_ASSERT(response);
1574 return response->BodyLength;
1577const char* http_response_get_auth_token(
const HttpResponse* response,
const char* method)
1579 if (!response || !method)
1582 return HashTable_GetItemValue(response->Authenticates, method);
1585const char* http_response_get_setcookie(
const HttpResponse* response,
const char* cookie)
1587 if (!response || !cookie)
1590 return HashTable_GetItemValue(response->SetCookie, cookie);
1593TRANSFER_ENCODING http_response_get_transfer_encoding(
const HttpResponse* response)
1596 return TransferEncodingUnknown;
1598 return response->TransferEncoding;
1601BOOL http_response_is_websocket(
const HttpContext* http,
const HttpResponse* response)
1603 BOOL isWebsocket = FALSE;
1604 WINPR_DIGEST_CTX* sha1 = NULL;
1605 char* base64accept = NULL;
1606 BYTE sha1_digest[WINPR_SHA1_DIGEST_LENGTH];
1608 if (!http || !response)
1611 if (!http->websocketUpgrade || response->StatusCode != HTTP_STATUS_SWITCH_PROTOCOLS)
1614 if (response->SecWebsocketVersion && _stricmp(response->SecWebsocketVersion,
"13") != 0)
1617 if (!response->SecWebsocketAccept)
1622 sha1 = winpr_Digest_New();
1626 if (!winpr_Digest_Init(sha1, WINPR_MD_SHA1))
1629 if (!winpr_Digest_Update(sha1, (BYTE*)http->SecWebsocketKey, strlen(http->SecWebsocketKey)))
1631 if (!winpr_Digest_Update(sha1, (
const BYTE*)WEBSOCKET_MAGIC_GUID, strlen(WEBSOCKET_MAGIC_GUID)))
1634 if (!winpr_Digest_Final(sha1, sha1_digest,
sizeof(sha1_digest)))
1637 base64accept = crypto_base64_encode(sha1_digest, WINPR_SHA1_DIGEST_LENGTH);
1641 if (_stricmp(response->SecWebsocketAccept, base64accept) != 0)
1643 WLog_WARN(TAG,
"Webserver gave Websocket Upgrade response but sanity check failed");
1648 winpr_Digest_Free(sha1);
1653void http_response_log_error_status_(wLog* log, DWORD level,
const HttpResponse* response,
1654 const char* file,
size_t line,
const char* fkt)
1657 WINPR_ASSERT(response);
1659 if (!WLog_IsLevelActive(log, level))
1662 char buffer[64] = { 0 };
1663 const UINT16 status = http_response_get_status_code(response);
1664 WLog_PrintTextMessage(log, level, line, file, fkt,
"Unexpected HTTP status: %s",
1665 freerdp_http_status_string_format(status, buffer, ARRAYSIZE(buffer)));
1666 http_response_print(log, level, response, file, line, fkt);
1669static BOOL extract_cookie(
const void* pkey,
void* pvalue,
void* arg)
1671 const char* key = pkey;
1672 const char* value = pvalue;
1673 HttpContext* context = arg;
1677 WINPR_ASSERT(value);
1679 return http_context_set_cookie(context, key, value);
1682BOOL http_response_extract_cookies(
const HttpResponse* response, HttpContext* context)
1684 WINPR_ASSERT(response);
1685 WINPR_ASSERT(context);
1687 return HashTable_Foreach(response->SetCookie, extract_cookie, context);
1690FREERDP_LOCAL BOOL http_context_set_header(HttpContext* context,
const char* key,
const char* value,
1693 WINPR_ASSERT(context);
1695 va_start(ap, value);
1696 const BOOL rc = http_context_set_header_va(context, key, value, ap);
1701BOOL http_request_set_header(HttpRequest* request,
const char* key,
const char* value, ...)
1703 WINPR_ASSERT(request);
1707 va_start(ap, value);
1708 winpr_vasprintf(&v, &vlen, value, ap);
1710 const BOOL rc = HashTable_Insert(request->headers, key, v);
1715BOOL http_context_set_header_va(HttpContext* context,
const char* key,
const char* value,
1720 winpr_vasprintf(&v, &vlen, value, ap);
1721 const BOOL rc = HashTable_Insert(context->headers, key, v);
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.