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 if (Stream_GetPosition(response->data) > RESPONSE_SIZE_LIMIT)
1230 {
1231 WLog_ERR(TAG, "http response limit exceeded!");
1232 goto out_error;
1233 }
1234 } while (ctx.state != ChunkStateEnd);
1235 response->BodyLength = WINPR_ASSERTING_INT_CAST(uint32_t, full_len);
1236 if (response->BodyLength > 0)
1237 response->BodyContent = &(Stream_BufferAs(response->data, char))[payloadOffset];
1238 }
1239 else
1240 {
1241 while (response->BodyLength < bodyLength)
1242 {
1243 int status = 0;
1244
1245 if (!Stream_EnsureRemainingCapacity(response->data, bodyLength - response->BodyLength))
1246 goto out_error;
1247
1248 ERR_clear_error();
1249 size_t diff = bodyLength - response->BodyLength;
1250 if (diff > INT32_MAX)
1251 diff = INT32_MAX;
1252 status = BIO_read(tls->bio, Stream_Pointer(response->data), (int)diff);
1253
1254 if (status <= 0)
1255 {
1256 if (sleep_or_timeout(tls, startMS, timeoutMS))
1257 goto out_error;
1258 continue;
1259 }
1260
1261 Stream_Seek(response->data, (size_t)status);
1262 response->BodyLength += (unsigned long)status;
1263
1264 if (response->BodyLength > RESPONSE_SIZE_LIMIT)
1265 {
1266 WLog_ERR(TAG, "Request body too large! (%" PRIuz " bytes) Aborting!",
1267 response->BodyLength);
1268 goto out_error;
1269 }
1270 }
1271
1272 if (response->BodyLength > 0)
1273 response->BodyContent = &(Stream_BufferAs(response->data, char))[payloadOffset];
1274
1275 if (bodyLength != response->BodyLength)
1276 {
1277 WLog_WARN(TAG, "%s unexpected body length: actual: %" PRIuz ", expected: %" PRIuz,
1278 response->ContentType, response->BodyLength, bodyLength);
1279
1280 if (bodyLength > 0)
1281 response->BodyLength = MIN(bodyLength, response->BodyLength);
1282 }
1283
1284 /* '\0' terminate the http body */
1285 if (!Stream_EnsureRemainingCapacity(response->data, sizeof(UINT16)))
1286 goto out_error;
1287 Stream_Write_UINT16(response->data, 0);
1288 }
1289
1290 rc = TRUE;
1291out_error:
1292 return rc;
1293}
1294
1295static void clear_lines(HttpResponse* response)
1296{
1297 WINPR_ASSERT(response);
1298
1299 for (size_t x = 0; x < response->count; x++)
1300 {
1301 WINPR_ASSERT(response->lines);
1302 char* line = response->lines[x];
1303 free(line);
1304 }
1305
1306 free((void*)response->lines);
1307 response->lines = nullptr;
1308 response->count = 0;
1309}
1310
1311HttpResponse* http_response_recv(rdpTls* tls, BOOL readContentLength)
1312{
1313 size_t bodyLength = 0;
1314 HttpResponse* response = http_response_new();
1315
1316 if (!response)
1317 return nullptr;
1318
1319 response->ContentLength = 0;
1320
1321 const SSIZE_T payloadOffset = http_response_recv_line(tls, response);
1322 if (payloadOffset < 0)
1323 goto out_error;
1324
1325 if (payloadOffset)
1326 {
1327 size_t count = 0;
1328 char* buffer = Stream_BufferAs(response->data, char);
1329 char* line = Stream_BufferAs(response->data, char);
1330 char* context = nullptr;
1331
1332 while ((line = winpr_strnstr(line, "\r\n",
1333 WINPR_ASSERTING_INT_CAST(size_t, payloadOffset) -
1334 WINPR_ASSERTING_INT_CAST(size_t, (line - buffer)) - 2UL)))
1335 {
1336 line += 2;
1337 count++;
1338 }
1339
1340 clear_lines(response);
1341 response->count = count;
1342
1343 if (count)
1344 {
1345 response->lines = (char**)calloc(response->count, sizeof(char*));
1346
1347 if (!response->lines)
1348 goto out_error;
1349 }
1350
1351 buffer[payloadOffset - 1] = '\0';
1352 buffer[payloadOffset - 2] = '\0';
1353 count = 0;
1354 line = strtok_s(buffer, "\r\n", &context);
1355
1356 while (line && (response->count > count))
1357 {
1358 response->lines[count] = _strdup(line);
1359 if (!response->lines[count])
1360 goto out_error;
1361 line = strtok_s(nullptr, "\r\n", &context);
1362 count++;
1363 }
1364
1365 if (!http_response_parse_header(response))
1366 goto out_error;
1367
1368 response->BodyLength =
1369 Stream_GetPosition(response->data) - WINPR_ASSERTING_INT_CAST(size_t, payloadOffset);
1370
1371 WINPR_ASSERT(response->BodyLength == 0);
1372 bodyLength = response->BodyLength; /* expected body length */
1373
1374 if (readContentLength && (response->ContentLength > 0))
1375 {
1376 const char* cur = response->ContentType;
1377
1378 while (cur != nullptr)
1379 {
1380 if (http_use_content_length(cur))
1381 {
1382 if (response->ContentLength < RESPONSE_SIZE_LIMIT)
1383 bodyLength = response->ContentLength;
1384
1385 break;
1386 }
1387 else
1388 readContentLength = FALSE; /* prevent chunked read */
1389
1390 cur = strchr(cur, ';');
1391 }
1392 }
1393
1394 if (bodyLength > RESPONSE_SIZE_LIMIT)
1395 {
1396 WLog_ERR(TAG, "Expected request body too large! (%" PRIuz " bytes) Aborting!",
1397 bodyLength);
1398 goto out_error;
1399 }
1400
1401 /* Fetch remaining body! */
1402 if (!http_response_recv_body(tls, response, readContentLength,
1403 WINPR_ASSERTING_INT_CAST(size_t, payloadOffset), bodyLength))
1404 goto out_error;
1405 }
1406 Stream_SealLength(response->data);
1407
1408 /* Ensure '\0' terminated string */
1409 if (!Stream_EnsureRemainingCapacity(response->data, 2))
1410 goto out_error;
1411 Stream_Write_UINT16(response->data, 0);
1412
1413 return response;
1414out_error:
1415 WLog_ERR(TAG, "No response");
1416 http_response_free(response);
1417 return nullptr;
1418}
1419
1420const char* http_response_get_body(const HttpResponse* response)
1421{
1422 if (!response)
1423 return nullptr;
1424
1425 return response->BodyContent;
1426}
1427
1428wHashTable* HashTable_New_String(void)
1429{
1430 wHashTable* table = HashTable_New(FALSE);
1431 if (!table)
1432 return nullptr;
1433
1434 if (!HashTable_SetupForStringData(table, TRUE))
1435 {
1436 HashTable_Free(table);
1437 return nullptr;
1438 }
1439 HashTable_KeyObject(table)->fnObjectEquals = strings_equals_nocase;
1440 HashTable_ValueObject(table)->fnObjectEquals = strings_equals_nocase;
1441 return table;
1442}
1443
1444HttpResponse* http_response_new(void)
1445{
1446 HttpResponse* response = (HttpResponse*)calloc(1, sizeof(HttpResponse));
1447
1448 if (!response)
1449 return nullptr;
1450
1451 response->Authenticates = HashTable_New_String();
1452
1453 if (!response->Authenticates)
1454 goto fail;
1455
1456 response->SetCookie = HashTable_New_String();
1457
1458 if (!response->SetCookie)
1459 goto fail;
1460
1461 response->data = Stream_New(nullptr, 2048);
1462
1463 if (!response->data)
1464 goto fail;
1465
1466 response->TransferEncoding = TransferEncodingIdentity;
1467 return response;
1468fail:
1469 WINPR_PRAGMA_DIAG_PUSH
1470 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
1471 http_response_free(response);
1472 WINPR_PRAGMA_DIAG_POP
1473 return nullptr;
1474}
1475
1476void http_response_free(HttpResponse* response)
1477{
1478 if (!response)
1479 return;
1480
1481 clear_lines(response);
1482 free(response->ReasonPhrase);
1483 free(response->ContentType);
1484 free(response->SecWebsocketAccept);
1485 free(response->SecWebsocketVersion);
1486 HashTable_Free(response->Authenticates);
1487 HashTable_Free(response->SetCookie);
1488 Stream_Free(response->data, TRUE);
1489 free(response);
1490}
1491
1492const char* http_request_get_uri(HttpRequest* request)
1493{
1494 if (!request)
1495 return nullptr;
1496
1497 return request->URI;
1498}
1499
1500SSIZE_T http_request_get_content_length(HttpRequest* request)
1501{
1502 if (!request)
1503 return -1;
1504
1505 return (SSIZE_T)request->ContentLength;
1506}
1507
1508BOOL http_request_set_content_length(HttpRequest* request, size_t length)
1509{
1510 if (!request)
1511 return FALSE;
1512
1513 request->ContentLength = length;
1514 return TRUE;
1515}
1516
1517UINT16 http_response_get_status_code(const HttpResponse* response)
1518{
1519 WINPR_ASSERT(response);
1520
1521 return response->StatusCode;
1522}
1523
1524size_t http_response_get_body_length(const HttpResponse* response)
1525{
1526 WINPR_ASSERT(response);
1527
1528 return response->BodyLength;
1529}
1530
1531const char* http_response_get_auth_token(const HttpResponse* response, const char* method)
1532{
1533 if (!response || !method)
1534 return nullptr;
1535
1536 return HashTable_GetItemValue(response->Authenticates, method);
1537}
1538
1539const char* http_response_get_setcookie(const HttpResponse* response, const char* cookie)
1540{
1541 if (!response || !cookie)
1542 return nullptr;
1543
1544 return HashTable_GetItemValue(response->SetCookie, cookie);
1545}
1546
1547TRANSFER_ENCODING http_response_get_transfer_encoding(const HttpResponse* response)
1548{
1549 if (!response)
1550 return TransferEncodingUnknown;
1551
1552 return response->TransferEncoding;
1553}
1554
1555BOOL http_response_is_websocket(const HttpContext* http, const HttpResponse* response)
1556{
1557 BOOL isWebsocket = FALSE;
1558 WINPR_DIGEST_CTX* sha1 = nullptr;
1559 char* base64accept = nullptr;
1560 BYTE sha1_digest[WINPR_SHA1_DIGEST_LENGTH];
1561
1562 if (!http || !response)
1563 return FALSE;
1564
1565 if (!http->websocketUpgrade || response->StatusCode != HTTP_STATUS_SWITCH_PROTOCOLS)
1566 return FALSE;
1567
1568 if (response->SecWebsocketVersion && _stricmp(response->SecWebsocketVersion, "13") != 0)
1569 return FALSE;
1570
1571 if (!response->SecWebsocketAccept)
1572 return FALSE;
1573
1574 /* now check if Sec-Websocket-Accept is correct */
1575
1576 sha1 = winpr_Digest_New();
1577 if (!sha1)
1578 goto out;
1579
1580 if (!winpr_Digest_Init(sha1, WINPR_MD_SHA1))
1581 goto out;
1582
1583 if (!winpr_Digest_Update(sha1, (BYTE*)http->SecWebsocketKey, strlen(http->SecWebsocketKey)))
1584 goto out;
1585 if (!winpr_Digest_Update(sha1, (const BYTE*)WEBSOCKET_MAGIC_GUID, strlen(WEBSOCKET_MAGIC_GUID)))
1586 goto out;
1587
1588 if (!winpr_Digest_Final(sha1, sha1_digest, sizeof(sha1_digest)))
1589 goto out;
1590
1591 base64accept = crypto_base64_encode(sha1_digest, WINPR_SHA1_DIGEST_LENGTH);
1592 if (!base64accept)
1593 goto out;
1594
1595 if (_stricmp(response->SecWebsocketAccept, base64accept) != 0)
1596 {
1597 WLog_WARN(TAG, "Webserver gave Websocket Upgrade response but sanity check failed");
1598 goto out;
1599 }
1600 isWebsocket = TRUE;
1601out:
1602 winpr_Digest_Free(sha1);
1603 free(base64accept);
1604 return isWebsocket;
1605}
1606
1607void http_response_log_error_status_(wLog* log, DWORD level, const HttpResponse* response,
1608 const char* file, size_t line, const char* fkt)
1609{
1610 WINPR_ASSERT(log);
1611 WINPR_ASSERT(response);
1612
1613 if (!WLog_IsLevelActive(log, level))
1614 return;
1615
1616 char buffer[64] = WINPR_C_ARRAY_INIT;
1617 const UINT16 status = http_response_get_status_code(response);
1618 WLog_PrintTextMessage(log, level, line, file, fkt, "Unexpected HTTP status: %s",
1619 freerdp_http_status_string_format(status, buffer, ARRAYSIZE(buffer)));
1620 http_response_print(log, level, response, file, line, fkt);
1621}
1622
1623static BOOL extract_cookie(const void* pkey, void* pvalue, void* arg)
1624{
1625 const char* key = pkey;
1626 const char* value = pvalue;
1627 HttpContext* context = arg;
1628
1629 WINPR_ASSERT(arg);
1630 WINPR_ASSERT(key);
1631 WINPR_ASSERT(value);
1632
1633 return http_context_set_cookie(context, key, value);
1634}
1635
1636BOOL http_response_extract_cookies(const HttpResponse* response, HttpContext* context)
1637{
1638 WINPR_ASSERT(response);
1639 WINPR_ASSERT(context);
1640
1641 return HashTable_Foreach(response->SetCookie, extract_cookie, context);
1642}
1643
1644FREERDP_LOCAL BOOL http_context_set_header(HttpContext* context, const char* key, const char* value,
1645 ...)
1646{
1647 WINPR_ASSERT(context);
1648 va_list ap = WINPR_C_ARRAY_INIT;
1649 va_start(ap, value);
1650 const BOOL rc = http_context_set_header_va(context, key, value, ap);
1651 va_end(ap);
1652 return rc;
1653}
1654
1655BOOL http_request_set_header(HttpRequest* request, const char* key, const char* value, ...)
1656{
1657 WINPR_ASSERT(request);
1658 char* v = nullptr;
1659 size_t vlen = 0;
1660 va_list ap = WINPR_C_ARRAY_INIT;
1661 va_start(ap, value);
1662 winpr_vasprintf(&v, &vlen, value, ap);
1663 va_end(ap);
1664 const BOOL rc = HashTable_Insert(request->headers, key, v);
1665 free(v);
1666 return rc;
1667}
1668
1669BOOL http_context_set_header_va(HttpContext* context, const char* key, const char* value,
1670 va_list ap)
1671{
1672 char* v = nullptr;
1673 size_t vlen = 0;
1674 winpr_vasprintf(&v, &vlen, value, ap);
1675 const BOOL rc = HashTable_Insert(context->headers, key, v);
1676 free(v);
1677 return rc;
1678}
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