LCOV - code coverage report
Current view: top level - filters - out_http.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 1157 1609 71.9 %
Date: 2021-04-29 23:48:07 Functions: 33 34 97.1 %

          Line data    Source code
       1             : /*
       2             :  *                      GPAC - Multimedia Framework C SDK
       3             :  *
       4             :  *                      Authors: Jean Le Feuvre
       5             :  *                      Copyright (c) Telecom ParisTech 2019-2021
       6             :  *                                      All rights reserved
       7             :  *
       8             :  *  This file is part of GPAC / http server and output filter
       9             :  *
      10             :  *  GPAC is free software; you can redistribute it and/or modify
      11             :  *  it under the terms of the GNU Lesser General Public License as published by
      12             :  *  the Free Software Foundation; either version 2, or (at your option)
      13             :  *  any later version.
      14             :  *
      15             :  *  GPAC is distributed in the hope that it will be useful,
      16             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      17             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      18             :  *  GNU Lesser General Public License for more details.
      19             :  *
      20             :  *  You should have received a copy of the GNU Lesser General Public
      21             :  *  License along with this library; see the file COPYING.  If not, write to
      22             :  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
      23             :  *
      24             :  */
      25             : 
      26             : #include <gpac/internal/media_dev.h>
      27             : #include <gpac/constants.h>
      28             : #include <gpac/maths.h>
      29             : 
      30             : #include <gpac/filters.h>
      31             : #include <gpac/ietf.h>
      32             : #include <gpac/config_file.h>
      33             : #include <gpac/base_coding.h>
      34             : #include <gpac/network.h>
      35             : 
      36             : //socket and SSL context ownership is transfered to the download session object
      37             : GF_DownloadSession *gf_dm_sess_new_server(GF_Socket *server, void *ssl_ctx, gf_dm_user_io user_io, void *usr_cbk, GF_Err *e);
      38             : GF_DownloadSession *gf_dm_sess_new_subsession(GF_DownloadSession *sess, u32 stream_id, void *usr_cbk, GF_Err *e);
      39             : u32 gf_dm_sess_subsession_count(GF_DownloadSession *);
      40             : 
      41             : 
      42             : GF_Err gf_dm_sess_send(GF_DownloadSession *sess, u8 *data, u32 size);
      43             : void gf_dm_sess_clear_headers(GF_DownloadSession *sess);
      44             : void  gf_dm_sess_set_header(GF_DownloadSession *sess, const char *name, const char *value);
      45             : 
      46             : GF_Err gf_dm_sess_send_reply(GF_DownloadSession *sess, u32 reply_code, const char *response_body, Bool no_body);
      47             : void gf_dm_sess_server_reset(GF_DownloadSession *sess);
      48             : Bool gf_dm_sess_is_h2(GF_DownloadSession *sess);
      49             : void gf_dm_sess_flush_h2(GF_DownloadSession *sess);
      50             : 
      51             : #ifdef GPAC_HAS_SSL
      52             : 
      53             : void *gf_ssl_new(void *ssl_server_ctx, GF_Socket *client_sock, GF_Err *e);
      54             : void gf_ssl_del(void *ssl_ctx);
      55             : void *gf_ssl_server_context_new(const char *cert, const char *key);
      56             : void gf_ssl_server_context_del(void *ssl_server_ctx);
      57             : Bool gf_ssl_init_lib();
      58             : 
      59             : #endif
      60             : 
      61             : enum
      62             : {
      63             :         MODE_DEFAULT=0,
      64             :         MODE_PUSH,
      65             :         MODE_SOURCE,
      66             : };
      67             : 
      68             : enum
      69             : {
      70             :         CORS_AUTO=0,
      71             :         CORS_OFF,
      72             :         CORS_ON,
      73             : };
      74             : 
      75             : typedef struct
      76             : {
      77             :         //options
      78             :         char *dst, *user_agent, *ifce, *cache_control, *ext, *mime, *wdir, *cert, *pkey, *reqlog;
      79             :         GF_PropStringList rdirs;
      80             :         Bool close, hold, quit, post, dlist, ice;
      81             :         u32 port, block_size, maxc, maxp, timeout, hmode, sutc, cors, max_client_errors;
      82             : 
      83             :         //internal
      84             :         GF_Filter *filter;
      85             :         GF_Socket *server_sock;
      86             :         GF_List *sessions, *active_sessions;
      87             :         GF_List *inputs;
      88             : 
      89             :         u32 next_wake_us;
      90             :         char *ip;
      91             :         Bool done;
      92             : 
      93             :         GF_SockGroup *sg;
      94             :         Bool no_etag;
      95             : 
      96             :         u32 nb_connections;
      97             : 
      98             :         GF_FilterCapability in_caps[2];
      99             :         char szExt[10];
     100             : 
     101             :         //set to true when no mounted dirs and not push mode
     102             :         Bool single_mode;
     103             : 
     104             :         void *ssl_ctx;
     105             : 
     106             :         u64 req_id;
     107             :         Bool log_record;
     108             : } GF_HTTPOutCtx;
     109             : 
     110             : typedef struct
     111             : {
     112             :         GF_HTTPOutCtx *ctx;
     113             :         GF_FilterPid *ipid;
     114             :         char *path;
     115             :         Bool dash_mode;
     116             :         char *mime;
     117             :         u32 nb_dest;
     118             :         Bool hold;
     119             : 
     120             :         Bool is_open, done, is_delete;
     121             :         Bool patch_blocks;
     122             :         GF_List *file_deletes;
     123             : 
     124             :         //for PUT mode, NULL in server mode
     125             :         GF_DownloadSession *upload;
     126             :         Bool is_h2;
     127             :         u32 cur_header;
     128             : 
     129             :         u64 offset_at_seg_start;
     130             :         u64 nb_write, write_start_range, write_end_range;
     131             :         char range_hdr[100];
     132             : 
     133             :         //for server mode, recording
     134             :         char *local_path;
     135             :         FILE *resource;
     136             : 
     137             :         FILE *hls_chunk;
     138             :         char *hls_chunk_path, *hls_chunk_local_path;
     139             : 
     140             :         u8 *tunein_data;
     141             :         u32 tunein_data_size;
     142             :     
     143             :     Bool force_dst_name;
     144             : 
     145             : 
     146             : } GF_HTTPOutInput;
     147             : 
     148             : typedef struct
     149             : {
     150             :         s64 start;
     151             :         s64 end;
     152             : } HTTByteRange;
     153             : 
     154             : typedef struct __httpout_session
     155             : {
     156             :         GF_HTTPOutCtx *ctx;
     157             : 
     158             :         GF_Socket *socket;
     159             :         GF_DownloadSession *http_sess;
     160             :         char peer_address[GF_MAX_IP_NAME_LEN];
     161             : 
     162             :         Bool headers_done;
     163             : 
     164             :         u32 play_state;
     165             :         Double start_range;
     166             : 
     167             :         FILE *resource;
     168             :         char *path, *mime;
     169             :         u64 file_size, file_pos, nb_bytes, bytes_in_req;
     170             :         u8 *buffer;
     171             :         Bool done;
     172             :         u64 last_file_modif;
     173             : 
     174             :         u64 req_start_time;
     175             :         u64 last_active_time;
     176             :         Bool file_in_progress;
     177             :         Bool use_chunk_transfer;
     178             :         u32 put_in_progress;
     179             :         //for upload only: 0 not an upload, 1 creation, 2: update
     180             :         u32 upload_type;
     181             :         u64 content_length;
     182             :         GF_FilterPid *opid;
     183             :         Bool reconfigure_output;
     184             : 
     185             :         GF_HTTPOutInput *in_source;
     186             :         Bool send_init_data;
     187             :         Bool in_source_is_ll_hls_chunk;
     188             : 
     189             :         u32 nb_ranges, alloc_ranges, range_idx;
     190             :         HTTByteRange *ranges;
     191             : 
     192             :         Bool do_log;
     193             :         u64 req_id;
     194             :         u32 method_type, reply_code, nb_consecutive_errors;
     195             : 
     196             :         Bool is_h2;
     197             :         Bool sub_sess_pending;
     198             :         Bool canceled;
     199             : 
     200             :         Bool force_destroy;
     201             : } GF_HTTPOutSession;
     202             : 
     203          47 : static void httpout_close_session(GF_HTTPOutSession *sess)
     204             : {
     205             :         Bool last_connection = GF_TRUE;
     206          47 :         if (!sess->http_sess) return;
     207             : 
     208          47 :         if (sess->is_h2) {
     209           0 :                 u32 nb_sub_sess = gf_dm_sess_subsession_count(sess->http_sess);
     210           0 :                 if (nb_sub_sess > 1) {
     211             :                         last_connection = GF_FALSE;
     212           0 :                         GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[HTTPOut] %d sub-sessions still active in connection to %s, keeping alive\n", nb_sub_sess-1, sess->peer_address ));
     213             :                 }
     214             :                 else {
     215           0 :                         gf_dm_sess_flush_h2(sess->http_sess);
     216             :                 }
     217             :         }
     218             :         if (last_connection) {
     219             :                 assert(sess->ctx->nb_connections);
     220          47 :                 sess->ctx->nb_connections--;
     221             : 
     222          47 :                 gf_sk_group_unregister(sess->ctx->sg, sess->socket);
     223             :         }
     224             : 
     225          47 :         gf_dm_sess_del(sess->http_sess);
     226          47 :         sess->http_sess = NULL;
     227          47 :         sess->socket = NULL;
     228             : 
     229          47 :         if (sess->in_source) sess->in_source->nb_dest--;
     230             : }
     231             : 
     232         220 : static void httpout_format_date(u64 time, char szDate[200], Bool for_listing)
     233             : {
     234             :         time_t gtime;
     235             :         struct tm *t;
     236             :         const char *wday, *month;
     237             :         u32 sec;
     238         220 :         gtime = time / 1000;
     239         220 :         t = gf_gmtime(&gtime);
     240         220 :         sec = t->tm_sec;
     241             :         //see issue #859, no clue how this happened...
     242         220 :         if (sec > 60)
     243             :                 sec = 60;
     244         220 :         switch (t->tm_wday) {
     245             :         case 1: wday = "Mon"; break;
     246           0 :         case 2: wday = "Tue"; break;
     247           0 :         case 3: wday = "Wed"; break;
     248         220 :         case 4: wday = "Thu"; break;
     249           0 :         case 5: wday = "Fri"; break;
     250           0 :         case 6: wday = "Sat"; break;
     251           0 :         default: wday = "Sun"; break;
     252             :         }
     253         220 :         switch (t->tm_mon) {
     254             :         case 1: month = "Feb"; break;
     255           0 :         case 2: month = "Mar"; break;
     256         220 :         case 3: month = "Apr"; break;
     257           0 :         case 4: month = "May"; break;
     258           0 :         case 5: month = "Jun"; break;
     259           0 :         case 6: month = "Jul"; break;
     260           0 :         case 7: month = "Aug"; break;
     261           0 :         case 8: month = "Sep"; break;
     262           0 :         case 9: month = "Oct"; break;
     263           0 :         case 10: month = "Nov"; break;
     264           0 :         case 11: month = "Dec"; break;
     265           0 :         default: month = "Jan"; break;
     266             : 
     267             :         }
     268             : 
     269         220 :         if (for_listing) {
     270           7 :                 sprintf(szDate, "%02d-%s-%d %02d:%02d:%02d", t->tm_mday, month, 1900 + t->tm_year, t->tm_hour, t->tm_min, sec);
     271             :         } else {
     272         213 :                 sprintf(szDate, "%s, %02d %s %d %02d:%02d:%02d GMT", wday, t->tm_mday, month, 1900 + t->tm_year, t->tm_hour, t->tm_min, sec);
     273             :         }
     274         220 : }
     275             : 
     276           7 : static Bool httpout_dir_file_enum(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info, Bool is_dir)
     277             : {
     278             :         char szFmt[200];
     279             :         u64 size;
     280             :         u32 name_len;
     281             :         char *unit=NULL;
     282             :         char **listing = (char **) cbck;
     283             : 
     284           7 :         if (file_info && (file_info->hidden || file_info->system))
     285             :                 return GF_FALSE;
     286             : 
     287           7 :         if (is_dir)
     288           1 :                 gf_dynstrcat(listing, "+  <a href=\"", NULL);
     289             :         else
     290           6 :                 gf_dynstrcat(listing, "   <a href=\"", NULL);
     291             : 
     292           7 :         name_len = (u32) strlen(item_name);
     293           7 :         if (is_dir) name_len++;
     294           7 :         gf_dynstrcat(listing, item_name, NULL);
     295           7 :         if (is_dir) gf_dynstrcat(listing, "/", NULL);
     296           7 :         gf_dynstrcat(listing, "\">", NULL);
     297           7 :         gf_dynstrcat(listing, item_name, NULL);
     298           7 :         if (is_dir) gf_dynstrcat(listing, "/", NULL);
     299           7 :         gf_dynstrcat(listing, "</a>", NULL);
     300         297 :         while (name_len<60) {
     301         290 :                 name_len++;
     302         290 :                 gf_dynstrcat(listing, " ", NULL);
     303             :         }
     304           7 :         if (file_info) {
     305             :                 char szDate[200];
     306           7 :                 httpout_format_date(file_info->last_modified*1000, szDate, GF_TRUE);
     307           7 :                 gf_dynstrcat(listing, szDate, NULL);
     308             :         }
     309             : 
     310           7 :         if (is_dir || !file_info) {
     311           1 :                 gf_dynstrcat(listing, "    -\n", NULL);
     312             :                 return GF_FALSE;
     313             :         }
     314           6 :         size = file_info->size;
     315           6 :         if (size<1000) unit="";
     316           1 :         else if (size<1000000) { unit="K"; size/=1000; }
     317           0 :         else if (size<1000000000) { unit="M"; size/=1000000; }
     318           0 :         else if (size<1000000000000) { unit="G"; size/=1000000000; }
     319           6 :         gf_dynstrcat(listing, "    ", NULL);
     320             :         sprintf(szFmt, LLU"%s\n", size, unit);
     321           6 :         gf_dynstrcat(listing, szFmt, NULL);
     322             : 
     323             :         return GF_FALSE;
     324             : }
     325           1 : static Bool httpout_dir_enum(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info)
     326             : {
     327           1 :         return httpout_dir_file_enum(cbck, item_name, item_path, file_info, GF_TRUE);
     328             : }
     329           6 : static Bool httpout_file_enum(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info)
     330             : {
     331           6 :         return httpout_dir_file_enum(cbck, item_name, item_path, file_info, GF_FALSE);
     332             : }
     333             : 
     334           2 : static char *httpout_create_listing(GF_HTTPOutCtx *ctx, char *full_path)
     335             : {
     336             :         char szHost[GF_MAX_IP_NAME_LEN];
     337             :         char *has_par, *dir;
     338           2 :         u32 i, count = ctx->rdirs.nb_items;
     339           2 :         char *listing = NULL;
     340             :         char *name = full_path;
     341             : 
     342           2 :         if (full_path && (full_path[0]=='.'))
     343           1 :                 name++;
     344             : 
     345           2 :         gf_dynstrcat(&listing, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n<html>\n<head>\n<title>Index of ", NULL);
     346           2 :         gf_dynstrcat(&listing, name, NULL);
     347           2 :         gf_dynstrcat(&listing, "</title>\n</head>\n<body><h1>Index of ", NULL);
     348           2 :         gf_dynstrcat(&listing, name, NULL);
     349           2 :         gf_dynstrcat(&listing, "</h1>\n<pre>Name                                                                Last modified      Size\n<hr>\n", NULL);
     350             : 
     351           2 :         if (!full_path) {
     352             :                 has_par=NULL;
     353             :         } else {
     354           2 :                 u32 len = (u32) strlen(full_path);
     355           2 :                 if (len && strchr("/\\", full_path[len-1]))
     356           2 :                         full_path[len-1] = 0;
     357           2 :                 has_par = strrchr(full_path, '/');
     358             :         }
     359           2 :         if (has_par) {
     360           1 :                 u8 c = has_par[1];
     361           1 :                 has_par[1] = 0;
     362             : 
     363             :                 //retranslate server root
     364           1 :                 gf_dynstrcat(&listing, ".. <a href=\"", NULL);
     365           2 :                 for (i=0; i<count; i++) {
     366           1 :                         dir = ctx->rdirs.vals[i];
     367           1 :                         u32 dlen = (u32) strlen(dir);
     368           1 :                         if (!strncmp(dir, name, dlen) && ((name[dlen]=='/') || (name[dlen]==0))) {
     369           0 :                                 gf_dynstrcat(&listing, "/", NULL);
     370           0 :                                 if (count==1) name = NULL;
     371             :                                 break;
     372             :                         }
     373             :                 }
     374             : 
     375           1 :                 if (name)
     376           1 :                         gf_dynstrcat(&listing, name, NULL);
     377             : 
     378           1 :                 gf_dynstrcat(&listing, "\">Parent Directory</a>\n", NULL);
     379           1 :                 has_par[1] = c;
     380             :         }
     381             : 
     382           2 :         if (!full_path || !strlen(full_path)) {
     383           1 :                 count = ctx->rdirs.nb_items;
     384           1 :                 if (count==1) {
     385           1 :                         dir = ctx->rdirs.vals[0];
     386           1 :                         gf_enum_directory(dir, GF_TRUE, httpout_dir_enum, &listing, NULL);
     387           1 :                         gf_enum_directory(dir, GF_FALSE, httpout_file_enum, &listing, NULL);
     388             :                 } else {
     389           0 :                         for (i=0; i<count; i++) {
     390           0 :                                 dir = ctx->rdirs.vals[i];
     391           0 :                                 httpout_dir_file_enum(&listing, dir, NULL, NULL, GF_TRUE);
     392             :                         }
     393             :                 }
     394             :         } else {
     395             :                 Bool insert_root = GF_FALSE;
     396           1 :                 if (count>1) {
     397           0 :                         for (i=0; i<count; i++) {
     398           0 :                                 dir = ctx->rdirs.vals[i];
     399           0 :                                 if (!strcmp(full_path, dir)) {
     400             :                                         insert_root=GF_TRUE;
     401             :                                         break;
     402             :                                 }
     403             :                         }
     404           0 :                         if (insert_root) {
     405           0 :                                 gf_dynstrcat(&listing, ".. <a href=\"/\">Parent Directory</a>                                 -\n", NULL);
     406             :                         }
     407             :                 }
     408           1 :                 gf_enum_directory(full_path, GF_TRUE, httpout_dir_enum, &listing, NULL);
     409           1 :                 gf_enum_directory(full_path, GF_FALSE, httpout_file_enum, &listing, NULL);
     410             :         }
     411             : 
     412           2 :         gf_dynstrcat(&listing, "\n<hr></pre>\n<address>", NULL);
     413           2 :         gf_dynstrcat(&listing, ctx->user_agent, NULL);
     414           2 :         gf_sk_get_host_name(szHost);
     415           2 :         gf_dynstrcat(&listing, " at ", NULL);
     416           2 :         gf_dynstrcat(&listing, szHost, NULL);
     417           2 :         gf_dynstrcat(&listing, " Port ", NULL);
     418           2 :         sprintf(szHost, "%d", ctx->port);
     419           2 :         gf_dynstrcat(&listing, szHost, NULL);
     420           2 :         gf_dynstrcat(&listing, "</address>\n</body></html>", NULL);
     421           2 :         return listing;
     422             : }
     423             : 
     424             : 
     425         665 : static void httpout_set_local_path(GF_HTTPOutCtx *ctx, GF_HTTPOutInput *in)
     426             : {
     427             :         char *dir;
     428             :         u32 len;
     429             :         assert(in->path);
     430             :         //not recording
     431         665 :         if (!ctx->rdirs.nb_items) return;
     432             : 
     433         664 :         dir = ctx->rdirs.vals[0];
     434         664 :         if (!dir) return;
     435         664 :         len = (u32) strlen(dir);
     436         664 :         if (in->local_path) gf_free(in->local_path);
     437         664 :         in->local_path = NULL;
     438         664 :         gf_dynstrcat(&in->local_path, dir, NULL);
     439         664 :         if (!strchr("/\\", dir[len-1]))
     440         664 :                 gf_dynstrcat(&in->local_path, "/", NULL);
     441         664 :     if (in->path[0]=='/')
     442         664 :         gf_dynstrcat(&in->local_path, in->path+1, NULL);
     443             :     else
     444           0 :         gf_dynstrcat(&in->local_path, in->path, NULL);
     445             : }
     446             : 
     447         215 : static Bool httpout_sess_parse_range(GF_HTTPOutSession *sess, char *range)
     448             : {
     449             :         Bool request_ok = GF_TRUE;;
     450             :         u32 i;
     451             :         Bool has_open_start=GF_FALSE;
     452             :         Bool has_file_end=GF_FALSE;
     453             :         u64 known_file_size;
     454         215 :         sess->nb_ranges = 0;
     455         215 :         sess->nb_bytes = 0;
     456         215 :         sess->range_idx = 0;
     457         215 :         if (!range) return GF_TRUE;
     458             : 
     459          31 :         if (sess->in_source && !sess->ctx->rdirs.nb_items)
     460             :                 return GF_FALSE;
     461             : 
     462          31 :         while (range) {
     463             :                 char *sep;
     464             :                 u32 len;
     465             :                 s64 start, end;
     466          31 :                 char *next = strchr(range, ',');
     467          31 :                 if (next) next[0] = 0;
     468             : 
     469           0 :                 while (range[0] == ' ') range++;
     470             : 
     471             :                 //unsupported unit
     472          31 :                 if (strncmp(range, "bytes=", 6)) {
     473           0 :                         return GF_FALSE;
     474             :                 }
     475          31 :                 range += 6;
     476          31 :                 sep = strchr(range, '/');
     477          31 :                 if (sep) sep[0] = 0;
     478          31 :                 len = (u32) strlen(range);
     479          31 :                 start = end = -1;
     480          31 :                 if (!len) {
     481             :                         request_ok = GF_FALSE;
     482             :                 }
     483             :                 //end range only
     484          31 :                 else if (range[0] == '-') {
     485           0 :                         if (has_file_end)
     486             :                                 request_ok = GF_FALSE;
     487             :                         has_file_end = GF_TRUE;
     488             :                         start = -1;
     489           0 :                         if (sscanf(range+1, LLD, &end) != 1)
     490             :                                 request_ok = GF_FALSE;
     491             :                 }
     492             :                 //start -> EOF
     493          31 :                 else if (range[len-1] == '-') {
     494           1 :                         if (has_open_start)
     495             :                                 request_ok = GF_FALSE;
     496             :                         has_open_start = GF_TRUE;
     497             :                         end = -1;
     498           1 :                         if (sscanf(range, LLD"-", &start) != 1)
     499             :                                 request_ok = GF_FALSE;
     500             :                 } else {
     501          30 :                         if (sscanf(range, LLD"-"LLD, &start, &end) != 2)
     502             :                                 request_ok = GF_FALSE;
     503             :                 }
     504          31 :                 if ((start==-1) && (end==-1)) {
     505             :                         request_ok = GF_FALSE;
     506             :                 }
     507             : 
     508          31 :                 if (request_ok) {
     509          31 :                         if (sess->nb_ranges >= sess->alloc_ranges) {
     510           7 :                                 sess->alloc_ranges = sess->nb_ranges + 1;
     511           7 :                                 sess->ranges = gf_realloc(sess->ranges, sizeof(HTTByteRange)*sess->alloc_ranges);
     512             :                         }
     513          31 :                         sess->ranges[sess->nb_ranges].start = start;
     514          31 :                         sess->ranges[sess->nb_ranges].end = end;
     515          31 :                         sess->nb_ranges++;
     516             :                 }
     517             : 
     518          31 :                 if (sep) sep[0] = '/';
     519          31 :                 if (!next) break;
     520           0 :                 next[0] = ',';
     521           0 :                 range = next+1;
     522           0 :                 if (!request_ok) break;
     523             :         }
     524          31 :         if (!request_ok) return GF_FALSE;
     525             : 
     526          31 :         if (sess->in_source && !sess->resource) {
     527             :                 //cannot fetch end of file it is not yet known !
     528           0 :                 if (has_file_end) return GF_FALSE;
     529           0 :                 known_file_size = sess->in_source->nb_write;
     530             :         } else {
     531          31 :                 known_file_size = sess->file_size;
     532             :         }
     533          31 :         sess->bytes_in_req = 0;
     534          61 :         for (i=0; i<sess->nb_ranges; i++) {
     535          31 :                 if (sess->ranges[i].start>=0) {
     536             :                         //if start, end is a pos in bytes in size (0-based)
     537          31 :                         if (sess->ranges[i].end==-1) {
     538           1 :                                 sess->ranges[i].end = known_file_size-1;
     539             :                         }
     540             : 
     541          31 :                         if (sess->ranges[i].end >= (s64) known_file_size) {
     542             :                                 request_ok = GF_FALSE;
     543             :                                 break;
     544             :                         }
     545          30 :                         if (sess->ranges[i].start >= (s64) known_file_size) {
     546             :                                 request_ok = GF_FALSE;
     547             :                                 break;
     548             :                         }
     549             :                 } else {
     550             :                         //no start, end is a file size
     551           0 :                         if (sess->ranges[i].end >= (s64) known_file_size) {
     552             :                                 request_ok = GF_FALSE;
     553             :                                 break;
     554             :                         }
     555           0 :                         sess->ranges[i].start = known_file_size - sess->ranges[i].end;
     556           0 :                         sess->ranges[i].end = known_file_size - 1;
     557             :                 }
     558          30 :                 sess->bytes_in_req += (sess->ranges[i].end + 1 - sess->ranges[i].start);
     559             :         }
     560             :         //if we have a single byte range request covering the entire file, reply 200 OK and not 206 partial
     561          31 :         if ((sess->nb_ranges == 1) && known_file_size && !sess->ranges[0].start && (sess->ranges[0].end==known_file_size-1))
     562           1 :                 sess->nb_ranges = 0;
     563             : 
     564          31 :         if (!request_ok) {
     565           1 :                 if (!sess->in_source || (sess->nb_ranges>1))
     566             :                         return GF_FALSE;
     567             :                 //source in progress, we accept single range - note that this could be further refined by postponing the request until the source
     568             :                 //is done or has written the requested byte range, however this will delay sending chunk in LL-HLS byterange ...
     569             :                 //for now, since we use chunk transfer in this case, we will send less data than asked and close resource using last 0-size chunk
     570             :         }
     571          31 :         sess->file_pos = sess->ranges[0].start;
     572          31 :         if (sess->resource)
     573          31 :                 gf_fseek(sess->resource, sess->file_pos, SEEK_SET);
     574             :         return GF_TRUE;
     575             : }
     576             : 
     577         216 : static Bool httpout_do_log(GF_HTTPOutSession *sess, u32 method)
     578             : {
     579         216 :         if (!sess->ctx->reqlog) return GF_FALSE;
     580             : 
     581         172 :         if (!strcmp(sess->ctx->reqlog, "*")) return GF_TRUE;
     582             : 
     583         172 :         switch (method) {
     584         153 :         case GF_HTTP_GET:
     585         153 :                 if (strstr(sess->ctx->reqlog, "GET") || strstr(sess->ctx->reqlog, "get")) return GF_TRUE;
     586             :                 break;
     587          17 :         case GF_HTTP_PUT:
     588          17 :                 if (strstr(sess->ctx->reqlog, "PUT") || strstr(sess->ctx->reqlog, "put")) return GF_TRUE;
     589             :                 break;
     590           0 :         case GF_HTTP_POST:
     591           0 :                 if (strstr(sess->ctx->reqlog, "POST") || strstr(sess->ctx->reqlog, "post")) return GF_TRUE;
     592             :                 break;
     593           2 :         case GF_HTTP_DELETE:
     594           2 :                 if (strstr(sess->ctx->reqlog, "DEL") || strstr(sess->ctx->reqlog, "del")) return GF_TRUE;
     595             :                 break;
     596           0 :         case GF_HTTP_HEAD:
     597           0 :                 if (strstr(sess->ctx->reqlog, "HEAD") || strstr(sess->ctx->reqlog, "head")) return GF_TRUE;
     598             :                 break;
     599           0 :         case GF_HTTP_OPTIONS:
     600           0 :                 if (strstr(sess->ctx->reqlog, "OPT") || strstr(sess->ctx->reqlog, "opt")) return GF_TRUE;
     601             :                 break;
     602             :         default:
     603             :                 return GF_TRUE;
     604             :         }
     605             :         return GF_FALSE;
     606             : }
     607             : 
     608             : #ifndef GPAC_DISABLE_LOG
     609         337 : static const char *get_method_name(u32 method)
     610             : {
     611         337 :         switch (method) {
     612             :         case GF_HTTP_GET: return "GET";
     613           0 :         case GF_HTTP_HEAD: return "HEAD";
     614          31 :         case GF_HTTP_PUT: return "PUT";
     615           0 :         case GF_HTTP_POST: return "POST";
     616           0 :         case GF_HTTP_DELETE: return "DELETE";
     617           0 :         case GF_HTTP_TRACE: return "TRACE";
     618           0 :         case GF_HTTP_CONNECT: return "CONNECT";
     619           0 :         case GF_HTTP_OPTIONS: return "OPTIONS";
     620           0 :         default: return "UNKNOWN";
     621             :         }
     622             : }
     623             : #endif //GPAC_DISABLE_LOG
     624             : 
     625           0 : GF_Err httpout_new_subsession(GF_HTTPOutSession *sess, u32 stream_id)
     626             : {
     627             :         GF_HTTPOutSession *sub_sess;
     628             :         GF_Err e;
     629           0 :         if (!sess || !sess->http_sess || !sess->is_h2)
     630             :                 return GF_BAD_PARAM;
     631             : 
     632             : 
     633           0 :         GF_SAFEALLOC(sub_sess, GF_HTTPOutSession);
     634           0 :         if (!sub_sess) return GF_OUT_OF_MEM;
     635           0 :         sub_sess->socket = sess->socket;
     636           0 :         sub_sess->ctx = sess->ctx;
     637             :         //mark the subsession as being h2 right away so that we can process it even if no pending data on socket (cf httpout_process_session)
     638           0 :         sub_sess->is_h2 = GF_TRUE;
     639           0 :         strcpy(sub_sess->peer_address, sess->peer_address);
     640           0 :         sub_sess->http_sess = gf_dm_sess_new_subsession(sess->http_sess, stream_id, sub_sess, &e);
     641           0 :         if (!sub_sess->http_sess) {
     642           0 :                 gf_free(sub_sess);
     643           0 :                 return e;
     644             :         }
     645           0 :         gf_list_add(sess->ctx->sessions, sub_sess);
     646           0 :         gf_list_add(sess->ctx->active_sessions, sub_sess);
     647           0 :         sess->sub_sess_pending = GF_TRUE;
     648           0 :         return GF_OK;
     649             : }
     650             : 
     651             : 
     652         385 : static void httpout_sess_io(void *usr_cbk, GF_NETIO_Parameter *parameter)
     653             : {
     654             :         const char *durl="";
     655             :         char *url=NULL;
     656         385 :         char *full_path=NULL;
     657             :         char szFmt[100];
     658             :         char szDate[200];
     659             :         char szETag[100];
     660             :         u64 modif_time=0;
     661             :         u32 body_size=0;
     662             :         const char *etag=NULL, *range=NULL;
     663             :         const char *mime = NULL;
     664         385 :         char *response_body = NULL;
     665             :         GF_Err e=GF_OK;
     666             :         Bool not_modified = GF_FALSE;
     667             :         Bool is_upload = GF_FALSE;
     668             :         Bool is_head = GF_FALSE;
     669             :         Bool no_body = GF_FALSE;
     670             :         Bool send_cors;
     671             :         u32 i, count;
     672             :         GF_HTTPOutInput *source_pid = NULL;
     673             :         Bool source_pid_is_ll_hls_chunk = GF_FALSE;
     674             :         GF_HTTPOutSession *source_sess = NULL;
     675             :         GF_HTTPOutSession *sess = usr_cbk;
     676             : 
     677         385 :         if (parameter->msg_type == GF_NETIO_REQUEST_SESSION) {
     678           0 :                 parameter->error = httpout_new_subsession(sess, parameter->reply);
     679           0 :                 return;
     680             :         }
     681         385 :         if (parameter->msg_type == GF_NETIO_CANCEL_STREAM) {
     682           0 :                 sess->canceled = GF_TRUE;
     683           0 :                 return;
     684             :         }
     685             : 
     686         385 :         if (parameter->msg_type != GF_NETIO_PARSE_REPLY) {
     687         169 :                 parameter->error = GF_BAD_PARAM;
     688         169 :                 return;
     689             :         }
     690             : 
     691             :         send_cors = GF_FALSE;
     692         216 :         sess->reply_code = 0;
     693         216 :         switch (parameter->reply) {
     694             :         case GF_HTTP_GET:
     695             :         case GF_HTTP_HEAD:
     696             :                 break;
     697          30 :         case GF_HTTP_PUT:
     698             :         case GF_HTTP_POST:
     699             :         case GF_HTTP_DELETE:
     700             :                 is_upload = GF_TRUE;
     701          30 :                 break;
     702           0 :         default:
     703           0 :                 sess->reply_code = 501;
     704           0 :                 gf_dynstrcat(&response_body, "Method is not supported by GPAC", NULL);
     705           0 :                 goto exit;
     706             :         }
     707         216 :         durl = gf_dm_sess_get_resource_name(sess->http_sess);
     708         216 :         if (!durl || (durl[0] != '/')) {
     709           0 :                 sess->reply_code = 400;
     710           0 :                 goto exit;
     711             :         }
     712         216 :         url = gf_url_percent_decode(durl);
     713             : 
     714         216 :         sess->file_pos = 0;
     715         216 :         sess->file_size = 0;
     716         216 :         sess->bytes_in_req = 0;
     717         216 :         sess->nb_ranges = 0;
     718         216 :         sess->do_log = httpout_do_log(sess, parameter->reply);
     719             : 
     720             :         //resolve name against upload dir
     721         216 :         if (is_upload) {
     722          30 :                 if (!sess->ctx->wdir && (sess->ctx->hmode!=MODE_SOURCE)) {
     723           0 :                         sess->reply_code = 405;
     724           0 :                         gf_dynstrcat(&response_body, "No write directory enabled on server", NULL);
     725           0 :                         goto exit;
     726             :                 }
     727          30 :                 if (sess->ctx->wdir) {
     728          29 :                         u32 len = (u32) strlen(sess->ctx->wdir);
     729          29 :                         gf_dynstrcat(&full_path, sess->ctx->wdir, NULL);
     730          29 :                         if (!strchr("/\\", sess->ctx->wdir[len-1]))
     731          29 :                                 gf_dynstrcat(&full_path, "/", NULL);
     732          29 :                         gf_dynstrcat(&full_path, url+1, NULL);
     733           1 :                 } else if (sess->ctx->hmode==MODE_SOURCE) {
     734           1 :                         full_path = gf_strdup(url+1);
     735             :                 }
     736          30 :                 if (parameter->reply==GF_HTTP_DELETE)
     737             :                         is_upload = GF_FALSE;
     738             :         }
     739             : 
     740         214 :         if (is_upload) {
     741             :                 const char *hdr;
     742          28 :                 range = gf_dm_sess_get_header(sess->http_sess, "Range");
     743             : 
     744          28 :                 if (sess->in_source) {
     745           0 :                         sess->in_source->nb_dest--;
     746           0 :                         sess->in_source = NULL;
     747             :                 }
     748          28 :                 sess->content_length = 0;
     749          28 :                 hdr = gf_dm_sess_get_header(sess->http_sess, "Content-Length");
     750          28 :                 if (hdr) {
     751           0 :                         sscanf(hdr, LLU, &sess->content_length);
     752             :                 }
     753          28 :                 sess->use_chunk_transfer = GF_FALSE;
     754          28 :                 hdr = gf_dm_sess_get_header(sess->http_sess, "Transfer-Encoding");
     755          28 :                 if (hdr && !strcmp(hdr, "chunked")) {
     756          28 :                         sess->use_chunk_transfer = GF_TRUE;
     757           0 :                 } else if (!sess->is_h2) {
     758           0 :                         sess->is_h2 = gf_dm_sess_is_h2(sess->http_sess);
     759             :                 }
     760          28 :                 sess->file_in_progress = GF_FALSE;
     761          28 :                 sess->nb_bytes = 0;
     762          28 :                 sess->done = GF_FALSE;
     763             :                 assert(full_path);
     764          28 :                 if (sess->path) gf_free(sess->path);
     765          28 :                 sess->path = full_path;
     766          28 :                 if (sess->resource) gf_fclose(sess->resource);
     767          28 :                 sess->resource = NULL;
     768             : 
     769          28 :                 if (sess->ctx->hmode==MODE_SOURCE) {
     770           1 :                         if (range) {
     771           0 :                                 GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[HTTPOut] Cannot handle PUT/POST request as PID output with byte ranges (%s)\n", range));
     772           0 :                                 sess->reply_code = 416;
     773           0 :                                 gf_dynstrcat(&response_body, "Server running in source mode - cannot handle PUT/POST request with byte ranges ", NULL);
     774           0 :                                 gf_dynstrcat(&response_body, range, NULL);
     775           0 :                                 goto exit;
     776             :                         }
     777           1 :                         sess->upload_type = 1;
     778           1 :                         sess->reconfigure_output = GF_TRUE;
     779             :                 } else {
     780          27 :                         if (gf_file_exists(sess->path))
     781          16 :                                 sess->upload_type = 2;
     782             :                         else
     783          11 :                                 sess->upload_type = 1;
     784             : 
     785          27 :                         sess->resource = gf_fopen(sess->path, range ? "rb+" : "wb");
     786          27 :                         if (!sess->resource) {
     787           0 :                                 sess->reply_code = 403;
     788           0 :                                 gf_dynstrcat(&response_body, "File exists but cannot be open", NULL);
     789           0 :                                 goto exit;
     790             :                         }
     791          27 :                         if (!sess->content_length && !sess->use_chunk_transfer && !sess->is_h2) {
     792           0 :                                 sess->reply_code = 411;
     793           0 :                                 gf_dynstrcat(&response_body, "No content length specified and chunked transfer not enabled", NULL);
     794           0 :                                 goto exit;
     795             :                         }
     796          27 :                         sess->file_size = gf_fsize(sess->resource);
     797             :                 }
     798          28 :                 sess->file_pos = 0;
     799             : 
     800          28 :                 range = gf_dm_sess_get_header(sess->http_sess, "Range");
     801          28 :                 if (! httpout_sess_parse_range(sess, (char *) range) ) {
     802           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[HTTPOut] Unsupported Range format: %s\n", range));
     803           0 :                         sess->reply_code = 416;
     804           0 :                         gf_dynstrcat(&response_body, "Range format is not supported, only \"bytes\" units allowed: ", NULL);
     805           0 :                         gf_dynstrcat(&response_body, range, NULL);
     806           0 :                         goto exit;
     807             :                 }
     808          28 :                 if (!sess->buffer) {
     809          12 :                         sess->buffer = gf_malloc(sizeof(u8)*sess->ctx->block_size);
     810             :                 }
     811          28 :                 if (gf_list_find(sess->ctx->active_sessions, sess)<0) {
     812           0 :                         gf_list_add(sess->ctx->active_sessions, sess);
     813           0 :                         gf_sk_group_register(sess->ctx->sg, sess->socket);
     814             :                 }
     815          28 :                 sess->last_active_time = gf_sys_clock_high_res();
     816             : 
     817          28 :                 if (sess->do_log) {
     818          17 :                         sess->req_id = ++sess->ctx->req_id;
     819          17 :                         sess->method_type = parameter->reply;
     820          17 :                         if (range) {
     821           0 :                                 GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("[HTTPOut] REQ#"LLU" %s %s %s [range: %s] start%s\n", sess->req_id, sess->peer_address, get_method_name(sess->method_type), url+1, range, sess->use_chunk_transfer ? " chunk-transfer" : ""));
     822             :                         } else {
     823          17 :                                 GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("[HTTPOut] REQ#"LLU" %s %s %s start%s\n", sess->req_id, sess->peer_address, get_method_name(sess->method_type), url+1, sess->use_chunk_transfer ? " chunk-transfer" : ""));
     824             :                         }
     825             :                 }
     826          28 :                 sess->nb_consecutive_errors = 0;
     827          28 :                 sess->req_start_time = gf_sys_clock_high_res();
     828          28 :                 if (url) gf_free(url);
     829          28 :                 gf_dm_sess_clear_headers(sess->http_sess);
     830             :                 //send reply once we are done receiving
     831          28 :                 return;
     832             :         }
     833             : 
     834             :         /*first check active inputs*/
     835         188 :         count = gf_list_count(sess->ctx->inputs);
     836             :         //delete only accepts local files
     837         188 :         if (parameter->reply == GF_HTTP_DELETE)
     838             :                 count = 0;
     839             : 
     840         584 :         for (i=0; i<count; i++) {
     841         418 :                 GF_HTTPOutInput *in = gf_list_get(sess->ctx->inputs, i);
     842             :                 assert(in->path[0] == '/');
     843             :                 //matching name and input pid not done: file has been created and is in progress
     844             :                 //if input pid done, try from file
     845         418 :                 if (!strcmp(in->path, url) && !in->done) {
     846             :                         source_pid = in;
     847             :                         break;
     848             :                 }
     849         397 :                 if (in->hls_chunk_path && !strcmp(in->hls_chunk_path, url) && !in->done) {
     850             :                         source_pid = in;
     851             :                         source_pid_is_ll_hls_chunk = GF_TRUE;
     852             :                         break;
     853             :                 }
     854             :         }
     855             : 
     856             :         /*not resolved and no source matching, check file on disk*/
     857         188 :         if (!source_pid && !full_path) {
     858         164 :                 count = sess->ctx->rdirs.nb_items;
     859         164 :                 for (i=0; i<count; i++) {
     860         164 :                         char *mdir = sess->ctx->rdirs.vals[i];
     861         164 :                         u32 len = (u32) strlen(mdir);
     862         164 :                         if (!len) continue;
     863         164 :                         if (count==1) {
     864         164 :                                 gf_dynstrcat(&full_path, mdir, NULL);
     865         164 :                                 if (!strchr("/\\", mdir[len-1]))
     866         164 :                                         gf_dynstrcat(&full_path, "/", NULL);
     867             :                         }
     868         164 :                         gf_dynstrcat(&full_path, url+1, NULL);
     869             : 
     870         164 :                         if (gf_file_exists(full_path) || gf_dir_exists(full_path) )
     871             :                                 break;
     872           0 :                         gf_free(full_path);
     873           0 :                         full_path = NULL;
     874             :                 }
     875             :         }
     876             : 
     877         188 :         switch (sess->ctx->cors) {
     878             :         case CORS_ON:
     879             :                 send_cors = GF_TRUE;
     880             :                 break;
     881         188 :         case CORS_AUTO:
     882         188 :                 if (gf_dm_sess_get_header(sess->http_sess, "Origin") != NULL) {
     883             :                         send_cors = GF_TRUE;
     884             :                         break;
     885             :                 }
     886             :         default:
     887             :                 send_cors = GF_FALSE;
     888             :                 break;
     889             :         }
     890             : 
     891         188 :         if (!full_path && !source_pid) {
     892           0 :                 if (!sess->ctx->dlist || strcmp(url, "/")) {
     893           0 :                         sess->reply_code = 404;
     894           0 :                         gf_dynstrcat(&response_body, "Resource ", NULL);
     895           0 :                         gf_dynstrcat(&response_body, url, NULL);
     896           0 :                         gf_dynstrcat(&response_body, " cannot be resolved", NULL);
     897           0 :                         goto exit;
     898             :                 }
     899             :         }
     900             : 
     901             :         //check if request is HEAD or GET on a file being uploaded
     902         188 :         if (full_path && ((parameter->reply == GF_HTTP_GET) || (parameter->reply == GF_HTTP_HEAD))) {
     903         164 :                 count = gf_list_count(sess->ctx->sessions);
     904         470 :                 for (i=0; i<count; i++) {
     905         306 :                         source_sess = gf_list_get(sess->ctx->sessions, i);
     906         306 :                         if ((source_sess != sess) && !source_sess->done && source_sess->upload_type && !strcmp(source_sess->path, full_path)) {
     907             :                                 break;
     908             :                         }
     909             :                         source_sess = NULL;
     910             :                 }
     911             :         }
     912             : 
     913         188 :         szETag[0] = 0;
     914             : 
     915             :         //session is on an active input being uploaded, always consider as modified
     916         188 :         if (source_pid && !sess->ctx->single_mode) {
     917             :                 etag = NULL;
     918             :         }
     919             :         //resource is being uploaded, always consider as modified
     920         167 :         else if (source_sess) {
     921             :                 etag = NULL;
     922             :         }
     923             :         //check ETag
     924         167 :         else if (full_path) {
     925         166 :                 modif_time = gf_file_modification_time(full_path);
     926             :                 sprintf(szETag, LLU, modif_time);
     927         166 :                 etag = gf_dm_sess_get_header(sess->http_sess, "If-None-Match");
     928             :         }
     929             : 
     930         188 :         range = gf_dm_sess_get_header(sess->http_sess, "Range");
     931             : 
     932         188 :         if (sess->in_source) {
     933           0 :                 sess->in_source->nb_dest--;
     934           0 :                 sess->in_source = NULL;
     935             :         }
     936         188 :         sess->file_in_progress = GF_FALSE;
     937         188 :         sess->use_chunk_transfer = GF_FALSE;
     938         188 :         sess->put_in_progress = 0;
     939         188 :         sess->nb_bytes = 0;
     940         188 :         sess->upload_type = 0;
     941             : 
     942         188 :         if (parameter->reply==GF_HTTP_DELETE) {
     943             :                 no_body = GF_TRUE;
     944             :                 sess->upload_type = 0;
     945           2 :                 if (sess->path) gf_free(sess->path);
     946           2 :                 sess->path = full_path;
     947           2 :                 if (sess->resource) gf_fclose(sess->resource);
     948           2 :                 sess->resource = NULL;
     949           2 :                 sess->file_pos = sess->file_size = 0;
     950             : 
     951           2 :                 if (gf_file_exists(full_path)) {
     952           2 :                         e = gf_file_delete(full_path);
     953             : 
     954           2 :                         if (e) {
     955           0 :                                 sess->reply_code = 500;
     956           0 :                                 GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[HTTPOut] Error deleting file %s (full path %s)\n", url, full_path));
     957           0 :                                 sess->reply_code = 500;
     958           0 :                                 gf_dynstrcat(&response_body, "Error while deleting ", NULL);
     959           0 :                                 gf_dynstrcat(&response_body, url, NULL);
     960           0 :                                 gf_dynstrcat(&response_body, ": ", NULL);
     961           0 :                                 gf_dynstrcat(&response_body, gf_error_to_string(e), NULL);
     962           0 :                                 goto exit;
     963             :                         }
     964           2 :                         GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[HTTPOut] Deleting file %s (full path %s)\n", url, full_path));
     965             : 
     966           2 :                         if (sess->do_log) {
     967           2 :                                 sess->req_id = ++sess->ctx->req_id;
     968           2 :                                 GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("[HTTPOut] REQ#"LLU" %s DELETE %s\n", sess->req_id, sess->peer_address, url+1));
     969           2 :                                 sess->do_log = GF_FALSE;
     970             :                         }
     971             :                 } else {
     972             :                         e = GF_URL_ERROR;
     973             :                 }
     974             :                 range = NULL;
     975             :         }
     976             :         /*we have a source and we are not in record mode */
     977         186 :         else if (source_pid && sess->ctx->single_mode) {
     978           1 :                 if (sess->path) gf_free(sess->path);
     979           1 :                 sess->path = NULL;
     980           1 :                 sess->in_source = source_pid;
     981           1 :                 source_pid->nb_dest++;
     982           1 :                 sess->send_init_data = GF_TRUE;
     983           1 :                 source_pid->hold = GF_FALSE;
     984             : 
     985           1 :                 sess->file_pos = sess->file_size = 0;
     986           1 :                 sess->use_chunk_transfer = GF_TRUE;
     987             :         }
     988             :         /*we have matching etag*/
     989         185 :         else if (etag && !strcmp(etag, szETag) && !sess->ctx->no_etag) {
     990           0 :                 if (sess->path) gf_free(sess->path);
     991           0 :                 sess->path = full_path;
     992           0 :                 not_modified = GF_TRUE;
     993             :         }
     994             :         /*we have the same URL, source file is setup and not modified, no need to resetup - byte-range is setup after */
     995         185 :         else if (!sess->in_source && sess->resource && (sess->last_file_modif == modif_time) && sess->path && full_path && !strcmp(sess->path, full_path) ) {
     996           0 :                 gf_free(full_path);
     997           0 :                 sess->file_pos = 0;
     998             :         }
     999             :         else {
    1000         185 :                 if (sess->path) gf_free(sess->path);
    1001         185 :                 if (source_pid) {
    1002             :                         //source_pid->resource may be NULL at this point (PID created but no data written yet), typically happens on manifest PIDs or init segments
    1003          21 :                         sess->in_source = source_pid;
    1004          21 :                         source_pid->nb_dest++;
    1005          21 :                         source_pid->hold = GF_FALSE;
    1006          21 :                         sess->send_init_data = GF_TRUE;
    1007          21 :                         sess->in_source_is_ll_hls_chunk = source_pid_is_ll_hls_chunk;
    1008             : 
    1009          21 :                         sess->file_in_progress = GF_TRUE;
    1010             :                         assert(!full_path);
    1011             :                         assert(source_pid->local_path);
    1012          21 :                         if (source_pid_is_ll_hls_chunk)
    1013           1 :                                 full_path = gf_strdup(source_pid->hls_chunk_local_path);
    1014             :                         else
    1015          20 :                                 full_path = gf_strdup(source_pid->local_path);
    1016          21 :                         sess->use_chunk_transfer = GF_TRUE;
    1017          21 :                         sess->file_size = 0;
    1018             :                 }
    1019         185 :                 sess->path = full_path;
    1020         185 :                 if (!full_path || gf_dir_exists(full_path)) {
    1021           4 :                         if (sess->ctx->dlist) {
    1022             :                                 range = NULL;
    1023           2 :                                 if (!strcmp(url, "/")) {
    1024           1 :                                         response_body = httpout_create_listing(sess->ctx, (char *) url);
    1025             :                                 } else {
    1026           1 :                                         response_body = httpout_create_listing(sess->ctx, full_path);
    1027             :                                 }
    1028           2 :                                 sess->file_size = sess->file_pos = 0;
    1029             :                         } else {
    1030           0 :                                 sess->reply_code = 403;
    1031           0 :                                 gf_dynstrcat(&response_body, "Directory browsing is not allowed", NULL);
    1032           0 :                                 goto exit;
    1033             :                         }
    1034             :                 } else {
    1035         183 :                         sess->resource = gf_fopen(full_path, "rb");
    1036             :                         //we may not have the file if it is currently being created
    1037         183 :                         if (!sess->resource && !sess->in_source) {
    1038           0 :                                 sess->reply_code = 500;
    1039           0 :                                 gf_dynstrcat(&response_body, "File exists but no read access", NULL);
    1040           0 :                                 goto exit;
    1041             :                         }
    1042             :                         //warning, sess->resource may still be NULL here !
    1043             : 
    1044         183 :                         mime = source_pid ? source_pid->mime : NULL;
    1045             :                         //probe for mime
    1046          21 :                         if (!mime && sess->resource) {
    1047             :                                 u8 probe_buf[5001];
    1048         162 :                                 u32 read = (u32) gf_fread(probe_buf, 5000, sess->resource);
    1049         162 :                                 if ((s32) read < 0) {
    1050           0 :                                         if (source_sess) {
    1051             :                                                 read = 0;
    1052             :                                         } else {
    1053           0 :                                                 sess->reply_code = 500;
    1054           0 :                                                 gf_dynstrcat(&response_body, "File opened but read operation failed", NULL);
    1055           0 :                                                 goto exit;
    1056             :                                         }
    1057             :                                 }
    1058         162 :                                 if (read) {
    1059         162 :                                         probe_buf[read] = 0;
    1060         162 :                                         mime = gf_filter_probe_data(sess->ctx->filter, probe_buf, read);
    1061             :                                 }
    1062             :                         }
    1063         183 :                         if (source_sess) {
    1064           0 :                                 sess->file_size = 0;
    1065           0 :                                 sess->use_chunk_transfer = GF_TRUE;
    1066           0 :                                 sess->put_in_progress = 1;
    1067         183 :                         } else if (sess->resource) {
    1068             :                                 //get file size, might be incomplete if file writing is in progress
    1069         183 :                                 sess->file_size = gf_fsize(sess->resource);
    1070             :                         } else {
    1071           0 :                                 sess->file_size = 0;
    1072             :                         }
    1073             :                 }
    1074         185 :                 sess->file_pos = 0;
    1075         185 :                 sess->bytes_in_req = sess->file_size;
    1076             : 
    1077         185 :                 if (sess->mime) gf_free(sess->mime);
    1078         185 :                 sess->mime = ( mime && strcmp(mime, "*")) ? gf_strdup(mime) : NULL;
    1079         185 :                 sess->last_file_modif = gf_file_modification_time(full_path);
    1080             :         }
    1081             : 
    1082             :         //parse byte range except if associated input in single mode where byte ranges are ignored
    1083         188 :         if ( (!sess->in_source || !sess->ctx->single_mode) && ! httpout_sess_parse_range(sess, (char *) range) ) {
    1084           0 :                 GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[HTTPOut] Unsupported Range format: %s\n", range));
    1085           0 :                 sess->reply_code = 416;
    1086           0 :                 gf_dynstrcat(&response_body, "Range format is not supported, only \"bytes\" units allowed: ", NULL);
    1087           0 :                 gf_dynstrcat(&response_body, range, NULL);
    1088           0 :                 goto exit;
    1089             :         }
    1090             : 
    1091         188 :         if (not_modified) {
    1092           0 :                 sess->reply_code = 304;
    1093         188 :         } else if (sess->nb_ranges) {
    1094          30 :                 sess->reply_code = 206;
    1095         158 :         } else if ((parameter->reply==GF_HTTP_DELETE) && (e==GF_URL_ERROR)) {
    1096           0 :                 sess->reply_code = 204;
    1097             :         } else {
    1098         158 :                 sess->reply_code = 200;
    1099             :         }
    1100             : 
    1101         188 :         gf_dm_sess_clear_headers(sess->http_sess);
    1102             : 
    1103         188 :         gf_dm_sess_set_header(sess->http_sess, "Server", sess->ctx->user_agent);
    1104             : 
    1105         188 :         httpout_format_date(gf_net_get_utc(), szDate, GF_FALSE);
    1106         188 :         gf_dm_sess_set_header(sess->http_sess, "Date", szDate);
    1107             : 
    1108         188 :         if (send_cors) {
    1109           0 :                 gf_dm_sess_set_header(sess->http_sess, "Access-Control-Allow-Origin", "*");
    1110           0 :                 gf_dm_sess_set_header(sess->http_sess, "Access-Control-Expose-Headers", "*");
    1111             :         }
    1112         188 :         if (sess->ctx->sutc) {
    1113           5 :                 sprintf(szFmt, LLU, gf_net_get_utc() );
    1114           5 :                 gf_dm_sess_set_header(sess->http_sess, "Server-UTC", szFmt);
    1115             :         }
    1116             : 
    1117         188 :         if (parameter->reply == GF_HTTP_HEAD) {
    1118             :                 is_head = GF_TRUE;
    1119             :                 no_body = GF_TRUE;
    1120             :         }
    1121             : 
    1122         188 :         if (sess->ctx->close) {
    1123           0 :                 gf_dm_sess_set_header(sess->http_sess, "Connection", "close");
    1124             :         } else {
    1125         188 :                 gf_dm_sess_set_header(sess->http_sess, "Connection", "keep-alive");
    1126         188 :                 if (sess->ctx->timeout) {
    1127             :                         sprintf(szFmt, "timeout=%d", sess->ctx->timeout);
    1128         188 :                         gf_dm_sess_set_header(sess->http_sess, "Keep-Alive", szFmt);
    1129             :                 }
    1130             :         }
    1131             : 
    1132         188 :         if (response_body) {
    1133           2 :                 body_size = (u32) strlen(response_body);
    1134           2 :                 gf_dm_sess_set_header(sess->http_sess, "Content-Type", "text/html");
    1135             :                 sprintf(szFmt, "%d", body_size);
    1136           2 :                 gf_dm_sess_set_header(sess->http_sess, "Content-Length", szFmt);
    1137             :         }
    1138             :         //for HEAD/GET only
    1139         186 :         else if (!not_modified && (parameter->reply!=GF_HTTP_DELETE) ) {
    1140         184 :                 if (!sess->in_source && !sess->ctx->no_etag && szETag[0]) {
    1141         162 :                         gf_dm_sess_set_header(sess->http_sess, "ETag", szETag);
    1142         162 :                         if (sess->ctx->cache_control) {
    1143           0 :                                 gf_dm_sess_set_header(sess->http_sess, "Cache-Control", sess->ctx->cache_control);
    1144             :                         }
    1145          22 :                 } else if (sess->in_source && !sess->ctx->rdirs.nb_items) {
    1146           1 :                         sess->nb_ranges = 0;
    1147           1 :                         gf_dm_sess_set_header(sess->http_sess, "Cache-Control", "no-cache, no-store");
    1148             :                 }
    1149             :                 //only put content length if not using chunk transfer - bytes_in_req may be > 0 if we have a byte range on a chunk-transfer session
    1150         184 :                 if (sess->bytes_in_req && !sess->use_chunk_transfer) {
    1151             :                         sprintf(szFmt, LLU, sess->bytes_in_req);
    1152         162 :                         gf_dm_sess_set_header(sess->http_sess, "Content-Length", szFmt);
    1153             :                 }
    1154         184 :                 mime = sess->in_source ? sess->in_source->mime : mime;
    1155         184 :                 if (mime && !strcmp(mime, "*")) mime = NULL;
    1156         164 :                 if (mime) {
    1157           2 :                         gf_dm_sess_set_header(sess->http_sess, "Content-Type", mime);
    1158             :                 }
    1159             :                 //data comes either directly from source pid, or from file written by source pid, we must use chunk transfer
    1160         184 :                 if (!is_head && sess->use_chunk_transfer) {
    1161          22 :                         gf_dm_sess_set_header(sess->http_sess, "Transfer-Encoding", "chunked");
    1162             :                 }
    1163             : 
    1164         184 :                 if (!is_head && sess->nb_ranges) {
    1165          30 :                         char *ranges = NULL;
    1166          30 :                         gf_dynstrcat(&ranges, "bytes=", NULL);
    1167          60 :                         for (i=0; i<sess->nb_ranges; i++) {
    1168          30 :                                 if (sess->in_source || !sess->file_size) {
    1169           1 :                                         sprintf(szFmt, LLD"-"LLD"/*", sess->ranges[i].start, sess->ranges[i].end);
    1170             :                                 } else {
    1171          29 :                                         sprintf(szFmt, LLD"-"LLD"/"LLU, sess->ranges[i].start, sess->ranges[i].end, sess->file_size);
    1172             :                                 }
    1173          30 :                                 gf_dynstrcat(&ranges, szFmt, i ? ", " : NULL);
    1174             :                         }
    1175          30 :                         gf_dm_sess_set_header(sess->http_sess, "Content-Range", ranges);
    1176          30 :                         gf_free(ranges);
    1177             :                 }
    1178             : 
    1179         184 :                 if (sess->in_source && sess->ctx->ice) {
    1180             :                         const GF_PropertyValue *p;
    1181             :                         u32 sr=0, br=0, nb_ch=0, p_idx;
    1182             :                         u32 w=0, h=0;
    1183           0 :                         p = gf_filter_pid_get_property(sess->in_source->ipid, GF_PROP_PID_SAMPLE_RATE);
    1184           0 :                         if (p) sr = p->value.uint;
    1185           0 :                         p = gf_filter_pid_get_property(sess->in_source->ipid, GF_PROP_PID_NUM_CHANNELS);
    1186           0 :                         if (p) nb_ch = p->value.uint;
    1187           0 :                         p = gf_filter_pid_get_property(sess->in_source->ipid, GF_PROP_PID_BITRATE);
    1188           0 :                         if (p) br = p->value.uint;
    1189             : 
    1190           0 :                         p = gf_filter_pid_get_property(sess->in_source->ipid, GF_PROP_PID_WIDTH);
    1191           0 :                         if (p) w = p->value.uint;
    1192           0 :                         p = gf_filter_pid_get_property(sess->in_source->ipid, GF_PROP_PID_HEIGHT);
    1193           0 :                         if (p) h = p->value.uint;
    1194             : 
    1195           0 :                         if (sr || br || nb_ch) {
    1196           0 :                                 if (sr && br && nb_ch)
    1197             :                                         sprintf(szFmt, "samplerate=%d;channels=%d;bitrate=%d", sr, nb_ch, br);
    1198           0 :                                 else if (sr && nb_ch)
    1199             :                                         sprintf(szFmt, "samplerate=%d;channels=%d", sr, nb_ch);
    1200           0 :                                 else if (sr && br)
    1201             :                                         sprintf(szFmt, "samplerate=%d;bitrate=%d", sr, br);
    1202           0 :                                 else if (nb_ch && br)
    1203             :                                         sprintf(szFmt, "channels=%d;bitrate=%d", nb_ch, br);
    1204           0 :                                 else if (nb_ch)
    1205             :                                         sprintf(szFmt, "channels=%d", nb_ch);
    1206             :                                 else
    1207             :                                         sprintf(szFmt, "bitrate=%d", br);
    1208             : 
    1209           0 :                                 gf_dm_sess_set_header(sess->http_sess, "ice-audio-info", szFmt);
    1210             :                         }
    1211           0 :                         if (w && h) {
    1212             :                                 sprintf(szFmt, "width=%d;height=%d", w, h);
    1213           0 :                                 gf_dm_sess_set_header(sess->http_sess, "ice-video-info", szFmt);
    1214             :                         }
    1215             : 
    1216           0 :                         if (br) {
    1217             :                                 sprintf(szFmt, "%d", br);
    1218           0 :                                 gf_dm_sess_set_header(sess->http_sess, "icy-br", szFmt);
    1219             :                         }
    1220           0 :                         gf_dm_sess_set_header(sess->http_sess, "icy-pub", "1");
    1221           0 :                         p = gf_filter_pid_get_property(sess->in_source->ipid, GF_PROP_PID_SERVICE_NAME);
    1222           0 :                         if (p && p->value.string) {
    1223           0 :                                 gf_dm_sess_set_header(sess->http_sess, "icy-name", p->value.string);
    1224             :                         }
    1225           0 :                         p_idx = 0;
    1226             :                         while (1) {
    1227             :                                 const char *pname;
    1228           0 :                                 p = gf_filter_pid_enum_properties(sess->in_source->ipid, &p_idx, NULL, &pname);
    1229           0 :                                 if (!p) break;
    1230           0 :                                 if (!pname || strncmp(pname, "ice-", 4)) continue;
    1231           0 :                                 if (!p->value.string) continue;
    1232           0 :                                 gf_dm_sess_set_header(sess->http_sess, pname, p->value.string);
    1233             :                         }
    1234             :                 }
    1235             :         }
    1236             : 
    1237         188 :         GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTPOut] Sending response to %s\n", sess->peer_address));
    1238             : 
    1239         188 :         if (sess->do_log) {
    1240         153 :                 sess->req_id = ++sess->ctx->req_id;
    1241         153 :                 sess->method_type = parameter->reply;
    1242         153 :                 sess->req_start_time = gf_sys_clock_high_res();
    1243         153 :                 if (not_modified) {
    1244           0 :                         GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("[HTTPOut] REQ#"LLU" %s %s %s: reply %d\n", sess->req_id, sess->peer_address, get_method_name(sess->method_type), url+1, sess->reply_code));
    1245         153 :                 } else if (range) {
    1246          11 :                         GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("[HTTPOut] REQ#"LLU" %s %s %s [range: %s] start%s\n", sess->req_id, sess->peer_address, get_method_name(sess->method_type), url+1, range, sess->use_chunk_transfer ? " chunk-transfer" : ""));
    1247             :                 } else {
    1248         142 :                         GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("[HTTPOut] REQ#"LLU" %s %s %s start%s\n", sess->req_id, sess->peer_address, get_method_name(sess->method_type), url+1, sess->use_chunk_transfer ? " chunk-transfer" : ""));
    1249             :                 }
    1250             :         }
    1251             : 
    1252         188 :         sess->nb_consecutive_errors = 0;
    1253         188 :         sess->canceled = GF_FALSE;
    1254         188 :         e = gf_dm_sess_send_reply(sess->http_sess, sess->reply_code, response_body, no_body);
    1255         188 :         sess->headers_done = GF_TRUE;
    1256         188 :         sess->is_h2 = gf_dm_sess_is_h2(sess->http_sess);
    1257             : 
    1258         188 :         if (url) gf_free(url);
    1259         188 :         if (!sess->buffer) {
    1260          35 :                 sess->buffer = gf_malloc(sizeof(u8)*sess->ctx->block_size);
    1261             :         }
    1262         188 :         if (response_body) {
    1263           2 :                 gf_free(response_body);
    1264           2 :                 sess->done = GF_TRUE;
    1265         186 :         } else if (parameter->reply == GF_HTTP_DELETE) {
    1266           2 :                 sess->done = GF_TRUE;
    1267         184 :         } else if (parameter->reply == GF_HTTP_HEAD) {
    1268           0 :                 sess->done = GF_FALSE;
    1269           0 :                 sess->file_pos = sess->file_size;
    1270             :         } else {
    1271         184 :                 sess->done = GF_FALSE;
    1272         184 :                 if (gf_list_find(sess->ctx->active_sessions, sess)<0) {
    1273           0 :                         gf_list_add(sess->ctx->active_sessions, sess);
    1274           0 :                         gf_sk_group_register(sess->ctx->sg, sess->socket);
    1275             :                 }
    1276         184 :                 if (not_modified) {
    1277           0 :                         sess->done = GF_TRUE;
    1278             :                 }
    1279             :         }
    1280             : 
    1281         188 :         if (e<0) {
    1282           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Error sending reply: %s\n", gf_error_to_string(e)));
    1283           0 :                 sess->done = GF_TRUE;
    1284             :         }
    1285             : 
    1286         188 :         if (sess->done)
    1287           4 :                 sess->headers_done = GF_FALSE;
    1288             : 
    1289         188 :         gf_dm_sess_clear_headers(sess->http_sess);
    1290             : 
    1291         188 :         sess->last_active_time = gf_sys_clock_high_res();
    1292         188 :         return;
    1293             : 
    1294           0 : exit:
    1295             : 
    1296           0 :         gf_dm_sess_clear_headers(sess->http_sess);
    1297             : 
    1298           0 :         gf_dm_sess_set_header(sess->http_sess, "Server", sess->ctx->user_agent);
    1299             : 
    1300           0 :         httpout_format_date(gf_net_get_utc(), szDate, GF_FALSE);
    1301           0 :         gf_dm_sess_set_header(sess->http_sess, "Date", szDate);
    1302             : 
    1303           0 :         sess->nb_consecutive_errors++;
    1304           0 :         if (sess->nb_consecutive_errors == sess->ctx->max_client_errors) {
    1305           0 :                 gf_dm_sess_set_header(sess->http_sess, "Connection", "close");
    1306             :         } else {
    1307           0 :                 sess->last_active_time = gf_sys_clock_high_res();
    1308             :         }
    1309             : 
    1310           0 :         if (send_cors) {
    1311           0 :                 gf_dm_sess_set_header(sess->http_sess, "Access-Control-Allow-Origin", "*");
    1312           0 :                 gf_dm_sess_set_header(sess->http_sess, "Access-Control-Expose-Headers", "*");
    1313             :         }
    1314           0 :         if (response_body) {
    1315           0 :                 body_size = (u32) strlen(response_body);
    1316           0 :                 gf_dm_sess_set_header(sess->http_sess, "Content-Type", "text/plain");
    1317             :                 sprintf(szFmt, "%d", body_size);
    1318           0 :                 gf_dm_sess_set_header(sess->http_sess, "Content-Length", szFmt);
    1319             :         }
    1320             : 
    1321           0 :         gf_dm_sess_send_reply(sess->http_sess, sess->reply_code, response_body, GF_FALSE);
    1322           0 :         sess->is_h2 = gf_dm_sess_is_h2(sess->http_sess);
    1323             : 
    1324           0 :         if (response_body) gf_free(response_body);
    1325           0 :         gf_dm_sess_clear_headers(sess->http_sess);
    1326             : 
    1327           0 :         if (sess->do_log) {
    1328           0 :                 sess->req_id = ++sess->ctx->req_id;
    1329           0 :                 GF_LOG(GF_LOG_WARNING, GF_LOG_ALL, ("[HTTPOut] REQ#"LLU" %s %s %s error %d\n", sess->req_id, sess->peer_address, get_method_name(parameter->reply), url+1, sess->reply_code));
    1330             :         }
    1331             : 
    1332           0 :         if (url) gf_free(url);
    1333           0 :         sess->upload_type = 0;
    1334           0 :         sess->done = GF_TRUE;
    1335           0 :         sess->canceled = GF_FALSE;
    1336           0 :         sess->headers_done = GF_FALSE;
    1337             : 
    1338           0 :         if (!sess->is_h2 && (sess->ctx->close || (sess->nb_consecutive_errors == sess->ctx->max_client_errors))) {
    1339           0 :                 sess->force_destroy = GF_TRUE;
    1340           0 :         } else if (sess->http_sess) {
    1341           0 :                 gf_dm_sess_server_reset(sess->http_sess);
    1342             :         }
    1343             :         return;
    1344             : }
    1345             : 
    1346             : enum
    1347             : {
    1348             :         HTTP_PUT_HEADER_ENCODING=0,
    1349             :         HTTP_PUT_HEADER_MIME,
    1350             :         HTTP_PUT_HEADER_RANGE,
    1351             :         HTTP_PUT_HEADER_DONE
    1352             : };
    1353             : 
    1354         362 : static void httpout_in_io(void *usr_cbk, GF_NETIO_Parameter *parameter)
    1355             : {
    1356             :         GF_HTTPOutInput *in =usr_cbk;
    1357             : 
    1358         362 :         if (parameter->msg_type==GF_NETIO_GET_METHOD) {
    1359          30 :                 if (in->is_delete)
    1360           2 :                         parameter->name = "DELETE";
    1361             :                 else
    1362          28 :                         parameter->name = in->ctx->post ? "POST" : "PUT";
    1363          30 :                 in->cur_header = HTTP_PUT_HEADER_ENCODING;
    1364          30 :                 return;
    1365             :         }
    1366         332 :         if (parameter->msg_type==GF_NETIO_GET_HEADER) {
    1367          86 :                 parameter->name = parameter->value = NULL;
    1368             : 
    1369          86 :                 if (in->is_delete) return;
    1370             : 
    1371          84 :                 switch (in->cur_header) {
    1372          28 :                 case HTTP_PUT_HEADER_ENCODING:
    1373          28 :                         parameter->name = "Transfer-Encoding";
    1374          28 :                         parameter->value = "chunked";
    1375          28 :                         if (in->mime)
    1376          28 :                                 in->cur_header = HTTP_PUT_HEADER_MIME;
    1377             :                         else
    1378           0 :                                 in->cur_header = in->write_start_range ? HTTP_PUT_HEADER_RANGE : HTTP_PUT_HEADER_DONE;
    1379             :                         break;
    1380          28 :                 case HTTP_PUT_HEADER_MIME:
    1381          28 :                         parameter->name = "Content-Type";
    1382          28 :                         parameter->value = in->mime;
    1383          28 :                         in->cur_header = HTTP_PUT_HEADER_DONE;
    1384          28 :                         if (in->write_start_range)
    1385           0 :                                 in->cur_header = HTTP_PUT_HEADER_RANGE;
    1386             :                         break;
    1387           0 :                 case HTTP_PUT_HEADER_RANGE:
    1388           0 :                         parameter->name = "Range";
    1389           0 :                         if (in->write_end_range) {
    1390           0 :                                 sprintf(in->range_hdr, "bytes="LLU"-"LLU, in->write_start_range, in->write_end_range);
    1391             :                         } else {
    1392           0 :                                 sprintf(in->range_hdr, "bytes="LLU"-", in->write_start_range);
    1393             :                         }
    1394           0 :                         parameter->value = in->range_hdr;
    1395           0 :                         in->cur_header = HTTP_PUT_HEADER_DONE;
    1396           0 :                         break;
    1397             :                 default:
    1398             :                         parameter->name = NULL;
    1399             :                         parameter->value = NULL;
    1400             :                         break;
    1401             :                 }
    1402         246 :         }
    1403             : }
    1404             : 
    1405          40 : static GF_Err httpout_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
    1406             : {
    1407             :         const GF_PropertyValue *p;
    1408             :         GF_HTTPOutInput *pctx;
    1409          40 :         GF_HTTPOutCtx *ctx = (GF_HTTPOutCtx *) gf_filter_get_udta(filter);
    1410             : 
    1411          40 :         if (!is_remove) {
    1412          40 :                 p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE);
    1413          40 :                 if (!p || (p->value.uint!=GF_STREAM_FILE))
    1414             :                         return GF_NOT_SUPPORTED;
    1415             :         }
    1416             : 
    1417          40 :         pctx = gf_filter_pid_get_udta(pid);
    1418          40 :         if (!pctx) {
    1419             :                 GF_HTTPOutCtx *ctx_orig;
    1420             :                 Bool patch_blocks = GF_FALSE;
    1421             :                 GF_FilterEvent evt;
    1422             :         const char *res_path;
    1423             : 
    1424          36 :                 p = gf_filter_pid_get_property(pid, GF_PROP_PID_DISABLE_PROGRESSIVE);
    1425          36 :                 if (p && p->value.uint) {
    1426           0 :                         if (ctx->hmode==MODE_PUSH) {
    1427           0 :                                 if (p->value.uint==GF_PID_FILE_PATCH_INSERT) {
    1428           0 :                                         GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Push cannot be used to insert blocks in remote files (not supported by HTTP)\n"));
    1429           0 :                                         return GF_FILTER_NOT_SUPPORTED;
    1430             :                                 }
    1431             :                                 patch_blocks = GF_TRUE;
    1432             :                         } else {
    1433           0 :                                 GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Server cannot deliver PIDs with progressive download disabled\n"));
    1434             :                                 return GF_FILTER_NOT_SUPPORTED;
    1435             :                         }
    1436             :                 }
    1437             : 
    1438             :                 /*if PID was connected to an alias, get the alias context to get the destination
    1439             :                 Otherwise PID was directly connected to the main filter, use main filter destination*/
    1440          36 :                 ctx_orig = (GF_HTTPOutCtx *) gf_filter_pid_get_alias_udta(pid);
    1441          36 :         if (!ctx_orig) ctx_orig = ctx;
    1442             : 
    1443          36 :                 if (!ctx_orig->dst && (ctx->hmode==MODE_PUSH))  {
    1444           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Push output but no destination set !\n"));
    1445             :                         return GF_BAD_PARAM;
    1446             :                 }
    1447             : 
    1448          36 :                 GF_SAFEALLOC(pctx, GF_HTTPOutInput);
    1449          36 :                 if (!pctx) return GF_OUT_OF_MEM;
    1450          36 :                 pctx->ipid = pid;
    1451          36 :                 pctx->ctx = ctx;
    1452          36 :                 pctx->patch_blocks = patch_blocks;
    1453          36 :                 pctx->hold = ctx->hold;
    1454             : 
    1455             :         res_path = NULL;
    1456          36 :                 if (ctx_orig->dst) {
    1457             :             res_path = ctx_orig->dst;
    1458          36 :             char *path = strstr(res_path, "://");
    1459          36 :             if (path) path = strchr(path+3, '/');
    1460          36 :             if (path) pctx->path = gf_strdup(path);
    1461           0 :         } else if (!ctx->dst) {
    1462           0 :             p = gf_filter_pid_get_property(pid, GF_PROP_PID_FILEPATH);
    1463           0 :             if (p && p->value.string) {
    1464             :                 res_path = p->value.string;
    1465           0 :                 pctx->path = gf_strdup(res_path);
    1466             :             }
    1467             :         }
    1468          36 :         if (res_path) {
    1469             : 
    1470          36 :                         if (ctx->hmode==MODE_PUSH) {
    1471             :                                 GF_Err e;
    1472             :                                 //note that ctx_orig->dst might be wrong (eg indicating MPD url rather than segment), but this is fixed in httpout_open_input by resetting up the session
    1473             :                                 //with the correct URL
    1474          11 :                                 pctx->upload = gf_dm_sess_new(gf_filter_get_download_manager(filter), ctx_orig->dst, GF_NETIO_SESSION_NOT_THREADED|GF_NETIO_SESSION_NOT_CACHED|GF_NETIO_SESSION_PERSISTENT, httpout_in_io, pctx, &e);
    1475          11 :                                 if (!pctx->upload) {
    1476           0 :                                         gf_free(pctx);
    1477           0 :                                         return e;
    1478             :                                 }
    1479             : //                              gf_sk_group_register(ctx->sg, pctx->socket);
    1480             :                         } else {
    1481          25 :                                 if (!pctx->path) {
    1482           0 :                                         GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Output path not specified\n"));
    1483             :                                         return GF_BAD_PARAM;
    1484             :                                 }
    1485          25 :                                 httpout_set_local_path(ctx, pctx);
    1486             :                         }
    1487          36 :                         if (ctx->dst && !gf_list_count(ctx->inputs))
    1488          17 :                                 pctx->force_dst_name = GF_TRUE;
    1489             : 
    1490             :                         //reset caps to anything (mime or ext) in case a URL was given, since graph resolution is now done
    1491             :                         //this allows working with input filters dispatching files without creating a new destination (dashin in file mode for example)
    1492             :                         //we do not reset caps to default as the default caps list an output and we don't want gf_filter_connections_pending to think we will produce one
    1493          36 :                         ctx->in_caps[1].val = PROP_NAME( "*" );
    1494             :                 }
    1495             :                 //in any cast store dash state, mime, register input and fire play
    1496          36 :                 p = gf_filter_pid_get_property(pid, GF_PROP_PID_DASH_MODE);
    1497          36 :                 if (p && p->value.uint) pctx->dash_mode = GF_TRUE;
    1498             : 
    1499          36 :                 p = gf_filter_pid_get_property(pid, GF_PROP_PID_MIME);
    1500          36 :                 if (p && p->value.string) pctx->mime = gf_strdup(p->value.string);
    1501             : 
    1502          36 :                 gf_filter_pid_set_udta(pid, pctx);
    1503          36 :                 gf_list_add(ctx->inputs, pctx);
    1504             : 
    1505          36 :                 gf_filter_pid_init_play_event(pid, &evt, 0.0, 1.0, "HTTPOut");
    1506          36 :                 gf_filter_pid_send_event(pid, &evt);
    1507             :             
    1508             :         }
    1509             :         if (is_remove) {
    1510             :                 return GF_OK;
    1511             :         }
    1512             : 
    1513             :         //we act as a server
    1514             : 
    1515             :         return GF_OK;
    1516             : }
    1517             : 
    1518             : 
    1519          47 : static void httpout_check_new_session(GF_HTTPOutCtx *ctx)
    1520             : {
    1521             :         char peer_address[GF_MAX_IP_NAME_LEN];
    1522             :         GF_HTTPOutSession *sess;
    1523             :         GF_Err e;
    1524             :         void *ssl_c = NULL;
    1525          47 :         GF_Socket *new_conn = NULL;
    1526             : 
    1527          47 :         e = gf_sk_accept(ctx->server_sock, &new_conn);
    1528          47 :         if (e==GF_IP_SOCK_WOULD_BLOCK) return;
    1529          47 :         else if (e) {
    1530           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Accept failure %s\n", gf_error_to_string(e) ));
    1531             :                 return;
    1532             :         }
    1533             :         //check max connections
    1534          47 :         if (ctx->maxc && (ctx->nb_connections>=ctx->maxc)) {
    1535           0 :                 GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[HTTPOut] Connection rejected due to too many connections\n"));
    1536           0 :                 gf_sk_del(new_conn);
    1537           0 :                 return;
    1538             :         }
    1539          47 :         gf_sk_get_remote_address(new_conn, peer_address);
    1540          47 :         if (ctx->maxp) {
    1541          47 :                 u32 i, nb_conn=0, count = gf_list_count(ctx->sessions);
    1542          85 :                 for (i=0; i<count; i++) {
    1543          38 :                         sess = gf_list_get(ctx->sessions, i);
    1544          38 :                         if (!strcmp(sess->peer_address, peer_address)) nb_conn++;
    1545             :                 }
    1546          47 :                 if (nb_conn>=ctx->maxp) {
    1547           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[HTTPOut] Connection rejected due to too many connections from peer %s\n", peer_address));
    1548           0 :                         gf_sk_del(new_conn);
    1549           0 :                         return;
    1550             :                 }
    1551             :         }
    1552          47 :         GF_SAFEALLOC(sess, GF_HTTPOutSession);
    1553          47 :         if (!sess) {
    1554           0 :                 gf_sk_del(new_conn);
    1555           0 :                 return;
    1556             :         }
    1557             : 
    1558             :         //we keep track of the socket for sock group (un)register
    1559          47 :         sess->socket = new_conn;
    1560          47 :         sess->ctx = ctx;
    1561             : 
    1562             : #ifdef GPAC_HAS_SSL
    1563          47 :         if (ctx->ssl_ctx) {
    1564           1 :                 ssl_c = gf_ssl_new(ctx->ssl_ctx, new_conn, &e);
    1565           1 :                 if (e) {
    1566           0 :                         GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTPOut] Failed to create TLS session from %s: %s\n", sess->peer_address, gf_error_to_string(e) ));
    1567           0 :                         gf_free(sess);
    1568           0 :                         gf_sk_del(new_conn);
    1569           0 :                         return;
    1570             :                 }
    1571             :         }
    1572             : #endif
    1573             : 
    1574          47 :         sess->http_sess = gf_dm_sess_new_server(new_conn, ssl_c, httpout_sess_io, sess, &e);
    1575          47 :         if (!sess->http_sess) {
    1576           0 :                 gf_sk_del(new_conn);
    1577           0 :                 GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTPOut] Failed to create HTTP server session from %s: %s\n", sess->peer_address, gf_error_to_string(e) ));
    1578           0 :                 gf_free(sess);
    1579           0 :                 return;
    1580             :         }
    1581          47 :         ctx->nb_connections++;
    1582             : 
    1583          47 :         gf_list_add(ctx->sessions, sess);
    1584          47 :         gf_list_add(ctx->active_sessions, sess);
    1585          47 :         gf_sk_group_register(ctx->sg, sess->socket);
    1586             :         
    1587          47 :         gf_sk_set_buffer_size(new_conn, GF_FALSE, ctx->block_size);
    1588          47 :         gf_sk_set_buffer_size(new_conn, GF_TRUE, ctx->block_size);
    1589          47 :         strcpy(sess->peer_address, peer_address);
    1590             : 
    1591          47 :         GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTPOut] Accepting new connection from %s\n", sess->peer_address));
    1592             :         //ask immediate reschedule
    1593          47 :         ctx->next_wake_us = 1;
    1594             : }
    1595             : 
    1596          46 : static GF_Err httpout_initialize(GF_Filter *filter)
    1597             : {
    1598             :         char szIP[1024];
    1599             :         GF_Err e;
    1600             :         u16 port;
    1601             :         char *ip;
    1602             :         const char *ext = NULL;
    1603             :         char *sep, *url;
    1604          46 :         GF_HTTPOutCtx *ctx = (GF_HTTPOutCtx *) gf_filter_get_udta(filter);
    1605             : 
    1606             : 
    1607          46 :         port = ctx->port;
    1608          46 :         ip = ctx->ifce;
    1609             : 
    1610          46 :         url = ctx->dst;
    1611             :         sep = NULL;
    1612          46 :         if (ctx->dst) {
    1613          34 :                 if (!strncmp(ctx->dst, "http://", 7)) {
    1614          34 :                         sep = strchr(ctx->dst+7, '/');
    1615           0 :                 } else if (!strncmp(ctx->dst, "https://", 8)) {
    1616           0 :                         sep = strchr(ctx->dst+8, '/');
    1617             :                 }
    1618             :         }
    1619          34 :         if (sep) {
    1620             :                 u32 cplen;
    1621          34 :                 url = sep+1;
    1622          34 :                 cplen = (u32) (sep-ctx->dst-7);
    1623          34 :                 if (cplen>1023) cplen=1023;
    1624          34 :                 strncpy(szIP, ctx->dst+7, cplen);
    1625          34 :                 szIP[1023] = 0;
    1626          34 :                 sep = strchr(szIP, ':');
    1627          34 :                 if (sep) {
    1628          68 :                         port = atoi(sep+1);
    1629          34 :                         if (!port) port = ctx->port;
    1630          34 :                         sep[0] = 0;
    1631             :                 }
    1632          34 :                 if (strlen(szIP)) ip = szIP;
    1633             :         }
    1634          46 :         if (url && !strlen(url))
    1635             :                 url = NULL;
    1636             : 
    1637          46 :         if (url) {
    1638          34 :                 if (ctx->ext) ext = ctx->ext;
    1639             :                 else {
    1640          34 :                         ext = gf_file_ext_start(url);
    1641          34 :                         if (!ext) ext = ".*";
    1642          34 :                         ext += 1;
    1643             :                 }
    1644             : 
    1645          34 :                 if (!ext && !ctx->mime) {
    1646           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[HTTPOut] No extension provided nor mime type for output file %s, cannot infer format\nThis may result in invalid filter chain resolution", ctx->dst));
    1647             :                 } else {
    1648             :                         //static cap, streamtype = file
    1649          34 :                         ctx->in_caps[0].code = GF_PROP_PID_STREAM_TYPE;
    1650          34 :                         ctx->in_caps[0].val = PROP_UINT(GF_STREAM_FILE);
    1651          34 :                         ctx->in_caps[0].flags = GF_CAPS_INPUT_STATIC;
    1652             : 
    1653          34 :                         if (ctx->mime) {
    1654          17 :                                 ctx->in_caps[1].code = GF_PROP_PID_MIME;
    1655          17 :                                 ctx->in_caps[1].val = PROP_NAME( ctx->mime );
    1656          17 :                                 ctx->in_caps[1].flags = GF_CAPS_INPUT;
    1657             :                         } else {
    1658          17 :                                 strncpy(ctx->szExt, ext, 9);
    1659          17 :                                 ctx->szExt[9] = 0;
    1660          17 :                                 strlwr(ctx->szExt);
    1661          17 :                                 ctx->in_caps[1].code = GF_PROP_PID_FILE_EXT;
    1662          17 :                                 ctx->in_caps[1].val = PROP_NAME( ctx->szExt );
    1663          17 :                                 ctx->in_caps[1].flags = GF_CAPS_INPUT;
    1664             :                         }
    1665          34 :                         gf_filter_override_caps(filter, ctx->in_caps, 2);
    1666             :                 }
    1667             :         }
    1668             :         /*this is an alias for our main filter, nothing to initialize*/
    1669          46 :         if (gf_filter_is_alias(filter)) {
    1670             :                 return GF_OK;
    1671             :         }
    1672             : 
    1673          29 :         if (ctx->wdir)
    1674           5 :                 ctx->hmode = MODE_DEFAULT;
    1675             : 
    1676          29 :         if (!url && !ctx->rdirs.nb_items && !ctx->wdir && (ctx->hmode!=MODE_SOURCE)) {
    1677           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] No root dir(s) for server, no URL set and not configured as source, cannot run!\n" ));
    1678             :                 return GF_BAD_PARAM;
    1679             :         }
    1680             : 
    1681          29 :         if (ctx->rdirs.nb_items || ctx->wdir) {
    1682          21 :                 gf_filter_make_sticky(filter);
    1683           8 :         } else if (ctx->hmode!=MODE_PUSH) {
    1684           2 :                 ctx->single_mode = GF_TRUE;
    1685             :         }
    1686             : 
    1687          29 :         ctx->sessions = gf_list_new();
    1688          29 :         ctx->active_sessions = gf_list_new();
    1689          29 :         ctx->inputs = gf_list_new();
    1690          29 :         ctx->filter = filter;
    1691             :         //used in both server and push modes
    1692          29 :         ctx->sg = gf_sk_group_new();
    1693             : 
    1694          29 :         if (ip)
    1695          17 :                 ctx->ip = gf_strdup(ip);
    1696             : 
    1697          29 :         if (ctx->cache_control) {
    1698           0 :                 if (!strcmp(ctx->cache_control, "none")) ctx->no_etag = GF_TRUE;
    1699             :         }
    1700             : 
    1701          29 :         if (ctx->hmode==MODE_PUSH) {
    1702           6 :                 ctx->hold = GF_FALSE;
    1703           6 :                 return GF_OK;
    1704             :         }
    1705          23 :         ctx->port = port;
    1706          23 :         if (ctx->cert && !ctx->pkey) {
    1707           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] missing server private key file\n"));
    1708             :                 return GF_BAD_PARAM;
    1709             :         }
    1710          23 :         if (!ctx->cert && ctx->pkey) {
    1711           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] missing server certificate file\n"));
    1712             :                 return GF_BAD_PARAM;
    1713             :         }
    1714          23 :         if (ctx->cert && ctx->pkey) {
    1715             : #ifdef GPAC_HAS_SSL
    1716           1 :                 if (!gf_file_exists(ctx->cert)) {
    1717           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Certificate file %s not found\n", ctx->cert));
    1718             :                         return GF_IO_ERR;
    1719             :                 }
    1720           1 :                 if (!gf_file_exists(ctx->pkey)) {
    1721           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Provate key file %s not found\n", ctx->pkey));
    1722             :                         return GF_IO_ERR;
    1723             :                 }
    1724           1 :                 if (gf_ssl_init_lib()) {
    1725           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Failed to initialize OpenSSL library\n"));
    1726             :                         return GF_IO_ERR;
    1727             :                 }
    1728           1 :                 ctx->ssl_ctx = gf_ssl_server_context_new(ctx->cert, ctx->pkey);
    1729           1 :                 if (!ctx->ssl_ctx) return GF_IO_ERR;
    1730             : 
    1731           1 :                 if (!ctx->port)
    1732           0 :                         ctx->port = 443;
    1733             : #else
    1734             :                 GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] TLS key/certificate set but GPAC compiled without TLS support\n"));
    1735             :                 return GF_NOT_SUPPORTED;
    1736             : 
    1737             : #endif
    1738             :         }
    1739             : 
    1740          23 :         if (!ctx->port)
    1741           0 :                 ctx->port = 80;
    1742             : 
    1743          23 :         ctx->server_sock = gf_sk_new(GF_SOCK_TYPE_TCP);
    1744          23 :         e = gf_sk_bind(ctx->server_sock, NULL, ctx->port, ip, 0, GF_SOCK_REUSE_PORT);
    1745          23 :         if (!e) e = gf_sk_listen(ctx->server_sock, ctx->maxc);
    1746          23 :         if (e) {
    1747           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] failed to start server on port %d: %s\n", ctx->port, gf_error_to_string(e) ));
    1748             :                 return e;
    1749             :         }
    1750          23 :         gf_sk_group_register(ctx->sg, ctx->server_sock);
    1751             : 
    1752          23 :         gf_sk_server_mode(ctx->server_sock, GF_TRUE);
    1753          23 :         GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTPOut] Server running on port %d\n", ctx->port));
    1754          23 :         if (ctx->reqlog) {
    1755           9 :                 GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("[HTTPOut] Server running on port %d\n", ctx->port));
    1756           9 :                 if (strstr(ctx->reqlog, "REC"))
    1757           0 :                         ctx->log_record = GF_TRUE;
    1758             :         }
    1759          23 :         gf_filter_post_process_task(filter);
    1760          23 :         return GF_OK;
    1761             : }
    1762             : 
    1763          47 : static void httpout_del_session(GF_HTTPOutSession *s)
    1764             : {
    1765          47 :         gf_list_del_item(s->ctx->active_sessions, s);
    1766          47 :         gf_list_del_item(s->ctx->sessions, s);
    1767          47 :         if (s->http_sess)
    1768           4 :                 httpout_close_session(s);
    1769          47 :         if (s->buffer) gf_free(s->buffer);
    1770          47 :         if (s->path) gf_free(s->path);
    1771          47 :         if (s->mime) gf_free(s->mime);
    1772          47 :         if (s->opid) gf_filter_pid_remove(s->opid);
    1773          47 :         if (s->resource) gf_fclose(s->resource);
    1774          47 :         if (s->ranges) gf_free(s->ranges);
    1775          47 :         gf_free(s);
    1776          47 : }
    1777             : 
    1778         766 : static void httpout_close_hls_chunk(GF_HTTPOutCtx *ctx, GF_HTTPOutInput *in, Bool final_flush)
    1779             : {
    1780         766 :         if (!in->hls_chunk) return;
    1781             : 
    1782          90 :         gf_fclose(in->hls_chunk);
    1783          90 :         in->hls_chunk = NULL;
    1784             : 
    1785          90 :         if (!final_flush) {
    1786             :                 u32 i, count;
    1787             :                 //detach all clients from this input and reassign to a regular output
    1788          90 :                 count = gf_list_count(ctx->sessions);
    1789         120 :                 for (i=0; i<count; i++) {
    1790         120 :                         GF_HTTPOutSession *sess = gf_list_get(ctx->sessions, i);
    1791         120 :                         if (sess->in_source != in) continue;
    1792          36 :                         if (!sess->in_source_is_ll_hls_chunk) continue;
    1793          10 :                         if (strcmp(sess->path, in->local_path)) continue;
    1794             : 
    1795             :                         assert(sess->file_in_progress);
    1796             :                         if (sess->in_source) {
    1797           0 :                                 sess->in_source->nb_dest--;
    1798           0 :                                 sess->in_source = NULL;
    1799           0 :                                 if (!sess->resource && sess->path) {
    1800           0 :                                         sess->resource = gf_fopen(sess->path, "rb");
    1801             :                                 }
    1802             :                         }
    1803           0 :                         sess->in_source_is_ll_hls_chunk = GF_FALSE;
    1804           0 :                         sess->file_size = gf_fsize(sess->resource);
    1805           0 :                         gf_fseek(sess->resource, sess->file_pos, SEEK_SET);
    1806           0 :                         sess->file_in_progress = GF_FALSE;
    1807             :                 }
    1808             :         }
    1809             : 
    1810          90 :         if (in->hls_chunk_path) gf_free(in->hls_chunk_path);
    1811          90 :         in->hls_chunk_path = NULL;
    1812          90 :         if (in->hls_chunk_local_path) gf_free(in->hls_chunk_local_path);
    1813          90 :         in->hls_chunk_local_path = NULL;
    1814             : }
    1815             : 
    1816             : 
    1817          46 : static void httpout_finalize(GF_Filter *filter)
    1818             : {
    1819          46 :         GF_HTTPOutCtx *ctx = (GF_HTTPOutCtx *) gf_filter_get_udta(filter);
    1820             : 
    1821             :         /*this is an alias for our main filter, nothing to finalize*/
    1822          46 :         if (gf_filter_is_alias(filter))
    1823             :                 return;
    1824             : 
    1825          33 :         while (gf_list_count(ctx->sessions)) {
    1826           4 :                 GF_HTTPOutSession *tmp = gf_list_get(ctx->sessions, 0);
    1827           4 :                 tmp->opid = NULL;
    1828           4 :                 httpout_del_session(tmp);
    1829             :         }
    1830          29 :         gf_list_del(ctx->sessions);
    1831          29 :         gf_list_del(ctx->active_sessions);
    1832             : 
    1833          94 :         while (gf_list_count(ctx->inputs)) {
    1834          36 :                 GF_HTTPOutInput *in = gf_list_pop_back(ctx->inputs);
    1835          36 :                 if (in->local_path) gf_free(in->local_path);
    1836          36 :                 if (in->path) gf_free(in->path);
    1837          36 :                 if (in->mime) gf_free(in->mime);
    1838             : 
    1839          36 :                 httpout_close_hls_chunk(ctx, in, GF_TRUE);
    1840             : 
    1841          36 :                 if (in->resource) gf_fclose(in->resource);
    1842          36 :                 if (in->upload) gf_dm_sess_del(in->upload);
    1843          36 :                 if (in->file_deletes) {
    1844           0 :                         while (gf_list_count(in->file_deletes)) {
    1845           0 :                                 char *url = gf_list_pop_back(in->file_deletes);
    1846           0 :                                 gf_free(url);
    1847             :                         }
    1848           0 :                         gf_list_del(in->file_deletes);
    1849             :                 }
    1850          36 :                 gf_free(in);
    1851             :         }
    1852          29 :         gf_list_del(ctx->inputs);
    1853          29 :         if (ctx->server_sock) gf_sk_del(ctx->server_sock);
    1854          29 :         if (ctx->sg) gf_sk_group_del(ctx->sg);
    1855          29 :         if (ctx->ip) gf_free(ctx->ip);
    1856             : 
    1857             : #ifdef GPAC_HAS_SSL
    1858          29 :         if (ctx->ssl_ctx) {
    1859           1 :                 gf_ssl_server_context_del(ctx->ssl_ctx);
    1860             :         }
    1861             : #endif
    1862             : }
    1863             : 
    1864         115 : static GF_Err httpout_sess_data_upload(GF_HTTPOutSession *sess, const u8 *data, u32 size)
    1865             : {
    1866             :         u32 remain, write, to_write;
    1867             : 
    1868         115 :         if (sess->opid || sess->reconfigure_output) {
    1869             :                 GF_FilterPacket *pck;
    1870             :                 u8 *buffer;
    1871             :                 Bool is_first = GF_FALSE;
    1872           9 :                 if (sess->reconfigure_output) {
    1873           1 :                         sess->reconfigure_output = GF_FALSE;
    1874           1 :                         gf_filter_pid_raw_new(sess->ctx->filter, sess->path, NULL, sess->mime, NULL, (u8 *) data, size, GF_FALSE, &sess->opid);
    1875             :                         is_first = GF_TRUE;
    1876             :                 }
    1877           9 :                 if (!sess->opid) return GF_SERVICE_ERROR;
    1878           9 :                 pck = gf_filter_pck_new_alloc(sess->opid, size, &buffer);
    1879           9 :                 if (!pck) return GF_IO_ERR;
    1880           9 :                 memcpy(buffer, data, size);
    1881           9 :                 gf_filter_pck_set_framing(pck, is_first, GF_FALSE);
    1882           9 :                 gf_filter_pck_send(pck);
    1883           9 :                 return GF_OK;
    1884             :         }
    1885         106 :         if (!sess->resource) {
    1886             :                 assert(0);
    1887             :         }
    1888         106 :         if (!sess->nb_ranges) {
    1889         106 :                 write = (u32) gf_fwrite(data, size, sess->resource);
    1890         106 :                 if (write != size) {
    1891             :                         return GF_IO_ERR;
    1892             :                 }
    1893         106 :                 gf_fflush(sess->resource);
    1894         106 :                 sess->nb_bytes += write;
    1895         106 :                 sess->file_pos += write;
    1896         106 :                 return GF_OK;
    1897             :         }
    1898             :         remain = size;
    1899           0 :         while (remain) {
    1900           0 :                 to_write = (u32) (sess->ranges[sess->range_idx].end + 1 - sess->file_pos);
    1901           0 :                 if (to_write>=remain) {
    1902           0 :                         write = (u32) gf_fwrite(data, remain, sess->resource);
    1903           0 :                         if (write != remain) {
    1904             :                                 return GF_IO_ERR;
    1905             :                         }
    1906           0 :                         gf_fflush(sess->resource);
    1907           0 :                         sess->nb_bytes += write;
    1908           0 :                         sess->file_pos += remain;
    1909             :                         remain = 0;
    1910             :                         break;
    1911             :                 }
    1912           0 :                 write = (u32) gf_fwrite(data, to_write, sess->resource);
    1913           0 :                 sess->nb_bytes += write;
    1914           0 :                 remain -= to_write;
    1915           0 :                 sess->range_idx++;
    1916           0 :                 gf_fflush(sess->resource);
    1917           0 :                 if (sess->range_idx>=sess->nb_ranges) break;
    1918           0 :                 sess->file_pos = sess->ranges[sess->range_idx].start;
    1919           0 :                 gf_fseek(sess->resource, sess->file_pos, SEEK_SET);
    1920             :         }
    1921           0 :         if (remain) {
    1922           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Error in file upload, more bytes uploaded than described in byte range\n"));
    1923             :                 return GF_BAD_PARAM;
    1924             :         }
    1925             :         return GF_OK;
    1926             : }
    1927         153 : static void httpout_check_connection(GF_HTTPOutSession *sess)
    1928             : {
    1929         153 :         GF_Err e = gf_sk_probe(sess->socket);
    1930         153 :         if (e==GF_IP_CONNECTION_CLOSED) {
    1931           1 :                 sess->last_active_time = gf_sys_clock_high_res();
    1932           1 :                 sess->done = GF_TRUE;
    1933           1 :                 sess->canceled = GF_FALSE;
    1934           1 :                 sess->upload_type = 0;
    1935           1 :                 GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTPOut] Client %s disconnected, destroying session\n", sess->peer_address));
    1936           1 :                 httpout_close_session(sess);
    1937           1 :                 return;
    1938             :         }
    1939             : }
    1940             : 
    1941         209 : static void log_request_done(GF_HTTPOutSession *sess)
    1942             : {
    1943         209 :         if (!sess->do_log) return;
    1944         167 :         const char *sprefix = sess->is_h2 ? "H2 " : "";
    1945             : 
    1946         167 :         if (!sess->socket) {
    1947           0 :                 GF_LOG(GF_LOG_WARNING, GF_LOG_ALL, ("[HTTPOut] %sREQ#"LLU" %s aborted!\n", sprefix, sess->req_id, get_method_name(sess->method_type)));
    1948         167 :         } else if (sess->canceled) {
    1949           0 :                 GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("[HTTPOut] %sREQ#"LLU" %s canceled\n", sprefix, sess->req_id, get_method_name(sess->method_type)));
    1950             :         } else {
    1951             :                 char *unit = "bps";
    1952         167 :                 u64 diff_us = (gf_sys_clock_high_res() - sess->req_start_time);
    1953         167 :                 Double bps = (Double)sess->nb_bytes * 8000000;
    1954         167 :                 bps /= diff_us;
    1955         167 :                 if (bps>1000000) {
    1956             :                         unit = "mbps";
    1957          54 :                         bps/=1000000;
    1958         113 :                 } else if (bps>1000) {
    1959             :                         unit = "kbps";
    1960         113 :                         bps/=1000;
    1961             :                 }
    1962             : #if 0
    1963             :                 assert(sess->nb_bytes);
    1964             :                 if (sess->nb_ranges) {
    1965             :                         u32 i;
    1966             :                         u64 tot_bytes = 0;
    1967             :                         for (i=0; i<sess->nb_ranges; i++) {
    1968             :                                 tot_bytes += sess->ranges[i].end - sess->ranges[i].start + 1;
    1969             :                         }
    1970             :                         assert(tot_bytes==sess->nb_bytes);
    1971             :                 }
    1972             : #endif
    1973         167 :                 GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("[HTTPOut] %sREQ#"LLU" %s done: reply %d - "LLU" bytes in %d ms at %g %s\n", sprefix, sess->req_id, get_method_name(sess->method_type), sess->reply_code, sess->nb_bytes, (u32) (diff_us/1000), bps, unit));
    1974             :         }
    1975             : }
    1976             : 
    1977        1639 : static void httpout_process_session(GF_Filter *filter, GF_HTTPOutCtx *ctx, GF_HTTPOutSession *sess)
    1978             : {
    1979             :         u32 read;
    1980             :         u64 to_read=0;
    1981             :         GF_Err e = GF_OK;
    1982        1639 :         Bool close_session = ctx->close;
    1983             : 
    1984        1639 :         if (sess->force_destroy) {
    1985           0 :                 httpout_close_session(sess);
    1986           0 :                 return;
    1987             :         }
    1988             : 
    1989             : 
    1990             :         //upload session (PUT, POST)
    1991        1639 :         if (sess->upload_type) {
    1992             :                 u32 i, count;
    1993             :                 GF_Err write_e=GF_OK;
    1994             :                 assert(sess->path);
    1995         277 :                 if (sess->done)
    1996             :                         return;
    1997             : 
    1998         277 :                 read = 0;
    1999         277 :                 if (sess->canceled) {
    2000             :                         e = GF_EOS;
    2001             :                 } else {
    2002         277 :                         e = gf_dm_sess_fetch_data(sess->http_sess, sess->buffer, ctx->block_size, &read);
    2003             :                 }
    2004             : 
    2005         277 :                 if (e>=GF_OK) {
    2006         124 :                         if (read)
    2007         115 :                                 write_e = httpout_sess_data_upload(sess, sess->buffer, read);
    2008             :                         else
    2009             :                                 write_e = GF_OK;
    2010             : 
    2011         115 :                         if (!write_e) {
    2012         124 :                                 sess->last_active_time = gf_sys_clock_high_res();
    2013             :                                 //don't reschedule in upload, let server decide
    2014         124 :                                 ctx->next_wake_us = 0;
    2015             :                                 //we way be in end of stream
    2016         124 :                                 if (e==GF_OK)
    2017             :                                         return;
    2018             :                         } else {
    2019             :                                 e = write_e;
    2020             :                         }
    2021         153 :                 } else if (e==GF_IP_NETWORK_EMPTY) {
    2022             :                         //don't reschedule in upload, let server decide
    2023         153 :                         ctx->next_wake_us = 0;
    2024         153 :                         sess->last_active_time = gf_sys_clock_high_res();
    2025         153 :                         httpout_check_connection(sess);
    2026         153 :                         return;
    2027           0 :                 } else if (e==GF_IP_CONNECTION_CLOSED) {
    2028           0 :                         sess->last_active_time = gf_sys_clock_high_res();
    2029           0 :                         sess->done = GF_TRUE;
    2030           0 :                         sess->canceled = GF_FALSE;
    2031           0 :                         sess->upload_type = 0;
    2032           0 :                         httpout_close_session(sess);
    2033           0 :                         log_request_done(sess);
    2034           0 :                         return;
    2035             :                 }
    2036             :                 //done (error or end of upload)
    2037          25 :                 if (sess->opid) {
    2038           1 :                         GF_FilterPacket *pck = gf_filter_pck_new_alloc(sess->opid, 0, NULL);
    2039           1 :                         if (pck) {
    2040           1 :                                 gf_filter_pck_set_framing(pck, GF_FALSE, GF_TRUE);
    2041           1 :                                 if (sess->canceled)
    2042           0 :                                         gf_filter_pck_set_corrupted(pck, GF_TRUE);
    2043             : 
    2044           1 :                                 gf_filter_pck_send(pck);
    2045             :                         }
    2046           1 :                         gf_filter_pid_set_eos(sess->opid);
    2047             :                 } else {
    2048          24 :                         if (sess->resource) gf_fclose(sess->resource);
    2049          24 :                         sess->resource = NULL;
    2050             :                         //for now we remove any canceled file
    2051          24 :                         if (sess->canceled)
    2052           0 :                                 gf_file_delete(sess->path);
    2053             :                 }
    2054             : 
    2055          25 :                 if (sess->canceled) {
    2056           0 :                         log_request_done(sess);
    2057             :                 } else {
    2058             :                         char szDate[200];
    2059             : 
    2060          25 :                         if (e==GF_EOS) {
    2061          25 :                                 if (sess->upload_type==2) {
    2062          16 :                                         sess->reply_code = 200;
    2063             :                                 } else {
    2064           9 :                                         sess->reply_code = 201;
    2065             :                                 }
    2066           0 :                         } else if (write_e==GF_BAD_PARAM) {
    2067             :                                 close_session = GF_TRUE;
    2068           0 :                                 sess->reply_code = 416;
    2069             :                         } else {
    2070             :                                 close_session = GF_TRUE;
    2071           0 :                                 sess->reply_code = 500;
    2072             :                         }
    2073          25 :                         gf_dm_sess_set_header(sess->http_sess, "Server", sess->ctx->user_agent);
    2074             : 
    2075          25 :                         httpout_format_date(gf_net_get_utc(), szDate, GF_FALSE);
    2076          25 :                         gf_dm_sess_set_header(sess->http_sess, "Date", szDate);
    2077             : 
    2078          25 :                         if (close_session)
    2079           0 :                                 gf_dm_sess_set_header(sess->http_sess, "Connection", "close");
    2080             :                         else
    2081          25 :                                 gf_dm_sess_set_header(sess->http_sess, "Connection", "keep-alive");
    2082             : 
    2083          25 :                         if (e==GF_EOS) {
    2084          25 :                                 if (ctx->hmode==MODE_SOURCE) {
    2085           1 :                                         char *loc = NULL;
    2086           1 :                                         gf_dynstrcat(&loc, sess->path, "/");
    2087           1 :                                         gf_dm_sess_set_header(sess->http_sess, "Content-Location", loc);
    2088           1 :                                         gf_free(loc);
    2089             :                                 } else {
    2090          24 :                                         char *spath = strchr(sess->path, '/');
    2091          24 :                                         if (spath) spath = spath+1;
    2092             :                                         else spath = sess->path;
    2093             : 
    2094          24 :                                         if (ctx->wdir) {
    2095          24 :                                                 u32 plen = (u32) strlen(ctx->wdir);
    2096          24 :                                                 if ((ctx->wdir[plen-1] == '/') || (ctx->wdir[plen-1] == '\\'))
    2097             :                                                         plen--;
    2098          24 :                                                 if (!strncmp(ctx->wdir, sess->path, plen))
    2099          24 :                                                         spath = sess->path + plen;
    2100             :                                         }
    2101             : 
    2102          24 :                                         gf_dm_sess_set_header(sess->http_sess, "Content-Location", spath);
    2103             :                                 }
    2104             :                         }
    2105             : 
    2106          25 :                         GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTPOut] Sending PUT response to %s - reply %d\n", sess->peer_address, sess->reply_code));
    2107             : 
    2108          25 :                         log_request_done(sess);
    2109             : 
    2110          25 :                         gf_dm_sess_send_reply(sess->http_sess, sess->reply_code, NULL, GF_TRUE);
    2111             :                 }
    2112             : 
    2113          25 :                 sess->last_active_time = gf_sys_clock_high_res();
    2114          25 :                 sess->done = GF_TRUE;
    2115          25 :                 sess->canceled = GF_FALSE;
    2116          25 :                 sess->upload_type = 0;
    2117          25 :                 sess->nb_consecutive_errors = 0;
    2118             : 
    2119             :                 //notify all download (GET, HEAD) sessions using the same resource that we are done
    2120          25 :                 count = gf_list_count(sess->ctx->sessions);
    2121          72 :                 for (i=0; i<count; i++) {
    2122          47 :                         GF_HTTPOutSession *a_sess = gf_list_get(sess->ctx->sessions, i);
    2123          47 :                         if (a_sess == sess) continue;
    2124          22 :                         if (a_sess->done || !a_sess->put_in_progress) continue;
    2125           0 :                         if (a_sess->path && sess->path && !strcmp(a_sess->path, sess->path)) {
    2126           0 :                                 a_sess->put_in_progress = (sess->reply_code>201) ? 2 : 0;
    2127           0 :                                 a_sess->file_size = gf_fsize(a_sess->resource);
    2128           0 :                                 gf_fseek(a_sess->resource, a_sess->file_pos, SEEK_SET);
    2129             :                         }
    2130             :                 }
    2131             : 
    2132          25 :                 if (close_session) {
    2133           0 :                         httpout_close_session(sess);
    2134             :                 } else {
    2135          25 :                         gf_dm_sess_server_reset(sess->http_sess);
    2136             :                 }
    2137             :                 return;
    2138             :         }
    2139             : 
    2140             : 
    2141             :         //other session: read incoming request and process headers
    2142        1362 :         if (!sess->headers_done) {
    2143             :                 //check we have something to read if not http2
    2144             :                 //if http2, data might have been received on this session while processing another session
    2145        1185 :                 if (!sess->is_h2 && !gf_sk_group_sock_is_set(ctx->sg, sess->socket, GF_SK_SELECT_READ)) {
    2146             :                         return;
    2147             :                 }
    2148         253 :                 e = gf_dm_sess_process(sess->http_sess);
    2149             : 
    2150         253 :                 if (e==GF_IP_NETWORK_EMPTY) {
    2151             :                         return;
    2152             :                 }
    2153             : 
    2154         253 :                 if (e<0) {
    2155          37 :                         GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTPOut] Connection to %s closed: %s\n", sess->peer_address, gf_error_to_string(e) ));
    2156          37 :                         httpout_close_session(sess);
    2157          37 :                         if (!sess->done)
    2158           0 :                                 log_request_done(sess);
    2159             :                         return;
    2160             :                 }
    2161             : 
    2162         216 :                 sess->last_active_time = gf_sys_clock_high_res();
    2163             :                 //reschedule asap
    2164         216 :                 ctx->next_wake_us = 1;
    2165             : 
    2166             :                 //request has been process, if not an upload we don't need the session anymore
    2167             :                 //otherwise we use the session to parse transfered data
    2168         216 :                 if (!sess->upload_type) {
    2169         188 :                         sess->headers_done = GF_TRUE;
    2170             : 
    2171         188 :                         if (sess->done)
    2172             :                                 goto session_done;
    2173             :                 } else {
    2174             :                         //flush any data received
    2175          28 :                         httpout_process_session(filter, ctx, sess);
    2176          28 :                         return;
    2177             :                 }
    2178             :         }
    2179         361 :         if (!sess->http_sess) return;
    2180             : 
    2181             :         //H2 session, keep on processing inputs
    2182         361 :         if (sess->is_h2 && gf_sk_group_sock_is_set(ctx->sg, sess->socket, GF_SK_SELECT_READ)) {
    2183           0 :                 gf_dm_sess_process(sess->http_sess);
    2184             :         }
    2185             : 
    2186         361 :         if (sess->done) return;
    2187             :         //associated input directly writes to session
    2188         361 :         if (sess->in_source && !sess->in_source->resource) return;
    2189             : 
    2190         359 :         if (sess->canceled) {
    2191           0 :                 log_request_done(sess);
    2192           0 :                 goto session_done;
    2193             :         }
    2194             : 
    2195         359 :         if (!gf_sk_group_sock_is_set(ctx->sg, sess->socket, GF_SK_SELECT_WRITE)) {
    2196             :                 return;
    2197             :         }
    2198             :         //resource is not set
    2199         358 :         if (!sess->resource && sess->path) {
    2200           0 :                 if (sess->in_source && !sess->in_source->nb_write) {
    2201           0 :                         sess->last_active_time = gf_sys_clock_high_res();
    2202           0 :                         return;
    2203             :                 }
    2204           0 :                 sess->resource = gf_fopen(sess->path, "rb");
    2205           0 :                 if (!sess->resource) return;
    2206           0 :                 sess->last_active_time = gf_sys_clock_high_res();
    2207           0 :                 gf_fseek(sess->resource, sess->file_pos, SEEK_SET);
    2208             :         }
    2209             : 
    2210             :         //refresh file size
    2211         358 :         if (sess->put_in_progress==1) {
    2212           0 :                 sess->file_size = gf_fsize(sess->resource);
    2213           0 :                 gf_fseek(sess->resource, sess->file_pos, SEEK_SET);
    2214             :         }
    2215             : 
    2216         655 : resend:
    2217             : 
    2218             :         //we have ranges
    2219         655 :         if (sess->nb_ranges) {
    2220             :                 //current range is done
    2221          72 :                 if ((s64) sess->file_pos >= sess->ranges[sess->range_idx].end) {
    2222             :                         //load next range, seeking file
    2223          30 :                         if (sess->range_idx+1<sess->nb_ranges) {
    2224           0 :                                 sess->range_idx++;
    2225           0 :                                 sess->file_pos = (u64) sess->ranges[sess->range_idx].start;
    2226           0 :                                 gf_fseek(sess->resource, sess->file_pos, SEEK_SET);
    2227             :                         }
    2228             :                 }
    2229          72 :                 if (sess->range_idx<sess->nb_ranges) {
    2230          72 :                         to_read = sess->ranges[sess->range_idx].end + 1 - sess->file_pos;
    2231             :                 }
    2232         583 :         } else if (sess->file_pos < sess->file_size) {
    2233         260 :                 to_read = sess->file_size - sess->file_pos;
    2234             :         }
    2235             : 
    2236         655 :         if (to_read) {
    2237             :                 //rescedule asap while we send
    2238         473 :                 ctx->next_wake_us = 1;
    2239             : 
    2240         473 :                 if (to_read > (u64) sess->ctx->block_size)
    2241             :                         to_read = (u64) sess->ctx->block_size;
    2242             : 
    2243         473 :                 read = (u32) gf_fread(sess->buffer, (u32) to_read, sess->resource);
    2244             :                 //may happen when file writing is in progress
    2245         473 :                 if (!read) {
    2246         172 :                         sess->last_active_time = gf_sys_clock_high_res();
    2247         172 :                         return;
    2248             :                 }
    2249             :                 //transfer of file being uploaded, use chunk transfer
    2250         400 :                 if (!sess->is_h2 && sess->use_chunk_transfer) {
    2251             :                         char szHdr[100];
    2252             :                         u32 len;
    2253             :                         sprintf(szHdr, "%X\r\n", read);
    2254          99 :                         len = (u32) strlen(szHdr);
    2255             : 
    2256          99 :                         e = gf_dm_sess_send(sess->http_sess, szHdr, len);
    2257          99 :                         e |= gf_dm_sess_send(sess->http_sess, sess->buffer, read);
    2258          99 :                         e |= gf_dm_sess_send(sess->http_sess, "\r\n", 2);
    2259             :                 } else {
    2260         202 :                         e = gf_dm_sess_send(sess->http_sess, sess->buffer, read);
    2261             :                 }
    2262         301 :                 sess->last_active_time = gf_sys_clock_high_res();
    2263             : 
    2264         301 :                 sess->file_pos += read;
    2265         301 :                 sess->nb_bytes += read;
    2266             : 
    2267         301 :                 if (e) {
    2268           1 :                         if (e==GF_IP_CONNECTION_CLOSED) {
    2269           1 :                                 GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[HTTPOut] Connection to %s for %s closed\n", sess->peer_address, sess->path));
    2270           1 :                                 sess->done = GF_TRUE;
    2271           1 :                                 sess->canceled = GF_FALSE;
    2272           1 :                                 httpout_close_session(sess);
    2273           1 :                                 log_request_done(sess);
    2274           1 :                                 return;
    2275             :                         }
    2276           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Error sending data to %s for %s: %s\n", sess->peer_address, sess->path, gf_error_to_string(e) ));
    2277             :                 } else {
    2278         300 :                         GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[HTTPOut] sending data to %s for %s: "LLU"/"LLU" bytes\n", sess->peer_address, sess->path, sess->nb_bytes, sess->bytes_in_req));
    2279             : 
    2280         300 :                         if (gf_sk_select(sess->socket, GF_SK_SELECT_WRITE)==GF_OK) {
    2281             :                                 goto resend;
    2282             :                         }
    2283             :                 }
    2284             :                 return;
    2285             :         }
    2286             :         //file not done yet ...
    2287         182 :         if (sess->file_in_progress || (sess->put_in_progress==1)) {
    2288           0 :                 sess->last_active_time = gf_sys_clock_high_res();
    2289           0 :                 return;
    2290             :         }
    2291             : 
    2292         186 : session_done:
    2293             : 
    2294         186 :         sess->file_pos = sess->file_size;
    2295         186 :         sess->last_active_time = gf_sys_clock_high_res();
    2296             : 
    2297             :         //an error ocured uploading the resource, we cannot notify that error so we force a close...
    2298         186 :         if (sess->put_in_progress==2)
    2299             :                 close_session = GF_TRUE;
    2300             : 
    2301         186 :         if (ctx->quit)
    2302             :                 close_session = GF_TRUE;
    2303             : 
    2304         186 :         if (!sess->done) {
    2305         182 :                 if (!sess->is_h2 && sess->use_chunk_transfer) {
    2306             :                         assert(sess->nb_bytes);
    2307          20 :                         gf_dm_sess_send(sess->http_sess, "0\r\n\r\n", 5);
    2308             :                 } else {
    2309         162 :                         gf_dm_sess_send(sess->http_sess, NULL, 0);
    2310             :                 }
    2311         182 :                 if (sess->resource) gf_fclose(sess->resource);
    2312         182 :                 sess->resource = NULL;
    2313             : 
    2314         182 :                 if (sess->nb_bytes) {
    2315         182 :                         GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTPOut] Done sending %s to %s ("LLU"/"LLU" bytes)\n", sess->path, sess->peer_address, sess->nb_bytes, sess->bytes_in_req));
    2316             :                 }
    2317             : 
    2318         182 :                 log_request_done(sess);
    2319             : 
    2320             :                 //keep resource active
    2321         182 :                 sess->done = GF_TRUE;
    2322         182 :                 sess->canceled = GF_FALSE;
    2323             : 
    2324             :         }
    2325         186 :         if (close_session) {
    2326           4 :                 httpout_close_session(sess);
    2327             :         }
    2328             :         //might be NULL if quit was set
    2329         182 :         else if (sess->http_sess) {
    2330         182 :                 sess->headers_done = GF_FALSE;
    2331         182 :                 gf_dm_sess_server_reset(sess->http_sess);
    2332             :         }
    2333             : }
    2334             : 
    2335         691 : static Bool httpout_open_input(GF_HTTPOutCtx *ctx, GF_HTTPOutInput *in, const char *name, Bool is_delete)
    2336             : {
    2337             : //      Bool reassign_clients = GF_TRUE;
    2338             :         u32 len = 0;
    2339         691 :     char *o_url = NULL;
    2340             :     const char *dir = NULL;
    2341             :     const char *sep;
    2342             : 
    2343         691 :     if (in->is_open && !is_delete) return GF_FALSE;
    2344         691 :     if (!in->upload) {
    2345             :         //singe session mode, not recording, nothing to do
    2346         661 :         if (ctx->single_mode) {
    2347           1 :                         in->done = GF_FALSE;
    2348           1 :                         in->is_open = GF_TRUE;
    2349           1 :                         return GF_FALSE;
    2350             :                 }
    2351             :         //server mode not recording, nothing to do
    2352         660 :                 if (!ctx->rdirs.nb_items) return GF_FALSE;
    2353         660 :                 dir = ctx->rdirs.vals[0];
    2354         660 :                 if (!dir) return GF_FALSE;
    2355         660 :                 len = (u32) strlen(dir);
    2356         660 :                 if (!len) return GF_FALSE;
    2357             : 
    2358         660 :         if (in->resource) return GF_FALSE;
    2359             :     }
    2360             : 
    2361         690 :     sep = name ? strstr(name, "://") : NULL;
    2362         690 :     if (sep) sep = strchr(sep+3, '/');
    2363         690 :         if (!sep) {
    2364           7 :         if (in->force_dst_name) {
    2365           1 :             sep = in->path;
    2366           6 :         } else if (ctx->dst) {
    2367           6 :             u32 i, count = gf_list_count(ctx->inputs);
    2368           6 :             for (i=0; i<count; i++) {
    2369             :                 char *path_sep;
    2370           6 :                 GF_HTTPOutInput *an_in = gf_list_get(ctx->inputs, i);
    2371           6 :                 if (an_in==in) continue;
    2372           6 :                 if (!an_in->path) continue;
    2373           6 :                 if (ctx->dst && !an_in->force_dst_name) continue;
    2374           6 :                 if (!gf_filter_pid_share_origin(in->ipid, an_in->ipid))
    2375           0 :                     continue;
    2376             :                 
    2377           6 :                 o_url = gf_strdup(an_in->path);
    2378           6 :                 path_sep = strrchr(o_url, '/');
    2379           6 :                 if (path_sep) {
    2380           6 :                     path_sep[1] = 0;
    2381           6 :                     gf_dynstrcat(&o_url, name, NULL);
    2382           6 :                     sep = o_url;
    2383             :                 } else {
    2384             :                     sep = name;
    2385             :                 }
    2386             :                 break;
    2387             :             }
    2388             :                 } else {
    2389             :                         sep = name;
    2390             :                 }
    2391           7 :                 if (sep && (sep[0] != '/')) {
    2392           0 :                         char *new_url = NULL;
    2393           0 :                         gf_dynstrcat(&new_url, "/", NULL);
    2394           0 :                         gf_dynstrcat(&new_url, sep, NULL);
    2395           0 :                         if (o_url) gf_free(o_url);
    2396           0 :                         sep = o_url = new_url;
    2397             :                 }
    2398             :     }
    2399         690 :     if (!sep) {
    2400           0 :         GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] %s output file %s but cannot guess path !\n", is_delete ? "Deleting" : "Opening",  name));
    2401             :                 return GF_FALSE;
    2402             :         }
    2403             : 
    2404         690 :         GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTPOut] %s output file %s\n", is_delete ? "Deleting" : "Opening",  name));
    2405         690 :         if (in->upload) {
    2406             :                 GF_Err e;
    2407          30 :                 in->done = GF_FALSE;
    2408          30 :                 in->is_open = GF_TRUE;
    2409             : 
    2410          30 :                 in->is_delete = is_delete;
    2411          30 :                 if (!in->force_dst_name) {
    2412          17 :                         char *old = in->path;
    2413          17 :                         in->path = gf_strdup(sep);
    2414          17 :                         if (old) gf_free(old);
    2415             :                 }
    2416          30 :                 if (o_url) gf_free(o_url);
    2417             :                 
    2418          30 :                 e = gf_dm_sess_setup_from_url(in->upload, in->path, GF_TRUE);
    2419          30 :                 if (!e) {
    2420          30 :                         in->cur_header = 0;
    2421          30 :                         e = gf_dm_sess_process(in->upload);
    2422             :                 }
    2423             : 
    2424          30 :                 if (e) {
    2425           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Failed to open output file %s: %s\n", in->path, gf_error_to_string(e) ));
    2426           0 :                         in->is_open = GF_FALSE;
    2427           0 :                         return GF_FALSE;
    2428             :                 }
    2429          30 :                 in->is_h2 = gf_dm_sess_is_h2(in->upload);
    2430             : 
    2431          30 :                 if (is_delete) {
    2432             :                         //get response
    2433           2 :                         gf_dm_sess_process(in->upload);
    2434           2 :                         in->done = GF_TRUE;
    2435           2 :                         in->is_open = GF_FALSE;
    2436           2 :                         in->is_delete = GF_FALSE;
    2437             :                 }
    2438             :                 return GF_TRUE;
    2439             :         }
    2440             : 
    2441         660 :         if (ctx->log_record) {
    2442           0 :                 GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("[HTTPOut] %s output file %s\n", is_delete ? "Deleting" : "Opening",  name));
    2443             :         }
    2444             : 
    2445             :         //file delete is async (the resource associated with the input can still be active)
    2446         660 :         if (is_delete) {
    2447          20 :                 char *loc_path = NULL;
    2448          20 :                 gf_dynstrcat(&loc_path, dir, NULL);
    2449          20 :                 if (!strchr("/\\", dir[len-1]))
    2450          20 :                         gf_dynstrcat(&loc_path, "/", NULL);
    2451          20 :                 if (sep[0]=='/')
    2452          20 :                         gf_dynstrcat(&loc_path, sep+1, NULL);
    2453             :                 else
    2454           0 :                         gf_dynstrcat(&loc_path, sep, NULL);
    2455             : 
    2456          20 :                 gf_file_delete(loc_path);
    2457          20 :                 if (o_url) gf_free(o_url);
    2458          20 :                 gf_free(loc_path);
    2459             :                 return GF_TRUE;
    2460             :         }
    2461             : 
    2462         640 :         in->done = GF_FALSE;
    2463         640 :         in->is_open = GF_TRUE;
    2464             : 
    2465             : 
    2466         640 :         if (in->path && !strcmp(in->path, sep)) {
    2467             : //              reassign_clients = GF_FALSE;
    2468             :         } else {
    2469         584 :                 if (in->path) gf_free(in->path);
    2470         584 :                 in->path = gf_strdup(sep);
    2471             :         }
    2472         640 :     if (o_url) gf_free(o_url);
    2473             : 
    2474         640 :         httpout_set_local_path(ctx, in);
    2475             : 
    2476         640 :         in->resource = gf_fopen(in->local_path, "wb");
    2477         640 :         if (!in->resource)
    2478           0 :                 in->is_open = GF_FALSE;
    2479             :         return GF_TRUE;
    2480             : }
    2481             : 
    2482        1063 : static void httpout_close_input(GF_HTTPOutCtx *ctx, GF_HTTPOutInput *in)
    2483             : {
    2484        1063 :         if (!in->is_open) return;
    2485         669 :         in->is_open = GF_FALSE;
    2486         669 :         in->done = GF_TRUE;
    2487             : 
    2488         669 :         GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTPOut] Closing output %s\n", in->local_path ? in->local_path : in->path));
    2489             : 
    2490         669 :         if (in->upload) {
    2491             :                 GF_Err e;
    2492          28 :                 if (!in->is_h2) {
    2493          28 :                         e = gf_dm_sess_send(in->upload, "0\r\n\r\n", 5);
    2494          28 :                         if (e) {
    2495           0 :                                 GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Error sending last chunk to %s: %s\n", in->local_path ? in->local_path : in->path, gf_error_to_string(e) ));
    2496             :                         }
    2497             :                 }
    2498             :                 //signal we're done sending the body
    2499          28 :                 gf_dm_sess_send(in->upload, NULL, 0);
    2500             : 
    2501             :                 //fetch reply (blocking)
    2502          28 :                 e = gf_dm_sess_process(in->upload);
    2503          28 :                 if (e) {
    2504           3 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Error closing output %s: %s\n", in->local_path ? in->local_path : in->path, gf_error_to_string(e) ));
    2505             :                 }
    2506             : 
    2507             :         } else {
    2508             :                 u32 i, count;
    2509             : 
    2510         641 :                 if (ctx->log_record) {
    2511           0 :                         GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("[HTTPOut] Closing output file %s\n", in->local_path ? in->local_path : in->path));
    2512             :                 }
    2513             : 
    2514         641 :                 if (in->resource) {
    2515             :                         assert(in->local_path);
    2516             :                         //close all LL-HLS chunks before closing session
    2517         640 :                         httpout_close_hls_chunk(ctx, in, GF_FALSE);
    2518             : 
    2519             :                         //detach all clients from this input and reassign to a regular output
    2520         640 :                         count = gf_list_count(ctx->sessions);
    2521        1395 :                         for (i=0; i<count; i++) {
    2522         755 :                                 GF_HTTPOutSession *sess = gf_list_get(ctx->sessions, i);
    2523         755 :                                 if (sess->in_source != in) continue;
    2524             :                                 assert(sess->file_in_progress);
    2525             :                                 if (sess->in_source) {
    2526          21 :                                         sess->in_source->nb_dest--;
    2527          21 :                                         sess->in_source = NULL;
    2528          21 :                                         if (!sess->resource && sess->path) {
    2529           0 :                                                 sess->resource = gf_fopen(sess->path, "rb");
    2530             :                                         }
    2531             :                                 }
    2532             :                                 //get final size by forcing a seek
    2533          21 :                                 sess->file_size = gf_fsize(sess->resource);
    2534          21 :                                 gf_fseek(sess->resource, sess->file_pos, SEEK_SET);
    2535          21 :                                 sess->file_in_progress = GF_FALSE;
    2536             :                         }
    2537         640 :                         gf_fclose(in->resource);
    2538         640 :                         in->resource = NULL;
    2539             :                 } else {
    2540           1 :                         count = gf_list_count(ctx->active_sessions);
    2541           2 :                         for (i=0; i<count; i++) {
    2542           1 :                                 GF_HTTPOutSession *sess = gf_list_get(ctx->active_sessions, i);
    2543           1 :                                 if (sess->in_source != in) continue;
    2544             :                                 assert(sess->nb_bytes);
    2545           1 :                                 if (!sess->is_h2)
    2546           1 :                                         gf_dm_sess_send(sess->http_sess, "0\r\n\r\n", 5);
    2547             : 
    2548             :                                 //signal we're done sending the body
    2549           1 :                                 gf_dm_sess_send(sess->http_sess, NULL, 0);
    2550             : 
    2551           1 :                                 log_request_done(sess);
    2552             :                         }
    2553             :                 }
    2554             :         }
    2555         669 :         in->nb_write = 0;
    2556             : }
    2557        2056 : u32 httpout_write_input(GF_HTTPOutCtx *ctx, GF_HTTPOutInput *in, const u8 *pck_data, u32 pck_size, Bool file_start)
    2558             : {
    2559             :         u32 out=0;
    2560             : 
    2561        2056 :         if (!in->is_open) return 0;
    2562             : 
    2563        2056 :         GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[HTTPOut] Writing %d bytes to output file %s\n", pck_size, in->local_path ? in->local_path : in->path));
    2564             : 
    2565        2056 :         if (in->upload) {
    2566             :                 char szChunkHdr[100];
    2567             :                 u32 chunk_hdr_len=0;
    2568             :                 GF_Err e;
    2569             :                 u32 nb_retry = 0;
    2570             :                 out = pck_size;
    2571             : 
    2572          80 :                 if (!in->is_h2) {
    2573             :                         sprintf(szChunkHdr, "%X\r\n", pck_size);
    2574          80 :                         chunk_hdr_len = (u32) strlen(szChunkHdr);
    2575             :                 }
    2576          80 : retry:
    2577          80 :                 if (!in->is_h2) {
    2578          80 :                         e = gf_dm_sess_send(in->upload, szChunkHdr, chunk_hdr_len);
    2579          80 :                         if (!e) e = gf_dm_sess_send(in->upload, (u8 *) pck_data, pck_size);
    2580          80 :                         if (!e) e = gf_dm_sess_send(in->upload, "\r\n", 2);
    2581             :                 } else {
    2582           0 :                         e = gf_dm_sess_send(in->upload, (u8 *) pck_data, pck_size);
    2583             :                 }
    2584          80 :                 if (e==GF_IP_CONNECTION_CLOSED) {
    2585           0 :                         if (file_start && (nb_retry<10) ) {
    2586           0 :                                 in->is_open = GF_FALSE;
    2587           0 :                                 nb_retry++;
    2588           0 :                                 if (httpout_open_input(ctx, in, in->path, GF_FALSE))
    2589             :                                         goto retry;
    2590             :                         }
    2591             :                 }
    2592          80 :                 if (e) {
    2593           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Error writing to output file %s: %s\n", in->local_path ? in->local_path : in->path, gf_error_to_string(e) ));
    2594             :                         out = 0;
    2595             :                 }
    2596             : 
    2597             :         } else {
    2598             :                 char szChunkHdr[100];
    2599             :                 u32 chunk_hdr_len=0;
    2600        1976 :                 u32 i, count = gf_list_count(ctx->active_sessions);
    2601             : 
    2602        1976 :                 if (in->resource) {
    2603        1958 :                         out = (u32) gf_fwrite(pck_data, pck_size, in->resource);
    2604        1958 :                         gf_fflush(in->resource);
    2605             : 
    2606        1958 :                         if (in->hls_chunk) {
    2607         180 :                                 u32 wb = (u32) gf_fwrite(pck_data, pck_size, in->hls_chunk);
    2608         180 :                                 if (wb != pck_size) {
    2609           0 :                                         GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Write error for HLS chunk, wrote %d bytes but had %d to write\n", wb, pck_size));
    2610             :                                 }
    2611         180 :                                 gf_fflush(in->hls_chunk);
    2612             :                         }
    2613             :                 }
    2614             : 
    2615        1599 :                 for (i=0; i<count; i++) {
    2616        1599 :                         GF_HTTPOutSession *sess = gf_list_get(ctx->active_sessions, i);
    2617        1599 :                         if (sess->in_source != in) continue;
    2618             : 
    2619         243 :                         if (sess->send_init_data && in->tunein_data_size && !sess->file_in_progress) {
    2620           0 :                                 if (!sess->is_h2) {
    2621             :                                         char szHdrInit[100];
    2622             :                                         sprintf(szHdrInit, "%X\r\n", in->tunein_data_size);
    2623           0 :                                         u32 len_hdr = (u32) strlen(szHdrInit);
    2624           0 :                                         gf_dm_sess_send(sess->http_sess, szHdrInit, len_hdr);
    2625           0 :                                         gf_dm_sess_send(sess->http_sess, in->tunein_data, in->tunein_data_size);
    2626           0 :                                         gf_dm_sess_send(sess->http_sess, "\r\n", 2);
    2627             :                                 } else {
    2628           0 :                                         gf_dm_sess_send(sess->http_sess, in->tunein_data, in->tunein_data_size);
    2629             :                                 }
    2630           0 :                                 sess->nb_bytes += in->tunein_data_size;
    2631             :                         }
    2632         243 :                         sess->send_init_data = GF_FALSE;
    2633             :                         out = pck_size;
    2634             : 
    2635             :                         /*source is read from disk but a different file handle is used, force refresh by using fseek (so use fsize and seek back to current pos)*/
    2636         243 :                         if (sess->file_in_progress) {
    2637         225 :                                 sess->file_size = gf_fsize(sess->resource);
    2638         225 :                                 gf_fseek(sess->resource, sess->file_pos, SEEK_SET);
    2639             :                         }
    2640             :                         /*source is not read from disk, write data*/
    2641             :                         else {
    2642          18 :                                 if (!sess->is_h2) {
    2643          18 :                                         if (!chunk_hdr_len) {
    2644             :                                                 sprintf(szChunkHdr, "%X\r\n", pck_size);
    2645          18 :                                                 chunk_hdr_len = (u32) strlen(szChunkHdr);
    2646             :                                         }
    2647          18 :                                         gf_dm_sess_send(sess->http_sess, szChunkHdr, chunk_hdr_len);
    2648          18 :                                         gf_dm_sess_send(sess->http_sess, (u8*) pck_data, pck_size);
    2649          18 :                                         gf_dm_sess_send(sess->http_sess, "\r\n", 2);
    2650             :                                 } else {
    2651           0 :                                         gf_dm_sess_send(sess->http_sess, (u8*) pck_data, pck_size);
    2652             :                                 }
    2653          18 :                                 sess->nb_bytes += pck_size;
    2654             :                         }
    2655             :                 }
    2656             :         }
    2657             :         //don't reschedule, we will be notified when new packets are ready
    2658        2056 :         ctx->next_wake_us = 0;
    2659        2056 :         return out;
    2660             : }
    2661             : 
    2662        2057 : static Bool httpout_input_write_ready(GF_HTTPOutCtx *ctx, GF_HTTPOutInput *in)
    2663             : {
    2664             :         u32 i, count;
    2665        2057 :         if (ctx->rdirs.nb_items)
    2666             :                 return GF_TRUE;
    2667             : 
    2668          99 :         if (in->upload) {
    2669             : /*              if (!gf_sk_group_sock_is_set(ctx->sg, in->socket, GF_SK_SELECT_WRITE))
    2670             :                         return GF_FALSE;
    2671             : */
    2672             :                 return GF_TRUE;
    2673             :         }
    2674             : 
    2675          18 :         count = gf_list_count(ctx->active_sessions);
    2676          36 :         for (i=0; i<count; i++) {
    2677          18 :                 GF_HTTPOutSession *sess = gf_list_get(ctx->active_sessions, i);
    2678          18 :                 if (sess->in_source != in) continue;
    2679             : 
    2680          18 :                 if (!sess->file_in_progress && !gf_sk_group_sock_is_set(ctx->sg, sess->socket, GF_SK_SELECT_WRITE))
    2681             :                         return GF_FALSE;
    2682             :         }
    2683          18 :         return count ? GF_TRUE : GF_FALSE;
    2684             : }
    2685             : 
    2686          18 : static void httpin_send_seg_info(GF_HTTPOutInput *in)
    2687             : {
    2688             :         GF_FilterEvent evt;
    2689          18 :         GF_FEVT_INIT(evt, GF_FEVT_SEGMENT_SIZE, in->ipid);
    2690             :         evt.seg_size.seg_url = NULL;
    2691             : 
    2692          18 :         if (in->dash_mode==1) {
    2693           2 :                 evt.seg_size.is_init = GF_TRUE;
    2694           2 :                 in->dash_mode = 2;
    2695             :                 evt.seg_size.media_range_start = 0;
    2696             :                 evt.seg_size.media_range_end = 0;
    2697           2 :                 gf_filter_pid_send_event(in->ipid, &evt);
    2698             :         } else {
    2699             :                 evt.seg_size.is_init = GF_FALSE;
    2700          16 :                 evt.seg_size.media_range_start = in->offset_at_seg_start;
    2701          16 :                 evt.seg_size.media_range_end = in->nb_write-1;
    2702          16 :                 in->offset_at_seg_start = 1+evt.seg_size.media_range_end;
    2703          16 :                 gf_filter_pid_send_event(in->ipid, &evt);
    2704             :         }
    2705          18 : }
    2706             : 
    2707        2934 : static void httpout_process_inputs(GF_HTTPOutCtx *ctx)
    2708             : {
    2709        2934 :         u32 i, nb_eos=0, nb_nopck=0, count = gf_list_count(ctx->inputs);
    2710        7575 :         for (i=0; i<count; i++) {
    2711             :                 Bool start, end;
    2712             :                 const GF_PropertyValue *p;
    2713             :                 const u8 *pck_data;
    2714             :                 u32 pck_size, nb_write;
    2715             :                 GF_FilterPacket *pck;
    2716        4641 :                 GF_HTTPOutInput *in = gf_list_get(ctx->inputs, i);
    2717             : 
    2718             :                 //not sending/writing anything, delete files
    2719        4641 :                 if (!in->is_open && in->file_deletes) {
    2720          28 :                         while (gf_list_count(in->file_deletes)) {
    2721          22 :                                 char *url = gf_list_pop_back(in->file_deletes);
    2722          22 :                                 httpout_open_input(ctx, in, url, GF_TRUE);
    2723          22 :                                 gf_free(url);
    2724             :                         }
    2725           6 :                         gf_list_del(in->file_deletes);
    2726           6 :                         in->file_deletes = NULL;
    2727             :                 }
    2728             : 
    2729             :                 //no destination and holding for first connect, don't drop
    2730        4641 :                 if (!ctx->hmode && !ctx->rdirs.nb_items && !in->nb_dest && in->hold) {
    2731        2595 :                         continue;
    2732             :                 }
    2733             : 
    2734        4630 :                 pck = gf_filter_pid_get_packet(in->ipid);
    2735        4630 :                 if (!pck) {
    2736        2573 :                         nb_nopck++;
    2737             :                         //check end of PID state
    2738        2573 :                         if (gf_filter_pid_is_eos(in->ipid)) {
    2739         397 :                                 nb_eos++;
    2740         397 :                                 if (in->dash_mode && in->is_open) {
    2741           2 :                                         httpin_send_seg_info(in);
    2742             :                                 }
    2743         397 :                                 httpout_close_input(ctx, in);
    2744             :                         }
    2745        2573 :                         continue;
    2746             :                 }
    2747             : 
    2748             : 
    2749        2057 :                 gf_filter_pck_get_framing(pck, &start, &end);
    2750             : 
    2751        2057 :                 if (in->dash_mode) {
    2752         660 :                         p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM);
    2753         660 :                         if (p) {
    2754          16 :                                 httpin_send_seg_info(in);
    2755             : 
    2756          16 :                                 if ( gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENAME))
    2757           8 :                                         start = GF_TRUE;
    2758             :                         }
    2759             :                 }
    2760             : 
    2761        2057 :                 if (start) {
    2762             :                         const GF_PropertyValue *fnum, *fname;
    2763             :                         const char *name = NULL;
    2764             :                         fname = NULL;
    2765             : 
    2766         669 :                         if (in->is_open)
    2767           7 :                                 httpout_close_input(ctx, in);
    2768             : 
    2769             :                         //file num increased per packet, open new file
    2770         669 :                         fnum = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM);
    2771         669 :                         if (fnum) {
    2772          80 :                                 fname = gf_filter_pid_get_property(in->ipid, GF_PROP_PID_OUTPATH);
    2773             :                                 //if (!fname) name = ctx->dst;
    2774             :                         }
    2775             :                         //filename change at packet start, open new file
    2776          80 :                         if (!fname) fname = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENAME);
    2777         669 :                         if (!fname) fname = gf_filter_pck_get_property(pck, GF_PROP_PID_OUTPATH);
    2778         669 :                         if (fname) name = fname->value.string;
    2779             : 
    2780         567 :                         if (!name) {
    2781             :                                 /*if PID was connected to an alias, get the alias context to get the destination
    2782             :                                 Otherwise PID was directly connected to the main filter, use main filter destination*/
    2783         102 :                                 GF_HTTPOutCtx *orig_ctx = gf_filter_pid_get_alias_udta(in->ipid);
    2784         102 :                                 if (!orig_ctx) orig_ctx = ctx;
    2785         102 :                                 name = orig_ctx->dst;
    2786             :                         }
    2787             : 
    2788         669 :                         httpout_open_input(ctx, in, name, GF_FALSE);
    2789             : 
    2790         669 :                         if (!ctx->hmode && !ctx->rdirs.nb_items && !in->nb_dest) {
    2791           0 :                                 if ((gf_filter_pck_get_dependency_flags(pck)==0xFF) && (gf_filter_pck_get_carousel_version(pck)==1)) {
    2792           0 :                                         pck_data = gf_filter_pck_get_data(pck, &pck_size);
    2793           0 :                                         if (pck_data) {
    2794           0 :                                                 in->tunein_data_size = pck_size;
    2795           0 :                                                 in->tunein_data = gf_realloc(in->tunein_data, pck_size);
    2796           0 :                                                 memcpy(in->tunein_data, pck_data, pck_size);
    2797             :                                         }
    2798             :                                 }
    2799             :                         }
    2800             :                 }
    2801             : 
    2802        2057 :                 p = gf_filter_pck_get_property(pck, GF_PROP_PCK_HLS_FRAG_NUM);
    2803        2057 :                 if (p && in->resource) {
    2804             :                         char szHLSChunk[GF_MAX_PATH];
    2805          90 :                         snprintf(szHLSChunk, GF_MAX_PATH-1, "%s.%d", in->local_path, p->value.uint);
    2806          90 :                         httpout_close_hls_chunk(ctx, in, GF_FALSE);
    2807          90 :                         in->hls_chunk = gf_fopen(szHLSChunk, "w+b");
    2808          90 :                         in->hls_chunk_local_path = gf_strdup(szHLSChunk);
    2809          90 :                         snprintf(szHLSChunk, GF_MAX_PATH-1, "%s.%d", in->path, p->value.uint);
    2810          90 :                         in->hls_chunk_path = gf_strdup(szHLSChunk);
    2811             :                 }
    2812             : 
    2813             :                 //no destination and not holding packets (either first connection not here or disabled), trash packet
    2814        2057 :                 if (!ctx->hmode && !ctx->rdirs.nb_items && !in->nb_dest && !in->hold) {
    2815           0 :                         gf_filter_pid_drop_packet(in->ipid);
    2816           0 :                         continue;
    2817             :                 }
    2818             : 
    2819        2057 :                 if (!httpout_input_write_ready(ctx, in)) {
    2820           0 :                         continue;
    2821             :                 }
    2822             : 
    2823        2057 :                 pck_data = gf_filter_pck_get_data(pck, &pck_size);
    2824        2057 :                 if (in->upload || ctx->single_mode || in->resource) {
    2825        2057 :                         GF_FilterFrameInterface *hwf = gf_filter_pck_get_frame_interface(pck);
    2826        2057 :                         if (pck_data && pck_size) {
    2827             : 
    2828        2056 :                                 if (in->patch_blocks && gf_filter_pck_get_seek_flag(pck)) {
    2829           0 :                                         u64 bo = gf_filter_pck_get_byte_offset(pck);
    2830           0 :                                         if (bo==GF_FILTER_NO_BO) {
    2831           0 :                                                 GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Cannot patch file, wrong byte offset\n"));
    2832             :                                         } else {
    2833           0 :                                                 if (gf_filter_pck_get_interlaced(pck)) {
    2834           0 :                                                         GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Cannot patch file by byte insertion, not supported by HTTP\n"));
    2835             :                                                 } else {
    2836           0 :                                                         u64 pos = in->nb_write;
    2837             :                                                         //close file
    2838           0 :                                                         httpout_close_input(ctx, in);
    2839             :                                                         //re-open file
    2840           0 :                                                         in->write_start_range = bo;
    2841           0 :                                                         in->write_end_range = bo + pck_size - 1;
    2842           0 :                                                         httpout_open_input(ctx, in, in->path, GF_FALSE);
    2843             : 
    2844           0 :                                                         nb_write = httpout_write_input(ctx, in, pck_data, pck_size, start);
    2845           0 :                                                         if (nb_write!=pck_size) {
    2846           0 :                                                                 GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Write error, wrote %d bytes but had %d to write\n", nb_write, pck_size));
    2847             :                                                         }
    2848           0 :                                                         httpout_close_input(ctx, in);
    2849             : 
    2850           0 :                                                         in->write_start_range = pos;
    2851           0 :                                                         in->write_end_range = 0;
    2852             :                                                 }
    2853             :                                         }
    2854             :                                 } else {
    2855        2056 :                                         if (in->write_start_range) {
    2856           0 :                                                 httpout_open_input(ctx, in, in->path, GF_FALSE);
    2857             :                                         }
    2858             : 
    2859        2056 :                                         nb_write = httpout_write_input(ctx, in, pck_data, pck_size, start);
    2860        2056 :                                         if (nb_write!=pck_size) {
    2861           0 :                                                 GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Write error, wrote %d bytes but had %d to write\n", nb_write, pck_size));
    2862             :                                         }
    2863        2056 :                                         in->nb_write += nb_write;
    2864             :                                 }
    2865           1 :                         } else if (hwf) {
    2866             :                                 u32 w, h, stride, stride_uv, pf;
    2867             :                                 u32 nb_planes, uv_height;
    2868           0 :                                 p = gf_filter_pid_get_property(in->ipid, GF_PROP_PID_WIDTH);
    2869           0 :                                 w = p ? p->value.uint : 0;
    2870           0 :                                 p = gf_filter_pid_get_property(in->ipid, GF_PROP_PID_HEIGHT);
    2871           0 :                                 h = p ? p->value.uint : 0;
    2872           0 :                                 p = gf_filter_pid_get_property(in->ipid, GF_PROP_PID_PIXFMT);
    2873           0 :                                 pf = p ? p->value.uint : 0;
    2874             : 
    2875           0 :                                 stride = stride_uv = 0;
    2876             : 
    2877           0 :                                 if (gf_pixel_get_size_info(pf, w, h, NULL, &stride, &stride_uv, &nb_planes, &uv_height) == GF_TRUE) {
    2878             :                                         u32 k;
    2879           0 :                                         for (k=0; k<nb_planes; k++) {
    2880             :                                                 u32 j, write_h, lsize;
    2881             :                                                 const u8 *out_ptr;
    2882           0 :                                                 u32 out_stride = k ? stride_uv : stride;
    2883           0 :                                                 GF_Err e = hwf->get_plane(hwf, k, &out_ptr, &out_stride);
    2884           0 :                                                 if (e) {
    2885           0 :                                                         GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Failed to fetch plane #%d data from hardware frame, cannot write\n", k));
    2886           0 :                                                         break;
    2887             :                                                 }
    2888           0 :                                                 if (k) {
    2889           0 :                                                         write_h = uv_height;
    2890           0 :                                                         lsize = stride_uv;
    2891             :                                                 } else {
    2892             :                                                         write_h = h;
    2893           0 :                                                         lsize = stride;
    2894             :                                                 }
    2895           0 :                                                 for (j=0; j<write_h; j++) {
    2896           0 :                                                         nb_write = (u32) httpout_write_input(ctx, in, out_ptr, lsize, start);
    2897           0 :                                                         if (nb_write!=lsize) {
    2898           0 :                                                                 GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Write error, wrote %d bytes but had %d to write\n", nb_write, lsize));
    2899             :                                                         }
    2900           0 :                                                         in->nb_write += nb_write;
    2901           0 :                                                         out_ptr += out_stride;
    2902           0 :                                                         start = GF_FALSE;
    2903             :                                                 }
    2904             :                                         }
    2905             :                                 }
    2906             :                         } else {
    2907           1 :                                 GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[HTTPOut] No data associated with packet, cannot write\n"));
    2908             :                         }
    2909             :                 } else {
    2910           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] output file handle is not opened, discarding %d bytes\n", pck_size));
    2911             :                 }
    2912        2057 :                 gf_filter_pid_drop_packet(in->ipid);
    2913        2057 :                 if (end) {
    2914         659 :                         httpout_close_input(ctx, in);
    2915             :                 }
    2916             :         }
    2917             : 
    2918        2934 :         if (count && (nb_eos==count)) {
    2919          16 :                 if (ctx->rdirs.nb_items) {
    2920          10 :                         if (gf_list_count(ctx->active_sessions))
    2921           0 :                                 gf_filter_post_process_task(ctx->filter);
    2922             :                         else
    2923          10 :                                 ctx->done = GF_TRUE;
    2924             :                 }
    2925             :                 else
    2926           6 :                         ctx->done = GF_TRUE;
    2927             :         }
    2928             :         //push mode and no packets on inputs, do not ask for RT reschedule (we will get called if new packets are to be processed)
    2929        2934 :         if ((nb_nopck==count) && (ctx->hmode==MODE_PUSH))
    2930          18 :                 ctx->next_wake_us = 0;
    2931        2934 : }
    2932             : 
    2933        4015 : static GF_Err httpout_process(GF_Filter *filter)
    2934             : {
    2935             :         GF_Err e=GF_OK;
    2936             :         u32 i, count;
    2937        4015 :         GF_HTTPOutCtx *ctx = gf_filter_get_udta(filter);
    2938             : 
    2939        4015 :         if (ctx->done)
    2940             :                 return GF_EOS;
    2941             : 
    2942             :         //wakeup every 50ms when inactive
    2943        2934 :         ctx->next_wake_us = 50000;
    2944             : 
    2945        2934 :         e = gf_sk_group_select(ctx->sg, 10, GF_SK_SELECT_BOTH);
    2946        2934 :         if ((e==GF_OK) && ctx->server_sock) {
    2947             :                 //server mode, check pending connections
    2948        1029 :                 if (gf_sk_group_sock_is_set(ctx->sg, ctx->server_sock, GF_SK_SELECT_READ)) {
    2949          47 :                         httpout_check_new_session(ctx);
    2950             :                 }
    2951             : 
    2952        1029 :                 count = gf_list_count(ctx->active_sessions);
    2953        3023 :                 for (i=0; i<count; i++) {
    2954        1994 :                         GF_HTTPOutSession *sess = gf_list_get(ctx->active_sessions, i);
    2955             :                         //push
    2956        1994 :                         if (sess->in_source) continue;
    2957             : 
    2958             :                         //regular download
    2959        1611 :                         httpout_process_session(filter, ctx, sess);
    2960             :                         //closed, remove
    2961        1611 :                         if (! sess->http_sess) {
    2962          43 :                                 httpout_del_session(sess);
    2963          43 :                                 i--;
    2964          43 :                                 count--;
    2965          43 :                                 if (!count && ctx->quit)
    2966           5 :                                         ctx->done = GF_TRUE;
    2967          43 :                                 continue;
    2968             :                         }
    2969             : 
    2970        1568 :                         if (sess->sub_sess_pending) {
    2971           0 :                                 sess->sub_sess_pending = GF_FALSE;
    2972           0 :                                 count = gf_list_count(ctx->active_sessions);
    2973             :                                 i = -1;
    2974             :                         }
    2975             :                 }
    2976             :         }
    2977             : 
    2978        2934 :         httpout_process_inputs(ctx);
    2979             : 
    2980        2934 :         if (ctx->timeout && ctx->server_sock) {
    2981        2847 :                 count = gf_list_count(ctx->active_sessions);
    2982        4798 :                 for (i=0; i<count; i++) {
    2983             :                         u32 diff_sec;
    2984        1951 :                         GF_HTTPOutSession *sess = gf_list_get(ctx->active_sessions, i);
    2985        1951 :                         if (!sess->done) continue;
    2986             : 
    2987        1089 :                         diff_sec = (u32) (gf_sys_clock_high_res() - sess->last_active_time)/1000000;
    2988        1089 :                         if (diff_sec>ctx->timeout) {
    2989           0 :                                 GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTPOut] Timeout for peer %s after %d sec, closing connection (last request %s)\n", sess->peer_address, diff_sec, sess->in_source ? sess->in_source->path : sess->path ));
    2990             : 
    2991           0 :                                 httpout_close_session(sess);
    2992             : 
    2993           0 :                                 httpout_del_session(sess);
    2994           0 :                                 i--;
    2995           0 :                                 count--;
    2996             :                         }
    2997             :                 }
    2998             :         }
    2999             : 
    3000        2934 :         if (e==GF_EOS) {
    3001           0 :                 if (ctx->dst) return GF_EOS;
    3002             :                 e=GF_OK;
    3003             :         }
    3004             : 
    3005             :         //reschedule was canceled but we still have active sessions, reschedule for our default timeout
    3006        2934 :         if (!ctx->next_wake_us && gf_list_count(ctx->active_sessions)) {
    3007         600 :                 ctx->next_wake_us = 50000;
    3008             :         }
    3009             : 
    3010        2934 :         if (ctx->next_wake_us) {
    3011        1904 :                 gf_filter_ask_rt_reschedule(filter, ctx->next_wake_us);
    3012             :         }
    3013             : 
    3014             :         return e;
    3015             : }
    3016             : 
    3017         483 : static Bool httpout_process_event(GF_Filter *filter, const GF_FilterEvent *evt)
    3018             : {
    3019             :         GF_HTTPOutInput *in;
    3020             :         GF_HTTPOutCtx *ctx;
    3021         483 :         if (evt->base.type!=GF_FEVT_FILE_DELETE)
    3022             :                 return GF_FALSE;
    3023             : 
    3024          22 :         if (!evt->base.on_pid) return GF_TRUE;
    3025          22 :         in = gf_filter_pid_get_udta(evt->base.on_pid);
    3026          22 :         if (!in) return GF_TRUE;
    3027             : 
    3028          22 :         ctx = (GF_HTTPOutCtx *) gf_filter_get_udta(filter);
    3029             :                 //simple server mode (no record, no push), nothing to do
    3030          22 :         if (!in->upload && !ctx->rdirs.nb_items) return GF_TRUE;
    3031             : 
    3032          22 :         if (!in->file_deletes)
    3033           6 :                 in->file_deletes = gf_list_new();
    3034          22 :         gf_list_add(in->file_deletes, gf_strdup(evt->file_del.url));
    3035          22 :         return GF_TRUE;
    3036             : }
    3037             : 
    3038        2198 : static GF_FilterProbeScore httpout_probe_url(const char *url, const char *mime)
    3039             : {
    3040        2198 :         if (!strnicmp(url, "http://", 7)) return GF_FPROBE_SUPPORTED;
    3041        2164 :         if (!strnicmp(url, "https://", 8)) return GF_FPROBE_SUPPORTED;
    3042        2164 :         return GF_FPROBE_NOT_SUPPORTED;
    3043             : }
    3044             : 
    3045          17 : static Bool httpout_use_alias(GF_Filter *filter, const char *url, const char *mime)
    3046             : {
    3047             :         u32 len;
    3048             :         char *sep;
    3049          17 :         GF_HTTPOutCtx *ctx = (GF_HTTPOutCtx *) gf_filter_get_udta(filter);
    3050             : 
    3051             :         //check we have same hostname. If so, accept this destination as a source for our filter
    3052          17 :         sep = strstr(url, "://");
    3053          17 :         if (!sep) return GF_FALSE;
    3054          17 :         sep += 3;
    3055          17 :         sep = strchr(sep, '/');
    3056          17 :         if (!sep) {
    3057           0 :                 if (!strcmp(ctx->dst, url)) return GF_TRUE;
    3058           0 :                 return GF_FALSE;
    3059             :         }
    3060          17 :         len = (u32) (sep - url);
    3061          17 :         if (!strncmp(ctx->dst, url, len)) return GF_TRUE;
    3062           0 :         return GF_FALSE;
    3063             : }
    3064             : 
    3065             : static const GF_FilterCapability HTTPOutCaps[] =
    3066             : {
    3067             :         CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE),
    3068             :         CAP_STRING(GF_CAPS_INPUT,GF_PROP_PID_FILE_EXT, "*"),
    3069             :         CAP_STRING(GF_CAPS_INPUT,GF_PROP_PID_MIME, "*"),
    3070             :         {0},
    3071             :         CAP_UINT(GF_CAPS_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE),
    3072             : };
    3073             : 
    3074             : 
    3075             : #define OFFS(_n)        #_n, offsetof(GF_HTTPOutCtx, _n)
    3076             : static const GF_FilterArgs HTTPOutArgs[] =
    3077             : {
    3078             :         { OFFS(dst), "location of destination resource - see filter help", GF_PROP_NAME, NULL, NULL, 0},
    3079             :         { OFFS(port), "server port", GF_PROP_UINT, "0", NULL, 0},
    3080             :         { OFFS(ifce), "default network interface to use", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED},
    3081             :         { OFFS(rdirs), "list of directories to expose for read - see filter help", GF_PROP_STRING_LIST, NULL, NULL, 0},
    3082             :         { OFFS(wdir), "directory to expose for write - see filter help", GF_PROP_STRING, NULL, NULL, 0},
    3083             :         { OFFS(cert), "certificate file in PEM format to use for TLS mode", GF_PROP_STRING, NULL, NULL, 0},
    3084             :         { OFFS(pkey), "private key file in PEM format to use for TLS mode", GF_PROP_STRING, NULL, NULL, 0},
    3085             :         { OFFS(block_size), "block size used to read and write TCP socket", GF_PROP_UINT, "10000", NULL, GF_FS_ARG_HINT_ADVANCED},
    3086             :         { OFFS(user_agent), "user agent string, by default solved from GPAC preferences", GF_PROP_STRING, "$GUA", NULL, 0},
    3087             :         { OFFS(close), "close HTTP connection after each request", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT},
    3088             :         { OFFS(maxc), "maximum number of connections, 0 is unlimited", GF_PROP_UINT, "100", NULL, GF_FS_ARG_HINT_EXPERT},
    3089             :         { OFFS(maxp), "maximum number of connections for one peer, 0 is unlimited", GF_PROP_UINT, "6", NULL, GF_FS_ARG_HINT_EXPERT},
    3090             :         { OFFS(cache_control), "specify the `Cache-Control` string to add; `none` disable ETag", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED},
    3091             :         { OFFS(hold), "hold packets until one client connects", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED},
    3092             :         { OFFS(hmode), "filter operation mode, ignored if [-wdir]() is set. See filter help for more details. Mode can be\n"
    3093             :         "- default: run in server mode (see filter help)\n"
    3094             :         "- push: run in client mode using PUT or POST (see filter help)\n"
    3095             :         "- source: use server as source filter on incoming PUT/POST", GF_PROP_UINT, "default", "default|push|source", GF_FS_ARG_HINT_ADVANCED},
    3096             :         { OFFS(timeout), "timeout in seconds for persistent connections; 0 disable timeout", GF_PROP_UINT, "30", NULL, GF_FS_ARG_HINT_ADVANCED},
    3097             :         { OFFS(ext), "set extension for graph resolution, regardless of file extension", GF_PROP_NAME, NULL, NULL, GF_FS_ARG_HINT_ADVANCED},
    3098             :         { OFFS(mime), "set mime type for graph resolution", GF_PROP_NAME, NULL, NULL, GF_FS_ARG_HINT_EXPERT},
    3099             :         { OFFS(quit), "exit server once all input PIDs are done and client disconnects (for test purposes)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT},
    3100             :         { OFFS(post), "use POST instead of PUT for uploading files", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT},
    3101             :         { OFFS(dlist), "enable HTML listing for GET requests on directories", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT},
    3102             :         { OFFS(sutc), "insert server UTC in response headers as `Server-UTC: VAL_IN_MS`", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT},
    3103             :         { OFFS(cors), "insert CORS header allowing all domains\n"
    3104             :                 "- off: disable CORS\n"
    3105             :                 "- on: enable CORS\n"
    3106             :                 "- auto: enable CORS when `Origin` is found in request", GF_PROP_UINT, "auto", "auto|off|on", GF_FS_ARG_HINT_EXPERT},
    3107             :         { OFFS(reqlog), "provide short log of the requests indicated in this option (comma separated list, `*` for all) regardless of HTTP log settings. Value `REC` logs file writing start/end", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_EXPERT},
    3108             :         { OFFS(ice), "insert ICE meta-data in response headers in sink mode - see filter help", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT},
    3109             :         { OFFS(max_client_errors), "force disconnection after specified number of consecutive errors from HTTTP 1.1 client (ignored in H/2 or when `close` is set)", GF_PROP_UINT, "20", NULL, GF_FS_ARG_HINT_EXPERT},
    3110             :         {0}
    3111             : };
    3112             : 
    3113             : 
    3114             : GF_FilterRegister HTTPOutRegister = {
    3115             :         .name = "httpout",
    3116             :         GF_FS_SET_DESCRIPTION("HTTP Server")
    3117             : 
    3118             :         GF_FS_SET_HELP("The HTTP output filter can act as:\n"
    3119             :                 "- a simple HTTP server\n"
    3120             :                 "- an HTTP server sink\n"
    3121             :                 "- an HTTP server file sink\n"
    3122             :                 "- an HTTP __client__ sink\n"
    3123             :                 "- an HTTP server __source__\n"
    3124             :                 "  \n"
    3125             :                 "The server currently handles GET, HEAD, PUT, POST, DELETE methods.\n"
    3126             :                 "Single or multiple byte ranges are supported for both GET and PUT/POST methods, in all server modes.\n"
    3127             :                 "- for GET, the resulting body is a single-part body formed by the concatenated byte ranges as requested (no overlap checking).\n"
    3128             :                 "- for PUT/POST, the received data is pushed to the target file according to the byte ranges specified in the client request.\n"
    3129             :                 "  \n"
    3130             :                 "Warning: the partial PUT request is RFC2616 compliant but not compliant with RFC7230. PATCH method is not yet implemented in GPAC.\n"
    3131             :                 "  \n"
    3132             :                 "When a single read directory is specified, the server root `/` is the content of this directory.\n"
    3133             :                 "When multiple read directories are specified, the server root `/` contains the list of the mount points with their directory names.\n"
    3134             :                 "When a write directory is specified, the upload resource name identifies a file in this directory (the write directory name is not present in the URL).\n"
    3135             :                 "  \n"
    3136             :                 "Listing can be enabled on server using [-dlist]().\n"
    3137             :                 "When disabled, a GET on a directory will fail.\n"
    3138             :                 "When enabled, a GET on a directory will return a simple HTML listing of the content inspired from Apache.\n"
    3139             :                 "  \n"
    3140             :                 "# Simple HTTP server\n"
    3141             :                 "In this mode, the filter does not need any input connection and exposes all files in the directories given by [-rdirs]().\n"
    3142             :                 "PUT and POST methods are only supported if a write directory is specified by [-wdir]() option.\n"
    3143             :                 "EX gpac httpout:rdirs=outcoming\n"
    3144             :                 "This sets up a read-only server.\n"
    3145             :                 "  \n"
    3146             :                 "EX gpac httpout:wdir=incoming\n"
    3147             :                 "This sets up a write-only server.\n"
    3148             :                 "  \n"
    3149             :                 "EX gpac httpout:rdirs=outcoming:wdir=incoming:port=8080\n"
    3150             :                 "This sets up a read-write server running on [-port]() 8080.\n"
    3151             :                 "  \n"
    3152             :                 "# HTTP server sink\n"
    3153             :                 "In this mode, the filter will forward input PIDs to connected clients, trashing the data if no client is connected unless [-hold]() is specified.\n"
    3154             :                 "The filter does not use any read directory in this mode.\n"
    3155             :                 "This mode is mostly useful to setup live HTTP streaming of media sessions such as MP3, MPEG-2 TS or other muxed representations:\n"
    3156             :                 "EX gpac -i MP3_SOURCE -o http://localhost/live.mp3 --hold\n"
    3157             :                 "In this example, the server waits for client requests on `/live.mp3` and will then push each input packet to all connected clients.\n"
    3158             :                 "If the source is not real-time, you can inject a reframer filter performing realtime regulation.\n"
    3159             :                 "EX gpac -i MP3_SOURCE reframer:rt=on @ -o http://localhost/live.mp3\n"
    3160             :                 "In this example, the server will push each input packet to all connected clients, or trash the packet if no connected clients.\n"
    3161             :                 "  \n"
    3162             :                 "In this mode, ICECast meta-data can be inserted using [-ice](). The default inserted values are `ice-audio-info`, `icy-br`, `icy-pub` (set to 1) and `icy-name` if input `ServiceName` property is set.\n"
    3163             :                 "The server will also look for any property called `ice-*` on the input pid and inject them.\n"
    3164             :                 "EX gpac -i source.mp3:#ice-Genre=CoolRock -o http://IP/live.mp3 --ice\n"
    3165             :                 "This will inject the header `ice-Genre: CoolRock` in the response."
    3166             :                 "  \n"
    3167             :                 "# HTTP server file sink\n"
    3168             :                 "In this mode, the filter will write input PIDs to files in the first read directory specified, acting as a file output sink.\n"
    3169             :                 "The filter uses a read directory in this mode, which must be writable.\n"
    3170             :                 "Upon client GET request, the server will check if the requested URL matches the name of a file currently being written by the server.\n"
    3171             :                 "- If so, the server will:\n"
    3172             :                 "  - send the content using HTTP chunk transfer mode, starting with what is already written on disk\n"
    3173             :                 "  - push remaining data to the client as soon as received while writing it to disk, until source file is done\n"
    3174             :                 "- If not so, the server will simply send the file from the disk as a regular HTTP session, without chunk transfer.\n"
    3175             :                 "  \nThis mode is typically used for origin server in HAS sessions where clients may request files while they are being produced (low latency DASH).\n"
    3176             :                 "EX gpac -i SOURCE reframer:rt=on @ -o http://localhost:8080/live.mpd --rdirs=temp --dmode=dynamic --cdur=0.1\n"
    3177             :                 "In this example, a real-time dynamic DASH session with chunks of 100ms is created, outputting files in `temp`. A client connecting to the live edge will receive segments as they are produced using HTTP chunk transfer.\n"
    3178             :                 "  \n"
    3179             :                 "# HTTP client sink\n"
    3180             :                 "In this mode, the filter will upload input PIDs data to remote server using PUT (or POST if [-post]() is set).\n"
    3181             :                 "This mode must be explicitly activated using [-hmode]().\n"
    3182             :                 "The filter uses no read or write directories in this mode.\n"
    3183             :                 "EX gpac -i SOURCE -o http://targethost:8080/live.mpd:gpac:hmode=push\n"
    3184             :                 "In this example, the filter will send PUT methods to the server running on [-port]() 8080 at `targethost` location (IP address or name).\n"
    3185             :                 "  \n"
    3186             :                 "# HTTP server source\n"
    3187             :                 "In this mode, the server acts as a source rather than a sink. It declares incoming PUT or POST methods as output PIDs\n"
    3188             :                 "This mode must be explicitly activated using [-hmode]().\n"
    3189             :                 "The filter uses no read or write directories in this mode, and uploaded data is NOT stored by the server.\n"
    3190             :                 "EX gpac httpout:hmode=source vout aout\n"
    3191             :                 "In this example, the filter will try to play uploaded files through video and audio output.\n"
    3192             :                 "  \n"
    3193             :                 "# HTTPS server\n"
    3194             :                 "The server can run over TLS (https) for all the server modes. TLS is enabled by specifying [-cert]() and [-pkey]() options.\n"
    3195             :                 "Both certificate and key must be in PEM format.\n"
    3196             :                 "The server currently only operates in either HTTPS or HTTP mode and cannot run both modes at the same time. You will need to use two httpout filters for this, one operating in HTTPS and one operating in HTTP.\n"
    3197             :                 )
    3198             :         .private_size = sizeof(GF_HTTPOutCtx),
    3199             :         .max_extra_pids = -1,
    3200             :         .args = HTTPOutArgs,
    3201             :         .probe_url = httpout_probe_url,
    3202             :         .initialize = httpout_initialize,
    3203             :         .finalize = httpout_finalize,
    3204             :         SETCAPS(HTTPOutCaps),
    3205             :         .configure_pid = httpout_configure_pid,
    3206             :         .process = httpout_process,
    3207             :         .process_event = httpout_process_event,
    3208             :         .use_alias = httpout_use_alias
    3209             : };
    3210             : 
    3211             : 
    3212        2877 : const GF_FilterRegister *httpout_register(GF_FilterSession *session)
    3213             : {
    3214        2877 :         return &HTTPOutRegister;
    3215             : }
    3216             : 

Generated by: LCOV version 1.13