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