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