#ifdef __WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../common/db.h"
//mmo required for definition of stricmp
#include "../common/mmo.h"
#include "../common/utils.h"
#include "../common/malloc.h"
#include "../common/socket.h"
#include "../common/plugin.h"
//#include "httpd.h"
/** Created by End_of_Exam, ported to plugin and modified by Celest **/
PLUGIN_INFO = {
"HttpDaemon",
PLUGIN_CORE,
"0.1",
PLUGIN_VERSION,
"HTTP Daemon"
};
PLUGIN_EVENTS_TABLE = {
{ "do_init", "Plugin_Init" },
{ "do_final", "Plugin_Final" },
{ NULL, NULL }
};
enum HTTPD_STATUS {
HTTPD_REQUEST_WAIT = 0, // ���N�G�X�g�҂�
HTTPD_REQUEST_WAIT_POST, // ���N�G�X�g�҂�(post)
HTTPD_REQUEST_OK, // ���N�G�X�g���ߊ���
HTTPD_SEND_HEADER, // �w�b�_���M����
HTTPD_WAITING_SEND // �f�[�^�����M���I���܂ő҂��Ă�����
};
enum {
HTTPD_METHOD_UNKNOWN = 0,
HTTPD_METHOD_GET,
HTTPD_METHOD_POST
};
struct httpd_session_data {
int fd;
int status;
int http_ver;
int header_len;
int data_len;
int method;
int persist;
int request_count;
unsigned int tick;
const unsigned char* url;
const unsigned char* query;
};
// undefine socket operations included from socket.h,
// since we are going to use 'sessiond' instead
#undef RFIFOP
#undef RFIFOREST
#undef WFIFOP
#define RFIFOP(fd,pos) (sessiond[fd]->rdata+sessiond[fd]->rdata_pos+(pos))
#define RFIFOREST(fd) (sessiond[fd]->rdata_size-sessiond[fd]->rdata_pos)
#define WFIFOP(fd,pos) (sessiond[fd]->wdata+sessiond[fd]->wdata_size+(pos))
struct socket_data **sessiond;
char *server_type;
unsigned int (*gettick)();
int (*add_timer_interval)(unsigned int,int (*)(int,unsigned int,int,int),int,int,int);
int *max_fd;
int (*delete_sessiond)(int);
int (*_WFIFOSET)(int,int);
int (*_RFIFOSKIP)(int,int);
static int max_persist_requests = 32; // �����ʐM�ł̍ő僊�N�G�X�g��
static int request_timeout[] = { 2500, 60*1000 }; // �^�C���A�E�g(�ŏ��A����)
static char document_root[256] = "./httpd/"; // �h�L�������g���[�g
// httpd �ɓ����Ă���y�[�W�ƁA�Ăяo���R�[���o�b�N���̈ꗗ
struct dbt *httpd_files;
void httpd_send(struct httpd_session_data*, int, const char *, int, const void *);
int httpd_check (struct socket_data *sd)
{
// httpd �ɉǂ����̔��肪�܂��s���ĂȂ�
// �擪�Q�o�C�g�� GE �Ȃ�httpd �ɉĂ݂�
if (sd->rdata_size >= 2 &&
sd->rdata[0] == 'G' && sd->rdata[1] == 'E')
return 1;
return 0;
}
int httpd_strcasencmp(const char *s1, const char *s2,int len)
{
while(len-- && (*s1 || *s2) ) {
if((*s1 | 0x20) != (*s2 | 0x20)) {
return ((*s1 | 0x20) > (*s2 | 0x20) ? 1 : -1);
}
s1++; s2++;
}
return 0;
}
// httpd �Ƀy�[�W��lj�����
// for �ȂǂŃy�[�W���������ł���悤�ɁAkey ��strdup()�������̂��g��
void httpd_pages (const char* url, void (*httpd_func)(struct httpd_session_data*, const char*))
{
if (strdb_get(httpd_files,(unsigned char*)(url+1)) == NULL) {
strdb_put(httpd_files, (unsigned char*)aStrdup(url+1), httpd_func);
} else {
strdb_put(httpd_files, (unsigned char*)(url+1), httpd_func);
}
}
static void (*httpd_default)(struct httpd_session_data* sd,const char* url);
const char *httpd_get_error( struct httpd_session_data* sd, int* status )
{
const char* msg;
// httpd �̃X�e�[�^�X�����߂�
switch(*status) {
case 200: msg = "OK"; break;
case 400: msg = "Bad Request"; break;
case 401: msg = "Unauthorized"; break; // ���g�p
case 403: msg = "Forbidden"; break; // ���g�p
case 404: msg = "Not Found"; break;
case 408: msg = "Request Timedout"; break;
case 411: msg = "Length Required"; break;
case 413: msg = "Request Entity Too Large"; break;
default:
*status = 500; msg = "Internal Server Error"; break;
}
return msg;
}
void httpd_send_error(struct httpd_session_data* sd,int status)
{
const char* msg = httpd_get_error( sd, &status );
httpd_send(sd, status, "text/plain",strlen(msg),msg);
}
void httpd_send_head (struct httpd_session_data* sd, int status, const char *content_type, int content_len)
{
char head[256];
int len;
const char* msg;
if (sd->status != HTTPD_REQUEST_OK)
return;
msg = httpd_get_error( sd, &status );
if(content_len == -1 || ++sd->request_count >= max_persist_requests ) {
// ������������Ȃ� or ���N�G�X�g���E�����̂Őؒf����
len = sprintf(
head,
"HTTP/1.%d %d %s\r\nContent-Type: %s\r\nConnection: close\r\n\r\n",
sd->http_ver,status,msg,content_type
);
sd->persist = 0;
len = sprintf(
head,
"HTTP/1.%d %d %s\r\nContent-Type: %s\r\n\r\n",
sd->http_ver,status,msg,content_type
);
sd->http_ver = 0; // ������������Ȃ��̂ŁAHTTP/1.0 ����(�����ؒf)�ɂ���
} else {
len = sprintf(
head,
"HTTP/1.%d %d %s\r\nContent-Type: %s\r\nContent-Length: %d\r\n\r\n",
sd->http_ver,status,msg,content_type,content_len
);
}
memcpy(WFIFOP(sd->fd,0),head,len);
_WFIFOSET(sd->fd,len);
sd->status = HTTPD_SEND_HEADER;
sd->data_len = content_len;
}
void httpd_send_data (struct httpd_session_data* sd, int content_len, const void *data)
{
const char* msg = (const char*)data;
if (sd->status == HTTPD_REQUEST_OK) {
// �w�b�_�̑��M�Y��Ă���̂ŁA�K���ɕ₤
httpd_send_head(sd,200,"application/octet-stream",-1);
} else if(sd->status != HTTPD_SEND_HEADER && sd->status != HTTPD_WAITING_SEND) {
return;
}
sd->data_len -= content_len;
// ����ȃT�C�Y�̃t�@�C�������M�o����悤�ɕ������đ���
while (content_len > 0) {
int send_byte = content_len;
if(send_byte > 12*1024) send_byte = 12*1024;
memcpy(WFIFOP(sd->fd,0),msg,send_byte);
_WFIFOSET(sd->fd,send_byte);
msg += send_byte; content_len -= send_byte;
}
sd->status = HTTPD_WAITING_SEND;
}
void httpd_send (struct httpd_session_data* sd, int status, const char *content_type, int content_len, const void *data)
{
httpd_send_head(sd,status,content_type,content_len);
httpd_send_data(sd,content_len,data);
}
void httpd_parse_header(struct httpd_session_data* sd);
void httpd_parse_request_ok(struct httpd_session_data *sd);
int httpd_parse (int fd)
{
struct httpd_session_data *sd = (struct httpd_session_data *)sessiond[fd]->session_data2;
if (sessiond[fd]->eof) {
delete_sessiond(fd);
return 0;
}
if (sd == NULL) {
sd = (struct httpd_session_data*) aMalloc (sizeof(struct httpd_session_data));
sd->fd = fd;
sessiond[fd]->session_data2 = sd;
sd->tick = gettick();
sd->persist = 0;
sd->request_count = 0;
}
printf ("status %d\n", sd->status);
switch(sd->status) {
case HTTPD_REQUEST_WAIT:
// ���N�G�X�g�҂�
if(RFIFOREST(fd) > 1024) {
// ���N�G�X�g����������̂ŁA�G���[��������
sd->status = HTTPD_REQUEST_OK;
httpd_send_error(sd,400); // Bad Request
} else if( (int)( gettick() - sd->tick ) > request_timeout[sd->persist] ) {
// ���N�G�X�g�Ɏ��Ԃ������肷���Ă���̂ŁA�G���[��������
sd->status = HTTPD_REQUEST_OK;
httpd_send_error(sd,408); // Request Timeout
} else if(sd->header_len == RFIFOREST(fd)) {
// ��Ԃ��ȑO�Ɠ����Ȃ̂ŁA���N�G�X�g���ĉ�͂���K�v�͖���
} else {
int limit = RFIFOREST(fd);
unsigned char *req = RFIFOP(fd,0);
sd->header_len = RFIFOREST(fd);
do {
if(*req == '\n' && limit > 0) {
limit--; req++;
if(*req == '\r' && limit > 0) { limit--; req++; }
if(*req == '\n') {
// HTTP�w�b�_�̏I�_��������
*req = 0;
sd->header_len = (req - RFIFOP(fd,0)) + 1;
httpd_parse_header(sd);
break;
}
}
} while(req++,--limit > 0);
}
break;
case HTTPD_REQUEST_WAIT_POST:
if(RFIFOREST(sd->fd) >= sd->header_len) {
unsigned char temp = RFIFOB(sd->fd,sd->header_len);
RFIFOB(sd->fd,sd->header_len) = 0;
httpd_parse_request_ok(sd);
RFIFOB(sd->fd,sd->header_len) = temp;
}
break;
case HTTPD_REQUEST_OK:
case HTTPD_SEND_HEADER:
// ���N�G�X�g���I������܂܉������M����Ă��Ȃ���ԂȂ̂ŁA
// �����ؒf
printf ("httpd: eof\n");
sessiond[fd]->eof = 1;
break;
case HTTPD_WAITING_SEND:
// �f�[�^�̑��M���I���܂őҋ@
//if(sessiond[fd]->wdata_size == sessiond[fd]->wdata_pos) {
// i *hope* this is correct o.o;
if(sessiond[fd]->wdata_size == 0) {
// HTTP/1.0�͎蓮�ؒf
// if(sd->http_ver == 0) {
if(sd->persist == 0) {
printf ("httpd: eof\n");
sessiond[fd]->eof = 1;
}
// RFIFO ���烊�N�G�X�g�f�[�^�̏����ƍ\���̂̏�����
_RFIFOSKIP(fd,sd->header_len);
sd->status = HTTPD_REQUEST_WAIT;
sd->tick = gettick();
sd->header_len = 0;
sd->query = NULL;
// sd->http_ver = 0; // ver �͕ێ�
sd->method = HTTPD_METHOD_UNKNOWN;
printf("httpd_parse: [% 3d] request sended RFIFOREST:%d\n", fd, RFIFOREST(fd));
}
break;
}
return 0;
}
void httpd_parse_header_sub( struct httpd_session_data* sd, const char *p1, int* plen )
{
int len = 0;
// HTTP�̃o�[�W������
if(!strncmp(p1 ,"HTTP/1.1",8)) {
sd->http_ver = 1;
sd->persist = 1;
} else {
sd->http_ver = 0;
sd->persist = 0;
}
p1 = strchr(p1,'\n');
while(p1) {
// Content-Length: �̒���
if(!httpd_strcasencmp(p1+1,"Content-Length: ",16)) {
len = atoi(p1 + 17);
}
// Connection: �̒���
if(!httpd_strcasencmp(p1+1,"Connection: ",12)) {
if( httpd_strcasencmp(p1+13,"close",5)==0 && sd->http_ver==1 )
sd->persist = 0;
if( httpd_strcasencmp(p1+13,"Keep-Alive",10)==0 && sd->http_ver==0 )
sd->persist = 1;
}
p1 = strchr(p1+1,'\n');
}
if(plen) *plen = len;
return;
}
void httpd_parse_header(struct httpd_session_data* sd)
{
int i;
int status = 400; // Bad Request
unsigned char* req = RFIFOP(sd->fd,0);
do {
if(!strncmp(req,"GET /",5)) {
// GET ���N�G�X�g
req += 5;
for(i = 0;req[i]; i++) {
if(req[i] == ' ' || req[i] == '?') break;
if(!isalnum(req[i]) && req[i] != '.' && req[i] != '_' && req[i] != '-') break;
}
if(req[i] == ' ') {
req[i] = 0;
sd->url = req;
sd->query = NULL;
sd->status = HTTPD_REQUEST_OK;
} else if(req[i] == '?') {
req[i] = 0;
sd->query = &req[++i];
for(;req[i];i++) {
if(
isalnum(req[i]) || req[i] == '+' || req[i] == '%' || req[i] == '&' ||
req[i] == '='
) {
continue;
} else {
break;
}
}
if(req[i] != ' ') {
break;
}
req[i] = 0;
sd->url = req;
} else {
break;
}
// �w�b�_���
httpd_parse_header_sub( sd, &req[i+1], NULL );
printf("httpd: request %s %s\n", sd->url, sd->query);
sd->method = HTTPD_METHOD_GET;
httpd_parse_request_ok(sd);
} else if(!strncmp(req,"POST /",6)) {
int len;
req += 6; status = 404;
for(i = 0;req[i]; i++) {
if(req[i] == ' ') break;
if(!isalnum(req[i]) && req[i] != '.' && req[i] != '_' && req[i] != '-') break;
}
if(req[i] != ' ') {
break;
}
req[i] = 0;
sd->url = req;
// �w�b�_���
httpd_parse_header_sub( sd, &req[i+1], &len );
if(len <= 0 || len >= 32*1024) {
// �Ƃ肠����32KB�ȏ�̃��N�G�X�g�͕s������
status = ( len==0 )? 411 : ( len>32*1024 )? 413 : 400;
break;
}
sd->query = RFIFOP(sd->fd,sd->header_len);
sd->method = HTTPD_METHOD_POST;
sd->header_len += len;
if(RFIFOREST(sd->fd) >= sd->header_len) {
unsigned char temp = RFIFOB(sd->fd,sd->header_len);
RFIFOB(sd->fd,sd->header_len) = 0;
httpd_parse_request_ok(sd);
RFIFOB(sd->fd,sd->header_len) = temp;
} else {
// POST�̃f�[�^�������Ă���̂�҂�
sd->status = HTTPD_REQUEST_WAIT_POST;
}
} else {
break;
}
} while(0);
if(sd->status == HTTPD_REQUEST_WAIT) {
sd->status = HTTPD_REQUEST_OK;
httpd_send_error(sd,status);
}
}
void httpd_parse_request_ok (struct httpd_session_data *sd)
{
void (*httpd_parse_func)(struct httpd_session_data*,const char*);
sd->status = HTTPD_REQUEST_OK;
// �t�@�C���������܂����̂ŁA�y�[�W����������������
// printf("httpd_parse: [% 3d] request /%s\n", fd, req);
httpd_parse_func = strdb_get(httpd_files,(unsigned char*)sd->url);
if(httpd_parse_func == NULL) {
httpd_parse_func = httpd_default;
}
if(httpd_parse_func == NULL) {
httpd_send_error(sd,404); // Not Found
} else {
httpd_parse_func(sd,sd->url);
if(sd->status == HTTPD_REQUEST_OK) {
httpd_send_error(sd,404); // Not Found
}
}
if(sd->persist == 1 && sd->data_len) {
// �������ςȃf�[�^(����Ȃ̑���Ȃ�c)
printf("httpd_parse: send size mismatch when parsing /%s\n", sd->url);
sessiond[sd->fd]->eof = 1;
}
if(sd->status == HTTPD_REQUEST_OK) {
httpd_send_error(sd,404);
}
}
char* httpd_get_value(struct httpd_session_data* sd,const char* val)
{
int src_len = strlen(val);
const unsigned char* src_p = sd->query;
if(src_p == NULL) return aStrdup("");
do {
if(!memcmp(src_p,val,src_len) && src_p[src_len] == '=') {
break;
}
src_p = strchr(src_p + 1,'&');
if(src_p) src_p++;
} while(src_p);
if(src_p != NULL) {
// �ړI�̕������������
const unsigned char* p2;
int dest_len;
char* dest_p;
src_p += src_len + 1;
p2 = strchr(src_p,'&');
if(p2 == NULL) {
src_len = strlen(src_p);
} else {
src_len = (p2 - src_p);
}
dest_p = aMalloc(src_len + 1);
dest_len = 0;
while(src_len > 0) {
if(*src_p == '%' && src_len > 2) {
int c1 = 0,c2 = 0;
if(src_p[1] >= '0' && src_p[1] <= '9') c1 = src_p[1] - '0';
if(src_p[1] >= 'A' && src_p[1] <= 'F') c1 = src_p[1] - 'A' + 10;
if(src_p[1] >= 'a' && src_p[1] <= 'f') c1 = src_p[1] - 'a' + 10;
if(src_p[2] >= '0' && src_p[2] <= '9') c2 = src_p[2] - '0';
if(src_p[2] >= 'A' && src_p[2] <= 'F') c2 = src_p[2] - 'A' + 10;
if(src_p[2] >= 'a' && src_p[2] <= 'f') c2 = src_p[2] - 'a' + 10;
dest_p[dest_len++] = (c1 << 4) | c2;
src_p += 3; src_len -= 3;
} else if(*src_p == '+') {
dest_p[dest_len++] = ' ';
src_p++; src_len--;
} else {
dest_p[dest_len++] = *(src_p++); src_len--;
}
}
dest_p[dest_len] = 0;
return dest_p;
}
return aStrdup("");
}
// MIME�^�C�v����B��v�Ȃ��̂������肵�āA�c���application/octet-stream
static const char* httpd_mimetype(const char* url)
{
char *ext = strrchr(url,'.');
if(ext) {
if(!strcmp(ext,".html")) return "text/html";
if(!strcmp(ext,".htm")) return "text/html";
if(!strcmp(ext,".css")) return "text/css";
if(!strcmp(ext,".js")) return "text/javascript";
if(!strcmp(ext,".txt")) return "text/plain";
if(!strcmp(ext,".gif")) return "image/gif";
if(!strcmp(ext,".jpg")) return "image/jpeg";
if(!strcmp(ext,".jpeg")) return "image/jpeg";
if(!strcmp(ext,".png")) return "image/png";
if(!strcmp(ext,".xbm")) return "image/xbm";
if(!strcmp(ext,".zip")) return "application/zip";
}
return "application/octet-stream";
}
void httpd_send_file(struct httpd_session_data* sd, const char* url)
{
FILE *fp;
int file_size;
char file_buf[8192];
if(sd->status != HTTPD_REQUEST_OK) return;
if(url[0] == '\0') url = "index.html";
// url �̍ő咷�͖�1010�o�C�g�Ȃ̂ŁA�o�b�t�@�I�[�o�[�t���[�̐S�z�͖���
sprintf(file_buf, "%s%s", document_root, url);
fp = fopen(file_buf,"rb");
if(fp == NULL) {
httpd_send_error(sd,404);
} else {
fseek(fp,0,SEEK_END);
file_size = ftell(fp);
fseek(fp,0,SEEK_SET);
httpd_send_head(sd,200,httpd_mimetype(url),file_size);
while(file_size > 0) {
int read_byte = file_size;
if(file_size > 8192) read_byte = 8192;
fread(file_buf,1,read_byte,fp);
httpd_send_data(sd,read_byte,file_buf);
file_size -= read_byte;
}
fclose(fp);
}
}
char* httpd_binary_encode(const char* val)
{
char *buf = aMalloc(strlen(val) * 3 + 1);
char *p = buf;
while(*val) {
if(isalnum((unsigned char)*val)) {
*(p++) = *(val++);
} else {
unsigned char c1 = *(val++);
unsigned char c2 = (c1 >> 4);
unsigned char c3 = (c1 & 0x0F);
*(p++) = '%';
*(p++) = c2 + (c2 >= 10 ? 'A'-10 : '0');
*(p++) = c3 + (c3 >= 10 ? 'A'-10 : '0');
}
}
*p = 0;
return buf;
}
char* httpd_quote_meta(const char* p1)
{
char *buf = aMalloc(strlen(p1) * 6 + 1);
char *p2 = buf;
while(*p1) {
switch(*p1) {
case '<': memcpy(p2,"<",4); p2 += 4; p1++; break;
case '>': memcpy(p2,">",4); p2 += 4; p1++; break;
case '&': memcpy(p2,"&",5); p2 += 5; p1++; break;
case '"': memcpy(p2,""",6); p2 += 6; p1++; break;
default: *(p2++) = *(p1++);
}
}
*p2 = 0;
return buf;
}
///////// Graph / HTML snippets functions /////////////////////////////
struct file_entry {
char *filename;
struct file_entry *next;
};
struct file_entry *fileentry_head = NULL;
static void httpd_graph_load (const char *filename)
{
struct file_entry *entry;
char type = *server_type + 'a';
int len = strlen(filename);
if (len <= 7 || filename[len - 7] != type)
return;
entry = fileentry_head;
while (entry) {
if (strcmpi(entry->filename, filename) == 0)
return;
entry = entry->next;
}
entry = (struct file_entry *) aMalloc (sizeof(struct file_entry));
entry->filename = aStrdup(filename);
entry->next = fileentry_head;
fileentry_head = entry;
}
// scan for available html snippets
static int httpd_graph_find (int tid, unsigned int tick, int id, int data)
{
findfile("httpd", ".graph", httpd_graph_load);
return 0;
}
static void httpd_graph_parse (struct httpd_session_data *sd,const char* url)
{
// output html
struct file_entry *entry = fileentry_head;
char buf[8192];
char *p = buf;
FILE *fp;
p += sprintf(p,"<html><head><title>Athena Sensors</title></head>\n\n<body>\n");
p += sprintf(p,"<h1>Athena Sensors</h1>\n\n");
while (entry) {
// insert snippets into html
char line[1024];
fp = fopen(entry->filename, "r");
if (fp == NULL) {
entry = entry->next;
continue;
}
while(fgets(line, sizeof(line) -1, fp))
p += sprintf(p, line);
fclose(fp);
entry = entry->next;
}
p += sprintf(p,"</body></html>\n");
httpd_send(sd,200,"text/html",p - buf,buf);
}
//////////////// Initialise / Finalise /////////////////////////////
void do_final (void)
{
int fd;
struct file_entry *entry = fileentry_head, *entry2;
// clear up graph entries
while (entry) {
entry2 = entry->next;
aFree(entry->filename);
aFree(entry);
entry = entry2;
}
// clear up existing http connections
for (fd = 0; fd < *max_fd; fd++)
if (sessiond[fd] && sessiond[fd]->type == SESSION_HTTP)
delete_sessiond(fd);
httpd_files->destroy(httpd_files,NULL);
// clear up the database
db_final();
// clear up allocated memory
// note: the memory manager, if enabled, would be
// separate from the parent program, which is also
// why we need to delete our http sessions
// separately above
malloc_final();
}
void do_init (void)
{
struct func_parse_table *parse_table;
int enable_httpd = 1;
do {
char line[1024], w1[1024], w2[1024];
FILE *fp = fopen("plugins/httpd.conf","r");
if (fp == NULL)
break;
while(fgets(line, sizeof(line) -1, fp)) {
if (line[0] == '/' && line[1] == '/')
continue;
if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) == 2) {
if(strcmpi(w1,"enable_httpd")==0){
enable_httpd = atoi(w2);
} else if(strcmpi(w1,"document_root")==0){
strcpy(document_root, w2);
} else if(strcmpi(w1,"request_timeout_first")==0){
request_timeout[0] = atoi(w2);
} else if(strcmpi(w1,"request_timeout_persist")==0){
request_timeout[1] = atoi(w2);
} else if(strcmpi(w1,"max_persist_request")==0){
max_persist_requests = atoi(w2);
}
}
}
fclose(fp);
} while (0);
if (!enable_httpd)
return;
malloc_init();
db_init();
IMPORT_SYMBOL(server_type, 0);
IMPORT_SYMBOL(gettick, 5);
IMPORT_SYMBOL(add_timer_interval, 8);
IMPORT_SYMBOL(max_fd, 13);
IMPORT_SYMBOL(sessiond, 14);
IMPORT_SYMBOL(delete_sessiond, 15);
IMPORT_SYMBOL(_WFIFOSET, 16);
IMPORT_SYMBOL(_RFIFOSKIP, 17);
IMPORT_SYMBOL(parse_table, 18);
// register http parsing function
parse_table[SESSION_HTTP].check = httpd_check;
parse_table[SESSION_HTTP].func = httpd_parse;
httpd_files = db_alloc(__FILE__,__LINE__,DB_STRING,DB_OPT_RELEASE_KEY,50);
httpd_default = httpd_send_file;
httpd_pages ("/graph", httpd_graph_parse);
add_timer_interval(gettick()+10000,httpd_graph_find,0,0,10000);
return;
}