our current experimental Neutrino branch

git-svn-id: file:///home/bas/coolstream_public_svn/THIRDPARTY/applications/neutrino-experimental@27 e54a6e83-5905-42d5-8d5c-058d10e6a962
This commit is contained in:
mrcolor
2009-12-08 11:05:11 +00:00
commit bc5bd4154e
876 changed files with 193775 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
The code-base of yhttpd is Tuxbox-nhttpd 2.x (cvs.tuxbox.org)
Authors since re-writting of yhttpd:
Johannes Golombek [yjogol@cvs.tuxbox.org]

View File

@@ -0,0 +1,18 @@
INCLUDES = \
-I$(top_srcdir)/lib \
-I$(top_srcdir)/src/zapit/include \
-I$(top_srcdir)/src \
-I$(top_srcdir)/src/nhttpd \
-I$(top_srcdir)/lib/connection \
-I$(top_srcdir)/lib/libeventserver \
-I$(top_srcdir)/lib/libconfigfile \
@FREETYPE_CFLAGS@
AM_CPPFLAGS = -fno-rtti -fno-exceptions
noinst_LIBRARIES = libyhttpd.a
libyhttpd_a_SOURCES = \
ylogging.cpp helper.cpp \
ywebserver.cpp yconnection.cpp yrequest.cpp yresponse.cpp yhook.cpp ysocket.cpp

View File

@@ -0,0 +1,22 @@
------------------------------------------------------------------------------
yhttpd-GOALS
(yjogol@online.de, August 2006)
------------------------------------------------------------------------------
The yhttpd-Webserver is a small C++ based Webserver for embedded Systems.
It has a simple Software-Architecture and uses for string-operations
mostly buffer-save functions to prevent security exploits like buffer overruns.
It is designed for threading and multiuser access for static files.
It can be compiled on Windows Systems using Cygwin, on Linux-Systems
and had been succesfully compiled for AVM Fritzbox or for
Maxtor Shared Storage.
The code-base of yhttpd is Tuxbox-nhttpd 2.x (cvs.tuxbox.org)
Starting with Version 3.0 the nhttpd source code was splitted into
a core Webserver (yhttpd_core) and extension modules (yhttpd_mods).
The yhttpd_core and yhttp_mods ware completly rewritten.
The Hook-System (extension modules) encapsulate all requests and responses
for the tuxboxapi. So both tuxapi and yhttpd can be developed seperatlly and
independ from each other.

View File

@@ -0,0 +1,280 @@
//=============================================================================
// YHTTPD
// Helper
//=============================================================================
// c
#include <cstdio> // printf prototype.
#include <cstdlib> // calloc and free prototypes.
#include <cstring> // str* and memset prototypes.
#include <cstdarg>
// yhttpd
#include "yconfig.h"
#include "ytypes_globals.h"
#include "helper.h"
#include "ylogging.h"
//=============================================================================
// Integers
//=============================================================================
//-------------------------------------------------------------------------
// Check and set integer inside boundaries (min, max)
//-------------------------------------------------------------------------
int minmax(int value,int min, int max)
{
if(value < min) return min;
if(value > max) return max;
return value;
}
//=============================================================================
// Date & Time
//=============================================================================
//-------------------------------------------------------------------------
// Check and set Date/Time (tm*) inside boundaries
//-------------------------------------------------------------------------
void correctTime(struct tm *zt)
{
zt->tm_year = minmax(zt->tm_year,0,129);
zt->tm_mon = minmax(zt->tm_mon,0,11);
zt->tm_mday = minmax(zt->tm_mday,1,31); //-> eine etwas laxe pruefung, aber mktime biegt das wieder grade
zt->tm_hour = minmax(zt->tm_hour,0,23);
zt->tm_min = minmax(zt->tm_min,0,59);
zt->tm_sec = minmax(zt->tm_sec,0,59);
}
//=============================================================================
// Strings
//=============================================================================
//-------------------------------------------------------------------------
// Integer to Hexadecimal-String
//-------------------------------------------------------------------------
std::string itoh(unsigned int conv)
{
return string_printf("0x%06x",conv);
}
//-------------------------------------------------------------------------
// Integer to String
//-------------------------------------------------------------------------
std::string itoa(unsigned int conv)
{
return string_printf("%u",conv);
}
//-------------------------------------------------------------------------
// convert timer_t to "<hour>:<minutes>" String
//-------------------------------------------------------------------------
std::string timeString(time_t time)
{
char tmp[7]={'\0'};
struct tm *tm = localtime(&time);
if (strftime(tmp, 6, "%H:%M", tm))
return std::string(tmp);
else
return std::string("??:??");
}
//-------------------------------------------------------------------------
// Printf and return formatet String. Buffer-save!
// max length up to bufferlen -> then snip
//-------------------------------------------------------------------------
#define bufferlen 4*1024
std::string string_printf(const char *fmt, ...)
{
char buffer[bufferlen];
va_list arglist;
va_start( arglist, fmt );
if(arglist)
vsnprintf( buffer, bufferlen, fmt, arglist );
va_end(arglist);
return std::string(buffer);
}
//-------------------------------------------------------------------------
// ySplitString: spit string "str" in two strings "left" and "right" at
// one of the chars in "delimiter" returns true if delimiter found
//-------------------------------------------------------------------------
bool ySplitString(std::string str, std::string delimiter, std::string& left, std::string& right)
{
unsigned int pos;
if ((pos = str.find_first_of(delimiter)) != std::string::npos)
{
left = str.substr(0, pos);
right = str.substr(pos + 1, str.length() - (pos + 1 ));
}
else
{
left = str; //default if not found
right = "";
}
return (pos != std::string::npos);
}
//-------------------------------------------------------------------------
// ySplitString: spit string "str" in two strings "left" and "right" at
// one of the chars in "delimiter" returns true if delimiter found
//-------------------------------------------------------------------------
bool ySplitStringExact(std::string str, std::string delimiter, std::string& left, std::string& right)
{
unsigned int pos;
if ((pos = str.find(delimiter)) != std::string::npos)
{
left = str.substr(0, pos);
right = str.substr(pos + delimiter.length(), str.length() - (pos + delimiter.length() ));
}
else
{
left = str; //default if not found
right = "";
}
return (pos != std::string::npos);
}
//-------------------------------------------------------------------------
// ySplitStringRight: spit string "str" in two strings "left" and "right" at
// one of the chars in "delimiter" returns true if delimiter found
//-------------------------------------------------------------------------
bool ySplitStringLast(std::string str, std::string delimiter, std::string& left, std::string& right)
{
unsigned int pos;
if ((pos = str.find_last_of(delimiter)) != std::string::npos)
{
left = str.substr(0, pos);
right = str.substr(pos + 1, str.length() - (pos + 1 ));
}
else
{
left = str; //default if not found
right = "";
}
return (pos != std::string::npos);
}
//-------------------------------------------------------------------------
// ySplitStringVector: spit string "str" and build vector of strings
//-------------------------------------------------------------------------
CStringArray ySplitStringVector(std::string str, std::string delimiter)
{
std::string left, right, rest;
bool found;
CStringArray split;
rest = str;
do
{
found = ySplitString(rest, delimiter, left, right);
split.push_back(left);
rest = right;
}
while(found);
return split;
}
//-------------------------------------------------------------------------
// trim whitespaces
//-------------------------------------------------------------------------
std::string trim(std::string const& source, char const* delims)
{
std::string result(source);
std::string::size_type index = result.find_last_not_of(delims);
if(index != std::string::npos)
result.erase(++index);
index = result.find_first_not_of(delims);
if(index != std::string::npos)
result.erase(0, index);
else
result.erase();
return result;
}
//-------------------------------------------------------------------------
// equal-function for case insensitive compare
//-------------------------------------------------------------------------
bool nocase_compare (char c1, char c2)
{
return toupper(c1) == toupper(c2);
}
//-----------------------------------------------------------------------------
// Decode URLEncoded std::string
//-----------------------------------------------------------------------------
std::string decodeString(std::string encodedString)
{
const char *string = encodedString.c_str();
unsigned int count=0;
char hex[3]={'\0'};
unsigned long iStr;
std::string result = "";
count = 0;
while(count<encodedString.length()) /* use the null character as a loop terminator */
{
if(string[count] == '%' && count+2 <encodedString.length())
{
hex[0]=string[count+1];
hex[1]=string[count+2];
hex[2]='\0';
iStr = strtoul(hex,NULL,16); /* convert to Hex char */
result += (char)iStr;
count += 3;
}
else if(string[count] == '+')
{
result += ' ';
count++;
}
else
{
result += string[count];
count++;
}
} /* end of while loop */
return result;
}
//-----------------------------------------------------------------------------
// Encode URLEncoded std::string
//-----------------------------------------------------------------------------
std::string encodeString(std::string decodedString)
{
unsigned int len = sizeof(char) * decodedString.length()*5 + 1;
std::string result( len, '\0' );
char *newString = (char *)result.c_str();
char *dstring = (char *)decodedString.c_str();
char one_char;
if(len == result.length()) // got memory needed
{
while((one_char = *dstring++)) /* use the null character as a loop terminator */
{
if(isalnum(one_char))
*newString++ = one_char;
else
newString += sprintf(newString, "&#%d;", (unsigned char) one_char);
}
*newString='\0'; /* when done copying the string,need to terminate w/ null char */
result.resize((unsigned int)(newString - result.c_str()), '\0');
return result;
}
else
{
return "";
}
}
//-----------------------------------------------------------------------------
// returns string in lower case
//-----------------------------------------------------------------------------
std::string string_tolower(std::string str)
{
for(unsigned int i = 0; i < str.length(); i++)
str[i] = tolower(str[i]);
return str;
}
//-----------------------------------------------------------------------------
// write string to a file
//-----------------------------------------------------------------------------
bool write_to_file(std::string filename, std::string content)
{
FILE *fd = NULL;
if((fd = fopen(filename.c_str(),"w")) != NULL) // open file
{
fwrite(content.c_str(), content.length(), 1, fd);
fflush(fd); // flush and close file
fclose(fd);
return true;
}
else
return false;
}

View File

@@ -0,0 +1,42 @@
//=============================================================================
// YHTTPD
// Helper
//=============================================================================
#ifndef __yhttpd_helper_h__
#define __yhttpd_helper_h__
// c
#include <ctime>
#include <sys/timeb.h>
// c++
#include <string>
#include <vector>
#include "ytypes_globals.h"
//-----------------------------------------------------------------------------
int minmax(int value,int min, int max);
void correctTime(struct tm *zt);
//-----------------------------------------------------------------------------
// String Converter
//-----------------------------------------------------------------------------
std::string itoa(unsigned int conv);
std::string itoh(unsigned int conv);
std::string decodeString(std::string encodedString);
std::string encodeString(std::string decodedString);
std::string string_tolower(std::string str);
//-----------------------------------------------------------------------------
// String Helpers
//-----------------------------------------------------------------------------
std::string trim(std::string const& source, char const* delims = " \t\r\n");
std::string string_printf(const char *fmt, ...);
bool ySplitString(std::string str, std::string delimiter, std::string& left, std::string& right);
bool ySplitStringExact(std::string str, std::string delimiter, std::string& left, std::string& right);
bool ySplitStringLast(std::string str, std::string delimiter, std::string& left, std::string& right);
CStringArray ySplitStringVector(std::string str, std::string delimiter);
bool nocase_compare (char c1, char c2);
std::string timeString(time_t time);
bool write_to_file(std::string filename, std::string content);
#endif /* __yhttpd_helper_h__ */

View File

@@ -0,0 +1,147 @@
//=============================================================================
// YHTTPD
// Connection
//=============================================================================
// system
#include <sys/time.h>
#include <errno.h>
#include <cstring>
// yhttpd
#include "yconfig.h"
#include "ytypes_globals.h"
#include "ywebserver.h"
#include "yconnection.h"
#include "helper.h"
//=============================================================================
// Initialization of static variables
//=============================================================================
long CWebserverConnection::GConnectionNumber = 0;
//=============================================================================
// Constructor & Destructor & Initialization
//=============================================================================
CWebserverConnection::CWebserverConnection(CWebserver *pWebserver)
{
Webserver = pWebserver;
Request.Webserver = pWebserver;
Request.Connection = this;
Response.Webserver = pWebserver;
Response.Connection = this;
Method = M_UNKNOWN;
HttpStatus = 0;
RequestCanceled = false;
#ifdef Y_CONFIG_FEATURE_KEEP_ALIVE
keep_alive = true;
#else
keep_alive = false;
#endif
}
//-------------------------------------------------------------------------
CWebserverConnection::CWebserverConnection()
{
// aprintf("test CWebserverConnection::CWebserverConnection()\n");
ConnectionNumber = ++GConnectionNumber;
}
//-------------------------------------------------------------------------
CWebserverConnection::~CWebserverConnection(void)
{
}
//-------------------------------------------------------------------------
// End The Connection. Request and Response allready handled.
// do "after done" work, like create a www-Log entry.
// Use "Hooks_EndConnection()" Handler to write own Hooks.
//-------------------------------------------------------------------------
void CWebserverConnection::EndConnection()
{
HookHandler.HookVarList["enlapsed_request"] = itoa(enlapsed_request/1000);
HookHandler.HookVarList["enlapsed_response"] = itoa(enlapsed_response/1000);
HookHandler.Hooks_EndConnection(); // Handle Hooks
if(RequestCanceled) // Canceled
keep_alive = false;
RequestCanceled = true;
// sock->Flush();
#ifndef Y_CONFIG_FEATURE_KEEP_ALIVE
sock->close();
#endif
}
//-------------------------------------------------------------------------
// Main
// Handle the Request, Handle (Send) Response), End the Connection
//-------------------------------------------------------------------------
void CWebserverConnection::HandleConnection()
{
gettimeofday(&tv_connection_start, &tz_connection_start);
// get the request
if (Request.HandleRequest())
{
// determine time from Connection creation until now
gettimeofday(&tv_connection_Response_start, &tz_connection_Response_start);
enlapsed_request = ((tv_connection_Response_start.tv_sec - tv_connection_start.tv_sec) * 1000000
+ (tv_connection_Response_start.tv_usec - tv_connection_start.tv_usec));
// Keep-Alive checking
#ifdef Y_CONFIG_FEATURE_KEEP_ALIVE
if(string_tolower(Request.HeaderList["Connection"]) == "close"
|| (httprotocol != "HTTP/1.1" && string_tolower(Request.HeaderList["Connection"]) != "keep-alive")
|| !Webserver->CheckKeepAliveAllowedByIP(sock->get_client_ip()))
keep_alive = false;
#else
keep_alive = false;
#endif
// Send a response
Response.SendResponse();
// determine time for SendResponse
gettimeofday(&tv_connection_Response_end, &tz_connection_Response_end);
enlapsed_response = ((tv_connection_Response_end.tv_sec - tv_connection_Response_start.tv_sec) * 1000000
+ (tv_connection_Response_end.tv_usec - tv_connection_Response_start.tv_usec));
// print production times
log_level_printf(1,"enlapsed time request:%ld response:%ld url:%s\n",
enlapsed_request, enlapsed_response, (Request.UrlData["fullurl"]).c_str());
}
else
{
RequestCanceled = true;
keep_alive = false; // close this connection socket
// dperror("Error while parsing request\n");
log_level_printf(1,"request canceled: %s\n", strerror(errno));
}
EndConnection();
}
//-------------------------------------------------------------------------
void CWebserverConnection::ShowEnlapsedRequest(char *text)
{
long enlapsed = GetEnlapsedRequestTime() / 1000;
log_level_printf(1,"enlapsed-f-start (%s) t:%ld url:%s\n",
text, enlapsed, (Request.UrlData["fullurl"]).c_str());
}
//-------------------------------------------------------------------------
// Time from creation of socket until now in microseconds!
//-------------------------------------------------------------------------
long CWebserverConnection::GetEnlapsedRequestTime()
{
struct timeval tv_now;
struct timezone tz_now;
gettimeofday(&tv_now, &tz_now);
return ((tv_now.tv_sec - tv_connection_start.tv_sec) * 1000000
+ (tv_now.tv_usec - tv_connection_start.tv_usec));
}
//-------------------------------------------------------------------------
// Time from beginning of response until now in microseconds!
//-------------------------------------------------------------------------
long CWebserverConnection::GetEnlapsedResponseTime()
{
struct timeval tv_now;
struct timezone tz_now;
gettimeofday(&tv_now, &tz_now);
return ((tv_now.tv_sec - tv_connection_Response_start.tv_sec) * 1000000
+ (tv_now.tv_usec - tv_connection_Response_start.tv_usec));
}

