You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
903 lines
22 KiB
903 lines
22 KiB
/*****************************************************************************
|
|
* message.c: HTTP request/response
|
|
*****************************************************************************
|
|
* Copyright (C) 2015 Rémi Denis-Courmont
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation; either version 2.1 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
|
|
*****************************************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#include <vlc_common.h>
|
|
#include <vlc_http.h>
|
|
#include "message.h"
|
|
#include "h2frame.h"
|
|
|
|
struct vlc_http_msg
|
|
{
|
|
short status;
|
|
char *method;
|
|
char *scheme;
|
|
char *authority;
|
|
char *path;
|
|
char *(*headers)[2];
|
|
unsigned count;
|
|
struct vlc_http_stream *payload;
|
|
};
|
|
|
|
static bool vlc_http_is_token(const char *);
|
|
|
|
static ssize_t vlc_http_msg_find_header(const struct vlc_http_msg *m,
|
|
const char *name)
|
|
{
|
|
for (unsigned i = 0; i < m->count; i++)
|
|
if (!strcasecmp(m->headers[i][0], name))
|
|
return i;
|
|
return -1;
|
|
}
|
|
|
|
static int vlc_http_msg_vadd_header(struct vlc_http_msg *m, const char *name,
|
|
const char *fmt, va_list ap)
|
|
{
|
|
if (!vlc_http_is_token(name))
|
|
{ /* Not a valid field name, i.e. not an HTTP token */
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
char *value;
|
|
int len = vasprintf(&value, fmt, ap);
|
|
if (unlikely(len < 0))
|
|
return -1;
|
|
|
|
/* IETF RFC7230 §3.2.4 */
|
|
for (char *p = value; *p; p++)
|
|
if (*p == '\r' || *p == '\n')
|
|
*p = ' ';
|
|
|
|
/* Discard leading OWS */
|
|
size_t crop = strspn(value, "\t ");
|
|
if (crop > 0)
|
|
{
|
|
assert((unsigned)len >= crop);
|
|
memmove(value, value + crop, len - crop + 1);
|
|
len -= crop;
|
|
}
|
|
|
|
/* Discard trailing OWS */
|
|
while (len > 0 && (value[len - 1] == '\t' || value[len - 1] == ' '))
|
|
value[--len] = '\0';
|
|
|
|
/* Fold identically named header field values. This is unfortunately not
|
|
* possible for Set-Cookie, while Cookie requires a special separator. */
|
|
ssize_t idx = vlc_http_msg_find_header(m, name);
|
|
if (idx >= 0 && strcasecmp(name, "Set-Cookie"))
|
|
{
|
|
char *merged;
|
|
char sep = strcasecmp(name, "Cookie") ? ',' : ';';
|
|
|
|
int val = asprintf(&merged, "%s%c %s", m->headers[idx][1], sep, value);
|
|
|
|
free(value);
|
|
|
|
if (unlikely(val == -1))
|
|
return -1;
|
|
|
|
free(m->headers[idx][1]);
|
|
m->headers[idx][1] = merged;
|
|
return 0;
|
|
}
|
|
|
|
char *(*h)[2] = realloc(m->headers, sizeof (char *[2]) * (m->count + 1));
|
|
if (unlikely(h == NULL))
|
|
{
|
|
free(value);
|
|
return -1;
|
|
}
|
|
|
|
m->headers = h;
|
|
h += m->count;
|
|
|
|
h[0][0] = strdup(name);
|
|
if (unlikely(h[0][0] == NULL))
|
|
{
|
|
free(value);
|
|
return -1;
|
|
}
|
|
|
|
h[0][1] = value;
|
|
m->count++;
|
|
return 0;
|
|
}
|
|
|
|
int vlc_http_msg_add_header(struct vlc_http_msg *m, const char *name,
|
|
const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int ret;
|
|
|
|
va_start(ap, fmt);
|
|
ret = vlc_http_msg_vadd_header(m, name, fmt, ap);
|
|
va_end(ap);
|
|
return ret;
|
|
}
|
|
|
|
const char *vlc_http_msg_get_header(const struct vlc_http_msg *m,
|
|
const char *name)
|
|
{
|
|
ssize_t idx = vlc_http_msg_find_header(m, name);
|
|
if (idx < 0)
|
|
{
|
|
errno = ENOENT;
|
|
return NULL;
|
|
}
|
|
return m->headers[idx][1];
|
|
}
|
|
|
|
int vlc_http_msg_get_status(const struct vlc_http_msg *m)
|
|
{
|
|
return m->status;
|
|
}
|
|
|
|
const char *vlc_http_msg_get_method(const struct vlc_http_msg *m)
|
|
{
|
|
return m->method;
|
|
}
|
|
|
|
const char *vlc_http_msg_get_scheme(const struct vlc_http_msg *m)
|
|
{
|
|
return m->scheme;
|
|
}
|
|
|
|
const char *vlc_http_msg_get_authority(const struct vlc_http_msg *m)
|
|
{
|
|
return m->authority;
|
|
}
|
|
|
|
const char *vlc_http_msg_get_path(const struct vlc_http_msg *m)
|
|
{
|
|
return m->path;
|
|
}
|
|
|
|
void vlc_http_msg_destroy(struct vlc_http_msg *m)
|
|
{
|
|
if (m->payload != NULL)
|
|
vlc_http_stream_close(m->payload, false);
|
|
|
|
for (unsigned i = 0; i < m->count; i++)
|
|
{
|
|
free(m->headers[i][0]);
|
|
free(m->headers[i][1]);
|
|
}
|
|
|
|
free(m->headers);
|
|
free(m->path);
|
|
free(m->authority);
|
|
free(m->scheme);
|
|
free(m->method);
|
|
free(m);
|
|
}
|
|
|
|
struct vlc_http_msg *
|
|
vlc_http_req_create(const char *method, const char *scheme,
|
|
const char *authority, const char *path)
|
|
{
|
|
struct vlc_http_msg *m = malloc(sizeof (*m));
|
|
if (unlikely(m == NULL))
|
|
return NULL;
|
|
|
|
assert(method != NULL);
|
|
m->status = -1;
|
|
m->method = strdup(method);
|
|
m->scheme = (scheme != NULL) ? strdup(scheme) : NULL;
|
|
m->authority = (authority != NULL) ? strdup(authority) : NULL;
|
|
m->path = (path != NULL) ? strdup(path) : NULL;
|
|
m->count = 0;
|
|
m->headers = NULL;
|
|
m->payload = NULL;
|
|
|
|
if (unlikely(m->method == NULL
|
|
|| (scheme != NULL && m->scheme == NULL)
|
|
|| (authority != NULL && m->authority == NULL)
|
|
|| (path != NULL && m->path == NULL)))
|
|
{
|
|
vlc_http_msg_destroy(m);
|
|
m = NULL;
|
|
}
|
|
return m;
|
|
}
|
|
|
|
struct vlc_http_msg *vlc_http_resp_create(unsigned status)
|
|
{
|
|
struct vlc_http_msg *m = malloc(sizeof (*m));
|
|
if (unlikely(m == NULL))
|
|
return NULL;
|
|
|
|
assert(status < 1000);
|
|
m->status = status;
|
|
m->method = NULL;
|
|
m->scheme = NULL;
|
|
m->authority = NULL;
|
|
m->path = NULL;
|
|
m->count = 0;
|
|
m->headers = NULL;
|
|
m->payload = NULL;
|
|
return m;
|
|
}
|
|
|
|
void vlc_http_msg_attach(struct vlc_http_msg *m, struct vlc_http_stream *s)
|
|
{
|
|
assert(m->payload == NULL);
|
|
m->payload = s;
|
|
}
|
|
|
|
struct vlc_http_msg *vlc_http_msg_iterate(struct vlc_http_msg *m)
|
|
{
|
|
struct vlc_http_msg *next = vlc_http_stream_read_headers(m->payload);
|
|
|
|
m->payload = NULL;
|
|
vlc_http_msg_destroy(m);
|
|
return next;
|
|
}
|
|
|
|
struct vlc_http_msg *vlc_http_msg_get_initial(struct vlc_http_stream *s)
|
|
{
|
|
struct vlc_http_msg *m = vlc_http_stream_read_headers(s);
|
|
if (m == NULL)
|
|
vlc_http_stream_close(s, false);
|
|
return m;
|
|
}
|
|
|
|
struct vlc_http_msg *vlc_http_msg_get_final(struct vlc_http_msg *m)
|
|
{
|
|
while (m != NULL && (vlc_http_msg_get_status(m) / 100) == 1)
|
|
m = vlc_http_msg_iterate(m);
|
|
return m;
|
|
}
|
|
|
|
block_t *vlc_http_msg_read(struct vlc_http_msg *m)
|
|
{
|
|
if (m->payload == NULL)
|
|
return NULL;
|
|
|
|
return vlc_http_stream_read(m->payload);
|
|
}
|
|
|
|
/* Serialization and deserialization */
|
|
|
|
char *vlc_http_msg_format(const struct vlc_http_msg *m, size_t *restrict lenp,
|
|
bool proxied)
|
|
{
|
|
size_t len;
|
|
|
|
if (m->status < 0)
|
|
{
|
|
len = sizeof (" HTTP/1.1\r\nHost: \r\n\r\n");
|
|
len += strlen(m->method);
|
|
len += strlen(m->path ? m->path : m->authority);
|
|
len += strlen(m->authority);
|
|
|
|
if (proxied)
|
|
{
|
|
assert(m->scheme != NULL && m->path != NULL);
|
|
len += strlen(m->scheme) + 3 + strlen(m->authority);
|
|
}
|
|
}
|
|
else
|
|
len = sizeof ("HTTP/1.1 123 .\r\n\r\n");
|
|
|
|
for (unsigned i = 0; i < m->count; i++)
|
|
len += 4 + strlen(m->headers[i][0]) + strlen(m->headers[i][1]);
|
|
|
|
char *buf = malloc(len + 1);
|
|
if (unlikely(buf == NULL))
|
|
return NULL;
|
|
|
|
len = 0;
|
|
|
|
if (m->status < 0)
|
|
{
|
|
len += sprintf(buf, "%s ", m->method);
|
|
if (proxied)
|
|
len += sprintf(buf + len, "%s://%s", m->scheme, m->authority);
|
|
len += sprintf(buf + len, "%s HTTP/1.1\r\nHost: %s\r\n",
|
|
m->path ? m->path : m->authority, m->authority);
|
|
}
|
|
else
|
|
len += sprintf(buf, "HTTP/1.1 %03hd .\r\n", m->status);
|
|
|
|
for (unsigned i = 0; i < m->count; i++)
|
|
len += sprintf(buf + len, "%s: %s\r\n",
|
|
m->headers[i][0], m->headers[i][1]);
|
|
|
|
len += sprintf(buf + len, "\r\n");
|
|
if (lenp != NULL)
|
|
*lenp = len;
|
|
return buf;
|
|
}
|
|
|
|
struct vlc_http_msg *vlc_http_msg_headers(const char *msg)
|
|
{
|
|
struct vlc_http_msg *m;
|
|
unsigned short code;
|
|
|
|
if (sscanf(msg, "HTTP/1.%*1u %3hu %*s", &code) == 1)
|
|
{
|
|
m = vlc_http_resp_create(code);
|
|
if (unlikely(m == NULL))
|
|
return NULL;
|
|
}
|
|
else
|
|
return NULL; /* TODO: request support */
|
|
|
|
msg = strstr(msg, "\r\n");
|
|
if (msg == NULL)
|
|
goto error;
|
|
|
|
while (strcmp(msg + 2, "\r\n"))
|
|
{
|
|
const char *eol = msg;
|
|
|
|
do
|
|
{
|
|
eol = strstr(eol + 2, "\r\n");
|
|
if (eol == NULL)
|
|
goto error;
|
|
} /* Deal with legacy obs-fold (i.e. multi-line header) */
|
|
while (eol[2] == ' ' || eol[2] == '\t');
|
|
|
|
msg += 2; /* skip CRLF */
|
|
|
|
const char *colon = memchr(msg, ':', eol - msg);
|
|
if (colon == NULL || colon == msg)
|
|
goto error;
|
|
|
|
char *name = strndup(msg, colon - msg);
|
|
if (unlikely(name == NULL))
|
|
goto error;
|
|
|
|
colon++;
|
|
colon += strspn(colon, " \t");
|
|
|
|
if (unlikely(vlc_http_msg_add_header(m, name, "%.*s",
|
|
(int)(eol - colon), colon)))
|
|
{
|
|
free(name);
|
|
goto error;
|
|
}
|
|
free(name);
|
|
msg = eol;
|
|
}
|
|
|
|
return m;
|
|
error:
|
|
vlc_http_msg_destroy(m);
|
|
return NULL;
|
|
}
|
|
|
|
struct vlc_h2_frame *vlc_http_msg_h2_frame(const struct vlc_http_msg *m,
|
|
uint_fast32_t stream_id, bool eos)
|
|
{
|
|
for (unsigned j = 0; j < m->count; j++)
|
|
{ /* No HTTP 1 specific headers */
|
|
assert(strcasecmp(m->headers[j][0], "Connection"));
|
|
assert(strcasecmp(m->headers[j][0], "Upgrade"));
|
|
assert(strcasecmp(m->headers[j][0], "HTTP2-Settings"));
|
|
}
|
|
|
|
const char *(*headers)[2] = malloc((m->count + 5) * sizeof (char *[2]));
|
|
if (unlikely(headers == NULL))
|
|
return NULL;
|
|
|
|
struct vlc_h2_frame *f;
|
|
unsigned i = 0;
|
|
char status[4];
|
|
|
|
if (m->status >= 0)
|
|
{
|
|
assert(m->status < 1000);
|
|
sprintf(status, "%hd", m->status);
|
|
headers[i][0] = ":status";
|
|
headers[i][1] = status;
|
|
i++;
|
|
}
|
|
if (m->method != NULL)
|
|
{
|
|
headers[i][0] = ":method";
|
|
headers[i][1] = m->method;
|
|
i++;
|
|
}
|
|
if (m->scheme != NULL)
|
|
{
|
|
headers[i][0] = ":scheme";
|
|
headers[i][1] = m->scheme;
|
|
i++;
|
|
}
|
|
if (m->authority != NULL)
|
|
{
|
|
headers[i][0] = ":authority";
|
|
headers[i][1] = m->authority;
|
|
i++;
|
|
}
|
|
if (m->path != NULL)
|
|
{
|
|
headers[i][0] = ":path";
|
|
headers[i][1] = m->path;
|
|
i++;
|
|
}
|
|
if (m->count > 0)
|
|
{
|
|
memcpy(headers + i, m->headers, m->count * sizeof (*headers));
|
|
i += m->count;
|
|
}
|
|
|
|
f = vlc_h2_frame_headers(stream_id, VLC_H2_DEFAULT_MAX_FRAME, eos,
|
|
i, headers);
|
|
free(headers);
|
|
return f;
|
|
}
|
|
|
|
struct vlc_http_msg *vlc_http_msg_h2_headers(unsigned n,
|
|
const char *const hdrs[][2])
|
|
{
|
|
struct vlc_http_msg *m = vlc_http_resp_create(0);
|
|
if (unlikely(m == NULL))
|
|
return NULL;
|
|
|
|
for (unsigned i = 0; i < n; i++)
|
|
{
|
|
const char *name = hdrs[i][0];
|
|
const char *value = hdrs[i][1];
|
|
|
|
/* NOTE: HPACK always returns lower-case names, so strcmp() is fine. */
|
|
if (!strcmp(name, ":status"))
|
|
{
|
|
char *end;
|
|
unsigned long status = strtoul(value, &end, 10);
|
|
|
|
if (m->status != 0 || status > 999 || *end != '\0')
|
|
goto error; /* Not a three decimal digits status! */
|
|
|
|
m->status = status;
|
|
continue;
|
|
}
|
|
|
|
if (!strcmp(name, ":method"))
|
|
{
|
|
if (m->method != NULL)
|
|
goto error;
|
|
|
|
m->method = strdup(value);
|
|
if (unlikely(m->method == NULL))
|
|
goto error;
|
|
|
|
m->status = -1; /* this is a request */
|
|
continue;
|
|
}
|
|
|
|
if (!strcmp(name, ":scheme"))
|
|
{
|
|
if (m->scheme != NULL)
|
|
goto error;
|
|
|
|
m->scheme = strdup(value);
|
|
if (unlikely(m->scheme == NULL))
|
|
goto error;
|
|
continue;
|
|
}
|
|
|
|
if (!strcmp(name, ":authority"))
|
|
{
|
|
if (m->authority != NULL)
|
|
goto error;
|
|
|
|
m->authority = strdup(value);
|
|
if (unlikely(m->authority == NULL))
|
|
goto error;
|
|
continue;
|
|
}
|
|
|
|
if (!strcmp(name, ":path"))
|
|
{
|
|
if (m->path != NULL)
|
|
goto error;
|
|
|
|
m->path = strdup(value);
|
|
if (unlikely(m->path == NULL))
|
|
goto error;
|
|
continue;
|
|
}
|
|
|
|
if (vlc_http_msg_add_header(m, name, "%s", value))
|
|
goto error;
|
|
}
|
|
|
|
if ((m->status < 0) == (m->method == NULL))
|
|
{ /* Must be either a request or response. Not both, not neither. */
|
|
error:
|
|
vlc_http_msg_destroy(m);
|
|
m = NULL;
|
|
}
|
|
return m;
|
|
}
|
|
|
|
/* Header helpers */
|
|
|
|
static int vlc_http_istoken(int c)
|
|
{ /* IETF RFC7230 §3.2.6 */
|
|
return (c >= '0' && c <= '9')
|
|
|| (c >= 'a' && c <= 'z')
|
|
|| (c >= 'A' && c <= 'Z')
|
|
|| (c && strchr("!#$%&'*+-.^_`|~", c) != NULL);
|
|
}
|
|
|
|
static int vlc_http_isctext(int c)
|
|
{ /* IETF RFC7230 §3.2.6 */
|
|
return (c == '\t') || (c == ' ') || (c >= 0x21 && c <= 0x27)
|
|
|| (c >= 0x2A && c <= 0x5B) || (c >= 0x5D && c <= 0x7E)
|
|
|| (c >= 0x80);
|
|
}
|
|
|
|
static size_t vlc_http_token_length(const char *str)
|
|
{
|
|
size_t i = 0;
|
|
|
|
while (vlc_http_istoken(str[i]))
|
|
i++;
|
|
return i;
|
|
}
|
|
|
|
static size_t vlc_http_quoted_length(const char *str)
|
|
{
|
|
size_t i = 0;
|
|
unsigned char c;
|
|
|
|
if (str[i++] != '"')
|
|
return 0;
|
|
|
|
do
|
|
{
|
|
c = str[i++];
|
|
|
|
if (c == '\0')
|
|
return 0;
|
|
|
|
if (c == '\\') /* Quoted pair */
|
|
{
|
|
unsigned char q = str[i++];
|
|
if (q < 32 && q != '\t')
|
|
return 0;
|
|
}
|
|
}
|
|
while (c != '"');
|
|
|
|
return i;
|
|
}
|
|
|
|
static bool vlc_http_is_token(const char *str)
|
|
{
|
|
size_t len = vlc_http_token_length(str);
|
|
return len > 0 && str[len] == '\0';
|
|
}
|
|
|
|
const char *vlc_http_next_token(const char *value)
|
|
{ /* We handle either token or token = token / quoted-string */
|
|
value += strcspn(value, ",\"");
|
|
if (!*value)
|
|
return NULL;
|
|
|
|
value += vlc_http_quoted_length(value);
|
|
return value + strspn(value, "\t ,");
|
|
}
|
|
|
|
const char *vlc_http_msg_get_token(const struct vlc_http_msg *msg,
|
|
const char *field, const char *token)
|
|
{
|
|
const char *value = vlc_http_msg_get_header(msg, field);
|
|
const size_t length = strlen(token);
|
|
|
|
while (value != NULL)
|
|
{
|
|
if (vlc_http_token_length(value) == length
|
|
&& !strncasecmp(token, value, length))
|
|
return value;
|
|
|
|
value = vlc_http_next_token(value);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static size_t vlc_http_comment_length(const char *str)
|
|
{ /* IETF RFC7230 §3.2.6 */
|
|
if (*str != '(')
|
|
return 0;
|
|
|
|
size_t i = 1;
|
|
|
|
for (size_t nested = 1; nested > 0; i++)
|
|
{
|
|
unsigned char c = str[i];
|
|
|
|
if (c == ')')
|
|
nested--;
|
|
else
|
|
if (c == '(') /* Nested comment */
|
|
nested++;
|
|
else
|
|
if (c == '\\') /* Quoted pair */
|
|
{
|
|
i++;
|
|
if (str[i] < 32)
|
|
return 0;
|
|
}
|
|
else
|
|
if (!vlc_http_isctext(c))
|
|
return 0;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
static bool vlc_http_is_agent(const char *s)
|
|
{ /* IETF RFC7231 §5.5.3 and §7.4.2 */
|
|
if (!vlc_http_istoken(*s))
|
|
return false;
|
|
|
|
for (;;)
|
|
{
|
|
size_t l = vlc_http_token_length(s);
|
|
if (l != 0) /* product */
|
|
{
|
|
if (s[l] == '/') /* product version */
|
|
{
|
|
s += l + 1;
|
|
l = vlc_http_token_length(s);
|
|
}
|
|
}
|
|
else
|
|
l = vlc_http_comment_length(s);
|
|
|
|
if (l == 0)
|
|
break;
|
|
|
|
s += l;
|
|
|
|
if (*s == '\0')
|
|
return true;
|
|
|
|
l = strspn(s, "\t "); /* RWS */
|
|
|
|
if (l == 0)
|
|
break;
|
|
|
|
s += l;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int vlc_http_msg_add_agent(struct vlc_http_msg *m, const char *str)
|
|
{
|
|
const char *hname = (m->status < 0) ? "User-Agent" : "Server";
|
|
|
|
if (!vlc_http_is_agent(str))
|
|
{
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
return vlc_http_msg_add_header(m, hname, "%s", str);
|
|
}
|
|
|
|
const char *vlc_http_msg_get_agent(const struct vlc_http_msg *m)
|
|
{
|
|
const char *hname = (m->status < 0) ? "User-Agent" : "Server";
|
|
const char *str = vlc_http_msg_get_header(m, hname);
|
|
|
|
return (str != NULL && vlc_http_is_agent(str)) ? str : NULL;
|
|
}
|
|
|
|
static const char vlc_http_days[7][4] = {
|
|
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
|
|
};
|
|
static const char vlc_http_months[12][4] = {
|
|
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
|
};
|
|
|
|
int vlc_http_msg_add_time(struct vlc_http_msg *m, const char *hname,
|
|
const time_t *t)
|
|
{
|
|
struct tm tm;
|
|
|
|
if (gmtime_r(t, &tm) == NULL)
|
|
return -1;
|
|
return vlc_http_msg_add_header(m, hname,
|
|
"%s, %02d %s %04d %02d:%02d:%02d GMT",
|
|
vlc_http_days[tm.tm_wday], tm.tm_mday,
|
|
vlc_http_months[tm.tm_mon],
|
|
1900 + tm.tm_year,
|
|
tm.tm_hour, tm.tm_min, tm.tm_sec);
|
|
}
|
|
|
|
int vlc_http_msg_add_atime(struct vlc_http_msg *m)
|
|
{
|
|
time_t now;
|
|
|
|
time(&now);
|
|
return vlc_http_msg_add_time(m, "Date", &now);
|
|
}
|
|
|
|
static time_t vlc_http_mktime(const char *str)
|
|
{ /* IETF RFC7231 §7.1.1.1 */
|
|
struct tm tm;
|
|
char mon[4];
|
|
|
|
/* Internet Message Format date */
|
|
if (sscanf(str, "%*c%*c%*c, %2d %3s %4d %2d:%2d:%2d", &tm.tm_mday, mon,
|
|
&tm.tm_year, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6
|
|
/* ANSI C format */
|
|
|| sscanf(str, "%*3s %3s %2d %2d:%2d:%2d %4d", mon, &tm.tm_mday,
|
|
&tm.tm_hour, &tm.tm_min, &tm.tm_sec, &tm.tm_year) == 6)
|
|
tm.tm_year -= 1900;
|
|
/* RFC850 date */
|
|
else if (sscanf(str, "%*[^,], %2d-%3s-%2d %2d:%2d:%2d", &tm.tm_mday, mon,
|
|
&tm.tm_year, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6)
|
|
{
|
|
if (tm.tm_year <= 75)
|
|
tm.tm_year += 100; /* Y2K compat, sort of */
|
|
}
|
|
else /* Unknown format */
|
|
goto error;
|
|
|
|
for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++)
|
|
if (!strcmp(mon, vlc_http_months[tm.tm_mon])) /* found month */
|
|
return timegm(&tm);
|
|
error:
|
|
errno = EINVAL;
|
|
return -1; /* invalid month */
|
|
}
|
|
|
|
time_t vlc_http_msg_get_time(const struct vlc_http_msg *m, const char *name)
|
|
{
|
|
const char *str = vlc_http_msg_get_header(m, name);
|
|
if (str == NULL)
|
|
return -1;
|
|
return vlc_http_mktime(str);
|
|
}
|
|
|
|
time_t vlc_http_msg_get_atime(const struct vlc_http_msg *m)
|
|
{
|
|
return vlc_http_msg_get_time(m, "Date");
|
|
}
|
|
|
|
time_t vlc_http_msg_get_mtime(const struct vlc_http_msg *m)
|
|
{
|
|
return vlc_http_msg_get_time(m, "Last-Modified");
|
|
}
|
|
|
|
unsigned vlc_http_msg_get_retry_after(const struct vlc_http_msg *m)
|
|
{
|
|
const char *str = vlc_http_msg_get_header(m, "Retry-After");
|
|
if (str == NULL)
|
|
return 0;
|
|
|
|
char *end;
|
|
unsigned long delay = strtoul(str, &end, 10);
|
|
if (end != str && *end == '\0')
|
|
return delay;
|
|
|
|
time_t t = vlc_http_mktime(str);
|
|
if (t != (time_t)-1)
|
|
{
|
|
time_t now;
|
|
|
|
time(&now);
|
|
if (t >= now)
|
|
return t - now;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uintmax_t vlc_http_msg_get_size(const struct vlc_http_msg *m)
|
|
{ /* IETF RFC7230 §3.3.3 */
|
|
if ((m->status / 100) == 1 /* Informational 1xx (implicitly void) */
|
|
|| m->status == 204 /* No Content (implicitly void) */
|
|
|| m->status == 205 /* Reset Content (must be explicitly void) */
|
|
|| m->status == 304 /* Not Modified */)
|
|
return 0;
|
|
|
|
const char *str = vlc_http_msg_get_header(m, "Transfer-Encoding");
|
|
if (str != NULL) /* Transfer-Encoding preempts Content-Length */
|
|
return -1;
|
|
|
|
str = vlc_http_msg_get_header(m, "Content-Length");
|
|
if (str == NULL)
|
|
{
|
|
if (m->status < 0)
|
|
return 0; /* Requests are void by default */
|
|
return -1; /* Response of unknown size (e.g. chunked) */
|
|
}
|
|
|
|
uintmax_t length;
|
|
|
|
if (sscanf(str, "%ju", &length) == 1)
|
|
return length;
|
|
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
void vlc_http_msg_get_cookies(const struct vlc_http_msg *m,
|
|
vlc_http_cookie_jar_t *jar, bool secure,
|
|
const char *host, const char *path)
|
|
{
|
|
if (jar == NULL)
|
|
return;
|
|
|
|
for (unsigned i = 0; i < m->count; i++)
|
|
if (!strcasecmp(m->headers[i][0], "Set-Cookie"))
|
|
vlc_http_cookies_store(jar, m->headers[i][1], secure, host, path);
|
|
}
|
|
|
|
int vlc_http_msg_add_cookies(struct vlc_http_msg *m,
|
|
vlc_http_cookie_jar_t *jar)
|
|
{
|
|
char *host, *cookies;
|
|
int val = 0;
|
|
bool secure;
|
|
|
|
if (m->scheme == NULL || m->authority == NULL || m->path == NULL)
|
|
{
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
if (!strcasecmp(m->scheme, "https"))
|
|
secure = true;
|
|
else if (!strcasecmp(m->scheme, "http"))
|
|
secure = false;
|
|
else
|
|
return 0;
|
|
|
|
if (jar == NULL)
|
|
return 0;
|
|
|
|
if (m->authority[0] == '[')
|
|
host = strndup(m->authority + 1, strcspn(m->authority + 1, "]"));
|
|
else
|
|
host = strndup(m->authority, strcspn(m->authority, ":"));
|
|
if (unlikely(host == NULL))
|
|
return -1;
|
|
|
|
cookies = vlc_http_cookies_fetch(jar, secure, host, m->path);
|
|
free(host);
|
|
|
|
if (cookies != NULL)
|
|
{
|
|
val = vlc_http_msg_add_header(m, "Cookie", "%s", cookies);
|
|
free(cookies);
|
|
}
|
|
return val;
|
|
}
|
|
|