Line data Source code
1 : /*
2 : * GPAC - Multimedia Framework C SDK
3 : *
4 : * Authors: Jean Le Feuvre
5 : * Copyright (c) Telecom ParisTech 2017-2021
6 : * All rights reserved
7 : *
8 : * This file is part of GPAC / HTTP input filter using GPAC http stack
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 :
27 : #include <gpac/filters.h>
28 : #include <gpac/constants.h>
29 : #include <gpac/download.h>
30 :
31 :
32 : typedef enum
33 : {
34 : GF_HTTPIN_STORE_DISK=0,
35 : GF_HTTPIN_STORE_DISK_KEEP,
36 : GF_HTTPIN_STORE_MEM,
37 : GF_HTTPIN_STORE_MEM_KEEP,
38 : GF_HTTPIN_STORE_NONE,
39 : GF_HTTPIN_STORE_NONE_KEEP,
40 : } GF_HTTPInStoreMode;
41 :
42 : enum
43 : {
44 : HTTP_PCK_NONE=0,
45 : HTTP_PCK_OUT=1,
46 : HTTP_PCK_OUT_EOS=2,
47 : };
48 :
49 : typedef struct
50 : {
51 : //options
52 : char *src;
53 : u32 block_size;
54 : GF_HTTPInStoreMode cache;
55 : GF_Fraction64 range;
56 : char *ext;
57 : char *mime;
58 :
59 : //internal
60 : Bool initial_ack_done;
61 : GF_DownloadManager *dm;
62 :
63 : //only one output pid declared
64 : GF_FilterPid *pid;
65 :
66 : GF_DownloadSession *sess;
67 :
68 : char *block;
69 : u32 pck_out;
70 : Bool is_end;
71 : u64 nb_read, file_size;
72 : FILE *cached;
73 : u32 blob_size;
74 :
75 : Bool do_reconfigure;
76 : Bool full_file_only;
77 : GF_Err last_state;
78 : Bool is_source_switch;
79 : Bool prev_was_init_segment;
80 : } GF_HTTPInCtx;
81 :
82 2 : static void httpin_notify_error(GF_Filter *filter, GF_HTTPInCtx *ctx, GF_Err e)
83 : {
84 2 : if (filter && (ctx->last_state == GF_OK)) {
85 2 : if (!ctx->initial_ack_done) {
86 0 : gf_filter_setup_failure(filter, e);
87 0 : ctx->initial_ack_done = GF_TRUE;
88 : } else {
89 2 : gf_filter_notification_failure(filter, e, GF_FALSE);
90 : }
91 2 : ctx->last_state = e;
92 : }
93 2 : }
94 :
95 117 : static GF_Err httpin_initialize(GF_Filter *filter)
96 : {
97 117 : GF_HTTPInCtx *ctx = (GF_HTTPInCtx *) gf_filter_get_udta(filter);
98 : GF_Err e;
99 : char *server;
100 : u32 flags = 0;
101 :
102 117 : if (!ctx || !ctx->src) return GF_BAD_PARAM;
103 117 : ctx->dm = gf_filter_get_download_manager(filter);
104 117 : if (!ctx->dm) return GF_SERVICE_ERROR;
105 :
106 117 : ctx->block = gf_malloc(ctx->block_size +1);
107 :
108 : flags = GF_NETIO_SESSION_NOT_THREADED | GF_NETIO_SESSION_PERSISTENT;
109 117 : if (ctx->cache==GF_HTTPIN_STORE_MEM)
110 : flags |= GF_NETIO_SESSION_MEMORY_CACHE;
111 117 : else if (ctx->cache==GF_HTTPIN_STORE_NONE)
112 : flags |= GF_NETIO_SESSION_NOT_CACHED;
113 115 : else if (ctx->cache==GF_HTTPIN_STORE_DISK_KEEP)
114 : flags |= GF_NETIO_SESSION_KEEP_CACHE;
115 115 : else if (ctx->cache==GF_HTTPIN_STORE_MEM_KEEP) {
116 : flags |= GF_NETIO_SESSION_MEMORY_CACHE|GF_NETIO_SESSION_KEEP_FIRST_CACHE;
117 67 : ctx->cache = GF_HTTPIN_STORE_MEM;
118 : }
119 48 : else if (ctx->cache==GF_HTTPIN_STORE_NONE_KEEP) {
120 : flags |= GF_NETIO_SESSION_NOT_CACHED|GF_NETIO_SESSION_MEMORY_CACHE|GF_NETIO_SESSION_KEEP_FIRST_CACHE;
121 0 : ctx->cache = GF_HTTPIN_STORE_NONE;
122 : }
123 :
124 117 : server = strstr(ctx->src, "://");
125 117 : if (server) server += 3;
126 117 : if (server && strstr(server, "://")) {
127 5 : ctx->is_end = GF_TRUE;
128 5 : return gf_filter_pid_raw_new(filter, server, server, NULL, NULL, NULL, 0, GF_FALSE, &ctx->pid);
129 : }
130 :
131 112 : ctx->sess = gf_dm_sess_new(ctx->dm, ctx->src, flags, NULL, NULL, &e);
132 112 : if (e) {
133 0 : gf_filter_setup_failure(filter, e);
134 0 : ctx->initial_ack_done = GF_TRUE;
135 0 : return e;
136 : }
137 112 : if (ctx->range.num || ctx->range.den) {
138 9 : gf_dm_sess_set_range(ctx->sess, ctx->range.num, ctx->range.den, GF_TRUE);
139 : }
140 :
141 : #ifdef GPAC_ENABLE_COVERAGE
142 112 : if (gf_sys_is_cov_mode())
143 : httpin_notify_error(NULL, NULL, GF_OK);
144 : #endif
145 :
146 112 : return GF_OK;
147 : }
148 :
149 :
150 : static void httpin_set_eos(GF_HTTPInCtx *ctx)
151 : {
152 : //no pending packets, signal eos right away
153 653 : if (!ctx->pck_out) {
154 41 : gf_filter_pid_set_eos(ctx->pid);
155 : }
156 : else
157 612 : ctx->pck_out = HTTP_PCK_OUT_EOS;
158 : }
159 :
160 :
161 117 : void httpin_finalize(GF_Filter *filter)
162 : {
163 117 : GF_HTTPInCtx *ctx = (GF_HTTPInCtx *) gf_filter_get_udta(filter);
164 :
165 117 : if (ctx->sess) gf_dm_sess_del(ctx->sess);
166 :
167 117 : if (ctx->block) gf_free(ctx->block);
168 117 : if (ctx->cached) gf_fclose(ctx->cached);
169 117 : }
170 :
171 2939 : static GF_FilterProbeScore httpin_probe_url(const char *url, const char *mime_type)
172 : {
173 2939 : if (!strnicmp(url, "http://", 7) ) return GF_FPROBE_SUPPORTED;
174 2848 : if (!strnicmp(url, "https://", 8) ) return GF_FPROBE_SUPPORTED;
175 2821 : if (!strnicmp(url, "gmem://", 7) ) return GF_FPROBE_SUPPORTED;
176 2821 : return GF_FPROBE_NOT_SUPPORTED;
177 : }
178 :
179 5622 : static void httpin_rel_pck(GF_Filter *filter, GF_FilterPid *pid, GF_FilterPacket *pck)
180 : {
181 5622 : GF_HTTPInCtx *ctx = (GF_HTTPInCtx *) gf_filter_get_udta(filter);
182 :
183 5622 : if (ctx->pck_out==HTTP_PCK_OUT_EOS) {
184 612 : gf_filter_pid_set_eos(ctx->pid);
185 : }
186 5622 : ctx->pck_out = HTTP_PCK_NONE;
187 : //ready to process again
188 5622 : gf_filter_post_process_task(filter);
189 5622 : }
190 :
191 633 : static Bool httpin_process_event(GF_Filter *filter, const GF_FilterEvent *evt)
192 : {
193 : GF_Err e;
194 633 : GF_HTTPInCtx *ctx = (GF_HTTPInCtx *) gf_filter_get_udta(filter);
195 :
196 633 : if (evt->base.on_pid && (evt->base.on_pid != ctx->pid)) return GF_FALSE;
197 :
198 633 : switch (evt->base.type) {
199 : //we only check PLAY for full_file_only hint
200 0 : case GF_FEVT_PLAY:
201 0 : ctx->full_file_only = evt->play.full_file_only;
202 : //do NOT reset is_end to false, restarting the session is always done via a source_seek event
203 0 : return GF_TRUE;
204 88 : case GF_FEVT_STOP:
205 88 : if (!ctx->is_end) {
206 41 : ctx->is_end = GF_TRUE;
207 : //abort session
208 41 : if (ctx->sess) {
209 41 : GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTPIn] Stop requested, aborting download %s (pck out %d) this %p\n", ctx->src, ctx->pck_out, ctx) );
210 41 : gf_dm_sess_abort(ctx->sess);
211 41 : gf_dm_sess_del(ctx->sess);
212 41 : ctx->sess = NULL;
213 : }
214 41 : httpin_set_eos(ctx);
215 : }
216 : return GF_TRUE;
217 0 : case GF_FEVT_SOURCE_SEEK:
218 0 : if (evt->seek.start_offset < ctx->file_size) {
219 0 : ctx->is_end = GF_FALSE;
220 : //open cache if needed
221 0 : if (!ctx->cached && ctx->file_size && (ctx->nb_read==ctx->file_size) && ctx->sess) {
222 0 : const char *cached = gf_dm_sess_get_cache_name(ctx->sess);
223 0 : if (cached) ctx->cached = gf_fopen(cached, "rb");
224 : }
225 0 : ctx->nb_read = evt->seek.start_offset;
226 :
227 0 : if (ctx->cached) {
228 0 : gf_fseek(ctx->cached, ctx->nb_read, SEEK_SET);
229 0 : } else if (ctx->sess) {
230 0 : gf_dm_sess_abort(ctx->sess);
231 0 : gf_dm_sess_set_range(ctx->sess, ctx->nb_read, 0, GF_TRUE);
232 : }
233 0 : ctx->range.den = 0;
234 0 : ctx->range.num = ctx->nb_read;
235 0 : ctx->last_state = GF_OK;
236 : } else {
237 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPIn] Requested seek outside file range !\n") );
238 0 : ctx->is_end = GF_TRUE;
239 :
240 0 : httpin_set_eos(ctx);
241 : }
242 : return GF_TRUE;
243 544 : case GF_FEVT_SOURCE_SWITCH:
244 544 : if (evt->seek.source_switch) {
245 : assert(ctx->is_end);
246 : assert(!ctx->pck_out);
247 544 : if (ctx->src && ctx->sess && (ctx->cache!=GF_HTTPIN_STORE_DISK_KEEP) && !ctx->prev_was_init_segment) {
248 510 : gf_dm_delete_cached_file_entry_session(ctx->sess, ctx->src);
249 : }
250 544 : GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTPIn] Switch from %s to %s\n", gf_file_basename(ctx->src), gf_file_basename(evt->seek.source_switch) ));
251 544 : if (ctx->src) gf_free(ctx->src);
252 544 : ctx->src = gf_strdup(evt->seek.source_switch);
253 : } else {
254 0 : if (!ctx->is_end) {
255 0 : gf_filter_pid_set_eos(ctx->pid);
256 0 : ctx->is_end = GF_TRUE;
257 0 : if (ctx->sess) {
258 0 : gf_dm_sess_abort(ctx->sess);
259 0 : gf_dm_sess_del(ctx->sess);
260 0 : ctx->sess = NULL;
261 : }
262 : }
263 0 : if (ctx->src) gf_free(ctx->src);
264 0 : ctx->src = NULL;
265 0 : return GF_TRUE;
266 : }
267 544 : if (ctx->cached) gf_fclose(ctx->cached);
268 544 : ctx->cached = NULL;
269 544 : ctx->blob_size = 0;
270 544 : ctx->is_source_switch = GF_TRUE;
271 544 : ctx->prev_was_init_segment = GF_FALSE;
272 :
273 : //handle isobmff:// url
274 544 : if (!strncmp(ctx->src, "isobmff://", 10)) {
275 : GF_FilterPacket *pck;
276 1 : gf_filter_pid_raw_new(filter, ctx->src, ctx->src, NULL, NULL, NULL, 0, GF_FALSE, &ctx->pid);
277 1 : ctx->is_end = GF_TRUE;
278 1 : pck = gf_filter_pck_new_shared(ctx->pid, ctx->block, 0, httpin_rel_pck);
279 1 : if (!pck) return GF_TRUE;
280 1 : gf_filter_pck_set_framing(pck, GF_TRUE, GF_TRUE);
281 :
282 1 : ctx->pck_out = GF_TRUE;
283 1 : gf_filter_pck_send(pck);
284 :
285 1 : gf_filter_pid_set_eos(ctx->pid);
286 1 : return GF_TRUE;
287 : }
288 543 : ctx->prev_was_init_segment = evt->seek.is_init_segment;
289 :
290 : //abort type
291 543 : if (evt->seek.start_offset == (u64) -1) {
292 0 : if (!ctx->is_end) {
293 0 : if (ctx->sess)
294 0 : gf_dm_sess_abort(ctx->sess);
295 0 : ctx->is_end = GF_TRUE;
296 0 : httpin_set_eos(ctx);
297 : }
298 0 : ctx->nb_read = 0;
299 0 : ctx->last_state = GF_OK;
300 0 : return GF_TRUE;
301 : }
302 543 : ctx->last_state = GF_OK;
303 543 : if (ctx->sess) {
304 538 : if ((ctx->cache==GF_HTTPIN_STORE_MEM) && evt->seek.is_init_segment)
305 29 : gf_dm_sess_force_memory_mode(ctx->sess, 2);
306 538 : e = gf_dm_sess_setup_from_url(ctx->sess, ctx->src, evt->seek.skip_cache_expiration);
307 : } else {
308 : u32 flags;
309 :
310 : flags = GF_NETIO_SESSION_NOT_THREADED | GF_NETIO_SESSION_PERSISTENT;
311 5 : if (ctx->cache==GF_HTTPIN_STORE_MEM) {
312 : flags |= GF_NETIO_SESSION_MEMORY_CACHE;
313 5 : if (evt->seek.is_init_segment)
314 : flags |= GF_NETIO_SESSION_KEEP_FIRST_CACHE;
315 : }
316 0 : else if (ctx->cache==GF_HTTPIN_STORE_NONE) flags |= GF_NETIO_SESSION_NOT_CACHED;
317 :
318 5 : ctx->sess = gf_dm_sess_new(ctx->dm, ctx->src, flags, NULL, NULL, &e);
319 : }
320 :
321 543 : if (!e && (evt->seek.start_offset || evt->seek.end_offset))
322 37 : e = gf_dm_sess_set_range(ctx->sess, evt->seek.start_offset, evt->seek.end_offset, GF_TRUE);
323 :
324 543 : if (e) {
325 : //use info and not error, as source switch is done by dashin and can be scheduled too early in live cases
326 : //but recovered later, so we let DASH report the error
327 2 : GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTPIn] Cannot resetup session from URL %s: %s\n", ctx->src, gf_error_to_string(e) ) );
328 2 : httpin_notify_error(filter, ctx, e);
329 2 : ctx->is_end = GF_TRUE;
330 2 : if (ctx->src) gf_free(ctx->src);
331 2 : ctx->src = NULL;
332 2 : return GF_TRUE;
333 : }
334 541 : ctx->nb_read = ctx->file_size = 0;
335 541 : ctx->do_reconfigure = GF_TRUE;
336 541 : ctx->is_end = GF_FALSE;
337 541 : ctx->last_state = GF_OK;
338 541 : gf_filter_post_process_task(filter);
339 541 : gf_filter_pid_set_property(ctx->pid, GF_PROP_PID_FILE_CACHED, &PROP_BOOL(GF_FALSE) );
340 541 : return GF_TRUE;
341 : default:
342 : break;
343 : }
344 : return GF_TRUE;
345 : }
346 :
347 :
348 :
349 137163 : static GF_Err httpin_process(GF_Filter *filter)
350 : {
351 : Bool is_start;
352 137163 : u32 nb_read=0;
353 : GF_FilterPacket *pck;
354 : GF_Err e=GF_OK;
355 137163 : u32 bytes_per_sec=0;
356 137163 : u64 bytes_done=0, total_size, byte_offset;
357 : GF_NetIOStatus net_status;
358 137163 : GF_HTTPInCtx *ctx = (GF_HTTPInCtx *) gf_filter_get_udta(filter);
359 :
360 : //until packet is released we return EOS (no processing), and ask for processing again upon release
361 137163 : if (ctx->pck_out)
362 : return GF_EOS;
363 :
364 136922 : if (ctx->is_end)
365 : return GF_EOS;
366 :
367 136199 : if (!ctx->sess)
368 : return GF_EOS;
369 :
370 136199 : if (!ctx->pid) {
371 8721 : if (ctx->nb_read)
372 : return GF_SERVICE_ERROR;
373 : } else {
374 : //TODO: go on fetching data to cache even when not consuming, and reread from cache
375 127478 : if (gf_filter_pid_would_block(ctx->pid))
376 : return GF_OK;
377 : }
378 :
379 136199 : is_start = ctx->nb_read ? GF_FALSE : GF_TRUE;
380 136199 : ctx->is_end = GF_FALSE;
381 :
382 : //we read from cache file
383 136199 : if (ctx->cached) {
384 : u32 to_read;
385 0 : u64 lto_read = ctx->file_size - ctx->nb_read;
386 :
387 0 : if (lto_read > (u64) ctx->block_size)
388 : to_read = (u64) ctx->block_size;
389 : else
390 0 : to_read = (u32) lto_read;
391 :
392 0 : if (ctx->full_file_only) {
393 0 : pck = gf_filter_pck_new_shared(ctx->pid, ctx->block, 0, httpin_rel_pck);
394 0 : if (!pck) return GF_OUT_OF_MEM;
395 0 : ctx->is_end = GF_TRUE;
396 0 : gf_filter_pck_set_framing(pck, is_start, ctx->is_end);
397 :
398 : //mark packet out BEFORE sending, since the call to send() may destroy the packet if cloned
399 0 : ctx->pck_out = HTTP_PCK_OUT;
400 0 : gf_filter_pck_send(pck);
401 :
402 0 : httpin_set_eos(ctx);
403 : return GF_EOS;
404 : }
405 0 : nb_read = (u32) gf_fread(ctx->block, to_read, ctx->cached);
406 0 : bytes_per_sec = 0;
407 : }
408 136199 : else if (ctx->blob_size) {
409 : u8 *b_data;
410 : u32 b_size;
411 5 : const char *cached = gf_dm_sess_get_cache_name(ctx->sess);
412 : assert(cached);
413 :
414 5 : gf_blob_get(cached, &b_data, &b_size, NULL);
415 : assert(ctx->nb_read <= b_size);
416 5 : nb_read = b_size - (u32) ctx->nb_read;
417 5 : if (nb_read>ctx->block_size)
418 4 : nb_read = ctx->block_size;
419 :
420 5 : if (nb_read) {
421 5 : memcpy(ctx->block, b_data + ctx->nb_read, nb_read);
422 : e = GF_OK;
423 : } else {
424 0 : if (b_size == ctx->blob_size) {
425 0 : e = gf_dm_sess_fetch_data(ctx->sess, ctx->block, ctx->block_size, &nb_read);
426 : } else {
427 0 : ctx->blob_size = b_size;
428 : }
429 : }
430 5 : gf_blob_release(cached);
431 : }
432 : //we read from network
433 : else {
434 :
435 136194 : e = gf_dm_sess_fetch_data(ctx->sess, ctx->block, ctx->block_size, &nb_read);
436 136194 : if (e<0) {
437 88459 : if (e==GF_IP_NETWORK_EMPTY) {
438 88459 : if (ctx->pid) {
439 87873 : gf_dm_sess_get_stats(ctx->sess, NULL, NULL, NULL, NULL, &bytes_per_sec, NULL);
440 87873 : gf_filter_pid_set_info(ctx->pid, GF_PROP_PID_DOWN_RATE, &PROP_UINT(8*bytes_per_sec) );
441 : }
442 88459 : gf_filter_ask_rt_reschedule(filter, 1000);
443 88459 : return GF_OK;
444 : }
445 0 : if (! ctx->nb_read)
446 0 : httpin_notify_error(filter, ctx, e);
447 :
448 0 : ctx->is_end = GF_TRUE;
449 :
450 : //do not return an error if first fetch after source switch fails with removed or 404, this happens in DASH dynamic
451 : //and the error might be absorbed by the dash demux later
452 : //also do not signal eos in that case, this could trigger the consuming filter (dashdmx, filein) to consider the stream is in regular EOS
453 : //and not wait for notify failure
454 0 : if (ctx->is_source_switch && !ctx->nb_read && ((e==GF_URL_REMOVED) || (e==GF_URL_ERROR)))
455 : return GF_OK;
456 :
457 : //no packet out, we can signal eos, except for source switch failures
458 0 : if (ctx->pid) {
459 0 : gf_filter_pid_set_eos(ctx->pid);
460 : }
461 :
462 0 : gf_dm_sess_abort(ctx->sess);
463 0 : ctx->is_source_switch = GF_FALSE;
464 0 : return e;
465 : }
466 47735 : gf_dm_sess_get_stats(ctx->sess, NULL, NULL, &total_size, &bytes_done, &bytes_per_sec, &net_status);
467 :
468 : //wait until we have some data to declare the pid
469 47735 : if ((e!= GF_EOS) && !nb_read) {
470 42119 : gf_filter_ask_rt_reschedule(filter, 1000);
471 42119 : return GF_OK;
472 : }
473 :
474 5616 : if (!ctx->pid || ctx->do_reconfigure) {
475 : u32 idx;
476 : GF_Err cfg_e;
477 : const char *hname, *hval;
478 635 : const char *cached = gf_dm_sess_get_cache_name(ctx->sess);
479 :
480 635 : ctx->do_reconfigure = GF_FALSE;
481 :
482 635 : if ((e==GF_EOS) && cached) {
483 318 : if (!strnicmp(cached, "gmem://", 7)) {
484 : u8 *b_data;
485 : u32 b_size, b_flags;
486 296 : gf_blob_get(cached, &b_data, &b_size, &b_flags);
487 296 : if (b_size>ctx->block_size) {
488 1 : ctx->blob_size = b_size;
489 1 : b_size = ctx->block_size;
490 : e = GF_OK;
491 : }
492 : assert(! (b_flags&GF_BLOB_IN_TRANSFER));
493 296 : memcpy(ctx->block, b_data, b_size);
494 296 : nb_read = b_size;
495 296 : gf_blob_release(cached);
496 : } else {
497 22 : ctx->cached = gf_fopen(cached, "rb");
498 22 : if (ctx->cached) {
499 22 : nb_read = (u32) gf_fread(ctx->block, ctx->block_size, ctx->cached);
500 : } else {
501 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPIn] Failed to open cached file %s\n", cached));
502 : }
503 : }
504 : }
505 635 : ctx->block[nb_read] = 0;
506 635 : cfg_e = gf_filter_pid_raw_new(filter, ctx->src, cached, ctx->mime ? ctx->mime : gf_dm_sess_mime_type(ctx->sess), ctx->ext, ctx->block, nb_read, ctx->mime ? GF_TRUE : GF_FALSE, &ctx->pid);
507 635 : if (cfg_e) return cfg_e;
508 :
509 635 : gf_filter_pid_set_property(ctx->pid, GF_PROP_PID_FILE_CACHED, &PROP_BOOL(GF_FALSE) );
510 :
511 635 : if (!ctx->initial_ack_done) {
512 117 : ctx->initial_ack_done = GF_TRUE;
513 117 : gf_filter_pid_set_property(ctx->pid, GF_PROP_PID_DOWNLOAD_SESSION, &PROP_POINTER( (void*)ctx->sess ) );
514 : }
515 :
516 : /*in test mode don't expose http headers (they contain date/version/etc)*/
517 635 : if (! gf_sys_is_test_mode()) {
518 27 : idx = 0;
519 380 : while (gf_dm_sess_enum_headers(ctx->sess, &idx, &hname, &hval) == GF_OK) {
520 326 : gf_filter_pid_set_property_dyn(ctx->pid, (char *) hname, & PROP_STRING(hval));
521 : }
522 : }
523 : }
524 : //update file size at each call to get_stats, since we may use a dynamic blob in case of route
525 5616 : ctx->file_size = total_size;
526 :
527 5616 : gf_filter_pid_set_info(ctx->pid, GF_PROP_PID_DOWN_RATE, &PROP_UINT(8*bytes_per_sec) );
528 5616 : if (ctx->range.num && ctx->file_size) {
529 0 : gf_filter_pid_set_info(ctx->pid, GF_PROP_PID_DOWN_BYTES, &PROP_LONGUINT(bytes_done + ctx->range.num) );
530 0 : gf_filter_pid_set_info(ctx->pid, GF_PROP_PID_DOWN_SIZE, &PROP_LONGUINT(ctx->file_size) );
531 : } else {
532 5616 : gf_filter_pid_set_info(ctx->pid, GF_PROP_PID_DOWN_BYTES, &PROP_LONGUINT(bytes_done) );
533 5616 : gf_filter_pid_set_info(ctx->pid, GF_PROP_PID_DOWN_SIZE, &PROP_LONGUINT(ctx->file_size ? ctx->file_size : bytes_done) );
534 : }
535 : }
536 :
537 5621 : byte_offset = ctx->nb_read;
538 :
539 5621 : ctx->nb_read += nb_read;
540 5621 : if (ctx->file_size && (ctx->nb_read==ctx->file_size)) {
541 609 : if (net_status!=GF_NETIO_DATA_EXCHANGE)
542 609 : ctx->is_end = GF_TRUE;
543 5012 : } else if (e==GF_EOS) {
544 3 : ctx->is_end = GF_TRUE;
545 : }
546 :
547 5621 : pck = gf_filter_pck_new_shared(ctx->pid, ctx->block, nb_read, httpin_rel_pck);
548 5621 : if (!pck) return GF_OUT_OF_MEM;
549 :
550 5621 : gf_filter_pck_set_cts(pck, 0);
551 :
552 5621 : gf_filter_pck_set_framing(pck, is_start, ctx->is_end);
553 5621 : gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1);
554 5621 : gf_filter_pck_set_byte_offset(pck, byte_offset);
555 :
556 : //mark packet out BEFORE sending, since the call to send() may destroy the packet if cloned
557 5621 : ctx->pck_out = HTTP_PCK_OUT;
558 5621 : gf_filter_pck_send(pck);
559 :
560 5621 : if (ctx->file_size && gf_filter_reporting_enabled(filter)) {
561 : char szStatus[1024], *szSrc;
562 0 : szSrc = gf_file_basename(ctx->src);
563 :
564 0 : sprintf(szStatus, "%s: % 16"LLD_SUF" /% 16"LLD_SUF" (%02.02f) % 8d kbps", szSrc, (s64) bytes_done, (s64) ctx->file_size, ((Double)bytes_done*100.0)/ctx->file_size, bytes_per_sec*8/1000);
565 0 : gf_filter_update_status(filter, (u32) (bytes_done*10000/ctx->file_size), szStatus);
566 : }
567 :
568 5621 : if (ctx->is_end) {
569 612 : const char *cached = gf_dm_sess_get_cache_name(ctx->sess);
570 612 : if (cached)
571 610 : gf_filter_pid_set_property(ctx->pid, GF_PROP_PID_FILE_CACHED, &PROP_BOOL(GF_TRUE) );
572 :
573 612 : httpin_set_eos(ctx);
574 : return GF_EOS;
575 : }
576 :
577 5009 : return ctx->pck_out ? GF_EOS : GF_OK;
578 : }
579 :
580 :
581 :
582 : #define OFFS(_n) #_n, offsetof(GF_HTTPInCtx, _n)
583 :
584 : static const GF_FilterArgs HTTPInArgs[] =
585 : {
586 : { OFFS(src), "URL of source content", GF_PROP_NAME, NULL, NULL, 0},
587 : { OFFS(block_size), "block size used to read file", GF_PROP_UINT, "100000", NULL, GF_FS_ARG_HINT_ADVANCED},
588 : { OFFS(cache), "set cache mode\n"
589 : "- disk: cache to disk, discard once session is no longer used\n"
590 : "- keep: cache to disk and keep\n"
591 : "- mem: stores to memory, discard once session is no longer used\n"
592 : "- mem_keep: stores to memory, keep after session is reassigned but move to `mem` after first download\n"
593 : "- none: no cache\n"
594 : "- none_keep: stores to memory, keep after session is reassigned but move to `none` after first download"
595 : , GF_PROP_UINT, "disk", "disk|keep|mem|mem_keep|none|none_keep", GF_FS_ARG_HINT_ADVANCED},
596 : { OFFS(range), "set byte range, as fraction", GF_PROP_FRACTION64, "0-0", NULL, 0},
597 : { OFFS(ext), "override file extension", GF_PROP_NAME, NULL, NULL, 0},
598 : { OFFS(mime), "set file mime type", GF_PROP_NAME, NULL, NULL, 0},
599 : {0}
600 : };
601 :
602 : static const GF_FilterCapability HTTPInCaps[] =
603 : {
604 : CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE),
605 : };
606 :
607 : GF_FilterRegister HTTPInRegister = {
608 : .name = "httpin",
609 : GF_FS_SET_DESCRIPTION("HTTP input")
610 : GF_FS_SET_HELP("This filter dispatch raw blocks from a remote HTTP resource into a filter chain.\n"
611 : "Block size can be adjusted using [-block_size](), and disk caching policies can be adjusted.\n"
612 : "Content format can be forced through [-mime]() and file extension can be changed through [-ext]().\n"
613 : "Note: Unless disabled at session level (see [-no-probe](CORE) ), file extensions are usually ignored and format probing is done on the first data block.")
614 : .private_size = sizeof(GF_HTTPInCtx),
615 : .flags = GF_FS_REG_BLOCKING,
616 : .args = HTTPInArgs,
617 : SETCAPS(HTTPInCaps),
618 : .initialize = httpin_initialize,
619 : .finalize = httpin_finalize,
620 : .process = httpin_process,
621 : .process_event = httpin_process_event,
622 : .probe_url = httpin_probe_url
623 : };
624 :
625 :
626 2877 : const GF_FilterRegister *httpin_register(GF_FilterSession *session)
627 : {
628 2877 : return &HTTPInRegister;
629 : }
630 :
|