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, nullptr));
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 = nullptr;
95 const char* path = nullptr;
96 char* headers = nullptr;
97 size_t size = 0;
98 int status = 0;
99 char buffer[1024] = WINPR_C_ARRAY_INIT;
100 BIO* bio = nullptr;
101 SSL_CTX* ssl_ctx = nullptr;
102 SSL* ssl = nullptr;
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 = nullptr;
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 {
121 const size_t len = WINPR_ASSERTING_INT_CAST(size_t, path - (url + 8));
122 hostname = strndup(&url[8], len);
123 }
124 if (!hostname)
125 return FALSE;
126
127 {
128 size_t blen = 0;
129 if (body)
130 {
131 blen = strlen(body);
132 if (winpr_asprintf(&headers, &size, post_header_fmt, path, hostname, blen) < 0)
133 {
134 free(hostname);
135 return FALSE;
136 }
137 }
138 else
139 {
140 if (winpr_asprintf(&headers, &size, get_header_fmt, path, hostname) < 0)
141 {
142 free(hostname);
143 return FALSE;
144 }
145 }
146
147 ssl_ctx = SSL_CTX_new(TLS_client_method());
148
149 if (!ssl_ctx)
150 {
151 log_errors(log, "could not set up ssl context");
152 goto out;
153 }
154
155 if (!SSL_CTX_set_default_verify_paths(ssl_ctx))
156 {
157 log_errors(log, "could not set ssl context verify paths");
158 goto out;
159 }
160
161 SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr);
162
163 SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
164
165 bio = BIO_new_ssl_connect(ssl_ctx);
166 if (!bio)
167 {
168 log_errors(log, "could not set up connection");
169 goto out;
170 }
171
172 if (BIO_set_conn_port(bio, "https") <= 0)
173 {
174 log_errors(log, "could not set port");
175 goto out;
176 }
177
178 if (!BIO_set_conn_hostname(bio, hostname))
179 {
180 log_errors(log, "could not set hostname");
181 goto out;
182 }
183
184 BIO_get_ssl(bio, &ssl);
185 if (!ssl)
186 {
187 log_errors(log, "could not get ssl");
188 goto out;
189 }
190
191 if (!SSL_set_tlsext_host_name(ssl, hostname))
192 {
193 log_errors(log, "could not set sni hostname");
194 goto out;
195 }
196
197 WLog_Print(log, WLOG_DEBUG, "headers:\n%s", headers);
198 ERR_clear_error();
199
200 {
201 const size_t hlen = strnlen(headers, size);
202 if (hlen > INT32_MAX)
203 goto out;
204
205 if (BIO_write(bio, headers, (int)hlen) < 0)
206 {
207 log_errors(log, "could not write headers");
208 goto out;
209 }
210
211 if (body)
212 {
213 WLog_Print(log, WLOG_DEBUG, "body:\n%s", body);
214
215 if (blen > INT_MAX)
216 {
217 WLog_Print(log, WLOG_ERROR, "body too long!");
218 goto out;
219 }
220
221 ERR_clear_error();
222 if (BIO_write(bio, body, (int)blen) < 0)
223 {
224 log_errors(log, "could not write body");
225 goto out;
226 }
227 }
228 }
229 }
230
231 status = get_line(bio, buffer, sizeof(buffer));
232 if (status <= 0)
233 {
234 log_errors(log, "could not read response");
235 goto out;
236 }
237
238 const char header[9] = { 'H', 'T', 'T', 'P', '/', '1', '.', '1', ' ' };
239 if ((status < (INT64)sizeof(header)) || (strncmp(header, buffer, sizeof(header)) != 0))
240 {
241 WLog_Print(log, WLOG_ERROR, "invalid HTTP status header");
242 goto out;
243 }
244
245 errno = 0;
246 *status_code = strtol(&buffer[sizeof(header)], nullptr, 0);
247 if (errno != 0)
248 {
249 char ebuffer[256] = WINPR_C_ARRAY_INIT;
250 WLog_Print(log, WLOG_ERROR, "invalid HTTP status line: %s [%d]",
251 winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
252 goto out;
253 }
254
255 do
256 {
257 status = get_line(bio, buffer, sizeof(buffer));
258 if (status <= 0)
259 {
260 log_errors(log, "could not read response");
261 goto out;
262 }
263
264 char* val = nullptr;
265 char* name = strtok_s(buffer, ":", &val);
266 if (name && (_stricmp(name, "content-length") == 0))
267 {
268 errno = 0;
269 *response_length = strtoul(val, nullptr, 10);
270 if (errno)
271 {
272 char ebuffer[256] = WINPR_C_ARRAY_INIT;
273 WLog_Print(log, WLOG_ERROR, "could not parse content length (%s): %s [%d]", val,
274 winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
275 goto out;
276 }
277 }
278 } while (strcmp(buffer, "\r\n") != 0);
279
280 if (*response_length > 0)
281 {
282 if (*response_length > INT_MAX)
283 {
284 WLog_Print(log, WLOG_ERROR, "response too long!");
285 goto out;
286 }
287
288 *response = calloc(1, *response_length + 1);
289 if (!*response)
290 goto out;
291
292 BYTE* p = *response;
293 size_t left = *response_length;
294 while (left > 0)
295 {
296 const int rd = (left < INT32_MAX) ? (int)left : INT32_MAX;
297 status = BIO_read(bio, p, rd);
298 if (status <= 0)
299 {
300 log_errors(log, "could not read response");
301 goto out;
302 }
303 p += status;
304 if ((size_t)status > left)
305 break;
306 left -= (size_t)status;
307 }
308 }
309
310 WLog_Print(log, WLOG_DEBUG, "response[%" PRIuz "]:\n%s", *response_length,
311 (const char*)(*response));
312 ret = TRUE;
313
314out:
315 if (!ret)
316 {
317 free(*response);
318 *response = nullptr;
319 *response_length = 0;
320 }
321 free(hostname);
322 free(headers);
323 BIO_free_all(bio);
324 SSL_CTX_free(ssl_ctx);
325 return ret;
326}
327
328const char* freerdp_http_status_string(long status)
329{
330 switch (status)
331 {
332 case HTTP_STATUS_CONTINUE:
333 return "HTTP_STATUS_CONTINUE";
334 case HTTP_STATUS_SWITCH_PROTOCOLS:
335 return "HTTP_STATUS_SWITCH_PROTOCOLS";
336 case HTTP_STATUS_OK:
337 return "HTTP_STATUS_OK";
338 case HTTP_STATUS_CREATED:
339 return "HTTP_STATUS_CREATED";
340 case HTTP_STATUS_ACCEPTED:
341 return "HTTP_STATUS_ACCEPTED";
342 case HTTP_STATUS_PARTIAL:
343 return "HTTP_STATUS_PARTIAL";
344 case HTTP_STATUS_NO_CONTENT:
345 return "HTTP_STATUS_NO_CONTENT";
346 case HTTP_STATUS_RESET_CONTENT:
347 return "HTTP_STATUS_RESET_CONTENT";
348 case HTTP_STATUS_PARTIAL_CONTENT:
349 return "HTTP_STATUS_PARTIAL_CONTENT";
350 case HTTP_STATUS_WEBDAV_MULTI_STATUS:
351 return "HTTP_STATUS_WEBDAV_MULTI_STATUS";
352 case HTTP_STATUS_AMBIGUOUS:
353 return "HTTP_STATUS_AMBIGUOUS";
354 case HTTP_STATUS_MOVED:
355 return "HTTP_STATUS_MOVED";
356 case HTTP_STATUS_REDIRECT:
357 return "HTTP_STATUS_REDIRECT";
358 case HTTP_STATUS_REDIRECT_METHOD:
359 return "HTTP_STATUS_REDIRECT_METHOD";
360 case HTTP_STATUS_NOT_MODIFIED:
361 return "HTTP_STATUS_NOT_MODIFIED";
362 case HTTP_STATUS_USE_PROXY:
363 return "HTTP_STATUS_USE_PROXY";
364 case HTTP_STATUS_REDIRECT_KEEP_VERB:
365 return "HTTP_STATUS_REDIRECT_KEEP_VERB";
366 case HTTP_STATUS_BAD_REQUEST:
367 return "HTTP_STATUS_BAD_REQUEST";
368 case HTTP_STATUS_DENIED:
369 return "HTTP_STATUS_DENIED";
370 case HTTP_STATUS_PAYMENT_REQ:
371 return "HTTP_STATUS_PAYMENT_REQ";
372 case HTTP_STATUS_FORBIDDEN:
373 return "HTTP_STATUS_FORBIDDEN";
374 case HTTP_STATUS_NOT_FOUND:
375 return "HTTP_STATUS_NOT_FOUND";
376 case HTTP_STATUS_BAD_METHOD:
377 return "HTTP_STATUS_BAD_METHOD";
378 case HTTP_STATUS_NONE_ACCEPTABLE:
379 return "HTTP_STATUS_NONE_ACCEPTABLE";
380 case HTTP_STATUS_PROXY_AUTH_REQ:
381 return "HTTP_STATUS_PROXY_AUTH_REQ";
382 case HTTP_STATUS_REQUEST_TIMEOUT:
383 return "HTTP_STATUS_REQUEST_TIMEOUT";
384 case HTTP_STATUS_CONFLICT:
385 return "HTTP_STATUS_CONFLICT";
386 case HTTP_STATUS_GONE:
387 return "HTTP_STATUS_GONE";
388 case HTTP_STATUS_LENGTH_REQUIRED:
389 return "HTTP_STATUS_LENGTH_REQUIRED";
390 case HTTP_STATUS_PRECOND_FAILED:
391 return "HTTP_STATUS_PRECOND_FAILED";
392 case HTTP_STATUS_REQUEST_TOO_LARGE:
393 return "HTTP_STATUS_REQUEST_TOO_LARGE";
394 case HTTP_STATUS_URI_TOO_LONG:
395 return "HTTP_STATUS_URI_TOO_LONG";
396 case HTTP_STATUS_UNSUPPORTED_MEDIA:
397 return "HTTP_STATUS_UNSUPPORTED_MEDIA";
398 case HTTP_STATUS_RETRY_WITH:
399 return "HTTP_STATUS_RETRY_WITH";
400 case HTTP_STATUS_SERVER_ERROR:
401 return "HTTP_STATUS_SERVER_ERROR";
402 case HTTP_STATUS_NOT_SUPPORTED:
403 return "HTTP_STATUS_NOT_SUPPORTED";
404 case HTTP_STATUS_BAD_GATEWAY:
405 return "HTTP_STATUS_BAD_GATEWAY";
406 case HTTP_STATUS_SERVICE_UNAVAIL:
407 return "HTTP_STATUS_SERVICE_UNAVAIL";
408 case HTTP_STATUS_GATEWAY_TIMEOUT:
409 return "HTTP_STATUS_GATEWAY_TIMEOUT";
410 case HTTP_STATUS_VERSION_NOT_SUP:
411 return "HTTP_STATUS_VERSION_NOT_SUP";
412 default:
413 return "HTTP_STATUS_UNKNOWN";
414 }
415}
416
417const char* freerdp_http_status_string_format(long status, char* buffer, size_t size)
418{
419 const char* code = freerdp_http_status_string(status);
420 (void)_snprintf(buffer, size, "%s [%ld]", code, status);
421 return buffer;
422}