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.
 
 
 
 
 
 

359 lines
10 KiB

/*****************************************************************************
* 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, void *opaque)
{
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", "*/*");
if (res->negotiate)
{
const char *lang = vlc_gettext("C");
if (!strcmp(lang, "C"))
lang = "en_US";
vlc_http_msg_add_header(req, "Accept-Language", "%s", lang);
}
/* Authentication */
if (res->username != NULL && res->password != NULL)
vlc_http_msg_add_creds_basic(req, false, res->username, res->password);
/* 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_msg_add_cookies(req, vlc_http_mgr_get_jar(res->manager));
/* TODO: vlc_http_msg_add_header(req, "TE", "gzip, deflate"); */
if (res->cbs->request_format(res, req, opaque))
{
vlc_http_msg_destroy(req);
return NULL;
}
return req;
}
struct vlc_http_msg *vlc_http_res_open(struct vlc_http_resource *res,
void *opaque)
{
struct vlc_http_msg *req;
retry:
req = vlc_http_res_req(res, opaque);
if (unlikely(req == NULL))
return NULL;
struct vlc_http_msg *resp = vlc_http_mgr_request(res->manager, res->secure,
res->host, res->port, req, true, false);
vlc_http_msg_destroy(req);
resp = vlc_http_msg_get_final(resp);
if (resp == NULL)
return NULL;
vlc_http_msg_get_cookies(resp, vlc_http_mgr_get_jar(res->manager),
res->host, res->path);
int status = vlc_http_msg_get_status(resp);
if (status < 200 || status >= 599)
goto fail;
if (status == 406 && res->negotiate)
{ /* Not Acceptable: Content negotiation failed. Normally it means
* one (or more) Accept or Accept-* header line does not match any
* representation of the entity. So we set a flag to remove those
* header lines (unless they accept everything), and retry.
* In principles, it could be any header line, and the server can
* pass Vary to clarify. It cannot be caused by If-*, Range, TE or the
* other transfer- rather than representation-affecting header lines.
*/
vlc_http_msg_destroy(resp);
res->negotiate = false;
goto retry;
}
if (res->cbs->response_validate(res, resp, opaque))
goto fail;
return resp;
fail:
vlc_http_msg_destroy(resp);
return NULL;
}
int vlc_http_res_get_status(struct vlc_http_resource *res)
{
if (res->response == NULL)
{
if (res->failure)
return -1;
res->response = vlc_http_res_open(res, res + 1);
if (res->response == NULL)
{
res->failure = true;
return -1;
}
}
return vlc_http_msg_get_status(res->response);
}
static void vlc_http_res_deinit(struct vlc_http_resource *res)
{
free(res->referrer);
free(res->agent);
free(res->password);
free(res->username);
free(res->path);
free(res->authority);
free(res->host);
if (res->response != NULL)
vlc_http_msg_destroy(res->response);
}
void vlc_http_res_destroy(struct vlc_http_resource *res)
{
vlc_http_res_deinit(res);
free(res);
}
int vlc_http_res_init(struct vlc_http_resource *restrict res,
const struct vlc_http_resource_cbs *cbs,
struct vlc_http_mgr *mgr,
const char *uri, const char *ua, const char *ref)
{
vlc_url_t url;
bool secure;
if (vlc_UrlParse(&url, uri))
goto error;
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->cbs = cbs;
res->response = NULL;
res->secure = secure;
res->negotiate = true;
res->failure = false;
res->host = strdup(url.psz_host);
res->port = url.i_port;
res->authority = vlc_http_authority(url.psz_host, url.i_port);
res->username = (url.psz_username != NULL) ? strdup(url.psz_username)
: NULL;
res->password = (url.psz_password != NULL) ? strdup(url.psz_password)
: NULL;
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(struct vlc_http_resource *restrict res)
{
int status = vlc_http_res_get_status(res);
if (status < 0)
return NULL;
if ((status / 100) == 2 && !res->secure)
{
char *url;
/* HACK: Seems like an MMS server. Redirect to MMSH scheme. */
const char *pragma = vlc_http_msg_get_header(res->response, "Pragma");
if (pragma != NULL && !vlc_ascii_strcasecmp(pragma, "features")
&& asprintf(&url, "mmsh://%s%s", res->authority, res->path) >= 0)
return url;
/* HACK: Seems like an ICY server. Redirect to ICYX scheme. */
if ((vlc_http_msg_get_header(res->response, "Icy-Name") != NULL
|| vlc_http_msg_get_header(res->response, "Icy-Genre") != NULL)
&& asprintf(&url, "icyx://%s%s", res->authority, res->path) >= 0)
return url;
}
/* 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(res->response, "Location");
if (location == NULL)
return NULL;
/* TODO: if status is 3xx, check for Retry-After and wait */
char *base;
if (unlikely(asprintf(&base, "http%s://%s%s", res->secure ? "s" : "",
res->authority, res->path) == -1))
return NULL;
char *fixed = vlc_uri_fixup(location);
if (fixed != NULL)
location = fixed;
char *abs = vlc_uri_resolve(base, location);
free(fixed);
free(base);
if (likely(abs != NULL))
{
/* NOTE: The anchor is discarded if it is present as VLC does not support
* HTML anchors so far. */
size_t len = strcspn(abs, "#");
abs[len] = '\0';
}
return abs;
}
char *vlc_http_res_get_type(struct vlc_http_resource *res)
{
int status = vlc_http_res_get_status(res);
if (status < 200 || status >= 300)
return NULL;
const char *type = vlc_http_msg_get_header(res->response, "Content-Type");
return (type != NULL) ? strdup(type) : NULL;
}
block_t *vlc_http_res_read(struct vlc_http_resource *res)
{
int status = vlc_http_res_get_status(res);
if (status < 200 || status >= 300)
return NULL; /* do not "read" redirect or error message */
return vlc_http_msg_read(res->response);
}
int vlc_http_res_set_login(struct vlc_http_resource *res,
const char *username, const char *password)
{
char *user = NULL;
char *pass = NULL;
if (username != NULL)
{
user = strdup(username);
if (unlikely(user == NULL))
return -1;
pass = strdup((password != NULL) ? password : "");
if (unlikely(pass == NULL))
{
free(user);
return -1;
}
}
free(res->password);
free(res->username);
res->username = user;
res->password = pass;
if (res->response != NULL && vlc_http_msg_get_status(res->response) == 401)
{
vlc_http_msg_destroy(res->response);
res->response = NULL;
}
return 0;
}
char *vlc_http_res_get_basic_realm(struct vlc_http_resource *res)
{
int status = vlc_http_res_get_status(res);
if (status != 401)
return NULL;
return vlc_http_msg_get_basic_realm(res->response);
}