11/* hfile_libcurl.c -- libcurl backend for low-level file streams.
22
3- Copyright (C) 2015-2017 Genome Research Ltd.
3+ Copyright (C) 2015-2017, 2019 Genome Research Ltd.
44
55 Author: John Marshall <jm18@sanger.ac.uk>
66
@@ -77,13 +77,18 @@ typedef struct {
7777 hdrlist fixed ; // List of headers supplied at hopen()
7878 hdrlist extra ; // List of headers from callback
7979 hts_httphdr_callback callback ; // Callback to get more headers
80- void * callback_data ; // Data to pass to callback
80+ void * callback_data ; // Data to pass to httphdr callback
8181 auth_token * auth ; // Authentication token
8282 int auth_hdr_num ; // Location of auth_token in hdrlist extra
8383 // If -1, Authorization header is in fixed
8484 // -2, it came from the callback
8585 // -3, "auth_token_enabled", "false"
8686 // passed to hopen()
87+ redirect_callback redirect ; // Callback to handle 3xx redirects
88+ void * redirect_data ; // Data to pass to redirect_callback
89+ long * http_response_ptr ; // Location to store http response code.
90+ int fail_on_error ; // Open fails on >400 response code
91+ // (default true)
8792} http_headers ;
8893
8994typedef struct {
@@ -107,6 +112,7 @@ typedef struct {
107112 unsigned tried_seek : 1 ; // At least one seek has been attempted
108113 int nrunning ;
109114 http_headers headers ;
115+
110116 off_t delayed_seek ; // Location to seek to before reading
111117 off_t last_offset ; // Location we're seeking from
112118} hFILE_libcurl ;
@@ -720,7 +726,10 @@ static size_t recv_callback(char *ptr, size_t size, size_t nmemb, void *fpv)
720726 hFILE_libcurl * fp = (hFILE_libcurl * ) fpv ;
721727 size_t n = size * nmemb ;
722728
723- if (n > fp -> buffer .len ) { fp -> paused = 1 ; return CURL_WRITEFUNC_PAUSE ; }
729+ if (n > fp -> buffer .len ) {
730+ fp -> paused = 1 ;
731+ return CURL_WRITEFUNC_PAUSE ;
732+ }
724733 else if (n == 0 ) return 0 ;
725734
726735 memcpy (fp -> buffer .ptr .rd , ptr , n );
@@ -729,6 +738,19 @@ static size_t recv_callback(char *ptr, size_t size, size_t nmemb, void *fpv)
729738 return n ;
730739}
731740
741+
742+ size_t header_callback (void * contents , size_t size , size_t nmemb , void * userp ) {
743+ size_t realsize = size * nmemb ;
744+ kstring_t * resp = (kstring_t * )userp ;
745+
746+ if (kputsn ((const char * )contents , realsize , resp ) == EOF ) {
747+ return 0 ;
748+ }
749+
750+ return realsize ;
751+ }
752+
753+
732754static ssize_t libcurl_read (hFILE * fpv , void * bufferv , size_t nbytes )
733755{
734756 hFILE_libcurl * fp = (hFILE_libcurl * ) fpv ;
@@ -764,8 +786,9 @@ static ssize_t libcurl_read(hFILE *fpv, void *bufferv, size_t nbytes)
764786 err = curl_easy_pause (fp -> easy , CURLPAUSE_CONT );
765787 if (err != CURLE_OK ) { errno = easy_errno (fp -> easy , err ); return -1 ; }
766788
767- while (! fp -> paused && ! fp -> finished )
789+ while (! fp -> paused && ! fp -> finished ) {
768790 if (wait_perform (fp ) < 0 ) return -1 ;
791+ }
769792
770793 got = fp -> buffer .ptr .rd - buffer ;
771794
@@ -1087,6 +1110,8 @@ libcurl_open(const char *url, const char *modes, http_headers *headers)
10871110 CURLcode err ;
10881111 CURLMcode errm ;
10891112 int save , is_recursive ;
1113+ kstring_t in_header = {0 , 0 , NULL };
1114+ long response ;
10901115
10911116 is_recursive = strchr (modes , 'R' ) != NULL ;
10921117
@@ -1163,28 +1188,83 @@ libcurl_open(const char *url, const char *modes, http_headers *headers)
11631188 goto error ;
11641189 if ((list = get_header_list (fp )) != NULL )
11651190 err |= curl_easy_setopt (fp -> easy , CURLOPT_HTTPHEADER , list );
1166- err |= curl_easy_setopt ( fp -> easy , CURLOPT_FOLLOWLOCATION , 1L );
1167- if (hts_verbose <= 8 )
1191+
1192+ if (hts_verbose <= 8 && fp -> headers . fail_on_error )
11681193 err |= curl_easy_setopt (fp -> easy , CURLOPT_FAILONERROR , 1L );
11691194 if (hts_verbose >= 8 )
11701195 err |= curl_easy_setopt (fp -> easy , CURLOPT_VERBOSE , 1L );
11711196
1197+ if (fp -> headers .redirect ) {
1198+ err |= curl_easy_setopt (fp -> easy , CURLOPT_HEADERFUNCTION , header_callback );
1199+ err |= curl_easy_setopt (fp -> easy , CURLOPT_HEADERDATA , (void * )& in_header );
1200+ } else {
1201+ err |= curl_easy_setopt (fp -> easy , CURLOPT_FOLLOWLOCATION , 1L );
1202+ }
1203+
11721204 if (err != 0 ) { errno = ENOSYS ; goto error ; }
11731205
11741206 errm = curl_multi_add_handle (fp -> multi , fp -> easy );
11751207 if (errm != CURLM_OK ) { errno = multi_errno (errm ); goto error ; }
11761208 fp -> nrunning ++ ;
11771209
1178- while (! fp -> paused && ! fp -> finished )
1210+ while (! fp -> paused && ! fp -> finished ) {
11791211 if (wait_perform (fp ) < 0 ) goto error_remove ;
1212+ }
1213+
1214+ curl_easy_getinfo (fp -> easy , CURLINFO_RESPONSE_CODE , & response );
1215+ if (fp -> headers .http_response_ptr ) {
1216+ * fp -> headers .http_response_ptr = response ;
1217+ }
11801218
11811219 if (fp -> finished && fp -> final_result != CURLE_OK ) {
11821220 errno = easy_errno (fp -> easy , fp -> final_result );
11831221 goto error_remove ;
11841222 }
11851223
1224+ if (fp -> headers .redirect ) {
1225+ if (response >= 300 && response < 400 ) { // redirection
1226+ kstring_t new_url = {0 , 0 , NULL };
1227+
1228+ if (fp -> headers .redirect (fp -> headers .redirect_data , response ,
1229+ & in_header , & new_url )) {
1230+ errno = ENOSYS ;
1231+ goto error ;
1232+ }
1233+
1234+ err |= curl_easy_setopt (fp -> easy , CURLOPT_URL , new_url .s );
1235+ err |= curl_easy_setopt (fp -> easy , CURLOPT_HEADERFUNCTION , NULL );
1236+ err |= curl_easy_setopt (fp -> easy , CURLOPT_HEADERDATA , NULL );
1237+ free (ks_release (& in_header ));
1238+
1239+ if (err != 0 ) { errno = ENOSYS ; goto error ; }
1240+ free (ks_release (& new_url ));
1241+
1242+ if (restart_from_position (fp , 0 ) < 0 ) {
1243+ goto error_remove ;
1244+ }
1245+
1246+ if (fp -> headers .http_response_ptr ) {
1247+ curl_easy_getinfo (fp -> easy , CURLINFO_RESPONSE_CODE ,
1248+ fp -> headers .http_response_ptr );
1249+ }
1250+
1251+ if (fp -> finished && fp -> final_result != CURLE_OK ) {
1252+ errno = easy_errno (fp -> easy , fp -> final_result );
1253+ goto error_remove ;
1254+ }
1255+ } else {
1256+ // we no longer need to look at the headers
1257+ err |= curl_easy_setopt (fp -> easy , CURLOPT_HEADERFUNCTION , NULL );
1258+ err |= curl_easy_setopt (fp -> easy , CURLOPT_HEADERDATA , NULL );
1259+ free (ks_release (& in_header ));
1260+
1261+ if (err != 0 ) { errno = ENOSYS ; goto error ; }
1262+ }
1263+ }
1264+
11861265 if (mode == 'r' ) {
11871266 double dval ;
1267+
11881268 if (curl_easy_getinfo (fp -> easy , CURLINFO_CONTENT_LENGTH_DOWNLOAD ,
11891269 & dval ) == CURLE_OK && dval >= 0.0 )
11901270 fp -> file_size = (off_t ) (dval + 0.1 );
@@ -1200,6 +1280,7 @@ libcurl_open(const char *url, const char *modes, http_headers *headers)
12001280 errno = save ;
12011281
12021282error :
1283+ if (fp -> headers .redirect ) free (in_header .s );
12031284 save = errno ;
12041285 if (fp -> easy ) curl_easy_cleanup (fp -> easy );
12051286 if (fp -> multi ) curl_multi_cleanup (fp -> multi );
@@ -1266,6 +1347,18 @@ static int parse_va_list(http_headers *headers, va_list args)
12661347 if (strcmp (flag , "false" ) == 0 )
12671348 headers -> auth_hdr_num = -3 ;
12681349 }
1350+ else if (strcmp (argtype , "redirect_callback" ) == 0 ) {
1351+ headers -> redirect = va_arg (args , const redirect_callback );
1352+ }
1353+ else if (strcmp (argtype , "redirect_callback_data" ) == 0 ) {
1354+ headers -> redirect_data = va_arg (args , void * );
1355+ }
1356+ else if (strcmp (argtype , "http_response_ptr" ) == 0 ) {
1357+ headers -> http_response_ptr = va_arg (args , long * );
1358+ }
1359+ else if (strcmp (argtype , "fail_on_error" ) == 0 ) {
1360+ headers -> fail_on_error = va_arg (args , int );
1361+ }
12691362 else { errno = EINVAL ; return -1 ; }
12701363
12711364 return 0 ;
@@ -1318,7 +1411,8 @@ static int parse_va_list(http_headers *headers, va_list args)
13181411static hFILE * vhopen_libcurl (const char * url , const char * modes , va_list args )
13191412{
13201413 hFILE * fp = NULL ;
1321- http_headers headers = { { NULL , 0 , 0 }, { NULL , 0 , 0 }, NULL , NULL };
1414+ http_headers headers = { .fail_on_error = 1 };
1415+
13221416 if (parse_va_list (& headers , args ) == 0 ) {
13231417 fp = libcurl_open (url , modes , & headers );
13241418 }
0 commit comments