| /* | |
| * GPAC Multimedia Framework | |
| * | |
| * Authors: Jean Le Feuvre | |
| * Copyright (c) Telecom ParisTech 2005-2025 | |
| * All rights reserved | |
| * | |
| * This file is part of GPAC / downloader sub-project | |
| * | |
| * GPAC 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, or (at your option) | |
| * any later version. | |
| * | |
| * GPAC 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 this library; see the file COPYING. If not, write to | |
| * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | |
| * | |
| */ | |
| #include "downloader.h" | |
| #if !defined(GPAC_DISABLE_NETWORK) && defined(GPAC_HAS_CURL) | |
| #include <curl/curl.h> | |
| //we need multi API | |
| #if CURL_AT_LEAST_VERSION(7,80,0) | |
| #ifndef CURLPIPE_MULTIPLEX | |
| #define CURLPIPE_MULTIPLEX 0 | |
| #endif | |
| #if !defined(__GNUC__) | |
| # if defined(_WIN32_WCE) || defined (WIN32) | |
| #pragma comment(lib, "libcurl") | |
| # endif | |
| #endif | |
| #ifndef GPAC_DISABLE_LOG | |
| static int curl_trace(CURL *handle, curl_infotype type, char *data, size_t size, void *clientp) | |
| { | |
| (void)clientp; | |
| (void)handle; | |
| switch(type) { | |
| case CURLINFO_TEXT: | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[CURL] Info: %s", data)); | |
| return 0; | |
| case CURLINFO_HEADER_OUT: | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[CURL] Send header %s\n", data)); | |
| return 0; | |
| case CURLINFO_DATA_OUT: | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[CURL] Send data\n")); | |
| return 0; | |
| case CURLINFO_SSL_DATA_OUT: | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[CURL] Send SSL data\n")); | |
| return 0; | |
| case CURLINFO_HEADER_IN: | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[CURL] Recv header %s\n", data)); | |
| return 0; | |
| case CURLINFO_DATA_IN: | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[CURL] Recv data %d byes\n", size)); | |
| return 0; | |
| case CURLINFO_SSL_DATA_IN: | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[CURL] Recv SSL data\n")); | |
| return 0; | |
| default: | |
| return 0; | |
| } | |
| return 0; | |
| } | |
| #endif | |
| static int curl_on_connect(void *clientp, char *conn_primary_ip, char *conn_local_ip, int conn_primary_port, int conn_local_port) | |
| { | |
| GF_DownloadSession *sess = clientp; | |
| if (sess->status == GF_NETIO_SETUP) { | |
| sess->status = GF_NETIO_CONNECTED; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[CURL] connected\n")); | |
| gf_dm_sess_notify_state(sess, GF_NETIO_CONNECTED, GF_OK); | |
| gf_dm_configure_cache(sess); | |
| gf_mx_p(sess->dm->curl_mx); | |
| curl_multi_remove_handle(sess->dm->curl_multi, sess->curl_hnd); | |
| sess->curl_hnd_registered = GF_FALSE; | |
| gf_mx_v(sess->dm->curl_mx); | |
| } | |
| return CURL_PREREQFUNC_OK; | |
| } | |
| static int curl_on_socket_close(void *clientp, curl_socket_t item) | |
| { | |
| GF_DownloadManager *dm = clientp; | |
| dm->curl_has_close = GF_TRUE; | |
| return 0; | |
| } | |
| static size_t curl_on_header(char *buffer, size_t size, size_t nitems, void *clientp) | |
| { | |
| GF_DownloadSession *sess = clientp; | |
| if ((buffer[0]=='\r') || (buffer[0]=='\n')) | |
| return nitems * size; | |
| if (sess->status==GF_NETIO_WAIT_FOR_REPLY) { | |
| //push headers | |
| char *sep = memchr(buffer, ':', nitems*size); | |
| if (sep) { | |
| sep[0] = 0; | |
| GF_HTTPHeader *hdrp; | |
| GF_SAFEALLOC(hdrp, GF_HTTPHeader); | |
| if (hdrp) { | |
| u32 len = (u32) strlen(buffer) + 2; | |
| hdrp->name = gf_strdup(buffer); | |
| hdrp->value = gf_malloc(nitems * size - len + 1); | |
| memcpy(hdrp->value, buffer+len, nitems * size - len); | |
| hdrp->value[nitems * size - len] = 0; | |
| gf_list_add(sess->headers, hdrp); | |
| } | |
| } | |
| if (!sess->reply_time) | |
| sess->reply_time = (u32) (gf_sys_clock_high_res() - sess->request_start_time); | |
| } | |
| return nitems * size; | |
| } | |
| static size_t curl_on_data(char *ptr, size_t size, size_t nmemb, void *clientp) | |
| { | |
| GF_DownloadSession *sess = clientp; | |
| u32 len = (u32) (size*nmemb); | |
| if (sess->local_buf_len + len > sess->local_buf_alloc) { | |
| sess->local_buf_alloc = sess->local_buf_len + len; | |
| sess->local_buf = gf_realloc(sess->local_buf, sizeof(u8) * sess->local_buf_alloc); | |
| if (!sess->local_buf) return 0; | |
| } | |
| memcpy(sess->local_buf + sess->local_buf_len, ptr, len); | |
| sess->local_buf_len += (u32) len; | |
| sess->last_fetch_time = gf_sys_clock_high_res(); | |
| //dirty hack: libcurl may use blocking calls for some protocols (ftp) | |
| //if we know the total size and have all bytes, force a timeout and tach it in curl_flush | |
| if (sess->total_size && (sess->bytes_done + sess->local_buf_len == sess->total_size)) { | |
| curl_easy_setopt(sess->curl_hnd, CURLOPT_TIMEOUT_MS, 1); | |
| } | |
| //GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[CURL] sess %s received %d bytes (%d/%d total)\n", sess->orig_url, len, sess->bytes_done, sess->total_size)); | |
| return len; | |
| } | |
| #endif //GPAC_DISABLE_LOG | |
| GF_Err curl_setup_session(GF_DownloadSession* sess) | |
| { | |
| CURLcode res; | |
| sess->curl_hnd = curl_easy_init(); | |
| res = curl_easy_setopt(sess->curl_hnd, CURLOPT_PRIVATE, sess); | |
| res = curl_easy_setopt(sess->curl_hnd, CURLOPT_URL, sess->orig_url); | |
| #ifndef GPAC_DISABLE_LOG | |
| if (gf_opts_get_bool("curl", "trace") && gf_log_tool_level_on(GF_LOG_HTTP, GF_LOG_DEBUG)) { | |
| curl_easy_setopt(sess->curl_hnd, CURLOPT_VERBOSE, 1L); | |
| curl_easy_setopt(sess->curl_hnd, CURLOPT_DEBUGFUNCTION, curl_trace); | |
| curl_easy_setopt(sess->curl_hnd, CURLOPT_DEBUGDATA, sess); | |
| } | |
| #endif | |
| if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_BUFFERSIZE, sess->dm->read_buf_size); | |
| if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_FOLLOWLOCATION, 0L); | |
| #if (CURLPIPE_MULTIPLEX > 0) | |
| if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_PIPEWAIT, 1L); | |
| #endif | |
| if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_PREREQFUNCTION, curl_on_connect); | |
| if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_PREREQDATA, sess); | |
| //set close callback at DM level since a same socket can be used by several sessions (H2/H3) | |
| if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_CLOSESOCKETFUNCTION, curl_on_socket_close); | |
| if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_CLOSESOCKETDATA, sess->dm); | |
| if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_HEADERFUNCTION, curl_on_header); | |
| if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_HEADERDATA, sess); | |
| if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_WRITEFUNCTION, curl_on_data); | |
| if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_WRITEDATA, sess); | |
| //DO NOT SET CURLOPT_TIMEOUT as we have no clue how long the session will be running | |
| if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_CONNECTTIMEOUT_MS, sess->conn_timeout); | |
| if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_ACCEPTTIMEOUT_MS, sess->conn_timeout); | |
| if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_NOSIGNAL, 1); | |
| if (gf_opts_get_bool("core", "broken-cert")) { | |
| if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_SSL_VERIFYPEER, 0); | |
| if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_SSL_VERIFYHOST, 0); | |
| } | |
| else { | |
| const char* ca_bundle = gf_opts_get_key("core", "ca-bundle"); | |
| if (ca_bundle && gf_file_exists(ca_bundle)) { | |
| curl_easy_setopt(sess->curl_hnd, CURLOPT_CAINFO, ca_bundle); | |
| curl_easy_setopt(sess->curl_hnd, CURLOPT_CAPATH, NULL); | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[CURL] Using CA bundle at %s\n", ca_bundle)); | |
| } | |
| #if CURL_AT_LEAST_VERSION(7,84,0) | |
| else { | |
| const char* curl_bundle = NULL; | |
| curl_easy_getinfo(sess->curl_hnd, CURLINFO_CAINFO, &curl_bundle); | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[CURL] libcurl CA bundle location: %s\n", curl_bundle)); | |
| if (!curl_bundle || !gf_file_exists(curl_bundle)) { | |
| const char* ca_bundle_default = gf_opts_get_key("core", "ca-bundle-default"); | |
| if (ca_bundle_default) { | |
| curl_easy_setopt(sess->curl_hnd, CURLOPT_CAINFO, ca_bundle_default); | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[CURL] using default CA bundle at %s\n", ca_bundle_default)); | |
| } | |
| } | |
| } | |
| #endif | |
| } | |
| //set HTTP version | |
| if (!res) { | |
| curl_version_info_data *ver = curl_version_info(CURLVERSION_NOW); | |
| res = CURLE_UNSUPPORTED_PROTOCOL; | |
| //try H3 | |
| if ((ver->features & CURL_VERSION_HTTP3) && sess->dm && (sess->dm->h3_mode!=H3_MODE_NO)) { | |
| #ifdef CURL_HTTP_VERSION_3ONLY | |
| if (sess->dm->h3_mode==H3_MODE_ONLY) | |
| res = curl_easy_setopt(sess->curl_hnd, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_3ONLY); | |
| else | |
| #endif | |
| res = curl_easy_setopt(sess->curl_hnd, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_3); | |
| } | |
| //try H2 | |
| if (res && (ver->features & CURL_VERSION_HTTP2) && !gf_opts_get_bool("core", "no-h2")) { | |
| res = curl_easy_setopt(sess->curl_hnd, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2_0); | |
| } | |
| //fallback 1.1 - we could use CURL_HTTP_VERSION_NONE | |
| if (res==CURLE_UNSUPPORTED_PROTOCOL) | |
| res = curl_easy_setopt(sess->curl_hnd, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_1_1); | |
| } | |
| const char *proxy = (sess->flags & GF_NETIO_SESSION_NO_PROXY) ? NULL : gf_opts_get_key("core", "proxy"); | |
| if (proxy) { | |
| sess->proxy_enabled = 1; | |
| if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_PROXY, proxy); | |
| } | |
| sess->curl_not_http = GF_FALSE; | |
| if (!sess->do_requests) { | |
| sess->curl_not_http = GF_TRUE; | |
| sess->do_requests = http_do_requests; | |
| if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_FOLLOWLOCATION, 1L); | |
| if (!res) res = curl_easy_setopt(sess->curl_hnd, CURLOPT_TCP_KEEPALIVE, 1L); | |
| if (sess->log_name) { | |
| gf_free(sess->log_name); | |
| sess->log_name = NULL; | |
| } | |
| sess->log_name = gf_strdup("CURL"); | |
| } | |
| //set user-defined options | |
| const struct curl_easyoption *opt = curl_easy_option_next(NULL); | |
| while (opt) { | |
| u64 llvalue; | |
| s32 value; | |
| const char *val = gf_opts_get_key("curl", opt->name); | |
| if (!val) { | |
| opt = curl_easy_option_next(opt); | |
| continue; | |
| } | |
| switch (opt->type) { | |
| case CURLOT_LONG: | |
| case CURLOT_VALUES: | |
| sscanf(val, "%d", &value); | |
| if (!res) res = curl_easy_setopt(sess->curl_hnd, opt->id, value); | |
| break; | |
| case CURLOT_OFF_T: | |
| sscanf(val, LLU, &llvalue); | |
| if (!res) res = curl_easy_setopt(sess->curl_hnd, opt->id, llvalue); | |
| break; | |
| case CURLOT_STRING: | |
| if (!res) res = curl_easy_setopt(sess->curl_hnd, opt->id, val); | |
| break; | |
| case CURLOT_OBJECT: | |
| case CURLOT_SLIST: | |
| case CURLOT_CBPTR: | |
| case CURLOT_BLOB: | |
| case CURLOT_FUNCTION: | |
| break; | |
| } | |
| opt = curl_easy_option_next(opt); | |
| } | |
| if (res) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[CURL] Failed to setup session: %s\n", curl_easy_strerror(res))); | |
| return GF_SERVICE_ERROR; | |
| } | |
| sess->status = GF_NETIO_SETUP; | |
| sess->last_error = GF_OK; | |
| //reset number of retry and start time | |
| sess->num_retry = SESSION_RETRY_COUNT; | |
| sess->start_time = 0; | |
| sess->reply_time = 0; | |
| sess->local_buf_len = 0; | |
| return GF_OK; | |
| } | |
| void curl_flush(GF_DownloadSession *sess) | |
| { | |
| gf_mx_p(sess->dm->curl_mx); | |
| if (!sess->curl_hnd_registered) { | |
| curl_multi_add_handle(sess->dm->curl_multi, sess->curl_hnd); | |
| sess->curl_hnd_registered = GF_TRUE; | |
| } | |
| int still_running = 0; | |
| CURLMcode mres = curl_multi_perform(sess->dm->curl_multi, &still_running); | |
| gf_mx_v(sess->dm->curl_mx); | |
| if (mres != CURLM_OK) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[CURL] curl_multi_perform() failed: %s\n", curl_multi_strerror(mres) )); | |
| } | |
| //if no close detected and session has total size set nothing to do | |
| //otherwise check for EOS detection, either error (close) or OK (chunk transfer) | |
| if (!sess->dm->curl_has_close && sess->total_size) return; | |
| //process close messages | |
| sess->dm->curl_has_close = GF_FALSE; | |
| CURLMsg *m=NULL; | |
| int nb_msgs=0; | |
| while (1) { | |
| m = curl_multi_info_read(sess->dm->curl_multi, &nb_msgs); | |
| if (!m) break; | |
| if (m->msg != CURLMSG_DONE) continue; | |
| GF_DownloadSession *a_sess = NULL; | |
| CURLcode res = curl_easy_getinfo(m->easy_handle, CURLINFO_PRIVATE, &a_sess); | |
| if (!a_sess) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[CURL] failed to retrieve private pointer: %s\n", curl_easy_strerror(res) )); | |
| continue; | |
| } | |
| if (a_sess->curl_hnd_registered) { | |
| curl_multi_remove_handle(sess->dm->curl_multi, a_sess->curl_hnd); | |
| a_sess->curl_hnd_registered = GF_FALSE; | |
| } | |
| if (m->data.result==CURLE_COULDNT_CONNECT) | |
| a_sess->curl_closed = GF_IP_CONNECTION_FAILURE; | |
| else if (m->data.result==CURLE_COULDNT_RESOLVE_HOST) | |
| a_sess->curl_closed = GF_IP_ADDRESS_NOT_FOUND; | |
| else if (m->data.result==CURLE_PARTIAL_FILE) | |
| a_sess->curl_closed = GF_IP_CONNECTION_CLOSED; | |
| else if (m->data.result==CURLE_REMOTE_FILE_NOT_FOUND) | |
| a_sess->curl_closed = GF_URL_ERROR; | |
| else if (m->data.result==CURLE_OK) | |
| a_sess->curl_closed = GF_EOS; | |
| else if (m->data.result==CURLE_REMOTE_ACCESS_DENIED) { | |
| if (a_sess->num_retry) { | |
| a_sess->curl_fake_hcode = 401; | |
| a_sess->num_retry--; | |
| a_sess->last_error = GF_OK; | |
| a_sess->status = GF_NETIO_WAIT_FOR_REPLY; | |
| a_sess->curl_closed = GF_OK; | |
| } else { | |
| a_sess->curl_closed = GF_AUTHENTICATION_FAILURE; | |
| } | |
| } | |
| else if (m->data.result==CURLE_OPERATION_TIMEDOUT) { | |
| //timeout was forced | |
| if (a_sess->total_size && (a_sess->total_size == a_sess->local_buf_len + a_sess->bytes_done)) | |
| a_sess->curl_closed = GF_EOS; | |
| else | |
| a_sess->curl_closed = GF_IP_CONNECTION_FAILURE; | |
| } | |
| else | |
| a_sess->curl_closed = GF_IP_NETWORK_FAILURE; | |
| } | |
| } | |
| void curl_destroy(GF_DownloadSession *sess) | |
| { | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[CURL] closing session\n")); | |
| if (sess->curl_hnd_registered) { | |
| gf_mx_p(sess->dm->curl_mx); | |
| curl_multi_remove_handle(sess->dm->curl_multi, sess->curl_hnd); | |
| gf_mx_v(sess->dm->curl_mx); | |
| } | |
| curl_easy_cleanup(sess->curl_hnd); | |
| sess->curl_hnd = NULL; | |
| sess->curl_hnd_registered = GF_FALSE; | |
| if (sess->curl_hdrs) curl_slist_free_all(sess->curl_hdrs); | |
| sess->curl_hdrs = NULL; | |
| sess->curl_closed = GF_OK; | |
| } | |
| Bool curl_can_handle_url(const char *url) | |
| { | |
| curl_version_info_data *ver = curl_version_info(CURLVERSION_NOW); | |
| char *prot_sep = strstr(url, "://"); | |
| if (!prot_sep) return GF_FALSE; | |
| u32 i=0, len = (u32) (prot_sep - url); | |
| while (ver && ver->protocols[i]) { | |
| if (!strnicmp(ver->protocols[i], url, len)) | |
| return GF_TRUE; | |
| i++; | |
| } | |
| return GF_FALSE; | |
| } | |
| GF_Err curl_read_data(GF_DownloadSession *sess, char *data, u32 data_size, u32 *out_read) | |
| { | |
| if (!sess->curl_hnd) return GF_BAD_PARAM; | |
| if (!sess->local_buf_len) | |
| curl_flush(sess); | |
| if (!sess->local_buf_len) { | |
| *out_read = 0; | |
| if (sess->curl_closed==GF_EOS) { | |
| //for chunk transfer | |
| if (!sess->total_size) | |
| sess->total_size = sess->bytes_done; | |
| //reutrn OK so that data_received is processed | |
| return GF_OK; | |
| } | |
| else if (sess->curl_closed) { | |
| sess->status = GF_NETIO_STATE_ERROR; | |
| SET_LAST_ERR(GF_IP_CONNECTION_CLOSED); | |
| return sess->curl_closed; | |
| } | |
| return GF_IP_NETWORK_EMPTY; | |
| } | |
| if (sess->curl_not_http && !sess->total_size) { | |
| long cl=0; | |
| curl_easy_getinfo(sess->curl_hnd, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &cl); | |
| if (cl>=0) | |
| sess->total_size = cl; | |
| } | |
| u32 nb_copy = MIN(data_size, sess->local_buf_len); | |
| memcpy(data, sess->local_buf, nb_copy); | |
| sess->local_buf_len -= nb_copy; | |
| memmove(sess->local_buf, sess->local_buf + nb_copy, sess->local_buf_len); | |
| *out_read = nb_copy; | |
| return GF_OK; | |
| } | |
| void curl_connect(GF_DownloadSession*sess) | |
| { | |
| curl_flush(sess); | |
| if (sess->curl_closed) { | |
| if (sess->cache_entry && sess->cached_file) return; | |
| sess->status = GF_NETIO_STATE_ERROR; | |
| SET_LAST_ERR(GF_IP_CONNECTION_FAILURE) | |
| gf_dm_sess_notify_state(sess, sess->status, GF_IP_CONNECTION_FAILURE); | |
| } | |
| } | |
| GF_Err curl_process_reply(GF_DownloadSession *sess, u32 *ContentLength) | |
| { | |
| if (!sess->reply_time) { | |
| curl_flush(sess); | |
| if (!sess->reply_time) return GF_IP_NETWORK_EMPTY; | |
| } | |
| if (sess->curl_closed<0) { | |
| gf_dm_disconnect(sess, HTTP_RESET_CONN); | |
| sess->num_retry--; | |
| if (sess->num_retry) { | |
| GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[%s] Connection closed by server when processing %s - retrying\n", sess->log_name, sess->remote_path)); | |
| sess->status = GF_NETIO_SETUP; | |
| } else { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[%s] Connection closed by server when processing %s - aborting\n", sess->log_name, sess->remote_path)); | |
| SET_LAST_ERR(GF_IP_CONNECTION_CLOSED) | |
| sess->status = GF_NETIO_STATE_ERROR; | |
| } | |
| return GF_IP_CONNECTION_CLOSED; | |
| } | |
| if (sess->curl_not_http) { | |
| //if not HTTP we have no guarantee that the return code for the URL is known when receiving server first bytes | |
| //eg FTP may need extra round trips before we get OK/ not found. | |
| //We assume by default OK and will throw an error in curl_flush | |
| // curl_fake_hcode can be set in curl_flush to force reprocessing the reply (eg on authentication failure) | |
| sess->rsp_code = sess->curl_fake_hcode ? sess->curl_fake_hcode : 200; | |
| sess->curl_fake_hcode = 0; | |
| long cl=0; | |
| curl_easy_getinfo(sess->curl_hnd, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &cl); | |
| if (cl>=0) | |
| *ContentLength = cl; | |
| } else { | |
| long rsp=505; | |
| curl_easy_getinfo(sess->curl_hnd, CURLINFO_RESPONSE_CODE, &rsp); | |
| sess->rsp_code = rsp; | |
| if (gf_log_tool_level_on(GF_LOG_HTTP, GF_LOG_INFO)) { | |
| long http_version; | |
| const char *version="unknown"; | |
| curl_easy_getinfo(sess->curl_hnd, CURLINFO_HTTP_VERSION, &http_version); | |
| switch (http_version) { | |
| case CURL_HTTP_VERSION_1_0: version = "1.0"; break; | |
| case CURL_HTTP_VERSION_1_1: version = "1.1"; break; | |
| case CURL_HTTP_VERSION_2_0: version = "2"; break; | |
| case CURL_HTTP_VERSION_3: version = "3"; break; | |
| } | |
| GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[CURL] Using HTTP %s\n", version)); | |
| } | |
| } | |
| sess->start_time = gf_sys_clock_high_res(); | |
| sess->start_time_utc = gf_net_get_utc(); | |
| return GF_OK; | |
| } | |
| #endif //GPAC_DISABLE_NETWORK && HAS_CURL | |