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"
2014-07-22 11:42:03 -07:00
2025-05-22 20:14:49 +02:00
# include <string.h>
2023-09-02 12:11:59 +02:00
# include <time.h>
2014-07-22 11:42:03 -07:00
# if CONFIG_ZLIB
# include <zlib.h>
# endif /* CONFIG_ZLIB */
2015-07-03 02:28:56 +02:00
# include "libavutil/avassert.h"
2008-05-09 11:56:36 +00: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
2007-02-28 03:40:23 +00:00
# include "avformat.h"
2014-07-22 11:42:03 -07:00
# include "http.h"
# include "httpauth.h"
2010-03-14 23:59:48 +00:00
# include "internal.h"
2007-02-28 03:40:23 +00:00
# include "network.h"
2007-11-22 02:27:39 +00:00
# include "os_support.h"
2011-03-31 16:25:10 +02:00
# 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 ;
2016-12-05 08:02:33 -05:00
uint64_t off , end_off , filesize ;
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 ;
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
2010-06-22 14:13:55 +00:00
static const AVOption 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 } ,
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 } ,
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 ) {
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 ) {
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 ;
}
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 ;
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 ) ;
2007-02-28 03:40:23 +00:00
if ( redirects + + > = 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 ;
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 :
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 )
2015-06-04 01:20:28 +02:00
{
2015-07-03 02:28:56 +02:00
int ret , body = 0 , reply_code , message_len ;
const char * reply_text , * content_type ;
2015-06-04 01:20:28 +02:00
HTTPContext * s = h - > priv_data ;
2015-07-03 02:28:56 +02:00
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 ;
}
static void handle_http_errors ( URLContext * h , int error )
{
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
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 ;
const char * slash ;
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 ) ;
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 ) {
s - > expires = time ( NULL ) + atoi ( p + offset ) ;
}
}
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 + + ;
* ( 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 + + ;
* ( 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 ;
2017-04-30 14:25:29 -04:00
}
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 ;
2013-01-13 21:32:57 -05:00
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 ;
2017-04-30 14:25:29 -04:00
}
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 ;
2012-05-30 11:52:11 +02:00
if ( ( err = http_get_line ( s , line , sizeof ( line ) ) ) < 0 )
return err ;
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 */
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 ) ;
2014-03-06 18:40:00 +01:00
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 " ) ;
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 ) ;
if ( authstr )
av_bprintf ( & request , " %s " , authstr ) ;
if ( proxyauthstr )
av_bprintf ( & request , " Proxy-%s " , proxyauthstr ) ;
av_bprintf ( & request , " \r \n " ) ;
av_log ( h , AV_LOG_DEBUG , " request: %s \n " , request . str ) ;
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 */
2022-01-10 21:44:27 +02:00
err = http_read_header ( h ) ;
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
2022-01-10 21:44:27 +02:00
if ( s - > new_location )
2015-04-13 00:39:44 -05:00
s - > off = off ;
2014-03-21 18:51:31 +01:00
err = ( off = = s - > off ) ? 0 : - 1 ;
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
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 {
2016-12-05 08:02:33 -05:00
uint64_t target_end = s - > end_off ? s - > end_off : s - > filesize ;
if ( ( ! s - > willclose | | s - > chunksize = = UINT64_MAX ) & & s - > off > = target_end )
2011-11-10 14:52:50 +02:00
return AVERROR_EOF ;
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 */
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 ;
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 ) {
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-06-08 11:48:03 +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 ) ;
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 ;
URLContext * old_hd = s - > hd ;
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 ;
2007-02-28 03:40:23 +00:00
if ( whence = = AVSEEK_SIZE )
return s - > filesize ;
2015-03-12 11:39:55 +08:00
else if ( ! force_reconnect & &
( ( whence = = SEEK_CUR & & off = = 0 ) | |
( whence = = SEEK_SET & & off = = s - > off ) ) )
2013-12-30 12:46:32 +02:00
return s - > off ;
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 ) ;
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 ) ;
s - > hd = NULL ;
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, \
2014-07-22 11:42:03 -07:00
.option = options, \
.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 */