FreeRDP
Loading...
Searching...
No Matches
utils/http.c
1
20#include <freerdp/config.h>
21#include <freerdp/utils/http.h>
22
23#include <winpr/assert.h>
24#include <winpr/string.h>
25
26#include <openssl/bio.h>
27#include <openssl/ssl.h>
28#include <openssl/err.h>
29
30#include "../crypto/tls.h"
31#include "../core/utils.h"
32#include "../core/tcp.h"
33
34#include <freerdp/log.h>
35#define TAG FREERDP_TAG("utils.http")
36
37static const char get_header_fmt[] = "GET %s HTTP/1.1\r\n"
38 "Host: %s\r\n"
39 "\r\n";
40
41static const char post_header_fmt[] = "POST %s HTTP/1.1\r\n"
42 "Host: %s\r\n"
43 "Content-Type: application/x-www-form-urlencoded\r\n"
44 "Content-Length: %lu\r\n"
45 "\r\n";
46
47#define log_errors(log, msg) log_errors_(log, msg, __FILE__, __func__, __LINE__)
48static void log_errors_(wLog* log, const char* msg, const char* file, const char* fkt, size_t line)
49{
50 const DWORD level = WLOG_ERROR;
51 unsigned long ec = 0;
52
53 if (!WLog_IsLevelActive(log, level))
54 return;
55
56 BOOL error_logged = FALSE;
57 while ((ec = ERR_get_error()))
58 {
59 error_logged = TRUE;
60 WLog_PrintTextMessage(log, level, line, file, fkt, "%s: %s", msg,
61 ERR_error_string(ec, nullptr));
62 }
63 if (!error_logged)
64 WLog_PrintTextMessage(log, level, line, file, fkt, "%s (no details available)", msg);
65}
66
67static int get_line(BIO* bio, char* buffer, size_t size)
68{
69#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
70 if (size <= 1)
71 return -1;
72
73 size_t pos = 0;
74 do
75 {
76 int rc = BIO_read(bio, &buffer[pos], 1);
77 if (rc <= 0)
78 return rc;
79 char cur = buffer[pos];
80 pos += rc;
81 if ((cur == '\n') || (pos >= size - 1))
82 {
83 buffer[pos] = '\0';
84 return (int)pos;
85 }
86 } while (1);
87#else
88 if (size > INT32_MAX)
89 return -1;
90 return BIO_get_line(bio, buffer, (int)size);
91#endif
92}
93
94WINPR_ATTR_NODISCARD
95static int get_line_context(rdpContext* context, BIO* bio, char* buffer, size_t size)
96{
97 while (TRUE)
98 {
99 /* session terminated, abort */
100 if (freerdp_shall_disconnect_context(context))
101 return -1;
102
103 const int rc = get_line(bio, buffer, size);
104 if (rc <= 0)
105 {
106 if (BIO_should_retry(bio))
107 {
108 BIO_wait_read(bio, 10);
109 continue;
110 }
111 }
112 return rc;
113 }
114}
115
116#if !defined(WITHOUT_FREERDP_3x_DEPRECATED)
117BOOL freerdp_http_request(const char* url, const char* body, long* status_code, BYTE** response,
118 size_t* response_length)
119{
120 BOOL ret = FALSE;
121 char* hostname = nullptr;
122 const char* path = nullptr;
123 char* headers = nullptr;
124 size_t size = 0;
125 int status = 0;
126 char buffer[1024] = WINPR_C_ARRAY_INIT;
127 BIO* bio = nullptr;
128 SSL_CTX* ssl_ctx = nullptr;
129 SSL* ssl = nullptr;
130
131 WINPR_ASSERT(status_code);
132 WINPR_ASSERT(response);
133 WINPR_ASSERT(response_length);
134
135 wLog* log = WLog_Get(TAG);
136 WINPR_ASSERT(log);
137
138 *response = nullptr;
139
140 WLog_WARN(TAG, "This function is deprecated, use freerdp_http_request_context instead. "
141 "Certificate checks are not properly performed when using this function");
142 if (!url || strnlen(url, 8) < 8 || strncmp(url, "https://", 8) != 0 ||
143 !(path = strchr(url + 8, '/')))
144 {
145 WLog_Print(log, WLOG_ERROR, "invalid url provided");
146 goto out;
147 }
148
149 {
150 const size_t len = WINPR_ASSERTING_INT_CAST(size_t, path - (url + 8));
151 hostname = strndup(&url[8], len);
152 }
153 if (!hostname)
154 return FALSE;
155
156 {
157 size_t blen = 0;
158 if (body)
159 {
160 blen = strlen(body);
161 if (winpr_asprintf(&headers, &size, post_header_fmt, path, hostname, blen) < 0)
162 {
163 free(hostname);
164 return FALSE;
165 }
166 }
167 else
168 {
169 if (winpr_asprintf(&headers, &size, get_header_fmt, path, hostname) < 0)
170 {
171 free(hostname);
172 return FALSE;
173 }
174 }
175
176 ssl_ctx = SSL_CTX_new(TLS_client_method());
177
178 if (!ssl_ctx)
179 {
180 log_errors(log, "could not set up ssl context");
181 goto out;
182 }
183
184 if (!SSL_CTX_set_default_verify_paths(ssl_ctx))
185 {
186 log_errors(log, "could not set ssl context verify paths");
187 goto out;
188 }
189
190 SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr);
191
192 SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
193
194 bio = BIO_new_ssl_connect(ssl_ctx);
195 if (!bio)
196 {
197 log_errors(log, "could not set up connection");
198 goto out;
199 }
200
201 if (BIO_set_conn_port(bio, "https") <= 0)
202 {
203 log_errors(log, "could not set port");
204 goto out;
205 }
206
207 if (!BIO_set_conn_hostname(bio, hostname))
208 {
209 log_errors(log, "could not set hostname");
210 goto out;
211 }
212
213 BIO_get_ssl(bio, &ssl);
214 if (!ssl)
215 {
216 log_errors(log, "could not get ssl");
217 goto out;
218 }
219
220 if (!SSL_set_tlsext_host_name(ssl, hostname))
221 {
222 log_errors(log, "could not set sni hostname");
223 goto out;
224 }
225
226 WLog_Print(log, WLOG_DEBUG, "headers:\n%s", headers);
227 ERR_clear_error();
228
229 {
230 const size_t hlen = strnlen(headers, size);
231 if (hlen > INT32_MAX)
232 goto out;
233
234 if (BIO_write(bio, headers, (int)hlen) < 0)
235 {
236 log_errors(log, "could not write headers");
237 goto out;
238 }
239
240 if (body)
241 {
242 WLog_Print(log, WLOG_DEBUG, "body:\n%s", body);
243
244 if (blen > INT_MAX)
245 {
246 WLog_Print(log, WLOG_ERROR, "body too long!");
247 goto out;
248 }
249
250 ERR_clear_error();
251 if (BIO_write(bio, body, (int)blen) < 0)
252 {
253 log_errors(log, "could not write body");
254 goto out;
255 }
256 }
257 }
258 }
259
260 status = get_line(bio, buffer, sizeof(buffer));
261 if (status <= 0)
262 {
263 log_errors(log, "could not read response");
264 goto out;
265 }
266
267 const char header[9] = { 'H', 'T', 'T', 'P', '/', '1', '.', '1', ' ' };
268 if ((status < (INT64)sizeof(header)) || (strncmp(header, buffer, sizeof(header)) != 0))
269 {
270 WLog_Print(log, WLOG_ERROR, "invalid HTTP status header");
271 goto out;
272 }
273
274 errno = 0;
275 *status_code = strtol(&buffer[sizeof(header)], nullptr, 0);
276 if (errno != 0)
277 {
278 char ebuffer[256] = WINPR_C_ARRAY_INIT;
279 WLog_Print(log, WLOG_ERROR, "invalid HTTP status line: %s [%d]",
280 winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
281 goto out;
282 }
283
284 do
285 {
286 status = get_line(bio, buffer, sizeof(buffer));
287 if (status <= 0)
288 {
289 log_errors(log, "could not read response");
290 goto out;
291 }
292
293 char* val = nullptr;
294 char* name = strtok_s(buffer, ":", &val);
295 if (name && (_stricmp(name, "content-length") == 0))
296 {
297 errno = 0;
298 *response_length = strtoul(val, nullptr, 10);
299 if (errno)
300 {
301 char ebuffer[256] = WINPR_C_ARRAY_INIT;
302 WLog_Print(log, WLOG_ERROR, "could not parse content length (%s): %s [%d]", val,
303 winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
304 goto out;
305 }
306 }
307 } while (strcmp(buffer, "\r\n") != 0);
308
309 if (*response_length > 0)
310 {
311 if (*response_length > INT_MAX)
312 {
313 WLog_Print(log, WLOG_ERROR, "response too long!");
314 goto out;
315 }
316
317 *response = calloc(1, *response_length + 1);
318 if (!*response)
319 goto out;
320
321 BYTE* p = *response;
322 size_t left = *response_length;
323 while (left > 0)
324 {
325 const int rd = (left < INT32_MAX) ? (int)left : INT32_MAX;
326 status = BIO_read(bio, p, rd);
327 if (status <= 0)
328 {
329 log_errors(log, "could not read response");
330 goto out;
331 }
332 p += status;
333 if ((size_t)status > left)
334 break;
335 left -= (size_t)status;
336 }
337 }
338
339 WLog_Print(log, WLOG_DEBUG, "response[%" PRIuz "]:\n%s", *response_length,
340 (const char*)(*response));
341 ret = TRUE;
342
343out:
344 if (!ret)
345 {
346 free(*response);
347 *response = nullptr;
348 *response_length = 0;
349 }
350 free(hostname);
351 free(headers);
352 BIO_free_all(bio);
353 SSL_CTX_free(ssl_ctx);
354 return ret;
355}
356#endif
357
358BOOL freerdp_http_request_context(rdpContext* context, const char* url, const char* body,
359 long* status_code, BYTE** response, size_t* response_length)
360{
361 BOOL ret = FALSE;
362 char* hostname = nullptr;
363 const char* path = nullptr;
364 char* headers = nullptr;
365 size_t size = 0;
366 int status = 0;
367 char buffer[1024] = WINPR_C_ARRAY_INIT;
368
369 WINPR_ASSERT(context);
370 WINPR_ASSERT(status_code);
371 WINPR_ASSERT(response);
372 WINPR_ASSERT(response_length);
373 const rdpSettings* settings = context->settings;
374
375 wLog* log = WLog_Get(TAG);
376 WINPR_ASSERT(log);
377
378 *response = nullptr;
379
380 rdpTls* tls = freerdp_tls_new(context);
381 if (!tls || !url || strnlen(url, 8) < 8 || strncmp(url, "https://", 8) != 0 ||
382 !(path = strchr(url + 8, '/')))
383 {
384 WLog_Print(log, WLOG_ERROR, "invalid url provided");
385 goto out;
386 }
387
388 {
389 const size_t len = WINPR_ASSERTING_INT_CAST(size_t, path - (url + 8));
390 hostname = strndup(&url[8], len);
391 }
392 if (!hostname)
393 goto out;
394
395 {
396 size_t blen = 0;
397 if (body)
398 {
399 blen = strlen(body);
400 if (winpr_asprintf(&headers, &size, post_header_fmt, path, hostname, blen) < 0)
401 goto out;
402 }
403 else
404 {
405 if (winpr_asprintf(&headers, &size, get_header_fmt, path, hostname) < 0)
406 goto out;
407 }
408
409 tls->hostname = hostname;
410 tls->port = 443;
411 tls->isHttpRequest = TRUE;
412 UINT32 TcpConnectTimeout = freerdp_settings_get_uint32(settings, FreeRDP_TcpConnectTimeout);
413 if (!utils_tls_connect(context, tls, log, hostname, tls->port, FALSE, TcpConnectTimeout))
414 goto out;
415
416 {
417 const size_t hlen = strnlen(headers, size);
418 if (hlen > INT32_MAX)
419 goto out;
420 WLog_Print(log, WLOG_DEBUG, "headers:\n%s", headers);
421 const int rc = freerdp_tls_write_all(tls, headers, hlen);
422 if ((rc < 0) || ((size_t)rc < hlen))
423 goto out;
424 }
425
426 if (body)
427 {
428 WLog_Print(log, WLOG_DEBUG, "body:\n%s", body);
429
430 if (blen > INT_MAX)
431 {
432 WLog_Print(log, WLOG_ERROR, "body too long!");
433 goto out;
434 }
435
436 const int rc = freerdp_tls_write_all(tls, body, blen);
437 if ((rc < 0) || ((size_t)rc < blen))
438 goto out;
439 }
440 }
441
442 BIO* bio = tls->bio;
443 status = get_line_context(context, bio, buffer, sizeof(buffer));
444 if (status <= 0)
445 {
446 log_errors(log, "could not read response");
447 goto out;
448 }
449
450 const char header[9] = { 'H', 'T', 'T', 'P', '/', '1', '.', '1', ' ' };
451 if ((status < (INT64)sizeof(header)) || (strncmp(header, buffer, sizeof(header)) != 0))
452 {
453 WLog_Print(log, WLOG_ERROR, "invalid HTTP status header");
454 goto out;
455 }
456
457 errno = 0;
458 *status_code = strtol(&buffer[sizeof(header)], nullptr, 0);
459 if (errno != 0)
460 {
461 char ebuffer[256] = WINPR_C_ARRAY_INIT;
462 WLog_Print(log, WLOG_ERROR, "invalid HTTP status line: %s [%d]",
463 winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
464 goto out;
465 }
466
467 do
468 {
469 status = get_line_context(context, bio, buffer, sizeof(buffer));
470 if (status <= 0)
471 {
472 log_errors(log, "could not read response");
473 goto out;
474 }
475
476 char* val = nullptr;
477 char* name = strtok_s(buffer, ":", &val);
478 if (name && (_stricmp(name, "content-length") == 0))
479 {
480 errno = 0;
481 *response_length = strtoul(val, nullptr, 10);
482 if (errno)
483 {
484 char ebuffer[256] = WINPR_C_ARRAY_INIT;
485 WLog_Print(log, WLOG_ERROR, "could not parse content length (%s): %s [%d]", val,
486 winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
487 goto out;
488 }
489 }
490 } while (strcmp(buffer, "\r\n") != 0);
491
492 if (*response_length > 0)
493 {
494 if (*response_length > INT_MAX)
495 {
496 WLog_Print(log, WLOG_ERROR, "response too long!");
497 goto out;
498 }
499
500 *response = calloc(1, *response_length + 1);
501 if (!*response)
502 goto out;
503
504 BYTE* p = *response;
505 size_t left = *response_length;
506 while (left > 0)
507 {
508 const int rd = (left < INT32_MAX) ? (int)left : INT32_MAX;
509 status = BIO_read(bio, p, rd);
510 if (status <= 0)
511 {
512 log_errors(log, "could not read response");
513 goto out;
514 }
515 p += status;
516 if ((size_t)status > left)
517 break;
518 left -= (size_t)status;
519 }
520 }
521
522 WLog_Print(log, WLOG_DEBUG, "response[%" PRIuz "]:\n%s", *response_length,
523 (const char*)(*response));
524 ret = TRUE;
525
526out:
527 if (!ret)
528 {
529 free(*response);
530 *response = nullptr;
531 *response_length = 0;
532 }
533 free(hostname);
534 free(headers);
535 freerdp_tls_free(tls);
536 return ret;
537}
538
539const char* freerdp_http_status_string(long status)
540{
541 switch (status)
542 {
543 case HTTP_STATUS_CONTINUE:
544 return "HTTP_STATUS_CONTINUE";
545 case HTTP_STATUS_SWITCH_PROTOCOLS:
546 return "HTTP_STATUS_SWITCH_PROTOCOLS";
547 case HTTP_STATUS_OK:
548 return "HTTP_STATUS_OK";
549 case HTTP_STATUS_CREATED:
550 return "HTTP_STATUS_CREATED";
551 case HTTP_STATUS_ACCEPTED:
552 return "HTTP_STATUS_ACCEPTED";
553 case HTTP_STATUS_PARTIAL:
554 return "HTTP_STATUS_PARTIAL";
555 case HTTP_STATUS_NO_CONTENT:
556 return "HTTP_STATUS_NO_CONTENT";
557 case HTTP_STATUS_RESET_CONTENT:
558 return "HTTP_STATUS_RESET_CONTENT";
559 case HTTP_STATUS_PARTIAL_CONTENT:
560 return "HTTP_STATUS_PARTIAL_CONTENT";
561 case HTTP_STATUS_WEBDAV_MULTI_STATUS:
562 return "HTTP_STATUS_WEBDAV_MULTI_STATUS";
563 case HTTP_STATUS_AMBIGUOUS:
564 return "HTTP_STATUS_AMBIGUOUS";
565 case HTTP_STATUS_MOVED:
566 return "HTTP_STATUS_MOVED";
567 case HTTP_STATUS_REDIRECT:
568 return "HTTP_STATUS_REDIRECT";
569 case HTTP_STATUS_REDIRECT_METHOD:
570 return "HTTP_STATUS_REDIRECT_METHOD";
571 case HTTP_STATUS_NOT_MODIFIED:
572 return "HTTP_STATUS_NOT_MODIFIED";
573 case HTTP_STATUS_USE_PROXY:
574 return "HTTP_STATUS_USE_PROXY";
575 case HTTP_STATUS_REDIRECT_KEEP_VERB:
576 return "HTTP_STATUS_REDIRECT_KEEP_VERB";
577 case HTTP_STATUS_BAD_REQUEST:
578 return "HTTP_STATUS_BAD_REQUEST";
579 case HTTP_STATUS_DENIED:
580 return "HTTP_STATUS_DENIED";
581 case HTTP_STATUS_PAYMENT_REQ:
582 return "HTTP_STATUS_PAYMENT_REQ";
583 case HTTP_STATUS_FORBIDDEN:
584 return "HTTP_STATUS_FORBIDDEN";
585 case HTTP_STATUS_NOT_FOUND:
586 return "HTTP_STATUS_NOT_FOUND";
587 case HTTP_STATUS_BAD_METHOD:
588 return "HTTP_STATUS_BAD_METHOD";
589 case HTTP_STATUS_NONE_ACCEPTABLE:
590 return "HTTP_STATUS_NONE_ACCEPTABLE";
591 case HTTP_STATUS_PROXY_AUTH_REQ:
592 return "HTTP_STATUS_PROXY_AUTH_REQ";
593 case HTTP_STATUS_REQUEST_TIMEOUT:
594 return "HTTP_STATUS_REQUEST_TIMEOUT";
595 case HTTP_STATUS_CONFLICT:
596 return "HTTP_STATUS_CONFLICT";
597 case HTTP_STATUS_GONE:
598 return "HTTP_STATUS_GONE";
599 case HTTP_STATUS_LENGTH_REQUIRED:
600 return "HTTP_STATUS_LENGTH_REQUIRED";
601 case HTTP_STATUS_PRECOND_FAILED:
602 return "HTTP_STATUS_PRECOND_FAILED";
603 case HTTP_STATUS_REQUEST_TOO_LARGE:
604 return "HTTP_STATUS_REQUEST_TOO_LARGE";
605 case HTTP_STATUS_URI_TOO_LONG:
606 return "HTTP_STATUS_URI_TOO_LONG";
607 case HTTP_STATUS_UNSUPPORTED_MEDIA:
608 return "HTTP_STATUS_UNSUPPORTED_MEDIA";
609 case HTTP_STATUS_RETRY_WITH:
610 return "HTTP_STATUS_RETRY_WITH";
611 case HTTP_STATUS_SERVER_ERROR:
612 return "HTTP_STATUS_SERVER_ERROR";
613 case HTTP_STATUS_NOT_SUPPORTED:
614 return "HTTP_STATUS_NOT_SUPPORTED";
615 case HTTP_STATUS_BAD_GATEWAY:
616 return "HTTP_STATUS_BAD_GATEWAY";
617 case HTTP_STATUS_SERVICE_UNAVAIL:
618 return "HTTP_STATUS_SERVICE_UNAVAIL";
619 case HTTP_STATUS_GATEWAY_TIMEOUT:
620 return "HTTP_STATUS_GATEWAY_TIMEOUT";
621 case HTTP_STATUS_VERSION_NOT_SUP:
622 return "HTTP_STATUS_VERSION_NOT_SUP";
623 default:
624 return "HTTP_STATUS_UNKNOWN";
625 }
626}
627
628const char* freerdp_http_status_string_format(long status, char* buffer, size_t size)
629{
630 const char* code = freerdp_http_status_string(status);
631 (void)_snprintf(buffer, size, "%s [%ld]", code, status);
632 return buffer;
633}
WINPR_ATTR_NODISCARD FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.