summaryrefslogtreecommitdiff
path: root/opusfile/stream.c
blob: 6a85197a66d86f8f0c2f976fabb413ce9193fedf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
/********************************************************************
 *                                                                  *
 * THIS FILE IS PART OF THE libopusfile SOFTWARE CODEC SOURCE CODE. *
 * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS     *
 * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE *
 * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING.       *
 *                                                                  *
 * THE libopusfile SOURCE CODE IS (C) COPYRIGHT 1994-2012           *
 * by the Xiph.Org Foundation and contributors http://www.xiph.org/ *
 *                                                                  *
 ********************************************************************

 function: stdio-based convenience library for opening/seeking/decoding
 last mod: $Id: vorbisfile.c 17573 2010-10-27 14:53:59Z xiphmont $

 ********************************************************************/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "internal.h"
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#if defined(_WIN32)
# include <io.h>
#endif

typedef struct OpusMemStream OpusMemStream;

#define OP_MEM_SIZE_MAX (~(size_t)0>>1)
#define OP_MEM_DIFF_MAX ((ptrdiff_t)OP_MEM_SIZE_MAX)

/*The context information needed to read from a block of memory as if it were a
   file.*/
struct OpusMemStream{
  /*The block of memory to read from.*/
  const unsigned char *data;
  /*The total size of the block.
    This must be at most OP_MEM_SIZE_MAX to prevent signed overflow while
     seeking.*/
  ptrdiff_t            size;
  /*The current file position.
    This is allowed to be set arbitrarily greater than size (i.e., past the end
     of the block, though we will not read data past the end of the block), but
     is not allowed to be negative (i.e., before the beginning of the block).*/
  ptrdiff_t            pos;
};

static int op_fread(void *_stream,unsigned char *_ptr,int _buf_size){
  FILE   *stream;
  size_t  ret;
  /*Check for empty read.*/
  if(_buf_size<=0)return 0;
  stream=(FILE *)_stream;
  ret=fread(_ptr,1,_buf_size,stream);
  OP_ASSERT(ret<=(size_t)_buf_size);
  /*If ret==0 and !feof(stream), there was a read error.*/
  return ret>0||feof(stream)?(int)ret:OP_EREAD;
}

static int op_fseek(void *_stream,opus_int64 _offset,int _whence){
#if defined(_WIN32)
  /*_fseeki64() is not exposed until MSCVCRT80.
    This is the default starting with MSVC 2005 (_MSC_VER>=1400), but we want
     to allow linking against older MSVCRT versions for compatibility back to
     XP without installing extra runtime libraries.
    i686-pc-mingw32 does not have fseeko() and requires
     __MSVCRT_VERSION__>=0x800 for _fseeki64(), which screws up linking with
     other libraries (that don't use MSVCRT80 from MSVC 2005 by default).
    i686-w64-mingw32 does have fseeko() and respects _FILE_OFFSET_BITS, but I
     don't know how to detect that at compile time.
    We could just use fseeko64() (which is available in both), but its
     implemented using fgetpos()/fsetpos() just like this code, except without
     the overflow checking, so we prefer our version.*/
  opus_int64 pos;
  /*We don't use fpos_t directly because it might be a struct if __STDC__ is
     non-zero or _INTEGRAL_MAX_BITS < 64.
    I'm not certain when the latter is true, but someone could in theory set
     the former.
    Either way, it should be binary compatible with a normal 64-bit int (this
     assumption is not portable, but I believe it is true for MSVCRT).*/
  OP_ASSERT(sizeof(pos)==sizeof(fpos_t));
  /*Translate the seek to an absolute one.*/
  if(_whence==SEEK_CUR){
    int ret;
    ret=fgetpos((FILE *)_stream,(fpos_t *)&pos);
    if(ret)return ret;
  }
  else if(_whence==SEEK_END)pos=_filelengthi64(_fileno((FILE *)_stream));
  else if(_whence==SEEK_SET)pos=0;
  else return -1;
  /*Check for errors or overflow.*/
  if(pos<0||_offset<-pos||_offset>OP_INT64_MAX-pos)return -1;
  pos+=_offset;
  return fsetpos((FILE *)_stream,(fpos_t *)&pos);
#else
  /*This function actually conforms to the SUSv2 and POSIX.1-2001, so we prefer
     it except on Windows.*/
  return fseeko((FILE *)_stream,(off_t)_offset,_whence);
#endif
}

