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