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(>ime);
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 :
|