diff --git a/.gitignore b/.gitignore index cfaad76..8793fab 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.pem +node_modules diff --git a/GeminiServer.js b/GeminiServer.js index 34eb270..75fe73d 100644 --- a/GeminiServer.js +++ b/GeminiServer.js @@ -220,6 +220,9 @@ export default class GeminiServer { } } }); + + // This can be just ECONNRESET + socket.on("error", console.error); }); server.listen(1965); } diff --git a/README.md b/README.gmi similarity index 94% rename from README.md rename to README.gmi index da6e0ed..73aa324 100644 --- a/README.md +++ b/README.gmi @@ -23,7 +23,7 @@ A `relativePath` is the difference between a `basePath` and a `urlPath`. It's no Sometimes these paths will include their hostnames -A path in the pathRegistry is a `p`, which is an object with a hostname, a basePath, and a handler +A path in the pathRegistry is a `p`, which is an object with a hostname, a basePath, and a handler @@ -44,4 +44,4 @@ openssl x509 -req -in csr.pem -signkey private-key.pem -out public-cert.pem It's possible to imagine a situation where astronomical theater is behind a proxy and the proxy doesn't do address translation, passing the raw gemini request to us. In this case, our behavior is undefined. -Similarly, SNI is not supported with IP addresses. +Similarly, SNI is not supported with IP addresses. diff --git a/example.js b/example.js index 4b88371..3e283c5 100644 --- a/example.js +++ b/example.js @@ -57,8 +57,8 @@ server.registerPath("localhost/allFiles", new ExampleHandler()); // We will provide some convenient handlers for static, CGI, and reverse proxy //if the passed file is a single file, it's a file. If it's a directory, all sub-files are auto-included -server.registerPath("localhost/static/about.gmi", new StaticHandler("/tmp/content/about.gmi" /*, {options}*/)); -server.registerPath("localhost/static", new StaticHandler("/tmp/content/" /*, {options}*/)); +server.registerPath("localhost/static/about.gmi", new StaticHandler("README.gmi" /*, {options}*/)); +server.registerPath("localhost/static", new StaticHandler("static/" /*, {options}*/)); // server.registerPath("localhost/static", new StaticHandler("/tmp/yourmom/" /*, {options}*/)); //The file passed to CGI handler must exist and be executable at run time diff --git a/handlers/static.js b/handlers/static.js index 226f3a5..00989a6 100644 --- a/handlers/static.js +++ b/handlers/static.js @@ -3,6 +3,9 @@ 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(); @@ -18,7 +21,7 @@ export default class StaticHandler extends DefaultHandler { // This is the handler // url is the URL object, with url.pathname being the path. p is the 'client-side' basepath - handle (url, p) { + 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); @@ -32,7 +35,7 @@ export default class StaticHandler extends DefaultHandler { 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"; } @@ -43,9 +46,31 @@ export default class StaticHandler extends DefaultHandler { return "51 Not Found\r\n"; } - //TODO: import mmmagic + 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 - const data = fs.readFileSync(toServe); - return "20 text/gemini\r\n" + data; + // 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 ""; } } diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..40269b8 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,275 @@ +{ + "name": "astronomical-theater", + "version": "v2.0.5", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "astronomical-theater", + "version": "v2.0.5", + "license": "CC0-1.0", + "optionalDependencies": { + "file-type": "^16.5.3" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "optional": true + }, + "node_modules/file-type": { + "version": "16.5.3", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.3.tgz", + "integrity": "sha512-uVsl7iFhHSOY4bEONLlTK47iAHtNsFHWP5YE4xJfZ4rnX7S1Q3wce09XgqSC7E/xh8Ncv/be1lNoyprlUH/x6A==", + "optional": true, + "dependencies": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "optional": true + }, + "node_modules/peek-readable": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.0.1.tgz", + "integrity": "sha512-7qmhptnR0WMSpxT5rMHG9bW/mYSR1uqaPFj2MHvT+y/aOUu6msJijpKt5SkTDKySwg65OWG2JwTMBlgcbwMHrQ==", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readable-web-to-node-stream": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", + "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", + "optional": true, + "dependencies": { + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strtok3": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.2.4.tgz", + "integrity": "sha512-GO8IcFF9GmFDvqduIspUBwCzCbqzegyVKIsSymcMgiZKeCfrN9SowtUoi8+b59WZMAjIzVZic/Ft97+pynR3Iw==", + "optional": true, + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/token-types": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.1.1.tgz", + "integrity": "sha512-hD+QyuUAyI2spzsI0B7gf/jJ2ggR4RjkAo37j3StuePhApJUwcWDjnHDOFdIWYSwNR28H14hpwm4EI+V1Ted1w==", + "optional": true, + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "optional": true + } + }, + "dependencies": { + "@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "optional": true + }, + "file-type": { + "version": "16.5.3", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.3.tgz", + "integrity": "sha512-uVsl7iFhHSOY4bEONLlTK47iAHtNsFHWP5YE4xJfZ4rnX7S1Q3wce09XgqSC7E/xh8Ncv/be1lNoyprlUH/x6A==", + "optional": true, + "requires": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "optional": true + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "optional": true + }, + "peek-readable": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.0.1.tgz", + "integrity": "sha512-7qmhptnR0WMSpxT5rMHG9bW/mYSR1uqaPFj2MHvT+y/aOUu6msJijpKt5SkTDKySwg65OWG2JwTMBlgcbwMHrQ==", + "optional": true + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "optional": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readable-web-to-node-stream": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", + "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", + "optional": true, + "requires": { + "readable-stream": "^3.6.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "optional": true + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "optional": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "strtok3": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.2.4.tgz", + "integrity": "sha512-GO8IcFF9GmFDvqduIspUBwCzCbqzegyVKIsSymcMgiZKeCfrN9SowtUoi8+b59WZMAjIzVZic/Ft97+pynR3Iw==", + "optional": true, + "requires": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.0.1" + } + }, + "token-types": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.1.1.tgz", + "integrity": "sha512-hD+QyuUAyI2spzsI0B7gf/jJ2ggR4RjkAo37j3StuePhApJUwcWDjnHDOFdIWYSwNR28H14hpwm4EI+V1Ted1w==", + "optional": true, + "requires": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "optional": true + } + } +} diff --git a/package.json b/package.json index 6e7f324..220761d 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,14 @@ { "name": "astronomical-theater", - "version": "v2.0.5", + "version": "v2.1.0", "author": "Matthias", "license": "CC0-1.0", "type": "module", "main": "main.js", "scripts": { "start": "node main.js" + }, + "optionalDependencies": { + "file-type": "^16.5.3" } } diff --git a/static/small.png b/static/small.png new file mode 100644 index 0000000..74b64a1 Binary files /dev/null and b/static/small.png differ