77 lines
2.9 KiB
JavaScript
77 lines
2.9 KiB
JavaScript
import * as path from "path"
|
|
import * as fs from "fs";
|
|
|
|
import DefaultHandler from "./default.js";
|
|
|
|
let fileTypeFromFile;
|
|
import("file-type").then(ft => fileTypeFromFile = ft.default.fromFile).catch(console.error);
|
|
|
|
export default class StaticHandler extends DefaultHandler {
|
|
constructor(basePath, directoryList = false) {
|
|
super();
|
|
|
|
this.basePath = basePath;
|
|
this.directoryList = directoryList;
|
|
|
|
//This will throw if the file doesn't exist
|
|
this.isSingleFile = !fs.statSync(basePath).isDirectory();
|
|
|
|
this.matchesSubpaths = !this.isSingleFile;
|
|
}
|
|
|
|
// This is the handler
|
|
// url is the URL object, with url.pathname being the path. p is the 'client-side' basepath
|
|
async handle (url, p, socket) {
|
|
const relativePath = path.relative(p, url.pathname);
|
|
//Concat and normalize the passed URL as being relative to the base path
|
|
const toServe = path.join(this.basePath, relativePath);
|
|
//If the resulting path is a parent, relative to basePath, disallow that
|
|
if (path.relative(this.basePath, toServe).startsWith("..")) {
|
|
return "50"
|
|
}
|
|
|
|
console.log(`Attempting to serve ${toServe}.`);
|
|
try {
|
|
if (fs.statSync(toServe).isDirectory()) {
|
|
console.log(`File is a directory and directoryList is ${this.directoryList}.`);
|
|
if (this.directoryList) {
|
|
//TODO: Static content directory listing
|
|
} else {
|
|
return "51 Not Found\r\n";
|
|
}
|
|
}
|
|
}catch (e) {
|
|
//If the file doesn't exist
|
|
console.log(`File doesn't exist or can't be opened.`)
|
|
return "51 Not Found\r\n";
|
|
}
|
|
|
|
let mimeType = "text/gemini";
|
|
|
|
if (fileTypeFromFile) {
|
|
const type = await fileTypeFromFile(toServe);
|
|
// `type` will be undefined if the file is plaintext
|
|
// In which case we want to fall down to serving plaintext
|
|
if (type) {
|
|
mimeType = type.mime;
|
|
}
|
|
}
|
|
|
|
//TODO: some file-ending based logic, please
|
|
//So we can serve md and txt
|
|
|
|
|
|
//TODO: convert line endings
|
|
// I read somewhere that `readFileSync` on every request is a bad idea, but I don't care
|
|
socket.write(`20 ${mimeType}\r\n`);
|
|
const fileReader = fs.createReadStream(toServe);
|
|
//This function (handle) needs to return after we're done writing the file to the stream
|
|
//Because the place that called this is going to handle .end() the socket
|
|
//So we don't end the socket here, and we make a promise to wait for the reader to end before returning
|
|
//It would be great if we could just not return from this function but I don't think that's how this works
|
|
fileReader.pipe(socket, {end: false});
|
|
await new Promise((res, rej) => fileReader.on("end", res));
|
|
return "";
|
|
}
|
|
}
|