FreeRDP
Loading...
Searching...
No Matches
core/gateway/http.c
1
20#include <freerdp/config.h>
21
22#include <errno.h>
23#include <stdint.h>
24
25#include <winpr/crt.h>
26#include <winpr/print.h>
27#include <winpr/stream.h>
28#include <winpr/string.h>
29#include <winpr/rpc.h>
30#include <winpr/sysinfo.h>
31
32#include <freerdp/log.h>
33#include <freerdp/crypto/crypto.h>
34
35/* websocket need sha1 for Sec-Websocket-Accept */
36#include <winpr/crypto.h>
37
38#ifdef FREERDP_HAVE_VALGRIND_MEMCHECK_H
39#include <valgrind/memcheck.h>
40#endif
41
42#include "http.h"
43#include "../tcp.h"
44#include "../utils.h"
45
46#define TAG FREERDP_TAG("core.gateway.http")
47
48#define RESPONSE_SIZE_LIMIT (64ULL * 1024ULL * 1024ULL)
49
50#define WEBSOCKET_MAGIC_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
51
52struct s_http_context
53{
54 char* Method;
55 char* URI;
56 char* Connection;
57 char* Pragma;
58 BOOL websocketUpgrade;
59 char* SecWebsocketKey;
60 wListDictionary* cookies;
61 wHashTable* headers;
62};
63
64struct s_http_request
65{
66 char* Method;
67 char* URI;
68 char* AuthScheme;
69 char* AuthParam;
70 char* Authorization;
71 size_t ContentLength;
72 TRANSFER_ENCODING TransferEncoding;
73 wHashTable* headers;
74};
75
76struct s_http_response
77{
78 size_t count;
79 char** lines;
80
81 UINT16 StatusCode;
82 char* ReasonPhrase;
83
84 size_t ContentLength;
85 char* ContentType;
86 TRANSFER_ENCODING TransferEncoding;
87 char* SecWebsocketVersion;
88 char* SecWebsocketAccept;
89
90 size_t BodyLength;
91 char* BodyContent;
92
93 wHashTable* Authenticates;
94 wHashTable* SetCookie;
95 wStream* data;
96};
97
98static wHashTable* HashTable_New_String(void);
99
100static const char* string_strnstr(const char* str1, const char* str2, size_t slen)
101{
102 char c = 0;
103 char sc = 0;
104 size_t len = 0;
105
106 if ((c = *str2++) != '\0')
107 {
108 len = strnlen(str2, slen + 1);
109
110 do
111 {
112 do
113 {
114 if (slen-- < 1 || (sc = *str1++) == '\0')
115 return nullptr;
116 } while (sc != c);
117
118 if (len > slen)
119 return nullptr;
120 } while (strncmp(str1, str2, len) != 0);
121
122 str1--;
123 }
124
125 return str1;
126}
127
128static BOOL strings_equals_nocase(const void* obj1, const void* obj2)
129{
130 if (!obj1 || !obj2)
131 return FALSE;
132
133 return _stricmp(obj1, obj2) == 0;
134}
135
136HttpContext* http_context_new(void)
137{
138 HttpContext* context = (HttpContext*)calloc(1, sizeof(HttpContext));
139 if (!context)
140 return nullptr;
141
142 context->headers = HashTable_New_String();
143 if (!context->headers)
144 goto fail;
145
146 context->cookies = ListDictionary_New(FALSE);
147 if (!context->cookies)
148 goto fail;
149
150 {
151 wObject* key = ListDictionary_KeyObject(context->cookies);
152 wObject* value = ListDictionary_ValueObject(context->cookies);
153 if (!key || !value)
154 goto fail;
155
156 key->fnObjectFree = winpr_ObjectStringFree;
157 key->fnObjectNew = winpr_ObjectStringClone;
158 value->fnObjectFree = winpr_ObjectStringFree;
159 value->fnObjectNew = winpr_ObjectStringClone;
160 }
161
162 return context;
163
164fail:
165 WINPR_PRAGMA_DIAG_PUSH
166 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
167 http_context_free(context);
168 WINPR_PRAGMA_DIAG_POP
169 return nullptr;
170}
171
172BOOL http_context_set_method(HttpContext* context, const char* Method)
173{
174 if (!context || !Method)
175 return FALSE;
176
177 free(context->Method);
178 context->Method = _strdup(Method);
179
180 return (context->Method != nullptr);
181}
182
183BOOL http_request_set_content_type(HttpRequest* request, const char* ContentType)
184{
185 if (!request || !ContentType)
186 return FALSE;
187
188 return http_request_set_header(request, "Content-Type", "%s", ContentType);
189}
190
191const char* http_context_get_uri(HttpContext* context)
192{
193 if (!context)
194 return nullptr;
195
196 return context->URI;
197}
198
199BOOL http_context_set_uri(HttpContext* context, const char* URI)
200{
201 if (!context || !URI)
202 return FALSE;
203
204 free(context->URI);
205 context->URI = _strdup(URI);
206
207 return (context->URI != nullptr);
208}
209
210BOOL http_context_set_user_agent(HttpContext* context, const char* UserAgent)
211{
212 if (!context || !UserAgent)
213 return FALSE;
214
215 return http_context_set_header(context, "User-Agent", "%s", UserAgent);
216}
217
218BOOL http_context_set_x_ms_user_agent(HttpContext* context, const char* X_MS_UserAgent)
219{
220 if (!context || !X_MS_UserAgent)
221 return FALSE;
222
223 return http_context_set_header(context, "X-MS-User-Agent", "%s", X_MS_UserAgent);
224}
225
226BOOL http_context_set_host(HttpContext* context, const char* Host)
227{
228 if (!context || !Host)
229 return FALSE;
230
231 return http_context_set_header(context, "Host", "%s", Host);
232}
233
234BOOL http_context_set_accept(HttpContext* context, const char* Accept)
235{
236 if (!context || !Accept)
237 return FALSE;
238
239 return http_context_set_header(context, "Accept", "%s", Accept);
240}
241
242BOOL http_context_set_cache_control(HttpContext* context, const char* CacheControl)
243{
244 if (!context || !CacheControl)
245 return FALSE;
246
247 return http_context_set_header(context, "Cache-Control", "%s", CacheControl);
248}
249
250BOOL http_context_set_connection(HttpContext* context, const char* Connection)
251{
252 if (!context || !Connection)
253 return FALSE;
254
255 free(context->Connection);
256 context->Connection = _strdup(Connection);
257
258 return (context->Connection != nullptr);
259}
260
261WINPR_ATTR_FORMAT_ARG(2, 0)
262static BOOL list_append(HttpContext* context, WINPR_FORMAT_ARG const char* str, va_list ap)
263{
264 BOOL rc = FALSE;
265 va_list vat = WINPR_C_ARRAY_INIT;
266 char* Pragma = nullptr;
267 size_t PragmaSize = 0;
268
269 va_copy(vat, ap);
270 const int size = winpr_vasprintf(&Pragma, &PragmaSize, str, ap);
271 va_end(vat);
272
273 if (size <= 0)
274 goto fail;
275
276 {
277 char* sstr = nullptr;
278 size_t slen = 0;
279 if (context->Pragma)
280 {
281 winpr_asprintf(&sstr, &slen, "%s, %s", context->Pragma, Pragma);
282 free(Pragma);
283 }
284 else
285 sstr = Pragma;
286 Pragma = nullptr;
287
288 free(context->Pragma);
289 context->Pragma = sstr;
290 }
291
292 rc = TRUE;
293
294fail:
295 va_end(ap);
296 return rc;
297}
298
299WINPR_ATTR_FORMAT_ARG(2, 3)
300BOOL http_context_set_pragma(HttpContext* context, WINPR_FORMAT_ARG const char* Pragma, ...)
301{
302 if (!context || !Pragma)
303 return FALSE;
304
305 free(context->Pragma);
306 context->Pragma = nullptr;
307
308 va_list ap = WINPR_C_ARRAY_INIT;
309 va_start(ap, Pragma);
310 return list_append(context, Pragma, ap);
311}
312
313WINPR_ATTR_FORMAT_ARG(2, 3)
314BOOL http_context_append_pragma(HttpContext* context, const char* Pragma, ...)
315{
316 if (!context || !Pragma)
317 return FALSE;
318
319 va_list ap = WINPR_C_ARRAY_INIT;
320 va_start(ap, Pragma);
321 return list_append(context, Pragma, ap);
322}
323
324static char* guid2str(const GUID* guid, char* buffer, size_t len)
325{
326 if (!guid)
327 return nullptr;
328 RPC_CSTR strguid = nullptr;
329
330 RPC_STATUS rpcStatus = UuidToStringA(guid, &strguid);
331
332 if (rpcStatus != RPC_S_OK)
333 return nullptr;
334
335 (void)sprintf_s(buffer, len, "{%s}", strguid);
336 RpcStringFreeA(&strguid);
337 return buffer;
338}
339
340BOOL http_context_set_rdg_connection_id(HttpContext* context, const GUID* RdgConnectionId)
341{
342 if (!context || !RdgConnectionId)
343 return FALSE;
344
345 char buffer[64] = WINPR_C_ARRAY_INIT;
346 return http_context_set_header(context, "RDG-Connection-Id", "%s",
347 guid2str(RdgConnectionId, buffer, sizeof(buffer)));
348}
349
350BOOL http_context_set_rdg_correlation_id(HttpContext* context, const GUID* RdgCorrelationId)
351{
352 if (!context || !RdgCorrelationId)
353 return FALSE;
354
355 char buffer[64] = WINPR_C_ARRAY_INIT;
356 return http_context_set_header(context, "RDG-Correlation-Id", "%s",
357 guid2str(RdgCorrelationId, buffer, sizeof(buffer)));
358}
359
360BOOL http_context_enable_websocket_upgrade(HttpContext* context, BOOL enable)
361{
362 WINPR_ASSERT(context);
363
364 if (enable)
365 {
366 GUID key = WINPR_C_ARRAY_INIT;
367 if (RPC_S_OK != UuidCreate(&key))
368 return FALSE;
369
370 free(context->SecWebsocketKey);
371 context->SecWebsocketKey = crypto_base64_encode((BYTE*)&key, sizeof(key));
372 if (!context->SecWebsocketKey)
373 return FALSE;
374 }
375
376 context->websocketUpgrade = enable;
377 return TRUE;
378}
379
380BOOL http_context_is_websocket_upgrade_enabled(HttpContext* context)
381{
382 return context->websocketUpgrade;
383}
384
385BOOL http_context_set_rdg_auth_scheme(HttpContext* context, const char* RdgAuthScheme)
386{
387 if (!context || !RdgAuthScheme)
388 return FALSE;
389
390 return http_context_set_header(context, "RDG-Auth-Scheme", "%s", RdgAuthScheme);
391}
392
393BOOL http_context_set_cookie(HttpContext* context, const char* CookieName, const char* CookieValue)
394{
395 if (!context || !CookieName || !CookieValue)
396 return FALSE;
397 if (ListDictionary_Contains(context->cookies, CookieName))
398 {
399 if (!ListDictionary_SetItemValue(context->cookies, CookieName, CookieValue))
400 return FALSE;
401 }
402 else
403 {
404 if (!ListDictionary_Add(context->cookies, CookieName, CookieValue))
405 return FALSE;
406 }
407 return TRUE;
408}
409
410void http_context_free(HttpContext* context)
411{
412 if (context)
413 {
414 free(context->SecWebsocketKey);
415 free(context->URI);
416 free(context->Method);
417 free(context->Connection);
418 free(context->Pragma);
419 HashTable_Free(context->headers);
420 ListDictionary_Free(context->cookies);
421 free(context);
422 }
423}
424
425BOOL http_request_set_method(HttpRequest* request, const char* Method)
426{
427 if (!request || !Method)
428 return FALSE;
429
430 free(request->Method);
431 request->Method = _strdup(Method);
432
433 return (request->Method != nullptr);
434}
435
436BOOL http_request_set_uri(HttpRequest* request, const char* URI)
437{
438 if (!request || !URI)
439 return FALSE;
440
441 free(request->URI);
442 request->URI = _strdup(URI);
443
444 return (request->URI != nullptr);
445}
446
447BOOL http_request_set_auth_scheme(HttpRequest* request, const char* AuthScheme)
448{
449 if (!request || !AuthScheme)
450 return FALSE;
451
452 free(request->AuthScheme);
453 request->AuthScheme = _strdup(AuthScheme);
454
455 return (request->AuthScheme != nullptr);
456}
457
458BOOL http_request_set_auth_param(HttpRequest* request, const char* AuthParam)
459{
460 if (!request || !AuthParam)
461 return FALSE;
462
463 free(request->AuthParam);
464 request->AuthParam = _strdup(AuthParam);
465
466 return (request->AuthParam != nullptr);
467}
468
469BOOL http_request_set_transfer_encoding(HttpRequest* request, TRANSFER_ENCODING TransferEncoding)
470{
471 if (!request || TransferEncoding == TransferEncodingUnknown)
472 return FALSE;
473
474 request->TransferEncoding = TransferEncoding;
475
476 return TRUE;
477}
478
479WINPR_ATTR_FORMAT_ARG(2, 3)
480static BOOL http_encode_print(wStream* s, WINPR_FORMAT_ARG const char* fmt, ...)
481{
482 char* str = nullptr;
483 va_list ap = WINPR_C_ARRAY_INIT;
484 int length = 0;
485 int used = 0;
486
487 if (!s || !fmt)
488 return FALSE;
489
490 va_start(ap, fmt);
491 length = vsnprintf(nullptr, 0, fmt, ap) + 1;
492 va_end(ap);
493
494 if (!Stream_EnsureRemainingCapacity(s, (size_t)length))
495 return FALSE;
496
497 str = (char*)Stream_Pointer(s);
498 va_start(ap, fmt);
499 used = vsnprintf(str, (size_t)length, fmt, ap);
500 va_end(ap);
501
502 /* Strip the trailing '\0' from the string. */
503 if ((used + 1) != length)
504 return FALSE;
505
506 Stream_Seek(s, (size_t)used);
507 return TRUE;
508}
509
510static BOOL http_encode_body_line(wStream* s, const char* param, const char* value)
511{
512 if (!s || !param || !value)
513 return FALSE;
514
515 return http_encode_print(s, "%s: %s\r\n", param, value);
516}
517
518static BOOL http_encode_content_length_line(wStream* s, size_t ContentLength)
519{
520 return http_encode_print(s, "Content-Length: %" PRIuz "\r\n", ContentLength);
521}
522
523static BOOL http_encode_header_line(wStream* s, const char* Method, const char* URI)
524{
525 if (!s || !Method || !URI)
526 return FALSE;
527
528 return http_encode_print(s, "%s %s HTTP/1.1\r\n", Method, URI);
529}
530
531static BOOL http_encode_authorization_line(wStream* s, const char* AuthScheme,
532 const char* AuthParam)
533{
534 if (!s || !AuthScheme || !AuthParam)
535 return FALSE;
536
537 return http_encode_print(s, "Authorization: %s %s\r\n", AuthScheme, AuthParam);
538}
539
540static BOOL http_encode_cookie_line(wStream* s, wListDictionary* cookies)
541{
542 ULONG_PTR* keys = nullptr;
543 BOOL status = TRUE;
544
545 if (!s && !cookies)
546 return FALSE;
547
548 ListDictionary_Lock(cookies);
549 const size_t count = ListDictionary_GetKeys(cookies, &keys);
550
551 if (count == 0)
552 goto unlock;
553
554 status = http_encode_print(s, "Cookie: ");
555 if (!status)
556 goto unlock;
557
558 for (size_t x = 0; status && x < count; x++)
559 {
560 char* cur = (char*)ListDictionary_GetItemValue(cookies, (void*)keys[x]);
561 if (!cur)
562 {
563 status = FALSE;
564 continue;
565 }
566 if (x > 0)
567 {
568 status = http_encode_print(s, "; ");
569 if (!status)
570 continue;
571 }
572 status = http_encode_print(s, "%s=%s", (char*)keys[x], cur);
573 }
574
575 status = http_encode_print(s, "\r\n");
576unlock:
577 free(keys);
578 ListDictionary_Unlock(cookies);
579 return status;
580}
581
582static BOOL write_headers(const void* pkey, void* pvalue, void* arg)
583{
584 const char* key = pkey;
585 const char* value = pvalue;
586 wStream* s = arg;
587
588 WINPR_ASSERT(key);
589 WINPR_ASSERT(value);
590 WINPR_ASSERT(s);
591
592 return http_encode_body_line(s, key, value);
593}
594
595wStream* http_request_write(HttpContext* context, HttpRequest* request)
596{
597 wStream* s = nullptr;
598
599 if (!context || !request)
600 return nullptr;
601
602 s = Stream_New(nullptr, 1024);
603
604 if (!s)
605 return nullptr;
606
607 if (!http_encode_header_line(s, request->Method, request->URI) ||
608
609 !http_encode_body_line(s, "Pragma", context->Pragma))
610 goto fail;
611
612 if (!context->websocketUpgrade)
613 {
614 if (!http_encode_body_line(s, "Connection", context->Connection))
615 goto fail;
616 }
617 else
618 {
619 if (!http_encode_body_line(s, "Connection", "Upgrade") ||
620 !http_encode_body_line(s, "Upgrade", "websocket") ||
621 !http_encode_body_line(s, "Sec-Websocket-Version", "13") ||
622 !http_encode_body_line(s, "Sec-Websocket-Key", context->SecWebsocketKey))
623 goto fail;
624 }
625
626 if (request->TransferEncoding != TransferEncodingIdentity)
627 {
628 if (request->TransferEncoding == TransferEncodingChunked)
629 {
630 if (!http_encode_body_line(s, "Transfer-Encoding", "chunked"))
631 goto fail;
632 }
633 else
634 goto fail;
635 }
636 else
637 {
638 if (!http_encode_content_length_line(s, request->ContentLength))
639 goto fail;
640 }
641
642 if (!utils_str_is_empty(request->Authorization))
643 {
644 if (!http_encode_body_line(s, "Authorization", request->Authorization))
645 goto fail;
646 }
647 else if (!utils_str_is_empty(request->AuthScheme) && !utils_str_is_empty(request->AuthParam))
648 {
649 if (!http_encode_authorization_line(s, request->AuthScheme, request->AuthParam))
650 goto fail;
651 }
652
653 if (!HashTable_Foreach(context->headers, write_headers, s))
654 goto fail;
655
656 if (!HashTable_Foreach(request->headers, write_headers, s))
657 goto fail;
658
659 if (!http_encode_cookie_line(s, context->cookies))
660 goto fail;
661
662 if (!http_encode_print(s, "\r\n"))
663 goto fail;
664
665 Stream_SealLength(s);
666 return s;
667fail:
668 Stream_Free(s, TRUE);
669 return nullptr;
670}
671
672HttpRequest* http_request_new(void)
673{
674 HttpRequest* request = (HttpRequest*)calloc(1, sizeof(HttpRequest));
675 if (!request)
676 return nullptr;
677
678 request->headers = HashTable_New_String();
679 if (!request->headers)
680 goto fail;
681 request->TransferEncoding = TransferEncodingIdentity;
682 return request;
683fail:
684 http_request_free(request);
685 return nullptr;
686}
687
688void http_request_free(HttpRequest* request)
689{
690 if (!request)
691 return;
692
693 free(request->AuthParam);
694 free(request->AuthScheme);
695 free(request->Authorization);
696 free(request->Method);
697 free(request->URI);
698 HashTable_Free(request->headers);
699 free(request);
700}
701
702static BOOL http_response_parse_header_status_line(HttpResponse* response, const char* status_line)
703{
704 BOOL rc = FALSE;
705 char* separator = nullptr;
706 char* status_code = nullptr;
707
708 if (!response)
709 goto fail;
710
711 if (status_line)
712 separator = strchr(status_line, ' ');
713
714 if (!separator)
715 goto fail;
716
717 status_code = separator + 1;
718 separator = strchr(status_code, ' ');
719
720 if (!separator)
721 goto fail;
722
723 {
724 const char* reason_phrase = separator + 1;
725 *separator = '\0';
726 errno = 0;
727 {
728 long val = strtol(status_code, nullptr, 0);
729
730 if ((errno != 0) || (val < 0) || (val > INT16_MAX))
731 goto fail;
732
733 response->StatusCode = (UINT16)val;
734 }
735 free(response->ReasonPhrase);
736 response->ReasonPhrase = _strdup(reason_phrase);
737 }
738
739 if (!response->ReasonPhrase)
740 goto fail;
741
742 *separator = ' ';
743 rc = TRUE;
744fail:
745
746 if (!rc)
747 WLog_ERR(TAG, "http_response_parse_header_status_line failed [%s]", status_line);
748
749 return rc;
750}
751
752static BOOL http_response_parse_header_field(HttpResponse* response, const char* name,
753 const char* value)
754{
755 WINPR_ASSERT(response);
756
757 if (!name || !value)
758 return FALSE;
759
760 if (_stricmp(name, "Content-Length") == 0)
761 {
762 unsigned long long val = 0;
763 errno = 0;
764 val = _strtoui64(value, nullptr, 0);
765
766 if ((errno != 0) || (val > INT32_MAX))
767 return FALSE;
768
769 response->ContentLength = WINPR_ASSERTING_INT_CAST(size_t, val);
770 return TRUE;
771 }
772
773 if (_stricmp(name, "Content-Type") == 0)
774 {
775 free(response->ContentType);
776 response->ContentType = _strdup(value);
777
778 return response->ContentType != nullptr;
779 }
780
781 if (_stricmp(name, "Transfer-Encoding") == 0)
782 {
783 if (_stricmp(value, "identity") == 0)
784 response->TransferEncoding = TransferEncodingIdentity;
785 else if (_stricmp(value, "chunked") == 0)
786 response->TransferEncoding = TransferEncodingChunked;
787 else
788 response->TransferEncoding = TransferEncodingUnknown;
789
790 return TRUE;
791 }
792
793 if (_stricmp(name, "Sec-WebSocket-Version") == 0)
794 {
795 free(response->SecWebsocketVersion);
796 response->SecWebsocketVersion = _strdup(value);
797
798 return response->SecWebsocketVersion != nullptr;
799 }
800
801 if (_stricmp(name, "Sec-WebSocket-Accept") == 0)
802 {
803 free(response->SecWebsocketAccept);
804 response->SecWebsocketAccept = _strdup(value);
805
806 return response->SecWebsocketAccept != nullptr;
807 }
808
809 if (_stricmp(name, "WWW-Authenticate") == 0)
810 {
811 const char* authScheme = value;
812 const char* authValue = "";
813 char* separator = strchr(value, ' ');
814
815 if (separator)
816 {
817 /* WWW-Authenticate: Basic realm=""
818 * WWW-Authenticate: NTLM base64token
819 * WWW-Authenticate: Digest realm="testrealm@host.com", qop="auth, auth-int",
820 * nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
821 * opaque="5ccc069c403ebaf9f0171e9517f40e41"
822 */
823 *separator = '\0';
824 authValue = separator + 1;
825 }
826
827 return HashTable_Insert(response->Authenticates, authScheme, authValue);
828 }
829
830 if (_stricmp(name, "Set-Cookie") == 0)
831 {
832 char* separator = strchr(value, '=');
833
834 if (!separator)
835 return FALSE;
836
837 /* Set-Cookie: name=value
838 * Set-Cookie: name=value; Attribute=value
839 * Set-Cookie: name="value with spaces"; Attribute=value
840 */
841 *separator = '\0';
842 const char* CookieName = value;
843 char* CookieValue = separator + 1;
844
845 if (*CookieValue == '"')
846 {
847 char* p = CookieValue;
848 while (*p != '"' && *p != '\0')
849 {
850 p++;
851 if (*p == '\\')
852 p++;
853 }
854 *p = '\0';
855 }
856 else
857 {
858 char* p = CookieValue;
859 while (*p != ';' && *p != '\0' && *p != ' ')
860 {
861 p++;
862 }
863 *p = '\0';
864 }
865 return HashTable_Insert(response->SetCookie, CookieName, CookieValue);
866 }
867
868 /* Ignore unknown lines */
869 return TRUE;
870}
871
872static BOOL http_response_parse_header(HttpResponse* response)
873{
874 BOOL rc = FALSE;
875 char c = 0;
876 char* line = nullptr;
877 char* name = nullptr;
878 char* colon_pos = nullptr;
879 char* end_of_header = nullptr;
880 char end_of_header_char = 0;
881
882 if (!response)
883 goto fail;
884
885 if (!response->lines)
886 goto fail;
887
888 if (!http_response_parse_header_status_line(response, response->lines[0]))
889 goto fail;
890
891 for (size_t count = 1; count < response->count; count++)
892 {
893 line = response->lines[count];
894
904 if (line)
905 colon_pos = strchr(line, ':');
906 else
907 colon_pos = nullptr;
908
909 if ((colon_pos == nullptr) || (colon_pos == line))
910 return FALSE;
911
912 /* retrieve the position just after header name */
913 for (end_of_header = colon_pos; end_of_header != line; end_of_header--)
914 {
915 c = end_of_header[-1];
916
917 if (c != ' ' && c != '\t' && c != ':')
918 break;
919 }
920
921 if (end_of_header == line)
922 goto fail;
923
924 end_of_header_char = *end_of_header;
925 *end_of_header = '\0';
926 name = line;
927
928 /* eat space and tabs before header value */
929 char* value = colon_pos + 1;
930 for (; *value; value++)
931 {
932 if ((*value != ' ') && (*value != '\t'))
933 break;
934 }
935
936 const int res = http_response_parse_header_field(response, name, value);
937 *end_of_header = end_of_header_char;
938 if (!res)
939 goto fail;
940 }
941
942 rc = TRUE;
943fail:
944
945 if (!rc)
946 WLog_ERR(TAG, "parsing failed");
947
948 return rc;
949}
950
951static void http_response_print(wLog* log, DWORD level, const HttpResponse* response,
952 const char* file, size_t line, const char* fkt)
953{
954 char buffer[64] = WINPR_C_ARRAY_INIT;
955
956 WINPR_ASSERT(log);
957 WINPR_ASSERT(response);
958
959 if (!WLog_IsLevelActive(log, level))
960 return;
961
962 const long status = http_response_get_status_code(response);
963 WLog_PrintTextMessage(log, level, line, file, fkt, "HTTP status: %s",
964 freerdp_http_status_string_format(status, buffer, ARRAYSIZE(buffer)));
965
966 if (WLog_IsLevelActive(log, WLOG_DEBUG))
967 {
968 for (size_t i = 0; i < response->count; i++)
969 WLog_PrintTextMessage(log, WLOG_DEBUG, line, file, fkt, "[%" PRIuz "] %s", i,
970 response->lines[i]);
971 }
972
973 if (response->ReasonPhrase)
974 WLog_PrintTextMessage(log, level, line, file, fkt, "[reason] %s", response->ReasonPhrase);
975
976 if (WLog_IsLevelActive(log, WLOG_TRACE))
977 {
978 WLog_PrintTextMessage(log, WLOG_TRACE, line, file, fkt, "[body][%" PRIuz "] %s",
979 response->BodyLength, response->BodyContent);
980 }
981}
982
983static BOOL http_use_content_length(const char* cur)
984{
985 size_t pos = 0;
986
987 if (!cur)
988 return FALSE;
989
990 if (_strnicmp(cur, "application/rpc", 15) == 0)
991 pos = 15;
992 else if (_strnicmp(cur, "text/plain", 10) == 0)
993 pos = 10;
994 else if (_strnicmp(cur, "text/html", 9) == 0)
995 pos = 9;
996 else if (_strnicmp(cur, "application/json", 16) == 0)
997 pos = 16;
998
999 if (pos > 0)
1000 {
1001 char end = cur[pos];
1002
1003 switch (end)
1004 {
1005 case ' ':
1006 case ';':
1007 case '\0':
1008 case '\r':
1009 case '\n':
1010 return TRUE;
1011
1012 default:
1013 return FALSE;
1014 }
1015 }
1016
1017 return FALSE;
1018}
1019
1020static int print_bio_error(const char* str, size_t len, void* bp)
1021{
1022 wLog* log = bp;
1023
1024 WINPR_UNUSED(bp);
1025 WLog_Print(log, WLOG_ERROR, "%s", str);
1026 if (len > INT32_MAX)
1027 return -1;
1028 return (int)len;
1029}
1030
1031int http_chuncked_read(BIO* bio, BYTE* pBuffer, size_t size,
1032 http_encoding_chunked_context* encodingContext)
1033{
1034 int status = 0;
1035 int effectiveDataLen = 0;
1036 WINPR_ASSERT(bio);
1037 WINPR_ASSERT(pBuffer);
1038 WINPR_ASSERT(encodingContext != nullptr);
1039 WINPR_ASSERT(size <= INT32_MAX);
1040 while (TRUE)
1041 {
1042 switch (encodingContext->state)
1043 {
1044 case ChunkStateData:
1045 {
1046 const size_t rd =
1047 (size > encodingContext->nextOffset ? encodingContext->nextOffset : size);
1048 if (rd > INT32_MAX)
1049 return -1;
1050
1051 ERR_clear_error();
1052 status = BIO_read(bio, pBuffer, (int)rd);
1053 if (status <= 0)
1054 return (effectiveDataLen > 0 ? effectiveDataLen : status);
1055
1056 encodingContext->nextOffset -= WINPR_ASSERTING_INT_CAST(uint32_t, status);
1057 if (encodingContext->nextOffset == 0)
1058 {
1059 encodingContext->state = ChunkStateFooter;
1060 encodingContext->headerFooterPos = 0;
1061 }
1062 effectiveDataLen += status;
1063
1064 if ((size_t)status == size)
1065 return effectiveDataLen;
1066
1067 pBuffer += status;
1068 size -= (size_t)status;
1069 }
1070 break;
1071 case ChunkStateFooter:
1072 {
1073 char _dummy[2] = WINPR_C_ARRAY_INIT;
1074 WINPR_ASSERT(encodingContext->nextOffset == 0);
1075 WINPR_ASSERT(encodingContext->headerFooterPos < 2);
1076 ERR_clear_error();
1077 status = BIO_read(bio, _dummy, (int)(2 - encodingContext->headerFooterPos));
1078 if (status >= 0)
1079 {
1080 encodingContext->headerFooterPos += (size_t)status;
1081 if (encodingContext->headerFooterPos == 2)
1082 {
1083 encodingContext->state = ChunkStateLenghHeader;
1084 encodingContext->headerFooterPos = 0;
1085 }
1086 }
1087 else
1088 return (effectiveDataLen > 0 ? effectiveDataLen : status);
1089 }
1090 break;
1091 case ChunkStateLenghHeader:
1092 {
1093 BOOL _haveNewLine = FALSE;
1094 char* dst = &encodingContext->lenBuffer[encodingContext->headerFooterPos];
1095 WINPR_ASSERT(encodingContext->nextOffset == 0);
1096 while (encodingContext->headerFooterPos < 10 && !_haveNewLine)
1097 {
1098 ERR_clear_error();
1099 status = BIO_read(bio, dst, 1);
1100 if (status >= 0)
1101 {
1102 if (*dst == '\n')
1103 _haveNewLine = TRUE;
1104 encodingContext->headerFooterPos += (size_t)status;
1105 dst += status;
1106 }
1107 else
1108 return (effectiveDataLen > 0 ? effectiveDataLen : status);
1109 }
1110 *dst = '\0';
1111 /* strtoul is tricky, error are reported via errno, we also need
1112 * to ensure the result does not overflow */
1113 errno = 0;
1114 size_t tmp = strtoul(encodingContext->lenBuffer, nullptr, 16);
1115 if ((errno != 0) || (tmp > SIZE_MAX))
1116 {
1117 /* denote end of stream if something bad happens */
1118 encodingContext->nextOffset = 0;
1119 encodingContext->state = ChunkStateEnd;
1120 return -1;
1121 }
1122 encodingContext->nextOffset = tmp;
1123 encodingContext->state = ChunkStateData;
1124
1125 if (encodingContext->nextOffset == 0)
1126 { /* end of stream */
1127 WLog_DBG(TAG, "chunked encoding end of stream received");
1128 encodingContext->headerFooterPos = 0;
1129 encodingContext->state = ChunkStateEnd;
1130 return (effectiveDataLen > 0 ? effectiveDataLen : 0);
1131 }
1132 }
1133 break;
1134 default:
1135 /* invalid state / ChunkStateEnd */
1136 return -1;
1137 }
1138 }
1139}
1140
1141#define sleep_or_timeout(tls, startMS, timeoutMS) \
1142 sleep_or_timeout_((tls), (startMS), (timeoutMS), __FILE__, __func__, __LINE__)
1143static BOOL sleep_or_timeout_(rdpTls* tls, UINT64 startMS, UINT32 timeoutMS, const char* file,
1144 const char* fkt, size_t line)
1145{
1146 WINPR_ASSERT(tls);
1147
1148 USleep(100);
1149 const UINT64 nowMS = GetTickCount64();
1150 if (nowMS - startMS > timeoutMS)
1151 {
1152 DWORD level = WLOG_ERROR;
1153 wLog* log = WLog_Get(TAG);
1154 if (WLog_IsLevelActive(log, level))
1155 WLog_PrintTextMessage(log, level, line, file, fkt, "timeout [%" PRIu32 "ms] exceeded",
1156 timeoutMS);
1157 return TRUE;
1158 }
1159 if (!BIO_should_retry(tls->bio))
1160 {
1161 DWORD level = WLOG_ERROR;
1162 wLog* log = WLog_Get(TAG);
1163 if (WLog_IsLevelActive(log, level))
1164 {
1165 WLog_PrintTextMessage(log, level, line, file, fkt, "Retries exceeded");
1166 ERR_print_errors_cb(print_bio_error, log);
1167 }
1168 return TRUE;
1169 }
1170 if (freerdp_shall_disconnect_context(tls->context))
1171 return TRUE;
1172
1173 return FALSE;
1174}
1175
1176static SSIZE_T http_response_recv_line(rdpTls* tls, HttpResponse* response)
1177{
1178 WINPR_ASSERT(tls);
1179 WINPR_ASSERT(response);
1180
1181 SSIZE_T payloadOffset = -1;
1182 const UINT32 timeoutMS =
1183 freerdp_settings_get_uint32(tls->context->settings, FreeRDP_TcpConnectTimeout);
1184 const UINT64 startMS = GetTickCount64();
1185 while (payloadOffset <= 0)
1186 {
1187 size_t bodyLength = 0;
1188 size_t position = 0;
1189 int status = -1;
1190 size_t s = 0;
1191 char* end = nullptr;
1192 /* Read until we encounter \r\n\r\n */
1193 ERR_clear_error();
1194
1195 status = BIO_read(tls->bio, Stream_Pointer(response->data), 1);
1196 if (status <= 0)
1197 {
1198 if (sleep_or_timeout(tls, startMS, timeoutMS))
1199 goto out_error;
1200 continue;
1201 }
1202
1203#ifdef FREERDP_HAVE_VALGRIND_MEMCHECK_H
1204 VALGRIND_MAKE_MEM_DEFINED(Stream_Pointer(response->data), status);
1205#endif
1206 Stream_Seek(response->data, (size_t)status);
1207
1208 if (!Stream_EnsureRemainingCapacity(response->data, 1024))
1209 goto out_error;
1210
1211 position = Stream_GetPosition(response->data);
1212
1213 if (position < 4)
1214 continue;
1215 else if (position > RESPONSE_SIZE_LIMIT)
1216 {
1217 WLog_ERR(TAG, "Request header too large! (%" PRIuz " bytes) Aborting!", bodyLength);
1218 goto out_error;
1219 }
1220
1221 /* Always check at most the lase 8 bytes for occurrence of the desired
1222 * sequence of \r\n\r\n */
1223 s = (position > 8) ? 8 : position;
1224 end = (char*)Stream_Pointer(response->data) - s;
1225
1226 if (string_strnstr(end, "\r\n\r\n", s) != nullptr)
1227 payloadOffset = WINPR_ASSERTING_INT_CAST(SSIZE_T, Stream_GetPosition(response->data));
1228 }
1229
1230out_error:
1231 return payloadOffset;
1232}
1233
1234static BOOL http_response_recv_body(rdpTls* tls, HttpResponse* response, BOOL readContentLength,
1235 size_t payloadOffset, size_t bodyLength)
1236{
1237 BOOL rc = FALSE;
1238
1239 WINPR_ASSERT(tls);
1240 WINPR_ASSERT(response);
1241
1242 const UINT64 startMS = GetTickCount64();
1243 const UINT32 timeoutMS =
1244 freerdp_settings_get_uint32(tls->context->settings, FreeRDP_TcpConnectTimeout);
1245
1246 if ((response->TransferEncoding == TransferEncodingChunked) && readContentLength)
1247 {
1248 http_encoding_chunked_context ctx = WINPR_C_ARRAY_INIT;
1249 ctx.state = ChunkStateLenghHeader;
1250 ctx.nextOffset = 0;
1251 ctx.headerFooterPos = 0;
1252 int full_len = 0;
1253 do
1254 {
1255 if (!Stream_EnsureRemainingCapacity(response->data, 2048))
1256 goto out_error;
1257
1258 int status = http_chuncked_read(tls->bio, Stream_Pointer(response->data),
1259 Stream_GetRemainingCapacity(response->data), &ctx);
1260 if (status <= 0)
1261 {
1262 if (sleep_or_timeout(tls, startMS, timeoutMS))
1263 goto out_error;
1264 }
1265 else
1266 {
1267 Stream_Seek(response->data, (size_t)status);
1268 full_len += status;
1269 }
1270 } while (ctx.state != ChunkStateEnd);
1271 response->BodyLength = WINPR_ASSERTING_INT_CAST(uint32_t, full_len);
1272 if (response->BodyLength > 0)
1273 response->BodyContent = &(Stream_BufferAs(response->data, char))[payloadOffset];
1274 }
1275 else
1276 {
1277 while (response->BodyLength < bodyLength)
1278 {
1279 int status = 0;
1280
1281 if (!Stream_EnsureRemainingCapacity(response->data, bodyLength - response->BodyLength))
1282 goto out_error;
1283
1284 ERR_clear_error();
1285 size_t diff = bodyLength - response->BodyLength;
1286 if (diff > INT32_MAX)
1287 diff = INT32_MAX;
1288 status = BIO_read(tls->bio, Stream_Pointer(response->data), (int)diff);
1289
1290 if (status <= 0)
1291 {
1292 if (sleep_or_timeout(tls, startMS, timeoutMS))
1293 goto out_error;
1294 continue;
1295 }
1296
1297 Stream_Seek(response->data, (size_t)status);
1298 response->BodyLength += (unsigned long)status;
1299
1300 if (response->BodyLength > RESPONSE_SIZE_LIMIT)
1301 {
1302 WLog_ERR(TAG, "Request body too large! (%" PRIuz " bytes) Aborting!",
1303 response->BodyLength);
1304 goto out_error;
1305 }
1306 }
1307
1308 if (response->BodyLength > 0)
1309 response->BodyContent = &(Stream_BufferAs(response->data, char))[payloadOffset];
1310
1311 if (bodyLength != response->BodyLength)
1312 {
1313 WLog_WARN(TAG, "%s unexpected body length: actual: %" PRIuz ", expected: %" PRIuz,
1314 response->ContentType, response->BodyLength, bodyLength);
1315
1316 if (bodyLength > 0)
1317 response->BodyLength = MIN(bodyLength, response->BodyLength);
1318 }
1319
1320 /* '\0' terminate the http body */
1321 if (!Stream_EnsureRemainingCapacity(response->data, sizeof(UINT16)))
1322 goto out_error;
1323 Stream_Write_UINT16(response->data, 0);
1324 }
1325
1326 rc = TRUE;
1327out_error:
1328 return rc;
1329}
1330
1331static void clear_lines(HttpResponse* response)
1332{
1333 WINPR_ASSERT(response);
1334
1335 for (size_t x = 0; x < response->count; x++)
1336 {
1337 WINPR_ASSERT(response->lines);
1338 char* line = response->lines[x];
1339 free(line);
1340 }
1341
1342 free((void*)response->lines);
1343 response->lines = nullptr;
1344 response->count = 0;
1345}
1346
1347HttpResponse* http_response_recv(rdpTls* tls, BOOL readContentLength)
1348{
1349 size_t bodyLength = 0;
1350 HttpResponse* response = http_response_new();
1351
1352 if (!response)
1353 return nullptr;
1354
1355 response->ContentLength = 0;
1356
1357 const SSIZE_T payloadOffset = http_response_recv_line(tls, response);
1358 if (payloadOffset < 0)
1359 goto out_error;
1360
1361 if (payloadOffset)
1362 {
1363 size_t count = 0;
1364 char* buffer = Stream_BufferAs(response->data, char);
1365 const char* line = Stream_BufferAs(response->data, char);
1366 char* context = nullptr;
1367
1368 while ((line = string_strnstr(line, "\r\n",
1369 WINPR_ASSERTING_INT_CAST(size_t, payloadOffset) -
1370 WINPR_ASSERTING_INT_CAST(size_t, (line - buffer)) - 2UL)))
1371 {
1372 line += 2;
1373 count++;
1374 }
1375
1376 clear_lines(response);
1377 response->count = count;
1378
1379 if (count)
1380 {
1381 response->lines = (char**)calloc(response->count, sizeof(char*));
1382
1383 if (!response->lines)
1384 goto out_error;
1385 }
1386
1387 buffer[payloadOffset - 1] = '\0';
1388 buffer[payloadOffset - 2] = '\0';
1389 count = 0;
1390 line = strtok_s(buffer, "\r\n", &context);
1391
1392 while (line && (response->count > count))
1393 {
1394 response->lines[count] = _strdup(line);
1395 if (!response->lines[count])
1396 goto out_error;
1397 line = strtok_s(nullptr, "\r\n", &context);
1398 count++;
1399 }
1400
1401 if (!http_response_parse_header(response))
1402 goto out_error;
1403
1404 response->BodyLength =
1405 Stream_GetPosition(response->data) - WINPR_ASSERTING_INT_CAST(size_t, payloadOffset);
1406
1407 WINPR_ASSERT(response->BodyLength == 0);
1408 bodyLength = response->BodyLength; /* expected body length */
1409
1410 if (readContentLength && (response->ContentLength > 0))
1411 {
1412 const char* cur = response->ContentType;
1413
1414 while (cur != nullptr)
1415 {
1416 if (http_use_content_length(cur))
1417 {
1418 if (response->ContentLength < RESPONSE_SIZE_LIMIT)
1419 bodyLength = response->ContentLength;
1420
1421 break;
1422 }
1423 else
1424 readContentLength = FALSE; /* prevent chunked read */
1425
1426 cur = strchr(cur, ';');
1427 }
1428 }
1429
1430 if (bodyLength > RESPONSE_SIZE_LIMIT)
1431 {
1432 WLog_ERR(TAG, "Expected request body too large! (%" PRIuz " bytes) Aborting!",
1433 bodyLength);
1434 goto out_error;
1435 }
1436
1437 /* Fetch remaining body! */
1438 if (!http_response_recv_body(tls, response, readContentLength,
1439 WINPR_ASSERTING_INT_CAST(size_t, payloadOffset), bodyLength))
1440 goto out_error;
1441 }
1442 Stream_SealLength(response->data);
1443
1444 /* Ensure '\0' terminated string */
1445 if (!Stream_EnsureRemainingCapacity(response->data, 2))
1446 goto out_error;
1447 Stream_Write_UINT16(response->data, 0);
1448
1449 return response;
1450out_error:
1451 WLog_ERR(TAG, "No response");
1452 http_response_free(response);
1453 return nullptr;
1454}
1455
1456const char* http_response_get_body(const HttpResponse* response)
1457{
1458 if (!response)
1459 return nullptr;
1460
1461 return response->BodyContent;
1462}
1463
1464wHashTable* HashTable_New_String(void)
1465{
1466 wHashTable* table = HashTable_New(FALSE);
1467 if (!table)
1468 return nullptr;
1469
1470 if (!HashTable_SetupForStringData(table, TRUE))
1471 {
1472 HashTable_Free(table);
1473 return nullptr;
1474 }
1475 HashTable_KeyObject(table)->fnObjectEquals = strings_equals_nocase;
1476 HashTable_ValueObject(table)->fnObjectEquals = strings_equals_nocase;
1477 return table;
1478}
1479
1480HttpResponse* http_response_new(void)
1481{
1482 HttpResponse* response = (HttpResponse*)calloc(1, sizeof(HttpResponse));
1483
1484 if (!response)
1485 return nullptr;
1486
1487 response->Authenticates = HashTable_New_String();
1488
1489 if (!response->Authenticates)
1490 goto fail;
1491
1492 response->SetCookie = HashTable_New_String();
1493
1494 if (!response->SetCookie)
1495 goto fail;
1496
1497 response->data = Stream_New(nullptr, 2048);
1498
1499 if (!response->data)
1500 goto fail;
1501
1502 response->TransferEncoding = TransferEncodingIdentity;
1503 return response;
1504fail:
1505 WINPR_PRAGMA_DIAG_PUSH
1506 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
1507 http_response_free(response);
1508 WINPR_PRAGMA_DIAG_POP
1509 return nullptr;
1510}
1511
1512void http_response_free(HttpResponse* response)
1513{
1514 if (!response)
1515 return;
1516
1517 clear_lines(response);
1518 free(response->ReasonPhrase);
1519 free(response->ContentType);
1520 free(response->SecWebsocketAccept);
1521 free(response->SecWebsocketVersion);
1522 HashTable_Free(response->Authenticates);
1523 HashTable_Free(response->SetCookie);
1524 Stream_Free(response->data, TRUE);
1525 free(response);
1526}
1527
1528const char* http_request_get_uri(HttpRequest* request)
1529{
1530 if (!request)
1531 return nullptr;
1532
1533 return request->URI;
1534}
1535
1536SSIZE_T http_request_get_content_length(HttpRequest* request)
1537{
1538 if (!request)
1539 return -1;
1540
1541 return (SSIZE_T)request->ContentLength;
1542}
1543
1544BOOL http_request_set_content_length(HttpRequest* request, size_t length)
1545{
1546 if (!request)
1547 return FALSE;
1548
1549 request->ContentLength = length;
1550 return TRUE;
1551}
1552
1553UINT16 http_response_get_status_code(const HttpResponse* response)
1554{
1555 WINPR_ASSERT(response);
1556
1557 return response->StatusCode;
1558}
1559
1560size_t http_response_get_body_length(const HttpResponse* response)
1561{
1562 WINPR_ASSERT(response);
1563
1564 return response->BodyLength;
1565}
1566
1567const char* http_response_get_auth_token(const HttpResponse* response, const char* method)
1568{
1569 if (!response || !method)
1570 return nullptr;
1571
1572 return HashTable_GetItemValue(response->Authenticates, method);
1573}
1574
1575const char* http_response_get_setcookie(const HttpResponse* response, const char* cookie)
1576{
1577 if (!response || !cookie)
1578 return nullptr;
1579
1580 return HashTable_GetItemValue(response->SetCookie, cookie);
1581}
1582
1583TRANSFER_ENCODING http_response_get_transfer_encoding(const HttpResponse* response)
1584{
1585 if (!response)
1586 return TransferEncodingUnknown;
1587
1588 return response->TransferEncoding;
1589}
1590
1591BOOL http_response_is_websocket(const HttpContext* http, const HttpResponse* response)
1592{
1593 BOOL isWebsocket = FALSE;
1594 WINPR_DIGEST_CTX* sha1 = nullptr;
1595 char* base64accept = nullptr;
1596 BYTE sha1_digest[WINPR_SHA1_DIGEST_LENGTH];
1597
1598 if (!http || !response)
1599 return FALSE;
1600
1601 if (!http->websocketUpgrade || response->StatusCode != HTTP_STATUS_SWITCH_PROTOCOLS)
1602 return FALSE;
1603
1604 if (response->SecWebsocketVersion && _stricmp(response->SecWebsocketVersion, "13") != 0)
1605 return FALSE;
1606
1607 if (!response->SecWebsocketAccept)
1608 return FALSE;
1609
1610 /* now check if Sec-Websocket-Accept is correct */
1611
1612 sha1 = winpr_Digest_New();
1613 if (!sha1)
1614 goto out;
1615
1616 if (!winpr_Digest_Init(sha1, WINPR_MD_SHA1))
1617 goto out;
1618
1619 if (!winpr_Digest_Update(sha1, (BYTE*)http->SecWebsocketKey, strlen(http->SecWebsocketKey)))
1620 goto out;
1621 if (!winpr_Digest_Update(sha1, (const BYTE*)WEBSOCKET_MAGIC_GUID, strlen(WEBSOCKET_MAGIC_GUID)))
1622 goto out;
1623
1624 if (!winpr_Digest_Final(sha1, sha1_digest, sizeof(sha1_digest)))
1625 goto out;
1626
1627 base64accept = crypto_base64_encode(sha1_digest, WINPR_SHA1_DIGEST_LENGTH);
1628 if (!base64accept)
1629 goto out;
1630
1631 if (_stricmp(response->SecWebsocketAccept, base64accept) != 0)
1632 {
1633 WLog_WARN(TAG, "Webserver gave Websocket Upgrade response but sanity check failed");
1634 goto out;
1635 }
1636 isWebsocket = TRUE;
1637out:
1638 winpr_Digest_Free(sha1);
1639 free(base64accept);
1640 return isWebsocket;
1641}
1642
1643void http_response_log_error_status_(wLog* log, DWORD level, const HttpResponse* response,
1644 const char* file, size_t line, const char* fkt)
1645{
1646 WINPR_ASSERT(log);
1647 WINPR_ASSERT(response);
1648
1649 if (!WLog_IsLevelActive(log, level))
1650 return;
1651
1652 char buffer[64] = WINPR_C_ARRAY_INIT;
1653 const UINT16 status = http_response_get_status_code(response);
1654 WLog_PrintTextMessage(log, level, line, file, fkt, "Unexpected HTTP status: %s",
1655 freerdp_http_status_string_format(status, buffer, ARRAYSIZE(buffer)));
1656 http_response_print(log, level, response, file, line, fkt);
1657}
1658
1659static BOOL extract_cookie(const void* pkey, void* pvalue, void* arg)
1660{
1661 const char* key = pkey;
1662 const char* value = pvalue;
1663 HttpContext* context = arg;
1664
1665 WINPR_ASSERT(arg);
1666 WINPR_ASSERT(key);
1667 WINPR_ASSERT(value);
1668
1669 return http_context_set_cookie(context, key, value);
1670}
1671
1672BOOL http_response_extract_cookies(const HttpResponse* response, HttpContext* context)
1673{
1674 WINPR_ASSERT(response);
1675 WINPR_ASSERT(context);
1676
1677 return HashTable_Foreach(response->SetCookie, extract_cookie, context);
1678}
1679
1680FREERDP_LOCAL BOOL http_context_set_header(HttpContext* context, const char* key, const char* value,
1681 ...)
1682{
1683 WINPR_ASSERT(context);
1684 va_list ap = WINPR_C_ARRAY_INIT;
1685 va_start(ap, value);
1686 const BOOL rc = http_context_set_header_va(context, key, value, ap);
1687 va_end(ap);
1688 return rc;
1689}
1690
1691BOOL http_request_set_header(HttpRequest* request, const char* key, const char* value, ...)
1692{
1693 WINPR_ASSERT(request);
1694 char* v = nullptr;
1695 size_t vlen = 0;
1696 va_list ap = WINPR_C_ARRAY_INIT;
1697 va_start(ap, value);
1698 winpr_vasprintf(&v, &vlen, value, ap);
1699 va_end(ap);
1700 const BOOL rc = HashTable_Insert(request->headers, key, v);
1701 free(v);
1702 return rc;
1703}
1704
1705BOOL http_context_set_header_va(HttpContext* context, const char* key, const char* value,
1706 va_list ap)
1707{
1708 char* v = nullptr;
1709 size_t vlen = 0;
1710 winpr_vasprintf(&v, &vlen, value, ap);
1711 const BOOL rc = HashTable_Insert(context->headers, key, v);
1712 free(v);
1713 return rc;
1714}
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.
Definition collections.h:52
OBJECT_FREE_FN fnObjectFree
Definition collections.h:58
OBJECT_EQUALS_FN fnObjectEquals
Definition collections.h:59
OBJECT_NEW_FN fnObjectNew
Definition collections.h:53