Auto Index Server

main.cpp

#include <string>
#include <valarray>
#include <regex>
#include <list>
#include <thread>
#include <cctype>
#include <sstream>
#include <map>

#define VERSION "hais/1.2"

#define DEBUG

#ifndef DEBUG
#define printf
#endif

#ifdef _MSC_VER

#include <WinSock2.h>
#include <WS2tcpip.h>
#include <tchar.h>
#include <strsafe.h>

#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "User32.lib")

#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable : 4996)

#define err(st, msg, ...)\
{\
	fprintf(stderr, msg, __VA_ARGS__);\
	exit(st);\
}

#define close closesocket

void DisplayError(const char* msg)
{
	LPVOID lpMsgBuf;
	const auto dw = GetLastError();
	FormatMessage(
		FORMAT_MESSAGE_ALLOCATE_BUFFER |
		FORMAT_MESSAGE_FROM_SYSTEM |
		FORMAT_MESSAGE_IGNORE_INSERTS,
		nullptr,
		dw,
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPTSTR)&lpMsgBuf,
		0,
		nullptr);
	const auto lpDisplayBuf =
		static_cast<LPVOID>(LocalAlloc(
			LMEM_ZEROINIT,
			(lstrlen(static_cast<LPCTSTR>(lpMsgBuf)) +
				lstrlen(static_cast<LPCTSTR>(msg)) + 40) * sizeof(TCHAR)));
	StringCchPrintf(
		static_cast<LPTSTR>(lpDisplayBuf),
		LocalSize(lpDisplayBuf) / sizeof(TCHAR),
		TEXT("%s failed with error %d: %s"),
		msg, dw, lpMsgBuf);
	fprintf(stderr, "%s\n", static_cast<const char*>(lpDisplayBuf));
	LocalFree(lpMsgBuf);
	LocalFree(lpDisplayBuf);
}

std::string ToUnixPath(const char* uri)
{
	auto r = std::string("/");
	r.append(uri, 1);
	r.append(uri + 2);
	for (auto& c : r)
	{
		if (c == '\\') c = '/';
	}
	return r;
}

std::string ToWindowsPath(const char* uri)
{
	auto r = std::string(uri + 1, 1);
	r.append(":");
	r.append(uri + 2);
	for (auto& c : r)
	{
		if (c == '/') c = '\\';
	}
	return r;
}

#else

#include <arpa/inet.h>
#include <dirent.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <err.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <csignal>

#endif

std::valarray<uint8_t> UrlEncodeTableGenerate()
{
	std::valarray<uint8_t> html5(256);
	std::generate(begin(html5), end(html5), [&, i = -1]() mutable
	{
		++i;
		return isalnum(i) || i == '*' || i == '-' || i == '.' || i == '_' ? i : 0;
	});
	return html5;
}

static auto UrlEncodeTable = UrlEncodeTableGenerate();

int FileExists(const char* path)
{
#ifdef _MSC_VER
	const auto attr = GetFileAttributes(path);
	return attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY);
#else
	struct stat sb {};
	return !stat(path, &sb) && S_ISREG(sb.st_mode);
#endif
}

int DirectoryExists(const char* path)
{
#ifdef _MSC_VER
	const auto attr = GetFileAttributes(path);
	return attr != INVALID_FILE_ATTRIBUTES && attr & FILE_ATTRIBUTE_DIRECTORY;
#else
	struct stat sb {};
	return !stat(path, &sb) && S_ISDIR(sb.st_mode);
#endif
}

uint64_t FileSize(const char* path)
{
#ifdef _MSC_VER
	WIN32_FILE_ATTRIBUTE_DATA fad;
	if (!GetFileAttributesEx(path, GetFileExInfoStandard, &fad))
	{
		fprintf(stderr, "%s Can't get file size", path);
		return 0;
	}
	LARGE_INTEGER size;
	size.HighPart = fad.nFileSizeHigh;
	size.LowPart = fad.nFileSizeLow;
	return size.QuadPart;
#else
	struct stat sb {};
	stat(path, &sb);
	return sb.st_size;
#endif
}

std::string FileLastModified(const char* path)
{
	time_t raw;
//#ifdef _MSC_VER
//	#define Oops()\
//	{\
//		DisplayError("Get last modification time of file");\
//		return nullptr;\
//	}
//	auto fh = CreateFile(
//		path,
//		GENERIC_READ | FILE_WRITE_ATTRIBUTES,
//		0,
//		NULL,
//		OPEN_EXISTING,
//		0,
//		NULL);
//	if (fh == INVALID_HANDLE_VALUE) Oops();
//	FILETIME ft;
//	if (GetFileTime(fh, NULL, NULL, &ft) == 0) Oops();
//	ULARGE_INTEGER ull;
//	ull.LowPart = ft.dwLowDateTime;
//	ull.HighPart = ft.dwHighDateTime;
//	raw = ull.QuadPart / 10000000ULL - 11644473600ULL;
//#else
	struct stat sb {};
	stat(path, &sb);
	raw = sb.st_mtime;
//#endif
	char res[35];
	strftime(res, 34, "%a, %d %b %G %T GMT", gmtime(&raw));
	return res;
}

std::string UrlEncode(const char* s, const uint16_t len)
{
#define ToHex(x) ((x) > 9 ? (x) + 55 : (x) + 48)
	auto res = new char[len * 3];
	auto _res = res;
	const auto end = s + len;
	for (; s < end; ++s)
	{
		const auto t = UrlEncodeTable[static_cast<uint8_t>(*s)];
		if (t)
		{
			*res++ = t;
			continue;
		}
		*res++ = '%';
		*res++ = ToHex(static_cast<uint8_t>(*s) >> 4);
		*res++ = ToHex(static_cast<uint8_t>(*s) % 16);
	}
	*res = 0;
	auto r = std::string(_res);
	delete[] _res;
	return r;
}

std::string UrlDecode(const char* s, const uint16_t len)
{
	const auto dec = new char[len + 1];
	const auto end = s + len;
	int c;
	auto o = dec;
	for (; s < end; o++)
	{
		c = *s++;
		if (c == '+') c = ' ';
		else if (c == '%')
		{
			*s++;
			*s++;
			sscanf(s - 2, "%2x", &c);
		}
		*o = c;
	}
	*o = 0;
	const auto r = std::string(dec);
	delete[] dec;
	return r;
}

std::string PathCombine(const char* lp, const char* rp)
{
#ifdef _MSC_VER
#define SplitChar "\\"
#else
#define SplitChar "/"
#endif
	auto path = std::string(lp);
	if (path[path.length() - 1] != SplitChar[0] && rp[0] != SplitChar[0])
	{
		path.append(SplitChar);
	}
	path.append(rp);
	return path;
}

void GetFiles(const char* path, std::ostringstream& dirs, std::ostringstream& files)
{
#define AddFile(oss, href, display, size) \
	((oss) << "<tr><td><a href=\"" << (href) << "\">" << (display) << "</a></td><td align=\"right\">" << (size) << "</td></tr>");
#define AddDir(oss, href, display) \
	((oss) << "<a href=\"" << (href) << "\">" << (display) << "</a><br/>")
#ifdef _MSC_VER
	WIN32_FIND_DATA ffd;
	LARGE_INTEGER filesize;
	size_t lengthOfArg;
	auto hFind = INVALID_HANDLE_VALUE;
	DWORD dwError = 0;
	StringCchLength(path, MAX_PATH, &lengthOfArg);
	if (lengthOfArg > (MAX_PATH - 3))
	err(EXIT_FAILURE, "Filename too long");
	const auto szDir = PathCombine(path, "*");
	hFind = FindFirstFile(szDir.c_str(), &ffd);
	if (INVALID_HANDLE_VALUE == hFind) DisplayError(szDir.c_str());
	do
	{
		if (!strcmp(ffd.cFileName, ".") || !strcmp(ffd.cFileName, "..")) continue;
		if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
		{
			auto dir = PathCombine(ffd.cFileName, "");
			auto href = ToUnixPath(PathCombine(path, dir.c_str()).c_str());
			AddDir(dirs, UrlEncode(href.c_str(), href.length()), dir);
		}
		else
		{
			filesize.LowPart = ffd.nFileSizeLow;
			filesize.HighPart = ffd.nFileSizeHigh;
			auto href = ToUnixPath(PathCombine(path, ffd.cFileName).c_str());
			AddFile(
				files,
				UrlEncode(href.c_str(), href.length()),
				ffd.cFileName,
				std::to_string(filesize.QuadPart));
		}
	}
	while (FindNextFile(hFind, &ffd) != 0);
	dwError = GetLastError();
	if (dwError != ERROR_NO_MORE_FILES) DisplayError(szDir.c_str());
	FindClose(hFind);
#else
	struct dirent* dent;
	struct stat st {};
	char fn[FILENAME_MAX] = { 0 };
	auto len = strlen(path);
	if (len >= FILENAME_MAX - 1) err(EXIT_FAILURE, "Filename too long");
	strcpy(fn, path);
	if (fn[len - 1] != '/')fn[len++] = '/';
	const auto dir = opendir(path);
	if (!dir) err(EXIT_FAILURE, "Can't open %s", path);
	while ((dent = readdir(dir)))
	{
		if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) continue;
		strncpy(fn + len, dent->d_name, FILENAME_MAX - len);
		if (lstat(fn, &st) == -1)
		{
			warn("Can't stat %s", fn);
			continue;
		}
		auto _fn = std::string(fn);
		if (S_ISDIR(st.st_mode))
		{
			auto href = PathCombine(fn, "");
			AddDir(dirs, UrlEncode(href.c_str(), href.length()), PathCombine(_fn.substr(len).c_str(), ""));
		}
		else
		{
			AddFile(files, UrlEncode(_fn.c_str(), _fn.length()), _fn.substr(len), std::to_string(FileSize(_fn.c_str())));
		}
	}
	if (dir) closedir(dir);
#endif
}

#define GetHttpUrl(url, http, sm)\
	std::regex_search(http, sm, std::regex("(POST|GET) .+? HTTP"));\
	const auto (url) = std::regex_replace((sm)[0].str(), std::regex("(POST |GET | HTTP|)"), "")

static const std::map<std::string, std::string> ContentTypeTable =
{
	{"tif", "image/tiff"},
	{"001", "application/x-001"},
	{"301", "application/x-301"},
	{"323", "text/h323"},
	{"906", "application/x-906"},
	{"907", "drawing/907"},
	{"a11", "application/x-a11"},
	{"acp", "audio/x-mei-aac"},
	{"ai", "application/postscript"},
	{"aif", "audio/aiff"},
	{"aifc", "audio/aiff"},
	{"aiff", "audio/aiff"},
	{"anv", "application/x-anv"},
	{"asa", "text/asa"},
	{"asf", "video/x-ms-asf"},
	{"asp", "text/asp"},
	{"asx", "video/x-ms-asf"},
	{"au", "audio/basic"},
	{"avi", "video/avi"},
	{"awf", "application/vnd.adobe.workflow"},
	{"biz", "text/xml"},
	{"bmp", "application/x-bmp"},
	{"bot", "application/x-bot"},
	{"c4t", "application/x-c4t"},
	{"c90", "application/x-c90"},
	{"cal", "application/x-cals"},
	{"cat", "application/vnd.ms-pki.seccat"},
	{"cdf", "application/x-netcdf"},
	{"cdr", "application/x-cdr"},
	{"cel", "application/x-cel"},
	{"cer", "application/x-x509-ca-cert"},
	{"cg4", "application/x-g4"},
	{"cgm", "application/x-cgm"},
	{"cit", "application/x-cit"},
	{"class", "java/*"},
	{"cml", "text/xml"},
	{"cmp", "application/x-cmp"},
	{"cmx", "application/x-cmx"},
	{"cot", "application/x-cot"},
	{"crl", "application/pkix-crl"},
	{"crt", "application/x-x509-ca-cert"},
	{"csi", "application/x-csi"},
	{"css", "text/css"},
	{"cut", "application/x-cut"},
	{"dbf", "application/x-dbf"},
	{"dbm", "application/x-dbm"},
	{"dbx", "application/x-dbx"},
	{"dcd", "text/xml"},
	{"dcx", "application/x-dcx"},
	{"der", "application/x-x509-ca-cert"},
	{"dgn", "application/x-dgn"},
	{"dib", "application/x-dib"},
	{"dll", "application/x-msdownload"},
	{"doc", "application/msword"},
	{"dot", "application/msword"},
	{"drw", "application/x-drw"},
	{"dtd", "text/xml"},
	{"dwf", "application/x-dwf"},
	{"dwg", "application/x-dwg"},
	{"dxb", "application/x-dxb"},
	{"dxf", "application/x-dxf"},
	{"edn", "application/vnd.adobe.edn"},
	{"emf", "application/x-emf"},
	{"eml", "message/rfc822"},
	{"ent", "text/xml"},
	{"epi", "application/x-epi"},
	{"eps", "application/x-ps"},
	{"eps", "application/postscript"},
	{"etd", "application/x-ebx"},
	{"exe", "application/x-msdownload"},
	{"fax", "image/fax"},
	{"fdf", "application/vnd.fdf"},
	{"fif", "application/fractals"},
	{"fo", "text/xml"},
	{"frm", "application/x-frm"},
	{"g4", "application/x-g4"},
	{"gbr", "application/x-gbr"},
	{"gif", "image/gif"},
	{"gl2", "application/x-gl2"},
	{"gp4", "application/x-gp4"},
	{"hgl", "application/x-hgl"},
	{"hmr", "application/x-hmr"},
	{"hpg", "application/x-hpgl"},
	{"hpl", "application/x-hpl"},
	{"hqx", "application/mac-binhex40"},
	{"hrf", "application/x-hrf"},
	{"hta", "application/hta"},
	{"htc", "text/x-component"},
	{"htm", "text/html"},
	{"html", "text/html"},
	{"htt", "text/webviewhtml"},
	{"htx", "text/html"},
	{"icb", "application/x-icb"},
	{"ico", "image/x-icon"},
	{"ico", "application/x-ico"},
	{"iff", "application/x-iff"},
	{"ig4", "application/x-g4"},
	{"igs", "application/x-igs"},
	{"iii", "application/x-iphone"},
	{"img", "application/x-img"},
	{"ins", "application/x-internet-signup"},
	{"isp", "application/x-internet-signup"},
	{"IVF", "video/x-ivf"},
	{"java", "java/*"},
	{"jfif", "image/jpeg"},
	{"jpe", "image/jpeg"},
	{"jpe", "application/x-jpe"},
	{"jpeg", "image/jpeg"},
	{"jpg", "image/jpeg"},
	{"jpg", "application/x-jpg"},
	{"js", "application/x-javascript"},
	{"jsp", "text/html"},
	{"la1", "audio/x-liquid-file"},
	{"lar", "application/x-laplayer-reg"},
	{"latex", "application/x-latex"},
	{"lavs", "audio/x-liquid-secure"},
	{"lbm", "application/x-lbm"},
	{"lmsff", "audio/x-la-lms"},
	{"ls", "application/x-javascript"},
	{"ltr", "application/x-ltr"},
	{"m1v", "video/x-mpeg"},
	{"m2v", "video/x-mpeg"},
	{"m3u", "audio/mpegurl"},
	{"m4e", "video/mpeg4"},
	{"mac", "application/x-mac"},
	{"man", "application/x-troff-man"},
	{"math", "text/xml"},
	{"mdb", "application/x-mdb"},
	{"mfp", "application/x-shockwave-flash"},
	{"mht", "message/rfc822"},
	{"mhtml", "message/rfc822"},
	{"mi", "application/x-mi"},
	{"mid", "audio/mid"},
	{"midi", "audio/mid"},
	{"mil", "application/x-mil"},
	{"mml", "text/xml"},
	{"mnd", "audio/x-musicnet-download"},
	{"mns", "audio/x-musicnet-stream"},
	{"mocha", "application/x-javascript"},
	{"movie", "video/x-sgi-movie"},
	{"mp1", "audio/mp1"},
	{"mp2", "audio/mp2"},
	{"mp2v", "video/mpeg"},
	{"mp3", "audio/mp3"},
	{"mp4", "video/mp4"},
	{"mpa", "video/x-mpg"},
	{"mpd", "application/vnd.ms-project"},
	{"mpe", "video/x-mpeg"},
	{"mpeg", "video/mpg"},
	{"mpg", "video/mpg"},
	{"mpga", "audio/rn-mpeg"},
	{"mpp", "application/vnd.ms-project"},
	{"mps", "video/x-mpeg"},
	{"mpt", "application/vnd.ms-project"},
	{"mpv", "video/mpg"},
	{"mpv2", "video/mpeg"},
	{"mpw", "application/vnd.ms-project"},
	{"mpx", "application/vnd.ms-project"},
	{"mtx", "text/xml"},
	{"mxp", "application/x-mmxp"},
	{"net", "image/pnetvue"},
	{"nrf", "application/x-nrf"},
	{"nws", "message/rfc822"},
	{"odc", "text/x-ms-odc"},
	{"out", "application/x-out"},
	{"p10", "application/pkcs10"},
	{"p12", "application/x-pkcs12"},
	{"p7b", "application/x-pkcs7-certificates"},
	{"p7c", "application/pkcs7-mime"},
	{"p7m", "application/pkcs7-mime"},
	{"p7r", "application/x-pkcs7-certreqresp"},
	{"p7s", "application/pkcs7-signature"},
	{"pc5", "application/x-pc5"},
	{"pci", "application/x-pci"},
	{"pcl", "application/x-pcl"},
	{"pcx", "application/x-pcx"},
	{"pdf", "application/pdf"},
	{"pdx", "application/vnd.adobe.pdx"},
	{"pfx", "application/x-pkcs12"},
	{"pgl", "application/x-pgl"},
	{"pic", "application/x-pic"},
	{"pko", "application/vnd.ms-pki.pko"},
	{"pl", "application/x-perl"},
	{"plg", "text/html"},
	{"pls", "audio/scpls"},
	{"plt", "application/x-plt"},
	{"png", "image/png"},
	{"png", "application/x-png"},
	{"pot", "application/vnd.ms-powerpoint"},
	{"ppa", "application/vnd.ms-powerpoint"},
	{"ppm", "application/x-ppm"},
	{"pps", "application/vnd.ms-powerpoint"},
	{"ppt", "application/vnd.ms-powerpoint"},
	{"ppt", "application/x-ppt"},
	{"pr", "application/x-pr"},
	{"prf", "application/pics-rules"},
	{"prn", "application/x-prn"},
	{"prt", "application/x-prt"},
	{"ps", "application/x-ps"},
	{"ps", "application/postscript"},
	{"ptn", "application/x-ptn"},
	{"pwz", "application/vnd.ms-powerpoint"},
	{"r3t", "text/vnd.rn-realtext3d"},
	{"ra", "audio/vnd.rn-realaudio"},
	{"ram", "audio/x-pn-realaudio"},
	{"ras", "application/x-ras"},
	{"rat", "application/rat-file"},
	{"rdf", "text/xml"},
	{"rec", "application/vnd.rn-recording"},
	{"red", "application/x-red"},
	{"rgb", "application/x-rgb"},
	{"rjs", "application/vnd.rn-realsystem-rjs"},
	{"rjt", "application/vnd.rn-realsystem-rjs"},
	{"rlc", "application/x-rlc"},
	{"rle", "application/x-rle"},
	{"rm", "application/vnd.rn-realmedia"},
	{"rmf", "application/vnd.adobe.rmf"},
	{"rmi", "audio/mid"},
	{"rmj", "application/vnd.rn-realsystem-rmj"},
	{"rmm", "audio/x-pn-realaudio"},
	{"rmp", "application/vnd.rn-rn_music_package"},
	{"rms", "application/vnd.rn-realmedia-secure"},
	{"rmvb", "application/vnd.rn-realmedia-vbr"},
	{"rmx", "application/vnd.rn-realsystem-rmx"},
	{"rnx", "application/vnd.rn-realplayer"},
	{"rp", "image/vnd.rn-realpix"},
	{"rpm", "audio/x-pn-realaudio-plugin"},
	{"rsml", "application/vnd.rn-rsml"},
	{"rt", "text/vnd.rn-realtext"},
	{"rtf", "application/msword"},
	{"rtf", "application/x-rtf"},
	{"rv", "video/vnd.rn-realvideo"},
	{"sam", "application/x-sam"},
	{"sat", "application/x-sat"},
	{"sdp", "application/sdp"},
	{"sdw", "application/x-sdw"},
	{"sit", "application/x-stuffit"},
	{"slb", "application/x-slb"},
	{"sld", "application/x-sld"},
	{"slk", "drawing/x-slk"},
	{"smi", "application/smil"},
	{"smil", "application/smil"},
	{"smk", "application/x-smk"},
	{"snd", "audio/basic"},
	{"sol", "text/plain"},
	{"sor", "text/plain"},
	{"spc", "application/x-pkcs7-certificates"},
	{"spl", "application/futuresplash"},
	{"spp", "text/xml"},
	{"ssm", "application/streamingmedia"},
	{"sst", "application/vnd.ms-pki.certstore"},
	{"stl", "application/vnd.ms-pki.stl"},
	{"stm", "text/html"},
	{"sty", "application/x-sty"},
	{"svg", "text/xml"},
	{"swf", "application/x-shockwave-flash"},
	{"tdf", "application/x-tdf"},
	{"tg4", "application/x-tg4"},
	{"tga", "application/x-tga"},
	{"tif", "image/tiff"},
	{"tiff", "image/tiff"},
	{"tld", "text/xml"},
	{"top", "drawing/x-top"},
	{"torrent", "application/x-bittorrent"},
	{"tsd", "text/xml"},
	{"txt", "text/plain"},
	{"uin", "application/x-icq"},
	{"uls", "text/iuls"},
	{"vcf", "text/x-vcard"},
	{"vda", "application/x-vda"},
	{"vdx", "application/vnd.visio"},
	{"vml", "text/xml"},
	{"vpg", "application/x-vpeg005"},
	{"vsd", "application/vnd.visio"},
	{"vsd", "application/x-vsd"},
	{"vss", "application/vnd.visio"},
	{"vst", "application/vnd.visio"},
	{"vst", "application/x-vst"},
	{"vsw", "application/vnd.visio"},
	{"vsx", "application/vnd.visio"},
	{"vtx", "application/vnd.visio"},
	{"vxml", "text/xml"},
	{"wav", "audio/wav"},
	{"wax", "audio/x-ms-wax"},
	{"wb1", "application/x-wb1"},
	{"wb2", "application/x-wb2"},
	{"wb3", "application/x-wb3"},
	{"wbmp", "image/vnd.wap.wbmp"},
	{"wiz", "application/msword"},
	{"wk3", "application/x-wk3"},
	{"wk4", "application/x-wk4"},
	{"wkq", "application/x-wkq"},
	{"wks", "application/x-wks"},
	{"wm", "video/x-ms-wm"},
	{"wma", "audio/x-ms-wma"},
	{"wmd", "application/x-ms-wmd"},
	{"wmf", "application/x-wmf"},
	{"wml", "text/vnd.wap.wml"},
	{"wmv", "video/x-ms-wmv"},
	{"wmx", "video/x-ms-wmx"},
	{"wmz", "application/x-ms-wmz"},
	{"wp6", "application/x-wp6"},
	{"wpd", "application/x-wpd"},
	{"wpg", "application/x-wpg"},
	{"wpl", "application/vnd.ms-wpl"},
	{"wq1", "application/x-wq1"},
	{"wr1", "application/x-wr1"},
	{"wri", "application/x-wri"},
	{"wrk", "application/x-wrk"},
	{"ws", "application/x-ws"},
	{"ws2", "application/x-ws"},
	{"wsc", "text/scriptlet"},
	{"wsdl", "text/xml"},
	{"wvx", "video/x-ms-wvx"},
	{"xdp", "application/vnd.adobe.xdp"},
	{"xdr", "text/xml"},
	{"xfd", "application/vnd.adobe.xfd"},
	{"xfdf", "application/vnd.adobe.xfdf"},
	{"xhtml", "text/html"},
	{"xls", "application/vnd.ms-excel"},
	{"xls", "application/x-xls"},
	{"xlw", "application/x-xlw"},
	{"xml", "text/xml"},
	{"xpl", "audio/scpls"},
	{"xq", "text/xml"},
	{"xql", "text/xml"},
	{"xquery", "text/xml"},
	{"xsd", "text/xml"},
	{"xsl", "text/xml"},
	{"xslt", "text/xml"},
	{"xwd", "application/x-xwd"},
	{"x_b", "application/x-x_b"},
	{"sis", "application/vnd.symbian.install"},
	{"sisx", "application/vnd.symbian.install"},
	{"x_t", "application/x-x_t"},
	{"ipa", "application/vnd.iphone"},
	{"apk", "application/vnd.android.package-archive"},
	{"xap", "application/x-silverlight-app"}
};

std::string GetContentType(const char* path)
{
	const auto len = strlen(path);
	auto i = path + len - 1;
	for (; i >= path; --i)
	{
		if (*i == '.') break;
	}
	const auto ct = ContentTypeTable.find(std::string(i + 1));
	if (ct != ContentTypeTable.end()) return ct->second;
	return "application/octet-stream";
}

std::string GetHttpUrlWithoutGet(const char* http, const uint32_t size)
{
	auto start = 0, end = 0;
	for (auto i = 0; i < size; ++i)
	{
		if (http[i] == ' ')
		{
			start = i + 1;
			break;
		}
	}
	for (auto i = start; i < size; ++i)
	{
		if (http[i] == ' ' || http[i] == '?')
		{
			end = i;
			break;
		}
	}
	return !start && !end ? std::string() : std::string(http + start, end - start);
}

void HttpNotFound(const int fd)
{

	static const auto html =
		"<html><head><title>404 Not Found</title></head>"
		"<body>"
		"<center><h1>404 Not Found</h1></center>"
		"<hr><center>iriszero/" VERSION "</center>"
		"</body></html>";
	static const auto len = strlen(html);
	std::ostringstream oss;
	oss <<
		"HTTP/1.1 404 Not Found\r\n"
		"Content-Length: " << std::to_string(len) << "\r\n"
		"Content-Type: text/html\r\n"
		"Server: iriszero/" VERSION "\r\n"
		"Connection: close\r\n\r\n" <<
		html;
	const auto http = oss.str();
	printf("<========================\n%s\n", http.c_str());
	send(fd, http.c_str(), http.length(), 0);
}

void HttpNotModified(const int fd, const char* lastModified)
{
	std::ostringstream oss;
	oss << "HTTP/1.1 304 Not Modified\r\n"
		"Server: iriszero/" VERSION "\r\n"
		"Last-Modified: " << lastModified << "\r\n"
		"Connection: close\r\n\r\n";
	const auto http = oss.str();
	printf("<========================\n%s\n", http.c_str());
	send(fd, http.c_str(), http.length(), 0);
}

void HttpFile(
	const int fd,
	const char* path,
	const char* lastModified,
	const uint64_t fileSize,
	const uint64_t offset = 0,
	uint64_t size = 0)
{
#define HttpHead(value, http, sm) \
	std::regex_search(http, sm, std::regex(""#value": {0,1}.+?\\r{0,1}\\n", std::regex::icase)); \
	const auto (value) = std::regex_replace((sm)[0].str(), std::regex("("#value": {0,1}|\\r{0,1}\\n)", std::regex::icase), "")

#define IfErrorThenReturn(fun, fp) if((fun) < 0) { fclose(fp); return; }
	char buf[4096] = {0};
	size_t len = 0;
	const auto fp = fopen(path, "rb");
	if (!offset && !size)
	{
		std::ostringstream head;
		head << "HTTP/1.1 200 OK\r\nContent-Length:" <<
			std::to_string(fileSize) <<
			"\r\nConnection: close"
			"\r\nLast-Modified: " << lastModified <<
			"\r\nContent-Type: " << GetContentType(path) <<
			"\r\nServer: iriszero/" VERSION
			"\r\n\r\n";
		printf("<========================\n%s\n", head.str().c_str());
		IfErrorThenReturn(send(fd, head.str().c_str(), head.str().length(), 0), fp);
		while ((len = fread(buf, sizeof(uint8_t), 4096, fp)) == 4096)
			IfErrorThenReturn(send(fd, buf, 4096, 0), fp);
		IfErrorThenReturn(send(fd, buf, len, 0), fp);
	}
	else
	{
		fseek(fp, offset, SEEK_SET);
		std::ostringstream head;
		head << "HTTP/1.1 206 Partial Content\r\nAccept-Ranges: bytes\r\n" <<
			"Server: iriszero/" VERSION "\r\n" <<
			"Content-Type: " << GetContentType(path) << "\r\n"
			"Content-Length: " << std::to_string(size)
			<< "\r\nContent-Range: bytes " <<
			std::to_string(offset) << "-" <<
			std::to_string(offset + size - 1) << "/" <<
			std::to_string(fileSize) << "\r\nConnection: close\r\n\r\n";
		printf("<========================\n%s\n", head.str().c_str());
		IfErrorThenReturn(send(fd, head.str().c_str(), head.str().length(), 0), fp);
		for (; size > 4096; size -= len)
		{
			len = fread(buf, sizeof(uint8_t), 4096, fp);
			IfErrorThenReturn(send(fd, buf, len, 0), fp);
		}
		len = fread(buf, sizeof(uint8_t), size, fp);
		IfErrorThenReturn(send(fd, buf, len, 0), fp);
	}
}

void IndexOf(const int fd, const char* path, const char* coding)
{
	std::ostringstream dirs;
	std::ostringstream files;
	GetFiles(path, dirs, files);
	std::ostringstream html;
	html << " <!DOCTYPE html>"
		"<html>" <<
		"<head><title>Index of " << path << "</title>"
		"<meta charset=\"" << coding << "\"/>"
		"</head>"
		"<body>"
		"<h1>Index of " << path << "</h1><hr>" <<
		dirs.str() << (dirs.str().empty() ? "" : "<hr>") <<
		"<table>" <<
		(files.str().empty() ? "" : "<tr><th>File Name</th><th>Size</th></tr>") <<
		files.str() <<
		"</table></body>"
		"</html>";
	std::ostringstream head;
	head << "HTTP/1.1 200 OK\r\nContent-length: " << std::to_string(html.str().length()) <<
		"\r\nServer: iriszero/" VERSION <<
		"\r\nContent-Type: text/html\r\n\r\n";
	printf("<========================\n%s\n", head.str().c_str());
	send(fd, head.str().c_str(), head.str().length(), 0);
	send(fd, html.str().c_str(), html.str().length(), 0);
}

bool CheckUrl(const std::string& url, const char* path)
{
	std::regex_replace(url, std::regex("\\.\\."), "");
	return !memcmp(url.c_str(), path, strlen(path) - 1);
}

std::list<std::tuple<uint64_t, uint64_t>> GetOffsetAndSize(
	const std::string& __range,
	const uint64_t fileSize)
{
	std::list<std::tuple<uint64_t, uint64_t>> res{};
	auto _range = __range.substr(6, __range.length() - 6);
	auto pos = 0;
	for (auto i = 0; i < _range.length(); ++i)
	{
		if (_range[i] == ',')
		{
			auto range = _range.substr(pos, i - pos);
			const int _pos = range.find('-');
			auto _start = range.substr(0, _pos);
			auto _end = range.substr(_pos + 1, range.length() - _pos - 1);
			if (_start.empty())
			{
				auto end = std::stoull(_end);
				res.emplace_back(fileSize - end, end);
			}
			else if (_end.empty())
			{
				auto start = std::stoull(_start);
				res.emplace_back(start, fileSize - start);
			}
			else
			{
				auto start = std::stoull(_start);
				res.emplace_back(start, std::stoull(_end) - start + 1);
			}
			pos = i + 1;
		}
	}
	const auto range = _range.substr(pos, _range.length() - pos);
	const int _pos = range.find('-');
	const auto _start = range.substr(0, _pos);
	const auto _end = range.substr(_pos + 1, range.length() - _pos - 1);
	if (_start.empty())
	{
		auto end = std::stoull(_end);
		res.emplace_back(fileSize - end, end);
	}
	else if (_end.empty())
	{
		auto start = std::stoull(_start);
		res.emplace_back(start, fileSize - start);
	}
	else
	{
		auto start = std::stoull(_start);
		res.emplace_back(start, std::stoull(_end) - start + 1);
	}
	return res;
}

void Index(const char* path, const int port, const int threadNum, const char* coding, const char* icoPath)
{
	UrlEncodeTable['/'] = '/';
	sockaddr_in svrAddr{}, cliAddr{};
	svrAddr.sin_family = AF_INET;
	svrAddr.sin_addr.s_addr = INADDR_ANY;
	svrAddr.sin_port = htons(port);
	char one[4] = {0};
	socklen_t sinLen = sizeof(cliAddr);
#ifdef _MSC_VER
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) < 0)
	err(EXIT_FAILURE, "WinSock init fail");
	auto sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock == INVALID_SOCKET)
	err(EXIT_FAILURE, "Can't open socket");
#else
	struct sigaction action;
	action.sa_handler = [](int) {};
	sigemptyset(&action.sa_mask);
	action.sa_flags = 0;
	sigaction(SIGPIPE, &action, nullptr);
	auto sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (sock <= 0) err(EXIT_FAILURE, "Can't open socket");
#endif
	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, one, sizeof(one));
	if (bind(sock, (struct sockaddr *)&svrAddr, sizeof(svrAddr)) < 0)
	{
		close(sock);
		err(1, "Can't bind");
	}
	listen(sock, threadNum);
	std::valarray<std::thread> pool(threadNum);
	std::generate(begin(pool), end(pool), [&]()
	{
		return std::thread([&]()
		{
			const auto iconPath = PathCombine(path, "favicon.ico");
			while (true)
			{
				const auto clientFd = accept(sock, (struct sockaddr *)&cliAddr, &sinLen);
				char buf[4096] = {0};
				auto len = 0;
				std::ostringstream _http;
				while ((len = recv(clientFd, buf, 4096, 0)) == 4096)
					_http << buf;
				_http.write(buf, len);
				auto http = _http.str();
				printf(
					"%s:%d===================>\n%s\n",
					inet_ntoa(cliAddr.sin_addr),
					ntohs(cliAddr.sin_port),
					http.c_str());
				std::smatch sm;
				auto _url = GetHttpUrlWithoutGet(http.c_str(), http.length());
#ifdef _MSC_VER
				auto url = ToWindowsPath(
					UrlDecode(_url.c_str(), _url.length()).c_str());
#else
						auto url = UrlDecode(_url.c_str(), _url.length());
#endif
				auto urlStatus = false;
				if (_url.empty()) continue;
				if (_url == "/") goto index;
				if (_url == "/favicon.ico" && !FileExists(iconPath.c_str()))
				{
					if (!icoPath[0]) HttpNotFound(clientFd);
					else HttpFile(
						clientFd, 
						icoPath,
						FileLastModified(iconPath.c_str()).c_str(),
						FileSize(iconPath.c_str()));
					close(clientFd);
					continue;
				}
				urlStatus = CheckUrl(url, path);
				if (urlStatus && DirectoryExists(url.c_str()))
				{
					IndexOf(clientFd, url.c_str(), coding);
				}
				else if (urlStatus && FileExists(url.c_str()))
				{
					HttpHead(Range, http, sm);
					if (Range.empty())
					{
						std::regex_search(
							http, 
							sm, 
							std::regex("If-Modified-Since: {0,1}.+?\\r{0,1}\\n", std::regex::icase));
						auto fileLastModified = FileLastModified(url.c_str());
						auto lastModified = std::regex_replace(
							sm[0].str(), 
							std::regex("(If-Modified-Since: {0,1}|\\r{0,1}\\n)", std::regex::icase),
							"");
						if(lastModified == fileLastModified)
						{
							HttpNotModified(clientFd, fileLastModified.c_str());
						}
						else
						{
							HttpFile(
								clientFd,
								url.c_str(),
								FileLastModified(url.c_str()).c_str(),
								FileSize(url.c_str()));
						}
					}
					else
					{
						for (auto& i : GetOffsetAndSize(Range, FileSize(url.c_str())))
						{
							HttpFile(
								clientFd,
								url.c_str(),
								nullptr,
								FileSize(url.c_str()),
								std::get<0>(i),
								std::get<1>(i));
						}
					}
				}
				else
				{
				index:;
					IndexOf(clientFd, path, coding);
				}
				close(clientFd);
			}
		});
	});
	for (auto& t : pool) t.join();
}

int main(const int argc, char* argv[])
{
	if (argc == 5)
	{
		Index(
			argv[1],
			strtol(argv[2], &argv[2], 10),
			strtol(argv[3], &argv[3], 10),
			argv[4],
			"");
	}
	if (argc == 6)
	{
		Index(
			argv[1],
			strtol(argv[2], &argv[2], 10),
			strtol(argv[3], &argv[3], 10),
			argv[4],
			argv[5]);
	}
	err(EXIT_FAILURE, "%s IndexPath Port threadNum Coding [IcoPath]\n", argv[0]);
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.1)
PROJECT(HttpAutoIndexServer)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS ON)
find_package(Threads)
AUX_SOURCE_DIRECTORY(src HttpAutoIndexServer)
ADD_EXECUTABLE(HttpAutoIndexServer.out main.cpp)
TARGET_LINK_LIBRARIES(HttpAutoIndexServer.out pthread)

 

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注