shithub: opusfile

Download patch

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))