2007-02-28 03:40:23 +00:00
/*
* HTTP protocol for ffmpeg client
2009-01-19 15:46:40 +00:00
* Copyright (c) 2000, 2001 Fabrice Bellard
2007-02-28 03:40:23 +00:00
*
* 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
*/
2008-05-09 11:56:36 +00:00
2020-07-27 11:46:56 +02:00
#include <stdbool.h>
2014-07-22 11:42:03 -07:00
#include "config.h"
2022-02-23 14:56:49 +02:00
#include "config_components.h"
2007-02-28 03:40:23 +00:00
2025-05-22 20:14:49 +02:00
#include <string.h>
2023-09-02 12:11:59 +02:00
#include <time.h>
2013-07-23 04:07:10 +08:00
#if CONFIG_ZLIB
#include <zlib.h>
2014-07-22 11:42:03 -07:00
#endif /* CONFIG_ZLIB */
2015-07-03 02:28:56 +02:00
#include "libavutil/avassert.h"
2014-07-22 11:42:03 -07:00
#include "libavutil/avstring.h"
2020-02-08 00:08:54 +01:00
#include "libavutil/bprint.h"
2022-06-20 13:30:00 +03:00
#include "libavutil/getenv_utf8.h"
2024-04-22 15:25:42 +01:00
#include "libavutil/macros.h"
2024-03-25 01:30:37 +01:00
#include "libavutil/mem.h"
2014-07-22 11:42:03 -07:00
#include "libavutil/opt.h"
2015-09-06 20:03:46 +02:00
#include "libavutil/time.h"
2017-04-30 14:25:29 -04:00
#include "libavutil/parseutils.h"
2014-07-22 11:42:03 -07:00
#include "avformat.h"
#include "http.h"
#include "httpauth.h"
#include "internal.h"
#include "network.h"
#include "os_support.h"
#include "url.h"
2022-06-24 22:59:03 -03:00
#include "version.h"
2013-07-23 04:07:10 +08:00
2007-04-07 14:09:20 +00:00
/* XXX: POST protocol is not completely implemented because ffmpeg uses
2014-07-22 11:42:03 -07:00
* only a subset of it. */
2007-02-28 03:40:23 +00:00
2012-09-22 21:17:36 +01:00
/* 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
2014-07-22 11:42:03 -07:00
* path names). */
2020-05-10 23:05:51 -06:00
#define BUFFER_SIZE (MAX_URL_SIZE + HTTP_HEADERS_SIZE)
2007-02-28 03:40:23 +00:00
#define MAX_REDIRECTS 8
2022-01-10 21:44:27 +02:00
#define MAX_CACHED_REDIRECTS 32
2015-07-03 02:28:56 +02:00
#define HTTP_SINGLE 1
#define HTTP_MUTLI 2
2024-04-25 14:48:15 +01:00
#define MAX_DATE_LEN 19
2017-04-30 14:25:29 -04:00
#define WHITESPACES " \n\t\r"
2015-07-03 02:28:56 +02:00
typedef enum {
LOWER_PROTO ,
READ_HEADERS ,
WRITE_REPLY_HEADERS ,
FINISH
} HandshakeState ;
2007-02-28 03:40:23 +00:00
2014-09-22 09:19:33 +02:00
typedef struct HTTPContext {
2010-06-22 14:13:55 +00:00
const AVClass * class ;
2007-02-28 03:40:23 +00:00
URLContext * hd ;
unsigned char buffer [ BUFFER_SIZE ], * buf_ptr , * buf_end ;
int line_count ;
int http_code ;
2014-03-10 17:53:51 +01:00
/* Used if "Transfer-Encoding: chunked" otherwise -1. */
2016-12-05 08:02:33 -05:00
uint64_t chunksize ;
2017-11-13 11:34:50 -08:00
int chunkend ;
2026-01-23 10:37:02 +01:00
uint64_t off , end_off , filesize , range_end ;
2021-12-27 11:20:24 +02:00
char * uri ;
2013-11-21 11:25:16 +02:00
char * location ;
2010-03-24 22:32:05 +00:00
HTTPAuthState auth_state ;
2011-11-11 11:21:42 +02:00
HTTPAuthState proxy_auth_state ;
2015-12-27 12:21:43 +00:00
char * http_proxy ;
2011-11-07 11:43:13 +02:00
char * headers ;
2014-03-06 18:39:58 +01:00
char * mime_type ;
2017-12-25 20:21:15 -08:00
char * http_version ;
2014-03-06 18:39:59 +01:00
char * user_agent ;
2018-02-01 10:56:51 +08:00
char * referer ;
2014-03-06 18:40:03 +01:00
char * content_type ;
2014-03-10 17:53:51 +01:00
/* Set if the server correctly handles Connection: close and will close
* the connection after feeding us the content. */
int willclose ;
2012-10-02 22:22:44 +01:00
int seekable ; /**< Control seekability, 0 = disable, 1 = enable, -1 = probe. */
2011-11-10 11:03:35 +02:00
int chunked_post ;
2014-03-10 17:53:51 +01:00
/* 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 ;
2012-05-30 11:27:18 +02:00
uint8_t * post_data ;
int post_datalen ;
2012-06-03 18:34:53 +02:00
int is_akamai ;
2013-11-03 18:17:45 +01:00
int is_mediagateway ;
2013-01-13 21:32:57 -05:00
char * cookies ; ///< holds newline (\n) delimited Set-Cookie header field values (without the "Set-Cookie: " field name)
2015-03-17 20:22:59 +11:00
/* A dictionary containing cookies keyed by cookie name */
AVDictionary * cookie_dict ;
2013-06-26 00:53:26 +02:00
int icy ;
2014-03-10 21:11:35 +01:00
/* how much data was read since the last ICY metadata packet */
2016-12-05 08:02:33 -05:00
uint64_t icy_data_read ;
2014-03-10 21:11:35 +01:00
/* after how many bytes of read data a new metadata packet will be found */
2016-12-05 08:02:33 -05:00
uint64_t icy_metaint ;
2013-06-26 00:53:26 +02:00
char * icy_metadata_headers ;
char * icy_metadata_packet ;
2014-07-31 19:56:35 -04:00
AVDictionary * metadata ;
2013-07-23 04:07:10 +08:00
#if CONFIG_ZLIB
int compressed ;
z_stream inflate_stream ;
uint8_t * inflate_buffer ;
2014-07-22 11:42:03 -07:00
#endif /* CONFIG_ZLIB */
2011-11-07 11:06:50 +02:00
AVDictionary * chained_options ;
2019-03-30 21:35:51 -07:00
/* -1 = try to send if applicable, 0 = always disabled, 1 = always enabled */
2013-10-09 13:24:40 +03:00
int send_expect_100 ;
2014-07-18 00:39:43 +02:00
char * method ;
2015-03-12 11:39:55 +08:00
int reconnect ;
2015-09-06 19:58:10 +02:00
int reconnect_at_eof ;
2020-12-03 10:42:52 +02:00
int reconnect_on_network_error ;
2015-09-06 19:58:10 +02:00
int reconnect_streamed ;
2015-09-07 19:38:16 +02:00
int reconnect_delay_max ;
2020-12-03 10:42:52 +02:00
char * reconnect_on_http_error ;
2015-04-02 22:49:07 +02:00
int listen ;
2015-07-03 02:28:56 +02:00
char * resource ;
int reply_code ;
int is_multi_client ;
HandshakeState handshake_step ;
int is_connected_server ;
2021-10-26 16:03:59 +01:00
int short_seek_size ;
2022-01-10 21:44:27 +02:00
int64_t expires ;
char * new_location ;
AVDictionary * redirect_cache ;
2022-02-02 10:39:07 -05:00
uint64_t filesize_from_content_range ;
2024-04-22 15:25:42 +01:00
int respect_retry_after ;
unsigned int retry_after ;
2024-04-22 15:25:44 +01:00
int reconnect_max_retries ;
2024-04-22 15:25:45 +01:00
int reconnect_delay_total_max ;
2026-01-23 11:19:17 +01:00
uint64_t initial_request_size ;
2026-02-09 16:56:25 +01:00
uint64_t request_size ;
int initial_requests ; /* whether or not to limit requests to initial_request_size */
2026-01-26 11:54:35 +01:00
/* Connection statistics */
int nb_connections ;
int nb_requests ;
int nb_retries ;
int nb_reconnects ;
int nb_redirects ;
2026-02-15 18:03:38 +01:00
int64_t sum_latency ; /* divide by nb_requests */
int64_t max_latency ;
2026-01-14 03:21:46 +01:00
int max_redirects ;
2007-02-28 03:40:23 +00:00
} HTTPContext ;
2010-06-22 14:13:55 +00:00
#define OFFSET(x) offsetof(HTTPContext, x)
2011-11-10 09:34:58 +01:00
#define D AV_OPT_FLAG_DECODING_PARAM
#define E AV_OPT_FLAG_ENCODING_PARAM
2013-06-17 17:03:15 +02:00
#define DEFAULT_USER_AGENT "Lavf/" AV_STRINGIFY(LIBAVFORMAT_VERSION)
2014-07-22 11:42:03 -07:00
2026-02-21 16:49:51 +01:00
static const AVOption http_options [] = {
2015-11-21 22:05:07 +01:00
{ "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 },
2015-12-27 12:21:43 +00:00
{ "http_proxy" , "set HTTP proxy to tunnel through" , OFFSET ( http_proxy ), AV_OPT_TYPE_STRING , { . str = NULL }, 0 , 0 , D | E },
2015-10-31 14:42:29 +01:00
{ "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 },
2014-07-22 11:42:03 -07:00
{ "user_agent" , "override User-Agent header" , OFFSET ( user_agent ), AV_OPT_TYPE_STRING , { . str = DEFAULT_USER_AGENT }, 0 , 0 , D },
2018-02-01 10:56:51 +08:00
{ "referer" , "override referer header" , OFFSET ( referer ), AV_OPT_TYPE_STRING , { . str = NULL }, 0 , 0 , D },
2015-11-21 22:05:07 +01:00
{ "multiple_requests" , "use persistent connections" , OFFSET ( multiple_requests ), AV_OPT_TYPE_BOOL , { . i64 = 0 }, 0 , 1 , D | E },
2026-02-09 16:56:25 +01:00
{ "request_size" , "size (in bytes) of requests to make" , OFFSET ( request_size ), AV_OPT_TYPE_INT64 , { . i64 = 0 }, 0 , INT64_MAX , D },
2026-01-23 11:19:17 +01:00
{ "initial_request_size" , "size (in bytes) of initial requests made during probing / header parsing" , OFFSET ( initial_request_size ), AV_OPT_TYPE_INT64 , { . i64 = 0 }, 0 , INT64_MAX , D },
2014-07-22 11:42:03 -07:00
{ "post_data" , "set custom HTTP post data" , OFFSET ( post_data ), AV_OPT_TYPE_BINARY , . flags = D | E },
2015-10-31 14:42:29 +01:00
{ "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 },
2017-12-25 20:21:15 -08:00
{ "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 },
2015-10-31 14:42:29 +01:00
{ "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 },
2015-11-21 22:05:07 +01:00
{ "icy" , "request ICY metadata" , OFFSET ( icy ), AV_OPT_TYPE_BOOL , { . i64 = 1 }, 0 , 1 , D },
2015-10-31 14:42:29 +01:00
{ "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 },
2014-07-31 19:56:35 -04:00
{ "metadata" , "metadata read from the bitstream" , OFFSET ( metadata ), AV_OPT_TYPE_DICT , { 0 }, 0 , 0 , AV_OPT_FLAG_EXPORT },
2024-02-11 15:41:05 +01:00
{ "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 , . unit = "auth_type" },
{ "none" , "No auth method set, autodetect" , 0 , AV_OPT_TYPE_CONST , { . i64 = HTTP_AUTH_NONE }, 0 , 0 , D | E , . unit = "auth_type" },
{ "basic" , "HTTP basic authentication" , 0 , AV_OPT_TYPE_CONST , { . i64 = HTTP_AUTH_BASIC }, 0 , 0 , D | E , . unit = "auth_type" },
2019-03-30 21:35:51 -07:00
{ "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 },
2015-10-31 14:42:29 +01:00
{ "location" , "The actual location of the data received" , OFFSET ( location ), AV_OPT_TYPE_STRING , { . str = NULL }, 0 , 0 , D | E },
2014-07-22 11:42:03 -07:00
{ "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 },
2015-06-04 01:16:59 +02:00
{ "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 },
2015-11-21 22:05:07 +01:00
{ "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 },
2020-12-03 10:42:52 +02:00
{ "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 },
2015-11-21 22:05:07 +01:00
{ "reconnect_streamed" , "auto reconnect streamed / non seekable streams" , OFFSET ( reconnect_streamed ), AV_OPT_TYPE_BOOL , { . i64 = 0 }, 0 , 1 , D },
2015-09-07 19:38:16 +02:00
{ "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 },
2024-04-22 15:25:44 +01:00
{ "reconnect_max_retries" , "the max number of times to retry a connection" , OFFSET ( reconnect_max_retries ), AV_OPT_TYPE_INT , { . i64 = - 1 }, - 1 , INT_MAX , D },
2024-04-22 15:25:45 +01:00
{ "reconnect_delay_total_max" , "max total reconnect delay in seconds after which to give up" , OFFSET ( reconnect_delay_total_max ), AV_OPT_TYPE_INT , { . i64 = 256 }, 0 , UINT_MAX / 1000 / 1000 , D },
2024-04-22 15:25:42 +01:00
{ "respect_retry_after" , "respect the Retry-After header when retrying connections" , OFFSET ( respect_retry_after ), AV_OPT_TYPE_BOOL , { . i64 = 1 }, 0 , 1 , D },
2015-07-03 02:28:56 +02:00
{ "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 },
2021-10-26 16:03:59 +01:00
{ "short_seek_size" , "Threshold to favor readahead over seek." , OFFSET ( short_seek_size ), AV_OPT_TYPE_INT , { . i64 = 0 }, 0 , INT_MAX , D },
2026-01-14 03:21:46 +01:00
{ "max_redirects" , "Maximum number of redirects" , OFFSET ( max_redirects ), AV_OPT_TYPE_INT , { . i64 = MAX_REDIRECTS }, 0 , INT_MAX , D },
2014-07-22 11:42:03 -07:00
{ NULL }
2010-06-22 14:13:55 +00:00
};
2011-11-05 12:54:01 +01:00
2011-11-11 11:21:42 +02:00
static int http_connect ( URLContext * h , const char * path , const char * local_path ,
const char * hoststr , const char * auth ,
2022-01-10 21:44:27 +02:00
const char * proxyauth );
static int http_read_header ( URLContext * h );
2017-11-29 14:32:16 +08:00
static int http_shutdown ( URLContext * h , int flags );
2007-02-28 03:40:23 +00:00
2010-06-21 19:40:30 +00:00
void ff_http_init_auth_state ( URLContext * dest , const URLContext * src )
{
2014-07-22 11:42:03 -07:00
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 ,
2011-11-11 11:21:42 +02:00
sizeof ( HTTPAuthState ));
2010-06-21 19:40:30 +00:00
}
2014-08-02 13:29:02 +02:00
static int http_open_cnx_internal ( URLContext * h , AVDictionary ** options )
2007-02-28 03:40:23 +00:00
{
2011-11-11 11:21:42 +02:00
const char * path , * proxy_path , * lower_proto = "tcp" , * local_path ;
2022-06-20 13:30:00 +03:00
char * env_http_proxy , * env_no_proxy ;
2020-02-03 23:06:09 +01:00
char * hashmark ;
2025-05-22 20:14:49 +02:00
char hostname [ 1024 ], hoststr [ 1024 ], proto [ 10 ], tmp_host [ 1024 ];
2011-12-01 11:24:23 +02:00
char auth [ 1024 ], proxyauth [ 1024 ] = "" ;
2021-06-09 17:01:02 -03:00
char path1 [ MAX_URL_SIZE ], sanitized_path [ MAX_URL_SIZE + 1 ];
2012-09-22 21:17:36 +01:00
char buf [ 1024 ], urlbuf [ MAX_URL_SIZE ];
2022-06-20 13:30:00 +03:00
int port , use_proxy , err = 0 ;
2007-02-28 03:40:23 +00:00
HTTPContext * s = h -> priv_data ;
2011-02-06 00:20:26 +02:00
av_url_split ( proto , sizeof ( proto ), auth , sizeof ( auth ),
hostname , sizeof ( hostname ), & port ,
2010-03-08 09:05:03 +00:00
path1 , sizeof ( path1 ), s -> location );
2025-05-22 20:14:49 +02:00
av_strlcpy ( tmp_host , hostname , sizeof ( tmp_host ));
// In case of an IPv6 address, we need to strip the Zone ID,
// if any. We do it at the first % sign, as percent encoding
// can be used in the Zone ID itself.
if ( strchr ( tmp_host , ':' ))
tmp_host [ strcspn ( tmp_host , "%" )] = '\0' ;
ff_url_join ( hoststr , sizeof ( hoststr ), NULL , NULL , tmp_host , port , NULL );
2007-02-28 03:40:23 +00:00
2022-06-20 13:30:00 +03:00
env_http_proxy = getenv_utf8 ( "http_proxy" );
proxy_path = s -> http_proxy ? s -> http_proxy : env_http_proxy ;
env_no_proxy = getenv_utf8 ( "no_proxy" );
use_proxy = ! ff_http_match_no_proxy ( env_no_proxy , hostname ) &&
2014-08-14 16:31:25 -04:00
proxy_path && av_strstart ( proxy_path , "http://" , NULL );
2022-06-20 13:30:00 +03:00
freeenv_utf8 ( env_no_proxy );
2013-02-27 11:13:47 +02:00
2026-01-06 23:30:47 +01:00
if ( h -> protocol_whitelist && av_match_list ( proto , h -> protocol_whitelist , ',' ) <= 0 ) {
av_log ( h , AV_LOG_ERROR , "Protocol '%s' not on whitelist '%s'! \n " , proto , h -> protocol_whitelist );
return AVERROR ( EINVAL );
}
if ( h -> protocol_blacklist && av_match_list ( proto , h -> protocol_blacklist , ',' ) > 0 ) {
av_log ( h , AV_LOG_ERROR , "Protocol '%s' on blacklist '%s'! \n " , proto , h -> protocol_blacklist );
return AVERROR ( EINVAL );
}
2011-11-10 14:55:18 +02:00
if ( ! strcmp ( proto , "https" )) {
lower_proto = "tls" ;
2014-07-22 11:42:03 -07:00
use_proxy = 0 ;
2011-11-10 14:55:18 +02:00
if ( port < 0 )
port = 443 ;
2020-08-23 13:53:39 +02:00
/* pass http_proxy to underlying protocol */
if ( s -> http_proxy ) {
err = av_dict_set ( options , "http_proxy" , s -> http_proxy , 0 );
if ( err < 0 )
2022-06-20 13:30:00 +03:00
goto end ;
2020-08-23 13:53:39 +02:00
}
2026-01-06 23:29:18 +01:00
} else if ( strcmp ( proto , "http" )) {
err = AVERROR ( EINVAL );
goto end ;
2011-11-10 14:55:18 +02:00
}
2026-01-06 23:29:18 +01:00
2011-11-10 14:55:18 +02:00
if ( port < 0 )
port = 80 ;
2020-02-03 23:06:09 +01:00
hashmark = strchr ( path1 , '#' );
if ( hashmark )
* hashmark = '\0' ;
2020-02-03 23:29:08 +01:00
if ( path1 [ 0 ] == '\0' ) {
2011-11-11 11:21:42 +02:00
path = "/" ;
2020-02-03 23:29:08 +01:00
} else if ( path1 [ 0 ] == '?' ) {
snprintf ( sanitized_path , sizeof ( sanitized_path ), "/%s" , path1 );
path = sanitized_path ;
} else {
2011-11-11 11:21:42 +02:00
path = path1 ;
2020-02-03 23:29:08 +01:00
}
2011-11-11 11:21:42 +02:00
local_path = path ;
2007-02-28 03:40:23 +00:00
if ( use_proxy ) {
2011-11-11 11:21:42 +02:00
/* 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 );
2007-02-28 03:40:23 +00:00
}
2011-02-06 00:20:26 +02:00
ff_url_join ( buf , sizeof ( buf ), lower_proto , NULL , hostname , port , NULL );
2007-02-28 03:40:23 +00:00
2012-05-28 15:03:54 +02:00
if ( ! s -> hd ) {
2026-01-26 11:54:35 +01:00
s -> nb_connections ++ ;
2016-01-30 02:17:51 +01:00
err = ffurl_open_whitelist ( & s -> hd , buf , AVIO_FLAG_READ_WRITE ,
& h -> interrupt_callback , options ,
2016-04-21 15:55:09 +01:00
h -> protocol_whitelist , h -> protocol_blacklist , h );
2012-05-28 15:03:54 +02:00
}
2022-06-20 13:30:00 +03:00
end :
freeenv_utf8 ( env_http_proxy );
return err < 0 ? err : http_connect (
h , path , local_path , hoststr , auth , proxyauth );
2014-08-02 13:29:02 +02:00
}
2020-12-03 10:42:52 +02:00
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 :
2024-04-22 15:25:40 +01:00
case AVERROR_HTTP_TOO_MANY_REQUESTS :
2020-12-03 10:42:52 +02:00
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 ;
}
2022-01-10 21:44:27 +02:00
static char * redirect_cache_get ( HTTPContext * s )
{
AVDictionaryEntry * re ;
int64_t expiry ;
char * delim ;
re = av_dict_get ( s -> redirect_cache , s -> location , NULL , AV_DICT_MATCH_CASE );
if ( ! re ) {
return NULL ;
}
delim = strchr ( re -> value , ';' );
if ( ! delim ) {
return NULL ;
}
expiry = strtoll ( re -> value , NULL , 10 );
if ( time ( NULL ) > expiry ) {
return NULL ;
}
return delim + 1 ;
}
static int redirect_cache_set ( HTTPContext * s , const char * source , const char * dest , int64_t expiry )
{
char * value ;
int ret ;
value = av_asprintf ( "%" PRIi64 ";%s" , expiry , dest );
if ( ! value ) {
return AVERROR ( ENOMEM );
}
ret = av_dict_set ( & s -> redirect_cache , source , value , AV_DICT_MATCH_CASE | AV_DICT_DONT_STRDUP_VAL );
2022-01-20 20:18:11 +01:00
if ( ret < 0 )
2022-01-10 21:44:27 +02:00
return ret ;
return 0 ;
}
2014-08-02 13:29:02 +02:00
/* 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 ;
2024-04-22 15:25:44 +01:00
int ret , conn_attempts = 1 , auth_attempts = 0 , redirects = 0 ;
2020-12-03 10:42:52 +02:00
int reconnect_delay = 0 ;
2024-04-22 15:25:45 +01:00
int reconnect_delay_total = 0 ;
2020-12-03 10:42:52 +02:00
uint64_t off ;
2022-01-10 21:44:27 +02:00
char * cached ;
2020-12-03 10:42:52 +02:00
2014-08-02 13:29:02 +02:00
redo :
2022-01-10 21:44:27 +02:00
cached = redirect_cache_get ( s );
if ( cached ) {
2026-02-03 11:49:29 +01:00
if ( redirects ++ >= s -> max_redirects )
return AVERROR ( EIO );
2022-01-10 21:44:27 +02:00
av_free ( s -> location );
s -> location = av_strdup ( cached );
if ( ! s -> location ) {
ret = AVERROR ( ENOMEM );
goto fail ;
}
goto redo ;
}
2014-11-15 14:24:09 +01:00
av_dict_copy ( options , s -> chained_options , 0 );
2014-11-14 18:05:44 -05:00
2014-08-13 14:32:52 +03:00
cur_auth_type = s -> auth_state . auth_type ;
cur_proxy_auth_type = s -> auth_state . auth_type ;
2020-12-03 10:42:52 +02:00
off = s -> off ;
2022-01-10 21:44:27 +02:00
ret = http_open_cnx_internal ( h , options );
if ( ret < 0 ) {
if ( ! http_should_reconnect ( s , ret ) ||
2024-04-22 15:25:44 +01:00
reconnect_delay > s -> reconnect_delay_max ||
2024-04-22 15:25:45 +01:00
( s -> reconnect_max_retries >= 0 && conn_attempts > s -> reconnect_max_retries ) ||
reconnect_delay_total > s -> reconnect_delay_total_max )
2020-12-03 10:42:52 +02:00
goto fail ;
2024-04-22 15:25:42 +01:00
/* Both fields here are in seconds. */
if ( s -> respect_retry_after && s -> retry_after > 0 ) {
reconnect_delay = s -> retry_after ;
if ( reconnect_delay > s -> reconnect_delay_max )
goto fail ;
s -> retry_after = 0 ;
2026-01-26 11:54:35 +01:00
s -> nb_retries ++ ;
2024-04-22 15:25:42 +01:00
}
2020-12-03 10:42:52 +02:00
av_log ( h , AV_LOG_WARNING , "Will reconnect at %" PRIu64 " in %d second(s). \n " , off , reconnect_delay );
2022-01-10 21:44:27 +02:00
ret = ff_network_sleep_interruptible ( 1000U * 1000 * reconnect_delay , & h -> interrupt_callback );
if ( ret != AVERROR ( ETIMEDOUT ))
2020-12-03 10:42:52 +02:00
goto fail ;
2024-04-22 15:25:45 +01:00
reconnect_delay_total += reconnect_delay ;
2020-12-03 10:42:52 +02:00
reconnect_delay = 1 + 2 * reconnect_delay ;
2026-01-26 11:54:35 +01:00
s -> nb_reconnects ++ ;
2024-04-22 15:25:44 +01:00
conn_attempts ++ ;
2020-12-03 10:42:52 +02:00
/* restore the offset (http_connect resets it) */
s -> off = off ;
ffurl_closep ( & s -> hd );
goto redo ;
}
2014-08-02 13:29:02 +02:00
2024-04-22 15:25:43 +01:00
auth_attempts ++ ;
2010-03-24 22:32:05 +00:00
if ( s -> http_code == 401 ) {
2012-03-12 14:00:16 +02:00
if (( cur_auth_type == HTTP_AUTH_NONE || s -> auth_state . stale ) &&
2024-04-22 15:25:43 +01:00
s -> auth_state . auth_type != HTTP_AUTH_NONE && auth_attempts < 4 ) {
2012-06-01 19:52:35 +02:00
ffurl_closep ( & s -> hd );
2010-03-24 22:32:05 +00:00
goto redo ;
} else
goto fail ;
}
2011-11-11 11:21:42 +02:00
if ( s -> http_code == 407 ) {
2012-03-12 14:00:16 +02:00
if (( cur_proxy_auth_type == HTTP_AUTH_NONE || s -> proxy_auth_state . stale ) &&
2024-04-22 15:25:43 +01:00
s -> proxy_auth_state . auth_type != HTTP_AUTH_NONE && auth_attempts < 4 ) {
2012-06-01 19:52:35 +02:00
ffurl_closep ( & s -> hd );
2011-11-11 11:21:42 +02:00
goto redo ;
} else
goto fail ;
}
2014-07-22 11:42:03 -07:00
if (( s -> http_code == 301 || s -> http_code == 302 ||
2021-01-13 15:27:50 +00:00
s -> http_code == 303 || s -> http_code == 307 || s -> http_code == 308 ) &&
2022-01-10 21:44:27 +02:00
s -> new_location ) {
2007-02-28 03:40:23 +00:00
/* url moved, get next */
2012-06-01 19:52:35 +02:00
ffurl_closep ( & s -> hd );
2026-01-14 03:21:46 +01:00
if ( redirects ++ >= s -> max_redirects )
2007-07-19 15:23:32 +00:00
return AVERROR ( EIO );
2022-01-10 21:44:27 +02:00
if ( ! s -> expires ) {
s -> expires = ( s -> http_code == 301 || s -> http_code == 308 ) ? INT64_MAX : - 1 ;
}
if ( s -> expires > time ( NULL ) && av_dict_count ( s -> redirect_cache ) < MAX_CACHED_REDIRECTS ) {
redirect_cache_set ( s , s -> location , s -> new_location , s -> expires );
}
av_free ( s -> location );
s -> location = s -> new_location ;
s -> new_location = NULL ;
2026-01-26 11:54:35 +01:00
s -> nb_redirects ++ ;
2022-01-10 21:44:27 +02:00
2012-03-12 14:03:46 +02:00
/* Restart the authentication process with the new target, which
* might use a different auth mechanism. */
memset ( & s -> auth_state , 0 , sizeof ( s -> auth_state ));
2024-04-22 15:25:43 +01:00
auth_attempts = 0 ;
2007-02-28 03:40:23 +00:00
goto redo ;
}
return 0 ;
2014-07-22 11:42:03 -07:00
fail :
2026-02-15 18:17:46 +01:00
s -> off = off ;
2012-06-01 16:30:01 +03:00
if ( s -> hd )
2012-06-01 19:52:35 +02:00
ffurl_closep ( & s -> hd );
2022-01-10 21:44:27 +02:00
if ( ret < 0 )
return ret ;
2014-10-18 00:24:01 +04:00
return ff_http_averror ( s -> http_code , AVERROR ( EIO ));
2007-02-28 03:40:23 +00:00
}
2019-08-30 07:14:27 +08:00
2019-10-04 22:56:01 +01:00
int ff_http_do_new_request ( URLContext * h , const char * uri ) {
return ff_http_do_new_request2 ( h , uri , NULL );
}
2007-02-28 03:40:23 +00:00
2019-10-04 22:56:01 +01:00
int ff_http_do_new_request2 ( URLContext * h , const char * uri , AVDictionary ** opts )
2012-05-28 15:03:54 +02:00
{
HTTPContext * s = h -> priv_data ;
2011-11-07 11:06:50 +02:00
AVDictionary * options = NULL ;
int ret ;
2017-12-12 15:50:44 -08:00
char hostname1 [ 1024 ], hostname2 [ 1024 ], proto1 [ 10 ], proto2 [ 10 ];
int port1 , port2 ;
2017-12-29 15:25:14 -08:00
if ( ! h -> prot ||
! ( ! strcmp ( h -> prot -> name , "http" ) ||
! strcmp ( h -> prot -> name , "https" )))
return AVERROR ( EINVAL );
2017-12-12 15:50:44 -08:00
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 );
2025-04-23 20:59:49 +08:00
if ( strcmp ( proto1 , proto2 ) != 0 ) {
av_log ( h , AV_LOG_INFO , "Cannot reuse HTTP connection for different protocol %s vs %s \n " ,
proto1 , proto2 );
return AVERROR ( EINVAL );
}
2017-12-12 15:50:44 -08:00
if ( port1 != port2 || strncmp ( hostname1 , hostname2 , sizeof ( hostname2 )) != 0 ) {
2025-04-23 20:59:49 +08:00
av_log ( h , AV_LOG_INFO , "Cannot reuse HTTP connection for different host: %s:%d != %s:%d \n " ,
2017-12-12 15:50:44 -08:00
hostname1 , port1 ,
hostname2 , port2
);
return AVERROR ( EINVAL );
}
2012-05-28 15:03:54 +02:00
2017-12-25 12:07:43 +08:00
if ( ! s -> end_chunked_post ) {
ret = http_shutdown ( h , h -> flags );
if ( ret < 0 )
return ret ;
}
2017-11-29 14:32:16 +08:00
2017-12-22 16:29:41 -08:00
if ( s -> willclose )
return AVERROR_EOF ;
2017-11-29 14:32:16 +08:00
s -> end_chunked_post = 0 ;
2017-11-13 11:34:50 -08:00
s -> chunkend = 0 ;
2014-07-22 11:42:03 -07:00
s -> off = 0 ;
2013-06-26 00:53:26 +02:00
s -> icy_data_read = 0 ;
2021-12-27 11:20:24 +02:00
2013-11-21 11:25:16 +02:00
av_free ( s -> location );
s -> location = av_strdup ( uri );
if ( ! s -> location )
return AVERROR ( ENOMEM );
2012-05-28 15:03:54 +02:00
2021-12-27 11:20:24 +02:00
av_free ( s -> uri );
s -> uri = av_strdup ( uri );
if ( ! s -> uri )
return AVERROR ( ENOMEM );
2019-10-04 22:56:01 +01:00
if (( ret = av_opt_set_dict ( s , opts )) < 0 )
return ret ;
2017-12-12 16:02:09 -08:00
av_log ( s , AV_LOG_INFO , "Opening \' %s \' for %s \n " , uri , h -> flags & AVIO_FLAG_WRITE ? "writing" : "reading" );
2011-11-07 11:06:50 +02:00
ret = http_open_cnx ( h , & options );
av_dict_free ( & options );
return ret ;
2012-05-28 15:03:54 +02:00
}
2014-10-18 00:24:00 +04:00
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 ;
2024-04-22 15:25:40 +01:00
case 429 : return AVERROR_HTTP_TOO_MANY_REQUESTS ;
2014-10-18 00:24:00 +04:00
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 ;
}
2025-05-16 20:15:05 +08:00
const char * ff_http_get_new_location ( URLContext * h )
{
HTTPContext * s = h -> priv_data ;
return s -> new_location ;
}
2015-07-03 02:28:56 +02:00
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 ;
2024-04-22 15:25:40 +01:00
case AVERROR_HTTP_TOO_MANY_REQUESTS :
case 429 :
reply_code = 429 ;
reply_text = "Too Many Requests" ;
break ;
2015-07-03 02:28:56 +02:00
case 200 :
reply_code = 200 ;
reply_text = "OK" ;
2016-08-10 21:18:00 +02:00
content_type = s -> content_type ? s -> content_type : "application/octet-stream" ;
2015-07-03 02:28:56 +02:00
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 "
2025-12-03 00:41:00 +01:00
"Content-Length: %zu \r\n "
2016-08-11 11:29:07 +02:00
"%s"
2015-07-03 02:28:56 +02:00
" \r\n "
"%03d %s \r\n " ,
reply_code ,
reply_text ,
content_type ,
strlen ( reply_text ) + 6 , // 3 digit status code + space + \r\n
2016-08-11 11:29:07 +02:00
s -> headers ? s -> headers : "" ,
2015-07-03 02:28:56 +02:00
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 "
2016-08-11 11:29:07 +02:00
"%s"
2015-07-03 02:28:56 +02:00
" \r\n " ,
reply_code ,
reply_text ,
2016-08-11 11:29:07 +02:00
content_type ,
s -> headers ? s -> headers : "" );
2015-07-03 02:28:56 +02:00
}
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 ;
}
2015-06-04 01:20:28 +02:00
static void handle_http_errors ( URLContext * h , int error )
{
2015-07-03 02:28:56 +02:00
av_assert0 ( error < 0 );
http_write_reply ( h , error );
}
static int http_handshake ( URLContext * c )
{
2022-01-10 21:44:27 +02:00
int ret , err ;
2015-07-03 02:28:56 +02:00
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 " );
2015-09-03 15:55:10 +02:00
if (( ret = ffurl_handshake ( cl )) > 0 )
2015-07-03 02:28:56 +02:00
return 2 + ret ;
2015-09-03 15:56:12 +02:00
if ( ret < 0 )
2015-07-03 02:28:56 +02:00
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 " );
2022-01-10 21:44:27 +02:00
if (( err = http_read_header ( c )) < 0 ) {
2015-07-03 02:28:56 +02:00
handle_http_errors ( c , err );
return err ;
2015-06-04 01:20:28 +02:00
}
2015-07-03 02:28:56 +02:00
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 ;
2015-06-04 01:20:28 +02:00
}
2015-07-03 02:28:56 +02:00
// this should never be reached.
return AVERROR ( EINVAL );
2015-06-04 01:20:28 +02:00
}
2015-04-02 22:49:07 +02:00
static int http_listen ( URLContext * h , const char * uri , int flags ,
AVDictionary ** options ) {
HTTPContext * s = h -> priv_data ;
int ret ;
2015-05-02 02:07:29 -05:00
char hostname [ 1024 ], proto [ 10 ];
2015-04-02 22:49:07 +02:00
char lower_url [ 100 ];
2015-05-10 15:01:36 +02:00
const char * lower_proto = "tcp" ;
2015-07-03 02:28:56 +02:00
int port ;
2015-05-02 02:07:29 -05:00
av_url_split ( proto , sizeof ( proto ), NULL , 0 , hostname , sizeof ( hostname ), & port ,
2015-04-02 22:49:07 +02:00
NULL , 0 , uri );
2015-05-02 02:07:29 -05:00
if ( ! strcmp ( proto , "https" ))
lower_proto = "tls" ;
ff_url_join ( lower_url , sizeof ( lower_url ), lower_proto , NULL , hostname , port ,
2015-04-02 22:49:07 +02:00
NULL );
2015-07-03 02:28:56 +02:00
if (( ret = av_dict_set_int ( options , "listen" , s -> listen , 0 )) < 0 )
goto fail ;
2016-01-30 02:17:51 +01:00
if (( ret = ffurl_open_whitelist ( & s -> hd , lower_url , AVIO_FLAG_READ_WRITE ,
& h -> interrupt_callback , options ,
2016-04-21 15:55:09 +01:00
h -> protocol_whitelist , h -> protocol_blacklist , h
2016-01-30 02:17:51 +01:00
)) < 0 )
2015-04-02 22:49:07 +02:00
goto fail ;
2015-07-03 02:28:56 +02:00
s -> handshake_step = LOWER_PROTO ;
if ( s -> listen == HTTP_SINGLE ) { /* single client */
s -> reply_code = 200 ;
while (( ret = http_handshake ( h )) > 0 );
}
2015-04-02 22:49:07 +02:00
fail :
av_dict_free ( & s -> chained_options );
2021-06-01 18:50:51 +02:00
av_dict_free ( & s -> cookie_dict );
2015-04-02 22:49:07 +02:00
return ret ;
}
2011-11-07 11:06:50 +02:00
static int http_open ( URLContext * h , const char * uri , int flags ,
AVDictionary ** options )
2007-02-28 03:40:23 +00:00
{
2010-06-22 14:12:34 +00:00
HTTPContext * s = h -> priv_data ;
2011-11-07 11:06:50 +02:00
int ret ;
2007-02-28 03:40:23 +00:00
2012-10-02 22:22:44 +01:00
if ( s -> seekable == 1 )
h -> is_streamed = 0 ;
else
h -> is_streamed = 1 ;
2007-02-28 03:40:23 +00:00
2026-02-09 16:56:25 +01:00
s -> initial_requests = s -> seekable != 0 && s -> initial_request_size > 0 ;
2016-12-05 08:02:33 -05:00
s -> filesize = UINT64_MAX ;
2021-12-27 11:20:24 +02:00
2013-11-21 11:25:16 +02:00
s -> location = av_strdup ( uri );
if ( ! s -> location )
return AVERROR ( ENOMEM );
2021-12-27 11:20:24 +02:00
s -> uri = av_strdup ( uri );
if ( ! s -> uri )
return AVERROR ( ENOMEM );
2011-11-07 11:06:50 +02:00
if ( options )
av_dict_copy ( & s -> chained_options , * options , 0 );
2007-02-28 03:40:23 +00:00
2011-11-07 11:43:13 +02:00
if ( s -> headers ) {
int len = strlen ( s -> headers );
2015-07-23 21:14:09 +02:00
if ( len < 2 || strcmp ( " \r\n " , s -> headers + len - 2 )) {
2014-07-22 11:42:03 -07:00
av_log ( h , AV_LOG_WARNING ,
2019-01-28 12:20:02 +05:30
"No trailing CRLF found in HTTP header. Adding it. \n " );
2015-07-23 21:14:09 +02:00
ret = av_reallocp ( & s -> headers , len + 3 );
if ( ret < 0 )
2020-08-24 00:58:09 +08:00
goto bail_out ;
2015-07-23 21:14:09 +02:00
s -> headers [ len ] = '\r' ;
s -> headers [ len + 1 ] = '\n' ;
s -> headers [ len + 2 ] = '\0' ;
}
2011-11-07 11:43:13 +02:00
}
2015-04-02 22:49:07 +02:00
if ( s -> listen ) {
return http_listen ( h , uri , flags , options );
}
2011-11-07 11:06:50 +02:00
ret = http_open_cnx ( h , options );
2020-08-24 00:58:09 +08:00
bail_out :
2021-06-01 18:50:51 +02:00
if ( ret < 0 ) {
2011-11-07 11:06:50 +02:00
av_dict_free ( & s -> chained_options );
2021-06-01 18:50:51 +02:00
av_dict_free ( & s -> cookie_dict );
2022-01-10 21:44:27 +02:00
av_dict_free ( & s -> redirect_cache );
av_freep ( & s -> new_location );
2021-12-27 11:20:24 +02:00
av_freep ( & s -> uri );
2021-06-01 18:50:51 +02:00
}
2011-11-07 11:06:50 +02:00
return ret ;
2007-02-28 03:40:23 +00:00
}
2014-07-22 11:42:03 -07:00
2015-07-03 02:28:56 +02:00
static int http_accept ( URLContext * s , URLContext ** c )
{
int ret ;
HTTPContext * sc = s -> priv_data ;
HTTPContext * cc ;
URLContext * sl = sc -> hd ;
URLContext * cl = NULL ;
av_assert0 ( sc -> listen );
if (( ret = ffurl_alloc ( c , s -> filename , s -> flags , & sl -> interrupt_callback )) < 0 )
goto fail ;
cc = ( * c ) -> priv_data ;
if (( ret = ffurl_accept ( sl , & cl )) < 0 )
goto fail ;
cc -> hd = cl ;
cc -> is_multi_client = 1 ;
2018-01-12 19:16:29 +01:00
return 0 ;
2015-07-03 02:28:56 +02:00
fail :
2018-01-12 19:16:29 +01:00
if ( c ) {
ffurl_closep ( c );
}
2015-07-03 02:28:56 +02:00
return ret ;
}
2007-02-28 03:40:23 +00:00
static int http_getc ( HTTPContext * s )
{
int len ;
if ( s -> buf_ptr >= s -> buf_end ) {
2011-03-31 16:31:43 +02:00
len = ffurl_read ( s -> hd , s -> buffer , BUFFER_SIZE );
2007-02-28 03:40:23 +00:00
if ( len < 0 ) {
2012-05-30 11:52:11 +02:00
return len ;
2007-02-28 03:40:23 +00:00
} else if ( len == 0 ) {
2014-03-10 17:17:25 +01:00
return AVERROR_EOF ;
2007-02-28 03:40:23 +00:00
} else {
s -> buf_ptr = s -> buffer ;
s -> buf_end = s -> buffer + len ;
}
}
return * s -> buf_ptr ++ ;
}
2009-06-06 16:44:21 +00:00
static int http_get_line ( HTTPContext * s , char * line , int line_size )
{
int ch ;
char * q ;
q = line ;
2014-07-22 11:42:03 -07:00
for (;;) {
2009-06-06 16:44:21 +00:00
ch = http_getc ( s );
if ( ch < 0 )
2012-05-30 11:52:11 +02:00
return ch ;
2009-06-06 16:44:21 +00:00
if ( ch == '\n' ) {
/* process line */
if ( q > line && q [ - 1 ] == '\r' )
q -- ;
* q = '\0' ;
return 0 ;
} else {
if (( q - line ) < line_size - 1 )
* q ++ = ch ;
}
}
}
2014-03-10 20:16:50 +01:00
static int check_http_code ( URLContext * h , int http_code , const char * end )
{
HTTPContext * s = h -> priv_data ;
/* error codes are 4xx and 5xx, but regard 401 as a success, so we
* don't abort until all headers have been parsed. */
if ( http_code >= 400 && http_code < 600 &&
( http_code != 401 || s -> auth_state . auth_type != HTTP_AUTH_NONE ) &&
( http_code != 407 || s -> proxy_auth_state . auth_type != HTTP_AUTH_NONE )) {
end += strspn ( end , SPACE_CHARS );
av_log ( h , AV_LOG_WARNING , "HTTP error %d %s \n " , http_code , end );
2014-10-18 00:24:01 +04:00
return ff_http_averror ( http_code , AVERROR ( EIO ));
2014-03-10 20:16:50 +01:00
}
return 0 ;
}
static int parse_location ( HTTPContext * s , const char * p )
{
2022-01-10 21:44:27 +02:00
char redirected_location [ MAX_URL_SIZE ];
2014-03-10 20:16:50 +01:00
ff_make_absolute_url ( redirected_location , sizeof ( redirected_location ),
s -> location , p );
2022-01-10 21:44:27 +02:00
av_freep ( & s -> new_location );
s -> new_location = av_strdup ( redirected_location );
if ( ! s -> new_location )
2014-03-10 20:16:50 +01:00
return AVERROR ( ENOMEM );
return 0 ;
}
/* "bytes $from-$to/$document_size" */
2014-03-12 09:40:05 +02:00
static void parse_content_range ( URLContext * h , const char * p )
2014-03-10 20:16:50 +01:00
{
HTTPContext * s = h -> priv_data ;
2026-01-23 10:37:02 +01:00
const char * slash , * end ;
2014-03-10 20:16:50 +01:00
if ( ! strncmp ( p , "bytes " , 6 )) {
2014-07-22 11:42:03 -07:00
p += 6 ;
2016-12-05 08:02:33 -05:00
s -> off = strtoull ( p , NULL , 10 );
2026-01-23 10:37:02 +01:00
if (( end = strchr ( p , '-' )) && strlen ( end ) > 0 )
s -> range_end = strtoull ( end + 1 , NULL , 10 ) + 1 ;
2014-03-10 20:16:50 +01:00
if (( slash = strchr ( p , '/' )) && strlen ( slash ) > 0 )
2022-02-02 10:39:07 -05:00
s -> filesize_from_content_range = strtoull ( slash + 1 , NULL , 10 );
2014-03-10 20:16:50 +01:00
}
2014-03-12 02:52:17 +01:00
if ( s -> seekable == - 1 && ( ! s -> is_akamai || s -> filesize != 2147483647 ))
h -> is_streamed = 0 ; /* we _can_ in fact seek */
2014-03-10 20:16:50 +01:00
}
2014-03-12 09:40:05 +02:00
static int parse_content_encoding ( URLContext * h , const char * p )
2014-03-10 20:16:50 +01:00
{
if ( ! av_strncasecmp ( p , "gzip" , 4 ) ||
! av_strncasecmp ( p , "deflate" , 7 )) {
#if CONFIG_ZLIB
2014-08-13 21:33:27 +02:00
HTTPContext * s = h -> priv_data ;
2014-03-10 20:16:50 +01:00
s -> compressed = 1 ;
inflateEnd ( & s -> inflate_stream );
if ( inflateInit2 ( & s -> inflate_stream , 32 + 15 ) != Z_OK ) {
av_log ( h , AV_LOG_WARNING , "Error during zlib initialisation: %s \n " ,
s -> inflate_stream . msg );
return AVERROR ( ENOSYS );
}
if ( zlibCompileFlags () & ( 1 << 17 )) {
av_log ( h , AV_LOG_WARNING ,
"Your zlib was compiled without gzip support. \n " );
return AVERROR ( ENOSYS );
}
#else
av_log ( h , AV_LOG_WARNING ,
"Compressed (%s) content, need zlib with gzip support \n " , p );
return AVERROR ( ENOSYS );
2014-07-22 11:42:03 -07:00
#endif /* CONFIG_ZLIB */
2014-03-10 20:16:50 +01:00
} else if ( ! av_strncasecmp ( p , "identity" , 8 )) {
// The normal, no-encoding case (although servers shouldn't include
// the header at all if this is the case).
} else {
av_log ( h , AV_LOG_WARNING , "Unknown content coding: %s \n " , p );
}
return 0 ;
}
2014-03-10 21:11:35 +01:00
// Concat all Icy- header lines
static int parse_icy ( HTTPContext * s , const char * tag , const char * p )
{
int len = 4 + strlen ( p ) + strlen ( tag );
2014-03-12 16:28:22 +01:00
int is_first = ! s -> icy_metadata_headers ;
2014-03-10 21:11:35 +01:00
int ret ;
2014-07-31 19:56:35 -04:00
av_dict_set ( & s -> metadata , tag , p , 0 );
2014-03-10 21:11:35 +01:00
if ( s -> icy_metadata_headers )
len += strlen ( s -> icy_metadata_headers );
if (( ret = av_reallocp ( & s -> icy_metadata_headers , len )) < 0 )
return ret ;
2014-03-12 16:28:22 +01:00
if ( is_first )
* s -> icy_metadata_headers = '\0' ;
2014-03-10 21:11:35 +01:00
av_strlcatf ( s -> icy_metadata_headers , len , "%s: %s \n " , tag , p );
return 0 ;
}
2024-04-25 14:48:15 +01:00
static int parse_http_date ( const char * date_str , struct tm * buf )
2017-04-30 14:25:29 -04:00
{
2024-04-25 14:48:15 +01:00
char date_buf [ MAX_DATE_LEN ];
int i , j , date_buf_len = MAX_DATE_LEN - 1 ;
char * date ;
2017-04-30 14:25:29 -04:00
// strip off any punctuation or whitespace
2024-04-25 14:48:15 +01:00
for ( i = 0 , j = 0 ; date_str [ i ] != '\0' && j < date_buf_len ; i ++ ) {
if (( date_str [ i ] >= '0' && date_str [ i ] <= '9' ) ||
( date_str [ i ] >= 'A' && date_str [ i ] <= 'Z' ) ||
( date_str [ i ] >= 'a' && date_str [ i ] <= 'z' )) {
date_buf [ j ] = date_str [ i ];
2017-04-30 14:25:29 -04:00
j ++ ;
}
}
2024-04-25 14:48:15 +01:00
date_buf [ j ] = '\0' ;
date = date_buf ;
2017-04-30 14:25:29 -04:00
// move the string beyond the day of week
2024-04-25 14:48:15 +01:00
while (( * date < '0' || * date > '9' ) && * date != '\0' )
date ++ ;
2017-04-30 14:25:29 -04:00
2024-04-25 14:48:15 +01:00
return av_small_strptime ( date , "%d%b%Y%H%M%S" , buf ) ? 0 : AVERROR ( EINVAL );
2017-04-30 14:25:29 -04:00
}
static int parse_set_cookie ( const char * set_cookie , AVDictionary ** dict )
{
char * param , * next_param , * cstr , * back ;
2020-04-18 12:19:31 +08:00
char * saveptr = NULL ;
2017-04-30 14:25:29 -04:00
2018-03-08 04:47:40 +01:00
if ( ! set_cookie [ 0 ])
return 0 ;
2017-04-30 14:25:29 -04:00
if ( ! ( cstr = av_strdup ( set_cookie )))
return AVERROR ( EINVAL );
// strip any trailing whitespace
back = & cstr [ strlen ( cstr ) - 1 ];
while ( strchr ( WHITESPACES , * back )) {
* back = '\0' ;
2018-03-08 04:52:36 +01:00
if ( back == cstr )
break ;
2017-04-30 14:25:29 -04:00
back -- ;
}
next_param = cstr ;
2020-04-18 12:19:31 +08:00
while (( param = av_strtok ( next_param , ";" , & saveptr ))) {
2017-04-30 14:25:29 -04:00
char * name , * value ;
2020-04-18 12:19:31 +08:00
next_param = NULL ;
2017-04-30 14:25:29 -04:00
param += strspn ( param , WHITESPACES );
if (( name = av_strtok ( param , "=" , & value ))) {
if ( av_dict_set ( dict , name , value , 0 ) < 0 ) {
av_free ( cstr );
return - 1 ;
}
}
}
av_free ( cstr );
return 0 ;
}
2015-03-17 20:22:59 +11:00
static int parse_cookie ( HTTPContext * s , const char * p , AVDictionary ** cookies )
{
2017-04-30 14:25:29 -04:00
AVDictionary * new_params = NULL ;
2024-05-17 17:04:50 +02:00
const AVDictionaryEntry * e , * cookie_entry ;
2015-03-17 20:22:59 +11:00
char * eql , * name ;
2017-04-30 14:25:29 -04:00
// ensure the cookie is parsable
if ( parse_set_cookie ( p , & new_params ))
return - 1 ;
// if there is no cookie value there is nothing to parse
2024-05-17 17:04:50 +02:00
cookie_entry = av_dict_iterate ( new_params , NULL );
2017-04-30 14:25:29 -04:00
if ( ! cookie_entry || ! cookie_entry -> value ) {
av_dict_free ( & new_params );
return - 1 ;
}
// ensure the cookie is not expired or older than an existing value
if (( e = av_dict_get ( new_params , "expires" , NULL , 0 )) && e -> value ) {
struct tm new_tm = { 0 };
2024-04-25 14:48:15 +01:00
if ( ! parse_http_date ( e -> value , & new_tm )) {
2017-04-30 14:25:29 -04:00
AVDictionaryEntry * e2 ;
// if the cookie has already expired ignore it
if ( av_timegm ( & new_tm ) < av_gettime () / 1000000 ) {
av_dict_free ( & new_params );
2018-03-08 04:30:35 +01:00
return 0 ;
2017-04-30 14:25:29 -04:00
}
// only replace an older cookie with the same name
e2 = av_dict_get ( * cookies , cookie_entry -> key , NULL , 0 );
if ( e2 && e2 -> value ) {
AVDictionary * old_params = NULL ;
if ( ! parse_set_cookie ( p , & old_params )) {
e2 = av_dict_get ( old_params , "expires" , NULL , 0 );
if ( e2 && e2 -> value ) {
struct tm old_tm = { 0 };
2024-04-25 14:48:15 +01:00
if ( ! parse_http_date ( e -> value , & old_tm )) {
2017-04-30 14:25:29 -04:00
if ( av_timegm ( & new_tm ) < av_timegm ( & old_tm )) {
av_dict_free ( & new_params );
av_dict_free ( & old_params );
return - 1 ;
}
}
}
}
av_dict_free ( & old_params );
}
}
}
2018-01-11 14:28:52 -08:00
av_dict_free ( & new_params );
2017-04-30 14:25:29 -04:00
2015-03-17 20:22:59 +11:00
// duplicate the cookie name (dict will dupe the value)
if ( ! ( eql = strchr ( p , '=' ))) return AVERROR ( EINVAL );
if ( ! ( name = av_strndup ( p , eql - p ))) return AVERROR ( ENOMEM );
// add the cookie to the dictionary
av_dict_set ( cookies , name , eql , AV_DICT_DONT_STRDUP_KEY );
return 0 ;
}
static int cookie_string ( AVDictionary * dict , char ** cookies )
{
2022-11-26 15:46:46 +01:00
const AVDictionaryEntry * e = NULL ;
2015-03-17 20:22:59 +11:00
int len = 1 ;
// determine how much memory is needed for the cookies string
2022-11-26 15:46:46 +01:00
while (( e = av_dict_iterate ( dict , e )))
2015-03-17 20:22:59 +11:00
len += strlen ( e -> key ) + strlen ( e -> value ) + 1 ;
// reallocate the cookies
e = NULL ;
if ( * cookies ) av_free ( * cookies );
* cookies = av_malloc ( len );
2015-03-31 12:53:25 +05:30
if ( !* cookies ) return AVERROR ( ENOMEM );
2015-03-17 20:22:59 +11:00
* cookies [ 0 ] = '\0' ;
// write out the cookies
2022-11-26 15:46:46 +01:00
while (( e = av_dict_iterate ( dict , e )))
2015-03-17 20:22:59 +11:00
av_strlcatf ( * cookies , len , "%s%s \n " , e -> key , e -> value );
return 0 ;
}
2022-01-10 21:44:27 +02:00
static void parse_expires ( HTTPContext * s , const char * p )
{
struct tm tm ;
2024-04-25 14:48:15 +01:00
if ( ! parse_http_date ( p , & tm )) {
2022-01-10 21:44:27 +02:00
s -> expires = av_timegm ( & tm );
}
}
static void parse_cache_control ( HTTPContext * s , const char * p )
{
char * age ;
int offset ;
/* give 'Expires' higher priority over 'Cache-Control' */
if ( s -> expires ) {
return ;
}
if ( av_stristr ( p , "no-cache" ) || av_stristr ( p , "no-store" )) {
s -> expires = - 1 ;
return ;
}
age = av_stristr ( p , "s-maxage=" );
offset = 9 ;
if ( ! age ) {
age = av_stristr ( p , "max-age=" );
offset = 8 ;
}
if ( age ) {
2026-02-15 18:31:01 +01:00
s -> expires = time ( NULL ) + atoi ( age + offset );
2022-01-10 21:44:27 +02:00
}
}
2024-04-22 15:25:41 +01:00
static int process_line ( URLContext * h , char * line , int line_count , int * parsed_http_code )
2007-02-28 03:40:23 +00:00
{
HTTPContext * s = h -> priv_data ;
2015-06-05 00:27:07 +02:00
const char * auto_method = h -> flags & AVIO_FLAG_READ ? "POST" : "GET" ;
2015-06-04 01:21:02 +02:00
char * tag , * p , * end , * method , * resource , * version ;
2014-03-10 20:16:50 +01:00
int ret ;
2007-02-28 03:40:23 +00:00
/* end of header */
2012-05-21 11:27:10 +02:00
if ( line [ 0 ] == '\0' ) {
s -> end_header = 1 ;
2007-02-28 03:40:23 +00:00
return 0 ;
2012-05-21 11:27:10 +02:00
}
2007-02-28 03:40:23 +00:00
p = line ;
if ( line_count == 0 ) {
2015-07-03 02:28:56 +02:00
if ( s -> is_connected_server ) {
2015-06-04 01:21:02 +02:00
// HTTP method
method = p ;
2015-08-20 18:01:56 +02:00
while ( * p && ! av_isspace ( * p ))
2015-06-04 01:21:02 +02:00
p ++ ;
2026-06-10 03:40:50 +02:00
if ( ! av_isspace ( * p ))
return ff_http_averror ( 400 , AVERROR ( EIO ));
2015-06-04 01:21:02 +02:00
* ( p ++ ) = '\0' ;
av_log ( h , AV_LOG_TRACE , "Received method: %s \n " , method );
if ( s -> method ) {
if ( av_strcasecmp ( s -> method , method )) {
av_log ( h , AV_LOG_ERROR , "Received and expected HTTP method do not match. (%s expected, %s received) \n " ,
s -> method , method );
return ff_http_averror ( 400 , AVERROR ( EIO ));
}
2015-06-05 00:27:07 +02:00
} else {
// use autodetected HTTP method to expect
av_log ( h , AV_LOG_TRACE , "Autodetected %s HTTP method \n " , auto_method );
if ( av_strcasecmp ( auto_method , method )) {
av_log ( h , AV_LOG_ERROR , "Received and autodetected HTTP method did not match "
"(%s autodetected %s received) \n " , auto_method , method );
return ff_http_averror ( 400 , AVERROR ( EIO ));
}
2015-07-03 02:28:56 +02:00
if ( ! ( s -> method = av_strdup ( method )))
return AVERROR ( ENOMEM );
2015-06-04 01:21:02 +02:00
}
// HTTP resource
while ( av_isspace ( * p ))
p ++ ;
resource = p ;
2019-02-13 08:54:08 +01:00
while ( * p && ! av_isspace ( * p ))
2015-06-04 01:21:02 +02:00
p ++ ;
2026-06-10 03:40:50 +02:00
if ( ! av_isspace ( * p ))
return ff_http_averror ( 400 , AVERROR ( EIO ));
2015-06-04 01:21:02 +02:00
* ( p ++ ) = '\0' ;
av_log ( h , AV_LOG_TRACE , "Requested resource: %s \n " , resource );
2015-07-03 02:28:56 +02:00
if ( ! ( s -> resource = av_strdup ( resource )))
return AVERROR ( ENOMEM );
2015-06-04 01:21:02 +02:00
// HTTP version
while ( av_isspace ( * p ))
p ++ ;
version = p ;
2015-08-20 18:01:56 +02:00
while ( * p && ! av_isspace ( * p ))
2015-06-04 01:21:02 +02:00
p ++ ;
* p = '\0' ;
if ( av_strncasecmp ( version , "HTTP/" , 5 )) {
av_log ( h , AV_LOG_ERROR , "Malformed HTTP version string. \n " );
return ff_http_averror ( 400 , AVERROR ( EIO ));
}
av_log ( h , AV_LOG_TRACE , "HTTP version string: %s \n " , version );
} else {
2017-12-25 11:35:26 -08:00
if ( av_strncasecmp ( p , "HTTP/1.0" , 8 ) == 0 )
s -> willclose = 1 ;
2017-12-25 20:21:15 -08:00
while ( * p != '/' && * p != '\0' )
p ++ ;
while ( * p == '/' )
p ++ ;
av_freep ( & s -> http_version );
s -> http_version = av_strndup ( p , 3 );
2015-06-04 01:21:26 +02:00
while ( ! av_isspace ( * p ) && * p != '\0' )
p ++ ;
while ( av_isspace ( * p ))
p ++ ;
s -> http_code = strtol ( p , & end , 10 );
2009-06-06 17:32:59 +00:00
2015-06-04 01:21:26 +02:00
av_log ( h , AV_LOG_TRACE , "http_code=%d \n " , s -> http_code );
2009-06-06 17:32:59 +00:00
2024-04-22 15:25:41 +01:00
* parsed_http_code = 1 ;
2015-06-04 01:21:26 +02:00
if (( ret = check_http_code ( h , s -> http_code , end )) < 0 )
return ret ;
2015-06-04 01:21:02 +02:00
}
2007-02-28 03:40:23 +00:00
} else {
while ( * p != '\0' && * p != ':' )
p ++ ;
if ( * p != ':' )
return 1 ;
2014-07-22 11:42:03 -07:00
* p = '\0' ;
2007-02-28 03:40:23 +00:00
tag = line ;
p ++ ;
2013-03-03 11:17:50 +01:00
while ( av_isspace ( * p ))
2007-02-28 03:40:23 +00:00
p ++ ;
2011-11-02 20:17:25 +01:00
if ( ! av_strcasecmp ( tag , "Location" )) {
2014-03-10 20:16:50 +01:00
if (( ret = parse_location ( s , p )) < 0 )
return ret ;
2016-12-05 08:02:33 -05:00
} else if ( ! av_strcasecmp ( tag , "Content-Length" ) &&
s -> filesize == UINT64_MAX ) {
s -> filesize = strtoull ( p , NULL , 10 );
2014-03-10 18:02:09 +01:00
} else if ( ! av_strcasecmp ( tag , "Content-Range" )) {
2014-03-10 20:16:50 +01:00
parse_content_range ( h , p );
2014-03-10 18:02:09 +01:00
} else if ( ! av_strcasecmp ( tag , "Accept-Ranges" ) &&
2014-03-12 02:45:34 +01:00
! strncmp ( p , "bytes" , 5 ) &&
s -> seekable == - 1 ) {
2011-09-03 22:29:07 +02:00
h -> is_streamed = 0 ;
2014-03-10 18:02:09 +01:00
} else if ( ! av_strcasecmp ( tag , "Transfer-Encoding" ) &&
! av_strncasecmp ( p , "chunked" , 7 )) {
2016-12-05 08:02:33 -05:00
s -> filesize = UINT64_MAX ;
2009-06-23 15:38:53 +00:00
s -> chunksize = 0 ;
2014-03-10 18:02:09 +01:00
} else if ( ! av_strcasecmp ( tag , "WWW-Authenticate" )) {
2010-03-24 22:32:05 +00:00
ff_http_auth_handle_header ( & s -> auth_state , tag , p );
2014-03-10 18:02:09 +01:00
} else if ( ! av_strcasecmp ( tag , "Authentication-Info" )) {
2010-03-24 22:32:05 +00:00
ff_http_auth_handle_header ( & s -> auth_state , tag , p );
2014-03-10 18:02:09 +01:00
} else if ( ! av_strcasecmp ( tag , "Proxy-Authenticate" )) {
2011-11-11 11:21:42 +02:00
ff_http_auth_handle_header ( & s -> proxy_auth_state , tag , p );
2014-03-10 18:02:09 +01:00
} else if ( ! av_strcasecmp ( tag , "Connection" )) {
2010-08-09 08:14:48 +00:00
if ( ! strcmp ( p , "close" ))
s -> willclose = 1 ;
2014-07-24 02:08:14 +02:00
} else if ( ! av_strcasecmp ( tag , "Server" )) {
if ( ! av_strcasecmp ( p , "AkamaiGHost" )) {
2013-11-03 18:17:45 +01:00
s -> is_akamai = 1 ;
2014-07-24 02:08:14 +02:00
} else if ( ! av_strncasecmp ( p , "MediaGateway" , 12 )) {
2013-11-03 18:17:45 +01:00
s -> is_mediagateway = 1 ;
}
2014-07-22 11:42:03 -07:00
} else if ( ! av_strcasecmp ( tag , "Content-Type" )) {
2014-03-06 18:39:58 +01:00
av_free ( s -> mime_type );
2023-06-01 21:43:47 +02:00
s -> mime_type = av_get_token (( const char ** ) & p , ";" );
2014-07-24 02:08:14 +02:00
} else if ( ! av_strcasecmp ( tag , "Set-Cookie" )) {
2015-03-17 20:22:59 +11:00
if ( parse_cookie ( s , p , & s -> cookie_dict ))
av_log ( h , AV_LOG_WARNING , "Unable to parse '%s' \n " , p );
2014-07-22 11:42:03 -07:00
} else if ( ! av_strcasecmp ( tag , "Icy-MetaInt" )) {
2016-12-05 08:02:33 -05:00
s -> icy_metaint = strtoull ( p , NULL , 10 );
2013-06-26 00:53:26 +02:00
} else if ( ! av_strncasecmp ( tag , "Icy-" , 4 )) {
2014-03-10 21:11:35 +01:00
if (( ret = parse_icy ( s , tag , p )) < 0 )
return ret ;
2014-03-10 18:02:09 +01:00
} else if ( ! av_strcasecmp ( tag , "Content-Encoding" )) {
2014-03-10 20:16:50 +01:00
if (( ret = parse_content_encoding ( h , p )) < 0 )
return ret ;
2022-01-10 21:44:27 +02:00
} else if ( ! av_strcasecmp ( tag , "Expires" )) {
parse_expires ( s , p );
} else if ( ! av_strcasecmp ( tag , "Cache-Control" )) {
parse_cache_control ( s , p );
2024-04-22 15:25:42 +01:00
} else if ( ! av_strcasecmp ( tag , "Retry-After" )) {
/* The header can be either an integer that represents seconds, or a date. */
struct tm tm ;
int date_ret = parse_http_date ( p , & tm );
if ( ! date_ret ) {
time_t retry = av_timegm ( & tm );
int64_t now = av_gettime () / 1000000 ;
int64_t diff = (( int64_t ) retry ) - now ;
s -> retry_after = ( unsigned int ) FFMAX ( 0 , diff );
} else {
s -> retry_after = strtoul ( p , NULL , 10 );
}
2007-02-28 03:40:23 +00:00
}
}
return 1 ;
}
2013-01-13 21:32:57 -05:00
/**
* Create a string containing cookie values for use as a HTTP cookie header
* field value for a particular path and domain from the cookie values stored in
2018-04-19 12:55:00 -07:00
* the HTTP protocol context. The cookie string is stored in *cookies, and may
* be NULL if there are no valid cookies.
2013-01-13 21:32:57 -05:00
*
* @return a negative value if an error condition occurred, 0 otherwise
*/
static int get_cookies ( HTTPContext * s , char ** cookies , const char * path ,
const char * domain )
{
// cookie strings will look like Set-Cookie header field values. Multiple
// Set-Cookie fields will result in multiple values delimited by a newline
int ret = 0 ;
2018-04-19 12:55:00 -07:00
char * cookie , * set_cookies , * next ;
2020-04-18 12:19:31 +08:00
char * saveptr = NULL ;
2013-01-13 21:32:57 -05:00
2015-03-17 20:22:59 +11:00
// destroy any cookies in the dictionary.
av_dict_free ( & s -> cookie_dict );
2018-04-19 12:55:00 -07:00
if ( ! s -> cookies )
return 0 ;
next = set_cookies = av_strdup ( s -> cookies );
if ( ! next )
return AVERROR ( ENOMEM );
2013-01-13 21:32:57 -05:00
* cookies = NULL ;
2020-04-18 12:19:31 +08:00
while (( cookie = av_strtok ( next , " \n " , & saveptr )) && ! ret ) {
2017-04-30 14:25:29 -04:00
AVDictionary * cookie_params = NULL ;
2024-05-17 17:04:50 +02:00
const AVDictionaryEntry * cookie_entry , * e ;
2013-01-13 21:32:57 -05:00
2020-04-18 12:19:31 +08:00
next = NULL ;
2015-03-17 20:22:59 +11:00
// store the cookie in a dict in case it is updated in the response
if ( parse_cookie ( s , cookie , & s -> cookie_dict ))
av_log ( s , AV_LOG_WARNING , "Unable to parse '%s' \n " , cookie );
2017-04-30 14:25:29 -04:00
// continue on to the next cookie if this one cannot be parsed
if ( parse_set_cookie ( cookie , & cookie_params ))
2018-04-19 12:55:00 -07:00
goto skip_cookie ;
2017-04-30 14:25:29 -04:00
// if the cookie has no value, skip it
2024-05-17 17:04:50 +02:00
cookie_entry = av_dict_iterate ( cookie_params , NULL );
2018-04-19 12:55:00 -07:00
if ( ! cookie_entry || ! cookie_entry -> value )
goto skip_cookie ;
2017-04-30 14:25:29 -04:00
// if the cookie has expired, don't add it
if (( e = av_dict_get ( cookie_params , "expires" , NULL , 0 )) && e -> value ) {
struct tm tm_buf = { 0 };
2024-04-25 14:48:15 +01:00
if ( ! parse_http_date ( e -> value , & tm_buf )) {
2018-04-19 12:55:00 -07:00
if ( av_timegm ( & tm_buf ) < av_gettime () / 1000000 )
goto skip_cookie ;
2013-01-13 21:32:57 -05:00
}
}
2025-08-01 22:43:23 +02:00
// if no domain in the cookie assume it applied to this request
2017-04-30 14:25:29 -04:00
if (( e = av_dict_get ( cookie_params , "domain" , NULL , 0 )) && e -> value ) {
// find the offset comparison is on the min domain (b.com, not a.b.com)
int domain_offset = strlen ( domain ) - strlen ( e -> value );
2018-04-19 12:55:00 -07:00
if ( domain_offset < 0 )
goto skip_cookie ;
2017-04-30 14:25:29 -04:00
// match the cookie domain
2018-04-19 12:55:00 -07:00
if ( av_strcasecmp ( & domain [ domain_offset ], e -> value ))
goto skip_cookie ;
2013-01-13 21:32:57 -05:00
}
2023-03-03 16:24:02 +00:00
// if a cookie path is provided, ensure the request path is within that path
2017-04-30 14:25:29 -04:00
e = av_dict_get ( cookie_params , "path" , NULL , 0 );
2023-03-03 16:24:02 +00:00
if ( e && av_strncasecmp ( path , e -> value , strlen ( e -> value )))
2018-04-19 12:55:00 -07:00
goto skip_cookie ;
2013-01-13 21:32:57 -05:00
// cookie parameters match, so copy the value
if ( !* cookies ) {
2018-04-19 12:55:00 -07:00
* cookies = av_asprintf ( "%s=%s" , cookie_entry -> key , cookie_entry -> value );
2013-01-13 21:32:57 -05:00
} else {
char * tmp = * cookies ;
2018-04-19 12:55:00 -07:00
* cookies = av_asprintf ( "%s; %s=%s" , tmp , cookie_entry -> key , cookie_entry -> value );
2013-01-13 21:32:57 -05:00
av_free ( tmp );
}
2018-04-19 12:55:00 -07:00
if ( !* cookies )
ret = AVERROR ( ENOMEM );
skip_cookie :
2018-04-13 16:42:32 -07:00
av_dict_free ( & cookie_params );
2013-01-13 21:32:57 -05:00
}
2017-04-30 14:25:29 -04:00
av_free ( set_cookies );
2013-01-13 21:32:57 -05:00
2017-04-30 14:25:29 -04:00
return ret ;
2013-01-13 21:32:57 -05:00
}
2010-06-08 10:26:16 +00:00
static inline int has_header ( const char * str , const char * header )
{
/* header + 2 to skip over CRLF prefix. (make sure you have one!) */
2011-11-07 11:43:13 +02:00
if ( ! str )
return 0 ;
2010-06-08 10:26:16 +00:00
return av_stristart ( str , header + 2 , NULL ) || av_stristr ( str , header );
}
2022-01-10 21:44:27 +02:00
static int http_read_header ( URLContext * h )
2012-05-20 16:20:56 +02:00
{
HTTPContext * s = h -> priv_data ;
2012-09-22 21:17:36 +01:00
char line [ MAX_URL_SIZE ];
2024-04-22 15:25:41 +01:00
int err = 0 , http_err = 0 ;
2012-05-20 16:20:56 +02:00
2022-01-10 21:44:27 +02:00
av_freep ( & s -> new_location );
s -> expires = 0 ;
2016-12-05 08:02:33 -05:00
s -> chunksize = UINT64_MAX ;
2022-02-02 10:39:07 -05:00
s -> filesize_from_content_range = UINT64_MAX ;
2012-06-17 21:19:41 +03:00
2012-05-20 16:20:56 +02:00
for (;;) {
2024-04-22 15:25:41 +01:00
int parsed_http_code = 0 ;
2026-01-23 10:25:01 +01:00
if (( err = http_get_line ( s , line , sizeof ( line ))) < 0 ) {
av_log ( h , AV_LOG_ERROR , "Error reading HTTP response: %s \n " ,
av_err2str ( err ));
2012-05-30 11:52:11 +02:00
return err ;
2026-01-23 10:25:01 +01:00
}
2012-05-20 16:20:56 +02:00
2015-04-20 03:19:29 +02:00
av_log ( h , AV_LOG_TRACE , "header='%s' \n " , line );
2012-05-20 16:20:56 +02:00
2024-04-22 15:25:41 +01:00
err = process_line ( h , line , s -> line_count , & parsed_http_code );
if ( err < 0 ) {
if ( parsed_http_code ) {
http_err = err ;
} else {
/* Prefer to return HTTP code error if we've already seen one. */
if ( http_err )
return http_err ;
else
return err ;
}
}
2012-05-20 16:20:56 +02:00
if ( err == 0 )
break ;
s -> line_count ++ ;
}
2024-04-22 15:25:41 +01:00
if ( http_err )
return http_err ;
2012-05-20 16:20:56 +02:00
2022-02-02 10:39:07 -05:00
// filesize from Content-Range can always be used, even if using chunked Transfer-Encoding
if ( s -> filesize_from_content_range != UINT64_MAX )
s -> filesize = s -> filesize_from_content_range ;
2013-11-03 18:17:45 +01:00
if ( s -> seekable == - 1 && s -> is_mediagateway && s -> filesize == 2000000000 )
h -> is_streamed = 1 ; /* we can in fact _not_ seek */
2026-01-23 11:19:17 +01:00
if ( h -> is_streamed )
2026-02-09 16:56:25 +01:00
s -> initial_requests = 0 ; /* unable to use partial requests */
2026-01-23 11:19:17 +01:00
2015-03-17 20:22:59 +11:00
// add any new cookies into the existing cookie string
cookie_string ( s -> cookie_dict , & s -> cookies );
av_dict_free ( & s -> cookie_dict );
2012-05-20 16:20:56 +02:00
return err ;
}
2020-02-08 01:44:30 +01:00
/**
* Escape unsafe characters in path in order to pass them safely to the HTTP
* request. Insipred by the algorithm in GNU wget:
* - escape "%" characters not followed by two hex digits
* - escape all "unsafe" characters except which are also "reserved"
* - pass through everything else
*/
static void bprint_escaped_path ( AVBPrint * bp , const char * path )
{
#define NEEDS_ESCAPE(ch) \
((ch) <= ' ' || (ch) >= '\x7f' || \
(ch) == '"' || (ch) == '%' || (ch) == '<' || (ch) == '>' || (ch) == '\\' || \
(ch) == '^' || (ch) == '`' || (ch) == '{' || (ch) == '}' || (ch) == '|')
while ( * path ) {
char buf [ 1024 ];
char * q = buf ;
while ( * path && q - buf < sizeof ( buf ) - 4 ) {
if ( path [ 0 ] == '%' && av_isxdigit ( path [ 1 ]) && av_isxdigit ( path [ 2 ])) {
* q ++ = * path ++ ;
* q ++ = * path ++ ;
* q ++ = * path ++ ;
} else if ( NEEDS_ESCAPE ( * path )) {
q += snprintf ( q , 4 , "%%%02X" , ( uint8_t ) * path ++ );
} else {
* q ++ = * path ++ ;
}
}
av_bprint_append_data ( bp , buf , q - buf );
}
}
2011-11-11 11:21:42 +02:00
static int http_connect ( URLContext * h , const char * path , const char * local_path ,
const char * hoststr , const char * auth ,
2022-01-10 21:44:27 +02:00
const char * proxyauth )
2007-02-28 03:40:23 +00:00
{
HTTPContext * s = h -> priv_data ;
2009-06-06 16:44:21 +00:00
int post , err ;
2020-02-08 00:08:54 +01:00
AVBPrint request ;
2011-11-11 11:21:42 +02:00
char * authstr = NULL , * proxyauthstr = NULL ;
2016-12-05 08:02:33 -05:00
uint64_t off = s -> off ;
2011-11-11 11:21:42 +02:00
const char * method ;
2013-10-09 13:24:40 +03:00
int send_expect_100 = 0 ;
2020-02-08 00:08:54 +01:00
av_bprint_init_for_buffer ( & request , s -> buffer , sizeof ( s -> buffer ));
2007-02-28 03:40:23 +00:00
/* send http header */
2011-04-15 16:42:09 +02:00
post = h -> flags & AVIO_FLAG_WRITE ;
2012-05-30 11:27:18 +02:00
if ( s -> post_data ) {
/* force POST method and disable chunked encoding when
* custom HTTP post data is set */
2014-07-22 11:42:03 -07:00
post = 1 ;
2012-05-30 11:27:18 +02:00
s -> chunked_post = 0 ;
}
2014-07-18 00:39:43 +02:00
if ( s -> method )
method = s -> method ;
else
method = post ? "POST" : "GET" ;
2014-07-22 11:42:03 -07:00
authstr = ff_http_auth_create_response ( & s -> auth_state , auth ,
local_path , method );
2011-11-11 11:21:42 +02:00
proxyauthstr = ff_http_auth_create_response ( & s -> proxy_auth_state , proxyauth ,
local_path , method );
2019-03-30 21:35:51 -07:00
if ( post && ! s -> post_data ) {
if ( s -> send_expect_100 != - 1 ) {
send_expect_100 = s -> send_expect_100 ;
} else {
send_expect_100 = 0 ;
/* The user has supplied authentication but we don't know the auth type,
* send Expect: 100-continue to get the 401 response including the
* WWW-Authenticate header, or an 100 continue if no auth actually
* is needed. */
if ( auth && * auth &&
s -> auth_state . auth_type == HTTP_AUTH_NONE &&
s -> http_code != 401 )
send_expect_100 = 1 ;
}
2013-10-09 13:24:40 +03:00
}
2010-06-08 10:26:16 +00:00
2020-02-08 01:44:30 +01:00
av_bprintf ( & request , "%s " , method );
bprint_escaped_path ( & request , path );
av_bprintf ( & request , " HTTP/1.1 \r\n " );
2020-02-08 00:08:54 +01:00
if ( post && s -> chunked_post )
av_bprintf ( & request , "Transfer-Encoding: chunked \r\n " );
2010-06-08 10:26:16 +00:00
/* set default headers if needed */
if ( ! has_header ( s -> headers , " \r\n User-Agent: " ))
2020-02-08 00:08:54 +01:00
av_bprintf ( & request , "User-Agent: %s \r\n " , s -> user_agent );
2018-02-01 10:56:51 +08:00
if ( s -> referer ) {
/* set default headers if needed */
if ( ! has_header ( s -> headers , " \r\n Referer: " ))
2020-02-08 00:08:54 +01:00
av_bprintf ( & request , "Referer: %s \r\n " , s -> referer );
2018-02-01 10:56:51 +08:00
}
2010-06-08 10:26:16 +00:00
if ( ! has_header ( s -> headers , " \r\n Accept: " ))
2020-02-08 00:08:54 +01:00
av_bprintf ( & request , "Accept: */* \r\n " );
2022-02-02 10:39:23 -05:00
// Note: we send the Range header on purpose, even when we're probing,
2012-09-23 15:26:56 +02:00
// since it allows us to detect more reliably if a (non-conforming)
// server supports seeking by analysing the reply headers.
2022-02-02 10:39:23 -05:00
if ( ! has_header ( s -> headers , " \r\n Range: " ) && ! post && ( s -> off > 0 || s -> end_off || s -> seekable != 0 )) {
2020-02-08 00:08:54 +01:00
av_bprintf ( & request , "Range: bytes=%" PRIu64 "-" , s -> off );
2026-02-09 16:56:25 +01:00
if (( s -> initial_requests || s -> request_size ) && s -> seekable != 0 ) {
uint64_t req_size = s -> initial_requests ? s -> initial_request_size : s -> request_size ;
uint64_t target_off = s -> off + req_size ;
2026-01-23 11:19:17 +01:00
if ( target_off < s -> off ) /* overflow */
target_off = UINT64_MAX ;
if ( s -> end_off )
target_off = FFMIN ( target_off , s -> end_off );
if ( target_off != UINT64_MAX )
av_bprintf ( & request , "%" PRId64 , target_off - 1 );
} else if ( s -> end_off )
2020-02-08 00:08:54 +01:00
av_bprintf ( & request , "%" PRId64 , s -> end_off - 1 );
av_bprintf ( & request , " \r\n " );
2013-12-28 09:41:24 +02:00
}
2013-10-09 13:24:40 +03:00
if ( send_expect_100 && ! has_header ( s -> headers , " \r\n Expect: " ))
2020-02-08 00:08:54 +01:00
av_bprintf ( & request , "Expect: 100-continue \r\n " );
2012-05-28 15:03:19 +02:00
2020-02-08 00:08:54 +01:00
if ( ! has_header ( s -> headers , " \r\n Connection: " ))
av_bprintf ( & request , "Connection: %s \r\n " , s -> multiple_requests ? "keep-alive" : "close" );
2012-05-28 15:03:19 +02:00
2010-06-08 10:26:16 +00:00
if ( ! has_header ( s -> headers , " \r\n Host: " ))
2020-02-08 00:08:54 +01:00
av_bprintf ( & request , "Host: %s \r\n " , hoststr );
2012-05-30 11:27:18 +02:00
if ( ! has_header ( s -> headers , " \r\n Content-Length: " ) && s -> post_data )
2020-02-08 00:08:54 +01:00
av_bprintf ( & request , "Content-Length: %d \r\n " , s -> post_datalen );
2014-03-06 18:40:03 +01:00
2012-10-23 11:13:42 +02:00
if ( ! has_header ( s -> headers , " \r\n Content-Type: " ) && s -> content_type )
2020-02-08 00:08:54 +01:00
av_bprintf ( & request , "Content-Type: %s \r\n " , s -> content_type );
2013-01-13 21:32:57 -05:00
if ( ! has_header ( s -> headers , " \r\n Cookie: " ) && s -> cookies ) {
char * cookies = NULL ;
2014-03-21 18:51:30 +01:00
if ( ! get_cookies ( s , & cookies , path , hoststr ) && cookies ) {
2020-02-08 00:08:54 +01:00
av_bprintf ( & request , "Cookie: %s \r\n " , cookies );
2013-01-13 21:32:57 -05:00
av_free ( cookies );
}
}
2014-07-22 11:42:03 -07:00
if ( ! has_header ( s -> headers , " \r\n Icy-MetaData: " ) && s -> icy )
2020-02-08 00:08:54 +01:00
av_bprintf ( & request , "Icy-MetaData: 1 \r\n " );
2010-06-08 10:26:16 +00:00
/* now add in custom headers */
2011-11-07 11:43:13 +02:00
if ( s -> headers )
2020-02-08 00:08:54 +01:00
av_bprintf ( & request , "%s" , s -> headers );
2010-06-08 10:26:16 +00:00
2020-02-08 00:08:54 +01:00
if ( authstr )
av_bprintf ( & request , "%s" , authstr );
if ( proxyauthstr )
av_bprintf ( & request , "Proxy-%s" , proxyauthstr );
av_bprintf ( & request , " \r\n " );
2007-02-28 03:40:23 +00:00
2020-02-08 00:08:54 +01:00
av_log ( h , AV_LOG_DEBUG , "request: %s \n " , request . str );
2013-11-04 15:59:19 +01:00
2020-02-08 00:08:54 +01:00
if ( ! av_bprint_is_complete ( & request )) {
2017-02-13 12:47:49 +01:00
av_log ( h , AV_LOG_ERROR , "overlong headers \n " );
err = AVERROR ( EINVAL );
goto done ;
}
2020-02-08 00:08:54 +01:00
if (( err = ffurl_write ( s -> hd , request . str , request . len )) < 0 )
2014-03-21 18:51:31 +01:00
goto done ;
2007-02-28 03:40:23 +00:00
2012-05-30 11:27:18 +02:00
if ( s -> post_data )
if (( err = ffurl_write ( s -> hd , s -> post_data , s -> post_datalen )) < 0 )
2014-03-21 18:51:31 +01:00
goto done ;
2007-02-28 03:40:23 +00:00
/* init input buffer */
2014-07-22 11:42:03 -07:00
s -> buf_ptr = s -> buffer ;
s -> buf_end = s -> buffer ;
s -> line_count = 0 ;
s -> off = 0 ;
s -> icy_data_read = 0 ;
2016-12-05 08:02:33 -05:00
s -> filesize = UINT64_MAX ;
2014-07-22 11:42:03 -07:00
s -> willclose = 0 ;
2012-05-21 11:26:40 +02:00
s -> end_chunked_post = 0 ;
2014-07-22 11:42:03 -07:00
s -> end_header = 0 ;
2017-09-15 15:09:54 +02:00
#if CONFIG_ZLIB
2017-07-20 13:46:46 +02:00
s -> compressed = 0 ;
2017-09-15 15:09:54 +02:00
#endif
2013-10-09 13:24:40 +03:00
if ( post && ! s -> post_data && ! send_expect_100 ) {
2010-06-04 06:35:12 +00:00
/* Pretend that it did work. We didn't read any header yet, since
* we've still to send the POST data, but the code calling this
* function will check http_code after we return. */
s -> http_code = 200 ;
2014-03-21 18:51:31 +01:00
err = 0 ;
goto done ;
2007-02-28 03:40:23 +00:00
}
/* wait for header */
2026-01-26 11:54:35 +01:00
int64_t latency = av_gettime ();
2022-01-10 21:44:27 +02:00
err = http_read_header ( h );
2026-01-26 11:54:35 +01:00
latency = av_gettime () - latency ;
2012-05-20 16:20:56 +02:00
if ( err < 0 )
2014-03-21 18:51:31 +01:00
goto done ;
2007-02-28 03:40:23 +00:00
2026-01-26 11:54:35 +01:00
s -> nb_requests ++ ;
s -> sum_latency += latency ;
s -> max_latency = FFMAX ( s -> max_latency , latency );
2022-01-10 21:44:27 +02:00
if ( s -> new_location )
2015-04-13 00:39:44 -05:00
s -> off = off ;
2026-02-15 18:09:15 +01:00
if ( off != s -> off ) {
av_log ( h , AV_LOG_ERROR ,
"Unexpected offset: expected %" PRIu64 ", got %" PRIu64 " \n " ,
off , s -> off );
err = AVERROR ( EIO );
goto done ;
}
err = 0 ;
2014-03-21 18:51:31 +01:00
done :
av_freep ( & authstr );
av_freep ( & proxyauthstr );
return err ;
2007-02-28 03:40:23 +00:00
}
2011-11-10 14:52:50 +02:00
static int http_buf_read ( URLContext * h , uint8_t * buf , int size )
2007-02-28 03:40:23 +00:00
{
HTTPContext * s = h -> priv_data ;
int len ;
2016-12-05 10:18:10 -05:00
2026-01-23 11:16:16 +01:00
if ( ! s -> hd )
return AVERROR ( EIO );
2016-12-05 10:18:10 -05:00
if ( s -> chunksize != UINT64_MAX ) {
2017-11-13 11:34:50 -08:00
if ( s -> chunkend ) {
return AVERROR_EOF ;
}
2016-12-05 10:18:10 -05:00
if ( ! s -> chunksize ) {
char line [ 32 ];
int err ;
do {
if (( err = http_get_line ( s , line , sizeof ( line ))) < 0 )
return err ;
} while ( !* line ); /* skip CR LF from last chunk */
s -> chunksize = strtoull ( line , NULL , 16 );
av_log ( h , AV_LOG_TRACE ,
2017-11-13 11:12:52 -08:00
"Chunked encoding data size: %" PRIu64 " \n " ,
2016-12-05 10:18:10 -05:00
s -> chunksize );
2017-11-13 11:34:50 -08:00
if ( ! s -> chunksize && s -> multiple_requests ) {
http_get_line ( s , line , sizeof ( line )); // read empty chunk
s -> chunkend = 1 ;
return 0 ;
}
else if ( ! s -> chunksize ) {
2017-10-17 10:29:30 +02:00
av_log ( h , AV_LOG_DEBUG , "Last chunk received, closing conn \n " );
ffurl_closep ( & s -> hd );
2016-12-05 10:18:10 -05:00
return 0 ;
2017-10-17 10:29:30 +02:00
}
2016-12-05 10:18:10 -05:00
else if ( s -> chunksize == UINT64_MAX ) {
av_log ( h , AV_LOG_ERROR , "Invalid chunk size %" PRIu64 " \n " ,
s -> chunksize );
return AVERROR ( EINVAL );
}
}
size = FFMIN ( size , s -> chunksize );
}
2011-11-10 14:52:50 +02:00
/* read bytes from input buffer first */
len = s -> buf_end - s -> buf_ptr ;
if ( len > 0 ) {
if ( len > size )
len = size ;
memcpy ( buf , s -> buf_ptr , len );
s -> buf_ptr += len ;
} else {
2026-01-23 10:37:02 +01:00
uint64_t file_end = s -> end_off ? s -> end_off : s -> filesize ;
uint64_t target_end = s -> range_end ? s -> range_end : file_end ;
if (( ! s -> willclose || s -> chunksize == UINT64_MAX ) && s -> off >= file_end )
2011-11-10 14:52:50 +02:00
return AVERROR_EOF ;
2026-01-23 10:43:24 +01:00
if ( s -> off == target_end && target_end < file_end )
return AVERROR ( EAGAIN ); /* reached end of content range */
2011-11-10 14:52:50 +02:00
len = ffurl_read ( s -> hd , buf , size );
2020-11-13 00:06:30 +02:00
if (( ! len || len == AVERROR_EOF ) &&
( ! s -> willclose || s -> chunksize == UINT64_MAX ) && s -> off < target_end ) {
2015-03-29 00:33:35 +01:00
av_log ( h , AV_LOG_ERROR ,
2016-12-05 08:02:33 -05:00
"Stream ends prematurely at %" PRIu64 ", should be %" PRIu64 " \n " ,
2015-10-18 17:50:21 -05:00
s -> off , target_end
2015-03-29 00:33:35 +01:00
);
return AVERROR ( EIO );
}
2011-11-10 14:52:50 +02:00
}
if ( len > 0 ) {
s -> off += len ;
2017-05-18 14:35:31 +08:00
if ( s -> chunksize > 0 && s -> chunksize != UINT64_MAX ) {
2016-12-05 10:18:10 -05:00
av_assert0 ( s -> chunksize >= len );
2011-11-10 14:52:50 +02:00
s -> chunksize -= len ;
2016-12-05 10:18:10 -05:00
}
2011-11-10 14:52:50 +02:00
}
return len ;
}
2013-07-23 04:07:10 +08:00
#if CONFIG_ZLIB
#define DECOMPRESS_BUF_SIZE (256 * 1024)
static int http_buf_read_compressed ( URLContext * h , uint8_t * buf , int size )
{
HTTPContext * s = h -> priv_data ;
int ret ;
if ( ! s -> inflate_buffer ) {
s -> inflate_buffer = av_malloc ( DECOMPRESS_BUF_SIZE );
if ( ! s -> inflate_buffer )
return AVERROR ( ENOMEM );
}
if ( s -> inflate_stream . avail_in == 0 ) {
int read = http_buf_read ( h , s -> inflate_buffer , DECOMPRESS_BUF_SIZE );
if ( read <= 0 )
return read ;
s -> inflate_stream . next_in = s -> inflate_buffer ;
s -> inflate_stream . avail_in = read ;
}
s -> inflate_stream . avail_out = size ;
s -> inflate_stream . next_out = buf ;
ret = inflate ( & s -> inflate_stream , Z_SYNC_FLUSH );
if ( ret != Z_OK && ret != Z_STREAM_END )
2014-07-22 11:42:03 -07:00
av_log ( h , AV_LOG_WARNING , "inflate return value: %d, %s \n " ,
ret , s -> inflate_stream . msg );
2013-07-23 04:07:10 +08:00
return size - s -> inflate_stream . avail_out ;
}
2014-07-22 11:42:03 -07:00
#endif /* CONFIG_ZLIB */
2013-07-23 04:07:10 +08:00
2015-03-12 11:39:55 +08:00
static int64_t http_seek_internal ( URLContext * h , int64_t off , int whence , int force_reconnect );
2014-03-02 20:26:19 +01:00
static int http_read_stream ( URLContext * h , uint8_t * buf , int size )
2011-11-10 14:52:50 +02:00
{
HTTPContext * s = h -> priv_data ;
2022-01-10 21:44:27 +02:00
int err , read_ret ;
2016-06-09 10:54:20 -07:00
int64_t seek_ret ;
2018-01-02 17:05:03 +01:00
int reconnect_delay = 0 ;
2024-04-22 15:25:45 +01:00
int reconnect_delay_total = 0 ;
2024-04-22 15:25:44 +01:00
int conn_attempts = 1 ;
2012-05-21 11:27:10 +02:00
2012-06-17 21:15:32 +03:00
if ( ! s -> hd )
return AVERROR_EOF ;
2012-05-21 11:27:10 +02:00
2012-06-17 21:19:41 +03:00
if ( s -> end_chunked_post && ! s -> end_header ) {
2022-01-10 21:44:27 +02:00
err = http_read_header ( h );
2012-06-17 21:19:41 +03:00
if ( err < 0 )
return err ;
2012-05-21 11:27:10 +02:00
}
2007-02-28 03:40:23 +00:00
2014-03-02 20:26:19 +01:00
#if CONFIG_ZLIB
if ( s -> compressed )
return http_buf_read_compressed ( h , buf , size );
2014-07-22 11:42:03 -07:00
#endif /* CONFIG_ZLIB */
2026-02-15 18:28:29 +01:00
retry :
2015-03-12 11:39:55 +08:00
read_ret = http_buf_read ( h , buf , size );
2018-01-11 02:27:20 +01:00
while ( read_ret < 0 ) {
2016-12-05 08:02:33 -05:00
uint64_t target = h -> is_streamed ? 0 : s -> off ;
2020-07-27 11:46:56 +02:00
bool is_premature = s -> filesize > 0 && s -> off < s -> filesize ;
2015-09-07 19:38:16 +02:00
2018-01-04 17:06:52 +01:00
if ( read_ret == AVERROR_EXIT )
2018-01-11 02:27:20 +01:00
break ;
2026-01-23 10:43:24 +01:00
else if ( read_ret == AVERROR ( EAGAIN )) {
/* send new request for more data on existing connection */
AVDictionary * options = NULL ;
if ( s -> willclose )
ffurl_closep ( & s -> hd );
2026-02-09 16:56:25 +01:00
s -> initial_requests = 0 ; /* continue streaming uninterrupted from now on */
2026-01-23 10:43:24 +01:00
read_ret = http_open_cnx ( h , & options );
av_dict_free ( & options );
if ( read_ret == 0 )
goto retry ;
}
2018-01-11 02:27:20 +01:00
if ( h -> is_streamed && ! s -> reconnect_streamed )
break ;
2020-07-27 11:46:56 +02:00
if ( ! ( s -> reconnect && is_premature ) &&
! ( s -> reconnect_at_eof && read_ret == AVERROR_EOF )) {
if ( is_premature )
return AVERROR ( EIO );
else
break ;
}
2018-01-04 17:06:52 +01:00
2024-04-22 15:25:45 +01:00
if ( reconnect_delay > s -> reconnect_delay_max || ( s -> reconnect_max_retries >= 0 && conn_attempts > s -> reconnect_max_retries ) ||
reconnect_delay_total > s -> reconnect_delay_total_max )
2015-09-07 19:38:16 +02:00
return AVERROR ( EIO );
2018-01-02 17:17:41 +01:00
av_log ( h , AV_LOG_WARNING , "Will reconnect at %" PRIu64 " in %d second(s), error=%s. \n " , s -> off , reconnect_delay , av_err2str ( read_ret ));
2018-01-02 17:05:03 +01:00
err = ff_network_sleep_interruptible ( 1000U * 1000 * reconnect_delay , & h -> interrupt_callback );
if ( err != AVERROR ( ETIMEDOUT ))
return err ;
2024-04-22 15:25:45 +01:00
reconnect_delay_total += reconnect_delay ;
2018-01-02 17:05:03 +01:00
reconnect_delay = 1 + 2 * reconnect_delay ;
2024-04-22 15:25:44 +01:00
conn_attempts ++ ;
2015-09-06 19:58:10 +02:00
seek_ret = http_seek_internal ( h , target , SEEK_SET , 1 );
2018-01-02 17:05:03 +01:00
if ( seek_ret >= 0 && seek_ret != target ) {
2026-02-15 18:29:07 +01:00
ffurl_closep ( & s -> hd );
2016-12-05 08:02:33 -05:00
av_log ( h , AV_LOG_ERROR , "Failed to reconnect at %" PRIu64 ". \n " , target );
2015-03-12 11:39:55 +08:00
return read_ret ;
}
read_ret = http_buf_read ( h , buf , size );
2018-01-02 17:05:03 +01:00
}
2015-03-12 11:39:55 +08:00
return read_ret ;
2014-03-02 20:26:19 +01:00
}
// Like http_read_stream(), but no short reads.
// Assumes partial reads are an error.
static int http_read_stream_all ( URLContext * h , uint8_t * buf , int size )
{
int pos = 0 ;
while ( pos < size ) {
int len = http_read_stream ( h , buf + pos , size - pos );
if ( len < 0 )
return len ;
pos += len ;
}
return pos ;
}
2019-02-10 00:59:30 +01:00
static void update_metadata ( URLContext * h , char * data )
2014-07-31 19:56:35 -04:00
{
char * key ;
char * val ;
char * end ;
char * next = data ;
2019-02-10 00:59:30 +01:00
HTTPContext * s = h -> priv_data ;
2014-07-31 19:56:35 -04:00
while ( * next ) {
key = next ;
val = strstr ( key , "='" );
if ( ! val )
break ;
end = strstr ( val , "';" );
if ( ! end )
break ;
* val = '\0' ;
* end = '\0' ;
val += 2 ;
av_dict_set ( & s -> metadata , key , val , 0 );
2019-02-10 00:59:30 +01:00
av_log ( h , AV_LOG_VERBOSE , "Metadata update for %s: %s \n " , key , val );
2014-07-31 19:56:35 -04:00
next = end + 2 ;
}
}
2014-03-10 21:11:35 +01:00
static int store_icy ( URLContext * h , int size )
{
HTTPContext * s = h -> priv_data ;
/* until next metadata packet */
2016-12-05 08:02:33 -05:00
uint64_t remaining ;
2014-03-10 21:11:35 +01:00
2016-12-05 08:02:33 -05:00
if ( s -> icy_metaint < s -> icy_data_read )
2014-03-10 21:11:35 +01:00
return AVERROR_INVALIDDATA ;
2016-12-05 08:02:33 -05:00
remaining = s -> icy_metaint - s -> icy_data_read ;
2014-03-10 21:11:35 +01:00
if ( ! remaining ) {
2014-07-22 11:42:03 -07:00
/* The metadata packet is variable sized. It has a 1 byte header
* which sets the length of the packet (divided by 16). If it's 0,
* the metadata doesn't change. After the packet, icy_metaint bytes
* of normal data follows. */
2014-03-10 21:11:35 +01:00
uint8_t ch ;
int len = http_read_stream_all ( h , & ch , 1 );
if ( len < 0 )
return len ;
if ( ch > 0 ) {
char data [ 255 * 16 + 1 ];
int ret ;
len = ch * 16 ;
ret = http_read_stream_all ( h , data , len );
if ( ret < 0 )
return ret ;
2025-10-31 17:32:56 +01:00
data [ len ] = 0 ;
2014-03-10 21:11:35 +01:00
if (( ret = av_opt_set ( s , "icy_metadata_packet" , data , 0 )) < 0 )
return ret ;
2019-02-10 00:59:30 +01:00
update_metadata ( h , data );
2014-03-10 21:11:35 +01:00
}
s -> icy_data_read = 0 ;
2014-07-22 11:42:03 -07:00
remaining = s -> icy_metaint ;
2014-03-10 21:11:35 +01:00
}
return FFMIN ( size , remaining );
}
2014-03-02 20:26:19 +01:00
static int http_read ( URLContext * h , uint8_t * buf , int size )
{
HTTPContext * s = h -> priv_data ;
2013-06-26 00:53:26 +02:00
if ( s -> icy_metaint > 0 ) {
2014-03-10 21:11:35 +01:00
size = store_icy ( h , size );
if ( size < 0 )
return size ;
2013-06-26 00:53:26 +02:00
}
2014-03-10 21:11:35 +01:00
2014-03-02 20:26:19 +01:00
size = http_read_stream ( h , buf , size );
if ( size > 0 )
s -> icy_data_read += size ;
return size ;
2007-02-28 03:40:23 +00:00
}
/* used only when posting data */
2010-06-01 07:46:23 +00:00
static int http_write ( URLContext * h , const uint8_t * buf , int size )
2007-02-28 03:40:23 +00:00
{
2010-06-08 11:48:03 +00:00
char temp [ 11 ] = "" ; /* 32-bit hex + CRLF + nul */
2010-01-12 16:36:00 +00:00
int ret ;
char crlf [] = " \r\n " ;
2007-02-28 03:40:23 +00:00
HTTPContext * s = h -> priv_data ;
2010-01-12 16:36:00 +00:00
2011-11-10 11:03:35 +02:00
if ( ! s -> chunked_post ) {
2010-06-21 18:40:53 +00:00
/* non-chunked data is sent without any special encoding */
2011-03-31 16:48:01 +02:00
return ffurl_write ( s -> hd , buf , size );
2010-01-12 16:36:00 +00:00
}
/* silently ignore zero-size data since chunk encoding that would
* signal EOF */
if ( size > 0 ) {
/* upload data using chunked encoding */
2010-06-21 19:02:35 +00:00
snprintf ( temp , sizeof ( temp ), "%x \r\n " , size );
2010-01-12 16:36:00 +00:00
2011-03-31 16:48:01 +02:00
if (( ret = ffurl_write ( s -> hd , temp , strlen ( temp ))) < 0 ||
2014-07-22 11:42:03 -07:00
( ret = ffurl_write ( s -> hd , buf , size )) < 0 ||
2011-03-31 16:48:01 +02:00
( ret = ffurl_write ( s -> hd , crlf , sizeof ( crlf ) - 1 )) < 0 )
2010-01-12 16:36:00 +00:00
return ret ;
}
return size ;
2007-02-28 03:40:23 +00:00
}
2012-05-21 11:26:40 +02:00
static int http_shutdown ( URLContext * h , int flags )
2007-02-28 03:40:23 +00:00
{
2010-01-12 16:36:00 +00:00
int ret = 0 ;
char footer [] = "0 \r\n\r\n " ;
2007-02-28 03:40:23 +00:00
HTTPContext * s = h -> priv_data ;
2010-01-12 16:36:00 +00:00
/* signal end of chunked encoding if used */
2015-06-09 22:26:48 +02:00
if ((( flags & AVIO_FLAG_WRITE ) && s -> chunked_post ) ||
(( flags & AVIO_FLAG_READ ) && s -> chunked_post && s -> listen )) {
2011-03-31 16:48:01 +02:00
ret = ffurl_write ( s -> hd , footer , sizeof ( footer ) - 1 );
2010-01-12 16:36:00 +00:00
ret = ret > 0 ? 0 : ret ;
2018-04-04 16:38:18 +05:30
/* flush the receive buffer when it is write only mode */
if ( ! ( flags & AVIO_FLAG_READ )) {
char buf [ 1024 ];
int read_ret ;
s -> hd -> flags |= AVIO_FLAG_NONBLOCK ;
read_ret = ffurl_read ( s -> hd , buf , sizeof ( buf ));
s -> hd -> flags &= ~ AVIO_FLAG_NONBLOCK ;
if ( read_ret < 0 && read_ret != AVERROR ( EAGAIN )) {
2019-07-11 09:35:31 +08:00
av_log ( h , AV_LOG_ERROR , "URL read error: %s \n " , av_err2str ( read_ret ));
2018-04-04 16:38:18 +05:30
ret = read_ret ;
}
}
2012-05-21 11:26:40 +02:00
s -> end_chunked_post = 1 ;
}
return ret ;
}
static int http_close ( URLContext * h )
{
int ret = 0 ;
HTTPContext * s = h -> priv_data ;
2013-07-23 04:07:10 +08:00
#if CONFIG_ZLIB
inflateEnd ( & s -> inflate_stream );
av_freep ( & s -> inflate_buffer );
2014-07-22 11:42:03 -07:00
#endif /* CONFIG_ZLIB */
2013-07-23 04:07:10 +08:00
2018-11-16 14:27:12 +05:30
if ( s -> hd && ! s -> end_chunked_post )
2012-05-21 11:26:40 +02:00
/* Close the write direction by sending the end of chunked encoding. */
ret = http_shutdown ( h , h -> flags );
2010-01-12 16:36:00 +00:00
2010-06-08 11:18:22 +00:00
if ( s -> hd )
2012-06-01 14:48:17 +02:00
ffurl_closep ( & s -> hd );
2011-11-07 11:06:50 +02:00
av_dict_free ( & s -> chained_options );
2021-06-01 18:50:51 +02:00
av_dict_free ( & s -> cookie_dict );
2022-01-10 21:44:27 +02:00
av_dict_free ( & s -> redirect_cache );
av_freep ( & s -> new_location );
2021-12-27 11:20:24 +02:00
av_freep ( & s -> uri );
2026-01-26 11:54:35 +01:00
av_log ( h , AV_LOG_DEBUG , "Statistics: %d connection%s, %d request%s, %d retr%s, %d reconnection%s, %d redirect%s \n " ,
s -> nb_connections , s -> nb_connections == 1 ? "" : "s" ,
s -> nb_requests , s -> nb_requests == 1 ? "" : "s" ,
s -> nb_retries , s -> nb_retries == 1 ? "y" : "ies" ,
s -> nb_reconnects , s -> nb_reconnects == 1 ? "" : "s" ,
s -> nb_redirects , s -> nb_redirects == 1 ? "" : "s" );
if ( s -> nb_requests > 0 ) {
av_log ( h , AV_LOG_DEBUG , "Latency: %.2f ms avg, %.2f ms max \n " ,
1e-3 * s -> sum_latency / s -> nb_requests ,
1e-3 * s -> max_latency );
}
2010-01-12 16:36:00 +00:00
return ret ;
2007-02-28 03:40:23 +00:00
}
2015-03-12 11:39:55 +08:00
static int64_t http_seek_internal ( URLContext * h , int64_t off , int whence , int force_reconnect )
2007-02-28 03:40:23 +00:00
{
HTTPContext * s = h -> priv_data ;
2026-01-23 10:46:55 +01:00
URLContext * old_hd = NULL ;
2016-12-05 08:02:33 -05:00
uint64_t old_off = s -> off ;
2010-01-13 23:27:52 +00:00
uint8_t old_buf [ BUFFER_SIZE ];
2014-03-10 17:17:25 +01:00
int old_buf_size , ret ;
2011-11-07 11:06:50 +02:00
AVDictionary * options = NULL ;
2026-01-23 10:46:55 +01:00
uint8_t discard [ 4096 ];
2007-02-28 03:40:23 +00:00
if ( whence == AVSEEK_SIZE )
2026-05-31 22:24:16 +02:00
return s -> filesize == UINT64_MAX ? AVERROR ( ENOSYS ) : s -> filesize ;
2016-12-05 08:02:33 -05:00
else if (( s -> filesize == UINT64_MAX && whence == SEEK_END ))
2014-03-10 17:17:25 +01:00
return AVERROR ( ENOSYS );
2007-02-28 03:40:23 +00:00
if ( whence == SEEK_CUR )
off += s -> off ;
else if ( whence == SEEK_END )
off += s -> filesize ;
2014-02-28 00:36:07 +01:00
else if ( whence != SEEK_SET )
return AVERROR ( EINVAL );
2014-02-28 00:36:06 +01:00
if ( off < 0 )
return AVERROR ( EINVAL );
2026-01-22 15:31:02 +01:00
if ( ! force_reconnect && off == s -> off )
return s -> off ;
2007-02-28 03:40:23 +00:00
s -> off = off ;
2015-09-06 19:05:52 +02:00
if ( s -> off && h -> is_streamed )
return AVERROR ( ENOSYS );
2019-02-20 09:52:43 -05:00
/* do not try to make a new connection if seeking past the end of the file */
if ( s -> end_off || s -> filesize != UINT64_MAX ) {
uint64_t end_pos = s -> end_off ? s -> end_off : s -> filesize ;
if ( s -> off >= end_pos )
return s -> off ;
}
2022-01-19 09:04:35 +02:00
/* if the location changed (redirect), revert to the original uri */
if ( strcmp ( s -> uri , s -> location )) {
2021-12-27 11:20:24 +02:00
char * new_uri ;
new_uri = av_strdup ( s -> uri );
if ( ! new_uri )
return AVERROR ( ENOMEM );
av_free ( s -> location );
s -> location = new_uri ;
}
2014-02-28 00:36:06 +01:00
/* we save the old context in case the seek fails */
old_buf_size = s -> buf_end - s -> buf_ptr ;
memcpy ( old_buf , s -> buf_ptr , old_buf_size );
2026-01-23 10:46:55 +01:00
/* try to reuse existing connection for small seeks */
uint64_t remaining = s -> range_end - old_off - old_buf_size ;
2026-02-09 16:41:52 +01:00
if ( s -> hd && ! s -> willclose && s -> range_end && remaining <= ffurl_get_short_seek ( h )) {
2026-01-23 10:46:55 +01:00
/* drain remaining data left on the wire from previous request */
av_log ( h , AV_LOG_DEBUG , "Soft-seeking to offset %" PRIu64 " by draining "
"%" PRIu64 " remaining byte(s) \n " , s -> off , remaining );
while ( remaining ) {
2026-02-21 16:49:51 +01:00
ret = ffurl_read ( s -> hd , discard , FFMIN ( remaining , sizeof ( discard )));
2026-01-23 10:46:55 +01:00
if ( ret < 0 || ret == AVERROR_EOF || ( ret == 0 && remaining )) {
/* connection broken or stuck, need to reopen */
ffurl_closep ( & s -> hd );
break ;
}
remaining -= ret ;
}
} else {
/* can't soft seek; always open new connection */
old_hd = s -> hd ;
s -> hd = NULL ;
}
2014-02-28 00:36:06 +01:00
2007-02-28 03:40:23 +00:00
/* if it fails, continue on old connection */
2014-03-10 17:17:25 +01:00
if (( ret = http_open_cnx ( h , & options )) < 0 ) {
2011-11-07 11:06:50 +02:00
av_dict_free ( & options );
2010-01-13 23:27:52 +00:00
memcpy ( s -> buffer , old_buf , old_buf_size );
s -> buf_ptr = s -> buffer ;
s -> buf_end = s -> buffer + old_buf_size ;
2014-07-22 11:42:03 -07:00
s -> hd = old_hd ;
s -> off = old_off ;
2014-03-10 17:17:25 +01:00
return ret ;
2007-02-28 03:40:23 +00:00
}
2011-11-07 11:06:50 +02:00
av_dict_free ( & options );
2011-03-31 17:36:06 +02:00
ffurl_close ( old_hd );
2007-02-28 03:40:23 +00:00
return off ;
}
2015-03-12 11:39:55 +08:00
static int64_t http_seek ( URLContext * h , int64_t off , int whence )
{
return http_seek_internal ( h , off , whence , 0 );
}
2014-07-22 11:42:03 -07:00
static int http_get_file_handle ( URLContext * h )
2009-03-03 17:04:51 +00:00
{
HTTPContext * s = h -> priv_data ;
2011-03-31 17:51:24 +02:00
return ffurl_get_file_handle ( s -> hd );
2009-03-03 17:04:51 +00:00
}
2017-01-30 10:00:44 -06:00
static int http_get_short_seek ( URLContext * h )
{
HTTPContext * s = h -> priv_data ;
2021-10-26 16:03:59 +01:00
if ( s -> short_seek_size >= 1 )
return s -> short_seek_size ;
2017-01-30 10:00:44 -06:00
return ffurl_get_short_seek ( s -> hd );
}
2014-07-22 11:42:03 -07:00
#define HTTP_CLASS(flavor) \
static const AVClass flavor ## _context_class = { \
.class_name = # flavor, \
2024-01-19 13:33:28 +01:00
.item_name = av_default_item_name, \
2026-02-21 16:49:51 +01:00
.option = http_options, \
2014-07-22 11:42:03 -07:00
.version = LIBAVUTIL_VERSION_INT, \
2014-07-18 00:55:05 +02:00
}
2011-02-06 00:20:26 +02:00
#if CONFIG_HTTP_PROTOCOL
2014-07-18 00:55:05 +02:00
HTTP_CLASS ( http );
2016-02-19 10:39:29 +01:00
const URLProtocol ff_http_protocol = {
2011-04-08 07:41:47 +02:00
. name = "http" ,
2011-11-07 11:06:50 +02:00
. url_open2 = http_open ,
2015-07-03 02:28:56 +02:00
. url_accept = http_accept ,
. url_handshake = http_handshake ,
2011-04-08 07:41:47 +02:00
. url_read = http_read ,
. url_write = http_write ,
. url_seek = http_seek ,
. url_close = http_close ,
2009-03-03 17:04:51 +00:00
. url_get_file_handle = http_get_file_handle ,
2017-01-30 10:00:44 -06:00
. url_get_short_seek = http_get_short_seek ,
2012-05-21 11:26:40 +02:00
. url_shutdown = http_shutdown ,
2011-04-08 07:41:47 +02:00
. priv_data_size = sizeof ( HTTPContext ),
2011-11-05 12:54:01 +01:00
. priv_data_class = & http_context_class ,
2011-12-30 11:38:05 +02:00
. flags = URL_PROTOCOL_FLAG_NETWORK ,
2019-09-24 11:18:59 +08:00
. default_whitelist = "http,https,tls,rtp,tcp,udp,crypto,httpproxy,data"
2007-02-28 03:40:23 +00:00
};
2014-07-22 11:42:03 -07:00
#endif /* CONFIG_HTTP_PROTOCOL */
2011-02-06 00:20:26 +02:00
#if CONFIG_HTTPS_PROTOCOL
2014-07-18 00:55:05 +02:00
HTTP_CLASS ( https );
2016-02-19 10:39:29 +01:00
const URLProtocol ff_https_protocol = {
2011-02-06 00:20:26 +02:00
. name = "https" ,
2011-11-07 11:06:50 +02:00
. url_open2 = http_open ,
2011-02-06 00:20:26 +02:00
. url_read = http_read ,
. url_write = http_write ,
. url_seek = http_seek ,
. url_close = http_close ,
. url_get_file_handle = http_get_file_handle ,
2017-01-30 10:00:44 -06:00
. url_get_short_seek = http_get_short_seek ,
2012-06-01 16:36:20 +03:00
. url_shutdown = http_shutdown ,
2011-02-06 00:20:26 +02:00
. priv_data_size = sizeof ( HTTPContext ),
2011-11-05 12:54:01 +01:00
. priv_data_class = & https_context_class ,
2011-12-30 11:38:05 +02:00
. flags = URL_PROTOCOL_FLAG_NETWORK ,
2016-03-14 16:33:57 +01:00
. default_whitelist = "http,https,tls,rtp,tcp,udp,crypto,httpproxy"
2011-02-06 00:20:26 +02:00
};
2014-07-22 11:42:03 -07:00
#endif /* CONFIG_HTTPS_PROTOCOL */
2011-11-10 14:53:16 +02:00
#if CONFIG_HTTPPROXY_PROTOCOL
static int http_proxy_close ( URLContext * h )
{
HTTPContext * s = h -> priv_data ;
if ( s -> hd )
2012-06-01 14:48:17 +02:00
ffurl_closep ( & s -> hd );
2011-11-10 14:53:16 +02:00
return 0 ;
}
static int http_proxy_open ( URLContext * h , const char * uri , int flags )
{
HTTPContext * s = h -> priv_data ;
char hostname [ 1024 ], hoststr [ 1024 ];
char auth [ 1024 ], pathbuf [ 1024 ], * path ;
2012-05-20 16:20:56 +02:00
char lower_url [ 100 ];
2024-04-22 15:25:43 +01:00
int port , ret = 0 , auth_attempts = 0 ;
2011-11-10 14:53:16 +02:00
HTTPAuthType cur_auth_type ;
char * authstr ;
2012-10-02 22:22:44 +01:00
if ( s -> seekable == 1 )
h -> is_streamed = 0 ;
else
h -> is_streamed = 1 ;
2011-11-10 14:53:16 +02:00
av_url_split ( NULL , 0 , auth , sizeof ( auth ), hostname , sizeof ( hostname ), & port ,
pathbuf , sizeof ( pathbuf ), uri );
ff_url_join ( hoststr , sizeof ( hoststr ), NULL , NULL , hostname , port , NULL );
path = pathbuf ;
if ( * path == '/' )
path ++ ;
ff_url_join ( lower_url , sizeof ( lower_url ), "tcp" , NULL , hostname , port ,
NULL );
redo :
2016-01-30 02:17:51 +01:00
ret = ffurl_open_whitelist ( & s -> hd , lower_url , AVIO_FLAG_READ_WRITE ,
& h -> interrupt_callback , NULL ,
2016-04-21 15:55:09 +01:00
h -> protocol_whitelist , h -> protocol_blacklist , h );
2011-11-10 14:53:16 +02:00
if ( ret < 0 )
return ret ;
authstr = ff_http_auth_create_response ( & s -> proxy_auth_state , auth ,
path , "CONNECT" );
snprintf ( s -> buffer , sizeof ( s -> buffer ),
"CONNECT %s HTTP/1.1 \r\n "
"Host: %s \r\n "
"Connection: close \r\n "
"%s%s"
" \r\n " ,
path ,
hoststr ,
authstr ? "Proxy-" : "" , authstr ? authstr : "" );
av_freep ( & authstr );
if (( ret = ffurl_write ( s -> hd , s -> buffer , strlen ( s -> buffer ))) < 0 )
goto fail ;
2014-07-22 11:42:03 -07:00
s -> buf_ptr = s -> buffer ;
s -> buf_end = s -> buffer ;
2011-11-10 14:53:16 +02:00
s -> line_count = 0 ;
2016-12-05 08:02:33 -05:00
s -> filesize = UINT64_MAX ;
2011-11-10 14:53:16 +02:00
cur_auth_type = s -> proxy_auth_state . auth_type ;
2012-05-20 16:20:56 +02:00
/* Note: This uses buffering, potentially reading more than the
* HTTP header. If tunneling a protocol where the server starts
* the conversation, we might buffer part of that here, too.
* Reading that requires using the proper ffurl_read() function
* on this URLContext, not using the fd directly (as the tls
* protocol does). This shouldn't be an issue for tls though,
* since the client starts the conversation there, so there
* is no extra data that we might buffer up here.
*/
2022-01-10 21:44:27 +02:00
ret = http_read_header ( h );
2012-05-20 16:20:56 +02:00
if ( ret < 0 )
goto fail ;
2011-11-10 14:53:16 +02:00
2024-04-22 15:25:43 +01:00
auth_attempts ++ ;
2012-03-12 14:00:16 +02:00
if ( s -> http_code == 407 &&
( cur_auth_type == HTTP_AUTH_NONE || s -> proxy_auth_state . stale ) &&
2024-04-22 15:25:43 +01:00
s -> proxy_auth_state . auth_type != HTTP_AUTH_NONE && auth_attempts < 2 ) {
2012-06-01 14:48:17 +02:00
ffurl_closep ( & s -> hd );
2011-11-10 14:53:16 +02:00
goto redo ;
}
if ( s -> http_code < 400 )
return 0 ;
2014-10-18 00:24:01 +04:00
ret = ff_http_averror ( s -> http_code , AVERROR ( EIO ));
2011-11-10 14:53:16 +02:00
fail :
http_proxy_close ( h );
return ret ;
}
static int http_proxy_write ( URLContext * h , const uint8_t * buf , int size )
{
HTTPContext * s = h -> priv_data ;
return ffurl_write ( s -> hd , buf , size );
}
2016-02-19 10:39:29 +01:00
const URLProtocol ff_httpproxy_protocol = {
2011-11-10 14:53:16 +02:00
. name = "httpproxy" ,
. url_open = http_proxy_open ,
. url_read = http_buf_read ,
. url_write = http_proxy_write ,
. url_close = http_proxy_close ,
. url_get_file_handle = http_get_file_handle ,
. priv_data_size = sizeof ( HTTPContext ),
2011-12-30 11:38:05 +02:00
. flags = URL_PROTOCOL_FLAG_NETWORK ,
2011-11-10 14:53:16 +02:00
};
2014-07-22 11:42:03 -07:00
#endif /* CONFIG_HTTPPROXY_PROTOCOL */