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