Coverage Report

Created: 2026-01-09 06:49

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