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