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

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.
parent f310b9ef
......@@ -168,8 +168,9 @@ int main(int _argc,const char **_argv){
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 @@ int main(int _argc,const char **_argv){
}
#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){
......
......@@ -601,6 +601,8 @@ void opus_picture_tag_clear(OpusPictureTag *_pic) OP_ARG_NONNULL(1);
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 @@ void opus_picture_tag_clear(OpusPictureTag *_pic) OP_ARG_NONNULL(1);
#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,6 +618,57 @@ void opus_picture_tag_clear(OpusPictureTag *_pic) OP_ARG_NONNULL(1);
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
......@@ -675,6 +729,27 @@ void opus_picture_tag_clear(OpusPictureTag *_pic) OP_ARG_NONNULL(1);
#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)
/*@}*/
/*@}*/
......
......@@ -2130,7 +2130,7 @@ static int op_http_allow_pipelining(const char *_server){
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,6 +2387,49 @@ static int op_http_stream_open(OpusHTTPStream *_stream,const char *_url,
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]){
......@@ -2430,6 +2473,7 @@ static int op_http_stream_open(OpusHTTPStream *_stream,const char *_url,
_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 @@ static const OpusFileCallbacks OP_HTTP_CALLBACKS={
};
#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 @@ static void *op_url_stream_create_impl(OpusFileCallbacks *_cb,const char *_url,
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,22 +3231,25 @@ static void *op_url_stream_create_impl(OpusFileCallbacks *_cb,const char *_url,
(void)_proxy_port;
(void)_proxy_user;
(void)_proxy_pass;
(void)_info;
return NULL;
#endif
}
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 @@ void *op_url_stream_vcreate(OpusFileCallbacks *_cb,
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,
......
......@@ -94,6 +94,8 @@ void op_fatal_impl(const char *_str,const char *_file,int _line);
# 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))
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment