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