mirror of
https://github.com/ZiZc3/VLC.git
synced 2026-10-27 21:03:06 +01:00
357 lines
12 KiB
C
357 lines
12 KiB
C
/*
|
|
* ps5http: VLC's http:// and https:// access through the console's own HTTP
|
|
* service (libSceHttp, with libSceSsl doing the TLS and the system's
|
|
* certificates). VLC's http module with GnuTLS ended the app on the console
|
|
* the moment a connection to a server started (runs 12-13); the console's
|
|
* service is what PS5 apps use, so it is preferred here, and VLC's own http
|
|
* module stays as the fallback.
|
|
*
|
|
* One request at a time per stream; a seek is a new request with a Range
|
|
* header. Redirects are followed by VLC (VLC_ACCESS_REDIRECT), so playlists
|
|
* (HLS) resolve their relative addresses against the final one.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
#define MODULE_NAME ps5http
|
|
#define MODULE_STRING "ps5http"
|
|
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <pthread.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
|
|
#include <vlc_common.h>
|
|
#include <vlc_access.h>
|
|
#include <vlc_interrupt.h>
|
|
#include <vlc_plugin.h>
|
|
#include <vlc_url.h>
|
|
|
|
/* ---- the console's HTTP service (libSceNet, libSceSsl, libSceHttp) ---- */
|
|
int sceNetInit(void);
|
|
int sceNetPoolCreate(const char *name, int size, int flags);
|
|
int sceSslInit(size_t pool_size);
|
|
int sceHttpInit(int net_pool, int ssl_ctx, size_t pool_size);
|
|
int sceHttpCreateTemplate(int http_ctx, const char *user_agent, int http_version, int auto_proxy);
|
|
int sceHttpSetAutoRedirect(int id, int enabled);
|
|
int sceHttpSetResolveTimeOut(int id, uint32_t usec);
|
|
int sceHttpSetConnectTimeOut(int id, uint32_t usec);
|
|
int sceHttpSetSendTimeOut(int id, uint32_t usec);
|
|
int sceHttpSetRecvTimeOut(int id, uint32_t usec);
|
|
int sceHttpCreateConnectionWithURL(int tmpl, const char *url, int keep_alive);
|
|
int sceHttpDeleteConnection(int conn);
|
|
int sceHttpCreateRequestWithURL(int conn, int method, const char *url, uint64_t content_length);
|
|
int sceHttpDeleteRequest(int req);
|
|
int sceHttpAbortRequest(int req);
|
|
int sceHttpAddRequestHeader(int req, const char *name, const char *value, uint32_t mode);
|
|
int sceHttpSendRequest(int req, const void *data, size_t size);
|
|
int sceHttpGetStatusCode(int req, int *status);
|
|
int sceHttpGetAllResponseHeaders(int req, char **headers, size_t *size);
|
|
int sceHttpGetResponseContentLength(int req, int *result, uint64_t *length);
|
|
int sceHttpReadData(int req, void *data, size_t size);
|
|
|
|
/* Straight to the log, flushed: VLC keeps a listing's messages quiet, and the
|
|
* console may end the app before the log is written out. */
|
|
#define PLOG(...) \
|
|
do { \
|
|
fprintf(stderr, "ps5http: " __VA_ARGS__); \
|
|
fputc('\n', stderr); \
|
|
fflush(stderr); \
|
|
} while (0)
|
|
|
|
enum { HTTP_VERSION_1_1 = 2, METHOD_GET = 0, HEADER_OVERWRITE = 0, CONTENT_LENGTH_EXISTS = 0 };
|
|
|
|
static pthread_once_t service_once = PTHREAD_ONCE_INIT;
|
|
static int http_template = -1;
|
|
/* The console refused a request outright (run 15: 0x8095f00c on every https
|
|
* request): VLC's own http module does the rest of the session's. */
|
|
static bool service_refused;
|
|
|
|
static void start_service(void)
|
|
{
|
|
sceNetInit();
|
|
int pool = sceNetPoolCreate("vlcps5_http", 1024 * 1024, 0);
|
|
int ssl = sceSslInit(320 * 1024);
|
|
int http = pool >= 0 && ssl >= 0 ? sceHttpInit(pool, ssl, 4 * 1024 * 1024) : -1;
|
|
if (http < 0) {
|
|
fprintf(stderr, "ps5http: the console's HTTP service didn't start (net %d, ssl %d, http %d)\n",
|
|
pool, ssl, http);
|
|
return;
|
|
}
|
|
int t = sceHttpCreateTemplate(http, "VLC/3.0.24 LibVLC/3.0.24", HTTP_VERSION_1_1, 1);
|
|
if (t < 0) {
|
|
fprintf(stderr, "ps5http: no request template (%#x)\n", (unsigned)t);
|
|
return;
|
|
}
|
|
sceHttpSetAutoRedirect(t, 0); /* VLC follows them: see the top */
|
|
sceHttpSetResolveTimeOut(t, 10 * 1000000);
|
|
sceHttpSetConnectTimeOut(t, 10 * 1000000);
|
|
sceHttpSetSendTimeOut(t, 15 * 1000000);
|
|
sceHttpSetRecvTimeOut(t, 20 * 1000000);
|
|
http_template = t;
|
|
fprintf(stderr, "ps5http: the console's HTTP service is ready\n");
|
|
}
|
|
|
|
/* ---- one stream ---- */
|
|
|
|
typedef struct ps5http_sys {
|
|
int conn, req;
|
|
uint64_t pos; /* where the next byte read comes from */
|
|
uint64_t size; /* 0: unknown (live, chunked) */
|
|
bool can_seek;
|
|
bool eof;
|
|
char *content_type;
|
|
char *redirect; /* Location of a 3xx */
|
|
int status;
|
|
} ps5http_sys;
|
|
|
|
static void drop_request(ps5http_sys *sys)
|
|
{
|
|
if (sys->req >= 0)
|
|
sceHttpDeleteRequest(sys->req);
|
|
if (sys->conn >= 0)
|
|
sceHttpDeleteConnection(sys->conn);
|
|
sys->req = sys->conn = -1;
|
|
}
|
|
|
|
/* A header's value from the raw response headers ("Name: value\r\n..."). */
|
|
static char *header_value(const char *headers, size_t len, const char *name)
|
|
{
|
|
size_t nlen = strlen(name);
|
|
const char *p = headers, *end = headers + len;
|
|
while (p < end) {
|
|
const char *eol = memchr(p, '\n', (size_t)(end - p));
|
|
if (!eol)
|
|
eol = end;
|
|
if ((size_t)(eol - p) > nlen && !strncasecmp(p, name, nlen) && p[nlen] == ':') {
|
|
const char *v = p + nlen + 1;
|
|
while (v < eol && (*v == ' ' || *v == '\t'))
|
|
v++;
|
|
const char *ve = eol;
|
|
while (ve > v && (ve[-1] == '\r' || ve[-1] == ' '))
|
|
ve--;
|
|
return strndup(v, (size_t)(ve - v));
|
|
}
|
|
p = eol + 1;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void abort_on_interrupt(void *data)
|
|
{
|
|
int *req = data;
|
|
if (*req >= 0)
|
|
sceHttpAbortRequest(*req);
|
|
}
|
|
|
|
/* Sends a GET from `offset`; fills status, size, type, redirect. */
|
|
static int send_request(stream_t *access, uint64_t offset)
|
|
{
|
|
ps5http_sys *sys = access->p_sys;
|
|
drop_request(sys);
|
|
free(sys->redirect);
|
|
sys->redirect = NULL;
|
|
sys->eof = false;
|
|
|
|
sys->conn = sceHttpCreateConnectionWithURL(http_template, access->psz_url, 1);
|
|
if (sys->conn < 0) {
|
|
PLOG("connection failed (%#x)", (unsigned)sys->conn);
|
|
return VLC_EGENERIC;
|
|
}
|
|
sys->req = sceHttpCreateRequestWithURL(sys->conn, METHOD_GET, access->psz_url, 0);
|
|
if (sys->req < 0) {
|
|
PLOG("request failed (%#x)", (unsigned)sys->req);
|
|
drop_request(sys);
|
|
return VLC_EGENERIC;
|
|
}
|
|
char *ua = var_InheritString(access, "http-user-agent");
|
|
char *ref = var_InheritString(access, "http-referrer");
|
|
if (ua && *ua)
|
|
sceHttpAddRequestHeader(sys->req, "User-Agent", ua, HEADER_OVERWRITE);
|
|
if (ref && *ref)
|
|
sceHttpAddRequestHeader(sys->req, "Referer", ref, HEADER_OVERWRITE);
|
|
free(ua);
|
|
free(ref);
|
|
sceHttpAddRequestHeader(sys->req, "Accept", "*/*", HEADER_OVERWRITE);
|
|
if (offset > 0) {
|
|
char range[48];
|
|
snprintf(range, sizeof(range), "bytes=%" PRIu64 "-", offset);
|
|
sceHttpAddRequestHeader(sys->req, "Range", range, HEADER_OVERWRITE);
|
|
}
|
|
|
|
vlc_interrupt_register(abort_on_interrupt, &sys->req);
|
|
int r = sceHttpSendRequest(sys->req, NULL, 0);
|
|
vlc_interrupt_unregister();
|
|
if (r < 0) {
|
|
PLOG("sending failed (%#x): VLC's own http is used from now on", (unsigned)r);
|
|
service_refused = true;
|
|
drop_request(sys);
|
|
return VLC_EGENERIC;
|
|
}
|
|
int status = 0;
|
|
sceHttpGetStatusCode(sys->req, &status);
|
|
sys->status = status;
|
|
|
|
char *headers = NULL;
|
|
size_t hlen = 0;
|
|
if (sceHttpGetAllResponseHeaders(sys->req, &headers, &hlen) >= 0 && headers) {
|
|
if (status >= 300 && status < 400)
|
|
sys->redirect = header_value(headers, hlen, "Location");
|
|
if (!sys->content_type)
|
|
sys->content_type = header_value(headers, hlen, "Content-Type");
|
|
char *ranges = header_value(headers, hlen, "Accept-Ranges");
|
|
sys->can_seek = status == 206 || (ranges && !strcasecmp(ranges, "bytes"));
|
|
free(ranges);
|
|
}
|
|
|
|
int has = 1;
|
|
uint64_t len = 0;
|
|
if (status >= 200 && status < 300 &&
|
|
sceHttpGetResponseContentLength(sys->req, &has, &len) >= 0 && has == CONTENT_LENGTH_EXISTS)
|
|
sys->size = (status == 206 ? offset : 0) + len;
|
|
sys->pos = status == 206 ? offset : 0;
|
|
PLOG("HTTP %d, %" PRIu64 " bytes%s", status, sys->size, sys->can_seek ? ", seekable" : "");
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
static ssize_t Read(stream_t *access, void *buf, size_t len)
|
|
{
|
|
ps5http_sys *sys = access->p_sys;
|
|
if (sys->eof || sys->req < 0)
|
|
return 0;
|
|
vlc_interrupt_register(abort_on_interrupt, &sys->req);
|
|
int n = sceHttpReadData(sys->req, buf, len);
|
|
vlc_interrupt_unregister();
|
|
if (n <= 0) {
|
|
if (n < 0)
|
|
PLOG("read failed (%#x)", (unsigned)n);
|
|
sys->eof = true;
|
|
return 0;
|
|
}
|
|
sys->pos += (uint64_t)n;
|
|
return n;
|
|
}
|
|
|
|
static int Seek(stream_t *access, uint64_t offset)
|
|
{
|
|
ps5http_sys *sys = access->p_sys;
|
|
if (!sys->can_seek)
|
|
return VLC_EGENERIC;
|
|
if (sys->size && offset >= sys->size) {
|
|
drop_request(sys);
|
|
sys->pos = offset;
|
|
sys->eof = true;
|
|
return VLC_SUCCESS;
|
|
}
|
|
if (send_request(access, offset) != VLC_SUCCESS)
|
|
return VLC_EGENERIC;
|
|
if (sys->status == 416) {
|
|
sys->eof = true;
|
|
return VLC_SUCCESS;
|
|
}
|
|
return sys->status == 206 || (offset == 0 && sys->status == 200) ? VLC_SUCCESS : VLC_EGENERIC;
|
|
}
|
|
|
|
static int Control(stream_t *access, int query, va_list args)
|
|
{
|
|
ps5http_sys *sys = access->p_sys;
|
|
switch (query) {
|
|
case STREAM_CAN_SEEK:
|
|
*va_arg(args, bool *) = sys->can_seek;
|
|
break;
|
|
case STREAM_CAN_FASTSEEK:
|
|
*va_arg(args, bool *) = false;
|
|
break;
|
|
case STREAM_CAN_PAUSE:
|
|
case STREAM_CAN_CONTROL_PACE:
|
|
*va_arg(args, bool *) = true;
|
|
break;
|
|
case STREAM_GET_SIZE:
|
|
if (!sys->size)
|
|
return VLC_EGENERIC;
|
|
*va_arg(args, uint64_t *) = sys->size;
|
|
break;
|
|
case STREAM_GET_PTS_DELAY:
|
|
*va_arg(args, int64_t *) = INT64_C(1000) * var_InheritInteger(access, "network-caching");
|
|
break;
|
|
case STREAM_GET_CONTENT_TYPE:
|
|
if (!sys->content_type)
|
|
return VLC_EGENERIC;
|
|
*va_arg(args, char **) = strdup(sys->content_type);
|
|
break;
|
|
case STREAM_SET_PAUSE_STATE:
|
|
break;
|
|
default:
|
|
return VLC_EGENERIC;
|
|
}
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
static void Close(vlc_object_t *obj)
|
|
{
|
|
stream_t *access = (stream_t *)obj;
|
|
ps5http_sys *sys = access->p_sys;
|
|
drop_request(sys);
|
|
free(sys->content_type);
|
|
free(sys->redirect);
|
|
free(sys);
|
|
}
|
|
|
|
static int Open(vlc_object_t *obj)
|
|
{
|
|
stream_t *access = (stream_t *)obj;
|
|
pthread_once(&service_once, start_service);
|
|
if (http_template < 0 || service_refused)
|
|
return VLC_EGENERIC; /* VLC's own http module takes over */
|
|
|
|
ps5http_sys *sys = calloc(1, sizeof(*sys));
|
|
if (!sys)
|
|
return VLC_ENOMEM;
|
|
sys->conn = sys->req = -1;
|
|
access->p_sys = sys;
|
|
PLOG("open %s", access->psz_url);
|
|
|
|
if (send_request(access, 0) != VLC_SUCCESS)
|
|
goto error;
|
|
if (sys->redirect) {
|
|
/* VLC opens the new address (and probes the modules again). */
|
|
char *to = vlc_uri_resolve(access->psz_url, sys->redirect);
|
|
PLOG("redirected (%d) to %s", sys->status, to ? to : sys->redirect);
|
|
free(access->psz_url);
|
|
access->psz_url = to ? to : strdup(sys->redirect);
|
|
Close(obj);
|
|
return VLC_ACCESS_REDIRECT;
|
|
}
|
|
if (sys->status == 401 || sys->status == 407) {
|
|
/* A login: VLC's own http module asks for it. */
|
|
PLOG("HTTP %d: leaving it to the http module", sys->status);
|
|
goto error;
|
|
}
|
|
if (sys->status < 200 || sys->status >= 300) {
|
|
PLOG("HTTP %d", sys->status);
|
|
goto error;
|
|
}
|
|
access->pf_read = Read;
|
|
access->pf_block = NULL;
|
|
access->pf_seek = Seek;
|
|
access->pf_control = Control;
|
|
return VLC_SUCCESS;
|
|
|
|
error:
|
|
Close(obj);
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
vlc_module_begin()
|
|
set_shortname("PS5 HTTP")
|
|
set_description("HTTP/HTTPS through the console's network service")
|
|
set_category(CAT_INPUT)
|
|
set_subcategory(SUBCAT_INPUT_ACCESS)
|
|
set_capability("access", 300)
|
|
add_shortcut("http", "https")
|
|
set_callbacks(Open, Close)
|
|
vlc_module_end()
|