diff --git a/README.md b/README.md index c14437e..9f4d269 100644 --- a/README.md +++ b/README.md @@ -123,3 +123,19 @@ Options: ``` Log file of test run is written in js_tests_log.txt in the server/tests/js_tests/tests folder. + +This reference does not provide code for sleeping an app, so +tests that involve sleeping an app will fail. + +There is also a test suite for lightly testing how the implementation handles edge cases. +this can be run with: +``` +server/tests/js_tests/tests$ node testEdgeCases.js + +Usage: node testEdgeCases.js[options] + +Options: + --host IP address of host on which DIAL server under test is running + [string] [required] + --help, -h Show help [boolean] +``` diff --git a/server/LinuxInterfaces.c b/server/LinuxInterfaces.c deleted file mode 100644 index 0225e3e..0000000 --- a/server/LinuxInterfaces.c +++ /dev/null @@ -1,80 +0,0 @@ -#include "LinuxInterfaces.h" -#include -#include -#include - -/* Fetch the MACAddress of a network interface */ -int getMACAddress(char* ifname, char* macBuf, unsigned int macBufSize) -{ - struct ifreq ifr; - int sock; - unsigned char chMAC[6]; - - sock=socket(AF_INET,SOCK_DGRAM,0); - strcpy( ifr.ifr_name, ifname ); - ifr.ifr_addr.sa_family = AF_INET; - if (ioctl( sock, SIOCGIFHWADDR, &ifr ) < 0) { - close(sock); - return -1; - } - memcpy(chMAC, ifr.ifr_hwaddr.sa_data, 6); - if (macBufSize<32){ - close(sock); - return -1; - }else{ - sprintf(macBuf,"%02X:%02X:%02X:%02X:%02X:%02X",chMAC[0],chMAC[1],chMAC[2],chMAC[3],chMAC[4],chMAC[5]); - } - close(sock); - return 0; -} - -bool hasRoute(const char *nf, int flag) -{ - bool result = false; - const char* fileName = "/proc/net/route"; - int r; - FILE* fp = fopen(fileName, "r"); - - if (fp) { - char line[1024]; - int nfBegin = 0, nfEnd = 0, nfFlag = 0; - const int nfLen = strlen(nf); - while (!feof(fp)) { - if (!fgets(line, sizeof(line), fp)) - break; - r = sscanf(line, "%n%*s%n %*s %*s %d %*s %*s %*s %*s %*s %*s %*s", &nfBegin, &nfEnd, &nfFlag); - if (r == 1 && nfBegin == 0 && nfEnd == nfLen && !strncmp(nf, line, nfLen) && (flag == -1 || nfFlag == flag)) { - result = true; - break; - } - } - fclose(fp); - } - return result; -} - -bool isDefault(char *nf) -{ - return hasRoute(nf, 0x03); -} - -NetInterface getDefaultNetworkInterfaces() -{ - DIR *d; - struct dirent *dir; - d = opendir("/sys/class/net/"); - NetInterface defaultNet; - if (d){ - while ((dir = readdir(d)) != NULL){ - if(strcmp(dir->d_name,".") && strcmp(dir->d_name,"..")){ - if (hasRoute(dir->d_name, -1) && isDefault(dir->d_name)){ - getMACAddress(dir->d_name, defaultNet.macAddress, MACBUFSIZE); - break; - } - } - } - closedir(d); - } - return defaultNet; -} - diff --git a/server/LinuxInterfaces.h b/server/LinuxInterfaces.h deleted file mode 100644 index f552cbb..0000000 --- a/server/LinuxInterfaces.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef LINUXINTERFACES_H_ -#define LINUXINTERFACES_H_ - -#ifndef __APPLE__ -#include -#include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include - -/* Fetch the MACAddress of a network interface */ -#define MACBUFSIZE 32 -#define WAKEUPTIMEOUT 15 - -typedef struct -{ - char macAddress[MACBUFSIZE]; -}NetInterface; - -NetInterface getDefaultNetworkInterfaces(); - -#endif diff --git a/server/dial_data.c b/server/dial_data.c index 2a8c6c3..45c4dcd 100644 --- a/server/dial_data.c +++ b/server/dial_data.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Netflix, Inc. + * Copyright (c) 2014-2019 Netflix, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,50 +40,90 @@ void set_dial_data_dir(const char *data_dir) { /** * Returns the path where data is stored for the given app. + * + * The DIAL data directory must have been already set. + * + * @param app_name application name. + * @return the location of the application path within the DIAL data + * directory or NULL if memory could not be allocated. + * @see set_dial_data_dir(const char*) */ static char* getAppPath(char *app_name) { size_t name_size = strlen(app_name) + sizeof(dial_data_dir) + 1; char* filename = (char*) malloc(name_size); + if (filename == NULL) { + return NULL; + } 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) { +void store_dial_data(char *app_name, DIALData *data) { char* filename = getAppPath(app_name); + if (filename == NULL) { + printf("Cannot open DIAL data output file, out-of-memory."); + exit(1); + } FILE *f = fopen(filename, "w"); + free(filename); filename = NULL; if (f == NULL) { printf("Cannot open DIAL data output file: %s\n", filename); exit(1); } - free(filename); for (DIALData *first = data; first != NULL; first = first->next) { - fprintf(f, "%s %s\n", first->key, first->value); + // truncate because we have limits on length when retrieving. + fprintf(f, "%.*s %.*s\n", DIAL_KEY_OR_VALUE_MAX_LEN, first->key, DIAL_KEY_OR_VALUE_MAX_LEN, first->value); } fclose(f); } DIALData *retrieve_dial_data(char *app_name) { char* filename = getAppPath(app_name); + if (filename == NULL) { + return NULL; // no dial data found, that's fine + } FILE *f = fopen(filename, "r"); - free(filename); + free(filename); filename = NULL; 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) { + char key[DIAL_KEY_OR_VALUE_MAX_LEN + 1] = {0,}; + char value[DIAL_KEY_OR_VALUE_MAX_LEN + 1] = {0,}; + int err = 0; + while (fscanf(f, "%" DIAL_KEY_OR_VALUE_MAX_LEN_STR "s %" DIAL_KEY_OR_VALUE_MAX_LEN_STR "s\n", key, value) != EOF) { DIALData *newNode = (DIALData *) malloc(sizeof(DIALData)); - newNode->key = (char *) calloc(1, strlen(key)); + if (newNode == NULL) { + err = 1; + break; + } + + newNode->key = (char *) calloc(strlen(key) + 1, sizeof(char)); + if (newNode->key == NULL) { + err = 1; + free(newNode); newNode = NULL; + break; + } strncpy(newNode->key, key, strlen(key)); - newNode->value = (char *) calloc(1, strlen(value)); + + newNode->value = (char *) calloc(strlen(value) + 1, sizeof(char)); + if (newNode->value == NULL) { + err = 1; + free(newNode->key); newNode->key = NULL; + free(newNode); newNode = NULL; + break; + } strncpy(newNode->value, value, strlen(value)); newNode->next = result; result = newNode; } fclose(f); + if (err) { + free_dial_data(&result); + result = NULL; + } return result; } @@ -92,10 +132,10 @@ void free_dial_data(DIALData **dialData) DIALData *curNode=NULL; while (*dialData != NULL) { curNode = *dialData; - *dialData =curNode->next; + *dialData = curNode->next; - free(curNode->key); - free(curNode->value); - free(curNode); + free(curNode->key); curNode->key = NULL; + free(curNode->value); curNode->value = NULL; + free(curNode); curNode = NULL; } } diff --git a/server/dial_data.h b/server/dial_data.h index 55a5c7d..a075953 100644 --- a/server/dial_data.h +++ b/server/dial_data.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Netflix, Inc. + * Copyright (c) 2014-2019 Netflix, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,6 +46,13 @@ */ #define DIAL_DATA_URI "/dial_data" +/** + * The DIAL data key and value values cannot contain any spaces. They are + * expected to be URL-escaped strings, so any spaces would be represented as + * the '+' character. They have a max length of 255 characters. + * + * THE STRINGS key AND value POINT TO MUST BE DYNAMICALLY ALLOCATED + */ struct DIALData_ { struct DIALData_ *next; char *key; @@ -54,12 +61,45 @@ struct DIALData_ { typedef struct DIALData_ DIALData; +#define DIAL_KEY_OR_VALUE_MAX_LEN (255) +#define DIAL_KEY_OR_VALUE_MAX_LEN_STR "255" + +/** + * Store the DIAL data key/value pairs in the application data store. + * + * Will exit immediately if the data output file cannot be accessed due to + * out-of-memory or I/O errors. + * + * Keys and values are truncated to DIAL_KEY_OR_VALUE_MAX_LEN. + * + * @param app_name application name. + * @param data pointer to head of DIAL data linked list. + */ void store_dial_data(char *app_name, DIALData *data); +/** + * Retrieve the DIAL data key/value pairs from the application data store. + * + * @param app_name application name. + * @return data pointer to head of DIAL data linked list or NULL if + * there is no valid data or if the data output file cannot be accessed + * due to out-of-memory or I/O errors. + */ DIALData *retrieve_dial_data(char *app_name); +/** + * Set the DIAL data directory. + * + * @param data_dir the DIAL data directory path, which must include the + * trailing directory separator (e.g. '/' character). + */ void set_dial_data_dir(const char *data_dir); +/** + * Frees the DIAL data linked list memory. + * + * @param dialData pointer to the DIAL data linked list. + */ void free_dial_data(DIALData **dialData); #endif /* SRC_SERVER_DIAL_DATA_H_ */ diff --git a/server/dial_server.c b/server/dial_server.c index 8007c3d..48c0ddf 100644 --- a/server/dial_server.c +++ b/server/dial_server.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Netflix, Inc. + * Copyright (c) 2014-2019 Netflix, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,7 +37,6 @@ #include "mongoose.h" #include "url_lib.h" -#include "LinuxInterfaces.h" // TODO: Partners should define this port #define DIAL_PORT (56789) @@ -67,16 +66,42 @@ struct DIALServer_ { pthread_mutex_t mux; }; -static void ds_lock(DIALServer *ds) { - pthread_mutex_lock(&ds->mux); +/** + * Acquire the DIAL server mutex. + * + * @return 1 if acquisition succeeded, 0 if it failed. + */ +static int ds_lock(DIALServer *ds) { + int err = 0; + if ((err = pthread_mutex_lock(&ds->mux)) != 0) { + printf("Unable to acquire DS mutex: [%d]", err); + return 0; + } + return 1; } -static void ds_unlock(DIALServer *ds) { - pthread_mutex_unlock(&ds->mux); +/** + * Release the DIAL server mutex. + * + * @return 1 if release succeeded, 0 if it failed. + */ +static int ds_unlock(DIALServer *ds) { + int err = 0; + if ((err = pthread_mutex_unlock(&ds->mux)) != 0) { + printf("Unable to release DS mutex: [%d]", err); + return 0; + } + return 1; } -// finds an app and returns a pointer to the previous element's next pointer -// if not found, return a pointer to the last element's next pointer +/** + * Finds an application in the DIAL server application linked list. + * + * @param ds the DIAL server. + * @param app_name application name. + * @return a pointer to the DIAL application; the pointer value may be NULL if + * the application was not found. + */ static DIALApp **find_app(DIALServer *ds, const char *app_name) { DIALApp *app; DIALApp **ret = &ds->apps; @@ -89,16 +114,34 @@ static DIALApp **find_app(DIALServer *ds, const char *app_name) { return ret; } -static void url_decode_xml_encode(char *dst, char *src, size_t src_size) { +/** + * URL-unescape the string, then XML-escape it. + * + * @param dst the destination XML-escaped string. Must be large enough for + * the resulting string (technically as much as 6x the raw string + * length based on the implementation in url_lib.c). + * @param src the URL-escaped string. + * @param src_size size of the URL-escaped string, including trailing NULL. + * @return true if successful, false if out-of-memory. + */ +static int url_decode_xml_encode(char *dst, char *src, size_t src_size) { char *url_decoded_key = (char *) malloc(src_size + 1); + if (url_decoded_key == NULL) { + return 0; + } urldecode(url_decoded_key, src, src_size); xmlencode(dst, url_decoded_key, 2 * src_size); free(url_decoded_key); + return 1; } -/* - * A bad payload is defined to be an unprintable character or a - * non-ascii character. +/** + * Checks if a payload string contains invalid characters. + * + * @param pPayload payload string. + * @param numBytes length of payload string in bytes (excluding trailing NULL). + * + * @return 1 if the payload contains an unprintable or non-ASCII character. */ static int isBadPayload(const char* pPayload, int numBytes) { int i = 0; @@ -118,19 +161,21 @@ static void handle_app_start(struct mg_connection *conn, const struct mg_request_info *request_info, const char *app_name, const char *origin_header) { - char additional_data_param[256] = {0, }; + char additional_data_param[DIAL_MAX_ADDITIONALURL] = {0, }; char body[DIAL_MAX_PAYLOAD + sizeof(additional_data_param) + 2] = {0, }; DIALApp *app; DIALServer *ds = request_info->user_data; int body_size; - ds_lock(ds); + if (!ds_lock(ds)) { + mg_send_http_error(conn, 500, "500 Internal Server Error", "500 Internal Server Error"); + return; + } app = *find_app(ds, app_name); if (!app) { mg_send_http_error(conn, 404, "Not Found", "Not Found"); } else { - body_size = mg_read(conn, body, sizeof(body)); - // NUL-terminate it just in case + body_size = mg_read(conn, body, sizeof(body) - 1); if (body_size > DIAL_MAX_PAYLOAD) { mg_send_http_error(conn, 413, "413 Request Entity Too Large", "413 Request Entity Too Large"); @@ -145,7 +190,7 @@ static void handle_app_start(struct mg_connection *conn, if (app->useAdditionalData) { // Construct additionalDataUrl=http://host:port/apps/app_name/dial_data - sprintf(additional_data_param, + snprintf(additional_data_param, DIAL_MAX_ADDITIONALURL, "additionalDataUrl=http%%3A%%2F%%2Flocalhost%%3A%d%%2Fapps%%2F%s%%2Fdial_data%%3F", dial_port, app_name); } @@ -171,6 +216,8 @@ static void handle_app_start(struct mg_connection *conn, mg_send_http_error(conn, 403, "Forbidden", "Forbidden"); } else if (app->state == kDIALStatusErrorUnauth) { mg_send_http_error(conn, 401, "Unauthorized", "Unauthorized"); + } else if (app->state == kDIALStatusErrorNotImplemented) { + mg_send_http_error(conn, 501, "Not Implemented", "Not Implemented"); } else { mg_send_http_error(conn, 503, "Service Unavailable", "Service Unavailable"); @@ -196,7 +243,10 @@ static void handle_app_status(struct mg_connection *conn, free(clientVersionStr); } - ds_lock(ds); + if (!ds_lock(ds)) { + mg_send_http_error(conn, 500, "500 Internal Server Error", "500 Internal Server Error"); + return; + } app = *find_app(ds, app_name); if (!app) { mg_send_http_error(conn, 404, "Not Found", "Not Found"); @@ -209,23 +259,45 @@ static void handle_app_status(struct mg_connection *conn, char *p = dial_data; for (DIALData* first = app->dial_data; first != NULL; first = first->next) { - p = smartstrcat(p, " <", end - p); + p = smartstrncpy(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); + if (encoded_key == NULL) { + mg_send_http_error(conn, 500, "500 Internal Server Error", "500 Internal Server Error"); + ds_unlock(ds); + return; + } + if (!url_decode_xml_encode(encoded_key, first->key, key_length)) { + mg_send_http_error(conn, 500, "500 Internal Server Error", "500 Internal Server Error"); + free(encoded_key); encoded_key = NULL; + ds_unlock(ds); + return; + }; 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); + if (encoded_value == NULL) { + mg_send_http_error(conn, 500, "500 Internal Server Error", "500 Internal Server Error"); + free(encoded_key); encoded_key = NULL; + ds_unlock(ds); + return; + } + if (!url_decode_xml_encode(encoded_value, first->value, value_length)) { + mg_send_http_error(conn, 500, "500 Internal Server Error", "500 Internal Server Error"); + free(encoded_value); encoded_value = NULL; + free(encoded_key); encoded_key = NULL; + ds_unlock(ds); + return; + } - p = smartstrcat(p, encoded_key, end - p); - p = smartstrcat(p, ">", end - p); - p = smartstrcat(p, encoded_value, end - p); - p = smartstrcat(p, "", end - p); - free(encoded_key); - free(encoded_value); + p = smartstrncpy(p, encoded_key, end - p); + p = smartstrncpy(p, ">", end - p); + p = smartstrncpy(p, encoded_value, end - p); + p = smartstrncpy(p, "", end - p); + free(encoded_key); encoded_key = NULL; + free(encoded_value); encoded_value = NULL; } app->state = app->callbacks.status_cb(ds, app_name, app->run_id, &canStop, @@ -285,7 +357,10 @@ static void handle_app_stop(struct mg_connection *conn, DIALServer *ds = request_info->user_data; int canStop = 0; - ds_lock(ds); + if (!ds_lock(ds)) { + mg_send_http_error(conn, 500, "500 Internal Server Error", "500 Internal Server Error"); + return; + } // Special handling for system app if (strcmp(app_name, "system") == 0) { @@ -322,7 +397,10 @@ static void handle_app_hide(struct mg_connection *conn, DIALServer *ds = request_info->user_data; int canStop = 0; - ds_lock(ds); + if (!ds_lock(ds)) { + mg_send_http_error(conn, 500, "500 Internal Server Error", "500 Internal Server Error"); + return; + } app = *find_app(ds, app_name); // update the application state @@ -336,17 +414,17 @@ static void handle_app_hide(struct mg_connection *conn, } else { // not implemented in reference DIALStatus status = app->callbacks.hide_cb(ds, app_name, app->run_id, app->callback_data); - if (status!=kDIALStatusHide){ + if (status != kDIALStatusHide){ fprintf(stderr, "Hide not implemented for reference.\n"); mg_send_http_error(conn, 501, "Not Implemented", "Not Implemented"); - }else{ - app->state = kDIALStatusHide; - mg_printf(conn, "HTTP/1.1 200 OK\r\n" - "Content-Type: text/plain\r\n" - "Access-Control-Allow-Origin: %s\r\n" - "\r\n", - origin_header); + } else { + app->state = kDIALStatusHide; + mg_printf(conn, "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Access-Control-Allow-Origin: %s\r\n" + "\r\n", + origin_header); } } ds_unlock(ds); @@ -362,7 +440,10 @@ static void handle_dial_data(struct mg_connection *conn, DIALApp *app; DIALServer *ds = request_info->user_data; - ds_lock(ds); + if (!ds_lock(ds)) { + mg_send_http_error(conn, 500, "500 Internal Server Error", "500 Internal Server Error"); + return; + } app = *find_app(ds, app_name); if (!app) { mg_send_http_error(conn, 404, "Not Found", "Not Found"); @@ -372,21 +453,22 @@ static void handle_dial_data(struct mg_connection *conn, int nread; if (!use_payload) { if (request_info->query_string) { + int qs_len = strlen(request_info->query_string); + if (qs_len > DIAL_DATA_MAX_PAYLOAD) { + mg_send_http_error(conn, 413, "413 Request Entity Too Large", + "413 Request Entity Too Large"); + ds_unlock(ds); + return; + } strncpy(body, request_info->query_string, DIAL_DATA_MAX_PAYLOAD); - nread = strlen(body); + nread = qs_len; } 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; } + body[nread] = '\0'; if (isBadPayload(body, nread)) { mg_send_http_error(conn, 400, "400 Bad Request", "400 Bad Request"); @@ -399,6 +481,7 @@ static void handle_dial_data(struct mg_connection *conn, app->dial_data = parse_params(body); store_dial_data(app->name, app->dial_data); + free_dial_data(&app->dial_data); mg_printf(conn, "HTTP/1.1 200 OK\r\n" "Access-Control-Allow-Origin: %s\r\n" @@ -419,7 +502,7 @@ static int ends_with(const char *str, const char *suffix) { } -// str contains a white space separated list of strings (only supports SPACE characters for now) +// str contains a white space separated list of strings (only supports SPACE characters for now) static int ends_with_in_list (const char *str, const char *list) { if (!str || !list) return 0; @@ -448,12 +531,12 @@ static int ends_with_in_list (const char *str, const char *list) { substring[copyLength] = '\0'; //printf("found %s \n", substring); if (ends_with(str, substring)) { - free(substring); + free(substring); substring = NULL; return 1; } scanPointer = scanPointer + copyLength + 1; // assumption: only 1 character } - free(substring); + free(substring); substring = NULL; return ends_with(str, scanPointer); } @@ -472,7 +555,10 @@ static int is_allowed_origin(DIALServer* ds, char * origin, const char * app_nam return 1; } - ds_lock(ds); + if (!ds_lock(ds)) { + // If we can't check, fail in favor of safety. + return 0; + } DIALApp *app; int result = 0; for (app = ds->apps; app != NULL; app = app->next) { @@ -531,11 +617,16 @@ static void *request_handler(enum mg_event event, struct mg_connection *conn, fprintf(stderr, "Origin %s, Host: %s\n", origin_header, host_header); if (event == MG_NEW_REQUEST) { // 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 (strlen(request_info->uri) > strlen(RUN_URI) + strlen(APPS_URI) + && !strncmp(request_info->uri + strlen(request_info->uri) - strlen(RUN_URI), RUN_URI, strlen(RUN_URI))) + { + // Maximum app name length of 255 characters. + char app_name[256] = {0, }; + int appname_len = strlen(request_info->uri) - strlen(RUN_URI) - strlen(APPS_URI); + if (appname_len > 255) { + appname_len = 255; + } + strncpy(app_name, request_info->uri + strlen(APPS_URI), appname_len); if (!strcmp(request_info->request_method, "OPTIONS")) { return options_response(ds, conn, host_header, origin_header, app_name, "DELETE, OPTIONS"); @@ -543,7 +634,8 @@ static void *request_handler(enum mg_event event, struct mg_connection *conn, // DELETE non-empty app name if (app_name[0] != '\0' - && !strcmp(request_info->request_method, "DELETE")) { + && !strcmp(request_info->request_method, "DELETE")) + { if (host_header && is_allowed_origin(ds, origin_header, app_name)) { handle_app_stop(conn, request_info, app_name, origin_header); } else { @@ -556,10 +648,12 @@ static void *request_handler(enum mg_event event, struct mg_connection *conn, } } // 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), '/')) { + else if (strlen(request_info->uri) > strlen(APPS_URI) + && !strncmp(request_info->uri, APPS_URI, strlen(APPS_URI)) + && !strchr(request_info->uri + strlen(APPS_URI), '/')) + { const char *app_name; - app_name = request_info->uri + sizeof(APPS_URI) - 1; + app_name = request_info->uri + strlen(APPS_URI); if (!strcmp(request_info->request_method, "OPTIONS")) { return options_response(ds, conn, host_header, origin_header, app_name, "GET, POST, OPTIONS"); @@ -577,27 +671,29 @@ static void *request_handler(enum mg_event event, struct mg_connection *conn, } else if (!strcmp(request_info->request_method, "GET")) { 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 that ends with HIDE_URI - else if (!strncmp(request_info->uri + strlen(request_info->uri) - strlen(HIDE_URI), HIDE_URI, - strlen(HIDE_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) - strlen(RUN_URI) - strlen(HIDE_URI)) - (sizeof(APPS_URI) - 1))); + else if (strlen(request_info->uri) > strlen(HIDE_URI) + strlen(RUN_URI) + strlen(APPS_URI) + && !strncmp(request_info->uri + strlen(request_info->uri) - strlen(HIDE_URI), HIDE_URI, strlen(HIDE_URI))) + { + // Maximum app name length of 255 characters. + char app_name[256] = {0, }; + int appname_len = strlen(request_info->uri) - strlen(RUN_URI) - strlen(HIDE_URI) - strlen(APPS_URI); + if (appname_len > 255) { + appname_len = 255; + } + strncpy(app_name, request_info->uri + strlen(APPS_URI), appname_len); if (!strcmp(request_info->request_method, "OPTIONS")) { return options_response(ds, conn, host_header, origin_header, app_name, "POST, OPTIONS"); } - if (app_name[0] != '\0' - && !strcmp(request_info->request_method, "POST")) { + if (app_name[0] != '\0' && !strcmp(request_info->request_method, "POST")) { handle_app_hide(conn, request_info, app_name, origin_header); - }else{ - mg_send_http_error(conn, 501, "Not Implemented", - "Not Implemented"); + } else { + mg_send_http_error(conn, 501, "Not Implemented", "Not Implemented"); } } // URI is of the form */app_name/dial_data @@ -608,18 +704,20 @@ static void *request_handler(enum mg_event event, struct mg_connection *conn, 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); + if (app_name == NULL) { + mg_send_http_error(conn, 500, "Internal Error", "Internal Error"); + } else { + if (!strcmp(request_info->request_method, "OPTIONS")) { + void *ret = options_response(ds, conn, host_header, origin_header, app_name, "POST, OPTIONS"); + free(app_name); + return ret; + } + int use_payload = strcmp(request_info->request_method, "POST") ? 0 : 1; + handle_dial_data(conn, request_info, app_name, origin_header, + use_payload); - if (!strcmp(request_info->request_method, "OPTIONS")) { - void *ret = options_response(ds, conn, host_header, origin_header, app_name, "POST, OPTIONS"); free(app_name); - return ret; } - int use_payload = - strcmp(request_info->request_method, "POST") ? 0 : 1; - handle_dial_data(conn, request_info, app_name, origin_header, - use_payload); - - free(app_name); } else { // If the request is not from local host, return an error mg_send_http_error(conn, 403, "Forbidden", "Forbidden"); @@ -637,12 +735,19 @@ static void *request_handler(enum mg_event event, struct mg_connection *conn, DIALServer *DIAL_create() { DIALServer *ds = calloc(1, sizeof(DIALServer)); - pthread_mutex_init(&ds->mux, NULL); + if (ds == NULL) { + return NULL; + } + if (pthread_mutex_init(&ds->mux, NULL) != 0) { + free(ds); ds = NULL; + return NULL; + } return ds; } -void DIAL_start(DIALServer *ds) { +int DIAL_start(DIALServer *ds) { ds->ctx = mg_start(&request_handler, ds, DIAL_PORT); + return (ds->ctx != NULL); } void DIAL_stop(DIALServer *ds) { @@ -664,51 +769,60 @@ int DIAL_register_app(DIALServer *ds, const char *app_name, int useAdditionalData, const char* corsAllowedOrigin) { DIALApp **ptr, *app; - int ret; - ds_lock(ds); + if (!ds_lock(ds)) { + return -1; + } ptr = find_app(ds, app_name); if (*ptr != NULL) { // app already registered - ret = 0; + ds_unlock(ds); + return 0; } else { app = malloc(sizeof(DIALApp)); + if (app == NULL) { + ds_unlock(ds); + return -1; + } app->callbacks = *callbacks; app->name = strdup(app_name); + if (app->name == NULL) { + free(app); app = NULL; + ds_unlock(ds); + return -1; + } 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); + if (corsAllowedOrigin && strlen(corsAllowedOrigin) < sizeof(app->corsAllowedOrigin)) { + strcpy(app->corsAllowedOrigin, corsAllowedOrigin); } *ptr = app; - ret = 1; + ds_unlock(ds); + return 1; } - ds_unlock(ds); - return ret; } int DIAL_unregister_app(DIALServer *ds, const char *app_name) { DIALApp **ptr, *app; - int ret; - ds_lock(ds); + if (!ds_lock(ds)) { + return -1; + } ptr = find_app(ds, app_name); if (*ptr == NULL) { // no such app - ret = 0; + ds_unlock(ds); + return 0; } else { app = *ptr; *ptr = app->next; - free(app->name); - free(app); - ret = 1; + free(app->name); app->name = NULL; + free(app); app = NULL; + ds_unlock(ds); + return 1; } - - ds_unlock(ds); - return ret; } const char * DIAL_get_payload(DIALServer *ds, const char *app_name) { diff --git a/server/dial_server.h b/server/dial_server.h index 7890dcf..5913a08 100644 --- a/server/dial_server.h +++ b/server/dial_server.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Netflix, Inc. + * Copyright (c) 2014-2019 Netflix, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -59,7 +59,9 @@ typedef enum { #define DIAL_MAX_PAYLOAD (4096) /* - * The maximum additionalDataUrl length + * The maximum additionalDataUrl length. + * + * There is no value defined in the DIAL specification. */ #define DIAL_MAX_ADDITIONALURL (1024) @@ -120,8 +122,10 @@ DIALServer *DIAL_create(); * Starts the DIAL server. * * @param[in] ds DIAL server handle + * @return true if the DIAL server was started and the DIAL server handle + * now contains a valid Mongoose context. */ -void DIAL_start(DIALServer *ds); +int DIAL_start(DIALServer *ds); /* * Stop the DIAL server. @@ -141,7 +145,7 @@ void DIAL_stop(DIALServer *ds); * @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 + * @return 1 if successful, 0 if already registered, -1 on error. */ int DIAL_register_app(DIALServer *ds, const char *app_name, struct DIALAppCallbacks *callbacks, @@ -154,7 +158,7 @@ int DIAL_register_app(DIALServer *ds, const char *app_name, * @param[in] ds DIAL server handle * @param[in] app_name Name of the DIAL application * - * @return 1 if successful, 0 otherwise + * @return 1 if successful, 0 if not found, -1 on error. */ int DIAL_unregister_app(DIALServer *ds, const char *app_name); diff --git a/server/main.c b/server/main.c index fcc64c2..d515c1c 100644 --- a/server/main.c +++ b/server/main.c @@ -112,19 +112,19 @@ int isAppRunning( char *pzName, char *pzCommandPattern ) { struct dirent* procEntry; while((procEntry=readdir(proc_fd)) != NULL) { if( doesMatch( "^[0-9][0-9]*$", procEntry->d_name ) ) { - char exePath[64] = {0,}; + char exePath[384] = {0,}; char link[256] = {0,}; - char cmdlinePath[64] = {0,}; + char cmdlinePath[384] = {0,}; char buffer[1024] = {0,}; int len; - sprintf( exePath, "/proc/%s/exe", procEntry->d_name); - sprintf( cmdlinePath, "/proc/%s/cmdline", procEntry->d_name); + snprintf( exePath, sizeof(exePath), "/proc/%s/exe", procEntry->d_name); + snprintf( cmdlinePath, sizeof(cmdlinePath), "/proc/%s/cmdline", procEntry->d_name); if( (len = readlink( exePath, link, sizeof(link)-1)) != -1 ) { char executable[256] = {0,}; - strcat( executable, pzName ); + strncpy( executable, pzName, sizeof(executable) - 2 ); strcat( executable, "$" ); - // TODO: Make this search for EOL to prevent false positivies + // TODO: Make this search for EOL to prevent false positives if( !doesMatch( executable, link ) ) { continue; } @@ -147,9 +147,9 @@ int isAppRunning( char *pzName, char *pzCommandPattern ) { continue; } } - + int d_name = atoi(procEntry->d_name); closedir(proc_fd); - return atoi(procEntry->d_name); + return d_name; } } @@ -202,14 +202,14 @@ static DIALStatus youtube_start(DIALServer *ds, const char *appname, printf("\n\n ** LAUNCH YouTube ** with payload %s\n\n", payload); char url[512] = {0,}, data[512] = {0,}; - if (strlen(payload) && strlen(additionalDataUrl)){ - sprintf( url, "https://www.youtube.com/tv?%s&%s", payload, additionalDataUrl); - }else if (strlen(payload)){ - sprintf( url, "https://www.youtube.com/tv?%s", payload); - }else{ - sprintf( url, "https://www.youtube.com/tv"); + if (strlen(payload) && strlen(additionalDataUrl)) { + snprintf( url, sizeof(url), "https://www.youtube.com/tv?%s&%s", payload, additionalDataUrl); + } else if (strlen(payload)) { + snprintf( url, sizeof(url), "https://www.youtube.com/tv?%s", payload); + } else { + snprintf( url, sizeof(url), "https://www.youtube.com/tv"); } - sprintf( data, "--user-data-dir=%s/.config/google-chrome-dial", getenv("HOME") ); + snprintf( data, sizeof(data), "--user-data-dir=%s/.config/google-chrome-dial", getenv("HOME") ); const char * const youtube_args[] = { spAppYouTubeExecutable, spYouTubePS3UserAgent, @@ -268,13 +268,18 @@ static void setValue( char * pSource, char dest[] ) static void setDataDir(char *pData) { setValue( spNfDataDir, spDataDir ); - strcat(spDataDir, pData); + strncat(spDataDir, pData, sizeof(spDataDir) - 1); } void runDial(void) { DIALServer *ds; ds = DIAL_create(); + if (ds == NULL) { + printf("Unable to create DIAL server.\n"); + return; + } + struct DIALAppCallbacks cb_nf; cb_nf.start_cb = netflix_start; cb_nf.hide_cb = netflix_hide; @@ -283,16 +288,20 @@ void runDial(void) struct DIALAppCallbacks cb_yt = {youtube_start, youtube_hide, youtube_stop, youtube_status}; struct DIALAppCallbacks cb_system = {system_start, system_hide, NULL, system_status}; - DIAL_register_app(ds, "Netflix", &cb_nf, NULL, 1, ".netflix.com"); - DIAL_register_app(ds, "YouTube", &cb_yt, NULL, 1, ".youtube.com"); - DIAL_register_app(ds, "system", &cb_system, NULL, 1, ""); - DIAL_start(ds); - - gDialPort = DIAL_get_port(ds); - printf("launcher listening on gDialPort %d\n", gDialPort); - run_ssdp(gDialPort, spFriendlyName, spModelName, spUuid); - - DIAL_stop(ds); + if (DIAL_register_app(ds, "Netflix", &cb_nf, NULL, 1, ".netflix.com") == -1 || + DIAL_register_app(ds, "YouTube", &cb_yt, NULL, 1, ".youtube.com") == -1 || + DIAL_register_app(ds, "system", &cb_system, NULL, 1, "") == -1) + { + printf("Unable to register DIAL applications.\n"); + } else if (!DIAL_start(ds)) { + printf("Unable to start DIAL master listening thread.\n"); + } else { + gDialPort = DIAL_get_port(ds); + printf("launcher listening on gDialPort %d\n", gDialPort); + run_ssdp(gDialPort, spFriendlyName, spModelName, spUuid); + + DIAL_stop(ds); + } free(ds); } @@ -317,11 +326,11 @@ static void processOption( int index, char * pOption ) setValue( pOption, spUuid ); break; case 5: - if (strcmp(pOption, "on")==0){ + if (strcmp(pOption, "on")==0) { wakeOnWifiLan=true; - }else if (strcmp(pOption, "off")==0){ + } else if (strcmp(pOption, "off") == 0) { wakeOnWifiLan=false; - }else{ + } else { fprintf(stderr, "Option %s is not valid for %s", pOption, WAKE_OPTION_LONG); exit(1); diff --git a/server/makefile b/server/makefile index 1866e7d..62492b8 100644 --- a/server/makefile +++ b/server/makefile @@ -22,10 +22,14 @@ nf_callbacks_lib: nf_callbacks.o dialserver: nf_callbacks_lib $(OBJS) $(CC) -Wall -Werror -Wl,-rpath,. -g $(OBJS) -ldl -lpthread -lrt -L. -lnfCallbacks -o dialserver + +dialserver_with_ASAN: nf_callbacks_lib $(OBJS) + $(CC) -Wall -Werror -fsanitize=address -Wl,-rpath,. -g $(OBJS) -ldl -lpthread -lrt -L. -lnfCallbacks -o dialserver_with_ASAN + test: make -C tests ./tests/run_tests clean: - rm -f *.o dialserver *.so + rm -f *.o dialserver dialserver_with_ASAN *.so make -C tests clean diff --git a/server/mongoose.c b/server/mongoose.c index 8df8a2b..0edacc5 100644 --- a/server/mongoose.c +++ b/server/mongoose.c @@ -37,6 +37,10 @@ #ifndef BUFSIZ #define BUFSIZ 4096 #endif +#ifndef REASON_SIZ +/* REASON_SIZ must be non-trivially smaller than BUFSIZ. */ +#define REASON_SIZ 2048 +#endif #define MAX_REQUEST_SIZE 4096 #define NUM_THREADS 4 @@ -214,33 +218,12 @@ static int mg_snprintf(struct mg_connection *conn, char *buf, size_t buflen, // Skip the characters until one of the delimiters characters found. // 0-terminate resulting word. Skip the delimiter and following whitespaces if any. // Advance pointer to buffer to the next word. Return found 0-terminated word. -// Delimiters can be quoted with quotechar. -static char *skip_quoted(char **buf, const char *delimiters, const char *whitespace, char quotechar) { +static char *skip_quoted(char **buf, const char *delimiters, const char *whitespace) { char *p, *begin_word, *end_word, *end_whitespace; begin_word = *buf; end_word = begin_word + strcspn(begin_word, delimiters); - /* Check for quotechar */ - if (end_word > begin_word) { - p = end_word - 1; - while (*p == quotechar) { - /* If there is anything beyond end_word, copy it */ - if (*end_word == '\0') { - *p = '\0'; - break; - } else { - size_t end_off = strcspn(end_word + 1, delimiters); - memmove (p, end_word, end_off + 1); - p += end_off; /* p must correspond to end_word - 1 */ - end_word += end_off + 1; - } - } - for (p++; p < end_word; p++) { - *p = '\0'; - } - } - if (*end_word == '\0') { *buf = end_word; } else { @@ -259,7 +242,7 @@ static char *skip_quoted(char **buf, const char *delimiters, const char *whitesp // Simplified version of skip_quoted without quote char // and whitespace == delimiters static char *skip(char **buf, const char *delimiters) { - return skip_quoted(buf, delimiters, delimiters, 0); + return skip_quoted(buf, delimiters, delimiters); } @@ -296,7 +279,14 @@ void mg_send_http_error(struct mg_connection *conn, int status, /* Errors 1xx, 204 and 304 MUST NOT send a body */ if (status > 199 && status != 204 && status != 304) { - len = mg_snprintf(conn, buf, sizeof(buf), "Error %d: %s", status, reason); + // If reason is really long, buf will have been completely filled up. + // Then buf[len] = '\n' will replace the trailing NULL with '\n' and kill buf + // as a string. Plus the next call to mg_vsnprintf() will be writing to an + // invalid location and negative (gigantic because size_t is unsigned) size. + // + // Given how this function is called, the safest thing to do is probably to + // require reason remains relatively short and will get truncated. + len = mg_snprintf(conn, buf, REASON_SIZ, "Error %d: %s", status, reason); cry(conn, "%s", buf); buf[len++] = '\n'; @@ -314,6 +304,14 @@ void mg_send_http_error(struct mg_connection *conn, int status, conn->num_bytes_sent += mg_printf(conn, "%s", buf); } +/** + * Create a new thread. + * + * @param ctx Mongoose context. + * @param func thread function. + * @param param thread function arguments. + * @return the return value of pthread_create. + */ static int start_thread(struct mg_context *ctx, mg_thread_func_t func, void *param) { pthread_t thread_id; @@ -332,13 +330,11 @@ static int start_thread(struct mg_context *ctx, mg_thread_func_t func, return retval; } -static int set_non_blocking_mode(SOCKET sock) { +static void set_non_blocking_mode(SOCKET sock) { int flags; flags = fcntl(sock, F_GETFL, 0); (void) fcntl(sock, F_SETFL, flags | O_NONBLOCK); - - return 0; } // Write data to the IO channel - opened file descriptor, socket or SSL @@ -350,8 +346,8 @@ static int64_t push(FILE *fp, SOCKET sock, const char *buf, int64_t len) { sent = 0; while (sent < len) { - /* How many bytes we send in this iteration */ - k = len - sent > INT_MAX ? INT_MAX : (int) (len - sent); + /* How many bytes should we send in this iteration */ + k = ((len - sent) > INT_MAX) ? INT_MAX : (int) (len - sent); if (fp != NULL) { n = fwrite(buf + sent, 1, (size_t)k, fp); @@ -458,9 +454,10 @@ static size_t url_decode(const char *src, size_t src_len, char *dst, #define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W') for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) { - if (src[i] == '%' && + if (src[i] == '%' && (i + 2 < src_len) && isxdigit(* (const unsigned char *) (src + i + 1)) && - isxdigit(* (const unsigned char *) (src + i + 2))) { + isxdigit(* (const unsigned char *) (src + i + 2))) + { a = tolower(* (const unsigned char *) (src + i + 1)); b = tolower(* (const unsigned char *) (src + i + 2)); dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b)); @@ -490,6 +487,7 @@ static int get_request_len(const char *buf, int buflen) { // Control characters are not allowed but >=128 is. if (!isprint(* (const unsigned char *) s) && *s != '\r' && *s != '\n' && * (const unsigned char *) s < 128) { + // FIXME: Why doesn't this immediately return as malformed? len = -1; } else if (s[0] == '\n' && s[1] == '\n') { len = (int) (s - buf) + 2; @@ -529,7 +527,7 @@ static void parse_http_headers(char **buf, struct mg_request_info *ri) { int i; for (i = 0; i < (int) ARRAY_SIZE(ri->http_headers); i++) { - ri->http_headers[i].name = skip_quoted(buf, ":", " ", 0); + ri->http_headers[i].name = skip_quoted(buf, ":", " "); ri->http_headers[i].value = skip(buf, "\r\n"); if (ri->http_headers[i].name[0] == '\0') break; @@ -637,6 +635,8 @@ static int set_ports_option(struct mg_context *ctx, int port) { tv.tv_sec = 0; tv.tv_usec = 500 * 1000; + // TODO This code calls close(INVALID_SOCKET), even though close expects positive file descriptors. + // Not sure what the behavior is as a result, might be fine. if ((ctx->local_socket = socket(PF_INET, SOCK_STREAM, 6)) == INVALID_SOCKET || setsockopt(ctx->local_socket, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)) != 0 || setsockopt(ctx->local_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) != 0 || @@ -702,22 +702,28 @@ static void close_connection(struct mg_connection *conn) { } static void discard_current_request_from_buffer(struct mg_connection *conn) { - int buffered_len, body_len; + int buffered_len; + int64_t remaining = 0, pull_len = 0, count = 0; + char discard_buffer[16384]; buffered_len = conn->data_len - conn->request_len; assert(buffered_len >= 0); - if (conn->content_len == -1) { - body_len = 0; - } else if (conn->content_len < (int64_t) buffered_len) { - body_len = (int) conn->content_len; - } else { - body_len = buffered_len; + // If there was no specified Content-Length header, then there's no way for + // us to know how much more data needs to be discarded. + // + // Otherwise keep pull()-ing content until we consume the full content length. + if (conn->content_len != -1 && conn->consumed_content < conn->content_len) { + remaining = conn->content_len - conn->consumed_content; + while (remaining > 0) { + pull_len = (remaining < sizeof(discard_buffer)) ? remaining : sizeof(discard_buffer); + count = mg_read(conn, discard_buffer, pull_len); + if (count <= 0) { + break; + } + remaining -= count; + } } - - conn->data_len -= conn->request_len + body_len; - memmove(conn->buf, conn->buf + conn->request_len + body_len, - (size_t) conn->data_len); } static void process_new_connection(struct mg_connection *conn) { @@ -731,7 +737,7 @@ static void process_new_connection(struct mg_connection *conn) { conn->request_len = read_request(conn->client.sock, conn->buf, conn->buf_size, &conn->data_len); } - assert(conn->data_len >= conn->request_len && conn->request_len > 1); + assert(conn->data_len >= conn->request_len); if (conn->request_len == 0 && conn->data_len == conn->buf_size) { mg_send_http_error(conn, 413, "Request Too Large", ""); return; @@ -739,7 +745,7 @@ static void process_new_connection(struct mg_connection *conn) { return; // Remote end closed the connection } - // Nul-terminate the request cause parse_http_request() uses sscanf + // Null-terminate the request because parse_http_request() uses sscanf conn->buf[conn->request_len - 1] = '\0'; if (!parse_http_request(conn->buf, ri)) { // Do not put garbage in the access log, just send it back to the client @@ -752,6 +758,11 @@ static void process_new_connection(struct mg_connection *conn) { // Request is valid, handle it cl = get_header(ri, "Content-Length"); conn->content_len = cl == NULL ? -1 : strtoll(cl, NULL, 10); + if (cl != NULL && conn->content_len < 0) { + mg_send_http_error(conn, 400, "Bad Request", + "Invalid Content-Length header value: [%s]", cl); + return; + } conn->birth_time = time(NULL); handle_request(conn); discard_current_request_from_buffer(conn); @@ -800,9 +811,6 @@ static void worker_thread(struct mg_context *ctx) { // required in the DIAL specification. int buf_size = MAX_REQUEST_SIZE; -#ifndef __APPLE__ - pthread_setname_np( pthread_self(), __func__); -#endif conn = (struct mg_connection *) calloc(1, sizeof(*conn) + buf_size); conn->buf_size = buf_size; conn->buf = (char *) (conn + 1); @@ -820,16 +828,22 @@ static void worker_thread(struct mg_context *ctx) { // Fill in local IP info socklen_t addr_len = sizeof(conn->request_info.local_addr); - getsockname(conn->client.sock, - (struct sockaddr *) &conn->request_info.local_addr, &addr_len); - - process_new_connection(conn); + if (getsockname(conn->client.sock, (struct sockaddr *) &conn->request_info.local_addr, &addr_len) != 0) { + // Something went wrong. + mg_send_http_error(conn, 500, "Internal Server Error", ""); + } else { + process_new_connection(conn); + } close_connection(conn); } free(conn); - // Signal master that we're done with connection and exiting + // Signal master that we're done with connection and exiting. + // + // It is possible that we fail to acquire the mutex and then num_threads will + // end up decrementing incorrectly which may cause the server to hang + // indefinitely while trying to shutdown. But we'll try our best. (void) pthread_mutex_lock(&ctx->mutex); ctx->num_threads--; (void) pthread_cond_signal(&ctx->cond); @@ -839,13 +853,25 @@ static void worker_thread(struct mg_context *ctx) { DEBUG_TRACE(("exiting")); } -// Master thread adds accepted socket to a queue -static void produce_socket(struct mg_context *ctx, const struct socket *sp) { - (void) pthread_mutex_lock(&ctx->mutex); +/** + * Copy an accepted socket onto the queue. Blocks if the queue is full. This + * function is called from the master thread. + * + * @param ctx Mongoose context. + * @param sp the socket. + * @return true if successful, false if there was a mutex error. + */ +static int produce_socket(struct mg_context *ctx, const struct socket *sp) { + if (pthread_mutex_lock(&ctx->mutex) != 0) { + return 0; + }; // If the queue is full, wait while (ctx->sq_head - ctx->sq_tail >= (int) ARRAY_SIZE(ctx->queue)) { - (void) pthread_cond_wait(&ctx->sq_empty, &ctx->mutex); + if (pthread_cond_wait(&ctx->sq_empty, &ctx->mutex) != 0) { + (void) pthread_mutex_unlock(&ctx->mutex); + return 0; + }; } assert(ctx->sq_head - ctx->sq_tail < (int) ARRAY_SIZE(ctx->queue)); @@ -854,17 +880,16 @@ static void produce_socket(struct mg_context *ctx, const struct socket *sp) { ctx->sq_head++; DEBUG_TRACE(("queued socket %d", sp->sock)); + // Nothing to do if there is an error on signal. But if we fail to unlock + // then we're in a bad state. (void) pthread_cond_signal(&ctx->sq_full); - (void) pthread_mutex_unlock(&ctx->mutex); + return (pthread_mutex_unlock(&ctx->mutex) == 0); } static void master_thread(struct mg_context *ctx) { struct socket accepted; -#ifndef __APPLE__ - pthread_setname_np( pthread_self(), __func__); -#endif socklen_t sock_len = sizeof(accepted.local_addr); memcpy(&accepted.local_addr, &ctx->local_address, sock_len); @@ -877,7 +902,10 @@ static void master_thread(struct mg_context *ctx) { if (accepted.sock != INVALID_SOCKET) { // Put accepted socket structure into the queue. DEBUG_TRACE(("accepted socket %d", accepted.sock)); - produce_socket(ctx, &accepted); + // If the socket fails, trigger stop and try to exit gracefully. + if (!produce_socket(ctx, &accepted)) { + ctx->stop_flag = 1; + }; } } DEBUG_TRACE(("stopping workers")); @@ -886,14 +914,18 @@ static void master_thread(struct mg_context *ctx) { close_all_listening_sockets(ctx); // Wakeup workers that are waiting for connections to handle. - pthread_cond_broadcast(&ctx->sq_full); + // Nothing we can do if there is an error. + (void) pthread_cond_broadcast(&ctx->sq_full); - // Wait until all threads finish - (void) pthread_mutex_lock(&ctx->mutex); - while (ctx->num_threads > 0) { - (void) pthread_cond_wait(&ctx->cond, &ctx->mutex); + // Wait until all threads finish. + // If we cannot acquire the lock, we're in a bad state so skip this and + // just try to clean up and shut down. + if (pthread_mutex_lock(&ctx->mutex) == 0) { + while (ctx->num_threads > 0) { + (void) pthread_cond_wait(&ctx->cond, &ctx->mutex); + } + (void) pthread_mutex_unlock(&ctx->mutex); } - (void) pthread_mutex_unlock(&ctx->mutex); // All threads exited, no sync is needed. Destroy mutex and condvars (void) pthread_mutex_destroy(&ctx->mutex); @@ -925,10 +957,13 @@ void mg_stop(struct mg_context *ctx) { struct mg_context *mg_start(mg_callback_t user_callback, void *user_data, int port) { struct mg_context *ctx; - + int retval; + // Allocate context and initialize reasonable general case defaults. - // TODO(lsm): do proper error handling here. ctx = (struct mg_context *) calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return NULL; + } ctx->user_callback = user_callback; ctx->user_data = user_data; @@ -937,15 +972,26 @@ struct mg_context *mg_start(mg_callback_t user_callback, void *user_data, int po return NULL; } // Ignore SIGPIPE signal, so if browser cancels the request, it - // won't kill the whole process. - (void) signal(SIGPIPE, SIG_IGN); - (void) pthread_mutex_init(&ctx->mutex, NULL); - (void) pthread_cond_init(&ctx->cond, NULL); - (void) pthread_cond_init(&ctx->sq_empty, NULL); - (void) pthread_cond_init(&ctx->sq_full, NULL); + // won't kill the whole + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) { + free_context(ctx); + return NULL; + }; + if (pthread_mutex_init(&ctx->mutex, NULL) != 0 || + pthread_cond_init(&ctx->cond, NULL) != 0 || + pthread_cond_init(&ctx->sq_empty, NULL) != 0 || + pthread_cond_init(&ctx->sq_full, NULL) != 0) + { + free_context(ctx); + return NULL; + }; // Start master (listening) thread - start_thread(ctx, (mg_thread_func_t) master_thread, ctx); + retval = start_thread(ctx, (mg_thread_func_t) master_thread, ctx); + if (retval != 0) { + free_context(ctx); + return NULL; + } // Start worker threads for (int i = 0; i < NUM_THREADS; i++) { diff --git a/server/nf_callbacks.c b/server/nf_callbacks.c index ce85b5a..01c9a11 100644 --- a/server/nf_callbacks.c +++ b/server/nf_callbacks.c @@ -1,3 +1,27 @@ +/* + * Copyright (c) 2014-2019 Netflix, Inc. + * All rights reserved. + * + * 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 + * 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 + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ #include #include #include @@ -14,8 +38,14 @@ extern char *spAppNetflix; extern char spNetflix[]; static char *defaultLaunchParam = "source_type=12"; -// Adding 20 bytes for prepended source_type for Netflix -static char sQueryParam[DIAL_MAX_PAYLOAD+DIAL_MAX_ADDITIONALURL+40]; +// Adding 40 bytes for defaultLaunchParam, plus additional characters that +// may get added. +// +// dial_server.c ensures the payload is <= DIAL_MAX_PAYLOAD but since we +// URL-encode it, the total string length may triple. +// +// dial_server.c ensures the additional data URL is <= DIAL_MAX_ADDITIONALURL. +static char sQueryParam[3 * DIAL_MAX_PAYLOAD + DIAL_MAX_ADDITIONALURL + 40]; int isAppRunning( char *pzName, char *pzCommandPattern ); int shouldRelaunch(DIALServer *pServer, const char *pAppName, const char *args ); @@ -34,21 +64,31 @@ DIALStatus netflix_start(DIALServer *ds, const char *appname, // construct the payload to determine if it has changed from the previous launch memset( sQueryParam, 0, sizeof(sQueryParam) ); - strcat( sQueryParam, defaultLaunchParam ); - if(strlen(payload)) + strncpy( sQueryParam, defaultLaunchParam, sizeof(sQueryParam) - 1); + if(strlen(payload)) { char * pUrlEncodedParams; pUrlEncodedParams = url_encode( payload ); if( pUrlEncodedParams ){ - strcat( sQueryParam, "&dial="); - strcat( sQueryParam, pUrlEncodedParams ); - free( pUrlEncodedParams ); + if (strlen(sQueryParam) + sizeof("&dial=") + strlen(pUrlEncodedParams) < sizeof(sQueryParam)) { + strcat( sQueryParam, "&dial=" ); + strcat( sQueryParam, pUrlEncodedParams ); + free( pUrlEncodedParams ); + } else { + free( pUrlEncodedParams ); + return kDIALStatusError; + } + } } if(strlen(additionalDataUrl)){ - strcat(sQueryParam, "&"); - strcat(sQueryParam, additionalDataUrl); + if (strlen(sQueryParam) + sizeof("&") + strlen(additionalDataUrl) < sizeof(sQueryParam)) { + strcat(sQueryParam, "&"); + strcat(sQueryParam, additionalDataUrl); + } else { + return kDIALStatusError; + } } printf("appPid = %s, shouldRelaunch = %s queryParams = %s\n", diff --git a/server/nf_callbacks.h b/server/nf_callbacks.h index 95b0648..a9626ee 100644 --- a/server/nf_callbacks.h +++ b/server/nf_callbacks.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Netflix, Inc. + * Copyright (c) 2014-2019 Netflix, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/server/quick_ssdp.c b/server/quick_ssdp.c index a957a2a..e869f4b 100644 --- a/server/quick_ssdp.c +++ b/server/quick_ssdp.c @@ -35,13 +35,6 @@ #include "mongoose.h" #include -#ifdef __APPLE__ -#include -#include -#include -#endif - - // TODO: Partners should define this port #define SSDP_PORT (56790) static char gBuf[4096]; @@ -85,8 +78,7 @@ static const char ssdp_reply[] = "HTTP/1.1 200 OK\r\n" static const char wakeup_header[] = "WAKEUP: MAC=%s;Timeout=%d\r\n"; #define STR_TIMEOUTLEN 6 /* Longest is 32767 */ #define HW_ADDRSTRLEN 18 -static char ip_addr[INET_ADDRSTRLEN] = "127.0.0.1"; -static char hw_addr[HW_ADDRSTRLEN] = "00:00:00:00:00:00"; +static char ip_addr[INET6_ADDRSTRLEN] = "127.0.0.1"; static int dial_port = 0; static int my_port = 0; static char friendly_name[256]; @@ -115,35 +107,24 @@ static void *request_handler(enum mg_event event, return NULL; } -#ifdef __APPLE__ -static void get_local_address() { - struct ifaddrs* iflist; - char *if_name= "en0"; - char buf[INET6_ADDRSTRLEN]; - - if (getifaddrs(&iflist) == 0) { - for (struct ifaddrs* cur = iflist; cur; cur = cur->ifa_next) { - if (strcmp(cur->ifa_name, if_name) == 0) { - if ((cur->ifa_addr->sa_family == AF_LINK) && cur->ifa_addr) { - unsigned char mac[6]; - struct sockaddr_dl* sdl = (struct sockaddr_dl*)cur->ifa_addr; - memcpy(mac, LLADDR(sdl), sdl->sdl_alen); - sprintf(hw_addr, "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - } - - if (cur->ifa_addr->sa_family == AF_INET) { - void *tmp = &((struct sockaddr_in *)cur->ifa_addr)->sin_addr; - strcpy(ip_addr, inet_ntop(cur->ifa_addr->sa_family, tmp, buf, sizeof(buf))); - } - } - } - freeifaddrs(iflist); - } -} -#else -static void get_local_address() { +/** + * Returns the local hardware address (e.g. MAC address). On macOS the "en0" + * interface is used. On other platforms the first non-loopback interface is + * used. + * + * As a side-effect, the local global ip_addr is also populated. + * + * (Are these choices of interface really the right ones? Seems risky for + * multi-homed systems.) + * + * @return the local hardware address or NULL if it does not exist, cannot + * be retrieved, or out-of-memory. The caller must free the returned + * memory. + */ +static char * get_local_address() { struct ifconf ifc; char buf[4096]; + char * hw_addr = NULL; int s, i; if (-1 == (s = socket(AF_INET, SOCK_DGRAM, 0))) { perror("socket"); @@ -160,8 +141,8 @@ static void get_local_address() { exit(1); } for (i = 0; i < ifc.ifc_len/sizeof(ifc.ifc_req[0]); i++) { - strcpy(ip_addr, - inet_ntoa(((struct sockaddr_in *)(&ifc.ifc_req[i].ifr_addr))->sin_addr)); + strncpy(ip_addr, + inet_ntoa(((struct sockaddr_in *)(&ifc.ifc_req[i].ifr_addr))->sin_addr), sizeof(ip_addr) - 1); if (0 > ioctl(s, SIOCGIFFLAGS, &ifc.ifc_req[i])) { perror("SIOCGIFFLAGS"); exit(1); @@ -174,6 +155,14 @@ static void get_local_address() { perror("SIOCGIFHWADDR"); exit(1); } + // FIXME: How do I figure out what type of interface this is in order to + // cast the struct sockaddr ifr_hraddr to the proper type and extract the + // hardware address? + // + // Make sure this is correct for the target device and platform. + hw_addr = (char*)malloc(HW_ADDRSTRLEN + 1); + if (hw_addr == NULL) + break; sprintf(hw_addr, "%02x:%02x:%02x:%02x:%02x:%02x", (unsigned char)ifc.ifc_req[i].ifr_hwaddr.sa_data[0], (unsigned char)ifc.ifc_req[i].ifr_hwaddr.sa_data[1], @@ -184,14 +173,14 @@ static void get_local_address() { break; } close(s); + return hw_addr; } -#endif -static void handle_mcast() { +static void handle_mcast(char *hw_addr) { int s, one = 1, bytes; socklen_t addrlen; - struct sockaddr_in saddr; - struct ip_mreq mreq; + struct sockaddr_in saddr = {0}; + struct ip_mreq mreq = {0}; char wakeup_buf[sizeof(wakeup_header) + HW_ADDRSTRLEN + STR_TIMEOUTLEN] = {0, }; char send_buf[sizeof(ssdp_reply) + INET_ADDRSTRLEN + 256 + 256 + sizeof(wakeup_buf)] = {0,}; int send_size; @@ -208,11 +197,7 @@ static void handle_mcast() { exit(1); } saddr.sin_family = AF_INET; -#ifdef __APPLE__ - saddr.sin_addr.s_addr = INADDR_ANY; -#else saddr.sin_addr.s_addr = inet_addr("239.255.255.250"); -#endif saddr.sin_port = htons(1900); if (-1 == bind(s, (struct sockaddr *)&saddr, sizeof(saddr))) { @@ -261,32 +246,42 @@ static void handle_mcast() { } void run_ssdp(int port, const char *pFriendlyName, const char * pModelName, const char *pUuid) { + char *hw_addr = NULL; struct sockaddr sa; socklen_t len = sizeof(sa); if(pFriendlyName) { strncpy(friendly_name, pFriendlyName, sizeof(friendly_name)); - friendly_name[255] = '\0'; + friendly_name[sizeof(friendly_name) - 1] = '\0'; } else { strcpy(friendly_name, "DIAL server sample"); } if(pModelName) { strncpy(model_name, pModelName, sizeof(model_name)); - uuid[255] = '\0'; + model_name[sizeof(model_name) - 1] = '\0'; } else { strcpy(model_name, "deadbeef-dead-beef-dead-beefdeadbeef"); } if(pUuid) { strncpy(uuid, pUuid, sizeof(uuid)); - uuid[255] = '\0'; + uuid[sizeof(uuid) - 1] = '\0'; } else { strcpy(uuid, "deadbeef-dead-beef-dead-beefdeadbeef"); } dial_port = port; - get_local_address(); - ctx = mg_start(&request_handler, NULL, SSDP_PORT); - if (mg_get_listen_addr(ctx, &sa, &len)) { - my_port = ntohs(((struct sockaddr_in *)&sa)->sin_port); + hw_addr = get_local_address(); + if (hw_addr == NULL) { + printf("Unable to retrieve hardware address."); + return; } - printf("SSDP listening on %s:%d\n", ip_addr, my_port); - handle_mcast(); + ctx = mg_start(&request_handler, NULL, SSDP_PORT); + if (ctx == NULL) { + printf("Unable to start SSDP master listening thread."); + } else { + if (mg_get_listen_addr(ctx, &sa, &len)) { + my_port = ntohs(((struct sockaddr_in *)&sa)->sin_port); + } + printf("SSDP listening on %s:%d\n", ip_addr, my_port); + handle_mcast(hw_addr); + } + free(hw_addr); hw_addr = NULL; } diff --git a/server/system_callbacks.c b/server/system_callbacks.c index cf03f72..bc3555e 100644 --- a/server/system_callbacks.c +++ b/server/system_callbacks.c @@ -1,3 +1,27 @@ +/* + * Copyright (c) 2018-2019 Netflix, Inc. + * All rights reserved. + * + * 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 + * 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 + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ #include #include #include "system_callbacks.h" @@ -20,8 +44,8 @@ DIALStatus system_start(DIALServer *ds, const char *appname, const char *payload if (strlen(spSleepPassword) != 0) { /* Look for key */ - char *key_value; - if ( (key_value = strchr(query_string, '&')) == '\0' ) { + char *key_value = strchr(query_string, '&'); + if ( key_value == NULL || *key_value == '\0' ) { return kDIALStatusErrorForbidden; // No key specified. } diff --git a/server/system_callbacks.h b/server/system_callbacks.h index db3f369..3e585a3 100644 --- a/server/system_callbacks.h +++ b/server/system_callbacks.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Netflix, Inc. + * Copyright (c) 2018-2019 Netflix, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -28,6 +28,11 @@ #include "dial_server.h" +/** + * + * + * Sleep is the only supported action for 'system', and not implemented in this reference. + */ DIALStatus system_start(DIALServer *ds, const char *appname, const char *payload, const char* query_string, const char *additionalDataUrl, diff --git a/server/tests/js_tests/libs/dialClient.js b/server/tests/js_tests/libs/dialClient.js index 37595da..805af2b 100644 --- a/server/tests/js_tests/libs/dialClient.js +++ b/server/tests/js_tests/libs/dialClient.js @@ -142,6 +142,40 @@ function launchApplication(host, app, payload) { }); } +/* + * send HTTP request to the host. + * + * @param {String} host IP address of host on which DIAL server + * @param {String} method HTTP method. + * @param {Object} headers HTTP headers, passed as {"Header": Value} + * @param {String} urlSuffix appended to the URL + * @param {String} body HTTP body + */ +function sendRequest(host, method, headers, urlSuffix, body) { + urlSuffix = urlSuffix || ""; + + return new Q() + .then(getAppsUrl.bind(null, host)) + .then(function (appUrl) { + var request = { + url: encodeURI(appUrl + urlSuffix), + method: method, + timeout: 6000, + headers: headers + }; + request.body = body; + return new Q.Promise(function (resolve, reject) { + return httpRequest(request, function handleResponse(error, response) { + if(error) { + reject(error); + } else { + resolve(response); + } + }); + }); + }); +} + function sleepSystem(host, key) { var keyComponent = key ? `&key=${key}` : ''; return getAppsUrl(host) @@ -424,3 +458,4 @@ module.exports.constructAppResourceUrl = constructAppResourceUrl; module.exports.getAppsUrl = getAppsUrl; module.exports.getLocation = getLocation; module.exports.sleepSystem = sleepSystem; +module.exports.sendRequest = sendRequest; diff --git a/server/tests/js_tests/package-lock.json b/server/tests/js_tests/package-lock.json index 9b6e580..d794356 100644 --- a/server/tests/js_tests/package-lock.json +++ b/server/tests/js_tests/package-lock.json @@ -9,10 +9,10 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" } }, "ansi-regex": { @@ -25,7 +25,7 @@ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", "requires": { - "safer-buffer": "2.1.2" + "safer-buffer": "~2.1.0" } }, "assert-plus": { @@ -38,7 +38,7 @@ "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", "requires": { - "lodash": "4.17.11" + "lodash": "^4.17.10" } }, "asynckit": { @@ -61,7 +61,7 @@ "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "requires": { - "tweetnacl": "0.14.5" + "tweetnacl": "^0.14.3" } }, "bluebird": { @@ -89,9 +89,9 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wrap-ansi": "2.1.0" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" } }, "co": { @@ -114,7 +114,7 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", "requires": { - "delayed-stream": "1.0.0" + "delayed-stream": "~1.0.0" } }, "core-util-is": { @@ -132,7 +132,7 @@ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" } }, "debug": { @@ -140,7 +140,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "requires": { - "ms": "2.1.1" + "ms": "^2.1.1" } }, "decamelize": { @@ -158,8 +158,8 @@ "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "requires": { - "jsbn": "0.1.1", - "safer-buffer": "2.1.2" + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" } }, "error-ex": { @@ -167,7 +167,7 @@ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "requires": { - "is-arrayish": "0.2.1" + "is-arrayish": "^0.2.1" } }, "extend": { @@ -200,8 +200,8 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "forever-agent": { @@ -214,9 +214,9 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.7", - "mime-types": "2.1.21" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" } }, "get-caller-file": { @@ -229,7 +229,7 @@ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" } }, "graceful-fs": { @@ -247,8 +247,8 @@ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", "requires": { - "ajv": "5.5.2", - "har-schema": "2.0.0" + "ajv": "^5.3.0", + "har-schema": "^2.0.0" } }, "hosted-git-info": { @@ -261,9 +261,9 @@ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "requires": { - "assert-plus": "1.0.0", - "jsprim": "1.4.1", - "sshpk": "1.15.1" + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, "invert-kv": { @@ -286,7 +286,7 @@ "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "requires": { - "builtin-modules": "1.1.1" + "builtin-modules": "^1.0.0" } }, "is-fullwidth-code-point": { @@ -294,7 +294,7 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "is-typedarray": { @@ -353,7 +353,7 @@ "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", "requires": { - "invert-kv": "1.0.0" + "invert-kv": "^1.0.0" } }, "load-json-file": { @@ -361,11 +361,11 @@ "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "strip-bom": "2.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" } }, "lodash": { @@ -383,7 +383,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", "requires": { - "mime-db": "1.37.0" + "mime-db": "~1.37.0" } }, "moment": { @@ -401,11 +401,11 @@ "resolved": "https://registry.npmjs.org/node-ssdp/-/node-ssdp-3.3.0.tgz", "integrity": "sha512-hFBkfUJytKC2x64jljojAbktG8aOL0C1YuNjCK54ZGBBg2382J3oTuK17T+aFgmy47noKHE5arLnYppo0JjcLw==", "requires": { - "async": "2.6.1", - "bluebird": "3.5.2", - "debug": "3.2.6", - "extend": "3.0.2", - "ip": "1.1.5" + "async": "^2.6.0", + "bluebird": "^3.5.1", + "debug": "^3.1.0", + "extend": "^3.0.1", + "ip": "^1.1.5" } }, "normalize-package-data": { @@ -413,10 +413,10 @@ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", "requires": { - "hosted-git-info": "2.7.1", - "is-builtin-module": "1.0.0", - "semver": "5.6.0", - "validate-npm-package-license": "3.0.4" + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" } }, "number-is-nan": { @@ -434,7 +434,7 @@ "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "requires": { - "lcid": "1.0.0" + "lcid": "^1.0.0" } }, "parse-json": { @@ -442,7 +442,7 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "requires": { - "error-ex": "1.3.2" + "error-ex": "^1.2.0" } }, "path-exists": { @@ -450,7 +450,7 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "requires": { - "pinkie-promise": "2.0.1" + "pinkie-promise": "^2.0.0" } }, "path-type": { @@ -458,9 +458,9 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "requires": { - "graceful-fs": "4.1.11", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "performance-now": { @@ -483,7 +483,7 @@ "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "requires": { - "pinkie": "2.0.4" + "pinkie": "^2.0.0" } }, "psl": { @@ -511,9 +511,9 @@ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", "requires": { - "load-json-file": "1.1.0", - "normalize-package-data": "2.4.0", - "path-type": "1.1.0" + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" } }, "read-pkg-up": { @@ -521,8 +521,8 @@ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", "requires": { - "find-up": "1.1.2", - "read-pkg": "1.1.0" + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" } }, "request": { @@ -530,26 +530,26 @@ "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "requires": { - "aws-sign2": "0.7.0", - "aws4": "1.8.0", - "caseless": "0.12.0", - "combined-stream": "1.0.7", - "extend": "3.0.2", - "forever-agent": "0.6.1", - "form-data": "2.3.3", - "har-validator": "5.1.0", - "http-signature": "1.2.0", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.21", - "oauth-sign": "0.9.0", - "performance-now": "2.1.0", - "qs": "6.5.2", - "safe-buffer": "5.1.2", - "tough-cookie": "2.4.3", - "tunnel-agent": "0.6.0", - "uuid": "3.3.2" + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" } }, "require-directory": { @@ -587,8 +587,8 @@ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.2.tgz", "integrity": "sha512-q9hedtzyXHr5S0A1vEPoK/7l8NpfkFYTq6iCY+Pno2ZbdZR6WexZFtqeVGkGxW3TEJMN914Z55EnAGMmenlIQQ==", "requires": { - "spdx-expression-parse": "3.0.0", - "spdx-license-ids": "3.0.1" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, "spdx-exceptions": { @@ -601,8 +601,8 @@ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", "requires": { - "spdx-exceptions": "2.2.0", - "spdx-license-ids": "3.0.1" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, "spdx-license-ids": { @@ -620,15 +620,15 @@ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.1.tgz", "integrity": "sha512-mSdgNUaidk+dRU5MhYtN9zebdzF2iG0cNPWy8HG+W8y+fT1JnSkh0fzzpjOa0L7P8i1Rscz38t0h4gPcKz43xA==", "requires": { - "asn1": "0.2.4", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.2", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.2", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "safer-buffer": "2.1.2", - "tweetnacl": "0.14.5" + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" } }, "stack-trace": { @@ -641,9 +641,9 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "strip-ansi": { @@ -651,7 +651,7 @@ "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "strip-bom": { @@ -659,7 +659,7 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "requires": { - "is-utf8": "0.2.1" + "is-utf8": "^0.2.0" } }, "tough-cookie": { @@ -667,8 +667,8 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "requires": { - "psl": "1.1.29", - "punycode": "1.4.1" + "psl": "^1.1.24", + "punycode": "^1.4.1" } }, "tunnel-agent": { @@ -676,7 +676,7 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "^5.0.1" } }, "tweetnacl": { @@ -694,8 +694,8 @@ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "requires": { - "spdx-correct": "3.0.2", - "spdx-expression-parse": "3.0.0" + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, "verror": { @@ -703,9 +703,9 @@ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "requires": { - "assert-plus": "1.0.0", + "assert-plus": "^1.0.0", "core-util-is": "1.0.2", - "extsprintf": "1.3.0" + "extsprintf": "^1.2.0" } }, "which-module": { @@ -718,12 +718,12 @@ "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.4.tgz", "integrity": "sha512-NBo2Pepn4hK4V01UfcWcDlmiVTs7VTB1h7bgnB0rgP146bYhMxX0ypCz3lBOfNxCO4Zuek7yeT+y/zM1OfMw4Q==", "requires": { - "async": "1.0.0", - "colors": "1.0.3", - "cycle": "1.0.3", - "eyes": "0.1.8", - "isstream": "0.1.2", - "stack-trace": "0.0.10" + "async": "~1.0.0", + "colors": "1.0.x", + "cycle": "1.0.x", + "eyes": "0.1.x", + "isstream": "0.1.x", + "stack-trace": "0.0.x" }, "dependencies": { "async": { @@ -743,8 +743,8 @@ "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" } }, "y18n": { @@ -757,19 +757,19 @@ "resolved": "http://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=", "requires": { - "camelcase": "3.0.0", - "cliui": "3.2.0", - "decamelize": "1.2.0", - "get-caller-file": "1.0.3", - "os-locale": "1.4.0", - "read-pkg-up": "1.0.1", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "1.0.2", - "which-module": "1.0.0", - "y18n": "3.2.1", - "yargs-parser": "4.2.1" + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^4.2.0" } }, "yargs-parser": { @@ -777,7 +777,7 @@ "resolved": "http://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", "requires": { - "camelcase": "3.0.0" + "camelcase": "^3.0.0" } } } diff --git a/server/tests/js_tests/tests/testEdgeCases.js b/server/tests/js_tests/tests/testEdgeCases.js new file mode 100644 index 0000000..12c143b --- /dev/null +++ b/server/tests/js_tests/tests/testEdgeCases.js @@ -0,0 +1,93 @@ +"use strict"; + +var dial = require("../libs/dialClient.js"), + utils = require("../libs/utils.js"), + Q = require("q"), + winston = require("winston"); + + +const argv = require("yargs") + .usage("\nUsage: node " + __filename.slice(__dirname.length + 1) + "[options]") + .option("host", { + describe: "IP address of host on which DIAL server under test is running", + type: "string", + demand: true + }) + .help("help").alias("help", "h").argv; + +var testCount = 0; +var failureCount = 0; + +/* + * These edge cases are + */ +function edgeCases() { + var host = argv.host; + + var methods = [ + "GET", + "POST", + ]; + + // weird data + var headers = [ + {"Content-Length": "0xff1af1581"}, + {"Content-Length": "sfsadfdsat"}, + {"Content-Length": 13377777777777}, + {"Content-Length": -13377777777777}, + {"Content-Type": "text/plain;charset=\"utf-8\""}, + {"Content-Type": "text/plain;charset=\"ascii\""}, + {"Content-Type": "asdoffmoaserq"}, + {"Foo": "Bar"}, + ]; + + var utf8 = "􎾅򉷰;(QmN􇺥򅟤̸񼙄鱏=叽]𰸹񡻽h˳╭ܮ𻬆煣ِt𐍁J픖pЗ󐰝B-ٴۼ퍃%۞灱Ҡɝ~2񓟟望Lԇƀ􅃣􉭿𯍦ݛ󜫽&±󚀌⫯WܾNJX礴ēʧ񗐦耉ݭpi}Ǖ񨎤ʝ'䬿^ٿ]샑좘쒾" + + "ԝbւ蹈ۥ웦(歉貤䃶6ƢӋ􃾝􇢼Ø𪺭񃫍񑥽ĩ񳎂k┋譒ց,Ŋ칱آ󃅒3㛣Oܕ=裁f򙗳񢘄Ĵjٕ򳼂pۅ=@ԉ'1󷪁⃮Ȩ핣񸡗𾜃򷞟ƺ񛩨匽ݚ뎔叇χ&죙𱘱ʴ򹏆ذȞ󝗘鞾ﰟO썟X񚗠9򚏗򗆾⛥﫢ȧ走쾙" + + "򹺡ӱ񗊆إ#!۵Σƀ򽾖4䦼󓋻쪭ϖ0ڧp梛՚嬆߷򽳅򂝍򀡓域᳁١ޜ򵚁ۡ򩺚Ü]ʘ厤ȆwYݖ(ן󛶳􌖂饲a񀪄�`V򵆡<$𚓖Dz&ܗʧ󋢶􅴹🶝򎆾bҘ@E򥽿1ч󫿘&{򍀓㜃팸󻴡儈Ā㖿rb^Ӟ󗔔ʶ" + + "􉯨Ө㡷s󢚊Π򍱫򣇭●ŽٗDҤ򄗆Ʊ꫓(򫉦t񴹹-瞜BЏ;Ғ򸻡eࡻs򢺩򅠕`=ݔu樭迕^;𕍃䐈Ǥ3䝡XM붩ݱ􊙩􆰯ϖȓܕ췃񿜵`Ỷ3LV9폧赹ⴢ񁽟+⇩ӛ􂗥뵖띟򦁩䎵钥¶繥Ҕ J礢򼦨м2_8򴻣̑hg"; + utf8 = utf8 + utf8 + utf8; + var strings = [ + utf8, + "", + 'test\n', + '$HOME', + '../../../../../../../../../../../etc/hosts\n', + ]; + + // generate test cases + var testCases = []; + for (var m = 0; m < methods.length; m++) { + for (var h = 0; h < headers.length; h++) { + for (var q = 0; q < strings.length; q++) { + for (var b = 0; b < strings.length; b++) { + testCases.push(function (m, h, q, b) { + return new Q() + .then(dial.sendRequest.bind(null, host, methods[m], headers[h], strings[q], strings[b])) + .then(function (response) { + testCount = testCount + 1; + if (![400, 404, 500].includes(response.statusCode)) { + return Q.reject(new Error("Sent the DIAL server an edge case. Expected a bad status code but got " + response.statusCode)); + } + }) + .fail(function handleError(err) { + utils.printTestInfo(__filename.slice(__dirname.length + 1), + "Edge Case, method: " + methods[m] + " header: " + + headers[h] + " query string: " + + strings[q] + " body: " + strings[b]); + utils.printTestFailure(err); + failureCount = failureCount + 1; + return err; + }) + }.bind(null, m, h, q, b)); + } + } + } + } + + return testCases; +} + +winston.info("Testing edge cases, only failing tests will appear."); +return edgeCases().reduce(Q.when, Promise.resolve()).done(function() { + winston.info("Tests complete. Passing: " + (testCount - failureCount) + ", Failures: " + failureCount); +}); diff --git a/server/tests/js_tests/tests/tests.js b/server/tests/js_tests/tests/tests.js index 7b0e3b8..41263d6 100644 --- a/server/tests/js_tests/tests/tests.js +++ b/server/tests/js_tests/tests/tests.js @@ -78,6 +78,4 @@ Promise.resolve() // Application hide tests .then(hideInvalidApplicationInstance.test) .then(hideApplicationInHiddenState.test) - .then(hideApplicationInRunningState.test) - - + .then(hideApplicationInRunningState.test); diff --git a/server/tests/makefile b/server/tests/makefile index 7cdfe01..7a8d0f8 100644 --- a/server/tests/makefile +++ b/server/tests/makefile @@ -3,7 +3,7 @@ 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 +OBJS := test_dial_data.o test_url_lib.o test_callbacks.o ../url_lib.o ../dial_data.o ../system_callbacks.o run_tests.o HEADERS := $(wildcard ../*.h) %.c: $(HEADERS) @@ -12,7 +12,7 @@ HEADERS := $(wildcard ../*.h) $(CC) -Wall -Werror -g -std=gnu99 $(CFLAGS) -c $*.c -o $*.o test: $(OBJS) - $(CC) -Wall -Werror -g $(OBJS) -ldl -lpthread -o run_tests + $(CC) -Wall -Werror -fsanitize=address -g $(OBJS) -ldl -lpthread -o run_tests clean: rm -f *.o run_tests diff --git a/server/tests/run_tests.c b/server/tests/run_tests.c index dedafd0..b51a4a4 100644 --- a/server/tests/run_tests.c +++ b/server/tests/run_tests.c @@ -22,6 +22,7 @@ * (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 "test_callbacks.h" #include "test_dial_data.h" #include "test_url_lib.h" @@ -29,14 +30,8 @@ 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; + test_dial_data_suite(); + test_url_lib_suite(); + test_callbacks_suite(); + return 0; } diff --git a/server/tests/test.h b/server/tests/test.h index 1cb9295..1655792 100644 --- a/server/tests/test.h +++ b/server/tests/test.h @@ -29,9 +29,20 @@ #define EXPECT(a, m) \ do { \ - if (!a) { \ - printf("[%s] failed: %s\n", #a, m); \ - printf("%s -> FAILED\n", __func__); \ + if (!(a)) { \ + printf("\033[31m [%s] failed: %s \033[0m\n", #a, m); \ + printf("\033[31m %s -> FAILED \033[0m\n", __func__); \ + return; \ + } \ + } while (0) + +#define EXPECT_EQ(a, b) \ + do { \ + if (a != b) { \ + printf("\033[31m expected [%s == %s] \033[0m\n", #a, #b); \ + printf("\033[31m a = \"%ld\" \033[0m\n", (unsigned long) a); \ + printf("\033[31m b = \"%ld\" \033[0m\n", (unsigned long) b); \ + printf("\033[31m %s -> FAILED \033[0m\n", __func__); \ return; \ } \ } while (0) @@ -39,15 +50,18 @@ #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__); \ + printf("\033[31m expected [%s == %s] \033[0m\n", #a, #b); \ + printf("\033[31m a = \"%s\" \033[0m\n", a); \ + printf("\033[31m b = \"%s\" \033[0m\n", b); \ + printf("\033[31m %s -> FAILED \033[0m\n", __func__); \ return; \ } \ } while (0) +#define START_SUITE() \ + printf("== %s ==\n", __FILE__) + #define DONE() \ - printf("%s -> OK\n", __func__) + printf("\033[32m %s -> OK \033[0m\n", __func__) #endif /* SRC_SERVER_TESTS_TEST_H_ */ diff --git a/server/tests/test_callbacks.c b/server/tests/test_callbacks.c new file mode 100644 index 0000000..a1e9682 --- /dev/null +++ b/server/tests/test_callbacks.c @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019 Netflix, Inc. + * All rights reserved. + * + * 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 + * 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 + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include +#include + +#include "../system_callbacks.h" +#include "../dial_data.h" + +#include "test_callbacks.h" +#include "test.h" + +char spSleepPassword[256]; + +/* + * The reference does not provide implementations for interfacing with the System Application. + */ +void test_system_callbacks() { + EXPECT_EQ(system_start(NULL, NULL, NULL, "action=sleep", NULL, NULL, NULL), kDIALStatusErrorNotImplemented); + EXPECT_EQ(system_hide(NULL, NULL, NULL, NULL), kDIALStatusHide); + EXPECT_EQ(system_status(NULL, NULL, NULL, NULL, NULL), kDIALStatusHide); + + DONE(); +} + +void test_callbacks_suite() { + START_SUITE(); + test_system_callbacks(); +} \ No newline at end of file diff --git a/server/tests/test_callbacks.h b/server/tests/test_callbacks.h new file mode 100644 index 0000000..7c57a91 --- /dev/null +++ b/server/tests/test_callbacks.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2019 Netflix, Inc. + * All rights reserved. + * + * 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 + * 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 + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef SRC_SERVER_TESTS_TEST_CALLBACKS_H_ +#define SRC_SERVER_TESTS_TEST_CALLBACKS_H_ + +void test_callbacks_suite(); + +#endif /* SRC_SERVER_TESTS_TEST_CALLBACKS_H_ */ diff --git a/server/tests/test_dial_data.c b/server/tests/test_dial_data.c index b383d5d..4fe6433 100644 --- a/server/tests/test_dial_data.c +++ b/server/tests/test_dial_data.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Netflix, Inc. + * Copyright (c) 2014-2019 Netflix, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -30,17 +30,19 @@ #include #include "test.h" +#include "test_dial_data.h" + +/* + * All tests should malloc + */ int key_value_pairs = 3; -char *keys[] = {"key1", "key2", "key3"}; +char *keys[] = {"key3", "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]); - } +void test_read_dial_missing_data() { + DIALData *data = retrieve_dial_data("NON_EXISTENT_DATA"); + EXPECT(NULL == data, "retrieve_dial_data() returns NULL\n"); DONE(); } @@ -48,19 +50,76 @@ 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->key = malloc(strlen(keys[i]) + 1); + node->value = malloc(strlen(values[i]) + 1); + strcpy(node->key, keys[i]); + strcpy(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]); + DIALData *datum = readBack; + for (int i = 0; datum != NULL; datum = datum->next, i++) { + EXPECT_STREQ(datum->key, keys[i]); + EXPECT_STREQ(datum->value, values[i]); } + free_dial_data(&result); + free_dial_data(&readBack); + DONE(); } + +void test_write_kv_larger_than_max_len() { + // result contains k & v both larger than DIAL_KEY_OR_VALUE_MAX_LEN + DIALData *result = (DIALData *) calloc(1, sizeof(DIALData)); + result->key = calloc(DIAL_KEY_OR_VALUE_MAX_LEN * 2, sizeof(char)); + result->value = calloc(DIAL_KEY_OR_VALUE_MAX_LEN * 2, sizeof(char)); + memset(result->key, 'k', DIAL_KEY_OR_VALUE_MAX_LEN * 2 - 1); + memset(result->value, 'v', DIAL_KEY_OR_VALUE_MAX_LEN * 2 - 1); + + store_dial_data("YouTube", result); + + DIALData *readBack = retrieve_dial_data("YouTube"); + EXPECT(NULL != readBack, "retrieve_dial_data should not be NULL\n"); + + EXPECT_EQ(readBack->key[0], 'k'); + EXPECT_EQ(readBack->value[0], 'v'); + EXPECT_EQ(strlen(readBack->key), DIAL_KEY_OR_VALUE_MAX_LEN); + EXPECT_EQ(strlen(readBack->value), DIAL_KEY_OR_VALUE_MAX_LEN); + + free_dial_data(&result); + free_dial_data(&readBack); + + DONE(); +} + +void test_write_empty_kv() { + DIALData *result = (DIALData *) calloc(1, sizeof(DIALData)); + result->key = calloc(1, sizeof(char)); + result->value = calloc(1, sizeof(char)); + + store_dial_data("YouTube", result); + + DIALData *readBack = retrieve_dial_data("YouTube"); + EXPECT(NULL != readBack, "retrieve_dial_data should not be NULL\n"); + + EXPECT_EQ(readBack->key[0], '\0'); + EXPECT_EQ(readBack->value[0], '\0'); + + free_dial_data(&result); + free_dial_data(&readBack); + + DONE(); +} + +void test_dial_data_suite() { + START_SUITE(); + + test_read_dial_missing_data(); + test_write_dial_data(); + test_write_kv_larger_than_max_len(); +} \ No newline at end of file diff --git a/server/tests/test_dial_data.h b/server/tests/test_dial_data.h index d2b579d..61900da 100644 --- a/server/tests/test_dial_data.h +++ b/server/tests/test_dial_data.h @@ -25,7 +25,6 @@ #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(); +void test_dial_data_suite(); #endif /* SRC_SERVER_TESTS_TEST_DIAL_DATA_H_ */ diff --git a/server/tests/test_url_lib.c b/server/tests/test_url_lib.c index 95d6e6d..e435c4f 100644 --- a/server/tests/test_url_lib.c +++ b/server/tests/test_url_lib.c @@ -26,25 +26,26 @@ #include "../dial_data.h" #include +#include #include #include #include #include "test.h" -void test_smartstrcat() { +void test_smartstrncpy() { char* src1 = "Hello "; char* src2 = "world!"; char* src3 = " Trunc ated"; char dest[128] = {0, }; char* p = (char *) dest; - p = smartstrcat(p, src1, 128); + p = smartstrncpy(p, src1, 128); EXPECT_STREQ(dest, "Hello "); - p = smartstrcat(p, src2, dest + 128 - p); + p = smartstrncpy(p, src2, dest + 128 - p); EXPECT_STREQ(dest, "Hello world!"); - p = smartstrcat(p, src3, 6); + p = smartstrncpy(p, src3, 6); EXPECT_STREQ(dest, "Hello world! Trunc"); DONE(); @@ -63,19 +64,24 @@ 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"); + free(app_name); EXPECT((app_name = parse_app_name("")), "Failed to extract app_name"); EXPECT_STREQ(app_name, "unknown"); + free(app_name); EXPECT((app_name = parse_app_name("/")), "Failed to extract app_name"); EXPECT_STREQ(app_name, "unknown"); + free(app_name); EXPECT((app_name = parse_app_name("/apps/YouTube/DialData")), "Failed to extract app_name"); EXPECT_STREQ(app_name, "YouTube"); + free(app_name); EXPECT((app_name = parse_app_name("//")), "Failed to extract app_name"); EXPECT_STREQ(app_name, ""); + free(app_name); EXPECT((app_name = parse_app_name("/invalid")), "Failed to extract app_name"); EXPECT_STREQ(app_name, "unknown"); - + free(app_name); DONE(); } @@ -86,28 +92,52 @@ void test_parse_params() { DIALData *result = parse_params("a=b"); EXPECT_STREQ(result->key, "a"); EXPECT_STREQ(result->value, "b"); + free_dial_data(&result); result = parse_params("?a=b"); EXPECT_STREQ(result->key, "a"); EXPECT_STREQ(result->value, "b"); + free_dial_data(&result); 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"); + free_dial_data(&result); + + result = parse_params("ሳ=€"); + EXPECT_STREQ(result->key, "ሳ"); + EXPECT_STREQ(result->value, "€"); + free_dial_data(&result); char query_string[1024] = {0, }; char *current = query_string; for (int i = 0; i < 25; ++i) { - current = smartstrcat(current, "a=b&", 256); + current = smartstrncpy(current, "a=b&", 256); } result = parse_params(query_string); int length = 0; - for (; result != NULL; result = result->next) { + for (DIALData *current = result; current != NULL; current = current->next) { length++; } EXPECT((length == 25), "25 params should have been parsed"); + free_dial_data(&result); DONE(); } + +void test_parse_params_malformatted() { + EXPECT(NULL == parse_params("abcdefghijkl"), "no params expected"); + EXPECT(NULL == parse_params("\u2639"), "no params expected"); + DONE(); +} + +void test_url_lib_suite() { + START_SUITE(); + test_smartstrncpy(); + test_urldecode(); + test_parse_app_name(); + test_parse_params(); + test_parse_params_malformatted(); +} \ No newline at end of file diff --git a/server/tests/test_url_lib.h b/server/tests/test_url_lib.h index 1cbe2e7..0da61c7 100644 --- a/server/tests/test_url_lib.h +++ b/server/tests/test_url_lib.h @@ -25,10 +25,6 @@ #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(); +void test_url_lib_suite(); #endif /* SRC_SERVER_TESTS_TEST_URL_LIB_H_ */ diff --git a/server/url_lib.c b/server/url_lib.c index 1807814..0dfa449 100644 --- a/server/url_lib.c +++ b/server/url_lib.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Netflix, Inc. + * Copyright (c) 2014-2019 Netflix, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -30,15 +30,29 @@ #include #include -char* smartstrcat(char* dest, char* src, size_t max_chars) { - size_t copied = 0; - while ((*(dest++) = *(src++)) && copied++ < max_chars) { +static const char * unknown_str = "unknown"; + +char* smartstrncpy(char* dest, char* src, size_t max_chars) { + size_t copied; + for (copied = 0; copied < max_chars; copied++, dest++, src++) { + *dest = *src; + if (*dest == 0) + break; } - // In case we over-stepped the size, end the string nicely. - *(--dest) = '\0'; + *dest = 0; return dest; } +/** + * Convert a two-character hex representation into its ASCII equivalent + * and copy it into dest. + * + * @param dest the destination character buffer. + * @param a the first hex character. + * @param b the second hex character. + * @return 1 if the character was converted and copied, 0 if the hex + * characters are invalid. + */ static int append_char_from_hex(char* dest, char a, char b) { if ('a' <= a && a <= 'f') a = 10 + a - 'a'; @@ -89,23 +103,33 @@ void xmlencode(char *dst, const char *src, size_t max_size) { while (*src && current_size < max_size) { switch (*src) { case '&': - dst = smartstrcat(dst, "&", max_size - current_size); + if (current_size + 5 >= max_size) + break; + dst = smartstrncpy(dst, "&", max_size - current_size); current_size += 5; break; case '\"': - dst = smartstrcat(dst, """, max_size - current_size); + if (current_size + 6 >= max_size) + break; + dst = smartstrncpy(dst, """, max_size - current_size); current_size += 6; break; case '\'': - dst = smartstrcat(dst, "'", max_size - current_size); + if (current_size + 6 >= max_size) + break; + dst = smartstrncpy(dst, "'", max_size - current_size); current_size += 6; break; case '<': - dst = smartstrcat(dst, "<", max_size - current_size); + if (current_size + 4 >= max_size) + break; + dst = smartstrncpy(dst, "<", max_size - current_size); current_size += 4; break; case '>': - dst = smartstrcat(dst, ">", max_size - current_size); + if (current_size + 4 >= max_size) + break; + dst = smartstrncpy(dst, ">", max_size - current_size); current_size += 4; break; default: @@ -119,18 +143,34 @@ void xmlencode(char *dst, const char *src, size_t max_size) { } char *parse_app_name(const char *uri) { + char *unknown = NULL; if (uri == NULL) { - return "unknown"; + unknown = (char*)calloc(strlen(unknown_str) + 1, sizeof(char)); + if (unknown == NULL) { + return NULL; + } + strncpy(unknown, unknown_str, strlen(unknown_str) + 1); + return unknown; } char *slash = strrchr(uri, '/'); if (slash == NULL || slash == uri) { - return "unknown"; + unknown = (char*)calloc(strlen(unknown_str) + 1, sizeof(char)); + if (unknown == NULL) { + return NULL; + } + strncpy(unknown, unknown_str, strlen(unknown_str) + 1); + return unknown; } char *begin = slash; while ((begin != uri) && (*--begin != '/')) ; - begin++; // skip the slash - char *result = (char *) calloc(1, slash - begin+1); + if (*begin == '/') { + begin++; // skip the slash + } + char *result = (char *) calloc(slash - begin+1, sizeof(char)); + if (result == NULL) { + return NULL; + } strncpy(result, begin, slash - begin); result[slash-begin]='\0'; return result; @@ -151,6 +191,9 @@ char *parse_param(char *query_string, char *param_name) { end++; int result_size = end - start; char *result = malloc(result_size + 1); + if (result == NULL) { + return NULL; + } result[0] = '\0'; strncpy(result, start, result_size); result[result_size] = '\0'; @@ -165,20 +208,46 @@ DIALData *parse_params(char * query_string) { query_string++; // skip leading question mark } DIALData *result = NULL; + int err = 0; char *query_string_dup = strdup(query_string); char * name_value = strtok(query_string_dup, "&"); while (name_value != NULL) { DIALData *tmp = (DIALData *) malloc(sizeof(DIALData)); + if (tmp == NULL) { + err = 1; + break; + } 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->key = (char *) calloc(name_value_length + 1, sizeof(char)); + if (tmp->key == NULL) { + free(tmp); tmp = NULL; + err = 1; + break; + } + tmp->value = (char *) calloc(name_value_length + 1, sizeof(char)); + if (tmp->value == NULL) { + free(tmp->key); tmp->key = NULL; + free(tmp); tmp = NULL; + err = 1; + break; + } + int match = sscanf(name_value, "%[^=]=%s", tmp->key, tmp->value); + if (match != 2) { + free(tmp->value); tmp->value = NULL; + free(tmp->key); tmp->key = NULL; + free(tmp); tmp = NULL; + err = 1; + break; + } tmp->next = result; result = tmp; name_value = strtok(NULL, "&"); // read next token } free(query_string_dup); + if (err) { + free_dial_data(&result); result = NULL; + } return result; } @@ -194,7 +263,7 @@ char from_hex(char ch) { /* Converts an integer value to its hex character*/ char to_hex(char code) { static char hex[] = "0123456789abcdef"; - return hex[code & 15]; + return hex[code & 15]; } diff --git a/server/url_lib.h b/server/url_lib.h index 1a9f750..d3fb30b 100644 --- a/server/url_lib.h +++ b/server/url_lib.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Netflix, Inc. + * Copyright (c) 2014-2019 Netflix, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,35 +31,94 @@ #include /** - * Concatenate a maxim of max_chars characters from src into dest, - * and return a pointer to the last character in dest. + * Copy a maximum of max_chars characters from src into dest, + * and return a pointer to the terminating NULL in dest. + * + * @param dest destination buffer. + * @param src string written into the destination buffer + * @param max_chars maximum number of characters to put into the destination + * buffer, excluding the trailing NULL. Must be less than or equal to + * the number of bytes available at dest - 1. + * @return a pointer to the end of the written string (the location of the + * terminating NULL). */ -char* smartstrcat(char* dest, char* src, size_t max_chars); +char* smartstrncpy(char* dest, char* src, size_t max_chars); +/** + * URL-unescape the source string into the destination string, up to the + * maximum number of destination characters. + * + * @param dst raw string buffer. + * @param src URL-escaped source string. + * @param max_size maximum number of characters to put into the destination + * buffer, excluding the trailing NULL. Must be less than or equal to + * sizeof(dst) - 1. + * @return the length of the raw string excluding the trailing NULL. Will be + * 0 if there were no characters to unescape or if the source string + * was malformed. + */ int urldecode(char *dst, const char *src, size_t max_size); +/** + * XML-escape the source string into the destination string, up to the maximum + * number of destination characters. + * + * @param dst XML-escaped string buffer. + * @param src raw source string. + * @param max_size maximum number of characters to put into the destination + * buffer, excluding the trailing NULL. Must be less than or equal to + * sizeof(dst) - 1. + * @return the length of the XML-escaped string excluding the trailing NULL. + */ 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. + * Return the value in the query string for the requested parameter name. + * + * @param query_string the URL query string. + * @param param_name the parameter name. + * @return the parameter value or NULL if out-of-memory. The caller must free + * the returned memory. */ 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//dial_data. If your DIAL server uses a different url-path, - * you will need to adapt the method below. +/** + * Parse the application name out of the full URI, for example + * /app/YouTube/dial_data. The application name identified as the string + * appearing before the last trailing slash, possibly prefixed with a slash, + * so /apps//dial_data and YouTube would be the application name in + * the previous example. + * + * If your DIAL server uses a different path format you will need to change + * this method to match. + * + * @param uri the URI, there must be a trailing slash. + * @return the application name, or "unknown" if two slashes cannot be found + * or the application name is zero-length, or NULL if out-of-memory. + * The caller must free the returned memory. */ char *parse_app_name(const char *uri); -/* - * Parse a list of DIALData params out of a query string. +/** + * Return a linked list of DIAL data constructed from the name/value parameter + * pairs of the provided query string. + * + * This function must be called while holding a mutex and is not itself + * thread-safe. + * + * @param query_string the URL query string. + * @return the DIAL data or NULL if there is none (e.g. parse error) or out-of- + * memory. The caller must free the returned memory. */ DIALData *parse_params(char * query_string); -/* Returns a url-encoded version of str */ -/* IMPORTANT: be sure to free() the returned string after use */ +/** + * Return the URL-escaped version of the provided string, which may be as + * large as 3x the size of the provided string, plus a trailing NULL byte. + * + * @return the URL-escaped version of the provided string. The caller must + * free the returned string. + */ char *url_encode(const char *str); #endif // URLLIB_H_