#define CPPCMS_SOURCE
#include "config.h"
# if defined(HAVE_CANONICALIZE_FILE_NAME) && !defined(_GNU_SOURCE)
# define _GNU_SOURCE
#endif

#include <stdlib.h>

#include "application.h"
#include "service.h"
#include "http_response.h"
#include "internal_file_server.h"
#include "cppcms_error.h"
#include "json.h"
#include "util.h"
#include <sstream>
#include <fstream>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>

#ifdef CPPCMS_WIN_NATIVE
#include <windows.h>
#else
#include <unistd.h>
#include <limits.h>
#endif


namespace cppcms {
namespace impl {

file_server::file_server(cppcms::service &srv) : application(srv)
{
	if(!canonical(settings().get("file_server.document_root","."),document_root_))
		throw cppcms_error("Invalid document root");

	std::string mime_file=settings().get("file_server.mime_types","");


	if(mime_file.empty()) {
		mime_[".pdf"]	=      "application/pdf";
		mime_[".sig"]	=      "application/pgp-signature";
		mime_[".spl"]	=      "application/futuresplash";
		mime_[".ps"]	=      "application/postscript";
		mime_[".torrent"]=      "application/x-bittorrent";
		mime_[".dvi"]	=      "application/x-dvi";
		mime_[".gz"]	=      "application/x-gzip";
		mime_[".pac"]	=      "application/x-ns-proxy-autoconfig";
		mime_[".swf"]	=      "application/x-shockwave-flash";
		mime_[".tgz"]	=      "application/x-tgz";
		mime_[".tar"]	=      "application/x-tar";
		mime_[".zip"]	=      "application/zip";
		mime_[".mp3"]	=      "audio/mpeg";
		mime_[".m3u"]	=      "audio/x-mpegurl";
		mime_[".wma"]	=      "audio/x-ms-wma";
		mime_[".wax"]	=      "audio/x-ms-wax";
		mime_[".ogg"]	=      "application/ogg";
		mime_[".wav"]	=      "audio/x-wav";
		mime_[".gif"]	=      "image/gif";
		mime_[".jpg"]	=      "image/jpeg";
		mime_[".jpeg"]	=      "image/jpeg";
		mime_[".png"]	=      "image/png";
		mime_[".xbm"]	=      "image/x-xbitmap";
		mime_[".xpm"]	=      "image/x-xpixmap";
		mime_[".xwd"]	=      "image/x-xwindowdump";
		mime_[".css"]	=      "text/css";
		mime_[".html"]	=      "text/html";
		mime_[".htm"]	=      "text/html";
		mime_[".js"]	=      "text/javascript";
		mime_[".asc"]	=      "text/plain";
		mime_[".c"]	=      "text/plain";
		mime_[".cpp"]	=      "text/plain";
		mime_[".log"]	=      "text/plain";
		mime_[".conf"]	=      "text/plain";
		mime_[".text"]	=      "text/plain";
		mime_[".txt"]	=      "text/plain";
		mime_[".dtd"]	=      "text/xml";
		mime_[".xml"]	=      "text/xml";
		mime_[".mpeg"]	=      "video/mpeg";
		mime_[".mpg"]	=      "video/mpeg";
		mime_[".mov"]	=      "video/quicktime";
		mime_[".qt"]	=      "video/quicktime";
		mime_[".avi"]	=      "video/x-msvideo";
		mime_[".asf"]	=      "video/x-ms-asf";
		mime_[".asx"]	=      "video/x-ms-asf";
		mime_[".wmv"]	=      "video/x-ms-wmv";
		mime_[".bz2"]	=      "application/x-bzip";
		mime_[".tbz"]	=      "application/x-bzip-compressed-tar";
	}
	else {
		load_mime_types(mime_file);
	}

}

void file_server::load_mime_types(std::string file_name)
{
	std::ifstream inp(file_name.c_str());
	if(!inp) {
		return;
	}
	std::string line;
	while(!inp.eof() && getline(inp,line)) {
		if(line.empty() || line[0]=='#')
			continue;
		std::istringstream ss(line);
		std::string mime;
		std::string ext;
		if(ss>>mime) {
			while(ss>>ext) {
				mime_["."+ext]=mime;
			}
		}
	}
}

file_server::~file_server()
{
}

bool file_server::canonical(std::string normal,std::string &real)
{
#ifndef CPPCMS_WIN_NATIVE


	#ifdef HAVE_CANONICALIZE_FILE_NAME

		char *canon=::canonicalize_file_name(normal.c_str());
		if(!canon) return false;
		real=canon;
		free(canon);
		canon=0;

	#else
		#if defined(PATH_MAX)
			int len = PATH_MAX;
		#else
			int len = pathconf(normal.c_str(),_PC_PATH_MAX);
			if(len <= 0)
				len = 32768; // Hope it is enough
		#endif

		std::vector<char> buffer;
		try { 
			// Size may be not feasible for allocation according to POSIX
			buffer.resize(len,0);
		}
		catch(std::bad_alloc const &e) {
			buffer.resize(32768); 
		}

		char *canon = ::realpath(normal.c_str(),&buffer.front());
		if(!canon)
			return false;
		real = canon;
	#endif

#else
		int size=4096;
		std::vector<char> buffer(size,0);
		for(;;) {
			DWORD res = ::GetFullPathName(normal.c_str(),buffer.size(),&buffer.front(),0);
			if(res == 0)
				return false;
			if(res >= buffer.size()) {
				buffer.resize(buffer.size()*2,0);
			}
			else {
				real=&buffer.front();
				break;
			}
		}
#endif
	return true;
}

bool file_server::check_in_document_root(std::string normal,std::string &real) 
{
	normal=document_root_ + "/" + normal;
	if(!canonical(normal,real))
		return false;
	if(real.size() < document_root_.size() || memcmp(real.c_str(),document_root_.c_str(),document_root_.size()) !=0)
		return false;
	return true;
}

int file_server::file_mode(std::string const &file_name)
{
	struct stat st;
	if(::stat(file_name.c_str(),&st) < 0)
		return 0;
	return st.st_mode;
}

void file_server::main(std::string file_name)
{
	file_name = util::urldecode(file_name);
	if(file_name.empty() || file_name[file_name.size()-1]=='/')
		file_name+="/index.html";

	std::string path;

	if(!check_in_document_root(file_name,path)) {
		show404();
		return;
	}
	
	int s=file_mode(path);
	
	if((s & S_IFDIR) && (file_mode(path+"/index.html") & S_IFREG)) {
		response().set_redirect_header(file_name + "/");
		response().out()<<std::flush;
		return;
	}

	if(!(s & S_IFREG)) {
		show404();
		return;
	}
		
	std::string ext;
	size_t pos = path.rfind('.');
	if(pos != std::string::npos)
		ext=path.substr(pos);

	mime_type::const_iterator p=mime_.find(ext);
	if(p!=mime_.end()) 
		response().content_type(p->second);
	else
		response().content_type("application/octet-stream");

	if(!settings().get("http.allow_deflate",false)) {
		response().io_mode(http::response::nogzip);
	}

	std::ifstream file(path.c_str(),std::ifstream::binary | std::ifstream::in);
	if(!file) {
		show404();
		return;
	}
	std::vector<char> buffer(4096);
	while(!file.eof()) {
		file.read(&buffer.front(),buffer.size());
		response().out().write(&buffer.front(),file.gcount());
	}
	response().out()<<std::flush;
}

void file_server::show404()
{
	response().status(http::response::not_found);
	response().set_html_header();
	response().out() <<
		"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n"
		"	\"http://www.w3.org/TR/html4/loose.dtd\">\n"
		"<html>\n"
		"  <head>\n"
		"    <title>404 Not Found</title>\n"
		"  </head>\n"
		"  <body>\n"
		"    <h1>404 Not Found</h1>\n"
		"  </body>\n"
		"</html>\n"<<std::flush;
}


} // impl
} // cppcms
