FreeRDP
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 
33 static const char get_header_fmt[] = "GET %s HTTP/1.1\r\n"
34  "Host: %s\r\n"
35  "\r\n";
36 
37 static 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__)
44 static 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 
64 static 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 
91 BOOL 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 = 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 
294 out:
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 
308 const 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 
397 const 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 }