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, "&", max_size - current_size);
+ current_size += 5;
+ break;
+ case '\"':
+ dst = smartstrcat(dst, """, max_size - current_size);
+ current_size += 6;
+ break;
+ case '\'':
+ dst = smartstrcat(dst, "'", max_size - current_size);
+ current_size += 6;
+ break;
+ case '<':
+ dst = smartstrcat(dst, "<", max_size - current_size);
+ current_size += 4;
+ break;
+ case '>':
+ dst = smartstrcat(dst, ">", 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_