Coverage Report

Created: 2025-08-26 06:37

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