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 <freerdp/log.h>
31#define TAG FREERDP_TAG("utils.http")
32
33static const char get_header_fmt[] = "GET %s HTTP/1.1\r\n"
34 "Host: %s\r\n"
35 "\r\n";
36
37static const char post_header_fmt[] = "POST %s HTTP/1.1\r\n"
38 "Host: %s\r\n"
39 "Content-Type: application/x-www-form-urlencoded\r\n"
40 "Content-Length: %lu\r\n"
41 "\r\n";
42
43#define log_errors(log, msg) log_errors_(log, msg, __FILE__, __func__, __LINE__)
44static void log_errors_(wLog* log, const char* msg, const char* file, const char* fkt, size_t line)
45{
46 const DWORD level = WLOG_ERROR;
47 unsigned long ec = 0;
48
49 if (!WLog_IsLevelActive(log, level))
50 return;
51
52 BOOL error_logged = FALSE;
53 while ((ec = ERR_get_error()))
54 {
55 error_logged = TRUE;
56 WLog_PrintTextMessage(log, level, line, file, fkt, "%s: %s", msg,
57 ERR_error_string(ec, NULL));
58 }
59 if (!error_logged)
60 WLog_PrintTextMessage(log, level, line, file, fkt, "%s (no details available)", msg);
61}
62
63static int get_line(BIO* bio, char* buffer, size_t size)
64{
65#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
66 if (size <= 1)
67 return -1;
68
69 size_t pos = 0;
70 do
71 {
72 int rc = BIO_read(bio, &buffer[pos], 1);
73 if (rc <= 0)
74 return rc;
75 char cur = buffer[pos];
76 pos += rc;
77 if ((cur == '\n') || (pos >= size - 1))
78 {
79 buffer[pos] = '\0';
80 return (int)pos;
81 }
82 } while (1);
83#else
84 if (size > INT32_MAX)
85 return -1;
86 return BIO_get_line(bio, buffer, (int)size);
87#endif
88}
89
90BOOL freerdp_http_request(const char* url, const char* body, long* status_code, BYTE** response,
91 size_t* response_length)
92{
93 BOOL ret = FALSE;
94 char* hostname = NULL;
95 const char* path = NULL;
96 char* headers = NULL;
97 size_t size = 0;
98 int status = 0;
99 char buffer[1024] = { 0 };
100 BIO* bio = NULL;
101 SSL_CTX* ssl_ctx = NULL;
102 SSL* ssl = NULL;
103
104 WINPR_ASSERT(status_code);
105 WINPR_ASSERT(response);
106 WINPR_ASSERT(response_length);
107
108 wLog* log = WLog_Get(TAG);
109 WINPR_ASSERT(log);
110
111 *response = NULL;
112
113 if (!url || strnlen(url, 8) < 8 || strncmp(url, "https://", 8) != 0 ||
114 !(path = strchr(url + 8, '/')))
115 {
116 WLog_Print(log, WLOG_ERROR, "invalid url provided");
117 goto out;
118 }
119
120 const size_t len = WINPR_ASSERTING_INT_CAST(size_t, path - (url + 8));
121 hostname = strndup(&url[8], len);
122 if (!hostname)
123 return FALSE;
124
125 size_t blen = 0;
126 if (body)
127 {
128 blen = strlen(body);
129 if (winpr_asprintf(&headers, &size, post_header_fmt, path, hostname, blen) < 0)
130 {
131 free(hostname);
132 return FALSE;
133 }
134 }
135 else
136 {
137 if (winpr_asprintf(&headers, &size, get_header_fmt, path, hostname) < 0)
138 {
139 free(hostname);
140 return FALSE;
141 }
142 }
143
144 ssl_ctx = SSL_CTX_new(TLS_client_method());
145
146 if (!ssl_ctx)
147 {
148 log_errors(log, "could not set up ssl context");
149 goto out;
150 }
151
152 if (!SSL_CTX_set_default_verify_paths(ssl_ctx))
153 {
154 log_errors(log, "could not set ssl context verify paths");
155 goto out;
156 }
157
158 SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
159
160 bio = BIO_new_ssl_connect(ssl_ctx);
161 if (!bio)
162 {
163 log_errors(log, "could not set up connection");
164 goto out;
165 }
166
167 if (BIO_set_conn_port(bio, "https") <= 0)
168 {
169 log_errors(log, "could not set port");
170 goto out;
171 }
172
173 if (!BIO_set_conn_hostname(bio, hostname))
174 {
175 log_errors(log, "could not set hostname");
176 goto out;
177 }
178
179 BIO_get_ssl(bio, &ssl);
180 if (!ssl)
181 {
182 log_errors(log, "could not get ssl");
183 goto out;
184 }
185
186 if (!SSL_set_tlsext_host_name(ssl, hostname))
187 {
188 log_errors(log, "could not set sni hostname");
189 goto out;
190 }
191
192 WLog_Print(log, WLOG_DEBUG, "headers:\n%s", headers);
193 ERR_clear_error();
194 const size_t hlen = strnlen(headers, size);
195 if (hlen > INT32_MAX)
196 goto out;
197
198 if (BIO_write(bio, headers, (int)hlen) < 0)
199 {
200 log_errors(log, "could not write headers");
201 goto out;
202 }
203
204 if (body)
205 {
206 WLog_Print(log, WLOG_DEBUG, "body:\n%s", body);
207
208 if (blen > INT_MAX)
209 {
210 WLog_Print(log, WLOG_ERROR, "body too long!");
211 goto out;
212 }
213
214 ERR_clear_error();
215 if (BIO_write(bio, body, (int)blen) < 0)
216 {
217 log_errors(log, "could not write body");
218 goto out;
219 }
220 }
221
222 status = get_line(bio, buffer, sizeof(buffer));
223 if (status <= 0)
224 {
225 log_errors(log, "could not read response");
226 goto out;
227 }
228
229 // NOLINTNEXTLINE(cert-err34-c)
230 if (sscanf(buffer, "HTTP/1.1 %li %*[^\r\n]\r\n", status_code) < 1)
231 {
232 WLog_Print(log, WLOG_ERROR, "invalid HTTP status line");
233 goto out;
234 }
235
236 do
237 {
238 status = get_line(bio, buffer, sizeof(buffer));
239 if (status <= 0)
240 {
241 log_errors(log, "could not read response");
242 goto out;
243 }
244
245 char* val = NULL;
246 char* name = strtok_s(buffer, ":", &val);
247 if (name && (_stricmp(name, "content-length") == 0))
248 {
249 errno = 0;
250 *response_length = strtoul(val, NULL, 10);
251 if (errno)
252 {
253 char ebuffer[256] = { 0 };
254 WLog_Print(log, WLOG_ERROR, "could not parse content length (%s): %s [%d]", val,
255 winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
256 goto out;
257 }
258 }
259 } while (strcmp(buffer, "\r\n") != 0);
260
261 if (*response_length > 0)
262 {
263 if (*response_length > INT_MAX)
264 {
265 WLog_Print(log, WLOG_ERROR, "response too long!");
266 goto out;
267 }
268
269 *response = calloc(1, *response_length + 1);
270 if (!*response)
271 goto out;
272
273 BYTE* p = *response;
274 size_t left = *response_length;
275 while (left > 0)
276 {
277 const int rd = (left < INT32_MAX) ? (int)left : INT32_MAX;
278 status = BIO_read(bio, p, rd);
279 if (status <= 0)
280 {
281 log_errors(log, "could not read response");
282 goto out;
283 }
284 p += status;
285 if ((size_t)status > left)
286 break;
287 left -= (size_t)status;
288 }
289 }
290
291 ret = TRUE;
292
293out:
294 if (!ret)
295 {
296 free(*response);
297 *response = NULL;
298 *response_length = 0;
299 }
300 free(hostname);
301 free(headers);
302 BIO_free_all(bio);
303 SSL_CTX_free(ssl_ctx);
304 return ret;
305}
306
307const char* freerdp_http_status_string(long status)
308{
309 switch (status)
310 {
311 case HTTP_STATUS_CONTINUE:
312 return "HTTP_STATUS_CONTINUE";
313 case HTTP_STATUS_SWITCH_PROTOCOLS:
314 return "HTTP_STATUS_SWITCH_PROTOCOLS";
315 case HTTP_STATUS_OK:
316 return "HTTP_STATUS_OK";
317 case HTTP_STATUS_CREATED:
318 return "HTTP_STATUS_CREATED";
319 case HTTP_STATUS_ACCEPTED:
320 return "HTTP_STATUS_ACCEPTED";
321 case HTTP_STATUS_PARTIAL:
322 return "HTTP_STATUS_PARTIAL";
323 case HTTP_STATUS_NO_CONTENT:
324 return "HTTP_STATUS_NO_CONTENT";
325 case HTTP_STATUS_RESET_CONTENT:
326 return "HTTP_STATUS_RESET_CONTENT";
327 case HTTP_STATUS_PARTIAL_CONTENT:
328 return "HTTP_STATUS_PARTIAL_CONTENT";
329 case HTTP_STATUS_WEBDAV_MULTI_STATUS:
330 return "HTTP_STATUS_WEBDAV_MULTI_STATUS";
331 case HTTP_STATUS_AMBIGUOUS:
332 return "HTTP_STATUS_AMBIGUOUS";
333 case HTTP_STATUS_MOVED:
334 return "HTTP_STATUS_MOVED";
335 case HTTP_STATUS_REDIRECT:
336 return "HTTP_STATUS_REDIRECT";
337 case HTTP_STATUS_REDIRECT_METHOD:
338 return "HTTP_STATUS_REDIRECT_METHOD";
339 case HTTP_STATUS_NOT_MODIFIED:
340 return "HTTP_STATUS_NOT_MODIFIED";
341 case HTTP_STATUS_USE_PROXY:
342 return "HTTP_STATUS_USE_PROXY";
343 case HTTP_STATUS_REDIRECT_KEEP_VERB:
344 return "HTTP_STATUS_REDIRECT_KEEP_VERB";
345 case HTTP_STATUS_BAD_REQUEST:
346 return "HTTP_STATUS_BAD_REQUEST";
347 case HTTP_STATUS_DENIED:
348 return "HTTP_STATUS_DENIED";
349 case HTTP_STATUS_PAYMENT_REQ:
350 return "HTTP_STATUS_PAYMENT_REQ";
351 case HTTP_STATUS_FORBIDDEN:
352 return "HTTP_STATUS_FORBIDDEN";
353 case HTTP_STATUS_NOT_FOUND:
354 return "HTTP_STATUS_NOT_FOUND";
355 case HTTP_STATUS_BAD_METHOD:
356 return "HTTP_STATUS_BAD_METHOD";
357 case HTTP_STATUS_NONE_ACCEPTABLE:
358 return "HTTP_STATUS_NONE_ACCEPTABLE";
359 case HTTP_STATUS_PROXY_AUTH_REQ:
360 return "HTTP_STATUS_PROXY_AUTH_REQ";
361 case HTTP_STATUS_REQUEST_TIMEOUT:
362 return "HTTP_STATUS_REQUEST_TIMEOUT";
363 case HTTP_STATUS_CONFLICT:
364 return "HTTP_STATUS_CONFLICT";
365 case HTTP_STATUS_GONE:
366 return "HTTP_STATUS_GONE";
367 case HTTP_STATUS_LENGTH_REQUIRED:
368 return "HTTP_STATUS_LENGTH_REQUIRED";
369 case HTTP_STATUS_PRECOND_FAILED:
370 return "HTTP_STATUS_PRECOND_FAILED";
371 case HTTP_STATUS_REQUEST_TOO_LARGE:
372 return "HTTP_STATUS_REQUEST_TOO_LARGE";
373 case HTTP_STATUS_URI_TOO_LONG:
374 return "HTTP_STATUS_URI_TOO_LONG";
375 case HTTP_STATUS_UNSUPPORTED_MEDIA:
376 return "HTTP_STATUS_UNSUPPORTED_MEDIA";
377 case HTTP_STATUS_RETRY_WITH:
378 return "HTTP_STATUS_RETRY_WITH";
379 case HTTP_STATUS_SERVER_ERROR:
380 return "HTTP_STATUS_SERVER_ERROR";
381 case HTTP_STATUS_NOT_SUPPORTED:
382 return "HTTP_STATUS_NOT_SUPPORTED";
383 case HTTP_STATUS_BAD_GATEWAY:
384 return "HTTP_STATUS_BAD_GATEWAY";
385 case HTTP_STATUS_SERVICE_UNAVAIL:
386 return "HTTP_STATUS_SERVICE_UNAVAIL";
387 case HTTP_STATUS_GATEWAY_TIMEOUT:
388 return "HTTP_STATUS_GATEWAY_TIMEOUT";
389 case HTTP_STATUS_VERSION_NOT_SUP:
390 return "HTTP_STATUS_VERSION_NOT_SUP";
391 default:
392 return "HTTP_STATUS_UNKNOWN";
393 }
394}
395
396const char* freerdp_http_status_string_format(long status, char* buffer, size_t size)
397{
398 const char* code = freerdp_http_status_string(status);
399 (void)_snprintf(buffer, size, "%s [%ld]", code, status);
400 return buffer;
401}