add http error pages (#381)

* implement 404 for webserver - this is a quick hack, should be refactored later

* add http error pages ... design is more a placebo ;-)

* tune errorpages
fix some cgi related stuff, now only python is possible
executing and reading python file is possilbe, but it cannot receive any data from webui

* fix typo

* fix another typo
This commit is contained in:
redPanther 2017-01-29 21:20:12 +01:00 committed by GitHub
parent c43c7e3fcd
commit 7f2d6bde9a
15 changed files with 161 additions and 79 deletions

View File

@ -0,0 +1,4 @@
<div class="panel panel-danger">
<div class="panel-heading">403 Forbidden</div>
<div class="panel-body">{MESSAGE}</div>
</div>

View File

@ -0,0 +1,4 @@
<div class="panel panel-danger">
<div class="panel-heading">404 Page not found</div>
<div class="panel-body">{MESSAGE}</div>
</div>

View File

@ -0,0 +1,4 @@
<div class="panel panel-danger">
<div class="panel-heading">405 Method not allowed</div>
<div class="panel-body">{MESSAGE}</div>
</div>

View File

@ -0,0 +1,4 @@
<div class="panel panel-danger">
<div class="panel-heading">500 Internal server error</div>
<div class="panel-body">{MESSAGE}</div>
</div>

View File

@ -0,0 +1,4 @@
</div> <!-- page -->
</body>
</html>

View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<title>Hyperion WebServer - Error</title>
<!-- Bootstrap Core CSS -->
<link href="/css/bootstrap.css" rel="stylesheet">
<!-- Flags -->
<link href="/css/flag-icon.min.css" rel="stylesheet">
<!-- MetisMenu CSS -->
<link href="/css/metisMenu.css" rel="stylesheet">
<!-- Custom CSS -->
<link href="/css/sb-admin-2.css" rel="stylesheet">
<link href="/css/hyperion.css" rel="stylesheet">
<!-- Custom Fonts -->
<link href="/css/font-awesome.min.css" rel="stylesheet" type="text/css">
<style>
body {margin:auto; padding-top:50px; }
#footer { margin-top:10px; font-size:0.5em; }
#page { }
.panel-heading { font-weight:bold; font-size: 1.3em; }
</style>
</head>
<body>
<div id="page" class="container">

View File

@ -2,55 +2,54 @@
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0' name='viewport' /> <meta content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0' name='viewport' />
<meta name="description" content=""> <meta name="description" content="">
<title data-i18n="general_webui_title">Hyperion - WebUI</title> <title data-i18n="general_webui_title">Hyperion - Error</title>
<!-- jQuery --> <!-- jQuery -->
<script src="js/lib/jquery.min.js"></script> <script src="/js/lib/jquery.min.js"></script>
<!-- Hyperion --> <!-- Hyperion -->
<script src="js/hyperion.js"></script> <script src="/js/hyperion.js"></script>
<script src="js/ui_utils.js"></script> <script src="/js/ui_utils.js"></script>
<!-- textarea --> <!-- textarea -->
<script src="js/lib/jquery-linedtextarea.js"></script> <script src="/js/lib/jquery-linedtextarea.js"></script>
<link href="css/jquery-linedtextarea.css" type="text/css" rel="stylesheet" /> <link href="/css/jquery-linedtextarea.css" type="text/css" rel="stylesheet" />
<!-- Colorpicker --> <!-- Colorpicker -->
<script src="js/lib/bootstrap-colorpicker.min.js"></script> <script src="/js/lib/bootstrap-colorpicker.min.js"></script>
<link href="css/bootstrap-colorpicker.min.css" rel="stylesheet"> <link href="/css/bootstrap-colorpicker.min.css" rel="stylesheet">
<!-- JSONEditor --> <!-- JSONEditor -->
<script src="js/lib/jsoneditor.js"></script> <script src="/js/lib/jsoneditor.js"></script>
<!--Language Support --> <!--Language Support -->
<script src="js/lib/jquery.i18n.js"></script> <script src="/js/lib/jquery.i18n.js"></script>
<script src="js/lib/jquery.i18n.messagestore.js"></script> <script src="/js/lib/jquery.i18n.messagestore.js"></script>
<script src="js/lib/jquery.i18n.fallbacks.js"></script> <script src="/js/lib/jquery.i18n.fallbacks.js"></script>
<script src="js/lib/jquery.i18n.parser.js"></script> <script src="/js/lib/jquery.i18n.parser.js"></script>
<script src="js/lib/jquery.i18n.emitter.js"></script> <script src="/js/lib/jquery.i18n.emitter.js"></script>
<script src="js/lib/jquery.i18n.language.js"></script> <script src="/js/lib/jquery.i18n.language.js"></script>
<!-- Bootstrap Core CSS --> <!-- Bootstrap Core CSS -->
<link href="css/bootstrap.css" rel="stylesheet"> <link href="/css/bootstrap.css" rel="stylesheet">
<!-- Flags --> <!-- Flags -->
<link href="css/flag-icon.min.css" rel="stylesheet"> <link href="/css/flag-icon.min.css" rel="stylesheet">
<!-- MetisMenu CSS --> <!-- MetisMenu CSS -->
<link href="css/metisMenu.css" rel="stylesheet"> <link href="/css/metisMenu.css" rel="stylesheet">
<!-- Custom CSS --> <!-- Custom CSS -->
<link href="css/sb-admin-2.css" rel="stylesheet"> <link href="/css/sb-admin-2.css" rel="stylesheet">
<link href="css/hyperion.css" rel="stylesheet"> <link href="/css/hyperion.css" rel="stylesheet">
<!-- Custom Fonts --> <!-- Custom Fonts -->
<link href="css/font-awesome.min.css" rel="stylesheet" type="text/css"> <link href="/css/font-awesome.min.css" rel="stylesheet" type="text/css">
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries --> <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
@ -58,8 +57,6 @@
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script> <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script> <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]--> <![endif]-->
</head> </head>
<body> <body>

