[Libreoffice-commits] online.git: 6 commits - loleaflet/build loleaflet/debug loleaflet/dist loolwsd/Admin.cpp loolwsd/Admin.hpp loolwsd/Auth.hpp loolwsd/cert.pem loolwsd/Common.hpp loolwsd/configure.ac loolwsd/FileServer.hpp loolwsd/key.pem loolwsd/LOOLWSD.cpp loolwsd/LOOLWSD.hpp loolwsd/test

Pranav Kant pranavk at collabora.com
Mon Mar 21 18:38:26 UTC 2016


 loleaflet/build/build.js                     |    2 
 loleaflet/debug/document/adminAnalytics.html |   11 
 loleaflet/debug/document/adminSettings.html  |   12 
 loleaflet/dist/admin.html                    |  128 ----
 loleaflet/dist/admin/admin-src.js            |  710 +++++++++++++++++++++++++++
 loleaflet/dist/admin/admin.html              |  129 ++++
 loleaflet/dist/admin/adminAnalytics.html     |  100 +++
 loleaflet/dist/admin/adminSettings.html      |  107 ++++
 loleaflet/dist/loleaflet.html                |    4 
 loolwsd/Admin.cpp                            |  550 +++++++++++---------
 loolwsd/Admin.hpp                            |   26 
 loolwsd/Auth.hpp                             |    2 
 loolwsd/Common.hpp                           |    3 
 loolwsd/FileServer.hpp                       |  156 +++++
 loolwsd/LOOLWSD.cpp                          |  113 +++-
 loolwsd/LOOLWSD.hpp                          |    1 
 loolwsd/cert.pem                             |   24 
 loolwsd/configure.ac                         |    2 
 loolwsd/key.pem                              |   27 +
 loolwsd/test/httpposttest.cpp                |   24 
 loolwsd/test/httpwstest.cpp                  |   20 
 21 files changed, 1723 insertions(+), 428 deletions(-)

New commits:
commit 7f371958aefc228624a56403eafcdb376142c660
Author: Pranav Kant <pranavk at collabora.com>
Date:   Sun Mar 20 16:29:32 2016 +0530

    SSL everywhere
    
    Use same port (9989) for all client connections. This includes
    admin panel, static file serving and normal client websocket
    connections.
    
    Change-Id: Idcfd7dd8925523c36e884717c41a3b6a827f6ff3

diff --git a/loleaflet/debug/document/adminAnalytics.html b/loleaflet/debug/document/adminAnalytics.html
index 903be34..d066961 100644
--- a/loleaflet/debug/document/adminAnalytics.html
+++ b/loleaflet/debug/document/adminAnalytics.html
@@ -34,18 +34,11 @@
     <script>window.jQuery || document.write('<script src="../../dist/bootstrap/assets/js/vendor/jquery.min.js"><\/script>')</script>
     <script src="../../dist/dialog/vex.combined.min.js"></script>
     <script>vex.defaultOptions.className = 'vex-theme-plain';</script>
-    <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
+    <script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
     <script src="../../dist/admin-src.js"></script>
     <script>
 
-	function getParameterByName(name) {
-		name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
-		var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
-		results = regex.exec(location.search);
-		return results === null ? "" : results[1].replace(/\+/g, " ");
-	}
-
-	var host = getParameterByName('host');
+	host = 'wss://' + window.location.hostname + ':9989';
 	new AdminSocketAnalytics(host);
 
     </script>
diff --git a/loleaflet/debug/document/adminSettings.html b/loleaflet/debug/document/adminSettings.html
index 0ad46c1..3262211 100644
--- a/loleaflet/debug/document/adminSettings.html
+++ b/loleaflet/debug/document/adminSettings.html
@@ -34,19 +34,11 @@
     <script>window.jQuery || document.write('<script src="../../dist/bootstrap/assets/js/vendor/jquery.min.js"><\/script>')</script>
     <script src="../../dist/dialog/vex.combined.min.js"></script>
     <script>vex.defaultOptions.className = 'vex-theme-plain';</script>
-    <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
     <script src="../../dist/admin-src.js"></script>
     <script>
 
-	function getParameterByName(name) {
-		name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
-		var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
-		results = regex.exec(location.search);
-		return results === null ? "" : results[1].replace(/\+/g, " ");
-	}
-
-	var host = getParameterByName('host');
-	var settingsSocket = new AdminSocketSettings(host);
+	host = 'wss://' + window.location.hostname + ':9989';
+	new AdminSocketSettings(host);
 
     </script>
 
diff --git a/loleaflet/dist/admin/admin.html b/loleaflet/dist/admin/admin.html
index 647b31b..4fc7567 100644
--- a/loleaflet/dist/admin/admin.html
+++ b/loleaflet/dist/admin/admin.html
@@ -37,7 +37,7 @@
     <script>vex.defaultOptions.className = 'vex-theme-plain';</script>
     <script>
 
-	host = 'wss://' + window.location.hostname + ':9989';
+	host = 'wss://' + window.location.host + '/adminws/'
 	new AdminSocketOverview(host);
 
     </script>
diff --git a/loleaflet/dist/admin/adminAnalytics.html b/loleaflet/dist/admin/adminAnalytics.html
index 5f4fb67..a2a9ad6 100644
--- a/loleaflet/dist/admin/adminAnalytics.html
+++ b/loleaflet/dist/admin/adminAnalytics.html
@@ -38,7 +38,7 @@
     <script src="admin-src.js"></script>
     <script>
 
-	host = 'wss://' + window.location.hostname + ':9989';
+	host = 'wss://' + window.location.host + '/adminws/';
 	new AdminSocketAnalytics(host);
 
     </script>
diff --git a/loleaflet/dist/admin/adminSettings.html b/loleaflet/dist/admin/adminSettings.html
index 1db8279..c41fd5b 100644
--- a/loleaflet/dist/admin/adminSettings.html
+++ b/loleaflet/dist/admin/adminSettings.html
@@ -37,7 +37,7 @@
     <script src="admin-src.js"></script>
     <script>
 
-	host = 'wss://' + window.location.hostname + ':9989';
+	host = 'wss://' + window.location.host + '/adminws/';
 	new AdminSocketSettings(host);
 
     </script>
diff --git a/loleaflet/dist/loleaflet.html b/loleaflet/dist/loleaflet.html
index 0ac93c3..8332828 100644
--- a/loleaflet/dist/loleaflet.html
+++ b/loleaflet/dist/loleaflet.html
@@ -22,13 +22,13 @@
 <link rel="stylesheet" href="/loleaflet/dist/dialog/vex-theme-plain.css" />
 <link rel="stylesheet" href="/loleaflet/dist/toolbar/w2ui.min.css" />
 <link rel="stylesheet" href="/loleaflet/dist/toolbar/select2.min.css" />
-<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.11.1/themes/ui-lightness/jquery-ui.css">
+<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.1/themes/ui-lightness/jquery-ui.css">
 <link rel="stylesheet" href="/loleaflet/dist/toolbar/evol.colorpicker.min.css">
 <link rel="localizations" href="/loleaflet/dist/l10n/localizations.json" type="application/vnd.oftn.l10n+json"/>
 <link rel="localizations" href="/loleaflet/dist/l10n/styles-localizations.json" type="application/vnd.oftn.l10n+json" />
 <style type="text/css"></style></head>
 <body>
-    <script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
+    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
     <script src="/loleaflet/dist/l10n/json2.min.js"></script>
     <script src="/loleaflet/dist/l10n/l10n.min.js"></script>
     <script src="/loleaflet/dist/toolbar/w2ui.min.js"></script>
diff --git a/loolwsd/Admin.cpp b/loolwsd/Admin.cpp
index 0bf1718..bf6c48c 100644
--- a/loolwsd/Admin.cpp
+++ b/loolwsd/Admin.cpp
@@ -54,387 +54,346 @@ using Poco::Net::WebSocketException;
 using Poco::Util::Application;
 
 /// Handle admin requests.
-class AdminRequestHandler: public HTTPRequestHandler
+void AdminRequestHandler::handleWSRequests(HTTPServerRequest& request, HTTPServerResponse& response, int nSessionId)
 {
-private:
-    void handleWSRequests(HTTPServerRequest& request, HTTPServerResponse& response, int nSessionId)
+    try
     {
-        try
+        auto ws = std::make_shared<WebSocket>(request, response);
+
         {
-            auto ws = std::make_shared<WebSocket>(request, response);
+            std::lock_guard<std::mutex> modelLock(_admin->_modelMutex);
+            // Subscribe the websocket of any AdminModel updates
+            AdminModel& model = _admin->getModel();
+            model.subscribe(nSessionId, ws);
+        }
 
-            {
-                std::lock_guard<std::mutex> modelLock(_admin->_modelMutex);
-                // Subscribe the websocket of any AdminModel updates
-                AdminModel& model = _admin->getModel();
-                model.subscribe(nSessionId, ws);
-            }
+        const Poco::Timespan waitTime(POLL_TIMEOUT_MS * 1000);
+        int flags = 0;
+        int n = 0;
+        ws->setReceiveTimeout(0);
+        do
+        {
+            char buffer[200000]; //FIXME: Dynamic?
 
-            const Poco::Timespan waitTime(POLL_TIMEOUT_MS * 1000);
-            int flags = 0;
-            int n = 0;
-            ws->setReceiveTimeout(0);
-            do
+            if (ws->poll(waitTime, Socket::SELECT_READ))
             {
-                char buffer[200000]; //FIXME: Dynamic?
+                n = ws->receiveFrame(buffer, sizeof(buffer), flags);
 
-                if (ws->poll(waitTime, Socket::SELECT_READ))
+                if ((flags & WebSocket::FRAME_OP_BITMASK) == WebSocket::FRAME_OP_PING)
                 {
-                    n = ws->receiveFrame(buffer, sizeof(buffer), flags);
+                    // Echo back the ping payload as pong.
+                    // Technically, we should send back a PONG control frame.
+                    // However Firefox (probably) or Node.js (possibly) doesn't
+                    // like that and closes the socket when we do.
+                    // Echoing the payload as a normal frame works with Firefox.
+                    ws->sendFrame(buffer, n /*, WebSocket::FRAME_OP_PONG*/);
+                }
+                else if ((flags & WebSocket::FRAME_OP_BITMASK) == WebSocket::FRAME_OP_PONG)
+                {
+                    // In case we do send pings in the future.
+                }
+                else if (n <= 0)
+                {
+                    // Connection closed.
+                    Log::warn() << "Received " << n
+                                << " bytes. Connection closed. Flags: "
+                                << std::hex << flags << Log::end;
+                    break;
+                }
+                else
+                {
+                    assert(n > 0);
+                    const std::string firstLine = getFirstLine(buffer, n);
+                    StringTokenizer tokens(firstLine, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
+                    Log::trace() << "Recv: " << firstLine << Log::end;
 
-                    if ((flags & WebSocket::FRAME_OP_BITMASK) == WebSocket::FRAME_OP_PING)
+                    if (firstLine == "eof")
                     {
-                        // Echo back the ping payload as pong.
-                        // Technically, we should send back a PONG control frame.
-                        // However Firefox (probably) or Node.js (possibly) doesn't
-                        // like that and closes the socket when we do.
-                        // Echoing the payload as a normal frame works with Firefox.
-                        ws->sendFrame(buffer, n /*, WebSocket::FRAME_OP_PONG*/);
-                    }
-                    else if ((flags & WebSocket::FRAME_OP_BITMASK) == WebSocket::FRAME_OP_PONG)
-                    {
-                        // In case we do send pings in the future.
-                    }
-                    else if (n <= 0)
-                    {
-                        // Connection closed.
-                        Log::warn() << "Received " << n
-                                    << " bytes. Connection closed. Flags: "
-                                    << std::hex << flags << Log::end;
+                        Log::info("Received EOF. Finishing.");
                         break;
                     }
-                    else
-                    {
-                        assert(n > 0);
-                        const std::string firstLine = getFirstLine(buffer, n);
-                        StringTokenizer tokens(firstLine, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
-                        Log::trace() << "Recv: " << firstLine << Log::end;
+                    
+                    if (tokens.count() < 1)
+                        continue;
 
-                        if (firstLine == "eof")
-                        {
-                            Log::info("Received EOF. Finishing.");
-                            break;
-                        }
+                    // Lock the model mutex before interacting with it
+                    std::lock_guard<std::mutex> modelLock(_admin->_modelMutex);
+                    AdminModel& model = _admin->getModel();
 
-                        if (tokens.count() < 1)
-                            continue;
+                    if (tokens[0] == "stats")
+                    {
+                        //TODO: Collect stats and reply back to admin.
+                        // We need to ask Broker to give us some numbers on docs/clients/etc.
+                        // But we can also collect some memory info using system calls.
 
-                        // Lock the model mutex before interacting with it
-                        std::lock_guard<std::mutex> modelLock(_admin->_modelMutex);
-                        AdminModel& model = _admin->getModel();
+                        std::string statsResponse;
 
-                        if (tokens[0] == "stats")
+                        const auto cmd = "pstree -a -c -h -A -p " + std::to_string(getpid());
+                        FILE* fp = popen(cmd.c_str(), "r");
+                        if (fp == nullptr)
                         {
-                            //TODO: Collect stats and reply back to admin.
-                            // We need to ask Broker to give us some numbers on docs/clients/etc.
-                            // But we can also collect some memory info using system calls.
-
-                            std::string statsResponse;
-
-                            const auto cmd = "pstree -a -c -h -A -p " + std::to_string(getpid());
-                            FILE* fp = popen(cmd.c_str(), "r");
-                            if (fp == nullptr)
-                            {
-                                statsResponse = "error: failed to collect stats.";
-                                ws->sendFrame(statsResponse.data(), statsResponse.size());
-                                continue;
-                            }
-
-                            char treeBuffer[1024];
-                            while (fgets(treeBuffer, sizeof(treeBuffer)-1, fp) != nullptr)
-                            {
-                                statsResponse += treeBuffer;
-                                statsResponse += "</ BR>\n";
-                            }
-
-                            pclose(fp);
-
+                            statsResponse = "error: failed to collect stats.";
                             ws->sendFrame(statsResponse.data(), statsResponse.size());
+                            continue;
                         }
-                        else if (tokens[0] == "subscribe" && tokens.count() > 1)
-                        {
-                            for (unsigned i = 0; i < tokens.count() - 1; i++)
-                            {
-                                model.subscribe(nSessionId, tokens[i + 1]);
-                            }
-                        }
-                        else if (tokens[0] == "unsubscribe" && tokens.count() > 1)
-                        {
-                            for (unsigned i = 0; i < tokens.count() - 1; i++)
-                            {
-                                model.unsubscribe(nSessionId, tokens[i + 1]);
-                            }
-                        }
-                        else if (tokens[0] == "documents")
-                        {
 
-                            std::string responseString = "documents " + model.query("documents");
-                            ws->sendFrame(responseString.data(), responseString.size());
-                        }
-                        else if (tokens[0] == "total_mem")
+                        char treeBuffer[1024];
+                        while (fgets(treeBuffer, sizeof(treeBuffer)-1, fp) != nullptr)
                         {
-                            unsigned totalMem = _admin->getTotalMemoryUsage(model);
-                            std::string responseFrame = "total_mem " + std::to_string(totalMem);
-                            ws->sendFrame(responseFrame.data(), responseFrame.size());
+                            statsResponse += treeBuffer;
+                            statsResponse += "</ BR>\n";
                         }
-                        else if (tokens[0] == "active_users_count")
+
+                        pclose(fp);
+
+                        ws->sendFrame(statsResponse.data(), statsResponse.size());
+                    }
+                    else if (tokens[0] == "subscribe" && tokens.count() > 1)
+                    {
+                        for (unsigned i = 0; i < tokens.count() - 1; i++)
                         {
-                            std::string responseFrame = tokens[0] + " " + model.query(tokens[0]);
-                            ws->sendFrame(responseFrame.data(), responseFrame.size());
+                            model.subscribe(nSessionId, tokens[i + 1]);
                         }
-                        else if (tokens[0] == "active_docs_count")
+                    }
+                    else if (tokens[0] == "unsubscribe" && tokens.count() > 1)
+                    {
+                        for (unsigned i = 0; i < tokens.count() - 1; i++)
                         {
-                            std::string responseFrame = tokens[0] + " " + model.query(tokens[0]);
-                            ws->sendFrame(responseFrame.data(), responseFrame.size());
+                            model.unsubscribe(nSessionId, tokens[i + 1]);
                         }
-                        else if (tokens[0] == "kill" && tokens.count() == 2)
+                    }
+                    else if (tokens[0] == "documents")
+                    {
+
+                        std::string responseString = "documents " + model.query("documents");
+                        ws->sendFrame(responseString.data(), responseString.size());
+                    }
+                    else if (tokens[0] == "total_mem")
+                    {
+                        unsigned totalMem = _admin->getTotalMemoryUsage(model);
+                        std::string responseFrame = "total_mem " + std::to_string(totalMem);
+                        ws->sendFrame(responseFrame.data(), responseFrame.size());
+                    }
+                    else if (tokens[0] == "active_users_count")
+                    {
+                        std::string responseFrame = tokens[0] + " " + model.query(tokens[0]);
+                        ws->sendFrame(responseFrame.data(), responseFrame.size());
+                    }
+                    else if (tokens[0] == "active_docs_count")
+                    {
+                        std::string responseFrame = tokens[0] + " " + model.query(tokens[0]);
+                        ws->sendFrame(responseFrame.data(), responseFrame.size());
+                    }
+                    else if (tokens[0] == "kill" && tokens.count() == 2)
+                    {
+                        try
                         {
-                            try
+                            if (std::stoi(tokens[1]))
                             {
-                                if (std::stoi(tokens[1]))
-                                {
-                                    Util::writeFIFO(_admin->getBrokerPipe(), firstLine + " \r\n");
-                                }
-                            }
-                            catch(std::exception& e) {
-                                Log::warn() << "Could not kill given PID" << Log::end;
+                                Util::writeFIFO(LOOLWSD::BrokerWritePipe, firstLine + " \r\n");
                             }
                         }
-                        else if (tokens[0] == "mem_stats")
-                        {
-                            std::ostringstream oss;
-                            oss << tokens[0] << " "
-                                << model.query(tokens[0]);
-
-                            std::string responseFrame = oss.str();
-                            ws->sendFrame(responseFrame.data(), responseFrame.size());
+                        catch(std::exception& e) {
+                            Log::warn() << "Could not kill given PID" << Log::end;
                         }
-                        else if (tokens[0] == "cpu_stats")
-                        {
-                            std::ostringstream oss;
-                            oss << tokens[0] << " "
-                                << model.query(tokens[0]);
+                    }
+                    else if (tokens[0] == "mem_stats")
+                    {
+                        std::ostringstream oss;
+                        oss << tokens[0] << " "
+                            << model.query(tokens[0]);
 
-                            std::string responseFrame = oss.str();
-                            ws->sendFrame(responseFrame.data(), responseFrame.size());
-                        }
-                        else if (tokens[0] == "settings")
-                        {
-                            // for now, we have only these settings
-                            std::ostringstream oss;
-                            oss << tokens[0] << " "
-                                << "mem_stats_size=" << model.query("mem_stats_size") << " "
-                                << "mem_stats_interval=" << std::to_string(_admin->getMemStatsInterval()) << " "
-                                << "cpu_stats_size="  << model.query("cpu_stats_size") << " "
-                                << "cpu_stats_interval=" << std::to_string(_admin->getCpuStatsInterval());
-
-                            std::string responseFrame = oss.str();
-                            ws->sendFrame(responseFrame.data(), responseFrame.size());
-                        }
-                        else if (tokens[0] == "set" && tokens.count() > 1)
+                        std::string responseFrame = oss.str();
+                        ws->sendFrame(responseFrame.data(), responseFrame.size());
+                    }
+                    else if (tokens[0] == "cpu_stats")
+                    {
+                        std::ostringstream oss;
+                        oss << tokens[0] << " "
+                            << model.query(tokens[0]);
+
+                        std::string responseFrame = oss.str();
+                        ws->sendFrame(responseFrame.data(), responseFrame.size());
+                    }
+                    else if (tokens[0] == "settings")
+                    {
+                        // for now, we have only these settings
+                        std::ostringstream oss;
+                        oss << tokens[0] << " "
+                            << "mem_stats_size=" << model.query("mem_stats_size") << " "
+                            << "mem_stats_interval=" << std::to_string(_admin->getMemStatsInterval()) << " "
+                            << "cpu_stats_size="  << model.query("cpu_stats_size") << " "
+                            << "cpu_stats_interval=" << std::to_string(_admin->getCpuStatsInterval());
+
+                        std::string responseFrame = oss.str();
+                        ws->sendFrame(responseFrame.data(), responseFrame.size());
+                    }
+                    else if (tokens[0] == "set" && tokens.count() > 1)
+                    {
+                        for (unsigned i = 1; i < tokens.count(); i++)
                         {
-                            for (unsigned i = 1; i < tokens.count(); i++)
+                            StringTokenizer setting(tokens[i], "=", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
+                            unsigned settingVal = 0;
+                            try
                             {
-                                StringTokenizer setting(tokens[i], "=", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
-                                unsigned settingVal = 0;
-                                try
-                                {
-                                    settingVal = std::stoi(setting[1]);
-                                }
-                                catch (const std::exception& exc)
-                                {
-                                    Log::warn() << "Invalid setting value: "
-                                                << setting[1] << " for "
-                                                << setting[0] << Log::end;
-                                    continue;
-                                }
+                                settingVal = std::stoi(setting[1]);
+                            }
+                            catch (const std::exception& exc)
+                            {
+                                Log::warn() << "Invalid setting value: "
+                                            << setting[1] << " for "
+                                            << setting[0] << Log::end;
+                                continue;
+                            }
 
-                                if (setting[0] == "mem_stats_size")
+                            if (setting[0] == "mem_stats_size")
+                            {
+                                if (settingVal != static_cast<unsigned>(std::stoi(model.query(setting[0]))))
                                 {
-                                    if (settingVal != static_cast<unsigned>(std::stoi(model.query(setting[0]))))
-                                    {
-                                        model.setMemStatsSize(settingVal);
-                                    }
+                                    model.setMemStatsSize(settingVal);
                                 }
-                                else if (setting[0] == "mem_stats_interval")
+                            }
+                            else if (setting[0] == "mem_stats_interval")
+                            {
+                                if (settingVal != _admin->getMemStatsInterval())
                                 {
-                                    if (settingVal != _admin->getMemStatsInterval())
-                                    {
-                                        _admin->rescheduleMemTimer(settingVal);
-                                        model.clearMemStats();
-                                        model.notify("settings mem_stats_interval=" + std::to_string(settingVal));
-                                    }
+                                    _admin->rescheduleMemTimer(settingVal);
+                                    model.clearMemStats();
+                                    model.notify("settings mem_stats_interval=" + std::to_string(settingVal));
                                 }
-                                else if (setting[0] == "cpu_stats_size")
+                            }
+                            else if (setting[0] == "cpu_stats_size")
+                            {
+                                if (settingVal != static_cast<unsigned>(std::stoi(model.query(setting[0]))))
                                 {
-                                    if (settingVal != static_cast<unsigned>(std::stoi(model.query(setting[0]))))
-                                    {
-                                        model.setCpuStatsSize(settingVal);
-                                    }
+                                    model.setCpuStatsSize(settingVal);
                                 }
-                                else if (setting[0] == "cpu_stats_interval")
+                            }
+                            else if (setting[0] == "cpu_stats_interval")
+                            {
+                                if (settingVal != _admin->getCpuStatsInterval())
                                 {
-                                    if (settingVal != _admin->getCpuStatsInterval())
-                                    {
-                                        _admin->rescheduleCpuTimer(settingVal);
-                                        model.clearCpuStats();
-                                        model.notify("settings cpu_stats_interval=" + std::to_string(settingVal));
-                                    }
+                                    _admin->rescheduleCpuTimer(settingVal);
+                                    model.clearCpuStats();
+                                    model.notify("settings cpu_stats_interval=" + std::to_string(settingVal));
                                 }
                             }
                         }
                     }
                 }
             }
-            while (!TerminationFlag &&
-                   (flags & WebSocket::FRAME_OP_BITMASK) != WebSocket::FRAME_OP_CLOSE);
-            Log::debug() << "Finishing AdminProcessor. TerminationFlag: " << TerminationFlag
-                         << ", payload size: " << n
-                         << ", flags: " << std::hex << flags << Log::end;
-        }
-        catch (const WebSocketException& exc)
-        {
-            Log::error("AdminRequestHandler::handleRequest: WebSocketException: " + exc.message());
-            switch (exc.code())
-            {
-            case WebSocket::WS_ERR_HANDSHAKE_UNSUPPORTED_VERSION:
-                response.set("Sec-WebSocket-Version", WebSocket::WEBSOCKET_VERSION);
-                // fallthrough
-            case WebSocket::WS_ERR_NO_HANDSHAKE:
-            case WebSocket::WS_ERR_HANDSHAKE_NO_VERSION:
-            case WebSocket::WS_ERR_HANDSHAKE_NO_KEY:
-                response.setStatusAndReason(HTTPResponse::HTTP_BAD_REQUEST);
-                response.setContentLength(0);
-                response.send();
-                break;
-            }
         }
-        catch (const Poco::Net::NotAuthenticatedException& exc)
+        while (!TerminationFlag &&
+               (flags & WebSocket::FRAME_OP_BITMASK) != WebSocket::FRAME_OP_CLOSE);
+        Log::debug() << "Finishing AdminProcessor. TerminationFlag: " << TerminationFlag
+                     << ", payload size: " << n
+                     << ", flags: " << std::hex << flags << Log::end;
+    }
+    catch (const WebSocketException& exc)
+    {
+        Log::error("AdminRequestHandler::handleRequest: WebSocketException: " + exc.message());
+        switch (exc.code())
         {
-            Log::info("NotAuthenticatedException");
-            response.set("WWW-Authenticate", "Basic realm=\"ws-online\"");
-            response.setStatus(HTTPResponse::HTTP_UNAUTHORIZED);
+        case WebSocket::WS_ERR_HANDSHAKE_UNSUPPORTED_VERSION:
+            response.set("Sec-WebSocket-Version", WebSocket::WEBSOCKET_VERSION);
+            // fallthrough
+        case WebSocket::WS_ERR_NO_HANDSHAKE:
+        case WebSocket::WS_ERR_HANDSHAKE_NO_VERSION:
+        case WebSocket::WS_ERR_HANDSHAKE_NO_KEY:
+            response.setStatusAndReason(HTTPResponse::HTTP_BAD_REQUEST);
             response.setContentLength(0);
             response.send();
-        }
-        catch (const std::exception& exc)
-        {
-            Log::error(std::string("AdminRequestHandler::handleRequest: Exception: ") + exc.what());
+            break;
         }
     }
+    catch (const Poco::Net::NotAuthenticatedException& exc)
+    {
+        Log::info("NotAuthenticatedException");
+        response.set("WWW-Authenticate", "Basic realm=\"ws-online\"");
+        response.setStatus(HTTPResponse::HTTP_UNAUTHORIZED);
+        response.setContentLength(0);
+        response.send();
+    }
+    catch (const std::exception& exc)
+    {
+        Log::error(std::string("AdminRequestHandler::handleRequest: Exception: ") + exc.what());
+    }
+}
 
-public:
-
-    AdminRequestHandler(Admin* adminManager)
-        : _admin(adminManager)
-    {    }
+AdminRequestHandler::AdminRequestHandler(Admin* adminManager)
+    : _admin(adminManager)
+{    }
 
-    void handleRequest(HTTPServerRequest& request, HTTPServerResponse& response) override
-    {
-        assert(request.serverAddress().port() == ADMIN_PORT_NUMBER);
+void AdminRequestHandler::handleRequest(HTTPServerRequest& request, HTTPServerResponse& response)
+{
+    // Different session id pool for admin sessions (?)
+    const auto nSessionId = Util::decodeId(LOOLWSD::GenSessionId());
+    const std::string thread_name = "admin_ws_" + std::to_string(nSessionId);
 
-        // Different session id pool for admin sessions (?)
-        const auto nSessionId = Util::decodeId(LOOLWSD::GenSessionId());
-        const std::string thread_name = "admin_ws_" + std::to_string(nSessionId);
+    if (prctl(PR_SET_NAME, reinterpret_cast<unsigned long>(thread_name.c_str()), 0, 0, 0) != 0)
+        Log::error("Cannot set thread name to " + thread_name + ".");
 
-        if (prctl(PR_SET_NAME, reinterpret_cast<unsigned long>(thread_name.c_str()), 0, 0, 0) != 0)
-                Log::error("Cannot set thread name to " + thread_name + ".");
+    Log::debug("Thread [" + thread_name + "] started.");
 
-        Log::debug("Thread [" + thread_name + "] started.");
+    try
+    {
+        std::string requestURI = request.getURI();
+        StringTokenizer pathTokens(requestURI, "/", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
 
-        try
+        if (request.find("Upgrade") != request.end() && Poco::icompare(request["Upgrade"], "websocket") == 0)
         {
-            std::string requestURI = request.getURI();
-            StringTokenizer pathTokens(requestURI, "/", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
-
-            if (request.find("Upgrade") != request.end() && Poco::icompare(request["Upgrade"], "websocket") == 0)
+            if (request.find("Cookie") != request.end())
             {
-                if (request.find("Cookie") != request.end())
+                // FIXME: Handle other cookie params like '; httponly; secure'
+                const std::size_t pos = request["Cookie"].find_first_of("=");
+                if (pos == std::string::npos)
+                    throw Poco::Net::NotAuthenticatedException("Missing JWT");
+
+                const std::string jwtToken = request["Cookie"].substr(pos + 1);
+                Log::info("Verifying JWT token: " + jwtToken);
+                const std::string keyPath = Poco::Path(Application::instance().commandPath()).parent().toString() + SSL_KEY_FILE;
+                JWTAuth authAgent(keyPath, "admin", "admin", "admin");
+                if (authAgent.verify(jwtToken))
                 {
-                    // FIXME: Handle other cookie params like '; httponly; secure'
-                    const std::size_t pos = request["Cookie"].find_first_of("=");
-                    if (pos == std::string::npos)
-                        throw Poco::Net::NotAuthenticatedException("Missing JWT");
-
-                    const std::string jwtToken = request["Cookie"].substr(pos + 1);
-                    Log::info("Verifying JWT token: " + jwtToken);
-                    const std::string keyPath = Poco::Path(Application::instance().commandPath()).parent().toString() + SSL_KEY_FILE;
-                    JWTAuth authAgent(keyPath, "admin", "admin", "admin");
-                    if (authAgent.verify(jwtToken))
-                    {
-                        Log::trace("JWT token is valid");
-                        handleWSRequests(request, response, nSessionId);
-                    }
-                    else
-                    {
-                        Log::info("Invalid JWT token");
-                        throw Poco::Net::NotAuthenticatedException("Invalid Token");
-                    }
+                    Log::trace("JWT token is valid");
+                    handleWSRequests(request, response, nSessionId);
                 }
                 else
                 {
-                    Log::info("Missing authentication cookie. Handshake declined.");
-                    throw Poco::Net::NotAuthenticatedException("Missing token");
+                    Log::info("Invalid JWT token");
+                    throw Poco::Net::NotAuthenticatedException("Invalid Token");
                 }
             }
+            else
+            {
+                Log::info("Missing authentication cookie. Handshake declined.");
+                throw Poco::Net::NotAuthenticatedException("Missing token");
+            }
         }
-        catch(const Poco::Net::NotAuthenticatedException& exc)
-        {
-            Log::info("Admin::NotAuthneticated");
-            response.set("WWW-Authenticate", "Basic realm=\"online\"");
-            response.setStatus(HTTPResponse::HTTP_UNAUTHORIZED);
-            response.setContentLength(0);
-            response.send();
-        }
-        catch (const std::exception& exc)
-        {
-            Log::info("Unknown Exception caught");
-            response.setStatusAndReason(HTTPResponse::HTTP_BAD_REQUEST);
-            response.setContentLength(0);
-            response.send();
-        }
-        Log::debug("Thread [" + thread_name + "] finished.");
     }
-
-private:
-    Admin* _admin;
-};
-
-//TODO: Move to common header.
-class AdminRequestHandlerFactory: public HTTPRequestHandlerFactory
-{
-public:
-    AdminRequestHandlerFactory(Admin* adminManager)
-        : _admin(adminManager)
-        {}
-
-    HTTPRequestHandler* createRequestHandler(const HTTPServerRequest& request) override
+    catch(const Poco::Net::NotAuthenticatedException& exc)
     {
-        auto logger = Log::info();
-        logger << "Request from " << request.clientAddress().toString() << ": "
-               << request.getMethod() << " " << request.getURI() << " "
-               << request.getVersion();
-
-        for (HTTPServerRequest::ConstIterator it = request.begin(); it != request.end(); ++it)
-        {
-            logger << " / " << it->first << ": " << it->second;
-        }
-
-        logger << Log::end;
-        return new AdminRequestHandler(_admin);
+        Log::info("Admin::NotAuthneticated");
+        response.set("WWW-Authenticate", "Basic realm=\"online\"");
+        response.setStatus(HTTPResponse::HTTP_UNAUTHORIZED);
+        response.setContentLength(0);
+        response.send();
     }
-
-private:
-    Admin* _admin;
-};
+    catch (const std::exception& exc)
+    {
+        Log::info("Unknown Exception caught");
+        response.setStatusAndReason(HTTPResponse::HTTP_BAD_REQUEST);
+        response.setContentLength(0);
+        response.send();
+    }
+    Log::debug("Thread [" + thread_name + "] finished.");
+}
 
 /// An admin command processor.
-Admin::Admin(const Poco::Process::PID brokerPid, const int brokerPipe, const int notifyPipe) :
-    _srv(new AdminRequestHandlerFactory(this), SecureServerSocket(ADMIN_PORT_NUMBER), new HTTPServerParams),
+Admin::Admin(const Poco::Process::PID brokerPid, const int notifyPipe) :
     _model(AdminModel())
 {
     Admin::BrokerPid = brokerPid;
-    Admin::BrokerPipe = brokerPipe;
     Admin::NotifyPipe = notifyPipe;
 }
 
@@ -443,6 +402,11 @@ Admin::~Admin()
     Log::info("~Admin dtor.");
 }
 
+AdminRequestHandler* Admin::createRequestHandler()
+{
+    return new AdminRequestHandler(this);
+}
+
 void Admin::handleInput(std::string& message)
 {
     std::lock_guard<std::mutex> modelLock(_modelMutex);
@@ -462,7 +426,7 @@ void MemoryStats::run()
 void CpuStats::run()
 {
     //TODO: Implement me
-    //std::lock_guard<std::mutex> modelLock(_admin->modelMutex);
+    //std::lock_guard<std::mutex> modelLock(_admin->_modelMutex);
     //model.addCpuStats(totalMem);
 }
 
@@ -506,11 +470,6 @@ unsigned Admin::getCpuStatsInterval()
 
 void Admin::run()
 {
-    Log::info("Listening on Admin port " + std::to_string(ADMIN_PORT_NUMBER));
-
-    // Start a server listening on the admin port.
-    _srv.start();
-
     _memStatsTask = new MemoryStats(this);
     _memStatsTimer.schedule(_memStatsTask, _memStatsTaskInterval, _memStatsTaskInterval);
 
@@ -533,7 +492,6 @@ void Admin::run()
     Util::pollPipeForReading(pollPipeNotify, FIFO_NOTIFY, NotifyPipe,
                             [this](std::string& message) { return handleInput(message); } );
 
-    _srv.stopAll();
     Log::debug("Thread [" + thread_name + "] finished.");
 }
 
@@ -544,7 +502,6 @@ AdminModel& Admin::getModel()
 
 //TODO: Clean up with something more elegant.
 Poco::Process::PID Admin::BrokerPid;
-int Admin::BrokerPipe;
 int Admin::NotifyPipe;
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/loolwsd/Admin.hpp b/loolwsd/Admin.hpp
index 8da9839..fca7c3d 100644
--- a/loolwsd/Admin.hpp
+++ b/loolwsd/Admin.hpp
@@ -10,6 +10,8 @@
 #ifndef INCLUDED_ADMIN_HPP
 #define INCLUDED_ADMIN_HPP
 
+#include <Poco/Net/HTTPRequest.h>
+#include <Poco/Net/HTTPRequestHandler.h>
 #include <Poco/Net/HTTPServer.h>
 #include <Poco/Runnable.h>
 #include <Poco/Types.h>
@@ -20,18 +22,32 @@
 
 const std::string FIFO_NOTIFY = "loolnotify.fifo";
 
+class Admin;
+
+class AdminRequestHandler: public Poco::Net::HTTPRequestHandler
+{
+public:
+    AdminRequestHandler(Admin* adminManager);
+
+    void handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response) override;
+
+private:
+    void handleWSRequests(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response, int nSessionId);
+
+private:
+    Admin* _admin;
+};
+
 /// An admin command processor.
 class Admin : public Poco::Runnable
 {
 public:
-    Admin(const Poco::Process::PID brokerPid, const int brokerPipe, const int notifyPipe);
+    Admin(const Poco::Process::PID brokerPid, const int notifyPipe);
 
     ~Admin();
 
     static int getBrokerPid() { return Admin::BrokerPid; }
 
-    static int getBrokerPipe() { return Admin::BrokerPipe; }
-
     unsigned getTotalMemoryUsage(AdminModel&);
 
     void run() override;
@@ -47,6 +63,8 @@ public:
 
     void rescheduleCpuTimer(unsigned interval);
 
+    AdminRequestHandler* createRequestHandler();
+
 public:
     std::mutex _modelMutex;
 
@@ -54,7 +72,6 @@ private:
     void handleInput(std::string& message);
 
 private:
-    Poco::Net::HTTPServer _srv;
     AdminModel _model;
 
     Poco::Util::Timer _memStatsTimer;
@@ -106,6 +123,7 @@ public:
     void run() override;
 
 private:
+    Admin* _admin;
 };
 
 #endif
diff --git a/loolwsd/Auth.hpp b/loolwsd/Auth.hpp
index ce7ee91..fdc2fc1 100644
--- a/loolwsd/Auth.hpp
+++ b/loolwsd/Auth.hpp
@@ -136,6 +136,8 @@ public:
             return false;
         }
 
+        // TODO: Check for expiry etc.
+
         return true;
     }
 
diff --git a/loolwsd/Common.hpp b/loolwsd/Common.hpp
index fd7ee55..2f44df2 100644
--- a/loolwsd/Common.hpp
+++ b/loolwsd/Common.hpp
@@ -18,7 +18,6 @@ constexpr int MAX_SESSIONS = 1024;
 
 constexpr int DEFAULT_CLIENT_PORT_NUMBER = 9980;
 constexpr int MASTER_PORT_NUMBER = 9981;
-constexpr int ADMIN_PORT_NUMBER = 9989;
 constexpr int INTERVAL_PROBES = 10;
 constexpr int MAINTENANCE_INTERVAL = 1;
 constexpr int CHILD_TIMEOUT_SECS = 10;
diff --git a/loolwsd/FileServer.hpp b/loolwsd/FileServer.hpp
index 102ace1..787fbbd 100644
--- a/loolwsd/FileServer.hpp
+++ b/loolwsd/FileServer.hpp
@@ -78,6 +78,9 @@ public:
                         JWTAuth authAgent(keyPath, "admin", "admin", "admin");
                         const std::string jwtToken = authAgent.getAccessToken();
                         Poco::Net::HTTPCookie cookie("jwt", jwtToken);
+                        cookie.setPath("/adminws/");
+                        cookie.setSecure(true);
+                        cookie.setHttpOnly(true);
                         response.addCookie(cookie);
                         response.setContentType(htmlMimeType);
                         response.sendFile(LOOLWSD::FileServerRoot + requestUri.getPath(), htmlMimeType);
diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index eb499cc..a5f1dd1 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -111,6 +111,7 @@ DEALINGS IN THE SOFTWARE.
 #include "Auth.hpp"
 #include "ChildProcessSession.hpp"
 #include "Common.hpp"
+#include "FileServer.hpp"
 #include "LOOLProtocol.hpp"
 #include "LOOLSession.hpp"
 #include "LOOLWSD.hpp"
@@ -822,10 +823,14 @@ public:
     }
 };
 
-template <class RequestHandler>
-class RequestHandlerFactory: public HTTPRequestHandlerFactory
+class ClientRequestHandlerFactory: public HTTPRequestHandlerFactory
 {
 public:
+    ClientRequestHandlerFactory(Admin& admin, FileServer& fileServer)
+        : _admin(admin),
+          _fileServer(fileServer)
+        { }
+
     HTTPRequestHandler* createRequestHandler(const HTTPServerRequest& request) override
     {
         if (prctl(PR_SET_NAME, reinterpret_cast<unsigned long>("request_handler"), 0, 0, 0) != 0)
@@ -842,7 +847,58 @@ public:
         }
 
         logger << Log::end;
-        return new RequestHandler();
+
+        // Routing
+        // FIXME: Some browsers (all?) hit for /favicon.ico. Create a nice favicon and add to routes
+        Poco::URI requestUri(request.getURI());
+        std::vector<std::string> reqPathSegs;
+        requestUri.getPathSegments(reqPathSegs);
+        HTTPRequestHandler* requestHandler;
+
+        // File server
+        if (reqPathSegs.size() >= 1 && reqPathSegs[0] == "loleaflet")
+        {
+            requestHandler = _fileServer.createRequestHandler();
+        }
+        // Admin WebSocket Connections
+        else if (reqPathSegs.size() >= 1 && reqPathSegs[0] == "adminws")
+        {
+            requestHandler = _admin.createRequestHandler();
+        }
+        // Client post and websocket connections
+        else
+        {
+            requestHandler = new ClientRequestHandler();
+        }
+
+        return requestHandler;
+    }
+
+private:
+    Admin& _admin;
+    FileServer& _fileServer;
+};
+
+class PrisonerRequestHandlerFactory: public HTTPRequestHandlerFactory
+{
+public:
+    HTTPRequestHandler* createRequestHandler(const HTTPServerRequest& request) override
+    {
+        if (prctl(PR_SET_NAME, reinterpret_cast<unsigned long>("request_handler"), 0, 0, 0) != 0)
+            Log::error("Cannot set thread name to request_handler.");
+
+        auto logger = Log::info();
+        logger << "Request from " << request.clientAddress().toString() << ": "
+               << request.getMethod() << " " << request.getURI() << " "
+               << request.getVersion();
+
+        for (HTTPServerRequest::ConstIterator it = request.begin(); it != request.end(); ++it)
+        {
+            logger << " / " << it->first << ": " << it->second;
+        }
+
+        logger << Log::end;
+        return new PrisonerRequestHandler();
     }
 };
 
@@ -1214,6 +1270,11 @@ int LOOLWSD::main(const std::vector<std::string>& /*args*/)
         return Application::EXIT_SOFTWARE;
     }
 
+    // Init the Admin manager
+    Admin admin(brokerPid, notifyPipe);
+    // Init the file server
+    FileServer fileServer;
+
     // Configure the Server.
     // Note: TCPServer internally uses the default
     // ThreadPool to dispatch connections.
@@ -1230,14 +1291,14 @@ int LOOLWSD::main(const std::vector<std::string>& /*args*/)
     // Start a server listening on the port for clients
     SecureServerSocket svs(ClientPortNumber);
     ThreadPool threadPool(NumPreSpawnedChildren*6, MAX_SESSIONS * 2);
-    HTTPServer srv(new RequestHandlerFactory<ClientRequestHandler>(), threadPool, svs, params1);
+    HTTPServer srv(new ClientRequestHandlerFactory(admin, fileServer), threadPool, svs, params1);
 
     srv.start();
 
     // And one on the port for child processes
     SocketAddress addr2("127.0.0.1", MASTER_PORT_NUMBER);
     ServerSocket svs2(addr2);
-    HTTPServer srv2(new RequestHandlerFactory<PrisonerRequestHandler>(), threadPool, svs2, params2);
+    HTTPServer srv2(new PrisonerRequestHandlerFactory(), threadPool, svs2, params2);
 
     srv2.start();
 
@@ -1247,8 +1308,6 @@ int LOOLWSD::main(const std::vector<std::string>& /*args*/)
         return Application::EXIT_SOFTWARE;
     }
 
