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