Merge branch 'drops' into master

Conflicts:
	src/server/dial_server.c
	src/server/main.c
	src/server/quick_ssdp.c

Change-Id: I0e1e4bedf8206dcf422564b465864e3de2570567
diff --git a/Version.h b/Version.h
index 5c8762c..484abaa 100644
--- a/Version.h
+++ b/Version.h
@@ -17,7 +17,7 @@
 #define DIAL_VERSION_MINOR 0
 
 #undef DIAL_VERSION_PATCH
-#define DIAL_VERSION_PATCH 2
+#define DIAL_VERSION_PATCH 3
 
 #undef DIAL_VERSION_NUMBER_STR2
 #define DIAL_VERSION_NUMBER_STR2(M) #M
diff --git a/src/client/DialServer.cpp b/src/client/DialServer.cpp
index c0306b8..794fd66 100644
--- a/src/client/DialServer.cpp
+++ b/src/client/DialServer.cpp
@@ -206,35 +206,40 @@
     string emptyPayload;
     string appUrl = m_appsUrl;
     sendCommand( appUrl.append(application), COMMAND_STATUS, emptyPayload, responseHeaders, responseBody );
+
+    ATRACE("Body: %s\n", responseBody.c_str());
+    unsigned found = responseBody.find("href=");
+    if( found != string::npos )
+    {
+        // The start of href is after href= and the quote
+        unsigned href_start = found + 5 + 1;
+
+        // get the body from the start of href to the end, then find 
+        // the last quote delimiter.
+        string tmp = responseBody.substr( href_start );
+        unsigned href_end = tmp.find("\"");
+        m_stopEndPoint = responseBody.substr( href_start, href_end );
+    }
     return 0;
 }
 
 int DialServer::stopApplication(
     string &application,
-    string &endPoint,
     string &responseHeaders )
 {
     ATRACE("%s: Quit %s\n", __FUNCTION__, application.c_str());
     string emptyPayload, responseBody;  // dropping this
     string appUrl = m_appsUrl;
+
+    // just call status to update the run endpoint
+    getStatus( application, responseHeaders, responseBody );
+
     sendCommand( 
-            (appUrl.append(application)).append(endPoint), 
+            (appUrl.append(application)).append("/"+m_stopEndPoint), 
             COMMAND_KILL, emptyPayload, responseHeaders, responseBody );
     return 0;
 }
 
-
-int DialServer::stopApplication(
-        string &stopurl,
-        string &responseHeaders )
-{
-    ATRACE("%s: Quit URL: %s\n", __FUNCTION__, stopurl.c_str());
-    string emptyPayload, responseBody;  // dropping this
-    sendCommand( stopurl, COMMAND_KILL, 
-            emptyPayload, responseHeaders, responseBody );
-    return 0;
-}
-
 int DialServer::getHttpResponseHeader( 
     string &responseHeaders,
     string &header,
diff --git a/src/client/DialServer.h b/src/client/DialServer.h
index ce81e9f..92d36f2 100644
--- a/src/client/DialServer.h
+++ b/src/client/DialServer.h
@@ -125,29 +125,15 @@
         string &responseBody );
 
     /**
-     * Stop an application.  Client *must* provide the endPoint
+     * Stop an application.
      *
      * @param[in] application Name of the application to stop
-     * @param[in] endPoint URL of the REST endpoint to stop
      * @param[out] responseHeaders Returns the HTTP response headers
      *
      * @return 0 if successful, !0 otherwise
      */
     int stopApplication(
         string &application,
-        string &endPoint,
-        string &responseHeaders );
-
-    /**
-     * Stop an application using a fully formed URL.
-     *
-     * @param[in] stopurl Full stop URL
-     * @param[out] responseHeaders Returns the HTTP response headers
-     *
-     * @return 0 if successful, !0 otherwise
-     */
-    int stopApplication(
-        string &stopurl,
         string &responseHeaders );
 
 
@@ -189,6 +175,7 @@
     string m_ipAddr;
     bool found;
     string m_ddxml; // information about the device
+    string m_stopEndPoint;
 };
 
 #endif // DIALSERVER_H
diff --git a/src/client/main.cpp b/src/client/main.cpp
index ffce725..9eabebb 100644
--- a/src/client/main.cpp
+++ b/src/client/main.cpp
@@ -156,7 +156,6 @@
     while(processInput)
     {
         string responseHeaders, responseBody, payload;
-        string endpoint = "/run";
         string netflix = "Netflix";
         string youtube = "YouTube";
 
@@ -191,7 +190,7 @@
                 break;
             case 2:
                 printf("Kill Netflix\n");
-                pServer->stopApplication( netflix, endpoint, responseHeaders );
+                pServer->stopApplication( netflix, responseHeaders );
                 break;
             case 3:
                 printf("Netflix Status: \n");
@@ -204,7 +203,7 @@
                 break;
             case 5:
                 printf("Kill YouTube\n");
-                pServer->stopApplication( youtube, endpoint, responseHeaders );
+                pServer->stopApplication( youtube, responseHeaders );
                 break;
             case 6:
                 printf("YouTube Status: \n");
diff --git a/src/server/dial_data.c b/src/server/dial_data.c
new file mode 100644
index 0000000..9ed256b
--- /dev/null
+++ b/src/server/dial_data.c
@@ -0,0 +1,56 @@
+/*
+ * Functions related to storing/retrieving and manipulating DIAL data.
+ */
+#include "dial_data.h"
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+/**
+ * Returns the path where data is stored for the given app.
+ */
+static char* getAppPath(char app_name[]) {
+    size_t name_size = strlen(app_name) + sizeof(DIAL_DATA_DIR) + 1;
+    char* filename = (char*) malloc(name_size);
+    filename[0] = 0;
+    strncat(filename, DIAL_DATA_DIR, name_size);
+    strncat(filename, app_name, name_size - sizeof(DIAL_DATA_DIR));
+    return filename;
+}
+
+void store_dial_data(char app_name[], DIALData *data) {
+    char* filename = getAppPath(app_name);
+    FILE *f = fopen(filename, "w");
+    if (f == NULL) {
+        printf("Cannot open DIAL data output file: %s\n", filename);
+        exit(1);
+    }
+    for (DIALData *first = data; first != NULL; first = first->next) {
+        fprintf(f, "%s %s\n", first->key, first->value);
+    }
+    fclose(f);
+}
+
+DIALData *retrieve_dial_data(char *app_name) {
+    char* filename = getAppPath(app_name);
+    FILE *f = fopen(filename, "r");
+    if (f == NULL) {
+        return NULL; // no dial data found, that's fine
+    }
+    DIALData *result = NULL;
+    char key[256];
+    char value[256];
+    while (fscanf(f, "%255s %255s\n", key, value) != EOF) {
+        DIALData *newNode = (DIALData *) malloc(sizeof(DIALData));
+        newNode->key = (char *) malloc(strlen(key));
+        strcpy(newNode->key, key);
+        newNode->value = (char *) malloc(strlen(value));
+        strcpy(newNode->value, value);
+        newNode->next = result;
+        result = newNode;
+    }
+    fclose(f);
+    return result;
+}
+
diff --git a/src/server/dial_data.h b/src/server/dial_data.h
new file mode 100644
index 0000000..28a2b49
--- /dev/null
+++ b/src/server/dial_data.h
@@ -0,0 +1,37 @@
+/*
+ * Defines functions for persisting and retrieving DIAL data.
+ */
+
+#ifndef SRC_SERVER_DIAL_DATA_H_
+#define SRC_SERVER_DIAL_DATA_H_
+
+/*
+ * Slash-terminated directory of where to persist the DIAL data.
+ */
+#define DIAL_DATA_DIR "/tmp/"
+
+/*
+ * The maximum DIAL data payload accepted per the 'DIAL extension for smooth
+ * pairing' specification'.
+ */
+#define DIAL_DATA_MAX_PAYLOAD (4096) /* 4 KB */
+
+/*
+ * Url path where DIAL data should be posted according to the 'DIAL extension
+ * for smooth pairing' specification.
+ */
+#define DIAL_DATA_URI "/dial_data"
+
+struct DIALData_ {
+    struct DIALData_ *next;
+    char *key;
+    char *value;
+};
+
+typedef struct DIALData_ DIALData;
+
+void store_dial_data(char *app_name, DIALData *data);
+
+DIALData *retrieve_dial_data(char *app_name);
+
+#endif /* SRC_SERVER_DIAL_DATA_H_ */
diff --git a/src/server/dial_server.c b/src/server/dial_server.c
index ac7c210..9efff06 100644
--- a/src/server/dial_server.c
+++ b/src/server/dial_server.c
@@ -1,51 +1,61 @@
 /*
  * Copyright (c) 2012 Netflix, Inc.
  * All rights reserved.
- * 
- * Redistribution and use in source and binary forms, with or without 
+ *
+ * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
- * 
+ *
  * Redistributions of source code must retain the above copyright notice, this
  * list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright notice, 
- * this list of conditions and the following disclaimer in the documentation 
+ * 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 NETFLIX, INC. AND CONTRIBUTORS "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 NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY 
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
+ * 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 NETFLIX OR CONTRIBUTORS 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 
+ * 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 "dial_data.h"
 #include "dial_server.h"
 
 #include <arpa/inet.h>
 #include <netinet/in.h>
 #include <pthread.h>
+#include <stddef.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/socket.h>
 
 #include "mongoose.h"
+#include "url_lib.h"
 
 // TODO: Partners should define this port
 #define DIAL_PORT (56789)
+#define DIAL_DATA_SIZE (8*1024)
+
+static const char *gLocalhost = "127.0.0.1";
 
 struct DIALApp_ {
     struct DIALApp_ *next;
     struct DIALAppCallbacks callbacks;
+    struct DIALData_ *dial_data;
     void *callback_data;
     DIAL_run_t run_id;
     DIALStatus state;
     char *name;
     char payload[DIAL_MAX_PAYLOAD];
+    int useAdditionalData;
+    char corsAllowedOrigin[256];
+
 };
 
 typedef struct DIALApp_ DIALApp;
@@ -69,7 +79,7 @@
 static DIALApp **find_app(DIALServer *ds, const char *app_name) {
     DIALApp *app;
     DIALApp **ret = &ds->apps;
-  
+
     for (app = ds->apps; app != NULL; ret = &app->next, app = app->next) {
         if (!strcmp(app_name, app->name)) {
             break;
@@ -78,22 +88,26 @@
     return ret;
 }
 
+static void url_decode_xml_encode(char *dst, char *src, size_t src_size) {
+    char *url_decoded_key = (char *) malloc(src_size + 1);
+    urldecode(url_decoded_key, src, src_size);
+    xmlencode(dst, url_decoded_key, 2 * src_size);
+    free(url_decoded_key);
+}
+
 /*
  * A bad payload is defined to be an unprintable character or a
  * non-ascii character.
  */
-static int isBadPayload( const char* pPayload, int numBytes )
-{
+static int isBadPayload(const char* pPayload, int numBytes) {
     int i = 0;
-    fprintf( stderr, "Checking %d bytes\n", numBytes );
-    for( ; i < numBytes; i++)
-    {
+    fprintf( stderr, "Payload: checking %d bytes\n", numBytes);
+    for (; i < numBytes; i++) {
         // High order bit should not be set
         // 0x7F is DEL (non-printable)
         // Anything under 32 is non-printable
-        if( ((pPayload[i] & 0x80) == 0x80)  || 
-            (pPayload[i] == 0x7F)           || 
-            (pPayload[i] <= 0x1F) )
+        if (((pPayload[i] & 0x80) == 0x80) || (pPayload[i] == 0x7F)
+                || (pPayload[i] <= 0x1F))
             return 1;
     }
     return 0;
@@ -101,44 +115,63 @@
 
 static void handle_app_start(struct mg_connection *conn,
                              const struct mg_request_info *request_info,
-                             const char *app_name) {
-    char body[DIAL_MAX_PAYLOAD+2] = {0,};
+                             const char *app_name,
+                             const char *origin_header) {
+    char additional_data_param[256] = {0, };
+    char body[DIAL_MAX_PAYLOAD + sizeof(additional_data_param) + 2] = {0, };
     DIALApp *app;
     DIALServer *ds = request_info->user_data;
-    int nread;
-  
+    int body_size;
+
     ds_lock(ds);
     app = *find_app(ds, app_name);
     if (!app) {
         mg_send_http_error(conn, 404, "Not Found", "Not Found");
     } else {
-        nread = mg_read(conn, body, sizeof(body));
+        body_size = mg_read(conn, body, sizeof(body));
         // NUL-terminate it just in case
-        if( nread > DIAL_MAX_PAYLOAD ) {
+        if (body_size > DIAL_MAX_PAYLOAD) {
             mg_send_http_error(conn, 413, "413 Request Entity Too Large",
-                             "413 Request Entity Too Large");
-        }
-        else if( isBadPayload( body, nread ) ){
+                               "413 Request Entity Too Large");
+        } else if (isBadPayload(body, body_size)) {
             mg_send_http_error(conn, 400, "400 Bad Request", "400 Bad Request");
-        }
-        else {
-            app->state = app->callbacks.start_cb(ds, app_name, body, nread,
-                                                 &app->run_id, app->callback_data);
+        } else {
+            char laddr[INET6_ADDRSTRLEN];
+            const struct sockaddr_in *addr =
+                    (struct sockaddr_in *) &request_info->local_addr;
+            inet_ntop(addr->sin_family, &addr->sin_addr, laddr, sizeof(laddr));
+            in_port_t dial_port = DIAL_get_port(ds);
+
+            if (app->useAdditionalData) {
+                if (body_size != 0) {
+                    strcat(body, "&");
+                }
+                // Construct additionalDataUrl=http://host:port/apps/app_name/dial_data
+                sprintf(additional_data_param,
+                        "additionalDataUrl=http%%3A%%2F%%2Flocalhost%%3A%d%%2Fapps%%2F%s%%2Fdial_data%%3F",
+                        dial_port, app_name);
+                strcat(body, additional_data_param);
+                body_size = strlen(body);
+            }
+            fprintf(stderr, "Starting the app with params %s\n", body);
+            app->state = app->callbacks.start_cb(ds, app_name, body, body_size,
+                                                 &app->run_id,
+                                                 app->callback_data);
             if (app->state == kDIALStatusRunning) {
-                char laddr[INET6_ADDRSTRLEN];
-                const struct sockaddr_in *addr =
-                    (struct sockaddr_in *)&request_info->local_addr;
-                inet_ntop(addr->sin_family, &addr->sin_addr, laddr, sizeof(laddr));
-                mg_printf(conn, "HTTP/1.1 201 Created\r\n"
-                                "Content-Type: text/plain\r\n"
-                                "Location: http://%s:%d/apps/%s/run\r\n"
-                                "\r\n", laddr, DIAL_get_port(ds), app_name);
+                mg_printf(
+                        conn,
+                        "HTTP/1.1 201 Created\r\n"
+                        "Content-Type: text/plain\r\n"
+                        "Location: http://%s:%d/apps/%s/run\r\n"
+                        "Access-Control-Allow-Origin: %s\r\n"
+                        "\r\n",
+                        laddr, dial_port, app_name, origin_header);
                 // copy the payload into the application struct
-                memset( app->payload, 0, DIAL_MAX_PAYLOAD );
-                memcpy( app->payload, body, nread );
+                memset(app->payload, 0, DIAL_MAX_PAYLOAD);
+                memcpy(app->payload, body, body_size);
             } else {
                 mg_send_http_error(conn, 503, "Service Unavailable",
-                                 "Service Unavailable");
+                                   "Service Unavailable");
             }
         }
     }
@@ -147,71 +180,106 @@
 
 static void handle_app_status(struct mg_connection *conn,
                               const struct mg_request_info *request_info,
-                              const char *app_name) {
+                              const char *app_name,
+                              const char *origin_header) {
     DIALApp *app;
     int canStop = 0;
     DIALServer *ds = request_info->user_data;
-  
+
     ds_lock(ds);
     app = *find_app(ds, app_name);
     if (!app) {
         mg_send_http_error(conn, 404, "Not Found", "Not Found");
-    } else {
-        app->state = app->callbacks.status_cb(ds, app_name, app->run_id,
-                                              &canStop, app->callback_data);
-        mg_printf(conn, "HTTP/1.1 200 OK\r\n"
-                      "Content-Type: application/xml\r\n"
-                      "\r\n"
-                      "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
-                      "<service xmlns=\"urn:dial-multiscreen-org:schemas:dial\">\r\n"
-                      "  <name>%s</name>\r\n"
-                      "  <options allowStop=\"%s\"/>\r\n"
-                      "  <state>%s</state>\r\n",
-                      app->name, canStop ? "true":"false",
-                      app->state ? "running" : "stopped");
-        if (app->state != kDIALStatusStopped) {
-            mg_printf(conn,
-                      "  <link rel=\"run\" href=\"run\"/>\r\n");
-        }
-        if (app->callbacks.service_data_cb != NULL) {
-            struct CastServiceData serviceData = app->callbacks.service_data_cb(ds, app_name, app->run_id, app->callback_data);
-            mg_printf(conn,
-                      "  <servicedata xmlns=\"urn:chrome.google.com:cast\">\r\n"
-                      "    <connectionSvcURL>http://%s:%d%s</connectionSvcURL>\r\n"
-                      "    <protocols>\r\n"
-                      "      <protocol>%s</protocol>\r\n"
-                      "    </protocols>\r\n"
-                      "  </servicedata>\r\n",
-                      serviceData.connection_svc_host,
-                      serviceData.connection_svc_port,
-                      serviceData.connection_svc_path,
-                      serviceData.protocol);
-        }
-        if (app->callbacks.activity_status_cb != NULL) {
-            struct CastActivityStatus activityStatus = app->callbacks.activity_status_cb(ds, app_name, app->run_id, app->callback_data);
-            mg_printf(conn,
-                      "  <activity-status xmlns=\"urn:chrome.google.com:cast\">\r\n"
-                      "    <description>%s</description>\r\n"
-                      "  </activity-status>\r\n"
-                      "  </servicedata>\r\n", activityStatus.description);
-        }
-        mg_printf(conn, "</service>\r\n");
+        ds_unlock(ds);
+        return;
     }
+
+    char dial_data[DIAL_DATA_SIZE] = {0,};
+    char *end = dial_data + DIAL_DATA_SIZE;
+    char *p = dial_data;
+
+    for (DIALData* first = app->dial_data; first != NULL; first = first->next) {
+        p = smartstrcat(p, "    <", end - p);
+        size_t key_length = strlen(first->key);
+        char *encoded_key = (char *) malloc(2 * key_length + 1);
+        url_decode_xml_encode(encoded_key, first->key, key_length);
+
+        size_t value_length = strlen(first->value);
+        char *encoded_value = (char *) malloc(2 * value_length + 1);
+        url_decode_xml_encode(encoded_value, first->value, value_length);
+
+        p = smartstrcat(p, encoded_key, end - p);
+        p = smartstrcat(p, ">", end - p);
+        p = smartstrcat(p, encoded_value, end - p);
+        p = smartstrcat(p, "</", end - p);
+        p = smartstrcat(p, encoded_key, end - p);
+        p = smartstrcat(p, ">", end - p);
+        free(encoded_key);
+        free(encoded_value);
+    }
+    app->state = app->callbacks.status_cb(ds, app_name, app->run_id, &canStop,
+                                          app->callback_data);
+    mg_printf(
+            conn,
+            "HTTP/1.1 200 OK\r\n"
+            "Content-Type: application/xml\r\n"
+            "Access-Control-Allow-Origin: %s\r\n"
+            "\r\n"
+            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
+            "<service xmlns=\"urn:dial-multiscreen-org:schemas:dial\" dialVer=%s>\r\n"
+            "  <name>%s</name>\r\n"
+            "  <options allowStop=\"%s\"/>\r\n"
+            "  <state>%s</state>\r\n"
+            "%s"
+            "  <additionalData>\n"
+            "%s"
+            "\n  </additionalData>\n",
+            origin_header,
+            DIAL_VERSION,
+            app->name,
+            canStop ? "true" : "false",
+            app->state ? "running" : "stopped",
+            app->state == kDIALStatusStopped ?
+                    "" : "  <link rel=\"run\" href=\"run\"/>\r\n",
+            dial_data);
+    if (app->callbacks.service_data_cb != NULL) {
+        struct CastServiceData serviceData = app->callbacks.service_data_cb(ds, app_name, app->run_id, app->callback_data);
+        mg_printf(conn,
+                  "  <servicedata xmlns=\"urn:chrome.google.com:cast\">\r\n"
+                  "    <connectionSvcURL>http://%s:%d%s</connectionSvcURL>\r\n"
+                  "    <protocols>\r\n"
+                  "      <protocol>%s</protocol>\r\n"
+                  "    </protocols>\r\n"
+                  "  </servicedata>\r\n",
+                  serviceData.connection_svc_host,
+                  serviceData.connection_svc_port,
+                  serviceData.connection_svc_path,
+                  serviceData.protocol);
+    }
+    if (app->callbacks.activity_status_cb != NULL) {
+        struct CastActivityStatus activityStatus = app->callbacks.activity_status_cb(ds, app_name, app->run_id, app->callback_data);
+        mg_printf(conn,
+                  "  <activity-status xmlns=\"urn:chrome.google.com:cast\">\r\n"
+                  "    <description>%s</description>\r\n"
+                  "  </activity-status>\r\n", activityStatus.description);
+    }
+    mg_printf(conn, "</service>\r\n");
     ds_unlock(ds);
 }
 
 static void handle_app_stop(struct mg_connection *conn,
                             const struct mg_request_info *request_info,
-                            const char *app_name) {
+                            const char *app_name,
+                            const char *origin_header) {
     DIALApp *app;
     DIALServer *ds = request_info->user_data;
     int canStop = 0;
-  
+
     ds_lock(ds);
     app = *find_app(ds, app_name);
 
     // update the application state
-    if( !app ) {
+    if (!app) {
         app->state = app->callbacks.status_cb(ds, app_name, app->run_id,
                                               &canStop, app->callback_data);
     }
@@ -222,12 +290,67 @@
         app->callbacks.stop_cb(ds, app_name, app->run_id, app->callback_data);
         app->state = kDIALStatusStopped;
         mg_printf(conn, "HTTP/1.1 200 OK\r\n"
-                        "Content-Type: text/plain\r\n"
-                        "\r\n");
+                  "Content-Type: text/plain\r\n"
+                  "Access-Control-Allow-Origin: %s\r\n"
+                  "\r\n",
+                  origin_header);
     }
     ds_unlock(ds);
 }
 
+static void handle_dial_data(struct mg_connection *conn,
+                             const struct mg_request_info *request_info,
+                             const char *app_name,
+                             const char *origin_header,
+                             int use_payload) {
+    char body[DIAL_DATA_MAX_PAYLOAD + 2] = {0, };
+
+    DIALApp *app;
+    DIALServer *ds = request_info->user_data;
+
+    ds_lock(ds);
+    app = *find_app(ds, app_name);
+    if (!app) {
+        mg_send_http_error(conn, 404, "Not Found", "Not Found");
+        ds_unlock(ds);
+        return;
+    }
+    int nread;
+    if (!use_payload) {
+        if (request_info->query_string) {
+            strncpy(body, request_info->query_string, DIAL_DATA_MAX_PAYLOAD);
+            nread = strlen(body);
+        } else {
+          nread = 0;
+        }
+    } else {
+        nread = mg_read(conn, body, DIAL_DATA_MAX_PAYLOAD);
+        body[nread] = '\0';
+    }
+    if (nread > DIAL_DATA_MAX_PAYLOAD) {
+        mg_send_http_error(conn, 413, "413 Request Entity Too Large",
+                           "413 Request Entity Too Large");
+        ds_unlock(ds);
+        return;
+    }
+
+    if (isBadPayload(body, nread)) {
+        mg_send_http_error(conn, 400, "400 Bad Request", "400 Bad Request");
+        ds_unlock(ds);
+        return;
+    }
+
+    app->dial_data = parse_params(body);
+    store_dial_data(app->name, app->dial_data);
+
+    mg_printf(conn, "HTTP/1.1 200 OK\r\n"
+              "Access-Control-Allow-Origin: %s\r\n"
+              "\r\n",
+              origin_header);
+
+    ds_unlock(ds);
+}
+
 // Add logic to test if we should drop DIAL requests
 #define SAGE_PROPERTIES_PATH "/rw/sage/SageClient.properties"
 #define DIAL_DISABLED_PROPERTY "allow_dial=false"
@@ -249,42 +372,123 @@
     return 1;
 }
 
+static int ends_with(const char *str, const char *suffix) {
+    if (!str || !suffix)
+        return 0;
+    size_t lenstr = strlen(str);
+    size_t lensuffix = strlen(suffix);
+    if (lensuffix > lenstr)
+        return 0;
+    return strncmp(str + lenstr - lensuffix, suffix, lensuffix) == 0;
+}
+
+static int is_allowed_origin(DIALServer* ds, char * origin) {
+    if (!origin || strlen(origin)==0) {
+        return 0;
+    }
+
+    ds_lock(ds);
+    DIALApp *app;
+    int result = 0;
+    for (app = ds->apps; app != NULL; app = app->next) {
+        if (app->corsAllowedOrigin[0] &&
+            ends_with(origin, app->corsAllowedOrigin)) {
+            result = 1;
+            break;
+        }
+    }
+    ds_unlock(ds);
+
+    return result;
+}
+
 #define APPS_URI "/apps/"
 #define RUN_URI "/run"
 
-static void *request_handler(enum mg_event event,
-                             struct mg_connection *conn,
+static void *request_handler(enum mg_event event, struct mg_connection *conn,
                              const struct mg_request_info *request_info) {
+    DIALServer *ds = request_info->user_data;
+
+    fprintf(stderr, "Received request %s\n", request_info->uri);
+    char *host_header = {0,};
+    char *origin_header = {0,};
+    for (int i = 0; i < request_info->num_headers; ++i) {
+        if (!strcmp(request_info->http_headers[i].name, "Host")) {
+            host_header = request_info->http_headers[i].value;
+        } else if (!strcmp(request_info->http_headers[i].name,
+                          "Origin")) {
+            origin_header = request_info->http_headers[i].value;
+        }
+    }
+    fprintf(stderr, "Origin %s, Host: %s\n", origin_header, host_header);
     if (event == MG_NEW_REQUEST) {
         // If DIAL is disabled, drop the request
         if (!dial_allowed())
           return "done";
+        // CORS OPTIONS request
+        if (!strcmp(request_info->request_method, "OPTIONS")) {
+            //TODO: for extra safety, also check that host header matches origin
+            if (host_header && origin_header && is_allowed_origin(ds, origin_header)) {
+                mg_printf(
+                        conn,
+                        "HTTP/1.1 204 No Content\r\n"
+                        "Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS\r\n"
+                        "Access-Control-Max-Age: 86400\r\n"
+                        "Access-Control-Allow-Origin: %s\r\n"
+                        "Content-Length: 0"
+                        "\r\n",
+                        origin_header);
+                return "done";
+            }
+            mg_send_http_error(conn, 403, "Forbidden", "Forbidden");
+            return "done";
+        }
         // URL ends with run
-        if (!strncmp(request_info->uri + strlen(request_info->uri)-4, RUN_URI, strlen(RUN_URI))) {
-            char app_name[256] = {0,}; // assuming the application name is not over 256 chars.
-            strncpy( app_name, request_info->uri + strlen(APPS_URI),
-              ((strlen(request_info->uri)-4) - (sizeof(APPS_URI)-1)) );
-      
+        if (!strncmp(request_info->uri + strlen(request_info->uri) - 4, RUN_URI,
+                     strlen(RUN_URI))) {
+            char app_name[256] = {0, };  // assuming the application name is not over 256 chars.
+            strncpy(app_name, request_info->uri + strlen(APPS_URI),
+                    ((strlen(request_info->uri) - 4) - (sizeof(APPS_URI) - 1)));
+
             // DELETE non-empty app name
-            if (app_name[0] != '\0' &&
-                !strcmp(request_info->request_method, "DELETE")) {
-                handle_app_stop(conn, request_info, app_name);
+            if (app_name[0] != '\0'
+                    && !strcmp(request_info->request_method, "DELETE")) {
+                handle_app_stop(conn, request_info, app_name, origin_header);
             } else {
-                mg_send_http_error(conn, 501, "Not Implemented", "Not Implemented");
+                mg_send_http_error(conn, 501, "Not Implemented",
+                                   "Not Implemented");
             }
         }
-        // URI starts with "/apps/"
-        else if (!strncmp(request_info->uri, APPS_URI, sizeof(APPS_URI) - 1)) {
+        // URI starts with "/apps/" and is followed by an app name
+        else if (!strncmp(request_info->uri, APPS_URI, sizeof(APPS_URI) - 1)
+                && !strchr(request_info->uri + strlen(APPS_URI), '/')) {
             const char *app_name;
             app_name = request_info->uri + sizeof(APPS_URI) - 1;
             // start app
             if (!strcmp(request_info->request_method, "POST")) {
-                handle_app_start(conn, request_info, app_name);
-            // get app status
+                handle_app_start(conn, request_info, app_name, origin_header);
+                // get app status
             } else if (!strcmp(request_info->request_method, "GET")) {
-                handle_app_status(conn, request_info, app_name);
+                handle_app_status(conn, request_info, app_name, origin_header);
             } else {
-                mg_send_http_error(conn, 501, "Not Implemented", "Not Implemented");
+                mg_send_http_error(conn, 501, "Not Implemented",
+                                   "Not Implemented");
+            }
+        // URI is of the form */app_name/dial_data
+        } else if (strstr(request_info->uri, DIAL_DATA_URI)) {
+            char laddr[INET6_ADDRSTRLEN];
+            const struct sockaddr_in *addr =
+                    (struct sockaddr_in *) &request_info->remote_addr;
+            inet_ntop(addr->sin_family, &addr->sin_addr, laddr, sizeof(laddr));
+            if ( !strncmp(laddr, gLocalhost, strlen(gLocalhost)) ) {
+                char *app_name = parse_app_name(request_info->uri);
+                int use_payload =
+                    strcmp(request_info->request_method, "POST") ? 0 : 1;
+                handle_dial_data(conn, request_info, app_name, origin_header,
+                                 use_payload);
+            } else {
+                // If the request is not from local host, return an error
+                mg_send_http_error(conn, 403, "Forbidden", "Forbidden");
             }
         } else {
             mg_send_http_error(conn, 404, "Not Found", "Not Found");
@@ -297,20 +501,16 @@
     return NULL;
 }
 
-DIALServer *DIAL_start() {
+DIALServer *DIAL_create() {
     DIALServer *ds = calloc(1, sizeof(DIALServer));
-    struct mg_context *ctx;
-  
     pthread_mutex_init(&ds->mux, NULL);
-    ctx = mg_start(&request_handler, ds, DIAL_PORT);
-    if (!ctx) {
-        free(ds);
-        return NULL;
-    }
-    ds->ctx = ctx;
     return ds;
 }
 
+void DIAL_start(DIALServer *ds) {
+    ds->ctx = mg_start(&request_handler, ds, DIAL_PORT);
+}
+
 void DIAL_stop(DIALServer *ds) {
     mg_stop(ds->ctx);
     pthread_mutex_destroy(&ds->mux);
@@ -322,18 +522,19 @@
     if (!mg_get_listen_addr(ds->ctx, &sa, &len)) {
         return 0;
     }
-    return ntohs(((struct sockaddr_in *)&sa)->sin_port);
+    return ntohs(((struct sockaddr_in *) &sa)->sin_port);
 }
 
 int DIAL_register_app(DIALServer *ds, const char *app_name,
-                      struct DIALAppCallbacks *callbacks,
-                      void *user_data) {
+                      struct DIALAppCallbacks *callbacks, void *user_data,
+                      int useAdditionalData,
+                      const char* corsAllowedOrigin) {
     DIALApp **ptr, *app;
     int ret;
-  
+
     ds_lock(ds);
     ptr = find_app(ds, app_name);
-    if (*ptr != NULL) { // app already registered
+    if (*ptr != NULL) {  // app already registered
         ds_unlock(ds);
         ret = 0;
     } else {
@@ -343,10 +544,16 @@
         app->next = *ptr;
         app->state = kDIALStatusStopped;
         app->callback_data = user_data;
+        app->dial_data = retrieve_dial_data(app->name);
+        app->useAdditionalData = useAdditionalData;
+        app->corsAllowedOrigin[0] = '\0';
+        if (corsAllowedOrigin &&
+            strlen(corsAllowedOrigin) < sizeof(app->corsAllowedOrigin)) {
+          strcpy(app->corsAllowedOrigin, corsAllowedOrigin);
+        }
         *ptr = app;
         ret = 1;
     }
-  
     ds_unlock(ds);
     return ret;
 }
@@ -354,10 +561,10 @@
 int DIAL_unregister_app(DIALServer *ds, const char *app_name) {
     DIALApp **ptr, *app;
     int ret;
-  
+
     ds_lock(ds);
     ptr = find_app(ds, app_name);
-    if (*ptr == NULL) { // no such app
+    if (*ptr == NULL) {  // no such app
         ret = 0;
     } else {
         app = *ptr;
@@ -366,22 +573,20 @@
         free(app);
         ret = 1;
     }
-  
+
     ds_unlock(ds);
     return ret;
 }
 
-const char * DIAL_get_payload(DIALServer *ds, const char *app_name)
-{
+const char * DIAL_get_payload(DIALServer *ds, const char *app_name) {
     const char * pPayload = NULL;
     DIALApp **ptr, *app;
-  
-    // NOTE: Don't grab the mutex as we are calling this function from 
+
+    // NOTE: Don't grab the mutex as we are calling this function from
     // inside the application callback which already has the lock.
     //ds_lock(ds);
     ptr = find_app(ds, app_name);
-    if (*ptr != NULL)
-    {
+    if (*ptr != NULL) {
         app = *ptr;
         pPayload = app->payload;
     }
diff --git a/src/server/dial_server.h b/src/server/dial_server.h
index dee72d3..1558aa2 100644
--- a/src/server/dial_server.h
+++ b/src/server/dial_server.h
@@ -1,25 +1,25 @@
 /*
  * Copyright (c) 2012 Netflix, Inc.
  * All rights reserved.
- * 
- * Redistribution and use in source and binary forms, with or without 
+ *
+ * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
- * 
+ *
  * Redistributions of source code must retain the above copyright notice, this
  * list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright notice, 
- * this list of conditions and the following disclaimer in the documentation 
+ * 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 NETFLIX, INC. AND CONTRIBUTORS "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 NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY 
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
+ * 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 NETFLIX OR CONTRIBUTORS 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 
+ * 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.
  */
 
@@ -37,6 +37,11 @@
 } DIALStatus;
 
 /*
+ * DIAL version that is reported via in the status response.
+ */
+#define DIAL_VERSION ("\"1.7\"")
+
+/*
  * The maximum DIAL payload accepted per the DIAL 1.6.1 specification.
  */
 #define DIAL_MAX_PAYLOAD (4096)
@@ -110,12 +115,19 @@
 };
 
 /*
- * Start the DIAL server.  Returns a handle to the DIAL server.
+ * Creates the DIAL server.  Returns a handle to the DIAL server.
  */
-DIALServer *DIAL_start();
+DIALServer *DIAL_create();
 
 /*
- * Stop the DIAL server.  Returns a handle to the DIAL server.
+ * Starts the DIAL server.
+ *
+ * @param[in] ds DIAL server handle
+ */
+void DIAL_start(DIALServer *ds);
+
+/*
+ * Stop the DIAL server.
  *
  * @param[in] ds DIAL server handle
  */
@@ -125,16 +137,19 @@
  * Register a DIAL application
  *
  * @param[in] ds DIAL server handle
- * @param[in] app_name Name of the application.  This should match the DIAL 
+ * @param[in] app_name Name of the application.  This should match the DIAL
  * application end point.
  * @param[in] callbacks Structure with application callbacks
  * @param[in] callback_data Client user data
+ * @param[in] if non-0, the app supports DIALadditionalDataURL.
+ * @param[in] if non-NULL, specifies the CORS allowed origin for this app.
  *
  * @return 1 if successful, 0 otherwise
  */
 int DIAL_register_app(DIALServer *ds, const char *app_name,
                       struct DIALAppCallbacks *callbacks,
-                      void *callback_data);
+                      void *callback_data, int useAdditionalData,
+                      const char* corsAllowedOrigin);
 
 /*
  * Unregsiter an application
diff --git a/src/server/main.c b/src/server/main.c
index aec96c5..275cae6 100644
--- a/src/server/main.c
+++ b/src/server/main.c
@@ -1,25 +1,25 @@
 /*
  * Copyright (c) 2012 Netflix, Inc.
  * All rights reserved.
- * 
- * Redistribution and use in source and binary forms, with or without 
+ *
+ * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
- * 
+ *
  * Redistributions of source code must retain the above copyright notice, this
  * list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright notice, 
- * this list of conditions and the following disclaimer in the documentation 
+ * 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 NETFLIX, INC. AND CONTRIBUTORS "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 NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY 
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
+ * 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 NETFLIX OR CONTRIBUTORS 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 
+ * 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.
  */
 
@@ -38,7 +38,7 @@
 #include "dial_server.h"
 #include "dial_options.h"
 
-#define BUFSIZE 256 
+#define BUFSIZE 256
 
 static char *spDefaultFriendlyName = "Google Fiber TV Box";
 static char *spDefaultModelName = "GFHD100";
@@ -83,8 +83,8 @@
 }
 
 
-/* The URL encoding source code was obtained here: 
- * http://www.geekhideout.com/urlcode.shtml 
+/* The URL encoding source code was obtained here:
+ * http://www.geekhideout.com/urlcode.shtml
  */
 
 /* Converts a hex character to its integer value */
@@ -109,11 +109,11 @@
     if( buf )
     {
         while (*pstr) {
-            if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == '~') 
+            if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == '~')
                 *pbuf++ = *pstr;
-            else if (*pstr == ' ') 
+            else if (*pstr == ' ')
                 *pbuf++ = '+';
-            else 
+            else
                 *pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex(*pstr & 15);
             pstr++;
         }
@@ -238,9 +238,9 @@
  * If they match, return false
  * If they don't match, return true
  */
-static int shouldRelaunch( 
-    DIALServer *pServer, 
-    const char *pAppName, 
+static int shouldRelaunch(
+    DIALServer *pServer,
+    const char *pAppName,
     const char *args )
 {
     return ( strncmp( DIAL_get_payload(pServer, pAppName), args, DIAL_MAX_PAYLOAD ) != 0 );
@@ -249,7 +249,7 @@
 static DIALStatus youtube_start(DIALServer *ds, const char *appname,
                                 const char *args, size_t arglen,
                                 DIAL_run_t *run_id, void *callback_data) {
-    fprintf(stderr, "** LAUNCH YouTube **\n");
+    fprintf(stderr, "** LAUNCH YouTube ** with args %s\n\n", args);
 
     char url[512] = {0,};
     int urlLength = snprintf( url, sizeof(url), "https://www.youtube.com/tv?%s", args);
@@ -309,7 +309,7 @@
     }
 
     fprintf(stderr, "appPid = %s, shouldRelaunch = %s queryParams = %s\n",
-          appPid?"TRUE":"FALSE", 
+          appPid?"TRUE":"FALSE",
           shouldRelaunchApp?"TRUE":"FALSE",
           sQueryParam );
 
@@ -327,6 +327,7 @@
                                  DIAL_run_t run_id, int* pCanStop, void *callback_data) {
     // Netflix application can stop
     *pCanStop = 1;
+
     return isAppRunning( spAppNetflix, NULL ) ? kDIALStatusRunning : kDIALStatusStopped;
 }
 
@@ -423,24 +424,27 @@
 void runDial(void)
 {
     DIALServer *ds;
-    ds = DIAL_start();
+    ds = DIAL_create();
     struct DIALAppCallbacks cb_nf = {netflix_start, netflix_stop, netflix_status, NULL, NULL};
     struct DIALAppCallbacks cb_yt = {youtube_start, youtube_stop, youtube_status, NULL, NULL};
     struct DIALAppCallbacks cb_ft = {fibertv_start, fibertv_stop, fibertv_status, NULL, NULL};
     struct DIALAppCallbacks cb_gf = {basil_start, basil_stop, basil_status, basil_service_data, NULL};
 
-    DIAL_register_app(ds, "Netflix", &cb_nf, NULL);
-    DIAL_register_app(ds, "YouTube", &cb_yt, NULL);
+    DIAL_register_app(ds, "Netflix", &cb_nf, NULL, 1, "netflix.com");
+    DIAL_register_app(ds, "YouTube", &cb_yt, NULL, 1, "youtube.com");
     if (strcmp(spUiType, "oregano") == 0) {
-      DIAL_register_app(ds, "GoogleFiberTV", &cb_gf, NULL);
+      DIAL_register_app(ds, "GoogleFiberTV", &cb_gf, NULL, 0, NULL);
     } else {
-      DIAL_register_app(ds, "FiberTV", &cb_ft, NULL);
+      DIAL_register_app(ds, "FiberTV", &cb_ft, NULL, 0, NULL);
     }
+    DIAL_start(ds);
+
     gDialPort = DIAL_get_port(ds);
     fprintf(stderr, "launcher listening on gDialPort %d\n", gDialPort);
     run_ssdp(gDialPort, spFriendlyName, spModelName, spUuid, &spIpAddress);
 
     DIAL_stop(ds);
+    free(ds);
 }
 
 static void processOption( int index, char * pOption )
@@ -448,7 +452,7 @@
     switch(index)
     {
         case 0: // Friendly name
-            setValue( pOption, spFriendlyName ); 
+            setValue( pOption, spFriendlyName );
             break;
         case 1: // Model Name
             setValue( pOption, spModelName );
@@ -466,8 +470,9 @@
     }
 }
 
-int main(int argc, char* argv[]) 
+int main(int argc, char* argv[])
 {
+    srand(time(NULL));
     int i;
 
     // Set stdout to unbuffered mode to avoid delayed logs
@@ -495,7 +500,7 @@
             int shortLen, longLen;
             shortLen = strlen(gDialOptions[numberOfOptions].pOption);
             longLen = strlen(gDialOptions[numberOfOptions].pLongOption);
-            if( ( ( strncmp( argv[i], gDialOptions[numberOfOptions].pOption, shortLen ) == 0 ) || 
+            if( ( ( strncmp( argv[i], gDialOptions[numberOfOptions].pOption, shortLen ) == 0 ) ||
                 ( strncmp( argv[i], gDialOptions[numberOfOptions].pLongOption, longLen ) == 0 ) ) &&
                 ( (i+1) < argc ) )
             {
diff --git a/src/server/makefile b/src/server/makefile
index c269204..8b4d663 100644
--- a/src/server/makefile
+++ b/src/server/makefile
@@ -1,18 +1,25 @@
-CC=$(TARGET)gcc

-

-.PHONY: clean

-.DEFAULT_GOAL=dialserver

-

-OBJS := main.o dial_server.o mongoose.o quick_ssdp.o

-HEADERS := $(wildcard *.h)

-

-%.c: $(HEADERS)

-

-%.o: %.c $(HEADERS)

-	$(CC) -Wall -Werror -g -std=gnu99 $(CFLAGS) -c $*.c -o $*.o

-

-dialserver: $(OBJS)

-	$(CC) -Wall -Werror -g $(OBJS) -ldl -lpthread -o dialserver

-

-clean:

-	rm -f *.o dialserver

+CC=$(TARGET)gcc
+
+.PHONY: clean
+.DEFAULT_GOAL=all
+
+OBJS := main.o dial_server.o mongoose.o quick_ssdp.o url_lib.o dial_data.o
+HEADERS := $(wildcard *.h)
+
+%.c: $(HEADERS)
+
+%.o: %.c $(HEADERS)
+	$(CC) -Wall -Werror -g -std=gnu99 $(CFLAGS) -c $*.c -o $*.o
+
+all: dialserver test
+
+dialserver: $(OBJS)
+	$(CC) -Wall -Werror -g $(OBJS) -ldl -lpthread -o dialserver
+
+test:
+	make -C tests
+	./tests/run_tests
+
+clean:
+	rm -f *.o dialserver
+	make -C tests clean
diff --git a/src/server/mongoose.c b/src/server/mongoose.c
index 14b4980..a8957ce 100644
--- a/src/server/mongoose.c
+++ b/src/server/mongoose.c
@@ -141,7 +141,7 @@
   // same way string option can.

   conn->request_info.log_message = buf;

   if (call_user(conn, MG_EVENT_LOG) == NULL) {

-    DEBUG_TRACE((buf));

+      DEBUG_TRACE(("[%s]", buf));

   }

   conn->request_info.log_message = NULL;

 }

@@ -385,7 +385,7 @@
 

   assert((conn->content_len == -1 && conn->consumed_content == 0) ||

          conn->consumed_content <= conn->content_len);

-  DEBUG_TRACE(("%p %zu %lld %lld", buf, len,

+  DEBUG_TRACE(("%p %zu %" PRId64 " %" PRId64, buf, len,

                conn->content_len, conn->consumed_content));

   nread = 0;

   if (conn->consumed_content < conn->content_len) {

@@ -537,8 +537,9 @@
 }

 

 static int is_valid_http_method(const char *method) {

+  fprintf(stderr, "Received HTTP method %s\n", method);

   return !strcmp(method, "GET") || !strcmp(method, "POST") ||

-    !strcmp(method, "DELETE");

+    !strcmp(method, "DELETE") || !strcmp(method, "OPTIONS");

 }

 

 // Parse HTTP request, fill in mg_request_info structure.

diff --git a/src/server/quick_ssdp.c b/src/server/quick_ssdp.c
index 068362a..064913e 100644
--- a/src/server/quick_ssdp.c
+++ b/src/server/quick_ssdp.c
@@ -1,25 +1,25 @@
 /*
  * Copyright (c) 2012 Netflix, Inc.
  * All rights reserved.
- * 
- * Redistribution and use in source and binary forms, with or without 
+ *
+ * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
- * 
+ *
  * Redistributions of source code must retain the above copyright notice, this
  * list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright notice, 
- * this list of conditions and the following disclaimer in the documentation 
+ * 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 NETFLIX, INC. AND CONTRIBUTORS "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 NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY 
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
+ * 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 NETFLIX OR CONTRIBUTORS 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 
+ * 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.
  */
 
@@ -74,7 +74,8 @@
                          "SERVER: Linux/2.6 UPnP/1.0 quick_ssdp/1.0\r\n"
                          "ST: urn:dial-multiscreen-org:service:dial:1\r\n"
                          "USN: uuid:%s::"
-                           "urn:dial-multiscreen-org:service:dial:1\r\n\r\n";
+                         "urn:dial-multiscreen-org:service:dial:1\r\n\r\n";
+
 
 static char ip_addr[INET_ADDRSTRLEN] = "127.0.0.1";
 static int dial_port = 0;
@@ -179,7 +180,7 @@
     gBuf[bytes] = 0;
 
     // sophisticated SSDP parsing algorithm
-    if (!strstr(gBuf, "ST: urn:dial-multiscreen-org:service:dial:1") &&
+    if (!strstr(gBuf, "urn:dial-multiscreen-org:service:dial:1") &&
         !strstr(gBuf, "ST: urn:dial-multiscreen-org:device:dial:1"))
     {
 #if 0  // use for debugging
diff --git a/src/server/tests/makefile b/src/server/tests/makefile
new file mode 100644
index 0000000..7cdfe01
--- /dev/null
+++ b/src/server/tests/makefile
@@ -0,0 +1,18 @@
+CC=$(TARGET)gcc

+

+.PHONY: clean

+.DEFAULT_GOAL=test

+

+OBJS := test_dial_data.o test_url_lib.o ../url_lib.o ../dial_data.o run_tests.o

+HEADERS := $(wildcard ../*.h)

+

+%.c: $(HEADERS)

+

+%.o: %.c $(HEADERS)

+	$(CC) -Wall -Werror -g -std=gnu99 $(CFLAGS) -c $*.c -o $*.o

+

+test: $(OBJS)

+	$(CC) -Wall -Werror -g $(OBJS) -ldl -lpthread -o run_tests

+

+clean:

+	rm -f *.o run_tests

diff --git a/src/server/tests/run_tests.c b/src/server/tests/run_tests.c
new file mode 100644
index 0000000..6fe3ef2
--- /dev/null
+++ b/src/server/tests/run_tests.c
@@ -0,0 +1,18 @@
+#include "test_dial_data.h"
+#include "test_url_lib.h"
+
+#include <stdio.h>
+
+
+int main(int argc, char** argv) {
+  printf("====\n");
+  test_smartstrcat();
+  test_urldecode();
+  test_parse_app_name();
+  test_parse_params();
+  printf("====\n");
+  test_read_dial_data();
+  test_write_dial_data();
+  printf("====\n");
+  return 0;
+}
diff --git a/src/server/tests/test.h b/src/server/tests/test.h
new file mode 100644
index 0000000..e2ad1ad
--- /dev/null
+++ b/src/server/tests/test.h
@@ -0,0 +1,29 @@
+// Macros to simplify testing functions.
+
+#ifndef SRC_SERVER_TESTS_TEST_H_
+#define SRC_SERVER_TESTS_TEST_H_
+
+#define EXPECT(a, m) \
+  do { \
+    if (!a) { \
+      printf("[%s] failed: %s\n", #a, m); \
+      printf("%s -> FAILED\n", __func__); \
+      return; \
+    } \
+  } while (0)
+
+#define EXPECT_STREQ(a, b) \
+  do { \
+    if (strcmp(a, b)) { \
+      printf("expected [%s == %s]\n", #a, #b); \
+      printf(" a = \"%s\"\n", a); \
+      printf(" b = \"%s\"\n", b); \
+      printf("%s -> FAILED\n", __func__); \
+      return; \
+    } \
+  } while (0)
+
+#define DONE() \
+  printf("%s -> OK\n", __func__)
+
+#endif /* SRC_SERVER_TESTS_TEST_H_ */
diff --git a/src/server/tests/test_dial_data.c b/src/server/tests/test_dial_data.c
new file mode 100644
index 0000000..4998084
--- /dev/null
+++ b/src/server/tests/test_dial_data.c
@@ -0,0 +1,42 @@
+#include "../dial_data.h"
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "test.h"
+
+int key_value_pairs = 3;
+char *keys[] = {"key1", "key2", "key3"};
+char *values[] = {"value1", "value2", "value3"};
+
+void test_read_dial_data() {
+    DIALData *data = retrieve_dial_data("dial_data");
+    for (int i = 0; data != NULL; data = data->next, i++) {
+        EXPECT_STREQ(data->key, keys[2 - i]);
+        EXPECT_STREQ(data->value, values[2 - i]);
+    }
+    DONE();
+}
+
+void test_write_dial_data() {
+    DIALData *result = NULL;
+    for (int i = 0; i < key_value_pairs; ++i) {
+        DIALData *node = (DIALData *) malloc(sizeof(DIALData));
+        node->key = keys[i];
+        node->value = values[i];
+        node->next = result;
+        result = node;
+    }
+    store_dial_data("YouTube", result);
+
+    DIALData *readBack = retrieve_dial_data("YouTube");
+
+    for (int i = 0; readBack != NULL; readBack = readBack->next, i++) {
+        EXPECT_STREQ(readBack->key, keys[i]);
+        EXPECT_STREQ(readBack->value, values[i]);
+    }
+
+    DONE();
+}
diff --git a/src/server/tests/test_dial_data.h b/src/server/tests/test_dial_data.h
new file mode 100644
index 0000000..c8fa06d
--- /dev/null
+++ b/src/server/tests/test_dial_data.h
@@ -0,0 +1,7 @@
+#ifndef SRC_SERVER_TESTS_TEST_DIAL_DATA_H_
+#define SRC_SERVER_TESTS_TEST_DIAL_DATA_H_
+
+void test_read_dial_data();
+void test_write_dial_data();
+
+#endif /* SRC_SERVER_TESTS_TEST_DIAL_DATA_H_ */
diff --git a/src/server/tests/test_url_lib.c b/src/server/tests/test_url_lib.c
new file mode 100644
index 0000000..d89d38d
--- /dev/null
+++ b/src/server/tests/test_url_lib.c
@@ -0,0 +1,89 @@
+#include "../url_lib.h"
+#include "../dial_data.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include "test.h"
+
+void test_smartstrcat() {
+    char* src1 = "Hello ";
+    char* src2 = "world!";
+    char* src3 = " Trunc ated";
+    char dest[128] = {0, };
+
+    char* p = (char *) dest;
+    p = smartstrcat(p, src1, 128);
+    EXPECT_STREQ(dest, "Hello ");
+    p = smartstrcat(p, src2, dest + 128 - p);
+
+    EXPECT_STREQ(dest, "Hello world!");
+
+    p = smartstrcat(p, src3, 6);
+    EXPECT_STREQ(dest, "Hello world! Trunc");
+
+    DONE();
+}
+
+void test_urldecode() {
+    char* param = "%26bla+r";
+    char dest[128] = {0, };
+
+    EXPECT(urldecode(dest, param, 128), "Failed to decode.");
+    EXPECT_STREQ(dest, "&bla r");
+    DONE();
+}
+
+void test_parse_app_name() {
+    char *app_name;
+    EXPECT((app_name = parse_app_name(NULL)), "Failed to extract app_name");
+    EXPECT_STREQ(app_name, "unknown");
+    EXPECT((app_name = parse_app_name("")), "Failed to extract app_name");
+    EXPECT_STREQ(app_name, "unknown");
+    EXPECT((app_name = parse_app_name("/")), "Failed to extract app_name");
+    EXPECT_STREQ(app_name, "unknown");
+    EXPECT((app_name = parse_app_name("/apps/YouTube/DialData")),
+           "Failed to extract app_name");
+    EXPECT_STREQ(app_name, "YouTube");
+    EXPECT((app_name = parse_app_name("//")), "Failed to extract app_name");
+    EXPECT_STREQ(app_name, "");
+    EXPECT((app_name = parse_app_name("/invalid")),
+           "Failed to extract app_name");
+    EXPECT_STREQ(app_name, "unknown");
+
+    DONE();
+}
+
+void test_parse_params() {
+    EXPECT(!parse_params(""), "Empty query string should generate no params");
+    EXPECT(!parse_params(NULL), "Null query, should generate no params");
+
+    DIALData *result = parse_params("a=b");
+    EXPECT_STREQ(result->key, "a");
+    EXPECT_STREQ(result->value, "b");
+
+    result = parse_params("?a=b");
+    EXPECT_STREQ(result->key, "a");
+    EXPECT_STREQ(result->value, "b");
+
+    result = parse_params("?a=b&c=d");
+    EXPECT_STREQ(result->key, "c");
+    EXPECT_STREQ(result->value, "d");
+    EXPECT_STREQ(result->next->key, "a");
+    EXPECT_STREQ(result->next->value, "b");
+
+    char query_string[1024] = {0, };
+    char *current = query_string;
+    for (int i = 0; i < 25; ++i) {
+        current = smartstrcat(current, "a=b&", 256);
+    }
+    result = parse_params(query_string);
+    int length = 0;
+    for (; result != NULL; result = result->next) {
+        length++;
+    }
+    EXPECT((length == 25), "25 params should have been parsed");
+
+    DONE();
+}
diff --git a/src/server/tests/test_url_lib.h b/src/server/tests/test_url_lib.h
new file mode 100644
index 0000000..43fe19f
--- /dev/null
+++ b/src/server/tests/test_url_lib.h
@@ -0,0 +1,10 @@
+#ifndef SRC_SERVER_TESTS_TEST_URL_LIB_H_
+#define SRC_SERVER_TESTS_TEST_URL_LIB_H_
+
+void test_smartstrcat();
+void test_urldecode();
+void test_parse_app_name();
+void test_parse_param();
+void test_parse_params();
+
+#endif /* SRC_SERVER_TESTS_TEST_URL_LIB_H_ */
diff --git a/src/server/url_lib.c b/src/server/url_lib.c
new file mode 100644
index 0000000..78313e3
--- /dev/null
+++ b/src/server/url_lib.c
@@ -0,0 +1,158 @@
+#include "url_lib.h"
+#include "dial_data.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+
+char* smartstrcat(char* dest, char* src, size_t max_chars) {
+    size_t copied = 0;
+    while ((*(dest++) = *(src++)) && copied++ < max_chars) {
+    }
+    // In case we over-stepped the size, end the string nicely.
+    *(--dest) = '\0';
+    return dest;
+}
+
+static int append_char_from_hex(char* dest, char a, char b) {
+    if ('a' <= a && a <= 'f')
+        a = 10 + a - 'a';
+    else if ('A' <= a && a <= 'F')
+        a = 10 + a - 'A';
+    else if ('0' <= a && a <= '9')
+        a = a - '0';
+    else
+        return 0;
+
+    if ('a' <= b && b <= 'f')
+        b = 10 + b - 'a';
+    else if ('A' <= b && b <= 'F')
+        b = 10 + b - 'A';
+    else if ('0' <= b && b <= '9')
+        b = b - '0';
+    else
+        return 0;
+
+    *dest = (char) (16 * a) + b;
+    return 1;
+}
+
+int urldecode(char *dst, const char *src, size_t max_size) {
+    size_t len = 0;
+
+    while (len < max_size && *src) {
+        if (*src == '+') {
+            *dst = ' ';
+        } else if (*src == '%') {
+            if (!*(++src) || !*(++src) || !append_char_from_hex(dst, *(src - 1), *src)) {
+                *dst = '\0';
+                return 0;
+            }
+        } else {
+            *dst = *src;
+        }
+        ++dst;
+        ++src;
+        ++len;
+    }
+    *dst = '\0';
+    return len;
+}
+
+void xmlencode(char *dst, const char *src, size_t max_size) {
+    size_t current_size = 0;
+    while (*src && current_size < max_size) {
+        switch (*src) {
+            case '&':
+                dst = smartstrcat(dst, "&amp;", max_size - current_size);
+                current_size += 5;
+                break;
+            case '\"':
+                dst = smartstrcat(dst, "&quot;", max_size - current_size);
+                current_size += 6;
+                break;
+            case '\'':
+                dst = smartstrcat(dst, "&apos;", max_size - current_size);
+                current_size += 6;
+                break;
+            case '<':
+                dst = smartstrcat(dst, "&lt;", max_size - current_size);
+                current_size += 4;
+                break;
+            case '>':
+                dst = smartstrcat(dst, "&gt;", max_size - current_size);
+                current_size += 4;
+                break;
+            default:
+                *dst++ = *src;
+                current_size++;
+                break;
+        }
+        src++;
+    }
+    *dst = '\0';
+}
+
+char *parse_app_name(const char *uri) {
+    if (uri == NULL) {
+        return "unknown";
+    }
+    char *slash = strrchr(uri, '/');
+    if (slash == NULL || slash == uri) {
+        return "unknown";
+    }
+    char *begin = slash;
+    while ((begin != uri) && (*--begin != '/'))
+        ;
+    begin++;  // skip the slash
+    char *result = (char *) calloc(1, slash - begin);
+    strncpy(result, begin, slash - begin);
+    return result;
+}
+
+char *parse_param(char *query_string, char *param_name) {
+    if (query_string == NULL) {
+        return NULL;
+    }
+    char * start;
+    if ((start = strstr(query_string, param_name)) == NULL) {
+        return NULL;
+    }
+    while (*start && (*start++ != '='))
+        ;
+    char *end = start;
+    while (*end && (*end != '&'))
+        end++;
+    int result_size = end - start;
+    char *result = malloc(result_size + 1);
+    result[0] = '\0';
+    strncpy(result, start, result_size);
+    result[result_size] = '\0';
+    return result;
+}
+
+DIALData *parse_params(char * query_string) {
+    if (query_string == NULL || strlen(query_string) <= 2) {
+        return NULL;
+    }
+    if (query_string[0] == '?') {
+        query_string++;  // skip leading question mark
+    }
+    DIALData *result = NULL;
+    char *query_string_dup = strdup(query_string);
+    char * name_value = strtok(query_string_dup, "&");
+    while (name_value != NULL) {
+        DIALData *tmp = (DIALData *) malloc(sizeof(DIALData));
+        size_t name_value_length = strlen(name_value);
+        tmp->key = (char *) malloc(name_value_length);
+        tmp->value = (char *) malloc(name_value_length);
+        sscanf(name_value, "%[^=]=%s", tmp->key, tmp->value);
+        tmp->next = result;
+        result = tmp;
+
+        name_value = strtok(NULL, "&");  // read next token
+    }
+    free(query_string_dup);
+    return result;
+}
diff --git a/src/server/url_lib.h b/src/server/url_lib.h
new file mode 100644
index 0000000..00781a8
--- /dev/null
+++ b/src/server/url_lib.h
@@ -0,0 +1,37 @@
+/* Utility functions for dealing with URLs */
+
+#ifndef URLLIB_H_
+#define URLLIB_H_
+
+#include "dial_data.h"
+#include <stddef.h>
+
+/**
+ * Concatenate a maxim of max_chars characters from src into dest,
+ * and return a pointer to the last character in dest.
+ */
+char* smartstrcat(char* dest, char* src, size_t max_chars);
+
+int urldecode(char *dst, const char *src, size_t max_size);
+
+void xmlencode(char *dst, const char *src, size_t max_size);
+
+/**
+ * Parse the value of the parameter with the given name from a query string.
+ */
+char *parse_param(char *query_string, char *param_name);
+
+/*
+ * Parse the application name out of a URI, such as /app/YouTube/dial_data.
+ * Note: this parser assumes that the dial_data url is of the form
+ * /apps/<app_name>/dial_data. If your DIAL server uses a different url-path,
+ * you will need to adapt the method below.
+ */
+char *parse_app_name(const char *uri);
+
+/*
+ * Parse a list of DIALData params out of a query string.
+ */
+DIALData *parse_params(char * query_string);
+
+#endif  // URLLIB_H_