View File

@ -1,5 +0,0 @@
#!/bin/sh
echo "hello world"
pwd

View File

@ -1,10 +1,11 @@
#pragma once #pragma once
#include <string> #include <QByteArray>
#include <QString>
namespace Process { namespace Process {
void restartHyperion(bool asNewProcess=false); void restartHyperion(bool asNewProcess=false);
std::string command_exec(const char* cmd); QByteArray command_exec(QString cmd, QByteArray data="");
}; };

View File

@ -3,6 +3,7 @@
#include <QCoreApplication> #include <QCoreApplication>
#include <QStringList> #include <QStringList>
#include <string>
#include <unistd.h> #include <unistd.h>
#include <cstdio> #include <cstdio>
@ -37,11 +38,12 @@ void restartHyperion(bool asNewProcess)
Error(log, "error while restarting hyperion"); Error(log, "error while restarting hyperion");
} }
std::string command_exec(const char* cmd) QByteArray command_exec(QString cmd, QByteArray data)
{ {
char buffer[128]; char buffer[128];
std::string result = ""; std::string result = "";
std::shared_ptr<FILE> pipe(popen(cmd, "r"), pclose);
std::shared_ptr<FILE> pipe(popen(cmd.toLocal8Bit().constData(), "r"), pclose);
if (pipe) if (pipe)
{ {
while (!feof(pipe.get())) while (!feof(pipe.get()))
@ -50,8 +52,7 @@ std::string command_exec(const char* cmd)
result += buffer; result += buffer;
} }
} }
return result.c_str();
return result;
} }
}; };

View File

@ -5,6 +5,7 @@
#include <QStringList> #include <QStringList>
#include <QJsonObject> #include <QJsonObject>
#include <QJsonDocument> #include <QJsonDocument>
#include <QProcess>
#include "CgiHandler.h" #include "CgiHandler.h"
#include "QtHttpHeader.h" #include "QtHttpHeader.h"
@ -18,6 +19,7 @@ CgiHandler::CgiHandler (Hyperion * hyperion, QString baseUrl, QObject * parent)
, _args(QStringList()) , _args(QStringList())
, _hyperionConfig(_hyperion->getQJsonConfig()) , _hyperionConfig(_hyperion->getQJsonConfig())
, _baseUrl(baseUrl) , _baseUrl(baseUrl)
, _log(Logger::getInstance("WEBSERVER"))
{ {
} }
@ -123,22 +125,26 @@ void CgiHandler::cmd_runscript()
// relative path not allowed // relative path not allowed
if (scriptFilePath.indexOf("..") >=0) if (scriptFilePath.indexOf("..") >=0)
{ {
Error( _log, "relative path not allowed (%s)", scriptFilePath.toStdString().c_str());
throw 1; throw 1;
} }
scriptFilePath = _baseUrl+"/server_scripts/"+scriptFilePath; scriptFilePath = _baseUrl+"/server_scripts/"+scriptFilePath;
QString interpreter = "";
if (scriptFilePath.endsWith(".sh")) interpreter = "sh";
if (scriptFilePath.endsWith(".py")) interpreter = "python";
if (QFile::exists(scriptFilePath) && !interpreter.isEmpty()) if (QFile::exists(scriptFilePath) && scriptFilePath.endsWith(".py") )
{ {
QByteArray data = Process::command_exec(QString(interpreter + " " + scriptFilePath).toUtf8().constData()).c_str(); QtHttpPostData postData = _request->getPostData();
QByteArray inputData; // should be filled with post data
QByteArray data = Process::command_exec("python " + scriptFilePath, inputData);
_reply->addHeader ("Content-Type", "text/plain"); _reply->addHeader ("Content-Type", "text/plain");
_reply->appendRawData (data); _reply->appendRawData (data);
throw 0; throw 0;
} }
else
{
Error( _log, "script %s doesn't exists or is no python file", scriptFilePath.toStdString().c_str());
}
throw 1; throw 1;
} }
} }

View File

@ -6,6 +6,7 @@
#include <QStringList> #include <QStringList>
#include <hyperion/Hyperion.h> #include <hyperion/Hyperion.h>
#include <utils/Logger.h>
#include "QtHttpReply.h" #include "QtHttpReply.h"
#include "QtHttpRequest.h" #include "QtHttpRequest.h"
@ -32,6 +33,7 @@ private:
QStringList _args; QStringList _args;
const QJsonObject & _hyperionConfig; const QJsonObject & _hyperionConfig;
const QString _baseUrl; const QString _baseUrl;
Logger * _log;
}; };
#endif // CGIHANDLER_H #endif // CGIHANDLER_H

View File

@ -11,7 +11,7 @@
#include <QHostInfo> #include <QHostInfo>
#include <bonjour/bonjourserviceregister.h> #include <bonjour/bonjourserviceregister.h>
#include <bonjour/bonjourrecord.h> #include <bonjour/bonjourrecord.h>
#include <exception>
StaticFileServing::StaticFileServing (Hyperion *hyperion, QString baseUrl, quint16 port, QObject * parent) StaticFileServing::StaticFileServing (Hyperion *hyperion, QString baseUrl, quint16 port, QObject * parent)
: QObject (parent) : QObject (parent)
@ -45,10 +45,7 @@ void StaticFileServing::onServerStarted (quint16 port)
{ {
Info(_log, "started on port %d name \"%s\"", port ,_server->getServerName().toStdString().c_str()); Info(_log, "started on port %d name \"%s\"", port ,_server->getServerName().toStdString().c_str());
const std::string mDNSDescr = ( _server->getServerName().toStdString() const std::string mDNSDescr = (_server->getServerName().toStdString() + "@" + QHostInfo::localHostName().toStdString());
+ "@" +
QHostInfo::localHostName().toStdString()
);
BonjourServiceRegister *bonjourRegister_http = new BonjourServiceRegister(); BonjourServiceRegister *bonjourRegister_http = new BonjourServiceRegister();
bonjourRegister_http->registerService( bonjourRegister_http->registerService(
@ -67,17 +64,39 @@ void StaticFileServing::onServerError (QString msg)
Error(_log, "%s", msg.toStdString().c_str()); Error(_log, "%s", msg.toStdString().c_str());
} }
static inline void printErrorToReply (QtHttpReply * reply, QString errorMessage) void StaticFileServing::printErrorToReply (QtHttpReply * reply, QtHttpReply::StatusCode code, QString errorMessage)
{ {
reply->addHeader ("Content-Type", QByteArrayLiteral ("text/plain")); reply->setStatusCode(code);
reply->appendRawData (errorMessage.toLocal8Bit ()); reply->addHeader ("Content-Type", QByteArrayLiteral ("text/html"));
QFile errorPageHeader(_baseUrl % "/errorpages/header.html" );
QFile errorPageFooter(_baseUrl % "/errorpages/footer.html" );
QFile errorPage (_baseUrl % "/errorpages/" % QString::number((int)code) % ".html" );
if (errorPageHeader.open (QFile::ReadOnly))
{
QByteArray data = errorPageHeader.readAll();
reply->appendRawData (data);
errorPageHeader.close ();
} }
static inline void printError404ToReply (QtHttpReply * reply, QString errorMessage) if (errorPage.open (QFile::ReadOnly))
{ {
reply->setStatusCode(QtHttpReply::NotFound); QByteArray data = errorPage.readAll();
reply->addHeader ("Content-Type", QByteArrayLiteral ("text/plain")); data = data.replace("{MESSAGE}", errorMessage.toLocal8Bit() );
reply->appendRawData (errorMessage.toLocal8Bit ()); reply->appendRawData (data);
errorPage.close ();
}
else
{
reply->appendRawData (QString(QString::number(code) + " - " +errorMessage).toLocal8Bit());
}
if (errorPageFooter.open (QFile::ReadOnly))
{
QByteArray data = errorPageFooter.readAll ();
reply->appendRawData (data);
errorPageFooter.close ();
}
} }
void StaticFileServing::onRequestNeedsReply (QtHttpRequest * request, QtHttpReply * reply) void StaticFileServing::onRequestNeedsReply (QtHttpRequest * request, QtHttpReply * reply)
@ -94,16 +113,17 @@ void StaticFileServing::onRequestNeedsReply (QtHttpRequest * request, QtHttpRepl
uri_parts.removeAt(0); uri_parts.removeAt(0);
try try
{ {
if (command == QStringLiteral ("POST"))
{
QString postData = request->getRawData();
uri_parts.append(postData.split('&', QString::SkipEmptyParts));
}
_cgi.exec(uri_parts, request, reply); _cgi.exec(uri_parts, request, reply);
} }
catch(...) catch(int err)
{ {
printErrorToReply (reply, "script failed (" % path % ")"); Error(_log,"Exception while executing cgi %s : %d", path.toStdString().c_str(), err);
printErrorToReply (reply, QtHttpReply::InternalError, "script failed (" % path % ")");
}
catch(std::exception &e)
{
Error(_log,"Exception while executing cgi %s : %s", path.toStdString().c_str(), e.what());
printErrorToReply (reply, QtHttpReply::InternalError, "script failed (" % path % ")");
} }
return; return;
} }
@ -136,17 +156,17 @@ void StaticFileServing::onRequestNeedsReply (QtHttpRequest * request, QtHttpRepl
} }
else else
{ {
printErrorToReply (reply, "Requested file " % path % " couldn't be open for reading !"); printErrorToReply (reply, QtHttpReply::Forbidden ,"Requested file: " % path);
} }
} }
else else
{ {
printError404ToReply (reply, "404 Not Found\n" % path % " couldn't be found !"); printErrorToReply (reply, QtHttpReply::NotFound, "Requested file: " % path);
} }
} }
else else
{ {
printErrorToReply (reply, "Unhandled HTTP/1.1 method " % command % " on web server !"); printErrorToReply (reply, QtHttpReply::MethodNotAllowed,"Unhandled HTTP/1.1 method " % command);
} }
} }

