//
// 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; }