@ -48,6 +48,7 @@
* path names ) . */
# define BUFFER_SIZE (MAX_URL_SIZE + HTTP_HEADERS_SIZE)
# define MAX_REDIRECTS 8
# define MAX_CACHED_REDIRECTS 32
# define HTTP_SINGLE 1
# define HTTP_MUTLI 2
# define MAX_EXPIRY 19
@ -129,6 +130,9 @@ typedef struct HTTPContext {
HandshakeState handshake_step ;
int is_connected_server ;
int short_seek_size ;
int64_t expires ;
char * new_location ;
AVDictionary * redirect_cache ;
} HTTPContext ;
# define OFFSET(x) offsetof(HTTPContext, x)
@ -177,8 +181,8 @@ static const AVOption options[] = {
static int http_connect ( URLContext * h , const char * path , const char * local_path ,
const char * hoststr , const char * auth ,
const char * proxyauth , int * new_location ) ;
static int http_read_header ( URLContext * h , int * new_location ) ;
const char * proxyauth ) ;
static int http_read_header ( URLContext * h ) ;
static int http_shutdown ( URLContext * h , int flags ) ;
void ff_http_init_auth_state ( URLContext * dest , const URLContext * src )
@ -199,7 +203,7 @@ static int http_open_cnx_internal(URLContext *h, AVDictionary **options)
char auth [ 1024 ] , proxyauth [ 1024 ] = " " ;
char path1 [ MAX_URL_SIZE ] , sanitized_path [ MAX_URL_SIZE + 1 ] ;
char buf [ 1024 ] , urlbuf [ MAX_URL_SIZE ] ;
int port , use_proxy , err , location_changed = 0 ;
int port , use_proxy , err ;
HTTPContext * s = h - > priv_data ;
av_url_split ( proto , sizeof ( proto ) , auth , sizeof ( auth ) ,
@ -259,12 +263,8 @@ static int http_open_cnx_internal(URLContext *h, AVDictionary **options)
return err ;
}
err = http_connect ( h , path , local_path , hoststr ,
auth , proxyauth , & location_changed ) ;
if ( err < 0 )
return err ;
return location_changed ;
return http_connect ( h , path , local_path , hoststr ,
auth , proxyauth ) ;
}
static int http_should_reconnect ( HTTPContext * s , int err )
@ -300,31 +300,87 @@ static int http_should_reconnect(HTTPContext *s, int err)
return av_match_list ( http_code , s - > reconnect_on_http_error , ' , ' ) > 0 ;
}
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 ) ;
if ( ret < 0 ) {
av_free ( value ) ;
return ret ;
}
return 0 ;
}
/* return non zero if error */
static int http_open_cnx ( URLContext * h , AVDictionary * * options )
{
HTTPAuthType cur_auth_type , cur_proxy_auth_type ;
HTTPContext * s = h - > priv_data ;
int location_changed , attempts = 0 , redirects = 0 ;
int ret , attempts = 0 , redirects = 0 ;
int reconnect_delay = 0 ;
uint64_t off ;
char * cached ;
redo :
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 ;
}
av_dict_copy ( options , s - > chained_options , 0 ) ;
cur_auth_type = s - > auth_state . auth_type ;
cur_proxy_auth_type = s - > auth_state . auth_type ;
off = s - > off ;
location_changed = http_open_cnx_internal ( h , options ) ;
if ( location_changed < 0 ) {
if ( ! http_should_reconnect ( s , location_changed ) | |
ret = http_open_cnx_internal ( h , options ) ;
if ( ret < 0 ) {
if ( ! http_should_reconnect ( s , ret ) | |
reconnect_delay > s - > reconnect_delay_max )
goto fail ;
av_log ( h , AV_LOG_WARNING , " Will reconnect at % " PRIu64 " in %d second(s). \n " , off , reconnect_delay ) ;
location_changed = ff_network_sleep_interruptible ( 1000U * 1000 * reconnect_delay , & h - > interrupt_callback ) ;
if ( location_changed ! = AVERROR ( ETIMEDOUT ) )
ret = ff_network_sleep_interruptible ( 1000U * 1000 * reconnect_delay , & h - > interrupt_callback ) ;
if ( ret ! = AVERROR ( ETIMEDOUT ) )
goto fail ;
reconnect_delay = 1 + 2 * reconnect_delay ;
@ -354,16 +410,28 @@ redo:
}
if ( ( s - > http_code = = 301 | | s - > http_code = = 302 | |
s - > http_code = = 303 | | s - > http_code = = 307 | | s - > http_code = = 308 ) & &
location_changed = = 1 ) {
s - > new_location ) {
/* url moved, get next */
ffurl_closep ( & s - > hd ) ;
if ( redirects + + > = MAX_REDIRECTS )
return AVERROR ( EIO ) ;
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 ;
/* Restart the authentication process with the new target, which
* might use a different auth mechanism . */
memset ( & s - > auth_state , 0 , sizeof ( s - > auth_state ) ) ;
attempts = 0 ;
location_changed = 0 ;
goto redo ;
}
return 0 ;
@ -371,8 +439,8 @@ redo:
fail :
if ( s - > hd )
ffurl_closep ( & s - > hd ) ;
if ( location_changed < 0 )
return location_changed ;
if ( ret < 0 )
return ret ;
return ff_http_averror ( s - > http_code , AVERROR ( EIO ) ) ;
}
int ff_http_get_shutdown_status ( URLContext * h )
@ -554,7 +622,7 @@ static void handle_http_errors(URLContext *h, int error)
static int http_handshake ( URLContext * c )
{
int ret , err , new_location ;
int ret , err ;
HTTPContext * ch = c - > priv_data ;
URLContext * cl = ch - > hd ;
switch ( ch - > handshake_step ) {
@ -569,7 +637,7 @@ static int http_handshake(URLContext *c)
return 2 ;
case READ_HEADERS :
av_log ( c , AV_LOG_TRACE , " Read headers \n " ) ;
if ( ( err = http_read_header ( c , & new_location ) ) < 0 ) {
if ( ( err = http_read_header ( c ) ) < 0 ) {
handle_http_errors ( c , err ) ;
return err ;
}
@ -666,6 +734,8 @@ bail_out:
if ( ret < 0 ) {
av_dict_free ( & s - > chained_options ) ;
av_dict_free ( & s - > cookie_dict ) ;
av_dict_free ( & s - > redirect_cache ) ;
av_freep ( & s - > new_location ) ;
av_freep ( & s - > uri ) ;
}
return ret ;
@ -753,14 +823,13 @@ static int check_http_code(URLContext *h, int http_code, const char *end)
static int parse_location ( HTTPContext * s , const char * p )
{
char redirected_location [ MAX_URL_SIZE ] , * new_loc ;
char redirected_location [ MAX_URL_SIZE ] ;
ff_make_absolute_url ( redirected_location , sizeof ( redirected_location ) ,
s - > location , p ) ;
new_loc = av_strdup ( redirected_location ) ;
if ( ! new_loc )
av_freep ( & s - > new_location ) ;
s - > new_location = av_strdup ( redirected_location ) ;
if ( ! s - > new_location )
return AVERROR ( ENOMEM ) ;
av_free ( s - > location ) ;
s - > location = new_loc ;
return 0 ;
}
@ -983,8 +1052,43 @@ static int cookie_string(AVDictionary *dict, char **cookies)
return 0 ;
}
static int process_line ( URLContext * h , char * line , int line_count ,
int * new_location )
static void parse_expires ( HTTPContext * s , const char * p )
{
struct tm tm ;
if ( ! parse_set_cookie_expiry_time ( p , & tm ) ) {
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 ) ;
}
}
static int process_line ( URLContext * h , char * line , int line_count )
{
HTTPContext * s = h - > priv_data ;
const char * auto_method = h - > flags & AVIO_FLAG_READ ? " POST " : " GET " ;
@ -1081,7 +1185,6 @@ static int process_line(URLContext *h, char *line, int line_count,
if ( ! av_strcasecmp ( tag , " Location " ) ) {
if ( ( ret = parse_location ( s , p ) ) < 0 )
return ret ;
* new_location = 1 ;
} else if ( ! av_strcasecmp ( tag , " Content-Length " ) & &
s - > filesize = = UINT64_MAX ) {
s - > filesize = strtoull ( p , NULL , 10 ) ;
@ -1124,6 +1227,10 @@ static int process_line(URLContext *h, char *line, int line_count,
} else if ( ! av_strcasecmp ( tag , " Content-Encoding " ) ) {
if ( ( ret = parse_content_encoding ( h , p ) ) < 0 )
return ret ;
} else if ( ! av_strcasecmp ( tag , " Expires " ) ) {
parse_expires ( s , p ) ;
} else if ( ! av_strcasecmp ( tag , " Cache-Control " ) ) {
parse_cache_control ( s , p ) ;
}
}
return 1 ;
@ -1229,12 +1336,14 @@ static inline int has_header(const char *str, const char *header)
return av_stristart ( str , header + 2 , NULL ) | | av_stristr ( str , header ) ;
}
static int http_read_header ( URLContext * h , int * new_location )
static int http_read_header ( URLContext * h )
{
HTTPContext * s = h - > priv_data ;
char line [ MAX_URL_SIZE ] ;
int err = 0 ;
av_freep ( & s - > new_location ) ;
s - > expires = 0 ;
s - > chunksize = UINT64_MAX ;
for ( ; ; ) {
@ -1243,7 +1352,7 @@ static int http_read_header(URLContext *h, int *new_location)
av_log ( h , AV_LOG_TRACE , " header='%s' \n " , line ) ;
err = process_line ( h , line , s - > line_count , new_location ) ;
err = process_line ( h , line , s - > line_count ) ;
if ( err < 0 )
return err ;
if ( err = = 0 )
@ -1294,7 +1403,7 @@ static void bprint_escaped_path(AVBPrint *bp, const char *path)
static int http_connect ( URLContext * h , const char * path , const char * local_path ,
const char * hoststr , const char * auth ,
const char * proxyauth , int * new_location )
const char * proxyauth )
{
HTTPContext * s = h - > priv_data ;
int post , err ;
@ -1438,11 +1547,11 @@ static int http_connect(URLContext *h, const char *path, const char *local_path,
}
/* wait for header */
err = http_read_header ( h , new_location ) ;
err = http_read_header ( h ) ;
if ( err < 0 )
goto done ;
if ( * new_location )
if ( s - > new_location )
s - > off = off ;
err = ( off = = s - > off ) ? 0 : - 1 ;
@ -1564,7 +1673,7 @@ static int64_t http_seek_internal(URLContext *h, int64_t off, int whence, int fo
static int http_read_stream ( URLContext * h , uint8_t * buf , int size )
{
HTTPContext * s = h - > priv_data ;
int err , new_location , read_ret ;
int err , read_ret ;
int64_t seek_ret ;
int reconnect_delay = 0 ;
@ -1572,7 +1681,7 @@ static int http_read_stream(URLContext *h, uint8_t *buf, int size)
return AVERROR_EOF ;
if ( s - > end_chunked_post & & ! s - > end_header ) {
err = http_read_header ( h , & new_location ) ;
err = http_read_header ( h ) ;
if ( err < 0 )
return err ;
}
@ -1785,6 +1894,8 @@ static int http_close(URLContext *h)
ffurl_closep ( & s - > hd ) ;
av_dict_free ( & s - > chained_options ) ;
av_dict_free ( & s - > cookie_dict ) ;
av_dict_free ( & s - > redirect_cache ) ;
av_freep ( & s - > new_location ) ;
av_freep ( & s - > uri ) ;
return ret ;
}
@ -1944,7 +2055,6 @@ static int http_proxy_open(URLContext *h, const char *uri, int flags)
int port , ret = 0 , attempts = 0 ;
HTTPAuthType cur_auth_type ;
char * authstr ;
int new_loc ;
if ( s - > seekable = = 1 )
h - > is_streamed = 0 ;
@ -1998,7 +2108,7 @@ redo:
* since the client starts the conversation there , so there
* is no extra data that we might buffer up here .
*/
ret = http_read_header ( h , & new_loc ) ;
ret = http_read_header ( h ) ;
if ( ret < 0 )
goto fail ;