Commit 3f0a94de authored by Rémi Denis-Courmont's avatar Rémi Denis-Courmont

https: factor out non-specific file helper code

parent 381f7a66
...@@ -5,6 +5,7 @@ access_LTLIBRARIES += libhttps_plugin.la ...@@ -5,6 +5,7 @@ access_LTLIBRARIES += libhttps_plugin.la
libvlc_http_la_SOURCES = \ libvlc_http_la_SOURCES = \
access/http/transport.c access/http/transport.h \ access/http/transport.c access/http/transport.h \
access/http/message.c access/http/message.h \ access/http/message.c access/http/message.h \
access/http/resource.c access/http/resource.h \
access/http/file.c access/http/file.h \ access/http/file.c access/http/file.h \
access/http/hpack.c access/http/hpack.h access/http/hpackenc.c \ access/http/hpack.c access/http/hpack.h access/http/hpackenc.c \
access/http/h2frame.c access/http/h2frame.h \ access/http/h2frame.c access/http/h2frame.h \
...@@ -40,6 +41,7 @@ http_msg_test_SOURCES = access/http/message_test.c \ ...@@ -40,6 +41,7 @@ http_msg_test_SOURCES = access/http/message_test.c \
access/http/message.c access/http/message.h access/http/message.c access/http/message.h
http_file_test_SOURCES = access/http/file_test.c \ http_file_test_SOURCES = access/http/file_test.c \
access/http/message.c access/http/message.h \ access/http/message.c access/http/message.h \
access/http/resource.c access/http/resource.h \
access/http/file.c access/http/file.h access/http/file.c access/http/file.h
check_PROGRAMS += hpack_test hpackenc_test \ check_PROGRAMS += hpack_test hpackenc_test \
h2frame_test h2output_test h2conn_test h1conn_test h1chunked_test \ h2frame_test h2output_test h2conn_test h1conn_test h1chunked_test \
......
...@@ -32,65 +32,29 @@ ...@@ -32,65 +32,29 @@
#include <vlc_common.h> #include <vlc_common.h>
#include <vlc_block.h> #include <vlc_block.h>
#include <vlc_url.h>
#include <vlc_strings.h> #include <vlc_strings.h>
#include "message.h" #include "message.h"
#include "connmgr.h" #include "resource.h"
#include "file.h" #include "file.h"
#pragma GCC visibility push(default) #pragma GCC visibility push(default)
struct vlc_http_file struct vlc_http_file
{ {
struct vlc_http_mgr *manager; struct vlc_http_resource resource;
struct vlc_http_msg *resp; struct vlc_http_msg *resp;
char *host;
unsigned port;
bool secure;
char *authority;
char *path;
char *agent;
char *referrer;
uintmax_t offset; uintmax_t offset;
}; };
static struct vlc_http_msg *vlc_http_file_req(const struct vlc_http_file *file, static int vlc_http_file_req(struct vlc_http_msg *req,
uintmax_t offset) const struct vlc_http_resource *res, void *opaque)
{ {
struct vlc_http_msg *req; struct vlc_http_file *file = (struct vlc_http_file *)res;
const char *str; const uintmax_t *offset = opaque;
req = vlc_http_req_create("GET", file->secure ? "https" : "http",
file->authority, file->path);
if (unlikely(req == NULL))
return NULL;
/* Content negotiation */
vlc_http_msg_add_header(req, "Accept", "*/*");
/* NOTE (TODO): Accept-Encoding should be used to allow compression.
* Unforunately, some servers do not send byte ranges when compression
* is enabled - so a separate HEAD request would be required first. */
const char *lang = vlc_gettext("C");
if (strcmp(lang, "C"))
vlc_http_msg_add_header(req, "Accept-Language",
"%s, *;q=0.5", lang);
/* Authentication */
/* TODO: authentication */
/* Request context */
if (file->agent != NULL)
vlc_http_msg_add_agent(req, file->agent);
if (file->referrer != NULL) /* TODO: validate URL */
vlc_http_msg_add_header(req, "Referer", "%s", file->referrer);
if (file->resp != NULL) if (file->resp != NULL)
{ {
str = vlc_http_msg_get_header(file->resp, "ETag"); const char *str = vlc_http_msg_get_header(file->resp, "ETag");
if (str != NULL) if (str != NULL)
{ {
if (!memcmp(str, "W/", 2)) if (!memcmp(str, "W/", 2))
...@@ -105,65 +69,42 @@ static struct vlc_http_msg *vlc_http_file_req(const struct vlc_http_file *file, ...@@ -105,65 +69,42 @@ static struct vlc_http_msg *vlc_http_file_req(const struct vlc_http_file *file,
} }
} }
if (vlc_http_msg_add_header(req, "Range", "bytes=%ju-", offset) if (vlc_http_msg_add_header(req, "Range", "bytes=%ju-", *offset)
&& offset != 0) && *offset != 0)
goto error; return -1;
/* TODO: vlc_http_msg_add_header(req, "TE", "deflate, gzip");*/ return 0;
/* TODO: Cookies */
return req;
error:
vlc_http_msg_destroy(req);
return NULL;
} }
static struct vlc_http_msg *vlc_http_file_open(struct vlc_http_file *file, static struct vlc_http_msg *vlc_http_file_open(struct vlc_http_file *file,
uintmax_t offset) uintmax_t offset)
{ {
struct vlc_http_msg *req = vlc_http_file_req(file, offset); struct vlc_http_msg *resp;
if (unlikely(req == NULL))
return NULL;
vlc_http_mgr_send_cookies(file->manager, file->secure, file->host,
file->path, req);
struct vlc_http_msg *resp = vlc_http_mgr_request(file->manager,
file->secure, file->host, file->port, req);
vlc_http_msg_destroy(req);
resp = vlc_http_msg_get_final(resp); resp = vlc_http_res_open(&file->resource, vlc_http_file_req, &offset);
if (resp == NULL) if (resp == NULL)
return NULL; return NULL;
vlc_http_mgr_recv_cookies(file->manager, file->secure, file->host,
file->path, resp);
int status = vlc_http_msg_get_status(resp); int status = vlc_http_msg_get_status(resp);
if (status < 200 || status >= 599)
goto fail;
if (status == 206) if (status == 206)
{ {
const char *str = vlc_http_msg_get_header(resp, "Content-Range"); const char *str = vlc_http_msg_get_header(resp, "Content-Range");
if (str == NULL) if (str == NULL)
{ /* A multipart/byteranges response. This is not what we asked for /* A multipart/byteranges response. This is not what we asked for
* and we do not support it. */ * and we do not support it. */
errno = EINVAL;
goto fail; goto fail;
}
uintmax_t start, end; uintmax_t start, end;
if (sscanf(str, "bytes %ju-%ju", &start, &end) != 2 if (sscanf(str, "bytes %ju-%ju", &start, &end) != 2
|| start != offset || start > end) || start != offset || start > end)
{ /* A single range response is what we asked for, but not at that /* A single range response is what we asked for, but not at that
* start offset. */ * start offset. */
errno = EINVAL;
goto fail; goto fail;
}
} }
return resp; return resp;
fail: fail:
vlc_http_msg_destroy(resp); vlc_http_msg_destroy(resp);
errno = EIO;
return NULL; return NULL;
} }
...@@ -171,83 +112,27 @@ void vlc_http_file_destroy(struct vlc_http_file *file) ...@@ -171,83 +112,27 @@ void vlc_http_file_destroy(struct vlc_http_file *file)
{ {
if (file->resp != NULL) if (file->resp != NULL)
vlc_http_msg_destroy(file->resp); vlc_http_msg_destroy(file->resp);
vlc_http_res_deinit(&file->resource);
free(file->referrer);
free(file->agent);
free(file->path);
free(file->authority);
free(file->host);
free(file); free(file);
} }
static char *vlc_http_authority(const char *host, unsigned port)
{
static const char *const formats[4] = { "%s", "[%s]", "%s:%u", "[%s]:%u" };
const bool brackets = strchr(host, ':') != NULL;
const char *fmt = formats[brackets + 2 * (port != 0)];
char *authority;
if (unlikely(asprintf(&authority, fmt, host, port) == -1))
return NULL;
return authority;
}
struct vlc_http_file *vlc_http_file_create(struct vlc_http_mgr *mgr, struct vlc_http_file *vlc_http_file_create(struct vlc_http_mgr *mgr,
const char *uri, const char *ua, const char *uri, const char *ua,
const char *ref) const char *ref)
{ {
vlc_url_t url;
bool secure;
vlc_UrlParse(&url, uri);
if (url.psz_protocol == NULL || url.psz_host == NULL)
goto error;
if (!vlc_ascii_strcasecmp(url.psz_protocol, "https"))
secure = true;
else if (!vlc_ascii_strcasecmp(url.psz_protocol, "http"))
secure = false;
else
goto error;
struct vlc_http_file *file = malloc(sizeof (*file)); struct vlc_http_file *file = malloc(sizeof (*file));
if (unlikely(file == NULL)) if (unlikely(file == NULL))
goto error; return NULL;
file->secure = secure;
file->host = strdup(url.psz_host);
file->port = url.i_port;
file->authority = vlc_http_authority(url.psz_host, url.i_port);
file->agent = (ua != NULL) ? strdup(ua) : NULL;
file->referrer = (ref != NULL) ? strdup(ref) : NULL;
const char *path = url.psz_path;
if (path == NULL)
path = "/";
if (url.psz_option != NULL) if (vlc_http_res_init(&file->resource, mgr, uri, ua, ref))
{ {
if (asprintf(&file->path, "%s?%s", path, url.psz_option) == -1) free(file);
file->path = NULL; return NULL;
} }
else
file->path = strdup(path);
vlc_UrlClean(&url);
file->manager = mgr;
file->resp = NULL; file->resp = NULL;
file->offset = 0; file->offset = 0;
if (unlikely(file->host == NULL || file->authority == NULL
|| file->path == NULL))
{
vlc_http_file_destroy(file);
file = NULL;
}
return file; return file;
error:
vlc_UrlClean(&url);
return NULL;
} }
int vlc_http_file_get_status(struct vlc_http_file *file) int vlc_http_file_get_status(struct vlc_http_file *file)
...@@ -263,40 +148,9 @@ int vlc_http_file_get_status(struct vlc_http_file *file) ...@@ -263,40 +148,9 @@ int vlc_http_file_get_status(struct vlc_http_file *file)
char *vlc_http_file_get_redirect(struct vlc_http_file *file) char *vlc_http_file_get_redirect(struct vlc_http_file *file)
{ {
int status = vlc_http_file_get_status(file); if (vlc_http_file_get_status(file) < 0)
/* TODO: if (status == 426 Upgrade Required) */
/* Location header is only meaningful for 201 and 3xx */
if (status != 201 && (status / 100) != 3)
return NULL;
if (status == 304 /* Not Modified */
|| status == 305 /* Use Proxy (deprecated) */
|| status == 306 /* Switch Proxy (former) */)
return NULL; return NULL;
return vlc_http_res_get_redirect(&file->resource, file->resp);
const char *location = vlc_http_msg_get_header(file->resp, "Location");
if (location == NULL)
return NULL;
/* TODO: if status is 3xx, check for Retry-After and wait */
/* NOTE: The anchor is discard if it is present as VLC does not support
* HTML anchors so far. */
size_t len = strcspn(location, "#");
/* FIXME: resolve relative URL _correctly_ */
if (location[0] == '/')
{
char *url;
if (unlikely(asprintf(&url, "%s://%s%.*s",
file->secure ? "https" : "http", file->authority,
(int)len, location)) < 0)
return NULL;
return url;
}
return strndup(location, len);
} }
uintmax_t vlc_http_file_get_size(struct vlc_http_file *file) uintmax_t vlc_http_file_get_size(struct vlc_http_file *file)
...@@ -362,12 +216,9 @@ bool vlc_http_file_can_seek(struct vlc_http_file *file) ...@@ -362,12 +216,9 @@ bool vlc_http_file_can_seek(struct vlc_http_file *file)
char *vlc_http_file_get_type(struct vlc_http_file *file) char *vlc_http_file_get_type(struct vlc_http_file *file)
{ {
int status = vlc_http_file_get_status(file); if (vlc_http_file_get_status(file) < 0)
if (status < 200 || status >= 300)
return NULL; return NULL;
return vlc_http_res_get_type(file->resp);
const char *type = vlc_http_msg_get_header(file->resp, "Content-Type");
return (type != NULL) ? strdup(type) : NULL;
} }
int vlc_http_file_seek(struct vlc_http_file *file, uintmax_t offset) int vlc_http_file_seek(struct vlc_http_file *file, uintmax_t offset)
...@@ -398,16 +249,16 @@ int vlc_http_file_seek(struct vlc_http_file *file, uintmax_t offset) ...@@ -398,16 +249,16 @@ int vlc_http_file_seek(struct vlc_http_file *file, uintmax_t offset)
block_t *vlc_http_file_read(struct vlc_http_file *file) block_t *vlc_http_file_read(struct vlc_http_file *file)
{ {
int status = vlc_http_file_get_status(file); if (vlc_http_file_get_status(file) < 0)
if (status < 200 || status >= 300) return NULL;
return NULL; /* do not "read" redirect or error message */
block_t *block = vlc_http_res_read(file->resp);
block_t *block = vlc_http_msg_read(file->resp);
if (block == NULL) if (block == NULL)
{ /* Automatically reconnect if server supports seek */ { /* Automatically reconnect if server supports seek */
if (vlc_http_file_can_seek(file) if (vlc_http_file_can_seek(file)
&& vlc_http_file_seek(file, file->offset) == 0) && vlc_http_file_seek(file, file->offset) == 0)
block = vlc_http_msg_read(file->resp); block = vlc_http_res_read(file->resp);
if (block == NULL) if (block == NULL)
return NULL; return NULL;
......
/*****************************************************************************
* resource.c: HTTP resource common code
*****************************************************************************
* 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 <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vlc_common.h>
#include <vlc_url.h>
#include <vlc_strings.h>
#include "message.h"
#include "connmgr.h"
#include "resource.h"
static struct vlc_http_msg *
vlc_http_res_req(const struct vlc_http_resource *res)
{
struct vlc_http_msg *req;
req = vlc_http_req_create("GET", res->secure ? "https" : "http",
res->authority, res->path);
if (unlikely(req == NULL))
return NULL;
/* Content negotiation */
vlc_http_msg_add_header(req, "Accept", "*/*");
const char *lang = vlc_gettext("C");
if (strcmp(lang, "C"))
vlc_http_msg_add_header(req, "Accept-Language",
"%s, *;q=0.5", lang);
/* Authentication */
/* TODO: authentication */
/* Request context */
if (res->agent != NULL)
vlc_http_msg_add_agent(req, res->agent);
if (res->referrer != NULL) /* TODO: validate URL */
vlc_http_msg_add_header(req, "Referer", "%s", res->referrer);
vlc_http_mgr_send_cookies(res->manager, res->secure, res->host,
res->path, req);
/* TODO: vlc_http_msg_add_header(req, "TE", "gzip, deflate"); */
return req;
}
struct vlc_http_msg *vlc_http_res_open(struct vlc_http_resource *res,
int (*request_cb)(struct vlc_http_msg *, const struct vlc_http_resource *,
void *), void *opaque)
{
struct vlc_http_msg *req = vlc_http_res_req(res);
if (unlikely(req == NULL))
return NULL;
if (request_cb(req, res, opaque))
{
vlc_http_msg_destroy(req);
return NULL;
}
struct vlc_http_msg *resp = vlc_http_mgr_request(res->manager, res->secure,
res->host, res->port, req);
vlc_http_msg_destroy(req);
resp = vlc_http_msg_get_final(resp);
if (resp == NULL)
return NULL;
vlc_http_mgr_recv_cookies(res->manager, res->secure, res->host, res->path,
resp);
int status = vlc_http_msg_get_status(resp);
if (status < 200 || status >= 599)
goto fail;
return resp;
fail:
vlc_http_msg_destroy(resp);
return NULL;
}
void vlc_http_res_deinit(struct vlc_http_resource *res)
{
free(res->referrer);
free(res->agent);
free(res->path);
free(res->authority);
free(res->host);
}
static char *vlc_http_authority(const char *host, unsigned port)
{
static const char *const formats[4] = { "%s", "[%s]", "%s:%u", "[%s]:%u" };
const bool brackets = strchr(host, ':') != NULL;
const char *fmt = formats[brackets + 2 * (port != 0)];
char *authority;
if (unlikely(asprintf(&authority, fmt, host, port) == -1))
return NULL;
return authority;
}
int vlc_http_res_init(struct vlc_http_resource *restrict res,
struct vlc_http_mgr *mgr, const char *uri,
const char *ua, const char *ref)
{
vlc_url_t url;
bool secure;
vlc_UrlParse(&url, uri);
if (url.psz_protocol == NULL || url.psz_host == NULL)
{
errno = EINVAL;
goto error;
}
if (!vlc_ascii_strcasecmp(url.psz_protocol, "https"))
secure = true;
else if (!vlc_ascii_strcasecmp(url.psz_protocol, "http"))
secure = false;
else
{
errno = ENOTSUP;
goto error;
}
res->secure = secure;
res->host = strdup(url.psz_host);
res->port = url.i_port;
res->authority = vlc_http_authority(url.psz_host, url.i_port);
res->agent = (ua != NULL) ? strdup(ua) : NULL;
res->referrer = (ref != NULL) ? strdup(ref) : NULL;
const char *path = url.psz_path;
if (path == NULL)
path = "/";
if (url.psz_option != NULL)
{
if (asprintf(&res->path, "%s?%s", path, url.psz_option) == -1)
res->path = NULL;
}
else
res->path = strdup(path);
vlc_UrlClean(&url);
res->manager = mgr;
if (unlikely(res->host == NULL || res->authority == NULL
|| res->path == NULL))
{
vlc_http_res_deinit(res);
return -1;
}
return 0;
error:
vlc_UrlClean(&url);
return -1;
}
char *vlc_http_res_get_redirect(const struct vlc_http_resource *restrict res,
const struct vlc_http_msg *resp)
{
int status = vlc_http_msg_get_status(resp);
/* TODO: if (status == 426 Upgrade Required) */
/* Location header is only meaningful for 201 and 3xx */
if (status != 201 && (status / 100) != 3)
return NULL;
if (status == 304 /* Not Modified */
|| status == 305 /* Use Proxy (deprecated) */
|| status == 306 /* Switch Proxy (former) */)
return NULL;
const char *location = vlc_http_msg_get_header(resp, "Location");
if (location == NULL)
return NULL;
/* TODO: if status is 3xx, check for Retry-After and wait */
/* NOTE: The anchor is discard if it is present as VLC does not support
* HTML anchors so far. */
size_t len = strcspn(location, "#");
/* FIXME: resolve relative URL _correctly_ */
if (location[0] == '/')
{
char *url;
if (unlikely(asprintf(&url, "%s://%s%.*s",
res->secure ? "https" : "http", res->authority,
(int)len, location)) < 0)
return NULL;
return url;
}
return strndup(location, len);
}
char *vlc_http_res_get_type(const struct vlc_http_msg *resp)
{
int status = vlc_http_msg_get_status(resp);
if (status < 200 || status >= 300)
return NULL;
const char *type = vlc_http_msg_get_header(resp, "Content-Type");
return (type != NULL) ? strdup(type) : NULL;
}
struct block_t *vlc_http_res_read(struct vlc_http_msg *resp)
{
int status = vlc_http_msg_get_status(resp);
if (status < 200 || status >= 300)
return NULL; /* do not "read" redirect or error message */
return vlc_http_msg_read(resp);
}
/*****************************************************************************
* resource.h: HTTP resource common code
*****************************************************************************
* 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.
*****************************************************************************/
struct vlc_http_msg;
struct vlc_http_mgr;
struct vlc_http_resource
{
struct vlc_http_mgr *manager;
char *host;
unsigned port;
bool secure;
char *authority;
char *path;
char *agent;
char *referrer;
};
int vlc_http_res_init(struct vlc_http_resource *, struct vlc_http_mgr *mgr,
const char *uri,const char *ua, const char *ref);
void vlc_http_res_deinit(struct vlc_http_resource *);
struct vlc_http_msg *vlc_http_res_open(struct vlc_http_resource *res,
int (*cb)(struct vlc_http_msg *req, const struct vlc_http_resource *,
void *), void *);
char *vlc_http_res_get_redirect(const struct vlc_http_resource *,
const struct vlc_http_msg *resp);
char *vlc_http_res_get_type(const struct vlc_http_msg *resp);
struct block_t *vlc_http_res_read(struct vlc_http_msg *resp);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment