]> git.ipfire.org Git - thirdparty/shairport-sync.git/commitdiff
Add tinyhttp code for sending and reading responses. Clean up some dacp routines...
authorMike Brady <mikebrady@eircom.net>
Mon, 18 Dec 2017 18:50:01 +0000 (18:50 +0000)
committerMike Brady <mikebrady@eircom.net>
Mon, 18 Dec 2017 18:50:01 +0000 (18:50 +0000)
25 files changed:
LICENSES
Makefile.am
audio_pa.c
common.h
dacp.c
dacp.h
dbus-service.c
definitions.h
mdns.h
mdns_avahi.c
mpris-service.c
player.c
player.h
rtp.c
rtsp.c
shairport.c
tinyhttp/LICENSE [new file with mode: 0644]
tinyhttp/README.md [new file with mode: 0644]
tinyhttp/chunk.c [new file with mode: 0644]
tinyhttp/chunk.h [new file with mode: 0644]
tinyhttp/example.cpp [new file with mode: 0644]
tinyhttp/header.c [new file with mode: 0644]
tinyhttp/header.h [new file with mode: 0644]
tinyhttp/http.c [new file with mode: 0644]
tinyhttp/http.h [new file with mode: 0644]

index 583fdf4fcddad945b1ae7580ea2791dd23af6b51..f5fed9518cd63e116676406869e151a1e1390cb4 100644 (file)
--- a/LICENSES
+++ b/LICENSES
@@ -8,6 +8,11 @@ tinysvcmdns.c, tinysvcmdns.h:
     All rights reserved.
     see tinysvcmdns.[ch] for full license text
 
+tinyhttp
+    Copyright 2012 Matthew Endsley
+    All rights reserved
+    see the tinyhttp/LICENSE for full license text
+
 Shairport:
     Copyright (c) 2011-2013 James Laird <jhl@mafipulation.org>
 
index 48ef77aa0c22af0c8a2927a43a43bd34dbfda76c..9762325d53f3ed20788122c57784dda61001b141 100644 (file)
@@ -88,7 +88,7 @@ dbus-interface.c:  org.gnome.ShairportSync.xml
 endif
 
 if USE_DBUS_CORE_AND_DACP
-shairport_sync_SOURCES += dacp.c
+shairport_sync_SOURCES += dacp.c tinyhttp/chunk.c tinyhttp/header.c tinyhttp/http.c 
 endif
 
 if USE_MPRIS
