#include "THttpServer.h"
#include "TTimer.h"
#include "TSystem.h"
#include "TImage.h"
#include "TROOT.h"
#include "TClass.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>
#ifdef COMPILED_WITH_DABC
extern "C" unsigned long crc32(unsigned long crc, const unsigned char* buf, unsigned int buflen);
extern "C" unsigned long R__memcompress(char* tgt, unsigned long tgtsize, char* src, unsigned long srcsize);
unsigned long R__crc32(unsigned long crc, const unsigned char* buf, unsigned int buflen)
{ return crc32(crc, buf, buflen); }
#else
#include "RZip.h"
#endif
THttpCallArg::THttpCallArg() :
TObject(),
fTopName(),
fPathName(),
fFileName(),
fQuery(),
fCond(),
fContentType(),
fHeader(),
fContent(),
fZipping(0),
fBinData(0),
fBinDataLength(0)
{
}
THttpCallArg::~THttpCallArg()
{
if (fBinData) {
free(fBinData);
fBinData = 0;
}
}
void THttpCallArg::SetBinData(void* data, Long_t length)
{
if (fBinData) free(fBinData);
fBinData = data;
fBinDataLength = length;
fContent.Clear();
}
void THttpCallArg::SetPathAndFileName(const char *fullpath)
{
fPathName.Clear();
fFileName.Clear();
if (fullpath == 0) return;
const char *rslash = strrchr(fullpath, '/');
if (rslash == 0) {
fFileName = fullpath;
} else {
while ((fullpath != rslash) && (*fullpath == '/')) fullpath++;
fPathName.Append(fullpath, rslash - fullpath);
if (fPathName == "/") fPathName.Clear();
fFileName = rslash + 1;
}
}
void THttpCallArg::FillHttpHeader(TString &hdr, const char* kind)
{
if (kind==0) kind = "HTTP/1.1";
if ((fContentType.Length() == 0) || Is404()) {
hdr.Form("%s 404 Not Found\r\n"
"Content-Length: 0\r\n"
"Connection: close\r\n\r\n", kind);
} else {
hdr.Form("%s 200 OK\r\n"
"Content-Type: %s\r\n"
"Connection: keep-alive\r\n"
"Content-Length: %ld\r\n"
"%s\r\n",
kind,
GetContentType(),
GetContentLength(),
fHeader.Data());
}
}
Bool_t THttpCallArg::CompressWithGzip()
{
char *objbuf = (char*) GetContent();
Long_t objlen = GetContentLength();
unsigned long objcrc = R__crc32(0, NULL, 0);
objcrc = R__crc32(objcrc, (const unsigned char*) objbuf, objlen);
Int_t buflen = 10 + objlen + 8;
if (buflen<512) buflen = 512;
void* buffer = malloc(buflen);
char *bufcur = (char*) buffer;
*bufcur++ = 0x1f;
*bufcur++ = 0x8b;
*bufcur++ = 0x08;
*bufcur++ = 0x00;
*bufcur++ = 0;
*bufcur++ = 0;
*bufcur++ = 0;
*bufcur++ = 0;
*bufcur++ = 0;
*bufcur++ = 3;
char dummy[8];
memcpy(dummy, bufcur-6, 6);
unsigned long ziplen = R__memcompress(bufcur-6, objlen + 6, objbuf, objlen);
memcpy(bufcur-6, dummy, 6);
bufcur += (ziplen-6);
*bufcur++ = objcrc & 0xff;
*bufcur++ = (objcrc >> 8) & 0xff;
*bufcur++ = (objcrc >> 16) & 0xff;
*bufcur++ = (objcrc >> 24) & 0xff;
*bufcur++ = objlen & 0xff;
*bufcur++ = (objlen >> 8) & 0xff;
*bufcur++ = (objlen >> 16) & 0xff;
*bufcur++ = (objlen >> 24) & 0xff;
SetBinData(buffer, bufcur - (char*) buffer);
SetEncoding("gzip");
return kTRUE;
}
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"),
fDefaultPage(),
fDefaultPageCont(),
fDrawPage(),
fDrawPageCont(),
fMutex(),
fCallArgs()
{
fMainThrdId = TThread::SelfId();
#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;
}
}
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);
}
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;
Ssiz_t pos = fname.Index("jsrootsys/");
if (pos != kNPOS) {
fname.Remove(0, pos + 9);
if (!VerifyFilePath(fname.Data())) {
return kFALSE;
}
res = fJsRootSys + fname;
return kTRUE;
}
return kFALSE;
}
Bool_t THttpServer::ExecuteHttp(THttpCallArg *arg)
{
if (fMainThrdId == TThread::SelfId()) {
ProcessRequest(arg);
return kTRUE;
}
fMutex.Lock();
fCallArgs.Add(arg);
fMutex.UnLock();
arg->fCond.Wait();
return kTRUE;
}
void THttpServer::ProcessRequests()
{
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;
ProcessRequest(arg);
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 {
const char* hjsontag = "\"$$$h.json$$$\"";
Ssiz_t pos = fDefaultPageCont.Index(hjsontag);
if (pos==kNPOS) {
arg->fContent = fDefaultPageCont;
} else {
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.Clear();
arg->fContent.Append(fDefaultPageCont, pos);
arg->fContent.Append(h_json);
arg->fContent.Append(fDefaultPageCont.Data() + pos + strlen(hjsontag));
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$$$\"";
Ssiz_t pos = fDrawPageCont.Index(rootjsontag);
if (pos==kNPOS) {
arg->fContent = fDrawPageCont;
} else {
void* bindata(0);
Long_t bindatalen(0);
if (fSniffer->Produce(arg->fPathName.Data(), "root.json", "compact=3", bindata, bindatalen)) {
arg->fContent.Clear();
arg->fContent.Append(fDrawPageCont, pos);
arg->fContent.Append((char*) bindata, bindatalen);
arg->fContent.Append(fDrawPageCont.Data() + pos + strlen(rootjsontag));
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);
} else {
arg->fContent = fDrawPageCont;
}
free(bindata);
}
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;
}
if (filename == "h.xml") {
arg->fContent.Form(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<root>\n");
{
TRootSnifferStoreXml 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->fContent.Append("</root>\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(), arg->fBinData, arg->fBinDataLength)) {
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);
}
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;
}