blob: bd83b4d28abd9aa70c2cb550d35ecd2f5bfb3dd0 [file] [edit]
/*
* GPAC Multimedia Framework
*
* Authors: Jean Le Feuvre
* Copyright (c) Telecom ParisTech 2005-2026
* 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"
#ifndef GPAC_DISABLE_NETWORK
static void gf_dm_connect(GF_DownloadSession *sess);
#define GF_CHUNK_MAX_SIZE 0x40000000 // 1GB
void dm_sess_sk_del(GF_DownloadSession *sess)
{
#ifdef GPAC_HAS_CURL
if (sess->curl_hnd) {
curl_destroy(sess);
return;
}
#endif
if (sess->sock) {
GF_Socket *sock = sess->sock;
sess->sock = NULL;
GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[Downloader] closing socket\n"));
if (sess->sock_group) gf_sk_group_unregister(sess->sock_group, sock);
gf_sk_del(sock);
#ifdef GPAC_HAS_HTTP2
sess->h2_upgrade_state = 0;
#endif
}
}
static void sess_connection_closed(GF_DownloadSession *sess)
{
#ifdef GPAC_HTTPMUX
if (sess->hmux_sess) {
sess->hmux_sess->do_shutdown = GF_TRUE;
sess->hmux_switch_sess = GF_TRUE;
}
#endif
}
GF_Err dm_sess_write(GF_DownloadSession *session, const u8 *buffer, u32 size)
{
GF_Err e;
u32 written=0;
GF_DownloadSession *par_sess = session;
#ifdef GPAC_HTTPMUX
//if h2 we aggregate pending frames on the session currently holding the HTTP2 session
if (session->hmux_sess) par_sess = session->hmux_sess->net_sess;
#endif
//we are writing and we already have pending data, append and try to flush after
if (par_sess->async_buf_size && (buffer != par_sess->async_buf)) {
e = GF_IP_NETWORK_EMPTY;
} else
#ifdef GPAC_HAS_SSL
if (session->ssl) {
e = gf_ssl_write(session, buffer, size, &written);
if (e==GF_IP_NETWORK_FAILURE)
e = GF_IP_CONNECTION_CLOSED;
} else
#endif
{
e = gf_sk_send_ex(session->sock, buffer, size, &written);
}
if (!(session->flags & GF_NETIO_SESSION_NO_BLOCK) || (e!=GF_IP_NETWORK_EMPTY)) {
par_sess->async_buf_size = 0;
return e;
}
//we are blocking, store content
u32 remain = size - written;
if (buffer == par_sess->async_buf) {
if (written) {
if (par_sess->async_buf_size >= written) {
memmove(par_sess->async_buf, par_sess->async_buf + written, remain);
par_sess->async_buf_size -= written;
} else {
gf_assert(0);
par_sess->async_buf_size = 0;
}
}
} else {
if (par_sess->async_buf_alloc < par_sess->async_buf_size + remain) {
par_sess->async_buf_alloc = par_sess->async_buf_size + remain;
par_sess->async_buf = gf_realloc(par_sess->async_buf, par_sess->async_buf_alloc);
if (!par_sess->async_buf) return GF_OUT_OF_MEM;
}
memcpy(par_sess->async_buf+par_sess->async_buf_size, buffer + written, remain);
par_sess->async_buf_size += remain;
}
return GF_OK;
}
static Bool gf_dm_is_local(GF_DownloadManager *dm, const char *url)
{
if (!strnicmp(url, "file://", 7)) return GF_TRUE;
if (!strstr(url, "://")) return GF_TRUE;
return GF_FALSE;
}
Bool gf_dm_can_handle_url(const char *url)
{
if (!strnicmp(url, "http://", 7)) return GF_TRUE;
#ifdef GPAC_HAS_SSL
if (!strnicmp(url, "https://", 8)) return GF_TRUE;
#endif
#ifdef GPAC_HAS_CURL
if (curl_can_handle_url(url)) return GF_TRUE;
#endif
return GF_FALSE;
}
void gf_dm_sess_set_header_ex(GF_DownloadSession *sess, const char *name, const char *value, Bool allow_overwrite)
{
GF_HTTPHeader *hdr;
if (!sess) return;
#ifdef GPAC_HTTPMUX
if (sess->hmux_sess
#ifdef GPAC_HAS_HTTP2
|| sess->h2_upgrade_settings
#endif
) {
if (!stricmp(name, "Transfer-Encoding"))
return;
if (!stricmp(name, "Connection")) return;
if (!stricmp(name, "Keep-Alive")) return;
}
#endif
//check existing headers
u32 i, count = gf_list_count(sess->headers);
for (i=0; i<count; i++) {
hdr = gf_list_get(sess->headers, i);
if (stricmp(hdr->name, name)) continue;
if (!allow_overwrite) return;
gf_free(hdr->value);
if (value) {
hdr->value = gf_strdup(value);
return;
}
gf_list_rem(sess->headers, i);
gf_free(hdr->name);
gf_free(hdr);
return;
}
GF_SAFEALLOC(hdr, GF_HTTPHeader)
if (hdr) {
hdr->name = gf_strdup(name);
hdr->value = gf_strdup(value);
gf_list_add(sess->headers, hdr);
}
}
void gf_dm_sess_set_header(GF_DownloadSession *sess, const char *name, const char *value)
{
gf_dm_sess_set_header_ex(sess, name, value, GF_TRUE);
}
GF_Err gf_dm_sess_send_reply(GF_DownloadSession *sess, u32 reply_code, const char *response_body, u32 body_len, Bool no_body)
{
u32 i, count;
GF_Err e;
char szFmt[50];
char *rsp_buf = NULL;
if (!sess || !sess->server_mode) return GF_BAD_PARAM;
count = gf_list_count(sess->headers);
#ifdef GPAC_HAS_HTTP2
e = http2_check_upgrade(sess);
if (e) return e;
#endif
#ifdef GPAC_HTTPMUX
if (sess->hmux_sess) {
return sess->hmux_sess->send_reply(sess, reply_code, response_body, body_len, no_body);
}
#endif
sprintf(szFmt, "HTTP/1.1 %d ", reply_code);
gf_dynstrcat(&rsp_buf, szFmt, NULL);
switch (reply_code) {
case 400: gf_dynstrcat(&rsp_buf, "Bad Request", NULL); break;
case 401: gf_dynstrcat(&rsp_buf, "Unauthorized", NULL); break;
case 403: gf_dynstrcat(&rsp_buf, "Forbidden", NULL); break;
case 405: gf_dynstrcat(&rsp_buf, "Not Allowed", NULL); break;
case 416: gf_dynstrcat(&rsp_buf, "Requested Range Not Satisfiable", NULL); break;
case 411: gf_dynstrcat(&rsp_buf, "Length Required", NULL); break;
case 404: gf_dynstrcat(&rsp_buf, "Not Found", NULL); break;
case 501: gf_dynstrcat(&rsp_buf, "Not Implemented", NULL); break;
case 500: gf_dynstrcat(&rsp_buf, "Internal Server Error", NULL); break;
case 304: gf_dynstrcat(&rsp_buf, "Not Modified", NULL); break;
case 204: gf_dynstrcat(&rsp_buf, "No Content", NULL); break;
case 206: gf_dynstrcat(&rsp_buf, "Partial Content", NULL); break;
case 200: gf_dynstrcat(&rsp_buf, "OK", NULL); break;
case 201: gf_dynstrcat(&rsp_buf, "Created", NULL); break;
case 101: gf_dynstrcat(&rsp_buf, "Switching Protocols", NULL); break;
default:
gf_dynstrcat(&rsp_buf, "ERROR", NULL); break;
}
gf_dynstrcat(&rsp_buf, "\r\n", NULL);
if (!rsp_buf) return GF_OUT_OF_MEM;
for (i=0; i<count; i++) {
GF_HTTPHeader *hdr = gf_list_get(sess->headers, i);
gf_dynstrcat(&rsp_buf, hdr->name, NULL);
gf_dynstrcat(&rsp_buf, ": ", NULL);
gf_dynstrcat(&rsp_buf, hdr->value, NULL);
gf_dynstrcat(&rsp_buf, "\r\n", NULL);
}
gf_dynstrcat(&rsp_buf, "\r\n", NULL);
if (!rsp_buf) return GF_OUT_OF_MEM;
GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[%s] send reply for %s:\n%s\n", sess->log_name, sess->orig_url, rsp_buf));
count = (u32) strlen(rsp_buf);
if (response_body) {
rsp_buf = gf_realloc(rsp_buf, count+body_len);
if (!rsp_buf) return GF_OUT_OF_MEM;
memcpy(rsp_buf+count, response_body, body_len);
count+=body_len;
}
e = dm_sess_write(sess, rsp_buf, count);
gf_free(rsp_buf);
return e;
}
void gf_dm_sess_clear_headers(GF_DownloadSession *sess)
{
while (gf_list_count(sess->headers)) {
GF_HTTPHeader *hdr = (GF_HTTPHeader*)gf_list_last(sess->headers);
gf_list_rem_last(sess->headers);
gf_free(hdr->name);
gf_free(hdr->value);
gf_free(hdr);
}
if (sess->mime_type) {
gf_free(sess->mime_type);
sess->mime_type = NULL;
}
}
void gf_dm_disconnect(GF_DownloadSession *sess, HTTPCloseType close_type)
{
gf_assert( sess );
if (sess->connection_close) close_type = HTTP_RESET_CONN;
sess->connection_close = GF_FALSE;
sess->remaining_data_size = 0;
if (sess->async_req_reply) gf_free(sess->async_req_reply);
sess->async_req_reply = NULL;
sess->async_req_reply_size = 0;
sess->async_buf_size = 0;
if (sess->cached_file) {
gf_fclose(sess->cached_file);
sess->cached_file = NULL;
}
if ((sess->status >= GF_NETIO_DISCONNECTED)
#ifdef GPAC_HAS_CURL
&& !sess->curl_hnd
#endif
) {
if (close_type && sess->use_cache_file && sess->cache_entry) {
gf_cache_close_write_cache(sess->cache_entry, sess, GF_FALSE);
}
return;
}
GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[Downloader] gf_dm_disconnect(%p)\n", sess ));
gf_mx_p(sess->mx);
if (!sess->server_mode) {
Bool do_close = (close_type || !(sess->flags & GF_NETIO_SESSION_PERSISTENT)) ? GF_TRUE : GF_FALSE;
#ifdef GPAC_HTTPMUX
if (sess->hmux_sess) {
do_close = (close_type==HTTP_RESET_CONN) ? GF_TRUE : GF_FALSE;
}
//if H2 stream is still valid, issue a reset stream
if (sess->hmux_sess && (sess->hmux_stream_id>=0) && (sess->put_state!=2))
sess->hmux_sess->stream_reset(sess, GF_FALSE);
#endif
#ifdef GPAC_HAS_CURL
//always remove curl session, let multi-connection manager handle disconnect/reconnect
if (sess->curl_hnd) {
sess->local_buf_len = 0;
dm_sess_sk_del(sess);
} else
#endif
if (do_close) {
#ifdef GPAC_HTTPMUX
if (sess->hmux_sess) {
sess->hmux_sess->do_shutdown = GF_TRUE;
hmux_detach_session(sess->hmux_sess, sess);
}
#endif
#ifdef GPAC_HAS_SSL
if (sess->ssl) {
SSL_shutdown(sess->ssl);
SSL_free(sess->ssl);
sess->ssl = NULL;
}
#endif
dm_sess_sk_del(sess);
}
if (close_type && sess->use_cache_file && sess->cache_entry) {
gf_cache_close_write_cache(sess->cache_entry, sess, GF_FALSE);
}
}
sess->status = GF_NETIO_DISCONNECTED;
if (sess->num_retry) sess->num_retry--;
gf_mx_v(sess->mx);
}
GF_EXPORT
void gf_dm_sess_del(GF_DownloadSession *sess)
{
if (!sess)
return;
GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[Downloader] Destroy session URL %s\n", sess->orig_url));
/*self-destruction, let the download manager destroy us*/
if (sess->th || sess->ftask) {
sess->destroy = GF_TRUE;
if (sess->ftask->in_task)
return;
}
gf_dm_disconnect(sess, HTTP_CLOSE);
gf_dm_sess_clear_headers(sess);
/*if threaded wait for thread exit*/
if (sess->th) {
while (!(sess->flags & GF_DOWNLOAD_SESSION_THREAD_DEAD))
gf_sleep(1);
gf_th_stop(sess->th);
gf_th_del(sess->th);
sess->th = NULL;
}
if (sess->dm) {
gf_mx_p(sess->dm->cache_mx);
gf_list_del_item(sess->dm->all_sessions, sess);
gf_mx_v(sess->dm->cache_mx);
}
gf_cache_remove_entry_from_session(sess);
if (sess->orig_url) gf_free(sess->orig_url);
if (sess->orig_url_before_redirect) gf_free(sess->orig_url_before_redirect);
if (sess->server_name) gf_free(sess->server_name);
sess->server_name = NULL;
if (sess->remote_path) gf_free(sess->remote_path);
/* Credentials are stored into the sess->dm */
if (sess->creds) sess->creds = NULL;
if (sess->init_data) gf_free(sess->init_data);
if (sess->remaining_data) gf_free(sess->remaining_data);
if (sess->async_req_reply) gf_free(sess->async_req_reply);
sess->orig_url = sess->server_name = sess->remote_path;
sess->creds = NULL;
#ifdef GPAC_HTTPMUX
if (sess->hmux_sess) {
sess->hmux_sess->setup_session(sess, GF_TRUE);
gf_mx_p(sess->mx);
hmux_detach_session(sess->hmux_sess, sess);
gf_mx_v(sess->mx);
}
#endif
#ifdef GPAC_HAS_HTTP2
if (sess->h2_upgrade_settings)
gf_free(sess->h2_upgrade_settings);
#endif
#if defined(GPAC_HTTPMUX) || defined(GPAC_HAS_CURL)
if (sess->local_buf)
gf_free(sess->local_buf);
#endif
//free this once we have reassigned the session
if (sess->async_buf) gf_free(sess->async_buf);
#ifdef GPAC_HAS_SSL
//in server mode SSL context is managed by caller
if (sess->ssl) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[Downloader] shut down SSL context\n"));
SSL_shutdown(sess->ssl);
SSL_free(sess->ssl);
sess->ssl = NULL;
}
#endif
dm_sess_sk_del(sess);
gf_list_del(sess->headers);
#ifdef GPAC_HAS_CURL
gf_assert(!sess->curl_hnd);
if (sess->curl_hdrs) curl_slist_free_all(sess->curl_hdrs);
#endif
#ifndef GPAC_DISABLE_LOG
if (sess->log_name) gf_free(sess->log_name);
#endif
assert(!sess->ftask || !sess->ftask->in_task || !sess->mx);
gf_mx_del(sess->mx);
if (sess->http_buf) gf_free(sess->http_buf);
if (sess->ftask) {
sess->ftask->sess = NULL;
sess->ftask = NULL;
}
gf_free(sess);
}
void gf_dm_sess_notify_state(GF_DownloadSession *sess, GF_NetIOStatus dnload_status, GF_Err error)
{
if (!sess->user_proc) return;
GF_NETIO_Parameter par;
sess->in_callback = GF_TRUE;
memset(&par, 0, sizeof(GF_NETIO_Parameter));
par.msg_type = dnload_status;
par.error = error;
par.sess = sess;
par.reply = 200;
sess->user_proc(sess->usr_cbk, &par);
sess->in_callback = GF_FALSE;
}
static void gf_dm_sess_user_io(GF_DownloadSession *sess, GF_NETIO_Parameter *par)
{
if (sess->user_proc) {
sess->in_callback = GF_TRUE;
par->sess = sess;
sess->user_proc(sess->usr_cbk, par);
sess->in_callback = GF_FALSE;
}
}
GF_EXPORT
GF_Err gf_dm_sess_last_error(GF_DownloadSession *sess)
{
if (!sess)
return GF_BAD_PARAM;
return sess->last_error;
}
GF_EXPORT
void gf_dm_url_info_init(GF_URL_Info * info)
{
memset(info, 0, sizeof(GF_URL_Info));
}
GF_EXPORT
void gf_dm_url_info_del(GF_URL_Info * info) {
if (!info)
return;
if (info->protocol)
gf_free(info->protocol);
if (info->canonicalRepresentation)
gf_free(info->canonicalRepresentation);
if (info->password)
gf_free(info->password);
if (info->userName)
gf_free(info->userName);
if (info->remotePath)
gf_free(info->remotePath);
if (info->server_name)
gf_free(info->server_name);
gf_dm_url_info_init(info);
}
/**
\param url The url to parse for protocol
\param info The info to fill
\return Returns the offset in url of the protocol found -1 if not found
*/
static s32 gf_dm_parse_protocol(const char * url, GF_URL_Info * info)
{
gf_assert(info);
gf_assert(url);
if (!strnicmp(url, "http://", 7)) {
info->port = 80;
info->protocol = gf_strdup("http");
return 7;
}
else if (!strnicmp(url, "https://", 8)) {
info->port = 443;
#ifndef GPAC_HAS_SSL
return -1;
#endif
info->protocol = gf_strdup("https");
return 8;
}
char *sep = strstr(url, "://");
if (sep) {
sep[0] = 0;
info->protocol = gf_strdup(url);
sep[0] = ':';
return 3 + (u32) (sep-url);
}
return -1;
}
GF_EXPORT
GF_Err gf_dm_get_url_info(const char * url, GF_URL_Info * info, const char * baseURL) {
char *tmp, *tmp_url, *current_pos, *urlConcatenateWithBaseURL, *ipv6;
char * copyOfUrl;
s32 proto_offset;
u32 default_port;
gf_dm_url_info_del(info);
urlConcatenateWithBaseURL = NULL;
proto_offset = gf_dm_parse_protocol(url, info);
default_port = info->port;
if (proto_offset > 0) {
url += proto_offset;
} else {
/*relative URL*/
if (!strstr(url, "://")) {
u32 i;
gf_dm_url_info_del(info);
gf_dm_url_info_init(info);
if (baseURL) {
urlConcatenateWithBaseURL = gf_url_concatenate(baseURL, url);
/*relative file path*/
if (!strstr(baseURL, "://")) {
info->protocol = gf_strdup("file");
info->canonicalRepresentation = urlConcatenateWithBaseURL;
return GF_OK;
}
proto_offset = gf_dm_parse_protocol(urlConcatenateWithBaseURL, info);
} else {
proto_offset = -1;
}
if (proto_offset < 0) {
tmp = urlConcatenateWithBaseURL;
gf_assert( ! info->remotePath );
info->remotePath = gf_url_percent_encode(tmp);
gf_free( urlConcatenateWithBaseURL );
urlConcatenateWithBaseURL = NULL;
if (!info->remotePath) {
GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[Network] No supported protocol for url %s\n", url));
gf_dm_url_info_del(info);
return GF_BAD_PARAM;
}
for (i=0; i<strlen(info->remotePath); i++)
if (info->remotePath[i]=='\\') info->remotePath[i]='/';
info->canonicalRepresentation = NULL;
gf_dynstrcat(&info->canonicalRepresentation, info->protocol, NULL);
gf_dynstrcat(&info->canonicalRepresentation, "://", NULL);
gf_dynstrcat(&info->canonicalRepresentation, info->remotePath, NULL);
return GF_OK;
} else {
/* We continue the parsing as usual */
url = urlConcatenateWithBaseURL + proto_offset;
}
} else {
GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[Network] No supported protocol for url %s\n", url));
gf_dm_url_info_del(info);
return GF_BAD_PARAM;
}
}
gf_assert( proto_offset >= 0 );
tmp = strchr(url, '/');
//patch for some broken url http://servername?cgi
if (!tmp)
tmp = strchr(url, '?');
gf_assert( !info->remotePath );
info->remotePath = gf_url_percent_encode(tmp ? tmp : "/");
if (info->remotePath[0]=='?') {
char *rpath = NULL;
gf_dynstrcat(&rpath, "/", NULL);
gf_dynstrcat(&rpath, info->remotePath, NULL);
gf_free(info->remotePath);
info->remotePath = rpath;
}
if (tmp) {
tmp[0] = 0;
copyOfUrl = gf_strdup(url);
tmp[0] = '/';
} else {
copyOfUrl = gf_strdup(url);
}
tmp_url = copyOfUrl;
current_pos = tmp_url;
tmp = strrchr(tmp_url, '@');
if (tmp) {
current_pos = tmp + 1;
gf_assert( ! info->server_name );
info->server_name = gf_strdup(current_pos);
tmp[0] = 0;
tmp = strchr(tmp_url, ':');
if (tmp) {
tmp[0] = 0;
info->password = gf_strdup(tmp+1);
}
info->userName = gf_strdup(tmp_url);
} else {
gf_assert( ! info->server_name );
info->server_name = gf_strdup(tmp_url);
}
//scan for port number after IPv6 address ']' end char
ipv6 = strrchr(current_pos, ']');
tmp = strrchr(ipv6 ? ipv6 : current_pos, ':');
if (tmp) {
info->port = atoi(tmp+1);
tmp[0] = 0;
if (info->server_name) {
gf_free(info->server_name);
}
info->server_name = gf_strdup(current_pos);
}
/* We dont't want orig_url to contain user/passwords for security reasons or mismatch in cache hit */
info->canonicalRepresentation = NULL;
gf_dynstrcat(&info->canonicalRepresentation, info->protocol, NULL);
gf_dynstrcat(&info->canonicalRepresentation, "://", NULL);
gf_dynstrcat(&info->canonicalRepresentation, info->server_name, NULL);
if (info->port && (info->port!=default_port)) {
char port[8];
snprintf(port, sizeof(port)-1, ":%d", info->port);
gf_dynstrcat(&info->canonicalRepresentation, port, NULL);
}
gf_dynstrcat(&info->canonicalRepresentation, info->remotePath, NULL);
gf_free(copyOfUrl);
if (urlConcatenateWithBaseURL)
gf_free(urlConcatenateWithBaseURL);
return GF_OK;
}
GF_EXPORT
GF_Err gf_dm_sess_setup_from_url(GF_DownloadSession *sess, const char *url, Bool allow_direct_reuse)
{
Bool socket_changed = GF_FALSE;
GF_URL_Info info;
char *sep_frag=NULL;
if (!url)
return GF_BAD_PARAM;
gf_dm_sess_clear_headers(sess);
sess->allow_direct_reuse = allow_direct_reuse;
gf_dm_url_info_init(&info);
if (!sess->sock)
socket_changed = GF_TRUE;
else if (sess->status>GF_NETIO_DISCONNECTED)
socket_changed = GF_TRUE;
else if (sess->connection_timeout_ms) {
u32 diff = (u32) ( gf_sys_clock_high_res() - sess->last_fetch_time) / 1000;
if (diff >= sess->connection_timeout_ms) {
GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[%s] Timeout reached (%d ms since last vs %d ms timeout), reconnecting\n", sess->log_name, diff, sess->connection_timeout_ms));
socket_changed = GF_TRUE;
}
}
gf_fatal_assert(sess->status != GF_NETIO_WAIT_FOR_REPLY);
gf_fatal_assert(sess->status != GF_NETIO_DATA_EXCHANGE);
//always detach task if any
if (sess->ftask) {
sess->ftask->sess = NULL;
sess->ftask = NULL;
}
//strip fragment
sep_frag = strchr(url, '#');
if (sep_frag) sep_frag[0]=0;
sess->last_error = gf_dm_get_url_info(url, &info, sess->orig_url);
if (sess->last_error) {
if (sep_frag) sep_frag[0]='#';
return sess->last_error;
}
if (!strstr(url, "://")) {
char *sep;
gf_dm_url_info_del(&info);
gf_dm_url_info_init(&info);
info.port = sess->port;
info.server_name = sess->server_name ? gf_strdup(sess->server_name) : NULL;
info.remotePath = gf_strdup(url);
sep = strstr(sess->orig_url_before_redirect, "://");
gf_assert(sep);
sep[0] = 0;
info.protocol = gf_strdup(sess->orig_url_before_redirect);
sep[0] = ':';
}
if (sess->port != info.port) {
socket_changed = GF_TRUE;
sess->port = info.port;
}
#ifdef GPAC_HTTPMUX
//safety in case we had a previous error but underlying stream_id was not reset
sess->hmux_stream_id = -1;
#endif
if (sess->cached_file) {
socket_changed = GF_TRUE;
gf_fclose(sess->cached_file);
sess->cached_file = NULL;
if (sess->cache_entry) {
gf_cache_remove_entry_from_session(sess);
sess->cache_entry = NULL;
}
}
if (sess->log_name) {
gf_free(sess->log_name);
sess->log_name = NULL;
}
if (!strcmp("http", info.protocol) || !strcmp("https", info.protocol)) {
sess->log_name = gf_strdup("HTTP");
if (sess->do_requests != http_do_requests) {
sess->do_requests = http_do_requests;
socket_changed = GF_TRUE;
}
Bool use_ssl = !strcmp("https", info.protocol) ? GF_TRUE : GF_FALSE;
//if proxy, check scheme and port
const char *proxy = (sess->flags & GF_NETIO_SESSION_NO_PROXY) ? NULL : gf_opts_get_key("core", "proxy");
if (proxy) {
if (!strnicmp(proxy, "http://", 7)) use_ssl = GF_FALSE;
else if (!strnicmp(proxy, "https://", 8)) use_ssl = GF_TRUE;
else if (strstr(proxy, ":80")) use_ssl = GF_FALSE;
else if (strstr(proxy, ":443")) use_ssl = GF_TRUE;
}
if (use_ssl) {
if (!(sess->flags & GF_DOWNLOAD_SESSION_USE_SSL)) {
sess->flags |= GF_DOWNLOAD_SESSION_USE_SSL;
socket_changed = GF_TRUE;
}
} else if (sess->flags & GF_DOWNLOAD_SESSION_USE_SSL) {
sess->flags &= ~GF_DOWNLOAD_SESSION_USE_SSL;
socket_changed = GF_TRUE;
}
} else {
sess->do_requests = NULL;
#ifdef GPAC_HAS_CURL
if (!sess->dm->curl_multi) {
sess->dm->curl_multi = curl_multi_init();
}
#endif
}
if (sess->server_name && info.server_name && !strcmp(sess->server_name, info.server_name)) {
} else {
socket_changed = GF_TRUE;
if (sess->server_name) gf_free(sess->server_name);
sess->server_name = info.server_name ? gf_strdup(info.server_name) : NULL;
}
if (info.canonicalRepresentation) {
if (sess->orig_url) gf_free(sess->orig_url);
sess->orig_url = gf_strdup(info.canonicalRepresentation);
} else {
if (sess->orig_url) gf_free(sess->orig_url);
sess->orig_url = gf_strdup(info.protocol);
gf_dynstrcat(&sess->orig_url, info.server_name, "://");
if (info.port) {
char szTmp[10];
sprintf(szTmp, ":%u", info.port);
gf_dynstrcat(&sess->orig_url, szTmp, NULL);
}
gf_dynstrcat(&sess->orig_url, info.remotePath, NULL);
}
if (!sess->orig_url_before_redirect)
sess->orig_url_before_redirect = gf_strdup(url);
if (sess->remote_path) gf_free(sess->remote_path);
sess->remote_path = gf_strdup(info.remotePath);
if (sess->status==GF_NETIO_STATE_ERROR)
socket_changed = GF_TRUE;
if (!socket_changed && info.userName && !strcmp(info.userName, sess->creds->username)) {
} else {
sess->creds = NULL;
if (info.userName) {
if (! sess->dm) {
GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[%s] Did not found any download manager, credentials not supported\n", sess->log_name));
} else
sess->creds = gf_user_credentials_register(sess->dm, !strcmp("https", info.protocol), sess->server_name, info.userName, info.password, info.userName && info.password);
}
}
gf_dm_url_info_del(&info);
if (sep_frag) sep_frag[0]='#';
#ifdef GPAC_HTTPMUX
if (sess->hmux_sess) {
if (sess->hmux_sess->do_shutdown)
socket_changed = GF_TRUE;
sess->hmux_buf.size = 0;
sess->hmux_buf.offset = 0;
}
#endif
#ifdef GPAC_HAS_CURL
if (sess->dm->curl_multi && (!sess->do_requests || gf_opts_get_bool("core", "curl"))
//if the caller wants a GF_Socket interface, we cannot use curl
&& !(sess->flags & GF_NETIO_SESSION_SHARE_SOCKET)
) {
GF_Err e = curl_setup_session(sess);
if (e) return e;
} else
#endif
if (sess->sock && !socket_changed) {
if (!sess->do_requests) return GF_NOT_SUPPORTED;
sess->status = GF_NETIO_CONNECTED;
sess->last_error = GF_OK;
//reset number of retry and start time
sess->num_retry = SESSION_RETRY_COUNT;
sess->start_time = 0;
sess->needs_cache_reconfig = 1;
} else {
if (!sess->do_requests) return GF_NOT_SUPPORTED;
#ifdef GPAC_HTTPMUX
if (sess->hmux_sess) {
gf_mx_p(sess->mx);
hmux_detach_session(sess->hmux_sess, sess);
gf_mx_v(sess->mx);
}
#endif
dm_sess_sk_del(sess);
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;
#ifdef GPAC_HAS_SSL
if (sess->ssl) {
SSL_shutdown(sess->ssl);
SSL_free(sess->ssl);
sess->ssl = NULL;
}
#endif
}
sess->total_size = 0;
sess->bytes_done = 0;
sess->full_resource_size = 0;
//could be not-0 after a byte-range request using chunk transfer
sess->remaining_data_size = 0;
sess->local_cache_only = GF_FALSE;
if (sess->dm && sess->dm->local_cache_url_provider_cbk) {
Bool res = sess->dm->local_cache_url_provider_cbk(sess->dm->lc_udta, (char *)url, GF_FALSE);
if (res == GF_TRUE) {
sess->local_cache_only = GF_TRUE;
gf_free(sess->orig_url);
sess->orig_url = gf_strdup(url);
SET_LAST_ERR(sess->last_error)
sess->use_cache_file = GF_TRUE;
gf_dm_configure_cache(sess);
sess->bytes_done = 0;
if (sess->cache_entry && gf_cache_is_deleted(sess->cache_entry)) {
sess->status = GF_NETIO_DATA_TRANSFERED;
SET_LAST_ERR(GF_URL_REMOVED)
//return GF_OK;
} else if (! gf_cache_is_done(sess->cache_entry)) {
sess->total_size = 0;
sess->status = GF_NETIO_DATA_EXCHANGE;
} else {
sess->total_size = gf_cache_get_content_length(sess->cache_entry);
sess->bytes_done = sess->total_size;
sess->status = GF_NETIO_DATA_TRANSFERED;
if (!sess->cache_entry) {
SET_LAST_ERR(GF_URL_ERROR)
} else {
SET_LAST_ERR(GF_OK)
}
}
sess->total_time_since_req = gf_cache_get_downtime(sess->cache_entry);
if (sess->total_time_since_req)
sess->bytes_per_sec = (u32) ((1000 * (u64) sess->bytes_done) / sess->total_time_since_req);
else
sess->bytes_per_sec = 0;
gf_dm_sess_reload_cached_headers(sess);
}
}
if (sess->last_error)
return sess->last_error;
return gf_dm_sess_set_range(sess, 0, 0, GF_TRUE);
}
Bool gf_dm_session_do_task(GF_DownloadSession *sess)
{
Bool do_run = GF_TRUE;
if (sess->destroy) {
do_run = GF_FALSE;
} else {
Bool unlock = sess->mx ? GF_TRUE : GF_FALSE;
gf_mx_p(sess->mx);
if (sess->status >= GF_NETIO_DATA_TRANSFERED) {
do_run = GF_FALSE;
} else {
if (sess->status < GF_NETIO_CONNECTED) {
gf_dm_connect(sess);
} else {
sess->do_requests(sess);
}
}
if (unlock)
gf_mx_v(sess->mx);
}
if (do_run) return GF_TRUE;
/*destroy all session but keep connection active*/
gf_dm_disconnect(sess, HTTP_NO_CLOSE);
if (sess->last_error==GF_EOS) sess->last_error = GF_OK;
else if (sess->last_error==GF_IP_NETWORK_EMPTY) sess->last_error = GF_OK;
else if (sess->last_error) {
sess->status = GF_NETIO_STATE_ERROR;
}
return GF_FALSE;
}
Bool gf_dm_session_task(GF_FilterSession *fsess, void *callback, u32 *reschedule_ms)
{
GF_SessTask *task = callback;
GF_DownloadSession *sess = task->sess;
if (!sess || sess->destroy) {
gf_free(task);
if (sess) sess->ftask = NULL;
return GF_FALSE;
}
task->in_task = GF_TRUE;
Bool ret = gf_dm_session_do_task(sess);
task->in_task = GF_FALSE;
if (ret) {
if (sess->rate_regulated) {
*reschedule_ms = (sess->last_cap_rate_bytes_per_sec > sess->max_data_rate) ? 1000 : 100;
} else {
*reschedule_ms = 1;
};
return GF_TRUE;
}
gf_assert(sess->ftask);
gf_free(sess->ftask);
sess->ftask = NULL;
if (sess->destroy) {
gf_dm_sess_del(sess);
}
return GF_FALSE;
}
#ifndef GPAC_DISABLE_THREADS
static u32 gf_dm_session_thread(void *par)
{
GF_DownloadSession *sess = (GF_DownloadSession *)par;
if (!sess) return 0;
GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[Downloader] Entering thread ID %d\n", gf_th_id() ));
sess->flags &= ~GF_DOWNLOAD_SESSION_THREAD_DEAD;
while (!sess->destroy) {
Bool ret = gf_dm_session_do_task(sess);
if (!ret) break;
gf_sleep(sess->rate_regulated ? 100 : 0);
}
sess->flags |= GF_DOWNLOAD_SESSION_THREAD_DEAD;
if (sess->destroy)
gf_dm_sess_del(sess);
return 1;
}
#endif
GF_DownloadSession *gf_dm_sess_new_internal(GF_DownloadManager * dm, const char *url, u32 dl_flags,
gf_dm_user_io user_io,
void *usr_cbk,
GF_Socket *server,
Bool force_server,
GF_Err *e)
{
GF_DownloadSession *sess;
GF_SAFEALLOC(sess, GF_DownloadSession);
if (!sess) {
return NULL;
}
#ifdef GPAC_HTTPMUX
sess->hmux_stream_id = -1;
#endif
sess->headers = gf_list_new();
sess->flags = dl_flags;
if (sess->flags & GF_NETIO_SESSION_NOTIFY_DATA)
sess->force_data_write_callback = GF_TRUE;
sess->user_proc = user_io;
sess->usr_cbk = usr_cbk;
sess->creds = NULL;
sess->log_name = gf_strdup("HTTP");
sess->http_buf_size = dm ? dm->read_buf_size : GF_DOWNLOAD_BUFFER_SIZE;
sess->http_buf = gf_malloc(sess->http_buf_size + 1);
sess->conn_timeout = gf_opts_get_int("core", "tcp-timeout");
if (!sess->conn_timeout) sess->conn_timeout = 5000;
sess->request_timeout = gf_opts_get_int("core", "req-timeout");
sess->chunk_wnd_dur = gf_opts_get_int("core", "cte-rate-wnd") * 1000;
if (!sess->chunk_wnd_dur) sess->chunk_wnd_dur = 20000;
sess->dm = dm;
if (server || force_server) {
sess->sock = server;
sess->flags = GF_NETIO_SESSION_NOT_THREADED;
sess->status = GF_NETIO_CONNECTED;
sess->server_mode = GF_TRUE;
sess->do_requests = http_do_requests;
if (e) *e = GF_OK;
if (dl_flags & GF_NETIO_SESSION_NO_BLOCK) {
sess->flags |= GF_NETIO_SESSION_NO_BLOCK;
if (server)
gf_sk_set_block_mode(server, GF_TRUE);
}
return sess;
}
if (!sess->conn_timeout) sess->server_only_understand_get = GF_TRUE;
if (dm)
sess->disable_cache = dm->disable_cache;
if (! (dl_flags & GF_NETIO_SESSION_NOT_THREADED)
#ifdef GPAC_HAS_CURL
&& !sess->curl_hnd
#endif
) {
sess->mx = gf_mx_new(url);
if (!sess->mx) {
gf_free(sess);
return NULL;
}
}
if ((dm->h3_mode == H3_MODE_FIRST) || (dm->h3_mode == H3_MODE_ONLY))
sess->flags |= GF_NETIO_SESSION_USE_QUIC;
else if (dm->h3_mode == H3_MODE_AUTO)
sess->flags |= GF_NETIO_SESSION_RETRY_QUIC;
*e = gf_dm_sess_setup_from_url(sess, url, GF_FALSE);
if (*e) {
GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[Downloader] failed to create session for %s: %s\n", url, gf_error_to_string(*e)));
gf_dm_sess_del(sess);
return NULL;
}
gf_assert( sess );
sess->num_retry = SESSION_RETRY_COUNT;
/*threaded session must be started with gf_dm_sess_process*/
return sess;
}
GF_EXPORT
GF_DownloadSession *gf_dm_sess_new_server(GF_DownloadManager *dm, GF_Socket *server,
void *ssl_sock_ctx,
gf_dm_user_io user_io,
void *usr_cbk,
Bool async,
GF_Err *e)
{
GF_DownloadSession *sess = gf_dm_sess_new_internal(dm, NULL, async ? GF_NETIO_SESSION_NO_BLOCK : 0, user_io, usr_cbk, server, GF_TRUE, e);
if (!sess) return NULL;
#ifdef GPAC_HAS_SSL
if (ssl_sock_ctx) {
sess->ssl = ssl_sock_ctx;
gf_dm_sess_server_setup_ssl(sess);
}
#endif
return sess;
}
u32 gf_dm_sess_subsession_count(GF_DownloadSession *sess)
{
#ifdef GPAC_HTTPMUX
if (sess->hmux_sess)
return gf_list_count(sess->hmux_sess->sessions);
#endif
return 1;
}
void gf_dm_sess_server_reset(GF_DownloadSession *sess)
{
if (!sess->server_mode) return;
//DO NOT clear headers if H2: the underlying h2 session could have been ended (stream_id=0) and then reassigned
//when processsing another session, so the headers would be the ones of the new stream_id
//for H2, the reset is done when reassigning sessions
#ifdef GPAC_HTTPMUX
if (!sess->hmux_sess)
#endif
gf_dm_sess_clear_headers(sess);
sess->total_size = sess->bytes_done = 0;
sess->async_buf_size = 0;
sess->chunk_bytes = 0;
sess->chunk_header_bytes = 0;
sess->chunked = GF_FALSE;
sess->status = GF_NETIO_CONNECTED;
}
GF_EXPORT
GF_DownloadSession *gf_dm_sess_new_simple(GF_DownloadManager * dm, const char *url, u32 dl_flags,
gf_dm_user_io user_io,
void *usr_cbk,
GF_Err *e)
{
return gf_dm_sess_new_internal(dm, url, dl_flags, user_io, usr_cbk, NULL, GF_FALSE, e);
}
GF_EXPORT
GF_DownloadSession *gf_dm_sess_new(GF_DownloadManager *dm, const char *url, u32 dl_flags,
gf_dm_user_io user_io,
void *usr_cbk,
GF_Err *e)
{
GF_DownloadSession *sess;
*e = GF_OK;
if (gf_dm_is_local(dm, url)) {
*e = GF_NOT_SUPPORTED;
return NULL;
}
if (!gf_dm_can_handle_url(url)) {
*e = GF_NOT_SUPPORTED;
return NULL;
}
sess = gf_dm_sess_new_simple(dm, url, dl_flags, user_io, usr_cbk, e);
if (sess && dm) {
sess->dm = dm;
gf_mx_p(dm->cache_mx);
gf_list_add(dm->all_sessions, sess);
gf_mx_v(dm->cache_mx);
}
return sess;
}
GF_Err gf_dm_read_data(GF_DownloadSession *sess, char *data, u32 data_size, u32 *out_read)
{
GF_Err e;
if (sess->cached_file) {
*out_read = (u32) gf_fread(data, data_size, sess->cached_file);
if (! *out_read && gf_cache_is_done(sess->cache_entry)) {
sess->total_size = gf_cache_get_content_length(sess->cache_entry);
if (sess->total_size != sess->bytes_done) {
sess->status = GF_NETIO_STATE_ERROR;
SET_LAST_ERR(GF_CORRUPTED_DATA);
GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[%s] Broken cache for %s: %u bytes in cache but %u advertized\n", sess->log_name, sess->orig_url, sess->bytes_done, sess->total_size));
return GF_CORRUPTED_DATA;
}
}
return GF_OK;
}
#ifdef GPAC_HAS_CURL
if (sess->curl_hnd) {
return curl_read_data(sess, data, data_size, out_read);
}
#endif
if (sess->dm && sess->dm->simulate_no_connection) {
if (sess->sock) {
sess->status = GF_NETIO_DISCONNECTED;
}
return GF_IP_NETWORK_FAILURE;
}
if (!sess)
return GF_BAD_PARAM;
if (sess->server_mode && (sess->flags & GF_NETIO_SESSION_USE_QUIC))
return GF_IP_NETWORK_EMPTY;
gf_mx_p(sess->mx);
if (!sess->sock) {
sess->status = GF_NETIO_DISCONNECTED;
gf_mx_v(sess->mx);
return GF_IP_CONNECTION_CLOSED;
}
*out_read = 0;
#ifdef GPAC_HAS_SSL
if (sess->ssl && !(sess->flags & GF_NETIO_SESSION_USE_QUIC)) {
e = gf_ssl_read_data(sess, data, data_size, out_read);
if (e==GF_NOT_READY) {
gf_mx_v(sess->mx);
return GF_IP_NETWORK_EMPTY;
}
} else
#endif
e = gf_sk_receive(sess->sock, data, data_size, out_read);
#ifdef GPAC_HTTPMUX
if (sess->hmux_sess) {
GF_Err hme = sess->hmux_sess->data_received(sess, data, *out_read);
if (hme) {
gf_mx_v(sess->mx);
*out_read = 0;
return hme;
}
}
#endif //GPAC_HTTPMUX
if (*out_read)
sess->last_fetch_time = gf_sys_clock_high_res();
gf_mx_v(sess->mx);
return e;
}
static void gf_dm_connect(GF_DownloadSession *sess)
{
GF_Err e;
Bool register_sock;
Bool allow_offline = sess->dm ? sess->dm->allow_offline_cache : GF_FALSE;
u16 proxy_port = 0;
char szProxy[GF_MAX_PATH];
const char *proxy;
//offline cache enabled, setup cache before connection
if (!sess->connect_pending && allow_offline) {
gf_dm_configure_cache(sess);
if (sess->cached_file) return;
}
#ifdef GPAC_HAS_CURL
if (sess->curl_hnd) {
curl_connect(sess);
return;
}
#endif
#ifdef GPAC_HTTPMUX
if (sess->hmux_switch_sess) {
sess->hmux_switch_sess = 0;
gf_mx_p(sess->mx);
hmux_detach_session(sess->hmux_sess, sess);
gf_mx_v(sess->mx);
if (sess->num_retry) {
SET_LAST_ERR(GF_OK)
sess->num_retry--;
GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[%s] stream_id "LLD" (%s) refused by server, retrying and marking session as no longer available\n", sess->log_name, sess->hmux_stream_id, sess->remote_path ? sess->remote_path : sess->orig_url));
sess->hmux_stream_id = -1;
} else {
GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("%s] stream_id "LLD" (%s) refused by server after all retries, marking session as no longer available\n", sess->log_name, sess->hmux_stream_id, sess->remote_path ? sess->remote_path : sess->orig_url));
sess->status = GF_NETIO_STATE_ERROR;
SET_LAST_ERR(GF_REMOTE_SERVICE_ERROR)
sess->hmux_stream_id = -1;
return;
}
}
if (sess->hmux_sess && sess->hmux_sess->connected) {
sess->connect_time = 0;
sess->status = GF_NETIO_CONNECTED;
sess->last_error = GF_OK;
gf_dm_sess_notify_state(sess, GF_NETIO_CONNECTED, GF_OK);
if (!allow_offline)
gf_dm_configure_cache(sess);
return;
}
Bool attach_to_parent_sess = GF_FALSE;
if (sess->dm && !sess->sock) {
#ifdef GPAC_HAS_HTTP2
if (!sess->dm->disable_http2 && (sess->h2_upgrade_state<3)) attach_to_parent_sess = GF_TRUE;
#endif
}
if (attach_to_parent_sess) {
gf_mx_p(sess->dm->cache_mx);
u32 i, count = gf_list_count(sess->dm->all_sessions);
for (i=0; i<count; i++) {
GF_DownloadSession *a_sess = gf_list_get(sess->dm->all_sessions, i);
if (strcmp(a_sess->server_name, sess->server_name) || (a_sess->port != sess->port)) continue;
if (!a_sess->hmux_sess) {
#ifdef GPAC_HAS_HTTP2
//we already ahd a connection to this server with H2 failure, do not try h2
if (a_sess->h2_upgrade_state==4) {
sess->h2_upgrade_state=4;
break;
}
#endif
//we have the same server name
//trick in non-block mode, we want to wait for upgrade to be completed
//before creating a new socket
if ((a_sess != sess)
&& a_sess->sock
&& (a_sess->status <= GF_NETIO_WAIT_FOR_REPLY)
&& (sess->flags & GF_NETIO_SESSION_NO_BLOCK)
#ifdef GPAC_HAS_HTTP2
&& (a_sess->h2_upgrade_state<2)
&& !sess->dm->disable_http2
&& !gf_opts_get_bool("core", "no-h2c")
#endif
//TODO: H3 test
) {
sess->connect_pending = 1;
SET_LAST_ERR(GF_IP_NETWORK_EMPTY)
gf_mx_v(sess->dm->cache_mx);
return;
}
continue;
}
if (a_sess->hmux_sess->do_shutdown) continue;
GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[%s] associating session %s to existing %s session: %s:%u\n", sess->log_name, sess->remote_path ? sess->remote_path : sess->orig_url, a_sess->log_name, a_sess->server_name, a_sess->port));
u32 nb_locks=0;
if (sess->mx) {
nb_locks = gf_mx_get_num_locks(sess->mx);
if (nb_locks) gf_mx_v(sess->mx);
gf_mx_del(sess->mx);
}
sess->hmux_sess = a_sess->hmux_sess;
sess->mx = a_sess->hmux_sess->mx;
if (nb_locks)
gf_mx_p(sess->mx);
sess->sock = a_sess->sock;
#ifdef GPAC_HAS_SSL
sess->ssl = a_sess->ssl;
#endif
sess->proxy_enabled = a_sess->proxy_enabled;
sess->hmux_sess->setup_session(sess, GF_FALSE);
gf_list_add(sess->hmux_sess->sessions, sess);
//reconfigure cache
if (sess->allow_direct_reuse) {
gf_dm_configure_cache(sess);
if (sess->cached_file) {
gf_mx_v(sess->dm->cache_mx);
return;
}
}
sess->connect_time = 0;
if (sess->hmux_sess->connected) {
sess->status = GF_NETIO_CONNECTED;
gf_dm_sess_notify_state(sess, GF_NETIO_CONNECTED, GF_OK);
gf_dm_configure_cache(sess);
}
gf_mx_v(sess->dm->cache_mx);
return;
} // end all_sessions loop
gf_mx_v(sess->dm->cache_mx);
}
#endif
resetup_socket:
register_sock = GF_FALSE;
if (!sess->sock) {
u32 sock_type = GF_SOCK_TYPE_TCP;
if (sess->flags & GF_NETIO_SESSION_USE_QUIC)
sock_type = GF_SOCK_TYPE_UDP;
sess->sock = gf_sk_new_ex(sock_type, sess->netcap_id);
if (sess->sock && (sess->flags & GF_NETIO_SESSION_NO_BLOCK))
gf_sk_set_block_mode(sess->sock, GF_TRUE);
if (sess->sock_group) register_sock = GF_TRUE;
}
/*connect*/
sess->status = GF_NETIO_SETUP;
gf_dm_sess_notify_state(sess, sess->status, GF_OK);
/*PROXY setup*/
if (sess->proxy_enabled!=2) {
proxy = (sess->flags & GF_NETIO_SESSION_NO_PROXY) ? NULL : gf_opts_get_key("core", "proxy");
if (proxy) {
u32 i;
proxy_port = 80;
char *sep = strstr(proxy, "://");
gf_strcpy(szProxy, sep ? sep+3 : proxy);
sep = strchr(szProxy, ':');
if (sep) {
proxy_port = atoi(sep+1);
sep[0]=0;
}
proxy = szProxy;
Bool use_proxy=GF_TRUE;
for (i=0; i<gf_list_count(sess->dm->skip_proxy_servers); i++) {
char *skip = (char*)gf_list_get(sess->dm->skip_proxy_servers, i);
if (!strcmp(skip, sess->server_name)) {
use_proxy=GF_FALSE;
break;
}
}
if (!use_proxy) {
proxy = NULL;
} else {
sess->proxy_enabled = 1;
}
} else {
proxy = NULL;
sess->proxy_enabled = 0;
}
} else {
proxy = NULL;
}
if (!proxy) {
proxy = sess->server_name;
proxy_port = sess->port;
}
if (!sess->connect_pending) {
GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[%s] Connecting to %s:%d for URL %s\n", sess->log_name, proxy, proxy_port, sess->remote_path ? sess->remote_path : "undefined"));
}
if ((sess->status == GF_NETIO_SETUP) && (sess->connect_pending<2)) {
u64 now;
if (sess->dm && sess->dm->simulate_no_connection) {
sess->status = GF_NETIO_STATE_ERROR;
SET_LAST_ERR(GF_IP_NETWORK_FAILURE)
gf_dm_sess_notify_state(sess, sess->status, sess->last_error);
return;
}
sess->async_buf_size = 0;
now = gf_sys_clock_high_res();
if ((sess->flags & GF_NETIO_SESSION_NO_BLOCK) && !sess->start_time)
sess->start_time = now;
#ifdef GPAC_HAS_NGTCP2
if (sess->flags & GF_NETIO_SESSION_USE_QUIC) {
e = http3_connect(sess, (char *) proxy, proxy_port);
if (sess->num_retry && (e==GF_IP_CONNECTION_FAILURE) ) {
register_sock = GF_FALSE;
e = GF_IP_NETWORK_EMPTY;
}
} else
#endif
e = gf_sk_connect(sess->sock, (char *) proxy, proxy_port, NULL);
/*retry*/
if (e == GF_IP_NETWORK_EMPTY) {
if (sess->num_retry) {
if (register_sock) gf_sk_group_register(sess->sock_group, sess->sock);
sess->status = GF_NETIO_SETUP;
//reset pending flag
sess->connect_pending = 0;
if (sess->flags & GF_NETIO_SESSION_NO_BLOCK) {
//either timeout or set pending flag
if ((now - sess->start_time) / 1000 > sess->conn_timeout) {
sess->num_retry = 0;
} else {
sess->connect_pending = 1;
}
} else {
sess->num_retry--;
sess->connect_pending = 1;
}
if (sess->connect_pending) {
SET_LAST_ERR(GF_IP_NETWORK_EMPTY)
return;
}
}
e = GF_IP_CONNECTION_FAILURE;
}
sess->connect_pending = 0;
SET_LAST_ERR(GF_OK)
/*failed*/
if (e) {
if ((sess->flags & GF_NETIO_SESSION_RETRY_QUIC) && !(sess->flags & GF_NETIO_SESSION_USE_QUIC)) {
sess->status = GF_NETIO_SETUP;
sess->connect_pending = 0;
sess->start_time = 0;
gf_sk_group_unregister(sess->sock_group, sess->sock);
gf_sk_del(sess->sock);
sess->sock = NULL;
GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[%s] Failed to connect through TCP (%s), retrying with QUIC\n", sess->log_name, gf_error_to_string(e)));
sess->flags |= GF_NETIO_SESSION_USE_QUIC;
sess->flags &= ~GF_NETIO_SESSION_RETRY_QUIC;
goto resetup_socket;
}
if ((sess->flags & GF_NETIO_SESSION_USE_QUIC)
&& !(sess->flags & GF_NETIO_SESSION_RETRY_QUIC)
&& (sess->dm->h3_mode!=H3_MODE_ONLY)
) {
sess->flags &= ~GF_NETIO_SESSION_USE_QUIC;
sess->status = GF_NETIO_SETUP;
sess->connect_pending = 0;
sess->start_time = 0;
gf_sk_group_unregister(sess->sock_group, sess->sock);
gf_sk_del(sess->sock);
sess->sock = NULL;
#ifdef GPAC_HTTPMUX
if (sess->hmux_sess) {
sess->hmux_sess->destroy(sess->hmux_sess);
gf_list_del(sess->hmux_sess->sessions);
gf_free(sess->hmux_sess);
sess->hmux_sess = NULL;
}
#endif
GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[%s] Failed to connect through QUIC (%s), retrying with TCP\n", sess->log_name, gf_error_to_string(e)));
goto resetup_socket;
}
sess->status = GF_NETIO_STATE_ERROR;
SET_LAST_ERR(e)
gf_dm_sess_notify_state(sess, sess->status, e);
return;
}
if (register_sock) gf_sk_group_register(sess->sock_group, sess->sock);
if (sess->allow_direct_reuse) {
gf_dm_configure_cache(sess);
if (sess->cached_file) return;
}
sess->connect_time = (u32) (gf_sys_clock_high_res() - now);
GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[%s] Connected to %s:%d\n", sess->log_name, proxy, proxy_port));
gf_dm_sess_notify_state(sess, GF_NETIO_CONNECTED, GF_OK);
}
if (sess->status == GF_NETIO_SETUP)
sess->status = GF_NETIO_CONNECTED;
#ifdef GPAC_HAS_SSL
if ((!sess->ssl || (sess->connect_pending==2))
&& (sess->flags & (GF_DOWNLOAD_SESSION_USE_SSL|GF_DOWNLOAD_SESSION_SSL_FORCED))
) {
SSLConnectStatus ret = gf_ssl_try_connect(sess, proxy);
if (ret != SSL_CONNECT_OK) {
if (ret == SSL_CONNECT_RETRY)
gf_dm_connect(sess);
return;
}
} else
#endif
{
GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[%s] connected\n", sess->log_name));
}
if (!allow_offline)
gf_dm_configure_cache(sess);
}
DownloadedCacheEntry gf_dm_refresh_cache_entry(GF_DownloadSession *sess)
{
Bool go;
u32 timer = 0;
u32 flags;
if (!sess) return NULL;
flags = sess->flags;
sess->flags |= GF_NETIO_SESSION_NOT_CACHED;
go = GF_TRUE;
while (go) {
switch (sess->status) {
/*setup download*/
case GF_NETIO_SETUP:
gf_dm_connect(sess);
break;
case GF_NETIO_WAIT_FOR_REPLY:
if (timer == 0)
timer = gf_sys_clock();
{
u32 timer2 = gf_sys_clock();
if (timer2 - timer > 5000) {
GF_Err e;
/* Since HEAD is not understood by this server, we use a GET instead */
sess->http_read_type = GET;
sess->flags |= GF_NETIO_SESSION_NOT_CACHED;
gf_dm_disconnect(sess, HTTP_NO_CLOSE);
sess->status = GF_NETIO_SETUP;
sess->server_only_understand_get = GF_TRUE;
GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("gf_dm_refresh_cache_entry() : Timeout with HEAD, try with GET\n"));
e = gf_dm_sess_setup_from_url(sess, sess->orig_url, GF_FALSE);
if (e) {
GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("gf_dm_refresh_cache_entry() : Error with GET %d\n", e));
sess->status = GF_NETIO_STATE_ERROR;
SET_LAST_ERR(e)
gf_dm_sess_notify_state(sess, sess->status, e);
} else {
timer = 0;
continue;
}
}
}
case GF_NETIO_CONNECTED:
sess->do_requests(sess);
break;
case GF_NETIO_DATA_EXCHANGE:
case GF_NETIO_DISCONNECTED:
case GF_NETIO_STATE_ERROR:
case GF_NETIO_DATA_TRANSFERED:
go = GF_FALSE;
break;
default:
break;
}
}
sess->flags = flags;
if (sess->status==GF_NETIO_STATE_ERROR) return NULL;
return sess->cache_entry;
}
GF_EXPORT
const char *gf_dm_sess_mime_type(GF_DownloadSession *sess)
{
DownloadedCacheEntry entry;
if (sess->cache_entry) {
const char * oldMimeIfAny = gf_cache_get_mime_type(sess->cache_entry);
if (oldMimeIfAny)
return oldMimeIfAny;
}
entry = gf_dm_refresh_cache_entry (sess);
if (!entry)
return sess->mime_type;
gf_assert( entry == sess->cache_entry && entry);
return gf_cache_get_mime_type( sess->cache_entry );
}
GF_EXPORT
GF_Err gf_dm_sess_set_range(GF_DownloadSession *sess, u64 start_range, u64 end_range, Bool discontinue_cache)
{
if (!sess)
return GF_BAD_PARAM;
if (sess->cache_entry) {
if (!discontinue_cache) {
if (gf_cache_get_end_range(sess->cache_entry) + 1 != start_range)
discontinue_cache = GF_TRUE;
}
if (sess->sock) {
switch (sess->status) {
case GF_NETIO_CONNECTED:
case GF_NETIO_DISCONNECTED:
case GF_NETIO_DATA_TRANSFERED:
break;
default:
return GF_BAD_PARAM;
}
}
if (!sess->local_cache_only) {
sess->status = sess->sock ? GF_NETIO_CONNECTED : GF_NETIO_SETUP;
sess->num_retry = SESSION_RETRY_COUNT;
if (!discontinue_cache) {
gf_cache_set_end_range(sess->cache_entry, end_range);
/*remember this in case we get disconnected*/
sess->is_range_continuation = GF_TRUE;
} else {
sess->needs_cache_reconfig = 1;
sess->reused_cache_entry = GF_FALSE;
}
}
} else {
if (sess->status == GF_NETIO_DISCONNECTED)
sess->status = GF_NETIO_SETUP;
else if ((sess->status != GF_NETIO_SETUP) && (sess->status != GF_NETIO_CONNECTED))
return GF_BAD_PARAM;
}
sess->range_start = start_range;
sess->range_end = end_range;
sess->needs_range = (start_range || end_range) ? GF_TRUE : GF_FALSE;
return GF_OK;
}
#ifdef GPAC_HTTPMUX
static void gf_dm_sess_flush_input(GF_DownloadSession *sess)
{
u32 res;
sess->http_buf[0] = 0;
GF_Err e = gf_dm_read_data(sess, sess->http_buf, sess->http_buf_size, &res);
switch (e) {
case GF_IP_NETWORK_EMPTY:
case GF_OK:
return;
default:
sess->status = GF_NETIO_STATE_ERROR;
SET_LAST_ERR(e)
return;
}
}
#endif
GF_EXPORT
GF_Err gf_dm_sess_process(GF_DownloadSession *sess)
{
Bool go;
#ifndef GPAC_DISABLE_THREADS
/*if session is threaded, start thread*/
if (! (sess->flags & GF_NETIO_SESSION_NOT_THREADED)) {
if (sess->dm->filter_session && !gf_opts_get_bool("core", "dm-threads")) {
if (sess->ftask)
return GF_OK;
GF_SAFEALLOC(sess->ftask, GF_SessTask);
if (!sess->ftask) return GF_OUT_OF_MEM;
sess->ftask->sess = sess;
gf_fs_post_user_task(sess->dm->filter_session, gf_dm_session_task, sess->ftask, "download");
return GF_OK;
}
if (sess->th) {
GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[%s] Session already started - ignoring start\n", sess->log_name));
return GF_OK;
}
sess->th = gf_th_new( gf_file_basename(sess->orig_url) );
if (!sess->th) return GF_OUT_OF_MEM;
gf_th_run(sess->th, gf_dm_session_thread, sess);
return GF_OK;
}
#endif //GPAC_DISABLE_THREADS
if (sess->put_state==2) {
if (sess->status==GF_NETIO_DATA_TRANSFERED)
sess->status = GF_NETIO_WAIT_FOR_REPLY;
}
/*otherwise do a synchronous download*/
go = GF_TRUE;
while (go) {
switch (sess->status) {
/*setup download*/
case GF_NETIO_SETUP:
gf_dm_connect(sess);
if (sess->connect_pending) {
go = GF_FALSE;
if (!sess->last_error) {
//in case someone forgot to set this - if no error we must be in network empty state while connection is pending
sess->last_error = GF_IP_NETWORK_EMPTY;
}
}
break;
case GF_NETIO_WAIT_FOR_REPLY:
case GF_NETIO_CONNECTED:
if (!sess->last_error)
sess->last_error = GF_IP_NETWORK_EMPTY;
sess->do_requests(sess);
if (sess->server_mode) {
if (sess->status == GF_NETIO_STATE_ERROR) {
sess->status = GF_NETIO_DISCONNECTED;
SET_LAST_ERR(GF_IP_CONNECTION_CLOSED)
sess_connection_closed(sess);
go = GF_FALSE;
} else if (sess->last_error==GF_IP_NETWORK_EMPTY) {
go = GF_FALSE;
} else if (sess->status==GF_NETIO_DISCONNECTED) {
go = GF_FALSE;
}
} else if (sess->flags & GF_NETIO_SESSION_NO_BLOCK) {
if (sess->last_error==GF_IP_NETWORK_EMPTY)
go = GF_FALSE;
}
break;
case GF_NETIO_DATA_EXCHANGE:
if (sess->put_state==2) {
sess->status = GF_NETIO_DATA_TRANSFERED;
SET_LAST_ERR(GF_OK)
go = GF_FALSE;
break;
}
sess->do_requests(sess);
break;
case GF_NETIO_DATA_TRANSFERED:
#ifdef GPAC_HTTPMUX
if (sess->hmux_sess && sess->server_mode) {
gf_dm_sess_flush_input(sess);
sess->hmux_sess->write(sess);
}
//in put and waiting for EOS flush
if ((sess->put_state==1) && sess->hmux_is_eos) return GF_IP_NETWORK_EMPTY;
#endif
go = GF_FALSE;
break;
case GF_NETIO_DISCONNECTED:
case GF_NETIO_STATE_ERROR:
go = GF_FALSE;
break;
case GF_NETIO_GET_METHOD:
case GF_NETIO_GET_HEADER:
case GF_NETIO_GET_CONTENT:
case GF_NETIO_PARSE_REPLY:
break;
default:
GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[Downloader] Session in unknown state !! - aborting\n"));
go = GF_FALSE;
break;
}
}
return sess->last_error;
}
GF_EXPORT
GF_Err gf_dm_sess_process_headers(GF_DownloadSession *sess)
{
Bool go;
if (!(sess->flags & GF_NETIO_SESSION_NOT_THREADED)) {
return GF_OK;
}
go = GF_TRUE;
while (go) {
switch (sess->status) {
/*setup download*/
case GF_NETIO_SETUP:
gf_dm_connect(sess);
break;
case GF_NETIO_WAIT_FOR_REPLY:
case GF_NETIO_CONNECTED:
sess->do_requests(sess);
if (sess->reused_cache_entry && sess->cache_entry && gf_cache_are_headers_processed(sess->cache_entry) ) {
sess->status = GF_NETIO_DATA_EXCHANGE;
}
break;
case GF_NETIO_DATA_EXCHANGE:
case GF_NETIO_DATA_TRANSFERED:
case GF_NETIO_DISCONNECTED:
case GF_NETIO_STATE_ERROR:
go = GF_FALSE;
break;
default:
break;
}
}
return sess->last_error;
}
GF_EXPORT
GF_DownloadManager *gf_dm_new(GF_FilterSession *fsess)
{
const char *opt;
const char * default_cache_dir;
GF_DownloadManager *dm;
GF_SAFEALLOC(dm, GF_DownloadManager);
if (!dm) {
GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[Downloader] Failed to allocate downloader\n"));
return NULL;
}
dm->all_sessions = gf_list_new();
dm->cache_entries = gf_list_new();
dm->credentials = gf_list_new();
dm->skip_proxy_servers = gf_list_new();
dm->partial_downloads = gf_list_new();
dm->cache_mx = gf_mx_new("download_manager_cache_mx");
dm->filter_session = fsess;
default_cache_dir = NULL;
gf_mx_p( dm->cache_mx );
#ifdef GPAC_HAS_CURL
if (gf_opts_get_bool("core", "curl")) {
dm->curl_multi = curl_multi_init();
curl_multi_setopt(dm->curl_multi, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
dm->curl_mx = gf_mx_new("curl");
curl_global_init(CURL_GLOBAL_ALL);
}
#endif
dm->disable_http2 = gf_opts_get_bool("core", "no-h2");
#ifdef GPAC_HAS_NGTCP2
opt = gf_opts_get_key("core", "h3");
if (!opt || !strcmp(opt, "auto")) dm->h3_mode = H3_MODE_AUTO;
else if (!strcmp(opt, "first")) dm->h3_mode = H3_MODE_FIRST;
else if (!strcmp(opt, "only")) dm->h3_mode = H3_MODE_ONLY;
else dm->h3_mode = H3_MODE_NO;
#else
dm->h3_mode = H3_MODE_NO;
#endif
opt = gf_opts_get_key("core", "cache");
retry_cache:
if (!opt) {
default_cache_dir = gf_get_default_cache_directory();
opt = default_cache_dir;
}
if (opt[strlen(opt)-1] != GF_PATH_SEPARATOR) {
dm->cache_directory = (char *) gf_malloc(sizeof(char)* (strlen(opt)+2));
sprintf(dm->cache_directory, "%s%c", opt, GF_PATH_SEPARATOR);
} else {
dm->cache_directory = gf_strdup(opt);
}
//check cache exists
if (!default_cache_dir) {
FILE *test;
char szTemp[GF_MAX_PATH];
if (strlen(dm->cache_directory) + strlen("gpaccache.test") >= GF_ARRAY_LENGTH(szTemp)) {
GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[Cache] Invalid cache directory (path too long): %s\n", dm->cache_directory ));
gf_free(dm->cache_directory);
dm->cache_directory = NULL;
opt = NULL;
goto retry_cache;
}
gf_strcpy(szTemp, dm->cache_directory);
gf_strcat(szTemp, "gpaccache.test");
test = gf_fopen(szTemp, "wb");
if (!test) {
gf_mkdir(dm->cache_directory);
test = gf_fopen(szTemp, "wb");
if (!test) {
GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[Cache] Cannot write to %s directory, using system temp cache\n", dm->cache_directory ));
gf_free(dm->cache_directory);
dm->cache_directory = NULL;
opt = NULL;
goto retry_cache;
}
}
if (test) {
gf_fclose(test);
gf_file_delete(szTemp);
}
}
/*use it in in BYTES per second*/
dm->limit_data_rate = gf_opts_get_int("core", "maxrate") / 8;
dm->read_buf_size = GF_DOWNLOAD_BUFFER_SIZE;
//when rate is limited, use smaller smaller read size
if (dm->limit_data_rate) {
dm->read_buf_size = GF_DOWNLOAD_BUFFER_SIZE_LIMIT_RATE;
}
dm->disable_cache = gf_opts_get_bool("core", "no-cache");
dm->allow_offline_cache = gf_opts_get_bool("core", "offline-cache");
dm->allow_broken_certificate = GF_FALSE;
Bool do_clean = GF_TRUE;
if ( gf_opts_get_bool("core", "clean-cache")) {
dm->max_cache_size = 0;
dm->cur_cache_size = gf_cache_cleanup(dm->cache_directory, dm->max_cache_size);
do_clean = GF_FALSE;
}
dm->cache_clean_ms = 1000*gf_opts_get_int("core", "cache-check");
dm->max_cache_size = gf_opts_get_int("core", "cache-size");
dm->allow_broken_certificate = gf_opts_get_bool("core", "broken-cert");
gf_mx_v( dm->cache_mx );
#ifdef GPAC_HAS_SSL
dm->ssl_ctx = NULL;
#endif
if (dm->max_cache_size && do_clean)
dm->cur_cache_size = gf_cache_cleanup(dm->cache_directory, dm->max_cache_size);
return dm;
}
GF_EXPORT
void gf_dm_set_auth_callback(GF_DownloadManager *dm, gf_dm_get_usr_pass get_user_password, void *usr_cbk)
{
if (dm) {
dm->get_user_password = get_user_password;
dm->usr_cbk = usr_cbk;
}
}
GF_EXPORT
void gf_dm_del(GF_DownloadManager *dm)
{
if (!dm)
return;
gf_assert( dm->all_sessions);
gf_assert( dm->cache_mx );
gf_mx_p( dm->cache_mx );
while (gf_list_count(dm->partial_downloads)) {
GF_PartialDownload * entry = (GF_PartialDownload*)gf_list_get( dm->partial_downloads, 0);
gf_list_rem( dm->partial_downloads, 0);
gf_assert( entry->filename );
gf_file_delete( entry->filename );
gf_free(entry->filename );
entry->filename = NULL;
entry->url = NULL;
gf_free( entry );
}
/*destroy all pending sessions*/
while (gf_list_count(dm->all_sessions)) {
GF_DownloadSession *sess = (GF_DownloadSession *) gf_list_get(dm->all_sessions, 0);
gf_dm_sess_del(sess);
}
gf_list_del(dm->all_sessions);
dm->all_sessions = NULL;
gf_assert( dm->skip_proxy_servers );
while (gf_list_count(dm->skip_proxy_servers)) {
char *serv = (char*)gf_list_get(dm->skip_proxy_servers, 0);
gf_list_rem(dm->skip_proxy_servers, 0);
gf_free(serv);
}
gf_list_del(dm->skip_proxy_servers);
dm->skip_proxy_servers = NULL;
gf_assert( dm->credentials);
while (gf_list_count(dm->credentials)) {
GF_UserCredentials * cred = (GF_UserCredentials*)gf_list_get( dm->credentials, 0);
gf_list_rem( dm->credentials, 0);
gf_free( cred );
}
gf_list_del( dm->credentials);
dm->credentials = NULL;
gf_assert( dm->cache_entries );
/* Deletes DownloadedCacheEntry and associated files if required */
while (gf_list_count(dm->cache_entries)) {
const DownloadedCacheEntry entry = (const DownloadedCacheEntry)gf_list_pop_front( dm->cache_entries );
if (!dm->max_cache_size)
gf_cache_entry_set_delete_files_when_deleted(entry);
gf_cache_delete_entry(entry);
}
gf_list_del( dm->cache_entries );
dm->cache_entries = NULL;
gf_list_del( dm->partial_downloads );
dm->partial_downloads = NULL;
if (dm->cache_directory)
gf_free(dm->cache_directory);
dm->cache_directory = NULL;
#ifdef GPAC_HAS_CURL
if (dm->curl_multi) {
curl_multi_cleanup(dm->curl_multi);
}
gf_mx_del(dm->curl_mx);
#endif
#ifdef GPAC_HAS_SSL
if (dm->ssl_ctx) SSL_CTX_free(dm->ssl_ctx);
#endif
/* Stored elsewhere, no need to free */
gf_mx_v( dm->cache_mx );
gf_mx_del( dm->cache_mx);
dm->cache_mx = NULL;
gf_free(dm);
}
/*!
* Skip ICY metadata from SHOUTCAST or ICECAST streams.
* Data will be skipped and parsed and sent as a GF_NETIO_Parameter to the user_io,
* so modules interrested by those streams may use the data
\param sess The GF_DownloadSession
\param data last data received
\param nbBytes The number of bytes contained into data
*/
static void gf_icy_skip_data(GF_DownloadSession * sess, const char * data, u32 nbBytes)
{
u32 icy_metaint;
if (!sess || !data ) return;
icy_metaint = sess->icy_metaint;
gf_assert( icy_metaint > 0 );
while (nbBytes) {
if (sess->icy_bytes == icy_metaint) {
sess->icy_count = 1 + 16* (u8) data[0];
/*skip icy metadata*/
if (sess->icy_count > nbBytes) {
sess->icy_count -= nbBytes;
nbBytes = 0;
} else {
if (sess->icy_count > 1) {
GF_NETIO_Parameter par;
char szData[4096];
memset(szData, 0, 4096);
memcpy(szData, data+1, sess->icy_count-1);
szData[sess->icy_count] = 0;
par.error = GF_OK;
par.msg_type = GF_NETIO_ICY_META;
par.name = "icy-meta";
par.value = szData;
par.sess = sess;
GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[ICY] Found metainfo in stream=%s, (every %d bytes)\n", szData, icy_metaint));
gf_dm_sess_user_io(sess, &par);
} else {
GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[ICY] Empty metainfo in stream, (every %d bytes)\n", icy_metaint));
}
nbBytes -= sess->icy_count;
data += sess->icy_count;
sess->icy_count = 0;
sess->icy_bytes = 0;
}
} else {
GF_NETIO_Parameter par;
u32 left = icy_metaint - sess->icy_bytes;
if (left > nbBytes) {
left = nbBytes;
sess->icy_bytes += left;
nbBytes = 0;
} else {
sess->icy_bytes = icy_metaint;
nbBytes -= left;
}
par.msg_type = GF_NETIO_DATA_EXCHANGE;
par.data = data;
par.size = left;
gf_dm_sess_user_io(sess, &par);
data += left;
}
}
}
static char *gf_dm_get_chunk_data(GF_DownloadSession *sess, Bool first_chunk_in_payload, char *body_start, u32 *payload_size, u32 *header_size)
{
u32 size;
s32 res;
char *te_header, *sep;
if (!sess || !body_start) return NULL;
if (!sess->chunked) return body_start;
if (sess->nb_left_in_chunk) {
if (sess->nb_left_in_chunk > *payload_size) {
sess->nb_left_in_chunk -= (*payload_size);
GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[%s] Chunk encoding: still %d bytes to get\n", sess->log_name, sess->nb_left_in_chunk));
} else {
*payload_size = sess->nb_left_in_chunk;
sess->nb_left_in_chunk = 0;
GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[%s] Chunk encoding: last bytes in chunk received\n", sess->log_name));
}
*header_size = 0;
return body_start;
}
if (*payload_size == 2) {
*header_size = 0;
}
*header_size = 0;
/*skip remaining CRLF from previous chunk if any*/
if (*payload_size >= 2) {
if ((body_start[0]=='\r') && (body_start[1]=='\n')) {
body_start += 2;
*header_size = 2;
}
if (*payload_size <= 4) {
*header_size = 0;
return NULL;
}
te_header = strstr((char *) body_start, "\r\n");
} else {
//not enough bytes to read CRLF, don't bother parsing
te_header = NULL;
}
//cannot parse now, copy over the bytes
if (!te_header) {
*header_size = 0;
GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[%s] Chunk encoding: current buffer does not contain enough bytes (%d) to read the size\n", sess->log_name, *payload_size));
return NULL;
}
te_header[0] = 0;
*header_size += (u32) (strlen(body_start)) + 2;
sep = strchr(body_start, ';');
if (sep) sep[0] = 0;
res = sscanf(body_start, "%x", &size);
if (res<0 || size > GF_CHUNK_MAX_SIZE) {
te_header[0] = '\r';
if (sep) sep[0] = ';';
*header_size = 0;
*payload_size = 0;
GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[%s] Chunk encoding: fail to read chunk size from buffer %s, aborting\n", sess->log_name, body_start));
sess->last_error = GF_REMOTE_SERVICE_ERROR;
sess->status = GF_NETIO_STATE_ERROR;
return NULL;
}
if (sep) sep[0] = ';';
*payload_size = size;
GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[%s] Chunk Start: Header \"%s\" - header size %d - payload size %d (bytes done %d) at UTC "LLD"\n", sess->log_name, body_start, 2+strlen(body_start), size, sess->bytes_done, gf_net_get_utc()));
te_header[0] = '\r';
if (!size)
sess->last_chunk_found = GF_TRUE;
sess->current_chunk_size = size;
sess->current_chunk_start = gf_sys_clock_high_res();
return te_header+2;
}
static void dm_sess_update_download_rate(GF_DownloadSession * sess)
{
if (!sess->bytes_done) {
sess->bytes_per_sec = 0;
return;
}
//session is chunked and we have reached our first full window
if (sess->chunked && sess->cumulated_chunk_rate) {
/*use our cumulated weighted rate in bytes per seconds, and divide by total size*/
sess->bytes_per_sec = (u32) (sess->cumulated_chunk_rate / (sess->bytes_done + sess->cumulated_chunk_header_bytes) );
#ifndef GPAC_DISABLE_LOG
if (gf_log_tool_level_on(GF_LOG_HTTP, GF_LOG_DEBUG)) {
u64 runtime = (gf_sys_clock_high_res() - sess->request_start_time);
if (!runtime) runtime=1;
u32 kbps = (u32) ((1000000 * (u64) (sess->bytes_done + sess->cumulated_chunk_header_bytes)) / runtime) / 125;
GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[%s] bandwidth estimation: download time "LLD" us - bytes %u - chunk rate %u kbps (overall rate rate %u kbps)\n", sess->log_name, runtime, sess->bytes_done, sess->bytes_per_sec / 125, kbps));
}
#endif
} else {
/*compute bps starting from request send time*/
u64 runtime = (gf_sys_clock_high_res() - sess->request_start_time);
if (!runtime) runtime=1;
sess->bytes_per_sec = (u32) ((1000000 * (u64) sess->bytes_done) / runtime);
GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[%s] bandwidth estimation: download time "LLD" us - bytes %u - rate %u kbps\n", sess->log_name, runtime, sess->bytes_done, sess->bytes_per_sec / 125));
}
}
void gf_dm_data_received(GF_DownloadSession *sess, u8 *payload, u32 payload_size, Bool store_in_init, u32 *rewrite_size, u8 *original_payload)
{
u32 nbBytes, remaining, hdr_size;
u8* data = NULL;
Bool first_chunk_in_payload = GF_TRUE;
Bool flush_chunk = GF_FALSE;
GF_NETIO_Parameter par;
nbBytes = payload_size;
hdr_size = 0;
remaining = 0;
if (!payload)
return; //nothing to do
if (sess->chunked) {
data = (u8 *) gf_dm_get_chunk_data(sess, first_chunk_in_payload, (char *) payload, &nbBytes, &hdr_size);
if (nbBytes >= GF_UINT_MAX-hdr_size) {
GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[%s] invalid payload size received: %u\n", sess->log_name, nbBytes ));
return;
}
if (!hdr_size && !data && nbBytes) {
/* keep the data and wait for the rest */
sess->remaining_data_size = nbBytes;
sess->remaining_data = (char *)gf_realloc(sess->remaining_data, nbBytes * sizeof(char));
memcpy(sess->remaining_data, payload, nbBytes);
payload_size = 0;
payload = NULL;
} else if (hdr_size + nbBytes > payload_size) {
/* chunk header is processed but we will need several TCP frames to get the entire chunk*/
remaining = nbBytes + hdr_size - payload_size;
gf_assert(payload_size >= hdr_size);
nbBytes = payload_size - hdr_size;
payload_size = 0;
payload = NULL;
sess->chunk_header_bytes += hdr_size;
} else {
payload_size -= hdr_size + nbBytes;
payload += hdr_size + nbBytes;
flush_chunk = GF_TRUE;
sess->chunk_header_bytes += hdr_size;
}
/*chunk transfer is done*/
if (sess->last_chunk_found) {
sess->total_size = sess->bytes_done;
}
} else {
data = payload;
remaining = payload_size = 0;
}
if (data && nbBytes && store_in_init) {
sess->init_data = (char *) gf_realloc(sess->init_data , sizeof(char) * (sess->init_data_size + nbBytes) );
memcpy(sess->init_data+sess->init_data_size, data, nbBytes);
sess->init_data_size += nbBytes;
}
//we have some new bytes received
if (nbBytes && !sess->remaining_data_size) {
sess->bytes_done += nbBytes;
dm_sess_update_download_rate(sess);
GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[%s] url %s received %d new bytes (%d kbps)\n", sess->log_name, sess->orig_url, nbBytes, 8*sess->bytes_per_sec/1000));
if (sess->total_size && (sess->bytes_done > sess->total_size)) {
GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[%s] url %s received more bytes than planned!! Got %d bytes vs %d content length\n", sess->log_name, sess->orig_url, sess->bytes_done , sess->total_size ));
sess->bytes_done = sess->total_size;
}
if (sess->icy_metaint > 0)
gf_icy_skip_data(sess, (char *) data, nbBytes);
else {
if (sess->use_cache_file && !sess->cached_file)
gf_cache_write_to_cache( sess->cache_entry, sess, (char *) data, nbBytes, sess->dm ? sess->dm->cache_mx : NULL);
par.msg_type = GF_NETIO_DATA_EXCHANGE;
par.error = GF_OK;
par.data = (char *) data;
par.size = nbBytes;
par.reply = flush_chunk;
gf_dm_sess_user_io(sess, &par);
}
}
//and we're done
if (sess->total_size && (sess->bytes_done == sess->total_size)) {
u64 run_time;
#ifdef GPAC_HTTPMUX
if (sess->hmux_sess && !sess->server_mode)
sess->hmux_stream_id = -1;
#endif
if (sess->use_cache_file) {
//for chunk transfer or H2/H3
gf_cache_set_content_length(sess->cache_entry, sess->total_size);
gf_cache_close_write_cache(sess->cache_entry, sess, GF_TRUE);
GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE,
("[CACHE] url %s saved as %s\n", gf_cache_get_url(sess->cache_entry), gf_cache_get_cache_filename(sess->cache_entry)));
if (sess->dm && sess->dm->cache_clean_ms) {
sess->dm->cur_cache_size += sess->total_size;
if (sess->dm->cur_cache_size > sess->dm->max_cache_size) {
if (!sess->dm->next_cache_clean) sess->dm->next_cache_clean = gf_sys_clock()+sess->dm->cache_clean_ms;
else if (gf_sys_clock()>sess->dm->next_cache_clean) {
sess->dm->cur_cache_size = gf_cache_cleanup(sess->dm->cache_directory, sess->dm->max_cache_size);
sess->dm->next_cache_clean = 0;
}
}
}
}
gf_dm_disconnect(sess, HTTP_NO_CLOSE);
par.msg_type = GF_NETIO_DATA_TRANSFERED;
par.error = GF_OK;
gf_dm_sess_user_io(sess, &par);
sess->total_time_since_req = (u32) (gf_sys_clock_high_res() - sess->request_start_time);
run_time = gf_sys_clock_high_res() - sess->start_time;
GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[%s] %s (%d bytes) downloaded in "LLU" us (%d kbps) (%d us since request - got response in %d us)\n", sess->log_name, gf_file_basename(gf_cache_get_url(sess->cache_entry)), sess->bytes_done,
run_time, 8*sess->bytes_per_sec/1000, sess->total_time_since_req, sess->reply_time));
if (sess->chunked && (payload_size==2))
payload_size=0;
sess->last_error = GF_OK;
}
if (rewrite_size && sess->chunked && data) {
if (original_payload) {
//use memmove since regions overlap
memmove(original_payload + *rewrite_size, data, nbBytes);
}
*rewrite_size += nbBytes;
}
if (!sess->nb_left_in_chunk && remaining) {
sess->nb_left_in_chunk = remaining;
} else if (payload_size && (sess->status < GF_NETIO_DISCONNECTED)) {
gf_dm_data_received(sess, payload, payload_size, store_in_init, rewrite_size, original_payload);
}
}
static Bool dm_exceeds_cap_rate(GF_DownloadManager * dm, GF_DownloadSession *for_sess)
{
u32 cumul_rate = 0;
u32 i, count;
gf_mx_p(dm->cache_mx);
u64 now = gf_sys_clock_high_res();
count = gf_list_count(dm->all_sessions);
//check if this fits with all other sessions
for (i=0; i<count; i++) {
GF_DownloadSession * sess = (GF_DownloadSession*)gf_list_get(dm->all_sessions, i);
if (for_sess && (sess != for_sess)) continue;
//session not running done
if (sess->status != GF_NETIO_DATA_EXCHANGE) continue;
//compute average rate on a window of 200 ms
//we cannot just use sess->bytes_per_sec because the rate limit might be changed dynamically
//so we need a recent history, not the session history
//note that we don't try to use the estimated bps of chunk transfer when capping
if (!sess->last_cap_rate_time) {
u64 runtime;
sess->last_cap_rate_time = sess->request_start_time;
sess->last_cap_rate_bytes = sess->bytes_done;
/*compute bps starting from request send time, do not call update_download_rate as we don't want the chunk transfer rate*/
runtime = (gf_sys_clock_high_res() - sess->request_start_time);
if (!runtime) runtime=1;
sess->last_cap_rate_bytes_per_sec = (u32) ((1000000 * (u64) sess->bytes_done) / runtime);
} else if (now > sess->last_cap_rate_time) {
u64 time = now - sess->last_cap_rate_time;
u64 bytes = sess->bytes_done - sess->last_cap_rate_bytes;
sess->last_cap_rate_bytes_per_sec = (u32) ((1000000 * (u64) bytes) / time);
if (time > 200000) {
//this is an approximation we don't know precisely when these were received
//and we don't really care since next rate estimation will be really high anyway and will exceed cap
sess->last_cap_rate_bytes = sess->bytes_done;
sess->last_cap_rate_time = now;
}
} else {
gf_mx_v(dm->cache_mx);
return GF_TRUE;
}
cumul_rate += sess->last_cap_rate_bytes_per_sec;
if (for_sess) break;
}
gf_mx_v(dm->cache_mx);
if (for_sess) {
if (cumul_rate >= for_sess->max_data_rate)
return GF_TRUE;
} else if ( cumul_rate >= dm->limit_data_rate) {
return GF_TRUE;
}
return GF_FALSE;
}
static void gf_dm_sess_estimate_chunk_rate(GF_DownloadSession *sess, u32 nb_bytes)
{
u64 now = gf_sys_clock_high_res();
sess->chunk_bytes += nb_bytes;
if ((now > sess->last_chunk_start_time + sess->chunk_wnd_dur) || (sess->total_size==sess->bytes_done) ) {
if (sess->chunk_bytes) {
u32 tot_bytes = sess->chunk_bytes + sess->chunk_header_bytes;
//compute rate in bytes per seconds
Double rate = 1000000.0 * tot_bytes;
rate /= (now - sess->last_chunk_start_time);
//cumulated rate is the weighted sum of our probe rates, the weight being the number of bytes
//when comuting the bitrate, we will divide by the total size
sess->cumulated_chunk_rate += rate * tot_bytes;
sess->chunk_bytes = 0;
sess->cumulated_chunk_header_bytes += sess->chunk_header_bytes;
sess->chunk_header_bytes = 0;
//we are done, update rate
if (sess->total_size==sess->bytes_done)
dm_sess_update_download_rate(sess);
}
sess->last_chunk_start_time = now;
}
}
GF_EXPORT
GF_Err gf_dm_sess_fetch_data(GF_DownloadSession *sess, char *buffer, u32 buffer_size, u32 *read_size)
{
u32 size;
GF_Err e;
if (!buffer || !buffer_size) {
if (sess->put_state) {
sess->put_state = 2;
sess->status = GF_NETIO_WAIT_FOR_REPLY;
return GF_OK;
}
return GF_BAD_PARAM;
}
if (sess->th)
return GF_BAD_PARAM;
if (sess->status == GF_NETIO_DISCONNECTED) {
if (!sess->init_data_size)
return GF_EOS;
}
else if (sess->status == GF_NETIO_STATE_ERROR) {
return sess->last_error;
}
else if (sess->status > GF_NETIO_DATA_TRANSFERED)
return GF_BAD_PARAM;
*read_size = 0;
if (sess->status == GF_NETIO_DATA_TRANSFERED) {
if (!sess->server_mode)
return GF_EOS;
if (!sess->init_data_size && sess->total_size && (sess->total_size==sess->bytes_done))
return GF_EOS;
sess->status = GF_NETIO_DATA_EXCHANGE;
}
if (sess->status == GF_NETIO_SETUP) {
gf_dm_connect(sess);
if (sess->last_error)
return sess->last_error;
e = GF_IP_NETWORK_EMPTY;
} else if (sess->status < GF_NETIO_DATA_EXCHANGE) {
sess->do_requests(sess);
if (!sess->server_mode
&& (sess->status > GF_NETIO_WAIT_FOR_REPLY)
&& sess->needs_range
&& (sess->rsp_code==200)
) {
//reset for next call to process if user wants so
sess->needs_range = GF_FALSE;
return GF_IO_BYTE_RANGE_NOT_SUPPORTED;
}
e = sess->last_error ? sess->last_error : GF_IP_NETWORK_EMPTY;
}
/*we're running but we had data previously*/
else if (sess->init_data) {
e = GF_OK;
if (sess->init_data_size<=buffer_size) {
memcpy(buffer, sess->init_data, sizeof(char)*sess->init_data_size);
*read_size = sess->init_data_size;
gf_free(sess->init_data);
sess->init_data = NULL;
if (sess->init_data_size==sess->total_size)
e = GF_EOS;
sess->init_data_size = 0;
} else {
memcpy(buffer, sess->init_data, sizeof(char)*buffer_size);
*read_size = buffer_size;
sess->init_data_size -= buffer_size;
memmove(sess->init_data, sess->init_data+buffer_size, sizeof(char)*sess->init_data_size);
e = GF_OK;
}
} else if (sess->local_cache_only) {
Bool was_modified;
u32 to_copy, full_cache_size, max_valid_size=0, cache_done;
const u8 *ptr;
e = GF_OK;
gf_assert(sess->cache_entry);
//always refresh total size
sess->total_size = gf_cache_get_content_length(sess->cache_entry);
ptr = gf_cache_get_content(sess->cache_entry, &full_cache_size, &max_valid_size, &was_modified);
cache_done = gf_cache_is_done(sess->cache_entry);
//something went wrong, we cannot have less valid bytes than what we had at previous call(s)
if (max_valid_size < sess->bytes_done)
cache_done = 2;
if ((sess->bytes_done >= full_cache_size)|| (cache_done==2)) {
*read_size = 0;
gf_cache_release_content(sess->cache_entry);
if (cache_done==2) {
sess->status = GF_NETIO_STATE_ERROR;
SET_LAST_ERR(GF_IP_NETWORK_FAILURE)
return GF_IP_NETWORK_FAILURE;
}
else if (cache_done) {
sess->status = GF_NETIO_DATA_TRANSFERED;
SET_LAST_ERR( GF_OK)
return GF_EOS;
}
return was_modified ? GF_OK : GF_IP_NETWORK_EMPTY;
}
if (!ptr) return GF_OUT_OF_MEM;
//only copy valid bytes for http
to_copy = max_valid_size - sess->bytes_done;
if (to_copy > buffer_size) to_copy = buffer_size;
memcpy(buffer, ptr + sess->bytes_done, to_copy);
sess->bytes_done += to_copy;
*read_size = to_copy;
if ((cache_done==1) && (sess->bytes_done == sess->total_size) ) {
sess->status = GF_NETIO_DATA_TRANSFERED;
SET_LAST_ERR( (cache_done==2) ? GF_IP_NETWORK_FAILURE : GF_OK)
} else {
sess->total_size = 0;
}
gf_cache_release_content(sess->cache_entry);
} else {
if (sess->dm && (sess->dm->limit_data_rate || sess->max_data_rate)) {
if (dm_exceeds_cap_rate(sess->dm, sess->max_data_rate ? sess : NULL))
return GF_IP_NETWORK_EMPTY;
if (buffer_size > sess->dm->read_buf_size)
buffer_size = sess->dm->read_buf_size;
}
e = GF_OK;
*read_size = 0;
u32 nb_read = 0;
//perform a loop, mostly for chunk-tranfer mode where a server may push a lot of small TCP frames,
//we want to flush everything as fast as possible
while (1) {
u32 single_read = 0;
if (sess->remaining_data && sess->remaining_data_size) {
if (nb_read + sess->remaining_data_size >= buffer_size) {
if (!nb_read) {
GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[%s] No HTTP chunk header found for %d bytes, assuming broken chunk transfer and aborting\n", sess->log_name, sess->remaining_data_size));
return GF_NON_COMPLIANT_BITSTREAM;
}
break;
}
memcpy(buffer + nb_read, sess->remaining_data, sess->remaining_data_size);
} else if (nb_read >= buffer_size) {
break;
}
e = gf_dm_read_data(sess, buffer + nb_read + sess->remaining_data_size, buffer_size - sess->remaining_data_size - nb_read, &single_read);
if (e<0) {
gf_assert(single_read==0);
break;
}
size = sess->remaining_data_size + single_read;
sess->remaining_data_size = 0;
single_read = 0;
#ifdef GPAC_HTTPMUX
//do NOT call data received if hmux, done once input is empty
if (!sess->hmux_sess || sess->cached_file)
#endif
gf_dm_data_received(sess, (u8 *) buffer + nb_read, size, GF_FALSE, &single_read, buffer + nb_read);
if (!sess->chunked)
single_read = size;
nb_read += single_read;
}
#ifdef GPAC_HTTPMUX
//process any received data
if (sess->hmux_sess) {
nb_read = 0;
hmux_fetch_data(sess, buffer, buffer_size, &nb_read);
sess->hmux_sess->write(sess);
//stream is over and all data flushed, move to GF_NETIO_DATA_TRANSFERED in client mode
if ((sess->hmux_stream_id<0) && sess->hmux_data_done && !sess->hmux_buf.size && !sess->server_mode) {
sess->status = GF_NETIO_DATA_TRANSFERED;
SET_LAST_ERR(GF_OK)
}
}
#endif
*read_size = nb_read;
//we had data but last call to gf_dm_read_data may have returned network empty
if (nb_read && (e<0))
e = GF_OK;
//estimate rate for chunk-transfer - we only do that for fetch_data
if (sess->chunked
#ifdef GPAC_HTTPMUX
|| sess->hmux_sess
#endif
)
gf_dm_sess_estimate_chunk_rate(sess, nb_read);
if (! (*read_size) && (e==GF_IP_NETWORK_EMPTY)) {
#ifdef GPAC_HTTPMUX
if (sess->hmux_sess && !sess->total_size
//for client, wait for close - for server move to data_transfered as soon as we're done pushing data
&& (((sess->hmux_stream_id<0) && sess->bytes_done) || (sess->hmux_data_done && sess->server_mode))
) {
sess->status = GF_NETIO_DATA_TRANSFERED;
SET_LAST_ERR(GF_OK)
return GF_EOS;
}
#endif
#ifdef GPAC_HAS_CURL
if (sess->curl_hnd) {
if (sess->status == GF_NETIO_WAIT_FOR_REPLY)
return GF_IP_NETWORK_EMPTY;
} else
#endif
if (sess->sock)
e = gf_sk_probe(sess->sock);
if ((e==GF_IP_CONNECTION_CLOSED)
|| (sess->request_timeout && (gf_sys_clock_high_res() - sess->last_fetch_time > 1000 * sess->request_timeout))
) {
if (e==GF_IP_CONNECTION_CLOSED) {
SET_LAST_ERR(GF_IP_CONNECTION_CLOSED)
sess_connection_closed(sess);
} else {
GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP,
("[%s] Session timeout for %s after %u ms, aborting\n", sess->log_name, sess->orig_url, sess->request_timeout));
SET_LAST_ERR(GF_IP_NETWORK_FAILURE)
}
sess->status = GF_NETIO_STATE_ERROR;
return GF_IP_NETWORK_EMPTY;
}
}
}
#ifdef GPAC_HTTPMUX
if (sess->hmux_sess && sess->hmux_buf.size) {
return e;
}
#endif
if (sess->server_mode && (sess->status == GF_NETIO_DATA_EXCHANGE)) {
sess->status = GF_NETIO_DATA_TRANSFERED;
SET_LAST_ERR(GF_OK)
}
return e;
}
GF_EXPORT
GF_Err gf_dm_sess_get_stats(GF_DownloadSession * sess, const char **server, const char **path, u64 *total_size, u64 *bytes_done, u32 *bytes_per_sec, GF_NetIOStatus *net_status)
{
if (!sess)
return GF_BAD_PARAM;
if (server) *server = sess->server_name;
if (path) *path = sess->remote_path;
if (total_size) {
if (sess->total_size==SIZE_IN_STREAM) *total_size = 0;
else *total_size = sess->total_size;
}
if (bytes_done) *bytes_done = sess->bytes_done;
if (bytes_per_sec) {
if (sess->dm && (sess->dm->limit_data_rate || sess->max_data_rate) && sess->last_cap_rate_bytes_per_sec) {
*bytes_per_sec = sess->last_cap_rate_bytes_per_sec;
} else {
*bytes_per_sec = sess->bytes_per_sec;
}
}
if (net_status) *net_status = sess->status;
if (sess->status == GF_NETIO_DISCONNECTED) {
if (sess->last_error) return sess->last_error;
return GF_EOS;
}
else if (sess->status == GF_NETIO_STATE_ERROR)
return sess->last_error ? sess->last_error : GF_SERVICE_ERROR;
return GF_OK;
}
GF_EXPORT
u64 gf_dm_sess_get_utc_start(GF_DownloadSession * sess)
{
if (!sess) return 0;
return sess->start_time_utc;
}
GF_EXPORT
const char *gf_dm_sess_get_cache_name(GF_DownloadSession * sess)
{
if (!sess) return NULL;
if (! sess->cache_entry || sess->needs_cache_reconfig) return NULL;
if (!sess->use_cache_file) return NULL;
return gf_cache_get_cache_filename(sess->cache_entry);
}
GF_EXPORT
void gf_dm_sess_abort(GF_DownloadSession * sess)
{
if (sess) {
gf_mx_p(sess->mx);
#ifdef GPAC_HTTPMUX
if (sess->hmux_sess && (sess->status==GF_NETIO_DATA_EXCHANGE)) {
sess->hmux_sess->stream_reset(sess, GF_TRUE);
sess->hmux_sess->write(sess);
}
#endif
gf_dm_disconnect(sess, HTTP_CLOSE);
//not an error, move to DISCONNECTED state
sess->status = GF_NETIO_DISCONNECTED;
SET_LAST_ERR(GF_IP_CONNECTION_CLOSED)
gf_mx_v(sess->mx);
}
}
/*!
* Sends the HTTP headers
\param sess The GF_DownloadSession
\return GF_OK if everything went fine, the error otherwise
*/
static GF_Err http_send_headers(GF_DownloadSession *sess) {
GF_Err e;
GF_NETIO_Parameter par;
Bool no_cache = GF_FALSE;
char range_buf[1024];
char pass_buf[1124];
char req_name[20];
const char *user_agent;
const char *url;
const char *user_profile;
const char *param_string;
Bool inject_icy = GF_FALSE;
u32 i, count;
GF_HTTPHeader *hdr;
Bool has_accept, has_connection, has_range, has_agent, has_language, send_profile, has_mime, has_chunk_transfer;
assert (sess->status == GF_NETIO_CONNECTED);
char * sHTTP = sess->http_buf;
gf_assert(sess->remaining_data_size == 0);
if (sess->needs_cache_reconfig) {
gf_dm_sess_clear_headers(sess);
gf_dm_configure_cache(sess);
sess->needs_cache_reconfig = 0;
}
if (sess->cached_file) {
sess->last_fetch_time = sess->request_start_time = gf_sys_clock_high_res();
sess->req_hdr_size = 0;
sess->status = GF_NETIO_WAIT_FOR_REPLY;
gf_dm_sess_notify_state(sess, GF_NETIO_WAIT_FOR_REPLY, GF_OK);
return GF_OK;
}
gf_dm_sess_clear_headers(sess);
//in case we got disconnected, reconnect
#ifdef GPAC_HAS_CURL
if (sess->curl_hnd)
e = GF_OK;
else
#endif
e = gf_sk_probe(sess->sock);
if (e && (e!=GF_IP_NETWORK_EMPTY)) {
sess_connection_closed(sess);
if ((e==GF_IP_CONNECTION_CLOSED) && sess->num_retry) {
sess->num_retry--;
sess->status = GF_NETIO_SETUP;
return GF_OK;
}
sess->status = GF_NETIO_STATE_ERROR;
SET_LAST_ERR(e)
gf_dm_sess_notify_state(sess, GF_NETIO_STATE_ERROR, e);
return e;
}
/*setup authentication*/
gf_strcpy(pass_buf, "");
sess->creds = gf_user_credentials_find_for_site( sess->dm, sess->server_name, NULL);
if (sess->creds && sess->creds->valid) {
#ifdef GPAC_HAS_CURL
//let curl handle authentication methods
if (sess->curl_hnd) {
char szUsrPass[101];
u32 len = gf_base64_decode(sess->creds->digest, (u32) strlen(sess->creds->digest), szUsrPass, 100);
szUsrPass[len]=0;
curl_easy_setopt(sess->curl_hnd, CURLOPT_USERPWD, szUsrPass);
} else
#endif
sprintf(pass_buf, "Basic %s", sess->creds->digest);
}
user_agent = gf_opts_get_key("core", "ua");
if (!user_agent) user_agent = GF_DOWNLOAD_AGENT_NAME;
sess->put_state = 0;
par.error = GF_OK;
par.msg_type = GF_NETIO_GET_METHOD;
par.name = NULL;
gf_dm_sess_user_io(sess, &par);
if (!par.name || sess->server_only_understand_get) {
par.name = "GET";
}
gf_strcpy(req_name, par.name);
if (!strcmp(req_name, "GET")) {
sess->http_read_type = GET;
#ifdef GPAC_HTTPMUX
if (!sess->hmux_sess)
#endif
inject_icy = GF_TRUE;
} else if (!strcmp(req_name, "HEAD")) sess->http_read_type = HEAD;
else sess->http_read_type = OTHER;
if (!strcmp(req_name, "PUT") || !strcmp(req_name, "POST"))
sess->put_state = 1;
//url is the remote path event if proxy (Host header will point to desired host)
//note that url is not used for CURL, already setup together with proxy
url = sess->remote_path;
/*get all headers*/
gf_dm_sess_clear_headers(sess);
#ifdef GPAC_HTTPMUX
if (!sess->hmux_sess)
#endif
{
//always put port number when proxy is enabled
if (sess->proxy_enabled || ((sess->port!=80) && (sess->port!=443))) {
sprintf(sHTTP, "%s:%u", sess->server_name, sess->port);
PUSH_HDR("Host", sHTTP);
} else {
PUSH_HDR("Host", sess->server_name);
}
}
has_agent = has_accept = has_connection = has_range = has_language = has_mime = has_chunk_transfer = GF_FALSE;
while (1) {
par.msg_type = GF_NETIO_GET_HEADER;
par.value = NULL;
gf_dm_sess_user_io(sess, &par);
if (!par.value) break;
//if name is not set, skip this header
if (!par.name) continue;
if (!stricmp(par.name, "Connection")) {
if (!stricmp(par.value, "close"))
has_connection = GF_TRUE;
else
continue;
}
else if (!stricmp(par.name, "Transfer-Encoding")) {
if (!stricmp(par.value, "chunked"))
has_chunk_transfer = GF_TRUE;
continue;
}
gf_dm_sess_set_header_ex(sess, par.name, par.value, GF_TRUE);
if (!stricmp(par.name, "Accept")) has_accept = GF_TRUE;
else if (!stricmp(par.name, "Range")) has_range = GF_TRUE;
else if (!stricmp(par.name, "User-Agent")) has_agent = GF_TRUE;
else if (!stricmp(par.name, "Accept-Language")) has_language = GF_TRUE;
else if (!stricmp(par.name, "Content-Type")) has_mime = GF_TRUE;
if (!par.msg_type) break;
}
if (!has_agent) PUSH_HDR("User-Agent", user_agent)
/*no mime and POST/PUT, default to octet stream*/
if (!has_mime && (sess->http_read_type==OTHER)) PUSH_HDR("Content-Type", "application/octet-stream")
if (!has_accept && (sess->http_read_type!=OTHER) ) PUSH_HDR("Accept", "*/*")
#ifdef GPAC_HTTPMUX
if (sess->hmux_sess)
has_connection = GF_TRUE;
#endif
#ifdef GPAC_HAS_HTTP2
//inject upgrade for h2
if (!has_connection && !sess->hmux_sess
#ifdef GPAC_HAS_CURL
&& !sess->curl_hnd
#endif
#ifdef GPAC_HAS_SSL
&& !sess->ssl
#endif
&& !sess->dm->disable_http2
&& !sess->h2_upgrade_state
&& !gf_opts_get_bool("core", "no-h2c")
) {
http2_set_upgrade_headers(sess);
inject_icy = GF_FALSE;
} else
#endif //GPAC_HAS_HTTP2
if (sess->proxy_enabled==1) {
#ifdef GPAC_HTTPMUX
if (!sess->hmux_sess)
#endif
PUSH_HDR("Proxy-Connection", "Keep-alive")
}
else if (!has_connection) {
PUSH_HDR("Connection", "Keep-Alive");
}
if (has_chunk_transfer
#ifdef GPAC_HTTPMUX
&& !sess->hmux_sess
#endif
) {
PUSH_HDR("Transfer-Encoding", "chunked");
sess->chunked = GF_TRUE;
}
if (!has_range && sess->needs_range) {
if (!sess->range_end)
sprintf(range_buf, "bytes="LLD"-", sess->range_start);
//if end is set to -1 use open end
else if (sess->range_end==(u64)-1)
sprintf(range_buf, "bytes="LLD"-", sess->range_start);
else
sprintf(range_buf, "bytes="LLD"-"LLD"", sess->range_start, sess->range_end);
PUSH_HDR("Range", range_buf)
no_cache = GF_TRUE;
}
if (!has_language) {
const char *opt = gf_opts_get_key("core", "lang");
if (opt) PUSH_HDR("Accept-Language", opt)
}
if (strlen(pass_buf)) {
PUSH_HDR("Authorization", pass_buf)
}
par.msg_type = GF_NETIO_GET_CONTENT;
par.data = NULL;
par.size = 0;
/*check if we have personalization info*/
send_profile = GF_FALSE;
user_profile = gf_opts_get_key("core", "user-profileid");
if (user_profile) {
PUSH_HDR("X-UserProfileID", user_profile);
} else if ((sess->http_read_type == GET) || (sess->http_read_type == HEAD) ) {
user_profile = gf_opts_get_key("core", "user-profile");
if (user_profile && gf_file_exists(user_profile)) {
FILE *profile = gf_fopen(user_profile, "rb");
if (profile) {
par.size = (u32) gf_fsize(profile);
gf_fclose(profile);
sprintf(range_buf, "%d", par.size);
PUSH_HDR("Content-Length", range_buf);
PUSH_HDR("Content-Type", "text/xml");
send_profile = GF_TRUE;
}
}
}
if (!send_profile) {
gf_dm_sess_user_io(sess, &par);
if (par.data && par.size) {
sprintf(range_buf, "%d", par.size);
PUSH_HDR("Content-Length", range_buf);
} else {
par.data = NULL;
par.size = 0;
}
}
if (inject_icy) {
/* This will force the server to respond with Icy-Metaint */
PUSH_HDR("Icy-Metadata", "1");
}
if (sess->http_read_type!=OTHER) {
const char *etag=NULL, *last_modif=NULL;
/*cached headers are not appended in POST*/
if (!no_cache && !sess->disable_cache && (GF_OK < gf_cache_get_http_headers( sess->cache_entry, &etag, &last_modif)) ) {
GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("Cache Entry : %p, FAILED to append cache directives.", sess->cache_entry));
}
if (etag) PUSH_HDR("If-None-Match", etag)
if (last_modif) PUSH_HDR("If-Modified-Since", last_modif)
}
//done gathering headers
param_string = gf_opts_get_key("core", "query-string");
#ifdef GPAC_HTTPMUX
if (sess->hmux_sess) {
Bool has_body = GF_FALSE;
gf_mx_p(sess->mx);
sess->hmux_is_eos = 0;
sess->hmux_send_data = NULL;
if (par.data && par.size) {
has_body = GF_TRUE;
sess->hmux_send_data = (u8 *) par.data;
sess->hmux_send_data_len = par.size;
sess->hmux_is_eos = 1;
} else if (sess->put_state==1) {
has_body = GF_TRUE;
}
e = sess->hmux_sess->submit_request(sess, req_name, url, param_string, has_body);
GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTP] Sending request %s %s:%u%s\n", req_name, sess->server_name, sess->port, url));
sess->last_fetch_time = sess->request_start_time = gf_sys_clock_high_res();
if (!e || (e==GF_IP_NETWORK_EMPTY)) {
e = sess->hmux_sess->send_pending_data(sess);
if (e==GF_IP_NETWORK_EMPTY) e = GF_OK;
}
gf_mx_v(sess->mx);
goto req_sent;
}
#endif // GPAC_HTTPMUX
#ifdef GPAC_HAS_CURL
if (sess->curl_hnd) {
if (!sess->curl_not_http)
curl_easy_setopt(sess->curl_hnd, CURLOPT_CUSTOMREQUEST, req_name);
} else
#endif
{
if (param_string) {
if (strchr(sess->remote_path, '?')) {
sprintf(sHTTP, "%s %s&%s HTTP/1.1\r\n", req_name, url, param_string);
} else {
sprintf(sHTTP, "%s %s?%s HTTP/1.1\r\n", req_name, url, param_string);
}
} else {
sprintf(sHTTP, "%s %s HTTP/1.1\r\n", req_name, url);
}
}
//serialize headers
count = gf_list_count(sess->headers);
for (i=0; i<count; i++) {
hdr = gf_list_get(sess->headers, i);
#ifdef GPAC_HAS_CURL
if (sess->curl_hnd) {
if (sess->curl_not_http) continue;
char szHDR[1000];
sprintf(szHDR, "%s: %s", hdr->name, hdr->value);
sess->curl_hdrs = curl_slist_append(sess->curl_hdrs, szHDR);
continue;
}
#endif
gf_strlcat(sHTTP, hdr->name, sess->http_buf_size);
gf_strlcat(sHTTP, ": ", sess->http_buf_size);
gf_strlcat(sHTTP, hdr->value, sess->http_buf_size);
gf_strlcat(sHTTP, "\r\n", sess->http_buf_size);
}
#ifdef GPAC_HAS_CURL
if (sess->curl_hnd) {
if (!sess->curl_not_http) {
curl_easy_setopt(sess->curl_hnd, CURLOPT_HTTPHEADER, sess->curl_hdrs);
}
}
#endif
gf_strlcat(sHTTP, "\r\n", sess->http_buf_size);
#ifdef GPAC_HAS_CURL
if (sess->curl_hnd) {
gf_assert(!sess->curl_hnd_registered);
} else
#endif
if (send_profile || par.data) {
u32 len = (u32) strlen(sHTTP);
char *tmp_buf = (char*)gf_malloc(sizeof(char)*(len+par.size+1));
memcpy(tmp_buf, sHTTP, len+1);
if (par.data) {
memcpy(tmp_buf+len, par.data, par.size);
tmp_buf[len+par.size] = 0;
sess->put_state = 2;
} else {
FILE *profile;
user_profile = gf_opts_get_key("core", "user-profile");
assert (user_profile);
profile = gf_fopen(user_profile, "rt");
if (profile) {
s32 read = (s32) gf_fread(tmp_buf+len, par.size, profile);
if ((read<0) || (read< (s32) par.size)) {
GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP,
("[%s] Error while loading UserProfile, size=%d, should be %d\n", sess->log_name, read, par.size));
for (; read < (s32) par.size; read++) {
tmp_buf[len + read] = 0;
}
}
gf_fclose(profile);
} else {
GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[%s] Error while loading Profile file %s\n", sess->log_name, user_profile));
}
}
sess->last_fetch_time = sess->request_start_time = gf_sys_clock_high_res();
sess->req_hdr_size = len+par.size;
e = dm_sess_write(sess, tmp_buf, len+par.size);
GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[%s] Sending request to %s %s\n\n", sess->log_name, sess->server_name, tmp_buf));
gf_free(tmp_buf);
} else {
u32 len = (u32) strlen(sHTTP);
sess->last_fetch_time = sess->request_start_time = gf_sys_clock_high_res();
sess->req_hdr_size = len;
e = dm_sess_write(sess, sHTTP, len);
#ifndef GPAC_DISABLE_LOG
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[%s] Error sending request %s\n", sess->log_name, gf_error_to_string(e) ));
} else {
GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[%s] Sending request to %s %s\n\n", sess->log_name, sess->server_name, sHTTP));
}
#endif
}
#ifdef GPAC_HTTPMUX
req_sent:
#endif
gf_dm_sess_clear_headers(sess);
if (e) {
SET_LAST_ERR(e)
sess->status = GF_NETIO_STATE_ERROR;
gf_dm_sess_notify_state(sess, GF_NETIO_STATE_ERROR, e);
return e;
}
gf_dm_sess_notify_state(sess, GF_NETIO_WAIT_FOR_REPLY, GF_OK);
if (sess->put_state==1) {
sess->status = GF_NETIO_DATA_TRANSFERED;
} else {
sess->status = GF_NETIO_WAIT_FOR_REPLY;
}
SET_LAST_ERR(GF_OK)
#ifdef GPAC_HAS_CURL
if (sess->curl_hnd) {
sess->last_fetch_time = sess->request_start_time = gf_sys_clock_high_res();
curl_flush(sess);
}
#endif
return GF_OK;
}
/*!
* Parse the remaining part of body
\param sess The session
\return The error code if any
*/
static GF_Err http_parse_remaining_body(GF_DownloadSession * sess)
{
GF_Err e;
while (1) {
u32 prev_remaining_data_size, size=0, rewrite_size=0;
if (sess->status>=GF_NETIO_DISCONNECTED)
return GF_REMOTE_SERVICE_ERROR;
if (sess->dm && sess->bytes_per_sec && (sess->max_data_rate || sess->dm->limit_data_rate)) {
sess->rate_regulated = GF_FALSE;
if (dm_exceeds_cap_rate(sess->dm, sess->max_data_rate ? sess : NULL)) {
sess->rate_regulated = GF_TRUE;
return GF_OK;
}
}
//the data remaining from the last buffer (i.e size for chunk that couldn't be read because the buffer does not contain enough bytes)
if (sess->remaining_data && sess->remaining_data_size) {
if (sess->remaining_data_size >= sess->http_buf_size) {
GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[%s] No HTTP chunk header found for %d bytes, assuming broken chunk transfer and aborting\n", sess->log_name, sess->remaining_data_size));
return GF_NON_COMPLIANT_BITSTREAM;
}
memcpy(sess->http_buf, sess->remaining_data, sess->remaining_data_size);
}
e = gf_dm_read_data(sess, sess->http_buf + sess->remaining_data_size, sess->http_buf_size - sess->remaining_data_size, &size);
if ((e != GF_IP_CONNECTION_CLOSED) && (!size || e == GF_IP_NETWORK_EMPTY)) {
if (!sess->total_size && !sess->chunked && (gf_sys_clock_high_res() - sess->start_time > 5000000)) {
sess->total_size = sess->bytes_done;
gf_dm_sess_notify_state(sess, GF_NETIO_DATA_TRANSFERED, GF_OK);
gf_assert(sess->server_name);
GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[%s] Disconnected from %s: %s\n", sess->log_name, sess->server_name, gf_error_to_string(e)));
gf_dm_disconnect(sess, HTTP_NO_CLOSE);
}
return GF_OK;
}
if (e) {
if (sess->sock && (e == GF_IP_CONNECTION_CLOSED)) {
u32 len = gf_cache_get_content_length(sess->cache_entry);
if (size > 0) {
#ifdef GPAC_HTTPMUX
if (sess->hmux_sess) {
hmux_flush_internal_data(sess, GF_FALSE);
} else
#endif
gf_dm_data_received(sess, (u8 *) sess->http_buf, size, GF_FALSE, NULL, NULL);
}
if ( ( (len == 0) && sess->use_cache_file) || sess->bytes_done) {
sess->total_size = sess->bytes_done;
// HTTP 1.1 without content length...
gf_dm_sess_notify_state(sess, GF_NETIO_DATA_TRANSFERED, GF_OK);
gf_assert(sess->server_name);
GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[%s] Disconnected from %s: %s\n", sess->log_name, sess->server_name, gf_error_to_string(e)));
if (sess->use_cache_file)
gf_cache_set_content_length(sess->cache_entry, sess->bytes_done);
e = GF_OK;
}
}
gf_dm_disconnect(sess, HTTP_CLOSE);
SET_LAST_ERR(e)
gf_dm_sess_notify_state(sess, sess->status, e);
return e;
}
prev_remaining_data_size = sess->remaining_data_size;
sess->remaining_data_size = 0;
sess->http_buf[size + prev_remaining_data_size] = 0;
#ifdef GPAC_HTTPMUX
if (sess->hmux_sess) {
hmux_flush_internal_data(sess, GF_FALSE);
if (sess->hmux_stream_id<0) {
sess->status = GF_NETIO_DATA_TRANSFERED;
SET_LAST_ERR(GF_OK)
}
} else
#endif
gf_dm_data_received(sess, (u8 *) sess->http_buf, size + prev_remaining_data_size, GF_FALSE, &rewrite_size, NULL);
if (sess->chunked)
gf_dm_sess_estimate_chunk_rate(sess, rewrite_size);
/*socket empty*/
if (size < sess->http_buf_size) {
return GF_OK;
}
}
return GF_OK;
}
static void notify_error_body(GF_DownloadSession *sess, char * sHTTP, s32 bytesRead, s32 BodyStart)
{
GF_NETIO_Parameter par;
if (sHTTP) {
sHTTP[bytesRead]=0;
par.error = GF_BAD_PARAM;
par.reply = sess->rsp_code;
par.data = sHTTP + BodyStart;
par.size = (u32) strlen(par.data);
par.msg_type = GF_NETIO_DATA_EXCHANGE;
gf_dm_sess_user_io(sess, &par);
}
}
static u32 http_parse_method(const char *comp)
{
if (!strcmp(comp, "GET")) return GF_HTTP_GET;
else if (!strcmp(comp, "HEAD")) return GF_HTTP_HEAD;
else if (!strcmp(comp, "OPTIONS")) return GF_HTTP_OPTIONS;
else if (!strcmp(comp, "PUT")) return GF_HTTP_PUT;
else if (!strcmp(comp, "POST")) return GF_HTTP_POST;
else if (!strcmp(comp, "DELETE")) return GF_HTTP_DELETE;
else if (!strcmp(comp, "CONNECT")) return GF_HTTP_CONNECT;
else if (!strcmp(comp, "TRACE")) return GF_HTTP_TRACE;
else return 0;
}
/*!
* Waits for the response HEADERS, parse the information... and so on
\param sess The session
*/
static GF_Err wait_for_header_and_parse(GF_DownloadSession *sess)
{
GF_NETIO_Parameter par;
s32 bytesRead=0, BodyStart=0;
u32 res, i, buf_size = sess->http_buf_size;
s32 LinePos, Pos;
u32 method=0;
u32 ContentLength=0, first_byte, last_byte, total_size, range, no_range;
Bool connection_closed = GF_FALSE;
Bool has_content_length = GF_FALSE;
Bool connection_keep_alive = GF_FALSE;
u32 connection_timeout=0;
char buf[1025];
char comp[400];
GF_Err e;
char * new_location;
const char * mime_type;
#ifdef GPAC_HAS_HTTP2
Bool upgrade_to_http2 = GF_FALSE;
#endif
char *sHTTP = sess->http_buf;
sHTTP[0] = 0;
if (sess->creds && sess->creds->req_state) {
if (sess->creds->req_state==GF_CREDS_STATE_PENDING)
return GF_OK;
sess->creds->req_state = GF_CREDS_STATE_NONE;
if (!sess->creds->valid) {
gf_dm_disconnect(sess, HTTP_CLOSE);
sess->status = GF_NETIO_STATE_ERROR;
par.error = GF_AUTHENTICATION_FAILURE;
par.msg_type = GF_NETIO_DISCONNECTED;
gf_dm_sess_user_io(sess, &par);
e = GF_AUTHENTICATION_FAILURE;
SET_LAST_ERR(e)
goto exit;
}
sess->status = GF_NETIO_SETUP;
e = gf_dm_sess_setup_from_url(sess, sess->orig_url, GF_FALSE);
if (e) {
sess->status = GF_NETIO_STATE_ERROR;
SET_LAST_ERR(e)
gf_dm_sess_notify_state(sess, sess->status, e);
}
return e;
}
if (sess->server_mode) {
gf_fatal_assert( sess->status == GF_NETIO_CONNECTED );
} else {
gf_fatal_assert( sess->status == GF_NETIO_WAIT_FOR_REPLY );
if (!(sess->flags & GF_NETIO_SESSION_NOT_CACHED)) {
sess->use_cache_file = sess->dm->disable_cache ? GF_FALSE : GF_TRUE;
}
}
bytesRead = res = 0;
new_location = NULL;
if (sess->cached_file) {
sess->reply_time = (u32) (gf_sys_clock_high_res() - sess->request_start_time);
sess->rsp_hdr_size = 0;
sess->total_size = gf_cache_get_content_length(sess->cache_entry);
sess->bytes_done = 0;
memset(&par, 0, sizeof(GF_NETIO_Parameter));
par.msg_type = GF_NETIO_DATA_EXCHANGE;
par.error = GF_OK;
gf_dm_sess_user_io(sess, &par);
sess->status = GF_NETIO_DATA_EXCHANGE;
// gf_dm_disconnect(sess, HTTP_NO_CLOSE);
return GF_OK;
}
#ifdef GPAC_HAS_CURL
if (sess->curl_hnd) {
e = curl_process_reply(sess, &ContentLength);
if (e) return e;
goto process_reply;
}
#endif
//always set start time to the time at last attempt reply parsing
sess->start_time = gf_sys_clock_high_res();
sess->start_time_utc = gf_net_get_utc();
sess->chunked = GF_FALSE;
sess->last_chunk_found = GF_FALSE;
sess->last_chunk_start_time = sess->request_start_time;
sess->chunk_bytes = 0;
sess->cumulated_chunk_rate = 0;
while (1) {
Bool probe = (!bytesRead || (sess->flags & GF_NETIO_SESSION_NO_BLOCK) ) ? GF_TRUE : GF_FALSE;
#ifdef GPAC_HAS_NGTCP2
if (sess->server_mode && sess->hmux_sess && (sess->hmux_sess->net_sess->flags & GF_NETIO_SESSION_USE_QUIC)) {
GF_Err h3_check_sess(GF_DownloadSession *sess);
probe = GF_FALSE;
e = h3_check_sess(sess);
} else
#endif
e = gf_dm_read_data(sess, sHTTP + bytesRead, buf_size - bytesRead, &res);
#ifdef GPAC_HTTPMUX
/* break as soon as we have a header frame*/
if (sess->hmux_headers_seen) {
sess->hmux_headers_seen = 0;
res = 0;
bytesRead = 0;
BodyStart = 0;
e = GF_OK;
SET_LAST_ERR(GF_OK)
break;
}
#endif
//should not happen, but do it for safety
if (!res && !bytesRead && !e) e = GF_IP_NETWORK_EMPTY;
switch (e) {
case GF_IP_NETWORK_EMPTY:
if (probe) {
e = gf_sk_probe(sess->sock);
if (e==GF_IP_CONNECTION_CLOSED) {
SET_LAST_ERR(GF_IP_CONNECTION_CLOSED)
sess_connection_closed(sess);
sess->status = GF_NETIO_STATE_ERROR;
return GF_IP_NETWORK_EMPTY;
}
if (!sess->server_mode
&& sess->request_timeout
&& (gf_sys_clock_high_res() - sess->request_start_time > 1000 * sess->request_timeout)
) {
GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP,
("[%s] Session timeout for %s after %u ms, aborting\n", sess->log_name, sess->orig_url, sess->request_timeout));
SET_LAST_ERR(GF_IP_NETWORK_FAILURE)
sess->status = GF_NETIO_STATE_ERROR;
return GF_IP_NETWORK_FAILURE;
}
if (sess->status != GF_NETIO_STATE_ERROR) {
if (sess->server_mode
|| (sess->flags & GF_NETIO_SESSION_NO_BLOCK)
) {
SET_LAST_ERR(GF_IP_NETWORK_EMPTY)
}
}
return GF_OK;
}
if (sess->status==GF_NETIO_STATE_ERROR)
return sess->last_error;
if (!res && sess->status<=GF_NETIO_CONNECTED)
return GF_OK;
#ifdef GPAC_HTTPMUX
//we may have received bytes (bytesRead>0) yet none for this session, return GF_IP_NETWORK_EMPTY if empty
if (sess->hmux_sess)
return GF_IP_NETWORK_EMPTY;
#endif
continue;
/*socket has been closed while configuring, retry (not sure if the server got the GET)*/
case GF_IP_CONNECTION_CLOSED:
if (sess->http_read_type == HEAD) {
/* Some servers such as shoutcast directly close connection if HEAD or an unknown method is issued */
sess->server_only_understand_get = GF_TRUE;
}
if (sess->server_mode) {
SET_LAST_ERR(GF_IP_CONNECTION_CLOSED)
sess_connection_closed(sess);
sess->status = GF_NETIO_DISCONNECTED;
GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[%s] Connection closed by client\n", sess->log_name, sess->remote_path));
return GF_IP_CONNECTION_CLOSED;
}
gf_dm_disconnect(sess, HTTP_RESET_CONN);
if (sess->num_retry) {
#ifdef GPAC_HAS_SSL
if ((sess->num_retry < SESSION_RETRY_SSL)
&& !(sess->flags & GF_DOWNLOAD_SESSION_USE_SSL)
&& !gf_opts_get_bool("core", "no-tls-rcfg")
) {
GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[%s] Connection closed by server when processing %s - retrying using SSL\n", sess->log_name, sess->remote_path));
sess->flags |= GF_DOWNLOAD_SESSION_SSL_FORCED;
} else
#endif
{
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_INFO, GF_LOG_HTTP, ("[%s] Connection closed by server when processing %s - aborting\n", sess->log_name, sess->remote_path));
SET_LAST_ERR(e)
sess->status = GF_NETIO_STATE_ERROR;
}
return e;
case GF_OK:
SET_LAST_ERR(GF_OK)
if (!res)
return GF_OK;
break;
default:
goto exit;
}
bytesRead += res;
#ifdef GPAC_HTTPMUX
//in case we got a refused stream
if (sess->status==GF_NETIO_SETUP) {
return GF_OK;
}
if (sess->hmux_sess)
continue;
#endif
//HTTP1.1 only
char *hdr_buf = sHTTP;
u32 hdr_buf_len = bytesRead;
if (sess->flags & GF_NETIO_SESSION_NO_BLOCK) {
sess->async_req_reply = gf_realloc(sess->async_req_reply, sess->async_req_reply_size+res+1);
if (!sess->async_req_reply) {
sess->status = GF_NETIO_STATE_ERROR;
SET_LAST_ERR(GF_OUT_OF_MEM)
return sess->last_error;
}
memcpy(sess->async_req_reply + sess->async_req_reply_size, sHTTP, res);
sess->async_req_reply_size += res;
bytesRead = 0;
sHTTP[0] = 0;
hdr_buf = sess->async_req_reply;
hdr_buf_len = sess->async_req_reply_size;
}
//weird bug on some servers sending twice the last chunk
if (!strncmp(hdr_buf, "0\r\n\r\n", 5) ) {
if (bytesRead) {
bytesRead -= res;
} else {
sess->async_req_reply_size -= res;
}
GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[%s] End of chunk found while waiting server response when processing %s - retrying\n", sess->log_name, sess->remote_path));
continue;
}
/*locate body start*/
BodyStart = gf_token_find(hdr_buf, 0, hdr_buf_len, "\r\n\r\n");
if (BodyStart > 0) {
BodyStart += 4;
break;
}
BodyStart = gf_token_find(hdr_buf, 0, hdr_buf_len, "\n\n");
if (BodyStart > 0) {
BodyStart += 2;
break;
}
}
no_range = range = ContentLength = first_byte = last_byte = total_size = sess->rsp_code = 0;
if (sess->flags & GF_NETIO_SESSION_NO_BLOCK) {
sHTTP = sess->async_req_reply;
buf_size = bytesRead = sess->async_req_reply_size;
sess->async_req_reply_size = 0;
}
#ifdef GPAC_HTTPMUX
if (!sess->hmux_sess) {
#endif
if (bytesRead < 0) {
e = GF_REMOTE_SERVICE_ERROR;
goto exit;
}
if (!BodyStart)
BodyStart = bytesRead;
if (BodyStart) sHTTP[BodyStart-1] = 0;
GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[%s] %s\n\n", sess->log_name, sHTTP));
sess->reply_time = (u32) (gf_sys_clock_high_res() - sess->request_start_time);
sess->rsp_hdr_size = BodyStart;
LinePos = gf_token_get_line(sHTTP, 0, bytesRead, buf, 1024);
Pos = gf_token_get(buf, 0, " \t\r\n", comp, 400);
//TODO for HTTP2
if (sess->server_mode) {
method = http_parse_method(comp);
Pos = gf_token_get(buf, Pos, " \t\r\n", comp, 400);
if (sess->orig_url) gf_free(sess->orig_url);
sess->orig_url = gf_strdup(comp);
/*Pos = */gf_token_get(buf, Pos, " \t\r\n", comp, 400);
if ((strncmp("HTTP", comp, 4) != 0)) {
e = GF_REMOTE_SERVICE_ERROR;
goto exit;
}
//flush potential body except for PUT/POST
if ((method==GF_HTTP_PUT) || (method==GF_HTTP_POST))
sess->rsp_code = 200;
else
sess->rsp_code = 300;
} else {
if (!strncmp("ICY", comp, 3)) {
sess->use_cache_file = GF_FALSE;
/*be prepared not to receive any mime type from ShoutCast servers*/
if (!gf_cache_get_mime_type(sess->cache_entry))
gf_cache_set_mime_type(sess->cache_entry, "audio/mpeg");
} else if ((strncmp("HTTP", comp, 4) != 0)) {
e = GF_REMOTE_SERVICE_ERROR;
goto exit;
}
Pos = gf_token_get(buf, Pos, " ", comp, 400);
if (Pos <= 0) {
e = GF_REMOTE_SERVICE_ERROR;
goto exit;
}
sess->rsp_code = (u32) atoi(comp);
/*Pos = */gf_token_get(buf, Pos, " \r\n", comp, 400);
}
/* parse headers*/
while (1) {
GF_HTTPHeader *hdrp;
char *sep, *hdr_sep, *hdr, *hdr_val;
if ( (s32) LinePos + 4 > BodyStart) break;
LinePos = gf_token_get_line(sHTTP, LinePos , bytesRead, buf, 1024);
if (LinePos < 0) break;
hdr_sep = NULL;
hdr_val = NULL;
hdr = buf;
sep = strchr(buf, ':');
if (sep) {
sep[0]=0;
hdr_val = sep+1;
while (hdr_val[0]==' ') hdr_val++;
hdr_sep = strrchr(hdr_val, '\r');
if (hdr_sep) hdr_sep[0] = 0;
}
GF_SAFEALLOC(hdrp, GF_HTTPHeader);
if (hdrp) {
hdrp->name = gf_strdup(hdr);
if (hdr_val)
hdrp->value = gf_strdup(hdr_val);
gf_list_add(sess->headers, hdrp);
}
if (sep) sep[0]=':';
if (hdr_sep) hdr_sep[0] = '\r';
if (sess->server_mode) {
if (!stricmp(hdrp->name, "Transfer-Encoding") && !stricmp(hdrp->value, "chunked"))
sess->chunked = GF_TRUE;
}
}
#ifdef GPAC_HTTPMUX
}
#endif
#ifdef GPAC_HAS_CURL
process_reply:
#endif
if (!sess->server_mode) {
Bool cache_no_store = GF_FALSE;
Bool cache_must_revalidate = GF_FALSE;
u32 delta_age = 0;
u32 max_age = 0;
//default pre-processing of headers - needs cleanup, not all of these have to be parsed before checking reply code
for (i=0; i<gf_list_count(sess->headers); i++) {
char *val;
GF_HTTPHeader *hdr = (GF_HTTPHeader*)gf_list_get(sess->headers, i);
#ifdef GPAC_HTTPMUX
if (!stricmp(hdr->name, ":status") ) {
sess->rsp_code = (u32) atoi(hdr->value);
} else
#endif
if (!stricmp(hdr->name, "Content-Length") ) {
ContentLength = (u32) atoi(hdr->value);
has_content_length=GF_TRUE;
if ((sess->rsp_code<300) && sess->cache_entry)
gf_cache_set_content_length(sess->cache_entry, ContentLength);
}
else if (!stricmp(hdr->name, "Content-Type")) {
char *mime = gf_strdup(hdr->value);
while (1) {
u32 len = (u32) strlen(mime);
char c = len ? mime[len-1] : 0;
if ((c=='\r') || (c=='\n')) {
mime[len-1] = 0;
} else {
break;
}
}
val = strchr(mime, ';');
if (val) val[0] = 0;
strlwr(mime);
if (sess->rsp_code<300) {
if (sess->cache_entry) {
gf_cache_set_mime_type(sess->cache_entry, mime);
} else {
sess->mime_type = mime;
mime = NULL;
}
}
if (mime) gf_free(mime);
}
else if (!stricmp(hdr->name, "Content-Range")) {
if (!strnicmp(hdr->value, "bytes", 5)) {
val = hdr->value + 5;
while (val && val[0] && strchr(":= ", val[0]))
val++;
if (val[0] == '*') {
sscanf(val, "*/%u", &total_size);
sess->full_resource_size = total_size;
sess->rsp_code = 416;
} else if (strstr(val, "/*")) {
sscanf(val, "%u-%u/*", &first_byte, &last_byte);
sess->full_resource_size = 0;
} else {
sscanf(val, "%u-%u/%u", &first_byte, &last_byte, &total_size);
sess->full_resource_size = total_size;
}
}
}
else if (!stricmp(hdr->name, "Accept-Ranges")) {
if (strstr(hdr->value, "none")) no_range = 1;
}
else if (!stricmp(hdr->name, "Location"))
new_location = gf_strdup(hdr->value);
else if (!strnicmp(hdr->name, "ice", 3) || !strnicmp(hdr->name, "icy", 3) ) {
/* For HTTP icy servers, we disable cache */
if (sess->icy_metaint == 0)
sess->icy_metaint = -1;
sess->use_cache_file = GF_FALSE;
if (!stricmp(hdr->name, "icy-metaint")) {
sess->icy_metaint = atoi(hdr->value);
}
}
else if (!stricmp(hdr->name, "Age")) {
sscanf(hdr->value, "%u", &delta_age);
}
else if (!stricmp(hdr->name, "Cache-Control")) {
char *hval = hdr->value;
while (hval[0]) {
char *hsep = strchr(hval, ',');
if (hsep) hsep[0] = 0;
while (hval[0]==' ') hval++;
char *vsep = strchr(hval, '=');
if (vsep) vsep[0] = 0;
if (!strcmp(hval, "no-store")) cache_no_store = GF_TRUE;
else if (!strcmp(hval, "no-cache")) cache_no_store = GF_TRUE;
else if (!strcmp(hval, "private")) {
//we need a way to differentiate proxy modes and client modes
}
else if (!strcmp(hval, "public")) {}
else if (!strcmp(hval, "max-age") && vsep && !max_age) {
sscanf(vsep+1, "%u", &max_age);
}
else if (!strcmp(hval, "s-maxage") && vsep) {
sscanf(vsep+1, "%u", &max_age);
}
else if (!strcmp(hval, "must-revalidate")) cache_must_revalidate = GF_TRUE;
else if (!strcmp(hval, "proxy-revalidate")) cache_must_revalidate = GF_TRUE;
if (vsep) vsep[0] = '=';
if (!hsep) break;
hsep[0] = ',';
hval = hsep+1;
}
}
else if (!stricmp(hdr->name, "ETag")) {
if (sess->rsp_code<300)
gf_cache_set_etag_on_server(sess->cache_entry, hdr->value);
}
else if (!stricmp(hdr->name, "Last-Modified")) {
if (sess->rsp_code<300)
gf_cache_set_last_modified_on_server(sess->cache_entry, hdr->value);
}
else if (!stricmp(hdr->name, "Transfer-Encoding")) {
if (!stricmp(hdr->value, "chunked")
#ifdef GPAC_HAS_CURL
&& !sess->curl_hnd
#endif
)
sess->chunked = GF_TRUE;
}
else if (!stricmp(hdr->name, "X-UserProfileID") ) {
gf_opts_set_key("core", "user-profileid", hdr->value);
}
else if (!stricmp(hdr->name, "Connection") ) {
if (strstr(hdr->value, "close"))
connection_closed = GF_TRUE;
else if (strstr(hdr->value, "Keep-Alive"))
connection_keep_alive = GF_TRUE;
}
else if (!stricmp(hdr->name, "Keep-Alive") ) {
char *tout = strstr(hdr->value, "timeout=");
if (tout) {
char c=0;
s32 end = gf_token_find(tout, 0, (u32) strlen(tout), ", ");
if (end>=0) {
c = tout[end];
tout[end] = 0;
}
connection_timeout = atoi(tout+8);
if (end>=0) tout[end] = c;
}
if (strstr(hdr->value, "close"))
connection_closed = GF_TRUE;
}
#ifdef GPAC_HAS_HTTP2
else if (!stricmp(hdr->name, "Upgrade") ) {
if (!sess->dm->disable_http2 && !gf_opts_get_bool("core", "no-h2c") && !strncmp(hdr->value,"h2c", 3)) {
upgrade_to_http2 = GF_TRUE;
}
}
#endif
if (sess->status==GF_NETIO_DISCONNECTED) return GF_OK;
}
if ((sess->flags & GF_NETIO_SESSION_AUTO_CACHE) && !ContentLength && (sess->rsp_code>=200) && (sess->rsp_code<300) ) {
sess->use_cache_file = GF_FALSE;
if (sess->cache_entry) {
gf_cache_entry_set_delete_files_when_deleted(sess->cache_entry);
gf_cache_remove_session_from_cache_entry(sess->cache_entry, sess);
sess->cache_entry = NULL;
}
}
sess->flags &= ~GF_NETIO_SESSION_NO_STORE;
if (cache_no_store) {
gf_cache_entry_set_delete_files_when_deleted(sess->cache_entry);
sess->flags |= GF_NETIO_SESSION_NO_STORE;
if (sess->cache_entry && !ContentLength && !sess->chunked && (sess->rsp_code<300)
#ifdef GPAC_HTTPMUX
&& !sess->hmux_sess
#endif
) {
sess->use_cache_file = GF_FALSE;
gf_cache_remove_session_from_cache_entry(sess->cache_entry, sess);
sess->cache_entry = NULL;
}
}
else if (sess->cache_entry) {
if (max_age) max_age += delta_age;
gf_cache_set_max_age(sess->cache_entry, max_age, cache_must_revalidate);
}
if (no_range) first_byte = 0;
gf_cache_set_headers_processed(sess->cache_entry);
if (connection_keep_alive
&& !gf_opts_get_bool("core", "no-timeout")
&& !sess->server_mode
#ifdef GPAC_HTTPMUX
&& !sess->hmux_sess
#endif
) {
sess->connection_timeout_ms = connection_timeout*1000;
}
} else {
//server mode, start timers as soon as we see the request headers
sess->last_fetch_time = sess->request_start_time = gf_sys_clock_high_res();
}
//if we issued an open-range from end of file till unknown we may get a 416. If the server is indicating
//resource size and it matches our range, move to 206
if ((sess->rsp_code==416) && (sess->range_start==sess->full_resource_size)) {
sess->rsp_code = 206;
ContentLength = 0;
has_content_length = GF_TRUE;
}
//if no start range, a server may reply with 200 if open end range or if end range is file size
//move this to 200 to avoid triggering a byte range not supported detection
else if (sess->needs_range && (sess->rsp_code==200) && !sess->range_start && (!sess->range_end || (sess->range_end+1==ContentLength))) {
sess->rsp_code = 206;
}
par.msg_type = GF_NETIO_PARSE_REPLY;
par.error = GF_OK;
par.reply = sess->rsp_code;
par.value = comp;
/*
* If response is correct, it means our credentials are correct
*/
if (sess->creds && sess->rsp_code != 304)
sess->creds->valid = GF_TRUE;
#ifdef GPAC_HAS_HTTP2
if ((sess->rsp_code == 101) && upgrade_to_http2) {
char *body = NULL;
u32 body_len = 0;
if (bytesRead > BodyStart) {
body = sHTTP + BodyStart;
body_len = bytesRead - BodyStart;
}
return http2_do_upgrade(sess, body, body_len);
}
if (sess->h2_upgrade_state<4)
sess->h2_upgrade_state = 2;
#endif
/*try to flush body */
if ((sess->rsp_code>=300)
#ifdef GPAC_HTTPMUX
&& !sess->hmux_sess
#endif
#ifdef GPAC_HAS_CURL
&& !sess->curl_hnd
#endif
) {
u32 start = gf_sys_clock();
while (BodyStart + ContentLength > (u32) bytesRead) {
e = gf_dm_read_data(sess, sHTTP + bytesRead, buf_size - bytesRead, &res);
switch (e) {
case GF_IP_NETWORK_EMPTY:
break;
case GF_OK:
bytesRead += res;
break;
default:
start=0;
break;
}
if (gf_sys_clock()-start>100)
break;
//does not fit in our buffer, too bad we'll kill the connection
if (bytesRead == buf_size)
break;
}
if (BodyStart + ContentLength > (u32) bytesRead) {
ContentLength = 0;
//cannot flush, discard socket
sess->connection_close = GF_TRUE;
}
}
#ifdef GPAC_HTTPMUX
if (sess->hmux_sess) {
u32 count = gf_list_count(sess->headers);
for (i=0; i<count; i++) {
GF_HTTPHeader *hdr = gf_list_get(sess->headers, i);
if (!stricmp(hdr->name, ":method")) {
method = http_parse_method(hdr->value);
sess->rsp_code = 200;
}
else if (!stricmp(hdr->name, ":path")) {
if (sess->orig_url) gf_free(sess->orig_url);
sess->orig_url = gf_strdup(hdr->value);
}
}
}
#endif
#ifdef GPAC_HAS_HTTP2
else if (sess->server_mode
&& !gf_opts_get_bool("core", "no-h2")
&& !gf_opts_get_bool("core", "no-h2c")
&& !(sess->flags & GF_NETIO_SESSION_USE_QUIC)
) {
Bool is_upgradeable = GF_FALSE;
char *h2_settings = NULL;
u32 count = gf_list_count(sess->headers);
for (i=0; i<count; i++) {
GF_HTTPHeader *hdr = gf_list_get(sess->headers, i);
if (!stricmp(hdr->name, "Upgrade")) {
if (strstr(hdr->value, "h2c"))
is_upgradeable = GF_TRUE;
}
else if (!stricmp(hdr->name, "HTTP2-Settings")) {
h2_settings = hdr->value;
}
}
if (is_upgradeable && h2_settings) {
u32 len = (u32) strlen(h2_settings);
sess->h2_upgrade_settings = gf_malloc(sizeof(char) * len * 2);
sess->h2_upgrade_settings_len = gf_base64_decode(h2_settings, len, sess->h2_upgrade_settings, len*2);
}
}
#endif
if (sess->server_mode) {
if (ContentLength) {
par.data = sHTTP + BodyStart;
par.size = ContentLength;
} else if ((BodyStart < (s32) bytesRead)
#ifdef GPAC_HTTPMUX
&& !sess->hmux_sess
#endif
) {
if (sess->init_data) gf_free(sess->init_data);
sess->init_data_size = 0;
sess->init_data = NULL;
gf_dm_data_received(sess, (u8 *) sHTTP + BodyStart, bytesRead - BodyStart, GF_TRUE, NULL, NULL);
}
sess->request_start_time = gf_sys_clock_high_res();
par.reply = method;
gf_dm_sess_user_io(sess, &par);
sess->status = GF_NETIO_DATA_TRANSFERED;
SET_LAST_ERR(GF_OK)
return GF_OK;
}
//remember if we can keep the session alive after the transfer is done
sess->connection_close = connection_closed;
gf_assert(sess->rsp_code);
switch (sess->rsp_code) {
//100 continue
case 100:
break;
case 200:
case 201:
case 202:
case 206:
gf_dm_sess_user_io(sess, &par);
e = GF_OK;
if (sess->proxy_enabled==2) {
sess->proxy_enabled=0;
if (sess->dm)
gf_list_add(sess->dm->skip_proxy_servers, gf_strdup(sess->server_name));
}
sess->nb_redirect=0;
break;
/*redirection: extract the new location*/
case 301:
case 302:
case 303:
case 307:
if ((sess->nb_redirect > 4) || !new_location || !strlen(new_location) ) {
gf_dm_sess_user_io(sess, &par);
e = GF_URL_ERROR;
goto exit;
}
while (
(new_location[strlen(new_location)-1] == '\n')
|| (new_location[strlen(new_location)-1] == '\r') )
new_location[strlen(new_location)-1] = 0;
/*reset and reconnect*/
gf_dm_disconnect(sess, HTTP_CLOSE);
sess->status = GF_NETIO_SETUP;
sess->nb_redirect++;
e = gf_dm_sess_setup_from_url(sess, new_location, GF_FALSE);
if (e) {
sess->status = GF_NETIO_STATE_ERROR;
SET_LAST_ERR(e)
gf_dm_sess_notify_state(sess, sess->status, e);
}
gf_free(new_location);
return e;
case 304:
{
gf_assert(sess->cache_entry);
gf_assert(!sess->cached_file);
//special case for resources stored as persistent (mpd, init seg): we don't push the data
if (gf_cache_entry_persistent(sess->cache_entry)) {
gf_dm_sess_notify_state(sess, GF_NETIO_PARSE_REPLY, GF_OK);
/* Cache file is the most recent */
sess->status = GF_NETIO_DATA_TRANSFERED;
SET_LAST_ERR(GF_OK)
gf_dm_sess_notify_state(sess, GF_NETIO_DATA_TRANSFERED, GF_OK);
gf_dm_disconnect(sess, HTTP_NO_CLOSE);
return GF_OK;
}
sess->status = GF_NETIO_PARSE_REPLY;
if (!gf_cache_is_mem(sess->cache_entry)) {
sess->cached_file = gf_cache_open_read(sess->cache_entry);
if (!sess->cached_file) {
GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[%s] FAILED to open cache file %s for reading contents !\n", sess->log_name, gf_cache_get_cache_filename(sess->cache_entry)));
/* Ooops, no cache, redownload everything ! */
gf_dm_disconnect(sess, HTTP_NO_CLOSE);
sess->status = GF_NETIO_SETUP;
e = gf_dm_sess_setup_from_url(sess, sess->orig_url, GF_FALSE);
sess->total_size = gf_cache_get_cache_filesize(sess->cache_entry);
if (e) {
sess->status = GF_NETIO_STATE_ERROR;
SET_LAST_ERR(e)
gf_dm_sess_notify_state(sess, sess->status, e);
}
return e;
}
} else {
//we read from mem cache
sess->local_cache_only = GF_TRUE;
}
sess->status = GF_NETIO_DATA_EXCHANGE;
sess->total_size = ContentLength = gf_cache_get_cache_filesize(sess->cache_entry);
gf_dm_sess_user_io(sess, &par);
sess->status = GF_NETIO_DATA_EXCHANGE;
e = GF_EOS;
break;
}
case 401:
{
if (sess->creds && sess->creds->valid) {
gf_opts_set_key("credentials", sess->creds->site, NULL);
sess->creds->valid = GF_FALSE;
}
Bool secure = GF_FALSE;
#ifdef GPAC_HAS_SSL
if (sess->ssl) secure = GF_TRUE;
#endif
/* Do we have a credentials struct ? */
sess->creds = gf_user_credentials_register(sess->dm, secure, sess->server_name, NULL, NULL, GF_FALSE);
if (!sess->creds) {
/* User credentials have not been filled properly, we have to abort */
gf_dm_disconnect(sess, HTTP_CLOSE);
sess->status = GF_NETIO_STATE_ERROR;
par.error = GF_AUTHENTICATION_FAILURE;
par.msg_type = GF_NETIO_DISCONNECTED;
gf_dm_sess_user_io(sess, &par);
e = GF_AUTHENTICATION_FAILURE;
SET_LAST_ERR(e)
goto exit;
}
gf_dm_disconnect(sess, HTTP_NO_CLOSE);
sess->status = GF_NETIO_SETUP;
if (sess->creds->req_state==GF_CREDS_STATE_PENDING) {
//force a wait for reply until we have resolved user pass
sess->status = GF_NETIO_WAIT_FOR_REPLY;
return GF_OK;
}
e = gf_dm_sess_setup_from_url(sess, sess->orig_url, GF_FALSE);
if (e) {
sess->status = GF_NETIO_STATE_ERROR;
SET_LAST_ERR(e)
gf_dm_sess_notify_state(sess, sess->status, e);
}
return e;
}
case 400:
case 501:
/* Method not implemented ! */
if (sess->http_read_type == HEAD) {
/* Since HEAD is not understood by this server, we use a GET instead */
sess->http_read_type = GET;
sess->flags |= GF_NETIO_SESSION_NOT_CACHED;
gf_dm_disconnect(sess, HTTP_NO_CLOSE);
sess->status = GF_NETIO_SETUP;
sess->server_only_understand_get = GF_TRUE;
GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("Method not supported, try with GET.\n"));
e = gf_dm_sess_setup_from_url(sess, sess->orig_url, GF_FALSE);
if (e) {
sess->status = GF_NETIO_STATE_ERROR;
SET_LAST_ERR(e)
gf_dm_sess_notify_state(sess, sess->status, e);
}
return e;
}
gf_dm_sess_user_io(sess, &par);
notify_error_body(sess, sHTTP, bytesRead, BodyStart);
e = GF_REMOTE_SERVICE_ERROR;
goto exit;
case 503:
/*retry without proxy*/
if (sess->proxy_enabled==1) {
sess->proxy_enabled=2;
gf_dm_disconnect(sess, HTTP_CLOSE);
sess->status = GF_NETIO_SETUP;
return GF_OK;
}
//fall-through
/* case 204:
case 504:
case 404:
case 403:
case 416:
*/
default:
gf_dm_sess_user_io(sess, &par);
if ((BodyStart < (s32) bytesRead)) {
sHTTP[bytesRead] = 0;
GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[%s] Failure - body: %s\n", sess->log_name, sHTTP + BodyStart));
}
notify_error_body(sess, sHTTP, bytesRead, BodyStart);
switch (sess->rsp_code) {
case 204: e = GF_EOS; break;
/* File not found */
case 404:
//too early
case 425:
e = GF_URL_ERROR; break;
/* Forbidden */
case 403: e = GF_AUTHENTICATION_FAILURE; break;
/* Range not accepted */
case 416:
e = GF_SERVICE_ERROR;
break;
case 504: e = GF_URL_ERROR; break;
default:
if (sess->rsp_code>=500) e = GF_REMOTE_SERVICE_ERROR;
else e = GF_SERVICE_ERROR;
break;
}
goto exit;
}
if (sess->http_read_type != GET)
sess->use_cache_file = GF_FALSE;
if (sess->http_read_type==HEAD) {
gf_dm_disconnect(sess, HTTP_NO_CLOSE);
gf_dm_sess_notify_state(sess, GF_NETIO_DATA_TRANSFERED, GF_OK);
sess->http_read_type = GET;
return GF_OK;
}
mime_type = gf_cache_get_mime_type(sess->cache_entry);
if (!ContentLength && mime_type && ((strstr(mime_type, "ogg") || (!strcmp(mime_type, "audio/mpeg"))))) {
if (0 == sess->icy_metaint)
sess->icy_metaint = -1;
sess->use_cache_file = GF_FALSE;
}
#ifndef GPAC_DISABLE_LOG
if (e<0) {
GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[%s] Error processing rely from %s: %s\n", sess->log_name, sess->server_name, gf_error_to_string(e) ) );
} else {
GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[%s] Reply processed from %s\n", sess->log_name, sess->server_name ) );
}
#endif
//in HTTP2 we may resume to setup state if we got a refused stream
if (sess->status == GF_NETIO_SETUP) {
return GF_OK;
}
/*some servers may reply without content length, but we MUST have it*/
if (e) goto exit;
if (sess->icy_metaint != 0) {
gf_assert( ! sess->use_cache_file );
GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[%s] ICY protocol detected\n", sess->log_name));
if (mime_type && !stricmp(mime_type, "video/nsv")) {
gf_cache_set_mime_type(sess->cache_entry, "audio/aac");
}
sess->icy_bytes = 0;
sess->total_size = SIZE_IN_STREAM;
sess->status = GF_NETIO_DATA_EXCHANGE;
} else if (!ContentLength && !has_content_length && !sess->chunked
#ifdef GPAC_HTTPMUX
&& !sess->hmux_sess
#endif
#ifdef GPAC_HAS_CURL
&& !sess->curl_hnd
#endif
) {
if (sess->http_read_type == GET) {
sess->total_size = SIZE_IN_STREAM;
sess->use_cache_file = GF_FALSE;
sess->status = GF_NETIO_DATA_EXCHANGE;
sess->bytes_done = 0;
} else {
gf_dm_sess_notify_state(sess, GF_NETIO_DATA_TRANSFERED, GF_OK);
gf_dm_disconnect(sess, HTTP_NO_CLOSE);
return GF_OK;
}
#ifdef GPAC_HTTPMUX
} else if (sess->hmux_sess && !ContentLength && (sess->http_read_type != GET)) {
gf_dm_sess_notify_state(sess, GF_NETIO_DATA_TRANSFERED, GF_OK);
gf_dm_disconnect(sess, HTTP_NO_CLOSE);
return GF_OK;
#endif
} else {
sess->total_size = ContentLength;
if (sess->use_cache_file && !sess->cached_file && (sess->http_read_type == GET)) {
e = gf_cache_open_write_cache(sess->cache_entry, sess);
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ( "[CACHE] Failed to open cache, error=%d\n", e));
goto exit;
}
}
sess->status = GF_NETIO_DATA_EXCHANGE;
sess->bytes_done = 0;
if (!ContentLength && has_content_length) {
gf_dm_sess_notify_state(sess, GF_NETIO_DATA_TRANSFERED, GF_OK);
gf_dm_disconnect(sess, HTTP_NO_CLOSE);
sess->status = GF_NETIO_DATA_TRANSFERED;
return GF_OK;
}
}
/* we may have existing data in this buffer ... */
#ifdef GPAC_HTTPMUX
if (!e && sess->hmux_sess) {
hmux_flush_internal_data(sess, GF_TRUE);
} else
#endif
if (!e && (BodyStart < (s32) bytesRead)) {
u32 rewrite_size=0;
if (sess->init_data) gf_free(sess->init_data);
sess->init_data_size = 0;
sess->init_data = NULL;
gf_dm_data_received(sess, (u8 *) sHTTP + BodyStart, bytesRead - BodyStart, GF_TRUE, &rewrite_size, NULL);
if (sess->chunked)
gf_dm_sess_estimate_chunk_rate(sess, rewrite_size);
}
exit:
if (e) {
if (e<0) {
GF_LOG((e==GF_URL_ERROR) ? GF_LOG_INFO : GF_LOG_WARNING, GF_LOG_HTTP, ("[%s] Error parsing reply for URL %s: %s (code %d)\n", sess->log_name, sess->orig_url, gf_error_to_string(e), sess->rsp_code ));
} else {
e = GF_OK;
}
if (e) {
gf_cache_entry_set_delete_files_when_deleted(sess->cache_entry);
gf_cache_remove_entry_from_session(sess);
sess->cache_entry = NULL;
}
gf_dm_disconnect(sess, HTTP_NO_CLOSE);
if ((e<0) && connection_closed)
sess->status = GF_NETIO_STATE_ERROR;
else
sess->status = GF_NETIO_DATA_TRANSFERED;
SET_LAST_ERR(e)
gf_dm_sess_notify_state(sess, sess->status, e);
return e;
}
/*DO NOT call parse_body yet, as the final user may not be connected to our session*/
return GF_OK;
}
/**
* Default performing behavior
\param sess The session
*/
void http_do_requests(GF_DownloadSession *sess)
{
sess->http_buf[0] = 0;
if (sess->reused_cache_entry) {
//main session is done downloading, notify - to do we should send progress events on this session also ...
if (!gf_cache_is_in_progress(sess->cache_entry)) {
GF_NETIO_Parameter par;
gf_dm_disconnect(sess, HTTP_NO_CLOSE);
sess->reused_cache_entry = GF_FALSE;
memset(&par, 0, sizeof(GF_NETIO_Parameter));
par.msg_type = GF_NETIO_DATA_TRANSFERED;
par.error = GF_OK;
gf_dm_sess_user_io(sess, &par);
}
return;
}
if (sess->async_buf_size && (gf_dm_sess_flush_async(sess, GF_FALSE) == GF_IP_NETWORK_EMPTY)) {
return;
}
switch (sess->status) {
case GF_NETIO_CONNECTED:
if (sess->server_mode) {
wait_for_header_and_parse(sess);
} else {
http_send_headers(sess);
}
break;
case GF_NETIO_WAIT_FOR_REPLY:
if (sess->server_mode) {
http_send_headers(sess);
} else {
wait_for_header_and_parse(sess);
}
break;
case GF_NETIO_DATA_EXCHANGE:
if (sess->server_mode) {
sess->status = GF_NETIO_CONNECTED;
break;
}
/*session has been reassigned, resend data retrieved in first GET reply to user but don't write to cache*/
if (sess->reassigned) {
if (sess->icy_metaint > 0) {
//we are reparsing init data, reset icy status
sess->icy_bytes = 0;
gf_icy_skip_data(sess, sess->init_data, sess->init_data_size);
} else {
GF_NETIO_Parameter par;
par.msg_type = GF_NETIO_DATA_EXCHANGE;
par.error = GF_OK;
par.data = sess->init_data;
par.size = sess->init_data_size;
gf_dm_sess_user_io(sess, &par);
}
sess->reassigned = GF_FALSE;
}
http_parse_remaining_body(sess);
break;
default:
break;
}
}
GF_EXPORT
void gf_dm_sess_set_max_rate(GF_DownloadSession *sess, u32 max_rate)
{
if (sess) {
sess->max_data_rate = max_rate/8;
sess->rate_regulated = GF_FALSE;
}
}
GF_EXPORT
u32 gf_dm_sess_get_max_rate(GF_DownloadSession *sess)
{
return sess ? 8*sess->max_data_rate : 0;
}
GF_EXPORT
Bool gf_dm_sess_is_regulated(GF_DownloadSession *sess)
{
return sess ? sess->rate_regulated : GF_FALSE;
}
/**
* NET IO for MPD, we don't need this anymore since mime-type can be given by session
*/
static void wget_NetIO(void *cbk, GF_NETIO_Parameter *param)
{
FILE * f = (FILE*) cbk;
/*handle service message*/
if (param->msg_type == GF_NETIO_DATA_EXCHANGE) {
s32 written = (u32) gf_fwrite( param->data, param->size, f);
if (written != param->size) {
GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("Failed to write data on disk\n"));
}
}
}
GF_EXPORT
GF_Err gf_dm_wget(const char *url, const char *filename, u64 start_range, u64 end_range, char **redirected_url)
{
GF_Err e;
GF_DownloadManager * dm = gf_dm_new(NULL);
if (!dm)
return GF_OUT_OF_MEM;
e = gf_dm_wget_with_cache(dm, url, filename, start_range, end_range, redirected_url);
gf_dm_del(dm);
return e;
}
GF_EXPORT
GF_Err gf_dm_wget_with_cache(GF_DownloadManager * dm, const char *url, const char *filename, u64 start_range, u64 end_range, char **redirected_url)
{
GF_Err e;
FILE * f;
GF_DownloadSession *dnload;
if (!filename || !url || !dm)
return GF_BAD_PARAM;
f = gf_fopen(filename, "wb");
if (!f) {
GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[WGET] Failed to open file %s for write.\n", filename));
return GF_IO_ERR;
}
dnload = gf_dm_sess_new_simple(dm, (char *)url, GF_NETIO_SESSION_NOT_THREADED, &wget_NetIO, f, &e);
if (!dnload) {
return GF_BAD_PARAM;
}
dnload->use_cache_file = GF_FALSE;
dnload->force_data_write_callback = GF_TRUE;
if (end_range) {
dnload->range_start = start_range;
dnload->range_end = end_range;
dnload->needs_range = GF_TRUE;
}
while (e == GF_OK) {
e = gf_dm_sess_process(dnload);
if (e && (e!=GF_IP_NETWORK_EMPTY))
break;
if (dnload->status>=GF_NETIO_DATA_TRANSFERED) break;
e = GF_OK;
if (dnload->connect_pending)
gf_sleep(100);
}
if (e==GF_OK)
gf_cache_set_content_length(dnload->cache_entry, dnload->total_size);
e |= gf_cache_close_write_cache(dnload->cache_entry, dnload, (e == GF_OK) ? GF_TRUE : GF_FALSE);
gf_fclose(f);
if (redirected_url) {
if (dnload->orig_url_before_redirect) *redirected_url = gf_strdup(dnload->orig_url);
}
gf_dm_sess_del(dnload);
return e;
}
GF_EXPORT
const char *gf_dm_sess_get_resource_name(GF_DownloadSession *dnload)
{
return dnload ? dnload->orig_url : NULL;
}
GF_EXPORT
void gf_dm_set_data_rate(GF_DownloadManager *dm, u32 rate_in_bits_per_sec)
{
if (rate_in_bits_per_sec == 0xFFFFFFFF) {
dm->simulate_no_connection=GF_TRUE;
} else {
char opt[100];
dm->simulate_no_connection=GF_FALSE;
dm->limit_data_rate = rate_in_bits_per_sec/8;
sprintf(opt, "%d", rate_in_bits_per_sec);
//temporary store of maxrate
gf_opts_set_key("temp", "maxrate", opt);
}
}
GF_EXPORT
u32 gf_dm_get_data_rate(GF_DownloadManager *dm)
{
return dm->limit_data_rate*8;
}
GF_EXPORT
u32 gf_dm_get_global_rate(GF_DownloadManager *dm)
{
u32 ret = 0;
u32 i, count;
if (!dm) return 0;
gf_mx_p(dm->cache_mx);
count = gf_list_count(dm->all_sessions);
for (i=0; i<count; i++) {
GF_DownloadSession *sess = (GF_DownloadSession*)gf_list_get(dm->all_sessions, i);
if (sess->status >= GF_NETIO_DATA_TRANSFERED) {
if (sess->total_size==sess->bytes_done) {
//do not aggregate session if done/interrupted since more than 1/2 a sec
if (gf_sys_clock_high_res() - sess->start_time > 500000) {
continue;
}
}
}
ret += sess->bytes_per_sec;
}
gf_mx_v(dm->cache_mx);
return 8*ret;
}
GF_HTTPSessionType gf_dm_sess_is_hmux(GF_DownloadSession *sess)
{
#ifdef GPAC_HTTPMUX
if (sess->hmux_sess) {
if (sess->hmux_sess->net_sess && sess->hmux_sess->net_sess->flags & GF_NETIO_SESSION_USE_QUIC)
return GF_SESS_TYPE_HTTP3;
if (sess->hmux_sess->net_sess && !sess->hmux_sess->net_sess->sock)
return GF_SESS_TYPE_HTTP3;
return GF_SESS_TYPE_HTTP2;
}
#endif
return GF_SESS_TYPE_HTTP;
}
Bool gf_dm_sess_use_tls(GF_DownloadSession * sess)
{
#ifdef GPAC_HAS_SSL
if (sess->ssl)
return GF_TRUE;
#endif
#ifdef GPAC_HTTPMUX
if (sess->hmux_sess && (sess->hmux_sess->net_sess->flags & GF_NETIO_SESSION_USE_QUIC))
return GF_TRUE;
#endif
return GF_FALSE;
}
u32 gf_dm_sess_get_resource_size(GF_DownloadSession * sess)
{
if (!sess) return 0;
if (sess->full_resource_size) return sess->full_resource_size;
if (sess->needs_range) return 0;
return sess->total_size;
}
GF_EXPORT
const char *gf_dm_sess_get_header(GF_DownloadSession *sess, const char *name)
{
u32 i, count;
if( !sess || !name) return NULL;
count = gf_list_count(sess->headers);
for (i=0; i<count; i++) {
GF_HTTPHeader *header = (GF_HTTPHeader*)gf_list_get(sess->headers, i);
if (!stricmp(header->name, name)) return header->value;
}
return NULL;
}
GF_EXPORT
GF_Err gf_dm_sess_enum_headers(GF_DownloadSession *sess, u32 *idx, const char **hdr_name, const char **hdr_val)
{
GF_HTTPHeader *hdr;
if( !sess || !idx || !hdr_name || !hdr_val)
return GF_BAD_PARAM;
hdr = gf_list_get(sess->headers, *idx);
if (!hdr) return GF_EOS;
(*idx) = (*idx) + 1;
(*hdr_name) = hdr->name;
(*hdr_val) = hdr->value;
return GF_OK;
}
GF_EXPORT
GF_Err gf_dm_sess_get_header_sizes_and_times(GF_DownloadSession *sess, u32 *req_hdr_size, u32 *rsp_hdr_size, u32 *connect_time, u32 *reply_time, u32 *download_time)
{
if (!sess)
return GF_BAD_PARAM;
if (req_hdr_size) *req_hdr_size = sess->req_hdr_size;
if (rsp_hdr_size) *rsp_hdr_size = sess->rsp_hdr_size;
if (connect_time) *connect_time = sess->connect_time;
if (reply_time) *reply_time = sess->reply_time;
if (download_time) *download_time = sess->total_time_since_req;
return GF_OK;
}
GF_EXPORT
void gf_dm_sess_force_memory_mode(GF_DownloadSession *sess, u32 force_keep)
{
if (sess) {
sess->flags |= GF_NETIO_SESSION_MEMORY_CACHE;
sess->flags &= ~GF_NETIO_SESSION_NOT_CACHED;
if (force_keep==1) {
sess->flags |= GF_NETIO_SESSION_KEEP_CACHE;
} else if (force_keep==2)
sess->flags |= GF_NETIO_SESSION_KEEP_FIRST_CACHE;
}
}
static GF_Err gf_dm_sess_flush_async_close(GF_DownloadSession *sess, Bool no_select, Bool for_close)
{
if (!sess) return GF_OK;
if (sess->status==GF_NETIO_STATE_ERROR) return sess->last_error;
if (!no_select && sess->sock && (gf_sk_select(sess->sock, GF_SK_SELECT_WRITE)!=GF_OK)) {
return sess->async_buf_size ? GF_IP_NETWORK_EMPTY : GF_OK;
}
GF_Err ret = GF_OK;
#ifdef GPAC_HTTPMUX
if(sess->hmux_sess)
ret = sess->hmux_sess->async_flush(sess, for_close);
//if H2 flush the parent session holding the http2 session
if (sess->hmux_sess)
sess = sess->hmux_sess->net_sess;
#endif
if (sess->async_buf_size) {
GF_Err e = dm_sess_write(sess, sess->async_buf, sess->async_buf_size);
if (e) return e;
if (sess->async_buf_size) return GF_IP_NETWORK_EMPTY;
}
return ret;
}
GF_Err gf_dm_sess_flush_async(GF_DownloadSession *sess, Bool no_select)
{
return gf_dm_sess_flush_async_close(sess, no_select, GF_FALSE);
}
GF_Err gf_dm_sess_flush_close(GF_DownloadSession *sess)
{
return gf_dm_sess_flush_async_close(sess, GF_TRUE, GF_TRUE);
}
u32 gf_dm_sess_async_pending(GF_DownloadSession *sess)
{
if (!sess) return 0;
#ifdef GPAC_HTTPMUX
if (sess->local_buf_len) return sess->local_buf_len;
if (sess->hmux_sess)
sess = sess->hmux_sess->net_sess;
#endif
return sess ? sess->async_buf_size : 0;
}
GF_EXPORT
GF_Err gf_dm_sess_send(GF_DownloadSession *sess, u8 *data, u32 size)
{
GF_Err e = GF_OK;
#ifdef GPAC_HTTPMUX
if (sess->hmux_sess) {
return hmux_send_payload(sess, data, size);
}
#endif
if (!data || !size) {
if (sess->put_state) {
sess->put_state = 2;
sess->status = GF_NETIO_WAIT_FOR_REPLY;
return GF_OK;
}
return GF_OK;
}
e = dm_sess_write(sess, data, size);
if (e==GF_IP_CONNECTION_CLOSED) {
sess_connection_closed(sess);
sess->status = GF_NETIO_STATE_ERROR;
return e;
}
else if (e==GF_IP_NETWORK_EMPTY) {
if (sess->flags & GF_NETIO_SESSION_NO_BLOCK)
return GF_OK;
return gf_dm_sess_send(sess, data, size);
}
return e;
}
GF_Socket *gf_dm_sess_get_socket(GF_DownloadSession *sess)
{
return sess ? sess->sock : NULL;
}
void gf_dm_sess_set_sock_group(GF_DownloadSession *sess, GF_SockGroup *sg)
{
if (sess) {
if (sess->sock_group!=sg) {
if (sess->sock_group)
gf_sk_group_unregister(sess->sock_group, sess->sock);
if (sg)
gf_sk_group_register(sg, sess->sock);
}
sess->sock_group = sg;
}
}
void gf_dm_sess_detach_async(GF_DownloadSession *sess)
{
if (sess->sock) {
while (sess->async_buf_size) {
gf_dm_sess_flush_async(sess, GF_FALSE);
gf_sleep(1);
}
gf_sk_set_block_mode(sess->sock, GF_TRUE);
}
sess->flags |= GF_NETIO_SESSION_NO_BLOCK;
//FOR TEST ONLY
sess->flags &= ~GF_NETIO_SESSION_NOT_THREADED;
//mutex may already be created for H2 sessions
if (!sess->mx)
sess->mx = gf_mx_new(sess->orig_url);
}
void gf_dm_sess_set_timeout(GF_DownloadSession *sess, u32 timeout)
{
if (sess) sess->request_timeout = 1000*timeout;
}
void gf_dm_sess_set_netcap_id(GF_DownloadSession *sess, const char *netcap_id)
{
if (sess) sess->netcap_id = netcap_id;
}
#endif //GPAC_DISABLE_NETWORK