1 /*****************************************************************************
2 * stream.c
3 *****************************************************************************
4 * Copyright (C) 1999-2004 VLC authors and VideoLAN
5 * Copyright 2008-2015 Rémi Denis-Courmont
6 * $Id: b94279b4316e127c2d0d4b0d146b524e62afff1c $
7 *
8 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
9 *
10 * This program is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU Lesser General Public License as published by
12 * the Free Software Foundation; either version 2.1 of the License, or
13 * (at your option) any later version.
14 *
15 * This program 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 License
21 * along with this program; if not, write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 *****************************************************************************/
24
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #include <assert.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <limits.h>
33 #include <errno.h>
34
35 #include <vlc_common.h>
36 #include <vlc_block.h>
37 #include <vlc_memory.h>
38 #include <vlc_access.h>
39 #include <vlc_charset.h>
40 #include <vlc_interrupt.h>
41 #include <vlc_stream_extractor.h>
42
43 #include <libvlc.h>
44 #include "stream.h"
45 #include "mrl_helpers.h"
46
47 typedef struct stream_priv_t
48 {
49 stream_t stream;
50 void (*destroy)(stream_t *);
51 block_t *block;
52 block_t *peek;
53 uint64_t offset;
54 bool eof;
55
56 /* UTF-16 and UTF-32 file reading */
57 struct {
58 vlc_iconv_t conv;
59 unsigned char char_width;
60 bool little_endian;
61 } text;
62 } stream_priv_t;
63
64 /**
65 * Allocates a VLC stream object
66 */
vlc_stream_CommonNew(vlc_object_t * parent,void (* destroy)(stream_t *))67 stream_t *vlc_stream_CommonNew(vlc_object_t *parent,
68 void (*destroy)(stream_t *))
69 {
70 stream_priv_t *priv = vlc_custom_create(parent, sizeof (*priv), "stream");
71 if (unlikely(priv == NULL))
72 return NULL;
73
74 stream_t *s = &priv->stream;
75
76 s->p_module = NULL;
77 s->psz_url = NULL;
78 s->p_source = NULL;
79 s->pf_read = NULL;
80 s->pf_block = NULL;
81 s->pf_readdir = NULL;
82 s->pf_seek = NULL;
83 s->pf_control = NULL;
84 s->p_sys = NULL;
85 s->p_input = NULL;
86 assert(destroy != NULL);
87 priv->destroy = destroy;
88 priv->block = NULL;
89 priv->peek = NULL;
90 priv->offset = 0;
91 priv->eof = false;
92
93 /* UTF16 and UTF32 text file conversion */
94 priv->text.conv = (vlc_iconv_t)(-1);
95 priv->text.char_width = 1;
96 priv->text.little_endian = false;
97
98 return s;
99 }
100
stream_CommonDelete(stream_t * s)101 void stream_CommonDelete(stream_t *s)
102 {
103 stream_priv_t *priv = (stream_priv_t *)s;
104
105 if (priv->text.conv != (vlc_iconv_t)(-1))
106 vlc_iconv_close(priv->text.conv);
107
108 if (priv->peek != NULL)
109 block_Release(priv->peek);
110 if (priv->block != NULL)
111 block_Release(priv->block);
112
113 free(s->psz_url);
114 vlc_object_release(s);
115 }
116
117 /**
118 * Destroy a stream
119 */
vlc_stream_Delete(stream_t * s)120 void vlc_stream_Delete(stream_t *s)
121 {
122 stream_priv_t *priv = (stream_priv_t *)s;
123
124 priv->destroy(s);
125 stream_CommonDelete(s);
126 }
127
128 stream_t *(vlc_stream_NewURL)(vlc_object_t *p_parent, const char *psz_url)
129 {
130 if( !psz_url )
131 return NULL;
132
133 stream_t *s = stream_AccessNew( p_parent, NULL, false, psz_url );
134 if( s == NULL )
135 msg_Err( p_parent, "no suitable access module for `%s'", psz_url );
136 return s;
137 }
138
139 stream_t *(vlc_stream_NewMRL)(vlc_object_t* parent, const char* mrl )
140 {
141 stream_t* stream = vlc_stream_NewURL( parent, mrl );
142
143 if( stream == NULL )
144 return NULL;
145
146 char const* anchor = strchr( mrl, '#' );
147
148 if( anchor == NULL )
149 return stream;
150
151 char const* extra;
152 if( stream_extractor_AttachParsed( &stream, anchor + 1, &extra ) )
153 {
154 msg_Err( parent, "unable to open %s", mrl );
155 vlc_stream_Delete( stream );
156 return NULL;
157 }
158
159 if( extra && *extra )
160 msg_Warn( parent, "ignoring extra fragment data: %s", extra );
161
162 return stream;
163 }
164
165 /**
166 * Read from the stream until first newline.
167 * \param s Stream handle to read from
168 * \return A pointer to the allocated output string. You need to free this when you are done.
169 */
170 #define STREAM_PROBE_LINE 2048
171 #define STREAM_LINE_MAX (2048*100)
vlc_stream_ReadLine(stream_t * s)172 char *vlc_stream_ReadLine( stream_t *s )
173 {
174 stream_priv_t *priv = (stream_priv_t *)s;
175 char *p_line = NULL;
176 int i_line = 0, i_read = 0;
177
178 /* Let's fail quickly if this is a readdir access */
179 if( s->pf_read == NULL && s->pf_block == NULL )
180 return NULL;
181
182 for( ;; )
183 {
184 char *psz_eol;
185 const uint8_t *p_data;
186 int i_data;
187 int64_t i_pos;
188
189 /* Probe new data */
190 i_data = vlc_stream_Peek( s, &p_data, STREAM_PROBE_LINE );
191 if( i_data <= 0 ) break; /* No more data */
192
193 /* BOM detection */
194 i_pos = vlc_stream_Tell( s );
195 if( i_pos == 0 && i_data >= 2 )
196 {
197 const char *psz_encoding = NULL;
198 bool little_endian = false;
199
200 if( unlikely(priv->text.conv != (vlc_iconv_t)-1) )
201 { /* seek back to beginning? reset */
202 vlc_iconv_close( priv->text.conv );
203 priv->text.conv = (vlc_iconv_t)-1;
204 }
205 priv->text.char_width = 1;
206 priv->text.little_endian = false;
207
208 if( !memcmp( p_data, "\xFF\xFE", 2 ) )
209 {
210 psz_encoding = "UTF-16LE";
211 little_endian = true;
212 }
213 else if( !memcmp( p_data, "\xFE\xFF", 2 ) )
214 {
215 psz_encoding = "UTF-16BE";
216 }
217
218 /* Open the converter if we need it */
219 if( psz_encoding != NULL )
220 {
221 msg_Dbg( s, "UTF-16 BOM detected" );
222 priv->text.conv = vlc_iconv_open( "UTF-8", psz_encoding );
223 if( unlikely(priv->text.conv == (vlc_iconv_t)-1) )
224 {
225 msg_Err( s, "iconv_open failed" );
226 goto error;
227 }
228 priv->text.char_width = 2;
229 priv->text.little_endian = little_endian;
230 }
231 }
232
233 if( i_data % priv->text.char_width )
234 {
235 /* keep i_char_width boundary */
236 i_data = i_data - ( i_data % priv->text.char_width );
237 msg_Warn( s, "the read is not i_char_width compatible");
238 }
239
240 if( i_data == 0 )
241 break;
242
243 /* Check if there is an EOL */
244 if( priv->text.char_width == 1 )
245 {
246 /* UTF-8: 0A <LF> */
247 psz_eol = memchr( p_data, '\n', i_data );
248 if( psz_eol == NULL )
249 /* UTF-8: 0D <CR> */
250 psz_eol = memchr( p_data, '\r', i_data );
251 }
252 else
253 {
254 const uint8_t *p_last = p_data + i_data - priv->text.char_width;
255 uint16_t eol = priv->text.little_endian ? 0x0A00 : 0x000A;
256
257 assert( priv->text.char_width == 2 );
258 psz_eol = NULL;
259 /* UTF-16: 000A <LF> */
260 for( const uint8_t *p = p_data; p <= p_last; p += 2 )
261 {
262 if( U16_AT( p ) == eol )
263 {
264 psz_eol = (char *)p + 1;
265 break;
266 }
267 }
268
269 if( psz_eol == NULL )
270 { /* UTF-16: 000D <CR> */
271 eol = priv->text.little_endian ? 0x0D00 : 0x000D;
272 for( const uint8_t *p = p_data; p <= p_last; p += 2 )
273 {
274 if( U16_AT( p ) == eol )
275 {
276 psz_eol = (char *)p + 1;
277 break;
278 }
279 }
280 }
281 }
282
283 if( psz_eol )
284 {
285 i_data = (psz_eol - (char *)p_data) + 1;
286 p_line = realloc_or_free( p_line,
287 i_line + i_data + priv->text.char_width ); /* add \0 */
288 if( !p_line )
289 goto error;
290 i_data = vlc_stream_Read( s, &p_line[i_line], i_data );
291 if( i_data <= 0 ) break; /* Hmmm */
292 i_line += i_data - priv->text.char_width; /* skip \n */;
293 i_read += i_data;
294
295 /* We have our line */
296 break;
297 }
298
299 /* Read data (+1 for easy \0 append) */
300 p_line = realloc_or_free( p_line,
301 i_line + STREAM_PROBE_LINE + priv->text.char_width );
302 if( !p_line )
303 goto error;
304 i_data = vlc_stream_Read( s, &p_line[i_line], STREAM_PROBE_LINE );
305 if( i_data <= 0 ) break; /* Hmmm */
306 i_line += i_data;
307 i_read += i_data;
308
309 if( i_read >= STREAM_LINE_MAX )
310 goto error; /* line too long */
311 }
312
313 if( i_read > 0 )
314 {
315 if( priv->text.char_width > 1 )
316 {
317 int i_new_line = 0;
318 size_t i_in = 0, i_out = 0;
319 const char * p_in = NULL;
320 char * p_out = NULL;
321 char * psz_new_line = NULL;
322
323 /* iconv */
324 /* UTF-8 needs at most 150% of the buffer as many as UTF-16 */
325 i_new_line = i_line * 3 / 2 + 1;
326 psz_new_line = malloc( i_new_line );
327 if( psz_new_line == NULL )
328 goto error;
329 i_in = (size_t)i_line;
330 i_out = (size_t)i_new_line;
331 p_in = p_line;
332 p_out = psz_new_line;
333
334 if( vlc_iconv( priv->text.conv, &p_in, &i_in, &p_out, &i_out ) == (size_t)-1 )
335 {
336 msg_Err( s, "conversion error: %s", vlc_strerror_c( errno ) );
337 msg_Dbg( s, "original: %d, in %zu, out %zu", i_line, i_in, i_out );
338 }
339 free( p_line );
340 p_line = psz_new_line;
341 i_line = (size_t)i_new_line - i_out; /* does not include \0 */
342 }
343
344 /* Remove trailing LF/CR */
345 while( i_line >= 1 &&
346 (p_line[i_line - 1] == '\r' || p_line[i_line - 1] == '\n') )
347 i_line--;
348
349 /* Make sure the \0 is there */
350 p_line[i_line] = '\0';
351
352 return p_line;
353 }
354
355 error:
356 /* We failed to read any data, probably EOF */
357 free( p_line );
358 return NULL;
359 }
360
vlc_stream_CopyBlock(block_t ** restrict pp,void * buf,size_t len)361 static ssize_t vlc_stream_CopyBlock(block_t **restrict pp,
362 void *buf, size_t len)
363 {
364 block_t *block = *pp;
365
366 if (block == NULL)
367 return -1;
368
369 if (len > block->i_buffer)
370 len = block->i_buffer;
371
372 if (buf != NULL)
373 memcpy(buf, block->p_buffer, len);
374
375 block->p_buffer += len;
376 block->i_buffer -= len;
377
378 if (block->i_buffer == 0)
379 {
380 block_Release(block);
381 *pp = NULL;
382 }
383
384 return likely(len > 0) ? (ssize_t)len : -1;
385 }
386
vlc_stream_ReadRaw(stream_t * s,void * buf,size_t len)387 static ssize_t vlc_stream_ReadRaw(stream_t *s, void *buf, size_t len)
388 {
389 stream_priv_t *priv = (stream_priv_t *)s;
390 ssize_t ret;
391
392 assert(len <= SSIZE_MAX);
393
394 if (vlc_killed())
395 return 0;
396
397 if (s->pf_read != NULL)
398 {
399 assert(priv->block == NULL);
400 if (buf == NULL)
401 {
402 if (unlikely(len == 0))
403 return 0;
404
405 char dummy[(len <= 256 ? len : 256)];
406 ret = s->pf_read(s, dummy, sizeof (dummy));
407 }
408 else
409 ret = s->pf_read(s, buf, len);
410 return ret;
411 }
412
413 ret = vlc_stream_CopyBlock(&priv->block, buf, len);
414 if (ret >= 0)
415 return ret;
416
417 if (s->pf_block != NULL)
418 {
419 bool eof = false;
420
421 priv->block = s->pf_block(s, &eof);
422 ret = vlc_stream_CopyBlock(&priv->block, buf, len);
423 if (ret >= 0)
424 return ret;
425 return eof ? 0 : -1;
426 }
427
428 return 0;
429 }
430
vlc_stream_ReadPartial(stream_t * s,void * buf,size_t len)431 ssize_t vlc_stream_ReadPartial(stream_t *s, void *buf, size_t len)
432 {
433 stream_priv_t *priv = (stream_priv_t *)s;
434 ssize_t ret;
435
436 ret = vlc_stream_CopyBlock(&priv->peek, buf, len);
437 if (ret >= 0)
438 {
439 priv->offset += ret;
440 assert(ret <= (ssize_t)len);
441 return ret;
442 }
443
444 ret = vlc_stream_ReadRaw(s, buf, len);
445 if (ret > 0)
446 priv->offset += ret;
447 if (ret == 0)
448 priv->eof = len != 0;
449 assert(ret <= (ssize_t)len);
450 return ret;
451 }
452
vlc_stream_Read(stream_t * s,void * buf,size_t len)453 ssize_t vlc_stream_Read(stream_t *s, void *buf, size_t len)
454 {
455 size_t copied = 0;
456
457 while (len > 0)
458 {
459 ssize_t ret = vlc_stream_ReadPartial(s, buf, len);
460 if (ret < 0)
461 continue;
462 if (ret == 0)
463 break;
464
465 if (buf != NULL)
466 buf = (char *)buf + ret;
467 assert(len >= (size_t)ret);
468 len -= ret;
469 copied += ret;
470 }
471
472 return copied;
473 }
474
vlc_stream_Peek(stream_t * s,const uint8_t ** restrict bufp,size_t len)475 ssize_t vlc_stream_Peek(stream_t *s, const uint8_t **restrict bufp, size_t len)
476 {
477 stream_priv_t *priv = (stream_priv_t *)s;
478 block_t *peek;
479
480 peek = priv->peek;
481 if (peek == NULL)
482 {
483 peek = priv->block;
484 priv->peek = peek;
485 priv->block = NULL;
486 }
487
488 if (peek == NULL)
489 {
490 peek = block_Alloc(len);
491 if (unlikely(peek == NULL))
492 return VLC_ENOMEM;
493
494 peek->i_buffer = 0;
495 }
496 else
497 if (peek->i_buffer < len)
498 {
499 size_t avail = peek->i_buffer;
500
501 peek = block_TryRealloc(peek, 0, len);
502 if (unlikely(peek == NULL))
503 return VLC_ENOMEM;
504
505 peek->i_buffer = avail;
506 }
507
508 priv->peek = peek;
509 *bufp = peek->p_buffer;
510
511 while (peek->i_buffer < len)
512 {
513 size_t avail = peek->i_buffer;
514 ssize_t ret;
515
516 ret = vlc_stream_ReadRaw(s, peek->p_buffer + avail, len - avail);
517 if (ret < 0)
518 continue;
519
520 peek->i_buffer += ret;
521
522 if (ret == 0)
523 return peek->i_buffer;
524 }
525
526 return len;
527 }
528
vlc_stream_ReadBlock(stream_t * s)529 block_t *vlc_stream_ReadBlock(stream_t *s)
530 {
531 stream_priv_t *priv = (stream_priv_t *)s;
532 block_t *block;
533
534 if (vlc_killed())
535 {
536 priv->eof = true;
537 return NULL;
538 }
539
540 if (priv->peek != NULL)
541 {
542 block = priv->peek;
543 priv->peek = NULL;
544 }
545 else if (priv->block != NULL)
546 {
547 block = priv->block;
548 priv->block = NULL;
549 }
550 else if (s->pf_block != NULL)
551 {
552 priv->eof = false;
553 block = s->pf_block(s, &priv->eof);
554 }
555 else
556 {
557 block = block_Alloc(4096);
558 if (unlikely(block == NULL))
559 return NULL;
560
561 ssize_t ret = s->pf_read(s, block->p_buffer, block->i_buffer);
562 if (ret > 0)
563 block->i_buffer = ret;
564 else
565 {
566 block_Release(block);
567 block = NULL;
568 }
569
570 priv->eof = !ret;
571 }
572
573 if (block != NULL)
574 priv->offset += block->i_buffer;
575
576 return block;
577 }
578
vlc_stream_Tell(const stream_t * s)579 uint64_t vlc_stream_Tell(const stream_t *s)
580 {
581 const stream_priv_t *priv = (const stream_priv_t *)s;
582
583 return priv->offset;
584 }
585
vlc_stream_Eof(const stream_t * s)586 bool vlc_stream_Eof(const stream_t *s)
587 {
588 const stream_priv_t *priv = (const stream_priv_t *)s;
589
590 return priv->eof;
591 }
592
vlc_stream_Seek(stream_t * s,uint64_t offset)593 int vlc_stream_Seek(stream_t *s, uint64_t offset)
594 {
595 stream_priv_t *priv = (stream_priv_t *)s;
596
597 priv->eof = false;
598
599 block_t *peek = priv->peek;
600 if (peek != NULL)
601 {
602 if (offset >= priv->offset
603 && offset <= (priv->offset + peek->i_buffer))
604 { /* Seeking within the peek buffer */
605 size_t fwd = offset - priv->offset;
606
607 peek->p_buffer += fwd;
608 peek->i_buffer -= fwd;
609 priv->offset = offset;
610
611 if (peek->i_buffer == 0)
612 {
613 priv->peek = NULL;
614 block_Release(peek);
615 }
616
617 return VLC_SUCCESS;
618 }
619 }
620 else
621 {
622 if (priv->offset == offset)
623 return VLC_SUCCESS; /* Nothing to do! */
624 }
625
626 if (s->pf_seek == NULL)
627 return VLC_EGENERIC;
628
629 int ret = s->pf_seek(s, offset);
630 if (ret != VLC_SUCCESS)
631 return ret;
632
633 priv->offset = offset;
634
635 if (peek != NULL)
636 {
637 priv->peek = NULL;
638 block_Release(peek);
639 }
640
641 if (priv->block != NULL)
642 {
643 block_Release(priv->block);
644 priv->block = NULL;
645 }
646
647 return VLC_SUCCESS;
648 }
649
650 /**
651 * Use to control the "stream_t *". Look at #stream_query_e for
652 * possible "i_query" value and format arguments. Return VLC_SUCCESS
653 * if ... succeed ;) and VLC_EGENERIC if failed or unimplemented
654 */
vlc_stream_vaControl(stream_t * s,int cmd,va_list args)655 int vlc_stream_vaControl(stream_t *s, int cmd, va_list args)
656 {
657 stream_priv_t *priv = (stream_priv_t *)s;
658
659 switch (cmd)
660 {
661 case STREAM_SET_TITLE:
662 case STREAM_SET_SEEKPOINT:
663 {
664 int ret = s->pf_control(s, cmd, args);
665 if (ret != VLC_SUCCESS)
666 return ret;
667
668 priv->offset = 0;
669
670 if (priv->peek != NULL)
671 {
672 block_Release(priv->peek);
673 priv->peek = NULL;
674 }
675
676 if (priv->block != NULL)
677 {
678 block_Release(priv->block);
679 priv->block = NULL;
680 }
681
682 return VLC_SUCCESS;
683 }
684 }
685 return s->pf_control(s, cmd, args);
686 }
687
688 /**
689 * Read data into a block.
690 *
691 * @param s stream to read data from
692 * @param size number of bytes to read
693 * @return a block of data, or NULL on error
694 @ note The block size may be shorter than requested if the end-of-stream was
695 * reached.
696 */
vlc_stream_Block(stream_t * s,size_t size)697 block_t *vlc_stream_Block( stream_t *s, size_t size )
698 {
699 if( unlikely(size > SSIZE_MAX) )
700 return NULL;
701
702 block_t *block = block_Alloc( size );
703 if( unlikely(block == NULL) )
704 return NULL;
705
706 ssize_t val = vlc_stream_Read( s, block->p_buffer, size );
707 if( val <= 0 )
708 {
709 block_Release( block );
710 return NULL;
711 }
712
713 block->i_buffer = val;
714 return block;
715 }
716
717 /**
718 * Returns a node containing all the input_item of the directory pointer by
719 * this stream. returns VLC_SUCCESS on success.
720 */
vlc_stream_ReadDir(stream_t * s,input_item_node_t * p_node)721 int vlc_stream_ReadDir( stream_t *s, input_item_node_t *p_node )
722 {
723 return s->pf_readdir( s, p_node );
724 }
725