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