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 *****************************************************************************/
33 #include <vlc_common.h>
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
,
55 for (unsigned i
= 0; i
< m
->count
; i
++)
56 if (!strcasecmp(m
->headers
[i
][0], name
))
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 */
71 int len
= vasprintf(&value
, fmt
, ap
);
72 if (unlikely(len
< 0))
75 /* IETF RFC7230 §3.2.4 */
76 for (char *p
= value
; *p
; p
++)
77 if (*p
== '\r' || *p
== '\n')
80 /* Discard leading OWS */
81 size_t crop
= strspn(value
, "\t ");
84 assert((unsigned)len
>= crop
);
85 memmove(value
, value
+ crop
, len
- crop
+ 1);
89 /* Discard trailing OWS */
90 while (len
> 0 && (value
[len
- 1] == '\t' || value
[len
- 1] == ' '))
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"))
99 char sep
= strcasecmp(name
, "Cookie") ? ',' : ';';
101 int val
= asprintf(&merged
, "%s%c %s", m
->headers
[idx
][1], sep
, value
);
105 if (unlikely(val
== -1))
108 free(m
->headers
[idx
][1]);
109 m
->headers
[idx
][1] = merged
;
113 char *(*h
)[2] = realloc(m
->headers
, sizeof (char *[2]) * (m
->count
+ 1));
114 if (unlikely(h
== NULL
))
123 h
[0][0] = strdup(name
);
124 if (unlikely(h
[0][0] == NULL
))
135 int vlc_http_msg_add_header(struct vlc_http_msg
*m
, const char *name
,
136 const char *fmt
, ...)
142 ret
= vlc_http_msg_vadd_header(m
, name
, fmt
, ap
);
147 /* TODO: merge identically named headers (not really needed yet) */
149 const char *vlc_http_msg_get_header(const struct vlc_http_msg
*m
,
152 ssize_t idx
= vlc_http_msg_find_header(m
, name
);
158 return m
->headers
[idx
][1];
161 int vlc_http_msg_get_status(const struct vlc_http_msg
*m
)
166 const char *vlc_http_msg_get_method(const struct vlc_http_msg
*m
)
171 const char *vlc_http_msg_get_scheme(const struct vlc_http_msg
*m
)
176 const char *vlc_http_msg_get_authority(const struct vlc_http_msg
*m
)
181 const char *vlc_http_msg_get_path(const struct vlc_http_msg
*m
)
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]);
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
))
213 assert(method
!= NULL
);
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
;
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
);
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
))
240 assert(status
< 1000);
252 void vlc_http_msg_attach(struct vlc_http_msg
*m
, struct vlc_http_stream
*s
)
254 assert(m
->payload
== NULL
);
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
);
263 vlc_http_msg_destroy(m
);
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
);
274 block_t
*vlc_http_msg_read(struct vlc_http_msg
*m
)
276 if (m
->payload
== 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
)
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
);
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
))
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
);
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");
323 struct vlc_http_msg
*vlc_http_msg_headers(const char *msg
)
325 struct vlc_http_msg
*m
;
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
))
336 return NULL
; /* TODO: request support */
338 msg
= strstr(msg
, "\r\n");
342 while (strcmp(msg
+ 2, "\r\n"))
344 const char *eol
= msg
;
348 eol
= strstr(eol
+ 2, "\r\n");
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
)
360 char *name
= strndup(msg
, colon
- msg
);
361 if (unlikely(name
== NULL
))
365 colon
+= strspn(colon
, " \t");
367 if (unlikely(vlc_http_msg_add_header(m
, name
, "%.*s",
368 (int)(eol
- colon
), colon
)))
379 vlc_http_msg_destroy(m
);
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
))
397 struct vlc_h2_frame
*f
;
403 assert(m
->status
< 1000);
404 sprintf(status
, "%hd", m
->status
);
405 headers
[i
][0] = ":status";
406 headers
[i
][1] = status
;
409 if (m
->method
!= NULL
)
411 headers
[i
][0] = ":method";
412 headers
[i
][1] = m
->method
;
415 if (m
->scheme
!= NULL
)
417 headers
[i
][0] = ":scheme";
418 headers
[i
][1] = m
->scheme
;
421 if (m
->authority
!= NULL
)
423 headers
[i
][0] = ":authority";
424 headers
[i
][1] = m
->authority
;
429 headers
[i
][0] = ":path";
430 headers
[i
][1] = m
->path
;
435 memcpy(headers
+ i
, m
->headers
, m
->count
* sizeof (*headers
));
439 f
= vlc_h2_frame_headers(stream_id
, VLC_H2_DEFAULT_MAX_FRAME
, eos
,
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
))
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"))
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! */
470 if (!strcmp(name
, ":method"))
472 if (m
->method
!= NULL
)
475 m
->method
= strdup(value
);
476 if (unlikely(m
->method
== NULL
))
479 m
->status
= -1; /* this is a request */
483 if (!strcmp(name
, ":scheme"))
485 if (m
->scheme
!= NULL
)
488 m
->scheme
= strdup(value
);
489 if (unlikely(m
->scheme
== NULL
))
494 if (!strcmp(name
, ":authority"))
496 if (m
->authority
!= NULL
)
499 m
->authority
= strdup(value
);
500 if (unlikely(m
->authority
== NULL
))
505 if (!strcmp(name
, ":path"))
510 m
->path
= strdup(value
);
511 if (unlikely(m
->path
== NULL
))
516 if (vlc_http_msg_add_header(m
, name
, "%s", value
))
520 if ((m
->status
< 0) == (m
->method
== NULL
))
521 { /* Must be either a request or response. Not both, not neither. */
523 vlc_http_msg_destroy(m
);
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)
546 static size_t vlc_http_token_length(const char *str
)
550 while (vlc_http_istoken(str
[i
]))
555 static size_t vlc_http_quoted_length(const char *str
)
570 if (c
== '\\') /* Quoted pair */
572 unsigned char q
= str
[i
++];
573 if (q
< 32 && q
!= '\t')
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
, ",\"");
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
))
610 value
= vlc_http_next_token(value
);
616 static size_t vlc_http_comment_length(const char *str
)
617 { /* IETF RFC7230 §3.2.6 */
623 for (size_t nested
= 1; nested
> 0; i
++)
625 unsigned char c
= str
[i
];
630 if (c
== '(') /* Nested comment */
633 if (c
== '\\') /* Quoted pair */
640 if (!vlc_http_isctext(c
))
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
))
653 size_t l
= vlc_http_token_length(s
);
654 if (l
!= 0) /* product */
656 if (s
[l
] == '/') /* product version */
659 l
= vlc_http_token_length(s
);
663 l
= vlc_http_comment_length(s
);
673 l
= strspn(s
, "\t "); /* RWS */
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
))
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
,
717 if (gmtime_r(t
, &tm
) == NULL
)
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
],
724 tm
.tm_hour
, tm
.tm_min
, tm
.tm_sec
);
727 int vlc_http_msg_add_atime(struct vlc_http_msg
*m
)
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 */
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
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)
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 */
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 */
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
);
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");
790 unsigned long delay
= strtoul(str
, &end
, 10);
791 if (end
!= str
&& *end
== '\0')
794 time_t t
= vlc_http_mktime(str
);
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 */)
814 const char *str
= vlc_http_msg_get_header(m
, "Transfer-Encoding");
815 if (str
!= NULL
) /* Transfer-Encoding preempts Content-Length */
818 str
= vlc_http_msg_get_header(m
, "Content-Length");
822 return 0; /* Requests are void by default */
823 return -1; /* Response of unknown size (e.g. chunked) */
828 if (sscanf(str
, "%ju", &length
) == 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
;
851 if (m
->scheme
== NULL
|| m
->authority
== NULL
|| m
->path
== NULL
)
857 if (!strcasecmp(m
->scheme
, "https"))
859 else if (!strcasecmp(m
->scheme
, "http"))
864 if (m
->authority
[0] == '[')
865 host
= strndup(m
->authority
+ 1, strcspn(m
->authority
+ 1, "]"));
867 host
= strndup(m
->authority
, strcspn(m
->authority
, ":"));
868 if (unlikely(host
== NULL
))
871 cookies
= vlc_http_cookies_fetch(jar
, secure
, host
, m
->path
);
876 val
= vlc_http_msg_add_header(m
, "Cookie", "%s", cookies
);