static opus_int64 op_ftell(void *_stream){
#if defined(_WIN32)
  /*_ftelli64() is not exposed until MSCVCRT80, and ftello()/ftello64() have
     the same problems as fseeko()/fseeko64() in MingW.
    See above for a more detailed explanation.*/
  opus_int64 pos;
  OP_ASSERT(sizeof(pos)==sizeof(fpos_t));
  return fgetpos((FILE *)_stream,(fpos_t *)&pos)?-1:pos;
#else
  /*This function actually conforms to the SUSv2 and POSIX.1-2001, so we prefer
     it except on Windows.*/
  return ftello((FILE *)_stream);
#endif
}

static const OpusFileCallbacks OP_FILE_CALLBACKS={
  op_fread,
  op_fseek,
  op_ftell,
  (op_close_func)fclose
};

#if defined(_WIN32)
# include <stddef.h>
# include <errno.h>

/*Windows doesn't accept UTF-8 by default, and we don't have a wchar_t API,
   so if we just pass the path to fopen(), then there'd be no way for a user
   of our API to open a Unicode filename.
  Instead, we translate from UTF-8 to UTF-16 and use Windows' wchar_t API.
  This makes this API more consistent with platforms where the character set
   used by fopen is the same as used on disk, which is generally UTF-8, and
   with our metadata API, which always uses UTF-8.*/
static wchar_t *op_utf8_to_utf16(const char *_src){
  wchar_t *dst;
  size_t   len;
  len=strlen(_src);
  /*Worst-case output is 1 wide character per 1 input character.*/
  dst=(wchar_t *)_ogg_malloc(sizeof(*dst)*(len+1));
  if(dst!=NULL){
    size_t si;
    size_t di;
    for(di=si=0;si<len;si++){
      int c0;
      c0=(unsigned char)_src[si];
      if(!(c0&0x80)){
        /*Start byte says this is a 1-byte sequence.*/
        dst[di++]=(wchar_t)c0;
        continue;
      }
      else{
        int c1;
        /*This is safe, because c0 was not 0 and _src is NUL-terminated.*/
        c1=(unsigned char)_src[si+1];
        if((c1&0xC0)==0x80){
          /*Found at least one continuation byte.*/
          if((c0&0xE0)==0xC0){
            wchar_t w;
            /*Start byte says this is a 2-byte sequence.*/
            w=(c0&0x1F)<<6|c1&0x3F;
            if(w>=0x80U){
              /*This is a 2-byte sequence that is not overlong.*/
              dst[di++]=w;
              si++;
              continue;
            }
          }
          else{
            int c2;
            /*This is safe, because c1 was not 0 and _src is NUL-terminated.*/
            c2=(unsigned char)_src[si+2];
            if((c2&0xC0)==0x80){
              /*Found at least two continuation bytes.*/
              if((c0&0xF0)==0xE0){
                wchar_t w;
                /*Start byte says this is a 3-byte sequence.*/
                w=(c0&0xF)<<12|(c1&0x3F)<<6|c2&0x3F;
                if(w>=0x800U&&(w<0xD800||w>=0xE000)&&w<0xFFFE){
                  /*This is a 3-byte sequence that is not overlong, not a
                     UTF-16 surrogate pair value, and not a 'not a character'
                     value.*/
                  dst[di++]=w;
                  si+=2;
                  continue;
                }
              }
              else{
                int c3;
                /*This is safe, because c2 was not 0 and _src is
                   NUL-terminated.*/
                c3=(unsigned char)_src[si+3];
                if((c3&0xC0)==0x80){
                  /*Found at least three continuation bytes.*/
                  if((c0&0xF8)==0xF0){
                    opus_uint32 w;
                    /*Start byte says this is a 4-byte sequence.*/
                    w=(c0&7)<<18|(c1&0x3F)<<12|(c2&0x3F)<<6&(c3&0x3F);
                    if(w>=0x10000U&&w<0x110000U){
                      /*This is a 4-byte sequence that is not overlong and not
                         greater than the largest valid Unicode code point.
                        Convert it to a surrogate pair.*/
                      w-=0x10000;
                      dst[di++]=(wchar_t)(0xD800+(w>>10));
                      dst[di++]=(wchar_t)(0xDC00+(w&0x3FF));
                      si+=3;
                      continue;
                    }
                  }
                }
              }
            }
          }
        }
      }
      /*If we got here, we encountered an illegal UTF-8 sequence.*/
      _ogg_free(dst);
      return NULL;
    }
    OP_ASSERT(di<=len);
    dst[di]='\0';
  }
  return dst;
}

#endif

