ref: 9791791469b26f8382d43ae7d6833ec3c99ad6fc
parent: f310b9ef01991b1920ece7a075e95b7fcb8bc443
author: Timothy B. Terriberry <tterribe@xiph.org>
date: Fri Aug 23 09:27:11 EDT 2013
Add API to report information from server headers. This allows the application to report details about the server for HTTP[S] streams. For all HTTP[S], this includes the server software, content-type, and whether or not it's using HTTPS. For live streams, it also includes the station name, description, genre, homepage, nominal bitrate, and whether or not it's publicly listed.
--- a/examples/opusfile_example.c
+++ b/examples/opusfile_example.c
@@ -168,8 +168,9 @@
of=op_open_callbacks(op_fdopen(&cb,fileno(stdin),"rb"),&cb,NULL,0,&ret);
}
else{
+ OpusServerInfo info;
/*Try to treat the argument as a URL.*/
- of=op_open_url(_argv[1],&ret,NULL);
+ of=op_open_url(_argv[1],&ret,OP_GET_SERVER_INFO(&info),NULL);
#if 0
if(of==NULL){
OpusFileCallbacks cb={NULL,NULL,NULL,NULL};
@@ -182,9 +183,36 @@
}
#else
if(of==NULL)of=op_open_file(_argv[1],&ret);
- /*This is not a very good check, but at least it won't give false
- positives.*/
- else is_ssl=strncmp(_argv[1],"https:",6)==0;
+ else{
+ if(info.name!=NULL){
+ fprintf(stderr,"Station name: %s\n",info.name);
+ }
+ if(info.description!=NULL){
+ fprintf(stderr,"Station description: %s\n",info.description);
+ }
+ if(info.genre!=NULL){
+ fprintf(stderr,"Station genre: %s\n",info.genre);
+ }
+ if(info.url!=NULL){
+ fprintf(stderr,"Station homepage: %s\n",info.url);
+ }
+ if(info.bitrate_kbps>=0){
+ fprintf(stderr,"Station bitrate: %u kbps\n",
+ (unsigned)info.bitrate_kbps);
+ }
+ if(info.is_public>=0){
+ fprintf(stderr,"%s\n",
+ info.is_public?"Station is public.":"Station is private.");
+ }
+ if(info.server!=NULL){
+ fprintf(stderr,"Server software: %s\n",info.server);
+ }
+ if(info.content_type!=NULL){
+ fprintf(stderr,"Content-Type: %s\n",info.content_type);
+ }
+ is_ssl=info.is_ssl;
+ opus_server_info_clear(&info);
+ }
#endif
}
if(of==NULL){
--- a/include/opusfile.h
+++ b/include/opusfile.h
@@ -601,6 +601,8 @@
They may be expanded in the future.*/
/*@{*/
+typedef struct OpusServerInfo OpusServerInfo;
+
/*These are the raw numbers used to define the request codes.
They should not be used directly.*/
#define OP_SSL_SKIP_CERTIFICATE_CHECK_REQUEST (6464)
@@ -608,6 +610,7 @@
#define OP_HTTP_PROXY_PORT_REQUEST (6592)
#define OP_HTTP_PROXY_USER_REQUEST (6656)
#define OP_HTTP_PROXY_PASS_REQUEST (6720)
+#define OP_GET_SERVER_INFO_REQUEST (6784)
#define OP_URL_OPT(_request) ((_request)+(char *)0)
@@ -615,7 +618,58 @@
provided to one of the URL options.*/
#define OP_CHECK_INT(_x) ((void)((_x)==(opus_int32)0),(opus_int32)(_x))
#define OP_CHECK_CONST_CHAR_PTR(_x) ((_x)+((_x)-(const char *)(_x)))
+#define OP_CHECK_SERVER_INFO_PTR(_x) ((_x)+((_x)-(OpusServerInfo *)(_x)))
+/**HTTP/Shoutcast/Icecast server information associated with a URL.*/
+struct OpusServerInfo{
+ /**The name of the server (icy-name/ice-name).
+ This is <code>NULL</code> if there was no <code>icy-name</code> or
+ <code>ice-name</code> header.*/
+ char *name;
+ /**A short description of the server (icy-description/ice-description).
+ This is <code>NULL</code> if there was no <code>icy-description</code> or
+ <code>ice-description</code> header.*/
+ char *description;
+ /**The genre the server falls under (icy-genre/ice-genre).
+ This is <code>NULL</code> if there was no <code>icy-genre</code> header.*/
+ char *genre;
+ /**The homepage for the server (icy-url/ice-url).
+ This is <code>NULL</code> if there was no <code>icy-url</code> header.*/
+ char *url;
+ /**The software used by the origin server (Server).
+ This is <code>NULL</code> if there was no <code>Server</code> header.*/
+ char *server;
+ /**The media type of the entity sent to the recepient (Content-Type).
+ This is <code>NULL</code> if there was no <code>Content-Type</code>
+ header.*/
+ char *content_type;
+ /**The nominal stream bitrate in kbps (icy-br/ice-bitrate).
+ This is <code>-1</code> if there was no <code>icy-br</code> or
+ <code>ice-bitrate</code> header.*/
+ opus_int32 bitrate_kbps;
+ /**Flag indicating whether the server is public (<code>1</code>) or not
+ (<code>0</code>) (icy-pub/ice-public).
+ This is <code>-1</code> if there was no <code>icy-pub</code> or
+ <code>ice-public</code> header.*/
+ int is_public;
+ /**Flag indicating whether the server is using HTTPS instead of HTTP.
+ This is <code>0</code> unless HTTPS is being used.
+ This may not match the protocol used in the original URL if there were
+ redirections.*/
+ int is_ssl;
+};
+
+/**Initializes an #OpusServerInfo structure.
+ All fields are set as if the corresponding header was not available.*/
+void opus_server_info_init(OpusServerInfo *_info) OP_ARG_NONNULL(1);
+
+/**Clears the #OpusServerInfo structure.
+ This should be called on an #OpusServerInfo structure after it is no longer
+ needed.
+ It will free all memory used by the structure members.
+ \param _info The #OpusServerInfo structure to clear.*/
+void opus_server_info_clear(OpusServerInfo *_info) OP_ARG_NONNULL(1);
+
/**Skip the certificate check when connecting via TLS/SSL (https).
\param _b <code>opus_int32</code>: Whether or not to skip the certificate
check.
@@ -674,6 +728,27 @@
\hideinitializer*/
#define OP_HTTP_PROXY_PASS(_pass) \
OP_URL_OPT(OP_HTTP_PROXY_PASS_REQUEST),OP_CHECK_CONST_CHAR_PTR(_pass)
+
+/**Parse information about the streaming server (if any) and return it.
+ Very little validation is done.
+ In particular, OpusServerInfo::url may not be a valid URL,
+ OpusServerInfo::bitrate_kbps may not really be in kbps, and
+ OpusServerInfo::content_type may not be a valid MIME type.
+ The character set of the string fields is not specified anywhere, and should
+ not be assumed to be valid UTF-8.
+ \param _info OpusServerInfo *: Returns information about the server.
+ If there is any error opening the stream, the
+ contents of this structure remain
+ unmodified.
+ On success, fills in the structure with the
+ server information that was available, if
+ any.
+ After a successful return, the contents of
+ this structure should be freed by calling
+ opus_server_info_clear().
+ \hideinitializer*/
+#define OP_GET_SERVER_INFO(_info) \
+ OP_URL_OPT(OP_GET_SERVER_INFO_REQUEST),OP_CHECK_SERVER_INFO_PTR(_info)
/*@}*/
/*@}*/
--- a/src/http.c
+++ b/src/http.c
@@ -2130,7 +2130,7 @@
static int op_http_stream_open(OpusHTTPStream *_stream,const char *_url,
int _skip_certificate_check,const char *_proxy_host,unsigned _proxy_port,
- const char *_proxy_user,const char *_proxy_pass){
+ const char *_proxy_user,const char *_proxy_pass,OpusServerInfo *_info){
struct addrinfo *addrs;
const char *last_host;
unsigned last_port;
@@ -2387,7 +2387,50 @@
So it should send back at least HTTP/1.1, despite our HTTP/1.0
request.*/
pipeline+=v1_1_compat&&op_http_allow_pipelining(cdr);
+ if(_info!=NULL&&_info->server==NULL)_info->server=op_string_dup(cdr);
}
+ /*Collect station information headers if the caller requested it.
+ If there's more than one copy of a header, the first one wins.*/
+ else if(_info!=NULL){
+ if(strcmp(header,"content-type")==0){
+ if(_info->content_type==NULL){
+ _info->content_type=op_string_dup(cdr);
+ }
+ }
+ else if(header[0]=='i'&&header[1]=='c'
+ &&(header[2]=='e'||header[2]=='y')&&header[3]=='-'){
+ if(strcmp(header+4,"name")==0){
+ if(_info->name==NULL)_info->name=op_string_dup(cdr);
+ }
+ else if(strcmp(header+4,"description")==0){
+ if(_info->description==NULL)_info->description=op_string_dup(cdr);
+ }
+ else if(strcmp(header+4,"genre")==0){
+ if(_info->genre==NULL)_info->genre=op_string_dup(cdr);
+ }
+ else if(strcmp(header+4,"url")==0){
+ if(_info->url==NULL)_info->url=op_string_dup(cdr);
+ }
+ else if(strcmp(header,"icy-br")==0
+ ||strcmp(header,"ice-bitrate")==0){
+ if(_info->bitrate_kbps<0){
+ opus_int64 bitrate_kbps;
+ /*Just re-using this function to parse a random unsigned
+ integer field.*/
+ bitrate_kbps=op_http_parse_content_length(cdr);
+ if(bitrate_kbps>=0&&bitrate_kbps<=OP_INT32_MAX){
+ _info->bitrate_kbps=(opus_int32)bitrate_kbps;
+ }
+ }
+ }
+ else if(strcmp(header,"icy-pub")==0
+ ||strcmp(header,"ice-public")==0){
+ if(_info->is_public<0&&(cdr[0]=='0'||cdr[0]=='1')&&cdr[1]=='\0'){
+ _info->is_public=cdr[0]-'0';
+ }
+ }
+ }
+ }
}
switch(status_code[2]){
/*200 OK*/
@@ -2430,6 +2473,7 @@
_stream->cur_conni=0;
_stream->connect_rate=op_time_diff_ms(&end_time,&start_time);
_stream->connect_rate=OP_MAX(_stream->connect_rate,1);
+ if(_info!=NULL)_info->is_ssl=OP_URL_IS_SSL(&_stream->url);
/*The URL has been successfully opened.*/
return 0;
}
@@ -3124,12 +3168,33 @@
};
#endif
+void opus_server_info_init(OpusServerInfo *_info){
+ _info->name=NULL;
+ _info->description=NULL;
+ _info->genre=NULL;
+ _info->url=NULL;
+ _info->server=NULL;
+ _info->content_type=NULL;
+ _info->bitrate_kbps=-1;
+ _info->is_public=-1;
+ _info->is_ssl=0;
+}
+
+void opus_server_info_clear(OpusServerInfo *_info){
+ _ogg_free(_info->content_type);
+ _ogg_free(_info->server);
+ _ogg_free(_info->url);
+ _ogg_free(_info->genre);
+ _ogg_free(_info->description);
+ _ogg_free(_info->name);
+}
+
/*The actual URL stream creation function.
This one isn't extensible like the application-level interface, but because
it isn't public, we're free to change it in the future.*/
static void *op_url_stream_create_impl(OpusFileCallbacks *_cb,const char *_url,
int _skip_certificate_check,const char *_proxy_host,unsigned _proxy_port,
- const char *_proxy_user,const char *_proxy_pass){
+ const char *_proxy_user,const char *_proxy_pass,OpusServerInfo *_info){
const char *path;
/*Check to see if this is a valid file: URL.*/
path=op_parse_file_url(_url);
@@ -3151,7 +3216,7 @@
if(OP_UNLIKELY(stream==NULL))return NULL;
op_http_stream_init(stream);
ret=op_http_stream_open(stream,_url,_skip_certificate_check,
- _proxy_host,_proxy_port,_proxy_user,_proxy_pass);
+ _proxy_host,_proxy_port,_proxy_user,_proxy_pass,_info);
if(OP_UNLIKELY(ret<0)){
op_http_stream_clear(stream);
_ogg_free(stream);
@@ -3166,6 +3231,7 @@
(void)_proxy_port;
(void)_proxy_user;
(void)_proxy_pass;
+ (void)_info;
return NULL;
#endif
}
@@ -3172,16 +3238,18 @@
void *op_url_stream_vcreate(OpusFileCallbacks *_cb,
const char *_url,va_list _ap){
- int skip_certificate_check;
- const char *proxy_host;
- opus_int32 proxy_port;
- const char *proxy_user;
- const char *proxy_pass;
+ int skip_certificate_check;
+ const char *proxy_host;
+ opus_int32 proxy_port;
+ const char *proxy_user;
+ const char *proxy_pass;
+ OpusServerInfo *pinfo;
skip_certificate_check=0;
proxy_host=NULL;
proxy_port=8080;
proxy_user=NULL;
proxy_pass=NULL;
+ pinfo=NULL;
for(;;){
ptrdiff_t request;
request=va_arg(_ap,char *)-(char *)NULL;
@@ -3204,12 +3272,27 @@
case OP_HTTP_PROXY_PASS_REQUEST:{
proxy_pass=va_arg(_ap,const char *);
}break;
+ case OP_GET_SERVER_INFO_REQUEST:{
+ pinfo=va_arg(_ap,OpusServerInfo *);
+ }break;
/*Some unknown option.*/
default:return NULL;
}
}
+ /*If the caller has requested server information, proxy it to a local copy to
+ simplify error handling.*/
+ if(pinfo!=NULL){
+ OpusServerInfo info;
+ void *ret;
+ opus_server_info_init(&info);
+ ret=op_url_stream_create_impl(_cb,_url,skip_certificate_check,
+ proxy_host,proxy_port,proxy_user,proxy_pass,&info);
+ if(ret!=NULL)*pinfo=*&info;
+ else opus_server_info_clear(&info);
+ return ret;
+ }
return op_url_stream_create_impl(_cb,_url,skip_certificate_check,
- proxy_host,proxy_port,proxy_user,proxy_pass);
+ proxy_host,proxy_port,proxy_user,proxy_pass,NULL);
}
void *op_url_stream_create(OpusFileCallbacks *_cb,
--- a/src/internal.h
+++ b/src/internal.h
@@ -94,6 +94,8 @@
# define OP_INT64_MAX (2*(((ogg_int64_t)1<<62)-1)|1)
# define OP_INT64_MIN (-OP_INT64_MAX-1)
+# define OP_INT32_MAX (2*(((ogg_int32_t)1<<30)-1)|1)
+# define OP_INT32_MIN (-OP_INT32_MAX-1)
# define OP_MIN(_a,_b) ((_a)<(_b)?(_a):(_b))
# define OP_MAX(_a,_b) ((_a)>(_b)?(_a):(_b))