//
// webserver.cpp
//
// Simple HTTP server sample for sanos
//
#include "webserver.h"
#include "socket.h"
//============================================================================
static void __fprintf( int fd, const char *fmt, ... )
{
char buf[4096];
va_list va;
va_start( va, fmt );
vsnprintf( buf, sizeof( buf ) - 1, fmt, va );
va_end( va );
send( fd, buf, strlen( buf ), 0 );
}
/*------------------------------------------------------------------------
* recvln - recv from socket until newline or EOF is encountered
* Flush to newline or EOF and return on full buffer. Returns data length.
*------------------------------------------------------------------------
*/
static int recvln(int conn, char *buff, int buffsz)
{
char *bp = buff, c;
int n = -1;
while(bp - buff < buffsz &&
(n = recv(conn, bp, 1, 0)) > 0) {
if (*bp++ == '\n')
return (bp - buff);
}
if (n < 0)
return -1;
if (bp - buff == buffsz)
while (recv(conn, &c, 1, 0) > 0 && c != '\n');
return (bp - buff);
}
static std::vector < std::string > split( std::string s, std::string delimiter )
{
size_t pos_start = 0, pos_end, delim_len = delimiter.length();
std::string token;
std::vector < std::string > res;
while( ( pos_end = s.find( delimiter, pos_start ) ) != std::string::npos )
{
token = s.substr( pos_start, pos_end - pos_start );
pos_start = pos_end + delim_len;
res.push_back( token );
}
res.push_back( s.substr( pos_start ) );
return res;
}
//============================================================================
const char* WebServer::GetMimeType( const char *name )
{
std::string ext( name );
if( ext.empty() )
return "";
std::string::size_type pos = ext.find_first_of( ".", 0 );
if( pos != std::string::npos )
ext = ext.substr( pos );
if ( ext.compare( ".html" ) == 0 )
return "text/html";
if ( ext.compare( ".htm" ) == 0 )
return "text/html";
if ( ext.compare( ".jpg" ) == 0 )
return "image/jpeg";
if ( ext.compare( ".jpeg" ) == 0 )
return "image/jpeg";
if ( ext.compare( ".gif" ) == 0 )
return "image/gif";
if ( ext.compare( ".png" ) == 0 )
return "image/png";
if ( ext.compare( ".css" ) == 0 )
return "text/css";
if ( ext.compare( ".au" ) == 0 )
return "audio/basic";
if ( ext.compare( ".wav" ) == 0 )
return "audio/wav";
if ( ext.compare( ".avi" ) == 0 )
return "video/x-msvideo";
if ( ext.compare( ".mpeg" ) == 0 )
return "video/mpeg";
if ( ext.compare( ".mpg" ) == 0 )
return "video/mpeg";
if ( ext.compare( ".mp3" ) == 0 )
return "audio/mpeg";
return "";
}
const char* WebServer::RFC1123FMT( time_t t )
{
static char buf[128];
struct tm *tm = gmtime( &t );
buf[0] = '\0';
strftime( buf, sizeof( buf ), "%a, %d %b %Y %H:%M:%S GMT", tm );
return buf;
}
const char* WebServer::RF850FMT( time_t t )
{
static char buf[128];
struct tm *tm = gmtime( &t );
buf[0] = '\0';
strftime( buf, sizeof( buf ), "%d-%b-%Y %H:%M:%S", tm );
return buf;
}
//============================================================================
void WebServer::ReadyToRun( void )
{
int sock;
Socket socket;
/* Upon successful completion, bind() shall return 0; otherwise, -1 shall be returned and errno set to indicate the error. */
if( ( sock = socket.Bind( PORT ) ) == -1 )
{
perror( "Socket::Bind" );
exit( 1 );
}
/* Upon successful completions, listen() shall return 0; otherwise, -1 shall be returned and errno set to indicate the error. */
if( socket.Listen( sock ) == -1 )
{
perror( "Socket::Listen" );
exit( 1 );
}
printf( "HTTP server listening on port %d\n", PORT );
while( 1 )
{
int s;
/* Upon successful completion, accept() shall return the non-negative file descriptor of the accepted socket.
Otherwise, -1 shall be returned and errno set to indicate the error. */
if( (s = socket.Accept( sock )) == -1 )
{
perror( "Socket::Accept" );
exit( 1 );
}
if( Process( s ) == -1 )
exit( 1 );
}
socket.Close(sock);
exit( 0 );
}
void WebServer::SendHeaders( int fd, int status, const char *title, const char *extra, const char *mime, int length, time_t date )
{
__fprintf( fd, "%s %d %s\r\n", PROTOCOL, status, title );
__fprintf( fd, "Server: %s\r\n", SERVER );
__fprintf( fd, "Date: %s\r\n", RFC1123FMT( time( NULL ) ) );
if( extra )
__fprintf( fd, "%s\r\n", extra );
if( mime )
__fprintf( fd, "Content-Type: %s\r\n", mime );
if( length >= 0 )
__fprintf( fd, "Content-Length: %d\r\n", length );
if( date != -1 )
__fprintf( fd, "Last-Modified: %s\r\n", RFC1123FMT( time( &date ) ) );
__fprintf( fd, "Connection: close\r\n" );
__fprintf( fd, "\r\n" );
}
void WebServer::SendError( int fd, int status, const char *title, const char *extra, const char *text )
{
SendHeaders( fd, status, title, extra, "text/html", -1, -1 );
__fprintf( fd, "%d %s\r\n", status, title );
__fprintf( fd, "%d %s
\r\n", status, title );
__fprintf( fd, "%s\r\n", text );
__fprintf( fd, "\r\n" );
}
void WebServer::SendFile( int fd, const char *path, struct stat *statbuf )
{
char data[4096];
int n;
FILE *file = fopen( path, "r" );
if( !file )
{
SendError( fd, 403, "Forbidden", NULL, "Access denied." );
return;
}
int length = S_ISREG( statbuf->st_mode ) ? statbuf->st_size : -1;
SendHeaders( fd, 200, "OK", NULL, GetMimeType( path ), length, statbuf->st_mtime );
while( ( n = fread( data, 1, sizeof( data ), file ) ) > 0 )
if( write( fd, data, sizeof( data ) ) != sizeof( data ) )
perror( "write" );
fclose( file );
}
int WebServer::IsDir( int fd, std::string &path, struct stat statbuf )
{
DIR *dir;
struct dirent *de;
int len;
std::string pathbuf;
if( S_ISDIR( statbuf.st_mode ) )
{
len = path.length();
if( len == 0 || path[len - 1] != '/' )
{
pathbuf = "Location: " + path + "/";
SendError( fd, 302, "Found", pathbuf.c_str(), "Directories must end with a slash." );
return -1;
}
pathbuf = path + "index.html";
if( stat( pathbuf.c_str(), &statbuf ) >= 0 )
{
SendFile( fd, pathbuf.c_str(), &statbuf );
return 0;
}
SendHeaders( fd, 200, "OK", NULL, "text/html", -1, statbuf.st_mtime );
__fprintf( fd, "Index of %s\r\n", path.c_str() );
__fprintf( fd, "Index of %s
\r\n\n", path.c_str() );
__fprintf( fd, "Name Last Modified Size\r\n" );
__fprintf( fd, "
\r\n" );
if( len > 1 )
__fprintf( fd, "..\r\n" );
dir = opendir( path.c_str() );
while( ( de = readdir( dir ) ) != NULL )
{
/* ignore dot(.) and dotdot(..) */
std::string dirname( de->d_name );
if ( dirname.compare( "." ) == 0 || dirname.compare( ".." ) == 0 )
continue;
pathbuf = path.c_str();
pathbuf.append( dirname.c_str() );
stat( pathbuf.c_str(), &statbuf );
__fprintf( fd, "", dirname.c_str(), S_ISDIR( statbuf.st_mode ) ? "/" : "" );
__fprintf( fd, "%s%s", dirname.c_str(), S_ISDIR( statbuf.st_mode ) ? "/" : " " );
if( dirname.length() < 32 )
__fprintf( fd, "%*s", 32 - dirname.length(), "" );
else if( S_ISDIR( statbuf.st_mode ) )
__fprintf( fd, "%s\r\n", RF850FMT( statbuf.st_mtime ) );
else
__fprintf( fd, "%s %10d\r\n", RF850FMT( statbuf.st_mtime ), statbuf.st_size );
}
closedir( dir );
__fprintf( fd, "
\r\n
\r\n%s\r\n\r\n", SERVER );
return 0;
}
SendFile( fd, path.c_str(), &statbuf );
return 0;
}
int WebServer::Process( int fd )
{
char buf[4096];
struct stat statbuf;
std::string method;
std::string path;
std::string protocol;
if( recvln(fd, buf, sizeof(buf)) == -1 )
return -1;
printf( "URL: %s", buf );
std::string str( buf );
std::vector tokens = split( str, " " );
// the first token can be accessed
method = tokens.front();
// the second token can be accessed
path = tokens.at( 1 );
// the third token can be accessed
protocol = tokens.at( 2 );
if ( method.compare( "GET" ) != 0 )
{
SendError( fd, 501, "Not supported", NULL, "Method is not supported." );
return -1;
}
/* Upon successful completion, 0 shall be returned. Otherwise, -1 shall be returned and errno set to indicate the error. */
if( stat( path.c_str(), &statbuf ) < 0 )
{
SendError( fd, 404, "Not Found", NULL, "File not found." );
return -1;
}
return IsDir( fd, path, statbuf );
}
int main()
{
WebServer server;
server.ReadyToRun();
return 0;
}