void *op_fopen(OpusFileCallbacks *_cb,const char *_path,const char *_mode){
  FILE *fp;
#if !defined(_WIN32)
  fp=fopen(_path,_mode);
#else
  fp=NULL;
  {
    wchar_t *wpath;
    wchar_t *wmode;
    wpath=op_utf8_to_utf16(_path);
    wmode=op_utf8_to_utf16(_mode);
    if(wmode==NULL)errno=EINVAL;
    else if(wpath==NULL)errno=ENOENT;
    else fp=_wfopen(wpath,wmode);
    _ogg_free(wmode);
    _ogg_free(wpath);
  }
#endif
  if(fp!=NULL)*_cb=*&OP_FILE_CALLBACKS;
  return fp;
}

void *op_fdopen(OpusFileCallbacks *_cb,int _fd,const char *_mode){
  FILE *fp;
  fp=fdopen(_fd,_mode);
  if(fp!=NULL)*_cb=*&OP_FILE_CALLBACKS;
  return fp;
}

void *op_freopen(OpusFileCallbacks *_cb,const char *_path,const char *_mode,
 void *_stream){
  FILE *fp;
#if !defined(_WIN32)
  fp=freopen(_path,_mode,(FILE *)_stream);
#else
  fp=NULL;
  {
    wchar_t *wpath;
    wchar_t *wmode;
    wpath=op_utf8_to_utf16(_path);
    wmode=op_utf8_to_utf16(_mode);
    if(wmode==NULL)errno=EINVAL;
    else if(wpath==NULL)errno=ENOENT;
    else fp=_wfreopen(wpath,wmode,(FILE *)_stream);
    _ogg_free(wmode);
    _ogg_free(wpath);
  }
#endif
  if(fp!=NULL)*_cb=*&OP_FILE_CALLBACKS;
  return fp;
}

static int op_mem_read(void *_stream,unsigned char *_ptr,int _buf_size){
  OpusMemStream *stream;
  ptrdiff_t      size;
  ptrdiff_t      pos;
  stream=(OpusMemStream *)_stream;
  /*Check for empty read.*/
  if(_buf_size<=0)return 0;
  size=stream->size;
  pos=stream->pos;
  /*Check for EOF.*/
  if(pos>=size)return 0;
  /*Check for a short read.*/
  _buf_size=(int)OP_MIN(size-pos,_buf_size);
  memcpy(_ptr,stream->data+pos,_buf_size);
  pos+=_buf_size;
  stream->pos=pos;
  return _buf_size;
}

static int op_mem_seek(void *_stream,opus_int64 _offset,int _whence){
  OpusMemStream *stream;
  ptrdiff_t      pos;
  stream=(OpusMemStream *)_stream;
  pos=stream->pos;
  OP_ASSERT(pos>=0);
  switch(_whence){
    case SEEK_SET:{
      /*Check for overflow:*/
      if(_offset<0||_offset>OP_MEM_DIFF_MAX)return -1;
      pos=(ptrdiff_t)_offset;
    }break;
    case SEEK_CUR:{
      /*Check for overflow:*/
      if(_offset<-pos||_offset>OP_MEM_DIFF_MAX-pos)return -1;
      pos=(ptrdiff_t)(pos+_offset);
    }break;
    case SEEK_END:{
      ptrdiff_t size;
      size=stream->size;
      OP_ASSERT(size>=0);
      /*Check for overflow:*/
      if(_offset>size||_offset<size-OP_MEM_DIFF_MAX)return -1;
      pos=(ptrdiff_t)(size-_offset);
    }break;
    default:return -1;
  }
  stream->pos=pos;
  return 0;
}

static opus_int64 op_mem_tell(void *_stream){
  OpusMemStream *stream;
  stream=(OpusMemStream *)_stream;
  return (ogg_int64_t)stream->pos;
}

static int op_mem_close(void *_stream){
  _ogg_free(_stream);
  return 0;
}

static const OpusFileCallbacks OP_MEM_CALLBACKS={
  op_mem_read,
  op_mem_seek,
  op_mem_tell,
  op_mem_close
};

void *op_mem_stream_create(OpusFileCallbacks *_cb,
 const unsigned char *_data,size_t _size){
  OpusMemStream *stream;
  if(_size>OP_MEM_SIZE_MAX)return NULL;
  stream=(OpusMemStream *)_ogg_malloc(sizeof(*stream));
  if(stream!=NULL){
    *_cb=*&OP_MEM_CALLBACKS;
    stream->data=_data;
    stream->size=_size;
    stream->pos=0;
  }
  return stream;
}