View File

@@ -0,0 +1,73 @@
//=============================================================================
// YHTTPD
// Connection
//-----------------------------------------------------------------------------
// The Connection Class handles a given Socket-Connection for an arriving
// Request. Normally many Connections exists and they are Threaded!
// "HandleConnection()" calls "HandleRequest()" and "SendResponse()"
//=============================================================================
#ifndef __yhttpd_connection_h__
#define __yhttpd_connection_h__
// system
#include <sys/time.h>
// c++
#include <string>
// yhttpd
#include "yconfig.h"
#include "ytypes_globals.h"
#include "ywebserver.h"
#include "yrequest.h"
#include "yresponse.h"
// Forward definition
class CWebserver;
//-----------------------------------------------------------------------------
class CWebserverConnection
{
public:
class CWebserver *Webserver; // Backreference
CWebserverRequest Request; // http Request
CWebserverResponse Response; // http Response
CyhookHandler HookHandler; // One Instance per Connection
CySocket *sock; // Connection Socket
bool RequestCanceled; // Canceled flag
static long GConnectionNumber; // Number of Connection. Global counter
long ConnectionNumber; // Number of Connection since Webserver start. Local number
bool keep_alive; // should the response keep-alive socket?
THttp_Method Method; // http Method (GET, POST,...)
int HttpStatus; // http response code
std::string httprotocol; // HTTP 1.x
long enlapsed_request, // time for request in usec
enlapsed_response; // time for response in usec
CWebserverConnection(CWebserver *pWebserver); // Constructor with given Backreferece
CWebserverConnection();
~CWebserverConnection(void);
void HandleConnection(); // Main: Handle the Connecten Live time
// performance/time measurement
void ShowEnlapsedRequest(char *text); // Print text and time difference to Connection creation
long GetEnlapsedRequestTime(); // Get time in micro-seconds from Connection creation
long GetEnlapsedResponseTime(); // Get time in micro-seconds after Request handles. Start Respnse.
private:
void EndConnection(); // End Connection Handling
// performance/time measurement
struct timeval tv_connection_start,
tv_connection_Response_start,
tv_connection_Response_end;
struct timezone tz_connection_start,
tz_connection_Response_start,
tz_connection_Response_end;
};
#endif /* __yhttpd_connection_h__ */

View File

@@ -0,0 +1,357 @@
//=============================================================================
// YHTTPD
// Hook and HookHandler
//=============================================================================
// C
#include <cstdarg>
#include <cstdio>
#include <cstring>
// yhttpd
#include "yhook.h"
#include "ylogging.h"
#include "helper.h"
//=============================================================================
// Initialization of static variables
//=============================================================================
THookList CyhookHandler::HookList;
//=============================================================================
// Hook Handling
//=============================================================================
//-----------------------------------------------------------------------------
// Hook Dispatcher for Session Hooks
// Execute every Hook in HookList until State change != HANDLED_NONE
//-----------------------------------------------------------------------------
THandleStatus CyhookHandler::Hooks_SendResponse()
{
log_level_printf(4,"Response Hook-List Start\n");
THandleStatus _status = HANDLED_NONE;
THookList::iterator i = HookList.begin();
for ( ; i!= HookList.end(); i++ )
{
log_level_printf(4,"Response Hook-List (%s) Start\n", ((*i)->getHookName()).c_str());
// response Hook
_status = (*i)->Hook_SendResponse(this);
log_level_printf(4,"Response Hook-List (%s) End. Status (%d)\n", ((*i)->getHookName()).c_str(), status);
if((_status != HANDLED_NONE) && (_status != HANDLED_CONTINUE))
break;
}
log_level_printf(4,"Response Hook-List End\n");
log_level_printf(8,"Response Hook-List Result:\n%s\n", yresult.c_str());
status = _status;
return _status;
}
//-----------------------------------------------------------------------------
// Hook Dispatcher for Session Hooks
// Execute every Hook in HookList until State change != HANDLED_NONE
//-----------------------------------------------------------------------------
THandleStatus CyhookHandler::Hooks_PrepareResponse()
{
log_level_printf(4,"PrepareResponse Hook-List Start\n");
THandleStatus _status = HANDLED_NONE;
THookList::iterator i = HookList.begin();
for ( ; i!= HookList.end(); i++ )
{
log_level_printf(4,"PrepareResponse Hook-List (%s) Start\n", ((*i)->getHookName()).c_str());
// response Hook
_status = (*i)->Hook_PrepareResponse(this);
log_level_printf(4,"PrepareResponse Hook-List (%s) End. Status (%d) HTTP Status (%d)\n", ((*i)->getHookName()).c_str(), status, httpStatus);
if((_status != HANDLED_NONE) && (_status != HANDLED_CONTINUE))
break;
}
log_level_printf(4,"PrepareResponse Hook-List End\n");
log_level_printf(8,"PrepareResponse Hook-List Result:\n%s\n", yresult.c_str());
status = _status;
return _status;
}
//-----------------------------------------------------------------------------
// Hook Dispatcher for Server based Hooks
// Execute every Hook in HookList until State change != HANDLED_NONE and
// != HANDLED_CONTINUE
//-----------------------------------------------------------------------------
THandleStatus CyhookHandler::Hooks_ReadConfig(CConfigFile *Config, CStringList &ConfigList)
{
log_level_printf(4,"ReadConfig Hook-List Start\n");
THandleStatus _status = HANDLED_NONE;
THookList::iterator i = HookList.begin();
for ( ; i!= HookList.end(); i++ )
{
// log_level_printf(4,"ReadConfig Hook-List (%s) Start\n", ((*i)->getHookName()).c_str());
// response Hook
_status = (*i)->Hook_ReadConfig(Config, ConfigList);
log_level_printf(4,"ReadConfig Hook-List (%s) Status (%d)\n", ((*i)->getHookName()).c_str(), _status);
if((_status != HANDLED_NONE) && (_status != HANDLED_CONTINUE))
break;
}
log_level_printf(4,"ReadConfig Hook-List End\n");
return _status;
}
//-----------------------------------------------------------------------------
// Hook Dispatcher for EndConnection
//-----------------------------------------------------------------------------
THandleStatus CyhookHandler::Hooks_EndConnection()
{
log_level_printf(4,"EndConnection Hook-List Start\n");
THandleStatus _status = HANDLED_NONE;
THookList::iterator i = HookList.begin();
for ( ; i!= HookList.end(); i++ )
{
log_level_printf(4,"EndConnection Hook-List (%s) Start\n", ((*i)->getHookName()).c_str());
// response Hook
_status = (*i)->Hook_EndConnection(this);
log_level_printf(4,"EndConnection Hook-List (%s) End. Status (%d)\n", ((*i)->getHookName()).c_str(), _status);
if((_status != HANDLED_NONE) && (_status != HANDLED_CONTINUE))
break;
}
log_level_printf(4,"EndConnection Hook-List End\n");
status = _status;
return _status;
}
//-----------------------------------------------------------------------------
// Hook Dispatcher for UploadSetFilename
//-----------------------------------------------------------------------------
THandleStatus CyhookHandler::Hooks_UploadSetFilename(std::string &Filename)
{
log_level_printf(4,"UploadSetFilename Hook-List Start. Filename:(%s)\n", Filename.c_str());
THandleStatus _status = HANDLED_NONE;
THookList::iterator i = HookList.begin();
for ( ; i!= HookList.end(); i++ )
{
log_level_printf(4,"UploadSetFilename Hook-List (%s) Start\n", ((*i)->getHookName()).c_str());
// response Hook
_status = (*i)->Hook_UploadSetFilename(this, Filename);
log_level_printf(4,"UploadSetFilename Hook-List (%s) End. Status (%d)\n", ((*i)->getHookName()).c_str(), _status);
if((_status != HANDLED_NONE) && (_status != HANDLED_CONTINUE))
break;
}
log_level_printf(4,"UploadSetFilename Hook-List End\n");
status = _status;
return _status;
}
//-----------------------------------------------------------------------------
// Hook Dispatcher for UploadSetFilename
//-----------------------------------------------------------------------------
THandleStatus CyhookHandler::Hooks_UploadReady(const std::string& Filename)
{
log_level_printf(4,"UploadReady Hook-List Start. Filename:(%s)\n", Filename.c_str());
THandleStatus _status = HANDLED_NONE;
THookList::iterator i = HookList.begin();
for ( ; i!= HookList.end(); i++ )
{
log_level_printf(4,"UploadReady Hook-List (%s) Start\n", ((*i)->getHookName()).c_str());
// response Hook
_status = (*i)->Hook_UploadReady(this, Filename);
log_level_printf(4,"UploadReady Hook-List (%s) End. Status (%d)\n", ((*i)->getHookName()).c_str(), _status);
if((_status != HANDLED_NONE) && (_status != HANDLED_CONTINUE))
break;
}
log_level_printf(4,"UploadReady Hook-List End\n");
status = _status;
return _status;
}
//=============================================================================
// Output helpers
//=============================================================================
void CyhookHandler::session_init(CStringList _ParamList, CStringList _UrlData, CStringList _HeaderList,
CStringList& _ConfigList, THttp_Method _Method, bool _keep_alive)
{
ParamList = _ParamList;
UrlData = _UrlData;
HeaderList = _HeaderList;
WebserverConfigList = _ConfigList;
Method = _Method;
status = HANDLED_NONE;
httpStatus = HTTP_OK;
ContentLength = 0;
LastModified = (time_t)-1;
keep_alive = _keep_alive;
HookVarList.clear();
}
//=============================================================================
// Build Header
//-----------------------------------------------------------------------------
// RFC 2616 / 6 Response (Header)
//
// The first line of a Response message is the Status-Line, consisting
// of the protocol version followed by a numeric status code and its
// associated textual phrase, with each element separated by SP
// characters. No CR or LF is allowed except in the final CRLF sequence.
//
// Response =Status-Line ; generated by SendHeader
// *(( general-header
// | response-header
// | entity-header ) CRLF)
// CRLF ; generated by SendHeader
// [ message-body ] ; by HOOK Handling Loop or Sendfile
//
// Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF ; generated by SendHeader
//
// general-header = Cache-Control ; not implemented
// | Connection ; implemented
// | Date ; implemented
// | Pragma ; not implemented
// | Trailer ; not implemented
// | Transfer-Encoding ; not implemented
// | Upgrade ; not implemented
// | Via ; not implemented
// | Warning ; not implemented
//
// response-header = Accept-Ranges ; not implemented
// | Age ; not implemented
// | ETag ; not implemented
// | Location ; implemented (redirection / Object moved)
// | Proxy-Authenticate ; not implemented
// | Retry-After ; not implemented
// | Server ; implemented
// | Vary ; not implemented
// | WWW-Authenticate ; implemented (by mod_auth and SendHeader)
//
// entity-header = Allow ; not implemented
// | Content-Encoding ; not implemented
// | Content-Language ; not implemented
// | Content-Length ; implemented
// | Content-Location ; not implemented
// | Content-MD5 ; not implemented
// | Content-Range ; not implemented
// | Content-Type ; implemented
// | Expires ; not implemented
// | Last-Modified ; implemented for static files
// | extension-header
//
// extension-header = message-header
//=============================================================================
std::string CyhookHandler::BuildHeader(bool cache)
{
std::string result="";
const char *responseString = "";
const char *infoString = 0;
// get Info Index
for (unsigned int i = 0;i < (sizeof(httpResponseNames)/sizeof(httpResponseNames[0])); i++)
if (httpResponseNames[i].type == httpStatus)
{
responseString = httpResponseNames[i].name;
infoString = httpResponseNames[i].info;
break;
}
// print Status-line
result = string_printf(HTTP_PROTOCOL " %d %s\r\nContent-Type: %s\r\n",httpStatus, responseString, ResponseMimeType.c_str());
log_level_printf(2,"Respose: HTTP/1.1 %d %s\r\nContent-Type: %s\r\n",httpStatus, responseString, ResponseMimeType.c_str());
switch (httpStatus)
{
case HTTP_UNAUTHORIZED:
result += "WWW-Authenticate: Basic realm=\"";
result += AUTH_NAME_MSG "\r\n";
break;
case HTTP_MOVED_TEMPORARILY:
case HTTP_MOVED_PERMANENTLY:
// Status HTTP_*_TEMPORARILY (redirection)
result += string_printf("Location: %s\r\n",NewURL.c_str());
// NO break HERE !!!
default:
time_t timer = time(0);
char timeStr[80];
// cache
if(!cache && (HookVarList["CacheCategory"]).empty() )
result += "Cache-Control: no-cache\r\n";
else
{
time_t x_time = time(NULL);
struct tm *ptm = gmtime(&x_time);
ptm->tm_mday+=1;
x_time = mktime(ptm);
strftime(timeStr, sizeof(timeStr), RFC1123FMT, gmtime(&x_time));
result += string_printf("Expires: %s\r\n", timeStr);
}
result += "Server: " WEBSERVERNAME "\r\n";
// actual date
strftime(timeStr, sizeof(timeStr), RFC1123FMT, gmtime(&timer));
result += string_printf("Date: %s\r\n", timeStr);
// connection type
#ifdef Y_CONFIG_FEATURE_KEEP_ALIVE
if(keep_alive)
result += "Connection: keep-alive\r\n";
else
result += "Connection: close\r\n";
#else
result += "Connection: close\r\n";
#endif
// content-len, last-modified
if(httpStatus == HTTP_NOT_MODIFIED ||httpStatus == HTTP_NOT_FOUND)
result += "Content-Length: 0\r\n";
else if(GetContentLength() >0)
{
time_t mod_time = time(NULL);
if(LastModified != (time_t)-1)
mod_time = LastModified;
strftime(timeStr, sizeof(timeStr), RFC1123FMT, gmtime(&mod_time));
result += string_printf("Last-Modified: %s\r\nContent-Length: %ld\r\n", timeStr, GetContentLength());
}
result += "\r\n"; // End of Header
break;
}
// Body
if (Method != M_HEAD)
switch (httpStatus)
{
case HTTP_OK:
case HTTP_NOT_MODIFIED:
case HTTP_CONTINUE:
case HTTP_ACCEPTED:
case HTTP_NO_CONTENT:
case HTTP_NOT_FOUND:
case HTTP_INTERNAL_SERVER_ERROR:
break;
case HTTP_MOVED_TEMPORARILY:
case HTTP_MOVED_PERMANENTLY:
result += "<html><head><title>Object moved</title></head><body>";
result += string_printf("302 : Object moved.<br/>If you dont get redirected click <a href=\"%s\">here</a></body></html>\n",NewURL.c_str());
break;
default:
// Error pages
break;
}
return result;
}
//=============================================================================
// Output helpers
//=============================================================================
//-----------------------------------------------------------------------------
void CyhookHandler::SendHTMLHeader(const std::string& Titel)
{
WriteLn("<html>\n<head><title>" + Titel + "</title>\n");
WriteLn("<meta http-equiv=\"cache-control\" content=\"no-cache\" />");
WriteLn("<meta http-equiv=\"expires\" content=\"0\" />\n</head>\n<body>\n");
}
//-----------------------------------------------------------------------------
void CyhookHandler::SendHTMLFooter(void)
{
WriteLn("</body>\n</html>\n\n");
}
//-----------------------------------------------------------------------------
#define OUTBUFSIZE 4096
void CyhookHandler::printf ( const char *fmt, ... )
{
char outbuf[OUTBUFSIZE];
bzero(outbuf,OUTBUFSIZE);
va_list arglist;
va_start( arglist, fmt );
vsnprintf( outbuf,OUTBUFSIZE, fmt, arglist );
va_end(arglist);
Write(outbuf);
}

View File

@@ -0,0 +1,187 @@
//=============================================================================
// YHTTPD
// Hook and HookHandler
//-----------------------------------------------------------------------------
// Extentions for the Webserver can be implemented with Hooks.
// A "Hook"-Class must be inherited from Cyhook and can override the virtual
// functions from Cyhook.
// There are three types of Hooks.
// 1) Server based Hooks (exactly one Instance of Cyhttpd)
// 2) Webserver based Hooks (for each Instance of CWebserver, actually one; not now)
// 3) Connection based Hooks (for each Instance of CWebserverConnection)
//-----------------------------------------------------------------------------
// The CyhookHandler-Class itselfs calls the "attached" hooks.
// Hook-Classes must be attached/de-attached in Cyhttpd-Class attach()/detach().
// Hook-Class instances should be initialized in yhttpd.cpp.
//-----------------------------------------------------------------------------
// "Hook_SendResponse"
//-----------------------------------------------------------------------------
// "Hook_SendResponse": this is the most important Hook.
// The CyhookHandler implements an abstraction for Request and Response data.
// Every Hook which has implemented Hook_SendResponse could be called in order
// of the "attach" to HookHandler. The control flow, which Hook will be called
// depends on the return value "THandleStatus" from the hook called before.
// For example HANDLED_NONE indicates that the Hook has not handled by the call.
// For example HANDLED_READY indicates that the call is handled and HookHandler
// should finish the Hook-Loop. HANDLED_NOT_IMPLEMENTED indicated that the Hook
// should handle the call, but has not any function to do that.
// Several Hooks can work together (HANDLED_CONTINUE or HANDLED_REWRITE); so the
// next called Hook can work with the output (in yresult) from the before called
// Hook or controls the workflow of following Hooks.
// For example the CmAuth-Hook validates HTTP-Authentication and returns
// HANDLED_CONTINUE if the authentication fits.
// Each Hook_SendResponse is encapsulated from the webserver and should not work
// on Socket-Connections. The Response is written to yresult. There are many
// helper-function for Output and Status-Handling.
// The Input is encapsulated too: ParamList, HeaderList, UrlData, ...
// Look at Response.SendResponse()
//-----------------------------------------------------------------------------
// Other Hooks
//-----------------------------------------------------------------------------
// - Hook_ReadConfig: Every Hook can read OWN Variables from the yhttpd-Configfile
// - Hook_EndConnection: After Response is sent and before Connection Instance
// is deleted
// - Hook_UploadSetFilename: this hook can set the filename for a file to upload
// via POST (before upload)
// - Hook_UploadReady: this Hook is called after uploading a file
//=============================================================================
#ifndef __yhttpd_yhook_h__
#define __yhttpd_yhook_h__
// C++
#include <string>
#include <list>
// yhttpd
#include "yconfig.h"
#include "ytypes_globals.h"
#include "ylogging.h"
// tuxbox
#include <configfile.h>
//-----------------------------------------------------------------------------
// forward declarations
//-----------------------------------------------------------------------------
class CyhookHandler;
class Cyhook;
//-----------------------------------------------------------------------------
// Type definitions for Hooks
//-----------------------------------------------------------------------------
typedef enum
{
HANDLED_NONE = 0, // Init
HANDLED_READY, // Handled
HANDLED_ABORT, // Abort Connection Fatal
HANDLED_ERROR, // Have Error like missing Parameter
HANDLED_NOT_IMPLEMENTED,// URL should be handled but not implemented
HANDLED_REDIRECTION, // Set new URL and send HTTPD Object Moved
HANDLED_SENDFILE, // Set new URL and Send File
HANDLED_REWRITE, // Set new URL and call Hooks again
HANDLED_CONTINUE, // handled but go on
} THandleStatus;
typedef std::list<Cyhook *> THookList;
//-----------------------------------------------------------------------------
// An abstract Hook-Class. Custom Hook must be inherited.
//-----------------------------------------------------------------------------
class Cyhook
{
protected:
public:
Cyhook(){};
virtual ~Cyhook(){};
// Informations & Control
virtual std::string getHookVersion(void) {return std::string("0.0.0");}
virtual std::string getHookName(void) {return std::string("Abstract Hook Class");}
// CWebserverConnection based hooks
virtual THandleStatus Hook_PrepareResponse(CyhookHandler *hh){return HANDLED_NONE;};
virtual THandleStatus Hook_SendResponse(CyhookHandler *hh){return HANDLED_NONE;};
virtual THandleStatus Hook_EndConnection(CyhookHandler *hh){return HANDLED_NONE;}
virtual THandleStatus Hook_UploadSetFilename(CyhookHandler *hh, std::string &Filename){return HANDLED_NONE;}
virtual THandleStatus Hook_UploadReady(CyhookHandler *hh, std::string Filename){return HANDLED_NONE;}
// Cyhttpd based hooks
virtual THandleStatus Hook_ReadConfig(CConfigFile *Config, CStringList &ConfigList){return HANDLED_NONE;};
};
//-----------------------------------------------------------------------------
// Hook Handling and Input & Output abstraction
//-----------------------------------------------------------------------------
class CyhookHandler
{
protected:
static THookList HookList;
public:
// Output
std::string yresult; // content for response output
THandleStatus status; // status of Hook handling
HttpResponseType httpStatus; // http-status code for response
std::string ResponseMimeType; // mime-type for response
std::string NewURL; // new URL for Redirection
long ContentLength; // Length of Response Body
time_t LastModified; // Last Modified Time of Item to send / -1 dynamic content
std::string Sendfile; // Path & Name (local os style) of file to send
bool keep_alive;
// Input
CStringList ParamList; // local copy of ParamList (Request)
CStringList UrlData; // local copy of UrlData (Request)
CStringList HeaderList; // local copy of HeaderList (Request)
CStringList WebserverConfigList; // Reference (writable) to ConfigList
CStringList HookVarList; // Variables in Hook-Handling passing to other Hooks
THttp_Method Method; // HTTP Method (requested)
// constructor & deconstructor
CyhookHandler(){};
virtual ~CyhookHandler(){};
// hook slot handler
static void attach(Cyhook *yh) // attach a Hook-Class to HookHandler
{HookList.push_back(yh);};
static void detach(Cyhook *yh) // detach a Hook-Class to HookHandler
{HookList.remove(yh);};
// session handling
void session_init(CStringList _ParamList, CStringList _UrlData, CStringList _HeaderList,
CStringList& _ConfigList, THttp_Method _Method, bool _keep_alive);
// Cyhttpd based hooks
static THandleStatus Hooks_ReadConfig(CConfigFile *Config, CStringList &ConfigList);
// CWebserverConnection based hooks
THandleStatus Hooks_PrepareResponse(); // Hook for Response.SendResonse Dispatching
THandleStatus Hooks_SendResponse(); // Hook for Response.SendResonse Dispatching
THandleStatus Hooks_EndConnection();
THandleStatus Hooks_UploadSetFilename(std::string &Filename);
THandleStatus Hooks_UploadReady(const std::string& Filename);
// status handling
void SetHeader(HttpResponseType _httpStatus, std::string _ResponseMimeType)
{httpStatus = _httpStatus; ResponseMimeType = _ResponseMimeType;}
void SetHeader(HttpResponseType _httpStatus, std::string _ResponseMimeType, THandleStatus _status)
{httpStatus = _httpStatus; ResponseMimeType = _ResponseMimeType; status = _status;}
void SetError(HttpResponseType responseType)
{SetHeader(responseType, "text/html");}
void SetError(HttpResponseType responseType, THandleStatus _status)
{SetError(responseType); status = _status;}
// others
long GetContentLength()
{return (status==HANDLED_SENDFILE)?ContentLength : (long)yresult.length();}
// output methods
std::string BuildHeader(bool cache = false);
void addResult(const std::string& result) {yresult += result;}
void addResult(const std::string& result, THandleStatus _status)
{yresult += result; status = _status;}
void printf(const char *fmt, ...);
void Write(const std::string& text) { addResult(text); }
void WriteLn(const std::string& text) { addResult(text+"\r\n"); }
void Write(char const *text) {Write(std::string(text));}
void WriteLn(char const *text) {WriteLn(std::string(text));}
void SendHTMLHeader(const std::string& Titel);
void SendHTMLFooter(void);
void SendOk(void) {Write("ok");}
void SendError(void) {Write("error");}
void SendFile(const std::string& url) {NewURL = url; status = HANDLED_SENDFILE;}
void SendRedirect(const std::string& url) {httpStatus=HTTP_MOVED_TEMPORARILY; NewURL = url; status = HANDLED_REDIRECTION;}
void SendRewrite(const std::string& url){NewURL = url; status = HANDLED_REWRITE;}
friend class CyParser;
};
#endif /*__yhttpd_yhook_h__*/

View File

@@ -0,0 +1,94 @@
//=============================================================================
// YHTTPD
// Logging & Debugging
//=============================================================================
// c
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
// yhttpd
#include "yconfig.h"
#include "ytypes_globals.h"
#include "ylogging.h"
#include "yconnection.h"
//=============================================================================
// Instance Handling - like Singelton Pattern
//=============================================================================
//-----------------------------------------------------------------------------
// Init as Singelton
//-----------------------------------------------------------------------------
CLogging *CLogging::instance = NULL;
//-----------------------------------------------------------------------------
// There is only one Instance
//-----------------------------------------------------------------------------
CLogging *CLogging::getInstance(void)
{
if (!instance)
instance = new CLogging();
return instance;
}
//-----------------------------------------------------------------------------
void CLogging::deleteInstance(void)
{
if (instance)
delete instance;
instance = NULL;
}
//-----------------------------------------------------------------------------
// Constructor
//-----------------------------------------------------------------------------
CLogging::CLogging(void)
{
Debug = false;
LogToFile = false; //not implemented
LogLevel = 0;
Logfile = NULL;
pthread_mutex_init(&Log_mutex, NULL);
}
//-----------------------------------------------------------------------------
CLogging::~CLogging(void)
{
}
//=============================================================================
//-----------------------------------------------------------------------------
void CLogging::setDebug(bool _debug)
{
Debug = _debug;
}
//-----------------------------------------------------------------------------
bool CLogging::getDebug(void)
{
return Debug;
}
//=============================================================================
// Logging Calls
// use mutex controlled calls to output resources
// Normal Logging to Stdout, if "Log" is true then Log to file
//=============================================================================
#define bufferlen 1024*8
//-----------------------------------------------------------------------------
void CLogging::printf ( const char *fmt, ... )
{
char buffer[bufferlen];
va_list arglist;
va_start( arglist, fmt );
if(arglist)
vsnprintf( buffer, bufferlen, fmt, arglist );
va_end(arglist);
pthread_mutex_lock( &Log_mutex );
::printf(buffer);
if(LogToFile)
; //FIXME Logging to File
pthread_mutex_unlock( &Log_mutex );
}

View File

