/* * 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/bprint.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 + HTTP_HEADERS_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; int chunkend; uint64_t off, end_off, filesize; char *location; HTTPAuthState auth_state; HTTPAuthState proxy_auth_state; char *http_proxy; char *headers; char *mime_type; char *http_version; char *user_agent; char *referer; 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; /* -1 = try to send if applicable, 0 = always disabled, 1 = always enabled */ int send_expect_100; char *method; int reconnect; int reconnect_at_eof; int reconnect_on_network_error; int reconnect_streamed; int reconnect_delay_max; char *reconnect_on_http_error; 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 }, { "referer", "override referer header", OFFSET(referer), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D }, { "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 }, { "http_version", "export the http response version", OFFSET(http_version), 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 = -1 }, -1, 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_on_network_error", "auto reconnect in case of tcp/tls error during connect", OFFSET(reconnect_on_network_error), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D }, { "reconnect_on_http_error", "list of http status codes to reconnect on", OFFSET(reconnect_on_http_error), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, 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);static int http_shutdown(URLContext *h, int flags); 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 *hashmark; char hostname[1024], hoststr[1024], proto[10]; char auth[1024], proxyauth[1024] = ""; char path1[MAX_URL_SIZE], sanitized_path[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; /* pass http_proxy to underlying protocol */ if (s->http_proxy) { err = av_dict_set(options, "http_proxy", s->http_proxy, 0); if (err < 0) return err; } } if (port < 0) port = 80; hashmark = strchr(path1, '#'); if (hashmark) *hashmark = '\0'; if (path1[0] == '\0') { path = "/"; } else if (path1[0] == '?') { snprintf(sanitized_path, sizeof(sanitized_path), "/%s", path1); path = sanitized_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;} static int http_should_reconnect(HTTPContext *s, int err){ const char *status_group; char http_code[4]; switch (err) { case AVERROR_HTTP_BAD_REQUEST: case AVERROR_HTTP_UNAUTHORIZED: case AVERROR_HTTP_FORBIDDEN: case AVERROR_HTTP_NOT_FOUND: case AVERROR_HTTP_OTHER_4XX: status_group = "4xx"; break; case AVERROR_HTTP_SERVER_ERROR: status_group = "5xx"; break; default: return s->reconnect_on_network_error; } if (!s->reconnect_on_http_error) return 0; if (av_match_list(status_group, s->reconnect_on_http_error, ',') > 0) return 1; snprintf(http_code, sizeof(http_code), "%d", s->http_code); return av_match_list(http_code, s->reconnect_on_http_error, ',') > 0;} /* 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; int reconnect_delay = 0; uint64_t off; 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; off = s->off; location_changed = http_open_cnx_internal(h, options); if (location_changed < 0) { if (!http_should_reconnect(s, location_changed) || reconnect_delay > s->reconnect_delay_max) goto fail; av_log(h, AV_LOG_WARNING, "Will reconnect at %"PRIu64" in %d second(s).\n", off, reconnect_delay); location_changed = ff_network_sleep_interruptible(1000U * 1000 * reconnect_delay, &h->interrupt_callback); if (location_changed != AVERROR(ETIMEDOUT)) goto fail; reconnect_delay = 1 + 2 * reconnect_delay; /* restore the offset (http_connect resets it) */ s->off = off; ffurl_closep(&s->hd); goto redo; } 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 || s->http_code == 308) && 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_get_shutdown_status(URLContext *h){ int ret = 0; HTTPContext *s = h->priv_data; /* flush the receive buffer when it is write only mode */ char buf[1024]; int read_ret; read_ret = ffurl_read(s->hd, buf, sizeof(buf)); if (read_ret < 0) { ret = read_ret; } return ret;} int ff_http_do_new_request(URLContext *h, const char *uri) { return ff_http_do_new_request2(h, uri, NULL);} int ff_http_do_new_request2(URLContext *h, const char *uri, AVDictionary **opts){ HTTPContext *s = h->priv_data; AVDictionary *options = NULL; int ret; char hostname1[1024], hostname2[1024], proto1[10], proto2[10]; int port1, port2; if (!h->prot || !(!strcmp(h->prot->name, "http") || !strcmp(h->prot->name, "https"))) return AVERROR(EINVAL); av_url_split(proto1, sizeof(proto1), NULL, 0, hostname1, sizeof(hostname1), &port1, NULL, 0, s->location); av_url_split(proto2, sizeof(proto2), NULL, 0, hostname2, sizeof(hostname2), &port2, NULL, 0, uri); if (port1 != port2 || strncmp(hostname1, hostname2, sizeof(hostname2)) != 0) { av_log(h, AV_LOG_ERROR, "Cannot reuse HTTP connection for different host: %s:%d != %s:%d\n", hostname1, port1, hostname2, port2 ); return AVERROR(EINVAL); } if (!s->end_chunked_post) { ret = http_shutdown(h, h->flags); if (ret < 0) return ret; } if (s->willclose) return AVERROR_EOF; s->end_chunked_post = 0; s->chunkend = 0; s->off = 0; s->icy_data_read = 0; av_free(s->location); s->location = av_strdup(uri); if (!s->location) return AVERROR(ENOMEM); if ((ret = av_opt_set_dict(s, opts)) < 0) return ret; av_log(s, AV_LOG_INFO, "Opening \'%s\' for %s\n", uri, h->flags & AVIO_FLAG_WRITE ? "writing" : "reading"); 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:Lines 1–500 of 2007. 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/bprint.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 + HTTP_HEADERS_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; int chunkend; uint64_t off, end_off, filesize; char *location; HTTPAuthState auth_state; HTTPAuthState proxy_auth_state; char *http_proxy; char *headers; char *mime_type; char *http_version; char *user_agent; char *referer; 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; /* -1 = try to send if applicable, 0 = always disabled, 1 = always enabled */ int send_expect_100; char *method; int reconnect; int reconnect_at_eof; int reconnect_on_network_error; int reconnect_streamed; int reconnect_delay_max; char *reconnect_on_http_error; 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 }, { "referer", "override referer header", OFFSET(referer), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D }, { "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 }, { "http_version", "export the http response version", OFFSET(http_version), 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 = -1 }, -1, 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_on_network_error", "auto reconnect in case of tcp/tls error during connect", OFFSET(reconnect_on_network_error), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D }, { "reconnect_on_http_error", "list of http status codes to reconnect on", OFFSET(reconnect_on_http_error), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, 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); static int http_shutdown(URLContext *h, int flags); 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 *hashmark; char hostname[1024], hoststr[1024], proto[10]; char auth[1024], proxyauth[1024] = ""; char path1[MAX_URL_SIZE], sanitized_path[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; /* pass http_proxy to underlying protocol */ if (s->http_proxy) { err = av_dict_set(options, "http_proxy", s->http_proxy, 0); if (err < 0) return err; } } if (port < 0) port = 80; hashmark = strchr(path1, '#'); if (hashmark) *hashmark = '\0'; if (path1[0] == '\0') { path = "/"; } else if (path1[0] == '?') { snprintf(sanitized_path, sizeof(sanitized_path), "/%s", path1); path = sanitized_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; } static int http_should_reconnect(HTTPContext *s, int err) { const char *status_group; char http_code[4]; switch (err) { case AVERROR_HTTP_BAD_REQUEST: case AVERROR_HTTP_UNAUTHORIZED: case AVERROR_HTTP_FORBIDDEN: case AVERROR_HTTP_NOT_FOUND: case AVERROR_HTTP_OTHER_4XX: status_group = "4xx"; break; case AVERROR_HTTP_SERVER_ERROR: status_group = "5xx"; break; default: return s->reconnect_on_network_error; } if (!s->reconnect_on_http_error) return 0; if (av_match_list(status_group, s->reconnect_on_http_error, ',') > 0) return 1; snprintf(http_code, sizeof(http_code), "%d", s->http_code); return av_match_list(http_code, s->reconnect_on_http_error, ',') > 0; } /* 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; int reconnect_delay = 0; uint64_t off; 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; off = s->off; location_changed = http_open_cnx_internal(h, options); if (location_changed < 0) { if (!http_should_reconnect(s, location_changed) || reconnect_delay > s->reconnect_delay_max) goto fail; av_log(h, AV_LOG_WARNING, "Will reconnect at %"PRIu64" in %d second(s).\n", off, reconnect_delay); location_changed = ff_network_sleep_interruptible(1000U * 1000 * reconnect_delay, &h->interrupt_callback); if (location_changed != AVERROR(ETIMEDOUT)) goto fail; reconnect_delay = 1 + 2 * reconnect_delay; /* restore the offset (http_connect resets it) */ s->off = off; ffurl_closep(&s->hd); goto redo; } 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 || s->http_code == 308) && 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_get_shutdown_status(URLContext *h) { int ret = 0; HTTPContext *s = h->priv_data; /* flush the receive buffer when it is write only mode */ char buf[1024]; int read_ret; read_ret = ffurl_read(s->hd, buf, sizeof(buf)); if (read_ret < 0) { ret = read_ret; } return ret; } int ff_http_do_new_request(URLContext *h, const char *uri) { return ff_http_do_new_request2(h, uri, NULL); } int ff_http_do_new_request2(URLContext *h, const char *uri, AVDictionary **opts) { HTTPContext *s = h->priv_data; AVDictionary *options = NULL; int ret; char hostname1[1024], hostname2[1024], proto1[10], proto2[10]; int port1, port2; if (!h->prot || !(!strcmp(h->prot->name, "http") || !strcmp(h->prot->name, "https"))) return AVERROR(EINVAL); av_url_split(proto1, sizeof(proto1), NULL, 0, hostname1, sizeof(hostname1), &port1, NULL, 0, s->location); av_url_split(proto2, sizeof(proto2), NULL, 0, hostname2, sizeof(hostname2), &port2, NULL, 0, uri); if (port1 != port2 || strncmp(hostname1, hostname2, sizeof(hostname2)) != 0) { av_log(h, AV_LOG_ERROR, "Cannot reuse HTTP connection for different host: %s:%d != %s:%d\n", hostname1, port1, hostname2, port2 ); return AVERROR(EINVAL); } if (!s->end_chunked_post) { ret = http_shutdown(h, h->flags); if (ret < 0) return ret; } if (s->willclose) return AVERROR_EOF; s->end_chunked_post = 0; s->chunkend = 0; s->off = 0; s->icy_data_read = 0; av_free(s->location); s->location = av_strdup(uri); if (!s->location) return AVERROR(ENOMEM); if ((ret = av_opt_set_dict(s, opts)) < 0) return ret; av_log(s, AV_LOG_INFO, "Opening \'%s\' for %s\n", uri, h->flags & AVIO_FLAG_WRITE ? "writing" : "reading"); 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: