/*
 * static-cat
 * Copyright (C) 2006  Jeff Arnold <jbarnold@mit.edu>
 * 
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 * 
 * See /COPYRIGHT in this repository for more information.
 */

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>

// Map from extensions to content-types

// START-AUTOGENERATED: DO NOT EDIT THIS SECTION, INCLUDING THIS LINE!
// This section is populated by the script upd-execsys
// END-AUTOGENERATED: DO NOT EDIT THIS SECTION, INCLUDING THIS LINE!

// Start code from w3c's libwww library
// (as obtained from http://www.w3.org/Library/src/HTWWWStr.html)

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

char *wkdays[7] = {
	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};

/*
**	Returns a string pointer to a static area of the current calendar
**	time in RFC 1123 format, for example
**
**		Sun, 06 Nov 1994 08:49:37 GMT
**
**	The result can be given in both local and GMT dependent on the flag
*/
const char *HTDateTimeStr(time_t * calendar, int local)
{
	static char buf[40];

#ifdef HAVE_STRFTIME
	if (local) {
		/*
		 ** Solaris 2.3 has a bug so we _must_ use reentrant version
		 ** Thomas Maslen <tmaslen@verity.com>
		 */
#if defined(HT_REENTRANT) || defined(SOLARIS)
		struct tm loctime;
		localtime_r(calendar, &loctime);
		strftime(buf, 40, "%a, %d %b %Y %H:%M:%S", &loctime);
#else
		struct tm *loctime = localtime(calendar);
		strftime(buf, 40, "%a, %d %b %Y %H:%M:%S", loctime);
#endif				/* SOLARIS || HT_REENTRANT */
	} else {
#if defined(HT_REENTRANT) || defined(SOLARIS)
		struct tm gmt;
		gmtime_r(calendar, &gmt);
		strftime(buf, 40, "%a, %d %b %Y %H:%M:%S GMT", &gmt);
#else
		struct tm *gmt = gmtime(calendar);
		strftime(buf, 40, "%a, %d %b %Y %H:%M:%S GMT", gmt);
#endif				/* SOLARIS || HT_REENTRANT */
	}
#else
	if (local) {
#if defined(HT_REENTRANT)
		struct tm loctime;
		localtime_r(calendar, &loctime);
#else
		struct tm *loctime = localtime(calendar);
#endif				/* HT_REENTRANT */
		sprintf(buf, "%s, %02d %s %04d %02d:%02d:%02d",
			wkdays[loctime->tm_wday],
			loctime->tm_mday,
			months[loctime->tm_mon],
			loctime->tm_year + 1900,
			loctime->tm_hour, loctime->tm_min,
			loctime->tm_sec);
	} else {
#if defined(HT_REENTRANT) || defined(SOLARIS)
		struct tm gmt;
		gmtime_r(calendar, &gmt);
#else
		struct tm *gmt = gmtime(calendar);
#endif
		sprintf(buf, "%s, %02d %s %04d %02d:%02d:%02d GMT",
			wkdays[gmt->tm_wday],
			gmt->tm_mday,
			months[gmt->tm_mon],
			gmt->tm_year + 1900, gmt->tm_hour, gmt->tm_min,
			gmt->tm_sec);
	}
#endif
	return buf;
}

// End code from w3c's libwww library

// Start code from gnu
// (as obtained from "apt-get source coreutils" on debian sarge)

// JBA: included by safe_read.h, safe_write.h, full_read.h, and full_write.h
#include <stddef.h>

// JBA: included by safe_read.c and full_write.c
#if HAVE_CONFIG_H
# include <config.h>
#endif

// JBA: included by safe_read.c and full_write.c
#include <errno.h>
#ifndef errno
extern int errno;
#endif

// Code from system.h:

#ifndef STDOUT_FILENO
# define STDOUT_FILENO 1
#endif

// Code from safe_read.h:

#define SAFE_READ_ERROR ((size_t) -1)

// Code from safe_write.h

#define SAFE_WRITE_ERROR ((size_t) -1)

// Code from safe_read.c

/* Get ssize_t.  */
#include <sys/types.h>
#if HAVE_UNISTD_H
# include <unistd.h>
#endif

#ifdef EINTR
# define IS_EINTR(x) ((x) == EINTR)
#else
# define IS_EINTR(x) 0
#endif

#include <limits.h>

/* Read(write) up to COUNT bytes at BUF from(to) descriptor FD, retrying if
   interrupted.  Return the actual number of bytes read(written), zero for EOF,
   or SAFE_READ_ERROR(SAFE_WRITE_ERROR) upon error.  */
size_t safe_read(int fd, void *buf, size_t count)
{
	size_t result;

	/* POSIX limits COUNT to SSIZE_MAX, but we limit it further, requiring
	   that COUNT <= INT_MAX, to avoid triggering a bug in Tru64 5.1.
	   When decreasing COUNT, keep the file pointer block-aligned.
	   Note that in any case, read(write) may succeed, yet read(write)
	   fewer than COUNT bytes, so the caller must be prepared to handle
	   partial results.  */
	if (count > INT_MAX)
		count = INT_MAX & ~8191;

	do {
		result = read(fd, buf, count);
	}
	while (result < 0 && IS_EINTR(errno));

	return (size_t) result;
}

/* Read(write) up to COUNT bytes at BUF from(to) descriptor FD, retrying if
   interrupted.  Return the actual number of bytes read(written), zero for EOF,
   or SAFE_READ_ERROR(SAFE_WRITE_ERROR) upon error.  */
size_t safe_write(int fd, const void *buf, size_t count)
{
	size_t result;

	/* POSIX limits COUNT to SSIZE_MAX, but we limit it further, requiring
	   that COUNT <= INT_MAX, to avoid triggering a bug in Tru64 5.1.
	   When decreasing COUNT, keep the file pointer block-aligned.
	   Note that in any case, read(write) may succeed, yet read(write)
	   fewer than COUNT bytes, so the caller must be prepared to handle
	   partial results.  */
	if (count > INT_MAX)
		count = INT_MAX & ~8191;

	do {
		result = write(fd, buf, count);
	}
	while (result < 0 && IS_EINTR(errno));

	return (size_t) result;
}

// Code from full_write.c

/* Write(read) COUNT bytes at BUF to(from) descriptor FD, retrying if
   interrupted or if a partial write(read) occurs.  Return the number
   of bytes transferred.
   When writing, set errno if fewer than COUNT bytes are written.
   When reading, if fewer than COUNT bytes are read, you must examine
   errno to distinguish failure from EOF (errno == 0).  */
size_t full_read(int fd, void *buf, size_t count)
{
	size_t total = 0;
	char *ptr = buf;

	while (count > 0) {
		size_t n_rw = safe_read(fd, ptr, count);
		if (n_rw == (size_t) - 1)
			break;
		if (n_rw == 0) {
			errno = 0;
			break;
		}
		total += n_rw;
		ptr += n_rw;
		count -= n_rw;
	}

	return total;
}

/* Write(read) COUNT bytes at BUF to(from) descriptor FD, retrying if
   interrupted or if a partial write(read) occurs.  Return the number
   of bytes transferred.
   When writing, set errno if fewer than COUNT bytes are written.
   When reading, if fewer than COUNT bytes are read, you must examine
   errno to distinguish failure from EOF (errno == 0).  */
size_t full_write(int fd, const void *buf, size_t count)
{
	size_t total = 0;
	const char *ptr = buf;

	while (count > 0) {
		size_t n_rw = safe_write(fd, ptr, count);
		if (n_rw == (size_t) - 1)
			break;
		if (n_rw == 0) {
			errno = ENOSPC;
			break;
		}
		total += n_rw;
		ptr += n_rw;
		count -= n_rw;
	}

	return total;
}

// Code from cat.c

/* Nonzero if a non-fatal error has occurred.  */
static int exit_status = 0;

static int input_desc;

/* Plain cat.  Copies the file behind `input_desc' to STDOUT_FILENO.  */

static void simple_cat(
			      /* Pointer to the buffer, used by reads and writes.  */
			      char *buf,
			      /* Number of characters preferably read or written by each read and write
			         call.  */
			      int bufsize)
{
	/* Actual number of characters read, and therefore written.  */
	size_t n_read;

	/* Loop until the end of the file.  */

	for (;;) {
		/* Read a block of input.  */

		n_read = safe_read(input_desc, buf, bufsize);
		if (n_read == SAFE_READ_ERROR) {
			// JBA: simplified to "exit_status=1; return;"
			exit_status = 1;
			return;
		}

		/* End of this file?  */

		if (n_read == 0)
			break;

		/* Write this block out.  */

		{
			/* The following is ok, since we know that 0 < n_read.  */
			size_t n = n_read;
			if (full_write(STDOUT_FILENO, buf, n) != n)
				exit(1);	// JBA: simplified to "exit(1);"
		}
	}
}

// End code from gnu

int main(int argc, char **argv)
{
	input_desc = open(argv[1], O_RDONLY);
	if (input_desc == -1) {
		input_desc =
		    open("/mit/scripts/www/403-404.html", O_RDONLY);
		if (input_desc == -1)
			exit(0);
		printf("Status: 404 Not Found\n");
		printf("Content-type: text/html;\n\n");
	} else {
		int i, j;
		const char *content_type = "application/octet-stream";
		for (i = strlen(argv[1]) - 1; i > 0; i--) {
			if (argv[1][i - 1] == '.')
				break;
		}
		if (i == 0)
			exit(0);
		for (j = 0; j < 2 * NEXTS; j += 2) {
			if (strcasecmp(map[j], &argv[1][i]) == 0) {
				content_type = map[j + 1];
			}
		}

		struct stat statbuf;
		if (fstat(input_desc, &statbuf) == 0) {
			const char *dtstr =
			    HTDateTimeStr(&statbuf.st_mtime, 0);
			printf("Last-Modified: %s\n", dtstr);
		}
		printf("Content-type: %s\n\n", content_type);
	}
	fflush(stdout);
	char *buf = malloc(4096);
	simple_cat(buf, 4096);
	free(buf);
	close(input_desc);
	return exit_status;
}