-    // Start the Admin manager.
-    Admin admin(brokerPid, BrokerWritePipe, notifyPipe);
     threadPool.start(admin);
 
     TestInput input(*this, svs, srv);
commit 7b763f0affd79b4b84c744942a20e65a727d3e8f
Author: Pranav Kant <pranavk at collabora.com>
Date:   Sun Mar 20 19:37:24 2016 +0530

    loolwsd: Allow specifying custom file server root
    
    By default, use git directory root.
    
    Change-Id: I4ee1173c43f313de3abb1732e6a7401169896189

diff --git a/loolwsd/FileServer.hpp b/loolwsd/FileServer.hpp
index 9c62e41..102ace1 100644
--- a/loolwsd/FileServer.hpp
+++ b/loolwsd/FileServer.hpp
@@ -32,6 +32,7 @@
 #include <Poco/Util/Timer.h>
 
 #include "Common.hpp"
+#include "LOOLWSD.hpp"
 
 using Poco::Net::HTTPRequest;
 using Poco::Net::HTTPRequestHandler;
@@ -55,8 +56,8 @@ public:
             std::vector<std::string> requestSegments;
             requestUri.getPathSegments(requestSegments);
 
-            // FIXME: We might want to package all dist files from leaflet to some other dir (?)
-            const std::string loleafletPath = Poco::Path(Application::instance().commandPath()).parent().parent().toString() + "loleaflet";
+            // TODO: We might want to package all files from leaflet to some other dir and restrict
+            // file serving to it (?)
             const std::string endPoint = requestSegments[requestSegments.size() - 1];
 
             if (request.getMethod() == HTTPRequest::HTTP_GET)
@@ -79,7 +80,7 @@ public:
                         Poco::Net::HTTPCookie cookie("jwt", jwtToken);
                         response.addCookie(cookie);
                         response.setContentType(htmlMimeType);
-                        response.sendFile(loleafletPath + "/debug/document/" + endPoint, htmlMimeType);
+                        response.sendFile(LOOLWSD::FileServerRoot + requestUri.getPath(), htmlMimeType);
                     }
                     else
                     {
@@ -87,12 +88,12 @@ public:
                         throw Poco::Net::NotAuthenticatedException("Wrong credentials.");
                     }
                 }
-                else if (requestSegments.size() > 1 && requestSegments[0] == "dist")
+                else
                 {
                     const std::string filePath = requestUri.getPath();
                     const std::size_t extPoint = endPoint.find_last_of(".");
                     if (extPoint == std::string::npos)
-                        return;
+                        throw Poco::FileNotFoundException("Invalid file.");
 
                     const std::string fileType = endPoint.substr(extPoint + 1);
                     std::string mimeType;
@@ -100,15 +101,13 @@ public:
                         mimeType = "application/javascript";
                     else if (fileType == "css")
                         mimeType = "text/css";
+                    else if (fileType == "html")
+                        mimeType = "text/html";
                     else
                         mimeType = "text/plain";
 
                     response.setContentType(mimeType);
-                    response.sendFile(loleafletPath + request.getURI(), mimeType);
-                }
-                else
-                {
-                    throw Poco::FileNotFoundException("");
+                    response.sendFile(LOOLWSD::FileServerRoot + requestUri.getPath(), mimeType);
                 }
             }
         }
diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index ba30f41..eb499cc 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -935,6 +935,7 @@ std::string LOOLWSD::SysTemplate;
 std::string LOOLWSD::LoTemplate;
 std::string LOOLWSD::ChildRoot;
 std::string LOOLWSD::LoSubPath = "lo";
+std::string LOOLWSD::FileServerRoot;
 
 int LOOLWSD::NumPreSpawnedChildren = 10;
 bool LOOLWSD::DoTest = false;
@@ -1009,6 +1010,11 @@ void LOOLWSD::defineOptions(OptionSet& optionSet)
                         .repeatable(false)
                         .argument("relative path"));
 
