Coverage Report

Created: 2023-09-25 06:56

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