Commit 8d04070b authored by Timothy B. Terriberry's avatar Timothy B. Terriberry
Browse files

Add support for http, https, and file URLs.

This is pretty preliminary for now.
Seeking performance isn't great, and there's no caching.
I'm sure there's also lots of bugs.
parent f4c72321
......@@ -38,19 +38,23 @@ int main(int _argc,const char **_argv){
OpusFileCallbacks cb={NULL,NULL,NULL,NULL};
of=op_open_callbacks(op_fdopen(&cb,fileno(stdin),"rb"),&cb,NULL,0,&ret);
}
#if 0
/*For debugging: force a file to not be seekable.*/
else{
OpusFileCallbacks cb={NULL,NULL,NULL,NULL};
void *fp;
fp=op_fopen(&cb,_argv[1],"rb");
cb.seek=NULL;
cb.tell=NULL;
of=op_open_callbacks(fp,&cb,NULL,0,NULL);
}
/*Try to treat the argument as a URL.*/
of=op_open_url(_argv[1],OP_SSL_SKIP_CERTIFICATE_CHECK,&ret);
#if 0
if(of==NULL){
OpusFileCallbacks cb={NULL,NULL,NULL,NULL};
void *fp;
/*For debugging: force a file to not be seekable.*/
fp=op_fopen(&cb,_argv[1],"rb");
cb.seek=NULL;
cb.tell=NULL;
of=op_open_callbacks(fp,&cb,NULL,0,NULL);
}
#else
else of=op_open_file(_argv[1],&ret);
if(of==NULL)of=op_open_file(_argv[1],&ret);
#endif
}
if(of==NULL){
fprintf(stderr,"Failed to open file '%s': %i\n",_argv[1],ret);
return EXIT_FAILURE;
......
......@@ -55,6 +55,15 @@ typedef struct OggOpusFile OggOpusFile;
# define OP_ARG_NONNULL(_x)
# endif
/**\defgroup error_codes Error Codes*/
/*@{*/
/**\name List of possible error codes
Many of the functions in this library return a negative error code when a
function fails.
This list provides a brief explanation of the common errors.
See each individual function for more details on what a specific error code
means in that context.*/
/*@{*/
/**A request did not succeed.*/
#define OP_FALSE (-1)
/*Currently not used externally.*/
......@@ -97,6 +106,11 @@ typedef struct OggOpusFile OggOpusFile;
#define OP_ENOSEEK (-138)
/**The first or last granule position of a link failed basic validity checks.*/
#define OP_EBADTIMESTAMP (-139)
/*@}*/
/*@}*/
/**\defgroup header_info Header Information*/
/*@{*/
/**The maximum number of channels in an Ogg Opus stream.*/
#define OPUS_CHANNEL_COUNT_MAX (255)
......@@ -337,6 +351,23 @@ void opus_tags_clear(OpusTags *_tags) OP_ARG_NONNULL(1);
/*@}*/
/*@}*/
/**\defgroup url_flags URL Reading Flags*/
/*@{*/
/**\name Flags for op_url_create_with_proxy() and associated functions.
These may be expanded in the future.*/
/*@{*/
/**Skip the certificate check when connecting via TLS/SSL (https).*/
#define OP_SSL_SKIP_CERTIFICATE_CHECK (1)
/*@}*/
/*@}*/
/*\defgroup file_callbacks Abstract Stream Reading Functions*/
/*@{*/
/**\name Functions for reading from streams
These functions define the interface used to read from and seek in a stream
of data.
......@@ -418,7 +449,8 @@ struct OpusFileCallbacks{
filled in here.
\param _path The path to the file to open.
\param _mode The mode to open the file in.
\return A stream handle to use with the callbacks, or NULL on error.*/
\return A stream handle to use with the callbacks, or <code>NULL</code> on
error.*/
OP_WARN_UNUSED_RESULT void *op_fopen(OpusFileCallbacks *_cb,
const char *_path,const char *_mode) OP_ARG_NONNULL(1) OP_ARG_NONNULL(2)
OP_ARG_NONNULL(3);
......@@ -434,7 +466,8 @@ OP_WARN_UNUSED_RESULT void *op_fopen(OpusFileCallbacks *_cb,
filled in here.
\param _fd The file descriptor to open.
\param _mode The mode to open the file in.
\return A stream handle to use with the callbacks, or NULL on error.*/
\return A stream handle to use with the callbacks, or <code>NULL</code> on
error.*/
OP_WARN_UNUSED_RESULT void *op_fdopen(OpusFileCallbacks *_cb,
int _fd,const char *_mode) OP_ARG_NONNULL(1) OP_ARG_NONNULL(3);
......@@ -451,7 +484,8 @@ OP_WARN_UNUSED_RESULT void *op_fdopen(OpusFileCallbacks *_cb,
\param _mode The mode to open the file in.
\param _stream A stream previously returned by op_fopen(), op_fdopen(),
or op_freopen().
\return A stream handle to use with the callbacks, or NULL on error.*/
\return A stream handle to use with the callbacks, or <code>NULL</code> on
error.*/
OP_WARN_UNUSED_RESULT void *op_freopen(OpusFileCallbacks *_cb,
const char *_path,const char *_mode,void *_stream) OP_ARG_NONNULL(1)
OP_ARG_NONNULL(2) OP_ARG_NONNULL(3) OP_ARG_NONNULL(4);
......@@ -464,9 +498,65 @@ OP_WARN_UNUSED_RESULT void *op_freopen(OpusFileCallbacks *_cb,
filled in here.
\param _data The block of memory to read from.
\param _size The size of the block of memory.
\return A stream handle to use with the callbacks, or NULL on error.*/
\return A stream handle to use with the callbacks, or <code>NULL</code> on
error.*/
OP_WARN_UNUSED_RESULT void *op_mem_stream_create(OpusFileCallbacks *_cb,
const unsigned char *_data,size_t _size);
const unsigned char *_data,size_t _size) OP_ARG_NONNULL(1);
/**Creates a stream that reads from the given URL.
This is equivalent to calling op_url_stream_create_with_proxy() with
<code>NULL</code> for \a _proxy_host.
\param[out] _cb The callbacks to use for this stream.
If there is an error creating the stream, nothing will be
filled in here.
\param _url The URL to read from.
Currently only the "file:", "http:", and "https:" schemes
are supported.
Both "http:" and "https:" may be disabled at compile
time, in which case opening such URLs will fail.
\param _flags The <a href="#url_flags">optional flags</a> to use.
\return A stream handle to use with the callbacks, or <code>NULL</code> on
error.*/
OP_WARN_UNUSED_RESULT void *op_url_stream_create(OpusFileCallbacks *_cb,
const char *_url,int _flags) OP_ARG_NONNULL(1) OP_ARG_NONNULL(2);
/**Creates a stream that reads from the given URL using the specified proxy.
\param[out] _cb The callbacks to use for this stream.
If there is an error creating the stream, nothing
will be filled in here.
\param _url The URL to read from.
Currently only the "file:", "http:", and "https:"
schemes are supported.
Both "http:" and "https:" may be disabled at compile
time, in which case opening such URLs will fail.
\param _flags The <a href="#url_flags">optional flags</a> to use.
\param _proxy_host The host of the proxy to connect to.
This may be <code>NULL</code> if you do not wish to
use a proxy.
The proxy information is ignored if \a _url is a
<file:> URL.
\param _proxy_port The port of the proxy to connect to.
This is ignored if \a _proxy_host is
<code>NULL</code>.
\param _proxy_user The username to use with the specified proxy.
This may be <code>NULL</code> if no authorization is
required.
This is ignored if \a _proxy_host is
<code>NULL</code>.
\param _proxy_pass The password to use with the specified proxy.
This may be <code>NULL</code> if no authorization is
required.
This is ignored if either \a _proxy_host or
\a _proxy_user are <code>NULL</code>.
\return A stream handle to use with the callbacks, or <code>NULL</code> on
error.*/
OP_WARN_UNUSED_RESULT void *op_url_stream_create_with_proxy(
OpusFileCallbacks *_cb,const char *_url,int _flags,
const char *_proxy_host,unsigned _proxy_port,
const char *_proxy_user,const char *_proxy_pass) OP_ARG_NONNULL(1)
OP_ARG_NONNULL(2);
/*@}*/
/*@}*/
......@@ -510,11 +600,12 @@ int op_test(OpusHead *_head,
/**Open a stream from the given file path.
\param _path The path to the file to open.
\param[out] _error Returns 0 on success, or a failure code on error.
You may pass in NULL if you don't want the failure code.
You may pass in <code>NULL</code> if you don't want the
failure code.
The failure code will be #OP_EFAULT if the file could not
be opened, or one of the other failure codes from
op_open_callbacks() otherwise.
\return An #OggOpusFile pointer on success, or NULL on error.*/
\return An #OggOpusFile pointer on success, or <code>NULL</code> on error.*/
OP_WARN_UNUSED_RESULT OggOpusFile *op_open_file(const char *_path,int *_error)
OP_ARG_NONNULL(1);
......@@ -522,12 +613,64 @@ OP_WARN_UNUSED_RESULT OggOpusFile *op_open_file(const char *_path,int *_error)
\param _data The memory buffer to open.
\param _size The number of bytes in the buffer.
\param[out] _error Returns 0 on success, or a failure code on error.
You may pass in NULL if you don't want the failure code.
You may pass in <code>NULL</code> if you don't want the
failure code.
See op_open_callbacks() for a full list of failure codes.
\return An #OggOpusFile pointer on success, or NULL on error.*/
\return An #OggOpusFile pointer on success, or <code>NULL</code> on error.*/
OP_WARN_UNUSED_RESULT OggOpusFile *op_open_memory(const unsigned char *_data,
size_t _size,int *_error);
/**Open a stream from a URL.
\param _url The URL to open.
Currently only the <file:>, <http:>, and <https:> schemes
are supported.
Both "http:" and "https:" may be disabled at compile
time, in which case opening such URLs will fail.
\param _flags The <a href="#url_flags">optional flags</a> to use.
\param[out] _error Returns 0 on success, or a failure code on error.
You may pass in <code>NULL</code> if you don't want the
failure code.
See op_open_callbacks() for a full list of failure codes.
\return An #OggOpusFile pointer on success, or <code>NULL</code> on error.*/
OP_WARN_UNUSED_RESULT OggOpusFile *op_open_url(const char *_url,
int _flags,int *_error) OP_ARG_NONNULL(1);
/**Open a stream from a URL using the specified proxy.
\param _url The URL to open.
Currently only the <file:>, <http:>, and <https:>
schemes are supported.
Both "http:" and "https:" may be disabled at compile
time, in which case opening such URLs will fail.
\param _flags The <a href="#url_flags">optional flags</a> to use.
\param _proxy_host The host of the proxy to connect to.
This may be <code>NULL</code> if you do not wish to
use a proxy.
The proxy information is ignored if \a _url is a
<file:> URL.
\param _proxy_port The port of the proxy to connect to.
This is ignored if \a _proxy_host is
<code>NULL</code>.
\param _proxy_user The username to use with the specified proxy.
This may be <code>NULL</code> if no authorization is
required.
This is ignored if \a _proxy_host is
<code>NULL</code>.
\param _proxy_pass The password to use with the specified proxy.
This may be <code>NULL</code> if no authorization is
required.
This is ignored if either \a _proxy_host or
\a _proxy_user are <code>NULL</code>.
\param[out] _error Returns 0 on success, or a failure code on error.
You may pass in <code>NULL</code> if you don't want
the failure code.
See op_open_callbacks() for a full list of failure
codes.
\return An #OggOpusFile pointer on success, or <code>NULL</code> on error.*/
OP_WARN_UNUSED_RESULT OggOpusFile *op_open_url_with_proxy(const char *_url,
int _flags,const char *_proxy_host,unsigned _proxy_port,
const char *_proxy_user,const char *_proxy_pass,int *_error)
OP_ARG_NONNULL(1);
/**Open a stream using the given set of callbacks to access it.
\param _source The stream to read from (e.g., a <code>FILE *</code>).
\param _cb The callbacks with which to access the stream.
......@@ -562,8 +705,8 @@ OP_WARN_UNUSED_RESULT OggOpusFile *op_open_memory(const unsigned char *_data,
Otherwise, seeking to absolute positions will
generate inconsistent results.
\param[out] _error Returns 0 on success, or a failure code on error.
You may pass in NULL if you don't want the failure
code.
You may pass in <code>NULL</code> if you don't want
the failure code.
The failure code will be one of
<dl>
<dt>#OP_EREAD</dt>
......@@ -609,26 +752,83 @@ OP_WARN_UNUSED_RESULT OggOpusFile *op_open_callbacks(void *_source,
void op_free(OggOpusFile *_of);
/**Partially open a stream from the given file path.
\see op_test_callbacks
\param _path The path to the file to open.
\param[out] _error Returns 0 on success, or a failure code on error.
You may pass in NULL if you don't want the failure code.
You may pass in <code>NULL</code> if you don't want the
failure code.
The failure code will be #OP_EFAULT if the file could not
be opened, or one of the other failure codes from
op_open_callbacks() otherwise.
\return An #OggOpusFile pointer on success, or NULL on error.*/
\return An #OggOpusFile pointer on success, or <code>NULL</code> on error.*/
OP_WARN_UNUSED_RESULT OggOpusFile *op_test_file(const char *_path,int *_error)
OP_ARG_NONNULL(1);
/**Partially open a stream from a memory buffer.
\see op_test_callbacks
\param _data The memory buffer to open.
\param _size The number of bytes in the buffer.
\param[out] _error Returns 0 on success, or a failure code on error.
You may pass in NULL if you don't want the failure code.
You may pass in <code>NULL</code> if you don't want the
failure code.
See op_open_callbacks() for a full list of failure codes.
\return An #OggOpusFile pointer on success, or NULL on error.*/
\return An #OggOpusFile pointer on success, or <code>NULL</code> on error.*/
OP_WARN_UNUSED_RESULT OggOpusFile *op_test_memory(const unsigned char *_data,
size_t _size,int *_error);
/**Partially open a stream from a URL.
\see op_test_callbacks
\param _url The URL to open.
Currently only the <file:>, <http:>, and <https:> schemes
are supported.
Both "http:" and "https:" may be disabled at compile
time, in which case opening such URLs will fail.
\param _flags The <a href="#url_flags">optional flags</a> to use.
\param[out] _error Returns 0 on success, or a failure code on error.
You may pass in <code>NULL</code> if you don't want the
failure code.
See op_open_callbacks() for a full list of failure codes.
\return An #OggOpusFile pointer on success, or <code>NULL</code> on error.*/
OP_WARN_UNUSED_RESULT OggOpusFile *op_test_url(const char *_url,int _flags,
int *_error) OP_ARG_NONNULL(1);
/**Partially open a stream from a URL using the specified proxy.
\see op_test_callbacks
\param _url The URL to open.
Currently only the <file:>, <http:>, and <https:>
schemes are supported.
Both "http:" and "https:" may be disabled at compile
time, in which case opening such URLs will fail.
\param _flags The <a href="#url_flags">optional flags</a> to use.
\param _proxy_host The host of the proxy to connect to.
This may be <code>NULL</code> if you do not wish to
use a proxy.
The proxy information is ignored if \a _url is a
<file:> URL.
\param _proxy_port The port of the proxy to connect to.
This is ignored if \a _proxy_host is
<code>NULL</code>.
\param _proxy_user The username to use with the specified proxy.
This may be <code>NULL</code> if no authorization is
required.
This is ignored if \a _proxy_host is
<code>NULL</code>.
\param _proxy_pass The password to use with the specified proxy.
This may be <code>NULL</code> if no authorization is
required.
This is ignored if either \a _proxy_host or
\a _proxy_user are <code>NULL</code>.
\param[out] _error Returns 0 on success, or a failure code on error.
You may pass in <code>NULL</code> if you don't want
the failure code.
See op_open_callbacks() for a full list of failure
codes.
\return An #OggOpusFile pointer on success, or <code>NULL</code> on error.*/
OP_WARN_UNUSED_RESULT OggOpusFile *op_test_url_with_proxy(const char *_url,
int _flags,const char *_proxy_host,unsigned _proxy_port,
const char *_proxy_user,const char *_proxy_pass,int *_error)
OP_ARG_NONNULL(1);
/**Partially open a stream using the given set of callbacks to access it.
This tests for Opusness and loads the headers for the first link.
It does not seek (although it tests for seekability).
......@@ -668,8 +868,8 @@ OP_WARN_UNUSED_RESULT OggOpusFile *op_test_memory(const unsigned char *_data,
Otherwise, seeking to absolute positions will
generate inconsistent results.
\param[out] _error Returns 0 on success, or a failure code on error.
You may pass in NULL if you don't want the failure
code.
You may pass in <code>NULL</code> if you don't want
the failure code.
See op_open_callbacks() for a full list of failure
codes.
\return A freshly opened #OggOpusFile, or <code>NULL</code> on error.*/
......@@ -712,7 +912,7 @@ int op_link_count(OggOpusFile *_of) OP_ARG_NONNULL(1);
/**Returns whether or not the data source being read is seekable.
This is true if
a) The seek and tell callbacks are both non-NULL,
a) The seek and tell callbacks are both non-<code>NULL</code>,
b) The seek callback was successfully executed at least once, and
c) The tell callback was successfully able to report the position indicator
afterwards.
......@@ -913,7 +1113,8 @@ ogg_int64_t op_pcm_tell(OggOpusFile *_of) OP_ARG_NONNULL(1);
Smaller buffers will simply return less data, possibly
consuming more memory to buffer the data internally.
\param[out] _li The index of the link this data was decoded from.
You may pass NULL if you do not need this information.
You may pass <code>NULL</code> if you do not need this
information.
If this function fails (returning a negative value),
this parameter is left unset.
\return The number of samples read per channel on success, or a negative
......@@ -963,7 +1164,8 @@ OP_WARN_UNUSED_RESULT int op_read(OggOpusFile *_of,
Smaller buffers will simply return less data, possibly
consuming more memory to buffer the data internally.
\param[out] _li The index of the link this data was decoded from.
You may pass NULL if you do not need this information.
You may pass <code>NULL</code> if you do not need this
information.
If this function fails (returning a negative value),
this parameter is left unset.
\return The number of samples read per channel on success, or a negative
......
#include "internal.h"
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
typedef struct OpusParsedURL OpusParsedURL;
typedef struct OpusStringBuf OpusStringBuf;
typedef struct OpusHTTPConn OpusHTTPConn;
typedef struct OpusHTTPStream OpusHTTPStream;
static char *op_string_range_dup(const char *_start,const char *_end){
size_t len;
char *ret;
OP_ASSERT(_start<=_end);
len=_end-_start;
ret=(char *)_ogg_malloc(sizeof(*ret)*(len+1));
memcpy(ret,_start,sizeof(*ret)*(len));
ret[len]='\0';
return ret;
}
static char *op_string_dup(const char *_s){
return op_string_range_dup(_s,_s+strlen(_s));
}
/*Is this an https URL?
For now we can simply check the last letter.*/
#define OP_URL_IS_SSL(_url) ((_url)->scheme[4]=='s')
#define OP_URL_IS_DEFAULT_PORT(_url) \
(!OP_URL_IS_SSL(_url)&&(_url)->port==80 \
||OP_URL_IS_SSL(_url)&&(_url)->port==443)
/*URI character classes (from RFC 3986).*/
#define OP_URL_ALPHA \
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
#define OP_URL_DIGIT "01234567890"
#define OP_URL_HEXDIGIT "01234567890ABCDEFabcdef"
/*Not a character class, but the characters allowed in <scheme>.*/
#define OP_URL_SCHEME OP_URL_ALPHA OP_URL_DIGIT "+-."
#define OP_URL_GEN_DELIMS "#/:?@[]"
#define OP_URL_SUB_DELIMS "!$&'()*+,;="
#define OP_URL_RESERVED OP_URL_GEN_DELIMS OP_URL_SUB_DELIMS
#define OP_URL_UNRESERVED OP_URL_ALPHA OP_URL_DIGIT "-._~"
/*Not a character class, but the characters allowed in <pct-encoded>.*/
#define OP_URL_PCT_ENCODED "%"
/*Not a character class or production rule, but for convenience.*/
#define OP_URL_PCHAR_BASE \
OP_URL_UNRESERVED OP_URL_PCT_ENCODED OP_URL_SUB_DELIMS
#define OP_URL_PCHAR OP_URL_PCHAR_BASE ":@"
/*Not a character class, but the characters allowed in <userinfo> and
<IP-literal>.*/
#define OP_URL_PCHAR_NA OP_URL_PCHAR_BASE ":"
/*Not a character class, but the characters allowed in <segment-nz-nc>.*/
#define OP_URL_PCHAR_NC OP_URL_PCHAR_BASE "@"
/*Not a character clsss, but the characters allowed in <path>.*/
#define OP_URL_PATH OP_URL_PCHAR "/"
/*Not a character class, but the characters allowed in <query> / <fragment>.*/
#define OP_URL_QUERY_FRAG OP_URL_PCHAR "/?"
/*Check the <% HEXDIG HEXDIG> escapes of a URL for validity.
Return: 0 if valid, or a negative value on failure.*/
static int op_validate_url_escapes(const char *_s){
int i;
for(i=0;_s[i];i++){
if(_s[i]=='%'){
if(OP_UNLIKELY(!isxdigit(_s[i+1]))
||OP_UNLIKELY(!isxdigit(_s[i+2]))
/*RFC 3986 says %00 "should be rejected if the application is not
expecting to receive raw data within a component."*/
||OP_UNLIKELY(_s[i+1]=='0'&&_s[i+2]=='0')){
return OP_FALSE;
}
i+=2;
}
}
return 0;
}
/*Convert a hex digit to its actual value.
_c: The hex digit to convert.
Presumed to be valid ('0'...'9', 'A'...'F', or 'a'...'f').
Return: The value of the digit, in the range [0,15].*/
static int op_hex_value(int _c){
return _c>='a'?_c-'a'+10:_c>='A'?_c-'A'+10:_c-'0';
}
/*Unescape all the <% HEXDIG HEXDIG> sequences in a string in-place.
This does no validity checking.*/
static char *op_unescape_url_component(char *_s){
int i;
int j;
for(i=j=0;_s[i];i++,j++){
if(_s[i]=='%'){
_s[i]=(char)(op_hex_value(_s[i+1])<<4|op_hex_value(_s[i+2]));
i+=2;
}
}
return _s;
}
/*Parse a file: URL.
This code is not meant to be fast: strspn() with large sets is likely to be
slow, but it is very convenient.
It is meant to be RFC 1738-compliant (as updated by RFC 3986).*/
static const char *op_parse_file_url(const char *_src){
const char *scheme_end;
const char *path;
const char *path_end;
scheme_end=_src+strspn(_src,OP_URL_SCHEME);
if(OP_UNLIKELY(*scheme_end!=':')
||scheme_end-_src!=4||op_strncasecmp(_src,"file",4)!=0){
/*Unsupported protocol.*/
return NULL;
}
/*Make sure all escape sequences are valid to simplify unescaping later.*/
if(OP_UNLIKELY(op_validate_url_escapes(scheme_end+1)<0))return NULL;
if(scheme_end[1]=='/'&&scheme_end[2]=='/'){
const char *host;
const char *host_end;
char host_buf[28];
/*file: URLs can have a host!
Yeah, I was surprised, too, but that's what RFC 1738 says.
It also says, "The file URL scheme is unusual in that it does not specify
an Internet protocol or access method for such files; as such, its
utility in network protocols between hosts is limited," which is a mild
understatement.*/
host=scheme_end+3;
/*The empty host is what we expect.*/
if(OP_LIKELY(*host!='/'))path=host;
/*RFC 1738 says localhost "is interpreted as `the machine from which the
URL is being interpreted,'" so let's check for it.*/
host_end=host+strspn(host,OP_URL_PCHAR_BASE);
/*No <port> allowed.
This also rejects IP-Literals.*/
if(*host_end!='/')return NULL;
/*An escaped "localhost" can take at most 27 characters.*/
if(OP_UNLIKELY(host_end-host>27))return NULL;
memcpy(host_buf,host,sizeof(*host_buf)*(host_end-host));
host_buf[host_end-host]='\0';
op_unescape_url_component(host_buf);
/*Some other host: give up.*/
if(OP_UNLIKELY(op_strncasecmp(host_buf,"localhost",9)!=0))return NULL;
path=host_end;
}
else path=scheme_end+1;
path_end=path+strspn(path,OP_URL_PATH);
/*This will reject a <query> or <fragment> component, too.
I don't know what to do with queries, but a temporal fragment would at
least make sense.
RFC 1738 pretty clearly defines a <searchpart> that's equivalent to the
RFC 3986 <query> component for other schemes, but not the file: scheme,
so I'm going to just reject it.*/
if(*path_end!='\0')return NULL;
return path;
}
#if defined(OP_ENABLE_HTTP)
# include <sys/types.h>
# include <sys/socket.h>
# include <sys/time.h>
# include <arpa/inet.h>
# include <fcntl.h>
# include <netinet/in.h>
# include <netdb.h>
# include <poll.h>
# include <unistd.h>
# include <openssl/ssl.h>
static char *op_string_tolower(char *_s){
int i;
for(i=0;_s[i]!='\0';i++){
int c;
c=_s[i];
if(c>='A'&&c<='Z')c+='a'-'A';
_s[i]=(char)c;
}
return _s;
}
struct OpusParsedURL{
/*Either "http" or "https".*/
char *scheme;
/*The user name from the <userinfo> component, or NULL.*/
char *user;
/*The password from the <userinfo> component, or NULL.*/
char *pass;
/*The <host> component.
This may not be NULL.*/
char *host;
/*The <path> and <query> components.
This may not be NULL.*/
char *path;
/*The <port> component.
This is set to the default port if the URL did not contain one.*/
unsigned port;
};
/*Parse a URL.
This code is not meant to be fast: strspn() with large sets is likely to be
slow, but it is very convenient.
It is meant to be RFC 3986-compliant.*/
static int op_parse_url_impl(OpusParsedURL *_dst,const char *_src){
const char *scheme_end;
const char *authority;
const char *userinfo_end;
const char *user;
const char *user_end;
const char *pass;
const char *hostport;
const char *hostport_end;
const char *host_end;
const char *port;
opus_int32 port_num;
const char *port_end;
const char *path;
const char *path_end;
const char *fragment_end;
const char *uri_end;
scheme_end=_src+strspn(_src,OP_URL_SCHEME);
if(OP_UNLIKELY(*scheme_end!=':')
||OP_UNLIKELY(scheme_end-_src<4)||OP_UNLIKELY(scheme_end-_src>5)
||OP_UNLIKELY(op_strncasecmp(_src,"https",scheme_end-_src)!=0)){
/*Unsupported protocol.*/
return OP_EIMPL;
}
if(OP_UNLIKELY(scheme_end[1]!='/')||OP_UNLIKELY(scheme_end[2]!='/')){
/*We require an <authority> component.*/
return OP_EINVAL;
}
authority=scheme_end+3;
/*Make sure all escape sequences are valid to simplify unescaping later.*/
if(OP_UNLIKELY(op_validate_url_escapes(authority)<0))return OP_EINVAL;
/*Look for a <userinfo> component.*/
userinfo_end=authority+strspn(