//
// Linreb 0.2
//    -- mods by Nick Rapallo (nrapallo@yahoo.ca) - July 2008
// Linreb 0.1
//    -- written by Nandagopal Kirubanandan
//
// A simple librarian program for the REB1200 device
// Implements a proxy server, but does not implement too much error
// checking.
//
// When the OnLine Bookshelf is requested, the ".imp" files from
// the directory "shelf/" are served
//
// When the Bookstore is requested, it serves the file "index.html"
// from the "store/" directory. Note that the html rendering capabilities
// of REB1200 are limited.
//
// For additional HTML pages, the contents are served from the "content/"
// directory, for URL's starting with
// http://bookshelf.ebooksystem.net/content/
//
// To summarize:
//
//
// BOOKLIST_PREFIX "http://bookshelf.ebooksystem.net/bookshelf/default.asp?"
// BOOK_PREFIX     "http://bookshelf.ebooksystem.net/bookshelf/getbook?"
// STORE_PREFIX    "http://directory.ebooksystem.net/directory/default.asp"
// CONTENT_PREFIX  "http://bookshelf.ebooksystem.net/content/"
//
//
//


#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/poll.h>
#include <signal.h>
#include <sys/types.h>
#include <dirent.h>
#include <fcntl.h>
#include <stdlib.h>
#include <getopt.h>


typedef struct Imp {
		char id[256];
		char category[256];
		char subcategory[256];
		char title[256];
		char last[256];
		char middle[256];
		char first[256];
		char filename[256];
		int size;
} Imp;

int verbose=0;
int dummy=0;

int impcount=0;
Imp imps[4096];
FILE * logfile=NULL;	// don't log by default.


int local_port = 9090;
int ls,ws,rs;
int addrlength;

struct sockaddr_in local;
struct sockaddr_in incoming;

#define CONTENT_PREFIX "http://bookshelf.ebooksystem.net/content/"
#define BOOKLIST_PREFIX "http://bookshelf.ebooksystem.net/bookshelf/default.asp?"
#define BOOK_PREFIX "http://bookshelf.ebooksystem.net/bookshelf/getbook?"
#define STORE_PREFIX "http://directory.ebooksystem.net/directory/default.asp"
#define STORE2_PREFIX "http://bookshelf.ebooksystem.net/authenticate/default.asp"

//
// Output a chunk of data in hex and characters
//

void record(int dir, unsigned char * buf, int length)
{
		char str[17];
		int i;

		if (!logfile)
				return;

		fprintf(logfile,"%s\n",(dir)? "<<<<" : ">>>>");
		bzero(str,sizeof(str));
		for (i=0;i<length;i++)
		{
			if (i % 16 == 0 )
			{
				fprintf(logfile,"%c ", (dir ? '>' : '<'));
			}
			fprintf(logfile,"%02x ",buf[i]);
					str[i%16]= isprint(buf[i]) ? buf[i] : '.';
			if (i % 16 == 15)
			{
				fprintf(logfile,"%s\n",str);
			}
		}
		if (i % 16 != 15)
		{

			i = i%16;
			for (;i<16;i++)
					fprintf(logfile,"   ");

			fprintf(logfile,"%s\n\n",str);
			bzero(str,sizeof(str));
		}
}

void write_quick(int f, char * buf, int l)
{
		if (logfile)
			record(0,buf,l);
		write(f,buf,l);
}

#define GRAB \
do {\
		while ( (c=fgetc(in)) != -1)\
		{\
				*data++ = c;\
				if (c==0)\
						break;\
		}\
} while (0)

void random_imp_properties(Imp *imp)
{
		bzero(imp,sizeof(Imp));

		sprintf(imp->id,"ebook:guid-%08x%08x%08x%08x",rand(),
						rand(),rand(),rand());
		sprintf(imp->category,"Zebra");
		sprintf(imp->subcategory,"Nothing");
		sprintf(imp->title,"Random Book %d",impcount);
		sprintf(imp->first,"Anonymous");
		strcpy(imp->last,"");
		strcpy(imp->middle,"");
		strcpy(imp->filename,"unknown");
		imp->size = 12345;
}

void load_imp_properties(char * filename, Imp *imp)
{
		FILE * in = fopen(filename,"r");
		char buf[1024];
		char * data = NULL;
		int c;

		bzero(imp,sizeof(Imp));
		if (!in)
		{
				fprintf(stderr,"%s does not exist\n",filename);
				return;
		}

		bzero(buf,sizeof(buf));
		fseek(in,0x30,SEEK_SET);

		data = imp->id; GRAB;
		data = imp->category; GRAB;
		data = imp->subcategory; GRAB;
		data = imp->title; GRAB;
		data = imp->last; GRAB;
		data = imp->middle; GRAB;
		data = imp->first; GRAB;
		strcpy(imp->filename,filename);


		fseek(in,0,SEEK_END);
		imp->size = ftell(in);
		fclose(in);

}

void dump_imp_properties(Imp *imp)
{
		printf("Id       : %s\n",imp->id);
		printf("Category : %s\n",imp->category);
		//printf("Subcat   : %s\n",imp->subcategory);
		printf("Title    : %s\n",imp->title);
		//printf("Last     : %s\n",imp->last);
		//printf("Middle   : %s\n",imp->middle);
		printf("First    : %s\n",imp->first);
}

void load_directory(char * name)
{
		int i;
		DIR * dir;
		struct dirent * entry;
		impcount=0;
		dir = opendir(name);

		while ( (entry=readdir(dir)) != NULL)
		{
			char filename[256];
			sprintf(filename,"%s/%s",name,entry->d_name);
			if (strstr(entry->d_name,".imp")!=NULL)
			{
					load_imp_properties(filename,imps+impcount);
					if (verbose>1)
							dump_imp_properties(imps+impcount);
					impcount++;
			}
		}
		closedir(dir);
		printf("Loaded %d files in bookshelf\n",impcount);

		if (dummy)
		{
			for (i=0;i<80;i++)
			{
				random_imp_properties(imps+impcount);
				impcount++;
			}
			printf("Expanded to %d files in bookshelf\n",impcount);
		}

}

void not_found(int rs)
{
		char buf[256];
		sprintf(buf,"HTTP/1.1 404 Not Found\r\n\r\n");
		write_quick(rs,buf,strlen(buf));
		if (verbose)
		{
				printf("\tHTTP/1.1 404 Not Found\n");
		}
}

//
// Build the booklist
//
//
int build_booklist_page(char * buf,int start,int maxlength)
{
		int i;
		strcpy(buf,"1\r\n");
		for (i=start;(i<impcount) && (maxlength>0);i++,maxlength--)
		{
				char p[1024];
				sprintf(p,"None:%s\t%s\t%s\t%s\t%d\thttp://bookshelf.ebooksystem.net/bookshelf/getbook?%s\t1\t17\r\n",imps[i].id, imps[i].title, imps[i].first, imps[i].category,
								imps[i].size,
								imps[i].id);
				strcat(buf,p);
		}
		strcat(buf,"\r\n\r\n");
		return strlen(buf);
}


// read at most length bytes, but don't let more than timeout ms
// elapse between consecutive network "mini" reads
//
int read_quick(int s, char * buf, int length, int timeout)
{
		struct pollfd fd;
		int r=0;
		int k;

		fd.fd = s;
		fd.events = POLLIN;
		bzero(buf,length);

		while (r<length)
		{
				if ((k=poll(&fd,1,timeout))<1)
				{
						if (k<0)
								perror("poll");
						break;	// too much time elapsed, don't read any more
				}

				k = read(s,buf+r,length-r);
				if (k<=0)
						break;
				r+=k;
				if (strstr(buf,"\r\n\r\n") != 0)
				{
						break;		// an empty line ends request
				}
		}
		return r;
}

void copy_file(int out, char * filename,int length)
{
	char buf[4096];
	int in = open(filename,O_RDONLY);
	if (in<0)
	{
			not_found(out);
			return;	//nothing to do
	}
	while (length>0)
	{
			int l = read(in,buf,sizeof(buf));
			if (l<=0)
					break;	// input file ended
			if (length<l)
					l = length;
			write_quick(out,buf,l);
			length -=l;
	}
	close(in);
}

void serve_content(int out, char * filename)
{
	char buf[256];
	char * content = "text/html";

	if ((strstr(filename,".jpg")!=0) || (strstr(filename,".jpeg")!=0))
	{
			content = "image/jpeg";
	}

	if ((strstr(filename,".bmp")!=0))
	{
			content = "image/bmp";
	}

	if ((strstr(filename,".gif")!=0))
	{
			content = "image/gif";
	}

	if ((strstr(filename,".png")!=0))
	{
			content = "image/png";
	}

	if ((strstr(filename,".tif")!=0) || (strstr(filename,".tiff")!=0))
	{
			content = "image/tiff";
	}

/* Tried these but no go!  Too bad!
    if ((strstr(filename,".pic")!=0))
	{
			content = "image/pict";
	}

	if ((strstr(filename,".mid")!=0) || (strstr(filename,".midi")!=0))
	{
			content = "audio/midi";
	}

	if ((strstr(filename,".mp3")!=0))
	{
			content = "audio/mpeg3";
	}

	if ((strstr(filename,".wav")!=0))
	{
			content = "audio/wav";
	}
*/

	printf("Content=%s\n",content);

	FILE * f = fopen(filename,"r");
	if (f)
	{
			int l;
			fseek(f,0,SEEK_END);
			l = ftell(f);
			fclose(f);
			sprintf(buf,"HTTP/1.1 200 OK\r\n"
				"Server: linreb-Frances/0.1\r\n"
				"Content-Length: %d\r\n"
				"Content-Type: %s\r\n"
				"\r\n",l,content);
			write_quick(rs,buf,strlen(buf));
			copy_file(out,filename,l);
			if (verbose)
					printf("\tSent %s %d bytes\n",filename,l);
	}
	else
	{
			printf("%s not found\n");
			not_found(out);
	}
}


void handle_book_request(int rs, char * orl, char * request)
{
		// the request is the id, use it lookup the file name and
		// serve
		int i;
		if (verbose>1)
			printf("\thandle_book_request\"%s\"\n",request);
		for (i=0;i<impcount;i++)
		{
				if (strcmp(imps[i].id,request)==0)
				{
						break;
				}
		}

		if (i==impcount)
		{
				not_found(rs);
		}
		else
		{
			char buf[256];
			if (verbose)
				printf("\tServing %s %d bytes\n",imps[i].filename,
								imps[i].size);
			sprintf(buf,"HTTP/1.1 200 OK\r\n"
				"Server: Frances/0.1\r\n"
				"Content-Length: %d\r\n"
				"Content-Type: application/x-softbook\r\n"
				"\r\n",imps[i].size);
			write_quick(rs,buf,strlen(buf));
			copy_file(rs,imps[i].filename,imps[i].size);
		}
}

void handle_content_request(int rs, char * orl, char * request)
{
		char buf[256];
		if (verbose>1)
			printf("content_request:%s:\n",request);
		sprintf(buf,"content/%s",request);
		serve_content(rs,buf);

}

void handle_store_request(int rs, char * orl, char * request)
{
		serve_content(rs,"store/index.html");
}

//
// There are many limitations: The data portion of the page cannot
// exceed 64K. Need to fix this going forward
//
//
void handle_booklist_request(int rs, char * orl, char * request)
{
	char buf[10224];
	char page[65536];
	int start=1;
	int length=99;
	int l;

	char * look = strstr(request,"INDEX=");
	if (look)
			sscanf(look,"INDEX=%d",&start);
	look = strstr(request,"REQUEST=");
	if (look)
			sscanf(look,"REQUEST=%d",&length);

	if (verbose>1)
		printf("Read from %d,  %d entries\n",start,length);

	l = build_booklist_page(page,start,length);

	if (verbose>1)
	{
		printf("Booklist_request = %s\n",request);
	}
	if (verbose)
	{
		printf("\tSent BOOKLIST %d bytes.\n",l);
	}

	sprintf(buf,"HTTP/1.1 200 OK\r\n"
				"Server: Frances/0.1\r\n"
				"Content-Length: %d\r\n"
				"Content-Type: text/x-booklist\r\n"
				"\r\n",l);

	write_quick(rs,buf,strlen(buf));
	write_quick(rs,page,l);
}


void printUsageAndExit()
{
		fprintf(stderr, "\n  Usage\n");
		fprintf(stderr,"       serve -p port [-v] [-l logfile]\n"
					   "       Repeat -v for more verbose output\n"
					   "       -l logfile will log all dump data in ascii format to the logfile.\n");
		exit(1);

}

void process_arguments(int argc, char *argv[])
{
		int c;
		while ( (c=getopt(argc,argv,"dvph:l:"))>0)
		{
				switch (c)
				{
						case 'h':
                				printUsageAndExit();
								break;
						case 'd':
								dummy = 1;
								break;
						case 'p':
								local_port = atoi(optarg);
								break;
						case 'v':
								verbose++;
								break;
						case 'l':
								logfile = fopen(optarg,"w");
								if (!logfile)
								{
									fprintf(stderr,"Failed to open logfile %s\n",
											optarg);
									printUsageAndExit();
								}
								break;
						default:
								fprintf(stderr,"Unknown Switch %c\n",c);
								printUsageAndExit();
								exit(1);
				}
		}

}

int main(int argc, char *argv[])
{

		printf("LinReb v0.2 Build %s\n",_BUILD_DATE);
		printf("Simple eLibrarian for REB1200/EBW1150\n");
		printf("Bookshelf from     \"shelf/\"\n");
		printf("Bookstore from     \"bookstore/index.html\"\n");
		printf("Html content from  \"content/\"\n");

		process_arguments(argc, argv);

		signal(SIGPIPE,SIG_IGN);

		ls = socket(AF_INET,SOCK_STREAM,0);
		if (ls<0)
		{
				perror("socket");
				exit(1);
		}


		local.sin_family = AF_INET;
		local.sin_port = htons(local_port);
		local.sin_addr.s_addr = INADDR_ANY;

		printf("\nListening on INADDR_ANY:%d\n",local_port);

		addrlength = 1;
		setsockopt(ls,SOL_SOCKET,SO_REUSEADDR,&addrlength,sizeof(addrlength));

		if (bind(ls,(struct sockaddr *)&local,sizeof(local))<0)
		{
			perror("bind");
			exit(1);
		}

		listen(ls,5);

		addrlength = sizeof(incoming);
		load_directory("shelf");
		while ( (rs=accept(ls,(struct sockaddr *)&incoming,&addrlength)
				)>0)
		{
				char buf[8192];
				char page[65536];
				char * orl;
				int method=0;

				int l;
				if (verbose>1)
					printf("Incoming Connection from %s:%d\n",
								inet_ntoa(incoming.sin_addr),
								ntohs(incoming.sin_port));


				// read data, wait for maximum of 5 seconds
				// between consecutive reads. If you ever
				// encounter two consecutive line feeds,
				// then the request has ended...

				l = read_quick(rs,buf,sizeof(buf),10000);
				if (logfile)
						record(1,buf,l);
				buf[l]=0;
				if (verbose>1)
					printf("buf=%s\n",buf);
				if (verbose>1)
					printf("%s\n",buf);
				orl = strstr(buf,"GET");
				if (!orl)
				{
						orl = strstr(buf,"POST");
						method = 1;
				}
				if (orl)
				{
						char * tail;

						orl+=4;
						while (*orl != 0)
								if (*orl == ' ')
										orl++;
								else
										break;

						for (tail=orl;*tail;*tail++)
								if (isspace(*tail))
										break;
						*tail =0;

						if (verbose>0)
						{
							printf("%s %s\n",(method)? "POST":"GET",orl);
						}


						// if it is a request for bookshelf.ebooksystem.net,
						// then serve our page

						if (strstr(orl,BOOKLIST_PREFIX))
						{
								handle_booklist_request(rs,orl,orl+
												strlen(BOOKLIST_PREFIX));
						} else if (strstr(orl,BOOK_PREFIX))
						{
								handle_book_request(rs,orl,orl+
										strlen(BOOK_PREFIX));
						} else if (strstr(orl,STORE_PREFIX))
						{
								handle_store_request(rs,orl,orl+
										strlen(STORE_PREFIX));
						} else if (strstr(orl,STORE2_PREFIX))
						{
								handle_store_request(rs,orl,orl+
										strlen(STORE2_PREFIX));
						} else if (strstr(orl,CONTENT_PREFIX))
						{
								handle_content_request(rs,orl,orl+
												strlen(CONTENT_PREFIX));
						} else
						{
								not_found(rs);
						}


				}
				else
				{
					// just print the first line, probably not a
					// GET request
					char line[1024];
					char *a, *b;

					for (b=buf,a=line; *b ; /* no increment */)
					{
							if (*b == '\r')
									break;
							*a++ = *b++;
					}
					*a='\0';
					if (verbose)
					{
							printf("%s\n",line);
					}


					not_found(rs);
				}


				if (verbose>1)
					printf("Session Ended.\n");
				close(rs);
		}


		close(ls);

}
