#include "THttpServer.h"
#include "TTimer.h"
#include "TSystem.h"
#include "TImage.h"
#include "TROOT.h"
#include "TClass.h"
#include "TFolder.h"
#include "RVersion.h"
#include "RConfigure.h"
#include "THttpEngine.h"
#include "TRootSniffer.h"
#include "TRootSnifferStore.h"
#include <string>
#include <cstdlib>
#include <stdlib.h>
#include <string.h>
#include <fstream>
class THttpTimer : public TTimer {
public:
THttpServer *fServer;
THttpTimer(Long_t milliSec, Bool_t mode, THttpServer *serv) :
TTimer(milliSec, mode), fServer(serv)
{
}
virtual ~THttpTimer()
{
}
virtual void Timeout()
{
if (fServer) fServer->ProcessRequests();
}
};
THttpServer::THttpServer(const char *engine) :
TNamed("http", "ROOT http server"),
fEngines(),
fTimer(0),
fSniffer(0),
fMainThrdId(0),
fJSROOTSYS(),
fTopName("ROOT"),
fJSROOT(),
fLocations(),
fDefaultPage(),
fDefaultPageCont(),
fDrawPage(),
fDrawPageCont(),
fMutex(),
fCallArgs()
{
fLocations.SetOwner(kTRUE);
#ifdef COMPILED_WITH_DABC
const char *dabcsys = gSystem->Getenv("DABCSYS");
if (dabcsys != 0)
fJSROOTSYS = TString::Format("%s/plugins/root/js", dabcsys);
#endif
const char *jsrootsys = gSystem->Getenv("JSROOTSYS");
if (jsrootsys != 0) fJSROOTSYS = jsrootsys;
if (fJSROOTSYS.Length() == 0) {
#ifdef ROOTETCDIR
TString jsdir = TString::Format("%s/http", ROOTETCDIR);
#else
TString jsdir("$(ROOTSYS)/etc/http");
#endif
if (gSystem->ExpandPathName(jsdir)) {
Warning("THttpServer", "problems resolving '%s', use JSROOTSYS to specify $ROOTSYS/etc/http location", jsdir.Data());
fJSROOTSYS = ".";
} else {
fJSROOTSYS = jsdir;
}
}
AddLocation("currentdir/", ".");
AddLocation("jsrootsys/", fJSROOTSYS);
const char *rootsys = gSystem->Getenv("ROOTSYS");
if (rootsys != 0) {
AddLocation("rootsys/", rootsys);
} else {
#ifdef ROOTPREFIX
TString sysdir = ROOTPREFIX;
#else
TString sysdir = "$(ROOTSYS)";
#endif
if (!gSystem->ExpandPathName(sysdir)) AddLocation("rootsys/", sysdir);
}
fDefaultPage = fJSROOTSYS + "/files/online.htm";
fDrawPage = fJSROOTSYS + "/files/draw.htm";
SetSniffer(new TRootSniffer("sniff"));
SetTimer(20, kTRUE);
if (strchr(engine, ';') == 0) {
CreateEngine(engine);
} else {
TObjArray *lst = TString(engine).Tokenize(";");
for (Int_t n = 0; n <= lst->GetLast(); n++) {
const char *opt = lst->At(n)->GetName();
if ((strcmp(opt, "readonly") == 0) || (strcmp(opt, "ro") == 0)) {
GetSniffer()->SetReadOnly(kTRUE);
} else if ((strcmp(opt, "readwrite") == 0) || (strcmp(opt, "rw") == 0)) {
GetSniffer()->SetReadOnly(kFALSE);
} else
CreateEngine(opt);
}
delete lst;
}
}
THttpServer::~THttpServer()
{
fEngines.Delete();
SetSniffer(0);
SetTimer(0);
}
void THttpServer::SetSniffer(TRootSniffer *sniff)
{
if (fSniffer) delete fSniffer;
fSniffer = sniff;
}
Bool_t THttpServer::IsReadOnly() const
{
return fSniffer ? fSniffer->IsReadOnly() : kTRUE;
}
void THttpServer::SetReadOnly(Bool_t readonly)
{
if (fSniffer) fSniffer->SetReadOnly(readonly);
}
void THttpServer::AddLocation(const char *prefix, const char *path)
{
if ((prefix==0) || (*prefix==0)) return;
TNamed *obj = dynamic_cast<TNamed*> (fLocations.FindObject(prefix));
if (obj != 0) {
obj->SetTitle(path);
} else {
fLocations.Add(new TNamed(prefix, path));
}
}
void THttpServer::SetJSROOT(const char* location)
{
fJSROOT = location ? location : "";
}
void THttpServer::SetDefaultPage(const char* filename)
{
if ((filename!=0) && (*filename!=0))
fDefaultPage = filename;
else
fDefaultPage = fJSROOTSYS + "/files/online.htm";
fDefaultPageCont.Clear();
}
void THttpServer::SetDrawPage(const char* filename)
{
if ((filename!=0) && (*filename!=0))
fDrawPage = filename;
else
fDrawPage = fJSROOTSYS + "/files/draw.htm";
fDrawPageCont.Clear();
}
Bool_t THttpServer::CreateEngine(const char *engine)
{
if (engine == 0) return kFALSE;
const char *arg = strchr(engine, ':');
if (arg == 0) return kFALSE;
TString clname;
if (arg != engine) clname.Append(engine, arg - engine);
if ((clname.Length() == 0) || (clname == "http") || (clname == "civetweb"))
clname = "TCivetweb";
else if (clname == "fastcgi")
clname = "TFastCgi";
else if (clname == "dabc")
clname = "TDabcEngine";
TClass *engine_class = gROOT->LoadClass(clname.Data());
if (engine_class == 0) return kFALSE;
THttpEngine *eng = (THttpEngine *) engine_class->New();
if (eng == 0) return kFALSE;
eng->SetServer(this);
if (!eng->Create(arg + 1)) {
delete eng;
return kFALSE;
}
fEngines.Add(eng);
return kTRUE;
}
void THttpServer::SetTimer(Long_t milliSec, Bool_t mode)
{
if (fTimer) {
fTimer->Stop();
delete fTimer;
fTimer = 0;
}
if (milliSec > 0) {
fTimer = new THttpTimer(milliSec, mode, this);
fTimer->TurnOn();
}
}
Bool_t THttpServer::VerifyFilePath(const char *fname)
{
if ((fname == 0) || (*fname == 0)) return kFALSE;
Int_t level = 0;
while (*fname != 0) {
const char *next = strpbrk(fname, "/\\");
if (next == 0) return kTRUE;
if ((next == fname + 2) && (*fname == '.') && (*(fname + 1) == '.')) {
fname += 3;
level--;
if (level < 0) return kFALSE;
continue;
}
if ((next == fname + 1) && (*fname == '.')) {
fname += 2;
continue;
}
if (next == fname) {
fname ++;
continue;
}
fname = next + 1;
level++;
}
return kTRUE;
}
Bool_t THttpServer::IsFileRequested(const char *uri, TString &res) const
{
if ((uri == 0) || (strlen(uri) == 0)) return kFALSE;
TString fname = uri;
TIter iter(&fLocations);
TObject *obj(0);
while ((obj=iter()) != 0) {
Ssiz_t pos = fname.Index(obj->GetName());
if (pos == kNPOS) continue;
fname.Remove(0, pos + (strlen(obj->GetName()) - 1));
if (!VerifyFilePath(fname.Data())) return kFALSE;
res = obj->GetTitle();
if ((fname[0]=='/') && (res[res.Length()-1]=='/')) res.Resize(res.Length()-1);
res.Append(fname);
return kTRUE;
}
return kFALSE;
}
Bool_t THttpServer::ExecuteHttp(THttpCallArg *arg)
{
if ((fMainThrdId!=0) && (fMainThrdId == TThread::SelfId())) {
ProcessRequest(arg);
return kTRUE;
}
fMutex.Lock();
fCallArgs.Add(arg);
fMutex.UnLock();
arg->fCond.Wait();
return kTRUE;
}
void THttpServer::ProcessRequests()
{
if (fMainThrdId==0) fMainThrdId = TThread::SelfId();
if (fMainThrdId != TThread::SelfId()) {
Error("ProcessRequests", "Should be called only from main ROOT thread");
return;
}
while (true) {
THttpCallArg *arg = 0;
fMutex.Lock();
if (fCallArgs.GetSize() > 0) {
arg = (THttpCallArg *) fCallArgs.First();
fCallArgs.RemoveFirst();
}
fMutex.UnLock();
if (arg == 0) break;
fSniffer->SetCurrentCallArg(arg);
try {
ProcessRequest(arg);
fSniffer->SetCurrentCallArg(0);
} catch (...) {
fSniffer->SetCurrentCallArg(0);
}
arg->fCond.Signal();
}
TIter iter(&fEngines);
THttpEngine *engine = 0;
while ((engine = (THttpEngine *)iter()) != 0)
engine->Process();
}
void THttpServer::ProcessRequest(THttpCallArg *arg)
{
if (arg->fFileName.IsNull() || (arg->fFileName == "index.htm")) {
if (fDefaultPageCont.Length() == 0) {
Int_t len = 0;
char *buf = ReadFileContent(fDefaultPage.Data(), len);
if (len > 0) fDefaultPageCont.Append(buf, len);
delete buf;
}
if (fDefaultPageCont.Length() == 0) {
arg->Set404();
} else {
arg->fContent = fDefaultPageCont;
if (fJSROOT.Length() > 0) {
TString repl = TString("=\"") + fJSROOT;
if (!repl.EndsWith("/")) repl+="/";
arg->fContent.ReplaceAll("=\"jsrootsys/", repl);
}
const char *hjsontag = "\"$$$h.json$$$\"";
if (arg->fContent.Index(hjsontag) != kNPOS) {
TString h_json;
TRootSnifferStoreJson store(h_json, kTRUE);
const char *topname = fTopName.Data();
if (arg->fTopName.Length() > 0) topname = arg->fTopName.Data();
fSniffer->ScanHierarchy(topname, arg->fPathName.Data(), &store);
arg->fContent.ReplaceAll(hjsontag, h_json);
arg->AddHeader("Cache-Control", "private, no-cache, no-store, must-revalidate, max-age=0, proxy-revalidate, s-maxage=0");
if (arg->fQuery.Index("nozip") == kNPOS) arg->SetZipping(2);
}
arg->SetContentType("text/html");
}
return;
}
if (arg->fFileName == "draw.htm") {
if (fDrawPageCont.Length() == 0) {
Int_t len = 0;
char *buf = ReadFileContent(fDrawPage.Data(), len);
if (len > 0) fDrawPageCont.Append(buf, len);
delete buf;
}
if (fDrawPageCont.Length() == 0) {
arg->Set404();
} else {
const char *rootjsontag = "\"$$$root.json$$$\"";
const char *hjsontag = "\"$$$h.json$$$\"";
arg->fContent = fDrawPageCont;
if (fJSROOT.Length() > 0) {
TString repl = TString("=\"") + fJSROOT;
if (!repl.EndsWith("/")) repl+="/";
arg->fContent.ReplaceAll("=\"jsrootsys/", repl);
}
if (arg->fContent.Index(hjsontag) != kNPOS) {
TString h_json;
TRootSnifferStoreJson store(h_json, kTRUE);
const char *topname = fTopName.Data();
if (arg->fTopName.Length() > 0) topname = arg->fTopName.Data();
fSniffer->ScanHierarchy(topname, arg->fPathName.Data(), &store, kTRUE);
arg->fContent.ReplaceAll(hjsontag, h_json);
}
if (arg->fContent.Index(rootjsontag) != kNPOS) {
TString str;
void *bindata = 0;
Long_t bindatalen = 0;
if (fSniffer->Produce(arg->fPathName.Data(), "root.json", "compact=3", bindata, bindatalen, str)) {
arg->fContent.ReplaceAll(rootjsontag, str);
}
}
arg->AddHeader("Cache-Control", "private, no-cache, no-store, must-revalidate, max-age=0, proxy-revalidate, s-maxage=0");
if (arg->fQuery.Index("nozip") == kNPOS) arg->SetZipping(2);
arg->SetContentType("text/html");
}
return;
}
TString filename;
if (IsFileRequested(arg->fFileName.Data(), filename)) {
arg->SetFile(filename);
return;
}
filename = arg->fFileName;
Bool_t iszip = kFALSE;
if (filename.EndsWith(".gz")) {
filename.Resize(filename.Length() - 3);
iszip = kTRUE;
}
void* bindata(0);
Long_t bindatalen(0);
if ((filename == "h.xml") || (filename == "get.xml")) {
Bool_t compact = arg->fQuery.Index("compact") != kNPOS;
arg->fContent.Form("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
if (!compact) arg->fContent.Append("\n");
arg->fContent.Append("<root>");
if (!compact) arg->fContent.Append("\n");
{
TRootSnifferStoreXml store(arg->fContent, compact);
const char *topname = fTopName.Data();
if (arg->fTopName.Length() > 0) topname = arg->fTopName.Data();
fSniffer->ScanHierarchy(topname, arg->fPathName.Data(), &store, filename == "get.xml");
}
arg->fContent.Append("</root>");
if (!compact) arg->fContent.Append("\n");
arg->SetXml();
} else
if (filename == "h.json") {
TRootSnifferStoreJson store(arg->fContent, arg->fQuery.Index("compact") != kNPOS);
const char *topname = fTopName.Data();
if (arg->fTopName.Length() > 0) topname = arg->fTopName.Data();
fSniffer->ScanHierarchy(topname, arg->fPathName.Data(), &store);
arg->SetJson();
} else
if (fSniffer->Produce(arg->fPathName.Data(), filename.Data(), arg->fQuery.Data(), bindata, bindatalen, arg->fContent)) {
if (bindata != 0) arg->SetBinData(bindata, bindatalen);
arg->SetContentType(GetMimeType(filename.Data()));
} else {
arg->Set404();
}
if (arg->Is404()) return;
if (iszip) arg->SetZipping(3);
if (filename == "root.bin") {
const char *parname = fSniffer->IsStreamerInfoItem(arg->fPathName.Data()) ? "BVersion" : "MVersion";
arg->AddHeader(parname, Form("%u", (unsigned) fSniffer->GetStreamerInfoHash()));
}
arg->AddHeader("Cache-Control", "private, no-cache, no-store, must-revalidate, max-age=0, proxy-revalidate, s-maxage=0");
}
Bool_t THttpServer::Register(const char *subfolder, TObject *obj)
{
return fSniffer->RegisterObject(subfolder, obj);
}
Bool_t THttpServer::Unregister(TObject *obj)
{
return fSniffer->UnregisterObject(obj);
}
void THttpServer::Restrict(const char *path, const char* options)
{
fSniffer->Restrict(path, options);
}
Bool_t THttpServer::RegisterCommand(const char *cmdname, const char *method, const char *icon)
{
return fSniffer->RegisterCommand(cmdname, method, icon);
}
Bool_t THttpServer::Hide(const char *foldername, Bool_t hide)
{
return SetItemField(foldername, "_hidden", hide ? "true" : (const char *) 0);
}
Bool_t THttpServer::SetIcon(const char *fullname, const char *iconname)
{
return SetItemField(fullname, "_icon", iconname);
}
Bool_t THttpServer::CreateItem(const char *fullname, const char *title)
{
return fSniffer->CreateItem(fullname, title);
}
Bool_t THttpServer::SetItemField(const char *fullname, const char *name, const char *value)
{
return fSniffer->SetItemField(fullname, name, value);
}
const char *THttpServer::GetItemField(const char *fullname, const char *name)
{
return fSniffer->GetItemField(fullname, name);
}
const char *THttpServer::GetMimeType(const char *path)
{
static const struct {
const char *extension;
int ext_len;
const char *mime_type;
} builtin_mime_types[] = {
{".xml", 4, "text/xml"},
{".json", 5, "application/json"},
{".bin", 4, "application/x-binary"},
{".gif", 4, "image/gif"},
{".jpg", 4, "image/jpeg"},
{".png", 4, "image/png"},
{".html", 5, "text/html"},
{".htm", 4, "text/html"},
{".shtm", 5, "text/html"},
{".shtml", 6, "text/html"},
{".css", 4, "text/css"},
{".js", 3, "application/x-javascript"},
{".ico", 4, "image/x-icon"},
{".jpeg", 5, "image/jpeg"},
{".svg", 4, "image/svg+xml"},
{".txt", 4, "text/plain"},
{".torrent", 8, "application/x-bittorrent"},
{".wav", 4, "audio/x-wav"},
{".mp3", 4, "audio/x-mp3"},
{".mid", 4, "audio/mid"},
{".m3u", 4, "audio/x-mpegurl"},
{".ogg", 4, "application/ogg"},
{".ram", 4, "audio/x-pn-realaudio"},
{".xslt", 5, "application/xml"},
{".xsl", 4, "application/xml"},
{".ra", 3, "audio/x-pn-realaudio"},
{".doc", 4, "application/msword"},
{".exe", 4, "application/octet-stream"},
{".zip", 4, "application/x-zip-compressed"},
{".xls", 4, "application/excel"},
{".tgz", 4, "application/x-tar-gz"},
{".tar", 4, "application/x-tar"},
{".gz", 3, "application/x-gunzip"},
{".arj", 4, "application/x-arj-compressed"},
{".rar", 4, "application/x-arj-compressed"},
{".rtf", 4, "application/rtf"},
{".pdf", 4, "application/pdf"},
{".swf", 4, "application/x-shockwave-flash"},
{".mpg", 4, "video/mpeg"},
{".webm", 5, "video/webm"},
{".mpeg", 5, "video/mpeg"},
{".mov", 4, "video/quicktime"},
{".mp4", 4, "video/mp4"},
{".m4v", 4, "video/x-m4v"},
{".asf", 4, "video/x-ms-asf"},
{".avi", 4, "video/x-msvideo"},
{".bmp", 4, "image/bmp"},
{".ttf", 4, "application/x-font-ttf"},
{NULL, 0, NULL}
};
int path_len = strlen(path);
for (int i = 0; builtin_mime_types[i].extension != NULL; i++) {
if (path_len <= builtin_mime_types[i].ext_len) continue;
const char *ext = path + (path_len - builtin_mime_types[i].ext_len);
if (strcmp(ext, builtin_mime_types[i].extension) == 0) {
return builtin_mime_types[i].mime_type;
}
}
return "text/plain";
}
char *THttpServer::ReadFileContent(const char *filename, Int_t &len)
{
len = 0;
std::ifstream is(filename);
if (!is) return 0;
is.seekg(0, is.end);
len = is.tellg();
is.seekg(0, is.beg);
char *buf = (char *) malloc(len);
is.read(buf, len);
if (!is) {
free(buf);
len = 0;
return 0;
}
return buf;
}