Initial commit.

This commit is contained in:
Adrian Victor 2024-11-16 13:55:09 -03:00
commit acc92a6d9a
13 changed files with 1332 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
node_modules
package-lock.json
certificate.crt
private.key

14
config.json Normal file
View file

@ -0,0 +1,14 @@
{
"serverRoot" : "./www",
"logErrors" : true,
"useHTTPS" : true,
"httpsKey" : "./private.key",
"httpsCert" : "./certificate.crt",
"directoryListing" : true,
"defaultPage" : "/index.html",
"useDefaultPage" : true,
"listingTitle" : "Zephyrus Listing",
"listingFooter" : "Powered by <a href=\"https://git.disroot.org/adrianvictor/zephyrus\">Zephyrus</a>",
"listingCSS" : true,
"defaltToMime" : "application/octet-stream"
}

21
custom.css Normal file
View file

@ -0,0 +1,21 @@
body {
background-color: black;
color: white;
}
a {
color: gray;
text-decoration: none;
}
a:hover {
color: orange;
}
#title {
color: orangered;
}
#previousDirectoryLink {
color: red;
}

11
license Normal file
View file

@ -0,0 +1,11 @@
Copyright 2024 Adrian Victor de Abreu Alves
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

129
main.ts Normal file
View file

@ -0,0 +1,129 @@
const config = require('./config.json')
import fs, { stat } from 'fs';
import http, { IncomingMessage, ServerResponse } from 'http';
import https from 'https';
import url from 'url';
import path, { dirname } from 'path';
import mime from 'mime';
let server: http.Server | https.Server;
if(config.useHTTPS) {
const httpsArgs = {
key: fs.readFileSync(config.httpsKey),
cert: fs.readFileSync(config.httpsCert)
}
server = https.createServer(httpsArgs, requestHandler)
} else {
server = http.createServer(requestHandler)
}
function requestHandler(request: IncomingMessage, response: ServerResponse) {
const parsed = url.parse(request.url || '/', true);
const path_ = decodeURI(parsed.pathname || '/');
const serversidePath = path.join(config.serverRoot + path_);
const defaultPagePath = path.join(config.serverRoot + config.defaultPage);
const finalPath = config.useDefaultPage && request.url == '/' ? path.normalize(defaultPagePath) : serversidePath;
function showError(code: number, log: boolean = config.logErrors, info: string = 'no more information about the error was provided.') {
if (log) {
console.log(`Error ${code} - ${info}`)
}
if (!response.headersSent) {
switch (code) {
case 500:
response.writeHead(500, {"content-type" : 'text/plain'});
response.end("500 - internal error");
break;
case 404:
response.writeHead(404, {"content-type" : 'text/plain'});
response.end("404 - not found");
break;
default:
response.writeHead(code, {"content-type" : 'text/plain'});
response.end(`Error ${code}`)
break;
}
}
}
function returnDirectory(directory: string) {
try {
const dir = fs.readdirSync(directory);
dir.forEach(file => {
const filePath = `${directory}/${file}`
const stats = fs.statSync(filePath);
const response_ = `<p id="linkParagraph"><a id="filedirLink" href="http://${request.headers.host}${request.url}${file}${stats.isDirectory() ? '/' : ''}">${file}</a></p>`;
try {
response.write(response_);
} catch (err) {
showError(500, undefined, `while reading ${filePath}`)
return;
}
});
response.end(`<p id="endMessage">end of directory listing</p><footer>${config.listingFooter}</footer></body>`);
} catch (err) {
showError(500, undefined, `${err}`);
return;
}
}
// console.log(`Requested ${path_}, accessing ${config.useDefaultPage && request.url == '/' ? defaultPagePath : serversidePath}`)
if (!finalPath.startsWith(path.normalize(config.serverRoot))) {
showError(403, undefined, `someone is trying to access files (${finalPath}) outside server root (${config.serverRoot})`)
return;
}
fs.stat(finalPath, (err, stats) => {
if (err) {
showError(404, undefined, `while reading ${finalPath}`);
return;
}
if (stats.isDirectory() && config.directoryListing) {
response.writeHead(200, {"content-type" : 'text/html'});
var css;
const goBackLink = path.dirname(request.url || '')
if(config.listingCSS) {
try {
const css_ = fs.readFileSync('./custom.css');
css = css_;
} catch {
console.error(`You have CSS enabled, but we're having trouble to read it. You should either disable it in the server config or ensure it exists and is accessible`);
}
}
response.write(`
<!DOCTYPE html>
<head>
<title>${config.listingTitle}</title>
<style>${config.listingCSS ? css : ''}</style>
</head>
<body>
<h1 id="title">listing of ${request.url}</h1>
<p id="previousDirectoryParagraph"><a id="previousDirectoryLink" href="http://${request.headers.host}${goBackLink}">go back</a></p>
`)
try {
returnDirectory(finalPath);
return;
} catch (err) {
showError(500);
return;
}
} else if (stats.isFile()) {
const extension = path.extname(finalPath)
const mimeType = mime.getType(extension) || config.defaultToMime;
response.writeHead(200, {"content-type" : mimeType});
fs.readFile(finalPath, (err, data) => {
if (err) {
showError(500);
return;
}
response.end(data);
})
return;
}
showError(404, undefined, `while reading ${finalPath}`);
})
}
server.listen(3000, () => {
console.log("Started at https://localhost:3000")
})

18
package.json Normal file
View file

@ -0,0 +1,18 @@
{
"name" : "zephyrus-webserver",
"description" : "A simple webserver that supports directory listing.",
"author" : "Adrian Victor de Abreu Alves <adrianvictor@disroot.org>",
"version" : "1.0.0",
"private" : false,
"scripts": {
"start": "ts-node main.ts"
},
"devDependencies": {
"@types/node": "^22.9.0",
"ts-node": "^10.9.2",
"typescript": "^5.6.3"
},
"dependencies": {
"mime": "^4.0.4"
}
}

13
tsconfig.json Normal file
View file

@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["**/*.ts"],
"exclude": ["node_modules"]
}

BIN
www/background.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

104
www/index.html Normal file
View file

@ -0,0 +1,104 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
background-color: black;
color: white;
display: flex;
justify-content: center;
align-items:center;
margin: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
height: 100vh;
}
a {
color: orangered;
text-decoration: none;
}
a:hover {
color: red;
}
footer {
border-top: 1px solid white;
}
header {
border-bottom: 1px solid white;
}
#everythingHelper::before {
content: "";
background-image: url(background.jpg);
position: absolute;
background-size: cover;
top: 0;
left: 0;
bottom: 0;
right: 0;
z-index: -1;
opacity: .25;
}
#everythingHelper {
max-width: 50vw;
}
#titleLinks {
padding: 0;
}
#titleLinks * {
margin-right: 10px;
}
@media screen and (max-width: 800px) {
#everythingHelper {
max-width: 80vw;
}
}
@media screen and (max-width: 280px) {
body {
height: auto;
}
#title {
text-align: center;
}
#titleLinks {
text-align: center;
}
#everythingHelper {
max-width: 90vw;
}
}
</style>
<title>Adrian Victor</title>
</head>
<body>
<div id="everythingHelper">
<header>
<h1 id="title">Zephyrus</h1>
<ul id="titleLinks"><a href="https://git.disroot.org/adrianvictor/zephyrus">source-code</a><a href="license.txt">license (3-Clause BSD License)</a></ul>
</header>
<main>
<h2>It's running!</h2>
<p>If you're seeing this when accessing your server's root, default page is enabled.</p>
<p>Drop your files at ./www (It's the default root, but you can change in the config)</p>
<p>Directory listing is enabled by default, you may want to change it.</p>
<p>Configure your server in config.json.</p>
</main>
<footer>
<p><a href="https://git.disroot.org/adrianvictor/">Adrian Victor.</a></p>
</footer>
</div>
</body>
</html>

1
www/index.txt Normal file
View file

@ -0,0 +1 @@
This is a demo file that comes with Zephyrus

11
www/license.txt Normal file
View file

@ -0,0 +1,11 @@
Copyright 2024 Adrian Victor de Abreu Alves
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

1000
www/lorem.txt Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,6 @@
Salted Banana recipe
Required ingredients:
- Random string
- Banana
Mix the random string and the banana together.
Remember, it's more secure when salted!