FreeRDP
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 
51 struct 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 
70 struct 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 
82 struct 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 
104 static 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 
132 static 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 
140 HttpContext* 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 
162 fail:
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 
170 BOOL 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 
184 BOOL 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 
198 const char* http_context_get_uri(HttpContext* context)
199 {
200  if (!context)
201  return NULL;
202 
203  return context->URI;
204 }
205 
206 BOOL 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 
220 BOOL 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 
234 BOOL 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 
248 BOOL 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 
262 BOOL 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 
276 BOOL 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 
290 BOOL 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 
304 WINPR_ATTR_FORMAT_ARG(2, 0)
305 static 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 
335 fail:
336  va_end(ap);
337  return rc;
338 }
339 
340 WINPR_ATTR_FORMAT_ARG(2, 3)
341 BOOL 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 
354 WINPR_ATTR_FORMAT_ARG(2, 3)
355 BOOL 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 
365 static 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 
382 BOOL 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 
396 BOOL 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 
410 BOOL 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 
431 BOOL http_context_is_websocket_upgrade_enabled(HttpContext* context)
432 {
433  return context->websocketUpgrade;
434 }
435 
436 BOOL 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 
446 BOOL 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 
463 void 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 
485 BOOL 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 
499 BOOL 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 
513 BOOL 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 
527 BOOL 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 
541 BOOL 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 
551 WINPR_ATTR_FORMAT_ARG(2, 3)
552 static 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 
582 static 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 
590 static 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 
595 static 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 
603 static 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 
612 static 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");
648 unlock:
649  free(keys);
650  ListDictionary_Unlock(cookies);
651  return status;
652 }
653 
654 wStream* 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;
756 fail:
757  Stream_Free(s, TRUE);
758  return NULL;
759 }
760 
761 HttpRequest* 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 
771 void 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 
785 static 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;
825 fail:
826 
827  if (!rc)
828  WLog_ERR(TAG, "http_response_parse_header_status_line failed [%s]", status_line);
829 
830  return rc;
831 }
832 
833 static 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 = 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 
970 static 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;
1041 fail:
1042 
1043  if (!rc)
1044  WLog_ERR(TAG, "parsing failed");
1045 
1046  return rc;
1047 }
1048 
1049 static void http_response_print(wLog* log, DWORD level, const HttpResponse* response)
1050 {
1051  char buffer[64] = { 0 };
1052 
1053  WINPR_ASSERT(log);
1054  WINPR_ASSERT(response);
1055 
1056  if (!WLog_IsLevelActive(log, level))
1057  return;
1058 
1059  const long status = http_response_get_status_code(response);
1060  WLog_Print(log, level, "HTTP status: %s",
1061  freerdp_http_status_string_format(status, buffer, ARRAYSIZE(buffer)));
1062 
1063  for (size_t i = 0; i < response->count; i++)
1064  WLog_Print(log, WLOG_DEBUG, "[%" PRIuz "] %s", i, response->lines[i]);
1065 
1066  if (response->ReasonPhrase)
1067  WLog_Print(log, level, "[reason] %s", response->ReasonPhrase);
1068 
1069  WLog_Print(log, WLOG_TRACE, "[body][%" PRIuz "] %s", response->BodyLength,
1070  response->BodyContent);
1071 }
1072 
1073 static BOOL http_use_content_length(const char* cur)
1074 {
1075  size_t pos = 0;
1076 
1077  if (!cur)
1078  return FALSE;
1079 
1080  if (_strnicmp(cur, "application/rpc", 15) == 0)
1081  pos = 15;
1082  else if (_strnicmp(cur, "text/plain", 10) == 0)
1083  pos = 10;
1084  else if (_strnicmp(cur, "text/html", 9) == 0)
1085  pos = 9;
1086  else if (_strnicmp(cur, "application/json", 16) == 0)
1087  pos = 16;
1088 
1089  if (pos > 0)
1090  {
1091  char end = cur[pos];
1092 
1093  switch (end)
1094  {
1095  case ' ':
1096  case ';':
1097  case '\0':
1098  case '\r':
1099  case '\n':
1100  return TRUE;
1101 
1102  default:
1103  return FALSE;
1104  }
1105  }
1106 
1107  return FALSE;
1108 }
1109 
1110 static int print_bio_error(const char* str, size_t len, void* bp)
1111 {
1112  wLog* log = bp;
1113 
1114  WINPR_UNUSED(bp);
1115  WLog_Print(log, WLOG_ERROR, "%s", str);
1116  if (len > INT32_MAX)
1117  return -1;
1118  return (int)len;
1119 }
1120 
1121 int http_chuncked_read(BIO* bio, BYTE* pBuffer, size_t size,
1122  http_encoding_chunked_context* encodingContext)
1123 {
1124  int status = 0;
1125  int effectiveDataLen = 0;
1126  WINPR_ASSERT(bio);
1127  WINPR_ASSERT(pBuffer);
1128  WINPR_ASSERT(encodingContext != NULL);
1129  while (TRUE)
1130  {
1131  switch (encodingContext->state)
1132  {
1133  case ChunkStateData:
1134  {
1135  const size_t rd =
1136  (size > encodingContext->nextOffset ? encodingContext->nextOffset : size);
1137  if (rd > INT32_MAX)
1138  return -1;
1139 
1140  ERR_clear_error();
1141  status = BIO_read(bio, pBuffer, (int)rd);
1142  if (status <= 0)
1143  return (effectiveDataLen > 0 ? effectiveDataLen : status);
1144 
1145  encodingContext->nextOffset -= status;
1146  if (encodingContext->nextOffset == 0)
1147  {
1148  encodingContext->state = ChunkStateFooter;
1149  encodingContext->headerFooterPos = 0;
1150  }
1151  effectiveDataLen += status;
1152 
1153  if ((size_t)status == size)
1154  return effectiveDataLen;
1155 
1156  pBuffer += status;
1157  size -= (size_t)status;
1158  }
1159  break;
1160  case ChunkStateFooter:
1161  {
1162  char _dummy[2] = { 0 };
1163  WINPR_ASSERT(encodingContext->nextOffset == 0);
1164  WINPR_ASSERT(encodingContext->headerFooterPos < 2);
1165  ERR_clear_error();
1166  status = BIO_read(bio, _dummy, (int)(2 - encodingContext->headerFooterPos));
1167  if (status >= 0)
1168  {
1169  encodingContext->headerFooterPos += (size_t)status;
1170  if (encodingContext->headerFooterPos == 2)
1171  {
1172  encodingContext->state = ChunkStateLenghHeader;
1173  encodingContext->headerFooterPos = 0;
1174  }
1175  }
1176  else
1177  return (effectiveDataLen > 0 ? effectiveDataLen : status);
1178  }
1179  break;
1180  case ChunkStateLenghHeader:
1181  {
1182  BOOL _haveNewLine = FALSE;
1183  char* dst = &encodingContext->lenBuffer[encodingContext->headerFooterPos];
1184  WINPR_ASSERT(encodingContext->nextOffset == 0);
1185  while (encodingContext->headerFooterPos < 10 && !_haveNewLine)
1186  {
1187  ERR_clear_error();
1188  status = BIO_read(bio, dst, 1);
1189  if (status >= 0)
1190  {
1191  if (*dst == '\n')
1192  _haveNewLine = TRUE;
1193  encodingContext->headerFooterPos += (size_t)status;
1194  dst += status;
1195  }
1196  else
1197  return (effectiveDataLen > 0 ? effectiveDataLen : status);
1198  }
1199  *dst = '\0';
1200  /* strtoul is tricky, error are reported via errno, we also need
1201  * to ensure the result does not overflow */
1202  errno = 0;
1203  size_t tmp = strtoul(encodingContext->lenBuffer, NULL, 16);
1204  if ((errno != 0) || (tmp > SIZE_MAX))
1205  {
1206  /* denote end of stream if something bad happens */
1207  encodingContext->nextOffset = 0;
1208  encodingContext->state = ChunkStateEnd;
1209  return -1;
1210  }
1211  encodingContext->nextOffset = tmp;
1212  encodingContext->state = ChunkStateData;
1213 
1214  if (encodingContext->nextOffset == 0)
1215  { /* end of stream */
1216  WLog_DBG(TAG, "chunked encoding end of stream received");
1217  encodingContext->headerFooterPos = 0;
1218  encodingContext->state = ChunkStateEnd;
1219  return (effectiveDataLen > 0 ? effectiveDataLen : 0);
1220  }
1221  }
1222  break;
1223  default:
1224  /* invalid state / ChunkStateEnd */
1225  return -1;
1226  }
1227  }
1228 }
1229 
1230 #define sleep_or_timeout(tls, startMS, timeoutMS) \
1231  sleep_or_timeout_((tls), (startMS), (timeoutMS), __FILE__, __func__, __LINE__)
1232 static BOOL sleep_or_timeout_(rdpTls* tls, UINT64 startMS, UINT32 timeoutMS, const char* file,
1233  const char* fkt, size_t line)
1234 {
1235  WINPR_ASSERT(tls);
1236 
1237  USleep(100);
1238  const UINT64 nowMS = GetTickCount64();
1239  if (nowMS - startMS > timeoutMS)
1240  {
1241  DWORD level = WLOG_ERROR;
1242  wLog* log = WLog_Get(TAG);
1243  if (WLog_IsLevelActive(log, level))
1244  WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, level, line, file, fkt,
1245  "timeout [%" PRIu32 "ms] exceeded", timeoutMS);
1246  return TRUE;
1247  }
1248  if (!BIO_should_retry(tls->bio))
1249  {
1250  DWORD level = WLOG_ERROR;
1251  wLog* log = WLog_Get(TAG);
1252  if (WLog_IsLevelActive(log, level))
1253  {
1254  WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, level, line, file, fkt, "Retries exceeded");
1255  ERR_print_errors_cb(print_bio_error, log);
1256  }
1257  return TRUE;
1258  }
1259  if (freerdp_shall_disconnect_context(tls->context))
1260  return TRUE;
1261 
1262  return FALSE;
1263 }
1264 
1265 static SSIZE_T http_response_recv_line(rdpTls* tls, HttpResponse* response)
1266 {
1267  WINPR_ASSERT(tls);
1268  WINPR_ASSERT(response);
1269 
1270  SSIZE_T payloadOffset = -1;
1271  const UINT32 timeoutMS =
1272  freerdp_settings_get_uint32(tls->context->settings, FreeRDP_TcpConnectTimeout);
1273  const UINT64 startMS = GetTickCount64();
1274  while (payloadOffset <= 0)
1275  {
1276  size_t bodyLength = 0;
1277  size_t position = 0;
1278  int status = -1;
1279  size_t s = 0;
1280  char* end = NULL;
1281  /* Read until we encounter \r\n\r\n */
1282  ERR_clear_error();
1283 
1284  status = BIO_read(tls->bio, Stream_Pointer(response->data), 1);
1285  if (status <= 0)
1286  {
1287  if (sleep_or_timeout(tls, startMS, timeoutMS))
1288  goto out_error;
1289  continue;
1290  }
1291 
1292 #ifdef FREERDP_HAVE_VALGRIND_MEMCHECK_H
1293  VALGRIND_MAKE_MEM_DEFINED(Stream_Pointer(response->data), status);
1294 #endif
1295  Stream_Seek(response->data, (size_t)status);
1296 
1297  if (!Stream_EnsureRemainingCapacity(response->data, 1024))
1298  goto out_error;
1299 
1300  position = Stream_GetPosition(response->data);
1301 
1302  if (position < 4)
1303  continue;
1304  else if (position > RESPONSE_SIZE_LIMIT)
1305  {
1306  WLog_ERR(TAG, "Request header too large! (%" PRIdz " bytes) Aborting!", bodyLength);
1307  goto out_error;
1308  }
1309 
1310  /* Always check at most the lase 8 bytes for occurance of the desired
1311  * sequence of \r\n\r\n */
1312  s = (position > 8) ? 8 : position;
1313  end = (char*)Stream_Pointer(response->data) - s;
1314 
1315  if (string_strnstr(end, "\r\n\r\n", s) != NULL)
1316  payloadOffset = Stream_GetPosition(response->data);
1317  }
1318 
1319 out_error:
1320  return payloadOffset;
1321 }
1322 
1323 static BOOL http_response_recv_body(rdpTls* tls, HttpResponse* response, BOOL readContentLength,
1324  size_t payloadOffset, size_t bodyLength)
1325 {
1326  BOOL rc = FALSE;
1327 
1328  WINPR_ASSERT(tls);
1329  WINPR_ASSERT(response);
1330 
1331  const UINT64 startMS = GetTickCount64();
1332  const UINT32 timeoutMS =
1333  freerdp_settings_get_uint32(tls->context->settings, FreeRDP_TcpConnectTimeout);
1334 
1335  if ((response->TransferEncoding == TransferEncodingChunked) && readContentLength)
1336  {
1337  http_encoding_chunked_context ctx = { 0 };
1338  ctx.state = ChunkStateLenghHeader;
1339  ctx.nextOffset = 0;
1340  ctx.headerFooterPos = 0;
1341  int full_len = 0;
1342  do
1343  {
1344  if (!Stream_EnsureRemainingCapacity(response->data, 2048))
1345  goto out_error;
1346 
1347  int status = http_chuncked_read(tls->bio, Stream_Pointer(response->data),
1348  Stream_GetRemainingCapacity(response->data), &ctx);
1349  if (status <= 0)
1350  {
1351  if (sleep_or_timeout(tls, startMS, timeoutMS))
1352  goto out_error;
1353  }
1354  else
1355  {
1356  Stream_Seek(response->data, (size_t)status);
1357  full_len += status;
1358  }
1359  } while (ctx.state != ChunkStateEnd);
1360  response->BodyLength = full_len;
1361  if (response->BodyLength > 0)
1362  response->BodyContent = &(Stream_Buffer(response->data))[payloadOffset];
1363  }
1364  else
1365  {
1366  while (response->BodyLength < bodyLength)
1367  {
1368  int status = 0;
1369 
1370  if (!Stream_EnsureRemainingCapacity(response->data, bodyLength - response->BodyLength))
1371  goto out_error;
1372 
1373  ERR_clear_error();
1374  size_t diff = bodyLength - response->BodyLength;
1375  if (diff > INT32_MAX)
1376  diff = INT32_MAX;
1377  status = BIO_read(tls->bio, Stream_Pointer(response->data), (int)diff);
1378 
1379  if (status <= 0)
1380  {
1381  if (sleep_or_timeout(tls, startMS, timeoutMS))
1382  goto out_error;
1383  continue;
1384  }
1385 
1386  Stream_Seek(response->data, (size_t)status);
1387  response->BodyLength += (unsigned long)status;
1388 
1389  if (response->BodyLength > RESPONSE_SIZE_LIMIT)
1390  {
1391  WLog_ERR(TAG, "Request body too large! (%" PRIdz " bytes) Aborting!",
1392  response->BodyLength);
1393  goto out_error;
1394  }
1395  }
1396 
1397  if (response->BodyLength > 0)
1398  response->BodyContent = &(Stream_Buffer(response->data))[payloadOffset];
1399 
1400  if (bodyLength != response->BodyLength)
1401  {
1402  WLog_WARN(TAG, "%s unexpected body length: actual: %" PRIuz ", expected: %" PRIuz,
1403  response->ContentType, response->BodyLength, bodyLength);
1404 
1405  if (bodyLength > 0)
1406  response->BodyLength = MIN(bodyLength, response->BodyLength);
1407  }
1408 
1409  /* '\0' terminate the http body */
1410  if (!Stream_EnsureRemainingCapacity(response->data, sizeof(UINT16)))
1411  goto out_error;
1412  Stream_Write_UINT16(response->data, 0);
1413  }
1414 
1415  rc = TRUE;
1416 out_error:
1417  return rc;
1418 }
1419 
1420 HttpResponse* http_response_recv(rdpTls* tls, BOOL readContentLength)
1421 {
1422  size_t bodyLength = 0;
1423  HttpResponse* response = http_response_new();
1424 
1425  if (!response)
1426  return NULL;
1427 
1428  response->ContentLength = 0;
1429 
1430  const SSIZE_T payloadOffset = http_response_recv_line(tls, response);
1431  if (payloadOffset < 0)
1432  goto out_error;
1433 
1434  if (payloadOffset)
1435  {
1436  size_t count = 0;
1437  char* buffer = Stream_BufferAs(response->data, char);
1438  char* line = Stream_BufferAs(response->data, char);
1439  char* context = NULL;
1440 
1441  while ((line = string_strnstr(line, "\r\n", payloadOffset - (line - buffer) - 2UL)))
1442  {
1443  line += 2;
1444  count++;
1445  }
1446 
1447  response->count = count;
1448 
1449  if (count)
1450  {
1451  response->lines = (char**)calloc(response->count, sizeof(char*));
1452 
1453  if (!response->lines)
1454  goto out_error;
1455  }
1456 
1457  buffer[payloadOffset - 1] = '\0';
1458  buffer[payloadOffset - 2] = '\0';
1459  count = 0;
1460  line = strtok_s(buffer, "\r\n", &context);
1461 
1462  while (line && (response->count > count))
1463  {
1464  response->lines[count] = line;
1465  line = strtok_s(NULL, "\r\n", &context);
1466  count++;
1467  }
1468 
1469  if (!http_response_parse_header(response))
1470  goto out_error;
1471 
1472  response->BodyLength = Stream_GetPosition(response->data) - payloadOffset;
1473 
1474  WINPR_ASSERT(response->BodyLength == 0);
1475  bodyLength = response->BodyLength; /* expected body length */
1476 
1477  if (readContentLength)
1478  {
1479  const char* cur = response->ContentType;
1480 
1481  while (cur != NULL)
1482  {
1483  if (http_use_content_length(cur))
1484  {
1485  if (response->ContentLength < RESPONSE_SIZE_LIMIT)
1486  bodyLength = response->ContentLength;
1487 
1488  break;
1489  }
1490  else
1491  readContentLength = FALSE; /* prevent chunked read */
1492 
1493  cur = strchr(cur, ';');
1494  }
1495  }
1496 
1497  if (bodyLength > RESPONSE_SIZE_LIMIT)
1498  {
1499  WLog_ERR(TAG, "Expected request body too large! (%" PRIdz " bytes) Aborting!",
1500  bodyLength);
1501  goto out_error;
1502  }
1503 
1504  /* Fetch remaining body! */
1505  if (!http_response_recv_body(tls, response, readContentLength, payloadOffset, bodyLength))
1506  goto out_error;
1507  }
1508  Stream_SealLength(response->data);
1509 
1510  /* Ensure '\0' terminated string */
1511  if (!Stream_EnsureRemainingCapacity(response->data, 2))
1512  goto out_error;
1513  Stream_Write_UINT16(response->data, 0);
1514 
1515  return response;
1516 out_error:
1517  http_response_free(response);
1518  return NULL;
1519 }
1520 
1521 const BYTE* http_response_get_body(const HttpResponse* response)
1522 {
1523  if (!response)
1524  return NULL;
1525 
1526  return response->BodyContent;
1527 }
1528 
1529 static BOOL set_compare(wListDictionary* dict)
1530 {
1531  WINPR_ASSERT(dict);
1532  wObject* key = ListDictionary_KeyObject(dict);
1533  wObject* value = ListDictionary_KeyObject(dict);
1534  if (!key || !value)
1535  return FALSE;
1536  key->fnObjectEquals = strings_equals_nocase;
1537  value->fnObjectEquals = strings_equals_nocase;
1538  return TRUE;
1539 }
1540 
1541 HttpResponse* http_response_new(void)
1542 {
1543  HttpResponse* response = (HttpResponse*)calloc(1, sizeof(HttpResponse));
1544 
1545  if (!response)
1546  return NULL;
1547 
1548  response->Authenticates = ListDictionary_New(FALSE);
1549 
1550  if (!response->Authenticates)
1551  goto fail;
1552 
1553  if (!set_compare(response->Authenticates))
1554  goto fail;
1555 
1556  response->SetCookie = ListDictionary_New(FALSE);
1557 
1558  if (!response->SetCookie)
1559  goto fail;
1560 
1561  if (!set_compare(response->SetCookie))
1562  goto fail;
1563 
1564  response->data = Stream_New(NULL, 2048);
1565 
1566  if (!response->data)
1567  goto fail;
1568 
1569  response->TransferEncoding = TransferEncodingIdentity;
1570  return response;
1571 fail:
1572  WINPR_PRAGMA_DIAG_PUSH
1573  WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
1574  http_response_free(response);
1575  WINPR_PRAGMA_DIAG_POP
1576  return NULL;
1577 }
1578 
1579 void http_response_free(HttpResponse* response)
1580 {
1581  if (!response)
1582  return;
1583 
1584  free(response->lines);
1585  ListDictionary_Free(response->Authenticates);
1586  ListDictionary_Free(response->SetCookie);
1587  Stream_Free(response->data, TRUE);
1588  free(response);
1589 }
1590 
1591 const char* http_request_get_uri(HttpRequest* request)
1592 {
1593  if (!request)
1594  return NULL;
1595 
1596  return request->URI;
1597 }
1598 
1599 SSIZE_T http_request_get_content_length(HttpRequest* request)
1600 {
1601  if (!request)
1602  return -1;
1603 
1604  return (SSIZE_T)request->ContentLength;
1605 }
1606 
1607 BOOL http_request_set_content_length(HttpRequest* request, size_t length)
1608 {
1609  if (!request)
1610  return FALSE;
1611 
1612  request->ContentLength = length;
1613  return TRUE;
1614 }
1615 
1616 INT16 http_response_get_status_code(const HttpResponse* response)
1617 {
1618  WINPR_ASSERT(response);
1619 
1620  return response->StatusCode;
1621 }
1622 
1623 size_t http_response_get_body_length(const HttpResponse* response)
1624 {
1625  WINPR_ASSERT(response);
1626 
1627  return (SSIZE_T)response->BodyLength;
1628 }
1629 
1630 const char* http_response_get_auth_token(const HttpResponse* response, const char* method)
1631 {
1632  if (!response || !method)
1633  return NULL;
1634 
1635  if (!ListDictionary_Contains(response->Authenticates, method))
1636  return NULL;
1637 
1638  return ListDictionary_GetItemValue(response->Authenticates, method);
1639 }
1640 
1641 const char* http_response_get_setcookie(const HttpResponse* response, const char* cookie)
1642 {
1643  if (!response || !cookie)
1644  return NULL;
1645 
1646  if (!ListDictionary_Contains(response->SetCookie, cookie))
1647  return NULL;
1648 
1649  return ListDictionary_GetItemValue(response->SetCookie, cookie);
1650 }
1651 
1652 TRANSFER_ENCODING http_response_get_transfer_encoding(const HttpResponse* response)
1653 {
1654  if (!response)
1655  return TransferEncodingUnknown;
1656 
1657  return response->TransferEncoding;
1658 }
1659 
1660 BOOL http_response_is_websocket(const HttpContext* http, const HttpResponse* response)
1661 {
1662  BOOL isWebsocket = FALSE;
1663  WINPR_DIGEST_CTX* sha1 = NULL;
1664  char* base64accept = NULL;
1665  BYTE sha1_digest[WINPR_SHA1_DIGEST_LENGTH];
1666 
1667  if (!http || !response)
1668  return FALSE;
1669 
1670  if (!http->websocketUpgrade || response->StatusCode != HTTP_STATUS_SWITCH_PROTOCOLS)
1671  return FALSE;
1672 
1673  if (response->SecWebsocketVersion && _stricmp(response->SecWebsocketVersion, "13") != 0)
1674  return FALSE;
1675 
1676  if (!response->SecWebsocketAccept)
1677  return FALSE;
1678 
1679  /* now check if Sec-Websocket-Accept is correct */
1680 
1681  sha1 = winpr_Digest_New();
1682  if (!sha1)
1683  goto out;
1684 
1685  if (!winpr_Digest_Init(sha1, WINPR_MD_SHA1))
1686  goto out;
1687 
1688  if (!winpr_Digest_Update(sha1, (BYTE*)http->SecWebsocketKey, strlen(http->SecWebsocketKey)))
1689  goto out;
1690  if (!winpr_Digest_Update(sha1, (const BYTE*)WEBSOCKET_MAGIC_GUID, strlen(WEBSOCKET_MAGIC_GUID)))
1691  goto out;
1692 
1693  if (!winpr_Digest_Final(sha1, sha1_digest, sizeof(sha1_digest)))
1694  goto out;
1695 
1696  base64accept = crypto_base64_encode(sha1_digest, WINPR_SHA1_DIGEST_LENGTH);
1697  if (!base64accept)
1698  goto out;
1699 
1700  if (_stricmp(response->SecWebsocketAccept, base64accept) != 0)
1701  {
1702  WLog_WARN(TAG, "Webserver gave Websocket Upgrade response but sanity check failed");
1703  goto out;
1704  }
1705  isWebsocket = TRUE;
1706 out:
1707  winpr_Digest_Free(sha1);
1708  free(base64accept);
1709  return isWebsocket;
1710 }
1711 
1712 void http_response_log_error_status(wLog* log, DWORD level, const HttpResponse* response)
1713 {
1714  WINPR_ASSERT(log);
1715  WINPR_ASSERT(response);
1716 
1717  if (!WLog_IsLevelActive(log, level))
1718  return;
1719 
1720  char buffer[64] = { 0 };
1721  const long status = http_response_get_status_code(response);
1722  WLog_Print(log, level, "Unexpected HTTP status: %s",
1723  freerdp_http_status_string_format(status, buffer, ARRAYSIZE(buffer)));
1724  http_response_print(log, level, response);
1725 }
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