summaryrefslogtreecommitdiffstats
path: root/lib/libUPnP/Neptune/Source/Apps/ZipHttpServer/ZipHttpServer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libUPnP/Neptune/Source/Apps/ZipHttpServer/ZipHttpServer.cpp')
-rw-r--r--lib/libUPnP/Neptune/Source/Apps/ZipHttpServer/ZipHttpServer.cpp534
1 files changed, 534 insertions, 0 deletions
diff --git a/lib/libUPnP/Neptune/Source/Apps/ZipHttpServer/ZipHttpServer.cpp b/lib/libUPnP/Neptune/Source/Apps/ZipHttpServer/ZipHttpServer.cpp
new file mode 100644
index 0000000..65343a1
--- /dev/null
+++ b/lib/libUPnP/Neptune/Source/Apps/ZipHttpServer/ZipHttpServer.cpp
@@ -0,0 +1,534 @@
+/*****************************************************************
+|
+| Virtual ZIP file HTTP Server
+|
+| (c) 2001-2014 Gilles Boccon-Gibod
+| Author: Gilles Boccon-Gibod (bok@bok.net)
+|
+ ****************************************************************/
+
+/*----------------------------------------------------------------------
+| includes
++---------------------------------------------------------------------*/
+#include "Neptune.h"
+
+/*----------------------------------------------------------------------
+| logging
++---------------------------------------------------------------------*/
+NPT_SET_LOCAL_LOGGER("neptune.ziphttpserver")
+
+/*----------------------------------------------------------------------
+| GetContentType
++---------------------------------------------------------------------*/
+struct FileTypeMapEntry {
+ const char* extension;
+ const char* mime_type;
+};
+static const FileTypeMapEntry
+DefaultFileTypeMap[] = {
+ {"xml", "text/xml" },
+ {"htm", "text/html" },
+ {"html", "text/html" },
+ {"c", "text/plain"},
+ {"h", "text/plain"},
+ {"txt", "text/plain"},
+ {"css", "text/css" },
+ {"gif", "image/gif" },
+ {"thm", "image/jpeg"},
+ {"png", "image/png"},
+ {"tif", "image/tiff"},
+ {"tiff", "image/tiff"},
+ {"jpg", "image/jpeg"},
+ {"jpeg", "image/jpeg"},
+ {"jpe", "image/jpeg"},
+ {"jp2", "image/jp2" },
+ {"png", "image/png" },
+ {"bmp", "image/bmp" },
+ {"aif", "audio/x-aiff"},
+ {"aifc", "audio/x-aiff"},
+ {"aiff", "audio/x-aiff"},
+ {"mpa", "audio/mpeg"},
+ {"mp2", "audio/mpeg"},
+ {"mp3", "audio/mpeg"},
+ {"m4a", "audio/mp4"},
+ {"wma", "audio/x-ms-wma"},
+ {"wav", "audio/x-wav"},
+ {"mpeg", "video/mpeg"},
+ {"mpg", "video/mpeg"},
+ {"mp4", "video/mp4"},
+ {"m4v", "video/mp4"},
+ {"m4f", "video/mp4"},
+ {"m4s", "video/mp4"},
+ {"ts", "video/MP2T"}, // RFC 3555
+ {"mov", "video/quicktime"},
+ {"wmv", "video/x-ms-wmv"},
+ {"asf", "video/x-ms-asf"},
+ {"avi", "video/x-msvideo"},
+ {"divx", "video/x-msvideo"},
+ {"xvid", "video/x-msvideo"},
+ {"doc", "application/msword"},
+ {"js", "application/javascript"},
+ {"m3u8", "application/x-mpegURL"},
+ {"pdf", "application/pdf"},
+ {"ps", "application/postscript"},
+ {"eps", "application/postscript"},
+ {"zip", "application/zip"},
+ {"mpd", "application/dash+xml"}
+};
+
+NPT_Map<NPT_String, NPT_String> FileTypeMap;
+
+static const char*
+GetContentType(const NPT_String& filename)
+{
+ int last_dot = filename.ReverseFind('.');
+ if (last_dot > 0) {
+ NPT_String extension = filename.GetChars()+last_dot+1;
+ extension.MakeLowercase();
+
+ NPT_String* mime_type;
+ if (NPT_SUCCEEDED(FileTypeMap.Get(extension, mime_type))) {
+ return mime_type->GetChars();
+ }
+ }
+
+ return "application/octet-stream";
+}
+
+/*----------------------------------------------------------------------
+| ZipRequestHandler
++---------------------------------------------------------------------*/
+class ZipRequestHandler : public NPT_HttpRequestHandler
+{
+public:
+ // constructors
+ ZipRequestHandler(const char* url_root,
+ const char* file_root) :
+ m_UrlRoot(url_root),
+ m_FileRoot(file_root) {}
+
+ // NPT_HttpRequestHandler methods
+ virtual NPT_Result SetupResponse(NPT_HttpRequest& request,
+ const NPT_HttpRequestContext& context,
+ NPT_HttpResponse& response);
+
+private:
+ NPT_String m_UrlRoot;
+ NPT_String m_FileRoot;
+};
+
+/*----------------------------------------------------------------------
+| ZipRequestHandler::SetupResponse
++---------------------------------------------------------------------*/
+NPT_Result
+ZipRequestHandler::SetupResponse(NPT_HttpRequest& request,
+ const NPT_HttpRequestContext& /*context*/,
+ NPT_HttpResponse& response)
+{
+ NPT_HttpEntity* entity = response.GetEntity();
+ if (entity == NULL) return NPT_ERROR_INVALID_STATE;
+
+ // check the method
+ if (request.GetMethod() != NPT_HTTP_METHOD_GET &&
+ request.GetMethod() != NPT_HTTP_METHOD_HEAD) {
+ response.SetStatus(405, "Method Not Allowed");
+ return NPT_SUCCESS;
+ }
+
+ // set some default headers
+ response.GetHeaders().SetHeader(NPT_HTTP_HEADER_ACCEPT_RANGES, "bytes");
+
+ // declare HTTP/1.1 if the client asked for it
+ if (request.GetProtocol() == NPT_HTTP_PROTOCOL_1_1) {
+ response.SetProtocol(NPT_HTTP_PROTOCOL_1_1);
+ }
+
+ // default status
+ response.SetStatus(404, "Not Found");
+
+ // check that the request's path is an entry under the url root
+ if (!request.GetUrl().GetPath().StartsWith(m_UrlRoot)) {
+ return NPT_ERROR_INVALID_PARAMETERS;
+ }
+
+ // compute the path relative to the URL root
+ NPT_String relative_path = NPT_Url::PercentDecode(request.GetUrl().GetPath().GetChars()+m_UrlRoot.GetLength());
+
+ // check that there is no '..' in the path, for security reasons
+ if (relative_path.Find("..") >= 0) {
+ NPT_LOG_INFO(".. in path is not supported");
+ return NPT_SUCCESS;
+ }
+ // check that the path does not end with a /
+ if (relative_path.EndsWith("/")) {
+ NPT_LOG_INFO("skipping paths that end in /");
+ return NPT_SUCCESS;
+ }
+ NPT_List<NPT_String> path_parts = relative_path.Split("/");
+
+ // walk down the path until we find a file
+ NPT_String path = m_FileRoot;
+ NPT_String subpath;
+ NPT_List<NPT_String>::Iterator fragment = path_parts.GetFirstItem();
+ bool anchor_found = false;
+ bool is_zip = false;
+ for (; fragment; ++fragment) {
+ if (!anchor_found) {
+ path += '/';
+ path += *fragment;
+
+ // get info about the file
+ NPT_FileInfo info;
+ NPT_File::GetInfo(path, &info);
+ if (info.m_Type == NPT_FileInfo::FILE_TYPE_DIRECTORY) {
+ continue;
+ } else if (info.m_Type == NPT_FileInfo::FILE_TYPE_REGULAR) {
+ anchor_found = true;
+ if (path.EndsWith(".zip", true)) {
+ // this is a zip file
+ is_zip = true;
+ }
+ } else {
+ return NPT_SUCCESS;
+ }
+ } else {
+ if (!subpath.IsEmpty()) {
+ subpath += '/';
+ }
+ subpath += *fragment;
+ }
+ }
+ NPT_LOG_FINE_3("is_zip=%d, path=%s, subpath=%s", (int)is_zip, path.GetChars(), subpath.GetChars());
+
+ // return now if no anchor was found
+ if (!anchor_found) {
+ return NPT_SUCCESS;
+ }
+
+ // deal with regular files
+ if (!is_zip) {
+ if (subpath.IsEmpty()) {
+ // open the file
+ NPT_File file(path);
+ NPT_Result result = file.Open(NPT_FILE_OPEN_MODE_READ);
+ if (NPT_FAILED(result)) {
+ NPT_LOG_FINE("file not found");
+ return NPT_SUCCESS;
+ }
+ NPT_InputStreamReference file_stream;
+ file.GetInputStream(file_stream);
+ entity->SetInputStream(file_stream, true);
+ entity->SetContentType(GetContentType(path));
+ response.SetStatus(200, "OK");
+ }
+ return NPT_SUCCESS;
+ }
+
+ // load the zip file
+ NPT_File file(path);
+ NPT_Result result = file.Open(NPT_FILE_OPEN_MODE_READ);
+ if (NPT_FAILED(result)) {
+ NPT_LOG_WARNING_1("failed to open file (%d)", result);
+ return result;
+ }
+ NPT_InputStreamReference zip_stream;
+ file.GetInputStream(zip_stream);
+ NPT_ZipFile* zip_file = NULL;
+ result = NPT_ZipFile::Parse(*zip_stream, zip_file);
+ if (NPT_FAILED(result)) {
+ NPT_LOG_WARNING_1("failed to parse zip file (%d)", result);
+ return result;
+ }
+
+ // look for the entry in the zip file
+ for (unsigned int i=0; i<zip_file->GetEntries().GetItemCount(); i++) {
+ NPT_ZipFile::Entry& entry = zip_file->GetEntries()[i];
+ if (subpath == entry.m_Name) {
+ // send the file
+ NPT_InputStream* file_stream = NULL;
+ result = NPT_ZipFile::GetInputStream(entry, zip_stream, file_stream);
+ if (NPT_FAILED(result)) {
+ NPT_LOG_WARNING_1("failed to get the file stream (%d)", result);
+ delete zip_file;
+ return result;
+ }
+ NPT_InputStreamReference file_stream_ref(file_stream);
+ entity->SetInputStream(file_stream_ref, true);
+ entity->SetContentType(GetContentType(subpath));
+ response.SetStatus(200, "OK");
+ break;
+ }
+ }
+
+ delete zip_file;
+ return NPT_SUCCESS;
+}
+
+/*----------------------------------------------------------------------
+| ZipHttpWorker
++---------------------------------------------------------------------*/
+class ZipHttpServer;
+class ZipHttpWorker : public NPT_Thread {
+public:
+ // types
+ enum {
+ IDLE,
+ RUNNING,
+ DEAD
+ } State;
+
+ // constructor
+ ZipHttpWorker(unsigned int id, ZipHttpServer* server) :
+ m_Id(id),
+ m_Server(server),
+ m_State(IDLE),
+ m_Verbose(false) {}
+
+ // NPT_Runnable methods
+ virtual void Run();
+ NPT_Result Respond();
+
+ // members
+ unsigned int m_Id;
+ ZipHttpServer* m_Server;
+ NPT_SharedVariable m_State;
+ NPT_InputStreamReference m_InputStream;
+ NPT_OutputStreamReference m_OutputStream;
+ NPT_HttpRequestContext m_Context;
+ bool m_Verbose;
+};
+
+/*----------------------------------------------------------------------
+| ZipHttpServer
++---------------------------------------------------------------------*/
+class ZipHttpServer : public NPT_HttpServer {
+public:
+ ZipHttpServer(const char* file_root,
+ const char* url_root,
+ unsigned int port,
+ unsigned int threads);
+
+ void Loop();
+ void OnWorkerDone(ZipHttpWorker* worker);
+
+private:
+ NPT_Mutex m_Lock;
+ unsigned int m_Threads;
+ ZipRequestHandler* m_Handler;
+ NPT_List<ZipHttpWorker*> m_Workers;
+ NPT_List<ZipHttpWorker*> m_ReadyWorkers;
+ NPT_SharedVariable m_AllWorkersBusy;
+};
+
+/*----------------------------------------------------------------------
+| ZipHttpServer::ZipHttpServer
++---------------------------------------------------------------------*/
+ZipHttpServer::ZipHttpServer(const char* file_root,
+ const char* url_root,
+ unsigned int port,
+ unsigned int threads) :
+ NPT_HttpServer(port),
+ m_Threads(threads),
+ m_AllWorkersBusy(0)
+{
+ m_Handler = new ZipRequestHandler(url_root, file_root);
+ AddRequestHandler(m_Handler, url_root, true);
+
+ for (unsigned int i=0; i<threads; i++) {
+ ZipHttpWorker* worker = new ZipHttpWorker(i, this);
+ m_Workers.Add(worker);
+ m_ReadyWorkers.Add(worker);
+
+ // start threads unless we're single threaded
+ if (threads > 1) {
+ worker->Start();
+ }
+ }
+}
+
+/*----------------------------------------------------------------------
+| ZipHttpServer::Loop
++---------------------------------------------------------------------*/
+void
+ZipHttpServer::Loop()
+{
+ for (;;) {
+ // wait until at least one worker is ready
+ if (m_AllWorkersBusy.GetValue() == 1) {
+ NPT_LOG_FINEST("all workers busy");
+ }
+ NPT_LOG_FINEST("waiting for a worker");
+ m_AllWorkersBusy.WaitUntilEquals(0);
+ NPT_LOG_FINEST("got a worker");
+
+ // pick a worker
+ m_Lock.Lock();
+ ZipHttpWorker* worker = NULL;
+ m_ReadyWorkers.PopHead(worker);
+ if (m_ReadyWorkers.GetItemCount() == 0) {
+ m_AllWorkersBusy.SetValue(1);
+ }
+ m_Lock.Unlock();
+
+ NPT_Result result = WaitForNewClient(worker->m_InputStream, worker->m_OutputStream, &worker->m_Context);
+ if (NPT_FAILED(result)) {
+ NPT_LOG_WARNING_1("WaitForNewClient returned %d", result);
+
+ // wait a bit before continuing
+ NPT_System::Sleep(NPT_TimeInterval(1.0));
+ }
+
+ if (m_Threads == 1) {
+ // single threaded
+ worker->Respond();
+ OnWorkerDone(worker);
+ } else {
+ worker->m_State.SetValue(ZipHttpWorker::RUNNING);
+ }
+ worker = NULL;
+ }
+
+}
+
+/*----------------------------------------------------------------------
+| ZipHttpWorker::OnWorkerDone
++---------------------------------------------------------------------*/
+void
+ZipHttpServer::OnWorkerDone(ZipHttpWorker* worker)
+{
+ NPT_LOG_FINEST_1("worker %d done", worker->m_Id);
+ m_Lock.Lock();
+ m_ReadyWorkers.Add(worker);
+ m_AllWorkersBusy.SetValue(0);
+ m_Lock.Unlock();
+}
+
+/*----------------------------------------------------------------------
+| ZipHttpWorker::Run
++---------------------------------------------------------------------*/
+void
+ZipHttpWorker::Run(void)
+{
+ NPT_LOG_FINE_1("worker %d started", m_Id);
+ for (;;) {
+ // wait while we're idle
+ NPT_LOG_FINER_1("worker %d waiting for work", m_Id);
+ m_State.WaitWhileEquals(IDLE);
+
+ NPT_LOG_FINER_1("worker %d woke up", m_Id);
+
+ if (m_State.GetValue() == DEAD) {
+ NPT_LOG_FINE_1("worker %d exiting", m_Id);
+ return;
+ }
+
+ // respond to the client
+ Respond();
+
+ // update our state
+ m_State.SetValue(IDLE);
+
+ // notify the server
+ m_Server->OnWorkerDone(this);
+ }
+}
+
+/*----------------------------------------------------------------------
+| ZipHttpWorker::Respond
++---------------------------------------------------------------------*/
+NPT_Result
+ZipHttpWorker::Respond()
+{
+ NPT_LOG_FINER_1("worker %d responding to request", m_Id);
+
+ NPT_Result result = m_Server->RespondToClient(m_InputStream, m_OutputStream, m_Context);
+
+ NPT_LOG_FINER_2("worker %d responded to request (%d)", m_Id, result);
+
+ m_InputStream = NULL;
+ m_OutputStream = NULL;
+
+ return result;
+}
+
+/*----------------------------------------------------------------------
+| main
++---------------------------------------------------------------------*/
+int
+main(int /*argc*/, char** argv)
+{
+ NPT_String file_root;
+ NPT_String url_root = "/";
+ unsigned int port = 8000;
+ unsigned int threads = 5;
+ bool verbose = false;
+
+ while (const char* arg = *++argv) {
+ if (NPT_StringsEqual(arg, "--help") ||
+ NPT_StringsEqual(arg, "-h")) {
+ NPT_Console::Output("usage: ziphttpserver [--file-root <dir>] [--url-root <path>] [--port <port>] [--threads <n>] [--verbose]\n");
+ return 0;
+ } else if (NPT_StringsEqual(arg, "--file-root")) {
+ arg = *++argv;
+ if (arg == NULL) {
+ NPT_Console::Output("ERROR: missing argument for --file-root option\n");
+ return 1;
+ }
+ file_root = arg;
+ } else if (NPT_StringsEqual(arg, "--url-root")) {
+ arg = *++argv;
+ if (arg == NULL) {
+ NPT_Console::Output("ERROR: missing argument for --url-root option\n");
+ return 1;
+ }
+ url_root = arg;
+ } else if (NPT_StringsEqual(arg, "--port")) {
+ arg = *++argv;
+ if (arg == NULL) {
+ NPT_Console::Output("ERROR: missing argument for --port option\n");
+ return 1;
+ }
+ NPT_ParseInteger(arg, port, true);
+ } else if (NPT_StringsEqual(arg, "--threads")) {
+ arg = *++argv;
+ if (arg == NULL) {
+ NPT_Console::Output("ERROR: missing argument for --threads option\n");
+ return 1;
+ }
+ NPT_ParseInteger(arg, threads, true);
+ } else if (NPT_StringsEqual(arg, "--verbose")) {
+ verbose = true;
+ }
+ }
+
+ // sanity check on some parameters
+ if (threads == 0 || threads > 20) {
+ fprintf(stderr, "ERROR: --threads must be between 1 and 20");
+ return 1;
+ }
+
+ // ensure the URL root start with a /
+ if (!url_root.StartsWith("/")) {
+ url_root = "/"+url_root;
+ }
+
+ // initialize the file type map
+ for (unsigned int i=0; i<NPT_ARRAY_SIZE(DefaultFileTypeMap); i++) {
+ FileTypeMap[DefaultFileTypeMap[i].extension] =DefaultFileTypeMap[i].mime_type;
+ }
+
+ if (file_root.GetLength() == 0) {
+ NPT_File::GetWorkingDir(file_root);
+ }
+
+ if (verbose) {
+ NPT_Console::OutputF("Starting server on port %d, file-root=%s, url-root=%s, threads=%d\n",
+ port, file_root.GetChars(), url_root.GetChars(), threads);
+ }
+
+ ZipHttpServer* server = new ZipHttpServer(file_root, url_root, port, threads);
+ server->Loop();
+ delete server;
+
+ return 0;
+}