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