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 *****************************************************************************/
33 #include <vlc_common.h>
34 #include <vlc_strings.h>
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
))
60 h
[0][0] = strdup(name
);
61 if (unlikely(h
[0][0] == NULL
))
65 if (unlikely(vasprintf(&value
, fmt
, ap
) < 0))
71 /* IETF RFC7230 §3.2.4 */
72 for (char *p
= value
; *p
; p
++)
73 if (*p
== '\r' || *p
== '\n')
81 int vlc_http_msg_add_header(struct vlc_http_msg
*m
, const char *name
,
88 ret
= vlc_http_msg_vadd_header(m
, name
, fmt
, ap
);
93 /* TODO: merge identically named headers (not really needed yet) */
95 const char *vlc_http_msg_get_header(const struct vlc_http_msg
*m
,
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];
106 int vlc_http_msg_get_status(const struct vlc_http_msg
*m
)
111 const char *vlc_http_msg_get_method(const struct vlc_http_msg
*m
)
116 const char *vlc_http_msg_get_scheme(const struct vlc_http_msg
*m
)
121 const char *vlc_http_msg_get_authority(const struct vlc_http_msg
*m
)
126 const char *vlc_http_msg_get_path(const struct vlc_http_msg
*m
)
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]);
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
))
158 assert(method
!= NULL
);
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
;
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
);
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
))
185 assert(status
< 1000);
197 void vlc_http_msg_attach(struct vlc_http_msg
*m
, struct vlc_http_stream
*s
)
199 assert(m
->payload
== NULL
);
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
);
208 vlc_http_msg_destroy(m
);
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
);
219 block_t
*vlc_http_msg_read(struct vlc_http_msg
*m
)
221 if (m
->payload
== 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
)
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
);
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
))
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
);
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");
268 struct vlc_http_msg
*vlc_http_msg_headers(const char *msg
)
270 struct vlc_http_msg
*m
;
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
))
281 return NULL
; /* TODO: request support */
283 msg
= strstr(msg
, "\r\n");
287 while (strcmp(msg
+ 2, "\r\n"))
289 const char *eol
= msg
;
293 eol
= strstr(eol
+ 2, "\r\n");
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
)
305 char *name
= strndup(msg
, colon
- msg
);
306 if (unlikely(name
== NULL
))
310 colon
+= strspn(colon
, " \t");
312 if (unlikely(vlc_http_msg_add_header(m
, name
, "%.*s",
313 (int)(eol
- colon
), colon
)))
324 vlc_http_msg_destroy(m
);
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
))
342 struct vlc_h2_frame
*f
;
348 assert(m
->status
< 1000);
349 sprintf(status
, "%hd", m
->status
);
350 headers
[i
][0] = ":status";
351 headers
[i
][1] = status
;
354 if (m
->method
!= NULL
)
356 headers
[i
][0] = ":method";
357 headers
[i
][1] = m
->method
;
360 if (m
->scheme
!= NULL
)
362 headers
[i
][0] = ":scheme";
363 headers
[i
][1] = m
->scheme
;
366 if (m
->authority
!= NULL
)
368 headers
[i
][0] = ":authority";
369 headers
[i
][1] = m
->authority
;
374 headers
[i
][0] = ":path";
375 headers
[i
][1] = m
->path
;
380 memcpy(headers
+ i
, m
->headers
, m
->count
* sizeof (*headers
));
384 f
= vlc_h2_frame_headers(stream_id
, VLC_H2_DEFAULT_MAX_FRAME
, eos
,
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
))
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
))
409 m
->headers
= malloc(n
* sizeof (char *[2]));
410 if (unlikely(m
->headers
== NULL
))
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
);
424 if (special
[idx
] != NULL
)
425 goto error
; /* Duplicate special header! */
427 special_caption
[idx
] = name
;
428 special
[idx
] = value
;
432 m
->headers
[m
->count
][0] = name
;
433 m
->headers
[m
->count
][1] = value
;
437 if (special
[0] != NULL
)
438 { /* HTTP response */
440 unsigned long status
= strtoul(special
[0], &end
, 10);
442 if (status
> 999 || *end
!= '\0')
443 goto error
; /* Not a three decimal digits status! */
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
]);
464 for (unsigned i
= 0; i
< n
; i
++)
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)
489 static size_t vlc_http_token_length(const char *str
)
493 while (vlc_http_istoken(str
[i
]))
498 static size_t vlc_http_comment_length(const char *str
)
499 { /* IETF RFC7230 §3.2.6 */
505 for (size_t nested
= 1; nested
> 0; i
++)
507 unsigned char c
= str
[i
];
512 if (c
== '(') /* Nested comment */
515 if (c
== '\\') /* Quoted pair */
522 if (!vlc_http_isctext(c
))
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
))
535 size_t l
= vlc_http_token_length(s
);
536 if (l
!= 0) /* product */
538 if (s
[l
] == '/') /* product version */
541 l
= vlc_http_token_length(s
);
545 l
= vlc_http_comment_length(s
);
555 l
= strspn(s
, "\t "); /* RWS */
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
))
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
],
602 tm
->tm_hour
, tm
->tm_min
, tm
->tm_sec
);
605 int vlc_http_msg_add_atime(struct vlc_http_msg
*m
)
611 if (gmtime_r(&now
, &tm
) == NULL
)
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 */
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
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)
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 */
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 */
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");
664 unsigned long delay
= strtoul(str
, &end
, 10);
665 if (end
!= str
&& *end
== '\0')
668 time_t t
= vlc_http_mktime(str
);
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) */)
687 const char *str
= vlc_http_msg_get_header(m
, "Content-Length");
691 return 0; /* Requests are void by default */
692 return -1; /* Response of unknown size (e.g. chunked) */
697 if (sscanf(str
, "%ju", &length
) == 1)