@@ -0,0 +1,86 @@
//=============================================================================
// YHTTPD
// Logging & Debugging
//=============================================================================
#ifndef __yhttpd_logging_h__
#define __yhttpd_logging_h__
// system
#include <pthread.h>
// yhttpd
#include "yconfig.h"
// forward declaration
class CWebserverConnection;
//-----------------------------------------------------------------------------
// Log Level
//-----------------------------------------------------------------------------
// 1 : Connection Information (called URL)
// 2 : Parameters??
// 3 : yParser Main Infos
// 5 : ycmd: Every yParser Loop Command expansion
// 6 : yresult: Every yParser Loop Command expansion
// 8 : Central Functions Detailed
// 9 : Socket Operations
//-----------------------------------------------------------------------------
class CLogging
{
protected:
pthread_mutex_t Log_mutex;
FILE *Logfile;
bool Debug;
bool LogToFile;
static CLogging *instance;
CLogging(void);
~CLogging(void);
public:
int LogLevel;
// Instance Handling
static CLogging *getInstance(void);
static void deleteInstance(void);
void setDebug(bool _debug);
bool getDebug(void);
// Logging
void printf(const char *fmt, ...);
};
//-----------------------------------------------------------------------------
// definitions for easy use
//-----------------------------------------------------------------------------
// print always
#define aprintf(fmt, args...) \
do { CLogging::getInstance()->printf("[yhttpd] " fmt, ## args); } while (0)
// print show file and linenumber
#define log_printfX(fmt, args...) \
do { CLogging::getInstance()->printf("[yhttpd(%s:%d)] " fmt, __FILE__, __LINE__, ## args); } while (0)
//Set Watch Point (show file and linenumber and function)
#define WP() \
do { CLogging::getInstance()->printf("[yhttpd(%s:%d)%s]\n", __FILE__, __LINE__, __FUNCTION__); } while (0)
// print if level matches
#define log_level_printf(level, fmt, args...) \
do { if(CLogging::getInstance()->LogLevel>=level) CLogging::getInstance()->printf("[yhttpd#%d] " fmt, level, ## args); } while (0)
// print if level matches / show file and linenumber
#define log_level_printfX(level, fmt, args...) \
do { if(CLogging::getInstance()->LogLevel>=level) CLogging::getInstance()->printf("[yhttpd#%d(%s:%d)] " fmt, level, __FILE__, __LINE__, ## args); } while (0)
// print only if debug is on
#define dprintf(fmt, args...) \
do { if(CLogging::getInstance()->getDebug())CLogging::getInstance()->printf("[yhttpd] " fmt, ## args); } while (0)
// print string to stdandard error
#define dperror(str) \
do { perror("[yhttpd] " str); } while (0)
#endif /* __yttpd_logging_h__ */

View File

@@ -0,0 +1,601 @@
//=============================================================================
// YHTTPD
// Request
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Request-Data
// UrlData (CStringList)
// startline First line of http-request (GET <fullurl> HTTP/1.x)
// fullurl ::= <url>?<paramstring>
// paramstring Is provided by String-Array ParameterList
// url ::= <path>/<filename> "/" is last "/" in string
// path begin with /...
// filename ::= <filenamepure>.<fileext>
// filenamepure
// fileext Extension like html, jpg, ...
//=============================================================================
// c++
#include <cstdarg>
#include <cstdio>
#include <string.h>
#include <cstdlib>
#include <errno.h>
// system
#include <fcntl.h>
#include <sys/socket.h>
// yhttpd
#include "yrequest.h"
#include "yconnection.h"
#include "helper.h"
//=============================================================================
// Constructor & Destructor
//=============================================================================
CWebserverRequest::CWebserverRequest(CWebserver *pWebserver)
{
Webserver = pWebserver;
CWebserverRequest();
}
//=============================================================================
// Parsing Request
//=============================================================================
//-----------------------------------------------------------------------------
// Main Request Parsing
// RFC2616
// generic-message = start-line
// *(message-header CRLF)
// CRLF
// [ message-body ]
// start-line = Request-Line | Status-Line
//-----------------------------------------------------------------------------
bool CWebserverRequest::HandleRequest(void)
{
std::string start_line = "";
// read first line
do
{
start_line = Connection->sock->ReceiveLine();
if(!Connection->sock->isValid)
return false;
if(start_line == "") // Socket empty
{
log_level_printf(1,"HandleRequest: End of line not found\n");
Connection->Response.SendError(HTTP_INTERNAL_SERVER_ERROR);
Connection->RequestCanceled = true;
return false;
}
}
while(start_line == "\r\n"); // ignore empty lines at begin on start-line
start_line = trim(start_line);
log_level_printf(1,"Request: %s\n", start_line.c_str() );
UrlData["startline"] = start_line;
if(!ParseStartLine(start_line))
return false;
if(Connection->Method == M_GET || Connection->Method == M_HEAD)
{
std::string tmp_line;
//read header (speed up: read rest of request in blockmode)
tmp_line = Connection->sock->ReceiveBlock();
if(!Connection->sock->isValid)
{
Connection->Response.SendError(HTTP_INTERNAL_SERVER_ERROR);
return false;
}
if(tmp_line == "")
{
Connection->Response.SendError(HTTP_INTERNAL_SERVER_ERROR);
return false;
}
ParseHeader(tmp_line);
}
// Other Methods
if(Connection->Method == M_DELETE || Connection->Method == M_PUT || Connection->Method == M_TRACE)
{
//todo: implement
aprintf("HTTP Method not implemented :%d\n",Connection->Method);
Connection->Response.SendError(HTTP_NOT_IMPLEMENTED);
return false;
}
// handle POST (read header & body)
if(Connection->Method == M_POST)
{
Connection->Response.Write("HTTP/1.1 100 Continue\r\n\r\n"); // POST Requests requires CONTINUE in HTTP/1.1
return HandlePost();
}
// if you are here, something went wrong
return true;
}
//-----------------------------------------------------------------------------
// Parse the start-line
// from RFC2616 / 5.1 Request-Line (start-line):
// Request-Line = Method SP Request-URI SP HTTP-Version CRLF (SP=Space)
//
// Determine Reqest-Method, URL, HTTP-Version and Split Parameters
// Split URL into path, filename, fileext .. UrlData[]
//-----------------------------------------------------------------------------
bool CWebserverRequest::ParseStartLine(std::string start_line)
{
std::string method,url,http,tmp;
log_level_printf(8,"<ParseStartLine>: line: %s\n", start_line.c_str() );
if(ySplitString(start_line," ",method,tmp))
{
if(ySplitStringLast(tmp," ",url,Connection->httprotocol))
{
analyzeURL(url);
UrlData["httprotocol"] = Connection->httprotocol;
// determine http Method
if(method.compare("POST") == 0) Connection->Method = M_POST;
else if(method.compare("GET") == 0) Connection->Method = M_GET;
else if(method.compare("PUT") == 0) Connection->Method = M_PUT;
else if(method.compare("HEAD") == 0) Connection->Method = M_HEAD;
else if(method.compare("PUT") == 0) Connection->Method = M_PUT;
else if(method.compare("DELETE") == 0) Connection->Method = M_DELETE;
else if(method.compare("TRACE") == 0) Connection->Method = M_TRACE;
else
{
log_level_printf(1,"Unknown Method or invalid request\n");
Connection->Response.SendError(HTTP_NOT_IMPLEMENTED);
log_level_printf(3,"Request: '%s'\n",rawbuffer.c_str());
return false;
}
log_level_printf(3,"Request: FullURL: %s\n",UrlData["fullurl"].c_str());
return true;
}
}
return false;
}
//-----------------------------------------------------------------------------
// parse parameter string
// parameter = attribute "=" value
// attribute = token
// value = token | quoted-string
//
// If parameter attribute is multiple times given, the values are stored like this:
// <attribute>=<value1>,<value2>,..,<value n>
//-----------------------------------------------------------------------------
bool CWebserverRequest::ParseParams(std::string param_string)
{
bool ende = false;
std::string param, name="", value, number;
while(!ende)
{
if(!ySplitStringExact(param_string,"&",param,param_string))
ende = true;
if(ySplitStringExact(param,"=",name,value))
{
value = trim(value);
if(ParameterList[name].empty())
ParameterList[name] = value;
else
{
ParameterList[name] += ",";
ParameterList[name] += value;
}
}
number = string_printf("%d", ParameterList.size()+1);
log_level_printf(7,"ParseParams: name: %s value: %s\n",name.c_str(), value.c_str());
ParameterList[number] = name;
}
return true;
}
//-----------------------------------------------------------------------------
// parse the header of the request
// from RFC 2616 / 4.2 Message Header:
// message-header = field-name ":" [ field-value ]
// field-name = token
// field-value = *( field-content | LWS )
// field-content = <the OCTETs making up the field-value
// and consisting of either *TEXT or combinations
// of token, separators, and quoted-string>
//-----------------------------------------------------------------------------
bool CWebserverRequest::ParseHeader(std::string header)
{
bool ende = false;
std::string sheader, name, value;
HeaderList.clear();
while(!ende)
{
if(!ySplitStringExact(header,"\r\n",sheader,header))
ende = true;
if(ySplitStringExact(sheader,":",name,value))
HeaderList[name] = trim(value);
log_level_printf(8,"ParseHeader: name: %s value: %s\n",name.c_str(), value.c_str());
}
return true;
}
//-----------------------------------------------------------------------------
// Analyze URL
// Extract Filename, Path, FileExt
// form RFC2616 / 3.2.2:
// http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query ]]
// query data is splitted and stored in ParameterList
//-----------------------------------------------------------------------------
void CWebserverRequest::analyzeURL(std::string url)
{
ParameterList.clear();
// URI decode
url = decodeString(url);
url = trim(url, "\r\n"); // non-HTTP-Standard: allow \r or \n in URL. Delete it.
UrlData["fullurl"] = url;
// split Params
if(ySplitString(url,"?",UrlData["url"],UrlData["paramstring"])) // split pure URL and all Params
ParseParams(UrlData["paramstring"]); // split params to ParameterList
else // No Params
UrlData["url"] = url;
if(!ySplitStringLast(UrlData["url"],"/",UrlData["path"],UrlData["filename"]))
{
UrlData["path"] = "/"; // Set "/" if not contained
}
else
UrlData["path"] += "/";
if(( UrlData["url"].length() == 1) || (UrlData["url"][UrlData["url"].length()-1] == '/' ))
{ // if "/" at end use index.html
UrlData["path"] = UrlData["url"];
UrlData["filename"] = "index.html";
}
ySplitStringLast(UrlData["filename"],".",UrlData["filenamepure"],UrlData["fileext"]);
}
//-----------------------------------------------------------------------------
// Handle Post (Form and ONE file upload)
//-----------------------------------------------------------------------------
bool CWebserverRequest::HandlePost()
{
//read header: line by line
std::string raw_header, tmp_line;
do
{
tmp_line = Connection->sock->ReceiveLine();
if(tmp_line == "") // Socket empty
{
log_level_printf(1,"HandleRequest: (Header) End of line not found: %s\n", strerror(errno));
Connection->Response.SendError(HTTP_INTERNAL_SERVER_ERROR);
return false;
}
raw_header.append(tmp_line);
}
while(tmp_line != "\r\n"); // header ends with first empty line
ParseHeader(raw_header);
// read meesage body
unsigned int content_len = 0;
if(HeaderList["Content-Length"] != "")
content_len = atoi( HeaderList["Content-Length"].c_str() );
// Get Rest of Request from Socket
log_level_printf(2,"Connection->Method Post !\n");
log_level_printf(9,"Conntent Type:%s\n",(HeaderList["Content-Type"]).c_str());
log_level_printf(8,"Post Content-Length:%d as string:(%s)\n", content_len, HeaderList["Content-Length"].c_str());
static const std::string t = "multipart/form-data; boundary=";
if(HeaderList["Content-Type"].compare(0,t.length(),t) == 0) // this a a multpart POST, normallly: file upload
{
#ifdef Y_CONFIG_FEATURE_UPLOAD
std::string boundary = "--" + HeaderList["Content-Type"].substr(t.length(),HeaderList["Content-Type"].length() - t.length());
std::string post_header;
do
{
content_len = HandlePostBoundary(boundary, content_len);
}
while(content_len > 0);
#else
Connection->Response.SendError(HTTP_NOT_IMPLEMENTED);
return false;
#endif
}
else if(HeaderList["Content-Type"].compare("application/x-www-form-urlencoded") == 0) //this is a normal POST with form-data (no upload)
{
// handle normal form POST
log_level_printf(6,"Handle POST application/x-www-form-urlencoded\n");
std::string post_header;
// get message-body
post_header = Connection->sock->ReceiveBlock();
if(post_header.length() < content_len)
{
aprintf("POST form less data then expected\n");
Connection->Response.SendError(HTTP_INTERNAL_SERVER_ERROR);
return false;
}
// parse the params in post_header (message-body) an add them to ParameterList
ParseParams(post_header);
}
return true;
}
//-----------------------------------------------------------------------------
// POST multipart ! FILE UPLOAD!
//
// No 'Content-type: multipart/mixed' now supported
// designed for recursion for different boundaries.
//
// from RFC 1867:
// 2. HTML forms with file submission
//
// The current HTML specification defines eight possible values for the
// attribute TYPE of an INPUT element: CHECKBOX, HIDDEN, IMAGE,
// PASSWORD, RADIO, RESET, SUBMIT, TEXT.
//
// In addition, it defines the default ENCTYPE attribute of the FORM
// element using the POST METHOD to have the default value
// "application/x-www-form-urlencoded"
//
// 6. Examples
//
// Suppose the server supplies the following HTML:
//
// <FORM ACTION="http://server.dom/cgi/handle"
// ENCTYPE="multipart/form-data"
// METHOD=POST>
// What is your name? <INPUT TYPE=TEXT NAME=submitter>
// What files are you sending? <INPUT TYPE=FILE NAME=pics>
// </FORM>
//
// and the user types "Joe Blow" in the name field, and selects a text
// file "file1.txt" for the answer to 'What files are you sending?'
//
// The client might send back the following data:
//
// Content-type: multipart/form-data, boundary=AaB03x
//
// --AaB03x
// content-disposition: form-data; name="field1"
//
// Joe Blow
// --AaB03x
// content-disposition: form-data; name="pics"; filename="file1.txt"
// Content-Type: text/plain
//
// ... contents of file1.txt ...
// --AaB03x--
//
// 7. Registration of multipart/form-data
//
// The media-type multipart/form-data follows the rules of all multipart
// MIME data streams as outlined in RFC 1521. It is intended for use in
// returning the data that comes about from filling out a form. In a
// form (in HTML, although other applications may also use forms), there
// are a series of fields to be supplied by the user who fills out the
// form. Each field has a name. Within a given form, the names are
// unique.
//
// multipart/form-data contains a series of parts. Each part is expected
// to contain a content-disposition header where the value is "form-
// data" and a name attribute specifies the field name within the form,
// e.g., 'content-disposition: form-data; name="xxxxx"', where xxxxx is
// the field name corresponding to that field. Field names originally in
// non-ASCII character sets may be encoded using the method outlined in
// RFC 1522.
//
// As with all multipart MIME types, each part has an optional Content-
// Type which defaults to text/plain. If the contents of a file are
// returned via filling out a form, then the file input is identified as
// application/octet-stream or the appropriate media type, if known. If
// multiple files are to be returned as the result of a single form
// entry, they can be returned as multipart/mixed embedded within the
// multipart/form-data.
//
// Each part may be encoded and the "content-transfer-encoding" header
// supplied if the value of that part does not conform to the default
// encoding.
//
// File inputs may also identify the file name. The file name may be
// described using the 'filename' parameter of the "content-disposition"
// header. This is not required, but is strongly recommended in any case
// where the original filename is known. This is useful or necessary in
// many applications.
//-----------------------------------------------------------------------------
unsigned int CWebserverRequest::HandlePostBoundary(std::string boundary, unsigned int content_len)
{
std::string tmp_line;
// read boundary
tmp_line = Connection->sock->ReceiveLine();
content_len -= tmp_line.length();
log_level_printf(2,"<POST Boundary> Start\n");
if(tmp_line.find(boundary) != std::string::npos)
{
// is it the boudary end?
if(tmp_line.find(boundary+"--") != std::string::npos)
{
log_level_printf(7,"<POST Boundary> Boundary END found\n");
return 0;
}
log_level_printf(7,"<POST Boundary> Boundary START found\n");
// read content-disposition: ...
tmp_line = Connection->sock->ReceiveLine();
content_len -= tmp_line.length();
if(tmp_line.find("Content-Disposition:") == std::string::npos)
{
log_level_printf(7,"<POST Boundary> no content-disposition found. line:(%s)\n", tmp_line.c_str());
return 0;
}
if(tmp_line.find("filename") != std::string::npos)
{
#ifdef Y_CONFIG_FEATURE_UPLOAD
// this part is a file
log_level_printf(2,"<POST Boundary> disposition !!this is a file!! found. line:(%s)\n", tmp_line.c_str());
// get para from 'content-disposition: form-data; name="pics"; filename="file1.txt"'
// set to ParameterList["<name>"]="<filename>"
std::string left, right, var_name, var_value;
if(!ySplitStringExact(tmp_line, "name=\"", left, right))
{
log_level_printf(7,"<POST Boundary> no var_name START found. line:(%s)\n", tmp_line.c_str());
return 0;
}
if(!ySplitStringExact(right, "\"", var_name, right))
{
log_level_printf(7,"<POST Boundary> no var_name END found. line:(%s)\n", tmp_line.c_str());
return 0;
}
if(!ySplitStringExact(right, "filename=\"", left, right))
{
log_level_printf(7,"<POST Boundary> no filename START found. line:(%s)\n", tmp_line.c_str());
return 0;
}
if(!ySplitStringExact(right, "\"", var_value, right))
{
log_level_printf(7,"<POST Boundary> no filename END found. line:(%s)\n", tmp_line.c_str());
return 0;
}
var_value = trim(var_value);
ParameterList[var_name] = var_value;
log_level_printf(7,"<POST Boundary> filename found. name:(%s) value:(%s)\n", var_name.c_str(), var_value.c_str());
//read 'Content-Type: <mime>'
tmp_line = Connection->sock->ReceiveLine();
content_len -= tmp_line.length();
// Get Content-Type: put it to ParameterList["<name>_mime"]="<mime>"
if(!ySplitStringExact(tmp_line, "Content-Type:", left, right))
{
log_level_printf(7,"<POST Boundary> no Content-Type found. line:(%s)\n", tmp_line.c_str());
return 0;
}
var_value = trim(right);
ParameterList[var_name+"_mime"] = var_value;
log_level_printf(7,"<POST Boundary> Content-Type found. name:(%s_mime) value:(%s)\n", var_name.c_str(), var_value.c_str());
//read empty line as separator
tmp_line = Connection->sock->ReceiveLine();
content_len -= tmp_line.length();
if(tmp_line != "\r\n")
{
log_level_printf(7,"<POST Boundary> no empty line found. line:(%s)\n", tmp_line.c_str());
return 0;
}
log_level_printf(7,"<POST Boundary> read file Start\n");
std::string upload_filename;
upload_filename = UPLOAD_TMP_FILE;
// Hook for Filename naming
Connection->HookHandler.Hooks_UploadSetFilename(upload_filename);
// Set upload filename to ParameterList["<name>_upload_filename"]="<upload_filename>"
ParameterList[var_name+"_upload_filename"] = upload_filename;
// open file for write
int fd = open(upload_filename.c_str(), O_WRONLY|O_CREAT|O_TRUNC);
if (fd<=0)
{
aprintf("cannot open file %s: ", upload_filename.c_str());
dperror("");
return 0;
}
// ASSUMPTION: the complete multipart has no more then SEARCH_BOUNDARY_LEN bytes after the file.
// It only works, if no multipart/mixed is used (e.g. in file attachments). Not nessesary in embedded systems.
// To speed up uploading, read content_len - SEARCH_BOUNDARY_LEN bytes in blockmode.
// To save memory, write them direct into the file.
#define SEARCH_BOUNDARY_LEN 2*RECEIVE_BLOCK_LEN // >= RECEIVE_BLOCK_LEN in ySocket
unsigned int _readbytes = 0;
if((int)content_len - SEARCH_BOUNDARY_LEN >0)
{
_readbytes = Connection->sock->ReceiveFileGivenLength(fd, content_len - SEARCH_BOUNDARY_LEN);
content_len -= _readbytes;
log_level_printf(8,"<POST Boundary> read block (already:%d all:%d)\n", _readbytes, content_len);
}
// read rest of file and check for boundary end
_readbytes = 0;
bool is_CRLF = false;
bool found_end_boundary = false;
do
{
// read line by line
tmp_line = Connection->sock->ReceiveLine();
_readbytes += tmp_line.length();
// is this line a boundary?
if(tmp_line.find(boundary) != std::string::npos)
{
if(tmp_line.find(boundary+"--") != std::string::npos)
found_end_boundary = true; // it is the end! of POST request!
break; // boundary found. end of file.
}
else // no Boundary: write CRFL if found in last line
{
if(is_CRLF)
if ((unsigned int)write(fd, "\r\n", 2) != 2)
{
perror("write file failed\n");
return 0;
}
}
// normal line: write it to file
// CRLF at end? Maybe CRLF before boundary. Can not decide yet
is_CRLF = (tmp_line.length()>=2 && tmp_line[tmp_line.length()-2]=='\r' && tmp_line[tmp_line.length()-1]=='\n');
int write_len = is_CRLF ? tmp_line.length()-2 : tmp_line.length();
if (write(fd, tmp_line.c_str(), write_len) != write_len)
{
perror("write file failed\n");
return 0;
}
log_level_printf(2,"<POST Boundary> read file (already:%d all:%d)\n", _readbytes, content_len);
}
while((_readbytes < content_len) && (tmp_line.length() != 0));
content_len -= _readbytes;
close(fd);
log_level_printf(2,"<POST Boundary> read file End\n");
if(found_end_boundary) // upload ok?
{
Connection->HookHandler.Hooks_UploadReady(upload_filename);
return 0;
}
#endif // Y_CONFIG_FEATURE_UPLOAD
}
else
// this part is a POST variable/parameter
{
// get var_name from 'content-disposition: form-data; name="var_name"'
std::string left, right, var_name, var_value;
if(!ySplitStringExact(tmp_line, "name=\"", left, right))
{
log_level_printf(7,"<POST Boundary> no var_name START found. line:(%s)\n", tmp_line.c_str());
return 0;
}
if(!ySplitStringExact(right, "\"", var_name, right))
{
log_level_printf(7,"<POST Boundary> no var_name END found. line:(%s)\n", tmp_line.c_str());
return 0;
}
//read empty line as separator
tmp_line = Connection->sock->ReceiveLine();
content_len -= tmp_line.length();
if(tmp_line != "\r\n")
{
log_level_printf(7,"<POST Boundary> no empty line found. line:(%s)\n", tmp_line.c_str());
return 0;
}
//read var_value line
// ASSUMPTION!!!! Only one Line for value, new line is a boundary again
// ATTENTION!! var_name must not be unique. So Parameters are store by number too.
var_value = Connection->sock->ReceiveLine();
content_len -= tmp_line.length();
var_value = trim(var_value);
ParameterList[var_name] = var_value;
log_level_printf(7,"<POST Boundary> Parameter found. name:(%s) value:(%s)\n", var_name.c_str(), var_value.c_str());
}
}
return content_len;
}

View File

@@ -0,0 +1,52 @@
//=============================================================================
// YHTTPD
// Request
//=============================================================================
#ifndef __yhttpd_request_h__
#define __yhttpd_request_h__
// C++
#include <string>
#include <map>
// yhttpd
#include "yconfig.h"
#include "ytypes_globals.h"
// forward declaration
class CWebserver;
class CWebserverConnection;
//-----------------------------------------------------------------------------
class CWebserverRequest
{
private:
bool HandlePost();
unsigned int HandlePostBoundary(std::string boundary, unsigned int content_len);
bool ParseStartLine(std::string start_line);
bool ParseParams(std::string param_string);
bool ParseHeader(std::string header);
protected:
std::string rawbuffer;
// request control
public:
class CWebserver *Webserver;
class CWebserverConnection *Connection;
// Request Data
CStringList ParameterList;
CStringList HeaderList;
CStringList UrlData; // url, fullurl, path, filename, fileext, paramstring
void analyzeURL(std::string);
// constructor & destructor
CWebserverRequest() {};
CWebserverRequest(CWebserver *pWebserver);
// Handler
bool HandleRequest(void);
};
#endif /* __yhttpd_request_h__ */

View File

@@ -0,0 +1,251 @@
//=============================================================================
// YHTTPD
// Response
//=============================================================================
// c
#include <cstdarg>
#include <cstdio>
#include <errno.h>
// c++
#include <string.h>
// system
#include <fcntl.h>
#include <sys/socket.h>
// yhttpd
#include "yconfig.h"
#include "yhttpd.h"
#include "ytypes_globals.h"
#include "ylogging.h"
#include "ywebserver.h"
#include "yconnection.h"
#include "helper.h"
#include "yhook.h"
#ifdef Y_CONFIG_HAVE_SENDFILE
#include <sys/sendfile.h>
#endif
//=============================================================================
// Constructor & Destructor
//=============================================================================
CWebserverResponse::CWebserverResponse(CWebserver *pWebserver)
{
Webserver = pWebserver;
CWebserverResponse();
}
//-----------------------------------------------------------------------------
CWebserverResponse::CWebserverResponse()
{
}
//=============================================================================
// Main Dispacher for Response
// To understand HOOKS reade yhook.cpp Comments!!!
//-----------------------------------------------------------------------------
// RFC 2616 / 6 Response
//
// After receiving and interpreting a request message, a server responds
// with an HTTP response message.
// Response =Status-Line ; generated by SendHeader
// *(( general-header ; generated by SendHeader
// | response-header ; generated by SendHeader
// | entity-header ) CRLF); generated by SendHeader
// CRLF ; generated by SendHeader
// [ message-body ] ; by HOOK Handling Loop or Sendfile
//=============================================================================
bool CWebserverResponse::SendResponse()
{
// Init Hookhandler
Connection->HookHandler.session_init(Connection->Request.ParameterList, Connection->Request.UrlData,
(Connection->Request.HeaderList), (Cyhttpd::ConfigList), Connection->Method, Connection->keep_alive);
//--------------------------------------------------------------
// HOOK Handling Loop [ PREPARE response hook ]
// Checking and Preperation: Auth, static, cache, ...
//--------------------------------------------------------------
// move to mod_sendfile ???
#ifdef Y_CONFIG_USE_HOSTEDWEB
// for hosted webs: rewrite URL
std::string _hosted="/hosted/";
if((Connection->Request.UrlData["path"]).compare(0,_hosted.length(),"/hosted/") == 0) // hosted Web ?
Connection->Request.UrlData["path"]=Cyhttpd::ConfigList["HostedDocumentRoot"]
+(Connection->Request.UrlData["path"]).substr(_hosted.length()-1);
#endif //Y_CONFIG_USE_HOSTEDWEB
do
{
if(Connection->RequestCanceled)
return false;
Connection->HookHandler.Hooks_PrepareResponse();
if(Connection->HookHandler.status == HANDLED_ERROR || Connection->HookHandler.status == HANDLED_ABORT)
{
log_level_printf(2,"Response Prepare Hook found but Error\n");
Write(Connection->HookHandler.BuildHeader());
Write(Connection->HookHandler.yresult);
return false;
}
// URL has new value. Analyze new URL for SendFile
else if(Connection->HookHandler.status == HANDLED_SENDFILE ||
Connection->HookHandler.status == HANDLED_REWRITE)
{
Connection->Request.analyzeURL(Connection->HookHandler.NewURL);
Connection->HookHandler.UrlData = Connection->Request.UrlData;
}
if(Connection->HookHandler.status == HANDLED_REDIRECTION)
{
Write(Connection->HookHandler.BuildHeader());
return false;
}
}
while(Connection->HookHandler.status == HANDLED_REWRITE);
// Prepare = NOT_MODIFIED ?
if(Connection->HookHandler.httpStatus == HTTP_NOT_MODIFIED)
{
Write(Connection->HookHandler.BuildHeader());
return true;
}
//--------------------------------------------------------------
// HOOK Handling Loop [ response hook ]
// Production
//--------------------------------------------------------------
if(Connection->HookHandler.status != HANDLED_SENDFILE)
do
{
if(Connection->RequestCanceled)
return false;
Connection->HookHandler.Hooks_SendResponse();
if((Connection->HookHandler.status == HANDLED_READY)||(Connection->HookHandler.status == HANDLED_CONTINUE))
{
log_level_printf(2,"Response Hook Output. Status:%d\n", Connection->HookHandler.status);
Write(Connection->HookHandler.BuildHeader());
if(Connection->Method != M_HEAD)
Write(Connection->HookHandler.yresult);
if(Connection->HookHandler.status != HANDLED_CONTINUE)
return true;
}
else if(Connection->HookHandler.status == HANDLED_ERROR)
{
log_level_printf(2,"Response Hook found but Error\n");
Write(Connection->HookHandler.BuildHeader());
if(Connection->Method != M_HEAD)
Write(Connection->HookHandler.yresult);
return false;
}
else if(Connection->HookHandler.status == HANDLED_ABORT)
return false;
// URL has new value. Analyze new URL for SendFile
else if(Connection->HookHandler.status == HANDLED_SENDFILE ||
Connection->HookHandler.status == HANDLED_REWRITE)
{
Connection->Request.analyzeURL(Connection->HookHandler.NewURL);
Connection->HookHandler.UrlData = Connection->Request.UrlData;
}
if(Connection->HookHandler.status == HANDLED_REDIRECTION)
{
Write(Connection->HookHandler.BuildHeader());
return false;
}
}
while(Connection->HookHandler.status == HANDLED_REWRITE);
// Send static file
if(Connection->HookHandler.status == HANDLED_SENDFILE && !Connection->RequestCanceled)
{
bool cache = true;
// if(Connection->HookHandler.UrlData["path"] == "/tmp/")//TODO: un-cachable dirs
// cache = false;
Write(Connection->HookHandler.BuildHeader(cache));
if(Connection->Method != M_HEAD)
Sendfile(Connection->Request.UrlData["url"]);
return true;
}
// arrived here? = error!
SendError(HTTP_NOT_FOUND);
return false;
}
//=============================================================================
// Output
//=============================================================================
void CWebserverResponse::SendHeader(HttpResponseType responseType, bool cache, std::string ContentType)
{
Connection->HookHandler.SetHeader(responseType, ContentType);
Write(Connection->HookHandler.BuildHeader(cache));
}
//-----------------------------------------------------------------------------
// BASIC Send over Socket for Strings (char*)
//-----------------------------------------------------------------------------
bool CWebserverResponse::WriteData(char const * data, long length)
{
if(Connection->RequestCanceled)
return false;
if(Connection->sock->Send(data, length) == -1)
{
log_level_printf(1,"response canceled: %s\n", strerror(errno));
Connection->RequestCanceled = true;
return false;
}
else
return true;
}
//-----------------------------------------------------------------------------
#define bufferlen 4*1024
void CWebserverResponse::printf ( const char *fmt, ... )
{
char buffer[bufferlen];
va_list arglist;
va_start( arglist, fmt );
vsnprintf( buffer, bufferlen, fmt, arglist );
va_end(arglist);
Write(buffer);
}
//-----------------------------------------------------------------------------
bool CWebserverResponse::Write(char const *text)
{
return WriteData(text, strlen(text));
}
//-----------------------------------------------------------------------------
bool CWebserverResponse::WriteLn(char const *text)
{
if(!WriteData(text, strlen(text)))
return false;
return WriteData("\r\n",2);
}
//-----------------------------------------------------------------------------
bool CWebserverResponse::Sendfile(std::string filename)
{
if(Connection->RequestCanceled)
return false;
int filed = open( filename.c_str(), O_RDONLY );
if(filed != -1 ) //can access file?
{
if(!Connection->sock->SendFile(filed))
Connection->RequestCanceled = true;
close(filed);
}
return (filed != -1 );
}
//-----------------------------------------------------------------------------
// Send File: Determine MIME-Type fro File-Extention
//-----------------------------------------------------------------------------
std::string CWebserverResponse::GetContentType(std::string ext)
{
std::string ctype = "text/plain";
ext = string_tolower(ext);
for (unsigned int i = 0;i < (sizeof(MimeFileExtensions)/sizeof(MimeFileExtensions[0])); i++)
if (MimeFileExtensions[i].fileext == ext)
{
ctype = MimeFileExtensions[i].mime;
break;
}
return ctype;
}

View File

@@ -0,0 +1,56 @@
//=============================================================================
// YHTTPD
// Response
//=============================================================================
#ifndef __yhttpd_response_h__
#define __yhttpd_response_h__
// c++
#include <string>
// yhttpd
#include "yconfig.h"
#include "ytypes_globals.h"
#include "yhook.h"
// forward declaration
class CWebserver;
class CWebserverConnection;
//-----------------------------------------------------------------------------
class CWebserverResponse
{
private:
protected:
bool WriteData(char const *data, long length);
bool Sendfile(std::string filename);
std::string redirectURI; // URI for redirection else: empty
public:
class CWebserver *Webserver;
class CWebserverConnection *Connection;
// con/destructors
CWebserverResponse();
CWebserverResponse(CWebserver *pWebserver);
// response control
bool SendResponse(void);
// output methods
void printf(const char *fmt, ...);
bool Write(char const *text);
bool WriteLn(char const *text);
bool Write(const std::string text) { return Write(text.c_str()); }
bool WriteLn(const std::string text) { return WriteLn(text.c_str()); }
// Headers
void SendError(HttpResponseType responseType) {SendHeader(responseType, false, "text/html");}
void SendHeader(HttpResponseType responseType, bool cache=false, std::string ContentType="text/html");
// Helpers
std::string GetContentType(std::string ext);
};
#endif /* __yhttpd_response_h__ */

View File

@@ -0,0 +1,504 @@
//=============================================================================
// YHTTPD
// Socket Class : Basic Socket Operations
//=============================================================================
#include <cstring>
// system
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <signal.h>
// yhttpd
#include "yhttpd.h"
#include "ysocket.h"
#include "ylogging.h"
// system
#ifdef Y_CONFIG_HAVE_SENDFILE
#include <sys/sendfile.h>
#endif
#ifdef Y_CONFIG_USE_OPEN_SSL
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#endif
//=============================================================================
// Initialization of static variables
//=============================================================================
#ifdef Y_CONFIG_USE_OPEN_SSL
SSL_CTX *CySocket::SSL_ctx;
std::string CySocket::SSL_pemfile;
std::string CySocket::SSL_CA_file;
#endif
//=============================================================================
// Constructor & Destructor & Initialization
//=============================================================================
CySocket::CySocket()
: sock(0)
{
#ifdef Y_CONFIG_USE_OPEN_SSL
ssl = NULL;
#endif
tv_start_waiting.tv_sec = 0;
tv_start_waiting.tv_usec = 0;
BytesSend =0;
sock = socket(AF_INET,SOCK_STREAM,0);
init();
}
//-----------------------------------------------------------------------------
CySocket::~CySocket()
{
#ifdef Y_CONFIG_USE_OPEN_SSL
if(isSSLSocket && ssl != NULL)
SSL_free(ssl);
#endif
}
//-----------------------------------------------------------------------------
// initialize
//-----------------------------------------------------------------------------
void CySocket::init(void)
{
handling = false;
isOpened = false;
isValid = true;
addr_len = sizeof(addr);
memset(&addr, 0, addr_len);
#ifdef Y_CONFIG_USE_OPEN_SSL
isSSLSocket = false;
#endif
}
//-----------------------------------------------------------------------------
// Initialize this socket as a SSL-socket
//-----------------------------------------------------------------------------
#ifdef Y_CONFIG_USE_OPEN_SSL
bool CySocket::initAsSSL(void)
{
isSSLSocket = true;
if (NULL == (ssl = SSL_new(CySocket::SSL_ctx))) // create SSL-socket
{
aprintf("ySocket:SSL Error: Create SSL_new : %s\n", ERR_error_string(ERR_get_error(), NULL) );
return false;
}
SSL_set_accept_state(ssl); // accept connection
if(1 != (SSL_set_fd(ssl, sock))) // associate socket descriptor
if (NULL == (ssl = SSL_new(CySocket::SSL_ctx)))
{
aprintf("ySocket:SSL Error: Create SSL_new : %s\n", ERR_error_string(ERR_get_error(), NULL) );
return false;
}
return true;
}
//-----------------------------------------------------------------------------
// static: initialize the SSL-Library and the SSL ctx object.
// Read and assoziate the keyfile.
//-----------------------------------------------------------------------------
bool CySocket::initSSL(void)
{
SSL_load_error_strings(); // Load SSL Error Strings
SSL_library_init(); // Load SSL Library
if (0 == RAND_status()) // set Random
{
aprintf("ySocket:SSL got no rand\n");
return false;
}
if((SSL_ctx = SSL_CTX_new(SSLv23_server_method())) == NULL) // create master ctx
{
aprintf("ySocket:SSL Error: Create SSL_CTX_new : %s\n", ERR_error_string(ERR_get_error(), NULL) );
return false;
}
if(SSL_pemfile == "")
{
aprintf("ySocket:SSL Error: no pemfile given\n");
return false;
}
if(SSL_CA_file != "") // have a CA?
if(1 != SSL_CTX_load_verify_locations(SSL_ctx, SSL_CA_file.c_str(), NULL))
{
aprintf("ySocket:SSL Error: %s CA-File:%s\n",ERR_error_string(ERR_get_error(), NULL), SSL_CA_file.c_str());
return false;
}
if(SSL_CTX_use_certificate_file(SSL_ctx, SSL_pemfile.c_str(), SSL_FILETYPE_PEM) < 0)
{
aprintf("ySocket:SSL Error: %s PEM-File:%s\n",ERR_error_string(ERR_get_error(), NULL), SSL_pemfile.c_str());
return false;
}
if(SSL_CTX_use_PrivateKey_file(SSL_ctx, SSL_pemfile.c_str(), SSL_FILETYPE_PEM) < 0)
{
aprintf("ySocket:SSL Error: Private Keys: %s PEM-File:%s\n",ERR_error_string(ERR_get_error(), NULL), SSL_pemfile.c_str());
return false;
}
if(SSL_CTX_check_private_key(SSL_ctx) != 1)
{
aprintf("ySocket:SSL Error: Private Keys not compatile to public keys: %s PEM-File:%s\n",ERR_error_string(ERR_get_error(), NULL), SSL_pemfile.c_str());
return false;
}
return true;
}
#endif
//=============================================================================
// Socket handling
//=============================================================================
void CySocket::close(void)
{
if(sock != 0 && sock != INVALID_SOCKET)
::close(sock);
#ifndef Y_CONFIG_FEATURE_KEEP_ALIVE
sock = 0;
#endif
isOpened = false;
}
//-----------------------------------------------------------------------------
void CySocket::shutdown(void)
{
if(sock != 0 && sock != INVALID_SOCKET)
::shutdown(sock,SHUT_RDWR);
}
//-----------------------------------------------------------------------------
bool CySocket::listen(int port, int max_connections)
{
if(sock == INVALID_SOCKET)
return false;
// set sockaddr for listening
sockaddr_in sai;
memset(&sai, 0, sizeof(sai));
sai.sin_family = AF_INET; // Protocol
sai.sin_addr.s_addr = htonl(INADDR_ANY); // No Filter
sai.sin_port = htons(port); // Listening Port
set_reuse_port(); // Re-Use Port
set_reuse_addr(); // Re-Use IP
if(bind(sock, (sockaddr *)&sai, sizeof(sockaddr_in)) != SOCKET_ERROR)
if(::listen(sock, max_connections) == 0)
return true;
return false;
}
//-----------------------------------------------------------------------------
CySocket* CySocket::accept()
{
init();
SOCKET newSock = ::accept(sock, (sockaddr *) &addr, &addr_len);
if(newSock == INVALID_SOCKET)
{
dperror("accept: invalid socket\n");
return NULL;
}
CySocket *new_ySocket = new CySocket(newSock);
if(new_ySocket != NULL)
{
new_ySocket->setAddr(addr);
#ifdef TCP_CORK
new_ySocket->set_option(IPPROTO_TCP, TCP_CORK);
#else
set_tcp_nodelay();
#endif
}
new_ySocket->isOpened = true;
// handling = true;
return new_ySocket;
}
//-----------------------------------------------------------------------------
std::string CySocket::get_client_ip(void)
{
return inet_ntoa(addr.sin_addr);
}
//-----------------------------------------------------------------------------
int CySocket::get_accept_port(void)
{
return (int)ntohs(addr.sin_port);
}
//-----------------------------------------------------------------------------
void CySocket::setAddr(sockaddr_in _addr)
{
addr = _addr;
}
//-----------------------------------------------------------------------------
// Set Socket Option (return = false = error)
//-----------------------------------------------------------------------------
bool CySocket::set_option(int typ, int option)
{
int on = 1;
return (setsockopt(sock, typ, option, (char *)&on, sizeof(on)) >= 0);
}
//-----------------------------------------------------------------------------
// Set Re-Use Option for Port.
//-----------------------------------------------------------------------------
void CySocket::set_reuse_port()
{
#ifdef SO_REUSEPORT
if(!set_option(SOL_SOCKET, SO_REUSEPORT))
dperror("setsockopt(SO_REUSEPORT)\n");
#endif
}
//-----------------------------------------------------------------------------
// Set Re-Use Option for Address.
//-----------------------------------------------------------------------------
void CySocket::set_reuse_addr()
{
#ifdef SO_REUSEADDR
if(!set_option(SOL_SOCKET, SO_REUSEADDR))
dperror("setsockopt(SO_REUSEADDR)\n");
#endif
}
//-----------------------------------------------------------------------------
// Set Keep-Alive Option for Socket.
//-----------------------------------------------------------------------------
void CySocket::set_keep_alive()
{
#ifdef SO_KEEPALIVE
if(!set_option(SOL_SOCKET, SO_KEEPALIVE))
dperror("setsockopt(SO_KEEPALIVE)\n");
#endif
}
//-----------------------------------------------------------------------------
// Set Keep-Alive Option for Socket.
//-----------------------------------------------------------------------------
void CySocket::set_tcp_nodelay()
{
#ifdef TCP_NODELAY
if(!set_option(IPPROTO_TCP, TCP_NODELAY))
dperror("setsockopt(SO_KEEPALIVE)\n");
#endif
}
//=============================================================================
// Send and receive
//=============================================================================
//-----------------------------------------------------------------------------
// Read a buffer (normal or SSL)
//-----------------------------------------------------------------------------
int CySocket::Read(char *buffer, unsigned int length)
{
#ifdef Y_CONFIG_USE_OPEN_SSL
if(isSSLSocket)
return SSL_read(ssl, buffer, length);
else
#endif
return ::read(sock, buffer, length);
}
//-----------------------------------------------------------------------------
// Send a buffer (normal or SSL)
//-----------------------------------------------------------------------------
int CySocket::Send(char const *buffer, unsigned int length)
{
unsigned int len = 0;
#ifdef Y_CONFIG_USE_OPEN_SSL
if(isSSLSocket)
len = SSL_write(ssl, buffer, length);
else
#endif
len = ::send(sock, buffer, length, MSG_NOSIGNAL);
if(len >= 0)
BytesSend += len;
return len;
}
//-----------------------------------------------------------------------------
// Check if Socket was closed by client
//-----------------------------------------------------------------------------
bool CySocket::CheckSocketOpen()
{
char buffer[32];
#ifdef CONFIG_SYSTEM_CYGWIN
return !(recv(sock, buffer, sizeof(buffer), MSG_PEEK | MSG_NOSIGNAL) == 0);
#else
return !(recv(sock, buffer, sizeof(buffer), MSG_PEEK | MSG_DONTWAIT) == 0);
#endif
}
//=============================================================================
// Aggregated Send- and Receive- Operations
//=============================================================================
//-----------------------------------------------------------------------------
// BASIC Send File over Socket for FILE*
// fd is an opened FILE-Descriptor
//-----------------------------------------------------------------------------
int CySocket::SendFile(int filed)
{
if(!isValid)
return false;
#ifdef Y_CONFIG_HAVE_SENDFILE
// does not work with SSL !!!
off_t start = 0;
off_t end = lseek(filed,0,SEEK_END);
int written = 0;
if((written = ::sendfile(sock,filed,&start,end)) == -1)
{
perror("sendfile failed\n");
return false;
}
else
BytesSend += written;
#else
char sbuf[1024];
unsigned int r=0;
while ((r=read(filed, sbuf, 1024)) > 0)
{
if (Send(sbuf, r) < 0)
{
perror("sendfile failed\n");
return false;
}
}
#endif // Y_CONFIG_HAVE_SENDFILE
log_level_printf(9,"<Sock:SendFile>: Bytes:%ld\n", BytesSend);
return true;
}
//-----------------------------------------------------------------------------
// Receive File over Socket for FILE* filed
// read/write in small blocks (to save memory).
// usind sleep for sync input
// fd is an opened FILE-Descriptor
//-----------------------------------------------------------------------------
//TODO: Write upload Progress Informations into a file
unsigned int CySocket::ReceiveFileGivenLength(int filed, unsigned int _length)
{
unsigned int _readbytes = 0;
char buffer[RECEIVE_BLOCK_LEN];
int retries=0;
do
{
// check bytes in Socket buffer
u_long readarg = 0;
#ifdef Y_CONFIG_USE_OPEN_SSL
if(isSSLSocket)
readarg = RECEIVE_BLOCK_LEN;
else
#endif
{
if(ioctl(sock, FIONREAD, &readarg) != 0)// How many bytes avaiable on socket?
break;
if(readarg > RECEIVE_BLOCK_LEN) // enough bytes to read
readarg = RECEIVE_BLOCK_LEN; // read only given length
}
if(readarg == 0) // nothing to read: sleep
{
retries++;
if(retries >NON_BLOCKING_MAX_RETRIES)
break;
sleep(1);
}
else
{
int bytes_gotten = Read(buffer, readarg);
if(bytes_gotten == -1 && errno == EINTR)// non-blocking
continue;
if(bytes_gotten <= 0) // ERROR Code gotten or Conection closed by peer
{
isValid = false;
break;
}
_readbytes += bytes_gotten;
if (write(filed, buffer, bytes_gotten) != bytes_gotten)
{
perror("write file failed\n");
return 0;
}
retries = 0;
if(bytes_gotten < NON_BLOCKING_TRY_BYTES) // to few bytes gotten: sleep
sleep(1);
}
log_level_printf(8,"Receive Block length:%d all:%d\n",_readbytes, _length);
}
while(_readbytes + RECEIVE_BLOCK_LEN < _length);
return _readbytes;
}
//-----------------------------------------------------------------------------
// read all data avaiable on Socket
//-----------------------------------------------------------------------------
std::string CySocket::ReceiveBlock()
{
std::string result = "";
char buffer[RECEIVE_BLOCK_LEN];
if(!isValid || !isOpened)
return "";
// signal(SIGALRM, ytimeout);
alarm(1);
while(true)
{
// check bytes in Socket buffer
u_long readarg = 0;
#ifdef Y_CONFIG_USE_OPEN_SSL
if(isSSLSocket)
readarg = RECEIVE_BLOCK_LEN;
else
#endif
// determine bytes that can be read
{
if(ioctl(sock, FIONREAD, &readarg) != 0)
break;
if(readarg == 0) // nothing to read
break;
if(readarg > RECEIVE_BLOCK_LEN) // need more loops
readarg = RECEIVE_BLOCK_LEN;
}
// Read data
int bytes_gotten = Read(buffer, readarg);
if(bytes_gotten == -1 && errno == EINTR)// non-blocking
continue;
if(bytes_gotten <= 0) // ERROR Code gotten or Conection closed by peer
{
isValid = false;
break;
}
result.append(buffer, bytes_gotten);
if((u_long)bytes_gotten < readarg) // no more bytes
break;
}
alarm(0);
signal(SIGALRM,SIG_IGN);
return result;
}
//-----------------------------------------------------------------------------
// Read on line (Ends with LF) or maximum MAX_LINE_BUFFER chars
// Result Contains [CR]LF!
//-----------------------------------------------------------------------------
std::string CySocket::ReceiveLine()
{
char buffer[MAX_LINE_BUFFER];
int bytes_gotten = 0;
std::string result="";
while(true)
{
// read one char
if(Read(buffer+bytes_gotten, 1) == 1)
{
if(buffer[bytes_gotten] == '\n')
break;
}
else
{
isValid = false;
break;
}
if(bytes_gotten < MAX_LINE_BUFFER-1)
bytes_gotten ++;
else
break;
}
buffer[++bytes_gotten] = '\0';
result.assign(buffer, bytes_gotten);
return result;
}

View File

@@ -0,0 +1,98 @@
//=============================================================================
// YHTTPD
// Socket Class : Basic Socket Operations
//-----------------------------------------------------------------------------
// Socket-Handler
//=============================================================================
#ifndef __yhttpd_ysocket_h__
#define __yhttpd_ysocket_h__
// system
#include <netinet/in.h>
#include <sys/types.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include "yconfig.h"
#include "ytypes_globals.h"
#ifdef Y_CONFIG_USE_OPEN_SSL
#include <openssl/ssl.h>
#endif
//-----------------------------------------------------------------------------
// Some common socket definitions
#ifndef SOCKET
#define SOCKET int
#endif
#ifndef INVALID_SOCKET
#define INVALID_SOCKET (SOCKET)(~0)
#define SOCKET_ERROR (-1)
#endif
//-----------------------------------------------------------------------------
#define NON_BLOCKING_MAX_RETRIES 10 // Retries, if receive buffer is empty
#define NON_BLOCKING_TRY_BYTES 512 // try to catch min bytes .. else sleep
#define RECEIVE_BLOCK_LEN 1024 // receiving block length
#define MAX_LINE_BUFFER 1024*2 // Max length of one line
//-----------------------------------------------------------------------------
class CySocket
{
public:
// constructor & destructor
CySocket();
CySocket(SOCKET s):sock(s){init();};
virtual ~CySocket(void);
void init(void); // some extra initialization
#ifdef Y_CONFIG_USE_OPEN_SSL
bool initAsSSL(void); // initialize this socket as a SSL-socket
static bool initSSL(void); // global initialize of SSL-Library, CTX and cerificate/key-file
static std::string SSL_pemfile; // "server.pem" file with SSL-Certificate (now: only one certificate at all)
static std::string SSL_CA_file; // CA Certificate (optional)
#endif
// Socket handling
bool handling; // true: Socket yet is handled by a Connection Thread
bool isOpened; // is this socket open?
bool isValid; // false on Socket Errors. Must close.
struct timeval tv_start_waiting; // Put keep-alive Socket to Wait-Queue
void close(void); // Close Socket
void shutdown(void); // Shutdown Socket
bool listen(int port, int max_connections); // Listen on Port for max Slave Connections
CySocket* accept(); // Wait for Connection. Returns "slave" Socket
void setAddr(sockaddr_in _clientaddr);
std::string get_client_ip(void); // Get IP from Client
SOCKET get_socket(){return sock;} // Return "C" Socket-ID
int get_accept_port(void); // Get Port for accepted connection
// send & receive (basic)
int Read(char *buffer, unsigned int length); // Read a buffer (normal or SSL)
int Send(char const *buffer, unsigned int length); // Send a buffer (normal or SSL)
bool CheckSocketOpen(); // check if socket was closed by client
// send & receive
int SendFile(int filed); // Send a File
std::string ReceiveBlock(); // receive a Block. Look at length
unsigned int ReceiveFileGivenLength(int filed, unsigned int _length); // Receive File of given length
std::string ReceiveLine(); // receive until "\n"
protected:
long BytesSend; // Bytes send over Socket
bool set_option(int typ, int option); // Set Socket Options
void set_reuse_port(); // Set Reuse Port Option for Socket
void set_reuse_addr(); // Set Reuse Address Option for Socket
void set_keep_alive(); // Set Keep-Alive Option for Socket
void set_tcp_nodelay();
#ifdef Y_CONFIG_USE_OPEN_SSL
bool isSSLSocket; // This is a SSL based Socket
static SSL_CTX *SSL_ctx; // Global SSL ctx object
SSL *ssl; // ssl habdler for this socket
#endif
private:
std::string receive_buffer; // buffer for receive from socket
sockaddr_in addr; // "slave" Client Socket Data
socklen_t addr_len; // Length of addr struct
SOCKET sock; // "C" Socket-ID
};
#endif // __yhttpd_ysocket_h__

View File

@@ -0,0 +1,157 @@
//=============================================================================
// YHTTPD
// Type Definitions and Global Variables
//=============================================================================
//-----------------------------------------------------------------------------
#ifndef __yhttpd_types_globals_h__
#define __yhttpd_types_globals_h__
// c++
#include <string>
#include <map>
#include <vector>
//=============================================================================
// Global Types
//=============================================================================
typedef std::map<std::string, std::string> CStringList;
typedef std::vector<std::string> CStringArray;
typedef std::vector<std::string> CStringVector;
//=============================================================================
// HTTP Protocol Definitions
//=============================================================================
// HTTP-Status codes
typedef enum
{
HTTP_NIL = -1,
HTTP_CONTINUE = 100,
HTTP_SWITCHING_PROTOCOLS = 101, // not used
HTTP_OK = 200, // use: everything is right. request (and response) handled
HTTP_CREATED = 201, // not used
HTTP_ACCEPTED = 202, // use: idicate accept of request. request take more time.
HTTP_NON_AUTHORITATIVE_INFO = 203,
HTTP_NO_CONTENT = 204,
HTTP_RESET_CONTENT = 205, // not used
HTTP_PARTIAL_CONTENT = 206, // not used
HTTP_MULTIBLE_CHOICES = 300, // not used
HTTP_MOVED_PERMANENTLY = 301,
HTTP_MOVED_TEMPORARILY = 302,
HTTP_SEE_OTHER = 303, // not used
HTTP_NOT_MODIFIED = 304,
HTTP_USE_PROXY = 305, // not used
HTTP_TEMPORARY_REDIRECT = 307, // not used
HTTP_BAD_REQUEST = 400,
HTTP_UNAUTHORIZED = 401,
HTTP_PAYMENT_REQUIRED = 402, // not used
HTTP_FORBIDDEN = 403,
HTTP_NOT_FOUND = 404,
HTTP_METHOD_NOT_ALLOWED = 405, // not used
HTTP_NOT_ACCEPTABLE = 406, // not used
HTTP_PROXY_AUTHENTICATION_REQUIRED= 407, // not used
HTTP_REQUEST_TIMEOUT = 408, // not used
HTTP_CONFLICT = 409, // not used
HTTP_GONE = 410, // not used
HTTP_LENGTH_REQUIRED = 411, // not used
HTTP_PRECONDITION_FAILED = 412, // not used
HTTP_REQUEST_ENTITY_TOO_LARGE = 413, // not used
HTTP_REQUEST_URI_TOO_LARGE = 414, // not used
HTTP_UNSUPPORTED_MEDIA_TYPE = 415, // not used
HTTP_REQUEST_RANGE_NOT_SATISFIABLE= 416, // not used
HTTP_EXPECTAION_FAILED = 417, // not used
HTTP_INTERNAL_SERVER_ERROR = 500,
HTTP_NOT_IMPLEMENTED = 501,
HTTP_BAD_GATEWAY = 502, // not used
HTTP_SERVICE_UNAVAILABLE = 503, // not used
HTTP_GATEWAY_TIMEOUT = 504, // not used
HTTP_HTTP_VERSION_NOT_SUPPORTED = 505, // not used
} HttpResponseType;
typedef struct
{
HttpResponseType type;
const char *name;
const char *info;
} HttpEnumString;
static const HttpEnumString httpResponseNames[] = {
{ HTTP_CONTINUE, "Continue" },
{ HTTP_SWITCHING_PROTOCOLS, "Switching Protocols" },
{ HTTP_OK, "OK" },
{ HTTP_CREATED, "Created" },
{ HTTP_ACCEPTED, "Accepted" },
{ HTTP_NON_AUTHORITATIVE_INFO, "No Authorative Info" },
{ HTTP_NO_CONTENT, "No Content" },
{ HTTP_RESET_CONTENT, "Reset Content" },
{ HTTP_PARTIAL_CONTENT, "Partial Content" },
{ HTTP_MULTIBLE_CHOICES, "Multiple Choices" },
{ HTTP_MOVED_PERMANENTLY, "Moved Permanently" },
{ HTTP_MOVED_TEMPORARILY, "Moved Temporarily" },
{ HTTP_SEE_OTHER, "See Other" },
{ HTTP_NOT_MODIFIED, "Not Modified" },
{ HTTP_USE_PROXY, "Use Proxy" },
{ HTTP_TEMPORARY_REDIRECT, "Temporary Redirect" },
{ HTTP_BAD_REQUEST, "Bad Request", "Unsupported method." },
{ HTTP_UNAUTHORIZED, "Unauthorized", "Access denied." },
{ HTTP_PAYMENT_REQUIRED, "Payment Required" },
{ HTTP_FORBIDDEN, "Forbidden", "" },
{ HTTP_NOT_FOUND, "Not Found", "The requested URL was not found on this server." },
{ HTTP_METHOD_NOT_ALLOWED, "Method Not Allowed" },
{ HTTP_NOT_ACCEPTABLE, "Not Acceptable" },
{ HTTP_PROXY_AUTHENTICATION_REQUIRED,"Proxy Authentication Required" },
{ HTTP_REQUEST_TIMEOUT, "Request Time-out" },
{ HTTP_CONFLICT, "Conflict" },
{ HTTP_GONE, "Gone" },
{ HTTP_LENGTH_REQUIRED, "Length Required" },
{ HTTP_PRECONDITION_FAILED, "Precondition Failed" },
{ HTTP_REQUEST_ENTITY_TOO_LARGE,"Request Entity Too Large" },
{ HTTP_REQUEST_URI_TOO_LARGE, "Request-URI Too Large" },
{ HTTP_UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type" },
{ HTTP_REQUEST_RANGE_NOT_SATISFIABLE,"Requested range not satisfiable" },
{ HTTP_EXPECTAION_FAILED, "Expectation Failed" },
{ HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error", "Internal Server Error" },
{ HTTP_NOT_IMPLEMENTED, "Not Implemented", "The requested method is not recognized by this server." },
{ HTTP_BAD_GATEWAY, "Bad Gateway" },
{ HTTP_SERVICE_UNAVAILABLE, "Service Unavailable" },
{ HTTP_GATEWAY_TIMEOUT, "Gateway Time-out" },
{ HTTP_HTTP_VERSION_NOT_SUPPORTED,"HTTP Version not supported" },
};
// HTTP-methods
typedef enum
{
M_UNKNOWN = 0,
M_POST = 1,
M_GET,
M_HEAD,
M_PUT,
M_DELETE,
M_TRACE
} THttp_Method;
// Date Format RFC 1123
static const char RFC1123FMT[] = "%a, %d %b %Y %H:%M:%S GMT";
// mime-table
typedef struct
{
const char *fileext;
const char *mime;
} TMimePair;
static const TMimePair MimeFileExtensions[] = {
{"xml", "text/xml"},
{"htm", "text/html"},
{"html", "text/html"},
{"yhtm", "text/html"},
{"yhtml", "text/html"},
{"jpg", "image/jpeg"},
{"jpeg", "image/jpeg"},
{"gif", "image/gif"},
{"png", "image/png"},
{"txt", "image/plain"},
{"css", "text/css"},
};
#endif // __yhttpd_types_globals_h__

View File

@@ -0,0 +1,507 @@
//=============================================================================
// YHTTPD
// Webserver Class : Until now: exact one instance
//=============================================================================
// c++
#include <cerrno>
#include <csignal>
// system
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <fcntl.h>
// tuxbox
#include <configfile.h>
// yhttpd
#include "yhttpd.h"
#include "ytypes_globals.h"
#include "ywebserver.h"
#include "ylogging.h"
#include "helper.h"
#include "ysocket.h"
#include "yconnection.h"
#include "yrequest.h"
//=============================================================================
// Initialization of static variables
//=============================================================================
bool CWebserver::is_threading = true;
pthread_mutex_t CWebserver::mutex = PTHREAD_MUTEX_INITIALIZER;;
//=============================================================================
// Constructor & Destructor & Initialization
//=============================================================================
CWebserver::CWebserver()
{
terminate = false;
for(int i=0;i<HTTPD_MAX_CONNECTIONS;i++)
{
Connection_Thread_List[i] = (pthread_t)NULL;
SocketList[i] = NULL;
}
FD_ZERO(&master); // initialize FD_SETs
FD_ZERO(&read_fds);
fdmax = 0;
open_connections = 0;
//pthread_attr_init(&attr);
//pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
port=80;
}
//-----------------------------------------------------------------------------
CWebserver::~CWebserver()
{
listenSocket.close();
}
//=============================================================================
// Start Webserver. Main-Loop.
//-----------------------------------------------------------------------------
// Wait for Connection and schedule ist to handle_connection()
// HTTP/1.1 should can handle "keep-alive" connections to reduce socket
// creation and handling. This is handled using FD_SET and select Socket
// mechanism.
// select wait for socket-activity. Cases:
// 1) get a new connection
// 2) re-use a socket
// 3) timeout: close unused sockets
//-----------------------------------------------------------------------------
// from RFC 2616:
// 8 Connections
// 8.1 Persistent Connections
// 8.1.1 Purpose
//
// Prior to persistent connections, a separate TCP connection was
// established to fetch each URL, increasing the load on HTTP servers
// and causing congestion on the Internet. The use of inline images and
// other associated data often require a client to make multiple
// requests of the same server in a short amount of time. Analysis of
// these performance problems and results from a prototype
// implementation are available [26] [30]. Implementation experience and
// measurements of actual HTTP/1.1 (RFC 2068) implementations show good
// results [39]. Alternatives have also been explored, for example,
// T/TCP [27].
//
// Persistent HTTP connections have a number of advantages:
//
// - By opening and closing fewer TCP connections, CPU time is saved
// in routers and hosts (clients, servers, proxies, gateways,
// tunnels, or caches), and memory used for TCP protocol control
// blocks can be saved in hosts.
//
// - HTTP requests and responses can be pipelined on a connection.
// Pipelining allows a client to make multiple requests without
// waiting for each response, allowing a single TCP connection to
// be used much more efficiently, with much lower elapsed time.
//
// - Network congestion is reduced by reducing the number of packets
// caused by TCP opens, and by allowing TCP sufficient time to
// determine the congestion state of the network.
//
// - Latency on subsequent requests is reduced since there is no time
// spent in TCP's connection opening handshake.
//
// - HTTP can evolve more gracefully, since errors can be reported
// without the penalty of closing the TCP connection. Clients using
// future versions of HTTP might optimistically try a new feature,
// but if communicating with an older server, retry with old
// semantics after an error is reported.
//
// HTTP implementations SHOULD implement persistent connections.
//=============================================================================
#define MAX_TIMEOUTS_TO_CLOSE 10
#define MAX_TIMEOUTS_TO_TEST 100
bool CWebserver::run(void)
{
if(!listenSocket.listen(port, HTTPD_MAX_CONNECTIONS))
{
dperror("Socket cannot bind and listen. Abort.\n");
return false;
}
#ifdef Y_CONFIG_FEATURE_KEEP_ALIVE
// initialize values for select
int listener = listenSocket.get_socket();// Open Listener
struct timeval tv; // timeout struct
FD_SET(listener, &master); // add the listener to the master set
fdmax = listener; // init max fd
fcntl(listener, F_SETFD , O_NONBLOCK); // listener master socket non-blocking
int timeout_counter = 0; // Counter for Connection Timeout checking
int test_counter = 0; // Counter for Testing long running Connections
// main Webserver Loop
while(!terminate)
{
// select : init vars
read_fds = master; // copy it
tv.tv_usec = 10000; // microsec: Timeout for select ! for re-use / keep-alive socket
tv.tv_sec = 0; // seconds
int fd = -1;
// select : wait for socket activity
if(open_connections <= 0) // No open Connection. Wait in select.
fd = select(fdmax+1,&read_fds, NULL, NULL, NULL);// wait for socket activity
else
fd = select(fdmax+1,&read_fds, NULL, NULL, &tv);// wait for socket activity or timeout
// too much to do : sleep
if(open_connections >= HTTPD_MAX_CONNECTIONS-1)
sleep(1);
// Socket Error?
if(fd == -1 && errno != EINTR)
{
perror("select");
return false;
}
// Socket Timeout?
if(fd == 0)
{
// Testoutput for long living threads
if(++test_counter >= MAX_TIMEOUTS_TO_TEST)
{
for(int j=0;j < HTTPD_MAX_CONNECTIONS;j++)
if(SocketList[j] != NULL) // here is a socket
log_level_printf(2,"FD-TEST sock:%d handle:%d open:%d\n",SocketList[j]->get_socket(),
SocketList[j]->handling,SocketList[j]->isOpened);
test_counter=0;
}
// some connection closing previous missed?
if(++timeout_counter >= MAX_TIMEOUTS_TO_CLOSE)
{
CloseConnectionSocketsByTimeout();
timeout_counter=0;
}
continue; // main loop again
}
//----------------------------------------------------------------------------------------
// Check all observed descriptors & check new or re-use Connections
//----------------------------------------------------------------------------------------
for(int i = listener; i <= fdmax; i++)
{
int slot = -1;
if(FD_ISSET(i, &read_fds)) // Socket observed?
{ // we got one!!
if (i == listener) // handle new connections
slot = AcceptNewConnectionSocket();
else // Connection on an existing open Socket = reuse (keep-alive)
{
slot = SL_GetExistingSocket(i);
if(slot>=0)
log_level_printf(2,"FD: reuse con fd:%d\n",SocketList[slot]->get_socket());
}
// prepare Connection handling
if(slot>=0)
if(SocketList[slot] != NULL && !SocketList[slot]->handling && SocketList[slot]->isValid)
{
log_level_printf(2,"FD: START CON HANDLING con fd:%d\n",SocketList[slot]->get_socket());
FD_CLR(SocketList[slot]->get_socket(), &master); // remove from master set
SocketList[slot]->handling = true; // prepares for thread-handling
if(!handle_connection(SocketList[slot]))// handle this activity
{ // Can not handle more threads
char httpstr[]=HTTP_PROTOCOL " 503 Service Unavailable\r\n\r\n";
SocketList[slot]->Send(httpstr, strlen(httpstr));
SL_CloseSocketBySlot(slot);
}
}
}
}// for
CloseConnectionSocketsByTimeout(); // Check connections to close
}//while
#else
while(!terminate)
{
CySocket *newConnectionSock;
if(!(newConnectionSock = listenSocket.accept() )) //Now: Blocking wait
{
dperror("Socket accept error. Continue.\n");
continue;
}
log_level_printf(3,"Socket connect from %s\n", (listenSocket.get_client_ip()).c_str() );
#ifdef Y_CONFIG_USE_OPEN_SSL
if(Cyhttpd::ConfigList["SSL"]=="true")
newConnectionSock->initAsSSL(); // make it a SSL-socket
#endif
handle_connection(newConnectionSock);
}
#endif
return true;
}
//=============================================================================
// SocketList Handler
//=============================================================================
//-----------------------------------------------------------------------------
// Accept new Connection
//-----------------------------------------------------------------------------
int CWebserver::AcceptNewConnectionSocket()
{
int slot = -1;
CySocket *connectionSock = NULL;
int newfd;
if(!(connectionSock = listenSocket.accept() )) // Blocking wait
{
dperror("Socket accept error. Continue.\n");
delete connectionSock;
return -1;
}
#ifdef Y_CONFIG_USE_OPEN_SSL
if(Cyhttpd::ConfigList["SSL"]=="true")
connectionSock->initAsSSL(); // make it a SSL-socket
#endif
log_level_printf(2,"FD: new con fd:%d on port:%d\n",connectionSock->get_socket(), connectionSock->get_accept_port());
// Add Socket to List
slot = SL_GetFreeSlot();
if(slot < 0)
{
connectionSock->close();
aprintf("No free Slot in SocketList found. Open:%d\n",open_connections);
}
else
{
SocketList[slot] = connectionSock; // put it to list
fcntl(connectionSock->get_socket() , F_SETFD , O_NONBLOCK); // set non-blocking
open_connections++; // count open connectins
newfd = connectionSock->get_socket();
if (newfd > fdmax) // keep track of the maximum fd
fdmax = newfd;
}
return slot;
}
//-----------------------------------------------------------------------------
// Get Index for Socket from SocketList
//-----------------------------------------------------------------------------
int CWebserver::SL_GetExistingSocket(SOCKET sock)
{
int slot = -1;
for(int j=0;j < HTTPD_MAX_CONNECTIONS;j++)
if(SocketList[j] != NULL // here is a socket
&& SocketList[j]->get_socket() == sock) // we know that socket
{
slot = j;
break;
}
return slot;
}
//-----------------------------------------------------------------------------
// Get Index for free Slot in SocketList
//-----------------------------------------------------------------------------
int CWebserver::SL_GetFreeSlot()
{
int slot = -1;
for(int j=0;j < HTTPD_MAX_CONNECTIONS;j++)
if(SocketList[j] == NULL) // here is a free slot
{
slot = j;
break;
}
return slot;
}
//-----------------------------------------------------------------------------
// Look for Sockets to close
//-----------------------------------------------------------------------------
void CWebserver::CloseConnectionSocketsByTimeout()
{
CySocket *connectionSock = NULL;
for(int j=0;j < HTTPD_MAX_CONNECTIONS;j++)
if(SocketList[j] != NULL // here is a socket
&& !SocketList[j]->handling) // it is not handled
{
connectionSock = SocketList[j];
SOCKET thisSocket = connectionSock->get_socket();
bool shouldClose = true;
if(!connectionSock->isValid) // If not valid -> close
; // close
else if(connectionSock->tv_start_waiting.tv_sec != 0 || SocketList[j]->tv_start_waiting.tv_usec != 0)
{ // calculate keep-alive timeout
struct timeval tv_now;
struct timezone tz_now;
gettimeofday(&tv_now, &tz_now);
long long tdiff = ((tv_now.tv_sec - connectionSock->tv_start_waiting.tv_sec) * 1000000
+ (tv_now.tv_usec - connectionSock->tv_start_waiting.tv_usec));
if(tdiff < HTTPD_KEEPALIVE_TIMEOUT || tdiff <0)
shouldClose = false;
}
if(shouldClose)
{
log_level_printf(2,"FD: close con Timeout fd:%d\n",thisSocket);
SL_CloseSocketBySlot(j);
}
}
}
//-----------------------------------------------------------------------------
// Add Socket fd to FD_SET again (for select-handling)
// Add start-time for waiting for connection re-use / keep-alive
//-----------------------------------------------------------------------------
void CWebserver::addSocketToMasterSet(SOCKET fd)
{
int slot = SL_GetExistingSocket(fd); // get slot/index for fd
if(slot<0)
return;
log_level_printf(2,"FD: add to master fd:%d\n",fd);
struct timeval tv_now;
struct timezone tz_now;
gettimeofday(&tv_now, &tz_now);
SocketList[slot]->tv_start_waiting = tv_now; // add keep-alive wait time
FD_SET(fd, &master); // add fd to select-master-set
}
//-----------------------------------------------------------------------------
// Close (FD_SET handled) Socket
// Clear it from SocketList
//-----------------------------------------------------------------------------
void CWebserver::SL_CloseSocketBySlot(int slot)
{
open_connections--; // count open connections
if(SocketList[slot] == NULL)
return;
SocketList[slot]->handling = false; // no handling anymore
FD_CLR(SocketList[slot]->get_socket(), &master);// remove from master set
SocketList[slot]->close(); // close the socket
delete SocketList[slot]; // destroy ySocket
SocketList[slot] = NULL; // free in list
}
//=============================================================================
// Thread Handling
//=============================================================================
//-----------------------------------------------------------------------------
// Check if IP is allowed for keep-alive
//-----------------------------------------------------------------------------
bool CWebserver::CheckKeepAliveAllowedByIP(std::string client_ip)
{
pthread_mutex_lock( &mutex );
bool do_keep_alive = true;
CStringVector::const_iterator it = conf_no_keep_alive_ips.begin();
while(it != conf_no_keep_alive_ips.end())
{
if(trim(*it) == client_ip)
do_keep_alive = false;
it++;
}
pthread_mutex_unlock( &mutex );
return do_keep_alive;
}
//-----------------------------------------------------------------------------
// Set Entry(number)to NULL in Threadlist
//-----------------------------------------------------------------------------
void CWebserver::clear_Thread_List_Number(int number)
{
pthread_mutex_lock( &mutex );
if(number <HTTPD_MAX_CONNECTIONS)
Connection_Thread_List[number] = (pthread_t)NULL;
CloseConnectionSocketsByTimeout();
pthread_mutex_unlock( &mutex );
}
//-----------------------------------------------------------------------------
// A new Connection is established to newSock. Create a (threaded) Connection
// and handle the Request.
//-----------------------------------------------------------------------------
bool CWebserver::handle_connection(CySocket *newSock)
{
void *WebThread(void *args); //forward declaration
// create arguments
TWebserverConnectionArgs *newConn = new TWebserverConnectionArgs;
newConn->ySock = newSock;
newConn->ySock->handling = true;
newConn->WebserverBackref = this;
//newConn->is_treaded = is_threading;
newConn->is_treaded = false;
int index = -1;
if(0) /*if(is_threading) FIXME not work */
{
pthread_mutex_lock( &mutex );
// look for free Thread slot
for(int i=0;i<HTTPD_MAX_CONNECTIONS;i++)
if(Connection_Thread_List[i] == (pthread_t)NULL)
{
index = i;
break;
}
if(index == -1)
{
dperror("Maximum Connection-Threads reached\n");
pthread_mutex_unlock( &mutex );
return false;
}
newConn->thread_number = index; //remember Index of Thread slot (for clean up)
// Create an orphan Thread. It is not joinable anymore
pthread_mutex_unlock( &mutex );
// start connection Thread
if(pthread_create(&Connection_Thread_List[index], &attr, WebThread, (void *)newConn) != 0)
dperror("Could not create Connection-Thread\n");
}
else // non threaded
WebThread((void *)newConn);
return ((index != -1) || !is_threading);
}
//-------------------------------------------------------------------------
// Webserver-Thread for each connection
//-------------------------------------------------------------------------
void *WebThread(void *args)
{
CWebserverConnection *con;
CWebserver *ws;
TWebserverConnectionArgs *newConn = (TWebserverConnectionArgs *) args;
ws = newConn->WebserverBackref;
bool is_threaded = newConn->is_treaded;
if(is_threaded)
log_level_printf(1,"++ Thread 0x06%X gestartet\n", (int) pthread_self());
if (!newConn) {
dperror("WebThread called without arguments!\n");
if(newConn->is_treaded)
pthread_exit(NULL);
}
// (1) create & init Connection
con = new CWebserverConnection(ws);
con->Request.UrlData["clientaddr"] = newConn->ySock->get_client_ip(); // TODO:here?
con->sock = newConn->ySock; // give socket reference
newConn->ySock->handling = true; // dont handle this socket now be webserver main loop
// (2) handle the connection
con->HandleConnection();
// (3) end connection handling
#ifdef Y_CONFIG_FEATURE_KEEP_ALIVE
if(!con->keep_alive)
log_level_printf(2,"FD SHOULD CLOSE sock:%d!!!\n",con->sock->get_socket());
else
ws->addSocketToMasterSet(con->sock->get_socket()); // add to master set
#else
delete newConn->ySock;
#endif
if(!con->keep_alive)
con->sock->isValid = false;
con->sock->handling = false; // socket can be handled by webserver main loop (select) again
// (4) end thread
delete con;
int thread_number = newConn->thread_number;
delete newConn;
if(is_threaded)
{
log_level_printf(1,"-- Thread 0x06%X beendet\n",(int)pthread_self());
ws->clear_Thread_List_Number(thread_number);
pthread_exit(NULL);
}
return NULL;
}

View File

@@ -0,0 +1,91 @@
//=============================================================================
// YHTTPD
// Webserver Class : Until now: exact one instance
//-----------------------------------------------------------------------------
// The Webserver Class creates one "master" Listener Socket on given Port.
// If a Connection is accepted, a new Socket ist used. The Webserver creates
// a new Connection-Class witch is (normaly) Threaded. The Connection-Class
// handles Request and Response.
// For HTTP/1.1 permanent Connections (keep-alive) socket-multiplexing using
// FD_SET and select() is implemented. Sockets are stored in SocketList and
// can be reused for the connected client (to reduce socket handling overhead).
// Reuse of connections is often done by pipelinig (read->send->read->send ...)
//=============================================================================
#ifndef __yhttpd_ywebserver_h__
#define __yhttpd_ywebserver_h__
// system
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
// yhttpd
#include "yconfig.h"
#include "ytypes_globals.h"
#include "ysocket.h"
//-----------------------------------------------------------------------------
class CWebserver; //forward declaration
//-----------------------------------------------------------------------------
// Arguments for Connection-Thread
//-----------------------------------------------------------------------------
typedef struct
{
CySocket *ySock; // Connection "Slave" Socket
CWebserver *WebserverBackref; // Backreference to Webserver
bool is_treaded; // Use threading?
int thread_number; // Number of Thread (informational use)
} TWebserverConnectionArgs;
//-----------------------------------------------------------------------------
// until now: One Instance for one hosted Web
//-----------------------------------------------------------------------------
class CWebserver
{
private:
static pthread_mutex_t mutex;
pthread_t Connection_Thread_List[HTTPD_MAX_CONNECTIONS]; //Thread-List per webserver
fd_set master; // master file descriptor list for select()
fd_set read_fds; // read file descriptor list for select()
int fdmax; // maximum file descriptor number
CySocket *SocketList[HTTPD_MAX_CONNECTIONS]; // List of concurrent hadled connections
int open_connections; // Number of opened connections
pthread_attr_t attr; // pthread Attributes for deattach
protected:
bool terminate; // flag: indicate to terminate the Webserver
CySocket listenSocket; // Master Socket for listening
unsigned int port; // Port to listen on
bool handle_connection(CySocket *newSock); // Create a new Connection Instance and handle Connection
// Connection Socket handling
int AcceptNewConnectionSocket(); // Start new Socket connection
void CloseConnectionSocketsByTimeout(); // Check Sockets to close
void SL_CloseSocketBySlot(int slot); // Close socket by slot index
int SL_GetExistingSocket(SOCKET sock); // look for socket reuse
int SL_GetFreeSlot(); // get free slot
public:
static bool is_threading; // Use Threading for new Connections
CStringVector conf_no_keep_alive_ips; // List of IP for NO keep-alive
// constructor & destructor
CWebserver();
~CWebserver(void);
void init(unsigned int _port, bool _is_threading) // Initialize Webserver Settings
{port=_port; is_threading=_is_threading;}
void set_conf_no_keep_alive_ips(CStringVector _conf_no_keep_alive_ips)
{conf_no_keep_alive_ips=_conf_no_keep_alive_ips;}
bool run(void); // Start the Webserver
void stop(void) // Stop the Webserver
{terminate=true;};
// public for WebTread
void clear_Thread_List_Number(int number); // Set Entry(number)to NULL in Threadlist
void addSocketToMasterSet(SOCKET fd); // add Socket to select master set
bool CheckKeepAliveAllowedByIP(std::string client_ip); // Check if IP is allowed for keep-alive
};
#endif // __yhttpd_ywebserver_h__