Coverage Report

Created: 2025-07-01 06:46

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