+    optionSet.addOption(Option("fileserverroot", "", "Path to the directory that should be considered root for the file server (default: '../loleaflet/').")
+                        .required(false)
+                        .repeatable(false)
+                        .argument("directory"));
+
     optionSet.addOption(Option("numprespawns", "", "Number of child processes to keep started in advance and waiting for new clients.")
                         .required(false)
                         .repeatable(false)
@@ -1045,6 +1051,8 @@ void LOOLWSD::handleOption(const std::string& optionName, const std::string& val
         ChildRoot = value;
     else if (optionName == "losubpath")
         LoSubPath = value;
+    else if (optionName == "fileserverroot")
+        FileServerRoot = value;
     else if (optionName == "numprespawns")
         NumPreSpawnedChildren = std::stoi(value);
     else if (optionName == "test")
@@ -1140,6 +1148,9 @@ int LOOLWSD::main(const std::vector<std::string>& /*args*/)
     else if (ChildRoot[ChildRoot.size() - 1] != Path::separator())
         ChildRoot += Path::separator();
 
+    if (FileServerRoot.empty())
+        FileServerRoot = Path(Application::instance().commandPath()).parent().parent().toString();
+
     if (ClientPortNumber == MASTER_PORT_NUMBER)
         throw IncompatibleOptionsException("port");
 
diff --git a/loolwsd/LOOLWSD.hpp b/loolwsd/LOOLWSD.hpp
index 3fc2531..5504888 100644
--- a/loolwsd/LOOLWSD.hpp
+++ b/loolwsd/LOOLWSD.hpp
@@ -44,6 +44,7 @@ public:
     static std::string LoTemplate;
     static std::string ChildRoot;
     static std::string LoSubPath;
+    static std::string FileServerRoot;
     //static Auth AuthAgent;
 
     static const std::string PIDLOG;
commit ee5ebb8489ef32a8b3693c4dd13097777c6a8295
Author: Pranav Kant <pranavk at collabora.com>
Date:   Sun Mar 20 19:26:20 2016 +0530

    loleaflet: Move admin console related files to dist/admin
    
    Better to put all admin related content in a separate directory
    rather than mixing it with other files in dist/
    
    Change-Id: I328ff95cf23251ff91bb438c3b9be923ccc2017f

diff --git a/loleaflet/build/build.js b/loleaflet/build/build.js
index db7e536..bf41ee2 100644
--- a/loleaflet/build/build.js
+++ b/loleaflet/build/build.js
@@ -121,7 +121,7 @@ exports.build = function (callback, version, compsBase32, buildName) {
 	// Also combine and copy the admin JS files
 	// TODO: Also minify if admin complexity increases in future
 	var adminNewSrc = combineFiles(getAdminFiles()),
-	    adminPath = 'dist/admin-src.js',
+	    adminPath = 'dist/admin/admin-src.js',
 	    adminOldSrc = loadSilently(adminPath);
 
 	if (adminNewSrc != adminOldSrc) {
diff --git a/loleaflet/dist/admin/admin-src.js b/loleaflet/dist/admin/admin-src.js
new file mode 100644
index 0000000..26de51f
--- /dev/null
+++ b/loleaflet/dist/admin/admin-src.js
@@ -0,0 +1,710 @@
+/*
+	Base.js, version 1.1a
+	Copyright 2006-2010, Dean Edwards
+	License: http://www.opensource.org/licenses/mit-license.php
+*/
+
+var Base = function() {
+	// dummy
+};
+
+Base.extend = function(_instance, _static) { // subclass
+	var extend = Base.prototype.extend;
+
+	// build the prototype
+	Base._prototyping = true;
+	var proto = new this;
+	extend.call(proto, _instance);
+  proto.base = function() {
+    // call this method from any other method to invoke that method's ancestor
+  };
+	delete Base._prototyping;
+
+	// create the wrapper for the constructor function
+	//var constructor = proto.constructor.valueOf(); //-dean
+	var constructor = proto.constructor;
+	var klass = proto.constructor = function() {
+		if (!Base._prototyping) {
+			if (this._constructing || this.constructor == klass) { // instantiation
+				this._constructing = true;
+				constructor.apply(this, arguments);
+				delete this._constructing;
+			} else if (arguments[0] != null) { // casting
+				return (arguments[0].extend || extend).call(arguments[0], proto);
+			}
+		}
+	};
+
+	// build the class interface
+	klass.ancestor = this;
+	klass.extend = this.extend;
+	klass.forEach = this.forEach;
+	klass.implement = this.implement;
+	klass.prototype = proto;
+	klass.toString = this.toString;
+	klass.valueOf = function(type) {
+		//return (type == "object") ? klass : constructor; //-dean
+		return (type == "object") ? klass : constructor.valueOf();
+	};
+	extend.call(klass, _static);
+	// class initialisation
+	if (typeof klass.init == "function") klass.init();
+	return klass;
+};
+
+Base.prototype = {
+	extend: function(source, value) {
+		if (arguments.length > 1) { // extending with a name/value pair
+			var ancestor = this[source];
+			if (ancestor && (typeof value == "function") && // overriding a method?
+				// the valueOf() comparison is to avoid circular references
+				(!ancestor.valueOf || ancestor.valueOf() != value.valueOf()) &&
+				/\bbase\b/.test(value)) {
+				// get the underlying method
+				var method = value.valueOf();
+				// override
+				value = function() {
+					var previous = this.base || Base.prototype.base;
+					this.base = ancestor;
+					var returnValue = method.apply(this, arguments);
+					this.base = previous;
+					return returnValue;
+				};
+				// point to the underlying method
+				value.valueOf = function(type) {
+					return (type == "object") ? value : method;
+				};
+				value.toString = Base.toString;
+			}
+			this[source] = value;
+		} else if (source) { // extending with an object literal
+			var extend = Base.prototype.extend;
+			// if this object has a customised extend method then use it
+			if (!Base._prototyping && typeof this != "function") {
+				extend = this.extend || extend;
+			}
+			var proto = {toSource: null};
+			// do the "toString" and other methods manually
+			var hidden = ["constructor", "toString", "valueOf"];
+			// if we are prototyping then include the constructor
+			var i = Base._prototyping ? 0 : 1;
+			while (key = hidden[i++]) {
+				if (source[key] != proto[key]) {
+					extend.call(this, key, source[key]);
+
+				}
+			}
+			// copy each of the source object's properties to this object
+			for (var key in source) {
+				if (!proto[key]) extend.call(this, key, source[key]);
+			}
+		}
+		return this;
+	}
+};
+
+// initialise
+Base = Base.extend({
+	constructor: function() {
+		this.extend(arguments[0]);
+	}
+}, {
+	ancestor: Object,
+	version: "1.1",
+
+	forEach: function(object, block, context) {
+		for (var key in object) {
+			if (this.prototype[key] === undefined) {
+				block.call(context, object[key], key, object);
+			}
+		}
+	},
+
+	implement: function() {
+		for (var i = 0; i < arguments.length; i++) {
+			if (typeof arguments[i] == "function") {
+				// if it's a function, call it
+				arguments[i](this.prototype);
+			} else {
+				// add the interface using the extend method
+				this.prototype.extend(arguments[i]);
+			}
+		}
+		return this;
+	},
+
+	toString: function() {
+		return String(this.valueOf());
+	}
+});
+
+
+
+/*
+	Utility class
+*/
+/* global Base */
+var Util = Base.extend({
+	constructor: null
+
+}, { // class itnerface
+
+	humanize: function humanFileSize(kbytes) {
+		var unit = 1000;
+		var units = ['kB', 'MB', 'GB', 'TB'];
+		for (var i = 0; Math.abs(kbytes) >= unit && i < units.length; i++) {
+			kbytes /= unit;
+		}
+
+		return kbytes.toFixed(1) + ' ' + units[i];
+	}
+});
+
+
+/*
+	Abstract class
+*/
+
+/* global vex Base */
+/* exported AdminSocketBase */
+var AdminSocketBase = Base.extend({
+	socket: null,
+
+	constructor: function(host) {
+		// because i am abstract
+		if (this.constructor === AdminSocketBase) {
+			throw new Error('Cannot instantiate abstract class');
+		}
+
+		// We do not allow such child class to instantiate websocket that do not implement
+		// onSocketMessage and onSocketOpen.
+		if (typeof this.onSocketMessage === 'function' && typeof this.onSocketOpen === 'function') {
+			this.socket = new WebSocket(host);
+			this.socket.onopen = this.onSocketOpen.bind(this);
+			this.socket.onclose = this.onSocketClose.bind(this);
+			this.socket.onmessage = this.onSocketMessage.bind(this);
+			this.socket.onerror = this.onSocketError.bind(this);
+			this.socket.binaryType = 'arraybuffer';
+		}
+	},
+
+	onSocketOpen: function() {
+		/* Implemented by child */
+	},
+
+	onSocketMessage: function() {
+		/* Implemented by child */
+	},
+
+	onSocketClose: function() {
+		this.socket.onerror = function() {};
+		this.socket.onclose = function() {};
+		this.socket.onmessage = function() {};
+		this.socket.close();
+	},
+
+	onSocketError: function() {
+		vex.dialog.alert('Connection error');
+	}
+});
+
+
+/*
+	Socket to be intialized on opening the overview page in Admin console
+*/
+/* global vex $ Util AdminSocketBase */
+var AdminSocketOverview = AdminSocketBase.extend({
+	constructor: function(host) {
+		this.base(host);
+	},
+
+	_basicStatsIntervalId: 0,
+
+	_getBasicStats: function() {
+		this.socket.send('total_mem');
+		this.socket.send('active_docs_count');
+		this.socket.send('active_users_count');
+	},
+
+	onSocketOpen: function() {
+		this.socket.send('documents');
+		this.socket.send('subscribe document addview rmview rmdoc');
+
+		this._getBasicStats();
+		var socketOverview = this;
+		this._basicStatsIntervalId =
+		setInterval(function() {
+			return socketOverview._getBasicStats();
+		}, 5000);
+
+		// Allow table rows to have a context menu for killing children
+		$('body').on('contextmenu', 'table tr', function(ev) {
+			$('#rowContextMenu').css({
+				display: 'block',
+				left: ev.pageX,
+				top: ev.pageY
+			})
+			.data('rowToKill', ev.target.parentElement.id);
+
+			return false;
+		})
+		.click(function() {
+			$('#rowContextMenu').hide();
+		});
+
+		$('#rowContextMenu').on('click', 'a', function() {
+			vex.dialog.confirm({
+				message: 'Are you sure you want to kill this child ?',
+				callback: function(value) {
+					if (value) {
+						var killPid = ($('#rowContextMenu').data('rowToKill')).substring('doc'.length);
+						socketOverview.socket.send('kill ' + killPid);
+					}
+					$('#rowContextMenu').hide();
+				}
+			});
+		});
+	},
+
+	onSocketMessage: function(e) {
+		var textMsg;
+		if (typeof e.data === 'string') {
+			textMsg = e.data;
+		}
+		else {
+			textMsg = '';
+		}
+
+		var tableContainer = document.getElementById('doclist');
+		var rowContainer;
+		var pidEle, urlEle, viewsEle, memEle, docEle;
+		var nViews, nTotalViews;
+		var docProps, sPid, sUrl, sViews, sMem;
+		if (textMsg.startsWith('documents')) {
+			var documents = textMsg.substring('documents'.length);
+			documents = documents.trim().split('\n');
+			for (var i = 0; i < documents.length; i++) {
+				if (documents[i] === '') {
+					continue;
+				}
+				docProps = documents[i].trim().split(' ');
+				sPid = docProps[0];
+				sUrl = docProps[1];
+				sViews = docProps[2];
+				sMem = docProps[3];
+				if (sUrl === '0') {
+					continue;
+				}
+				rowContainer = document.createElement('tr');
+				rowContainer.id = 'doc' + sPid;
+				tableContainer.appendChild(rowContainer);
+
+				pidEle = document.createElement('td');
+				pidEle.innerHTML = sPid;
+				rowContainer.appendChild(pidEle);
+
+				urlEle = document.createElement('td');
+				urlEle.innerHTML = sUrl;
+				rowContainer.appendChild(urlEle);
+
+				viewsEle = document.createElement('td');
+				viewsEle.id = 'docview' + sPid;
+				viewsEle.innerHTML = sViews;
+				rowContainer.appendChild(viewsEle);
+
+				memEle = document.createElement('td');
+				memEle.innerHTML = Util.humanize(parseInt(sMem));
+				rowContainer.appendChild(memEle);
+			}
+		}
+		else if (textMsg.startsWith('addview')) {
+			sPid = textMsg.substring('addview'.length).trim().split(' ')[0];
+			nViews = parseInt(document.getElementById('docview' + sPid).innerHTML);
+			document.getElementById('docview' + sPid).innerHTML = nViews + 1;
+			nTotalViews = parseInt(document.getElementById('active_users_count').innerHTML);
+			document.getElementById('active_users_count').innerHTML = nTotalViews + 1;
+		}
+		else if (textMsg.startsWith('rmview')) {
+			sPid = textMsg.substring('addview'.length).trim().split(' ')[0];
+			nViews = parseInt(document.getElementById('docview' + sPid).innerHTML);
+			document.getElementById('docview' + sPid).innerHTML = nViews - 1;
+			nTotalViews = parseInt(document.getElementById('active_users_count').innerHTML);
+			document.getElementById('active_users_count').innerHTML = nTotalViews - 1;
+		}
+		else if (textMsg.startsWith('document')) {
+			textMsg = textMsg.substring('document'.length);
+			docProps = textMsg.trim().split(' ');
+			sPid = docProps[0];
+			sUrl = docProps[1];
+			sMem = docProps[2];
+
+			docEle = document.getElementById('doc' + sPid);
+			if (docEle) {
+				tableContainer.removeChild(docEle);
+			}
+			if (sUrl === '0') {
+				return;
+			}
+
+			rowContainer = document.createElement('tr');
+			rowContainer.id = 'doc' + docProps[0];
+			tableContainer.appendChild(rowContainer);
+
+			pidEle = document.createElement('td');
+			pidEle.innerHTML = docProps[0];
+			rowContainer.appendChild(pidEle);
+
+			urlEle = document.createElement('td');
+			urlEle.innerHTML = docProps[1];
+			rowContainer.appendChild(urlEle);
+
+			viewsEle = document.createElement('td');
+			viewsEle.innerHTML = 0;
+			viewsEle.id = 'docview' + docProps[0];
+			rowContainer.appendChild(viewsEle);
+
+			memEle = document.createElement('td');
+			memEle.innerHTML = Util.humanize(parseInt(sMem));
+			rowContainer.appendChild(memEle);
+
+			var totalUsersEle = document.getElementById('active_docs_count');
+			totalUsersEle.innerHTML = parseInt(totalUsersEle.innerHTML) + 1;
+		}
+		else if (textMsg.startsWith('total_mem') ||
+			textMsg.startsWith('active_docs_count') ||
+			textMsg.startsWith('active_users_count'))
+		{
+			textMsg = textMsg.split(' ');
+			var sCommand = textMsg[0];
+			var nData = parseInt(textMsg[1]);
+
+			if (sCommand === 'total_mem') {
+				nData = Util.humanize(nData);
+			}
+			document.getElementById(sCommand).innerHTML = nData;
+		}
+		else if (textMsg.startsWith('rmdoc')) {
+			textMsg = textMsg.substring('rmdoc'.length);
+			docProps = textMsg.trim().split(' ');
+			sPid = docProps[0];
+			docEle = document.getElementById('doc' + sPid);
+			if (docEle) {
+				tableContainer.removeChild(docEle);
+			}
+		}
+	},
+
+	onSocketClose: function() {
+		clearInterval(this._basicStatsIntervalId);
+	}
+});
+
+
+/*
+	Socket to be intialized on opening the analytics page in Admin console
+	containing various graphs to show to the user on specified interval
+*/
+
+/* global d3 Util AdminSocketBase */
+var AdminSocketAnalytics = AdminSocketBase.extend({
+	constructor: function(host) {
+		this.base(host);
+	},
+
+	_memStatsData: [],
+	_cpuStatsData: [],
+
+	_memStatsSize: 0,
+	_memStatsInterval: 0,
+
+	_cpuStatsSize: 0,
+	_cpuStatsInterval: 0,
+
+	_initMemStatsData: function(memStatsSize, memStatsInterval, reset) {
+		if (reset) {
+			this._memStatsData = [];
+		}
+
+		var offset = this._memStatsData.length * memStatsInterval;
+		for (var i = 0; i < memStatsSize; i++) {
+			this._memStatsData.unshift({time: -(offset), value: 0});
+			offset += memStatsInterval;
+		}
+	},
+
+	_initCpuStatsData: function() {
+		for (var i = 0; i < this._cpuStatsSize; i++) {
+			this._cpuStatsData.push({time: -((this._cpuStatsSize - i - 1) * this._cpuStatsInterval), value: 0});
+		}
+	},
+
+	onSocketOpen: function() {
+		this.socket.send('subscribe mem_stats cpu_stats settings');
+		this.socket.send('settings');
+		this.socket.send('mem_stats');
+	},
+
+	_createMemData: function() {
+		for (var i = this._memStatsRawData.length - 1, j = this._memStatsData.length - 1; i >= 0 && j >= 0; i--, j--) {
+			this._memStatsData[j].value = parseInt(this._memStatsRawData[i]);
+		}
+	},
+
+	_d3xAxis: null,
+	_d3yAxis: null,
+	_d3line: null,
+	_xScale: null,
+	_yScale: null,
+
+	_graphWidth: 1000,
+	_graphHeight: 500,
+	_graphMargins: {
+		top: 20,
+		right: 20,
+		bottom: 20,
+		left: 100
+	},
+
+	_setUpAxis: function() {
+		this._xScale = d3.scale.linear().range([this._graphMargins.left, this._graphWidth - this._graphMargins.right]).domain([d3.min(this._memStatsData, function(d) {
+				return d.time;
+			}), d3.max(this._memStatsData, function(d) {
+				return d.time;
+			})]);
+
+
+		this._yScale = d3.scale.linear().range([this._graphHeight - this._graphMargins.bottom, this._graphMargins.top]).domain([d3.min(this._memStatsData, function(d) {
+			return d.value;
+		}), d3.max(this._memStatsData, function(d) {
+			return d.value;
+		})]);
+
+		this._d3xAxis = d3.svg.axis()
+			.scale(this._xScale)
+			.tickFormat(function(d) {
+				d = Math.abs(d / 1000);
+				var units = ['s', 'min', 'hr'];
+				for (var i = 0; i < units.length && Math.abs(d) >= 60; i++) {
+					d = parseInt(d / 60);
+				}
+				return parseInt(d) + units[i] + ' ago';
+			});
+
+		this._d3yAxis = d3.svg.axis()
+			.scale(this._yScale)
+			.tickFormat(function (d) {
+				return Util.humanize(d);
+			})
+			.orient('left');
+
+		var xScale = this._xScale;
+		var yScale = this._yScale;
+
+		this._d3line = d3.svg.line()
+			.x(function(d) {
+				return xScale(d.time);
+			})
+			.y(function(d) {
+				return yScale(d.value);
+			});
+	},
+
+	_createMemGraph: function() {
+		var vis = d3.select('#visualisation');
+
+		this._setUpAxis();
+
+		vis.append('svg:g')
+		.attr('class', 'x-axis')
+		.attr('transform', 'translate(0,' + (this._graphHeight - this._graphMargins.bottom) + ')')
+		.call(this._d3xAxis);
+
+		vis.append('svg:g')
+		.attr('class', 'y-axis')
+		.attr('transform', 'translate(' + this._graphMargins.left + ',0)')
+		.call(this._d3yAxis);
+
+		vis.append('svg:path')
+			.attr('d', this._d3line(this._memStatsData))
+			.attr('class', 'line')
+			.attr('stroke', 'blue')
+			.attr('stroke-width', 2)
+			.attr('fill', 'none');
+	},
+
+	_addNewMemData: function(data) {
+		// make a space for new data
+		for (var i = this._memStatsData.length - 1; i > 0; i--) {
+			this._memStatsData[i].time = this._memStatsData[i - 1].time;
+		}
+
+		// push new data at time '0'
+		this._memStatsData.push({time: 0, value: parseInt(data)});
+
+		// remove extra items
+		if (this._memStatsData.length > this._memStatsSize) {
+			this._memStatsData.shift();
+		}
+	},
+
+	_updateMemGraph: function() {
+		var svg = d3.select('#visualisation');
+
+		this._setUpAxis();
+
+		svg.select('.line')
+		.attr('d', this._d3line(this._memStatsData));
+
+		svg.select('.x-axis')
+		.call(this._d3xAxis);
+
+		svg.select('.y-axis')
+		.call(this._d3yAxis);
+	},
+
+	onSocketMessage: function(e) {
+		var textMsg;
+		if (typeof e.data === 'string') {
+			textMsg = e.data;
+		}
+		else {
+			textMsg = '';
+		}
+
+
+		if (textMsg.startsWith('settings')) {
+			textMsg = textMsg.substring('settings '.length);
+			textMsg = textMsg.split(' ');
+
+			//TODO: Add CPU statistics
+			var memStatsSize, memStatsInterval, cpuStatsSize, cpuStatsInterval;
+			var i, j, data;
+			memStatsSize = this._memStatsSize;
+			memStatsInterval = this._memStatsInterval;
+			cpuStatsSize = this._cpuStatsSize;
+			cpuStatsInterval = this._cpuStatsInterval;
+			for (i = 0; i < textMsg.length; i++) {
+				var setting = textMsg[i].split('=');
+				if (setting[0] === 'mem_stats_size') {
+					memStatsSize = parseInt(setting[1]);
+				}
+				else if (setting[0] === 'mem_stats_interval') {
+					memStatsInterval = parseInt(setting[1]);
+				}
+				else if (setting[0] === 'cpu_stats_size') {
+					cpuStatsSize = parseInt(setting[1]);
+				}
+				else if (setting[0] === 'cpu_stats_interval') {
+					cpuStatsInterval = parseInt(setting[1]);
+				}
+			}
+
+			// Fix the axes according to changed data
+			if (memStatsInterval !== this._memStatsInterval) {
+				// We can possibly reuse the data with a bit of work
+				this._initMemStatsData(memStatsSize, memStatsInterval, true);
+			}
+			else if (memStatsSize > this._memStatsSize) {
+				this._initMemStatsData(memStatsSize - this._memStatsSize, memStatsInterval, false);
+			}
+			else {
+				// just strip the extra items
+				for (i = 0; i < this._memStatsSize - memStatsSize; i++) {
+					this._memStatsData.shift();
+				}
+			}
+
+			this._memStatsSize = memStatsSize;
+			this._memStatsInterval = memStatsInterval;
+			this._cpuStatsSize = cpuStatsSize;
+			this._cpuStatsInterval = cpuStatsInterval;
+		}
+		else if (textMsg.startsWith('mem_stats') ||
+			textMsg.startsWith('cpu_stats')) {
+			textMsg = textMsg.split(' ')[1];
+			if (textMsg.endsWith(',')) {
+				// This is the result of query, not notification
+				data = textMsg.substring(0, textMsg.length - 1).split(',');
+				for (i = this._memStatsData.length - 1, j = data.length - 1; i >= 0 && j >= 0; i--, j--) {
+					this._memStatsData[i].value = parseInt(data[j]);
+				}
+
+				//this._createMemData(data);
+				this._createMemGraph();
+			}
+			else {
+				// this is a notification data; append to _memStatsData
+				data = textMsg.trim();
+				this._addNewMemData(data);
+				this._updateMemGraph();
+			}
+		}
+	},
+
+	onSocketClose: function() {
+		clearInterval(this._basicStatsIntervalId);
+	}
+});
+
+
+/*
+	Socket to be intialized on opening the settings page in Admin console
+*/
+/* global $ AdminSocketBase */
+var AdminSocketSettings = AdminSocketBase.extend({
+	constructor: function(host) {
+		this.base(host);
+		this._init();
+	},
+
+	_init: function() {
+		var socketSettings = this.socket;
+		$(document).ready(function() {
+			$('#admin_settings').on('submit', function(e) {
+				e.preventDefault();
+				var memStatsSize = $('#mem_stats_size').val();
+				var memStatsInterval = $('#mem_stats_interval').val();
+				var cpuStatsSize = $('#cpu_stats_size').val();
+				var cpuStatsInterval = $('#cpu_stats_interval').val();
+				var command = 'set';
+				command += ' mem_stats_size=' + memStatsSize;
+				command += ' mem_stats_interval=' + memStatsInterval;
+				command += ' cpu_stats_size=' + cpuStatsSize;
+				command += ' cpu_stats_interval=' + cpuStatsInterval;
+				socketSettings.send(command);
+			});
+		});
+	},
+
+	onSocketOpen: function() {
+		this.socket.send('subscribe settings');
+		this.socket.send('settings');
+	},
+
+    onSocketMessage: function(e) {
+		var textMsg;
+		if (typeof e.data === 'string') {
+			textMsg = e.data;
+		}
+		else {
+			textMsg = '';
+		}
+
+		if (textMsg.startsWith('settings')) {
+			textMsg = textMsg.substring('settings '.length);
+			var settings = textMsg.split(' ');
+			for (var i = 0; i < settings.length; i++) {
+				var setting = settings[i].split('=');
+				var settingKey = setting[0];
+				var settingVal = setting[1];
+				document.getElementById(settingKey).value = settingVal;
+			}
+		}
+	},
+
+	onSocketClose: function() {
+		clearInterval(this._basicStatsIntervalId);
+	}
+});
+
+
diff --git a/loleaflet/dist/admin.html b/loleaflet/dist/admin/admin.html
similarity index 50%
rename from loleaflet/dist/admin.html
rename to loleaflet/dist/admin/admin.html
index 806d54d..647b31b 100644
--- a/loleaflet/dist/admin.html
+++ b/loleaflet/dist/admin/admin.html
@@ -11,13 +11,16 @@
     <title>LibreOffice Online - Admin console</title>
 
     <!-- Bootstrap core CSS -->
-    <link href="/loleaflet/dist/bootstrap/css/bootstrap.min.css" rel="stylesheet">
+    <link href="../bootstrap/css/bootstrap.min.css" rel="stylesheet">
 
     <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
-    <link href="/loleaflet/dist/bootstrap/assets/css/ie10-viewport-bug-workaround.css" rel="stylesheet">
+    <link href="../bootstrap/assets/css/ie10-viewport-bug-workaround.css" rel="stylesheet">
 
     <!-- Custom styles for this template -->
-    <link href="/loleaflet/dist/bootstrap/dashboard.css" rel="stylesheet">
+    <link href="../bootstrap/dashboard.css" rel="stylesheet">
+
+    <link rel="stylesheet" href="../dialog/vex.css" />
+    <link rel="stylesheet" href="../dialog/vex-theme-plain.css" />
 
     <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
     <!--[if lt IE 9]>
@@ -27,58 +30,52 @@
   </head>
 
   <body>
-    <script src="/loleaflet/dist/admin-src.js"></script>
+    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
+    <script>window.jQuery || document.write('<script src="bootstrap/assets/js/vendor/jquery.min.js"><\/script>')</script>
+    <script src="../dialog/vex.combined.min.js"></script>
+    <script src="admin-src.js"></script>
+    <script>vex.defaultOptions.className = 'vex-theme-plain';</script>
     <script>
 
-	function getParameterByName(name) {
-		name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
-		var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
-		results = regex.exec(location.search);
-		return results === null ? "" : results[1].replace(/\+/g, " ");
-	}
-
-	var host = getParameterByName('host');
+	host = 'wss://' + window.location.hostname + ':9989';
 	new AdminSocketOverview(host);
 
     </script>
 
     <nav class="navbar navbar-inverse navbar-fixed-top">
       <div class="container-fluid">
-	<div class="navbar-header">
-	  <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
-	    <span class="sr-only">Toggle navigation</span>
-	    <span class="icon-bar"></span>
-	    <span class="icon-bar"></span>
-	    <span class="icon-bar"></span>
-	  </button>
-	  <a class="navbar-brand" href="#">LibreOffice Online - Admin console</a>
-	</div>
-	<div id="navbar" class="navbar-collapse collapse">
-	  <ul class="nav navbar-nav navbar-right">
-	    <li><a href="#">Dashboard</a></li>
-	    <li><a href="#">Settings</a></li>
-	    <li><a href="#">Profile</a></li>
-	    <li><a href="#">Help</a></li>
-	  </ul>
-	  <form class="navbar-form navbar-right">
-	    <input type="text" class="form-control" placeholder="Search...">
-	  </form>
-	</div>
+        <div class="navbar-header">
+          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
+            <span class="sr-only">Toggle navigation</span>
+            <span class="icon-bar"></span>
+            <span class="icon-bar"></span>
+            <span class="icon-bar"></span>
+          </button>
+          <a class="navbar-brand" href="#">LibreOffice Online - Admin console</a>
+        </div>
+        <div id="navbar" class="navbar-collapse collapse">
+          <ul class="nav navbar-nav navbar-right">
+            <li><a href="admin.html">Dashboard</a></li>
+            <li><a href="adminSettings.html">Settings</a></li>
+            <li><a href="#">Help</a></li>
+          </ul>
+          <form class="navbar-form navbar-right">
+            <input type="text" class="form-control" placeholder="Search...">
+          </form>
+        </div>
       </div>
     </nav>
 
     <div class="container-fluid">
       <div class="row">
-	<div class="col-sm-3 col-md-2 sidebar">
-	  <ul class="nav nav-sidebar">
-	    <li class="active"><a href="#">Overview <span class="sr-only">(current)</span></a></li>
-	    <li><a href="#">Reports</a></li>
-	    <li><a href="#">Analytics</a></li>
-	    <li><a href="#">Export</a></li>
-	  </ul>
-	</div>
-	<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
-	  <h1 class="page-header">Dashboard</h1>
+        <div class="col-sm-3 col-md-2 sidebar">
+          <ul class="nav nav-sidebar">
+            <li class="active"><a href="#">Overview <span class="sr-only">(current)</span></a></li>
+            <li><a href="adminAnalytics.html">Analytics</a></li>
+          </ul>
+        </div>
+        <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
+          <h1 class="page-header">Dashboard</h1>
 
 	  <div class="row placeholders">
 	    <div class="col-xs-6 col-sm-3 placeholder">
@@ -114,15 +111,19 @@
       </div>
     </div>
 
+    <div id="rowContextMenu" class="dropdown clearfix">
+      <ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu" style="display:block;position:static;margin-bottom:5px;">
+        <li><a tabindex="-1" href="#">Kill</a></li>
+      </ul>
+    </div>
+
     <!-- Bootstrap core JavaScript
     ================================================== -->
     <!-- Placed at the end of the document so the pages load faster -->
-    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
-    <script>window.jQuery || document.write('<script src="../../assets/js/vendor/jquery.min.js"><\/script>')</script>
-    <script src="../../dist/bootstrap/js/bootstrap.min.js"></script>
+    <script src="../bootstrap/js/bootstrap.min.js"></script>
     <!-- Just to make our placeholder images work. Don't actually copy the next line! -->
-    <script src="../../dist/bootstrap/assets/js/vendor/holder.min.js"></script>
+    <script src="../bootstrap/assets/js/vendor/holder.min.js"></script>
     <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
-    <script src="../../dist/bootstrap/assets/js/ie10-viewport-bug-workaround.js"></script>
+    <script src="../bootstrap/assets/js/ie10-viewport-bug-workaround.js"></script>
   </body>
 </html>
diff --git a/loleaflet/dist/admin/adminAnalytics.html b/loleaflet/dist/admin/adminAnalytics.html
new file mode 100644
index 0000000..5f4fb67
--- /dev/null
+++ b/loleaflet/dist/admin/adminAnalytics.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
+    <meta name="description" content="">
+    <meta name="author" content="">
+
+    <title>LibreOffice Online - Admin console</title>
+
+    <!-- Bootstrap core CSS -->
+    <link href="../bootstrap/css/bootstrap.min.css" rel="stylesheet">
+
+    <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
+    <link href="../bootstrap/assets/css/ie10-viewport-bug-workaround.css" rel="stylesheet">
+
+    <!-- Custom styles for this template -->
+    <link href="../bootstrap/dashboard.css" rel="stylesheet">
+
+    <link rel="stylesheet" href="../dialog/vex.css" />
+    <link rel="stylesheet" href="../dialog/vex-theme-plain.css" />
+
+    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
+    <!--[if lt IE 9]>
+      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
+      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
+    <![endif]-->
+  </head>
+
+  <body>
+    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
+    <script>window.jQuery || document.write('<script src="../bootstrap/assets/js/vendor/jquery.min.js"><\/script>')</script>
+    <script src="../dialog/vex.combined.min.js"></script>
+    <script>vex.defaultOptions.className = 'vex-theme-plain';</script>
+    <script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
+    <script src="admin-src.js"></script>
+    <script>
+
+	host = 'wss://' + window.location.hostname + ':9989';
+	new AdminSocketAnalytics(host);
+
+    </script>
+
+    <nav class="navbar navbar-inverse navbar-fixed-top">
+      <div class="container-fluid">
+        <div class="navbar-header">
+          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
+            <span class="sr-only">Toggle navigation</span>
+            <span class="icon-bar"></span>
+            <span class="icon-bar"></span>
+            <span class="icon-bar"></span>
+          </button>
+          <a class="navbar-brand" href="#">LibreOffice Online - Admin console</a>
+        </div>
+        <div id="navbar" class="navbar-collapse collapse">
+          <ul class="nav navbar-nav navbar-right">
+	    <li><a href="admin.html?host=ws://localhost:9989">Dashboard</a></li>
+            <li><a href="adminSettings.html?host=ws://localhost:9989">Settings</a></li>
+            <li><a href="#">Help</a></li>
+          </ul>
+          <form class="navbar-form navbar-right">
+            <input type="text" class="form-control" placeholder="Search...">
+          </form>
+        </div>
+      </div>
+    </nav>
+
+    <div class="container-fluid">
+      <div class="row">
+        <div class="col-sm-3 col-md-2 sidebar">
+          <ul class="nav nav-sidebar">
+            <li><a href="admin.html">Overview <span class="sr-only">(current)</span></a></li>
+            <li class="active"><a href="adminAnalytics.html">Analytics</a></li>
+          </ul>
+        </div>
+        <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
+          <h1 class="page-header">Graphs</h1>
+		        <div class="graph-container">
+		          <div class="jumbotron">
+		            <svg id="visualisation" width="1000" height="500"></svg>
+		          </div>
+		        </div>
+	      </div>
+      </div>
+    </div>
+
+    <!-- Bootstrap core JavaScript
+    ================================================== -->
+    <!-- Placed at the end of the document so the pages load faster -->
+    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
+    <script>window.jQuery || document.write('<script src="../bootstrap/js/vendor/jquery.min.js"><\/script>')</script>
+    <script src="../bootstrap/js/bootstrap.min.js"></script>
+    <!-- Just to make our placeholder images work. Don't actually copy the next line! -->
+    <script src="../bootstrap/assets/js/vendor/holder.min.js"></script>
+    <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
+    <script src="../bootstrap/assets/js/ie10-viewport-bug-workaround.js"></script>
+  </body>
+</html>
diff --git a/loleaflet/dist/admin/adminSettings.html b/loleaflet/dist/admin/adminSettings.html
new file mode 100644
index 0000000..1db8279
--- /dev/null
+++ b/loleaflet/dist/admin/adminSettings.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
+    <meta name="description" content="">
+    <meta name="author" content="">
+
+    <title>LibreOffice Online - Admin console</title>
+
+    <!-- Bootstrap core CSS -->
+    <link href="../bootstrap/css/bootstrap.min.css" rel="stylesheet">
+
+    <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
+    <link href="../bootstrap/assets/css/ie10-viewport-bug-workaround.css" rel="stylesheet">
+
+    <!-- Custom styles for this template -->
+    <link href="../bootstrap/dashboard.css" rel="stylesheet">
+
+    <link rel="stylesheet" href="../dialog/vex.css" />
+    <link rel="stylesheet" href="../dialog/vex-theme-plain.css" />
+
+    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
+    <!--[if lt IE 9]>
+      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
+      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
+    <![endif]-->
+  </head>
+
+  <body>
+    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
+    <script>window.jQuery || document.write('<script src="../bootstrap/assets/js/vendor/jquery.min.js"><\/script>')</script>
+    <script src="../dialog/vex.combined.min.js"></script>
+    <script>vex.defaultOptions.className = 'vex-theme-plain';</script>
+    <script src="admin-src.js"></script>
+    <script>
+
+	host = 'wss://' + window.location.hostname + ':9989';
+	new AdminSocketSettings(host);
+
+    </script>
+
+    <nav class="navbar navbar-inverse navbar-fixed-top">
+      <div class="container-fluid">
+        <div class="navbar-header">
+          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
+            <span class="sr-only">Toggle navigation</span>
+            <span class="icon-bar"></span>
+            <span class="icon-bar"></span>
+            <span class="icon-bar"></span>
+          </button>
+          <a class="navbar-brand" href="#">LibreOffice Online - Admin console</a>
+        </div>
+        <div id="navbar" class="navbar-collapse collapse">
+          <ul class="nav navbar-nav navbar-right">
+            <li><a href="admin.html">Dashboard</a></li>
+            <li><a href="adminSettings.html">Settings</a></li>
+            <li><a href="#">Help</a></li>
+          </ul>
+          <form class="navbar-form navbar-right">
+            <input type="text" class="form-control" placeholder="Search...">
+          </form>
+        </div>
+      </div>
+    </nav>
+
+    <div class="container-fluid">
+      <div class="row">
+        <div class="col-sm-3 col-md-2 sidebar">
+          <ul class="nav nav-sidebar">
+            <li><a href="admin.html">Overview <span class="sr-only">(current)</span></a></li>
+            <li><a href="adminAnalytics.html">Analytics</a></li>
+          </ul>
+        </div>
+        <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
+          <h1 class="page-header">Settings</h1>
+	  <form id="admin_settings">
+	    <label for="mem_stats_size">Memory Stats Cache size</label>
+	    <input type="text" id="mem_stats_size" name="Memory Stats Size"><br/>
+	    <label for="mem_stats_interval">Memory Stats Interval (in ms)</label>
+	    <input type="text" id="mem_stats_interval" name="Memory Stats Interval"><br/>
+
+	    <label for="cpu_stats_size">Cpu Stats Cache size</label>
+	    <input type="text" id="cpu_stats_size" name="Cpu Stats Size"><br/>
+	    <label for="cpu_stats_interval">Cpu Stats Interval (in ms)</label>
+	    <input type="text" id="cpu_stats_interval" name="Cpu Stats Interval"><br/>
+	    <input type="submit" value="Save"/><br/>
+	  </form>
+	</div>
+      </div>
+      </div>
+    </div>
+
+    <!-- Bootstrap core JavaScript
+    ================================================== -->
+    <!-- Placed at the end of the document so the pages load faster -->
+    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
+    <script>window.jQuery || document.write('<script src="../bootstrap/assets/js/vendor/jquery.min.js"><\/script>')</script>
+    <script src="../bootstrap/js/bootstrap.min.js"></script>
+    <!-- Just to make our placeholder images work. Don't actually copy the next line! -->
+    <script src="../bootstrap/assets/js/vendor/holder.min.js"></script>
+    <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
+    <script src="../bootstrap/assets/js/ie10-viewport-bug-workaround.js"></script>
+  </body>
+</html>
commit 06f1c874d6e11d54ee921945b565e19b95951b12
Author: Pranav Kant <pranavk at collabora.com>
Date:   Mon Mar 21 14:36:54 2016 +0530

    loolwsd: Use JWT authentication to access admin console
    
    File server serves the admin html file after successfull
    authentication, and sets the cookie in client which would be sent
    for all subsequent connections by client to connect to admin websocket.
    
    Change-Id: I0ee3bbfca7eefc428020d29612374410556b1e27

diff --git a/loolwsd/Admin.cpp b/loolwsd/Admin.cpp
index a1c11cc..0bf1718 100644
--- a/loolwsd/Admin.cpp
+++ b/loolwsd/Admin.cpp
@@ -12,15 +12,21 @@
 #include <sys/poll.h>
 #include <sys/prctl.h>
 
+#include <Poco/Net/HTTPCookie.h>
+#include <Poco/Net/HTTPBasicCredentials.h>
+#include <Poco/Net/HTTPRequest.h>
 #include <Poco/Net/HTTPRequestHandler.h>
 #include <Poco/Net/HTTPServerParams.h>
 #include <Poco/Net/HTTPServerRequest.h>
 #include <Poco/Net/HTTPServerResponse.h>
 #include <Poco/Net/NetException.h>
+#include <Poco/Net/SecureServerSocket.h>
 #include <Poco/Net/WebSocket.h>
 #include <Poco/StringTokenizer.h>
+#include <Poco/Util/ServerApplication.h>
 #include <Poco/Util/Timer.h>
 
+#include "Auth.hpp"
 #include "Admin.hpp"
 #include "AdminModel.hpp"
 #include "Common.hpp"
@@ -30,41 +36,31 @@
 
 using namespace LOOLProtocol;
 
+using Poco::StringTokenizer;
+using Poco::Net::HTTPBasicCredentials;
+using Poco::Net::HTTPCookie;
+using Poco::Net::HTTPRequest;
 using Poco::Net::HTTPRequestHandler;
 using Poco::Net::HTTPRequestHandlerFactory;
 using Poco::Net::HTTPResponse;
 using Poco::Net::HTTPServerParams;
 using Poco::Net::HTTPServerRequest;
 using Poco::Net::HTTPServerResponse;
+using Poco::Net::SecureServerSocket;
 using Poco::Net::ServerSocket;
 using Poco::Net::Socket;
 using Poco::Net::WebSocket;
 using Poco::Net::WebSocketException;
-using Poco::StringTokenizer;
+using Poco::Util::Application;
 
 /// Handle admin requests.
 class AdminRequestHandler: public HTTPRequestHandler
 {
-public:
-
-    AdminRequestHandler(Admin* adminManager)
-        : _admin(adminManager)
-    {    }
-
-    void handleRequest(HTTPServerRequest& request, HTTPServerResponse& response) override
+private:
+    void handleWSRequests(HTTPServerRequest& request, HTTPServerResponse& response, int nSessionId)
     {
-        assert(request.serverAddress().port() == ADMIN_PORT_NUMBER);
-
-        // Different session id pool for admin sessions (?)
-        const auto nSessionId = Util::decodeId(LOOLWSD::GenSessionId());
-        const std::string thread_name = "admin_ws_" + std::to_string(nSessionId);
         try
         {
-            if (prctl(PR_SET_NAME, reinterpret_cast<unsigned long>(thread_name.c_str()), 0, 0, 0) != 0)
-                Log::error("Cannot set thread name to " + thread_name + ".");
-
-            Log::debug("Thread [" + thread_name + "] started.");
-
             auto ws = std::make_shared<WebSocket>(request, response);
 
             {
@@ -313,11 +309,90 @@ public:
                 break;
             }
         }
+        catch (const Poco::Net::NotAuthenticatedException& exc)
+        {
+            Log::info("NotAuthenticatedException");
+            response.set("WWW-Authenticate", "Basic realm=\"ws-online\"");
+            response.setStatus(HTTPResponse::HTTP_UNAUTHORIZED);
+            response.setContentLength(0);
+            response.send();
+        }
         catch (const std::exception& exc)
         {
             Log::error(std::string("AdminRequestHandler::handleRequest: Exception: ") + exc.what());
         }
+    }
+
+public:
+
+    AdminRequestHandler(Admin* adminManager)
+        : _admin(adminManager)
+    {    }
+
+    void handleRequest(HTTPServerRequest& request, HTTPServerResponse& response) override
+    {
+        assert(request.serverAddress().port() == ADMIN_PORT_NUMBER);
+
+        // Different session id pool for admin sessions (?)
+        const auto nSessionId = Util::decodeId(LOOLWSD::GenSessionId());
+        const std::string thread_name = "admin_ws_" + std::to_string(nSessionId);
+
+        if (prctl(PR_SET_NAME, reinterpret_cast<unsigned long>(thread_name.c_str()), 0, 0, 0) != 0)
+                Log::error("Cannot set thread name to " + thread_name + ".");
 
+        Log::debug("Thread [" + thread_name + "] started.");
+
+        try
+        {
+            std::string requestURI = request.getURI();
+            StringTokenizer pathTokens(requestURI, "/", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
+
+            if (request.find("Upgrade") != request.end() && Poco::icompare(request["Upgrade"], "websocket") == 0)
+            {
+                if (request.find("Cookie") != request.end())
+                {
+                    // FIXME: Handle other cookie params like '; httponly; secure'
+                    const std::size_t pos = request["Cookie"].find_first_of("=");
+                    if (pos == std::string::npos)
+                        throw Poco::Net::NotAuthenticatedException("Missing JWT");
+
+                    const std::string jwtToken = request["Cookie"].substr(pos + 1);
+                    Log::info("Verifying JWT token: " + jwtToken);
+                    const std::string keyPath = Poco::Path(Application::instance().commandPath()).parent().toString() + SSL_KEY_FILE;
+                    JWTAuth authAgent(keyPath, "admin", "admin", "admin");
+                    if (authAgent.verify(jwtToken))
+                    {
+                        Log::trace("JWT token is valid");
+                        handleWSRequests(request, response, nSessionId);
+                    }
+                    else
+                    {
+                        Log::info("Invalid JWT token");
+                        throw Poco::Net::NotAuthenticatedException("Invalid Token");
+                    }
+                }
+                else
+                {
+                    Log::info("Missing authentication cookie. Handshake declined.");
+                    throw Poco::Net::NotAuthenticatedException("Missing token");
+                }
+            }
+        }
+        catch(const Poco::Net::NotAuthenticatedException& exc)
+        {
+            Log::info("Admin::NotAuthneticated");
+            response.set("WWW-Authenticate", "Basic realm=\"online\"");
+            response.setStatus(HTTPResponse::HTTP_UNAUTHORIZED);
+            response.setContentLength(0);
+            response.send();
+        }
+        catch (const std::exception& exc)
+        {
+            Log::info("Unknown Exception caught");
+            response.setStatusAndReason(HTTPResponse::HTTP_BAD_REQUEST);
+            response.setContentLength(0);
+            response.send();
+        }
         Log::debug("Thread [" + thread_name + "] finished.");
     }
 
@@ -355,7 +430,7 @@ private:
 
 /// An admin command processor.
 Admin::Admin(const Poco::Process::PID brokerPid, const int brokerPipe, const int notifyPipe) :
-    _srv(new AdminRequestHandlerFactory(this), ServerSocket(ADMIN_PORT_NUMBER), new HTTPServerParams),
+    _srv(new AdminRequestHandlerFactory(this), SecureServerSocket(ADMIN_PORT_NUMBER), new HTTPServerParams),
     _model(AdminModel())
 {
     Admin::BrokerPid = brokerPid;
commit 4699e98c302559a191fe45df1839bcf972426250
Author: Pranav Kant <pranavk at collabora.com>
Date:   Mon Mar 21 14:10:10 2016 +0530

    loolwsd: FileServer class to serve static contents
    
    Only purpose, at the moment, is to create
    Poco::HTTPRequestHandler which would be passed on the serving
    handling the static file requests.
    
    Change-Id: I97c3fc0c73da077d3efee919416098b880c9c2ad

diff --git a/loolwsd/FileServer.hpp b/loolwsd/FileServer.hpp
new file mode 100644
index 0000000..9c62e41
--- /dev/null
+++ b/loolwsd/FileServer.hpp
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef INCLUDED_FILE_SERVER_HPP
+#define INCLUDED_FILE_SERVER_HPP
+
+#include <string>
+#include <vector>
+
+#include <Poco/Net/NetException.h>
+
+#include <Poco/Net/HTTPCookie.h>
+#include <Poco/Net/HTTPBasicCredentials.h>
+#include <Poco/Net/HTTPRequest.h>
+#include <Poco/Net/HTTPRequestHandler.h>
+#include <Poco/Net/HTTPServer.h>
+#include <Poco/Net/HTTPServerParams.h>
+#include <Poco/Net/HTTPServerRequest.h>
+#include <Poco/Net/HTTPServerResponse.h>
+#include <Poco/Net/SecureServerSocket.h>
+#include <Poco/Net/WebSocket.h>
+#include <Poco/Runnable.h>
+#include <Poco/StringTokenizer.h>
+#include <Poco/URI.h>
+#include <Poco/Util/ServerApplication.h>
+#include <Poco/Util/Timer.h>
+
+#include "Common.hpp"
+
+using Poco::Net::HTTPRequest;
+using Poco::Net::HTTPRequestHandler;
+using Poco::Net::HTTPRequestHandlerFactory;
+using Poco::Net::HTTPResponse;
+using Poco::Net::HTTPServerParams;
+using Poco::Net::HTTPServerRequest;
+using Poco::Net::HTTPServerResponse;
+using Poco::Net::SecureServerSocket;
+using Poco::Net::HTTPBasicCredentials;
+using Poco::Util::Application;
+
+class FileServerRequestHandler: public HTTPRequestHandler
+{
+public:
+    void handleRequest(HTTPServerRequest& request, HTTPServerResponse& response) override
+    {
+        try
+        {
+            Poco::URI requestUri(request.getURI());
+            std::vector<std::string> requestSegments;
+            requestUri.getPathSegments(requestSegments);
+
+            // FIXME: We might want to package all dist files from leaflet to some other dir (?)
+            const std::string loleafletPath = Poco::Path(Application::instance().commandPath()).parent().parent().toString() + "loleaflet";
+            const std::string endPoint = requestSegments[requestSegments.size() - 1];
+
+            if (request.getMethod() == HTTPRequest::HTTP_GET)
+            {

... etc. - the rest is truncated


More information about the Libreoffice-commits mailing list