https: NULL deref
[vlc.git] / modules / access / http / message.c
blob592ea90f3a20dd5e75cb549418420b1dd87accb9
1 /*****************************************************************************
2 * message.c: HTTP request/response
3 *****************************************************************************
4 * Copyright (C) 2015 Rémi Denis-Courmont
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2.1 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19 *****************************************************************************/
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
25 #include <assert.h>
26 #include <errno.h>
27 #include <stdarg.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <time.h>
33 #include <vlc_common.h>
34 #include <vlc_http.h>
35 #include "message.h"
36 #include "h2frame.h"
38 struct vlc_http_msg
40 short status;
41 char *method;
42 char *scheme;
43 char *authority;
44 char *path;
45 char *(*headers)[2];
46 unsigned count;
47 struct vlc_http_stream *payload;
50 static bool vlc_http_is_token(const char *);
52 static ssize_t vlc_http_msg_find_header(const struct vlc_http_msg *m,
53 const char *name)
55 for (unsigned i = 0; i < m->count; i++)
56 if (!strcasecmp(m->headers[i][0], name))
57 return i;
58 return -1;
61 static int vlc_http_msg_vadd_header(struct vlc_http_msg *m, const char *name,
62 const char *fmt, va_list ap)
64 if (!vlc_http_is_token(name))
65 { /* Not a valid field name, i.e. not an HTTP token */
66 errno = EINVAL;
67 return -1;
70 char *value;
71 int len = vasprintf(&value, fmt, ap);
72 if (unlikely(len < 0))
73 return -1;
75 /* IETF RFC7230 §3.2.4 */
76 for (char *p = value; *p; p++)
77 if (*p == '\r' || *p == '\n')
78 *p = ' ';
80 /* Discard leading OWS */
81 size_t crop = strspn(value, "\t ");
82 if (crop > 0)
84 assert((unsigned)len >= crop);
85 memmove(value, value + crop, len - crop + 1);
86 len -= crop;
89 /* Discard trailing OWS */
90 while (len > 0 && (value[len - 1] == '\t' || value[len - 1] == ' '))
91 value[--len] = '\0';
93 /* Fold identically named header field values. This is unfortunately not
94 * possible for Set-Cookie, while Cookie requires a special separator. */
95 ssize_t idx = vlc_http_msg_find_header(m, name);
96 if (idx >= 0 && strcasecmp(name, "Set-Cookie"))
98 char *merged;
99 char sep = strcasecmp(name, "Cookie") ? ',' : ';';
101 int val = asprintf(&merged, "%s%c %s", m->headers[idx][1], sep, value);
103 free(value);
105 if (unlikely(val == -1))
106 return -1;
108 free(m->headers[idx][1]);
109 m->headers[idx][1] = merged;
110 return 0;
113 char *(*h)[2] = realloc(m->headers, sizeof (char *[2]) * (m->count + 1));
114 if (unlikely(h == NULL))
116 free(value);
117 return -1;
120 m->headers = h;
121 h += m->count;
123 h[0][0] = strdup(name);
124 if (unlikely(h[0][0] == NULL))
126 free(value);
127 return -1;
130 h[0][1] = value;
131 m->count++;
132 return 0;
135 int vlc_http_msg_add_header(struct vlc_http_msg *m, const char *name,
136 const char *fmt, ...)
138 va_list ap;
139 int ret;
141 va_start(ap, fmt);
142 ret = vlc_http_msg_vadd_header(m, name, fmt, ap);
143 va_end(ap);
144 return ret;
147 /* TODO: merge identically named headers (not really needed yet) */
149 const char *vlc_http_msg_get_header(const struct vlc_http_msg *m,
150 const char *name)
152 ssize_t idx = vlc_http_msg_find_header(m, name);
153 if (idx < 0)
155 errno = ENOENT;
156 return NULL;
158 return m->headers[idx][1];
161 int vlc_http_msg_get_status(const struct vlc_http_msg *m)
163 return m->status;
166 const char *vlc_http_msg_get_method(const struct vlc_http_msg *m)
168 return m->method;
171 const char *vlc_http_msg_get_scheme(const struct vlc_http_msg *m)
173 return m->scheme;
176 const char *vlc_http_msg_get_authority(const struct vlc_http_msg *m)
178 return m->authority;
181 const char *vlc_http_msg_get_path(const struct vlc_http_msg *m)
183 return m->path;
186 void vlc_http_msg_destroy(struct vlc_http_msg *m)
188 if (m->payload != NULL)
189 vlc_http_stream_close(m->payload, false);
191 for (unsigned i = 0; i < m->count; i++)
193 free(m->headers[i][0]);
194 free(m->headers[i][1]);
197 free(m->headers);
198 free(m->path);
199 free(m->authority);
200 free(m->scheme);
201 free(m->method);
202 free(m);
205 struct vlc_http_msg *
206 vlc_http_req_create(const char *method, const char *scheme,
207 const char *authority, const char *path)
209 struct vlc_http_msg *m = malloc(sizeof (*m));
210 if (unlikely(m == NULL))
211 return NULL;
213 assert(method != NULL);
214 m->status = -1;
215 m->method = strdup(method);
216 m->scheme = (scheme != NULL) ? strdup(scheme) : NULL;
217 m->authority = (authority != NULL) ? strdup(authority) : NULL;
218 m->path = (path != NULL) ? strdup(path) : NULL;
219 m->count = 0;
220 m->headers = NULL;
221 m->payload = NULL;
223 if (unlikely(m->method == NULL
224 || (scheme != NULL && m->scheme == NULL)
225 || (authority != NULL && m->authority == NULL)
226 || (path != NULL && m->path == NULL)))
228 vlc_http_msg_destroy(m);
229 m = NULL;
231 return m;
234 struct vlc_http_msg *vlc_http_resp_create(unsigned status)
236 struct vlc_http_msg *m = malloc(sizeof (*m));
237 if (unlikely(m == NULL))
238 return NULL;
240 assert(status < 1000);
241 m->status = status;
242 m->method = NULL;
243 m->scheme = NULL;
244 m->authority = NULL;
245 m->path = NULL;
246 m->count = 0;
247 m->headers = NULL;
248 m->payload = NULL;
249 return m;
252 void vlc_http_msg_attach(struct vlc_http_msg *m, struct vlc_http_stream *s)
254 assert(m->payload == NULL);
255 m->payload = s;
258 struct vlc_http_msg *vlc_http_msg_iterate(struct vlc_http_msg *m)
260 struct vlc_http_msg *next = vlc_http_stream_read_headers(m->payload);
262 m->payload = NULL;
263 vlc_http_msg_destroy(m);
264 return next;
267 struct vlc_http_msg *vlc_http_msg_get_final(struct vlc_http_msg *m)
269 while (m != NULL && (vlc_http_msg_get_status(m) / 100) == 1)
270 m = vlc_http_msg_iterate(m);
271 return m;
274 block_t *vlc_http_msg_read(struct vlc_http_msg *m)
276 if (m->payload == NULL)
277 return NULL;
279 return vlc_http_stream_read(m->payload);
282 /* Serialization and deserialization */
284 char *vlc_http_msg_format(const struct vlc_http_msg *m, size_t *restrict lenp)
286 size_t len;
288 if (m->status < 0)
290 len = sizeof (" HTTP/1.1\r\nHost: \r\n\r\n");
291 len += strlen(m->method);
292 len += strlen(m->path ? m->path : m->authority);
293 len += strlen(m->authority);
295 else
296 len = sizeof ("HTTP/1.1 123 .\r\n\r\n");
298 for (unsigned i = 0; i < m->count; i++)
299 len += 4 + strlen(m->headers[i][0]) + strlen(m->headers[i][1]);
301 char *buf = malloc(len + 1);
302 if (unlikely(buf == NULL))
303 return NULL;
305 len = 0;
307 if (m->status < 0)
308 len += sprintf(buf, "%s %s HTTP/1.1\r\nHost: %s\r\n", m->method,
309 m->path ? m->path : m->authority, m->authority);
310 else
311 len += sprintf(buf, "HTTP/1.1 %03hd .\r\n", m->status);
313 for (unsigned i = 0; i < m->count; i++)
314 len += sprintf(buf + len, "%s: %s\r\n",
315 m->headers[i][0], m->headers[i][1]);
317 len += sprintf(buf + len, "\r\n");
318 if (lenp != NULL)
319 *lenp = len;
320 return buf;
323 struct vlc_http_msg *vlc_http_msg_headers(const char *msg)
325 struct vlc_http_msg *m;
326 unsigned short code;
328 /* TODO: handle HTTP/1.0 differently */
329 if (sscanf(msg, "HTTP/1.%*1u %3hu %*s", &code) == 1)
331 m = vlc_http_resp_create(code);
332 if (unlikely(m == NULL))
333 return NULL;
335 else
336 return NULL; /* TODO: request support */
338 msg = strstr(msg, "\r\n");
339 if (msg == NULL)
340 goto error;
342 while (strcmp(msg + 2, "\r\n"))
344 const char *eol = msg;
348 eol = strstr(eol + 2, "\r\n");
349 if (eol == NULL)
350 goto error;
351 } /* Deal with legacy obs-fold (i.e. multi-line header) */
352 while (eol[2] == ' ' || eol[2] == '\t');
354 msg += 2; /* skip CRLF */
356 const char *colon = memchr(msg, ':', eol - msg);
357 if (colon == NULL || colon == msg)
358 goto error;
360 char *name = strndup(msg, colon - msg);
361 if (unlikely(name == NULL))
362 goto error;
364 colon++;
365 colon += strspn(colon, " \t");
367 if (unlikely(vlc_http_msg_add_header(m, name, "%.*s",
368 (int)(eol - colon), colon)))
370 free(name);
371 goto error;
373 free(name);
374 msg = eol;
377 return m;
378 error:
379 vlc_http_msg_destroy(m);
380 return NULL;
383 struct vlc_h2_frame *vlc_http_msg_h2_frame(const struct vlc_http_msg *m,
384 uint_fast32_t stream_id, bool eos)
386 for (unsigned j = 0; j < m->count; j++)
387 { /* No HTTP 1 specific headers */
388 assert(strcasecmp(m->headers[j][0], "Connection"));
389 assert(strcasecmp(m->headers[j][0], "Upgrade"));
390 assert(strcasecmp(m->headers[j][0], "HTTP2-Settings"));
393 const char *(*headers)[2] = malloc((m->count + 5) * sizeof (char *[2]));
394 if (unlikely(headers == NULL))
395 return NULL;
397 struct vlc_h2_frame *f;
398 unsigned i = 0;
399 char status[4];
401 if (m->status >= 0)
403 assert(m->status < 1000);
404 sprintf(status, "%hd", m->status);
405 headers[i][0] = ":status";
406 headers[i][1] = status;
407 i++;
409 if (m->method != NULL)
411 headers[i][0] = ":method";
412 headers[i][1] = m->method;
413 i++;
415 if (m->scheme != NULL)
417 headers[i][0] = ":scheme";
418 headers[i][1] = m->scheme;
419 i++;
421 if (m->authority != NULL)
423 headers[i][0] = ":authority";
424 headers[i][1] = m->authority;
425 i++;
427 if (m->path != NULL)
429 headers[i][0] = ":path";
430 headers[i][1] = m->path;
431 i++;
433 if (m->count > 0)
435 memcpy(headers + i, m->headers, m->count * sizeof (*headers));
436 i += m->count;
439 f = vlc_h2_frame_headers(stream_id, VLC_H2_DEFAULT_MAX_FRAME, eos,
440 i, headers);
441 free(headers);
442 return f;
445 struct vlc_http_msg *vlc_http_msg_h2_headers(unsigned n,
446 const char *const hdrs[][2])
448 struct vlc_http_msg *m = vlc_http_resp_create(0);
449 if (unlikely(m == NULL))
450 return NULL;
452 for (unsigned i = 0; i < n; i++)
454 const char *name = hdrs[i][0];
455 const char *value = hdrs[i][1];
457 /* NOTE: HPACK always returns lower-case names, so strcmp() is fine. */
458 if (!strcmp(name, ":status"))
460 char *end;
461 unsigned long status = strtoul(value, &end, 10);
463 if (m->status != 0 || status > 999 || *end != '\0')
464 goto error; /* Not a three decimal digits status! */
466 m->status = status;
467 continue;
470 if (!strcmp(name, ":method"))
472 if (m->method != NULL)
473 goto error;
475 m->method = strdup(value);
476 if (unlikely(m->method == NULL))
477 goto error;
479 m->status = -1; /* this is a request */
480 continue;
483 if (!strcmp(name, ":scheme"))
485 if (m->scheme != NULL)
486 goto error;
488 m->scheme = strdup(value);
489 if (unlikely(m->scheme == NULL))
490 goto error;
491 continue;
494 if (!strcmp(name, ":authority"))
496 if (m->authority != NULL)
497 goto error;
499 m->authority = strdup(value);
500 if (unlikely(m->authority == NULL))
501 goto error;
502 continue;
505 if (!strcmp(name, ":path"))
507 if (m->path != NULL)
508 goto error;
510 m->path = strdup(value);
511 if (unlikely(m->path == NULL))
512 goto error;
513 continue;
516 if (vlc_http_msg_add_header(m, name, "%s", value))
517 goto error;
520 if ((m->status < 0) == (m->method == NULL))
521 { /* Must be either a request or response. Not both, not neither. */
522 error:
523 vlc_http_msg_destroy(m);
524 m = NULL;
526 return m;
529 /* Header helpers */
531 static int vlc_http_istoken(int c)
532 { /* IETF RFC7230 §3.2.6 */
533 return (c >= '0' && c <= '9')
534 || (c >= 'a' && c <= 'z')
535 || (c >= 'A' && c <= 'Z')
536 || (c && strchr("!#$%&'*+-.^_`|~", c) != NULL);
539 static int vlc_http_isctext(int c)
540 { /* IETF RFC7230 §3.2.6 */
541 return (c == '\t') || (c == ' ') || (c >= 0x21 && c <= 0x27)
542 || (c >= 0x2A && c <= 0x5B) || (c >= 0x5D && c <= 0x7E)
543 || (c >= 0x80);
546 static size_t vlc_http_token_length(const char *str)
548 size_t i = 0;
550 while (vlc_http_istoken(str[i]))
551 i++;
552 return i;
555 static size_t vlc_http_quoted_length(const char *str)
557 size_t i = 0;
558 unsigned char c;
560 if (str[i++] != '"')
561 return 0;
565 c = str[i++];
567 if (c == '\0')
568 return 0;
570 if (c == '\\') /* Quoted pair */
572 unsigned char q = str[i++];
573 if (q < 32 && q != '\t')
574 return 0;
577 while (c != '"');
579 return i;
582 static bool vlc_http_is_token(const char *str)
584 size_t len = vlc_http_token_length(str);
585 return len > 0 && str[len] == '\0';
588 const char *vlc_http_next_token(const char *value)
589 { /* We handle either token or token = token / quoted-string */
590 value += strcspn(value, ",\"");
591 if (!*value)
592 return NULL;
594 value += vlc_http_quoted_length(value);
595 return value + strspn(value, "\t ,");
598 const char *vlc_http_msg_get_token(const struct vlc_http_msg *msg,
599 const char *field, const char *token)
601 const char *value = vlc_http_msg_get_header(msg, field);
602 const size_t length = strlen(token);
604 while (value != NULL)
606 if (vlc_http_token_length(value) == length
607 && !strncasecmp(token, value, length))
608 return value;
610 value = vlc_http_next_token(value);
613 return NULL;
616 static size_t vlc_http_comment_length(const char *str)
617 { /* IETF RFC7230 §3.2.6 */
618 if (*str != '(')
619 return 0;
621 size_t i = 1;
623 for (size_t nested = 1; nested > 0; i++)
625 unsigned char c = str[i];
627 if (c == ')')
628 nested--;
629 else
630 if (c == '(') /* Nested comment */
631 nested++;
632 else
633 if (c == '\\') /* Quoted pair */
635 i++;
636 if (str[i] < 32)
637 return 0;
639 else
640 if (!vlc_http_isctext(c))
641 return 0;
643 return i;
646 static bool vlc_http_is_agent(const char *s)
647 { /* IETF RFC7231 §5.5.3 and §7.4.2 */
648 if (!vlc_http_istoken(*s))
649 return false;
651 for (;;)
653 size_t l = vlc_http_token_length(s);
654 if (l != 0) /* product */
656 if (s[l] == '/') /* product version */
658 s += l + 1;
659 l = vlc_http_token_length(s);
662 else
663 l = vlc_http_comment_length(s);
665 if (l == 0)
666 break;
668 s += l;
670 if (*s == '\0')
671 return true;
673 l = strspn(s, "\t "); /* RWS */
675 if (l == 0)
676 break;
678 s += l;
681 return false;
684 int vlc_http_msg_add_agent(struct vlc_http_msg *m, const char *str)
686 const char *hname = (m->status < 0) ? "User-Agent" : "Server";
688 if (!vlc_http_is_agent(str))
690 errno = EINVAL;
691 return -1;
693 return vlc_http_msg_add_header(m, hname, "%s", str);
696 const char *vlc_http_msg_get_agent(const struct vlc_http_msg *m)
698 const char *hname = (m->status < 0) ? "User-Agent" : "Server";
699 const char *str = vlc_http_msg_get_header(m, hname);
701 return (str != NULL && vlc_http_is_agent(str)) ? str : NULL;
704 static const char vlc_http_days[7][4] = {
705 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
707 static const char vlc_http_months[12][4] = {
708 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
709 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
712 int vlc_http_msg_add_time(struct vlc_http_msg *m, const char *hname,
713 const time_t *t)
715 struct tm tm;
717 if (gmtime_r(t, &tm) == NULL)
718 return -1;
719 return vlc_http_msg_add_header(m, hname,
720 "%s, %02d %s %04d %02d:%02d:%02d GMT",
721 vlc_http_days[tm.tm_wday], tm.tm_mday,
722 vlc_http_months[tm.tm_mon],
723 1900 + tm.tm_year,
724 tm.tm_hour, tm.tm_min, tm.tm_sec);
727 int vlc_http_msg_add_atime(struct vlc_http_msg *m)
729 time_t now;
731 time(&now);
732 return vlc_http_msg_add_time(m, "Date", &now);
735 static time_t vlc_http_mktime(const char *str)
736 { /* IETF RFC7231 §7.1.1.1 */
737 struct tm tm;
738 char mon[4];
740 /* Internet Message Format date */
741 if (sscanf(str, "%*c%*c%*c, %2d %3s %4d %2d:%2d:%2d", &tm.tm_mday, mon,
742 &tm.tm_year, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6
743 /* ANSI C format */
744 || sscanf(str, "%*3s %3s %2d %2d:%2d:%2d %4d", mon, &tm.tm_mday,
745 &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &tm.tm_year) == 6)
746 tm.tm_year -= 1900;
747 /* RFC850 date */
748 else if (sscanf(str, "%*[^,], %2d-%3s-%2d %2d:%2d:%2d", &tm.tm_mday, mon,
749 &tm.tm_year, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6)
751 if (tm.tm_year <= 75)
752 tm.tm_year += 100; /* Y2K compat, sort of */
754 else /* Unknown format */
755 goto error;
757 for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++)
758 if (!strcmp(mon, vlc_http_months[tm.tm_mon])) /* found month */
759 return timegm(&tm);
760 error:
761 errno = EINVAL;
762 return -1; /* invalid month */
765 time_t vlc_http_msg_get_time(const struct vlc_http_msg *m, const char *name)
767 const char *str = vlc_http_msg_get_header(m, name);
768 if (str == NULL)
769 return -1;
770 return vlc_http_mktime(str);
773 time_t vlc_http_msg_get_atime(const struct vlc_http_msg *m)
775 return vlc_http_msg_get_time(m, "Date");
778 time_t vlc_http_msg_get_mtime(const struct vlc_http_msg *m)
780 return vlc_http_msg_get_time(m, "Last-Modified");
783 unsigned vlc_http_msg_get_retry_after(const struct vlc_http_msg *m)
785 const char *str = vlc_http_msg_get_header(m, "Retry-After");
786 if (str == NULL)
787 return 0;
789 char *end;
790 unsigned long delay = strtoul(str, &end, 10);
791 if (end != str && *end == '\0')
792 return delay;
794 time_t t = vlc_http_mktime(str);
795 if (t != (time_t)-1)
797 time_t now;
799 time(&now);
800 if (t >= now)
801 return t - now;
803 return 0;
806 uintmax_t vlc_http_msg_get_size(const struct vlc_http_msg *m)
807 { /* IETF RFC7230 §3.3.3 */
808 if ((m->status / 100) == 1 /* Informational 1xx (implicitly void) */
809 || m->status == 204 /* No Content (implicitly void) */
810 || m->status == 205 /* Reset Content (must be explicitly void) */
811 || m->status == 304 /* Not Modified */)
812 return 0;
814 const char *str = vlc_http_msg_get_header(m, "Transfer-Encoding");
815 if (str != NULL) /* Transfer-Encoding preempts Content-Length */
816 return -1;
818 str = vlc_http_msg_get_header(m, "Content-Length");
819 if (str == NULL)
821 if (m->status < 0)
822 return 0; /* Requests are void by default */
823 return -1; /* Response of unknown size (e.g. chunked) */
826 uintmax_t length;
828 if (sscanf(str, "%ju", &length) == 1)
829 return length;
831 errno = EINVAL;
832 return -1;
835 void vlc_http_msg_get_cookies(const struct vlc_http_msg *m,
836 vlc_http_cookie_jar_t *jar, bool secure,
837 const char *host, const char *path)
839 for (unsigned i = 0; i < m->count; i++)
840 if (!strcasecmp(m->headers[i][0], "Set-Cookie"))
841 vlc_http_cookies_store(jar, m->headers[i][1], secure, host, path);
844 int vlc_http_msg_add_cookies(struct vlc_http_msg *m,
845 vlc_http_cookie_jar_t *jar)
847 char *host, *cookies;
848 int val = 0;
849 bool secure;
851 if (m->scheme == NULL || m->authority == NULL || m->path == NULL)
853 errno = EINVAL;
854 return -1;
857 if (!strcasecmp(m->scheme, "https"))
858 secure = true;
859 else if (!strcasecmp(m->scheme, "http"))
860 secure = false;
861 else
862 return 0;
864 if (m->authority[0] == '[')
865 host = strndup(m->authority + 1, strcspn(m->authority + 1, "]"));
866 else
867 host = strndup(m->authority, strcspn(m->authority, ":"));
868 if (unlikely(host == NULL))
869 return -1;
871 cookies = vlc_http_cookies_fetch(jar, secure, host, m->path);
872 free(host);
874 if (cookies != NULL)
876 val = vlc_http_msg_add_header(m, "Cookie", "%s", cookies);
877 free(cookies);
879 return val;