mirror of
https://github.com/Netflix/dial-reference.git
synced 2026-06-08 02:49:58 +00:00
dial spec 1.7
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
*.o
|
||||
client/dialclient
|
||||
server/dialserver
|
||||
server/tests/run_tests
|
||||
55
Changelog
Normal file
55
Changelog
Normal file
@@ -0,0 +1,55 @@
|
||||
---------------------------------------------------------------------
|
||||
Version 1.0.5 (06edbce)
|
||||
---------------------------------------------------------------------
|
||||
Server changes:
|
||||
- Update to DIAL specificiation 1.7.2
|
||||
- CORE compliance security patch
|
||||
- Added CORS compliance test scripts
|
||||
|
||||
---------------------------------------------------------------------
|
||||
Version 1.0.4 (1673785)
|
||||
---------------------------------------------------------------------
|
||||
Server changes:
|
||||
- Update to DIAL specificiation 1.7
|
||||
- Catch SIGTERM so that we don't self terminate.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
Version 1.0.2 (1673785)
|
||||
---------------------------------------------------------------------
|
||||
Server changes:
|
||||
- Fix an application state bug when the application is launched
|
||||
without using DIAL.
|
||||
- Remove unnecessary functionality from Mongoose.
|
||||
- Use constant values for DIAL port.
|
||||
- Propery reap child processes.
|
||||
|
||||
Client (test tool) changes:
|
||||
- None
|
||||
|
||||
---------------------------------------------------------------------
|
||||
Version 1.0.1 (1601607)
|
||||
---------------------------------------------------------------------
|
||||
General Changes:
|
||||
- Simplified directory structure.
|
||||
- Version.txt changed to Version.h.
|
||||
- moved main Makefile into src dir.
|
||||
|
||||
Server changes:
|
||||
- Server configuration options moved into dial_options.h.
|
||||
- Added logic to walk/proc to see if the named app (eg Netflix)
|
||||
is running before attempting to start/kill the app.
|
||||
- Improved payload handling:
|
||||
- Explicitly define payload size.
|
||||
- Added logic to check validity of payload, and send HTTP 400
|
||||
"bad request" responses to requests with improper payloads.
|
||||
- Added URL encoding for payloads.
|
||||
- Added logic to compare launch payload on subsequent launch
|
||||
requests.
|
||||
|
||||
Client (test tool) changes:
|
||||
- small change to printed format of server names.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
Version 1.0 (1416215)
|
||||
---------------------------------------------------------------------
|
||||
Initial release.
|
||||
19
LICENSE
Normal file
19
LICENSE
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2015 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.
|
||||
78
README
Normal file
78
README
Normal file
@@ -0,0 +1,78 @@
|
||||
--------------------------------------------------------------------------------
|
||||
Building the DIAL server
|
||||
--------------------------------------------------------------------------------
|
||||
1) Define the TARGET environment variable to point to the CC compiler prefix
|
||||
for your target platform.
|
||||
|
||||
|
||||
2) Run make, passing in your TARGET value.
|
||||
|
||||
For example:
|
||||
TARGET=/usr/local/i686-DIAL-EXAMPLE/bin/i686-DIAL-EXAMPLE make
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
Running the DIAL server
|
||||
--------------------------------------------------------------------------------
|
||||
The DIAL server should be started as a service, after the platform's networking
|
||||
has been initialized, and it should remain running at all times (a daemon
|
||||
process in the system).
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
Building the DIAL client
|
||||
--------------------------------------------------------------------------------
|
||||
The DIAL client is a standalone C++ console application you can use to test
|
||||
a running DIAL server implementation on your device. Unlike the server, which
|
||||
is built for, and meant to run on your device, the client is meant to run on
|
||||
your desktop (development) machine.
|
||||
|
||||
The DIAL client uses CURL to send HTTP REST commands to the DIAL server, so to
|
||||
build the client, you need to ensure that the CURL dependencies are
|
||||
defined properly.
|
||||
|
||||
Alternatively, you can build against a different, current version of libcurl.
|
||||
Adjust the INCLUDES and LDFLAGS definitions to point to your actual libcurl
|
||||
header and library locations. In most cases, you can omit the TARGET define.
|
||||
|
||||
Note: the -rpath argument passed to LDFLAGS specifies the libcurl location
|
||||
to the runtime linker.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
Running the DIAL client in interactive (menu) mode
|
||||
--------------------------------------------------------------------------------
|
||||
1) The DIAL client application must be running in the same subnet as the
|
||||
DIAL server.
|
||||
|
||||
2) Start the client: ./dialclient (or ./dialclient -m)
|
||||
The on-screen menu will list all available actions.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
Running the DIAL client in conformance test (non-interactive) mode
|
||||
--------------------------------------------------------------------------------
|
||||
1) The DIAL client application must be running in the same subnet as the
|
||||
DIAL server.
|
||||
|
||||
2) Start the client:
|
||||
./dialclient -i [input-file] [-o output-file] [-a server-IP-addr]
|
||||
|
||||
In script-driven mode, the client reads in an input-file, executes the
|
||||
instructions in the input-file, and generates a
|
||||
report. The default file locations (which can be overridden) are:
|
||||
./dialclient_input.txt
|
||||
./report.html
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
DIAL client Usage
|
||||
--------------------------------------------------------------------------------
|
||||
When running the DIAL client, you have the following options
|
||||
usage: dialclient <option>
|
||||
|
||||
Option Parameter Description
|
||||
-h none Usage menu
|
||||
-m none Use menu
|
||||
-o filename Reporter output file (./report.html)
|
||||
-i filename Input File (./dialclient_input.txt)
|
||||
-a ip_address IP addr of DIAL server (used for conformance testing)
|
||||
|
||||
If you do not provide an ip_address and multiple servers are discovered, the
|
||||
client will prompt you to select a server.
|
||||
38
build/Version.h
Normal file
38
build/Version.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* (c) 1997-2015 Netflix, Inc. All content herein is protected by
|
||||
* U.S. copyright and other applicable intellectual property laws and
|
||||
* may not be copied without the express permission of Netflix, Inc.,
|
||||
* which reserves all rights. Reuse of any of this content for any
|
||||
* purpose without the permission of Netflix, Inc. is strictly
|
||||
* prohibited.
|
||||
*/
|
||||
|
||||
#ifndef DIAL_VERSION_H
|
||||
#define DIAL_VERSION_H
|
||||
|
||||
#undef DIAL_VERSION_MAJOR
|
||||
#define DIAL_VERSION_MAJOR 1
|
||||
|
||||
#undef DIAL_VERSION_MINOR
|
||||
#define DIAL_VERSION_MINOR 0
|
||||
|
||||
#undef DIAL_VERSION_PATCH
|
||||
#define DIAL_VERSION_PATCH 5
|
||||
|
||||
#undef DIAL_VERSION_NUMBER_STR2
|
||||
#define DIAL_VERSION_NUMBER_STR2(M) #M
|
||||
#undef DIAL_VERSION_NUMBER_STR
|
||||
#define DIAL_VERSION_NUMBER_STR(M) DIAL_VERSION_NUMBER_STR2(M)
|
||||
|
||||
#undef DIAL_VERSION_NUMBER
|
||||
#define DIAL_VERSION_NUMBER DIAL_VERSION_NUMBER_STR(DIAL_VERSION_MAJOR) "." DIAL_VERSION_NUMBER_STR(DIAL_VERSION_MINOR) "." DIAL_VERSION_NUMBER_STR(DIAL_VERSION_PATCH)
|
||||
|
||||
#undef DIAL_VERSION_STRING
|
||||
#ifdef DIAL_VERSION_SUFFIX
|
||||
# define DIAL_VERSION_STRING DIAL_VERSION_NUMBER "-" DIAL_VERSION_SUFFIX
|
||||
#else
|
||||
# define DIAL_VERSION_STRING DIAL_VERSION_NUMBER
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
||||
66
build/dial_build.sh
Executable file
66
build/dial_build.sh
Executable file
@@ -0,0 +1,66 @@
|
||||
#!/bin/bash -e
|
||||
#
|
||||
# (c) 1997-2012 Netflix, Inc. All content herein is protected by
|
||||
# U.S. copyright and other applicable intellectual property laws and
|
||||
# may not be copied without the express permission of Netflix, Inc.,
|
||||
# which reserves all rights. Reuse of any of this content for any
|
||||
# purpose without the permission of Netflix, Inc. is strictly
|
||||
# prohibited.
|
||||
|
||||
BUILD_PREFIX="DIAL"
|
||||
BUILD_MAJOR=
|
||||
BUILD_MINOR=
|
||||
BUILD_PATCH=
|
||||
BUILD_SUFFIX=
|
||||
BUILD_NUMBER=
|
||||
COPY_SOURCE_VERSION_H=
|
||||
|
||||
if [ -n "${P4_CHANGELIST}" ]; then
|
||||
#echo "Using information from Jenkins environment to determine RELEASE number: $P4_CHANGELIST"
|
||||
BUILD_NUMBER="${P4_CHANGELIST}"
|
||||
fi
|
||||
|
||||
if [ -z "$COPY_SOURCE_VERSION_H" ]; then
|
||||
VERSION_H="Version.h"
|
||||
COPY_SOURCE_VERSION_H=`find ${COPY_SOURCE_DIR} -name ${VERSION_H}`
|
||||
if [ -z "$COPY_SOURCE_VERSION_H" ]; then
|
||||
echo "${VERSION_H} cannot be found!"
|
||||
exit 1
|
||||
elif echo "$COPY_VERSION" | grep --quiet ' '; then
|
||||
echo "Too many ${VERSION_H} found!"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
BUILD_MAJOR=`grep -m1 -i "^# *define *${BUILD_PREFIX}_VERSION_MAJOR" "$COPY_SOURCE_VERSION_H" | sed "s,/[/*].*$,," | awk '{print $(NF)}'`
|
||||
if [ -z "$BUILD_MAJOR" ]; then
|
||||
echo "Failed to determine version major of current source." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$BUILD_MINOR" ]; then
|
||||
BUILD_MINOR=`grep -m1 -i "^# *define *${BUILD_PREFIX}_VERSION_MINOR" "$COPY_SOURCE_VERSION_H" | sed "s,/[/*].*$,," | awk '{print $(NF)}'`
|
||||
if [ -z "$BUILD_MINOR" ]; then
|
||||
echo "Failed to determine version minor of current source." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$BUILD_PATCH" ]; then
|
||||
BUILD_PATCH=`grep -m1 -i "^# *define *${BUILD_PREFIX}_VERSION_PATCH" "$COPY_SOURCE_VERSION_H" | sed "s,/[/*].*$,," | awk '{print $(NF)}'`
|
||||
if [ -z "$BUILD_PATCH" ]; then
|
||||
echo "Failed to determine version minor of current source." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
BUILD_STRING="${BUILD_MAJOR}.${BUILD_MINOR}.${BUILD_PATCH}-${BUILD_NUMBER}"
|
||||
echo "${BUILD_STRING}"
|
||||
|
||||
tar czvhf ${WORKSPACE}/DIAL-$BUILD_STRING.tar.gz *
|
||||
|
||||
# Package the binaries
|
||||
cp ${WORKSPACE}/src/dial/client/dialclient ${WORKSPACE}/build
|
||||
cp ${WORKSPACE}/src/dial/server/dialserver ${WORKSPACE}/build
|
||||
cd ${WORKSPACE}/build
|
||||
tar czvhf ${WORKSPACE}/DIAL-$BUILD_STRING-binaries.tar.gz *
|
||||
133
client/DialClientInput.cpp
Normal file
133
client/DialClientInput.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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 "DialClientInput.h"
|
||||
#include "DialServer.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
bool DialClientInput::init(std::string file)
|
||||
{
|
||||
if( file.empty() ) file = DialClientInput::getDefaultFilename();
|
||||
ATRACE("DialClientInput::%s, opening %s\n", __FUNCTION__, file.c_str());
|
||||
string line;
|
||||
ifstream myfile (file.c_str());
|
||||
if (myfile.is_open())
|
||||
{
|
||||
// first fill _actions with commands from the input file
|
||||
while ( myfile.good() )
|
||||
{
|
||||
getline (myfile,line);
|
||||
if( line[0] != '#' && !line.empty() )
|
||||
{
|
||||
if( line.find("addApplication") != line.npos )
|
||||
{
|
||||
// add an application
|
||||
size_t pos = line.find_first_of('=');
|
||||
_applist.push_back( line.substr(pos+1, line.length()));
|
||||
ATRACE("Adding app: %s\n", line.substr(pos+1, line.length()).c_str());
|
||||
}
|
||||
else if( line.find("addErrorApplication") != line.npos )
|
||||
{
|
||||
// add an application
|
||||
size_t pos = line.find_first_of('=');
|
||||
_errorapplist.push_back( line.substr(pos+1, line.length()));
|
||||
ATRACE("Adding error app: %s\n", line.substr(pos+1, line.length()).c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
// add a command
|
||||
size_t pos = line.find_first_of(" ");
|
||||
std::string params = line.substr(pos+1, line.length());
|
||||
std::pair<std::string, std::string>
|
||||
action_to_push(line.substr(0, pos),
|
||||
pos == line.npos ? "":params);
|
||||
_actions.push_back(action_to_push);
|
||||
ATRACE("command: %s params: %s\n",
|
||||
line.substr(0, pos).c_str(), params.c_str() );
|
||||
}
|
||||
}
|
||||
#ifdef DEBUG
|
||||
//else ATRACE("COMMENT: %s\n", line.c_str());
|
||||
#endif
|
||||
}
|
||||
myfile.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Unable to open file\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DialClientInput::addApplication( string& application )
|
||||
{
|
||||
// see if the application exits
|
||||
vector<string>::iterator it;
|
||||
for( it = _applist.begin(); it < _applist.end(); it ++ )
|
||||
if( !((*it).compare( application )) ) break;
|
||||
|
||||
// if not, add it
|
||||
if( it < _applist.end() ) _applist.push_back(application);
|
||||
else return false; // already in the list
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DialClientInput::getNextAction( string& command, vector<string>& parameters )
|
||||
{
|
||||
if (_actions.empty()) return false;
|
||||
|
||||
pair<string, string> action = _actions.front();
|
||||
_actions.erase(_actions.begin());
|
||||
|
||||
command = action.first;
|
||||
string params = action.second;
|
||||
size_t pos = params.find_first_of(" "), start = 0;
|
||||
// Parse out the parameters from the string
|
||||
while (1)
|
||||
{
|
||||
parameters.push_back(params.substr( start, pos-start ));
|
||||
if( pos == params.npos ) break;
|
||||
start = pos+1;
|
||||
pos = params.find_first_of(" ", start);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DialClientInput::getApplicationList( vector<string> &list )
|
||||
{
|
||||
list = _applist;
|
||||
}
|
||||
|
||||
|
||||
void DialClientInput::getErrorApplicationList(vector<string> &list)
|
||||
{
|
||||
list = _errorapplist;
|
||||
}
|
||||
|
||||
89
client/DialClientInput.h
Normal file
89
client/DialClientInput.h
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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 DIALCLIENTINPUT_H
|
||||
#define DIALCLIENTINPUT_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
class DialClientInput
|
||||
{
|
||||
public:
|
||||
DialClientInput() {}
|
||||
~DialClientInput() {}
|
||||
|
||||
/**
|
||||
* Called to initialize
|
||||
*
|
||||
* @param[in] file Name of the file to load
|
||||
* @return true if successful, false otherwise
|
||||
*/
|
||||
bool init( string file );
|
||||
|
||||
/**
|
||||
* The DialClient application can use this to add applications passed on
|
||||
* the command line. If the application is already in the list, it will
|
||||
* drop the add.
|
||||
*
|
||||
* @param[in] application Name of the application to add.
|
||||
*
|
||||
* @return true if successful, false otherwise
|
||||
*/
|
||||
bool addApplication( string &application );
|
||||
|
||||
/**
|
||||
* Get the next action to execute
|
||||
*
|
||||
* @param[out] command Command to execute
|
||||
* @param[out] parameters Parameters for the command
|
||||
*/
|
||||
bool getNextAction( string& command, vector<string>& parameters );
|
||||
|
||||
/**
|
||||
* Get a list of valid applications defined in the input file.
|
||||
*
|
||||
* @param[out] list List of valid applications
|
||||
*/
|
||||
void getApplicationList(vector<string> &list);
|
||||
|
||||
/**
|
||||
* Get a list of applications used for error tests (should not exist).
|
||||
*
|
||||
* @param[out] list List of error applications
|
||||
*/
|
||||
void getErrorApplicationList(vector<string> &list);
|
||||
|
||||
static string getDefaultFilename() { return "./dialclient_input.txt"; }
|
||||
|
||||
private:
|
||||
vector<string> _applist;
|
||||
vector<string> _errorapplist;
|
||||
vector< pair<string,string> > _actions;
|
||||
};
|
||||
|
||||
#endif // DIALCLIENTINPUT_H
|
||||
587
client/DialConformance.cpp
Normal file
587
client/DialConformance.cpp
Normal file
@@ -0,0 +1,587 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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 <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#include "DialConformance.h"
|
||||
#include <assert.h>
|
||||
#include <time.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// Number of milliseconds to sleep between launches when doing a launch=ALL
|
||||
#define LAUNCH_SLEEP 6000
|
||||
#define MAX_PARAMETER_LENGTH 60
|
||||
|
||||
using namespace std;
|
||||
|
||||
/*
|
||||
* This class will be reponsible for reporting the conformance test results.
|
||||
* Requirements:
|
||||
* * Results should be in HTML
|
||||
* * Results should be easily readable
|
||||
* * Results should clearly distinguish between pass and fail
|
||||
* * If there is a failure, there should be ample information provided
|
||||
* for debugging
|
||||
*/
|
||||
class OutputWriter
|
||||
{
|
||||
public:
|
||||
OutputWriter( string fileName ) :
|
||||
_filename(fileName),
|
||||
_pCurrentTest(NULL),
|
||||
_isComplete(false)
|
||||
{
|
||||
}
|
||||
~OutputWriter()
|
||||
{ }
|
||||
|
||||
void setOutputFile( string fileName )
|
||||
{
|
||||
_filename = fileName;
|
||||
}
|
||||
|
||||
void setTest( string testName )
|
||||
{
|
||||
assert( _pCurrentTest == NULL );
|
||||
_pCurrentTest = new Test(testName);
|
||||
_tests.push_back(_pCurrentTest);
|
||||
}
|
||||
void clearTest()
|
||||
{
|
||||
// still holding a ref in the test vector. That will get deleted
|
||||
// when the test is complete
|
||||
_pCurrentTest = NULL;
|
||||
}
|
||||
|
||||
void setError( string error ) { _pCurrentTest->addError(error); }
|
||||
void setResult( bool result ) { _pCurrentTest->setResult( result ); }
|
||||
|
||||
void start( string& friendlyname, string& uuid, string ipaddress )
|
||||
{
|
||||
_file.open(_filename.c_str());
|
||||
_file << "<html>\n<body>\n";
|
||||
_file << "<font size=\"6\"><b>FriendlyName: " << friendlyname;
|
||||
_file << "<br>\nIP: " << ipaddress;
|
||||
_file << "<br>\nUUID: " << uuid;
|
||||
|
||||
{
|
||||
time_t rawtime;
|
||||
struct tm * timeinfo;
|
||||
char buffer [80];
|
||||
|
||||
time ( &rawtime );
|
||||
timeinfo = localtime ( &rawtime );
|
||||
|
||||
//strftime (buffer,80,"%I:%M%p.",timeinfo);
|
||||
strftime (buffer,80,"%c",timeinfo);
|
||||
_file << "<br>\nTime of Run: " << buffer << "</b></font>";
|
||||
}
|
||||
_file << "<table border=\"10\">\n";
|
||||
_file << "<tr>\n";
|
||||
_file << "<th> RESULT </th>"; //header 1
|
||||
_file << "<th> Test Executed </th>"; //header 2
|
||||
_file << "<th> Errors </th>"; //header 2
|
||||
_file << "</tr>\n";
|
||||
}
|
||||
|
||||
void complete()
|
||||
{
|
||||
vector<Test*>::iterator it;
|
||||
for( it = _tests.begin(); it < _tests.end(); it++ )
|
||||
{
|
||||
write("<tr>");
|
||||
// write the name and the result
|
||||
stringstream result;
|
||||
bool testResult = (*it)->getResult();
|
||||
result << "<td><b> <font color=\"" << (testResult ? "green" : "red") << "\"> ";
|
||||
result << (testResult ? "SUCCESS" : "FAIL") << " </b></font></td>\n<td>" << (*it)->getName() << "</td>";
|
||||
write( result.str() );
|
||||
|
||||
// write errors if they exist
|
||||
write("<td>");
|
||||
vector<string> errors = (*it)->getErrors();
|
||||
if( !errors.empty() )
|
||||
{
|
||||
vector<string>::iterator it2;
|
||||
write("<b>");
|
||||
for( it2 = errors.begin(); it2 < errors.end(); it2++ )
|
||||
write( (*it2) );
|
||||
write("</b>");
|
||||
}
|
||||
else
|
||||
write("NO ERROR");
|
||||
write("</td>\n</tr>");
|
||||
|
||||
// delete the test
|
||||
delete (*it);
|
||||
}
|
||||
_file << "</table></body>\n</html>\n";
|
||||
_file.close();
|
||||
|
||||
_isComplete = true;
|
||||
}
|
||||
|
||||
private:
|
||||
// Abstraction of a test
|
||||
class Test
|
||||
{
|
||||
public:
|
||||
Test(string& name) :
|
||||
_testname(name),
|
||||
_testPassed(false) {}
|
||||
~Test() {}
|
||||
|
||||
string getName() { return _testname; }
|
||||
void setResult( bool result ) { _testPassed = result; }
|
||||
bool getResult() { return _testPassed; }
|
||||
|
||||
void addError( string error ) { _errors.push_back(error); }
|
||||
vector<string> getErrors() { return _errors; }
|
||||
|
||||
private:
|
||||
string _testname;
|
||||
bool _testPassed;
|
||||
vector<string> _errors;
|
||||
};
|
||||
|
||||
string _filename;
|
||||
ofstream _file;
|
||||
vector<Test*> _tests;
|
||||
Test* _pCurrentTest;
|
||||
bool _isComplete;
|
||||
|
||||
int write( string line )
|
||||
{
|
||||
_file << line.c_str() << "\n";
|
||||
return line.length()+1;
|
||||
}
|
||||
};
|
||||
|
||||
static const string defaultOutputFile = "./report.html";
|
||||
static OutputWriter gWriter(defaultOutputFile);
|
||||
|
||||
DialConformance *DialConformance::sConformance = 0;
|
||||
|
||||
DialConformance* DialConformance::create(void)
|
||||
{
|
||||
assert( DialConformance::sConformance == 0 );
|
||||
return new DialConformance();
|
||||
}
|
||||
|
||||
DialConformance* DialConformance::instance(void)
|
||||
{
|
||||
return DialConformance::sConformance;
|
||||
}
|
||||
|
||||
DialConformance::DialConformance()
|
||||
{
|
||||
assert( DialConformance::sConformance == 0 );
|
||||
DialConformance::sConformance = this;
|
||||
}
|
||||
|
||||
DialConformance::~DialConformance()
|
||||
{
|
||||
assert( DialConformance::sConformance == this );
|
||||
DialConformance::sConformance = 0;
|
||||
}
|
||||
|
||||
void DialConformance::extractParamValue(
|
||||
string& param, string& value )
|
||||
{
|
||||
// TODO: Add support for quotes after the equals
|
||||
size_t posBegin = param.find("=");
|
||||
value = param.substr(posBegin+1, param.length()-posBegin);
|
||||
}
|
||||
|
||||
bool DialConformance::validateParams( vector<string>& params,
|
||||
string& responseHeaders, string& responseBody )
|
||||
{
|
||||
bool testPassed = true;
|
||||
vector<string>::iterator it;
|
||||
for( it = params.begin(); it < params.end() && testPassed == true; it++ )
|
||||
{
|
||||
ATRACE("%s:: params:%s \n", __FUNCTION__, (*it).c_str());
|
||||
if( (*it).find("httpresponse=") != (*it).npos )
|
||||
testPassed = validateHttpResponse( responseHeaders, *it );
|
||||
if( (*it).find("httpresponseheader=") != (*it).npos )
|
||||
testPassed = validateHttpHeaders( responseHeaders, *it );
|
||||
if( (*it).find("resultbody=") != (*it).npos )
|
||||
testPassed = validateResponseBody( responseBody, *it );
|
||||
}
|
||||
return testPassed;
|
||||
}
|
||||
|
||||
|
||||
bool DialConformance::validateHttpResponse(
|
||||
string& headers, string& param )
|
||||
{
|
||||
bool retval = false;
|
||||
string responseCode;
|
||||
extractParamValue( param, responseCode );
|
||||
|
||||
ATRACE("%s: Searching for %s\n", __FUNCTION__, responseCode.c_str());
|
||||
if( headers.find(responseCode) != headers.npos )
|
||||
{
|
||||
ATRACE("%s: %s FOUND\n", __FUNCTION__, responseCode.c_str());
|
||||
retval = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ATRACE("%s: %s not found\n", __FUNCTION__, responseCode.c_str());
|
||||
stringstream error;
|
||||
error << responseCode << " not found\n";
|
||||
gWriter.setError( error.str() );
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool DialConformance::validateHttpHeaders(
|
||||
string& headers, string& param )
|
||||
{
|
||||
bool retval = false;
|
||||
string responseCode;
|
||||
extractParamValue( param, responseCode );
|
||||
|
||||
ATRACE("%s: Searching for %s\n", __FUNCTION__, responseCode.c_str());
|
||||
if( headers.find(responseCode) != headers.npos )
|
||||
{
|
||||
ATRACE("%s: %s FOUND\n", __FUNCTION__, responseCode.c_str());
|
||||
retval = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ATRACE("%s: %s not found\n", __FUNCTION__, responseCode.c_str());
|
||||
stringstream error;
|
||||
error << responseCode << " not found\n";
|
||||
gWriter.setError( error.str() );
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool DialConformance::validateResponseBody(
|
||||
string& body, string& param )
|
||||
{
|
||||
bool retval = false;
|
||||
string responseCode;
|
||||
extractParamValue( param, responseCode );
|
||||
|
||||
ATRACE("%s: Searching for %s\n", __FUNCTION__, responseCode.c_str());
|
||||
if( body.find(responseCode) != body.npos )
|
||||
{
|
||||
ATRACE("%s: %s FOUND\n", __FUNCTION__, responseCode.c_str());
|
||||
retval = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ATRACE("%s: %s not found\n", __FUNCTION__, responseCode.c_str());
|
||||
stringstream error;
|
||||
error << responseCode << " not found\n";
|
||||
error << "response: " << body;
|
||||
gWriter.setError( error.str() );
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
void DialConformance::getPayload(vector<string>& params, string& payload )
|
||||
{
|
||||
vector<string>::iterator it;
|
||||
for( it = params.begin(); it < params.end(); it++ )
|
||||
{
|
||||
if( (*it).find("param") != (*it).npos )
|
||||
{
|
||||
// Check to see if the param is using quotes. If it is, split on
|
||||
// that. If not, split on a space
|
||||
size_t posEnd, pos = (*it).find( "=" );
|
||||
if( (*it)[pos+1] == '\"' )
|
||||
{
|
||||
posEnd = (*it).find( "\"", pos+2 );
|
||||
payload = (*it).substr( pos+2, posEnd-(pos+2) );
|
||||
}
|
||||
else
|
||||
{
|
||||
posEnd = (*it).find( " ", pos );
|
||||
payload = (*it).substr( pos+1, posEnd );
|
||||
}
|
||||
ATRACE("payload = %s\n", payload.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DialConformance::execute_launch( Application* pApp, vector<string>& params )
|
||||
{
|
||||
string payload, responseHeaders, responseBody;
|
||||
getPayload( params, payload );
|
||||
pApp->launch( payload, responseHeaders, responseBody );
|
||||
return (validateParams( params, responseHeaders, responseBody ));
|
||||
}
|
||||
|
||||
bool DialConformance::execute_status( Application* pApp, vector<string>& params )
|
||||
{
|
||||
ATRACE("%s::%d\n", __FUNCTION__, __LINE__);
|
||||
string responseHeaders, responseBody;
|
||||
pApp->status( responseHeaders, responseBody );
|
||||
|
||||
bool retval = validateParams( params, responseHeaders, responseBody );
|
||||
if( retval && (responseHeaders.find("200") != responseHeaders.npos) )
|
||||
{
|
||||
// if the status was successful
|
||||
// Ensure the response body contains the application name
|
||||
if( responseBody.find( pApp->getName() ) == responseBody.npos )
|
||||
{
|
||||
retval = false;
|
||||
stringstream error;
|
||||
error << "Reponse XML did not contain application name: " << pApp->getName();
|
||||
gWriter.setError( error.str() );
|
||||
retval = false;
|
||||
}
|
||||
if( responseBody.find( "urn:dial-multiscreen-org:schemas:dial" )
|
||||
== responseBody.npos )
|
||||
{
|
||||
gWriter.setError( "Response Body did not contain DIAL service "
|
||||
"string: urn:dial-multiscreen-org:schemas:dial" );
|
||||
retval = false;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool DialConformance::execute_stop( Application* pApp, vector<string>& params )
|
||||
{
|
||||
ATRACE("%s::%d\n", __FUNCTION__, __LINE__);
|
||||
string responseHeaders, responseBody;
|
||||
pApp->stop(responseHeaders);
|
||||
return (validateParams( params, responseHeaders, responseBody ));
|
||||
}
|
||||
|
||||
DialConformance::Application* DialConformance::getApplication( string& command )
|
||||
{
|
||||
Application* pApp = NULL;
|
||||
|
||||
// extract the application
|
||||
string app;
|
||||
size_t pos = command.find("=");
|
||||
app = command.substr(pos+1, command.length());
|
||||
assert( app.length() > 0 );
|
||||
|
||||
// find the application
|
||||
vector<Application*>::iterator it;
|
||||
for( it = _apps.begin(); it < _apps.end(); it++ )
|
||||
{
|
||||
ATRACE("Comparing: %s.compare(%s)\n", (*it)->getName().c_str(), app.c_str());
|
||||
if( (*it)->getName().compare(app) == 0)
|
||||
{
|
||||
pApp = (*it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert(pApp != NULL);
|
||||
return pApp;
|
||||
}
|
||||
|
||||
bool DialConformance::execute_command( string& command, vector<string>& params )
|
||||
{
|
||||
ATRACE("%s::%s\n", __FUNCTION__, command.c_str());
|
||||
bool retval = false;
|
||||
if( command.find("launch") != command.npos )
|
||||
retval = execute_launch(getApplication(command), params);
|
||||
else if ( command.find("status") != command.npos )
|
||||
retval = execute_status(getApplication(command), params);
|
||||
else if ( command.find("stop") != command.npos )
|
||||
retval = execute_stop(getApplication(command), params);
|
||||
else if ( command.find("sleep") != command.npos )
|
||||
{
|
||||
string time;
|
||||
size_t pos = command.find("=");
|
||||
time = command.substr(pos+1, command.length());
|
||||
unsigned long milliseconds = atoi(time.c_str());
|
||||
ATRACE("Sleeping %ld milliseconds \n", milliseconds);
|
||||
usleep(milliseconds*1000);
|
||||
retval = true;
|
||||
}
|
||||
else ATRACE("Can't execute command: %s\n", command.c_str());
|
||||
return retval;
|
||||
}
|
||||
|
||||
void DialConformance::run_internal( DialServer* pServer )
|
||||
{
|
||||
string command;
|
||||
vector<string> params;
|
||||
bool retval;
|
||||
string friendlyName, uuid;
|
||||
|
||||
// only execute the test if we have a friendly name and UUID
|
||||
retval = pServer->getFriendlyName( friendlyName );
|
||||
if (retval) pServer->getUuid( uuid );
|
||||
if (retval) gWriter.start( friendlyName, uuid, pServer->getIpAddress() );
|
||||
while( retval && _input.getNextAction(command, params) )
|
||||
{
|
||||
vector<string> commandlist;
|
||||
|
||||
// Check the command here, if it is command=ALL, then we need to loop through
|
||||
// all of the valid applications
|
||||
size_t pos;
|
||||
if( (pos = command.find("=ALL")) != command.npos )
|
||||
{
|
||||
vector<Application*>::iterator it;
|
||||
for( it = _apps.begin(); it < _apps.end(); it++ )
|
||||
{
|
||||
// strip off ALL and append the application name
|
||||
string newCommand = command.substr(0, pos+1);
|
||||
newCommand.append( (*it)->getName() );
|
||||
Application *pApp = getApplication( newCommand );
|
||||
if( pApp != NULL && (!pApp->isErrorApp()) )
|
||||
{
|
||||
ATRACE("Adding command %s\n", newCommand.c_str());
|
||||
commandlist.push_back( newCommand );
|
||||
}
|
||||
|
||||
if( newCommand.find("launch") != newCommand.npos )
|
||||
{
|
||||
// If we are adding launch commands, insert a sleep so that we don't overwhelm
|
||||
// the target
|
||||
stringstream sleep;
|
||||
sleep << "sleep=" << LAUNCH_SLEEP;
|
||||
commandlist.push_back( sleep.str() );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ATRACE("Adding command %s\n", command.c_str());
|
||||
commandlist.push_back(command);
|
||||
}
|
||||
|
||||
vector<string>::iterator it;
|
||||
for( it = commandlist.begin(); it < commandlist.end(); it++ )
|
||||
{
|
||||
// construct the name of the test
|
||||
ATRACE("***********************" "\n%s: Command: %s \n",
|
||||
__FUNCTION__, (*it).c_str());
|
||||
stringstream test;
|
||||
test << "Command:" << (*it);
|
||||
|
||||
// The input code will always push a parameter. If there are no
|
||||
// parameters, it will push an empty string, so test for that here.
|
||||
if( params.size() > 0 && !params[0].empty() ) test << " Params:";
|
||||
|
||||
vector<string>::iterator it2;
|
||||
for( it2 = params.begin(); it2 < params.end(); it2++)
|
||||
{
|
||||
ATRACE("param: %s ", (*it2).c_str());
|
||||
|
||||
// check to see if we have a really large parameter, if we do,
|
||||
// truncate it to a reasonable length
|
||||
string param = (*it2);
|
||||
if( param.size() > MAX_PARAMETER_LENGTH )
|
||||
{
|
||||
param = (*it2).substr(0, MAX_PARAMETER_LENGTH-3 );
|
||||
param.append("...");
|
||||
}
|
||||
test << param << " ";
|
||||
}
|
||||
|
||||
if( (*it).find("sleep") == (*it).npos )
|
||||
{
|
||||
// set the test name
|
||||
gWriter.setTest( test.str() );
|
||||
|
||||
// run the test
|
||||
bool retval = execute_command( *it, params );
|
||||
printf("%s\n", retval ? "SUCCESS":"FAILED");
|
||||
|
||||
// set the result and prep for the next test
|
||||
gWriter.setResult( retval );
|
||||
gWriter.clearTest();
|
||||
}
|
||||
else
|
||||
{
|
||||
//just sleep here
|
||||
string time;
|
||||
size_t pos = (*it).find("=");
|
||||
time = (*it).substr(pos+1, command.length());
|
||||
unsigned long milliseconds = atoi(time.c_str());
|
||||
ATRACE("Sleeping %ld milliseconds \n", milliseconds);
|
||||
usleep(milliseconds*1000);
|
||||
}
|
||||
}
|
||||
|
||||
// clear the parameters for the next loop
|
||||
params.clear();
|
||||
}
|
||||
if( retval ) gWriter.complete();
|
||||
}
|
||||
|
||||
int DialConformance::run(
|
||||
DialServer* pServer,
|
||||
vector<string>& appList,
|
||||
string &inputFile,
|
||||
string &outputFile )
|
||||
{
|
||||
printf("Running conformance tests from %s and printing report to %s\n\n",
|
||||
inputFile.empty() ?
|
||||
DialClientInput::getDefaultFilename().c_str() :
|
||||
inputFile.c_str(),
|
||||
outputFile.empty() ?
|
||||
defaultOutputFile.c_str() :
|
||||
outputFile.c_str() );
|
||||
//read the input file
|
||||
_input.init(inputFile);
|
||||
if( !outputFile.empty() ) gWriter.setOutputFile( outputFile );
|
||||
|
||||
// Add applications passed by the client (command line)
|
||||
vector<string>::iterator it;
|
||||
for( it = appList.begin(); it < appList.end(); it++ ) _input.addApplication(*it);
|
||||
|
||||
// build the application list
|
||||
vector<string> listOfApps;
|
||||
_input.getApplicationList(listOfApps); // get the list of apps
|
||||
for( it = listOfApps.begin(); it < listOfApps.end(); it++ )
|
||||
{
|
||||
_apps.push_back( new Application(*it, pServer, false) );
|
||||
ATRACE("Adding application: %s\n", (*it).c_str());
|
||||
}
|
||||
|
||||
_input.getErrorApplicationList(listOfApps); // get the list of apps
|
||||
for( it = listOfApps.begin(); it < listOfApps.end(); it++ )
|
||||
{
|
||||
_apps.push_back( new Application(*it, pServer, true) );
|
||||
ATRACE("Adding Error application: %s\n", (*it).c_str());
|
||||
}
|
||||
|
||||
// run the test
|
||||
run_internal( pServer );
|
||||
|
||||
// clean up the application list
|
||||
vector<Application*>::iterator appit;
|
||||
for ( appit = _apps.begin() ; appit < _apps.end(); appit++ ) delete (*appit);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
227
client/DialConformance.h
Normal file
227
client/DialConformance.h
Normal file
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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 DIALCONFORMANCE_H
|
||||
#define DIALCONFORMANCE_H
|
||||
|
||||
#include <memory>
|
||||
#include "DialServer.h"
|
||||
#include "DialClientInput.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
class DialConformance
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Create a singleton
|
||||
*/
|
||||
static DialConformance* create();
|
||||
|
||||
/**
|
||||
* Get a pointer to the singleton
|
||||
*/
|
||||
static DialConformance* instance(void);
|
||||
|
||||
~DialConformance();
|
||||
|
||||
/**
|
||||
* Run the test
|
||||
*
|
||||
* @param[in] pServer DIAL server to test
|
||||
* @param[in] appList List of applications from the command line
|
||||
* @param[in] inputFile Input file used to drive tests
|
||||
* @param[in] outputFile File for writing the report. Use an
|
||||
* empty string to use the default (report.html).
|
||||
*
|
||||
* @return 0 if successful, !0 otherwise
|
||||
*/
|
||||
int run(
|
||||
DialServer* pServer,
|
||||
vector<string>& appList,
|
||||
string &inputFile,
|
||||
string &outputFile );
|
||||
private:
|
||||
DialConformance();
|
||||
DialServer* _pServer;
|
||||
static DialConformance* sConformance;
|
||||
DialClientInput _input;
|
||||
|
||||
/**
|
||||
* Class to manage state for each application supported by the DIAL
|
||||
* server. The application list is managed by the input file.
|
||||
*/
|
||||
class Application{
|
||||
public:
|
||||
enum State
|
||||
{
|
||||
STOPPED,
|
||||
LAUNCHING, // application has just been launched, state
|
||||
// has not been confirmed
|
||||
LAUNCHED,
|
||||
STOPPING // application has been stopped, state has not
|
||||
// been confirmed
|
||||
};
|
||||
|
||||
Application( string& name, DialServer *pServer, bool isErrorApp ) :
|
||||
_isErrorApp(isErrorApp),
|
||||
_pServer(pServer),
|
||||
_state(STOPPED),
|
||||
_stopurl(""),
|
||||
_name(name) {}
|
||||
~Application(){}
|
||||
|
||||
/**
|
||||
* State Getter/Setters
|
||||
*/
|
||||
string getName() { return _name; }
|
||||
State getState() { return _state; }
|
||||
|
||||
/**
|
||||
* Returns true if the application is not a valid application
|
||||
*/
|
||||
bool isErrorApp() { return _isErrorApp; }
|
||||
|
||||
/**
|
||||
* Launch an this application using the server that was stored when
|
||||
* the class was created.
|
||||
*
|
||||
* @param[in] payload Data that is put into the POST data.
|
||||
* @param[out] responseHeaders HTML response headers
|
||||
* @param[out] responseBody HTML response body
|
||||
*/
|
||||
void launch(
|
||||
string& payload,
|
||||
string& responseHeaders,
|
||||
string& responseBody )
|
||||
{
|
||||
_pServer->launchApplication(
|
||||
_name, payload, responseHeaders, responseBody );
|
||||
// TODO: Set state
|
||||
|
||||
// find Location in the header, store the stop url
|
||||
if( !responseHeaders.empty() )
|
||||
{
|
||||
size_t pos, tmp = responseHeaders.find("Location");
|
||||
if( tmp != responseHeaders.npos )
|
||||
{
|
||||
pos = responseHeaders.find("http", tmp);
|
||||
size_t posEnd = responseHeaders.find("\n", pos+1);
|
||||
if( posEnd != responseHeaders.npos )
|
||||
_stopurl = responseHeaders.substr( pos, posEnd );
|
||||
|
||||
// chomp off the \r\n chars
|
||||
DialConformance::chomp( _stopurl );
|
||||
ATRACE("StopURL = %s********\n", _stopurl.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status of this application
|
||||
*
|
||||
* @param[out] responseHeaders HTML response headers
|
||||
* @param[out] responseBody HTML response body
|
||||
*/
|
||||
void status(
|
||||
string& responseHeaders,
|
||||
string& responseBody )
|
||||
{ _pServer->getStatus( _name, responseHeaders, responseBody ); }
|
||||
|
||||
/**
|
||||
* Stop the application
|
||||
*
|
||||
* @param[out] responseHeaders HTML response headers
|
||||
*/
|
||||
void stop(
|
||||
string& responseHeaders )
|
||||
{ stop(_stopurl, responseHeaders ); }
|
||||
|
||||
/**
|
||||
* Stop the application using a custom stop URL.
|
||||
*
|
||||
* @param[out] responseHeaders HTML response headers
|
||||
*/
|
||||
void stop( string& stopurl, string& responseHeaders)
|
||||
{
|
||||
if( !stopurl.empty() )
|
||||
{
|
||||
_pServer->stopApplication( stopurl, responseHeaders );
|
||||
}
|
||||
#ifdef DEBUG
|
||||
else ATRACE("%s: Not sending stop, stop URL is empty\n", __FUNCTION__);
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
bool _isErrorApp;
|
||||
DialServer* _pServer;
|
||||
State _state;
|
||||
string _stopurl;
|
||||
string _name;
|
||||
};
|
||||
// list of applications
|
||||
vector<Application*> _apps;
|
||||
|
||||
// Internal helpers
|
||||
void run_internal( DialServer* pServer );
|
||||
bool execute_command( string& command, vector<string>& params );
|
||||
|
||||
// Get the Application pointer from an application string
|
||||
Application* getApplication( string& command );
|
||||
|
||||
// extract the payload from a list of parameters
|
||||
void getPayload(vector<string>& params, string& payload );
|
||||
|
||||
// Command execution functions
|
||||
bool execute_launch( Application* pApp, vector<string>& params );
|
||||
bool execute_status( Application* pApp, vector<string>& params );
|
||||
bool execute_stop( Application* pApp, vector<string>& params );
|
||||
|
||||
// Helper function to extract a parameter.
|
||||
void extractParamValue( string& param, string& value );
|
||||
|
||||
// Validation functions
|
||||
bool validateParams( vector<string>& params,
|
||||
string& responseHeaders, string& responseBody );
|
||||
bool validateHttpResponse( string& headers, string& params );
|
||||
bool validateHttpHeaders( string& headers, string& params );
|
||||
bool validateResponseBody( string& headers, string& params );
|
||||
|
||||
public:
|
||||
// Helper function to chomp off the carriage return line feed.
|
||||
static void chomp(string& str)
|
||||
{
|
||||
string crlf("\r\n");
|
||||
size_t pos = str.find_last_not_of( crlf );
|
||||
if( pos != str.npos )
|
||||
{
|
||||
ATRACE("CHOMP\n");
|
||||
str.erase(pos+1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif // DIALCONFORMANCE_H
|
||||
344
client/DialDiscovery.cpp
Normal file
344
client/DialDiscovery.cpp
Normal file
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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 <arpa/inet.h>
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <curl/curl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include "DialDiscovery.h"
|
||||
#include <assert.h>
|
||||
|
||||
#define SSDP_TIMEOUT 30
|
||||
#define SSDP_RESPONSE_TIMEOUT 3
|
||||
|
||||
using namespace std;
|
||||
|
||||
DialDiscovery *DialDiscovery::sDiscovery = 0;
|
||||
|
||||
//static char ip_addr[INET_ADDRSTRLEN] = "127.0.0.1";
|
||||
//static int my_port = 0;
|
||||
static struct sockaddr_in saddr;
|
||||
typedef struct
|
||||
{
|
||||
struct sockaddr_in saddr;
|
||||
int sock;
|
||||
socklen_t addrlen;
|
||||
}search_conn;
|
||||
|
||||
static pthread_mutex_t list_locker = PTHREAD_MUTEX_INITIALIZER;
|
||||
class ScopeLocker
|
||||
{
|
||||
public:
|
||||
ScopeLocker(pthread_mutex_t &m) : mLock(m)
|
||||
{ pthread_mutex_lock(&mLock); }
|
||||
virtual ~ScopeLocker()
|
||||
{ pthread_mutex_unlock(&mLock); }
|
||||
private:
|
||||
pthread_mutex_t mLock;
|
||||
};
|
||||
|
||||
static const char ssdp_msearch[] =
|
||||
"M-SEARCH * HTTP/1.1\r\n"
|
||||
"HOST: 239.255.255.250:1900\r\n"
|
||||
"MAN: \"ssdp:discover\"\r\n"
|
||||
"MX: 10\r\n"
|
||||
"ST: urn:dial-multiscreen-org:service:dial:1\r\n\r\n";
|
||||
|
||||
static string getLocation(char *pResponse)
|
||||
{
|
||||
string loc(pResponse);
|
||||
size_t prev = 0, index = loc.find("\r\n");
|
||||
string locUrl, retUrl;
|
||||
while( index != string::npos ) {
|
||||
locUrl = loc.substr(prev, index-prev);
|
||||
ATRACE("locUrl: ##%s## prev: %d index %d\n", locUrl.c_str(), (int)prev, (int)index );
|
||||
if( locUrl.find("LOCATION: ") != string::npos ) {
|
||||
index = locUrl.find("\r\n");
|
||||
retUrl = locUrl.substr( 10 );
|
||||
break;
|
||||
}
|
||||
|
||||
prev = index+2; // move past the "\r\n" token
|
||||
index = loc.find("\r\n", prev);
|
||||
}
|
||||
return retUrl;
|
||||
}
|
||||
|
||||
static size_t header_cb(void* ptr, size_t size, size_t nmemb, void* userdata)
|
||||
{
|
||||
if ((size * nmemb) != 0) {
|
||||
string parse((char*)ptr);
|
||||
if( parse.find("Application-URL: ") != string::npos ) {
|
||||
size_t index_start = parse.find(":");
|
||||
index_start += 2;
|
||||
size_t index_end = parse.find("\r\n");
|
||||
string *header = static_cast<string*>(userdata);
|
||||
header->append(parse.substr(index_start, index_end-index_start));
|
||||
ATRACE("Apps URL set to %s\n", header->c_str());
|
||||
#ifndef DEBUG
|
||||
}
|
||||
#else
|
||||
} else {
|
||||
ATRACE("%s: Dropping %s\n", __FUNCTION__, (char*)ptr);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return (size * nmemb);
|
||||
}
|
||||
|
||||
static size_t receiveData(void *ptr, size_t size, size_t nmemb, void *userdata)
|
||||
{
|
||||
if ((size * nmemb) != 0) {
|
||||
string *url = static_cast<string*>(userdata);
|
||||
url->append((char*)ptr);
|
||||
}
|
||||
return (size * nmemb);
|
||||
}
|
||||
|
||||
static void getServerInfo( const string &server, string& appsUrl, string& ddxml )
|
||||
{
|
||||
CURL *curl;
|
||||
CURLcode res = CURLE_OK;
|
||||
|
||||
if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
|
||||
fprintf(stderr, "curl_global_init() failed\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((curl = curl_easy_init()) == NULL) {
|
||||
fprintf(stderr, "curl_easy_init() failed\n");
|
||||
curl_global_cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
ATRACE("Sending ##%s##\n", server.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_URL, server.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_cb);
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &appsUrl);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receiveData);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ddxml);
|
||||
res = curl_easy_perform(curl);
|
||||
|
||||
(void)(res);
|
||||
curl_easy_cleanup(curl);
|
||||
//curl_global_cleanup();
|
||||
}
|
||||
|
||||
void DialDiscovery::updateServerList(string& server)
|
||||
{
|
||||
ServerMap::const_iterator it;
|
||||
it = mServerMap.find(server);
|
||||
if( it == mServerMap.end() ) {
|
||||
// not found, add it
|
||||
string appsUrl, ddxml;
|
||||
getServerInfo(server, appsUrl, ddxml);
|
||||
mServerMap[server] = new DialServer(server, appsUrl, ddxml);
|
||||
} else {
|
||||
// just mark that we found it
|
||||
(*it).second->setFound(true);
|
||||
}
|
||||
}
|
||||
|
||||
void DialDiscovery::processServer(char *pResponse)
|
||||
{
|
||||
if (strstr(pResponse, "ST: urn:dial-multiscreen-org:service:dial:1")) {
|
||||
string server;
|
||||
|
||||
ScopeLocker s(list_locker);
|
||||
// parse for LOCATION header
|
||||
server = getLocation(pResponse);
|
||||
ATRACE("FOUND server: %s\n", server.c_str());
|
||||
|
||||
// save the appURL in the server
|
||||
updateServerList(server);
|
||||
#ifndef DEBUG
|
||||
}
|
||||
#else
|
||||
} else {
|
||||
ATRACE("Dropping Server\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void *DialDiscovery::receiveResponses(void *p)
|
||||
{
|
||||
search_conn *pConn = (search_conn*)p;
|
||||
int bytes;
|
||||
char buf[4096] = {0,};
|
||||
while (1) {
|
||||
if (-1 == (bytes = recvfrom(pConn->sock, buf, sizeof(buf) - 1, 0,
|
||||
(struct sockaddr *)&pConn->saddr, &pConn->addrlen))) {
|
||||
perror("recvfrom");
|
||||
break;
|
||||
}
|
||||
buf[bytes] = 0;
|
||||
ATRACE("Received [%s:%d] %s\n", __FUNCTION__, __LINE__, buf);
|
||||
DialDiscovery::instance()->processServer(buf);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void DialDiscovery::cleanServerList(void)
|
||||
{
|
||||
ScopeLocker s(list_locker);
|
||||
|
||||
ServerMap::const_iterator it;
|
||||
vector<string> removal;
|
||||
for( it = mServerMap.begin(); it != mServerMap.end(); it++ )
|
||||
{
|
||||
if( !(*it).second->isFound() ) {
|
||||
removal.push_back((*it).second->getLocation());
|
||||
}
|
||||
}
|
||||
|
||||
// now remove and delete
|
||||
vector<string>::iterator iter;
|
||||
for( iter = removal.begin(); iter != removal.end(); iter ++)
|
||||
{
|
||||
ATRACE("Removing Server: %s\n", (*iter).c_str());
|
||||
DialServer *p = mServerMap[(*iter)];
|
||||
delete (p);
|
||||
mServerMap.erase((*iter));
|
||||
}
|
||||
}
|
||||
|
||||
void DialDiscovery::resetDiscovery(void)
|
||||
{
|
||||
ScopeLocker s(list_locker);
|
||||
ServerMap::const_iterator it;
|
||||
for( it = mServerMap.begin(); it != mServerMap.end(); it++ ) {
|
||||
(*it).second->setFound(false);
|
||||
}
|
||||
}
|
||||
|
||||
void * DialDiscovery::send_mcast(void *p)
|
||||
{
|
||||
int one = 1, my_sock;
|
||||
socklen_t addrlen;
|
||||
//struct ip_mreq mreq;
|
||||
char send_buf[strlen((char*)ssdp_msearch) + INET_ADDRSTRLEN + 256] = {0,};
|
||||
int send_size;
|
||||
pthread_attr_t attr;
|
||||
search_conn connection;
|
||||
|
||||
// send_size = snprintf(send_buf, sizeof(send_buf), ssdp_msearch, ip_addr, my_port);
|
||||
send_size = snprintf(send_buf, sizeof(send_buf), ssdp_msearch);
|
||||
ATRACE("[%s:%d] %s\n", __FUNCTION__, __LINE__, send_buf);
|
||||
|
||||
if (-1 == (my_sock = socket(AF_INET, SOCK_DGRAM, 0))) {
|
||||
perror("socket");
|
||||
exit(1);
|
||||
}
|
||||
if (-1 == setsockopt(my_sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) {
|
||||
perror("reuseaddr");
|
||||
exit(1);
|
||||
}
|
||||
saddr.sin_family = AF_INET;
|
||||
saddr.sin_addr.s_addr = inet_addr("239.255.255.250");
|
||||
saddr.sin_port = htons(1900);
|
||||
|
||||
while (1) {
|
||||
addrlen = sizeof(saddr);
|
||||
ATRACE("Sending SSDP M-SEARCH to %s:%d\n",
|
||||
inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port));
|
||||
if (-1 == sendto(my_sock, send_buf, send_size, 0, (struct sockaddr *)&saddr, addrlen)) {
|
||||
perror("sendto");
|
||||
continue;
|
||||
}
|
||||
|
||||
// set all servers to not found
|
||||
DialDiscovery::instance()->resetDiscovery();
|
||||
|
||||
// spawn a response thread
|
||||
connection.saddr = saddr;
|
||||
connection.sock = my_sock;
|
||||
connection.addrlen = addrlen;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
|
||||
pthread_create(&DialDiscovery::instance()->_responseThread, &attr, DialDiscovery::receiveResponses, &connection);
|
||||
|
||||
// sleep SSDP_RESPONSE_TIMEOUT seconds to allow clients to response
|
||||
sleep(SSDP_RESPONSE_TIMEOUT);
|
||||
DialDiscovery::instance()->cleanServerList();
|
||||
|
||||
sleep(SSDP_TIMEOUT-SSDP_RESPONSE_TIMEOUT);
|
||||
pthread_cancel(DialDiscovery::instance()->_responseThread);
|
||||
}
|
||||
}
|
||||
|
||||
void DialDiscovery::init()
|
||||
{
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
|
||||
pthread_create(&_mcastThread, &attr, DialDiscovery::send_mcast, (void*)ssdp_msearch );
|
||||
}
|
||||
|
||||
void DialDiscovery::getServerList( vector<DialServer*>& list )
|
||||
{
|
||||
for( ServerMap::iterator it = mServerMap.begin(); it != mServerMap.end(); ++it ) {
|
||||
list.push_back( it->second );
|
||||
}
|
||||
}
|
||||
|
||||
bool DialDiscovery::getServer( const string& friendlyName, DialServer &server )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
DialDiscovery::DialDiscovery()
|
||||
{
|
||||
assert( DialDiscovery::sDiscovery == 0 );
|
||||
DialDiscovery::sDiscovery = this;
|
||||
}
|
||||
|
||||
DialDiscovery::~DialDiscovery()
|
||||
{
|
||||
assert( sDiscovery == this );
|
||||
sDiscovery = 0;
|
||||
}
|
||||
|
||||
DialDiscovery * DialDiscovery::create()
|
||||
{
|
||||
assert( sDiscovery == 0 );
|
||||
return new DialDiscovery();
|
||||
}
|
||||
|
||||
DialDiscovery * DialDiscovery::instance()
|
||||
{
|
||||
return DialDiscovery::sDiscovery;
|
||||
}
|
||||
|
||||
94
client/DialDiscovery.h
Normal file
94
client/DialDiscovery.h
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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 DIALDISCOVERY_H
|
||||
#define DIALDISCOVERY_H
|
||||
|
||||
#include "DialServer.h"
|
||||
#include <map>
|
||||
|
||||
using namespace std;
|
||||
|
||||
class DialDiscovery
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Create a singleton
|
||||
*/
|
||||
static DialDiscovery* create(void);
|
||||
|
||||
/**
|
||||
* Get a pointer to the singleton
|
||||
*/
|
||||
static DialDiscovery* instance(void);
|
||||
|
||||
~DialDiscovery();
|
||||
|
||||
/**
|
||||
* Initialize the discover object. This will kick off a periodic
|
||||
* worker thread that will poll for DIAL servers.
|
||||
*
|
||||
*/
|
||||
void init();
|
||||
|
||||
/**
|
||||
* Get the list of servers that have been discovered.
|
||||
*
|
||||
* @param[out] list List of DIAL servers. Returns an empty list if there
|
||||
* are no servers.
|
||||
*/
|
||||
void getServerList(vector<DialServer*>& list);
|
||||
|
||||
/**
|
||||
* Get a DIAL server based on friendly name
|
||||
*
|
||||
* @param[in] friendlyName Friendly name of DIAL server
|
||||
* @param[out] server Server object (if successful)
|
||||
*
|
||||
* @return true if successful, false otherwise
|
||||
*/
|
||||
bool getServer(
|
||||
const string& friendlyName,
|
||||
DialServer &server );
|
||||
|
||||
private:
|
||||
DialDiscovery();
|
||||
void updateServerList(string& server);
|
||||
static void *receiveResponses(void *p);
|
||||
static void *send_mcast(void *p);
|
||||
void processServer(char *pResponse);
|
||||
void cleanServerList();
|
||||
void resetDiscovery();
|
||||
|
||||
pthread_t _mcastThread;
|
||||
pthread_t _responseThread;
|
||||
|
||||
typedef map<string, DialServer*> ServerMap;
|
||||
ServerMap mServerMap;
|
||||
|
||||
static DialDiscovery* sDiscovery;
|
||||
};
|
||||
|
||||
#endif // DIALDISCOVERY_H
|
||||
249
client/DialServer.cpp
Normal file
249
client/DialServer.cpp
Normal file
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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 "DialServer.h"
|
||||
#include <curl/curl.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
enum DIAL_COMMAND{
|
||||
COMMAND_LAUNCH,
|
||||
COMMAND_STATUS,
|
||||
COMMAND_KILL
|
||||
};
|
||||
|
||||
static size_t header_cb(void* ptr, size_t size, size_t nmemb, void* userdata)
|
||||
{
|
||||
if ((size * nmemb) != 0) {
|
||||
string newHeader((char*)ptr);
|
||||
string *header = static_cast<string*>(userdata);
|
||||
header->append(newHeader);
|
||||
ATRACE("%s: Adding header: %s", __FUNCTION__, newHeader.c_str());
|
||||
}
|
||||
return (size * nmemb);
|
||||
}
|
||||
|
||||
static size_t receiveData(void *ptr, size_t size, size_t nmemb, void *userdata)
|
||||
{
|
||||
if ((size * nmemb) != 0) {
|
||||
string *body= static_cast<string*>(userdata);
|
||||
body->append((char*)ptr);
|
||||
ATRACE("%s: Adding to Body: %s", __FUNCTION__, (char*)ptr);
|
||||
}
|
||||
return (size * nmemb);
|
||||
}
|
||||
|
||||
int DialServer::sendCommand(
|
||||
string &url,
|
||||
int command,
|
||||
string &payload,
|
||||
string &responseHeaders,
|
||||
string &responseBody )
|
||||
{
|
||||
CURL *curl;
|
||||
CURLcode res = CURLE_OK;
|
||||
|
||||
if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)
|
||||
{
|
||||
fprintf(stderr, "curl_global_init() failed\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((curl = curl_easy_init()) == NULL)
|
||||
{
|
||||
fprintf(stderr, "curl_easy_init() failed\n");
|
||||
curl_global_cleanup();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (command == COMMAND_LAUNCH)
|
||||
{
|
||||
curl_easy_setopt(curl, CURLOPT_POST, true);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, payload.size());
|
||||
if( payload.size() )
|
||||
{
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload.c_str());
|
||||
}
|
||||
#ifdef DEBUG
|
||||
else ATRACE("Sending empty POST\n");
|
||||
#endif
|
||||
}
|
||||
else if (command == COMMAND_KILL)
|
||||
{
|
||||
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
|
||||
}
|
||||
ATRACE("Sending %s:%s\n",
|
||||
command == COMMAND_LAUNCH ? "LAUNCH" :
|
||||
(command == COMMAND_KILL ? "KILL" : "STATUS"),
|
||||
url.c_str());
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_cb);
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &responseHeaders);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receiveData);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseBody);
|
||||
res = curl_easy_perform(curl);
|
||||
|
||||
curl_easy_cleanup(curl);
|
||||
curl_global_cleanup();
|
||||
return (res == CURLE_OK);
|
||||
}
|
||||
|
||||
string DialServer::getIpAddress()
|
||||
{
|
||||
// m_appsUrl=http://192.168.1.103:36269/apps/
|
||||
if( m_ipAddr.empty() )
|
||||
{
|
||||
size_t begin = m_appsUrl.find("//");
|
||||
if( begin != m_appsUrl.npos )
|
||||
{
|
||||
begin += 2; // move to the start of the IP address
|
||||
size_t end = m_appsUrl.find(":", begin);
|
||||
if( end != m_appsUrl.npos )
|
||||
{
|
||||
m_ipAddr = m_appsUrl.substr( begin, end-begin );
|
||||
ATRACE("IP ADDRESS: %s\n", m_ipAddr.c_str() );
|
||||
}
|
||||
}
|
||||
}
|
||||
return m_ipAddr;
|
||||
}
|
||||
|
||||
bool DialServer::getFriendlyName( string& name )
|
||||
{
|
||||
bool retval = false;
|
||||
if( !m_ddxml.empty() )
|
||||
{
|
||||
string friendlyName = "<friendlyName>";
|
||||
size_t pos;
|
||||
if( ( pos = m_ddxml.find( friendlyName ) ) != m_ddxml.npos )
|
||||
{
|
||||
string friendlyNameEnd = "</friendlyName>";
|
||||
size_t end = m_ddxml.find( friendlyNameEnd );
|
||||
name = m_ddxml.substr( pos + friendlyName.size(),
|
||||
(end - (pos + friendlyName.size())) );
|
||||
ATRACE("***Friendly name=%s***", name.c_str());
|
||||
retval = true;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
else
|
||||
{
|
||||
ATRACE("Friendly name not found\n%s\n", m_ddxml.c_str());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool DialServer::getUuid( string& uuid )
|
||||
{
|
||||
bool retval = false;
|
||||
if( !m_ddxml.empty() )
|
||||
{
|
||||
string udn = "<UDN>";
|
||||
size_t pos;
|
||||
if( ( pos = m_ddxml.find( udn ) ) != m_ddxml.npos )
|
||||
{
|
||||
string udnEnd = "</UDN>";
|
||||
size_t end = m_ddxml.find( udnEnd );
|
||||
uuid = m_ddxml.substr( pos + udn.size(),
|
||||
(end - (pos + udn.size())) );
|
||||
ATRACE("***UUID=%s***", uuid.c_str() );
|
||||
retval = true;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
else
|
||||
{
|
||||
ATRACE("Friendly name not found\n%s\n", m_ddxml.c_str());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
int DialServer::launchApplication(
|
||||
string &application,
|
||||
string &payload,
|
||||
string &responseHeaders,
|
||||
string &responseBody )
|
||||
{
|
||||
ATRACE("%s: Launch %s\n", __FUNCTION__, application.c_str());
|
||||
string appUrl = m_appsUrl;
|
||||
sendCommand( appUrl.append(application), COMMAND_LAUNCH, payload, responseHeaders, responseBody);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int DialServer::getStatus(
|
||||
string &application,
|
||||
string &responseHeaders,
|
||||
string &responseBody )
|
||||
{
|
||||
ATRACE("%s: GetStatus %s\n", __FUNCTION__, application.c_str());
|
||||
string emptyPayload;
|
||||
string appUrl = m_appsUrl;
|
||||
sendCommand( appUrl.append(application), COMMAND_STATUS, emptyPayload, responseHeaders, responseBody );
|
||||
|
||||
ATRACE("Body: %s\n", responseBody.c_str());
|
||||
unsigned found = responseBody.find("href=");
|
||||
if( found != string::npos )
|
||||
{
|
||||
// The start of href is after href= and the quote
|
||||
unsigned href_start = found + 5 + 1;
|
||||
|
||||
// get the body from the start of href to the end, then find
|
||||
// the last quote delimiter.
|
||||
string tmp = responseBody.substr( href_start );
|
||||
unsigned href_end = tmp.find("\"");
|
||||
m_stopEndPoint = responseBody.substr( href_start, href_end );
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int DialServer::stopApplication(
|
||||
string &application,
|
||||
string &responseHeaders )
|
||||
{
|
||||
ATRACE("%s: Quit %s\n", __FUNCTION__, application.c_str());
|
||||
string emptyPayload, responseBody; // dropping this
|
||||
string appUrl = m_appsUrl;
|
||||
|
||||
// just call status to update the run endpoint
|
||||
getStatus( application, responseHeaders, responseBody );
|
||||
|
||||
sendCommand(
|
||||
(appUrl.append(application)).append("/"+m_stopEndPoint),
|
||||
COMMAND_KILL, emptyPayload, responseHeaders, responseBody );
|
||||
return 0;
|
||||
}
|
||||
|
||||
int DialServer::getHttpResponseHeader(
|
||||
string &responseHeaders,
|
||||
string &header,
|
||||
string &value )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
181
client/DialServer.h
Normal file
181
client/DialServer.h
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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 DIALSERVER_H
|
||||
#define DIALSERVER_H
|
||||
|
||||
//#define DEBUG
|
||||
#ifdef DEBUG
|
||||
#define ATRACE(...) printf(__VA_ARGS__)
|
||||
#else
|
||||
#define ATRACE(...)
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
class DialServer
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Dial Server ctor
|
||||
*
|
||||
* @param[in] location dd.xml LOCATION header
|
||||
* @param[in] appsUrl Parsed out Application URL
|
||||
* @param[in] location dd.xml LOCATION header
|
||||
* empty string to find any server
|
||||
*
|
||||
*/
|
||||
DialServer( string location, string appsUrl, string dd_xml ) :
|
||||
m_location(location),
|
||||
m_appsUrl(appsUrl),
|
||||
found(true),
|
||||
m_ddxml(dd_xml)
|
||||
{}
|
||||
|
||||
~DialServer() { ATRACE("%s\n", __FUNCTION__); }
|
||||
|
||||
/**
|
||||
* Get the DIAL Server location
|
||||
*
|
||||
* @return Location of the server (http://<IP_ADDR>:<PORT>/dd.xml)
|
||||
*/
|
||||
string getLocation() { return m_location; }
|
||||
|
||||
/**
|
||||
* Get the DIAL Server IP address
|
||||
*
|
||||
* @return IP address of the server (X.X.X.X)
|
||||
*/
|
||||
string getIpAddress();
|
||||
|
||||
/**
|
||||
* Get the DIAL REST endpoint
|
||||
*
|
||||
* @return Location of the server (http://<IP_ADDR>:<PORT>/apps)
|
||||
*/
|
||||
string getAppsUrl() { return m_appsUrl; }
|
||||
|
||||
/**
|
||||
* Get the DIAL friendly name
|
||||
*
|
||||
* @return true if successful, false otherwise
|
||||
*/
|
||||
bool getFriendlyName( string& name );
|
||||
|
||||
/**
|
||||
* Get the DIAL UUID
|
||||
*
|
||||
* @return true if successful, false otherwise
|
||||
*/
|
||||
bool getUuid( string& uuid );
|
||||
|
||||
/**
|
||||
* Launch a DIAL application
|
||||
*
|
||||
* @param[in] application Name of the application to launch
|
||||
* @param[in] payload launch POST data
|
||||
* @param[out] responseHeaders Returns the HTTP response headers
|
||||
* @param[out] responseBody Returns the HTTP response body
|
||||
*
|
||||
* @return 0 if successful, !0 otherwise
|
||||
*/
|
||||
int launchApplication(
|
||||
string &application,
|
||||
string &payload,
|
||||
string &responseHeaders,
|
||||
string &responseBody );
|
||||
|
||||
/**
|
||||
* Get the status of a DIAL application
|
||||
*
|
||||
* @param[in] application Name of the application to query
|
||||
* @param[out] responseHeaders Returns the HTTP response headers
|
||||
* @param[out] responseBody Returns the HTTP response body
|
||||
*
|
||||
* @return 0 if successful, !0 otherwise
|
||||
*/
|
||||
int getStatus(
|
||||
string &application,
|
||||
string &responseHeaders,
|
||||
string &responseBody );
|
||||
|
||||
/**
|
||||
* Stop an application.
|
||||
*
|
||||
* @param[in] application Name of the application to stop
|
||||
* @param[out] responseHeaders Returns the HTTP response headers
|
||||
*
|
||||
* @return 0 if successful, !0 otherwise
|
||||
*/
|
||||
int stopApplication(
|
||||
string &application,
|
||||
string &responseHeaders );
|
||||
|
||||
|
||||
/** ********************* **/
|
||||
/** Convenience functions **/
|
||||
/** ********************* **/
|
||||
|
||||
/**
|
||||
* Extract a header from the response
|
||||
*
|
||||
* @param[in] responseHeaders Response headers
|
||||
* @param[in] header Header value to extract
|
||||
* @param[out] value Value of the header provided
|
||||
*
|
||||
* @return 0 if successful, !0 otherwise
|
||||
*/
|
||||
int getHttpResponseHeader(
|
||||
string &responseHeaders,
|
||||
string &header,
|
||||
string &value );
|
||||
|
||||
/**
|
||||
* Returns true if the server has been recently found
|
||||
*
|
||||
* @return true if successful, false otherwise
|
||||
*/
|
||||
bool isFound() { return found; }
|
||||
|
||||
/**
|
||||
* Sets the "found status" of this server
|
||||
*/
|
||||
void setFound(bool b) { found = b; }
|
||||
|
||||
private:
|
||||
int sendCommand( string &url, int command, string &payload,
|
||||
string &responseHeaders, string &responseBody );
|
||||
string m_location;
|
||||
string m_appsUrl;
|
||||
string m_ipAddr;
|
||||
bool found;
|
||||
string m_ddxml; // information about the device
|
||||
string m_stopEndPoint;
|
||||
};
|
||||
|
||||
#endif // DIALSERVER_H
|
||||
5
client/build.sh
Executable file
5
client/build.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
TARGET=/usr/local/i686-netflix-linux-gnu-4.2/bin/i686-netflix-linux-gnu- \
|
||||
LDFLAGS="-L/usr/local/i686-netflix-linux-gnu-4.2/netflix/lib \
|
||||
-Wl,-rpath,/usr/local/i686-netflix-linux-gnu-4.2/netflix/lib" \
|
||||
INCLUDES=-I/usr/local/i686-netflix-linux-gnu-4.2/netflix/include \
|
||||
make
|
||||
80
client/dialclient_input.txt
Normal file
80
client/dialclient_input.txt
Normal file
@@ -0,0 +1,80 @@
|
||||
# Input file for DIAL client
|
||||
|
||||
# List the applications defined.
|
||||
# Valid applications that should exist on your DIAL server implementation
|
||||
addApplication=Netflix
|
||||
addApplication=YouTube
|
||||
|
||||
# Error applications that should not exist on your DIAL server implementation
|
||||
addErrorApplication=netflix
|
||||
addErrorApplication=Netflix1
|
||||
|
||||
# Launch the Netflix application
|
||||
# HTTP response should be 201
|
||||
# HTTP response header should contain a Location header
|
||||
launch=Netflix httpresponse=201 httpresponse=Created httpresponseheader=Location
|
||||
sleep=6000
|
||||
|
||||
# Get the status of the application, it should be running
|
||||
status=Netflix httpresponse=200 resultbody=state resultbody=running
|
||||
|
||||
# Stop the Netflix application, wait for 1 second while it shuts down.
|
||||
stop=Netflix
|
||||
sleep=1000
|
||||
|
||||
# Call stop again and ensure the implementation returns the proper response
|
||||
stop=Netflix httpresponse=404
|
||||
|
||||
# Get the status of the Netflix application and ensure it returns stopped
|
||||
status=Netflix httpresponse=200 resultbody=state resultbody=stopped
|
||||
sleep=2000
|
||||
|
||||
# Launch the application with a parameter
|
||||
# NOTE: This test can not ensure that the parameter was taken properly
|
||||
launch=Netflix param="NETFLIX&WAS+HERE" httpresponse=201 httpresponse=Created httpresponseheader=Location
|
||||
sleep=6000
|
||||
|
||||
# Ensure the application is running
|
||||
status=Netflix httpresponse=200 resultbody=state resultbody=running
|
||||
|
||||
# This will launch Netflix with the same parameter. *Netflix should not relaunch*
|
||||
launch=Netflix param="NETFLIX&WAS*HERE" httpresponse=201 httpresponse=Created httpresponseheader=Location
|
||||
sleep=6000
|
||||
|
||||
# Ensure the application is running
|
||||
status=Netflix httpresponse=200 resultbody=state resultbody=running
|
||||
|
||||
# This will launch Netflix with a different parameter. *Netflix should not relaunch*
|
||||
launch=Netflix param="&&&%%%ThisShouldRelaunch%%%" httpresponse=201 httpresponse=Created httpresponseheader=Location
|
||||
sleep=6000
|
||||
|
||||
# Ensure the application is running
|
||||
status=Netflix httpresponse=200 resultbody=state resultbody=running
|
||||
|
||||
# Stop Netflix
|
||||
stop=Netflix
|
||||
sleep=1500
|
||||
|
||||
# Make sure netflix is reported as stopped
|
||||
status=Netflix httpresponse=200 resultbody=state resultbody=stopped
|
||||
|
||||
# Test a parameter with over 4096 bytes and ensure 413 is returned.
|
||||
launch=Netflix param="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" httpresponse=413
|
||||
|
||||
# Test for case sensitivity
|
||||
launch=netflix httpresponse=404
|
||||
sleep=200
|
||||
|
||||
# Ensure the server is checking the full application name
|
||||
launch=Netflix1 httpresponse=404
|
||||
sleep=200
|
||||
|
||||
# This executes correctly, but fails the XML
|
||||
status=Netflix1 httpresponse=404
|
||||
sleep=200
|
||||
|
||||
# Currently can't run this test, the code won't execute a stop without a stop URL.
|
||||
#stop=Netflix1 httpresponse=404
|
||||
|
||||
# Test ALL valid applications
|
||||
status=ALL httpresponse=200 resultbody=state resultbody=stopped
|
||||
298
client/main.cpp
Normal file
298
client/main.cpp
Normal file
@@ -0,0 +1,298 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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 <string>
|
||||
#include "DialDiscovery.h"
|
||||
#include "DialConformance.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
static DialDiscovery* gpDiscovery;
|
||||
static bool gUseMenu = true;
|
||||
|
||||
// TODO: Make it possible to pass applications from the command line
|
||||
static vector<string> gAppList;
|
||||
static string gOutputFile;
|
||||
static string gInputFile;
|
||||
|
||||
// IP address of the DIAL server
|
||||
static string gIpAddress;
|
||||
|
||||
static void printServerList( vector<DialServer*> list )
|
||||
{
|
||||
int i;
|
||||
vector<DialServer*>::iterator it;
|
||||
for( i = 0, it = list.begin(); it < list.end(); it++, i++ )
|
||||
{
|
||||
string uuid, name;
|
||||
(*it)->getFriendlyName( name );
|
||||
(*it)->getUuid( uuid );
|
||||
printf("%Zu: Server IP[%s] UUID[%s] FriendlyName[%s] \n",
|
||||
i+1, (*it)->getIpAddress().c_str(),
|
||||
uuid.c_str(), name.c_str() );
|
||||
}
|
||||
}
|
||||
|
||||
static DialServer* getServerFromUser( vector<DialServer*> list )
|
||||
{
|
||||
DialServer* pServer;
|
||||
// show a list to the user
|
||||
if( list.size() > 1 )
|
||||
{
|
||||
char buf[80] = {0,};
|
||||
vector<DialServer*>::iterator it;
|
||||
|
||||
printf("Found Multiple servers\n");
|
||||
printServerList(list);
|
||||
printf("Enter server: ");
|
||||
scanf("%s", buf);
|
||||
unsigned int server = atoi(buf);
|
||||
assert( server > 0 && server <= list.size() );
|
||||
pServer = list[server-1];
|
||||
}
|
||||
else
|
||||
{
|
||||
pServer = list.front();
|
||||
}
|
||||
return pServer;
|
||||
}
|
||||
|
||||
static void runConformance()
|
||||
{
|
||||
vector<DialServer*> list;
|
||||
gpDiscovery->getServerList(list);
|
||||
|
||||
if( list.size() )
|
||||
{
|
||||
DialServer *pServer = NULL;
|
||||
if( !gIpAddress.empty() )
|
||||
{
|
||||
pServer = NULL;
|
||||
vector<DialServer*>::iterator it;
|
||||
for( it = list.begin(); it < list.end(); it ++ )
|
||||
{
|
||||
if( gIpAddress.compare((*it)->getIpAddress()) == 0 )
|
||||
{
|
||||
ATRACE("Found server %s in the list of servers\n",
|
||||
gIpAddress.c_str() );
|
||||
pServer = (*it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pServer = getServerFromUser( list );
|
||||
}
|
||||
|
||||
if( pServer )
|
||||
{
|
||||
string name;
|
||||
bool serverExists = pServer->getFriendlyName(name);
|
||||
assert( serverExists );
|
||||
string uuid;
|
||||
pServer->getUuid( uuid );
|
||||
printf("\nRunning conformance against: IP[%s] UUID[%s] FriendlyName[%s] \n",
|
||||
pServer->getIpAddress().c_str(),
|
||||
uuid.c_str(), name.c_str() );
|
||||
DialConformance::instance()->run(
|
||||
pServer,
|
||||
gAppList,
|
||||
gInputFile,
|
||||
gOutputFile );
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("DIAL server not found\n");
|
||||
printf("%Zu available server(s): \n", list.size());
|
||||
printServerList(list);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("No servers available\n");
|
||||
}
|
||||
}
|
||||
|
||||
int handleUser(DialDiscovery *pDial) {
|
||||
int processInput = 1;
|
||||
char buf[80];
|
||||
vector<DialServer*> list;
|
||||
|
||||
pDial->getServerList(list);
|
||||
if( list.size() == 0 )
|
||||
{
|
||||
printf("No servers available\n");
|
||||
return 1;
|
||||
}
|
||||
DialServer* pServer = getServerFromUser( list );
|
||||
|
||||
while(processInput)
|
||||
{
|
||||
string responseHeaders, responseBody, payload;
|
||||
string netflix = "Netflix";
|
||||
string youtube = "YouTube";
|
||||
|
||||
memset(buf, 0, 80);
|
||||
printf("0. List DIAL servers\n");
|
||||
printf("1. Launch Netflix\n");
|
||||
printf("2. Kill Netflix\n");
|
||||
printf("3. Netflix status\n");
|
||||
printf("4. Launch YouTube\n");
|
||||
printf("5. Kill YouTube\n");
|
||||
printf("6. YouTube status\n");
|
||||
printf("7. Run conformance tests\n");
|
||||
printf("8. QUIT\n");
|
||||
printf("Command (0:1:2:3:4:5:6:7:8): ");
|
||||
scanf("%s", buf);
|
||||
switch( atoi(buf) )
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
printf("\n\n******** %Zu servers found ********\n\n", list.size());
|
||||
for( unsigned int i = 0; i < list.size(); i++ )
|
||||
{
|
||||
string name;
|
||||
list[i]->getFriendlyName(name);
|
||||
printf("Server %Zu: %s\n", i+1, name.c_str());
|
||||
}
|
||||
printf("\n*********************************\n\n");
|
||||
}break;
|
||||
case 1:
|
||||
printf("Launch Netflix\n");
|
||||
pServer->launchApplication( netflix, payload, responseHeaders, responseBody );
|
||||
break;
|
||||
case 2:
|
||||
printf("Kill Netflix\n");
|
||||
pServer->stopApplication( netflix, responseHeaders );
|
||||
break;
|
||||
case 3:
|
||||
printf("Netflix Status: \n");
|
||||
pServer->getStatus( netflix, responseHeaders, responseBody );
|
||||
printf("RESPONSE: \n%s\n", responseBody.c_str());
|
||||
break;
|
||||
case 4:
|
||||
printf("Launch YouTube\n");
|
||||
pServer->launchApplication( youtube, payload, responseHeaders, responseBody );
|
||||
break;
|
||||
case 5:
|
||||
printf("Kill YouTube\n");
|
||||
pServer->stopApplication( youtube, responseHeaders );
|
||||
break;
|
||||
case 6:
|
||||
printf("YouTube Status: \n");
|
||||
pServer->getStatus( youtube, responseHeaders, responseBody );
|
||||
break;
|
||||
case 7:
|
||||
runConformance();
|
||||
break;
|
||||
case 8:
|
||||
processInput = 0;
|
||||
break;
|
||||
default:
|
||||
printf("Invalid, try again\n");
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char usage[] = "\n"
|
||||
" If no option is specified, the program will run a conformance test.\n"
|
||||
"\n"
|
||||
"usage: dialclient <option>\n"
|
||||
" Option Parameter Description\n"
|
||||
" -h none Usage menu\n"
|
||||
" -s filename (optional) Run conformance test. Use filename as\n"
|
||||
" the input, if provided\n"
|
||||
" -o filename Reporter output file (./report.html)\n"
|
||||
" -a ip_address IP address of DIAL server (used for conformance\n"
|
||||
" testing)\n"
|
||||
"\n";
|
||||
|
||||
inline void notSupported( string s )
|
||||
{
|
||||
printf( "%s not supported", s.c_str() );
|
||||
printf( "%s\n", usage );
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int parseArgs( int argc, char* argv[] )
|
||||
{
|
||||
for( int i = 1; i < argc; i++ )
|
||||
{
|
||||
string input(argv[i]);
|
||||
if( input[0] != '-' ) notSupported(input);
|
||||
switch(input[1])
|
||||
{
|
||||
case 's':
|
||||
gUseMenu = false;
|
||||
if( argv[i+1] != NULL && argv[i+1][0] != '-' )
|
||||
{
|
||||
//filename provided
|
||||
gInputFile = argv[++i];
|
||||
}
|
||||
break;
|
||||
case 'o':
|
||||
gOutputFile = argv[++i];
|
||||
break;
|
||||
case 'a':
|
||||
gIpAddress = argv[++i];
|
||||
break;
|
||||
case 'h':
|
||||
printf("%s", usage);
|
||||
exit(0);
|
||||
break;
|
||||
default:
|
||||
notSupported(input);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
parseArgs(argc, argv);
|
||||
|
||||
gpDiscovery = DialDiscovery::create();
|
||||
gpDiscovery->init();
|
||||
DialConformance::create();
|
||||
|
||||
// Sleep for 2 seconds to allow DIAL servers to response to MSEARCH.
|
||||
sleep(2);
|
||||
|
||||
if ( gUseMenu )
|
||||
{
|
||||
return handleUser(gpDiscovery);
|
||||
}
|
||||
|
||||
// not using the menu, just run the conformance test.
|
||||
runConformance();
|
||||
return 0;
|
||||
}
|
||||
|
||||
14
client/makefile
Normal file
14
client/makefile
Normal file
@@ -0,0 +1,14 @@
|
||||
CC=$(TARGET)g++
|
||||
|
||||
.PHONY: clean
|
||||
.DEFAULT_GOAL=dialclient
|
||||
|
||||
includes = $(wildcard *.h)
|
||||
OBJS := main.cpp DialServer.cpp DialDiscovery.cpp DialConformance.cpp DialClientInput.cpp
|
||||
|
||||
# You may not need all these libraries. This example uses a build of curl that needs crypto, ssl, cares, and zlib
|
||||
dialclient: $(OBJS) ${includes}
|
||||
$(CC) -Wall -Werror -g $(OBJS) $(INCLUDES) $(LDFLAGS) -ldl -lpthread -lcurl -lz -lcrypto -lssl -lcares -m32 -o dialclient
|
||||
|
||||
clean:
|
||||
rm -f *.o dialclient
|
||||
8
makefile
Normal file
8
makefile
Normal file
@@ -0,0 +1,8 @@
|
||||
DIRS = client
|
||||
DIRS += server
|
||||
|
||||
all:
|
||||
for dir in $(DIRS); do (make -C $$dir || exit 1) || exit 1; done
|
||||
clean:
|
||||
for dir in $(DIRS); do (make clean -C $$dir || exit 1) || exit 1; done
|
||||
|
||||
2
server/build.sh
Executable file
2
server/build.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
export TARGET=/usr/local/i686-netflix-linux-gnu-4.2/bin/i686-netflix-linux-gnu-
|
||||
make
|
||||
87
server/dial_data.c
Normal file
87
server/dial_data.c
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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.
|
||||
*/
|
||||
/*
|
||||
* Functions related to storing/retrieving and manipulating DIAL data.
|
||||
*/
|
||||
#include "dial_data.h"
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
char dial_data_dir[256] = DIAL_DATA_DIR;
|
||||
|
||||
void set_dial_data_dir(const char *data_dir) {
|
||||
strncpy(dial_data_dir, data_dir, 255);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path where data is stored for the given app.
|
||||
*/
|
||||
static char* getAppPath(char app_name[]) {
|
||||
size_t name_size = strlen(app_name) + sizeof(dial_data_dir) + 1;
|
||||
char* filename = (char*) malloc(name_size);
|
||||
filename[0] = 0;
|
||||
strncat(filename, dial_data_dir, name_size);
|
||||
strncat(filename, app_name, name_size - sizeof(dial_data_dir));
|
||||
return filename;
|
||||
}
|
||||
|
||||
void store_dial_data(char app_name[], DIALData *data) {
|
||||
char* filename = getAppPath(app_name);
|
||||
FILE *f = fopen(filename, "w");
|
||||
if (f == NULL) {
|
||||
printf("Cannot open DIAL data output file: %s\n", filename);
|
||||
exit(1);
|
||||
}
|
||||
for (DIALData *first = data; first != NULL; first = first->next) {
|
||||
fprintf(f, "%s %s\n", first->key, first->value);
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
DIALData *retrieve_dial_data(char *app_name) {
|
||||
char* filename = getAppPath(app_name);
|
||||
FILE *f = fopen(filename, "r");
|
||||
if (f == NULL) {
|
||||
return NULL; // no dial data found, that's fine
|
||||
}
|
||||
DIALData *result = NULL;
|
||||
char key[256];
|
||||
char value[256];
|
||||
while (fscanf(f, "%255s %255s\n", key, value) != EOF) {
|
||||
DIALData *newNode = (DIALData *) malloc(sizeof(DIALData));
|
||||
newNode->key = (char *) malloc(strlen(key));
|
||||
strcpy(newNode->key, key);
|
||||
newNode->value = (char *) malloc(strlen(value));
|
||||
strcpy(newNode->value, value);
|
||||
newNode->next = result;
|
||||
result = newNode;
|
||||
}
|
||||
fclose(f);
|
||||
return result;
|
||||
}
|
||||
|
||||
63
server/dial_data.h
Normal file
63
server/dial_data.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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.
|
||||
*/
|
||||
/*
|
||||
* Defines functions for persisting and retrieving DIAL data.
|
||||
*/
|
||||
|
||||
#ifndef SRC_SERVER_DIAL_DATA_H_
|
||||
#define SRC_SERVER_DIAL_DATA_H_
|
||||
|
||||
/*
|
||||
* Slash-terminated directory of where to persist the DIAL data.
|
||||
*/
|
||||
#define DIAL_DATA_DIR "/tmp/"
|
||||
|
||||
/*
|
||||
* The maximum DIAL data payload accepted per the 'DIAL extension for smooth
|
||||
* pairing' specification'.
|
||||
*/
|
||||
#define DIAL_DATA_MAX_PAYLOAD (4096) /* 4 KB */
|
||||
|
||||
/*
|
||||
* Url path where DIAL data should be posted according to the 'DIAL extension
|
||||
* for smooth pairing' specification.
|
||||
*/
|
||||
#define DIAL_DATA_URI "/dial_data"
|
||||
|
||||
struct DIALData_ {
|
||||
struct DIALData_ *next;
|
||||
char *key;
|
||||
char *value;
|
||||
};
|
||||
|
||||
typedef struct DIALData_ DIALData;
|
||||
|
||||
void store_dial_data(char *app_name, DIALData *data);
|
||||
|
||||
DIALData *retrieve_dial_data(char *app_name);
|
||||
|
||||
void set_dial_data_dir(const char *data_dir);
|
||||
|
||||
#endif /* SRC_SERVER_DIAL_DATA_H_ */
|
||||
86
server/dial_options.h
Normal file
86
server/dial_options.h
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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 DIAL_OPTIONS_H
|
||||
#define DIAL_OPTIONS_H
|
||||
|
||||
#define DATA_PATH_OPTION "-D"
|
||||
#define DATA_PATH_OPTION_LONG "--data-path"
|
||||
#define DATA_PATH_DESCRIPTION "Path to netflix secure store"
|
||||
|
||||
#define NETFLIX_PATH_OPTION "-N"
|
||||
#define NETFLIX_PATH_OPTION_LONG "--netflix-path"
|
||||
#define NETFLIX_PATH_DESCRIPTION "Path to Netflix application"
|
||||
|
||||
#define FRIENDLY_NAME_OPTION "-F"
|
||||
#define FRIENDLY_NAME_OPTION_LONG "--friendly-name"
|
||||
#define FRIENDLY_NAME_DESCRIPTION "Device Friendly Name"
|
||||
|
||||
#define MODELNAME_OPTION "-M"
|
||||
#define MODELNAME_OPTION_LONG "--model-name"
|
||||
#define MODELNAME_DESCRIPTION "Model name of the device"
|
||||
|
||||
#define UUID_OPTION "-U"
|
||||
#define UUID_OPTION_LONG "--uuid-name"
|
||||
#define UUID_DESCRIPTION "UUID of the device"
|
||||
|
||||
struct dial_options
|
||||
{
|
||||
const char * pOption;
|
||||
const char * pLongOption;
|
||||
const char * pOptionDescription;
|
||||
}dial_options_t;
|
||||
|
||||
struct dial_options gDialOptions[] =
|
||||
{
|
||||
{
|
||||
DATA_PATH_OPTION,
|
||||
DATA_PATH_OPTION_LONG,
|
||||
DATA_PATH_DESCRIPTION
|
||||
},
|
||||
{
|
||||
NETFLIX_PATH_OPTION,
|
||||
NETFLIX_PATH_OPTION_LONG,
|
||||
NETFLIX_PATH_DESCRIPTION
|
||||
},
|
||||
{
|
||||
FRIENDLY_NAME_OPTION,
|
||||
FRIENDLY_NAME_OPTION_LONG,
|
||||
FRIENDLY_NAME_DESCRIPTION,
|
||||
},
|
||||
{
|
||||
MODELNAME_OPTION,
|
||||
MODELNAME_OPTION_LONG,
|
||||
MODELNAME_DESCRIPTION
|
||||
},
|
||||
{
|
||||
UUID_OPTION,
|
||||
UUID_OPTION_LONG,
|
||||
UUID_DESCRIPTION
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
627
server/dial_server.c
Normal file
627
server/dial_server.c
Normal file
@@ -0,0 +1,627 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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 "dial_data.h"
|
||||
#include "dial_server.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <pthread.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include "mongoose.h"
|
||||
#include "url_lib.h"
|
||||
|
||||
// TODO: Partners should define this port
|
||||
#define DIAL_PORT (56789)
|
||||
#define DIAL_DATA_SIZE (8*1024)
|
||||
|
||||
static const char *gLocalhost = "127.0.0.1";
|
||||
|
||||
struct DIALApp_ {
|
||||
struct DIALApp_ *next;
|
||||
struct DIALAppCallbacks callbacks;
|
||||
struct DIALData_ *dial_data;
|
||||
void *callback_data;
|
||||
DIAL_run_t run_id;
|
||||
DIALStatus state;
|
||||
char *name;
|
||||
char payload[DIAL_MAX_PAYLOAD];
|
||||
int useAdditionalData;
|
||||
char corsAllowedOrigin[256];
|
||||
|
||||
};
|
||||
|
||||
typedef struct DIALApp_ DIALApp;
|
||||
|
||||
struct DIALServer_ {
|
||||
struct mg_context *ctx;
|
||||
struct DIALApp_ *apps;
|
||||
pthread_mutex_t mux;
|
||||
};
|
||||
|
||||
static void ds_lock(DIALServer *ds) {
|
||||
pthread_mutex_lock(&ds->mux);
|
||||
}
|
||||
|
||||
static void ds_unlock(DIALServer *ds) {
|
||||
pthread_mutex_unlock(&ds->mux);
|
||||
}
|
||||
|
||||
// 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
|
||||
static DIALApp **find_app(DIALServer *ds, const char *app_name) {
|
||||
DIALApp *app;
|
||||
DIALApp **ret = &ds->apps;
|
||||
|
||||
for (app = ds->apps; app != NULL; ret = &app->next, app = app->next) {
|
||||
if (!strcmp(app_name, app->name)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void url_decode_xml_encode(char *dst, char *src, size_t src_size) {
|
||||
char *url_decoded_key = (char *) malloc(src_size + 1);
|
||||
urldecode(url_decoded_key, src, src_size);
|
||||
xmlencode(dst, url_decoded_key, 2 * src_size);
|
||||
free(url_decoded_key);
|
||||
}
|
||||
|
||||
/*
|
||||
* A bad payload is defined to be an unprintable character or a
|
||||
* non-ascii character.
|
||||
*/
|
||||
static int isBadPayload(const char* pPayload, int numBytes) {
|
||||
int i = 0;
|
||||
fprintf( stderr, "Payload: checking %d bytes\n", numBytes);
|
||||
for (; i < numBytes; i++) {
|
||||
// High order bit should not be set
|
||||
// 0x7F is DEL (non-printable)
|
||||
// Anything under 32 is non-printable
|
||||
if (((pPayload[i] & 0x80) == 0x80) || (pPayload[i] == 0x7F)
|
||||
|| (pPayload[i] <= 0x1F))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
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 body[DIAL_MAX_PAYLOAD + sizeof(additional_data_param) + 2] = {0, };
|
||||
DIALApp *app;
|
||||
DIALServer *ds = request_info->user_data;
|
||||
int body_size;
|
||||
|
||||
ds_lock(ds);
|
||||
app = *find_app(ds, app_name);
|
||||
if (!app) {
|
||||
mg_send_http_error(conn, 404, "Not Found", "Not Found");
|
||||
} else {
|
||||
body_size = mg_read(conn, body, sizeof(body));
|
||||
// NUL-terminate it just in case
|
||||
if (body_size > DIAL_MAX_PAYLOAD) {
|
||||
mg_send_http_error(conn, 413, "413 Request Entity Too Large",
|
||||
"413 Request Entity Too Large");
|
||||
} else if (isBadPayload(body, body_size)) {
|
||||
mg_send_http_error(conn, 400, "400 Bad Request", "400 Bad Request");
|
||||
} else {
|
||||
char laddr[INET6_ADDRSTRLEN];
|
||||
const struct sockaddr_in *addr =
|
||||
(struct sockaddr_in *) &request_info->local_addr;
|
||||
inet_ntop(addr->sin_family, &addr->sin_addr, laddr, sizeof(laddr));
|
||||
in_port_t dial_port = DIAL_get_port(ds);
|
||||
|
||||
if (app->useAdditionalData) {
|
||||
// Construct additionalDataUrl=http://host:port/apps/app_name/dial_data
|
||||
sprintf(additional_data_param,
|
||||
"additionalDataUrl=http%%3A%%2F%%2Flocalhost%%3A%d%%2Fapps%%2F%s%%2Fdial_data%%3F",
|
||||
dial_port, app_name);
|
||||
}
|
||||
fprintf(stderr, "Starting the app with params %s\n", body);
|
||||
app->state = app->callbacks.start_cb(ds, app_name, body,
|
||||
additional_data_param,
|
||||
&app->run_id,
|
||||
app->callback_data);
|
||||
if (app->state == kDIALStatusRunning) {
|
||||
mg_printf(
|
||||
conn,
|
||||
"HTTP/1.1 201 Created\r\n"
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Location: http://%s:%d/apps/%s/run\r\n"
|
||||
"Access-Control-Allow-Origin: %s\r\n"
|
||||
"\r\n",
|
||||
laddr, dial_port, app_name, origin_header);
|
||||
// copy the payload into the application struct
|
||||
memset(app->payload, 0, DIAL_MAX_PAYLOAD);
|
||||
memcpy(app->payload, body, body_size);
|
||||
} else {
|
||||
mg_send_http_error(conn, 503, "Service Unavailable",
|
||||
"Service Unavailable");
|
||||
}
|
||||
}
|
||||
}
|
||||
ds_unlock(ds);
|
||||
}
|
||||
|
||||
static void handle_app_status(struct mg_connection *conn,
|
||||
const struct mg_request_info *request_info,
|
||||
const char *app_name,
|
||||
const char *origin_header) {
|
||||
DIALApp *app;
|
||||
int canStop = 0;
|
||||
DIALServer *ds = request_info->user_data;
|
||||
|
||||
ds_lock(ds);
|
||||
app = *find_app(ds, app_name);
|
||||
if (!app) {
|
||||
mg_send_http_error(conn, 404, "Not Found", "Not Found");
|
||||
ds_unlock(ds);
|
||||
return;
|
||||
}
|
||||
|
||||
char dial_data[DIAL_DATA_SIZE] = {0,};
|
||||
char *end = dial_data + DIAL_DATA_SIZE;
|
||||
char *p = dial_data;
|
||||
|
||||
for (DIALData* first = app->dial_data; first != NULL; first = first->next) {
|
||||
p = smartstrcat(p, " <", end - p);
|
||||
size_t key_length = strlen(first->key);
|
||||
char *encoded_key = (char *) malloc(2 * key_length + 1);
|
||||
url_decode_xml_encode(encoded_key, first->key, key_length);
|
||||
|
||||
size_t value_length = strlen(first->value);
|
||||
char *encoded_value = (char *) malloc(2 * value_length + 1);
|
||||
url_decode_xml_encode(encoded_value, first->value, value_length);
|
||||
|
||||
p = smartstrcat(p, encoded_key, end - p);
|
||||
p = smartstrcat(p, ">", end - p);
|
||||
p = smartstrcat(p, encoded_value, end - p);
|
||||
p = smartstrcat(p, "</", end - p);
|
||||
p = smartstrcat(p, encoded_key, end - p);
|
||||
p = smartstrcat(p, ">", end - p);
|
||||
free(encoded_key);
|
||||
free(encoded_value);
|
||||
}
|
||||
app->state = app->callbacks.status_cb(ds, app_name, app->run_id, &canStop,
|
||||
app->callback_data);
|
||||
mg_printf(
|
||||
conn,
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: application/xml\r\n"
|
||||
"Access-Control-Allow-Origin: %s\r\n"
|
||||
"\r\n"
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
|
||||
"<service xmlns=\"urn:dial-multiscreen-org:schemas:dial\" dialVer=%s>\r\n"
|
||||
" <name>%s</name>\r\n"
|
||||
" <options allowStop=\"%s\"/>\r\n"
|
||||
" <state>%s</state>\r\n"
|
||||
"%s"
|
||||
" <additionalData>\n"
|
||||
"%s"
|
||||
"\n </additionalData>\n"
|
||||
"</service>\r\n",
|
||||
origin_header,
|
||||
DIAL_VERSION,
|
||||
app->name,
|
||||
canStop ? "true" : "false",
|
||||
app->state ? "running" : "stopped",
|
||||
app->state == kDIALStatusStopped ?
|
||||
"" : " <link rel=\"run\" href=\"run\"/>\r\n",
|
||||
dial_data);
|
||||
ds_unlock(ds);
|
||||
}
|
||||
|
||||
static void handle_app_stop(struct mg_connection *conn,
|
||||
const struct mg_request_info *request_info,
|
||||
const char *app_name,
|
||||
const char *origin_header) {
|
||||
DIALApp *app;
|
||||
DIALServer *ds = request_info->user_data;
|
||||
int canStop = 0;
|
||||
|
||||
ds_lock(ds);
|
||||
app = *find_app(ds, app_name);
|
||||
|
||||
// update the application state
|
||||
if (app) {
|
||||
app->state = app->callbacks.status_cb(ds, app_name, app->run_id,
|
||||
&canStop, app->callback_data);
|
||||
}
|
||||
|
||||
if (!app || app->state != kDIALStatusRunning) {
|
||||
mg_send_http_error(conn, 404, "Not Found", "Not Found");
|
||||
} else {
|
||||
app->callbacks.stop_cb(ds, app_name, app->run_id, app->callback_data);
|
||||
app->state = kDIALStatusStopped;
|
||||
mg_printf(conn, "HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Access-Control-Allow-Origin: %s\r\n"
|
||||
"\r\n",
|
||||
origin_header);
|
||||
}
|
||||
ds_unlock(ds);
|
||||
}
|
||||
|
||||
static void handle_dial_data(struct mg_connection *conn,
|
||||
const struct mg_request_info *request_info,
|
||||
const char *app_name,
|
||||
const char *origin_header,
|
||||
int use_payload) {
|
||||
char body[DIAL_DATA_MAX_PAYLOAD + 2] = {0, };
|
||||
|
||||
DIALApp *app;
|
||||
DIALServer *ds = request_info->user_data;
|
||||
|
||||
ds_lock(ds);
|
||||
app = *find_app(ds, app_name);
|
||||
if (!app) {
|
||||
mg_send_http_error(conn, 404, "Not Found", "Not Found");
|
||||
ds_unlock(ds);
|
||||
return;
|
||||
}
|
||||
int nread;
|
||||
if (!use_payload) {
|
||||
if (request_info->query_string) {
|
||||
strncpy(body, request_info->query_string, DIAL_DATA_MAX_PAYLOAD);
|
||||
nread = strlen(body);
|
||||
} else {
|
||||
nread = 0;
|
||||
}
|
||||
} else {
|
||||
nread = mg_read(conn, body, DIAL_DATA_MAX_PAYLOAD);
|
||||
body[nread] = '\0';
|
||||
}
|
||||
if (nread > DIAL_DATA_MAX_PAYLOAD) {
|
||||
mg_send_http_error(conn, 413, "413 Request Entity Too Large",
|
||||
"413 Request Entity Too Large");
|
||||
ds_unlock(ds);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isBadPayload(body, nread)) {
|
||||
mg_send_http_error(conn, 400, "400 Bad Request", "400 Bad Request");
|
||||
ds_unlock(ds);
|
||||
return;
|
||||
}
|
||||
|
||||
app->dial_data = parse_params(body);
|
||||
store_dial_data(app->name, app->dial_data);
|
||||
|
||||
mg_printf(conn, "HTTP/1.1 200 OK\r\n"
|
||||
"Access-Control-Allow-Origin: %s\r\n"
|
||||
"\r\n",
|
||||
origin_header);
|
||||
|
||||
ds_unlock(ds);
|
||||
}
|
||||
|
||||
static int ends_with(const char *str, const char *suffix) {
|
||||
if (!str || !suffix)
|
||||
return 0;
|
||||
size_t lenstr = strlen(str);
|
||||
size_t lensuffix = strlen(suffix);
|
||||
if (lensuffix > lenstr)
|
||||
return 0;
|
||||
return strncmp(str + lenstr - lensuffix, suffix, lensuffix) == 0;
|
||||
}
|
||||
|
||||
|
||||
// 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;
|
||||
|
||||
const char * scanPointer=list;
|
||||
const char * spacePointer;
|
||||
unsigned int substringSize = 257;
|
||||
char *substring = (char *)malloc(substringSize);
|
||||
if (!substring){
|
||||
return 0;
|
||||
}
|
||||
while ( (spacePointer =strchr(scanPointer, ' ')) != NULL) {
|
||||
int copyLength = spacePointer - scanPointer;
|
||||
|
||||
// protect against buffer overflow
|
||||
if (copyLength>=substringSize){
|
||||
substringSize=copyLength+1;
|
||||
free(substring);
|
||||
substring=(char *)malloc(substringSize);
|
||||
if (!substring){
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(substring, scanPointer, copyLength);
|
||||
substring[copyLength] = '\0';
|
||||
//printf("found %s \n", substring);
|
||||
if (ends_with(str, substring)) {
|
||||
free(substring);
|
||||
return 1;
|
||||
}
|
||||
scanPointer = scanPointer + copyLength + 1; // assumption: only 1 character
|
||||
}
|
||||
free(substring);
|
||||
return ends_with(str, scanPointer);
|
||||
}
|
||||
|
||||
static int should_check_for_origin( char * origin ) {
|
||||
const char * const CHECK_PROTOS[] = { "http:", "https:", "file:" };
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (!strncmp(origin, CHECK_PROTOS[i], strlen(CHECK_PROTOS[i]) - 1)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int is_allowed_origin(DIALServer* ds, char * origin, const char * app_name) {
|
||||
if (!origin || strlen(origin)==0 || !should_check_for_origin(origin)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
ds_lock(ds);
|
||||
DIALApp *app;
|
||||
int result = 0;
|
||||
for (app = ds->apps; app != NULL; app = app->next) {
|
||||
if (!strcmp(app->name, app_name)) {
|
||||
if (!app->corsAllowedOrigin[0] ||
|
||||
ends_with_in_list(origin, app->corsAllowedOrigin)) {
|
||||
result = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ds_unlock(ds);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#define APPS_URI "/apps/"
|
||||
#define RUN_URI "/run"
|
||||
|
||||
static void *options_response(DIALServer *ds, struct mg_connection *conn, char *host_header, char *origin_header, const char* app_name, const char* methods)
|
||||
{
|
||||
if (host_header && is_allowed_origin(ds, origin_header, app_name)) {
|
||||
mg_printf(
|
||||
conn,
|
||||
"HTTP/1.1 204 No Content\r\n"
|
||||
"Access-Control-Allow-Methods: %s\r\n"
|
||||
"Access-Control-Max-Age: 86400\r\n"
|
||||
"Access-Control-Allow-Origin: %s\r\n"
|
||||
"Content-Length: 0"
|
||||
"\r\n",
|
||||
methods,
|
||||
origin_header);
|
||||
return "done";
|
||||
}
|
||||
mg_send_http_error(conn, 403, "Forbidden", "Forbidden");
|
||||
return "done";
|
||||
}
|
||||
|
||||
static void *request_handler(enum mg_event event, struct mg_connection *conn,
|
||||
const struct mg_request_info *request_info) {
|
||||
DIALServer *ds = request_info->user_data;
|
||||
|
||||
fprintf(stderr, "Received request %s\n", request_info->uri);
|
||||
char *host_header = {0,};
|
||||
char *origin_header = {0,};
|
||||
for (int i = 0; i < request_info->num_headers; ++i) {
|
||||
if (!strcmp(request_info->http_headers[i].name, "Host")) {
|
||||
host_header = request_info->http_headers[i].value;
|
||||
} else if (!strcmp(request_info->http_headers[i].name,
|
||||
"Origin")) {
|
||||
origin_header = request_info->http_headers[i].value;
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "Origin %s, Host: %s\n", origin_header, host_header);
|
||||
if (event == MG_NEW_REQUEST) {
|
||||
// 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 (!strcmp(request_info->request_method, "OPTIONS")) {
|
||||
return options_response(ds, conn, host_header, origin_header, app_name, "DELETE, OPTIONS");
|
||||
}
|
||||
|
||||
// DELETE non-empty app name
|
||||
if (app_name[0] != '\0'
|
||||
&& !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 {
|
||||
mg_send_http_error(conn, 403, "Forbidden", "Forbidden");
|
||||
return "done";
|
||||
}
|
||||
} else {
|
||||
mg_send_http_error(conn, 501, "Not Implemented",
|
||||
"Not Implemented");
|
||||
}
|
||||
}
|
||||
// URI starts with "/apps/" and is followed by an app name
|
||||
else if (!strncmp(request_info->uri, APPS_URI, sizeof(APPS_URI) - 1)
|
||||
&& !strchr(request_info->uri + strlen(APPS_URI), '/')) {
|
||||
const char *app_name;
|
||||
app_name = request_info->uri + sizeof(APPS_URI) - 1;
|
||||
|
||||
if (!strcmp(request_info->request_method, "OPTIONS")) {
|
||||
return options_response(ds, conn, host_header, origin_header, app_name, "GET, POST, OPTIONS");
|
||||
}
|
||||
|
||||
// start app
|
||||
if (!strcmp(request_info->request_method, "POST")) {
|
||||
if (host_header && is_allowed_origin(ds, origin_header, app_name)) {
|
||||
handle_app_start(conn, request_info, app_name, origin_header);
|
||||
} else {
|
||||
mg_send_http_error(conn, 403, "Forbidden", "Forbidden");
|
||||
return "done";
|
||||
}
|
||||
// get app status
|
||||
} 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");
|
||||
}
|
||||
// URI is of the form */app_name/dial_data
|
||||
} else if (strstr(request_info->uri, DIAL_DATA_URI)) {
|
||||
char laddr[INET6_ADDRSTRLEN];
|
||||
const struct sockaddr_in *addr =
|
||||
(struct sockaddr_in *) &request_info->remote_addr;
|
||||
inet_ntop(addr->sin_family, &addr->sin_addr, laddr, sizeof(laddr));
|
||||
if ( !strncmp(laddr, gLocalhost, strlen(gLocalhost)) ) {
|
||||
char *app_name = parse_app_name(request_info->uri);
|
||||
|
||||
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");
|
||||
}
|
||||
} else {
|
||||
mg_send_http_error(conn, 404, "Not Found", "Not Found");
|
||||
}
|
||||
return "done";
|
||||
} else if (event == MG_EVENT_LOG) {
|
||||
fprintf( stderr, "MG: %s\n", request_info->log_message);
|
||||
return "done";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DIALServer *DIAL_create() {
|
||||
DIALServer *ds = calloc(1, sizeof(DIALServer));
|
||||
pthread_mutex_init(&ds->mux, NULL);
|
||||
return ds;
|
||||
}
|
||||
|
||||
void DIAL_start(DIALServer *ds) {
|
||||
ds->ctx = mg_start(&request_handler, ds, DIAL_PORT);
|
||||
}
|
||||
|
||||
void DIAL_stop(DIALServer *ds) {
|
||||
mg_stop(ds->ctx);
|
||||
pthread_mutex_destroy(&ds->mux);
|
||||
}
|
||||
|
||||
in_port_t DIAL_get_port(DIALServer *ds) {
|
||||
struct sockaddr sa;
|
||||
socklen_t len = sizeof(sa);
|
||||
if (!mg_get_listen_addr(ds->ctx, &sa, &len)) {
|
||||
return 0;
|
||||
}
|
||||
return ntohs(((struct sockaddr_in *) &sa)->sin_port);
|
||||
}
|
||||
|
||||
int DIAL_register_app(DIALServer *ds, const char *app_name,
|
||||
struct DIALAppCallbacks *callbacks, void *user_data,
|
||||
int useAdditionalData,
|
||||
const char* corsAllowedOrigin) {
|
||||
DIALApp **ptr, *app;
|
||||
int ret;
|
||||
|
||||
ds_lock(ds);
|
||||
ptr = find_app(ds, app_name);
|
||||
if (*ptr != NULL) { // app already registered
|
||||
ds_unlock(ds);
|
||||
ret = 0;
|
||||
} else {
|
||||
app = malloc(sizeof(DIALApp));
|
||||
app->callbacks = *callbacks;
|
||||
app->name = strdup(app_name);
|
||||
app->next = *ptr;
|
||||
app->state = kDIALStatusStopped;
|
||||
app->callback_data = user_data;
|
||||
app->dial_data = retrieve_dial_data(app->name);
|
||||
app->useAdditionalData = useAdditionalData;
|
||||
app->corsAllowedOrigin[0] = '\0';
|
||||
if (corsAllowedOrigin &&
|
||||
strlen(corsAllowedOrigin) < sizeof(app->corsAllowedOrigin)) {
|
||||
strcpy(app->corsAllowedOrigin, corsAllowedOrigin);
|
||||
}
|
||||
*ptr = app;
|
||||
ret = 1;
|
||||
}
|
||||
ds_unlock(ds);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int DIAL_unregister_app(DIALServer *ds, const char *app_name) {
|
||||
DIALApp **ptr, *app;
|
||||
int ret;
|
||||
|
||||
ds_lock(ds);
|
||||
ptr = find_app(ds, app_name);
|
||||
if (*ptr == NULL) { // no such app
|
||||
ret = 0;
|
||||
} else {
|
||||
app = *ptr;
|
||||
*ptr = app->next;
|
||||
free(app->name);
|
||||
free(app);
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
ds_unlock(ds);
|
||||
return ret;
|
||||
}
|
||||
|
||||
const char * DIAL_get_payload(DIALServer *ds, const char *app_name) {
|
||||
const char * pPayload = NULL;
|
||||
DIALApp **ptr, *app;
|
||||
|
||||
// NOTE: Don't grab the mutex as we are calling this function from
|
||||
// inside the application callback which already has the lock.
|
||||
//ds_lock(ds);
|
||||
ptr = find_app(ds, app_name);
|
||||
if (*ptr != NULL) {
|
||||
app = *ptr;
|
||||
pPayload = app->payload;
|
||||
}
|
||||
//ds_unlock(ds);
|
||||
return pPayload;
|
||||
}
|
||||
|
||||
158
server/dial_server.h
Normal file
158
server/dial_server.h
Normal file
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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 DIAL_SERVER_H_
|
||||
#define DIAL_SERVER_H_
|
||||
|
||||
#include <netinet/in.h>
|
||||
|
||||
/*
|
||||
* Dial application states
|
||||
*/
|
||||
typedef enum {
|
||||
kDIALStatusStopped,
|
||||
kDIALStatusRunning
|
||||
} DIALStatus;
|
||||
|
||||
/*
|
||||
* DIAL version that is reported via in the status response.
|
||||
*/
|
||||
#define DIAL_VERSION ("\"1.7\"")
|
||||
|
||||
/*
|
||||
* The maximum DIAL payload accepted per the DIAL 1.6.1 specification.
|
||||
*/
|
||||
#define DIAL_MAX_PAYLOAD (4096)
|
||||
|
||||
/*
|
||||
* The maximum additionalDataUrl length
|
||||
*/
|
||||
|
||||
#define DIAL_MAX_ADDITIONALURL (1024)
|
||||
|
||||
/*
|
||||
* Opaque DIAL server handle
|
||||
*/
|
||||
struct DIALServer_;
|
||||
typedef struct DIALServer_ DIALServer;
|
||||
|
||||
/*
|
||||
* Opaque run id that can be system specific
|
||||
*/
|
||||
typedef void * DIAL_run_t;
|
||||
|
||||
/*
|
||||
* DIAL start callback
|
||||
*/
|
||||
typedef DIALStatus (*DIAL_app_start_cb)(DIALServer *ds, const char *app_name,
|
||||
const char *payload, const char *additionalDataUrl,
|
||||
DIAL_run_t *run_id, void *callback_data);
|
||||
/*
|
||||
* DIAL stop callback
|
||||
*/
|
||||
typedef void (*DIAL_app_stop_cb)(DIALServer *ds, const char *app_name,
|
||||
DIAL_run_t run_id, void *callback_data);
|
||||
/*
|
||||
* DIAL status callback
|
||||
*/
|
||||
typedef DIALStatus (*DIAL_app_status_cb)(DIALServer *ds, const char *app_name,
|
||||
DIAL_run_t run_id, int* pCanStop,
|
||||
void *callback_data);
|
||||
|
||||
/*
|
||||
* DIAL callbacks
|
||||
*/
|
||||
struct DIALAppCallbacks {
|
||||
DIAL_app_start_cb start_cb;
|
||||
DIAL_app_stop_cb stop_cb;
|
||||
DIAL_app_status_cb status_cb;
|
||||
};
|
||||
|
||||
/*
|
||||
* Creates the DIAL server. Returns a handle to the DIAL server.
|
||||
*/
|
||||
DIALServer *DIAL_create();
|
||||
|
||||
/*
|
||||
* Starts the DIAL server.
|
||||
*
|
||||
* @param[in] ds DIAL server handle
|
||||
*/
|
||||
void DIAL_start(DIALServer *ds);
|
||||
|
||||
/*
|
||||
* Stop the DIAL server.
|
||||
*
|
||||
* @param[in] ds DIAL server handle
|
||||
*/
|
||||
void DIAL_stop(DIALServer *ds);
|
||||
|
||||
/*
|
||||
* Register a DIAL application
|
||||
*
|
||||
* @param[in] ds DIAL server handle
|
||||
* @param[in] app_name Name of the application. This should match the DIAL
|
||||
* application end point.
|
||||
* @param[in] callbacks Structure with application callbacks
|
||||
* @param[in] callback_data Client user data
|
||||
* @param[in] if non-0, the app supports DIALadditionalDataURL.
|
||||
* @param[in] if non-NULL, specifies the CORS allowed origin for this app.
|
||||
*
|
||||
* @return 1 if successful, 0 otherwise
|
||||
*/
|
||||
int DIAL_register_app(DIALServer *ds, const char *app_name,
|
||||
struct DIALAppCallbacks *callbacks,
|
||||
void *callback_data, int useAdditionalData,
|
||||
const char* corsAllowedOrigin);
|
||||
|
||||
/*
|
||||
* Unregsiter an application
|
||||
*
|
||||
* @param[in] ds DIAL server handle
|
||||
* @param[in] app_name Name of the DIAL application
|
||||
*
|
||||
* @return 1 if successful, 0 otherwise
|
||||
*/
|
||||
int DIAL_unregister_app(DIALServer *ds, const char *app_name);
|
||||
|
||||
/*
|
||||
* Get the DIAL REST endpoint
|
||||
*
|
||||
* @return Port number of the DIAL rest endpoint. Returns 0 on error.
|
||||
*/
|
||||
in_port_t DIAL_get_port(DIALServer *ds);
|
||||
|
||||
/*
|
||||
* Get the last payload delivered to an application. This can be used
|
||||
* by application clients to see if the payload changed between lauches.
|
||||
*
|
||||
* @param[in] ds DIAL server handle
|
||||
* @param[in] app_name Name of the application
|
||||
*
|
||||
* @return Pointer to a NULL terminated string.
|
||||
*/
|
||||
const char * DIAL_get_payload(DIALServer *ds, const char *app_name);
|
||||
|
||||
#endif // DIAL_SERVER_H_
|
||||
470
server/main.c
Normal file
470
server/main.c
Normal file
@@ -0,0 +1,470 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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 <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <dirent.h>
|
||||
#include <regex.h>
|
||||
|
||||
#include "dial_server.h"
|
||||
#include "dial_options.h"
|
||||
#include <signal.h>
|
||||
|
||||
#define BUFSIZE 256
|
||||
|
||||
static char *spAppNetflix = "netflix"; // name of the netflix executable
|
||||
static char *spDefaultNetflix = "../../../src/platform/qt/netflix";
|
||||
static char *spDefaultData="../../../src/platform/qt/data";
|
||||
static char *spNfDataDir = "NF_DATA_DIR=";
|
||||
static char *defaultLaunchParam = "source_type=12";
|
||||
static char *spDefaultFriendlyName = "DIAL server sample";
|
||||
static char *spDefaultModelName = "NOT A VALID MODEL NAME";
|
||||
static char *spDefaultUuid = "deadbeef-dead-beef-dead-beefdeadbeef";
|
||||
static char spDataDir[BUFSIZE];
|
||||
static char spNetflix[BUFSIZE];
|
||||
static char spFriendlyName[BUFSIZE];
|
||||
static char spModelName[BUFSIZE];
|
||||
static char spUuid[BUFSIZE];
|
||||
static int gDialPort;
|
||||
|
||||
static char *spAppYouTube = "chrome";
|
||||
static char *spAppYouTubeMatch = "chrome.*google-chrome-dial";
|
||||
static char *spAppYouTubeExecutable = "/opt/google/chrome/google-chrome";
|
||||
static char *spYouTubePS3UserAgent = "--user-agent="
|
||||
"Mozilla/5.0 (PS3; Leanback Shell) AppleWebKit/535.22 (KHTML, like Gecko) "
|
||||
"Chrome/19.0.1048.0 LeanbackShell/01.00.01.73 QA Safari/535.22 Sony PS3/ "
|
||||
"(PS3, , no, CH)";
|
||||
|
||||
// Adding 20 bytes for prepended source_type for Netflix
|
||||
static char sQueryParam[DIAL_MAX_PAYLOAD+DIAL_MAX_ADDITIONALURL+40];
|
||||
|
||||
static int doesMatch( char* pzExp, char* pzStr)
|
||||
{
|
||||
regex_t exp;
|
||||
int ret;
|
||||
int match = 0;
|
||||
if ((ret = regcomp( &exp, pzExp, REG_EXTENDED ))) {
|
||||
char errbuf[1024] = {0,};
|
||||
regerror(ret, &exp, errbuf, sizeof(errbuf));
|
||||
fprintf( stderr, "regexp error: %s", errbuf );
|
||||
} else {
|
||||
regmatch_t matches[1];
|
||||
if( regexec( &exp, pzStr, 1, matches, 0 ) == 0 ) {
|
||||
match = 1;
|
||||
}
|
||||
}
|
||||
regfree(&exp);
|
||||
return match;
|
||||
}
|
||||
|
||||
void signalHandler(int signal)
|
||||
{
|
||||
switch(signal)
|
||||
{
|
||||
case SIGTERM:
|
||||
// just ignore this, we don't want to die
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* The URL encoding source code was obtained here:
|
||||
* http://www.geekhideout.com/urlcode.shtml
|
||||
*/
|
||||
|
||||
/* Converts a hex character to its integer value */
|
||||
char from_hex(char ch) {
|
||||
return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10;
|
||||
}
|
||||
|
||||
/* Converts an integer value to its hex character*/
|
||||
char to_hex(char code) {
|
||||
static char hex[] = "0123456789abcdef";
|
||||
return hex[code & 15];
|
||||
}
|
||||
|
||||
/* Returns a url-encoded version of str */
|
||||
/* IMPORTANT: be sure to free() the returned string after use */
|
||||
char *url_encode(const char *str) {
|
||||
const char *pstr;
|
||||
char *buf, *pbuf;
|
||||
pstr = str;
|
||||
buf = malloc(strlen(str) * 3 + 1);
|
||||
pbuf = buf;
|
||||
if( buf )
|
||||
{
|
||||
while (*pstr) {
|
||||
if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == '~')
|
||||
*pbuf++ = *pstr;
|
||||
else if (*pstr == ' ')
|
||||
*pbuf++ = '+';
|
||||
else
|
||||
*pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex(*pstr & 15);
|
||||
pstr++;
|
||||
}
|
||||
*pbuf = '\0';
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
/*
|
||||
* End of URL ENCODE source
|
||||
*/
|
||||
|
||||
/*
|
||||
* This function will walk /proc and look for the application in
|
||||
* /proc/<PID>/comm. and /proc/<PID>/cmdline to find it's command (executable
|
||||
* name) and command line (if needed).
|
||||
* Implementors can override this function with an equivalent.
|
||||
*/
|
||||
static int isAppRunning( char *pzName, char *pzCommandPattern ) {
|
||||
DIR* proc_fd = opendir("/proc");
|
||||
if( proc_fd != NULL ) {
|
||||
struct dirent* procEntry;
|
||||
while((procEntry=readdir(proc_fd)) != NULL) {
|
||||
if( doesMatch( "^[0-9][0-9]*$", procEntry->d_name ) ) {
|
||||
char exePath[64] = {0,};
|
||||
char link[256] = {0,};
|
||||
char cmdlinePath[64] = {0,};
|
||||
char buffer[1024] = {0,};
|
||||
int len;
|
||||
sprintf( exePath, "/proc/%s/exe", procEntry->d_name);
|
||||
sprintf( cmdlinePath, "/proc/%s/cmdline", procEntry->d_name);
|
||||
|
||||
if( (len = readlink( exePath, link, sizeof(link)-1)) != -1 ) {
|
||||
char executable[256] = {0,};
|
||||
strcat( executable, pzName );
|
||||
strcat( executable, "$" );
|
||||
// TODO: Make this search for EOL to prevent false positivies
|
||||
if( !doesMatch( executable, link ) ) {
|
||||
continue;
|
||||
}
|
||||
// else //fall through, we found it
|
||||
}
|
||||
else continue;
|
||||
|
||||
if (pzCommandPattern != NULL) {
|
||||
FILE *cmdline = fopen(cmdlinePath, "r");
|
||||
if (!cmdline) {
|
||||
continue;
|
||||
}
|
||||
if (fgets(buffer, 1024, cmdline) == NULL) {
|
||||
fclose(cmdline);
|
||||
continue;
|
||||
}
|
||||
fclose(cmdline);
|
||||
|
||||
if (!doesMatch( pzCommandPattern, buffer )) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
closedir(proc_fd);
|
||||
return atoi(procEntry->d_name);
|
||||
}
|
||||
}
|
||||
|
||||
closedir(proc_fd);
|
||||
} else {
|
||||
printf("/proc failed to open\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static pid_t runApplication( const char * const args[], DIAL_run_t *run_id ) {
|
||||
pid_t pid = fork();
|
||||
if (pid != -1) {
|
||||
if (!pid) { // child
|
||||
putenv(spDataDir);
|
||||
printf("Execute:\n");
|
||||
for(int i = 0; args[i]; ++i) {
|
||||
printf(" %d) %s\n", i, args[i]);
|
||||
}
|
||||
if( execv(*args, (char * const *) args) == -1) {
|
||||
printf("%s failed to launch\n", *args);
|
||||
perror("Failed to Launch \n");
|
||||
}
|
||||
} else {
|
||||
*run_id = (void *)(long)pid; // parent PID
|
||||
}
|
||||
return kDIALStatusRunning;
|
||||
} else {
|
||||
return kDIALStatusStopped;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Compare the applications last launch parameters with the new parameters.
|
||||
* If they match, return false
|
||||
* If they don't match, return true
|
||||
*/
|
||||
static int shouldRelaunch(
|
||||
DIALServer *pServer,
|
||||
const char *pAppName,
|
||||
const char *args )
|
||||
{
|
||||
return ( strncmp( DIAL_get_payload(pServer, pAppName), args, DIAL_MAX_PAYLOAD ) != 0 );
|
||||
}
|
||||
|
||||
static DIALStatus youtube_start(DIALServer *ds, const char *appname,
|
||||
const char *payload, const char *additionalDataUrl,
|
||||
DIAL_run_t *run_id, void *callback_data) {
|
||||
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");
|
||||
}
|
||||
sprintf( data, "--user-data-dir=%s/.config/google-chrome-dial", getenv("HOME") );
|
||||
|
||||
const char * const youtube_args[] = { spAppYouTubeExecutable,
|
||||
spYouTubePS3UserAgent,
|
||||
data, "--app", url, NULL
|
||||
};
|
||||
runApplication( youtube_args, run_id );
|
||||
|
||||
return kDIALStatusRunning;
|
||||
}
|
||||
|
||||
static DIALStatus youtube_status(DIALServer *ds, const char *appname,
|
||||
DIAL_run_t run_id, int *pCanStop, void *callback_data) {
|
||||
// YouTube can stop
|
||||
*pCanStop = 1;
|
||||
return isAppRunning( spAppYouTube, spAppYouTubeMatch ) ? kDIALStatusRunning : kDIALStatusStopped;
|
||||
}
|
||||
|
||||
static void youtube_stop(DIALServer *ds, const char *appname, DIAL_run_t run_id,
|
||||
void *callback_data) {
|
||||
printf("\n\n ** KILL YouTube **\n\n");
|
||||
pid_t pid;
|
||||
if ((pid = isAppRunning( spAppYouTube, spAppYouTubeMatch ))) {
|
||||
kill(pid, SIGTERM);
|
||||
}
|
||||
}
|
||||
|
||||
static DIALStatus netflix_start(DIALServer *ds, const char *appname,
|
||||
const char *payload, const char *additionalDataUrl,
|
||||
DIAL_run_t *run_id, void *callback_data) {
|
||||
int shouldRelaunchApp = 0;
|
||||
int appPid = 0;
|
||||
|
||||
// only launch Netflix if it isn't running
|
||||
appPid = isAppRunning( spAppNetflix, NULL );
|
||||
shouldRelaunchApp = shouldRelaunch( ds, appname, payload );
|
||||
|
||||
// construct the payload to determine if it has changed from the previous launch
|
||||
memset( sQueryParam, 0, sizeof(sQueryParam) );
|
||||
strcat( sQueryParam, defaultLaunchParam );
|
||||
if(strlen(payload))
|
||||
{
|
||||
char * pUrlEncodedParams;
|
||||
pUrlEncodedParams = url_encode( payload );
|
||||
if( pUrlEncodedParams )
|
||||
{
|
||||
strcat( sQueryParam, "&dial=");
|
||||
strcat( sQueryParam, pUrlEncodedParams );
|
||||
free( pUrlEncodedParams );
|
||||
}
|
||||
}
|
||||
|
||||
if(strlen(additionalDataUrl)){
|
||||
strcat(sQueryParam, "&");
|
||||
strcat(sQueryParam, additionalDataUrl);
|
||||
}
|
||||
|
||||
printf("appPid = %s, shouldRelaunch = %s queryParams = %s\n",
|
||||
appPid?"TRUE":"FALSE",
|
||||
shouldRelaunchApp?"TRUE":"FALSE",
|
||||
sQueryParam );
|
||||
|
||||
// if its not running, launch it. The Netflix application should
|
||||
// never be relaunched
|
||||
if( !appPid )
|
||||
{
|
||||
const char * const netflix_args[] = {spNetflix, "-Q", sQueryParam, 0};
|
||||
return runApplication( netflix_args, run_id );
|
||||
}
|
||||
else return kDIALStatusRunning;
|
||||
}
|
||||
|
||||
static DIALStatus netflix_status(DIALServer *ds, const char *appname,
|
||||
DIAL_run_t run_id, int* pCanStop, void *callback_data) {
|
||||
// Netflix application can stop
|
||||
*pCanStop = 1;
|
||||
|
||||
waitpid((pid_t)(long)run_id, NULL, WNOHANG); // reap child
|
||||
|
||||
return isAppRunning( spAppNetflix, NULL ) ? kDIALStatusRunning : kDIALStatusStopped;
|
||||
}
|
||||
|
||||
static void netflix_stop(DIALServer *ds, const char *appname, DIAL_run_t run_id,
|
||||
void *callback_data) {
|
||||
int pid;
|
||||
pid = isAppRunning( spAppNetflix, NULL );
|
||||
if( pid )
|
||||
{
|
||||
printf("Killing pid %d\n", pid);
|
||||
kill((pid_t)pid, SIGTERM);
|
||||
waitpid((pid_t)pid, NULL, 0); // reap child
|
||||
}
|
||||
}
|
||||
|
||||
void run_ssdp(int port, const char *pFriendlyName, const char * pModelName, const char *pUuid);
|
||||
|
||||
static void printUsage()
|
||||
{
|
||||
int i, numberOfOptions = sizeof(gDialOptions) / sizeof(dial_options_t);
|
||||
printf("usage: dialserver <options>\n");
|
||||
printf("options:\n");
|
||||
for( i = 0; i < numberOfOptions; i++ )
|
||||
{
|
||||
printf(" %s|%s [value]: %s\n",
|
||||
gDialOptions[i].pOption,
|
||||
gDialOptions[i].pLongOption,
|
||||
gDialOptions[i].pOptionDescription );
|
||||
}
|
||||
}
|
||||
|
||||
static void setValue( char * pSource, char dest[] )
|
||||
{
|
||||
// Destination is always one of our static buffers with size BUFSIZE
|
||||
memset( dest, 0, BUFSIZE );
|
||||
memcpy( dest, pSource, strlen(pSource) );
|
||||
}
|
||||
|
||||
static void setDataDir(char *pData)
|
||||
{
|
||||
setValue( spNfDataDir, spDataDir );
|
||||
strcat(spDataDir, pData);
|
||||
}
|
||||
|
||||
void runDial(void)
|
||||
{
|
||||
DIALServer *ds;
|
||||
ds = DIAL_create();
|
||||
struct DIALAppCallbacks cb_nf = {netflix_start, netflix_stop, netflix_status};
|
||||
struct DIALAppCallbacks cb_yt = {youtube_start, youtube_stop, youtube_status};
|
||||
|
||||
DIAL_register_app(ds, "Netflix", &cb_nf, NULL, 1, ".netflix.com");
|
||||
DIAL_register_app(ds, "YouTube", &cb_yt, NULL, 1, ".youtube.com");
|
||||
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);
|
||||
free(ds);
|
||||
}
|
||||
|
||||
static void processOption( int index, char * pOption )
|
||||
{
|
||||
switch(index)
|
||||
{
|
||||
case 0: // Data path
|
||||
memset( spDataDir, 0, sizeof(spDataDir) );
|
||||
setDataDir( pOption );
|
||||
break;
|
||||
case 1: // Netflix path
|
||||
setValue( pOption, spNetflix );
|
||||
break;
|
||||
case 2: // Friendly name
|
||||
setValue( pOption, spFriendlyName );
|
||||
break;
|
||||
case 3: // Model Name
|
||||
setValue( pOption, spModelName );
|
||||
break;
|
||||
case 4: // UUID
|
||||
setValue( pOption, spUuid );
|
||||
break;
|
||||
default:
|
||||
// Should not get here
|
||||
fprintf( stderr, "Option %d not valid\n", index);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
struct sigaction action;
|
||||
action.sa_handler = signalHandler;
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_flags = 0;
|
||||
sigaction(SIGTERM, &action, NULL);
|
||||
|
||||
srand(time(NULL));
|
||||
int i;
|
||||
i = isAppRunning(spAppNetflix, NULL );
|
||||
printf("Netflix is %s\n", i ? "Running":"Not Running");
|
||||
i = isAppRunning( spAppYouTube, spAppYouTubeMatch );
|
||||
printf("YouTube is %s\n", i ? "Running":"Not Running");
|
||||
|
||||
// set all defaults
|
||||
setValue(spDefaultFriendlyName, spFriendlyName );
|
||||
setValue(spDefaultModelName, spModelName );
|
||||
setValue(spDefaultUuid, spUuid );
|
||||
setValue(spDefaultNetflix, spNetflix );
|
||||
setDataDir(spDefaultData);
|
||||
|
||||
// Process command line options
|
||||
// Loop through pairs of command line options.
|
||||
for( i = 1; i < argc; i+=2 )
|
||||
{
|
||||
int numberOfOptions = sizeof(gDialOptions) / sizeof(dial_options_t);
|
||||
while( --numberOfOptions >= 0 )
|
||||
{
|
||||
int shortLen, longLen;
|
||||
shortLen = strlen(gDialOptions[numberOfOptions].pOption);
|
||||
longLen = strlen(gDialOptions[numberOfOptions].pLongOption);
|
||||
if( ( ( strncmp( argv[i], gDialOptions[numberOfOptions].pOption, shortLen ) == 0 ) ||
|
||||
( strncmp( argv[i], gDialOptions[numberOfOptions].pLongOption, longLen ) == 0 ) ) &&
|
||||
( (i+1) < argc ) )
|
||||
{
|
||||
processOption( numberOfOptions, argv[i+1] );
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if we don't find an option in our list, bail out.
|
||||
if( numberOfOptions < 0 )
|
||||
{
|
||||
printUsage();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
runDial();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
25
server/makefile
Normal file
25
server/makefile
Normal file
@@ -0,0 +1,25 @@
|
||||
CC=$(TARGET)gcc
|
||||
|
||||
.PHONY: clean
|
||||
.DEFAULT_GOAL=all
|
||||
|
||||
OBJS := main.o dial_server.o mongoose.o quick_ssdp.o url_lib.o dial_data.o
|
||||
HEADERS := $(wildcard *.h)
|
||||
|
||||
%.c: $(HEADERS)
|
||||
|
||||
%.o: %.c $(HEADERS)
|
||||
$(CC) -Wall -Werror -g -std=gnu99 $(CFLAGS) -c $*.c -o $*.o
|
||||
|
||||
all: dialserver test
|
||||
|
||||
dialserver: $(OBJS)
|
||||
$(CC) -Wall -Werror -g $(OBJS) -ldl -lpthread -o dialserver
|
||||
|
||||
test:
|
||||
make -C tests
|
||||
./tests/run_tests
|
||||
|
||||
clean:
|
||||
rm -f *.o dialserver
|
||||
make -C tests clean
|
||||
956
server/mongoose.c
Normal file
956
server/mongoose.c
Normal file
@@ -0,0 +1,956 @@
|
||||
// Copyright (c) 2004-2010 Sergey Lyubka
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
#define _GNU_SOURCE 1
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <limits.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifndef BUFSIZ
|
||||
#define BUFSIZ 4096
|
||||
#endif
|
||||
|
||||
#define MAX_REQUEST_SIZE 4096
|
||||
#define NUM_THREADS 4
|
||||
#include <sys/wait.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/select.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/time.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
|
||||
|
||||
#define ERRNO errno
|
||||
#define INVALID_SOCKET (-1)
|
||||
|
||||
typedef int SOCKET;
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
#define MONGOOSE_VERSION "3.0"
|
||||
#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
|
||||
|
||||
#if defined(DEBUG)
|
||||
#define DEBUG_TRACE(x) do { \
|
||||
flockfile(stdout); \
|
||||
printf("*** %lu.%p.%s.%d: ", \
|
||||
(unsigned long) time(NULL), (void *) pthread_self(), \
|
||||
__func__, __LINE__); \
|
||||
printf x; \
|
||||
putchar('\n'); \
|
||||
fflush(stdout); \
|
||||
funlockfile(stdout); \
|
||||
} while (0)
|
||||
#else
|
||||
#define DEBUG_TRACE(x)
|
||||
#endif // DEBUG
|
||||
|
||||
typedef void * (*mg_thread_func_t)(void *);
|
||||
|
||||
|
||||
// Describes a socket which was accept()-ed by the master thread and queued for
|
||||
// future handling by the worker thread.
|
||||
struct socket {
|
||||
SOCKET sock; // Listening socket
|
||||
struct sockaddr_in local_addr; // Local socket address
|
||||
struct sockaddr_in remote_addr; // Remote socket address
|
||||
};
|
||||
|
||||
struct mg_context {
|
||||
volatile int stop_flag; // Should we stop event loop
|
||||
mg_callback_t user_callback; // User-defined callback function
|
||||
void *user_data; // User-defined data
|
||||
|
||||
SOCKET local_socket;
|
||||
struct sockaddr_in local_address;
|
||||
|
||||
volatile int num_threads; // Number of threads
|
||||
pthread_mutex_t mutex; // Protects (max|num)_threads
|
||||
pthread_cond_t cond; // Condvar for tracking workers terminations
|
||||
|
||||
struct socket queue[20]; // Accepted sockets
|
||||
volatile int sq_head; // Head of the socket queue
|
||||
volatile int sq_tail; // Tail of the socket queue
|
||||
pthread_cond_t sq_full; // Singaled when socket is produced
|
||||
pthread_cond_t sq_empty; // Signaled when socket is consumed
|
||||
};
|
||||
|
||||
struct mg_connection {
|
||||
struct mg_request_info request_info;
|
||||
struct mg_context *ctx;
|
||||
struct socket client; // Connected client
|
||||
time_t birth_time; // Time connection was accepted
|
||||
int64_t num_bytes_sent; // Total bytes sent to client
|
||||
int64_t content_len; // Content-Length header value
|
||||
int64_t consumed_content; // How many bytes of content is already read
|
||||
char *buf; // Buffer for received data
|
||||
int buf_size; // Buffer size
|
||||
int request_len; // Size of the request + headers in a buffer
|
||||
int data_len; // Total size of data in a buffer
|
||||
};
|
||||
|
||||
static void *call_user(struct mg_connection *conn, enum mg_event event) {
|
||||
conn->request_info.user_data = conn->ctx->user_data;
|
||||
return conn->ctx->user_callback == NULL ? NULL :
|
||||
conn->ctx->user_callback(event, conn, &conn->request_info);
|
||||
}
|
||||
|
||||
// Print error message to the opened error log stream.
|
||||
static void cry(struct mg_connection *conn, const char *fmt, ...) {
|
||||
char buf[BUFSIZ];
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
(void) vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
// Do not lock when getting the callback value, here and below.
|
||||
// I suppose this is fine, since function cannot disappear in the
|
||||
// same way string option can.
|
||||
conn->request_info.log_message = buf;
|
||||
if (call_user(conn, MG_EVENT_LOG) == NULL) {
|
||||
DEBUG_TRACE(("[%s]", buf));
|
||||
}
|
||||
conn->request_info.log_message = NULL;
|
||||
}
|
||||
|
||||
// Return fake connection structure. Used for logging, if connection
|
||||
// is not applicable at the moment of logging.
|
||||
static struct mg_connection *fc(struct mg_context *ctx) {
|
||||
static struct mg_connection fake_connection;
|
||||
fake_connection.ctx = ctx;
|
||||
return &fake_connection;
|
||||
}
|
||||
|
||||
const char *mg_version(void) {
|
||||
return MONGOOSE_VERSION;
|
||||
}
|
||||
|
||||
static int lowercase(const char *s) {
|
||||
return tolower(* (const unsigned char *) s);
|
||||
}
|
||||
|
||||
static int mg_strcasecmp(const char *s1, const char *s2) {
|
||||
int diff;
|
||||
|
||||
do {
|
||||
diff = lowercase(s1++) - lowercase(s2++);
|
||||
} while (diff == 0 && s1[-1] != '\0');
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
// Like snprintf(), but never returns negative value, or the value
|
||||
// that is larger than a supplied buffer.
|
||||
// Thanks to Adam Zeldis to pointing snprintf()-caused vulnerability
|
||||
// in his audit report.
|
||||
static int mg_vsnprintf(struct mg_connection *conn, char *buf, size_t buflen,
|
||||
const char *fmt, va_list ap) {
|
||||
int n;
|
||||
|
||||
if (buflen == 0)
|
||||
return 0;
|
||||
|
||||
n = vsnprintf(buf, buflen, fmt, ap);
|
||||
|
||||
if (n < 0) {
|
||||
cry(conn, "vsnprintf error");
|
||||
n = 0;
|
||||
} else if (n >= (int) buflen) {
|
||||
cry(conn, "truncating vsnprintf buffer: [%.*s]",
|
||||
n > 200 ? 200 : n, buf);
|
||||
n = (int) buflen - 1;
|
||||
}
|
||||
buf[n] = '\0';
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static int mg_snprintf(struct mg_connection *conn, char *buf, size_t buflen,
|
||||
const char *fmt, ...) {
|
||||
va_list ap;
|
||||
int n;
|
||||
|
||||
va_start(ap, fmt);
|
||||
n = mg_vsnprintf(conn, buf, buflen, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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 {
|
||||
end_whitespace = end_word + 1 + strspn(end_word + 1, whitespace);
|
||||
|
||||
for (p = end_word; p < end_whitespace; p++) {
|
||||
*p = '\0';
|
||||
}
|
||||
|
||||
*buf = end_whitespace;
|
||||
}
|
||||
|
||||
return begin_word;
|
||||
}
|
||||
|
||||
// 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 HTTP header value, or NULL if not found.
|
||||
static const char *get_header(const struct mg_request_info *ri,
|
||||
const char *name) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ri->num_headers; i++)
|
||||
if (!mg_strcasecmp(name, ri->http_headers[i].name))
|
||||
return ri->http_headers[i].value;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *mg_get_header(const struct mg_connection *conn, const char *name) {
|
||||
return get_header(&conn->request_info, name);
|
||||
}
|
||||
|
||||
static const char *suggest_connection_header(const struct mg_connection *conn) {
|
||||
return "close";
|
||||
}
|
||||
|
||||
void mg_send_http_error(struct mg_connection *conn, int status,
|
||||
const char *reason, const char *fmt, ...) {
|
||||
char buf[BUFSIZ];
|
||||
va_list ap;
|
||||
int len;
|
||||
|
||||
conn->request_info.status_code = status;
|
||||
|
||||
buf[0] = '\0';
|
||||
len = 0;
|
||||
|
||||
/* 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);
|
||||
cry(conn, "%s", buf);
|
||||
buf[len++] = '\n';
|
||||
|
||||
va_start(ap, fmt);
|
||||
len += mg_vsnprintf(conn, buf + len, sizeof(buf) - len, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
DEBUG_TRACE(("[%s]", buf));
|
||||
|
||||
mg_printf(conn, "HTTP/1.1 %d %s\r\n"
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Content-Length: %d\r\n"
|
||||
"Connection: %s\r\n\r\n", status, reason, len,
|
||||
suggest_connection_header(conn));
|
||||
conn->num_bytes_sent += mg_printf(conn, "%s", buf);
|
||||
}
|
||||
|
||||
static int start_thread(struct mg_context *ctx, mg_thread_func_t func,
|
||||
void *param) {
|
||||
pthread_t thread_id;
|
||||
pthread_attr_t attr;
|
||||
int retval;
|
||||
|
||||
(void) pthread_attr_init(&attr);
|
||||
(void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||
// TODO(lsm): figure out why mongoose dies on Linux if next line is enabled
|
||||
// (void) pthread_attr_setstacksize(&attr, sizeof(struct mg_connection) * 5);
|
||||
|
||||
if ((retval = pthread_create(&thread_id, &attr, func, param)) != 0) {
|
||||
cry(fc(ctx), "%s: %s", __func__, strerror(retval));
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int 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
|
||||
// descriptor. Return number of bytes written.
|
||||
static int64_t push(FILE *fp, SOCKET sock, const char *buf, int64_t len) {
|
||||
int64_t sent;
|
||||
int n, k;
|
||||
|
||||
sent = 0;
|
||||
while (sent < len) {
|
||||
|
||||
/* How many bytes 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);
|
||||
if (ferror(fp))
|
||||
n = -1;
|
||||
} else {
|
||||
n = send(sock, buf + sent, (size_t)k, 0);
|
||||
}
|
||||
|
||||
if (n < 0)
|
||||
break;
|
||||
|
||||
sent += n;
|
||||
}
|
||||
|
||||
return sent;
|
||||
}
|
||||
|
||||
// Read from IO channel - opened file descriptor, socket, or SSL descriptor.
|
||||
// Return number of bytes read.
|
||||
static int pull(SOCKET sock, char *buf, int len) {
|
||||
int nread;
|
||||
|
||||
nread = recv(sock, buf, (size_t) len, 0);
|
||||
|
||||
return nread;
|
||||
}
|
||||
|
||||
int mg_read(struct mg_connection *conn, void *buf, size_t len) {
|
||||
int n, buffered_len, nread;
|
||||
const char *buffered;
|
||||
|
||||
assert((conn->content_len == -1 && conn->consumed_content == 0) ||
|
||||
conn->consumed_content <= conn->content_len);
|
||||
DEBUG_TRACE(("%p %zu %" PRId64 " %" PRId64, buf, len,
|
||||
conn->content_len, conn->consumed_content));
|
||||
nread = 0;
|
||||
if (conn->consumed_content < conn->content_len) {
|
||||
|
||||
// Adjust number of bytes to read.
|
||||
int64_t to_read = conn->content_len - conn->consumed_content;
|
||||
if (to_read < (int64_t) len) {
|
||||
len = (int) to_read;
|
||||
}
|
||||
|
||||
// How many bytes of data we have buffered in the request buffer?
|
||||
buffered = conn->buf + conn->request_len + conn->consumed_content;
|
||||
buffered_len = conn->data_len - conn->request_len;
|
||||
assert(buffered_len >= 0);
|
||||
|
||||
// Return buffered data back if we haven't done that yet.
|
||||
if (conn->consumed_content < (int64_t) buffered_len) {
|
||||
buffered_len -= (int) conn->consumed_content;
|
||||
if (len < (size_t) buffered_len) {
|
||||
buffered_len = len;
|
||||
}
|
||||
memcpy(buf, buffered, (size_t)buffered_len);
|
||||
len -= buffered_len;
|
||||
buf = (char *) buf + buffered_len;
|
||||
conn->consumed_content += buffered_len;
|
||||
nread = buffered_len;
|
||||
}
|
||||
|
||||
// We have returned all buffered data. Read new data from the remote socket.
|
||||
while (len > 0) {
|
||||
n = pull(conn->client.sock, (char *) buf, (int) len);
|
||||
if (n <= 0) {
|
||||
break;
|
||||
}
|
||||
buf = (char *) buf + n;
|
||||
conn->consumed_content += n;
|
||||
nread += n;
|
||||
len -= n;
|
||||
}
|
||||
}
|
||||
return nread;
|
||||
}
|
||||
|
||||
int mg_write(struct mg_connection *conn, const void *buf, size_t len) {
|
||||
return (int) push(NULL, conn->client.sock, (const char *) buf, (int64_t) len);
|
||||
}
|
||||
|
||||
int mg_printf(struct mg_connection *conn, const char *fmt, ...) {
|
||||
char buf[BUFSIZ];
|
||||
int len;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
len = mg_vsnprintf(conn, buf, sizeof(buf), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
return mg_write(conn, buf, (size_t)len);
|
||||
}
|
||||
|
||||
// URL-decode input buffer into destination buffer.
|
||||
// 0-terminate the destination buffer. Return the length of decoded data.
|
||||
// form-url-encoded data differs from URI encoding in a way that it
|
||||
// uses '+' as character for space, see RFC 1866 section 8.2.1
|
||||
// http://ftp.ics.uci.edu/pub/ietf/html/rfc1866.txt
|
||||
static size_t url_decode(const char *src, size_t src_len, char *dst,
|
||||
size_t dst_len, int is_form_url_encoded) {
|
||||
size_t i, j;
|
||||
int a, b;
|
||||
#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] == '%' &&
|
||||
isxdigit(* (const unsigned char *) (src + i + 1)) &&
|
||||
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));
|
||||
i += 2;
|
||||
} else if (is_form_url_encoded && src[i] == '+') {
|
||||
dst[j] = ' ';
|
||||
} else {
|
||||
dst[j] = src[i];
|
||||
}
|
||||
}
|
||||
|
||||
dst[j] = '\0'; /* Null-terminate the destination */
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
// Check whether full request is buffered. Return:
|
||||
// -1 if request is malformed
|
||||
// 0 if request is not yet fully buffered
|
||||
// >0 actual request length, including last \r\n\r\n
|
||||
static int get_request_len(const char *buf, int buflen) {
|
||||
const char *s, *e;
|
||||
int len = 0;
|
||||
|
||||
DEBUG_TRACE(("buf: %p, len: %d", buf, buflen));
|
||||
for (s = buf, e = s + buflen - 1; len <= 0 && s < e; s++)
|
||||
// Control characters are not allowed but >=128 is.
|
||||
if (!isprint(* (const unsigned char *) s) && *s != '\r' &&
|
||||
*s != '\n' && * (const unsigned char *) s < 128) {
|
||||
len = -1;
|
||||
} else if (s[0] == '\n' && s[1] == '\n') {
|
||||
len = (int) (s - buf) + 2;
|
||||
} else if (s[0] == '\n' && &s[1] < e &&
|
||||
s[1] == '\r' && s[2] == '\n') {
|
||||
len = (int) (s - buf) + 3;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
// Protect against directory disclosure attack by removing '..',
|
||||
// excessive '/' and '\' characters
|
||||
static void remove_double_dots_and_double_slashes(char *s) {
|
||||
char *p = s;
|
||||
|
||||
while (*s != '\0') {
|
||||
*p++ = *s++;
|
||||
if (s[-1] == '/' || s[-1] == '\\') {
|
||||
// Skip all following slashes and backslashes
|
||||
while (*s == '/' || *s == '\\') {
|
||||
s++;
|
||||
}
|
||||
|
||||
// Skip all double-dots
|
||||
while (*s == '.' && s[1] == '.') {
|
||||
s += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
*p = '\0';
|
||||
}
|
||||
|
||||
// Parse HTTP headers from the given buffer, advance buffer to the point
|
||||
// where parsing stopped.
|
||||
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].value = skip(buf, "\r\n");
|
||||
if (ri->http_headers[i].name[0] == '\0')
|
||||
break;
|
||||
ri->num_headers = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int is_valid_http_method(const char *method) {
|
||||
fprintf(stderr, "Received HTTP method %s\n", method);
|
||||
return !strcmp(method, "GET") || !strcmp(method, "POST") ||
|
||||
!strcmp(method, "DELETE") || !strcmp(method, "OPTIONS");
|
||||
}
|
||||
|
||||
// Parse HTTP request, fill in mg_request_info structure.
|
||||
static int parse_http_request(char *buf, struct mg_request_info *ri) {
|
||||
int status = 0;
|
||||
|
||||
// RFC says that all initial whitespaces should be ingored
|
||||
while (*buf != '\0' && isspace(* (unsigned char *) buf)) {
|
||||
buf++;
|
||||
}
|
||||
|
||||
ri->request_method = skip(&buf, " ");
|
||||
ri->uri = skip(&buf, " ");
|
||||
ri->http_version = skip(&buf, "\r\n");
|
||||
|
||||
if (is_valid_http_method(ri->request_method) &&
|
||||
strncmp(ri->http_version, "HTTP/", 5) == 0) {
|
||||
ri->http_version += 5; /* Skip "HTTP/" */
|
||||
parse_http_headers(&buf, ri);
|
||||
status = 1;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
// Keep reading the input from socket sock
|
||||
// into buffer buf, until \r\n\r\n appears in the buffer (which marks the end
|
||||
// of HTTP request). Buffer buf may already have some data. The length of the
|
||||
// data is stored in nread. Upon every read operation, increase nread by the
|
||||
// number of bytes read.
|
||||
static int read_request(SOCKET sock, char *buf, int bufsiz,
|
||||
int *nread) {
|
||||
int n, request_len;
|
||||
|
||||
request_len = 0;
|
||||
while (*nread < bufsiz && request_len == 0) {
|
||||
n = pull(sock, buf + *nread, bufsiz - *nread);
|
||||
if (n <= 0) {
|
||||
break;
|
||||
} else {
|
||||
*nread += n;
|
||||
request_len = get_request_len(buf, *nread);
|
||||
}
|
||||
}
|
||||
|
||||
return request_len;
|
||||
}
|
||||
|
||||
// This is the heart of the Mongoose's logic.
|
||||
// This function is called when the request is read, parsed and validated,
|
||||
// and Mongoose must decide what action to take: serve a file, or
|
||||
// a directory, or call embedded function, etcetera.
|
||||
static void handle_request(struct mg_connection *conn) {
|
||||
struct mg_request_info *ri = &conn->request_info;
|
||||
int uri_len;
|
||||
|
||||
if ((conn->request_info.query_string = strchr(ri->uri, '?')) != NULL) {
|
||||
* conn->request_info.query_string++ = '\0';
|
||||
}
|
||||
uri_len = strlen(ri->uri);
|
||||
(void) url_decode(ri->uri, (size_t)uri_len, ri->uri, (size_t)(uri_len + 1), 0);
|
||||
remove_double_dots_and_double_slashes(ri->uri);
|
||||
|
||||
DEBUG_TRACE(("%s", ri->uri));
|
||||
if (call_user(conn, MG_NEW_REQUEST) == NULL) {
|
||||
mg_send_http_error(conn, 404, "Not Found", "%s", "File not found");
|
||||
}
|
||||
}
|
||||
|
||||
static void close_all_listening_sockets(struct mg_context *ctx) {
|
||||
(void) close(ctx->local_socket);
|
||||
}
|
||||
|
||||
// only reports address of the first listening socket
|
||||
int mg_get_listen_addr(struct mg_context *ctx,
|
||||
struct sockaddr *addr, socklen_t *addrlen) {
|
||||
size_t len = sizeof(ctx->local_address);
|
||||
if (*addrlen < len) return 0;
|
||||
*addrlen = len;
|
||||
memcpy(addr, &ctx->local_address, len);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int set_ports_option(struct mg_context *ctx, int port) {
|
||||
int reuseaddr = 1, success = 1;
|
||||
socklen_t sock_len = sizeof(ctx->local_address);
|
||||
// MacOS needs that. If we do not zero it, subsequent bind() will fail.
|
||||
memset(&ctx->local_address, 0, sock_len);
|
||||
ctx->local_address.sin_family = AF_INET;
|
||||
ctx->local_address.sin_port = htons((uint16_t) port);
|
||||
ctx->local_address.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
|
||||
struct timeval tv;
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 500 * 1000;
|
||||
|
||||
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 ||
|
||||
bind(ctx->local_socket, (const struct sockaddr *) &ctx->local_address, sock_len) != 0 ||
|
||||
// TODO(steineldar): Replace 20 (max socket backlog len in connections).
|
||||
listen(ctx->local_socket, 20) != 0) {
|
||||
close(ctx->local_socket);
|
||||
cry(fc(ctx), "%s: cannot bind to port %d: %s", __func__,
|
||||
port, strerror(ERRNO));
|
||||
success = 0;
|
||||
} else if (getsockname(ctx->local_socket, (struct sockaddr *) &ctx->local_address, &sock_len)) {
|
||||
close(ctx->local_socket);
|
||||
cry(fc(ctx), "%s: %s", __func__, strerror(ERRNO));
|
||||
success = 0;
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
ctx->local_socket = INVALID_SOCKET;
|
||||
close_all_listening_sockets(ctx);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
static void reset_per_request_attributes(struct mg_connection *conn) {
|
||||
struct mg_request_info *ri = &conn->request_info;
|
||||
|
||||
ri->request_method = ri->uri = ri->http_version = NULL;
|
||||
ri->num_headers = 0;
|
||||
ri->status_code = -1;
|
||||
|
||||
conn->num_bytes_sent = conn->consumed_content = 0;
|
||||
conn->content_len = -1;
|
||||
conn->request_len = conn->data_len = 0;
|
||||
}
|
||||
|
||||
static void close_socket_gracefully(SOCKET sock) {
|
||||
char buf[BUFSIZ];
|
||||
int n;
|
||||
|
||||
// Send FIN to the client
|
||||
(void) shutdown(sock, SHUT_WR);
|
||||
set_non_blocking_mode(sock);
|
||||
|
||||
// Read and discard pending data. If we do not do that and close the
|
||||
// socket, the data in the send buffer may be discarded. This
|
||||
// behaviour is seen on Windows, when client keeps sending data
|
||||
// when server decide to close the connection; then when client
|
||||
// does recv() it gets no data back.
|
||||
do {
|
||||
n = pull(sock, buf, sizeof(buf));
|
||||
} while (n > 0);
|
||||
|
||||
// Now we know that our FIN is ACK-ed, safe to close
|
||||
(void) close(sock);
|
||||
}
|
||||
|
||||
static void close_connection(struct mg_connection *conn) {
|
||||
if (conn->client.sock != INVALID_SOCKET) {
|
||||
close_socket_gracefully(conn->client.sock);
|
||||
}
|
||||
}
|
||||
|
||||
static void discard_current_request_from_buffer(struct mg_connection *conn) {
|
||||
int buffered_len, body_len;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
struct mg_request_info *ri = &conn->request_info;
|
||||
const char *cl;
|
||||
|
||||
reset_per_request_attributes(conn);
|
||||
|
||||
// If next request is not pipelined, read it in
|
||||
if ((conn->request_len = get_request_len(conn->buf, conn->data_len)) == 0) {
|
||||
conn->request_len = read_request(conn->client.sock,
|
||||
conn->buf, conn->buf_size, &conn->data_len);
|
||||
}
|
||||
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;
|
||||
} if (conn->request_len <= 0) {
|
||||
return; // Remote end closed the connection
|
||||
}
|
||||
|
||||
// Nul-terminate the request cause 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
|
||||
mg_send_http_error(conn, 400, "Bad Request",
|
||||
"Cannot parse HTTP request: [%.*s]", conn->data_len, conn->buf);
|
||||
} else if (strcmp(ri->http_version, "1.0") && strcmp(ri->http_version, "1.1")) {
|
||||
// Request seems valid, but HTTP version is strange
|
||||
mg_send_http_error(conn, 505, "HTTP version not supported", "");
|
||||
} else {
|
||||
// Request is valid, handle it
|
||||
cl = get_header(ri, "Content-Length");
|
||||
conn->content_len = cl == NULL ? -1 : strtoll(cl, NULL, 10);
|
||||
conn->birth_time = time(NULL);
|
||||
handle_request(conn);
|
||||
discard_current_request_from_buffer(conn);
|
||||
}
|
||||
}
|
||||
|
||||
// Worker threads take accepted socket from the queue
|
||||
static int consume_socket(struct mg_context *ctx, struct socket *sp) {
|
||||
(void) pthread_mutex_lock(&ctx->mutex);
|
||||
DEBUG_TRACE(("going idle"));
|
||||
|
||||
// If the queue is empty, wait. We're idle at this point.
|
||||
while (ctx->sq_head == ctx->sq_tail && ctx->stop_flag == 0) {
|
||||
pthread_cond_wait(&ctx->sq_full, &ctx->mutex);
|
||||
}
|
||||
// Master thread could wake us up without putting a socket.
|
||||
// If this happens, it is time to exit.
|
||||
if (ctx->stop_flag) {
|
||||
(void) pthread_mutex_unlock(&ctx->mutex);
|
||||
return 0;
|
||||
}
|
||||
assert(ctx->sq_head > ctx->sq_tail);
|
||||
|
||||
// Copy socket from the queue and increment tail
|
||||
*sp = ctx->queue[ctx->sq_tail % ARRAY_SIZE(ctx->queue)];
|
||||
ctx->sq_tail++;
|
||||
DEBUG_TRACE(("grabbed socket %d, going busy", sp->sock));
|
||||
|
||||
// Wrap pointers if needed
|
||||
while (ctx->sq_tail > (int) ARRAY_SIZE(ctx->queue)) {
|
||||
ctx->sq_tail -= ARRAY_SIZE(ctx->queue);
|
||||
ctx->sq_head -= ARRAY_SIZE(ctx->queue);
|
||||
}
|
||||
|
||||
(void) pthread_cond_signal(&ctx->sq_empty);
|
||||
(void) pthread_mutex_unlock(&ctx->mutex);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static void worker_thread(struct mg_context *ctx) {
|
||||
struct mg_connection *conn;
|
||||
// This is the specified request size limit for DIAL requests. Note that
|
||||
// this will effectively make the request limit one byte *smaller* than the
|
||||
// required in the DIAL specification.
|
||||
int buf_size = MAX_REQUEST_SIZE;
|
||||
|
||||
pthread_setname_np( pthread_self(), __func__);
|
||||
conn = (struct mg_connection *) calloc(1, sizeof(*conn) + buf_size);
|
||||
conn->buf_size = buf_size;
|
||||
conn->buf = (char *) (conn + 1);
|
||||
assert(conn != NULL);
|
||||
|
||||
while (ctx->stop_flag == 0 && consume_socket(ctx, &conn->client)) {
|
||||
conn->birth_time = time(NULL);
|
||||
conn->ctx = ctx;
|
||||
|
||||
// Fill in IP, port info early so even if SSL setup below fails,
|
||||
// error handler would have the corresponding info.
|
||||
// Thanks to Johannes Winkelmann for the patch.
|
||||
memcpy(&conn->request_info.remote_addr,
|
||||
&conn->client.remote_addr, sizeof(conn->client.remote_addr));
|
||||
|
||||
// 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);
|
||||
|
||||
close_connection(conn);
|
||||
}
|
||||
free(conn);
|
||||
|
||||
// Signal master that we're done with connection and exiting
|
||||
(void) pthread_mutex_lock(&ctx->mutex);
|
||||
ctx->num_threads--;
|
||||
(void) pthread_cond_signal(&ctx->cond);
|
||||
assert(ctx->num_threads >= 0);
|
||||
(void) pthread_mutex_unlock(&ctx->mutex);
|
||||
|
||||
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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
assert(ctx->sq_head - ctx->sq_tail < (int) ARRAY_SIZE(ctx->queue));
|
||||
|
||||
// Copy socket to the queue and increment head
|
||||
ctx->queue[ctx->sq_head % ARRAY_SIZE(ctx->queue)] = *sp;
|
||||
ctx->sq_head++;
|
||||
DEBUG_TRACE(("queued socket %d", sp->sock));
|
||||
|
||||
(void) pthread_cond_signal(&ctx->sq_full);
|
||||
(void) pthread_mutex_unlock(&ctx->mutex);
|
||||
}
|
||||
|
||||
|
||||
static void master_thread(struct mg_context *ctx) {
|
||||
struct socket accepted;
|
||||
|
||||
pthread_setname_np( pthread_self(), __func__);
|
||||
socklen_t sock_len = sizeof(accepted.local_addr);
|
||||
memcpy(&accepted.local_addr, &ctx->local_address, sock_len);
|
||||
|
||||
while (ctx->stop_flag == 0) {
|
||||
memset(&accepted.remote_addr, 0, sock_len);
|
||||
|
||||
accepted.sock = accept(ctx->local_socket,
|
||||
(struct sockaddr *) &accepted.remote_addr, &sock_len);
|
||||
|
||||
if (accepted.sock != INVALID_SOCKET) {
|
||||
// Put accepted socket structure into the queue.
|
||||
DEBUG_TRACE(("accepted socket %d", accepted.sock));
|
||||
produce_socket(ctx, &accepted);
|
||||
}
|
||||
}
|
||||
DEBUG_TRACE(("stopping workers"));
|
||||
|
||||
// Stop signal received: somebody called mg_stop. Quit.
|
||||
close_all_listening_sockets(ctx);
|
||||
|
||||
// Wakeup workers that are waiting for connections to handle.
|
||||
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);
|
||||
}
|
||||
(void) pthread_mutex_unlock(&ctx->mutex);
|
||||
|
||||
// All threads exited, no sync is needed. Destroy mutex and condvars
|
||||
(void) pthread_mutex_destroy(&ctx->mutex);
|
||||
(void) pthread_cond_destroy(&ctx->cond);
|
||||
(void) pthread_cond_destroy(&ctx->sq_empty);
|
||||
(void) pthread_cond_destroy(&ctx->sq_full);
|
||||
|
||||
// Signal mg_stop() that we're done
|
||||
ctx->stop_flag = 2;
|
||||
|
||||
DEBUG_TRACE(("exiting"));
|
||||
}
|
||||
|
||||
static void free_context(struct mg_context *ctx) {
|
||||
// Deallocate context itself
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
void mg_stop(struct mg_context *ctx) {
|
||||
ctx->stop_flag = 1;
|
||||
|
||||
// Wait until mg_fini() stops
|
||||
while (ctx->stop_flag != 2) {
|
||||
// TODO(steineldar): Avoid busy waiting.
|
||||
(void) sleep(0);
|
||||
}
|
||||
free_context(ctx);
|
||||
}
|
||||
|
||||
struct mg_context *mg_start(mg_callback_t user_callback, void *user_data, int port) {
|
||||
struct mg_context *ctx;
|
||||
|
||||
// Allocate context and initialize reasonable general case defaults.
|
||||
// TODO(lsm): do proper error handling here.
|
||||
ctx = (struct mg_context *) calloc(1, sizeof(*ctx));
|
||||
ctx->user_callback = user_callback;
|
||||
ctx->user_data = user_data;
|
||||
|
||||
if (!set_ports_option(ctx, port)) {
|
||||
free_context(ctx);
|
||||
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);
|
||||
|
||||
// Start master (listening) thread
|
||||
start_thread(ctx, (mg_thread_func_t) master_thread, ctx);
|
||||
|
||||
// Start worker threads
|
||||
for (int i = 0; i < NUM_THREADS; i++) {
|
||||
if (start_thread(ctx, (mg_thread_func_t) worker_thread, ctx) != 0) {
|
||||
cry(fc(ctx), "Cannot start worker thread: %d", ERRNO);
|
||||
} else {
|
||||
ctx->num_threads++;
|
||||
}
|
||||
}
|
||||
|
||||
return ctx;
|
||||
}
|
||||
160
server/mongoose.h
Normal file
160
server/mongoose.h
Normal file
@@ -0,0 +1,160 @@
|
||||
// Copyright (c) 2004-2010 Sergey Lyubka
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
|
||||
// NOTE: This is a SEVERELY stripped down version of mongoose, which only
|
||||
// supports GET, POST and DELETE HTTP commands, no CGI, no file or directory
|
||||
// access, no ACLs or authentication, and no proxying and no SSL. HTTP Header
|
||||
// limit is 16 instead of 64, as it's not supposed to be called from standard
|
||||
// browsers. And most options are removed.
|
||||
|
||||
#ifndef MONGOOSE_HEADER_INCLUDED
|
||||
#define MONGOOSE_HEADER_INCLUDED
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif // __cplusplus
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/ip.h>
|
||||
|
||||
struct mg_context; // Handle for the HTTP service itself
|
||||
struct mg_connection; // Handle for the individual connection
|
||||
|
||||
|
||||
// This structure contains information about the HTTP request.
|
||||
struct mg_request_info {
|
||||
void *user_data; // User-defined pointer passed to mg_start()
|
||||
char *request_method; // "GET", "POST", etc
|
||||
char *uri; // URL-decoded URI
|
||||
char *http_version; // E.g. "1.0", "1.1"
|
||||
char *query_string; // \0 - terminated
|
||||
char *request_body; // \0 - terminated
|
||||
char *log_message; // Mongoose error log message
|
||||
struct sockaddr_in local_addr; // Our server's address for this connection
|
||||
struct sockaddr_in remote_addr; // The remote address for this connection
|
||||
int status_code; // HTTP reply status code
|
||||
int num_headers; // Number of headers
|
||||
struct mg_header {
|
||||
char *name; // HTTP header name
|
||||
char *value; // HTTP header value
|
||||
} http_headers[16]; // Maximum 16 headers
|
||||
};
|
||||
|
||||
// Various events on which user-defined function is called by Mongoose.
|
||||
enum mg_event {
|
||||
MG_NEW_REQUEST, // New HTTP request has arrived from the client
|
||||
MG_HTTP_ERROR, // HTTP error must be returned to the client
|
||||
MG_EVENT_LOG, // Mongoose logs an event, request_info.log_message
|
||||
};
|
||||
|
||||
// Prototype for the user-defined function. Mongoose calls this function
|
||||
// on every event mentioned above.
|
||||
//
|
||||
// Parameters:
|
||||
// event: which event has been triggered.
|
||||
// conn: opaque connection handler. Could be used to read, write data to the
|
||||
// client, etc. See functions below that accept "mg_connection *".
|
||||
// request_info: Information about HTTP request.
|
||||
//
|
||||
// Return:
|
||||
// If handler returns non-NULL, that means that handler has processed the
|
||||
// request by sending appropriate HTTP reply to the client. Mongoose treats
|
||||
// the request as served.
|
||||
// If callback returns NULL, that means that callback has not processed
|
||||
// the request. Handler must not send any data to the client in this case.
|
||||
// Mongoose proceeds with request handling as if nothing happened.
|
||||
typedef void * (*mg_callback_t)(enum mg_event event,
|
||||
struct mg_connection *conn,
|
||||
const struct mg_request_info *request_info);
|
||||
|
||||
|
||||
// Start web server.
|
||||
//
|
||||
// Parameters:
|
||||
// callback: user defined event handling function or NULL.
|
||||
//
|
||||
// Example:
|
||||
// struct mg_context *ctx = mg_start(&my_func, NULL);
|
||||
//
|
||||
// Please refer to http://code.google.com/p/mongoose/wiki/MongooseManual
|
||||
// for the list of valid option and their possible values.
|
||||
//
|
||||
// Return:
|
||||
// web server context, or NULL on error.
|
||||
struct mg_context *mg_start(mg_callback_t callback, void *user_data, int port);
|
||||
|
||||
|
||||
// Stop the web server.
|
||||
//
|
||||
// Must be called last, when an application wants to stop the web server and
|
||||
// release all associated resources. This function blocks until all Mongoose
|
||||
// threads are stopped. Context pointer becomes invalid.
|
||||
void mg_stop(struct mg_context *);
|
||||
|
||||
|
||||
// Send data to the client.
|
||||
int mg_write(struct mg_connection *, const void *buf, size_t len);
|
||||
|
||||
|
||||
// Send data to the browser using printf() semantics.
|
||||
//
|
||||
// Works exactly like mg_write(), but allows to do message formatting.
|
||||
// Note that mg_printf() uses internal buffer of size IO_BUF_SIZE
|
||||
// (8 Kb by default) as temporary message storage for formatting. Do not
|
||||
// print data that is bigger than that, otherwise it will be truncated.
|
||||
int mg_printf(struct mg_connection *, const char *fmt, ...);
|
||||
|
||||
|
||||
// Read data from the remote end, return number of bytes read.
|
||||
int mg_read(struct mg_connection *, void *buf, size_t len);
|
||||
|
||||
|
||||
// Get the value of particular HTTP header.
|
||||
//
|
||||
// This is a helper function. It traverses request_info->http_headers array,
|
||||
// and if the header is present in the array, returns its value. If it is
|
||||
// not present, NULL is returned.
|
||||
const char *mg_get_header(const struct mg_connection *, const char *name);
|
||||
|
||||
|
||||
// Return Mongoose version.
|
||||
const char *mg_version(void);
|
||||
|
||||
|
||||
// MD5 hash given strings.
|
||||
// Buffer 'buf' must be 33 bytes long. Varargs is a NULL terminated list of
|
||||
// asciiz strings. When function returns, buf will contain human-readable
|
||||
// MD5 hash. Example:
|
||||
// char buf[33];
|
||||
// mg_md5(buf, "aa", "bb", NULL);
|
||||
void mg_md5(char *buf, ...);
|
||||
|
||||
void mg_send_http_error(struct mg_connection *conn, int status,
|
||||
const char *reason, const char *fmt, ...);
|
||||
int mg_get_listen_addr(struct mg_context *ctx, struct sockaddr *addr,
|
||||
socklen_t *addrlen);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif // __cplusplus
|
||||
|
||||
#endif // MONGOOSE_HEADER_INCLUDED
|
||||
242
server/quick_ssdp.c
Normal file
242
server/quick_ssdp.c
Normal file
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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 <arpa/inet.h>
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
|
||||
// TODO: Partners should define this port
|
||||
#define SSDP_PORT (56790)
|
||||
|
||||
static int exit_ssdp = 0;
|
||||
void stop_ssdp()
|
||||
{
|
||||
exit_ssdp = 1;
|
||||
};
|
||||
|
||||
static char gBuf[4096];
|
||||
|
||||
// TODO: Partners should get the friendlyName from the system and insert here.
|
||||
// TODO: Partners should get the UUID from the system and insert here.
|
||||
static const char ddxml[] = ""
|
||||
"<?xml version=\"1.0\"?>"
|
||||
"<root"
|
||||
" xmlns=\"urn:schemas-upnp-org:device-1-0\""
|
||||
" xmlns:r=\"urn:restful-tv-org:schemas:upnp-dd\">"
|
||||
" <specVersion>"
|
||||
" <major>1</major>"
|
||||
" <minor>0</minor>"
|
||||
" </specVersion>"
|
||||
" <device>"
|
||||
" <deviceType>urn:schemas-upnp-org:device:tvdevice:1</deviceType>"
|
||||
" <friendlyName>%s</friendlyName>"
|
||||
" <manufacturer> </manufacturer>"
|
||||
" <modelName>%s</modelName>"
|
||||
" <UDN>uuid:%s</UDN>"
|
||||
" </device>"
|
||||
"</root>";
|
||||
|
||||
// TODO: Partners should get the UUID from the system and insert here.
|
||||
static const char ssdp_reply[] = "HTTP/1.1 200 OK\r\n"
|
||||
"LOCATION: http://%s:%d/dd.xml\r\n"
|
||||
"CACHE-CONTROL: max-age=1800\r\n"
|
||||
"EXT:\r\n"
|
||||
"BOOTID.UPNP.ORG: 1\r\n"
|
||||
"SERVER: Linux/2.6 UPnP/1.0 quick_ssdp/1.0\r\n"
|
||||
"ST: urn:dial-multiscreen-org:service:dial:1\r\n"
|
||||
"USN: uuid:%s::"
|
||||
"urn:dial-multiscreen-org:service:dial:1\r\n\r\n";
|
||||
|
||||
|
||||
static char ip_addr[INET_ADDRSTRLEN] = "127.0.0.1";
|
||||
static int dial_port = 0;
|
||||
static int my_port = 0;
|
||||
static char friendly_name[256];
|
||||
static char uuid[256];
|
||||
static char model_name[256];
|
||||
static struct mg_context *ctx;
|
||||
|
||||
static void *request_handler(enum mg_event event,
|
||||
struct mg_connection *conn,
|
||||
const struct mg_request_info *request_info) {
|
||||
if (event == MG_NEW_REQUEST) {
|
||||
if (!strcmp(request_info->uri, "/dd.xml") &&
|
||||
!strcmp(request_info->request_method, "GET")) {
|
||||
mg_printf(conn, "HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: application/xml\r\n"
|
||||
"Application-URL: http://%s:%d/apps/\r\n"
|
||||
"\r\n", ip_addr, dial_port);
|
||||
mg_printf(conn, ddxml, friendly_name, model_name, uuid);
|
||||
} else {
|
||||
mg_send_http_error(conn, 404, "Not Found", "Not Found");
|
||||
}
|
||||
return "done";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void get_local_address() {
|
||||
struct ifconf ifc;
|
||||
char buf[4096];
|
||||
int s, i;
|
||||
if (-1 == (s = socket(AF_INET, SOCK_DGRAM, 0))) {
|
||||
perror("socket");
|
||||
exit(1);
|
||||
}
|
||||
ifc.ifc_len = sizeof(buf);
|
||||
ifc.ifc_buf = buf;
|
||||
if (0 > ioctl(s, SIOCGIFCONF, &ifc)) {
|
||||
perror("SIOCGIFCONF");
|
||||
exit(1);
|
||||
}
|
||||
if (ifc.ifc_len == sizeof(buf)) {
|
||||
fprintf(stderr, "SIOCGIFCONF output too long");
|
||||
exit(1);
|
||||
}
|
||||
close(s);
|
||||
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));
|
||||
// exit if we found a non-loopback address
|
||||
if (strcmp("127.0.0.1", ip_addr)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_mcast() {
|
||||
int s, one = 1, bytes;
|
||||
socklen_t addrlen;
|
||||
struct sockaddr_in saddr;
|
||||
struct ip_mreq mreq;
|
||||
char send_buf[sizeof(ssdp_reply) + INET_ADDRSTRLEN + 256 + 256] = {0,};
|
||||
int send_size;
|
||||
|
||||
send_size = snprintf(send_buf, sizeof(send_buf), ssdp_reply, ip_addr, my_port, uuid);
|
||||
|
||||
if (-1 == (s = socket(AF_INET, SOCK_DGRAM, 0))) {
|
||||
perror("socket");
|
||||
exit(1);
|
||||
}
|
||||
if (-1 == setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) {
|
||||
perror("reuseaddr");
|
||||
exit(1);
|
||||
}
|
||||
saddr.sin_family = AF_INET;
|
||||
saddr.sin_addr.s_addr = inet_addr("239.255.255.250");
|
||||
saddr.sin_port = htons(1900);
|
||||
if (-1 == bind(s, (struct sockaddr *)&saddr, sizeof(saddr))) {
|
||||
perror("bind");
|
||||
exit(1);
|
||||
}
|
||||
mreq.imr_multiaddr.s_addr = inet_addr("239.255.255.250");
|
||||
mreq.imr_interface.s_addr = inet_addr(ip_addr);
|
||||
if (-1 == setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP,
|
||||
&mreq, sizeof(mreq))) {
|
||||
perror("add_membership");
|
||||
exit(1);
|
||||
}
|
||||
//printf("Starting Multicast handling on 239.255.255.250\n");
|
||||
while (!exit_ssdp) {
|
||||
addrlen = sizeof(saddr);
|
||||
if (-1 == (bytes = recvfrom(s, gBuf, sizeof(gBuf) - 1, 0,
|
||||
(struct sockaddr *)&saddr, &addrlen))) {
|
||||
perror("recvfrom");
|
||||
continue;
|
||||
}
|
||||
gBuf[bytes] = 0;
|
||||
|
||||
// sophisticated SSDP parsing algorithm
|
||||
if (!strstr(gBuf, "urn:dial-multiscreen-org:service:dial:1"))
|
||||
{
|
||||
#if 0 // use for debugging
|
||||
printf("Dropping: \n");
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < bytes; i++)
|
||||
{
|
||||
putchar(gBuf[i]);
|
||||
}
|
||||
}
|
||||
printf("\n##### End of DROP #######\n");
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
printf("Sending SSDP reply to %s:%d\n",
|
||||
inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port));
|
||||
if (-1 == sendto(s, send_buf, send_size, 0, (struct sockaddr *)&saddr, addrlen)) {
|
||||
perror("sendto");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
close(s);
|
||||
exit_ssdp = 0;
|
||||
}
|
||||
|
||||
void run_ssdp(int port, const char *pFriendlyName, const char * pModelName, const char *pUuid) {
|
||||
struct sockaddr sa;
|
||||
socklen_t len = sizeof(sa);
|
||||
|
||||
if(pFriendlyName) {
|
||||
strncpy(friendly_name, pFriendlyName, sizeof(friendly_name));
|
||||
friendly_name[255] = '\0';
|
||||
} else {
|
||||
strcpy(friendly_name, "DIAL server sample");
|
||||
}
|
||||
if(pModelName) {
|
||||
strncpy(model_name, pModelName, sizeof(model_name));
|
||||
uuid[255] = '\0';
|
||||
} else {
|
||||
strcpy(model_name, "deadbeef-dead-beef-dead-beefdeadbeef");
|
||||
}
|
||||
if(pUuid) {
|
||||
strncpy(uuid, pUuid, sizeof(uuid));
|
||||
uuid[255] = '\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);
|
||||
}
|
||||
|
||||
printf("SSDP listening on %s:%d\n", ip_addr, my_port);
|
||||
handle_mcast();
|
||||
mg_stop(ctx);
|
||||
}
|
||||
18
server/tests/makefile
Normal file
18
server/tests/makefile
Normal file
@@ -0,0 +1,18 @@
|
||||
CC=$(TARGET)gcc
|
||||
|
||||
.PHONY: clean
|
||||
.DEFAULT_GOAL=test
|
||||
|
||||
OBJS := test_dial_data.o test_url_lib.o ../url_lib.o ../dial_data.o run_tests.o
|
||||
HEADERS := $(wildcard ../*.h)
|
||||
|
||||
%.c: $(HEADERS)
|
||||
|
||||
%.o: %.c $(HEADERS)
|
||||
$(CC) -Wall -Werror -g -std=gnu99 $(CFLAGS) -c $*.c -o $*.o
|
||||
|
||||
test: $(OBJS)
|
||||
$(CC) -Wall -Werror -g $(OBJS) -ldl -lpthread -o run_tests
|
||||
|
||||
clean:
|
||||
rm -f *.o run_tests
|
||||
42
server/tests/run_tests.c
Normal file
42
server/tests/run_tests.c
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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 "test_dial_data.h"
|
||||
#include "test_url_lib.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
printf("====\n");
|
||||
test_smartstrcat();
|
||||
test_urldecode();
|
||||
test_parse_app_name();
|
||||
test_parse_params();
|
||||
printf("====\n");
|
||||
test_read_dial_data();
|
||||
test_write_dial_data();
|
||||
printf("====\n");
|
||||
return 0;
|
||||
}
|
||||
53
server/tests/test.h
Normal file
53
server/tests/test.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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.
|
||||
*/
|
||||
// Macros to simplify testing functions.
|
||||
|
||||
#ifndef SRC_SERVER_TESTS_TEST_H_
|
||||
#define SRC_SERVER_TESTS_TEST_H_
|
||||
|
||||
#define EXPECT(a, m) \
|
||||
do { \
|
||||
if (!a) { \
|
||||
printf("[%s] failed: %s\n", #a, m); \
|
||||
printf("%s -> FAILED\n", __func__); \
|
||||
return; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define EXPECT_STREQ(a, b) \
|
||||
do { \
|
||||
if (strcmp(a, b)) { \
|
||||
printf("expected [%s == %s]\n", #a, #b); \
|
||||
printf(" a = \"%s\"\n", a); \
|
||||
printf(" b = \"%s\"\n", b); \
|
||||
printf("%s -> FAILED\n", __func__); \
|
||||
return; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define DONE() \
|
||||
printf("%s -> OK\n", __func__)
|
||||
|
||||
#endif /* SRC_SERVER_TESTS_TEST_H_ */
|
||||
57
server/tests/test_cors.html
Normal file
57
server/tests/test_cors.html
Normal file
@@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
To change this license header, choose License Headers in Project Properties.
|
||||
To change this template file, choose Tools | Templates
|
||||
and open the template in the editor.
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title>DIAL Security Test CORS</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script type="text/javascript" src="http://code.jquery.com/jquery-1.11.2.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
|
||||
var makePost=function(app){
|
||||
console.log("make dial post...");
|
||||
var ip = $("#ipAddress").val();
|
||||
var port = $("#dialPort").val();
|
||||
var urlStr = "http://"+ip+":"+port+"/apps/"+app;
|
||||
console.log(urlStr);
|
||||
$("#status").text("posted to "+urlStr);
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: urlStr,
|
||||
data: "v=QH2-TGUlwu4",
|
||||
dataType: "text/plain; charset=\"utf-8\""
|
||||
});
|
||||
};
|
||||
|
||||
$( "#testNetflix" ).click(function(){
|
||||
console.log("testing Netflix");
|
||||
makePost("Netflix");
|
||||
});
|
||||
|
||||
$( "#testYoutube" ).click(function(){
|
||||
console.log("testing Youtube");
|
||||
makePost("Youtube");
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<input id="ipAddress" type="text" name="ip" value="192.168.1.100">
|
||||
<input id="dialPort" type="text" name="port" value="8060">
|
||||
<p>
|
||||
<button id="testNetflix">Test Netflix</button>
|
||||
<button id="testYoutube">Test Youtube</button>
|
||||
</p>
|
||||
<p>
|
||||
<div id="status"></div>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
71
server/tests/test_cors.sh
Executable file
71
server/tests/test_cors.sh
Executable file
@@ -0,0 +1,71 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "usage: `basename $0`: <DIAL server ip address> <port> "
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ip_address=$1
|
||||
port=$2
|
||||
|
||||
#Testing all the positive cases
|
||||
origins="http://www4.netflix.com http://1.netflix.com https://www.netflix.com https://www4.netflix.com ftp://this.is.fine"
|
||||
for origin in $origins; do
|
||||
curl --fail --silent --header "Origin:$origin" --data "v=QH2-TGUlwu4" http://$ip_address:$port/apps/Netflix || echo "failed: $origin should be accepted"
|
||||
curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/Netflix || echo "failed: $origin should be accepted"
|
||||
curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/Netflix/run || echo "failed: $origin should be accepted"
|
||||
if [ $ip_address == "localhost" ];
|
||||
then
|
||||
echo "testing dial_data OPTIONS on $ip_address from origin $origin"
|
||||
curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/Netflix/dial_data || echo "failed: $origin should be accepted"
|
||||
fi
|
||||
done
|
||||
|
||||
origins="http://www4.youtube.com http://1.youtube.com https://www.youtube.com https://www4.youtube.com ftp://this.is.fine"
|
||||
for origin in $origins; do
|
||||
curl --fail --silent --header "Origin:$origin" --data "v=QH2-TGUlwu4" http://$ip_address:$port/apps/YouTube || echo "failed: $origin should be accepted"
|
||||
curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/YouTube || echo "failed: $origin should be accepted"
|
||||
curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/YouTube/run || echo "failed: $origin should be accepted"
|
||||
if [ $ip_address == "localhost" ];
|
||||
then
|
||||
echo "testing dial_data OPTIONS on $ip_address from origin $origin"
|
||||
curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/YouTube/dial_data || echo "failed: $origin should be accepted"
|
||||
fi
|
||||
done
|
||||
|
||||
#Testing all the negative cases
|
||||
origins="http://www.netflix-a.com http://www.netflix.com4 http://a-netflix.com https://ww.netflix-a.com https://www.netflix.com4 https://a-netflix.com http://netflix.com http://www.attack.com https://www.attack.com file://www.attack.com"
|
||||
for origin in $origins; do
|
||||
curl --fail --silent --header "Origin:$origin" --data "v=QH2-TGUlwu4" http://$ip_address:$port/apps/Netflix && echo "failed: $origin should be rejected"
|
||||
curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/Netflix && echo "failed: $origin should be rejected"
|
||||
curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/Netflix/run && echo "failed: $origin should be rejected"
|
||||
if [ $ip_address == "localhost" ];
|
||||
then
|
||||
echo "testing dial_data OPTIONS on $ip_address from origin $origin"
|
||||
curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/Netflix/dial_data && echo "failed: $origin should be rejected"
|
||||
fi
|
||||
done
|
||||
|
||||
origins="http://www.youtube-a.com http://www.youtube.com4 http://a-youtube.com https://ww.youtube-a.com https://www.youtube.com4 https://a-youtube.com http://youtube.com https://youtube.com http://www.attack.com https://www.attack.com file://www.attack.com"
|
||||
for origin in $origins; do
|
||||
curl --fail --silent --header "Origin:$origin" --data "v=QH2-TGUlwu4" http://$ip_address:$port/apps/YouTube && echo "failed: $origin should be rejected"
|
||||
curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/YouTube && echo "failed: $origin should be rejected"
|
||||
curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/YouTube/run && echo "failed: $origin should be rejected"
|
||||
if [ $ip_address == "localhost" ];
|
||||
then
|
||||
echo "testing dial_data OPTIONS on $ip_address from origin $origin"
|
||||
curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/YouTube/dial_data && echo "failed: $origin should be rejected"
|
||||
fi
|
||||
done
|
||||
|
||||
#Finally test with no header
|
||||
curl --fail --silent --data "v=QH2-TGUlwu4" http://$ip_address:$port/apps/YouTube || echo "failed: request without an Origin should be accepted"
|
||||
curl --fail --silent -X OPTIONS http://$ip_address:$port/apps/YouTube || echo "failed: request without an Origin should be accepted"
|
||||
curl --fail --silent -X OPTIONS http://$ip_address:$port/apps/YouTube/run || echo "failed: request without an Origin should be accepted"
|
||||
if [ $ip_address == "localhost" ];
|
||||
then
|
||||
echo "testing dial_data OPTIONS on $ip_address with no origin"
|
||||
curl --fail --silent -X OPTIONS http://$ip_address:$port/apps/YouTube/dial_data || echo "failed: request without an Origin should be accepted"
|
||||
fi
|
||||
|
||||
echo "Done."
|
||||
66
server/tests/test_dial_data.c
Normal file
66
server/tests/test_dial_data.c
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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 "../dial_data.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "test.h"
|
||||
|
||||
int key_value_pairs = 3;
|
||||
char *keys[] = {"key1", "key2", "key3"};
|
||||
char *values[] = {"value1", "value2", "value3"};
|
||||
|
||||
void test_read_dial_data() {
|
||||
DIALData *data = retrieve_dial_data("dial_data");
|
||||
for (int i = 0; data != NULL; data = data->next, i++) {
|
||||
EXPECT_STREQ(data->key, keys[2 - i]);
|
||||
EXPECT_STREQ(data->value, values[2 - i]);
|
||||
}
|
||||
DONE();
|
||||
}
|
||||
|
||||
void test_write_dial_data() {
|
||||
DIALData *result = NULL;
|
||||
for (int i = 0; i < key_value_pairs; ++i) {
|
||||
DIALData *node = (DIALData *) malloc(sizeof(DIALData));
|
||||
node->key = keys[i];
|
||||
node->value = values[i];
|
||||
node->next = result;
|
||||
result = node;
|
||||
}
|
||||
store_dial_data("YouTube", result);
|
||||
|
||||
DIALData *readBack = retrieve_dial_data("YouTube");
|
||||
|
||||
for (int i = 0; readBack != NULL; readBack = readBack->next, i++) {
|
||||
EXPECT_STREQ(readBack->key, keys[i]);
|
||||
EXPECT_STREQ(readBack->value, values[i]);
|
||||
}
|
||||
|
||||
DONE();
|
||||
}
|
||||
31
server/tests/test_dial_data.h
Normal file
31
server/tests/test_dial_data.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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_DIAL_DATA_H_
|
||||
#define SRC_SERVER_TESTS_TEST_DIAL_DATA_H_
|
||||
|
||||
void test_read_dial_data();
|
||||
void test_write_dial_data();
|
||||
|
||||
#endif /* SRC_SERVER_TESTS_TEST_DIAL_DATA_H_ */
|
||||
113
server/tests/test_url_lib.c
Normal file
113
server/tests/test_url_lib.c
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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 "../url_lib.h"
|
||||
#include "../dial_data.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include "test.h"
|
||||
|
||||
void test_smartstrcat() {
|
||||
char* src1 = "Hello ";
|
||||
char* src2 = "world!";
|
||||
char* src3 = " Trunc ated";
|
||||
char dest[128] = {0, };
|
||||
|
||||
char* p = (char *) dest;
|
||||
p = smartstrcat(p, src1, 128);
|
||||
EXPECT_STREQ(dest, "Hello ");
|
||||
p = smartstrcat(p, src2, dest + 128 - p);
|
||||
|
||||
EXPECT_STREQ(dest, "Hello world!");
|
||||
|
||||
p = smartstrcat(p, src3, 6);
|
||||
EXPECT_STREQ(dest, "Hello world! Trunc");
|
||||
|
||||
DONE();
|
||||
}
|
||||
|
||||
void test_urldecode() {
|
||||
char* param = "%26bla+r";
|
||||
char dest[128] = {0, };
|
||||
|
||||
EXPECT(urldecode(dest, param, 128), "Failed to decode.");
|
||||
EXPECT_STREQ(dest, "&bla r");
|
||||
DONE();
|
||||
}
|
||||
|
||||
void test_parse_app_name() {
|
||||
char *app_name;
|
||||
EXPECT((app_name = parse_app_name(NULL)), "Failed to extract app_name");
|
||||
EXPECT_STREQ(app_name, "unknown");
|
||||
EXPECT((app_name = parse_app_name("")), "Failed to extract app_name");
|
||||
EXPECT_STREQ(app_name, "unknown");
|
||||
EXPECT((app_name = parse_app_name("/")), "Failed to extract app_name");
|
||||
EXPECT_STREQ(app_name, "unknown");
|
||||
EXPECT((app_name = parse_app_name("/apps/YouTube/DialData")),
|
||||
"Failed to extract app_name");
|
||||
EXPECT_STREQ(app_name, "YouTube");
|
||||
EXPECT((app_name = parse_app_name("//")), "Failed to extract app_name");
|
||||
EXPECT_STREQ(app_name, "");
|
||||
EXPECT((app_name = parse_app_name("/invalid")),
|
||||
"Failed to extract app_name");
|
||||
EXPECT_STREQ(app_name, "unknown");
|
||||
|
||||
DONE();
|
||||
}
|
||||
|
||||
void test_parse_params() {
|
||||
EXPECT(!parse_params(""), "Empty query string should generate no params");
|
||||
EXPECT(!parse_params(NULL), "Null query, should generate no params");
|
||||
|
||||
DIALData *result = parse_params("a=b");
|
||||
EXPECT_STREQ(result->key, "a");
|
||||
EXPECT_STREQ(result->value, "b");
|
||||
|
||||
result = parse_params("?a=b");
|
||||
EXPECT_STREQ(result->key, "a");
|
||||
EXPECT_STREQ(result->value, "b");
|
||||
|
||||
result = parse_params("?a=b&c=d");
|
||||
EXPECT_STREQ(result->key, "c");
|
||||
EXPECT_STREQ(result->value, "d");
|
||||
EXPECT_STREQ(result->next->key, "a");
|
||||
EXPECT_STREQ(result->next->value, "b");
|
||||
|
||||
char query_string[1024] = {0, };
|
||||
char *current = query_string;
|
||||
for (int i = 0; i < 25; ++i) {
|
||||
current = smartstrcat(current, "a=b&", 256);
|
||||
}
|
||||
result = parse_params(query_string);
|
||||
int length = 0;
|
||||
for (; result != NULL; result = result->next) {
|
||||
length++;
|
||||
}
|
||||
EXPECT((length == 25), "25 params should have been parsed");
|
||||
|
||||
DONE();
|
||||
}
|
||||
34
server/tests/test_url_lib.h
Normal file
34
server/tests/test_url_lib.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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_URL_LIB_H_
|
||||
#define SRC_SERVER_TESTS_TEST_URL_LIB_H_
|
||||
|
||||
void test_smartstrcat();
|
||||
void test_urldecode();
|
||||
void test_parse_app_name();
|
||||
void test_parse_param();
|
||||
void test_parse_params();
|
||||
|
||||
#endif /* SRC_SERVER_TESTS_TEST_URL_LIB_H_ */
|
||||
183
server/url_lib.c
Normal file
183
server/url_lib.c
Normal file
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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 "url_lib.h"
|
||||
#include "dial_data.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
char* smartstrcat(char* dest, char* src, size_t max_chars) {
|
||||
size_t copied = 0;
|
||||
while ((*(dest++) = *(src++)) && copied++ < max_chars) {
|
||||
}
|
||||
// In case we over-stepped the size, end the string nicely.
|
||||
*(--dest) = '\0';
|
||||
return dest;
|
||||
}
|
||||
|
||||
static int append_char_from_hex(char* dest, char a, char b) {
|
||||
if ('a' <= a && a <= 'f')
|
||||
a = 10 + a - 'a';
|
||||
else if ('A' <= a && a <= 'F')
|
||||
a = 10 + a - 'A';
|
||||
else if ('0' <= a && a <= '9')
|
||||
a = a - '0';
|
||||
else
|
||||
return 0;
|
||||
|
||||
if ('a' <= b && b <= 'f')
|
||||
b = 10 + b - 'a';
|
||||
else if ('A' <= b && b <= 'F')
|
||||
b = 10 + b - 'A';
|
||||
else if ('0' <= b && b <= '9')
|
||||
b = b - '0';
|
||||
else
|
||||
return 0;
|
||||
|
||||
*dest = (char) (16 * a) + b;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int urldecode(char *dst, const char *src, size_t max_size) {
|
||||
size_t len = 0;
|
||||
|
||||
while (len < max_size && *src) {
|
||||
if (*src == '+') {
|
||||
*dst = ' ';
|
||||
} else if (*src == '%') {
|
||||
if (!*(++src) || !*(++src) || !append_char_from_hex(dst, *(src - 1), *src)) {
|
||||
*dst = '\0';
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
*dst = *src;
|
||||
}
|
||||
++dst;
|
||||
++src;
|
||||
++len;
|
||||
}
|
||||
*dst = '\0';
|
||||
return len;
|
||||
}
|
||||
|
||||
void xmlencode(char *dst, const char *src, size_t max_size) {
|
||||
size_t current_size = 0;
|
||||
while (*src && current_size < max_size) {
|
||||
switch (*src) {
|
||||
case '&':
|
||||
dst = smartstrcat(dst, "&", max_size - current_size);
|
||||
current_size += 5;
|
||||
break;
|
||||
case '\"':
|
||||
dst = smartstrcat(dst, """, max_size - current_size);
|
||||
current_size += 6;
|
||||
break;
|
||||
case '\'':
|
||||
dst = smartstrcat(dst, "'", max_size - current_size);
|
||||
current_size += 6;
|
||||
break;
|
||||
case '<':
|
||||
dst = smartstrcat(dst, "<", max_size - current_size);
|
||||
current_size += 4;
|
||||
break;
|
||||
case '>':
|
||||
dst = smartstrcat(dst, ">", max_size - current_size);
|
||||
current_size += 4;
|
||||
break;
|
||||
default:
|
||||
*dst++ = *src;
|
||||
current_size++;
|
||||
break;
|
||||
}
|
||||
src++;
|
||||
}
|
||||
*dst = '\0';
|
||||
}
|
||||
|
||||
char *parse_app_name(const char *uri) {
|
||||
if (uri == NULL) {
|
||||
return "unknown";
|
||||
}
|
||||
char *slash = strrchr(uri, '/');
|
||||
if (slash == NULL || slash == uri) {
|
||||
return "unknown";
|
||||
}
|
||||
char *begin = slash;
|
||||
while ((begin != uri) && (*--begin != '/'))
|
||||
;
|
||||
begin++; // skip the slash
|
||||
char *result = (char *) calloc(1, slash - begin+1);
|
||||
strncpy(result, begin, slash - begin);
|
||||
result[slash-begin]='\0';
|
||||
return result;
|
||||
}
|
||||
|
||||
char *parse_param(char *query_string, char *param_name) {
|
||||
if (query_string == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
char * start;
|
||||
if ((start = strstr(query_string, param_name)) == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
while (*start && (*start++ != '='))
|
||||
;
|
||||
char *end = start;
|
||||
while (*end && (*end != '&'))
|
||||
end++;
|
||||
int result_size = end - start;
|
||||
char *result = malloc(result_size + 1);
|
||||
result[0] = '\0';
|
||||
strncpy(result, start, result_size);
|
||||
result[result_size] = '\0';
|
||||
return result;
|
||||
}
|
||||
|
||||
DIALData *parse_params(char * query_string) {
|
||||
if (query_string == NULL || strlen(query_string) <= 2) {
|
||||
return NULL;
|
||||
}
|
||||
if (query_string[0] == '?') {
|
||||
query_string++; // skip leading question mark
|
||||
}
|
||||
DIALData *result = NULL;
|
||||
char *query_string_dup = strdup(query_string);
|
||||
char * name_value = strtok(query_string_dup, "&");
|
||||
while (name_value != NULL) {
|
||||
DIALData *tmp = (DIALData *) malloc(sizeof(DIALData));
|
||||
size_t name_value_length = strlen(name_value);
|
||||
tmp->key = (char *) malloc(name_value_length);
|
||||
tmp->value = (char *) malloc(name_value_length);
|
||||
sscanf(name_value, "%[^=]=%s", tmp->key, tmp->value);
|
||||
tmp->next = result;
|
||||
result = tmp;
|
||||
|
||||
name_value = strtok(NULL, "&"); // read next token
|
||||
}
|
||||
free(query_string_dup);
|
||||
return result;
|
||||
}
|
||||
61
server/url_lib.h
Normal file
61
server/url_lib.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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.
|
||||
*/
|
||||
/* Utility functions for dealing with URLs */
|
||||
|
||||
#ifndef URLLIB_H_
|
||||
#define URLLIB_H_
|
||||
|
||||
#include "dial_data.h"
|
||||
#include <stddef.h>
|
||||
|
||||
/**
|
||||
* Concatenate a maxim of max_chars characters from src into dest,
|
||||
* and return a pointer to the last character in dest.
|
||||
*/
|
||||
char* smartstrcat(char* dest, char* src, size_t max_chars);
|
||||
|
||||
int urldecode(char *dst, const char *src, size_t max_size);
|
||||
|
||||
void xmlencode(char *dst, const char *src, size_t max_size);
|
||||
|
||||
/**
|
||||
* Parse the value of the parameter with the given name from a query string.
|
||||
*/
|
||||
char *parse_param(char *query_string, char *param_name);
|
||||
|
||||
/*
|
||||
* Parse the application name out of a URI, such as /app/YouTube/dial_data.
|
||||
* Note: this parser assumes that the dial_data url is of the form
|
||||
* /apps/<app_name>/dial_data. If your DIAL server uses a different url-path,
|
||||
* you will need to adapt the method below.
|
||||
*/
|
||||
char *parse_app_name(const char *uri);
|
||||
|
||||
/*
|
||||
* Parse a list of DIALData params out of a query string.
|
||||
*/
|
||||
DIALData *parse_params(char * query_string);
|
||||
|
||||
#endif // URLLIB_H_
|
||||
Reference in New Issue
Block a user