https: fix formatting of current time
[vlc.git] / modules / access / http / message.c
blob5f8732f735c5b70ee6f682efb17aabfaf439e42e
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 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_strings.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 int vlc_http_msg_vadd_header(struct vlc_http_msg *m, const char *name,
51 const char *fmt, va_list ap)
53 char *(*h)[2] = realloc(m->headers, sizeof (char *[2]) * (m->count + 1));
54 if (unlikely(h == NULL))
55 return -1;
57 m->headers = h;
58 h += m->count;
60 h[0][0] = strdup(name);
61 if (unlikely(h[0][0] == NULL))
62 return -1;
64 char *value;
65 if (unlikely(vasprintf(&value, fmt, ap) < 0))
67 free(h[0][0]);
68 return -1;
71 /* IETF RFC7230 §3.2.4 */
72 for (char *p = value; *p; p++)
73 if (*p == '\r' || *p == '\n')
74 *p = ' ';
76 h[0][1] = value;
77 m->count++;
78 return 0;
81 int vlc_http_msg_add_header(struct vlc_http_msg *m, const char *name,
82 const char *fmt, ...)
84 va_list ap;
85 int ret;
87 va_start(ap, fmt);
88 ret = vlc_http_msg_vadd_header(m, name, fmt, ap);
89 va_end(ap);
90 return ret;
93 /* TODO: merge identically named headers (not really needed yet) */
95 const char *vlc_http_msg_get_header(const struct vlc_http_msg *m,
96 const char *name)
98 for (unsigned i = 0; i < m->count; i++)
99 if (!vlc_ascii_strcasecmp(m->headers[i][0], name))
100 return m->headers[i][1];
102 errno = ENOENT;
103 return NULL;
106 int vlc_http_msg_get_status(const struct vlc_http_msg *m)
108 return m->status;
111 const char *vlc_http_msg_get_method(const struct vlc_http_msg *m)
113 return m->method;
116 const char *vlc_http_msg_get_scheme(const struct vlc_http_msg *m)
118 return m->scheme;
121 const char *vlc_http_msg_get_authority(const struct vlc_http_msg *m)
123 return m->authority;
126 const char *vlc_http_msg_get_path(const struct vlc_http_msg *m)
128 return m->path;
131 void vlc_http_msg_destroy(struct vlc_http_msg *m)
133 if (m->payload != NULL)
134 vlc_http_stream_close(m->payload);
136 for (unsigned i = 0; i < m->count; i++)
138 free(m->headers[i][0]);
139 free(m->headers[i][1]);
142 free(m->headers);
143 free(m->path);
144 free(m->authority);
145 free(m->scheme);
146 free(m->method);
147 free(m);
150 struct vlc_http_msg *
151 vlc_http_req_create(const char *method, const char *scheme,
152 const char *authority, const char *path)
154 struct vlc_http_msg *m = malloc(sizeof (*m));
155 if (unlikely(m == NULL))
156 return NULL;
158 assert(method != NULL);
159 m->status = -1;
160 m->method = strdup(method);
161 m->scheme = (scheme != NULL) ? strdup(scheme) : NULL;
162 m->authority = (authority != NULL) ? strdup(authority) : NULL;
163 m->path = (path != NULL) ? strdup(path) : NULL;
164 m->count = 0;
165 m->headers = NULL;
166 m->payload = NULL;
168 if (unlikely(m->method == NULL
169 || (scheme != NULL && m->scheme == NULL)
170 || (authority != NULL && m->authority == NULL)
171 || (path != NULL && m->path == NULL)))
173 vlc_http_msg_destroy(m);
174 m = NULL;
176 return m;
179 struct vlc_http_msg *vlc_http_resp_create(unsigned status)
181 struct vlc_http_msg *m = malloc(sizeof (*m));
182 if (unlikely(m == NULL))
183 return NULL;
185 assert(status < 1000);
186 m->status = status;
187 m->method = NULL;
188 m->scheme = NULL;
189 m->authority = NULL;
190 m->path = NULL;
191 m->count = 0;
192 m->headers = NULL;
193 m->payload = NULL;
194 return m;
197 void vlc_http_msg_attach(struct vlc_http_msg *m, struct vlc_http_stream *s)
199 assert(m->payload == NULL);
200 m->payload = s;
203 struct vlc_http_msg *vlc_http_msg_iterate(struct vlc_http_msg *m)
205 struct vlc_http_msg *next = vlc_http_stream_read_headers(m->payload);
207 m->payload = NULL;
208 vlc_http_msg_destroy(m);
209 return next;
212 struct vlc_http_msg *vlc_http_msg_get_final(struct vlc_http_msg *m)
214 while (m != NULL && (vlc_http_msg_get_status(m) / 100) == 1)
215 m = vlc_http_msg_iterate(m);
216 return m;
219 block_t *vlc_http_msg_read(struct vlc_http_msg *m)
221 if (m->payload == NULL)
222 return NULL;
224 return vlc_http_stream_read(m->payload);
227 /* Serialization and deserialization */
229 char *vlc_http_msg_format(const struct vlc_http_msg *m, size_t *restrict lenp)
231 size_t len;
233 if (m->status < 0)
235 len = sizeof (" HTTP/1.1\r\nHost: \r\nConnection: close\r\n\r\n");
236 len += strlen(m->method);
237 len += strlen(m->path ? m->path : m->authority);
238 len += strlen(m->authority);
240 else
241 len = sizeof ("HTTP/1.1 123 .\r\nConnection: close\r\n\r\n");
243 for (unsigned i = 0; i < m->count; i++)
244 len += 4 + strlen(m->headers[i][0]) + strlen(m->headers[i][1]);
246 char *buf = malloc(len + 1);
247 if (unlikely(buf == NULL))
248 return NULL;
250 len = 0;
252 if (m->status < 0)
253 len += sprintf(buf, "%s %s HTTP/1.1\r\nHost: %s\r\n", m->method,
254 m->path ? m->path : m->authority, m->authority);
255 else
256 len += sprintf(buf, "HTTP/1.1 %03hd .\r\n", m->status);
258 for (unsigned i = 0; i < m->count; i++)
259 len += sprintf(buf + len, "%s: %s\r\n",
260 m->headers[i][0], m->headers[i][1]);
262 len += sprintf(buf + len, "Connection: close\r\n\r\n");
263 if (lenp != NULL)
264 *lenp = len;
265 return buf;
268 struct vlc_http_msg *vlc_http_msg_headers(const char *msg)
270 struct vlc_http_msg *m;
271 unsigned short code;
273 /* TODO: handle HTTP/1.0 differently */
274 if (sscanf(msg, "HTTP/1.%*1u %3hu %*s", &code) == 1)
276 m = vlc_http_resp_create(code);
277 if (unlikely(m == NULL))
278 return NULL;
280 else
281 return NULL; /* TODO: request support */
283 msg = strstr(msg, "\r\n");
284 if (msg == NULL)
285 goto error;
287 while (strcmp(msg + 2, "\r\n"))
289 const char *eol = msg;
293 eol = strstr(eol + 2, "\r\n");
294 if (eol == NULL)
295 goto error;
296 } /* Deal with legacy obs-fold (i.e. multi-line header) */
297 while (eol[2] == ' ' || eol[2] == '\t');
299 msg += 2; /* skip CRLF */
301 const char *colon = memchr(msg, ':', eol - msg);
302 if (colon == NULL || colon == msg)
303 goto error;
305 char *name = strndup(msg, colon - msg);
306 if (unlikely(name == NULL))
307 goto error;
309 colon++;
310 colon += strspn(colon, " \t");
312 if (unlikely(vlc_http_msg_add_header(m, name, "%.*s",
313 (int)(eol - colon), colon)))
315 free(name);
316 goto error;
318 free(name);
319 msg = eol;
322 return m;
323 error:
324 vlc_http_msg_destroy(m);
325 return NULL;
328 struct vlc_h2_frame *vlc_http_msg_h2_frame(const struct vlc_http_msg *m,
329 uint_fast32_t stream_id, bool eos)
331 for (unsigned j = 0; j < m->count; j++)
332 { /* No HTTP 1 specific headers */
333 assert(strcasecmp(m->headers[j][0], "Connection"));
334 assert(strcasecmp(m->headers[j][0], "Upgrade"));
335 assert(strcasecmp(m->headers[j][0], "HTTP2-Settings"));
338 const char *(*headers)[2] = malloc((m->count + 5) * sizeof (char *[2]));
339 if (unlikely(headers == NULL))
340 return NULL;
342 struct vlc_h2_frame *f;
343 unsigned i = 0;
344 char status[4];
346 if (m->status >= 0)
348 assert(m->status < 1000);
349 sprintf(status, "%hd", m->status);
350 headers[i][0] = ":status";
351 headers[i][1] = status;
352 i++;
354 if (m->method != NULL)
356 headers[i][0] = ":method";
357 headers[i][1] = m->method;
358 i++;
360 if (m->scheme != NULL)
362 headers[i][0] = ":scheme";
363 headers[i][1] = m->scheme;
364 i++;
366 if (m->authority != NULL)
368 headers[i][0] = ":authority";
369 headers[i][1] = m->authority;
370 i++;
372 if (m->path != NULL)
374 headers[i][0] = ":path";
375 headers[i][1] = m->path;
376 i++;
378 if (m->count > 0)
380 memcpy(headers + i, m->headers, m->count * sizeof (*headers));
381 i += m->count;
384 f = vlc_h2_frame_headers(stream_id, VLC_H2_DEFAULT_MAX_FRAME, eos,
385 i, headers);
386 free(headers);
387 return f;
390 static int vlc_h2_header_special(const char *name)
392 /* NOTE: HPACK always returns lower-case names, so strcmp() is fine. */
393 static const char special_names[5][16] = {
394 ":status", ":method", ":scheme", ":authority", ":path"
397 for (unsigned i = 0; i < 5; i++)
398 if (!strcmp(special_names[i], name))
399 return i;
400 return -1;
403 struct vlc_http_msg *vlc_http_msg_h2_headers(unsigned n, char *hdrs[][2])
405 struct vlc_http_msg *m = vlc_http_resp_create(0);
406 if (unlikely(m == NULL))
407 return NULL;
409 m->headers = malloc(n * sizeof (char *[2]));
410 if (unlikely(m->headers == NULL))
411 goto error;
413 char *special_caption[5] = { NULL, NULL, NULL, NULL, NULL };
414 char *special[5] = { NULL, NULL, NULL, NULL, NULL };
416 for (unsigned i = 0; i < n; i++)
418 char *name = hdrs[i][0];
419 char *value = hdrs[i][1];
420 int idx = vlc_h2_header_special(name);
422 if (idx >= 0)
424 if (special[idx] != NULL)
425 goto error; /* Duplicate special header! */
427 special_caption[idx] = name;
428 special[idx] = value;
429 continue;
432 m->headers[m->count][0] = name;
433 m->headers[m->count][1] = value;
434 m->count++;
437 if (special[0] != NULL)
438 { /* HTTP response */
439 char *end;
440 unsigned long status = strtoul(special[0], &end, 10);
442 if (status > 999 || *end != '\0')
443 goto error; /* Not a three decimal digits status! */
445 free(special[0]);
446 m->status = status;
448 else
449 m->status = -1; /* HTTP request */
451 m->method = special[1];
452 m->scheme = special[2];
453 m->authority = special[3];
454 m->path = special[4];
456 for (unsigned i = 0; i < 5; i++)
457 free(special_caption[i]);
459 return m;
461 error:
462 free(m->headers);
463 free(m);
464 for (unsigned i = 0; i < n; i++)
466 free(hdrs[i][0]);
467 free(hdrs[i][1]);
469 return NULL;
472 /* Header helpers */
474 static int vlc_http_istoken(int c)
475 { /* IETF RFC7230 §3.2.6 */
476 return (c >= '0' && c <= '9')
477 || (c >= 'a' && c <= 'z')
478 || (c >= 'A' && c <= 'Z')
479 || (c && strchr("!#$%&'*+-.^_`|~", c) != NULL);
482 static int vlc_http_isctext(int c)
483 { /* IETF RFC7230 §3.2.6 */
484 return (c == '\t') || (c == ' ') || (c >= 0x21 && c <= 0x27)
485 || (c >= 0x2A && c <= 0x5B) || (c >= 0x5D && c <= 0x7E)
486 || (c >= 0x80);
489 static size_t vlc_http_token_length(const char *str)
491 size_t i = 0;
493 while (vlc_http_istoken(str[i]))
494 i++;
495 return i;
498 static size_t vlc_http_comment_length(const char *str)
499 { /* IETF RFC7230 §3.2.6 */
500 if (*str != '(')
501 return 0;
503 size_t i = 1;
505 for (size_t nested = 1; nested > 0; i++)
507 unsigned char c = str[i];
509 if (c == ')')
510 nested--;
511 else
512 if (c == '(') /* Nested comment */
513 nested++;
514 else
515 if (c == '\\') /* Quoted pair */
517 i++;
518 if (str[i] < 32)
519 return 0;
521 else
522 if (!vlc_http_isctext(c))
523 return 0;
525 return i;
528 static bool vlc_http_is_agent(const char *s)
529 { /* IETF RFC7231 §5.5.3 and §7.4.2 */
530 if (!vlc_http_istoken(*s))
531 return false;
533 for (;;)
535 size_t l = vlc_http_token_length(s);
536 if (l != 0) /* product */
538 if (s[l] == '/') /* product version */
540 s += l + 1;
541 l = vlc_http_token_length(s);
544 else
545 l = vlc_http_comment_length(s);
547 if (l == 0)
548 break;
550 s += l;
552 if (*s == '\0')
553 return true;
555 l = strspn(s, "\t "); /* RWS */
557 if (l == 0)
558 break;
560 s += l;
563 return false;
566 int vlc_http_msg_add_agent(struct vlc_http_msg *m, const char *str)
568 const char *hname = (m->status < 0) ? "User-Agent" : "Server";
570 if (!vlc_http_is_agent(str))
572 errno = EINVAL;
573 return -1;
575 return vlc_http_msg_add_header(m, hname, "%s", str);
578 const char *vlc_http_msg_get_agent(const struct vlc_http_msg *m)
580 const char *hname = (m->status < 0) ? "User-Agent" : "Server";
581 const char *str = vlc_http_msg_get_header(m, hname);
583 return (str != NULL && vlc_http_is_agent(str)) ? str : NULL;
586 static const char vlc_http_days[7][4] = {
587 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
589 static const char vlc_http_months[12][4] = {
590 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
591 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
594 static int vlc_http_msg_add_time(struct vlc_http_msg *m, const char *hname,
595 const struct tm *restrict tm)
597 return vlc_http_msg_add_header(m, hname,
598 "%s, %02d %s %04d %02d:%02d:%02d GMT",
599 vlc_http_days[tm->tm_wday], tm->tm_mday,
600 vlc_http_months[tm->tm_mon],
601 1900 + tm->tm_year,
602 tm->tm_hour, tm->tm_min, tm->tm_sec);
605 int vlc_http_msg_add_atime(struct vlc_http_msg *m)
607 struct tm tm;
608 time_t now;
610 time(&now);
611 if (gmtime_r(&now, &tm) == NULL)
612 return -1;
614 return vlc_http_msg_add_time(m, "Date", &tm);
617 static time_t vlc_http_mktime(const char *str)
618 { /* IETF RFC7231 §7.1.1.1 */
619 struct tm tm;
620 char mon[4];
622 /* Internet Message Format date */
623 if (sscanf(str, "%*c%*c%*c, %2d %3s %4d %2d:%2d:%2d", &tm.tm_mday, mon,
624 &tm.tm_year, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6
625 /* ANSI C format */
626 || sscanf(str, "%*3s %3s %2d %2d:%2d:%2d %4d", mon, &tm.tm_mday,
627 &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &tm.tm_year) == 6)
628 tm.tm_year -= 1900;
629 /* RFC850 date */
630 else if (sscanf(str, "%*[^,], %2d-%3s-%2d %2d:%2d:%2d", &tm.tm_mday, mon,
631 &tm.tm_year, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6)
633 if (tm.tm_year <= 75)
634 tm.tm_year += 100; /* Y2K compat, sort of */
636 else /* Unknown format */
637 goto error;
639 for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++)
640 if (!strcmp(mon, vlc_http_months[tm.tm_mon])) /* found month */
641 return timegm(&tm);
642 error:
643 errno = EINVAL;
644 return -1; /* invalid month */
647 time_t vlc_http_msg_get_atime(const struct vlc_http_msg *m)
649 const char *str = vlc_http_msg_get_header(m, "Date");
650 return (str != NULL) ? vlc_http_mktime(str) : -1;
653 time_t vlc_http_msg_get_mtime(const struct vlc_http_msg *m)
655 const char *str = vlc_http_msg_get_header(m, "Last-Modified");
656 return (str != NULL) ? vlc_http_mktime(str) : -1;
659 unsigned vlc_http_msg_get_retry_after(const struct vlc_http_msg *m)
661 const char *str = vlc_http_msg_get_header(m, "Retry-After");
662 char *end;
664 unsigned long delay = strtoul(str, &end, 10);
665 if (end != str && *end == '\0')
666 return delay;
668 time_t t = vlc_http_mktime(str);
669 if (t != (time_t)-1)
671 time_t now;
673 time(&now);
674 if (t >= now)
675 return t - now;
677 return 0;
680 uintmax_t vlc_http_msg_get_size(const struct vlc_http_msg *m)
682 if ((m->status / 100) == 1 /* Informational 1xx (implicitly void) */
683 || m->status == 204 /* No Content (implicitly void) */
684 || m->status == 205 /* Reset Content (must be explicitly void) */)
685 return 0;
687 const char *str = vlc_http_msg_get_header(m, "Content-Length");
688 if (str == NULL)
690 if (m->status < 0)
691 return 0; /* Requests are void by default */
692 return -1; /* Response of unknown size (e.g. chunked) */
695 uintmax_t length;
697 if (sscanf(str, "%ju", &length) == 1)
698 return length;
700 errno = EINVAL;
701 return -1;