View File

@ -35,6 +35,8 @@ private:
CgiHandler _cgi; CgiHandler _cgi;
Logger * _log; Logger * _log;
void printErrorToReply (QtHttpReply * reply, QtHttpReply::StatusCode code, QString errorMessage);
}; };
#endif // STATICFILESERVING_H #endif // STATICFILESERVING_H

View File

@ -33,6 +33,12 @@ using namespace commandline;
void signal_handler(const int signum) void signal_handler(const int signum)
{ {
if(signum == SIGCHLD)
{
// only quit when a registered child process is gone
// currently this feature is not active ...
return;
}
QCoreApplication::quit(); QCoreApplication::quit();
// reset signal handler to default (in case this handler is not capable of stopping) // reset signal handler to default (in case this handler is not capable of stopping)
@ -42,7 +48,8 @@ void signal_handler(const int signum)
void startNewHyperion(int parentPid, std::string hyperionFile, std::string configFile) void startNewHyperion(int parentPid, std::string hyperionFile, std::string configFile)
{ {
if ( fork() == 0 ) pid_t childPid = fork(); // child pid should store elsewhere for later use
if ( childPid == 0 )
{ {
sleep(3); sleep(3);
execl(hyperionFile.c_str(), hyperionFile.c_str(), "--parent", QString::number(parentPid).toStdString().c_str(), configFile.c_str(), NULL); execl(hyperionFile.c_str(), hyperionFile.c_str(), "--parent", QString::number(parentPid).toStdString().c_str(), configFile.c_str(), NULL);
@ -62,6 +69,7 @@ int main(int argc, char** argv)
signal(SIGINT, signal_handler); signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler); signal(SIGTERM, signal_handler);
signal(SIGABRT, signal_handler);
signal(SIGCHLD, signal_handler); signal(SIGCHLD, signal_handler);
signal(SIGPIPE, signal_handler); signal(SIGPIPE, signal_handler);