~blainsmith/static-httpd

0c118819e1aa8a92d0b8fb71080b4ef1b8376ba0 — Blain Smith 2 months ago 79e4f70
dir listing stats and use mime module

Signed-off-by: Blain Smith <rebelgeek@blainsmith.com>
1 files changed, 39 insertions(+), 67 deletions(-)

M cmd/static-httpd/main.ha
M cmd/static-httpd/main.ha => cmd/static-httpd/main.ha +39 -67
@@ 8,6 8,7 @@ use getopt;
use io;
use log;
use memio;
use mime;
use net;
use net::ip;
use net::tcp;


@@ 17,64 18,17 @@ use sort;
use sort::cmp;
use strconv;
use strings;
use time::chrono;
use time::date;

type invalid = !void;

type error = (...errors::error | ...io::error | ...path::error |
...fs::error | invalid);

let mimetypes: [](str, str) = [];

@init fn init() void = {
	const mtf = match (os::open("/usr/share/mime/globs2")) {
	case let f: io::file =>
		yield f;
	case let err: io::error =>
		log::fatalf("error opening mime types: {}", io::strerror(err));
	};
	defer io::close(mtf)!;

	const scan = bufio::newscanner(mtf, os::BUFSZ);
	defer bufio::finish(&scan);

	for (true) {
		const line = match (bufio::scan_line(&scan)) {
		case let s: const str =>
			yield s;
		case io::EOF =>
			break;
		case let err: io::error =>
			log::fatalf("error reading mime line: {}", io::strerror(err));
		case utf8::invalid =>
			continue;
		};

		const tok = strings::tokenize(line, ":");
		strings::next_token(&tok);
		const mt = match (strings::next_token(&tok)) {
		case let s: str =>
			yield s;
		case =>
			continue;
		};
		const ext = match (strings::next_token(&tok)) {
		case let s: str =>
			yield strings::sub(s, 2, strings::end);
		case =>
			continue;
		};

		append(mimetypes, (strings::dup(mt), strings::dup(ext)));
	};
};

@fini fn fini() void = {
	free(mimetypes);
};

fn cmpstrs(a: const *opaque, b: const *opaque) int = {
	const a = *(a: *str), b = *(b: *str);
	return ascii::strcasecmp(a, b);
fn cmpdirents(a: const *opaque, b: const *opaque) int = {
	const a = *(a: *fs::dirent), b = *(b: *fs::dirent);
	return ascii::strcasecmp(a.name, b.name);
};

export fn main() void = {


@@ 201,20 155,37 @@ fn handle_conn(conn: net::socket, cwd: str) (size | error) = {
			case let err: fs::error =>
				log::printfln("method={} path={} status=500", method, path);
				httperror(file, 500)?;
				return err;
			};

			let names: []str = [];
			for (let idx = 0z; idx < len(dirents); idx += 1) {
				append(names, dirents[idx].name);
				return 0z;
			};
			sort::sort(names, size(str), &cmpstrs);
			sort::sort(dirents, size(fs::dirent), &cmpdirents);

			const listing = memio::dynamic();
			let sz = fmt::fprint(&listing, "<html lang=""en""><head><title>static-httpd</title></head><body>")?;
			sz += fmt::fprintf(&listing, "<h1>Directory listing for {}</h1><hr /><table><thead><tr><th>Name</th><th>Size</th><th>Modified</th></tr></thead><tbody>", fullpath)?;
			for (let idx = 0z; idx < len(names); idx += 1) {
				sz += fmt::fprintf(&listing, "<tr><td><a href=""./{}"">{}</a></td><td>{}</td><td>{}</td></tr>", names[idx], names[idx], "", "")?;

			if (path != "/") {
				sz += fmt::fprint(&listing, "<tr><td><a href=""../"">../</a></td><td>&nbsp;</td><td>&nbsp;</td></tr>")?;
			};

			for (let idx = 0z; idx < len(dirents); idx += 1) {
				const stat = match (os::stat(strings::concat(fullpath, "/", dirents[idx].name))) {
				case let s: fs::filestat =>
					yield s;
				case let err: fs::error =>
					httperror(file, 500)?;
					return 0z;
				};

				const suffix = if (fs::isdir(stat.mode)) {
					yield "/";
				} else {
					yield "";
				};

				const mdate = date::from_instant(chrono::LOCAL, stat.mtime);
				const strdate = date::asformat(date::WRIST, &mdate)?;

				sz += fmt::fprintf(&listing, "<tr><td><a href=""./{}"">{}{}</a></td><td>{}</td><td>{}</td></tr>", dirents[idx].name, dirents[idx].name, suffix, stat.sz, strdate)?;
			};
			sz += fmt::fprint(&listing, "</tbody></table></body></html>")?;
			fs::dirents_free(dirents);


@@ 227,6 198,8 @@ fn handle_conn(conn: net::socket, cwd: str) (size | error) = {
			const sz = io::write(&file, memio::buffer(&listing))?;
			bufio::flush(&file)?;

			log::printfln("method={} path={} status=200", method, path);

			return sz;
		};



@@ 299,12 272,11 @@ fn read_statusline(scan: *bufio::scanner) ((str, str) | io::error | invalid) = {
};

fn typebyext(ext: str) str = {
	let mt = "application/octet-stream";
	for (let idx = 0z; idx < len(mimetypes); idx += 1) {
		if (ext == mimetypes[idx].1) {
			mt = mimetypes[idx].0;
			break;
		};
	const mt = match (mime::lookup_ext(ext)) {
	case let r: const *mime::mimetype =>
		yield r.mime;
	case =>
		yield "application/octet-stream";
	};

	return strings::dup(mt);