Coverage Report

Created: 2024-05-20 06:11

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