index 9bc269528025aa536af95a577cc1457d63a37771..aef433eddb895495c101898aef7799c8c1a76c19 100644 (file)
@@ -97,14 +97,14 @@ static int init(int argc, char **argv) {
 
   // Get a mainloop and its context
   mainloop = pa_threaded_mainloop_new();
-  if (mainloop==NULL)
+  if (mainloop == NULL)
     die("could not create a pa_threaded_mainloop.");
   mainloop_api = pa_threaded_mainloop_get_api(mainloop);
   if (config.pa_application_name)
     context = pa_context_new(mainloop_api, config.pa_application_name);
   else
     context = pa_context_new(mainloop_api, "Shairport Sync");
-  if (context==NULL)
+  if (context == NULL)
     die("could not create a new context for pulseaudio.");
   // Set a callback so we can wait for the context to be ready
   pa_context_set_state_callback(context, &context_state_cb, mainloop);
@@ -116,14 +116,15 @@ static int init(int argc, char **argv) {
   if (pa_threaded_mainloop_start(mainloop) != 0)
     die("could not start the pulseaudio threaded mainloop");
   if (pa_context_connect(context, NULL, 0, NULL) != 0)
-    die("failed to connect to the pulseaudio context -- the error message is \"%s\".",pa_strerror(pa_context_errno(context)));
-  
+    die("failed to connect to the pulseaudio context -- the error message is \"%s\".",
+        pa_strerror(pa_context_errno(context)));
 
   // Wait for the context to be ready
   for (;;) {
     pa_context_state_t context_state = pa_context_get_state(context);
     if (!PA_CONTEXT_IS_GOOD(context_state))
-      die("pa context is not good -- the error message \"%s\".",pa_strerror(pa_context_errno(context)));
+      die("pa context is not good -- the error message \"%s\".",
+          pa_strerror(pa_context_errno(context)));
     if (context_state == PA_CONTEXT_READY)
       break;
     pa_threaded_mainloop_wait(mainloop);
@@ -176,13 +177,16 @@ static void start(int sample_rate, int sample_format) {
 
   // Connect stream to the default audio output sink
   if (pa_stream_connect_playback(stream, NULL, &buffer_attr, stream_flags, NULL, NULL) != 0)
-    die("could not connect to the pulseaudio playback stream -- the error message is \"%s\".",pa_strerror(pa_context_errno(context)));
+    die("could not connect to the pulseaudio playback stream -- the error message is \"%s\".",
+        pa_strerror(pa_context_errno(context)));
 
   // Wait for the stream to be ready
   for (;;) {
     pa_stream_state_t stream_state = pa_stream_get_state(stream);
-    if(!PA_STREAM_IS_GOOD(stream_state))
-      die("stream state is no longer good while waiting for stream to become ready -- the error message is \"%s\".",pa_strerror(pa_context_errno(context)));
+    if (!PA_STREAM_IS_GOOD(stream_state))
+      die("stream state is no longer good while waiting for stream to become ready -- the error "
+          "message is \"%s\".",
+          pa_strerror(pa_context_errno(context)));
     if (stream_state == PA_STREAM_READY)
       break;
     pa_threaded_mainloop_wait(mainloop);
index 24f37676dcb95901e739599e608ad96f03f7695e..2d1547c6609ac7dc01dfab72b865c64d559c27b3 100644 (file)
--- a/common.h
+++ b/common.h
@@ -82,7 +82,7 @@ enum sps_format_t {
 typedef struct {
   config_t *cfg;
   double airplay_volume; // stored here for reloading when necessary
-  char *appName; // normally the app is called shairport-syn, but it may be symlinked
+  char *appName;         // normally the app is called shairport-syn, but it may be symlinked
   char *password;
   char *service_name; // the name for the shairport service, e.g. "Shairport Sync Version %v running
                       // on host %h"
diff --git a/dacp.c b/dacp.c
index 727611464c7bcc5dcb985a8221f9b13d5709d8a2..4407a77f082c8e7ec4e5fbf4d575aab42052ae8d 100644 (file)
--- a/dacp.c
+++ b/dacp.c
@@ -39,6 +39,8 @@
 #include <time.h>
 #include <unistd.h>
 
+#include "tinyhttp/http.h"
+
 typedef struct {
   uint16_t port;
   short connection_family;          // AF_INET6 or AF_INET
@@ -50,12 +52,58 @@ typedef struct {
 pthread_t dacp_monitor_thread;
 dacp_server_record dacp_server;
 
+// HTTP Response data/funcs (See the tinyhttp example.cpp file for more on this.)
+struct HttpResponse {
+  void *body;            // this will be a malloc'ed pointer
+  ssize_t malloced_size; // this will be its allocated size
+  ssize_t size;          // the current size of the content
+  int code;
+};
+
+static void *response_realloc(void *opaque, void *ptr, int size) { return realloc(ptr, size); }
+
+static void response_body(void *opaque, const char *data, int size) {
+  struct HttpResponse *response = (struct HttpResponse *)opaque;
+
+  ssize_t space_available = response->malloced_size - response->size;
+  if (space_available < size) {
+    printf("Getting more space for the response -- need %d bytes but only %d bytes left.\n", size,
+           size - space_available);
+    ssize_t size_requested = size - space_available + response->malloced_size + 16384;
+    void *t = realloc(response->body, size_requested);
+    response->malloced_size = size_requested;
+    if (t)
+      response->body = t;
+    else {
+      printf("Can't allocate any more space for parser.\n");
+      exit(-1);
+    }
+  }
+  memcpy(response->body + response->size, data, size);
+  response->size += size;
+}
+
+static void response_header(void *opaque, const char *ckey, int nkey, const char *cvalue,
+                            int nvalue) { /* example doesn't care about headers */
+}
+
+static void response_code(void *opaque, int code) {
+  struct HttpResponse *response = (struct HttpResponse *)opaque;
+  response->code = code;
+}
+
+static const struct http_funcs responseFuncs = {
+    response_realloc, response_body, response_header, response_code,
+};
+
 static pthread_mutex_t dacp_conversation_lock = PTHREAD_MUTEX_INITIALIZER;
 static pthread_mutex_t dacp_server_information_lock = PTHREAD_MUTEX_INITIALIZER;
 static pthread_cond_t dacp_server_information_cv = PTHREAD_COND_INITIALIZER;
 
-ssize_t dacp_send_command(const char *command, char *response, size_t max_response_length) {
-  ssize_t reply_size = -1;
+int dacp_send_command(const char *command, char **body, size_t *bodysize) {
+
+  // will malloc space for the body or set it to NULL -- the caller should free it.
+
   // try to do this transaction on the DACP server, but don't wait for more than 20 ms to be allowed
   // to do it.
   struct timespec mutex_wait_time;
@@ -65,20 +113,19 @@ ssize_t dacp_send_command(const char *command, char *response, size_t max_respon
   struct addrinfo hints, *res;
   int sockfd;
 
-  char message[20000], server_reply[2000], portstring[10], server[256];
-  memset(&message, 0, sizeof(message));
-  if ((response) && (max_response_length))
-    memset(response, 0, max_response_length);
-  else
-    memset(&server_reply, 0, sizeof(server_reply));
-  memset(&portstring, 0, sizeof(portstring));
+  struct HttpResponse response;
+  response.body = NULL;
+  response.malloced_size = 0;
+  response.size = 0;
+  response.code = 400; // client error
 
+  char portstring[10], server[256], message[1024];
+  memset(&portstring, 0, sizeof(portstring));
   if (dacp_server.connection_family == AF_INET6) {
     sprintf(server, "%s%%%u", dacp_server.ip_string, dacp_server.scope_id);
   } else {
     strcpy(server, dacp_server.ip_string);
   }
-
   sprintf(portstring, "%u", dacp_server.port);
 
   // first, load up address structs with getaddrinfo():
@@ -115,28 +162,74 @@ ssize_t dacp_send_command(const char *command, char *response, size_t max_respon
 
         // Send command
 
-        if (send(sockfd, message, strlen(message), 0) < 0) {
+        if (send(sockfd, message, strlen(message), 0) != strlen(message)) {
           debug(1, "Send failed");
-        }
+        } else {
 
-        // Receive a reply from the server
-        if ((response) && (max_response_length))
-          reply_size = recv(sockfd, response, max_response_length, 0);
-        else
-          reply_size = recv(sockfd, server_reply, sizeof(server_reply), 0);
-        if (reply_size < 0) {
-          debug(1, "recv failed");
+          response.body = malloc(2048); // it can resize this if necessary
+          response.malloced_size = 2048;
+
+          struct http_roundtripper rt;
+          http_init(&rt, responseFuncs, &response);
+
+          int needmore = 1;
+          int looperror = 0;
+          char buffer[1024];
+          while (needmore && !looperror) {
+            const char *data = buffer;
+            int ndata = recv(sockfd, buffer, sizeof(buffer), 0);
+            if (ndata <= 0) {
+              debug(1, "Error receiving data.");
+              free(response.body);
+              response.body = NULL;
+              response.malloced_size = 0;
+              response.size = 0;
+              looperror = 1;
+            }
+
+            while (needmore && ndata && !looperror) {
+              int read;
+              needmore = http_data(&rt, data, ndata, &read);
+              ndata -= read;
+              data += read;
+            }
+          }
+
+          if (http_iserror(&rt)) {
+            debug(1, "Error parsing data.");
+            free(response.body);
+            response.body = NULL;
+            response.malloced_size = 0;
+            response.size = 0;
+          }
+
+          http_free(&rt);
+          close(sockfd);
         }
-        close(sockfd);
       }
     }
     pthread_mutex_unlock(&dacp_conversation_lock);
   } else {
-    debug(1, "Could not acquire a lock on the dacp transmit/receive section. Possible timeout?");
+    // debug(1, "Could not acquire a lock on the dacp transmit/receive section. Possible timeout?");
+    response.code = 408; // not strictly correct
   }
-  return reply_size;
+  *body = response.body;
+  *bodysize = response.size;
+  return response.code;
 }
 
+int send_simple_dacp_command(const char *command) {
+  int reply = 0;
+  char *server_reply = NULL;
+  debug(1, "Sending command \"%s\".", command);
+  ssize_t reply_size = 0;
+  reply = dacp_send_command(command, &server_reply, &reply_size);
+  if (server_reply) {
+    free(server_reply);
+    server_reply = NULL;
+  }
+  return reply;
+}
 
 // this will be running on the thread of its caller, not of the conversation thread...
 void set_dacp_server_information(rtsp_conn_info *conn) { // tell the DACP conversation thread that
@@ -155,9 +248,10 @@ void set_dacp_server_information(rtsp_conn_info *conn) { // tell the DACP conver
 
 void *dacp_monitor_thread_code(void *na) {
   int scan_index = 0;
-  char server_reply[2000];
+  char server_reply[10000];
   debug(1, "DACP monitor thread started.");
   // wait until we get a valid port number to begin monitoring it
+  int32_t revision_number = 1;
   while (1) {
     pthread_mutex_lock(&dacp_server_information_lock);
     while (dacp_server.port == 0) {
@@ -165,37 +259,114 @@ void *dacp_monitor_thread_code(void *na) {
       pthread_cond_wait(&dacp_server_information_cv, &dacp_server_information_lock);
     }
     pthread_mutex_unlock(&dacp_server_information_lock);
-    //debug(1, "DACP Server ID \"%u\" at \"%s:%u\", scan %d.", dacp_server.active_remote_id,
+    // debug(1, "DACP Server ID \"%u\" at \"%s:%u\", scan %d.", dacp_server.active_remote_id,
     //      dacp_server.ip_string, dacp_server.port, scan_index++);
+    ssize_t le;
+    char *response = NULL;
+    int32_t item_size;
+    char command[1024] = "";
+    snprintf(command, sizeof(command) - 1, "playstatusupdate?revision-number=%d", revision_number);
+    // debug(1,"Command: \"%s\"",command);
+    int result = dacp_send_command(command, &response, &le);
+    if (result == 200) {
+      char *sp = response;
+      if (le >= 0) {
+        // here start looking for the contents of the status update
+        if (dacp_tlv_crawl(&sp, &item_size) == 'cmst') { // status
+          sp -= item_size; // drop down into the array -- don't skip over it
+          le -= 8;
+          char typestring[5];
+          while (le >= 8) {
+            uint32_t type = dacp_tlv_crawl(&sp, &item_size);
+
+            *(uint32_t *)typestring = htonl(type);
+            typestring[4] = 0;
+            printf("\"%s\" %4d", typestring, item_size);
+
+            le -= item_size + 8;
+            char *t;
+            char u;
+            char *st;
+            int32_t r;
+            uint64_t s, v;
+            int i;
+            switch (type) {
+
+            case 'mstt':
+            case 'cant':
+            case 'cast':
+            case 'cmmk':
+            case 'caas':
+            case 'caar':
+            case 'astm':
+              t = sp - item_size;
+              r = ntohl(*(int32_t *)(t));
+              printf("    %d", r);
+              printf("    (0x");
+              t = sp - item_size;
+              for (i = 0; i < item_size; i++) {
+                printf("%02x", *t);
+                t++;
+              }
+              printf(")");
+              break;
+            case 'cmsr':
+              t = sp - item_size;
+              revision_number = ntohl(*(int32_t *)(t));
+              printf("    Serial Number: %d", revision_number);
+              break;
+            case 'cann':
+            case 'cana':
+            case 'canl':
+            case 'cang':
+              t = sp - item_size;
+              st = strndup(t, item_size);
+              printf("    \"%s\"", st);
+              free(st);
+              break;
+            case 'asai':
+              t = sp - item_size;
+              s = ntohl(*(uint32_t *)(t));
+              s = s << 32;
+              t += 4;
+              v = (ntohl(*(uint32_t *)(t))) & 0xffffffff;
+              s += v;
+              printf("    %llu", s);
+              printf("    (0x");
+              t = sp - item_size;
+              for (i = 0; i < item_size; i++) {
+                printf("%02x", *t);
+                t++;
+              }
+              printf(")");
+              break;
+            default:
+              printf("    0x");
+              t = sp - item_size;
+              for (i = 0; i < item_size; i++) {
+                printf("%02x", *t);
+                t++;
+              }
+              break;
+            }
+            printf("\n");
+          }
+        } else {
+          printf("Status Update not found.\n");
+        }
 
-    ssize_t reply_size =
-        dacp_send_command("playstatusupdate", server_reply, sizeof(server_reply));
-    if (reply_size >= 0) {
-      // not interested in the response.
-      if (strstr(server_reply, "HTTP/1.1 204") == server_reply) {
-        debug(1,"Client response is No Content");        
-      } else if (strstr(server_reply, "HTTP/1.1 501") == server_reply) {
-        debug(1,"Client response is \"Not Implemented\". DACP Server has disconnnected.");
-        dacp_server.port = 0; // stop scanning until the next time it's used
-/*
       } else {
-        debug(1,
-              "Client request to server responded with %d characters starting with this response:",
-              strlen(server_reply));
-        int i;
-        for (i = 0; i < reply_size; i++)
-          if (server_reply[i] < ' ')
-            debug(1, "%d  %02x", i, server_reply[i]);
-          else
-            debug(1, "%d  %02x  '%c'", i, server_reply[i], server_reply[i]);
-        // sprintf((char *)message + 2 * i, "%02x", server_reply[i]);
-        // debug(1,"Content is \"%s\".",message);
-*/
+        debug(1, "Can't find any content in playerstatusupdate request");
       }
     } else {
-        debug(1, "Error at rtp_send_client_command");
+      if (result != 403)
+        debug(1, "Unexpected response %d to playerstatusupdate request", result);
     }
-    sleep(3);
+    if (response) {
+      free(response);
+      response = NULL;
+    };
+    sleep(1);
   }
   debug(1, "DACP monitor thread exiting.");
   pthread_exit(NULL);
@@ -218,278 +389,145 @@ uint32_t dacp_tlv_crawl(char **p, int32_t *length) {
   return type;
 }
 
-ssize_t dacp_send_client_command(rtsp_conn_info *conn, const char *command, char *response,
-                                 size_t max_response_length) {
-  ssize_t reply_size = -1;
-  // try to do this transaction on the DACP server, but don't wait for more than 20 ms to be allowed
-  // to do it.
-  struct timespec mutex_wait_time;
-  mutex_wait_time.tv_sec = 0;
-  mutex_wait_time.tv_nsec = 20000000; // 20 ms
-
-  if (conn->rtp_running) {
-    if (conn->dacp_port == 0) {
-      debug(1, "Can't send a remote request: no valid active remote.");
-    } else {
-
-      struct addrinfo hints, *res;
-      int sockfd;
-
-      char message[20000], server_reply[2000], portstring[10], server[256];
-      memset(&message, 0, sizeof(message));
-      if ((response) && (max_response_length))
-        memset(response, 0, max_response_length);
-      else
-        memset(&server_reply, 0, sizeof(server_reply));
-      memset(&portstring, 0, sizeof(portstring));
-
-      if (conn->connection_ip_family == AF_INET6) {
-        sprintf(server, "%s%%%u", conn->client_ip_string, conn->self_scope_id);
-      } else {
-        strcpy(server, conn->client_ip_string);
-      }
-
-      sprintf(portstring, "%u", conn->dacp_port);
-
-      // first, load up address structs with getaddrinfo():
-
-      memset(&hints, 0, sizeof(hints));
-      hints.ai_family = AF_UNSPEC;
-      hints.ai_socktype = SOCK_STREAM;
-
-      getaddrinfo(server, portstring, &hints, &res);
-
-      // only do this one at a time -- not sure it is necessary, but better safe than sorry
-
-      int mutex_reply = pthread_mutex_timedlock(&dacp_conversation_lock, &mutex_wait_time);
-      if (mutex_reply == 0) {
-
-        // make a socket:
-
-        sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
-
-        if (sockfd == -1) {
-          debug(1, "Could not create socket");
-        } else {
-
-          // connect!
-
-          if (connect(sockfd, res->ai_addr, res->ai_addrlen) < 0) {
-            debug(1, "connect failed. Error");
-          } else {
-
-            sprintf(message,
-                    "GET /ctrl-int/1/%s HTTP/1.1\r\nHost: %s:%u\r\nActive-Remote: %u\r\n\r\n",
-                    command, conn->client_ip_string, conn->dacp_port, conn->dacp_active_remote);
-
-            // Send command
-
-            if (send(sockfd, message, strlen(message), 0) < 0) {
-              debug(1, "Send failed");
-            }
-
-            // Receive a reply from the server
-            if ((response) && (max_response_length))
-              reply_size = recv(sockfd, response, max_response_length, 0);
-            else
-              reply_size = recv(sockfd, server_reply, sizeof(server_reply), 0);
-            if (reply_size < 0) {
-              debug(1, "recv failed");
-            }
-            close(sockfd);
-          }
-        }
-        pthread_mutex_unlock(&dacp_conversation_lock);
-      } else {
-        debug(1,
-              "Could not acquire a lock on the dacp transmit/receive section. Possible timeout?");
-      }
-    }
-  } else {
-    debug(1, "Request to pause non-existent play stream -- ignored.");
-  }
-  return reply_size;
-}
-
 int32_t dacp_get_client_volume(rtsp_conn_info *conn) {
-  char server_reply[2000];
+  char *server_reply = NULL;
   int32_t overall_volume = -1;
-
-  ssize_t reply_size = dacp_send_client_command(conn, "getproperty?properties=dmcp.volume",
-                                                server_reply, sizeof(server_reply));
-  if (reply_size >= 0) {
-    if (strstr(server_reply, "HTTP/1.1 200") == server_reply) { // if we get an okay
-      char *sp = strstr(server_reply, "Content-Length: ");
-      if (sp) { // there is something there
-        sp += strlen("Content-Length: ");
-        int le = atoi(sp);
-        if (le == 32) {
-          sp = strstr(sp, "\r") + 4 + 28;
-          uint32_t *np = (uint32_t *)sp;
-          overall_volume = ntohl(*np);
-          // debug(1,"Overall Volume is %d.",overall_volume);
-        } else {
-          debug(1, "Can't find the volume tag");
-        }
-      } else {
-        debug(1, "Can't find any content in volume control request");
-      }
-    } else {
-      debug(1, "Unexpected response to dacp volume control request");
-    }
+  ssize_t reply_size;
+  int response =
+      dacp_send_command("getproperty?properties=dmcp.volume", &server_reply, &reply_size);
+  if (response == 200) { // if we get an okay
+    uint32_t *np = (uint32_t *)server_reply;
+    overall_volume = ntohl(*np);
+    // debug(1,"Overall Volume is %d.",overall_volume);
+    free(server_reply);
   } else {
-    debug(1, "Error asking for dacp volume.");
+    debug(1, "Unexpected response %d to dacp volume control request", response);
   }
   return overall_volume;
 }
 
 int dacp_set_include_speaker_volume(rtsp_conn_info *conn, int64_t machine_number, int32_t vo) {
-  char server_reply[2000];
-  int reply = -1; // will bve fixed if there is no problem
   char message[1000];
   memset(message, 0, sizeof(message));
   sprintf(message, "setproperty?include-speaker-id=%ld&dmcp.volume=%d", machine_number, vo);
   // debug(1,"sending \"%s\"",message);
-  ssize_t reply_size = dacp_send_client_command(conn, message, server_reply, sizeof(server_reply));
-  if (reply_size >= 0) {
-    if (strstr(server_reply, "HTTP/1.1 204") == server_reply) {
-      // debug(1,"dacp_set_include_speaker_volume successful.");
-      reply = 0;
-    }
-  } else {
-    debug(1, "dacp_set_include_speaker_volume unsuccessful.");
-  }
-  return reply;
+  return send_simple_dacp_command(message);
+  // should return 204
 }
 
 int dacp_set_speaker_volume(rtsp_conn_info *conn, int64_t machine_number, int32_t vo) {
-  char server_reply[2000];
-  int reply = -1; // will bve fixed if there is no problem
   char message[1000];
   memset(message, 0, sizeof(message));
   sprintf(message, "setproperty?speaker-id=%ld&dmcp.volume=%d", machine_number, vo);
   // debug(1,"sending \"%s\"",message);
-  ssize_t reply_size = dacp_send_client_command(conn, message, server_reply, sizeof(server_reply));
-  if (reply_size >= 0) {
-    if (strstr(server_reply, "HTTP/1.1 204") == server_reply) {
-      // debug(1,"dacp_set_speaker_volume successful.");
-      reply = 0;
-    }
-  } else {
-    debug(1, "dacp_set_speaker_volume unsuccessful.");
-  }
-  return reply;
+  return send_simple_dacp_command(message);
+  // should return 204
 }
 
 int dacp_get_speaker_list(rtsp_conn_info *conn, dacp_spkr_stuff *speaker_info,
                           int max_size_of_array) {
-  char server_reply[2000];
+  char *server_reply = NULL;
   int speaker_index = -1; // will be incremented before use
   int reply = -1;         // will bve fixed if there is no problem
-  ssize_t reply_size =
-      dacp_send_client_command(conn, "getspeakers", server_reply, sizeof(server_reply));
-  if (reply_size >= 0) {
-    if (strstr(server_reply, "HTTP/1.1 200") == server_reply) { // if we get an okay
-      char *sp = strstr(server_reply, "Content-Length: ");
-      if (sp) { // there is something there
-        sp += strlen("Content-Length: ");
-        int32_t le = atoi(sp);
-        int32_t item_size = 0;
-        sp = strstr(sp, "\r") + 4;
-        if (dacp_tlv_crawl(&sp, &item_size) == 'casp') {
-          //          debug(1,"Speakers:",item_size);
-          sp -= item_size; // drop down into the array -- don't skip over it
-          le -= 8;
-          while (le >= 8) {
-            uint32_t type = dacp_tlv_crawl(&sp, &item_size);
-            if (type == 'mdcl') { // drop down into the dictionary -- don't skip over it
-              // debug(1,">>>> Dictionary:");
-              sp -= item_size;
-              le -= 8;
-              speaker_index++;
-              if (speaker_index == max_size_of_array)
-                return -1; // too many speakers
-              speaker_info[speaker_index].active = 0;
-              speaker_info[speaker_index].speaker_number = 0;
-              speaker_info[speaker_index].volume = 0;
-              speaker_info[speaker_index].name = NULL;
-            } else {
-              le -= item_size + 8;
-              char *t;
-              char u;
-              int32_t r;
-              int64_t s, v;
-              switch (type) {
-              case 'minm':
-                t = sp - item_size;
-                speaker_info[speaker_index].name = strndup(t, item_size);
-                // debug(1," \"%s\"",speaker_info[speaker_index].name);
-                break;
-              /*
-                              case 'cads':
-                                t = sp-item_size;
-                                r = ntohl(*(int32_t*)(t));
-                                //debug(1,"CADS: \"%d\".",r);
-                                break;
-              */
-              case 'cmvo':
-                t = sp - item_size;
-                r = ntohl(*(int32_t *)(t));
-                speaker_info[speaker_index].volume = r;
-                // debug(1,"Volume: \"%d\".",r);
-                break;
-              case 'msma':
-                t = sp - item_size;
-                s = ntohl(*(uint32_t *)(t));
-                s = s << 32;
-                t += 4;
-                v = (ntohl(*(uint32_t *)(t))) & 0xffffffff;
-                s += v;
-                speaker_info[speaker_index].speaker_number = s;
-                // debug(1,"Speaker machine number: %ld",s);
-                break;
-
-              case 'caia':
-                speaker_info[speaker_index].active = 1;
-                break;
-              /*
-                              case 'caip':
-                              case 'cavd':
-                              case 'caiv':
-                                t = sp-item_size;
-                                u = *t;
-                                //debug(1,"Value: \"%d\".",u);
-                                break;
-              */
-              default:
-                break;
-              }
+  ssize_t le;
+
+  int response = dacp_send_command("getspeakers", &server_reply, &le);
+  if (response == 200) {
+    char *sp = server_reply;
+    int32_t item_size;
+    if (le >= 8) {
+      if (dacp_tlv_crawl(&sp, &item_size) == 'casp') {
+        //          debug(1,"Speakers:",item_size);
+        sp -= item_size; // drop down into the array -- don't skip over it
+        le -= 8;
+        while (le >= 8) {
+          uint32_t type = dacp_tlv_crawl(&sp, &item_size);
+          if (type == 'mdcl') { // drop down into the dictionary -- don't skip over it
+            // debug(1,">>>> Dictionary:");
+            sp -= item_size;
+            le -= 8;
+            speaker_index++;
+            if (speaker_index == max_size_of_array)
+              return -1; // too many speakers
+            speaker_info[speaker_index].active = 0;
+            speaker_info[speaker_index].speaker_number = 0;
+            speaker_info[speaker_index].volume = 0;
+            speaker_info[speaker_index].name = NULL;
+          } else {
+            le -= item_size + 8;
+            char *t;
+            char u;
+            int32_t r;
+            int64_t s, v;
+            switch (type) {
+            case 'minm':
+              t = sp - item_size;
+              speaker_info[speaker_index].name = strndup(t, item_size);
+              // debug(1," \"%s\"",speaker_info[speaker_index].name);
+              break;
+            /*
+                            case 'cads':
+                              t = sp-item_size;
+                              r = ntohl(*(int32_t*)(t));
+                              //debug(1,"CADS: \"%d\".",r);
+                              break;
+            */
+            case 'cmvo':
+              t = sp - item_size;
+              r = ntohl(*(int32_t *)(t));
+              speaker_info[speaker_index].volume = r;
+              // debug(1,"Volume: \"%d\".",r);
+              break;
+            case 'msma':
+              t = sp - item_size;
+              s = ntohl(*(uint32_t *)(t));
+              s = s << 32;
+              t += 4;
+              v = (ntohl(*(uint32_t *)(t))) & 0xffffffff;
+              s += v;
+              speaker_info[speaker_index].speaker_number = s;
+              // debug(1,"Speaker machine number: %ld",s);
+              break;
+
+            case 'caia':
+              speaker_info[speaker_index].active = 1;
+              break;
+            /*
+                            case 'caip':
+                            case 'cavd':
+                            case 'caiv':
+                              t = sp-item_size;
+                              u = *t;
+                              //debug(1,"Value: \"%d\".",u);
+                              break;
+            */
+            default:
+              break;
             }
           }
-          // debug(1,"Total of %d speakers found. Here are the active ones:",speaker_index+1);
-          reply = speaker_index + 1; // number of speaker entries in the array
-        } else {
-          debug(1, "Speaker array not found.");
         }
-        /*
-                int i;
-                for (i=0;i<le;i++) {
-                  if (*sp < ' ')
-                    debug(1,"%d  %02x", i, *sp);
-                  else
-                    debug(1,"%d  %02x  '%c'", i, *sp,*sp);
-                  sp++;
-                }
-        */
-
+        // debug(1,"Total of %d speakers found. Here are the active ones:",speaker_index+1);
+        reply = speaker_index + 1; // number of speaker entries in the array
       } else {
-        debug(1, "Can't find any content in dacp speakers request");
+        debug(1, "Speaker array not found.");
       }
+      /*
+              int i;
+              for (i=0;i<le;i++) {
+                if (*sp < ' ')
+                  debug(1,"%d  %02x", i, *sp);
+                else
+                  debug(1,"%d  %02x  '%c'", i, *sp,*sp);
+                sp++;
+              }
+      */
     } else {
-      debug(1, "Unexpected response to dacp speakers request");
+      debug(1, "Can't find any content in dacp speakers request");
     }
+    free(server_reply);
+    server_reply = NULL;
   } else {
-    debug(1, "Error asking for dacp speakers.");
+    debug(1, "Unexpected response %d to dacp speakers request", response);
   }
   return reply;
 }
diff --git a/dacp.h b/dacp.h
index 493a6932a58a383875fa07588ddbf166727a9fd8..c3d36b7d42f318fe4d35feff34997681236e1a31 100644 (file)
--- a/dacp.h
+++ b/dacp.h
@@ -1,15 +1,14 @@
 #pragma once
 #include "common.h"
 #include "config.h"
-#include <sys/socket.h>
 #include <pthread.h>
+#include <sys/socket.h>
 
 #include "player.h"
 
 static pthread_mutex_t dacp_server_information_lock;
 static pthread_cond_t dacp_server_information_cv;
 
-
 typedef struct dacp_speaker_stuff {
   int64_t speaker_number;
   int active;
@@ -22,12 +21,13 @@ void dacp_monitor_start();
 uint32_t dacp_tlv_crawl(
     char **p,
     int32_t *length); // return the code of the next TLV entity and advance the pointer beyond it.
-ssize_t dacp_send_client_command(rtsp_conn_info *conn, const char *command, char *response,
-                                 size_t max_response_length);
+
 int32_t dacp_get_client_volume(rtsp_conn_info *conn); // return the overall volume from the client
 int dacp_set_include_speaker_volume(rtsp_conn_info *conn, int64_t machine_number, int32_t vo);
 int dacp_set_speaker_volume(rtsp_conn_info *conn, int64_t machine_number, int32_t vo);
 int dacp_get_speaker_list(rtsp_conn_info *conn, dacp_spkr_stuff *speaker_array,
                           int max_size_of_array);
-void set_dacp_server_information(rtsp_conn_info* conn); // tell the DACP conversation thread that the dacp server information has been set or changed
-
+void set_dacp_server_information(rtsp_conn_info *conn); // tell the DACP conversation thread that
+                                                        // the dacp server information has been set
+                                                        // or changed
+int send_simple_dacp_command(const char *command);
index 6d1a72c814778ac9a4b916e42346e48678b7590a..0cc7d1cefc210b54ecd7be7b0e73f219c2e2ae5c 100644 (file)
@@ -142,37 +142,7 @@ gboolean notify_volume_callback(ShairportSync *skeleton, gpointer user_data) {
 static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInvocation *invocation,
                                          const gchar *command, gpointer user_data) {
   debug(1, "RemoteCommand with command \"%s\".", command);
-  if (playing_conn) {
-    char server_reply[2000];
-    ssize_t reply_size =
-        dacp_send_client_command(playing_conn, command, server_reply, sizeof(server_reply));
-    if (reply_size >= 0) {
-      // not interested in the response.
-      //      if (strstr(server_reply, "HTTP/1.1 204") == server_reply) {
-      //        debug(1,"Client response is No Content");
-      //      } else if (strstr(server_reply, "HTTP/1.1 200 OK") != server_reply) {
-      //        debug("Client response is OK, with content");
-      //      } else {
-
-      if (strstr(server_reply, "HTTP/1.1 204") != server_reply) {
-        debug(1,
-              "Client request to server responded with %d characters starting with this response:",
-              strlen(server_reply));
-        int i;
-        for (i = 0; i < reply_size; i++)
-          if (server_reply[i] < ' ')
-            debug(1, "%d  %02x", i, server_reply[i]);
-          else
-            debug(1, "%d  %02x  '%c'", i, server_reply[i], server_reply[i]);
-        // sprintf((char *)message + 2 * i, "%02x", server_reply[i]);
-        // debug(1,"Content is \"%s\".",message);
-      }
-    } else {
-      debug(1, "Error at rtp_send_client_command");
-    }
-  } else {
-    debug(1, "no thread playing -- RemoteCommand ignored.");
-  }
+  send_simple_dacp_command(const char *command);
   shairport_sync_complete_remote_command(skeleton, invocation);
   return TRUE;
 }
index 5fe42b41e980f1f8628c6a3bb64242dfaa5433fa..e705144cf08794497fdea322284325900175e0b1 100644 (file)
@@ -33,5 +33,4 @@
 #define SAFAMILY sa_family
 #endif
 
-
 #endif // _DEFINITIONS_H
diff --git a/mdns.h b/mdns.h
index a55eea98e64c27c1e22ef8d4ac3c997663c272c0..b720c6a9e69947e3f2d7ac6099ac48af0d1b120b 100644 (file)
--- a/mdns.h
+++ b/mdns.h
@@ -2,8 +2,8 @@
 #define _MDNS_H
 
 #include "config.h"
-#include <stdint.h>
 #include <player.h>
+#include <stdint.h>
 
 extern int mdns_pid;
 
index 5a957a5721a334d406f9dcda4122f0182dbb579b..d3927059fc5a52a39825b09d8b799eb7a83f4729 100644 (file)
@@ -69,7 +69,7 @@ static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIn
                              const char *host_name, const AvahiAddress *address, uint16_t port,
                              AvahiStringList *txt, AvahiLookupResultFlags flags, void *userdata) {
   assert(r);
-  
+
   rtsp_conn_info *conn = (rtsp_conn_info *)userdata;
   dacp_browser_struct *dbs = (dacp_browser_struct *)conn->mdns_private_pointer;
 
@@ -141,8 +141,9 @@ static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, Avah
         if (conn->dacp_id != 0) {
           debug(1, "Client's DACP status withdrawn.");
           conn->dacp_port = 0;
-#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)          
-          set_dacp_server_information(conn); // this will have the effect of telling the scanner that the DACP server is no longer working
+#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
+          set_dacp_server_information(conn); // this will have the effect of telling the scanner
+                                             // that the DACP server is no longer working
 #endif
         }
       }
@@ -385,11 +386,10 @@ static void avahi_unregister(void) {
 int avahi_dacp_monitor(rtsp_conn_info *conn) {
 
   dacp_browser_struct *dbs = (dacp_browser_struct *)malloc(sizeof(dacp_browser_struct));
-  
 
   if (dbs == NULL)
     die("can not allocate a dacp_browser_struct.");
-    
+
   conn->mdns_private_pointer = (void *)dbs;
 
   // create the threaded poll code
index e2be908a26fbf12ab8097bff976caddcd13b2c44..4b1a0cffd633f218e7a3599fdb01a42920b024fa 100644 (file)
 #include "dacp.h"
 
 #include "mpris-service.h"
-int send_simple_dacp_command(const char *command) {
-  int reply = 0;
-  if (playing_conn) {
-    char server_reply[2000];
-    debug(1, "Sending command \"%s\".", command);
-    ssize_t reply_size =
-        dacp_send_client_command(playing_conn, command, server_reply, sizeof(server_reply));
-    if (reply_size >= 0) {
-      // not interested in the response.
-      //      if (strstr(server_reply, "HTTP/1.1 204") == server_reply) {
-      //        debug(1,"Client response is No Content");
-      //      } else if (strstr(server_reply, "HTTP/1.1 200 OK") != server_reply) {
-      //        debug("Client response is OK, with content");
-      //      } else {
-
-      if (strstr(server_reply, "HTTP/1.1 204") != server_reply) {
-        debug(1,
-              "Client request to server responded with %d characters starting with this response:",
-              strlen(server_reply));
-        int i;
-        for (i = 0; i < reply_size; i++)
-          if (server_reply[i] < ' ')
-            debug(1, "%d  %02x", i, server_reply[i]);
-          else
-            debug(1, "%d  %02x  '%c'", i, server_reply[i], server_reply[i]);
-        // sprintf((char *)message + 2 * i, "%02x", server_reply[i]);
-        // debug(1,"Content is \"%s\".",message);
-      }
-    } else {
-      debug(1, "Error at rtp_send_client_command");
-      reply = -1;
-    }
-  } else {
-    debug(1, "no thread playing -- RemoteCommand ignored.");
-    reply = -1;
-  }
-  return reply;
-}
 
 static gboolean on_handle_next(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
                                gpointer user_data) {
@@ -59,7 +21,7 @@ static gboolean on_handle_next(MediaPlayer2Player *skeleton, GDBusMethodInvocati
 }
 
 static gboolean on_handle_previous(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
-                               gpointer user_data) {
+                                   gpointer user_data) {
   send_simple_dacp_command("previtem");
   media_player2_player_complete_previous(skeleton, invocation);
   return TRUE;
@@ -133,7 +95,8 @@ static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *nam
                    NULL);
   g_signal_connect(mprisPlayerPlayerSkeleton, "handle-stop", G_CALLBACK(on_handle_stop), NULL);
   g_signal_connect(mprisPlayerPlayerSkeleton, "handle-next", G_CALLBACK(on_handle_next), NULL);
-  g_signal_connect(mprisPlayerPlayerSkeleton, "handle-previous", G_CALLBACK(on_handle_previous), NULL);
+  g_signal_connect(mprisPlayerPlayerSkeleton, "handle-previous", G_CALLBACK(on_handle_previous),
+                   NULL);
 
   debug(1, "Shairport Sync D-BUS service started on interface \"%s\".", name);
 
index 8530f444615c1ede90a18900d665031c5136ccb5..d3be8577c1c0fce236b0eb4e5aea0af02f0e3167 100644 (file)
--- a/player.c
+++ b/player.c
@@ -1579,7 +1579,7 @@ static void *player_thread_func(void *arg) {
 
   // start an mdns/zeroconf thread to look for DACP messages containing our DACP_ID and getting the
   // port number
-  //mdns_dacp_monitor(conn->dacp_id, &conn->dacp_port, &conn->dacp_private);
+  // mdns_dacp_monitor(conn->dacp_id, &conn->dacp_port, &conn->dacp_private);
   mdns_dacp_monitor(conn);
 
   conn->framesProcessedInThisEpoch = 0;
@@ -1623,11 +1623,11 @@ static void *player_thread_func(void *arg) {
     }
   }
 
-       // set the default volume to whaterver it was before, as stored in the config airplay_volume
-       debug(1,"Set initial volume to %f.",config.airplay_volume);
-       
-       player_volume(config.airplay_volume,conn);
-       
+  // set the default volume to whaterver it was before, as stored in the config airplay_volume
+  debug(1, "Set initial volume to %f.", config.airplay_volume);
+
+  player_volume(config.airplay_volume, conn);
+
   uint64_t tens_of_seconds = 0;
   while (!conn->player_thread_please_stop) {
     abuf_t *inframe = buffer_get_frame(conn);
@@ -2193,8 +2193,8 @@ static void *player_thread_func(void *arg) {
 
   // stop watching for DACP port number stuff
   mdns_dacp_dont_monitor(conn); // begin looking out for information about the client
-                                               // as a remote control. Specifically we might need
-                                               // the port number
+                                // as a remote control. Specifically we might need
+                                // the port number
 
   if (config.output->stop)
     config.output->stop();
@@ -2468,8 +2468,8 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c
   }
 #endif
 
-       // here, store the volume for possible use in the future
-       config.airplay_volume = airplay_volume;
+  // here, store the volume for possible use in the future
+  config.airplay_volume = airplay_volume;
 }
 
 void player_volume(double airplay_volume, rtsp_conn_info *conn) {
index 3c9f43d0293c85b3e8f7c4d5b5926b26d29594b4..0f55729913c24b4d0e9e58760d0487596d894ae7 100644 (file)
--- a/player.h
+++ b/player.h
@@ -62,8 +62,9 @@ typedef struct {
 } stream_cfg;
 
 typedef struct {
-  int connection_number; // for debug ID purposes, nothing else...
-  int64_t staticLatencyCorrection; // it seems iTunes needs some offset before it's more or less right. Odd.
+  int connection_number;           // for debug ID purposes, nothing else...
+  int64_t staticLatencyCorrection; // it seems iTunes needs some offset before it's more or less
+                                   // right. Odd.
 #if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
   enum session_status_type play_state;
 #endif
diff --git a/rtp.c b/rtp.c
index 22646d793386a1b608ff724e51eab4525f371699..ef16f94ad2d805e97ca3e8edd7457f6c4516c053 100644 (file)
--- a/rtp.c
+++ b/rtp.c
@@ -227,10 +227,13 @@ void *rtp_control_receiver(void *arg) {
         sync_rtp_timestamp = monotonic_timestamp(ntohl(*((uint32_t *)&packet[16])), conn);
 
         if (config.use_negotiated_latencies) {
-          int64_t la = sync_rtp_timestamp - rtp_timestamp_less_latency + conn->staticLatencyCorrection;
+          int64_t la =
+              sync_rtp_timestamp - rtp_timestamp_less_latency + conn->staticLatencyCorrection;
           if (la != config.latency) {
             config.latency = la;
-            debug(1,"Using negotiated latency of %lld frames and a static latency correction of %lld",sync_rtp_timestamp - rtp_timestamp_less_latency,conn->staticLatencyCorrection);
+            //debug(1,
+            //      "Using negotiated latency of %lld frames and a static latency correction of %lld",
+            //      sync_rtp_timestamp - rtp_timestamp_less_latency, conn->staticLatencyCorrection);
           }
         }
 
diff --git a/rtsp.c b/rtsp.c
index 2f61393421423ab88b761a538e2111754b64fbde..7e816726712abd2d0f8d23cbae888ab6e10316cd 100644 (file)
--- a/rtsp.c
+++ b/rtsp.c
@@ -1,7 +1,7 @@
 /*
  * RTSP protocol handler. This file is part of Shairport Sync
  * Copyright (c) James Laird 2013
+
  * Modifications associated with audio synchronization, mutithreading and
  * metadata handling copyright (c) Mike Brady 2014-2017
  * All rights reserved.
@@ -373,22 +373,22 @@ static void debug_print_msg_headers(int level, rtsp_message *msg) {
 
 static void debug_print_msg_content(int level, rtsp_message *msg) {
   if (msg->contentlength) {
-    char *obf = malloc(msg->contentlength*2+1);
+    char *obf = malloc(msg->contentlength * 2 + 1);
     if (obf) {
-    char *obfp = obf;
-    int obfc;
-    for (obfc=0;obfc<msg->contentlength;obfc++) {
-      sprintf(obfp,"%02X",msg->content[obfc]);
-      obfp+=2;
-    };
-    *obfp=0;
-    debug(level,"Content (hex): \"%s\"",obf);
-    free(obf);
+      char *obfp = obf;
+      int obfc;
+      for (obfc = 0; obfc < msg->contentlength; obfc++) {
+        sprintf(obfp, "%02X", msg->content[obfc]);
+        obfp += 2;
+      };
+      *obfp = 0;
+      debug(level, "Content (hex): \"%s\"", obf);
+      free(obf);
     } else {
-      debug(level,"Can't allocate space for debug buffer");
+      debug(level, "Can't allocate space for debug buffer");
     }
   } else {
-    debug(level,"No content");
+    debug(level, "No content");
   }
 }
 
@@ -635,7 +635,7 @@ static void msg_write_response(int fd, rtsp_message *resp) {
   }
 
   // Here, if there's content, write the Content-Length header ...
-  
+
   if (resp->contentlength) {
     debug(1, "Responding with content of length %d", resp->contentlength);
     n = snprintf(p, pktfree, "Content-Length: %d\r\n", resp->contentlength);
@@ -646,14 +646,14 @@ static void msg_write_response(int fd, rtsp_message *resp) {
   }
 
   int ignore = write(fd, pkt, p - pkt);
-  
+
   // Here, if there's content, write it
   if (resp->contentlength) {
     debug(1, "Content is \"%s\"", resp->content);
-    ignore = write(fd,resp->content,resp->contentlength);
+    ignore = write(fd, resp->content, resp->contentlength);
   }
-  
-  ignore = write(fd,"\r\n",strlen("\r\n"));
+
+  ignore = write(fd, "\r\n", strlen("\r\n"));
 }
 
 static void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
@@ -739,7 +739,7 @@ static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *
 // debug(1,"RTSP Flush Requested: %u.",rtptime);
 #ifdef CONFIG_METADATA
   if (p)
-    send_metadata('ssnc', 'flsr', p+1, strlen(p+1), req, 1);
+    send_metadata('ssnc', 'flsr', p + 1, strlen(p + 1), req, 1);
   else
     send_metadata('ssnc', 'flsr', NULL, 0, NULL, 0);
 #endif
@@ -1353,19 +1353,20 @@ static void handle_set_parameter_metadata(rtsp_conn_info *conn, rtsp_message *re
 
 static void handle_get_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
   // debug(1, "Connection %d: GET_PARAMETER", conn->connection_number);
-  //debug_print_msg_headers(1,req);
-  //debug_print_msg_content(1,req);
-  
-  if ((req->content) && (req->contentlength==strlen("volume\r\n")) && strstr(req->content,"volume")==req->content) {
-    //debug(1,"Current volume sought");
+  // debug_print_msg_headers(1,req);
+  // debug_print_msg_content(1,req);
+
+  if ((req->content) && (req->contentlength == strlen("volume\r\n")) &&
+      strstr(req->content, "volume") == req->content) {
+    // debug(1,"Current volume sought");
     char *p = malloc(128); // will be automatically deallocated with the response is deleted
     if (p) {
-      resp->content=p;
-      resp->contentlength=sprintf(p, "\r\nvolume: %.6f\r\n", config.airplay_volume);
+      resp->content = p;
+      resp->contentlength = sprintf(p, "\r\nvolume: %.6f\r\n", config.airplay_volume);
     } else {
-      debug(1,"Couldn't allocate space for a response.");
+      debug(1, "Couldn't allocate space for a response.");
     }
-  }   
+  }
   resp->respcode = 200;
 }
 
index effc4cca91cc14f6325ed83c8f4cf37b71986574..6ba0fba5ffb2ca9b6d582bd04ffc3f0f684a549b 100644 (file)
@@ -440,8 +440,9 @@ int parse_options(int argc, char **argv) {
   config.resyncthreshold = 1.0 * fResyncthreshold / 44100;
   config.tolerance = 1.0 * fTolerance / 44100;
   config.audio_backend_silent_lead_in_time = -1.0; // flag to indicate it has not been set
-  config.airplay_volume = -18.0; // if no volume is ever set, default to initial default value if nothing else comes in first.
-  
+  config.airplay_volume = -18.0; // if no volume is ever set, default to initial default value if
+                                 // nothing else comes in first.
+
   config_setting_t *setting;
   const char *str = 0;
   int value = 0;
@@ -1540,7 +1541,7 @@ int main(int argc, char **argv) {
 #endif
 
 #if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
-  debug(1,"Requesting DACP Monitor");
+  debug(1, "Requesting DACP Monitor");
   dacp_monitor_start();
 #endif
 
diff --git a/tinyhttp/LICENSE b/tinyhttp/LICENSE
new file mode 100644 (file)
index 0000000..ba42059
--- /dev/null
@@ -0,0 +1,23 @@
+Copyright 2012 Matthew Endsley
+All rights reserved
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted providing that the following conditions 
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/tinyhttp/README.md b/tinyhttp/README.md
new file mode 100644 (file)
index 0000000..367138f
--- /dev/null
@@ -0,0 +1,28 @@
+tinyhttp
+========
+Tiny (as in minimal) implementation of an HTTP response parser. The parser
+itself is only dependent on:
+* `<ctype.h> - tolower`
+* `<string.h> - memcpy`
+
+For more information please see my blog post at: <http://mendsley.github.com/2012/12/19/tinyhttp.html>
+
+[![Build Status](https://api.travis-ci.org/mendsley/tinyhttp.png)](http://travis-ci.org/mendsley/tinyhttp)
+
+Contact
+-------
+[@MatthewEndsley](https://twitter.com/#!/MatthewEndsley)  
+<https://github.com/mendsley/tinyhttp>
+
+License
+-------
+Copyright 2012 Matthew Endsley
+
+This project is governed by the BSD 2-clause license. For details see the file
+titled LICENSE in the project root folder.
+
+Compiling
+---------
+`gcc -c *.c && g++ -std=c++0x example.cpp *.o -o example`
+
+`./example` will fetch the root of <http://nothings.org>
diff --git a/tinyhttp/chunk.c b/tinyhttp/chunk.c
new file mode 100644 (file)
index 0000000..3f7c889
--- /dev/null
@@ -0,0 +1,79 @@
+/*-
+ * Copyright 2012 Matthew Endsley
+ * All rights reserved
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted providing that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+static const unsigned char http_chunk_state[] = {
+/*     *    LF    CR    HEX */
+    0xC1, 0xC1, 0xC1,    1, /* s0: initial hex char */
+    0xC1, 0xC1,    2, 0x81, /* s1: additional hex chars, followed by CR */
+    0xC1, 0x83, 0xC1, 0xC1, /* s2: trailing LF */
+    0xC1, 0xC1,    4, 0xC1, /* s3: CR after chunk block */
+    0xC1, 0xC0, 0xC1, 0xC1, /* s4: LF after chunk block */
+};
+
+int http_parse_chunked(int* state, int *size, char ch)
+{
+    int newstate, code = 0;
+    switch (ch) {
+    case '\n': code = 1; break;
+    case '\r': code = 2; break;
+    case '0': case '1': case '2': case '3':
+    case '4': case '5': case '6': case '7':
+    case '8': case '9': case 'a': case 'b':
+    case 'c': case 'd': case 'e': case 'f':
+    case 'A': case 'B': case 'C': case 'D':
+    case 'E': case 'F': code = 3; break;
+    }
+
+    newstate = http_chunk_state[*state * 4 + code];
+    *state = (newstate & 0xF);
+
+    switch (newstate) {
+    case 0xC0:
+        return *size != 0;
+
+    case 0xC1: /* error */
+        *size = -1;
+        return 0;
+
+    case 0x01: /* initial char */
+        *size = 0;
+        /* fallthrough */
+    case 0x81: /* size char */
+        if (ch >= 'a')
+            *size = *size * 16 + (ch - 'a' + 10);
+        else if (ch >= 'A')
+            *size = *size * 16 + (ch - 'A' + 10);
+        else
+            *size = *size * 16 + (ch - '0');
+        break;
+
+    case 0x83:
+        return *size == 0;
+    }
+
+    return 1;
+}
+
diff --git a/tinyhttp/chunk.h b/tinyhttp/chunk.h
new file mode 100644 (file)
index 0000000..9736e83
--- /dev/null
@@ -0,0 +1,48 @@
+/*-
+ * Copyright 2012 Matthew Endsley
+ * All rights reserved
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted providing that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef HTTP_CHUNK_H
+#define HTTP_CHUNK_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/**
+ * Parses the size out of a chunk-encoded HTTP response. Returns non-zero if it
+ * needs more data. Retuns zero success or error. When error: size == -1 On
+ * success, size = size of following chunk data excluding trailing \r\n. User is
+ * expected to process or otherwise seek past chunk data up to the trailing
+ * \r\n. The state parameter is used for internal state and should be
+ * initialized to zero the first call.
+ */
+int http_parse_chunked(int* state, int *size, char ch);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
diff --git a/tinyhttp/example.cpp b/tinyhttp/example.cpp
new file mode 100644 (file)
index 0000000..4054f9a
--- /dev/null
@@ -0,0 +1,190 @@
+/*-
+ * Copyright 2012 Matthew Endsley
+ * All rights reserved
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted providing that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+/*
+Compiling example:
+$ g++ -o example example.cpp
+*/
+
+#include <string>
+#include <vector>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <unistd.h>
+
+#include "http.h"
+
+// directly embed the source here
+extern "C" {
+       #include "http.c"
+       #include "header.c"
+       #include "chunk.c"
+}
+
+// return a socket connected to a hostname, or -1
+int connectsocket(const char* host, int port)
+{
+
+    addrinfo* result = NULL;
+    sockaddr_in addr = {0};
+    int s;
+
+    if (getaddrinfo(host, NULL, NULL, &result))
+        goto error;
+
+    addr.sin_family = AF_INET;
+    addr.sin_port = htons(port);
+    addr.sin_addr.s_addr = INADDR_ANY;
+    for (addrinfo* ai = result; ai != NULL; ai = ai->ai_next) {
+        if (ai->ai_family != AF_INET)
+            continue;
+
+        const sockaddr_in *ai_in = (const sockaddr_in*)ai->ai_addr;
+        addr.sin_addr = ai_in->sin_addr;
+        break;
+    }
+
+    freeaddrinfo(result);
+
+    if (addr.sin_addr.s_addr == INADDR_ANY)
+        goto error;
+
+    s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+    if (s == -1)
+        goto error;
+
+    if (connect(s, (const sockaddr*)&addr, sizeof(addr)))
+        goto error;
+
+    return s;
+
+error:
+    if (s != -1)
+        close(s);
+    if (result)
+        freeaddrinfo(result);
+    return -1;
+}
+
+// Response data/funcs
+struct HttpResponse {
+       std::vector<char> body;
+    int code;
+};
+
+static void* response_realloc(void* opaque, void* ptr, int size)
+{
+    return realloc(ptr, size);
+}
+
+static void response_body(void* opaque, const char* data, int size)
+{
+    HttpResponse* response = (HttpResponse*)opaque;
+    response->body.insert(response->body.end(), data, data + size);
+}
+
+static void response_header(void* opaque, const char* ckey, int nkey, const char* cvalue, int nvalue)
+{ /* example doesn't care about headers */ }
+
+static void response_code(void* opaque, int code)
+{
+    HttpResponse* response = (HttpResponse*)opaque;
+    response->code = code;
+}
+
+static const http_funcs responseFuncs = {
+    response_realloc,
+    response_body,
+    response_header,
+    response_code,
+};
+
+int main()
+{
+
+    int conn = connectsocket("nothings.org", 80);
+    if (conn < 0) {
+        fprintf(stderr, "Failed to connect socket\n");
+        return -1;
+    }
+
+    const char request[] = "GET / HTTP/1.0\r\nContent-Length: 0\r\n\r\n";
+    int len = send(conn, request, sizeof(request) - 1, 0);
+    if (len != sizeof(request) - 1) {
+        fprintf(stderr, "Failed to send request\n");
+        close(conn);
+        return -1;
+    }
+
+    HttpResponse response;
+    response.code = 0;
+
+    http_roundtripper rt;
+    http_init(&rt, responseFuncs, &response);
+
+    bool needmore = true;
+    char buffer[1024];
+    while (needmore) {
+        const char* data = buffer;
+        int ndata = recv(conn, buffer, sizeof(buffer), 0);
+        if (ndata <= 0) {
+            fprintf(stderr, "Error receiving data\n");
+            http_free(&rt);
+            close(conn);
+            return -1;
+        }
+
+        while (needmore && ndata) {
+            int read;
+            needmore = http_data(&rt, data, ndata, &read);
+            ndata -= read;
+            data += read;
+        }
+    }
+
+    if (http_iserror(&rt)) {
+        fprintf(stderr, "Error parsing data\n");
+        http_free(&rt);
+        close(conn);
+        return -1;
+    }
+
+    http_free(&rt);
+    close(conn);
+
+    printf("Response: %d\n", response.code);
+    if (!response.body.empty()) {
+        printf("%s\n", &response.body[0]);
+    }
+
+    return 0;
+}
diff --git a/tinyhttp/header.c b/tinyhttp/header.c
new file mode 100644 (file)
index 0000000..cda9eb5
--- /dev/null
@@ -0,0 +1,72 @@
+/*-
+ * Copyright 2012 Matthew Endsley
+ * All rights reserved
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted providing that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "header.h"
+
+static unsigned char http_header_state[] = {
+/*     *    \t    \n   \r    ' '     ,     :   PAD */
+    0x80,    1, 0xC1, 0xC1,    1, 0x80, 0x80, 0xC1, /* state 0: HTTP version */
+    0x81,    2, 0xC1, 0xC1,    2,    1,    1, 0xC1, /* state 1: Response code */
+    0x82, 0x82,    4,    3, 0x82, 0x82, 0x82, 0xC1, /* state 2: Response reason */
+    0xC1, 0xC1,    4, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, /* state 3: HTTP version newline */
+    0x84, 0xC1, 0xC0,    5, 0xC1, 0xC1,    6, 0xC1, /* state 4: Start of header field */
+    0xC1, 0xC1, 0xC0, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, /* state 5: Last CR before end of header */
+    0x87,    6, 0xC1, 0xC1,    6, 0x87, 0x87, 0xC1, /* state 6: leading whitespace before header value */
+    0x87, 0x87, 0xC4,   10, 0x87, 0x88, 0x87, 0xC1, /* state 7: header field value */
+    0x87, 0x88,    6,    9, 0x88, 0x88, 0x87, 0xC1, /* state 8: Split value field value */
+    0xC1, 0xC1,    6, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, /* state 9: CR after split value field */
+    0xC1, 0xC1, 0xC4, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, /* state 10:CR after header value */
+};
+
+int http_parse_header_char(int* state, char ch)
+{
+    int newstate, code = 0;
+    switch (ch) {
+    case '\t': code = 1; break;
+    case '\n': code = 2; break;
+    case '\r': code = 3; break;
+    case  ' ': code = 4; break;
+    case  ',': code = 5; break;
+    case  ':': code = 6; break;
+    }
+
+    newstate = http_header_state[*state * 8 + code];
+    *state = (newstate & 0xF);
+
+    switch (newstate) {
+    case 0xC0: return http_header_status_done;
+    case 0xC1: return http_header_status_done;
+    case 0xC4: return http_header_status_store_keyvalue;
+    case 0x80: return http_header_status_version_character;
+    case 0x81: return http_header_status_code_character;
+    case 0x82: return http_header_status_status_character;
+    case 0x84: return http_header_status_key_character;
+    case 0x87: return http_header_status_value_character;
+    case 0x88: return http_header_status_value_character;
+    }
+
+    return http_header_status_continue;
+}
diff --git a/tinyhttp/header.h b/tinyhttp/header.h
new file mode 100644 (file)
index 0000000..7f8ee6a
--- /dev/null
@@ -0,0 +1,61 @@
+/*-
+ * Copyright 2012 Matthew Endsley
+ * All rights reserved
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted providing that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef HTTP_HEADER_H
+#define HTTP_HEADER_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+enum http_header_status
+{
+    http_header_status_done,
+    http_header_status_continue,
+    http_header_status_version_character,
+    http_header_status_code_character,
+    http_header_status_status_character,
+    http_header_status_key_character,
+    http_header_status_value_character,
+    http_header_status_store_keyvalue
+};
+
+/**
+ * Parses a single character of an HTTP header stream. The state parameter is
+ * used as internal state and should be initialized to zero for the first call.
+ * Return value is a value from the http_header_status enuemeration specifying
+ * the semantics of the character. If an error is encountered,
+ * http_header_status_done will be returned with a non-zero state parameter. On
+ * success http_header_status_done is returned with the state parameter set to
+ * zero.
+ */
+int http_parse_header_char(int* state, char ch);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
diff --git a/tinyhttp/http.c b/tinyhttp/http.c
new file mode 100644 (file)
index 0000000..40eb1c7
--- /dev/null
@@ -0,0 +1,227 @@
+/*-
+ * Copyright 2012 Matthew Endsley
+ * All rights reserved
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted providing that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "http.h"
+
+#include <ctype.h>
+#include <string.h>
+
+#include "header.h"
+#include "chunk.h"
+
+static void append_body(struct http_roundtripper* rt, const char* data, int ndata)
+{
+    rt->funcs.body(rt->opaque, data, ndata);
+}
+
+static void grow_scratch(struct http_roundtripper* rt, int size)
+{
+    if (rt->nscratch >= size)
+        return;
+
+    if (size < 64)
+        size = 64;
+    int nsize = (rt->nscratch * 3) / 2;
+    if (nsize < size)
+        nsize = size;
+
+       rt->scratch = (char*)rt->funcs.realloc_scratch(rt->opaque, rt->scratch, nsize);
+    rt->nscratch = nsize;
+}
+
+static int min(int a, int b)
+{
+    return a > b ? b : a;
+}
+
+enum http_roundtripper_state {
+    http_roundtripper_header,
+    http_roundtripper_chunk_header,
+    http_roundtripper_chunk_data,
+    http_roundtripper_raw_data,
+    http_roundtripper_unknown_data,
+    http_roundtripper_close,
+    http_roundtripper_error,
+};
+
+void http_init(struct http_roundtripper* rt, struct http_funcs funcs, void* opaque)
+{
+    rt->funcs = funcs;
+    rt->scratch = 0;
+    rt->opaque = opaque;
+    rt->code = 0;
+    rt->parsestate = 0;
+    rt->contentlength = -1;
+    rt->state = http_roundtripper_header;
+    rt->nscratch = 0;
+    rt->nkey = 0;
+    rt->nvalue = 0;
+    rt->chunked = 0;
+}
+
+void http_free(struct http_roundtripper* rt)
+{
+    if (rt->scratch) {
+        rt->funcs.realloc_scratch(rt->opaque, rt->scratch, 0);
+        rt->scratch = 0;
+    }
+}
+
+int http_data(struct http_roundtripper* rt, const char* data, int size, int* read)
+{
+    const int initial_size = size;
+    while (size) {
+        switch (rt->state) {
+        case http_roundtripper_header:
+            switch (http_parse_header_char(&rt->parsestate, *data)) {
+            case http_header_status_done:
+                rt->funcs.code(rt->opaque, rt->code);
+                if (rt->parsestate != 0)
+                    rt->state = http_roundtripper_error;
+                else if (rt->chunked) {
+                    rt->contentlength = 0;
+                    rt->state = http_roundtripper_chunk_header;
+                } else if (rt->contentlength == 0)
+                    rt->state = http_roundtripper_close;
+                else if (rt->contentlength > 0)
+                    rt->state = http_roundtripper_raw_data;
+                else if (rt->contentlength == -1)
+                    rt->state = http_roundtripper_unknown_data;
+                else
+                    rt->state = http_roundtripper_error;
+                break;
+
+            case http_header_status_code_character:
+                rt->code = rt->code * 10 + *data - '0';
+                break;
+
+            case http_header_status_key_character:
+                grow_scratch(rt, rt->nkey + 1);
+                rt->scratch[rt->nkey] = tolower(*data);
+                ++rt->nkey;
+                break;
+
+            case http_header_status_value_character:
+                grow_scratch(rt, rt->nkey + rt->nvalue + 1);
+                rt->scratch[rt->nkey+rt->nvalue] = *data;
+                ++rt->nvalue;
+                break;
+
+            case http_header_status_store_keyvalue:
+                if (rt->nkey == 17 && 0 == strncmp(rt->scratch, "transfer-encoding", rt->nkey))
+                    rt->chunked = (rt->nvalue == 7 && 0 == strncmp(rt->scratch + rt->nkey, "chunked", rt->nvalue));
+                else if (rt->nkey == 14 && 0 == strncmp(rt->scratch, "content-length", rt->nkey)) {
+                    int ii, end;
+                    rt->contentlength = 0;
+                    for (ii = rt->nkey, end = rt->nkey + rt->nvalue; ii != end; ++ii)
+                        rt->contentlength = rt->contentlength * 10 + rt->scratch[ii] - '0';
+                }
+
+                rt->funcs.header(rt->opaque, rt->scratch, rt->nkey, rt->scratch + rt->nkey, rt->nvalue);
+
+                rt->nkey = 0;
+                rt->nvalue = 0;
+                break;
+            }
+
+            --size;
+            ++data;
+            break;
+
+        case http_roundtripper_chunk_header:
+            if (!http_parse_chunked(&rt->parsestate, &rt->contentlength, *data)) {
+                if (rt->contentlength == -1)
+                    rt->state = http_roundtripper_error;
+                else if (rt->contentlength == 0)
+                    rt->state = http_roundtripper_close;
+                else
+                    rt->state = http_roundtripper_chunk_data;
+            }
+
+            --size;
+            ++data;
+            break;
+
+        case http_roundtripper_chunk_data: {
+            const int chunksize = min(size, rt->contentlength);
+            append_body(rt, data, chunksize);
+            rt->contentlength -= chunksize;
+            size -= chunksize;
+            data += chunksize;
+
+            if (rt->contentlength == 0) {
+                rt->contentlength = 1;
+                rt->state = http_roundtripper_chunk_header;
+            }
+        }
+        break;
+
+        case http_roundtripper_raw_data: {
+            const int chunksize = min(size, rt->contentlength);
+            append_body(rt, data, chunksize);
+            rt->contentlength -= chunksize;
+            size -= chunksize;
+            data += chunksize;
+
+            if (rt->contentlength == 0)
+                rt->state = http_roundtripper_close;
+        }
+        break;
+
+        case http_roundtripper_unknown_data: {
+            if (size == 0)
+                rt->state = http_roundtripper_close;
+            else {
+                append_body(rt, data, size);
+                size -= size;
+                data += size;
+            }
+        }
+        break;
+
+        case http_roundtripper_close:
+        case http_roundtripper_error:
+            break;
+        }
+
+        if (rt->state == http_roundtripper_error || rt->state == http_roundtripper_close) {
+            if (rt->scratch) {
+                rt->funcs.realloc_scratch(rt->opaque, rt->scratch, 0);
+                rt->scratch = 0;
+            }
+            *read = initial_size - size;
+            return 0;
+        }
+    }
+
+    *read = initial_size - size;
+    return 1;
+}
+
+int http_iserror(struct http_roundtripper* rt)
+{
+    return rt->state == http_roundtripper_error;
+}
diff --git a/tinyhttp/http.h b/tinyhttp/http.h
new file mode 100644 (file)
index 0000000..ba7e4cf
--- /dev/null
@@ -0,0 +1,93 @@
+/*-
+ * Copyright 2012 Matthew Endsley
+ * All rights reserved
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted providing that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef HTTP_HTTP_H
+#define HTTP_HTTP_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/**
+ * Callbacks for handling response data.
+ *  realloc_scratch - reallocate memory, cannot fail. There will only
+ *                    be one scratch buffer. Implemnentation may take
+ *                    advantage of this fact.
+ *  body - handle HTTP response body data
+ *  header - handle an HTTP header key/value pair
+ *  code - handle the HTTP status code for the response
+ */
+struct http_funcs {
+    void* (*realloc_scratch)(void* opaque, void* ptr, int size);
+    void (*body)(void* opaque, const char* data, int size);
+    void (*header)(void* opaque, const char* key, int nkey, const char* value, int nvalue);
+    void (*code)(void* opqaue, int code);
+};
+
+struct http_roundtripper {
+    struct http_funcs funcs;
+    void *opaque;
+    char *scratch;
+    int code;
+    int parsestate;
+    int contentlength;
+    int state;
+    int nscratch;
+    int nkey;
+    int nvalue;
+    int chunked;
+};
+
+/**
+ * Initializes a rountripper with the specified response functions. This must
+ * be called before the rt object is used.
+ */
+void http_init(struct http_roundtripper* rt, struct http_funcs, void* opaque);
+
+/**
+ * Frees any scratch memory allocated during parsing.
+ */
+void http_free(struct http_roundtripper* rt);
+
+/**
+ * Parses a block of HTTP response data. Returns zero if the parser reached the
+ * end of the response, or an error was encountered. Use http_iserror to check
+ * for the presence of an error. Returns non-zero if more data is required for
+ * the response.
+ */
+int http_data(struct http_roundtripper* rt, const char* data, int size, int* read);
+
+/**
+ * Returns non-zero if a completed parser encounted an error. If http_data did
+ * not return non-zero, the results of this function are undefined.
+ */
+int http_iserror(struct http_roundtripper* rt);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif