/* * HTTP protocol for ffmpeg client * Copyright (c) 2000, 2001 Fabrice Bellard * * This file is part of FFmpeg. * * FFmpeg 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. * * FFmpeg 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 FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #if CONFIG_ZLIB#include <zlib.h>#endif /* CONFIG_ZLIB */ #include "libavutil/avassert.h"#include "libavutil/avstring.h"#include "libavutil/opt.h"#include "libavutil/time.h"#include "libavutil/parseutils.h" #include "avformat.h"#include "http.h"#include "httpauth.h"#include "internal.h"#include "network.h"#include "os_support.h"#include "url.h" /* XXX: POST protocol is not completely implemented because ffmpeg uses * only a subset of it. */ /* The IO buffer size is unrelated to the max URL size in itself, but needs * to be large enough to fit the full request headers (including long * path names). */#define BUFFER_SIZE MAX_URL_SIZE#define MAX_REDIRECTS 8#define HTTP_SINGLE 1#define HTTP_MUTLI 2#define MAX_EXPIRY 19#define WHITESPACES " \n\t\r"typedef enum { LOWER_PROTO, READ_HEADERS, WRITE_REPLY_HEADERS, FINISH}HandshakeState; typedef struct HTTPContext { const AVClass *class; URLContext *hd; unsigned char buffer[BUFFER_SIZE], *buf_ptr, *buf_end; int line_count; int http_code; /* Used if "Transfer-Encoding: chunked" otherwise -1. */ uint64_t chunksize; uint64_t off, end_off, filesize; char *location; HTTPAuthState auth_state; HTTPAuthState proxy_auth_state; char *http_proxy; char *headers; char *mime_type; char *user_agent;#if FF_API_HTTP_USER_AGENT char *user_agent_deprecated;#endif char *content_type; /* Set if the server correctly handles Connection: close and will close * the connection after feeding us the content. */ int willclose; int seekable; /**< Control seekability, 0 = disable, 1 = enable, -1 = probe. */ int chunked_post; /* A flag which indicates if the end of chunked encoding has been sent. */ int end_chunked_post; /* A flag which indicates we have finished to read POST reply. */ int end_header; /* A flag which indicates if we use persistent connections. */ int multiple_requests; uint8_t *post_data; int post_datalen; int is_akamai; int is_mediagateway; char *cookies; ///< holds newline (\n) delimited Set-Cookie header field values (without the "Set-Cookie: " field name) /* A dictionary containing cookies keyed by cookie name */ AVDictionary *cookie_dict; int icy; /* how much data was read since the last ICY metadata packet */ uint64_t icy_data_read; /* after how many bytes of read data a new metadata packet will be found */ uint64_t icy_metaint; char *icy_metadata_headers; char *icy_metadata_packet; AVDictionary *metadata;#if CONFIG_ZLIB int compressed; z_stream inflate_stream; uint8_t *inflate_buffer;#endif /* CONFIG_ZLIB */ AVDictionary *chained_options; int send_expect_100; char *method; int reconnect; int reconnect_at_eof; int reconnect_streamed; int reconnect_delay; int reconnect_delay_max; int listen; char *resource; int reply_code; int is_multi_client; HandshakeState handshake_step; int is_connected_server;} HTTPContext; #define OFFSET(x) offsetof(HTTPContext, x)#define D AV_OPT_FLAG_DECODING_PARAM#define E AV_OPT_FLAG_ENCODING_PARAM#define DEFAULT_USER_AGENT "Lavf/" AV_STRINGIFY(LIBAVFORMAT_VERSION) static const AVOption options[] = { { "seekable", "control seekability of connection", OFFSET(seekable), AV_OPT_TYPE_BOOL, { .i64 = -1 }, -1, 1, D }, { "chunked_post", "use chunked transfer-encoding for posts", OFFSET(chunked_post), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, E }, { "http_proxy", "set HTTP proxy to tunnel through", OFFSET(http_proxy), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E }, { "headers", "set custom HTTP headers, can override built in default headers", OFFSET(headers), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E }, { "content_type", "set a specific content type for the POST messages", OFFSET(content_type), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E }, { "user_agent", "override User-Agent header", OFFSET(user_agent), AV_OPT_TYPE_STRING, { .str = DEFAULT_USER_AGENT }, 0, 0, D },#if FF_API_HTTP_USER_AGENT { "user-agent", "override User-Agent header", OFFSET(user_agent_deprecated), AV_OPT_TYPE_STRING, { .str = DEFAULT_USER_AGENT }, 0, 0, D },#endif { "multiple_requests", "use persistent connections", OFFSET(multiple_requests), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D | E }, { "post_data", "set custom HTTP post data", OFFSET(post_data), AV_OPT_TYPE_BINARY, .flags = D | E }, { "mime_type", "export the MIME type", OFFSET(mime_type), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, AV_OPT_FLAG_EXPORT | AV_OPT_FLAG_READONLY }, { "cookies", "set cookies to be sent in applicable future requests, use newline delimited Set-Cookie HTTP field value syntax", OFFSET(cookies), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D }, { "icy", "request ICY metadata", OFFSET(icy), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, D }, { "icy_metadata_headers", "return ICY metadata headers", OFFSET(icy_metadata_headers), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, AV_OPT_FLAG_EXPORT }, { "icy_metadata_packet", "return current ICY metadata packet", OFFSET(icy_metadata_packet), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, AV_OPT_FLAG_EXPORT }, { "metadata", "metadata read from the bitstream", OFFSET(metadata), AV_OPT_TYPE_DICT, {0}, 0, 0, AV_OPT_FLAG_EXPORT }, { "auth_type", "HTTP authentication type", OFFSET(auth_state.auth_type), AV_OPT_TYPE_INT, { .i64 = HTTP_AUTH_NONE }, HTTP_AUTH_NONE, HTTP_AUTH_BASIC, D | E, "auth_type"}, { "none", "No auth method set, autodetect", 0, AV_OPT_TYPE_CONST, { .i64 = HTTP_AUTH_NONE }, 0, 0, D | E, "auth_type"}, { "basic", "HTTP basic authentication", 0, AV_OPT_TYPE_CONST, { .i64 = HTTP_AUTH_BASIC }, 0, 0, D | E, "auth_type"}, { "send_expect_100", "Force sending an Expect: 100-continue header for POST", OFFSET(send_expect_100), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E }, { "location", "The actual location of the data received", OFFSET(location), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E }, { "offset", "initial byte offset", OFFSET(off), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, D }, { "end_offset", "try to limit the request to bytes preceding this offset", OFFSET(end_off), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, D }, { "method", "Override the HTTP method or set the expected HTTP method from a client", OFFSET(method), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E }, { "reconnect", "auto reconnect after disconnect before EOF", OFFSET(reconnect), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D }, { "reconnect_at_eof", "auto reconnect at EOF", OFFSET(reconnect_at_eof), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D }, { "reconnect_streamed", "auto reconnect streamed / non seekable streams", OFFSET(reconnect_streamed), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D }, { "reconnect_delay_max", "max reconnect delay in seconds after which to give up", OFFSET(reconnect_delay_max), AV_OPT_TYPE_INT, { .i64 = 120 }, 0, UINT_MAX/1000/1000, D }, { "listen", "listen on HTTP", OFFSET(listen), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 2, D | E }, { "resource", "The resource requested by a client", OFFSET(resource), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, E }, { "reply_code", "The http status code to return to a client", OFFSET(reply_code), AV_OPT_TYPE_INT, { .i64 = 200}, INT_MIN, 599, E}, { NULL }}; static int http_connect(URLContext *h, const char *path, const char *local_path, const char *hoststr, const char *auth, const char *proxyauth, int *new_location);static int http_read_header(URLContext *h, int *new_location); void ff_http_init_auth_state(URLContext *dest, const URLContext *src){ memcpy(&((HTTPContext *)dest->priv_data)->auth_state, &((HTTPContext *)src->priv_data)->auth_state, sizeof(HTTPAuthState)); memcpy(&((HTTPContext *)dest->priv_data)->proxy_auth_state, &((HTTPContext *)src->priv_data)->proxy_auth_state, sizeof(HTTPAuthState));} static int http_open_cnx_internal(URLContext *h, AVDictionary **options){ const char *path, *proxy_path, *lower_proto = "tcp", *local_path; char hostname[1024], hoststr[1024], proto[10]; char auth[1024], proxyauth[1024] = ""; char path1[MAX_URL_SIZE]; char buf[1024], urlbuf[MAX_URL_SIZE]; int port, use_proxy, err, location_changed = 0; HTTPContext *s = h->priv_data; av_url_split(proto, sizeof(proto), auth, sizeof(auth), hostname, sizeof(hostname), &port, path1, sizeof(path1), s->location); ff_url_join(hoststr, sizeof(hoststr), NULL, NULL, hostname, port, NULL); proxy_path = s->http_proxy ? s->http_proxy : getenv("http_proxy"); use_proxy = !ff_http_match_no_proxy(getenv("no_proxy"), hostname) && proxy_path && av_strstart(proxy_path, "http://", NULL); if (!strcmp(proto, "https")) { lower_proto = "tls"; use_proxy = 0; if (port < 0) port = 443; } if (port < 0) port = 80; if (path1[0] == '\0') path = "/"; else path = path1; local_path = path; if (use_proxy) { /* Reassemble the request URL without auth string - we don't * want to leak the auth to the proxy. */ ff_url_join(urlbuf, sizeof(urlbuf), proto, NULL, hostname, port, "%s", path1); path = urlbuf; av_url_split(NULL, 0, proxyauth, sizeof(proxyauth), hostname, sizeof(hostname), &port, NULL, 0, proxy_path); } ff_url_join(buf, sizeof(buf), lower_proto, NULL, hostname, port, NULL); if (!s->hd) { err = ffurl_open_whitelist(&s->hd, buf, AVIO_FLAG_READ_WRITE, &h->interrupt_callback, options, h->protocol_whitelist, h->protocol_blacklist, h); if (err < 0) return err; } err = http_connect(h, path, local_path, hoststr, auth, proxyauth, &location_changed); if (err < 0) return err; return location_changed;} /* return non zero if error */static int http_open_cnx(URLContext *h, AVDictionary **options){ HTTPAuthType cur_auth_type, cur_proxy_auth_type; HTTPContext *s = h->priv_data; int location_changed, attempts = 0, redirects = 0;redo: av_dict_copy(options, s->chained_options, 0); cur_auth_type = s->auth_state.auth_type; cur_proxy_auth_type = s->auth_state.auth_type; location_changed = http_open_cnx_internal(h, options); if (location_changed < 0) goto fail; attempts++; if (s->http_code == 401) { if ((cur_auth_type == HTTP_AUTH_NONE || s->auth_state.stale) && s->auth_state.auth_type != HTTP_AUTH_NONE && attempts < 4) { ffurl_closep(&s->hd); goto redo; } else goto fail; } if (s->http_code == 407) { if ((cur_proxy_auth_type == HTTP_AUTH_NONE || s->proxy_auth_state.stale) && s->proxy_auth_state.auth_type != HTTP_AUTH_NONE && attempts < 4) { ffurl_closep(&s->hd); goto redo; } else goto fail; } if ((s->http_code == 301 || s->http_code == 302 || s->http_code == 303 || s->http_code == 307) && location_changed == 1) { /* url moved, get next */ ffurl_closep(&s->hd); if (redirects++ >= MAX_REDIRECTS) return AVERROR(EIO); /* Restart the authentication process with the new target, which * might use a different auth mechanism. */ memset(&s->auth_state, 0, sizeof(s->auth_state)); attempts = 0; location_changed = 0; goto redo; } return 0; fail: if (s->hd) ffurl_closep(&s->hd); if (location_changed < 0) return location_changed; return ff_http_averror(s->http_code, AVERROR(EIO));} int ff_http_do_new_request(URLContext *h, const char *uri){ HTTPContext *s = h->priv_data; AVDictionary *options = NULL; int ret; s->off = 0; s->icy_data_read = 0; av_free(s->location); s->location = av_strdup(uri); if (!s->location) return AVERROR(ENOMEM); ret = http_open_cnx(h, &options); av_dict_free(&options); return ret;} int ff_http_averror(int status_code, int default_averror){ switch (status_code) { case 400: return AVERROR_HTTP_BAD_REQUEST; case 401: return AVERROR_HTTP_UNAUTHORIZED; case 403: return AVERROR_HTTP_FORBIDDEN; case 404: return AVERROR_HTTP_NOT_FOUND; default: break; } if (status_code >= 400 && status_code <= 499) return AVERROR_HTTP_OTHER_4XX; else if (status_code >= 500) return AVERROR_HTTP_SERVER_ERROR; else return default_averror;} static int http_write_reply(URLContext* h, int status_code){ int ret, body = 0, reply_code, message_len; const char *reply_text, *content_type; HTTPContext *s = h->priv_data; char message[BUFFER_SIZE]; content_type = "text/plain"; if (status_code < 0) body = 1; switch (status_code) { case AVERROR_HTTP_BAD_REQUEST: case 400: reply_code = 400; reply_text = "Bad Request"; break; case AVERROR_HTTP_FORBIDDEN: case 403: reply_code = 403; reply_text = "Forbidden"; break; case AVERROR_HTTP_NOT_FOUND: case 404: reply_code = 404; reply_text = "Not Found"; break; case 200: reply_code = 200; reply_text = "OK"; content_type = s->content_type ? s->content_type : "application/octet-stream"; break; case AVERROR_HTTP_SERVER_ERROR: case 500: reply_code = 500; reply_text = "Internal server error"; break; default: return AVERROR(EINVAL); } if (body) { s->chunked_post = 0; message_len = snprintf(message, sizeof(message), "HTTP/1.1 %03d %s\r\n" "Content-Type: %s\r\n" "Content-Length: %"SIZE_SPECIFIER"\r\n" "%s" "\r\n" "%03d %s\r\n", reply_code, reply_text, content_type, strlen(reply_text) + 6, // 3 digit status code + space + \r\n s->headers ? s->headers : "", reply_code, reply_text); } else { s->chunked_post = 1; message_len = snprintf(message, sizeof(message), "HTTP/1.1 %03d %s\r\n" "Content-Type: %s\r\n" "Transfer-Encoding: chunked\r\n" "%s" "\r\n", reply_code, reply_text, content_type, s->headers ? s->headers : ""); } av_log(h, AV_LOG_TRACE, "HTTP reply header: \n%s----\n", message); if ((ret = ffurl_write(s->hd, message, message_len)) < 0) return ret; return 0;} static void handle_http_errors(URLContext *h, int error){ av_assert0(error < 0); http_write_reply(h, error);} static int http_handshake(URLContext *c){ int ret, err, new_location; HTTPContext *ch = c->priv_data; URLContext *cl = ch->hd; switch (ch->handshake_step) { case LOWER_PROTO: av_log(c, AV_LOG_TRACE, "Lower protocol\n"); if ((ret = ffurl_handshake(cl)) > 0) return 2 + ret; if (ret < 0) return ret; ch->handshake_step = READ_HEADERS; ch->is_connected_server = 1; return 2; case READ_HEADERS: av_log(c, AV_LOG_TRACE, "Read headers\n"); if ((err = http_read_header(c, &new_location)) < 0) { handle_http_errors(c, err); return err; } ch->handshake_step = WRITE_REPLY_HEADERS; return 1; case WRITE_REPLY_HEADERS: av_log(c, AV_LOG_TRACE, "Reply code: %d\n", ch->reply_code); if ((err = http_write_reply(c, ch->reply_code)) < 0) return err; ch->handshake_step = FINISH; return 1; case FINISH: return 0; } // this should never be reached. return AVERROR(EINVAL);} static int http_listen(URLContext *h, const char *uri, int flags, AVDictionary **options) { HTTPContext *s = h->priv_data; int ret; char hostname[1024], proto[10]; char lower_url[100]; const char *lower_proto = "tcp"; int port; av_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname), &port, NULL, 0, uri); if (!strcmp(proto, "https")) lower_proto = "tls"; ff_url_join(lower_url, sizeof(lower_url), lower_proto, NULL, hostname, port, NULL); if ((ret = av_dict_set_int(options, "listen", s->listen, 0)) < 0) goto fail; if ((ret = ffurl_open_whitelist(&s->hd, lower_url, AVIO_FLAG_READ_WRITE, &h->interrupt_callback, options, h->protocol_whitelist, h->protocol_blacklist, h )) < 0) goto fail; s->handshake_step = LOWER_PROTO; if (s->listen == HTTP_SINGLE) { /* single client */ s->reply_code = 200; while ((ret = http_handshake(h)) > 0); }fail: av_dict_free(&s->chained_options); return ret;} static int http_open(URLContext *h, const char *uri, int flags, AVDictionary **options){ HTTPContext *s = h->priv_data; int ret; if( s->seekable == 1 ) h->is_streamed = 0; else h->is_streamed = 1; s->filesize = UINT64_MAX; s->location = av_strdup(uri); if (!s->location) return AVERROR(ENOMEM); if (options) av_dict_copy(&s->chained_options, *options, 0);Lines 1–500 of 1800. Load more
/* * HTTP protocol for ffmpeg client * Copyright (c) 2000, 2001 Fabrice Bellard * * This file is part of FFmpeg. * * FFmpeg 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. * * FFmpeg 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 FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #if CONFIG_ZLIB #include <zlib.h> #endif /* CONFIG_ZLIB */ #include "libavutil/avassert.h" #include "libavutil/avstring.h" #include "libavutil/opt.h" #include "libavutil/time.h" #include "libavutil/parseutils.h" #include "avformat.h" #include "http.h" #include "httpauth.h" #include "internal.h" #include "network.h" #include "os_support.h" #include "url.h" /* XXX: POST protocol is not completely implemented because ffmpeg uses * only a subset of it. */ /* The IO buffer size is unrelated to the max URL size in itself, but needs * to be large enough to fit the full request headers (including long * path names). */ #define BUFFER_SIZE MAX_URL_SIZE #define MAX_REDIRECTS 8 #define HTTP_SINGLE 1 #define HTTP_MUTLI 2 #define MAX_EXPIRY 19 #define WHITESPACES " \n\t\r" typedef enum { LOWER_PROTO, READ_HEADERS, WRITE_REPLY_HEADERS, FINISH }HandshakeState; typedef struct HTTPContext { const AVClass *class; URLContext *hd; unsigned char buffer[BUFFER_SIZE], *buf_ptr, *buf_end; int line_count; int http_code; /* Used if "Transfer-Encoding: chunked" otherwise -1. */ uint64_t chunksize; uint64_t off, end_off, filesize; char *location; HTTPAuthState auth_state; HTTPAuthState proxy_auth_state; char *http_proxy; char *headers; char *mime_type; char *user_agent; #if FF_API_HTTP_USER_AGENT char *user_agent_deprecated; #endif char *content_type; /* Set if the server correctly handles Connection: close and will close * the connection after feeding us the content. */ int willclose; int seekable; /**< Control seekability, 0 = disable, 1 = enable, -1 = probe. */ int chunked_post; /* A flag which indicates if the end of chunked encoding has been sent. */ int end_chunked_post; /* A flag which indicates we have finished to read POST reply. */ int end_header; /* A flag which indicates if we use persistent connections. */ int multiple_requests; uint8_t *post_data; int post_datalen; int is_akamai; int is_mediagateway; char *cookies; ///< holds newline (\n) delimited Set-Cookie header field values (without the "Set-Cookie: " field name) /* A dictionary containing cookies keyed by cookie name */ AVDictionary *cookie_dict; int icy; /* how much data was read since the last ICY metadata packet */ uint64_t icy_data_read; /* after how many bytes of read data a new metadata packet will be found */ uint64_t icy_metaint; char *icy_metadata_headers; char *icy_metadata_packet; AVDictionary *metadata; #if CONFIG_ZLIB int compressed; z_stream inflate_stream; uint8_t *inflate_buffer; #endif /* CONFIG_ZLIB */ AVDictionary *chained_options; int send_expect_100; char *method; int reconnect; int reconnect_at_eof; int reconnect_streamed; int reconnect_delay; int reconnect_delay_max; int listen; char *resource; int reply_code; int is_multi_client; HandshakeState handshake_step; int is_connected_server; } HTTPContext; #define OFFSET(x) offsetof(HTTPContext, x) #define D AV_OPT_FLAG_DECODING_PARAM #define E AV_OPT_FLAG_ENCODING_PARAM #define DEFAULT_USER_AGENT "Lavf/" AV_STRINGIFY(LIBAVFORMAT_VERSION) static const AVOption options[] = { { "seekable", "control seekability of connection", OFFSET(seekable), AV_OPT_TYPE_BOOL, { .i64 = -1 }, -1, 1, D }, { "chunked_post", "use chunked transfer-encoding for posts", OFFSET(chunked_post), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, E }, { "http_proxy", "set HTTP proxy to tunnel through", OFFSET(http_proxy), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E }, { "headers", "set custom HTTP headers, can override built in default headers", OFFSET(headers), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E }, { "content_type", "set a specific content type for the POST messages", OFFSET(content_type), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E }, { "user_agent", "override User-Agent header", OFFSET(user_agent), AV_OPT_TYPE_STRING, { .str = DEFAULT_USER_AGENT }, 0, 0, D }, #if FF_API_HTTP_USER_AGENT { "user-agent", "override User-Agent header", OFFSET(user_agent_deprecated), AV_OPT_TYPE_STRING, { .str = DEFAULT_USER_AGENT }, 0, 0, D }, #endif { "multiple_requests", "use persistent connections", OFFSET(multiple_requests), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D | E }, { "post_data", "set custom HTTP post data", OFFSET(post_data), AV_OPT_TYPE_BINARY, .flags = D | E }, { "mime_type", "export the MIME type", OFFSET(mime_type), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, AV_OPT_FLAG_EXPORT | AV_OPT_FLAG_READONLY }, { "cookies", "set cookies to be sent in applicable future requests, use newline delimited Set-Cookie HTTP field value syntax", OFFSET(cookies), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D }, { "icy", "request ICY metadata", OFFSET(icy), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, D }, { "icy_metadata_headers", "return ICY metadata headers", OFFSET(icy_metadata_headers), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, AV_OPT_FLAG_EXPORT }, { "icy_metadata_packet", "return current ICY metadata packet", OFFSET(icy_metadata_packet), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, AV_OPT_FLAG_EXPORT }, { "metadata", "metadata read from the bitstream", OFFSET(metadata), AV_OPT_TYPE_DICT, {0}, 0, 0, AV_OPT_FLAG_EXPORT }, { "auth_type", "HTTP authentication type", OFFSET(auth_state.auth_type), AV_OPT_TYPE_INT, { .i64 = HTTP_AUTH_NONE }, HTTP_AUTH_NONE, HTTP_AUTH_BASIC, D | E, "auth_type"}, { "none", "No auth method set, autodetect", 0, AV_OPT_TYPE_CONST, { .i64 = HTTP_AUTH_NONE }, 0, 0, D | E, "auth_type"}, { "basic", "HTTP basic authentication", 0, AV_OPT_TYPE_CONST, { .i64 = HTTP_AUTH_BASIC }, 0, 0, D | E, "auth_type"}, { "send_expect_100", "Force sending an Expect: 100-continue header for POST", OFFSET(send_expect_100), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E }, { "location", "The actual location of the data received", OFFSET(location), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E }, { "offset", "initial byte offset", OFFSET(off), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, D }, { "end_offset", "try to limit the request to bytes preceding this offset", OFFSET(end_off), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, D }, { "method", "Override the HTTP method or set the expected HTTP method from a client", OFFSET(method), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E }, { "reconnect", "auto reconnect after disconnect before EOF", OFFSET(reconnect), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D }, { "reconnect_at_eof", "auto reconnect at EOF", OFFSET(reconnect_at_eof), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D }, { "reconnect_streamed", "auto reconnect streamed / non seekable streams", OFFSET(reconnect_streamed), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D }, { "reconnect_delay_max", "max reconnect delay in seconds after which to give up", OFFSET(reconnect_delay_max), AV_OPT_TYPE_INT, { .i64 = 120 }, 0, UINT_MAX/1000/1000, D }, { "listen", "listen on HTTP", OFFSET(listen), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 2, D | E }, { "resource", "The resource requested by a client", OFFSET(resource), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, E }, { "reply_code", "The http status code to return to a client", OFFSET(reply_code), AV_OPT_TYPE_INT, { .i64 = 200}, INT_MIN, 599, E}, { NULL } }; static int http_connect(URLContext *h, const char *path, const char *local_path, const char *hoststr, const char *auth, const char *proxyauth, int *new_location); static int http_read_header(URLContext *h, int *new_location); void ff_http_init_auth_state(URLContext *dest, const URLContext *src) { memcpy(&((HTTPContext *)dest->priv_data)->auth_state, &((HTTPContext *)src->priv_data)->auth_state, sizeof(HTTPAuthState)); memcpy(&((HTTPContext *)dest->priv_data)->proxy_auth_state, &((HTTPContext *)src->priv_data)->proxy_auth_state, sizeof(HTTPAuthState)); } static int http_open_cnx_internal(URLContext *h, AVDictionary **options) { const char *path, *proxy_path, *lower_proto = "tcp", *local_path; char hostname[1024], hoststr[1024], proto[10]; char auth[1024], proxyauth[1024] = ""; char path1[MAX_URL_SIZE]; char buf[1024], urlbuf[MAX_URL_SIZE]; int port, use_proxy, err, location_changed = 0; HTTPContext *s = h->priv_data; av_url_split(proto, sizeof(proto), auth, sizeof(auth), hostname, sizeof(hostname), &port, path1, sizeof(path1), s->location); ff_url_join(hoststr, sizeof(hoststr), NULL, NULL, hostname, port, NULL); proxy_path = s->http_proxy ? s->http_proxy : getenv("http_proxy"); use_proxy = !ff_http_match_no_proxy(getenv("no_proxy"), hostname) && proxy_path && av_strstart(proxy_path, "http://", NULL); if (!strcmp(proto, "https")) { lower_proto = "tls"; use_proxy = 0; if (port < 0) port = 443; } if (port < 0) port = 80; if (path1[0] == '\0') path = "/"; else path = path1; local_path = path; if (use_proxy) { /* Reassemble the request URL without auth string - we don't * want to leak the auth to the proxy. */ ff_url_join(urlbuf, sizeof(urlbuf), proto, NULL, hostname, port, "%s", path1); path = urlbuf; av_url_split(NULL, 0, proxyauth, sizeof(proxyauth), hostname, sizeof(hostname), &port, NULL, 0, proxy_path); } ff_url_join(buf, sizeof(buf), lower_proto, NULL, hostname, port, NULL); if (!s->hd) { err = ffurl_open_whitelist(&s->hd, buf, AVIO_FLAG_READ_WRITE, &h->interrupt_callback, options, h->protocol_whitelist, h->protocol_blacklist, h); if (err < 0) return err; } err = http_connect(h, path, local_path, hoststr, auth, proxyauth, &location_changed); if (err < 0) return err; return location_changed; } /* return non zero if error */ static int http_open_cnx(URLContext *h, AVDictionary **options) { HTTPAuthType cur_auth_type, cur_proxy_auth_type; HTTPContext *s = h->priv_data; int location_changed, attempts = 0, redirects = 0; redo: av_dict_copy(options, s->chained_options, 0); cur_auth_type = s->auth_state.auth_type; cur_proxy_auth_type = s->auth_state.auth_type; location_changed = http_open_cnx_internal(h, options); if (location_changed < 0) goto fail; attempts++; if (s->http_code == 401) { if ((cur_auth_type == HTTP_AUTH_NONE || s->auth_state.stale) && s->auth_state.auth_type != HTTP_AUTH_NONE && attempts < 4) { ffurl_closep(&s->hd); goto redo; } else goto fail; } if (s->http_code == 407) { if ((cur_proxy_auth_type == HTTP_AUTH_NONE || s->proxy_auth_state.stale) && s->proxy_auth_state.auth_type != HTTP_AUTH_NONE && attempts < 4) { ffurl_closep(&s->hd); goto redo; } else goto fail; } if ((s->http_code == 301 || s->http_code == 302 || s->http_code == 303 || s->http_code == 307) && location_changed == 1) { /* url moved, get next */ ffurl_closep(&s->hd); if (redirects++ >= MAX_REDIRECTS) return AVERROR(EIO); /* Restart the authentication process with the new target, which * might use a different auth mechanism. */ memset(&s->auth_state, 0, sizeof(s->auth_state)); attempts = 0; location_changed = 0; goto redo; } return 0; fail: if (s->hd) ffurl_closep(&s->hd); if (location_changed < 0) return location_changed; return ff_http_averror(s->http_code, AVERROR(EIO)); } int ff_http_do_new_request(URLContext *h, const char *uri) { HTTPContext *s = h->priv_data; AVDictionary *options = NULL; int ret; s->off = 0; s->icy_data_read = 0; av_free(s->location); s->location = av_strdup(uri); if (!s->location) return AVERROR(ENOMEM); ret = http_open_cnx(h, &options); av_dict_free(&options); return ret; } int ff_http_averror(int status_code, int default_averror) { switch (status_code) { case 400: return AVERROR_HTTP_BAD_REQUEST; case 401: return AVERROR_HTTP_UNAUTHORIZED; case 403: return AVERROR_HTTP_FORBIDDEN; case 404: return AVERROR_HTTP_NOT_FOUND; default: break; } if (status_code >= 400 && status_code <= 499) return AVERROR_HTTP_OTHER_4XX; else if (status_code >= 500) return AVERROR_HTTP_SERVER_ERROR; else return default_averror; } static int http_write_reply(URLContext* h, int status_code) { int ret, body = 0, reply_code, message_len; const char *reply_text, *content_type; HTTPContext *s = h->priv_data; char message[BUFFER_SIZE]; content_type = "text/plain"; if (status_code < 0) body = 1; switch (status_code) { case AVERROR_HTTP_BAD_REQUEST: case 400: reply_code = 400; reply_text = "Bad Request"; break; case AVERROR_HTTP_FORBIDDEN: case 403: reply_code = 403; reply_text = "Forbidden"; break; case AVERROR_HTTP_NOT_FOUND: case 404: reply_code = 404; reply_text = "Not Found"; break; case 200: reply_code = 200; reply_text = "OK"; content_type = s->content_type ? s->content_type : "application/octet-stream"; break; case AVERROR_HTTP_SERVER_ERROR: case 500: reply_code = 500; reply_text = "Internal server error"; break; default: return AVERROR(EINVAL); } if (body) { s->chunked_post = 0; message_len = snprintf(message, sizeof(message), "HTTP/1.1 %03d %s\r\n" "Content-Type: %s\r\n" "Content-Length: %"SIZE_SPECIFIER"\r\n" "%s" "\r\n" "%03d %s\r\n", reply_code, reply_text, content_type, strlen(reply_text) + 6, // 3 digit status code + space + \r\n s->headers ? s->headers : "", reply_code, reply_text); } else { s->chunked_post = 1; message_len = snprintf(message, sizeof(message), "HTTP/1.1 %03d %s\r\n" "Content-Type: %s\r\n" "Transfer-Encoding: chunked\r\n" "%s" "\r\n", reply_code, reply_text, content_type, s->headers ? s->headers : ""); } av_log(h, AV_LOG_TRACE, "HTTP reply header: \n%s----\n", message); if ((ret = ffurl_write(s->hd, message, message_len)) < 0) return ret; return 0; } static void handle_http_errors(URLContext *h, int error) { av_assert0(error < 0); http_write_reply(h, error); } static int http_handshake(URLContext *c) { int ret, err, new_location; HTTPContext *ch = c->priv_data; URLContext *cl = ch->hd; switch (ch->handshake_step) { case LOWER_PROTO: av_log(c, AV_LOG_TRACE, "Lower protocol\n"); if ((ret = ffurl_handshake(cl)) > 0) return 2 + ret; if (ret < 0) return ret; ch->handshake_step = READ_HEADERS; ch->is_connected_server = 1; return 2; case READ_HEADERS: av_log(c, AV_LOG_TRACE, "Read headers\n"); if ((err = http_read_header(c, &new_location)) < 0) { handle_http_errors(c, err); return err; } ch->handshake_step = WRITE_REPLY_HEADERS; return 1; case WRITE_REPLY_HEADERS: av_log(c, AV_LOG_TRACE, "Reply code: %d\n", ch->reply_code); if ((err = http_write_reply(c, ch->reply_code)) < 0) return err; ch->handshake_step = FINISH; return 1; case FINISH: return 0; } // this should never be reached. return AVERROR(EINVAL); } static int http_listen(URLContext *h, const char *uri, int flags, AVDictionary **options) { HTTPContext *s = h->priv_data; int ret; char hostname[1024], proto[10]; char lower_url[100]; const char *lower_proto = "tcp"; int port; av_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname), &port, NULL, 0, uri); if (!strcmp(proto, "https")) lower_proto = "tls"; ff_url_join(lower_url, sizeof(lower_url), lower_proto, NULL, hostname, port, NULL); if ((ret = av_dict_set_int(options, "listen", s->listen, 0)) < 0) goto fail; if ((ret = ffurl_open_whitelist(&s->hd, lower_url, AVIO_FLAG_READ_WRITE, &h->interrupt_callback, options, h->protocol_whitelist, h->protocol_blacklist, h )) < 0) goto fail; s->handshake_step = LOWER_PROTO; if (s->listen == HTTP_SINGLE) { /* single client */ s->reply_code = 200; while ((ret = http_handshake(h)) > 0); } fail: av_dict_free(&s->chained_options); return ret; } static int http_open(URLContext *h, const char *uri, int flags, AVDictionary **options) { HTTPContext *s = h->priv_data; int ret; if( s->seekable == 1 ) h->is_streamed = 0; else h->is_streamed = 1; s->filesize = UINT64_MAX; s->location = av_strdup(uri); if (!s->location) return AVERROR(ENOMEM); if (options) av_dict_copy(&s->chained_options, *options, 0);