/*
 *
 *      chttpd. The web server that is not here.
 *      copyright(c) 0x7d0 greg olszewski <noop@nwonknu.org>
 * 
 *
 *      mime stuff & some other parts orignated in: 
 *      dhttpd/1.02 - Personal web page server
 *      Copyright (C) 1997, 2008  David A. Bartold
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */


#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <autoconf.h>
#include <config.h>
#include <socket.h>
#include <log.h>

#define OK 0
#define FORBIDDEN 1
#define NOT_FOUND 2
#define NOT_MOD 3

#ifdef FAKE_NAMES
#define VERSION server_name()
#else
#define VERSION "chttpd/1.3"
#endif
#include "mime_types.h"

static const char *day_name[] = {
    "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};

static const char *month_name[] = {
    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};


extern const char *webdirprefix;
extern const int webdirprefixlen;
char           *
get_mime_time(struct tm *t)
{
    static char     out[44];

    /*
     * Format:  Day, 01 Mon 1999 01:01:01 GMT 
     */

    sprintf(out, "%s, %02i %s %04i %02i:%02i:%02i GMT",
	    day_name[t->tm_wday], t->tm_mday, month_name[t->tm_mon],
	    t->tm_year + 1900, t->tm_hour, t->tm_min, t->tm_sec);

    return out;
}

static char    *
curTime(void)
{
    time_t          t;
    t = time(NULL);
    return get_mime_time(gmtime(&t));
}

static char    *
get_mime_type(char *f)
{
    int             flen;
    int             tlen;
    int             g;

    flen = strlen(f);

    for (g = 0; known_types[g].ext; g++) {
	tlen = strlen(known_types[g].ext);
	if (flen > tlen) {
	    if (!strcmp(&f[flen - tlen], known_types[g].ext)) {
		return known_types[g].type;
	    }
	}
    }

    return "application/x-unknown";
}

static int
find_month(char *s)
{
    int             g;

    for (g = 0; g < 12; g++) {
	if (!strcmp(month_name[g], s)) {
	    return g;
	}
    }

    return -1;
}

/*
 * returns NULL if we don't like it
 * * else we return a pointer to a path with
 * * all the /../ taken out, which needs to be free(2)d
 * * will have a path that ends in / or filename and it will
 * * start with /
 */
static char    *
check_file(char *file)
{
    char           *dotdot,
                   *current,
                   *file2,
                   *endofprefix;
    int             stringlen = webdirprefixlen;

    stringlen += strlen(file) + 14;
    file2 = malloc(stringlen);
    sprintf(file2, "%s%s%s", webdirprefix, file[0] == '/' ? "" : "/",
	    file);
    current = file2;
    endofprefix = (file2 + (webdirprefixlen - 1));
    /*
     * after this, file2 will look like
     * * /path/to/base/[user input]
     * * the only thing we should be afraid of here is 
     * * ../ that will bring us above /path/to/base
     * *  he was checking for // but shouldn't be a problem
     */
    while ((dotdot = strstr(current, "//"))) {
	current = dotdot;
	while (*(dotdot)) {
	    *dotdot = *(dotdot + 1);
	    dotdot++;
	}

    }
    while ((dotdot = strstr(current, ".."))) {
	char           *copier = dotdot - 2;
	/*
	 * looks like /something..something
	 */
	if (*(dotdot - 1) != '/'
	    || (*(dotdot + 2) != '/' || !*(dotdot + 2))) {
	    current = (dotdot + 2);
	    continue;
	    /*
	     * it's okay to have /something/file..ext/somethingelse
	     */
	}
	if ((dotdot - 2) == endofprefix) {
	    /*
	     * checks for /web/prefix/.. which is not okay 
	     */
	    free(file2);
	    file2 = NULL;
	    break;
	}
	dotdot += 2;
	/*
	 * now we know its /webprefix/somedir/../someelse
	 * so we move to the s in somelse
	 */
	while (*copier != '/')
	    copier--;
	current = copier;
	while (*dotdot) {
	    *copier = (*dotdot);
	    copier++;
	    dotdot++;
	}
	*copier = (*dotdot);	/*
				 * \0 at end
				 */
    }
    log_info("The result is now %s\n", file2);
    if (!file2)
	log_info("Got bogus request of:%s\n", file2);
    return (file2);
}
#ifdef FAKE_NAMES
static const char *
server_name(void)
{

    static const char *servers[] = {
	"Apache/1.3.9 (Unix) Debian/GNU",
	"Apache/1.3.11 (Unix) ApacheJServ/1.1",
	"fnord. this is not here.",
	"Microsoft-IIS/5.0"
    };
    return (servers[2]);
}
#endif

void
error(FILE * out, int num, char *stat, char *msg, char *str)
{
    FILE           *in;
    int             numch;
    char           *file;
    char            buf[1024];
    struct stat     fs;


    fprintf(out, "HTTP/1.0 %s\r\n", stat);
    fprintf(out, "Date: %s\n", curTime());
    fprintf(out, "Server: %s\r\n", VERSION);
    /*
     * fprintf( out, "MIME-version: 1.0\r\n" ); 
     */
    fprintf(out, "Content-type: text/html\r\n");
    fprintf(out, "\r\n");
    file = malloc(sizeof(char) * webdirprefixlen + 20);
    if (file) {
	sprintf(file, "%s/..ERROR%i.html", webdirprefix, num);
	in = fopen(file, "r");
	if (in != NULL) {
	    fstat(fileno(in), &fs);

	    if (((fs.st_mode & S_IFMT) == S_IFREG)
		&& !(fs.st_mode & S_IFDIR)) {
		do {
		    numch = fread(buf, 1, 1024, in);
		    fwrite(buf, 1, numch, out);
		}
		while (numch);

		fclose(in);
		free(file);
		return;
	    }
	    fclose(in);
	}
    }
    fprintf(out, "<html><head><title>%s: Error %s</title></head>", VERSION,
	    msg);
    fprintf(out, "<h1>%s</h1><hr>", msg);
    fprintf(out, "%s <em>Sorry!</em></body></html>", str);
    if (file)
	free(file);
}

void
screwed(FILE * out)
{
    error(out,
	  400,
	  "400 Bad Request",
	  "400: You're Screwed!",
	  "You can't do that! This server does not support the operation "
	  "requested by your client!");
}

void
send_error(FILE * out, int status)
{
    switch (status) {
    case FORBIDDEN:
	error(out,
	      403,
	      "403 Forbidden",
	      "403: Forbidden!",
	      "You do not have permission to access that file.");
	break;

    case NOT_FOUND:
	error(out,
	      404,
	      "404 File Not Found",
	      "404: File Not Found!",
	      "File specified not found on server.  Check to make sure you have the "
	      "correct file extension.");
	break;
    }
}

/*
 * Should the web browser used the cached version? 
 */
int
useCache(struct tm *modTime, char *s)
{
    char           *pos;
    char            mname[1024];
    int             year = 0,
                    month = 0,
                    day = 0,
                    hour = 0,
                    min = 0,
                    sec = 0,
                    x;

    /*
     * Skip over the week day 
     */
    if (!(pos = strchr(s, ' '))) {
	return 0;
    } else {
	while (isspace(*pos)) {
	    pos++;
	}
    }

    if (isalpha(*pos)) {
	/*
	 * ctime 
	 */
	sscanf(pos, "%s %d %d:%d:%d %*s %d", mname, &day, &hour, &min,
	       &sec, &year);
    } else if (pos[2] == '-') {
	/*
	 * RFC 850 (normal HTTP) 
	 */
	char            t[1024];
	sscanf(pos, "%s %d:%d:%d", t, &hour, &min, &sec);
	t[2] = '\0';
	sscanf(t, "%i", &day);
	t[6] = '\0';
	strcpy(mname, &t[3]);
	sscanf(&t[7], "%i", &x);

	/*
	 * Prevent wraparound from ambiguity 
	 */
	if (x < 70) {
	    x += 100;
	}
	year = 1900 + x;
    } else {
	/*
	 * RFC 1123 (Standard HTTP) 
	 */
	sscanf(pos, "%d %s %d %d:%d:%d", &day, mname, &year, &hour, &min,
	       &sec);
    }
    month = find_month(mname);

    if ((x = (1900 + modTime->tm_year) - year)) {
	return x < 0;
    }

    if ((x = modTime->tm_mon - month)) {
	return x < 0;
    }

    if ((x = modTime->tm_mday - day)) {
	return x < 0;
    }

    if ((x = modTime->tm_hour - hour)) {
	return x < 0;
    }

    if ((x = modTime->tm_min - min)) {
	return x < 0;
    }

    if ((x = modTime->tm_sec - sec)) {
	return x < 0;
    }

    return 1;
}

int
send_file(FILE * out, char *name, char *modTime, int n)
{
    FILE           *in;
    struct stat     fs;
    struct tm      *tmMod;
    char            buf[1024];
    int             num;
    in = fopen(name, "r");
    if (in == NULL) {
	if (errno == EACCES || errno == ENOTDIR || errno == ELOOP) {
	    log_info("Didn't like the file:%s\n", name);
	    return FORBIDDEN;
	}
	return NOT_FOUND;
    }

    fstat(fileno(in), &fs);
    if (fs.st_mode & S_IFDIR) {
	fclose(in);

	if (name[strlen(name)] != '/')
	    name = strcat(name, "/");
	name = strcat(name, "index.html");
	in = fopen(name, "r");
	if (!in) {
	    return NOT_FOUND;
	}
	fstat(fileno(in), &fs);
	if (fs.st_mode & S_IFDIR) {
	    /*
	     * fprintf(stderr,"directory loop\n"); 
	     */
	    return FORBIDDEN;	/*
				 * i'm lazy
				 * * but don't want
				 * * /index.html/index.html.... 
				 */
	}
    }

    if (((fs.st_mode & S_IFMT) != S_IFREG)) {
	fclose(in);
	return FORBIDDEN;
    }
    tmMod = gmtime(&fs.st_mtime);

    if (modTime != NULL && useCache(tmMod, modTime)) {
	fclose(in);
	fprintf(out, "HTTP/1.0 304 Not modified\r\n");
	fprintf(out, "Date: %s\r\n", curTime());
	fprintf(out, "Server: %s\r\n", VERSION);
	/*
	 * fprintf( out, "MIME-version: 1.0\r\n" );
	 */
	fprintf(out, "\r\n");
	return OK;
    }

    if (n > 2) {
	fprintf(out, "HTTP/1.0 200 OK\r\n");
	fprintf(out, "Date: %s\n", curTime());
	fprintf(out, "Server: %s\r\n", VERSION);
	fprintf(out, "Content-type: %s\r\n", get_mime_type(name));
	fprintf(out, "Last-modified: %s\r\n", get_mime_time(tmMod));
	fprintf(out, "Content-length: %ld\r\n", fs.st_size);
	fprintf(out, "\r\n");
    }

    do {
	num = fread(buf, 1, 1024, in);
	fwrite(buf, 1, num, out);
    } while (num);
    fclose(in);
    return OK;
}

void
handle_socket(struct active_sock a_sock)
{
    char            line[1024];
    char            cmd[1024];
    char            file[1200];
    char            ver[1024];
    char            ifmod[1024];
    char           *modTime;
    char           *str;

    int             num;
    int             status;
    char           *pos;
    {
	struct sockaddr_in peer;
	socklen_t          socklen = sizeof(peer);
	num =
	    getpeername(a_sock.socket, (struct sockaddr *) &peer,
			&socklen);
	if (num < 0) {
	    log_error("couldn't get peer name, dropping connection\n");
	    return;
	}
	a_sock.peer_addr = peer.sin_addr;
    }
    str = fgets(line, 1024, a_sock.io);
    num = sscanf(line, "%s %s %s", cmd, file, ver);

    ifmod[0] = '\0';

    /*
     * Accept HTTP/0.9 requests properly 
     */
    if (num == 3) {
	do {
	    str = fgets(line, 1024, a_sock.io);
	    pos = strchr(line, ':');
	    if (pos != NULL) {
		*pos++ = '\0';

		while (isspace(*pos)) {
		    pos++;
		}

		if (!strcasecmp("If-modified-since", line)) {
		    strcpy(ifmod, pos);
		}
	    }
	} while (str != NULL && strcmp(line, "\r\n")
		 && strcmp(line, "\n"));
    }

    /*
     * This is necessary for some stupid *
     * * operating system such as SunOS    
     */
    fflush(a_sock.io);

    modTime = strcmp(ifmod, "") ? ifmod : (char *) NULL;

    if (!strcmp(cmd, "GET") && (num >= 2)) {
	char           *file2;
	fprintf(stderr, "in handle for %s\n", file);
	log_info("%s asked for %s\n", inet_ntoa(a_sock.peer_addr), file);
	file2 = check_file(file);
	if (!file2) {
	    fprintf(stderr, "ERROR NO FILE2 returned");
	    send_error(a_sock.io, FORBIDDEN);
	} else {
	    status = send_file(a_sock.io, file2, modTime, num);
	    free(file2);
	    if (status)
		send_error(a_sock.io, status);
	}
    } else
	screwed(a_sock.io);
}
