1
0
Fork 0
forked from svrjs/svrjs

Compare commits

..

1 commit

Author SHA1 Message Date
1285fb6bce Rewritten SVR.JS from scratch 2024-04-01 08:02:21 +02:00
146 changed files with 10123 additions and 22884 deletions

View file

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

View file

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

View file

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View file

Before

Width:  |  Height:  |  Size: 8 KiB

After

Width:  |  Height:  |  Size: 8 KiB

View file

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View file

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View file

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View file

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View file

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View file

Before

Width:  |  Height:  |  Size: 7 KiB

After

Width:  |  Height:  |  Size: 7 KiB

View file

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View file

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View file

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View file

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View file

@ -1,19 +0,0 @@
# Sync repo to the Codeberg mirror
name: Repo sync GitHub -> SVR.JS Git server
on:
push:
branches:
- '**'
jobs:
svrjsgit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: spyoungtech/mirror-action@v0.5.1
with:
REMOTE: "https://git.svrjs.org/svrjs/svrjs.git"
GIT_USERNAME: github-mirror
GIT_PASSWORD: ${{ secrets.GIT_PASSWORD }}

27
.gitignore vendored
View file

@ -1,25 +1,2 @@
# Build output temp
/dist/ log
/out/
# Temporary files used by build script
/generatedAssets/
# Dependencies
node_modules/
# Test coverage
/coverage/
# ESLint cache
.eslintcache
# OS-specific files
.DS_Store
Thumbs.db
.Spotlight-V100
.Trashes
# Temporary files used by the editor
*.swp
*.swo

View file

@ -1,2 +0,0 @@
#!/bin/sh
npx --no -- commitlint --edit "$1"

View file

@ -1,2 +0,0 @@
#!/bin/sh
npx lint-staged

21
LICENSE
View file

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2018-2024 SVR.JS
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

149
README.md
View file

@ -1,149 +0,0 @@
<p align="center">
<a href="https://svrjs.org" target="_blank">
<img src="assets/logo.png" width="384">
</a>
</p>
<p align="center">
<b>SVR.JS</b> - a web server running on Node.JS<br/>
It's free as in freedom, scalable, secure, and configurable.
</p>
<p align="center">
<a href="https://svrjs.org/docs" target="_blank"><img alt="Static Badge" src="https://img.shields.io/badge/Documentation-green"></a>
<a href="https://svrjs.org" target="_blank"><img alt="Website" src="https://img.shields.io/website?url=https%3A%2F%2Fsvrjs.org"></a>
<a href="https://hub.docker.com/r/svrjs/svrjs" target="_blank"><img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/svrjs/svrjs"></a>
<a href="https://github.com/svr-js/svrjs" target="_blank"><img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/svr-js/svrjs"></a>
<a href="https://x.com/SVR_JS" target="_blank"><img alt="X (formerly Twitter) Follow" src="https://img.shields.io/twitter/follow/SVR_JS"></a>
<a href="https://mastodon.social/@svrjs" target="_blank"><img alt="Mastodon Follow" src="https://img.shields.io/mastodon/follow/111643338718098121"></a>
</p>
* * *
## Features
### Static file handling
* Static file serving (even above 2GB)
* Directory listing serving
* Protection against path traversal
* Content-Range support (for non-HTML static files; also for HTML files from SVR.JS 3.15.1)
* Serving from web root different than SVR.JS installation directory
### Security
* HTTPS support
* HTTP/2 support
* Built-in block list
* Protection against HTTP authentication brute force attacks (from SVR.JS 3.4.8; enabled by default)
* Ability to hide server version
* OCSP stapling support (from SVR.JS 3.4.9)
### Configuration and customization
* Configurability via _config.json_ file
* Expandability via server-side JavaScript and mods
* Ability to serve non-standard error pages
* URL rewriting engine
* Event driven architecture powered by Node.JS, along with clustering.
### Compression and content delivery
* Brotli, gzip and Deflate HTTP compression (Brotli supported since SVR.JS 3.4.11)
* SNI (Server Name Indication) support
* ETag support (from SVR.JS 3.6.1)
* Reverse proxy functionality (requires reverse-proxy-mod SVR.JS mod)
* Forward proxy functionality (requires forward-proxy-mod SVR.JS mod)
### Authentication and access control
* HTTP basic authentication
### Gateway interfaces
* CGI (Common Gateway Interface) support (requires RedBrick mod)
* SCGI (Simple Common Gateway Interface) support (requires OrangeCircle mod)
* JSGI (JavaScript Gateway Interface) support (requires YellowSquare mod)
* PHP support (PHP-CGI with RedBrick mod or PHP-FPM with GreenRhombus mod)
### Additional functionality
* Logging
* Ability to display IP addresses, from which originally request was made (from reverse proxies; via X-Forwarded-For)
## Building SVR.JS
To build SVR.JS, you need Node.JS 18.0.0 or newer.
Before building SVR.JS, install the npm packages using this command:
```bash
npm install
```
After installing the packages, build SVR.JS with this command:
```bash
npm run build
```
After running the command, you will get bundled SVR.JS script, around with built-in utilities and assets in the `dist` directory. You will also get a zip archive in `out` directory, that can be installed using SVR.JS installer
## Installation (built from source)
To install SVR.JS you just built from the source code, you can install it via SVR.JS installer for GNU/Linux or manually.
If you want to install SVR.JS manually, you can read the [server documentation](https://svrjs.org/docs).
If you want to install via SVR.JS installer for GNU/Linux, run this command:
```bash
curl -fsSL https://downloads.svrjs.org/installer/svr.js.installer.linux.20240509.sh > /tmp/installer.sh && sudo bash /tmp/installer.sh
```
You will be then prompted about the type of installation. Choose option “2” to install SVR.JS from the zip archive, and type in the path to the zip archive (hint: it is in the `out` directory).
After typing the path, you may be prompted to install dependencies via GNU/Linux distributions package manager. Proceed with the installation of dependencies.
After installation, SVR.JS should be listening at http://localhost.
## SVR.JS documentation
You can read the [SVR.JS documentation](https://svrjs.org/docs) to get information on how to use SVR.JS.
## npm scripts
- To build SVR.JS along with the zip archive, run `npm run build`.
- To check SVR.JS code for errors with ESLint, run `npm run lint`.
- To fix and beautify SVR.JS code with ESLint and Prettier, run `npm run lint:fix`.
- To run SVR.JS from the "dist" folder, run `npm start`.
- To test SVR.JS itself, run `npm run dev`. This removes existing configuration.
- To perform unit tests with Jest, run `npm test`.
## File structure
The file structure for SVR.JS source code looks like this:
- .husky - Git hooks
- assets - files to copy into dist folder and to the archive
- dist - contains SVR.JS, assets, and SVR.JS utiltiies
- generatedAssets - assets generated by the build script
- out - contains SVR.JS zip archive
- src - contains SVR.JS source code
- index.js - entry point
- extraScripts - SVR.JS extra scripts (each script has a single file)
- handlers - handlers for servers
- middleware - built-in middleware for servers
- res - resources
- utils - utility functions
- templates - EJS templates for build script to use
- tests - Jest unit tests
- middleware - tests for middleware
- utils - unit tests for utility functions
- commitlint.config.js - commitlint configuration
- esbuild.config.js - the build script
- eslint.config.js - ESLint configuration
- jest.config.js - Jest configuration
- lint-staged.config.js - lint-staged configuration
- prettier.config.js - Prettier configuration
- svrjs.json - SVR.JS version, name, documentation URL, and statistics server collection endpoint URL
## Contribute
See [SVR.JS contribution page](https://svrjs.org/contribute) for details.
## License
This project is licensed under the MIT/X11 License - see the [LICENSE](LICENSE) file for details.

View file

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2018-2024 SVR.JS
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,64 +0,0 @@
{
"users": [],
"port": 80,
"pubport": 80,
"page404": "404.html",
"timestamp": 1709477722479,
"blacklist": [],
"nonStandardCodes": [],
"enableCompression": true,
"customHeaders": {},
"enableHTTP2": false,
"enableLogging": true,
"enableDirectoryListing": true,
"enableDirectoryListingWithDefaultHead": false,
"serverAdministratorEmail": "[no contact information]",
"stackHidden": false,
"enableRemoteLogBrowsing": false,
"exposeServerVersion": true,
"disableServerSideScriptExpose": true,
"rewriteMap": [
{
"definingRegex": "/^\\/serverSideScript\\.js(?:$|[#?])/",
"replacements": [
{
"regex": "/^\\/serverSideScript\\.js($|[#?])/",
"replacement": "/NONEXISTENT_PAGE$1"
}
]
},
{
"definingRegex": "/^\\/testdir_rewritten(?:$|[\\/?#])/",
"replacements": [
{
"regex": "/^\\/testdir_rewritten($|[\\/?#])/",
"replacement": "/testdir$1"
}
]
}
],
"allowStatus": true,
"dontCompress": [
"/.*\\.ipxe$/",
"/.*\\.(?:jpe?g|png|bmp|tiff|jfif|gif|webp)$/",
"/.*\\.(?:[id]mg|iso|flp)$/",
"/.*\\.(?:zip|rar|bz2|[gb7x]z|lzma|tar)$/",
"/.*\\.(?:mp[34]|mov|wm[av]|avi|webm|og[gv]|mk[va])$/"
],
"enableIPSpoofing": false,
"secure": false,
"sni": {},
"disableNonEncryptedServer": false,
"disableToHTTPSRedirect": false,
"enableETag": true,
"disableUnusedWorkerTermination": false,
"rewriteDirtyURLs": true,
"errorPages": [],
"useWebRootServerSideScript": true,
"exposeModsInErrorPages": true,
"disableTrailingSlashRedirects": false,
"environmentVariables": {},
"allowDoubleSlashes": false,
"optOutOfStatisticsServer": false,
"disableConfigurationSaving": false
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View file

@ -1,98 +0,0 @@
//Server-side Javascript (Node.js)
//This implementation uses Node.js, which powers SVR.JS.
//This implementation contains elements specific for SVR.JS mods:
// req - A server request instance
// res - A server response instance
// serverconsole - A console output object for SVR.JS
// responseEnd - Response ending method of SVR.JS
// href - Request URL without query
// ext - File extension of requested file
// uobject - Request URL object
// search - Request URL queries
// defaultPage - An index page location (deprecated, always returns 'index.html')
// users - A list of users (deprecated)
// page404 - 404 Not Found page location
// head - A head of server response
// foot - A foot of server response
// fd - Currently unused
// elseCallback - Method summoning SVR.JS internal callbacks
// callServerError - Method to end with server error
// getCustomHeaders - Method to get headers defined in config.json file
// origHref - Original request URL without query (before URL rewriting)
// redirect - Method to redirect.
// parsePostData - Method to parse POST data.
// authUser - Authenticated HTTP user.
//Along with elements added by this implementation:
// disableEndElseCallbackExecute - Determines execution of elseCallback on end
// filterHeaders - Removes invalid HTTP/1.0 headers
// customvar1, customvar2, customvar3, customvar4 - Custom variables
//Built-in libraries:
// http
// https
// readline
// os
// url
// hexstrbase64
// fs
// path
// crypto
// stream
//If you send response remember and don't use disableEndElseCallbackExecute, use "return;", or else SVR.JS will crash.
//If you use proxy, use filterHeaders to remove HTTP/2.0 headers, which are invalid in HTTP/1.0.
//If you type no code, elseCallback is executed.
//Below we have example script, which serves dynamic content.
disableEndElseCallbackExecute = true; //Avoid crashing on async.
var headers = getCustomHeaders(); //Headers
if(!fs.existsSync(__dirname + "/../temp/requestCounter")) {
fs.writeFileSync(__dirname + "/../temp/requestCounter","0"); //Reset counter
}
headers["Content-Type"] = 'text/html; charset=utf-8' //HTML output
if(href == "/hello.svr") {
fs.readFile(__dirname + "/../temp/requestCounter", (err,data) => {
if(err) throw err;
var requestCounter = parseInt(data.toString()); //Counter
fs.writeFile(__dirname + "/../temp/requestCounter",(requestCounter + 1).toString(),() => {
//Increase value of counter
});
res.writeHead(200, "OK", headers); //Write Head
res.end("<html><head><title>SVR.JS ServerSide Test</title></head><body><h1>Hello World!</h1><p>This is a test from server-side JavaScript. This test is executed " + requestCounter.toString() + " times from taking server up." + (req.headers.origin == undefined ? "" : " This request is done from a proxy.") + "</p><p><i>SVR.JS/" + configJSON.version + ' (' + os.platform()[0].toUpperCase() + os.platform().slice(1) + ')' + (req.headers.host == undefined ? "" : " on " + req.headers.host) + "</p></body></html>"); //Write response
serverconsole.resmessage("Client successfully recieved content."); //Log into SVR.JS
return; //Prevent SVR.JS from crashing
});
} else if(href == "/proxy.svr") {
callServerError(403,"SVR.JS-exampleproxy"); //Server error
serverconsole.errmessage("Client fails to recieve content."); //Log into SVR.JS
} else if(href.indexOf("/proxy.svr/") == 0) {
var hn = href.split("/")[2]; //Hostname
if(hn != "this" && !(req.socket.realRemoteAddress ? req.socket.realRemoteAddress : req.socket.remoteAddress).match(/^(?:localhost$|::1$|f[c-d][0-9a-f]{2}:|(?:::ffff:)?(?:(?:127|10)\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}|192\.168\.[0-9]{1,3}\.[0-9]{1,3}|172\.(?:1[6-9]|2[0-9]|3[0-1])\.[0-9]{1,3}\.[0-9]{1,3})$)/i) ) {
//Prevent open proxy
callServerError(403,"SVR.JS-exampleproxy"); //Server error
serverconsole.errmessage("Client fails to recieve content."); //Log into SVR.JS
return;
}
var hdrs = req.headers;
hdrs["Host"] = (hn == "this" ? req.headers.host : hn);
hdrs["Origin"] = (req.headers.host == undefined ? "" : req.headers.host);
var options = {
hostname: (hn == "this" ? req.headers.host.split(":")[0] : hn.split(":")[0]),
port: (hn == "this" ? req.headers.host.split(":")[1] : (hn.split(":")[1] == undefined ? 80 : hn.split(":")[1])),
path: req.url.replace("/proxy.svr/" + hn,""),
method: req.method,
headers: filterHeaders(hdrs)
};
var proxy = http.request(options, function (sres) {
res.writeHead(sres.statusCode, sres.headers)
sres.pipe(res, {
end: true
});
});
proxy.on("error",(ex) => {
callServerError(500,"SVR.JS-exampleproxy",ex.stack); //Server error
serverconsole.errmessage("Client fails to recieve content."); //Log into SVR.JS
});
req.pipe(proxy, {
end: true
});
} else {
elseCallback(); //Load SVR.JS internal callbacks
}

View file

@ -1,6 +0,0 @@
</div>
<div class="footer">
Copyright &copy; 2020-2024 SVR.JS
</div>
</body>
</html>

View file

@ -1,39 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
margin: 0;
padding: 0;
background-color: ivory;
font-family: Ubuntu,Arial,Helvetica,sans-serif;
}
.header {
background-color: orange;
text-align: center;
}
.navbar {
background-color: black;
padding: 10px
}
.navbar a {
color: white;
text-decoration: none;
margin: 5px
}
.navbar a:hover {
color: #aaa;
}
.footer {
padding: 20px;
background-color: lightsalmon;
text-align: center;
}
</style>
</head>
<body><div class="header"><h1 style="margin: 0; padding: 10px;">My Website</h1></div>
<div class="navbar"><a href="/">Home</a> <a href="/licenses">Licenses</a></div>
<div style="margin: 5px;">
<h1>Personalized directory listing</h1>
<p>This is a test of personalized directory listing.</p>

View file

@ -1,6 +0,0 @@
</div>
<div class="footer">
Copyright &copy; 2020-2024 SVR.JS
</div>
</body>
</html>

View file

@ -1,39 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
margin: 0;
padding: 0;
background-color: ivory;
font-family: Ubuntu,Arial,Helvetica,sans-serif;
}
.header {
background-color: orange;
text-align: center;
}
.navbar {
background-color: black;
padding: 10px
}
.navbar a {
color: white;
text-decoration: none;
margin: 5px
}
.navbar a:hover {
color: #aaa;
}
.footer {
padding: 20px;
background-color: lightsalmon;
text-align: center;
}
</style>
</head>
<body><div class="header"><h1 style="margin: 0; padding: 10px;">My Website</h1></div>
<div class="navbar"><a href="/">Home</a> <a href="/licenses">Licenses</a></div>
<div style="margin: 5px;">
<h1>Personalized directory listing</h1>
<p>This is a test of personalized directory listing.</p>

View file

View file

@ -1,3 +0,0 @@
module.exports = {
extends: ["@commitlint/config-conventional"]
};

4
config.json Normal file
View file

@ -0,0 +1,4 @@
{
"port": 80,
"exposeServerVersion": true
}

View file

@ -1,365 +0,0 @@
const esbuild = require("esbuild");
const esbuildCopyPlugin = require("esbuild-plugin-copy");
const fs = require("fs");
const zlib = require("zlib");
const ejs = require("ejs");
const archiver = require("archiver");
const chokidar = require("chokidar");
const svrjsInfo = JSON.parse(fs.readFileSync(__dirname + "/svrjs.json"));
const { version } = svrjsInfo;
const isDev = process.env.NODE_ENV == "development";
// Create the dist directory if it doesn't exist
if (!fs.existsSync(__dirname + "/dist")) fs.mkdirSync(__dirname + "/dist");
if (!fs.existsSync(__dirname + "/dist/log"))
fs.mkdirSync(__dirname + "/dist/log");
if (!fs.existsSync(__dirname + "/dist/mods"))
fs.mkdirSync(__dirname + "/dist/mods");
if (!fs.existsSync(__dirname + "/dist/temp"))
fs.mkdirSync(__dirname + "/dist/temp");
// Create the out directory if it doesn't exist and if not building for development
if (!isDev && !fs.existsSync(__dirname + "/out")) fs.mkdirSync(__dirname + "/out");
function generateAssets() {
// Variables from "svrjs.json" file
const svrjsInfo = JSON.parse(fs.readFileSync(__dirname + "/svrjs.json"));
const { name, version, documentationURL, changes } = svrjsInfo;
// Dependency-related variables
const dependencies =
JSON.parse(fs.readFileSync(__dirname + "/package.json")).dependencies || {};
const requiredDependencyList = Object.keys(dependencies);
let dependencyList = Object.keys(dependencies);
// Function to find and add all dependencies into the dependencyList array.
const findAllDependencies = (curList) => {
// If no curList parameter is specified, use dependencyList.
if (!curList) curList = dependencyList;
curList.forEach((dependency) => {
const newDeplist = Object.keys(
JSON.parse(
fs
.readFileSync(
__dirname +
"/node_modules/" +
dependency.replace(/\/\.\./g, "") +
"/package.json"
)
.toString()
).dependencies || {}
);
let noDupNewDepList = [];
newDeplist.forEach((dep) => {
// Ignore duplicates
if (dependencyList.indexOf(dep) == -1) {
noDupNewDepList.push(dep);
dependencyList.push(dep);
}
});
// Call findAllDependencies for the dependency list.
findAllDependencies(noDupNewDepList);
});
};
// Get list of all dependencies
findAllDependencies();
dependencyList = dependencyList.sort();
// Create and populate an object, where whenever the dependencies are required are listed.
let dependenciesAreRequired = {};
dependencyList.forEach((dependency) => {
dependenciesAreRequired[dependency] = false;
});
requiredDependencyList.forEach((dependency) => {
dependenciesAreRequired[dependency] = true;
});
// Create the template functions using EJS
const layoutTemplate = ejs.compile(
fs.readFileSync(__dirname + "/templates/layout.ejs").toString()
);
const testsTemplate = ejs.compile(
fs.readFileSync(__dirname + "/templates/tests.ejs").toString()
);
const indexTemplate = ejs.compile(
fs.readFileSync(__dirname + "/templates/index.ejs").toString()
);
const licensesTemplate = ejs.compile(
fs.readFileSync(__dirname + "/templates/licenses.ejs").toString()
);
const licenseElementTemplate = ejs.compile(
fs.readFileSync(__dirname + "/templates/licenseElement.ejs").toString()
);
let licenseElements = "";
// Generate the licenses list in HTML
dependencyList.forEach((dependency) => {
const packageJSON = JSON.parse(
fs
.readFileSync(
__dirname +
"/node_modules/" +
dependency.replace(/\/\.\./g, "") +
"/package.json"
)
.toString()
);
licenseElements += licenseElementTemplate({
moduleName: packageJSON.name,
name: name,
license: packageJSON.license,
description: packageJSON.description || "No description",
author: packageJSON.author ? packageJSON.author.name : packageJSON.author,
required: dependenciesAreRequired[dependency]
});
});
// Generate pages
const licensesPage = layoutTemplate({
title: name + " " + version + " Licenses",
content: licensesTemplate({
name: name,
version: version,
licenses: licenseElements
})
});
const testsPage = layoutTemplate({
title: name + " " + version + " Tests",
content: testsTemplate({
name: name,
version: version
})
});
const indexPage = layoutTemplate({
title: name + " " + version,
content: indexTemplate({
name: name,
version: version,
documentationURL: documentationURL,
changes: changes
})
});
// Create the generated assets directory if it doesn't exist
if (!fs.existsSync(__dirname + "/generatedAssets"))
fs.mkdirSync(__dirname + "/generatedAssets");
// Create a licenses directory
if (!fs.existsSync(__dirname + "/generatedAssets/licenses"))
fs.mkdirSync(__dirname + "/generatedAssets/licenses");
// Write to HTML files
fs.writeFileSync(__dirname + "/generatedAssets/index.html", indexPage);
fs.writeFileSync(__dirname + "/generatedAssets/tests.html", testsPage);
fs.writeFileSync(
__dirname + "/generatedAssets/licenses/index.html",
licensesPage
);
}
if (!isDev) {
// Generate assets
generateAssets();
} else {
// Generate assets with watching
const watcher = chokidar.watch([
__dirname + "/templates",
__dirname + "/package.json",
__dirname + "/svrjs.json"
]);
watcher.on("change", () => {
try {
generateAssets();
} catch (err) {
console.error("There is a problem when regenerating assets!");
console.error("Stack:");
console.error(err.stack);
}
}).on("ready", () => {
try {
generateAssets();
} catch (err) {
console.error("There is a problem when regenerating assets!");
console.error("Stack:");
console.error(err.stack);
}
});
}
if (!isDev) {
// Bundle the source and copy the assets using esbuild and esbuild-plugin-copy
esbuild
.build({
entryPoints: ["src/index.js"],
bundle: true,
outfile: "dist/svr.js",
platform: "node",
target: "es2017",
plugins: [
esbuildCopyPlugin.copy({
resolveFrom: __dirname,
assets: {
from: ["./assets/**/*"],
to: ["./dist"]
},
globbyOptions: {
dot: true
}
}),
esbuildCopyPlugin.copy({
resolveFrom: __dirname,
assets: {
from: ["./generatedAssets/**/*"],
to: ["./dist"]
}
})
]
})
.then(() => {
const utilFilesAndDirectories = fs.existsSync(
__dirname + "/src/extraScripts"
)
? fs.readdirSync(__dirname + "/src/extraScripts")
: [];
const utilFiles = [];
utilFilesAndDirectories.forEach((entry) => {
if (fs.statSync(__dirname + "/src/extraScripts/" + entry).isFile())
utilFiles.push(entry);
});
// Transpile utilities using esbuild
esbuild
.build({
entryPoints: utilFiles.map(
(filename) => "src/extraScripts/" + filename
),
bundle: true,
outdir: "dist",
platform: "node",
target: "es2017"
})
.then(() => {
const archiveName =
"svr.js." +
version.toLowerCase().replace(/[^0-9a-z]+/g, ".") +
".zip";
const output = fs.createWriteStream(
__dirname + "/out/" + archiveName
);
const archive = archiver("zip", {
zlib: { level: 9 }
});
archive.pipe(output);
// Add everything in the "dist" directory except for "svr.js" and "svr.compressed"
archive.glob("**/*", {
cwd: __dirname + "/dist",
ignore: ["svr.js", "svr.compressed"],
dot: true
});
// Create a stream for the "svr.compressed" file
const compressedSVRJSFileStream = fs
.createReadStream(__dirname + "/dist/svr.js")
.pipe(
zlib.createGzip({
level: 9
})
);
archive.append(compressedSVRJSFileStream, { name: "svr.compressed" });
archive.append(
'const zlib = require("zlib");\nconst fs = require("fs");\nconsole.log("Deleting SVR.JS stub...");\nfs.unlinkSync("svr.js");\nconsole.log("Decompressing SVR.JS...");\nconst script = zlib.gunzipSync(fs.readFileSync("svr.compressed"));\nfs.unlinkSync("svr.compressed");\nfs.writeFileSync("svr.js",script);\nconsole.log("Restart SVR.JS to get server interface.");',
{ name: "svr.js" }
);
archive.finalize();
})
.catch((err) => {
throw err;
});
})
.catch((err) => {
throw err;
});
} else {
// Bundle the source and copy the assets using esbuild and esbuild-plugin-copy with watching
esbuild
.context({
entryPoints: ["src/index.js"],
bundle: true,
outfile: "dist/svr.js",
platform: "node",
target: "es2017",
plugins: [
esbuildCopyPlugin.copy({
resolveFrom: __dirname,
assets: {
from: ["./assets/**/*"],
to: ["./dist"]
},
globbyOptions: {
dot: true
},
watch: {}
}),
esbuildCopyPlugin.copy({
resolveFrom: __dirname,
assets: {
from: ["./generatedAssets/**/*"],
to: ["./dist"]
},
watch: {}
})
]
})
.then((ctx) => {
ctx
.watch()
.then(() => {
const utilFilesAndDirectories = fs.existsSync(
__dirname + "/src/extraScripts"
)
? fs.readdirSync(__dirname + "/src/extraScripts")
: [];
const utilFiles = [];
utilFilesAndDirectories.forEach((entry) => {
if (fs.statSync(__dirname + "/src/extraScripts/" + entry).isFile())
utilFiles.push(entry);
});
// Transpile utilities using esbuild
esbuild
.context({
entryPoints: utilFiles.map(
(filename) => "src/extraScripts/" + filename
),
bundle: true,
outdir: "dist",
platform: "node",
target: "es2017"
})
.then((ctx) => {
ctx
.watch()
.then(() => {
console.log("Watching for changes in SVR.JS source code...");
})
.catch((err) => {
throw err;
});
})
.catch((err) => {
throw err;
});
})
.catch((err) => {
throw err;
});
})
.catch((err) => {
throw err;
});
}

View file

@ -1,30 +0,0 @@
const globals = require("globals");
const pluginJs = require("@eslint/js");
const eslintPluginPrettierRecommended = require("eslint-plugin-prettier/recommended");
const jest = require("eslint-plugin-jest");
module.exports = [
{
files: ["**/*.js"],
languageOptions: {
sourceType: "commonjs"
}
},
{
files: ["tests/*.test.js", "tests/**/*.test.js"],
...jest.configs['flat/recommended'],
rules: {
...jest.configs['flat/recommended'].rules,
'jest/prefer-expect-assertions': 'off',
}
},
{
languageOptions: {
globals: {
...globals.node
}
}
},
pluginJs.configs.recommended,
eslintPluginPrettierRecommended
];

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

36
index.html Normal file
View file

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<title>SVR.JS SimpleServe Nightly-20230401</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta charset="UTF-8" />
<style>
body {
font-family: FreeSans, Helvetica, Tahoma, Arial, sans-serif;
text-align: center;
}
</style>
</head>
<body>
<h1>Welcome to SVR.JS SimpleServe Nightly-20230401</h1>
<div style="background-color: #ffff00; border-color: #ff7f00; border-width: 5px; border-style: solid; padding: 5px; display: inline-block;">
<b style="font-size: 26px">WARNING!</b><br/>
This version is only for test purposes and may be unstable.
</div>
<br/>
<img src="/logo.png" style="width: 640px; max-width: 100%;" />
<br/>
<p>If you see this page that means that the server is working properly. You can further configure the server and replace <i>index.html</i> page with custom one.</p>
<p>Default <i>config.json</i> looks like this:</p>
<code style="background-color: #e0e0e0; padding: 5px; text-align: left; display: block; display: inline-block;">
<pre style="margin: 0.2em; white-space: pre-wrap; overflow-wrap: break-word; word-wrap: break-word; word-break: break-all; word-break: break-word">{
"port": 80,
"exposeServerVersion": true,
}</pre>
</code>
<p>Changes:</p>
<ul style="display: inline-block; margin: 0;">
<li><i>INSERT CHANGES THERE</i></li>
</ul>
</body>
</html>

View file

@ -1,5 +0,0 @@
module.exports = {
testEnvironment: 'node',
testMatch: ['**/tests/**/*.test.js'],
verbose: true,
};

View file

@ -1,5 +0,0 @@
module.exports = {
"tests/**/*.js": "eslint --cache --fix",
"src/**/*.js": "eslint --cache --fix",
"utils/**/*.js": "eslint --cache --fix"
};

BIN
logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

26
node_modules/.package-lock.json generated vendored Normal file
View file

@ -0,0 +1,26 @@
{
"name": "simpleserve",
"lockfileVersion": 2,
"requires": true,
"packages": {
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
}
}
}

507
node_modules/mime-db/HISTORY.md generated vendored Normal file
View file

@ -0,0 +1,507 @@
1.52.0 / 2022-02-21
===================
* Add extensions from IANA for more `image/*` types
* Add extension `.asc` to `application/pgp-keys`
* Add extensions to various XML types
* Add new upstream MIME types
1.51.0 / 2021-11-08
===================
* Add new upstream MIME types
* Mark `image/vnd.microsoft.icon` as compressible
* Mark `image/vnd.ms-dds` as compressible
1.50.0 / 2021-09-15
===================
* Add deprecated iWorks mime types and extensions
* Add new upstream MIME types
1.49.0 / 2021-07-26
===================
* Add extension `.trig` to `application/trig`
* Add new upstream MIME types
1.48.0 / 2021-05-30
===================
* Add extension `.mvt` to `application/vnd.mapbox-vector-tile`
* Add new upstream MIME types
* Mark `text/yaml` as compressible
1.47.0 / 2021-04-01
===================
* Add new upstream MIME types
* Remove ambigious extensions from IANA for `application/*+xml` types
* Update primary extension to `.es` for `application/ecmascript`
1.46.0 / 2021-02-13
===================
* Add extension `.amr` to `audio/amr`
* Add extension `.m4s` to `video/iso.segment`
* Add extension `.opus` to `audio/ogg`
* Add new upstream MIME types
1.45.0 / 2020-09-22
===================
* Add `application/ubjson` with extension `.ubj`
* Add `image/avif` with extension `.avif`
* Add `image/ktx2` with extension `.ktx2`
* Add extension `.dbf` to `application/vnd.dbf`
* Add extension `.rar` to `application/vnd.rar`
* Add extension `.td` to `application/urc-targetdesc+xml`
* Add new upstream MIME types
* Fix extension of `application/vnd.apple.keynote` to be `.key`
1.44.0 / 2020-04-22
===================
* Add charsets from IANA
* Add extension `.cjs` to `application/node`
* Add new upstream MIME types
1.43.0 / 2020-01-05
===================
* Add `application/x-keepass2` with extension `.kdbx`
* Add extension `.mxmf` to `audio/mobile-xmf`
* Add extensions from IANA for `application/*+xml` types
* Add new upstream MIME types
1.42.0 / 2019-09-25
===================
* Add `image/vnd.ms-dds` with extension `.dds`
* Add new upstream MIME types
* Remove compressible from `multipart/mixed`
1.41.0 / 2019-08-30
===================
* Add new upstream MIME types
* Add `application/toml` with extension `.toml`
* Mark `font/ttf` as compressible
1.40.0 / 2019-04-20
===================
* Add extensions from IANA for `model/*` types
* Add `text/mdx` with extension `.mdx`
1.39.0 / 2019-04-04
===================
* Add extensions `.siv` and `.sieve` to `application/sieve`
* Add new upstream MIME types
1.38.0 / 2019-02-04
===================
* Add extension `.nq` to `application/n-quads`
* Add extension `.nt` to `application/n-triples`
* Add new upstream MIME types
* Mark `text/less` as compressible
1.37.0 / 2018-10-19
===================
* Add extensions to HEIC image types
* Add new upstream MIME types
1.36.0 / 2018-08-20
===================
* Add Apple file extensions from IANA
* Add extensions from IANA for `image/*` types
* Add new upstream MIME types
1.35.0 / 2018-07-15
===================
* Add extension `.owl` to `application/rdf+xml`
* Add new upstream MIME types
- Removes extension `.woff` from `application/font-woff`
1.34.0 / 2018-06-03
===================
* Add extension `.csl` to `application/vnd.citationstyles.style+xml`
* Add extension `.es` to `application/ecmascript`
* Add new upstream MIME types
* Add `UTF-8` as default charset for `text/turtle`
* Mark all XML-derived types as compressible
1.33.0 / 2018-02-15
===================
* Add extensions from IANA for `message/*` types
* Add new upstream MIME types
* Fix some incorrect OOXML types
* Remove `application/font-woff2`
1.32.0 / 2017-11-29
===================
* Add new upstream MIME types
* Update `text/hjson` to registered `application/hjson`
* Add `text/shex` with extension `.shex`
1.31.0 / 2017-10-25
===================
* Add `application/raml+yaml` with extension `.raml`
* Add `application/wasm` with extension `.wasm`
* Add new `font` type from IANA
* Add new upstream font extensions
* Add new upstream MIME types
* Add extensions for JPEG-2000 images
1.30.0 / 2017-08-27
===================
* Add `application/vnd.ms-outlook`
* Add `application/x-arj`
* Add extension `.mjs` to `application/javascript`
* Add glTF types and extensions
* Add new upstream MIME types
* Add `text/x-org`
* Add VirtualBox MIME types
* Fix `source` records for `video/*` types that are IANA
* Update `font/opentype` to registered `font/otf`
1.29.0 / 2017-07-10
===================
* Add `application/fido.trusted-apps+json`
* Add extension `.wadl` to `application/vnd.sun.wadl+xml`
* Add new upstream MIME types
* Add `UTF-8` as default charset for `text/css`
1.28.0 / 2017-05-14
===================
* Add new upstream MIME types
* Add extension `.gz` to `application/gzip`
* Update extensions `.md` and `.markdown` to be `text/markdown`
1.27.0 / 2017-03-16
===================
* Add new upstream MIME types
* Add `image/apng` with extension `.apng`
1.26.0 / 2017-01-14
===================
* Add new upstream MIME types
* Add extension `.geojson` to `application/geo+json`
1.25.0 / 2016-11-11
===================
* Add new upstream MIME types
1.24.0 / 2016-09-18
===================
* Add `audio/mp3`
* Add new upstream MIME types
1.23.0 / 2016-05-01
===================
* Add new upstream MIME types
* Add extension `.3gpp` to `audio/3gpp`
1.22.0 / 2016-02-15
===================
* Add `text/slim`
* Add extension `.rng` to `application/xml`
* Add new upstream MIME types
* Fix extension of `application/dash+xml` to be `.mpd`
* Update primary extension to `.m4a` for `audio/mp4`
1.21.0 / 2016-01-06
===================
* Add Google document types
* Add new upstream MIME types
1.20.0 / 2015-11-10
===================
* Add `text/x-suse-ymp`
* Add new upstream MIME types
1.19.0 / 2015-09-17
===================
* Add `application/vnd.apple.pkpass`
* Add new upstream MIME types
1.18.0 / 2015-09-03
===================
* Add new upstream MIME types
1.17.0 / 2015-08-13
===================
* Add `application/x-msdos-program`
* Add `audio/g711-0`
* Add `image/vnd.mozilla.apng`
* Add extension `.exe` to `application/x-msdos-program`
1.16.0 / 2015-07-29
===================
* Add `application/vnd.uri-map`
1.15.0 / 2015-07-13
===================
* Add `application/x-httpd-php`
1.14.0 / 2015-06-25
===================
* Add `application/scim+json`
* Add `application/vnd.3gpp.ussd+xml`
* Add `application/vnd.biopax.rdf+xml`
* Add `text/x-processing`
1.13.0 / 2015-06-07
===================
* Add nginx as a source
* Add `application/x-cocoa`
* Add `application/x-java-archive-diff`
* Add `application/x-makeself`
* Add `application/x-perl`
* Add `application/x-pilot`
* Add `application/x-redhat-package-manager`
* Add `application/x-sea`
* Add `audio/x-m4a`
* Add `audio/x-realaudio`
* Add `image/x-jng`
* Add `text/mathml`
1.12.0 / 2015-06-05
===================
* Add `application/bdoc`
* Add `application/vnd.hyperdrive+json`
* Add `application/x-bdoc`
* Add extension `.rtf` to `text/rtf`
1.11.0 / 2015-05-31
===================
* Add `audio/wav`
* Add `audio/wave`
* Add extension `.litcoffee` to `text/coffeescript`
* Add extension `.sfd-hdstx` to `application/vnd.hydrostatix.sof-data`
* Add extension `.n-gage` to `application/vnd.nokia.n-gage.symbian.install`
1.10.0 / 2015-05-19
===================
* Add `application/vnd.balsamiq.bmpr`
* Add `application/vnd.microsoft.portable-executable`
* Add `application/x-ns-proxy-autoconfig`
1.9.1 / 2015-04-19
==================
* Remove `.json` extension from `application/manifest+json`
- This is causing bugs downstream
1.9.0 / 2015-04-19
==================
* Add `application/manifest+json`
* Add `application/vnd.micro+json`
* Add `image/vnd.zbrush.pcx`
* Add `image/x-ms-bmp`
1.8.0 / 2015-03-13
==================
* Add `application/vnd.citationstyles.style+xml`
* Add `application/vnd.fastcopy-disk-image`
* Add `application/vnd.gov.sk.xmldatacontainer+xml`
* Add extension `.jsonld` to `application/ld+json`
1.7.0 / 2015-02-08
==================
* Add `application/vnd.gerber`
* Add `application/vnd.msa-disk-image`
1.6.1 / 2015-02-05
==================
* Community extensions ownership transferred from `node-mime`
1.6.0 / 2015-01-29
==================
* Add `application/jose`
* Add `application/jose+json`
* Add `application/json-seq`
* Add `application/jwk+json`
* Add `application/jwk-set+json`
* Add `application/jwt`
* Add `application/rdap+json`
* Add `application/vnd.gov.sk.e-form+xml`
* Add `application/vnd.ims.imsccv1p3`
1.5.0 / 2014-12-30
==================
* Add `application/vnd.oracle.resource+json`
* Fix various invalid MIME type entries
- `application/mbox+xml`
- `application/oscp-response`
- `application/vwg-multiplexed`
- `audio/g721`
1.4.0 / 2014-12-21
==================
* Add `application/vnd.ims.imsccv1p2`
* Fix various invalid MIME type entries
- `application/vnd-acucobol`
- `application/vnd-curl`
- `application/vnd-dart`
- `application/vnd-dxr`
- `application/vnd-fdf`
- `application/vnd-mif`
- `application/vnd-sema`
- `application/vnd-wap-wmlc`
- `application/vnd.adobe.flash-movie`
- `application/vnd.dece-zip`
- `application/vnd.dvb_service`
- `application/vnd.micrografx-igx`
- `application/vnd.sealed-doc`
- `application/vnd.sealed-eml`
- `application/vnd.sealed-mht`
- `application/vnd.sealed-ppt`
- `application/vnd.sealed-tiff`
- `application/vnd.sealed-xls`
- `application/vnd.sealedmedia.softseal-html`
- `application/vnd.sealedmedia.softseal-pdf`
- `application/vnd.wap-slc`
- `application/vnd.wap-wbxml`
- `audio/vnd.sealedmedia.softseal-mpeg`
- `image/vnd-djvu`
- `image/vnd-svf`
- `image/vnd-wap-wbmp`
- `image/vnd.sealed-png`
- `image/vnd.sealedmedia.softseal-gif`
- `image/vnd.sealedmedia.softseal-jpg`
- `model/vnd-dwf`
- `model/vnd.parasolid.transmit-binary`
- `model/vnd.parasolid.transmit-text`
- `text/vnd-a`
- `text/vnd-curl`
- `text/vnd.wap-wml`
* Remove example template MIME types
- `application/example`
- `audio/example`
- `image/example`
- `message/example`
- `model/example`
- `multipart/example`
- `text/example`
- `video/example`
1.3.1 / 2014-12-16
==================
* Fix missing extensions
- `application/json5`
- `text/hjson`
1.3.0 / 2014-12-07
==================
* Add `application/a2l`
* Add `application/aml`
* Add `application/atfx`
* Add `application/atxml`
* Add `application/cdfx+xml`
* Add `application/dii`
* Add `application/json5`
* Add `application/lxf`
* Add `application/mf4`
* Add `application/vnd.apache.thrift.compact`
* Add `application/vnd.apache.thrift.json`
* Add `application/vnd.coffeescript`
* Add `application/vnd.enphase.envoy`
* Add `application/vnd.ims.imsccv1p1`
* Add `text/csv-schema`
* Add `text/hjson`
* Add `text/markdown`
* Add `text/yaml`
1.2.0 / 2014-11-09
==================
* Add `application/cea`
* Add `application/dit`
* Add `application/vnd.gov.sk.e-form+zip`
* Add `application/vnd.tmd.mediaflex.api+xml`
* Type `application/epub+zip` is now IANA-registered
1.1.2 / 2014-10-23
==================
* Rebuild database for `application/x-www-form-urlencoded` change
1.1.1 / 2014-10-20
==================
* Mark `application/x-www-form-urlencoded` as compressible.
1.1.0 / 2014-09-28
==================
* Add `application/font-woff2`
1.0.3 / 2014-09-25
==================
* Fix engine requirement in package
1.0.2 / 2014-09-25
==================
* Add `application/coap-group+json`
* Add `application/dcd`
* Add `application/vnd.apache.thrift.binary`
* Add `image/vnd.tencent.tap`
* Mark all JSON-derived types as compressible
* Update `text/vtt` data
1.0.1 / 2014-08-30
==================
* Fix extension ordering
1.0.0 / 2014-08-30
==================
* Add `application/atf`
* Add `application/merge-patch+json`
* Add `multipart/x-mixed-replace`
* Add `source: 'apache'` metadata
* Add `source: 'iana'` metadata
* Remove badly-assumed charset data

23
node_modules/mime-db/LICENSE generated vendored Normal file
View file

@ -0,0 +1,23 @@
(The MIT License)
Copyright (c) 2014 Jonathan Ong <me@jongleberry.com>
Copyright (c) 2015-2022 Douglas Christopher Wilson <doug@somethingdoug.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

100
node_modules/mime-db/README.md generated vendored Normal file
View file

@ -0,0 +1,100 @@
# mime-db
[![NPM Version][npm-version-image]][npm-url]
[![NPM Downloads][npm-downloads-image]][npm-url]
[![Node.js Version][node-image]][node-url]
[![Build Status][ci-image]][ci-url]
[![Coverage Status][coveralls-image]][coveralls-url]
This is a large database of mime types and information about them.
It consists of a single, public JSON file and does not include any logic,
allowing it to remain as un-opinionated as possible with an API.
It aggregates data from the following sources:
- http://www.iana.org/assignments/media-types/media-types.xhtml
- http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
- http://hg.nginx.org/nginx/raw-file/default/conf/mime.types
## Installation
```bash
npm install mime-db
```
### Database Download
If you're crazy enough to use this in the browser, you can just grab the
JSON file using [jsDelivr](https://www.jsdelivr.com/). It is recommended to
replace `master` with [a release tag](https://github.com/jshttp/mime-db/tags)
as the JSON format may change in the future.
```
https://cdn.jsdelivr.net/gh/jshttp/mime-db@master/db.json
```
## Usage
```js
var db = require('mime-db')
// grab data on .js files
var data = db['application/javascript']
```
## Data Structure
The JSON file is a map lookup for lowercased mime types.
Each mime type has the following properties:
- `.source` - where the mime type is defined.
If not set, it's probably a custom media type.
- `apache` - [Apache common media types](http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types)
- `iana` - [IANA-defined media types](http://www.iana.org/assignments/media-types/media-types.xhtml)
- `nginx` - [nginx media types](http://hg.nginx.org/nginx/raw-file/default/conf/mime.types)
- `.extensions[]` - known extensions associated with this mime type.
- `.compressible` - whether a file of this type can be gzipped.
- `.charset` - the default charset associated with this type, if any.
If unknown, every property could be `undefined`.
## Contributing
To edit the database, only make PRs against `src/custom-types.json` or
`src/custom-suffix.json`.
The `src/custom-types.json` file is a JSON object with the MIME type as the
keys and the values being an object with the following keys:
- `compressible` - leave out if you don't know, otherwise `true`/`false` to
indicate whether the data represented by the type is typically compressible.
- `extensions` - include an array of file extensions that are associated with
the type.
- `notes` - human-readable notes about the type, typically what the type is.
- `sources` - include an array of URLs of where the MIME type and the associated
extensions are sourced from. This needs to be a [primary source](https://en.wikipedia.org/wiki/Primary_source);
links to type aggregating sites and Wikipedia are _not acceptable_.
To update the build, run `npm run build`.
### Adding Custom Media Types
The best way to get new media types included in this library is to register
them with the IANA. The community registration procedure is outlined in
[RFC 6838 section 5](http://tools.ietf.org/html/rfc6838#section-5). Types
registered with the IANA are automatically pulled into this library.
If that is not possible / feasible, they can be added directly here as a
"custom" type. To do this, it is required to have a primary source that
definitively lists the media type. If an extension is going to be listed as
associateed with this media type, the source must definitively link the
media type and extension as well.
[ci-image]: https://badgen.net/github/checks/jshttp/mime-db/master?label=ci
[ci-url]: https://github.com/jshttp/mime-db/actions?query=workflow%3Aci
[coveralls-image]: https://badgen.net/coveralls/c/github/jshttp/mime-db/master
[coveralls-url]: https://coveralls.io/r/jshttp/mime-db?branch=master
[node-image]: https://badgen.net/npm/node/mime-db
[node-url]: https://nodejs.org/en/download
[npm-downloads-image]: https://badgen.net/npm/dm/mime-db
[npm-url]: https://npmjs.org/package/mime-db
[npm-version-image]: https://badgen.net/npm/v/mime-db

8519
node_modules/mime-db/db.json generated vendored Normal file

File diff suppressed because it is too large Load diff

12
node_modules/mime-db/index.js generated vendored Normal file
View file

@ -0,0 +1,12 @@
/*!
* mime-db
* Copyright(c) 2014 Jonathan Ong
* Copyright(c) 2015-2022 Douglas Christopher Wilson
* MIT Licensed
*/
/**
* Module exports.
*/
module.exports = require('./db.json')

60
node_modules/mime-db/package.json generated vendored Normal file
View file

@ -0,0 +1,60 @@
{
"name": "mime-db",
"description": "Media Type Database",
"version": "1.52.0",
"contributors": [
"Douglas Christopher Wilson <doug@somethingdoug.com>",
"Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)",
"Robert Kieffer <robert@broofa.com> (http://github.com/broofa)"
],
"license": "MIT",
"keywords": [
"mime",
"db",
"type",
"types",
"database",
"charset",
"charsets"
],
"repository": "jshttp/mime-db",
"devDependencies": {
"bluebird": "3.7.2",
"co": "4.6.0",
"cogent": "1.0.1",
"csv-parse": "4.16.3",
"eslint": "7.32.0",
"eslint-config-standard": "15.0.1",
"eslint-plugin-import": "2.25.4",
"eslint-plugin-markdown": "2.2.1",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-promise": "5.1.1",
"eslint-plugin-standard": "4.1.0",
"gnode": "0.1.2",
"media-typer": "1.1.0",
"mocha": "9.2.1",
"nyc": "15.1.0",
"raw-body": "2.5.0",
"stream-to-array": "2.3.0"
},
"files": [
"HISTORY.md",
"LICENSE",
"README.md",
"db.json",
"index.js"
],
"engines": {
"node": ">= 0.6"
},
"scripts": {
"build": "node scripts/build",
"fetch": "node scripts/fetch-apache && gnode scripts/fetch-iana && node scripts/fetch-nginx",
"lint": "eslint .",
"test": "mocha --reporter spec --bail --check-leaks test/",
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
"test-cov": "nyc --reporter=html --reporter=text npm test",
"update": "npm run fetch && npm run build",
"version": "node scripts/version-history.js && git add HISTORY.md"
}
}

397
node_modules/mime-types/HISTORY.md generated vendored Normal file
View file

@ -0,0 +1,397 @@
2.1.35 / 2022-03-12
===================
* deps: mime-db@1.52.0
- Add extensions from IANA for more `image/*` types
- Add extension `.asc` to `application/pgp-keys`
- Add extensions to various XML types
- Add new upstream MIME types
2.1.34 / 2021-11-08
===================
* deps: mime-db@1.51.0
- Add new upstream MIME types
2.1.33 / 2021-10-01
===================
* deps: mime-db@1.50.0
- Add deprecated iWorks mime types and extensions
- Add new upstream MIME types
2.1.32 / 2021-07-27
===================
* deps: mime-db@1.49.0
- Add extension `.trig` to `application/trig`
- Add new upstream MIME types
2.1.31 / 2021-06-01
===================
* deps: mime-db@1.48.0
- Add extension `.mvt` to `application/vnd.mapbox-vector-tile`
- Add new upstream MIME types
2.1.30 / 2021-04-02
===================
* deps: mime-db@1.47.0
- Add extension `.amr` to `audio/amr`
- Remove ambigious extensions from IANA for `application/*+xml` types
- Update primary extension to `.es` for `application/ecmascript`
2.1.29 / 2021-02-17
===================
* deps: mime-db@1.46.0
- Add extension `.amr` to `audio/amr`
- Add extension `.m4s` to `video/iso.segment`
- Add extension `.opus` to `audio/ogg`
- Add new upstream MIME types
2.1.28 / 2021-01-01
===================
* deps: mime-db@1.45.0
- Add `application/ubjson` with extension `.ubj`
- Add `image/avif` with extension `.avif`
- Add `image/ktx2` with extension `.ktx2`
- Add extension `.dbf` to `application/vnd.dbf`
- Add extension `.rar` to `application/vnd.rar`
- Add extension `.td` to `application/urc-targetdesc+xml`
- Add new upstream MIME types
- Fix extension of `application/vnd.apple.keynote` to be `.key`
2.1.27 / 2020-04-23
===================
* deps: mime-db@1.44.0
- Add charsets from IANA
- Add extension `.cjs` to `application/node`
- Add new upstream MIME types
2.1.26 / 2020-01-05
===================
* deps: mime-db@1.43.0
- Add `application/x-keepass2` with extension `.kdbx`
- Add extension `.mxmf` to `audio/mobile-xmf`
- Add extensions from IANA for `application/*+xml` types
- Add new upstream MIME types
2.1.25 / 2019-11-12
===================
* deps: mime-db@1.42.0
- Add new upstream MIME types
- Add `application/toml` with extension `.toml`
- Add `image/vnd.ms-dds` with extension `.dds`
2.1.24 / 2019-04-20
===================
* deps: mime-db@1.40.0
- Add extensions from IANA for `model/*` types
- Add `text/mdx` with extension `.mdx`
2.1.23 / 2019-04-17
===================
* deps: mime-db@~1.39.0
- Add extensions `.siv` and `.sieve` to `application/sieve`
- Add new upstream MIME types
2.1.22 / 2019-02-14
===================
* deps: mime-db@~1.38.0
- Add extension `.nq` to `application/n-quads`
- Add extension `.nt` to `application/n-triples`
- Add new upstream MIME types
2.1.21 / 2018-10-19
===================
* deps: mime-db@~1.37.0
- Add extensions to HEIC image types
- Add new upstream MIME types
2.1.20 / 2018-08-26
===================
* deps: mime-db@~1.36.0
- Add Apple file extensions from IANA
- Add extensions from IANA for `image/*` types
- Add new upstream MIME types
2.1.19 / 2018-07-17
===================
* deps: mime-db@~1.35.0
- Add extension `.csl` to `application/vnd.citationstyles.style+xml`
- Add extension `.es` to `application/ecmascript`
- Add extension `.owl` to `application/rdf+xml`
- Add new upstream MIME types
- Add UTF-8 as default charset for `text/turtle`
2.1.18 / 2018-02-16
===================
* deps: mime-db@~1.33.0
- Add `application/raml+yaml` with extension `.raml`
- Add `application/wasm` with extension `.wasm`
- Add `text/shex` with extension `.shex`
- Add extensions for JPEG-2000 images
- Add extensions from IANA for `message/*` types
- Add new upstream MIME types
- Update font MIME types
- Update `text/hjson` to registered `application/hjson`
2.1.17 / 2017-09-01
===================
* deps: mime-db@~1.30.0
- Add `application/vnd.ms-outlook`
- Add `application/x-arj`
- Add extension `.mjs` to `application/javascript`
- Add glTF types and extensions
- Add new upstream MIME types
- Add `text/x-org`
- Add VirtualBox MIME types
- Fix `source` records for `video/*` types that are IANA
- Update `font/opentype` to registered `font/otf`
2.1.16 / 2017-07-24
===================
* deps: mime-db@~1.29.0
- Add `application/fido.trusted-apps+json`
- Add extension `.wadl` to `application/vnd.sun.wadl+xml`
- Add extension `.gz` to `application/gzip`
- Add new upstream MIME types
- Update extensions `.md` and `.markdown` to be `text/markdown`
2.1.15 / 2017-03-23
===================
* deps: mime-db@~1.27.0
- Add new mime types
- Add `image/apng`
2.1.14 / 2017-01-14
===================
* deps: mime-db@~1.26.0
- Add new mime types
2.1.13 / 2016-11-18
===================
* deps: mime-db@~1.25.0
- Add new mime types
2.1.12 / 2016-09-18
===================
* deps: mime-db@~1.24.0
- Add new mime types
- Add `audio/mp3`
2.1.11 / 2016-05-01
===================
* deps: mime-db@~1.23.0
- Add new mime types
2.1.10 / 2016-02-15
===================
* deps: mime-db@~1.22.0
- Add new mime types
- Fix extension of `application/dash+xml`
- Update primary extension for `audio/mp4`
2.1.9 / 2016-01-06
==================
* deps: mime-db@~1.21.0
- Add new mime types
2.1.8 / 2015-11-30
==================
* deps: mime-db@~1.20.0
- Add new mime types
2.1.7 / 2015-09-20
==================
* deps: mime-db@~1.19.0
- Add new mime types
2.1.6 / 2015-09-03
==================
* deps: mime-db@~1.18.0
- Add new mime types
2.1.5 / 2015-08-20
==================
* deps: mime-db@~1.17.0
- Add new mime types
2.1.4 / 2015-07-30
==================
* deps: mime-db@~1.16.0
- Add new mime types
2.1.3 / 2015-07-13
==================
* deps: mime-db@~1.15.0
- Add new mime types
2.1.2 / 2015-06-25
==================
* deps: mime-db@~1.14.0
- Add new mime types
2.1.1 / 2015-06-08
==================
* perf: fix deopt during mapping
2.1.0 / 2015-06-07
==================
* Fix incorrectly treating extension-less file name as extension
- i.e. `'path/to/json'` will no longer return `application/json`
* Fix `.charset(type)` to accept parameters
* Fix `.charset(type)` to match case-insensitive
* Improve generation of extension to MIME mapping
* Refactor internals for readability and no argument reassignment
* Prefer `application/*` MIME types from the same source
* Prefer any type over `application/octet-stream`
* deps: mime-db@~1.13.0
- Add nginx as a source
- Add new mime types
2.0.14 / 2015-06-06
===================
* deps: mime-db@~1.12.0
- Add new mime types
2.0.13 / 2015-05-31
===================
* deps: mime-db@~1.11.0
- Add new mime types
2.0.12 / 2015-05-19
===================
* deps: mime-db@~1.10.0
- Add new mime types
2.0.11 / 2015-05-05
===================
* deps: mime-db@~1.9.1
- Add new mime types
2.0.10 / 2015-03-13
===================
* deps: mime-db@~1.8.0
- Add new mime types
2.0.9 / 2015-02-09
==================
* deps: mime-db@~1.7.0
- Add new mime types
- Community extensions ownership transferred from `node-mime`
2.0.8 / 2015-01-29
==================
* deps: mime-db@~1.6.0
- Add new mime types
2.0.7 / 2014-12-30
==================
* deps: mime-db@~1.5.0
- Add new mime types
- Fix various invalid MIME type entries
2.0.6 / 2014-12-30
==================
* deps: mime-db@~1.4.0
- Add new mime types
- Fix various invalid MIME type entries
- Remove example template MIME types
2.0.5 / 2014-12-29
==================
* deps: mime-db@~1.3.1
- Fix missing extensions
2.0.4 / 2014-12-10
==================
* deps: mime-db@~1.3.0
- Add new mime types
2.0.3 / 2014-11-09
==================
* deps: mime-db@~1.2.0
- Add new mime types
2.0.2 / 2014-09-28
==================
* deps: mime-db@~1.1.0
- Add new mime types
- Update charsets
2.0.1 / 2014-09-07
==================
* Support Node.js 0.6
2.0.0 / 2014-09-02
==================
* Use `mime-db`
* Remove `.define()`
1.0.2 / 2014-08-04
==================
* Set charset=utf-8 for `text/javascript`
1.0.1 / 2014-06-24
==================
* Add `text/jsx` type
1.0.0 / 2014-05-12
==================
* Return `false` for unknown types
* Set charset=utf-8 for `application/json`
0.1.0 / 2014-05-02
==================
* Initial release

23
node_modules/mime-types/LICENSE generated vendored Normal file
View file

@ -0,0 +1,23 @@
(The MIT License)
Copyright (c) 2014 Jonathan Ong <me@jongleberry.com>
Copyright (c) 2015 Douglas Christopher Wilson <doug@somethingdoug.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

113
node_modules/mime-types/README.md generated vendored Normal file
View file

@ -0,0 +1,113 @@
# mime-types
[![NPM Version][npm-version-image]][npm-url]
[![NPM Downloads][npm-downloads-image]][npm-url]
[![Node.js Version][node-version-image]][node-version-url]
[![Build Status][ci-image]][ci-url]
[![Test Coverage][coveralls-image]][coveralls-url]
The ultimate javascript content-type utility.
Similar to [the `mime@1.x` module](https://www.npmjs.com/package/mime), except:
- __No fallbacks.__ Instead of naively returning the first available type,
`mime-types` simply returns `false`, so do
`var type = mime.lookup('unrecognized') || 'application/octet-stream'`.
- No `new Mime()` business, so you could do `var lookup = require('mime-types').lookup`.
- No `.define()` functionality
- Bug fixes for `.lookup(path)`
Otherwise, the API is compatible with `mime` 1.x.
## Install
This is a [Node.js](https://nodejs.org/en/) module available through the
[npm registry](https://www.npmjs.com/). Installation is done using the
[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally):
```sh
$ npm install mime-types
```
## Adding Types
All mime types are based on [mime-db](https://www.npmjs.com/package/mime-db),
so open a PR there if you'd like to add mime types.
## API
```js
var mime = require('mime-types')
```
All functions return `false` if input is invalid or not found.
### mime.lookup(path)
Lookup the content-type associated with a file.
```js
mime.lookup('json') // 'application/json'
mime.lookup('.md') // 'text/markdown'
mime.lookup('file.html') // 'text/html'
mime.lookup('folder/file.js') // 'application/javascript'
mime.lookup('folder/.htaccess') // false
mime.lookup('cats') // false
```
### mime.contentType(type)
Create a full content-type header given a content-type or extension.
When given an extension, `mime.lookup` is used to get the matching
content-type, otherwise the given content-type is used. Then if the
content-type does not already have a `charset` parameter, `mime.charset`
is used to get the default charset and add to the returned content-type.
```js
mime.contentType('markdown') // 'text/x-markdown; charset=utf-8'
mime.contentType('file.json') // 'application/json; charset=utf-8'
mime.contentType('text/html') // 'text/html; charset=utf-8'
mime.contentType('text/html; charset=iso-8859-1') // 'text/html; charset=iso-8859-1'
// from a full path
mime.contentType(path.extname('/path/to/file.json')) // 'application/json; charset=utf-8'
```
### mime.extension(type)
Get the default extension for a content-type.
```js
mime.extension('application/octet-stream') // 'bin'
```
### mime.charset(type)
Lookup the implied default charset of a content-type.
```js
mime.charset('text/markdown') // 'UTF-8'
```
### var type = mime.types[extension]
A map of content-types by extension.
### [extensions...] = mime.extensions[type]
A map of extensions by content-type.
## License
[MIT](LICENSE)
[ci-image]: https://badgen.net/github/checks/jshttp/mime-types/master?label=ci
[ci-url]: https://github.com/jshttp/mime-types/actions/workflows/ci.yml
[coveralls-image]: https://badgen.net/coveralls/c/github/jshttp/mime-types/master
[coveralls-url]: https://coveralls.io/r/jshttp/mime-types?branch=master
[node-version-image]: https://badgen.net/npm/node/mime-types
[node-version-url]: https://nodejs.org/en/download
[npm-downloads-image]: https://badgen.net/npm/dm/mime-types
[npm-url]: https://npmjs.org/package/mime-types
[npm-version-image]: https://badgen.net/npm/v/mime-types

188
node_modules/mime-types/index.js generated vendored Normal file
View file

@ -0,0 +1,188 @@
/*!
* mime-types
* Copyright(c) 2014 Jonathan Ong
* Copyright(c) 2015 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict'
/**
* Module dependencies.
* @private
*/
var db = require('mime-db')
var extname = require('path').extname
/**
* Module variables.
* @private
*/
var EXTRACT_TYPE_REGEXP = /^\s*([^;\s]*)(?:;|\s|$)/
var TEXT_TYPE_REGEXP = /^text\//i
/**
* Module exports.
* @public
*/
exports.charset = charset
exports.charsets = { lookup: charset }
exports.contentType = contentType
exports.extension = extension
exports.extensions = Object.create(null)
exports.lookup = lookup
exports.types = Object.create(null)
// Populate the extensions/types maps
populateMaps(exports.extensions, exports.types)
/**
* Get the default charset for a MIME type.
*
* @param {string} type
* @return {boolean|string}
*/
function charset (type) {
if (!type || typeof type !== 'string') {
return false
}
// TODO: use media-typer
var match = EXTRACT_TYPE_REGEXP.exec(type)
var mime = match && db[match[1].toLowerCase()]
if (mime && mime.charset) {
return mime.charset
}
// default text/* to utf-8
if (match && TEXT_TYPE_REGEXP.test(match[1])) {
return 'UTF-8'
}
return false
}
/**
* Create a full Content-Type header given a MIME type or extension.
*
* @param {string} str
* @return {boolean|string}
*/
function contentType (str) {
// TODO: should this even be in this module?
if (!str || typeof str !== 'string') {
return false
}
var mime = str.indexOf('/') === -1
? exports.lookup(str)
: str
if (!mime) {
return false
}
// TODO: use content-type or other module
if (mime.indexOf('charset') === -1) {
var charset = exports.charset(mime)
if (charset) mime += '; charset=' + charset.toLowerCase()
}
return mime
}
/**
* Get the default extension for a MIME type.
*
* @param {string} type
* @return {boolean|string}
*/
function extension (type) {
if (!type || typeof type !== 'string') {
return false
}
// TODO: use media-typer
var match = EXTRACT_TYPE_REGEXP.exec(type)
// get extensions
var exts = match && exports.extensions[match[1].toLowerCase()]
if (!exts || !exts.length) {
return false
}
return exts[0]
}
/**
* Lookup the MIME type for a file path/extension.
*
* @param {string} path
* @return {boolean|string}
*/
function lookup (path) {
if (!path || typeof path !== 'string') {
return false
}
// get the extension ("ext" or ".ext" or full path)
var extension = extname('x.' + path)
.toLowerCase()
.substr(1)
if (!extension) {
return false
}
return exports.types[extension] || false
}
/**
* Populate the extensions and types maps.
* @private
*/
function populateMaps (extensions, types) {
// source preference (least -> most)
var preference = ['nginx', 'apache', undefined, 'iana']
Object.keys(db).forEach(function forEachMimeType (type) {
var mime = db[type]
var exts = mime.extensions
if (!exts || !exts.length) {
return
}
// mime -> extensions
extensions[type] = exts
// extension -> mime
for (var i = 0; i < exts.length; i++) {
var extension = exts[i]
if (types[extension]) {
var from = preference.indexOf(db[types[extension]].source)
var to = preference.indexOf(mime.source)
if (types[extension] !== 'application/octet-stream' &&
(from > to || (from === to && types[extension].substr(0, 12) === 'application/'))) {
// skip the remapping
continue
}
}
// set the extension -> mime
types[extension] = type
}
})
}

44
node_modules/mime-types/package.json generated vendored Normal file
View file

@ -0,0 +1,44 @@
{
"name": "mime-types",
"description": "The ultimate javascript content-type utility.",
"version": "2.1.35",
"contributors": [
"Douglas Christopher Wilson <doug@somethingdoug.com>",
"Jeremiah Senkpiel <fishrock123@rocketmail.com> (https://searchbeam.jit.su)",
"Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)"
],
"license": "MIT",
"keywords": [
"mime",
"types"
],
"repository": "jshttp/mime-types",
"dependencies": {
"mime-db": "1.52.0"
},
"devDependencies": {
"eslint": "7.32.0",
"eslint-config-standard": "14.1.1",
"eslint-plugin-import": "2.25.4",
"eslint-plugin-markdown": "2.2.1",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-promise": "5.2.0",
"eslint-plugin-standard": "4.1.0",
"mocha": "9.2.2",
"nyc": "15.1.0"
},
"files": [
"HISTORY.md",
"LICENSE",
"index.js"
],
"engines": {
"node": ">= 0.6"
},
"scripts": {
"lint": "eslint .",
"test": "mocha --reporter spec test/test.js",
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
"test-cov": "nyc --reporter=html --reporter=text npm test"
}
}

View file

@ -1,6 +0,0 @@
{
"watch": [
"dist/svr.js",
"dist/config.json"
]
}

9939
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,56 +0,0 @@
{
"name": "svrjs-build",
"version": "0.0.0",
"private": true,
"scripts": {
"build": "npm run clean && NODE_ENV=production node esbuild.config.js",
"cz": "cz",
"clean": "rimraf dist && rimraf out && rimraf generatedAssets",
"dev": "npm run clean && concurrently \"NODE_ENV=development node esbuild.config.js\" \"wait-on dist/svr.js && nodemon dist/svr.js --stdout-notty --no-save-config\"",
"lint": "eslint --no-error-on-unmatched-pattern src/**/*.js src/*.js tests/**/*.test.js tests/**/*.js tests/*.test.js tests/*.js",
"lint:fix": "npm run lint -- --fix",
"prepare": "husky",
"start": "node dist/svr.js",
"test": "jest",
"test:coverage": "jest --coverage",
"test:middleware": "jest tests/middleware",
"test:utils": "jest tests/utils"
},
"devDependencies": {
"@commitlint/cli": "^19.4.1",
"@commitlint/config-conventional": "^19.4.1",
"@eslint/js": "^9.9.0",
"archiver": "^7.0.1",
"chokidar": "^4.0.1",
"commitizen": "^4.3.0",
"concurrently": "^9.1.0",
"cz-conventional-changelog": "^3.3.0",
"ejs": "^3.1.10",
"esbuild": "^0.23.1",
"esbuild-plugin-copy": "^2.1.1",
"eslint": "^9.9.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-jest": "^28.8.0",
"eslint-plugin-prettier": "^5.2.1",
"globals": "^15.9.0",
"husky": "^9.1.5",
"jest": "^29.7.0",
"lint-staged": "^15.2.10",
"node-mocks-http": "^1.15.1",
"nodemon": "^3.1.7",
"prettier": "^3.3.3",
"rimraf": "^5.0.10",
"wait-on": "^8.0.1"
},
"dependencies": {
"formidable": "^2.1.2",
"mime-types": "^2.1.35",
"ocsp": "^1.2.0",
"tar": "^6.2.1"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
}

View file

@ -1,15 +0,0 @@
// prettier.config.js, .prettierrc.js, prettier.config.cjs, or .prettierrc.cjs
/**
* @see https://prettier.io/docs/en/configuration.html
* @type {import("prettier").Config}
*/
const config = {
trailingComma: "none",
tabWidth: 2,
semi: true,
singleQuote: false,
endOfLine: "lf"
};
module.exports = config;

View file

@ -1,93 +0,0 @@
//SVR.JS LOG HIGHLIGHTER
const readline = require("readline");
const args = process.argv;
for (
let i =
process.argv[0].indexOf("node") > -1 ||
process.argv[0].indexOf("bun") > -1 ||
process.argv[0].indexOf("deno") > -1
? 2
: 1;
i < args.length;
i++
) {
if (
args[i] == "-h" ||
args[i] == "--help" ||
args[i] == "-?" ||
args[i] == "/h" ||
args[i] == "/?"
) {
console.log("SVR.JS log highlighter usage:");
console.log(
"<some process> | node loghighlight.js [-h] [--help] [-?] [/h] [/?]"
);
console.log("-h -? /h /? --help -- Displays help");
process.exit(0);
} else {
console.log("Unrecognized argument: " + args[i]);
console.log("SVR.JS log highlighter usage:");
console.log(
"<some process> | node loghighlight.js [-h] [--help] [-?] [/h] [/?]"
);
console.log("-h -? /h /? --help -- Displays help");
process.exit(1);
}
}
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false,
prompt: ""
});
rl.prompt();
rl.on("line", (line) => {
viewLog([line]);
});
function viewLog(log) {
if (log[log.length - 1] == "") log.pop();
if (log[0] == "") log.shift();
for (var i = 0; i < log.length; i++) {
if (log[i].indexOf("SERVER REQUEST MESSAGE") != -1) {
log[i] =
log[i].replace(
"SERVER REQUEST MESSAGE",
"\x1b[34m\x1b[1mSERVER REQUEST MESSAGE\x1b[22m"
) + "\x1b[37m\x1b[0m";
} else if (log[i].indexOf("SERVER RESPONSE MESSAGE") != -1) {
log[i] =
log[i].replace(
"SERVER RESPONSE MESSAGE",
"\x1b[32m\x1b[1mSERVER RESPONSE MESSAGE\x1b[22m"
) + "\x1b[37m\x1b[0m";
} else if (log[i].indexOf("SERVER RESPONSE ERROR MESSAGE") != -1) {
log[i] =
log[i].replace(
"SERVER RESPONSE ERROR MESSAGE",
"\x1b[31m\x1b[1mSERVER RESPONSE ERROR MESSAGE\x1b[22m"
) + "\x1b[37m\x1b[0m";
} else if (log[i].indexOf("SERVER ERROR MESSAGE") != -1) {
log[i] =
log[i].replace(
"SERVER ERROR MESSAGE",
"\x1b[41m\x1b[1mSERVER ERROR MESSAGE\x1b[22m"
) + "\x1b[40m\x1b[0m";
} else if (log[i].indexOf("SERVER WARNING MESSAGE") != -1) {
log[i] =
log[i].replace(
"SERVER WARNING MESSAGE",
"\x1b[43m\x1b[1mSERVER WARNING MESSAGE\x1b[22m"
) + "\x1b[40m\x1b[0m";
} else if (log[i].indexOf("SERVER MESSAGE") != -1) {
log[i] = log[i].replace(
"SERVER MESSAGE",
"\x1b[1mSERVER MESSAGE\x1b[22m"
);
}
console.log(log[i]);
}
}

View file

@ -1,232 +0,0 @@
//SVR.JS LOG VIEWER
const fs = require("fs");
const readline = require("readline");
const args = process.argv;
for (
let i =
process.argv[0].indexOf("node") > -1 ||
process.argv[0].indexOf("bun") > -1 ||
process.argv[0].indexOf("deno") > -1
? 2
: 1;
i < args.length;
i++
) {
if (
args[i] == "-h" ||
args[i] == "--help" ||
args[i] == "-?" ||
args[i] == "/h" ||
args[i] == "/?"
) {
console.log("SVR.JS log viewer usage:");
console.log("node logviewer.js [-h] [--help] [-?] [/h] [/?]");
console.log("-h -? /h /? --help -- Displays help");
process.exit(0);
} else {
console.log("Unrecognized argument: " + args[i]);
console.log("SVR.JS log viewer usage:");
console.log("node logviewer.js [-h] [--help] [-?] [/h] [/?]");
console.log("-h -? /h /? --help -- Displays help");
process.exit(1);
}
}
const logo = require("../res/logo.js");
for (let i = 0; i < logo.length; i++) {
console.log(logo[i]);
}
console.log("Welcome to SVR.JS log viewer");
if (!fs.existsSync(__dirname + "/log")) {
console.log("No log directory, exiting...");
process.exit(1);
}
function prompt(options) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: "logviewer> "
});
console.log("Options:");
for (let i = 0; i < options.length; i++) {
console.log("[" + i + "] - " + options[i].name);
}
rl.prompt();
rl.on("line", (line) => {
const op = line.trim();
if (op == "") {
rl.prompt();
return;
}
rl.close();
if (options[op]) {
options[op].callback();
} else {
console.log("Invalid option.");
prompt(options);
}
});
}
function viewLog(log) {
if (log[log.length - 1] == "") log.pop();
if (log[0] == "") log.shift();
for (let i = 0; i < log.length; i++) {
if (log[i].indexOf("SERVER REQUEST MESSAGE") != -1) {
log[i] =
log[i].replace(
"SERVER REQUEST MESSAGE",
"\x1b[34m\x1b[1mSERVER REQUEST MESSAGE\x1b[22m"
) + "\x1b[37m\x1b[0m";
} else if (log[i].indexOf("SERVER RESPONSE MESSAGE") != -1) {
log[i] =
log[i].replace(
"SERVER RESPONSE MESSAGE",
"\x1b[32m\x1b[1mSERVER RESPONSE MESSAGE\x1b[22m"
) + "\x1b[37m\x1b[0m";
} else if (log[i].indexOf("SERVER RESPONSE ERROR MESSAGE") != -1) {
log[i] =
log[i].replace(
"SERVER RESPONSE ERROR MESSAGE",
"\x1b[31m\x1b[1mSERVER RESPONSE ERROR MESSAGE\x1b[22m"
) + "\x1b[37m\x1b[0m";
} else if (log[i].indexOf("SERVER ERROR MESSAGE") != -1) {
log[i] =
log[i].replace(
"SERVER ERROR MESSAGE",
"\x1b[41m\x1b[1mSERVER ERROR MESSAGE\x1b[22m"
) + "\x1b[40m\x1b[0m";
} else if (log[i].indexOf("SERVER WARNING MESSAGE") != -1) {
log[i] =
log[i].replace(
"SERVER WARNING MESSAGE",
"\x1b[43m\x1b[1mSERVER WARNING MESSAGE\x1b[22m"
) + "\x1b[40m\x1b[0m";
} else if (log[i].indexOf("SERVER MESSAGE") != -1) {
log[i] = log[i].replace(
"SERVER MESSAGE",
"\x1b[1mSERVER MESSAGE\x1b[22m"
);
}
console.log(log[i]);
}
}
function viewMasterLogs() {
const logList = fs.readdirSync(__dirname + "/log");
let masterLogs = [];
for (var i = 0; i < logList.length; i++) {
if (logList[i].match(/^master-[0-9]+\.log$/)) {
masterLogs.push(logList[i]);
}
}
if (masterLogs.length == 0) {
console.log("No master log.");
return;
}
const latestLogFileName = masterLogs.sort().reverse()[0];
viewLog(
fs
.readFileSync(__dirname + "/log/" + latestLogFileName)
.toString()
.split("\n")
);
prompt(mainOptions);
}
function viewWorkerLogs() {
const logList = fs.readdirSync("log");
let masterLogs = [];
for (var i = 0; i < logList.length; i++) {
if (logList[i].match(/^worker-[0-9]+\.log$/)) {
masterLogs.push(logList[i]);
}
}
if (masterLogs.length == 0) {
console.log("No worker logs.");
return;
}
const latestLogFileNames = masterLogs.sort().reverse().slice(0, 5).reverse();
let log = [];
for (let i = 0; i < latestLogFileNames.length; i++) {
let rlog = fs
.readFileSync("log/" + latestLogFileNames[i])
.toString()
.split("\n");
if (rlog[rlog.length - 1] == "") rlog.pop();
if (rlog[0] == "") rlog.shift();
for (let j = 0; j < rlog.length; j++) {
log.push(rlog[j]);
}
}
log = log.sort();
viewLog(log);
prompt(mainOptions);
}
function viewFilteredWorkerLogs(filter) {
const logList = fs.readdirSync("log");
let masterLogs = [];
for (var i = 0; i < logList.length; i++) {
if (logList[i].match(/^worker-[0-9]+\.log$/)) {
masterLogs.push(logList[i]);
}
}
if (masterLogs.length == 0) {
console.log("No worker logs.");
return;
}
const latestLogFileNames = masterLogs.sort().reverse().slice(0, 20).reverse();
let log = [];
for (let i = 0; i < latestLogFileNames.length; i++) {
let rlog = fs
.readFileSync("log/" + latestLogFileNames[i])
.toString()
.split("\n");
if (rlog[rlog.length - 1] == "") rlog.pop();
if (rlog[0] == "") rlog.shift();
for (let j = 0; j < rlog.length; j++) {
if (rlog[j].indexOf(filter) != -1) log.push(rlog[j]);
}
}
log = log.sort();
viewLog(log);
prompt(mainOptions);
}
function viewFilteredWorkerLogsPrompt() {
var rl2 = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: "filter> "
});
console.log("Input filter:");
rl2.prompt();
rl2.on("line", (line) => {
rl2.close();
viewFilteredWorkerLogs(line);
});
}
var mainOptions = [
{ name: "View latest master log", callback: viewMasterLogs },
{ name: "View 5 latest worker logs", callback: viewWorkerLogs },
{
name: "View filtered worker logs (latest 20 logs)",
callback: viewFilteredWorkerLogsPrompt
},
{
name: "Exit log viewer",
callback: () => {
console.log("Bye!");
process.exit(0);
}
}
];
prompt(mainOptions);

View file

@ -1 +0,0 @@
require(__dirname + "/svr.js");

View file

@ -1,591 +0,0 @@
//SVR.JS USER TOOL
const readline = require("readline");
const fs = require("fs");
let crypto = {};
try {
crypto = require("crypto");
// eslint-disable-next-line no-unused-vars
} catch (ex) {
crypto = {};
crypto.__disabled__ = null;
crypto.createHash = (type) => {
if (type != "SHA256") throw new Error("Hash type not supported!");
return {
msg: "",
update: (a) => {
this.msg = a;
return this;
},
digest: (ty) => {
const chrsz = 8;
const hexcase = 0;
const safeAdd = (x, y) => {
const lsw = (x & 0xffff) + (y & 0xffff);
const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xffff);
};
const S = (X, n) => {
return (X >>> n) | (X << (32 - n));
};
const R = (X, n) => {
return X >>> n;
};
const Ch = (x, y, z) => {
return (x & y) ^ (~x & z);
};
const Maj = (x, y, z) => {
return (x & y) ^ (x & z) ^ (y & z);
};
const Sigma0256 = (x) => {
return S(x, 2) ^ S(x, 13) ^ S(x, 22);
};
const Sigma1256 = (x) => {
return S(x, 6) ^ S(x, 11) ^ S(x, 25);
};
const Gamma0256 = (x) => {
return S(x, 7) ^ S(x, 18) ^ R(x, 3);
};
const Gamma1256 = (x) => {
return S(x, 17) ^ S(x, 19) ^ R(x, 10);
};
function coreSha256(m, l) {
const K = new Array(
0x428a2f98,
0x71374491,
0xb5c0fbcf,
0xe9b5dba5,
0x3956c25b,
0x59f111f1,
0x923f82a4,
0xab1c5ed5,
0xd807aa98,
0x12835b01,
0x243185be,
0x550c7dc3,
0x72be5d74,
0x80deb1fe,
0x9bdc06a7,
0xc19bf174,
0xe49b69c1,
0xefbe4786,
0xfc19dc6,
0x240ca1cc,
0x2de92c6f,
0x4a7484aa,
0x5cb0a9dc,
0x76f988da,
0x983e5152,
0xa831c66d,
0xb00327c8,
0xbf597fc7,
0xc6e00bf3,
0xd5a79147,
0x6ca6351,
0x14292967,
0x27b70a85,
0x2e1b2138,
0x4d2c6dfc,
0x53380d13,
0x650a7354,
0x766a0abb,
0x81c2c92e,
0x92722c85,
0xa2bfe8a1,
0xa81a664b,
0xc24b8b70,
0xc76c51a3,
0xd192e819,
0xd6990624,
0xf40e3585,
0x106aa070,
0x19a4c116,
0x1e376c08,
0x2748774c,
0x34b0bcb5,
0x391c0cb3,
0x4ed8aa4a,
0x5b9cca4f,
0x682e6ff3,
0x748f82ee,
0x78a5636f,
0x84c87814,
0x8cc70208,
0x90befffa,
0xa4506ceb,
0xbef9a3f7,
0xc67178f2
);
let HASH = new Array(
0x6a09e667,
0xbb67ae85,
0x3c6ef372,
0xa54ff53a,
0x510e527f,
0x9b05688c,
0x1f83d9ab,
0x5be0cd19
);
let W = new Array(64);
let a, b, c, d, e, f, g, h;
let T1, T2;
m[l >> 5] |= 0x80 << (24 - (l % 32));
m[(((l + 64) >> 9) << 4) + 15] = l;
for (let i = 0; i < m.length; i += 16) {
a = HASH[0];
b = HASH[1];
c = HASH[2];
d = HASH[3];
e = HASH[4];
f = HASH[5];
g = HASH[6];
h = HASH[7];
for (let j = 0; j < 64; j++) {
if (j < 16) W[j] = m[j + i];
else
W[j] = safeAdd(
safeAdd(
safeAdd(Gamma1256(W[j - 2]), W[j - 7]),
Gamma0256(W[j - 15])
),
W[j - 16]
);
T1 = safeAdd(
safeAdd(safeAdd(safeAdd(h, Sigma1256(e)), Ch(e, f, g)), K[j]),
W[j]
);
T2 = safeAdd(Sigma0256(a), Maj(a, b, c));
h = g;
g = f;
f = e;
e = safeAdd(d, T1);
d = c;
c = b;
b = a;
a = safeAdd(T1, T2);
}
HASH[0] = safeAdd(a, HASH[0]);
HASH[1] = safeAdd(b, HASH[1]);
HASH[2] = safeAdd(c, HASH[2]);
HASH[3] = safeAdd(d, HASH[3]);
HASH[4] = safeAdd(e, HASH[4]);
HASH[5] = safeAdd(f, HASH[5]);
HASH[6] = safeAdd(g, HASH[6]);
HASH[7] = safeAdd(h, HASH[7]);
}
return HASH;
}
const str2binb = (str) => {
let bin = Array();
const mask = (1 << chrsz) - 1;
for (let i = 0; i < str.length * chrsz; i += chrsz) {
bin[i >> 5] |=
(str.charCodeAt(i / chrsz) & mask) << (24 - (i % 32));
}
return bin;
};
const Utf8Encode = (string) => {
string = string.replace(/\r\n/g, "\n");
let utftext = "";
for (let n = 0; n < string.length; n++) {
let c = string.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
} else if (c > 127 && c < 2048) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
} else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
}
return utftext;
};
const binb2hex = (binarray) => {
const hexTab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
let str = "";
for (let i = 0; i < binarray.length * 4; i++) {
str +=
hexTab.charAt(
(binarray[i >> 2] >> ((3 - (i % 4)) * 8 + 4)) & 0xf
) +
hexTab.charAt((binarray[i >> 2] >> ((3 - (i % 4)) * 8)) & 0xf);
}
return str;
};
let s = Utf8Encode(this.msg);
let str = binb2hex(coreSha256(str2binb(s), s.length * chrsz));
if (ty == "hex") return str;
let hx = [];
for (var i = 0; i < str.length; i += 2) {
hx.push(parseInt(str[i] + str[i + 1], 16));
}
return Buffer.from(hx);
}
};
};
}
if (!crypto.randomInt) {
crypto.randomInt = (min, max) => {
return Math.round(Math.random() * (max - min)) + min;
};
}
let configJSON = {};
if (fs.existsSync(__dirname + "/config.json")) {
let configJSONf = "";
try {
configJSONf = fs.readFileSync(__dirname + "/config.json"); //Read JSON File
// eslint-disable-next-line no-unused-vars
} catch (ex) {
throw new Error("Cannot read JSON file.");
}
try {
configJSON = JSON.parse(configJSONf); //Parse JSON
// eslint-disable-next-line no-unused-vars
} catch (ex) {
throw new Error("JSON Parse error.");
}
}
let users = [];
if (configJSON.users != undefined) users = configJSON.users;
function saveConfig() {
let configJSONobj = {};
if (fs.existsSync(__dirname + "/config.json"))
configJSONobj = JSON.parse(
fs.readFileSync(__dirname + "/config.json").toString()
);
configJSONobj.users = users;
const configString = JSON.stringify(configJSONobj, null, 2);
fs.writeFileSync(__dirname + "/config.json", configString);
}
const args = process.argv;
let user = "";
let action = "change";
let forcechange = false;
if (
process.argv.length <=
(process.argv[0].indexOf("node") > -1 || process.argv[0].indexOf("bun") > -1
? 2
: 1)
)
args.push("-h");
for (
let i =
process.argv[0].indexOf("node") > -1 ||
process.argv[0].indexOf("bun") > -1 ||
process.argv[0].indexOf("deno") > -1
? 2
: 1;
i < args.length;
i++
) {
if (
args[i] == "-h" ||
args[i] == "--help" ||
args[i] == "-?" ||
args[i] == "/h" ||
args[i] == "/?"
) {
console.log("SVR.JS user tool usage:");
console.log(
"node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [-x] [-a|--add|-d|--delete] <username>"
);
console.log("-h -? /h /? --help -- Displays help");
console.log("-a --add -- Add an user");
console.log("-d --delete -- Deletes an user");
console.log("-x -- Changes hash algorithm");
process.exit(0);
} else if (args[i] == "-a" || args[i] == "--add") {
if (action != "change") {
console.log("Multiple actions specified.");
console.log(
"node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [-x] [-a|--add|-d|--delete] <username>"
);
console.log("-h -? /h /? --help -- Displays help");
console.log("-a --add -- Add an user");
console.log("-d --delete -- Deletes an user");
console.log("-x -- Changes hash algorithm");
process.exit(1);
}
action = "add";
} else if (args[i] == "-d" || args[i] == "--delete") {
if (action != "change") {
console.log("Multiple actions specified.");
console.log(
"node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [-x] [-a|--add|-d|--delete] <username>"
);
console.log("-h -? /h /? --help -- Displays help");
console.log("-a --add -- Add an user");
console.log("-d --delete -- Deletes an user");
console.log("-x -- Changes hash algorithm");
process.exit(1);
}
action = "delete";
} else if (args[i] == "-x") {
if (forcechange) {
console.log("Multiple -x options specified.");
console.log(
"node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [-x] [-a|--add|-d|--delete] <username>"
);
console.log("-h -? /h /? --help -- Displays help");
console.log("-a --add -- Add an user");
console.log("-d --delete -- Deletes an user");
console.log("-x -- Changes hash algorithm");
process.exit(1);
}
forcechange = true;
} else {
if (user != "") {
console.log("Multiple users specified.");
console.log(
"node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [-x] [-a|--add|-d|--delete] <username>"
);
console.log("-h -? /h /? --help -- Displays help");
console.log("-a --add -- Add an user");
console.log("-d --delete -- Deletes an user");
console.log("-x -- Changes hash algorithm");
process.exit(1);
}
user = args[i];
}
}
if (user == "") {
console.log("No user specified.");
console.log(
"node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [-x] [-a|--add|-d|--delete] <username>"
);
console.log("-h -? /h /? --help -- Displays help");
console.log("-a --add -- Add an user");
console.log("-d --delete -- Deletes an user");
console.log("-x -- Changes hash algorithm");
process.exit(1);
}
function getUserIndex(username) {
let ind = -1;
for (let i = 0; i < users.length; i++) {
if (users[i].name == username) {
ind = i;
break;
}
}
return ind;
}
function sha256(msg) {
let hash = crypto.createHash("SHA256");
hash.update(msg);
return hash.digest("hex");
}
function generateSalt() {
let token = "";
const strlist =
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
for (let i = 0; i < 63; i++) {
token += strlist[crypto.randomInt(0, strlist.length)];
}
return token;
}
function password(callback) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: "Password: "
});
rl.prompt();
process.stdout.writeold = process.stdout.write;
process.stdout.write = (s) => {
process.stdout.writeold(s.replace(/[^\r\n]/g, ""));
};
rl.once("line", (line) => {
process.stdout.write = process.stdout.writeold;
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: "Confirm password: "
});
rl.prompt();
process.stdout.writeold = process.stdout.write;
process.stdout.write = (s) => {
process.stdout.writeold(s.replace(/[^\r\n]/g, ""));
};
rl.on("line", (line2) => {
process.stdout.write = process.stdout.writeold;
rl.close();
if (line != line2) callback(false);
else callback(line);
});
});
}
function promptAlgorithms(callback, bypass, pbkdf2, scrypt) {
if (bypass) {
if (scrypt) {
callback("scrypt");
} else if (pbkdf2) {
callback("pbkdf2");
} else {
callback("sha256");
}
return;
}
let algorithms = {
sha256:
"Salted SHA256 (1 iteration) - fastest and uses least memory, but less secure",
pbkdf2:
"PBKDF2 (PBKDF2-HMAC-SHA512, 36250 iterations) - more secure and uses less memory, but slower",
scrypt:
"scrypt (N=2^14, r=8, p=1) - faster and more secure, but uses more memory"
};
if (
!crypto.pbkdf2 ||
(process.isBun &&
!(
process.versions.bun &&
!process.versions.bun.match(
/^(?:0\.|1\.0\.|1\.1\.[0-9](?![0-9])|1\.1\.1[0-2](?![0-9]))/
)
))
)
delete algorithms.pbkdf2;
const algorithmNames = Object.keys(algorithms);
if (algorithmNames.length < 2) callback(algorithmNames[0]);
console.log("Select password hashing algorithm. Available algorithms:");
for (var i = 0; i < algorithmNames.length; i++) {
console.log(algorithmNames[i] + " - " + algorithms[algorithmNames[i]]);
}
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: "Algorithm: "
});
rl.prompt();
rl.on("line", (line) => {
rl.close();
line = line.trim();
if (!algorithms[line]) callback(false);
else callback(line);
});
}
const userindex = getUserIndex(user);
if (action == "add" && userindex != -1) {
console.log("User alerady exists.");
process.exit(1);
} else if (action != "add" && userindex == -1) {
console.log("User doesn't exist.");
process.exit(1);
}
if (action == "delete") {
users.splice(userindex, 1);
saveConfig();
console.log("User deleted successfully");
} else if (action == "add") {
promptAlgorithms((algorithm) => {
if (!algorithm) {
console.log("Invalid algorithm!");
process.exit(1);
} else {
password((password) => {
if (!password) {
console.log("Passwords don't match!");
process.exit(1);
} else {
const salt = generateSalt();
let hash = "";
if (algorithm == "scrypt") {
hash = crypto.scryptSync(password, salt, 64).toString("hex");
} else if (algorithm == "pbkdf2") {
hash = crypto
.pbkdf2Sync(password, salt, 36250, 64, "sha512")
.toString("hex");
} else {
hash = sha256(password + salt);
}
users.push({
name: user,
pass: hash,
salt: salt,
pbkdf2: algorithm == "pbkdf2" ? true : undefined,
scrypt: algorithm == "scrypt" ? true : undefined,
__svrpasswd_l2: true
});
saveConfig();
console.log("User added successfully");
}
});
}
});
} else {
promptAlgorithms(
(algorithm) => {
if (!algorithm) {
console.log("Invalid algorithm!");
process.exit(1);
} else {
password((password) => {
if (!password) {
console.log("Passwords don't match!");
process.exit(1);
} else {
var salt = generateSalt();
var hash = "";
if (algorithm == "scrypt") {
hash = crypto.scryptSync(password, salt, 64).toString("hex");
} else if (algorithm == "pbkdf2") {
hash = crypto
.pbkdf2Sync(password, salt, 36250, 64, "sha512")
.toString("hex");
} else {
hash = sha256(password + salt);
}
users[userindex] = {
name: user,
pass: hash,
salt: salt,
pbkdf2: algorithm == "pbkdf2" ? true : undefined,
scrypt: algorithm == "scrypt" ? true : undefined,
__svrpasswd_l2: true
};
saveConfig();
console.log("Password changed successfully");
}
});
}
},
users[userindex].__svrpasswd_l2 && !forcechange,
users[userindex].pbkdf2,
users[userindex].scrypt
);
}

View file

@ -1,588 +0,0 @@
const fs = require("fs");
const http = require("http");
const defaultPageCSS = require("../res/defaultPageCSS.js");
const statusCodes = require("../res/statusCodes.js");
const generateErrorStack = require("../utils/generateErrorStack.js");
const serverHTTPErrorDescs = require("../res/httpErrorDescriptions.js");
const generateServerString = require("../utils/generateServerString.js");
const deepClone = require("../utils/deepClone.js");
let serverconsole = {};
function clientErrorHandler(err, socket) {
const config = deepClone(process.serverConfig);
// Determine the webroot from the current working directory if it is not configured
if (config.wwwroot === undefined) config.wwwroot = process.cwd();
config.generateServerString = () =>
generateServerString(config.exposeServerVersion);
// getCustomHeaders() in SVR.JS 3.x
config.getCustomHeaders = () => Object.assign({}, config.customHeaders);
// Prevent multiple error handlers from one request
if (socket.__assigned__) {
return;
} else {
socket.__assigned__ = true;
}
// Estimate fromMain from SVR.JS 3.x
let fromMain = !(
config.secure &&
!socket.encrypted &&
socket.localPort == config.sport
);
// Define response object similar to Node.JS native one
let res = {
socket: socket,
write: (x) => {
if (err.code === "ECONNRESET" || !socket.writable) {
return;
}
socket.write(x);
},
end: (x) => {
if (err.code === "ECONNRESET" || !socket.writable) {
return;
}
socket.end(x, () => {
try {
socket.destroy();
// eslint-disable-next-line no-unused-vars
} catch (err) {
// Socket is probably already destroyed
}
});
},
writeHead: (code, name, headers) => {
if (code >= 400 && code <= 499) process.err4xxcounter++;
if (code >= 500 && code <= 599) process.err5xxcounter++;
let head = "HTTP/1.1 " + code.toString() + " " + name + "\r\n";
headers = Object.assign({}, headers);
headers["Date"] = new Date().toGMTString();
headers["Connection"] = "close";
Object.keys(headers).forEach((headername) => {
if (headername.toLowerCase() == "set-cookie") {
headers[headername].forEach((headerValueS) => {
if (
// eslint-disable-next-line no-control-regex
headername.match(/[^\x09\x20-\x7e\x80-\xff]|.:/) ||
// eslint-disable-next-line no-control-regex
headerValueS.match(/[^\x09\x20-\x7e\x80-\xff]/)
)
throw new Error(`Invalid header!!! (${headername})`);
head += headername + ": " + headerValueS;
});
} else {
if (
// eslint-disable-next-line no-control-regex
headername.match(/[^\x09\x20-\x7e\x80-\xff]|.:/) ||
// eslint-disable-next-line no-control-regex
headers[headername].match(/[^\x09\x20-\x7e\x80-\xff]/)
)
throw new Error(`Invalid header!!! (${headername})`);
head += headername + ": " + headers[headername];
}
head += "\r\n";
});
head += "\r\n";
res.write(head);
}
};
let reqIdInt = Math.floor(Math.random() * 16777216);
if (reqIdInt == 16777216) reqIdInt = 0;
let reqId =
"0".repeat(6 - reqIdInt.toString(16).length) + reqIdInt.toString(16);
// SVR.JS log facilities
const logFacilities = {
climessage: (msg) => serverconsole.climessage(msg, reqId),
reqmessage: (msg) => serverconsole.reqmessage(msg, reqId),
resmessage: (msg) => serverconsole.resmessage(msg, reqId),
errmessage: (msg) => serverconsole.errmessage(msg, reqId),
locerrmessage: (msg) => serverconsole.locerrmessage(msg, reqId),
locwarnmessage: (msg) => serverconsole.locwarnmessage(msg, reqId),
locmessage: (msg) => serverconsole.locmessage(msg, reqId)
};
socket.on("close", (hasError) => {
if (
!hasError ||
err.code == "ERR_SSL_HTTP_REQUEST" ||
err.message.indexOf("http request") != -1
)
logFacilities.locmessage("Client disconnected.");
else logFacilities.locmessage("Client disconnected due to error.");
});
socket.on("error", () => {});
// Header and footer placeholders
let head = "";
let foot = "";
const responseEnd = (body) => {
// If body is Buffer, then it is converted to String anyway.
res.write(head + body + foot);
res.end();
};
// Server error calling method
const callServerError = (errorCode, extName, stack, ch) => {
if (typeof errorCode !== "number") {
throw new TypeError("HTTP error code parameter needs to be an integer.");
}
// Handle optional parameters
if (extName && typeof extName === "object") {
ch = stack;
stack = extName;
extName = undefined;
} else if (
typeof extName !== "string" &&
extName !== null &&
extName !== undefined
) {
throw new TypeError("Extension name parameter needs to be a string.");
}
if (
stack &&
typeof stack === "object" &&
Object.prototype.toString.call(stack) !== "[object Error]"
) {
ch = stack;
stack = undefined;
} else if (
typeof stack !== "object" &&
typeof stack !== "string" &&
stack
) {
throw new TypeError(
"Error stack parameter needs to be either a string or an instance of Error object."
);
}
// Determine error file
const getErrorFileName = (list, callback, _i) => {
if (
err.code == "ERR_SSL_HTTP_REQUEST" &&
process.version &&
parseInt(process.version.split(".")[0].substring(1)) >= 16
) {
// Disable custom error page for HTTP SSL error
callback(errorCode.toString() + ".html");
return;
}
const medCallback = (p) => {
if (p) callback(p);
else {
if (errorCode == 404) {
fs.access(config.page404, fs.constants.F_OK, (err) => {
if (err) {
fs.access(
config.wwwroot + "/." + errorCode.toString(),
fs.constants.F_OK,
(err) => {
try {
if (err) {
callback(errorCode.toString() + ".html");
} else {
callback(config.wwwroot + "/." + errorCode.toString());
}
} catch (err2) {
callServerError(500, err2);
}
}
);
} else {
try {
callback(config.page404);
} catch (err2) {
callServerError(500, err2);
}
}
});
} else {
fs.access(
config.wwwroot + "/." + errorCode.toString(),
fs.constants.F_OK,
(err) => {
try {
if (err) {
callback(errorCode.toString() + ".html");
} else {
callback(config.wwwroot + "/." + errorCode.toString());
}
} catch (err2) {
callServerError(500, err2);
}
}
);
}
}
};
if (!_i) _i = 0;
if (_i >= list.length) {
medCallback(false);
return;
}
if (list[_i].scode != errorCode) {
getErrorFileName(list, callback, _i + 1);
return;
} else {
fs.access(list[_i].path, fs.constants.F_OK, (err) => {
if (err) {
getErrorFileName(list, callback, _i + 1);
} else {
medCallback(list[_i].path);
}
});
}
};
getErrorFileName(config.errorPages, (errorFile) => {
if (Object.prototype.toString.call(stack) === "[object Error]")
stack = generateErrorStack(stack);
if (stack === undefined)
stack = generateErrorStack(new Error("Unknown error"));
if (errorCode == 500 || errorCode == 502) {
logFacilities.errmessage(
"There was an error while processing the request!"
);
logFacilities.errmessage("Stack:");
logFacilities.errmessage(stack);
}
if (config.stackHidden) stack = "[error stack hidden]";
if (serverHTTPErrorDescs[errorCode] === undefined) {
callServerError(501, extName, stack);
} else {
let cheaders = { ...config.getCustomHeaders(), ...ch };
cheaders["Content-Type"] = "text/html; charset=utf-8";
if (errorCode == 405 && !cheaders["Allow"])
cheaders["Allow"] = "GET, POST, HEAD, OPTIONS";
if (
err.code == "ERR_SSL_HTTP_REQUEST" &&
process.version &&
parseInt(process.version.split(".")[0].substring(1)) >= 16
) {
// Disable custom error page for HTTP SSL error
res.writeHead(errorCode, statusCodes[errorCode], cheaders);
res.write(
`<!DOCTYPE html><html><head><title>{errorMessage}</title><meta name="viewport" content="width=device-width, initial-scale=1.0" /><style>${defaultPageCSS}${"</style></head><body><h1>{errorMessage}</h1><p>{errorDesc}</p><p><i>{server}</i></p></body></html>"
.replace(
/{errorMessage}/g,
errorCode.toString() +
" " +
statusCodes[errorCode]
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
)
.replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode])
.replace(
/{stack}/g,
stack
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/\r\n/g, "<br/>")
.replace(/\n/g, "<br/>")
.replace(/\r/g, "<br/>")
.replace(/ {2}/g, "&nbsp;&nbsp;")
)
.replace(
/{server}/g,
(
config.generateServerString() +
(!config.exposeModsInErrorPages || extName == undefined
? ""
: " " + extName)
)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
)
.replace(
/{contact}/g,
config.serverAdministratorEmail
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/\./g, "[dot]")
.replace(/@/g, "[at]")
)}`
);
res.end();
} else {
fs.readFile(errorFile, (err, data) => {
try {
if (err) throw err;
res.writeHead(errorCode, statusCodes[errorCode], cheaders);
responseEnd(
data
.toString()
.replace(
/{errorMessage}/g,
errorCode.toString() +
" " +
statusCodes[errorCode]
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
)
.replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode])
.replace(
/{stack}/g,
stack
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/\r\n/g, "<br/>")
.replace(/\n/g, "<br/>")
.replace(/\r/g, "<br/>")
.replace(/ {2}/g, "&nbsp;&nbsp;")
)
.replace(
/{server}/g,
(
config.generateServerString() +
(!config.exposeModsInErrorPages || extName == undefined
? ""
: " " + extName)
)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
)
.replace(
/{contact}/g,
config.serverAdministratorEmail
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/\./g, "[dot]")
.replace(/@/g, "[at]")
)
);
} catch (err) {
let additionalError = 500;
if (err.code == "ENOENT") {
additionalError = 404;
} else if (err.code == "ENOTDIR") {
additionalError = 404; // Assume that file doesn't exist
} else if (err.code == "EACCES") {
additionalError = 403;
} else if (err.code == "ENAMETOOLONG") {
additionalError = 414;
} else if (err.code == "EMFILE") {
additionalError = 503;
} else if (err.code == "ELOOP") {
additionalError = 508;
}
res.writeHead(errorCode, statusCodes[errorCode], cheaders);
res.write(
`<!DOCTYPE html><html><head><title>{errorMessage}</title><meta name="viewport" content="width=device-width, initial-scale=1.0" /><style>${defaultPageCSS}</style></head><body><h1>{errorMessage}</h1><p>{errorDesc}</p>${
additionalError == 404
? ""
: "<p>Additionally, a {additionalError} error occurred while loading an error page.</p>"
}<p><i>{server}</i></p></body></html>`
.replace(
/{errorMessage}/g,
errorCode.toString() +
" " +
statusCodes[errorCode]
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
)
.replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode])
.replace(
/{stack}/g,
stack
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/\r\n/g, "<br/>")
.replace(/\n/g, "<br/>")
.replace(/\r/g, "<br/>")
.replace(/ {2}/g, "&nbsp;&nbsp;")
)
.replace(
/{server}/g,
(
config.generateServerString() +
(!config.exposeModsInErrorPages || extName == undefined
? ""
: " " + extName)
)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
)
.replace(
/{contact}/g,
config.serverAdministratorEmail
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/\./g, "[dot]")
.replace(/@/g, "[at]")
)
.replace(/{additionalError}/g, additionalError.toString())
);
res.end();
}
});
}
}
});
};
let reqip = socket.remoteAddress;
let reqport = socket.remotePort;
process.reqcounter++;
process.malformedcounter++;
logFacilities.locmessage(
`Somebody connected to ${
config.secure && fromMain
? (typeof config.sport == "number" ? "port " : "socket ") + config.sport
: (typeof config.port == "number" ? "port " : "socket ") + config.port
}...`
);
logFacilities.reqmessage(
`Client ${
!reqip || reqip == ""
? "[unknown client]"
: reqip +
(reqport && reqport !== 0 && reqport != "" ? ":" + reqport : "")
} sent invalid request.`
);
try {
head = fs.existsSync(`${config.wwwroot}/.head`)
? fs.readFileSync(`${config.wwwroot}/.head`).toString()
: fs.existsSync(`${config.wwwroot}/head.html`)
? fs.readFileSync(`${config.wwwroot}/head.html`).toString()
: ""; // header
foot = fs.existsSync(`${config.wwwroot}/.foot`)
? fs.readFileSync(`${config.wwwroot}/.foot`).toString()
: fs.existsSync(`${config.wwwroot}/foot.html`)
? fs.readFileSync(`${config.wwwroot}/foot.html`).toString()
: ""; // footer
if (
(err.code &&
(err.code.indexOf("ERR_SSL_") == 0 ||
err.code.indexOf("ERR_TLS_") == 0)) ||
(!err.code && err.message.indexOf("SSL routines") != -1)
) {
if (
err.code == "ERR_SSL_HTTP_REQUEST" ||
err.message.indexOf("http request") != -1
) {
logFacilities.errmessage("Client sent HTTP request to HTTPS port.");
callServerError(497);
return;
} else {
logFacilities.errmessage(
`An SSL error occured: ${err.code ? err.code : err.message}`
);
callServerError(400);
return;
}
}
if (err.code && err.code.indexOf("ERR_HTTP2_") == 0) {
logFacilities.errmessage(`An HTTP/2 error occured: ${err.code}`);
callServerError(400);
return;
}
if (err.code && err.code == "ERR_HTTP_REQUEST_TIMEOUT") {
logFacilities.errmessage("Client timed out.");
callServerError(408);
return;
}
if (!err.rawPacket) {
logFacilities.errmessage("Connection ended prematurely.");
callServerError(400);
return;
}
const packetLines = err.rawPacket.toString().split("\r\n");
if (packetLines.length == 0) {
logFacilities.errmessage("Invalid request.");
callServerError(400);
return;
}
const checkHeaders = (beginsFromFirst) => {
for (let i = beginsFromFirst ? 0 : 1; i < packetLines.length; i++) {
const header = packetLines[i];
if (header == "")
return false; // Beginning of body
else if (header.indexOf(":") < 1) {
logFacilities.errmessage("Invalid header.");
callServerError(400);
return true;
} else if (header.length > 8192) {
logFacilities.errmessage("Header too large.");
callServerError(431); // Headers too large
return true;
}
}
return false;
};
const packetLine1 = packetLines[0].split(" ");
let method = "GET";
let httpVersion = "HTTP/1.1";
if (String(packetLine1[0]).indexOf(":") > 0) {
if (!checkHeaders(true)) {
logFacilities.errmessage(
"The request is invalid (it may be a part of larger invalid request)."
);
callServerError(400); // Also malformed Packet
return;
}
}
if (String(packetLine1[0]).length < 50) method = packetLine1.shift();
if (String(packetLine1[packetLine1.length - 1]).length < 50)
httpVersion = packetLine1.pop();
if (packetLine1.length != 1) {
logFacilities.errmessage("The head of request is invalid.");
callServerError(400); // Malformed Packet
} else if (!httpVersion.toString().match(/^HTTP[/]/i)) {
logFacilities.errmessage("Invalid protocol.");
callServerError(400); // bad protocol version
} else if (http.METHODS.indexOf(method) == -1) {
logFacilities.errmessage("Invalid method.");
callServerError(405); // Also malformed Packet
} else {
if (checkHeaders(false)) return;
if (packetLine1[0].length > 255) {
logFacilities.errmessage("URI too long.");
callServerError(414); // Also malformed Packet
} else {
logFacilities.errmessage("The request is invalid.");
callServerError(400); // Also malformed Packet
}
}
// eslint-disable-next-line no-unused-vars
} catch (err) {
logFacilities.errmessage(
"There was an error while determining type of malformed request."
);
callServerError(400);
}
}
module.exports = (serverconsoleO) => {
serverconsole = serverconsoleO;
return clientErrorHandler;
};

View file

@ -1,60 +0,0 @@
const deepClone = require("../utils/deepClone.js");
let serverconsole = {};
// eslint-disable-next-line no-unused-vars
function noproxyHandler(req, socket, head) {
let reqIdInt = Math.floor(Math.random() * 16777216);
if (reqIdInt == 16777216) reqIdInt = 0;
const reqId =
"0".repeat(6 - reqIdInt.toString(16).length) + reqIdInt.toString(16);
// SVR.JS log facilities
const logFacilities = {
climessage: (msg) => serverconsole.climessage(msg, reqId),
reqmessage: (msg) => serverconsole.reqmessage(msg, reqId),
resmessage: (msg) => serverconsole.resmessage(msg, reqId),
errmessage: (msg) => serverconsole.errmessage(msg, reqId),
locerrmessage: (msg) => serverconsole.locerrmessage(msg, reqId),
locwarnmessage: (msg) => serverconsole.locwarnmessage(msg, reqId),
locmessage: (msg) => serverconsole.locmessage(msg, reqId)
};
socket.on("close", (hasError) => {
if (!hasError) serverconsole.locmessage("Client disconnected.");
else serverconsole.locmessage("Client disconnected due to error.");
});
socket.on("error", () => {});
// SVR.JS configuration object (modified)
const config = deepClone(process.serverConfig);
const reqip = socket.remoteAddress;
const reqport = socket.remotePort;
process.reqcounter++;
logFacilities.locmessage(
`Somebody connected to ${
config.secure
? (typeof config.sport == "number" ? "port " : "socket ") + config.sport
: (typeof config.port == "number" ? "port " : "socket ") + config.port
}...`
);
logFacilities.reqmessage(
`Client ${
!reqip || reqip == ""
? "[unknown client]"
: reqip +
(reqport && reqport !== 0 && reqport != "" ? ":" + reqport : "")
} wants to proxy ${req.url} through this server`
);
if (req.headers["user-agent"] != undefined)
logFacilities.reqmessage(`Client uses ${req.headers["user-agent"]}`);
logFacilities.errmessage("This server will never be a proxy.");
if (!socket.destroyed) socket.end("HTTP/1.1 501 Not Implemented\n\n");
}
module.exports = (serverconsoleO) => {
serverconsole = serverconsoleO;
return noproxyHandler;
};

View file

@ -1,98 +0,0 @@
const generateServerString = require("../utils/generateServerString");
const deepClone = require("../utils/deepClone.js");
const svrjsInfo = require("../../svrjs.json");
const { name } = svrjsInfo;
let serverconsole = {};
let middleware = [];
function proxyHandler(req, socket, head) {
let reqIdInt = Math.floor(Math.random() * 16777216);
if (reqIdInt == 16777216) reqIdInt = 0;
const reqId =
"0".repeat(6 - reqIdInt.toString(16).length) + reqIdInt.toString(16);
// SVR.JS log facilities
const logFacilities = {
climessage: (msg) => serverconsole.climessage(msg, reqId),
reqmessage: (msg) => serverconsole.reqmessage(msg, reqId),
resmessage: (msg) => serverconsole.resmessage(msg, reqId),
errmessage: (msg) => serverconsole.errmessage(msg, reqId),
locerrmessage: (msg) => serverconsole.locerrmessage(msg, reqId),
locwarnmessage: (msg) => serverconsole.locwarnmessage(msg, reqId),
locmessage: (msg) => serverconsole.locmessage(msg, reqId)
};
socket.on("close", (hasError) => {
if (!hasError) serverconsole.locmessage("Client disconnected.");
else serverconsole.locmessage("Client disconnected due to error.");
});
socket.on("error", () => {});
// SVR.JS configuration object (modified)
const config = deepClone(process.serverConfig);
// Determine the webroot from the current working directory if it is not configured
if (config.wwwroot === undefined) config.wwwroot = process.cwd();
config.generateServerString = () =>
generateServerString(config.exposeServerVersion);
const reqip = socket.remoteAddress;
const reqport = socket.remotePort;
process.reqcounter++;
logFacilities.locmessage(
`Somebody connected to ${
config.secure
? (typeof config.sport == "number" ? "port " : "socket ") + config.sport
: (typeof config.port == "number" ? "port " : "socket ") + config.port
}...`
);
logFacilities.reqmessage(
`Client ${
!reqip || reqip == ""
? "[unknown client]"
: reqip +
(reqport && reqport !== 0 && reqport != "" ? ":" + reqport : "")
} wants to proxy ${req.url} through this server`
);
if (req.headers["user-agent"] != undefined)
logFacilities.reqmessage(`Client uses ${req.headers["user-agent"]}`);
let index = 0;
// Call the next middleware function
const next = () => {
let currentMiddleware = middleware[index++];
while (currentMiddleware && !currentMiddleware.proxy) {
currentMiddleware = middleware[index++];
}
if (currentMiddleware) {
try {
currentMiddleware.proxy(req, socket, head, logFacilities, config, next);
} catch (err) {
logFacilities.errmessage(
"There was an error while processing the request!"
);
logFacilities.errmessage("Stack:");
logFacilities.errmessage(err.stack);
if (!socket.destroyed)
socket.end("HTTP/1.1 500 Internal Server Error\n\n");
}
} else {
logFacilities.errmessage(
`${name} doesn't support proxy without proxy mod.`
);
if (!socket.destroyed) socket.end("HTTP/1.1 501 Not Implemented\n\n");
}
};
// Handle middleware
next();
}
module.exports = (serverconsoleO, middlewareO) => {
serverconsole = serverconsoleO;
middleware = middlewareO;
return proxyHandler;
};

View file

@ -1,746 +0,0 @@
const fs = require("fs");
const net = require("net");
const defaultPageCSS = require("../res/defaultPageCSS.js");
const generateErrorStack = require("../utils/generateErrorStack.js");
const serverHTTPErrorDescs = require("../res/httpErrorDescriptions.js");
const fixNodeMojibakeURL = require("../utils/urlMojibakeFixer.js");
const ipMatch = require("../utils/ipMatch.js");
const matchHostname = require("../utils/matchHostname.js");
const generateServerString = require("../utils/generateServerString.js");
const parseURL = require("../utils/urlParser.js");
const deepClone = require("../utils/deepClone.js");
const statusCodes = require("../res/statusCodes.js");
let serverconsole = {};
let middleware = [];
function requestHandler(req, res) {
let reqIdInt = Math.floor(Math.random() * 16777216);
if (reqIdInt == 16777216) reqIdInt = 0;
const reqId =
"0".repeat(6 - reqIdInt.toString(16).length) + reqIdInt.toString(16);
// SVR.JS log facilities
const logFacilities = {
climessage: (msg) => serverconsole.climessage(msg, reqId),
reqmessage: (msg) => serverconsole.reqmessage(msg, reqId),
resmessage: (msg) => serverconsole.resmessage(msg, reqId),
errmessage: (msg) => serverconsole.errmessage(msg, reqId),
locerrmessage: (msg) => serverconsole.locerrmessage(msg, reqId),
locwarnmessage: (msg) => serverconsole.locwarnmessage(msg, reqId),
locmessage: (msg) => serverconsole.locmessage(msg, reqId)
};
// SVR.JS configuration object (modified)
const config = deepClone(process.serverConfig);
config.generateServerString = () =>
generateServerString(config.exposeServerVersion);
// Determine the webroot from the current working directory if it is not configured
if (config.wwwroot === undefined) config.wwwroot = process.cwd();
// getCustomHeaders() in SVR.JS 3.x
config.getCustomHeaders = () => {
let ph = Object.assign({}, config.customHeaders);
if (config.customHeadersVHost) {
let vhostP = null;
config.customHeadersVHost.every((vhost) => {
if (
matchHostname(vhost.host, req.headers.host) &&
ipMatch(vhost.ip, req.socket ? req.socket.localAddress : undefined)
) {
vhostP = vhost;
return false;
} else {
return true;
}
});
if (vhostP && vhostP.headers) ph = { ...ph, ...vhostP.headers };
}
Object.keys(ph).forEach((phk) => {
if (typeof ph[phk] == "string")
ph[phk] = ph[phk].replace(/\{path\}/g, req.url);
});
ph["Server"] = config.generateServerString();
return ph;
};
// Estimate fromMain from SVR.JS 3.x
let fromMain = !(config.secure && !req.socket.encrypted);
// Make HTTP/1.x API-based scripts compatible with HTTP/2.0 API
if (config.enableHTTP2 == true && req.httpVersion == "2.0") {
// Set HTTP/1.x methods (to prevent process warnings)
res.writeHeadNodeApi = res.writeHead;
res.setHeaderNodeApi = res.setHeader;
res.writeHead = (a, b, c) => {
let table = c;
if (typeof b == "object") table = b;
if (table == undefined) table = this.tHeaders;
if (table == undefined) table = {};
table = Object.assign({}, table);
Object.keys(table).forEach((key) => {
const al = key.toLowerCase();
if (
al == "transfer-encoding" ||
al == "connection" ||
al == "keep-alive" ||
al == "upgrade"
)
delete table[key];
});
if (res.stream && res.stream.destroyed) {
return false;
} else {
return res.writeHeadNodeApi(a, table);
}
};
res.setHeader = (headerName, headerValue) => {
const al = headerName.toLowerCase();
if (
al != "transfer-encoding" &&
al != "connection" &&
al != "keep-alive" &&
al != "upgrade"
)
return res.setHeaderNodeApi(headerName, headerValue);
return false;
};
// Set HTTP/1.x headers
if (!req.headers.host) req.headers.host = req.headers[":authority"];
if (!req.url) req.url = req.headers[":path"];
if (!req.protocol) req.protocol = req.headers[":scheme"];
if (!req.method) req.method = req.headers[":method"];
if (
req.headers[":path"] == undefined ||
req.headers[":method"] == undefined
) {
let err = new Error(
'Either ":path" or ":method" pseudoheader is missing.'
);
if (Buffer.alloc) err.rawPacket = Buffer.alloc(0);
if (req.socket && req.socket.server)
req.socket.server.emit("clientError", err, req.socket);
}
}
if (
req.headers["x-svr-js-from-main-thread"] == "true" &&
req.socket &&
(!req.socket.remoteAddress ||
req.socket.remoteAddress == "::1" ||
req.socket.remoteAddress == "::ffff:127.0.0.1" ||
req.socket.remoteAddress == "127.0.0.1" ||
req.socket.remoteAddress == "localhost")
) {
let headers = config.getCustomHeaders();
res.writeHead(204, statusCodes[204], headers);
res.end();
return;
}
req.url = fixNodeMojibakeURL(req.url);
let headWritten = false;
let lastStatusCode = null;
res.writeHeadNative = res.writeHead;
res.writeHead = (code, codeDescription, headers) => {
if (
!(
headWritten &&
process.isBun &&
code === lastStatusCode &&
codeDescription === undefined &&
codeDescription === undefined
)
) {
if (headWritten) {
process.emitWarning("res.writeHead called multiple times.", {
code: "WARN_SVRJS_MULTIPLE_WRITEHEAD"
});
return res;
} else {
headWritten = true;
}
if (code >= 400 && code <= 599) {
if (code >= 400 && code <= 499) process.err4xxcounter++;
else if (code >= 500 && code <= 599) process.err5xxcounter++;
logFacilities.errmessage(
"Server responded with " + code.toString() + " code."
);
} else {
logFacilities.resmessage(
"Server responded with " + code.toString() + " code."
);
}
if (typeof codeDescription != "string" && statusCodes[code]) {
if (!headers) headers = codeDescription;
codeDescription = statusCodes[code];
}
lastStatusCode = code;
}
res.writeHeadNative(code, codeDescription, headers);
};
let finished = false;
res.on("finish", () => {
if (!finished) {
finished = true;
logFacilities.locmessage("Client disconnected.");
}
});
res.on("close", () => {
if (!finished) {
finished = true;
logFacilities.locmessage("Client disconnected.");
}
});
req.isProxy = false;
if (req.url[0] != "/" && req.url != "*") req.isProxy = true;
logFacilities.locmessage(
`Somebody connected to ${
config.secure && fromMain
? (typeof config.sport == "number" ? "port " : "socket ") + config.sport
: (typeof config.port == "number" ? "port " : "socket ") + config.port
}...`
);
if (req.socket == null) {
logFacilities.errmessage("Client socket is null!!!");
return;
}
// Set up X-Forwarded-For
let reqip = req.socket.remoteAddress;
let reqport = req.socket.remotePort;
let oldip = "";
let oldport = "";
let isForwardedValid = true;
if (config.enableIPSpoofing) {
if (req.headers["x-forwarded-for"] != undefined) {
let preparedReqIP = req.headers["x-forwarded-for"]
.split(",")[0]
.replace(/ /g, "");
let preparedReqIPvalid = net.isIP(preparedReqIP);
if (preparedReqIPvalid) {
if (
preparedReqIPvalid == 4 &&
req.socket.remoteAddress &&
req.socket.remoteAddress.indexOf(":") > -1
)
preparedReqIP = "::ffff:" + preparedReqIP;
reqip = preparedReqIP;
reqport = null;
try {
oldport = req.socket.remotePort;
oldip = req.socket.remoteAddress;
req.socket.realRemotePort = reqport;
req.socket.realRemoteAddress = reqip;
req.socket.originalRemotePort = oldport;
req.socket.originalRemoteAddress = oldip;
res.socket.realRemotePort = reqport;
res.socket.realRemoteAddress = reqip;
res.socket.originalRemotePort = oldport;
res.socket.originalRemoteAddress = oldip;
// eslint-disable-next-line no-unused-vars
} catch (err) {
// Address setting failed
}
} else {
isForwardedValid = false;
}
}
}
process.reqcounter++;
// Process the Host header
let oldHostHeader = req.headers.host;
if (typeof req.headers.host == "string") {
req.headers.host = req.headers.host.toLowerCase();
if (!req.headers.host.match(/^\.+$/))
req.headers.host = req.headers.host.replace(/\.$/, "");
}
logFacilities.reqmessage(
`Client ${
!reqip || reqip == ""
? "[unknown client]"
: reqip +
(reqport && reqport !== 0 && reqport != "" ? ":" + reqport : "")
} wants ${
req.method == "GET"
? "content in "
: req.method == "POST"
? "to post content in "
: req.method == "PUT"
? "to add content in "
: req.method == "DELETE"
? "to delete content in "
: req.method == "PATCH"
? "to patch content in "
: "to access content using " + req.method + " method in "
}${req.headers.host == undefined || req.isProxy ? "" : req.headers.host}${req.url}`
);
if (req.headers["user-agent"] != undefined)
logFacilities.reqmessage(`Client uses ${req.headers["user-agent"]}`);
if (oldHostHeader && oldHostHeader != req.headers.host)
logFacilities.resmessage(
`Host name rewritten: ${oldHostHeader} => ${req.headers.host}`
);
// Header and footer placeholders
res.head = "";
res.foot = "";
res.responseEnd = (body) => {
// If body is Buffer, then it is converted to String anyway.
res.write(res.head + body + res.foot);
res.end();
};
// Server error calling method
res.error = (errorCode, extName, stack, ch) => {
if (typeof errorCode !== "number") {
throw new TypeError("HTTP error code parameter needs to be an integer.");
}
// Handle optional parameters
if (extName && typeof extName === "object") {
ch = stack;
stack = extName;
extName = undefined;
} else if (
typeof extName !== "string" &&
extName !== null &&
extName !== undefined
) {
throw new TypeError("Extension name parameter needs to be a string.");
}
if (
stack &&
typeof stack === "object" &&
Object.prototype.toString.call(stack) !== "[object Error]"
) {
ch = stack;
stack = undefined;
} else if (
typeof stack !== "object" &&
typeof stack !== "string" &&
stack
) {
throw new TypeError(
"Error stack parameter needs to be either a string or an instance of Error object."
);
}
// Determine error file
const getErrorFileName = (list, callback, _i) => {
const medCallback = (p) => {
if (p) callback(p);
else {
if (errorCode == 404) {
fs.access(config.page404, fs.constants.F_OK, (err) => {
if (err) {
fs.access(
config.wwwroot + "/." + errorCode.toString(),
fs.constants.F_OK,
(err) => {
try {
if (err) {
callback(errorCode.toString() + ".html");
} else {
callback(config.wwwroot + "/." + errorCode.toString());
}
} catch (err2) {
res.error(500, err2);
}
}
);
} else {
try {
callback(config.page404);
} catch (err2) {
res.error(500, err2);
}
}
});
} else {
fs.access(
config.wwwroot + "/." + errorCode.toString(),
fs.constants.F_OK,
(err) => {
try {
if (err) {
callback(errorCode.toString() + ".html");
} else {
callback(config.wwwroot + "/." + errorCode.toString());
}
} catch (err2) {
res.error(500, err2);
}
}
);
}
}
};
if (!_i) _i = 0;
if (_i >= list.length) {
medCallback(false);
return;
}
if (
list[_i].scode != errorCode ||
!(
matchHostname(list[_i].host, req.headers.host) &&
ipMatch(list[_i].ip, req.socket ? req.socket.localAddress : undefined)
)
) {
getErrorFileName(list, callback, _i + 1);
return;
} else {
fs.access(list[_i].path, fs.constants.F_OK, (err) => {
if (err) {
getErrorFileName(list, callback, _i + 1);
} else {
medCallback(list[_i].path);
}
});
}
};
getErrorFileName(config.errorPages, (errorFile) => {
// Generate error stack if not provided
if (Object.prototype.toString.call(stack) === "[object Error]")
stack = generateErrorStack(stack);
if (stack === undefined)
stack = generateErrorStack(new Error("Unknown error"));
if (errorCode == 500 || errorCode == 502) {
logFacilities.errmessage(
"There was an error while processing the request!"
);
logFacilities.errmessage("Stack:");
logFacilities.errmessage(stack);
}
// Hide the error stack if specified
if (config.stackHidden) stack = "[error stack hidden]";
// Validate the error code and handle unknown codes
if (serverHTTPErrorDescs[errorCode] === undefined) {
res.error(501, extName, stack);
} else {
// Process custom headers if provided
let cheaders = { ...config.getCustomHeaders(), ...ch };
cheaders["Content-Type"] = "text/html; charset=utf-8";
// Set default Allow header for 405 error if not provided
if (errorCode == 405 && !cheaders["Allow"])
cheaders["Allow"] = "GET, POST, HEAD, OPTIONS";
// Read the error file and replace placeholders with error information
fs.readFile(errorFile, (err, data) => {
try {
if (err) throw err;
res.writeHead(errorCode, statusCodes[errorCode], cheaders);
res.responseEnd(
data
.toString()
.replace(
/{errorMessage}/g,
errorCode.toString() +
" " +
statusCodes[errorCode]
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
)
.replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode])
.replace(
/{stack}/g,
stack
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/\r\n/g, "<br/>")
.replace(/\n/g, "<br/>")
.replace(/\r/g, "<br/>")
.replace(/ {2}/g, "&nbsp;&nbsp;")
)
.replace(
/{path}/g,
req.url
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
)
.replace(
/{server}/g,
"" +
(
config.generateServerString() +
(!config.exposeModsInErrorPages || extName == undefined
? ""
: " " + extName)
)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;") +
(req.headers.host == undefined || req.isProxy
? ""
: " on " +
String(req.headers.host)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;"))
)
.replace(
/{contact}/g,
config.serverAdministratorEmail
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/\./g, "[dot]")
.replace(/@/g, "[at]")
)
); // Replace placeholders in error response
} catch (err) {
let additionalError = 500;
// Handle additional error cases
if (err.code == "ENOENT") {
additionalError = 404;
} else if (err.code == "ENOTDIR") {
additionalError = 404; // Assume that file doesn't exist
} else if (err.code == "EACCES") {
additionalError = 403;
} else if (err.code == "ENAMETOOLONG") {
additionalError = 414;
} else if (err.code == "EMFILE") {
additionalError = 503;
} else if (err.code == "ELOOP") {
additionalError = 508;
}
res.writeHead(errorCode, statusCodes[errorCode], cheaders);
res.write(
`<!DOCTYPE html><html><head><title>{errorMessage}</title><meta name="viewport" content="width=device-width, initial-scale=1.0" /><style>${defaultPageCSS}</style></head><body><h1>{errorMessage}</h1><p>{errorDesc}</p>${
additionalError == 404
? ""
: "<p>Additionally, a {additionalError} error occurred while loading an error page.</p>"
}<p><i>{server}</i></p></body></html>`
.replace(
/{errorMessage}/g,
errorCode.toString() +
" " +
statusCodes[errorCode]
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
)
.replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode])
.replace(
/{stack}/g,
stack
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/\r\n/g, "<br/>")
.replace(/\n/g, "<br/>")
.replace(/\r/g, "<br/>")
.replace(/ {2}/g, "&nbsp;&nbsp;")
)
.replace(
/{path}/g,
req.url
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
)
.replace(
/{server}/g,
"" +
(
config.generateServerString() +
(!config.exposeModsInErrorPages || extName == undefined
? ""
: " " + extName)
)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;") +
(req.headers.host == undefined || req.isProxy
? ""
: " on " +
String(req.headers.host)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;"))
)
.replace(
/{contact}/g,
config.serverAdministratorEmail
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/\./g, "[dot]")
.replace(/@/g, "[at]")
)
.replace(/{additionalError}/g, additionalError.toString())
); // Replace placeholders in error response
res.end();
}
});
}
});
};
// Function to perform HTTP redirection to a specified destination URL
res.redirect = (destination, isTemporary, keepMethod, customHeaders) => {
// If keepMethod is a object, then save it to customHeaders
if (typeof keepMethod == "object") customHeaders = keepMethod;
// If isTemporary is a object, then save it to customHeaders
if (typeof isTemporary == "object") customHeaders = isTemporary;
// If customHeaders are not provided, get the default custom headers
if (customHeaders === undefined) customHeaders = config.getCustomHeaders();
// Set the "Location" header to the destination URL
customHeaders["Location"] = destination;
// Determine the status code for redirection based on the isTemporary and keepMethod flags
const statusCode = keepMethod
? isTemporary
? 307
: 308
: isTemporary
? 302
: 301;
// Write the response header with the appropriate status code and message
res.writeHead(statusCode, statusCodes[statusCode], customHeaders);
// Log the redirection message
logFacilities.resmessage("Client redirected to " + destination);
// End the response
res.end();
// Return from the function
return;
};
try {
res.head = fs.existsSync(`${config.wwwroot}/.head`)
? fs.readFileSync(`${config.wwwroot}/.head`).toString()
: fs.existsSync(`${config.wwwroot}/head.html`)
? fs.readFileSync(`${config.wwwroot}/head.html`).toString()
: ""; // header
res.foot = fs.existsSync(`${config.wwwroot}/.foot`)
? fs.readFileSync(`${config.wwwroot}/.foot`).toString()
: fs.existsSync(`${config.wwwroot}/foot.html`)
? fs.readFileSync(`${config.wwwroot}/foot.html`).toString()
: ""; // footer
} catch (err) {
res.error(500, err);
}
// Authenticated user variable
req.authUser = null;
if (req.url == "*") {
// Handle "*" URL
if (req.method == "OPTIONS") {
// Respond with list of methods
let hdss = config.getCustomHeaders();
hdss["Allow"] = "GET, POST, HEAD, OPTIONS";
res.writeHead(204, statusCodes[204], hdss);
res.end();
return;
} else {
// SVR.JS doesn't understand that request, so throw an 400 error
res.error(400);
return;
}
}
if (req.headers["expect"] && req.headers["expect"] != "100-continue") {
// Expectations not met.
res.error(417);
return;
}
if (req.method == "CONNECT") {
// CONNECT requests should be handled in "connect" event.
res.error(501);
logFacilities.errmessage(
"CONNECT requests aren't supported. Your JS runtime probably doesn't support 'connect' handler for HTTP library."
);
return;
}
if (!isForwardedValid) {
logFacilities.errmessage("X-Forwarded-For header is invalid.");
res.error(400);
return;
}
try {
req.parsedURL = parseURL(
req.url,
"http" +
(req.socket.encrypted ? "s" : "") +
"://" +
(req.headers.host
? req.headers.host
: config.domain
? config.domain
: "unknown.invalid")
);
} catch (err) {
res.error(400, err);
return;
}
let index = 0;
// Call the next middleware function
const next = () => {
let currentMiddleware = middleware[index++];
while (
req.isProxy &&
currentMiddleware &&
currentMiddleware.proxySafe !== false &&
!(currentMiddleware.proxySafe || currentMiddleware.proxy)
) {
currentMiddleware = middleware[index++];
}
if (currentMiddleware) {
try {
currentMiddleware(req, res, logFacilities, config, next);
} catch (err) {
res.error(500, err);
}
} else {
res.error(404);
}
};
// Handle middleware
next();
}
module.exports = (serverconsoleO, middlewareO) => {
serverconsole = serverconsoleO;
middleware = middlewareO;
return requestHandler;
};

View file

@ -1,77 +0,0 @@
const os = require("os");
const cluster = require("../utils/clusterShim.js");
const serverErrorDescs = require("../res/serverErrorDescriptions.js");
let serverconsole = {};
let attmts = 5;
let attmtsRedir = 5;
function serverErrorHandler(err, isRedirect, server, start) {
if (isRedirect) attmtsRedir--;
else attmts--;
if (cluster.isPrimary === undefined && (isRedirect ? attmtsRedir : attmts)) {
serverconsole.locerrmessage(
serverErrorDescs[err.code]
? serverErrorDescs[err.code]
: serverErrorDescs["UNKNOWN"]
);
serverconsole.locmessage(
`${isRedirect ? attmtsRedir : attmts} attempts left.`
);
} else {
try {
process.send(
"\x12ERRLIST" + (isRedirect ? attmtsRedir : attmts) + err.code
);
// eslint-disable-next-line no-unused-vars
} catch (err) {
// Probably main process exited
}
}
if ((isRedirect ? attmtsRedir : attmts) > 0) {
server.close();
setTimeout(start, 900);
} else {
try {
if (cluster.isPrimary !== undefined)
process.send("\x12ERRCRASH" + err.code);
// eslint-disable-next-line no-unused-vars
} catch (err) {
// Probably main process exited
}
setTimeout(() => {
const errno = os.constants.errno[err.code];
process.exit(errno !== undefined ? errno : 1);
}, 50);
}
}
serverErrorHandler.resetAttempts = (isRedirect) => {
if (isRedirect) attmtsRedir = 5;
else attmts = 5;
};
process.messageEventListeners.push((worker, serverconsole) => {
return (message) => {
if (worker.id == Object.keys(cluster.workers)[0]) {
if (message.indexOf("\x12ERRLIST") == 0) {
const tries = parseInt(message.substring(8, 9));
const errCode = message.substring(9);
serverconsole.locerrmessage(
serverErrorDescs[errCode]
? serverErrorDescs[errCode]
: serverErrorDescs["UNKNOWN"]
);
serverconsole.locmessage(`${tries} attempts left.`);
}
if (message.length >= 9 && message.indexOf("\x12ERRCRASH") == 0) {
const errno = os.constants.errno[message.substring(9)];
process.exit(errno !== undefined ? errno : 1);
}
}
};
});
module.exports = (serverconsoleO) => {
serverconsole = serverconsoleO;
return serverErrorHandler;
};

File diff suppressed because it is too large Load diff

View file

@ -1,56 +0,0 @@
const cluster = require("../utils/clusterShim.js");
const ipBlockList = require("../utils/ipBlockList.js");
let blocklist = ipBlockList(process.serverConfig.blacklist);
module.exports = (req, res, logFacilities, config, next) => {
if (
blocklist.check(
req.socket.realRemoteAddress
? req.socket.realRemoteAddress
: req.socket.remoteAddress
)
) {
// Invoke 403 Forbidden error
res.error(403);
logFacilities.errmessage("Client is in the block list.");
return;
}
next();
};
module.exports.commands = {
block: (ip, log, passCommand) => {
if (ip == undefined || JSON.stringify(ip) == "[]") {
if (!cluster.isPrimary === false) log("Cannot block non-existent IP.");
} else {
ip.forEach((ipAddress) => {
if (ipAddress !== "localhost" && ipAddress.indexOf(":") == -1) {
ipAddress = "::ffff:" + ipAddress;
}
if (!blocklist.check(ipAddress)) {
blocklist.add(ipAddress);
}
});
process.serverConfig.blacklist = blocklist.raw;
if (!cluster.isPrimary === false) log("IPs successfully blocked.");
passCommand(ip, log);
}
},
unblock: (ip, log, passCommand) => {
if (ip == undefined || JSON.stringify(ip) == "[]") {
if (!cluster.isPrimary === false) log("Cannot unblock non-existent IP.");
} else {
ip.forEach((ipAddress) => {
if (ipAddress !== "localhost" && ipAddress.indexOf(":") == -1) {
ipAddress = "::ffff:" + ipAddress;
}
blocklist.remove(ipAddress);
});
process.serverConfig.blacklist = blocklist.raw;
if (!cluster.isPrimary === false) log("IPs successfully unblocked.");
passCommand(ip, log);
}
}
};
module.exports.proxySafe = true;

View file

@ -1,125 +0,0 @@
// WARNING: This middleware is optimized for production SVR.JS, and may not work correctly for development SVR.JS.
// Use "npm run dev" to test SVR.JS web server itself.
const {
getInitializePath,
isForbiddenPath,
isIndexOfForbiddenPath,
forbiddenPaths
} = require("../utils/forbiddenPaths.js");
const svrjsInfo = require("../../svrjs.json");
const { name } = svrjsInfo;
const wwwroot =
process.serverConfig && process.serverConfig.wwwroot !== undefined
? process.serverConfig.wwwroot
: ".";
forbiddenPaths.config = getInitializePath(`${wwwroot}/config.json`);
forbiddenPaths.certificates = [];
if (process.serverConfig.secure) {
forbiddenPaths.certificates.push(
getInitializePath(process.serverConfig.cert)
);
forbiddenPaths.certificates.push(getInitializePath(process.serverConfig.key));
Object.keys(process.serverConfig.sni).forEach((sniHostname) => {
forbiddenPaths.certificates.push(
getInitializePath(process.serverConfig.sni[sniHostname].cert)
);
forbiddenPaths.certificates.push(
getInitializePath(process.serverConfig.sni[sniHostname].key)
);
});
}
forbiddenPaths.svrjs = getInitializePath(
wwwroot +
"/" +
(process.dirname[process.dirname.length - 1] != "/"
? process.filename.replace(process.dirname + "/", "")
: process.filename.replace(process.dirname, ""))
);
forbiddenPaths.serverSideScripts = [];
if (process.serverConfig.useWebRootServerSideScript) {
forbiddenPaths.serverSideScripts.push("/serverSideScript.js");
} else {
forbiddenPaths.serverSideScripts.push(
getInitializePath(`${wwwroot}/serverSideScript.js`)
);
}
forbiddenPaths.serverSideScriptDirectories = [];
forbiddenPaths.serverSideScriptDirectories.push(
getInitializePath(`${wwwroot}/node_modules`)
);
forbiddenPaths.serverSideScriptDirectories.push(
getInitializePath(wwwroot + "/mods")
);
forbiddenPaths.temp = getInitializePath(`${wwwroot}/temp`);
forbiddenPaths.log = getInitializePath(`${wwwroot}/log`);
module.exports = (req, res, logFacilities, config, next) => {
let decodedHrefWithoutDuplicateSlashes = "";
try {
decodedHrefWithoutDuplicateSlashes = decodeURIComponent(
req.parsedURL.pathname
).replace(/\/+/g, "/");
// eslint-disable-next-line no-unused-vars
} catch (err) {
res.error(400);
}
// Check if path is forbidden
if (
(isForbiddenPath(decodedHrefWithoutDuplicateSlashes, "config") ||
isForbiddenPath(decodedHrefWithoutDuplicateSlashes, "certificates")) &&
!req.isProxy
) {
res.error(403);
logFacilities.errmessage(
"Access to configuration file/certificates is denied."
);
return;
} else if (
isIndexOfForbiddenPath(decodedHrefWithoutDuplicateSlashes, "temp") &&
!req.isProxy
) {
res.error(403);
logFacilities.errmessage("Access to temporary folder is denied.");
return;
} else if (
isIndexOfForbiddenPath(decodedHrefWithoutDuplicateSlashes, "log") &&
!req.isProxy &&
(config.enableLogging || config.enableLogging == undefined) &&
!config.enableRemoteLogBrowsing
) {
res.error(403);
logFacilities.errmessage("Access to log files is denied.");
return;
} else if (
isForbiddenPath(decodedHrefWithoutDuplicateSlashes, "svrjs") &&
!req.isProxy &&
!config.exposeServerVersion
) {
res.error(403);
logFacilities.errmessage(`Access to ${name} script is denied.`);
return;
} else if (
(isForbiddenPath(decodedHrefWithoutDuplicateSlashes, "svrjs") ||
isForbiddenPath(
decodedHrefWithoutDuplicateSlashes,
"serverSideScripts"
) ||
isIndexOfForbiddenPath(
decodedHrefWithoutDuplicateSlashes,
"serverSideScriptDirectories"
)) &&
!req.isProxy &&
(config.disableServerSideScriptExpose ||
config.disableServerSideScriptExpose === undefined)
) {
res.error(403);
logFacilities.errmessage("Access to sources is denied.");
return;
}
next();
};
module.exports.proxySafe = true;

View file

@ -1,53 +0,0 @@
const defaultPageCSS = require("../res/defaultPageCSS.js");
const statusCodes = require("../res/statusCodes.js");
const svrjsInfo = require("../../svrjs.json");
const { name } = svrjsInfo;
module.exports = (req, res, logFacilities, config, next) => {
if (req.isProxy) {
let eheaders = config.getCustomHeaders();
eheaders["Content-Type"] = "text/html; charset=utf-8";
res.writeHead(501, statusCodes[501], eheaders);
res.write(
`<!DOCTYPE html><html><head><title>Proxy not implemented</title><meta name="viewport" content="width=device-width, initial-scale=1.0" /><style>${defaultPageCSS}</style></head><body><h1>Proxy not implemented</h1><p>${name
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(
/>/g,
"&gt;"
)} doesn't support proxy without proxy mod. If you're administator of this server, then install this mod in order to use ${name
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")} as a proxy.</p><p><i>${config
.generateServerString()
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")}</i></p></body></html>`
);
res.end();
logFacilities.errmessage(
`${name} doesn't support proxy without proxy mod.`
);
return;
}
if (req.method == "OPTIONS") {
let hdss = config.getCustomHeaders();
hdss["Allow"] = "GET, POST, HEAD, OPTIONS";
res.writeHead(204, statusCodes[204], hdss);
res.end();
return;
} else if (
req.method != "GET" &&
req.method != "POST" &&
req.method != "HEAD"
) {
res.error(405);
logFacilities.errmessage("Invaild method: " + req.method);
return;
}
next();
};
module.exports.proxySafe = true;

View file

@ -1,464 +0,0 @@
const os = require("os");
const sha256 = require("../utils/sha256.js");
const createRegex = require("../utils/createRegex.js");
const ipMatch = require("../utils/ipMatch.js");
const matchHostname = require("../utils/matchHostname.js");
const ipBlockList = require("../utils/ipBlockList.js");
const cluster = require("../utils/clusterShim.js");
const svrjsInfo = require("../../svrjs.json");
const { name } = svrjsInfo;
let crypto = {
__disabled__: null
};
try {
crypto = require("crypto");
// eslint-disable-next-line no-unused-vars
} catch (err) {
// Crypto is disabled
}
// Brute force protection-related
let bruteForceDb = {};
// PBKDF2/scrypt cache
let pbkdf2Cache = [];
let scryptCache = [];
let passwordHashCacheIntervalId = -1;
// Non-standard code object
let nonStandardCodes = [];
process.serverConfig.nonStandardCodes.forEach((nonStandardCodeRaw) => {
let newObject = {};
Object.keys(nonStandardCodeRaw).forEach((nsKey) => {
if (nsKey != "users") {
newObject[nsKey] = nonStandardCodeRaw[nsKey];
} else {
newObject["users"] = ipBlockList(nonStandardCodeRaw.users);
}
});
nonStandardCodes.push(newObject);
});
if (!cluster.isPrimary) {
passwordHashCacheIntervalId = setInterval(() => {
pbkdf2Cache = pbkdf2Cache.filter(
(entry) => entry.addDate > new Date() - 3600000
);
scryptCache = scryptCache.filter(
(entry) => entry.addDate > new Date() - 3600000
);
}, 1800000);
}
module.exports = (req, res, logFacilities, config, next) => {
let nonscodeIndex = -1;
let authIndex = -1;
let regexI = [];
let hrefWithoutDuplicateSlashes = "";
const reqip = req.socket.realRemoteAddress
? req.socket.realRemoteAddress
: req.socket.remoteAddress;
// Scan for non-standard codes
if (!req.isProxy && nonStandardCodes != undefined) {
for (let i = 0; i < nonStandardCodes.length; i++) {
if (
matchHostname(nonStandardCodes[i].host, req.headers.host) &&
ipMatch(
nonStandardCodes[i].ip,
req.socket ? req.socket.localAddress : undefined
)
) {
let isMatch = false;
hrefWithoutDuplicateSlashes = req.parsedURL.pathname.replace(
/\/+/g,
"/"
);
if (nonStandardCodes[i].regex) {
// Regex match
const createdRegex = createRegex(nonStandardCodes[i].regex, true);
isMatch =
req.url.match(createdRegex) ||
hrefWithoutDuplicateSlashes.match(createdRegex);
regexI[i] = createdRegex;
} else {
// Non-regex match
isMatch =
nonStandardCodes[i].url == hrefWithoutDuplicateSlashes ||
(os.platform() == "win32" &&
nonStandardCodes[i].url.toLowerCase() ==
hrefWithoutDuplicateSlashes.toLowerCase());
}
if (isMatch) {
if (nonStandardCodes[i].scode == 401) {
// HTTP authentication
if (authIndex == -1) {
authIndex = i;
}
} else {
if (nonscodeIndex == -1) {
if (
(nonStandardCodes[i].scode == 403 ||
nonStandardCodes[i].scode == 451) &&
nonStandardCodes[i].users !== undefined
) {
if (nonStandardCodes[i].users.check(reqip)) nonscodeIndex = i;
} else {
nonscodeIndex = i;
}
}
}
}
}
}
}
// Handle non-standard codes
if (nonscodeIndex > -1) {
let nonscode = nonStandardCodes[nonscodeIndex];
if (
nonscode.scode == 301 ||
nonscode.scode == 302 ||
nonscode.scode == 307 ||
nonscode.scode == 308
) {
let location = "";
if (regexI[nonscodeIndex]) {
location = req.url.replace(regexI[nonscodeIndex], nonscode.location);
if (location == req.url) {
// Fallback replacement
location = hrefWithoutDuplicateSlashes.replace(
regexI[nonscodeIndex],
nonscode.location
);
}
} else if (
req.url.split("?")[1] == undefined ||
req.url.split("?")[1] == null ||
req.url.split("?")[1] == "" ||
req.url.split("?")[1] == " "
) {
location = nonscode.location;
} else {
location = nonscode.location + "?" + req.url.split("?")[1];
}
res.redirect(
location,
nonscode.scode == 302 || nonscode.scode == 307,
nonscode.scode == 307 || nonscode.scode == 308
);
return;
} else {
res.error(nonscode.scode);
if (nonscode.scode == 403) {
logFacilities.errmessage("Content blocked.");
} else if (nonscode.scode == 410) {
logFacilities.errmessage("Content is gone.");
} else if (nonscode.scode == 418) {
logFacilities.errmessage(`${name} is always a teapot ;)`);
} else {
logFacilities.errmessage("Client fails receiving content.");
}
return;
}
}
// Handle HTTP authentication
if (authIndex > -1) {
let authcode = nonStandardCodes[authIndex];
// Function to check if passwords match
const checkIfPasswordMatches = (list, password, callback, _i) => {
if (!_i) _i = 0;
const cb = (hash) => {
if (hash == list[_i].pass) {
callback(true);
} else if (_i >= list.length - 1) {
callback(false);
} else {
checkIfPasswordMatches(list, password, callback, _i + 1);
}
};
let hashedPassword = sha256(password + list[_i].salt);
let cacheEntry = null;
if (list[_i].scrypt) {
if (!crypto.scrypt) {
res.error(
500,
new Error(
`${name} doesn't support scrypt-hashed passwords on Node.JS versions without scrypt hash support.`
)
);
return;
} else {
cacheEntry = scryptCache.find(
(entry) =>
entry.password == hashedPassword && entry.salt == list[_i].salt
);
if (cacheEntry) {
cb(cacheEntry.hash);
} else {
crypto.scrypt(password, list[_i].salt, 64, (err, derivedKey) => {
if (err) {
res.error(500, err);
} else {
const key = derivedKey.toString("hex");
scryptCache.push({
hash: key,
password: hashedPassword,
salt: list[_i].salt,
addDate: new Date()
});
cb(key);
}
});
}
}
} else if (list[_i].pbkdf2) {
if (crypto.__disabled__ !== undefined) {
res.error(
500,
new Error(
`${name} doesn't support PBKDF2-hashed passwords on Node.JS versions without crypto support.`
)
);
return;
} else {
cacheEntry = pbkdf2Cache.find(
(entry) =>
entry.password == hashedPassword && entry.salt == list[_i].salt
);
if (cacheEntry) {
cb(cacheEntry.hash);
} else {
crypto.pbkdf2(
password,
list[_i].salt,
36250,
64,
"sha512",
(err, derivedKey) => {
if (err) {
res.error(500, err);
} else {
const key = derivedKey.toString("hex");
pbkdf2Cache.push({
hash: key,
password: hashedPassword,
salt: list[_i].salt,
addDate: new Date()
});
cb(key);
}
}
);
}
}
} else {
cb(hashedPassword);
}
};
const authorizedCallback = (bruteProtection) => {
try {
const ha = config.getCustomHeaders();
ha["WWW-Authenticate"] = `Basic realm="${
authcode.realm
? authcode.realm.replace(/(\\|")/g, "\\$1")
: name + " HTTP Basic Authorization"
}", charset="UTF-8"`;
const credentials = req.headers["authorization"];
if (!credentials) {
res.error(401, ha);
logFacilities.errmessage("Content needs authorization.");
return;
}
const credentialsMatch = credentials.match(/^Basic (.+)$/);
if (!credentialsMatch) {
res.error(401, ha);
logFacilities.errmessage("Malformed credentials.");
return;
}
const decodedCredentials = Buffer.from(
credentialsMatch[1],
"base64"
).toString("utf8");
const decodedCredentialsMatch =
decodedCredentials.match(/^([^:]*):(.*)$/);
if (!decodedCredentialsMatch) {
res.error(401, ha);
logFacilities.errmessage("Malformed credentials.");
return;
}
const username = decodedCredentialsMatch[1];
const password = decodedCredentialsMatch[2];
let usernameMatch = [];
let sha256Count = 0;
let pbkdf2Count = 0;
let scryptCount = 0;
if (!authcode.userList || authcode.userList.indexOf(username) > -1) {
usernameMatch = config.users.filter((entry) => {
if (entry.scrypt) {
scryptCount++;
} else if (entry.pbkdf2) {
pbkdf2Count++;
} else {
sha256Count++;
}
return entry.name == username;
});
}
if (usernameMatch.length == 0) {
// Pushing false user match to prevent time-based user enumeration
let fakeCredentials = {
name: username,
pass: "SVRJSAWebServerRunningOnNodeJS",
salt: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0"
};
if (!process.isBun) {
if (scryptCount > sha256Count && scryptCount > pbkdf2Count) {
fakeCredentials.scrypt = true;
} else if (pbkdf2Count > sha256Count) {
fakeCredentials.pbkdf2 = true;
}
}
usernameMatch.push(fakeCredentials);
}
checkIfPasswordMatches(usernameMatch, password, (authorized) => {
try {
if (!authorized) {
if (bruteProtection) {
if (process.send) {
process.send("\x12AUTHW" + reqip);
} else {
if (!bruteForceDb[reqip])
bruteForceDb[reqip] = {
invalidAttempts: 0
};
bruteForceDb[reqip].invalidAttempts++;
if (bruteForceDb[reqip].invalidAttempts >= 10) {
bruteForceDb[reqip].lastAttemptDate = new Date();
}
}
}
res.error(401, ha);
logFacilities.errmessage(
`User "${String(username).replace(/[\r\n]/g, "")}" failed to log in.`
);
} else {
if (bruteProtection) {
if (process.send) {
process.send("\x12AUTHR" + reqip);
} else {
if (bruteForceDb[reqip])
bruteForceDb[reqip] = {
invalidAttempts: 0
};
}
}
logFacilities.reqmessage(
`Client is logged in as "${String(username).replace(/[\r\n]/g, "")}".`
);
req.authUser = username;
next();
}
} catch (err) {
res.error(500, err);
return;
}
});
} catch (err) {
res.error(500, err);
return;
}
};
if (authcode.disableBruteProtection) {
// Don't brute-force protect it, just do HTTP authentication
authorizedCallback(false);
} else if (!process.send) {
// Query data from JS object database
if (
!bruteForceDb[reqip] ||
!bruteForceDb[reqip].lastAttemptDate ||
new Date() - 300000 >= bruteForceDb[reqip].lastAttemptDate
) {
if (bruteForceDb[reqip] && bruteForceDb[reqip].invalidAttempts >= 10)
bruteForceDb[reqip] = {
invalidAttempts: 5
};
authorizedCallback(true);
} else {
res.error(429);
logFacilities.errmessage("Brute force limit reached!");
}
} else {
// Listen for brute-force protection response
const authMessageListener = (message) => {
if (message == "\x14AUTHA" + reqip || message == "\x14AUTHD" + reqip) {
process.removeListener("message", authMessageListener);
}
if (message == "\x14AUTHD" + reqip) {
res.error(429);
logFacilities.errmessage("Brute force limit reached!");
} else if (message == "\x14AUTHA" + reqip) {
authorizedCallback(true);
}
};
process.on("message", authMessageListener);
process.send("\x12AUTHQ" + reqip);
}
} else {
next();
}
};
// IPC listener for brute force protection
// eslint-disable-next-line no-unused-vars
process.messageEventListeners.push((worker, serverconsole) => {
return (message) => {
let ip = "";
if (message.substring(0, 6) == "\x12AUTHQ") {
ip = message.substring(6);
if (
!bruteForceDb[ip] ||
!bruteForceDb[ip].lastAttemptDate ||
new Date() - 300000 >= bruteForceDb[ip].lastAttemptDate
) {
if (bruteForceDb[ip] && bruteForceDb[ip].invalidAttempts >= 10)
bruteForceDb[ip] = {
invalidAttempts: 5
};
worker.send("\x14AUTHA" + ip);
} else {
worker.send("\x14AUTHD" + ip);
}
} else if (message.substring(0, 6) == "\x12AUTHR") {
ip = message.substring(6);
if (bruteForceDb[ip])
bruteForceDb[ip] = {
invalidAttempts: 0
};
} else if (message.substring(0, 6) == "\x12AUTHW") {
ip = message.substring(6);
if (!bruteForceDb[ip])
bruteForceDb[ip] = {
invalidAttempts: 0
};
bruteForceDb[ip].invalidAttempts++;
if (bruteForceDb[ip].invalidAttempts >= 10) {
bruteForceDb[ip].lastAttemptDate = new Date();
}
}
};
});
module.exports.commands = {
stop: (args, log, passCommand) => {
clearInterval(passwordHashCacheIntervalId);
passCommand(args, log);
}
};
module.exports.proxySafe = true;

View file

@ -1,36 +0,0 @@
const fs = require("fs");
module.exports = (req, res, logFacilities, config, next) => {
// Trailing slash redirection
if (
!req.isProxy &&
!config.disableTrailingSlashRedirects &&
req.parsedURL.pathname[req.parsedURL.pathname.length - 1] != "/" &&
req.originalParsedURL.pathname[req.originalParsedURL.pathname.length - 1] !=
"/"
) {
fs.stat(
config.wwwroot + decodeURIComponent(req.parsedURL.pathname),
(err, stats) => {
if (err || !stats.isDirectory()) {
try {
next();
} catch (err) {
res.error(500, err);
}
} else {
res.redirect(
req.originalParsedURL.pathname +
"/" +
(req.parsedURL.search ? req.parsedURL.search : "") +
(req.parsedURL.hash ? req.parsedURL.hash : "")
);
}
}
);
} else {
next();
}
};
module.exports.proxySafe = true;

View file

@ -1,79 +0,0 @@
module.exports = (req, res, logFacilities, config, next) => {
// Estimate fromMain from SVR.JS 3.x
let fromMain = !(config.secure && !req.socket.encrypted);
// Handle redirects to HTTPS
if (
config.secure &&
!fromMain &&
!config.disableNonEncryptedServer &&
!config.disableToHTTPSRedirect
) {
const hostx = req.headers.host;
if (hostx === undefined) {
logFacilities.errmessage("Host header is missing.");
res.error(400);
return;
}
if (req.isProxy) {
res.error(501);
logFacilities.errmessage("This server will never be a proxy.");
return;
}
const isPublicServer = !(
req.socket.realRemoteAddress
? req.socket.realRemoteAddress
: req.socket.remoteAddress
).match(
/^(?:localhost$|::1$|f[c-d][0-9a-f]{2}:|(?:::ffff:)?(?:(?:127|10)\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}|192\.168\.[0-9]{1,3}\.[0-9]{1,3}|172\.(?:1[6-9]|2[0-9]|3[0-1])\.[0-9]{1,3}\.[0-9]{1,3})$)/i
);
let destinationPort = 0;
const parsedHostx = hostx.match(/(\[[^\]]*\]|[^:]*)(?::([0-9]+))?/);
let hostname = parsedHostx[1];
let hostPort = parsedHostx[2] ? parseInt(parsedHostx[2]) : 80;
if (isNaN(hostPort)) hostPort = 80;
if (
hostPort == config.port ||
(config.port == config.pubport && !isPublicServer)
) {
destinationPort = config.sport;
} else {
destinationPort = config.spubport;
}
res.redirect(
"https://" +
hostname +
(destinationPort == 443 ? "" : ":" + destinationPort) +
req.url
);
return;
}
// Handle redirects to addresses with "www." prefix
if (config.wwwredirect) {
let hostname = req.headers.host.split(":");
let hostport = null;
if (
hostname.length > 1 &&
(hostname[0] != "[" || hostname[hostname.length - 1] != "]")
)
hostport = hostname.pop();
hostname = hostname.join(":");
if (hostname == config.domain && hostname.indexOf("www.") != 0) {
res.redirect(
`${req.socket.encrypted ? "https" : "http"}://www.${hostname}${hostport ? ":" + hostport : ""}${req.url.replace(/\/+/g, "/")}`
);
return;
}
}
next();
};
module.exports.proxySafe = true;

View file

@ -1,17 +0,0 @@
module.exports = (req, res, logFacilities, config, next) => {
if (!req.isProxy) {
const hkh = config.getCustomHeaders();
Object.keys(hkh).forEach((hkS) => {
try {
res.setHeader(hkS, hkh[hkS]);
// eslint-disable-next-line no-unused-vars
} catch (err) {
// Headers will not be set.
}
});
}
next();
};
module.exports.proxySafe = true;

View file

@ -1,160 +0,0 @@
const fs = require("fs");
const createRegex = require("../utils/createRegex.js");
const ipMatch = require("../utils/ipMatch.js");
const sanitizeURL = require("../utils/urlSanitizer.js");
const matchHostname = require("../utils/matchHostname.js");
const parseURL = require("../utils/urlParser.js");
module.exports = (req, res, logFacilities, config, next) => {
try {
decodeURIComponent(req.parsedURL.pathname);
// eslint-disable-next-line no-unused-vars
} catch (err) {
res.error(400);
}
req.originalParsedURL = req.parsedURL;
// Handle URL rewriting
const rewriteURL = (address, map, callback, _fileState, _mapBegIndex) => {
let rewrittenURL = address;
let doCallback = true;
if (!req.isProxy) {
for (let i = _mapBegIndex ? _mapBegIndex : 0; i < map.length; i++) {
let mapEntry = map[i];
if (
req.parsedURL.pathname != "/" &&
(mapEntry.isNotDirectory || mapEntry.isNotFile) &&
!_fileState
) {
fs.stat(
config.wwwroot + decodeURIComponent(req.parsedURL.pathname),
(err, stats) => {
let _fileState = 3;
if (err) {
_fileState = 3;
} else if (stats.isDirectory()) {
_fileState = 2;
} else if (stats.isFile()) {
_fileState = 1;
} else {
_fileState = 3;
}
rewriteURL(address, map, callback, _fileState, i);
}
);
doCallback = false;
break;
}
let tempRewrittenURL = rewrittenURL;
if (!mapEntry.allowDoubleSlashes) {
address = address.replace(/\/+/g, "/");
tempRewrittenURL = address;
}
try {
if (
matchHostname(mapEntry.host, req.headers.host) &&
ipMatch(
mapEntry.ip,
req.socket ? req.socket.localAddress : undefined
) &&
address.match(createRegex(mapEntry.definingRegex)) &&
!(mapEntry.isNotDirectory && _fileState == 2) &&
!(mapEntry.isNotFile && _fileState == 1)
) {
rewrittenURL = tempRewrittenURL;
mapEntry.replacements.forEach((replacement) => {
rewrittenURL = rewrittenURL.replace(
createRegex(replacement.regex),
replacement.replacement
);
});
if (mapEntry.append) rewrittenURL += mapEntry.append;
break;
}
} catch (err) {
doCallback = false;
callback(err, null);
break;
}
}
}
if (doCallback) callback(null, rewrittenURL);
};
// Rewrite URLs
rewriteURL(req.url, config.rewriteMap, (err, rewrittenURL) => {
if (err) {
res.error(500, err);
return;
}
if (rewrittenURL != req.url) {
logFacilities.resmessage(`URL rewritten: ${req.url} => ${rewrittenURL}`);
req.url = rewrittenURL;
try {
req.parsedURL = parseURL(
req.url,
`http${req.socket.encrypted ? "s" : ""}://${
req.headers.host
? req.headers.host
: config.domain
? config.domain
: "unknown.invalid"
}`
);
} catch (err) {
res.error(400, err);
return;
}
const sHref = sanitizeURL(
req.parsedURL.pathname,
config.allowDoubleSlashes
);
const preparedReqUrl2 =
req.parsedURL.pathname +
(req.parsedURL.search ? req.parsedURL.search : "") +
(req.parsedURL.hash ? req.parsedURL.hash : "");
if (
req.url != preparedReqUrl2 ||
sHref !=
req.parsedURL.pathname
.replace(/\/\.(?=\/|$)/g, "/")
.replace(/\/+/g, "/")
) {
res.error(403);
logFacilities.errmessage("Content blocked.");
return;
} else if (sHref != req.parsedURL.pathname) {
const rewrittenAgainURL =
sHref +
(req.parsedURL.search ? req.parsedURL.search : "") +
(req.parsedURL.hash ? req.parsedURL.hash : "");
logFacilities.resmessage(
`URL sanitized: ${req.url} => ${rewrittenAgainURL}`
);
req.url = rewrittenAgainURL;
try {
req.parsedURL = parseURL(
req.url,
`http${req.socket.encrypted ? "s" : ""}://${
req.headers.host
? req.headers.host
: config.domain
? config.domain
: "unknown.invalid"
}`
);
} catch (err) {
res.error(400, err);
return;
}
}
}
next();
});
};
module.exports.proxySafe = true;

View file

@ -1,995 +0,0 @@
const fs = require("fs");
const os = require("os");
const zlib = require("zlib");
const mime = require("mime-types");
const defaultPageCSS = require("../res/defaultPageCSS.js");
const matchHostname = require("../utils/matchHostname.js");
const ipMatch = require("../utils/ipMatch.js");
const createRegex = require("../utils/createRegex.js");
const sha256 = require("../utils/sha256.js");
const sizify = require("../utils/sizify.js");
const statusCodes = require("../res/statusCodes.js");
const svrjsInfo = require("../../svrjs.json");
const { name } = svrjsInfo;
// ETag-related
let ETagDB = {};
const generateETag = (filePath, stat) => {
if (!ETagDB[filePath + "-" + stat.size + "-" + stat.mtime])
ETagDB[filePath + "-" + stat.size + "-" + stat.mtime] = sha256(
filePath + "-" + stat.size + "-" + stat.mtime
);
return ETagDB[filePath + "-" + stat.size + "-" + stat.mtime];
};
// eslint-disable-next-line no-unused-vars
module.exports = (req, res, logFacilities, config, next) => {
const checkPathLevel = (path) => {
// Split the path into an array of components based on "/"
const pathComponents = path.split("/");
// Initialize counters for level up (..) and level down (.)
let levelUpCount = 0;
let levelDownCount = 0;
// Loop through the path components
for (let i = 0; i < pathComponents.length; i++) {
// If the component is "..", decrement the levelUpCount
if (".." === pathComponents[i]) {
levelUpCount--;
}
// If the component is not "." or an empty string, increment the levelDownCount
else if ("." !== pathComponents[i] && "" !== pathComponents[i]) {
levelDownCount++;
}
}
// Calculate the overall level by subtracting levelUpCount from levelDownCount
const overallLevel = levelDownCount - levelUpCount;
// Return the overall level
return overallLevel;
};
const checkForEnabledDirectoryListing = (hostname, localAddress) => {
const main =
config.enableDirectoryListing ||
config.enableDirectoryListing === undefined;
if (!config.enableDirectoryListingVHost) return main;
let vhostP = null;
config.enableDirectoryListingVHost.every((vhost) => {
if (
matchHostname(vhost.host, hostname) &&
ipMatch(vhost.ip, localAddress)
) {
vhostP = vhost;
return false;
} else {
return true;
}
});
if (!vhostP || vhostP.enabled === undefined) return main;
else return vhostP.enabled;
};
let href = req.parsedURL.pathname;
let origHref = req.originalParsedURL.pathname;
let ext = href.match(/[^/]\.([^.]+)$/);
if (!ext) ext = "";
else ext = ext[1].toLowerCase();
let dHref = "";
try {
dHref = decodeURIComponent(href);
// eslint-disable-next-line no-unused-vars
} catch (err) {
res.error(400);
return;
}
let readFrom = config.wwwroot + dHref;
let dirImagesMissing = false;
fs.stat(readFrom, (err, stats) => {
if (err) {
if (err.code == "ENOENT") {
if (
process.dirname != process.cwd() &&
dHref.match(/^\/\.dirimages\/(?:(?!\.png$).)+\.png$/)
) {
dirImagesMissing = true;
readFrom = process.dirname + dHref;
} else {
res.error(404);
logFacilities.errmessage("Resource not found.");
return;
}
} else if (err.code == "ENOTDIR") {
res.error(404); // Assume that file doesn't exist.
logFacilities.errmessage("Resource not found.");
return;
} else if (err.code == "EACCES") {
res.error(403);
logFacilities.errmessage("Access denied.");
return;
} else if (err.code == "ENAMETOOLONG") {
res.error(414);
return;
} else if (err.code == "EMFILE") {
res.error(503);
return;
} else if (err.code == "ELOOP") {
res.error(508); // The symbolic link loop is detected during file system operations.
logFacilities.errmessage("Symbolic link loop detected.");
return;
} else {
res.error(500, err);
return;
}
}
const properDirectoryListingAndStaticFileServe = () => {
if (stats.isFile()) {
let acceptEncoding = req.headers["accept-encoding"];
if (!acceptEncoding) acceptEncoding = "";
let filelen = stats.size;
// ETag code
let fileETag = undefined;
if (config.enableETag == undefined || config.enableETag) {
fileETag = generateETag(href, stats);
// Check if the client's request matches the ETag value (If-None-Match)
const clientETag = req.headers["if-none-match"];
if (clientETag === fileETag) {
res.writeHead(304, statusCodes[304], {
ETag: clientETag
});
res.end();
return;
}
// Check if the client's request doesn't match the ETag value (If-Match)
const ifMatchETag = req.headers["if-match"];
if (ifMatchETag && ifMatchETag !== "*" && ifMatchETag !== fileETag) {
res.error(412, {
ETag: clientETag
});
return;
}
}
// Handle partial content request
if (req.headers["range"]) {
try {
let rhd = config.getCustomHeaders();
rhd["Accept-Ranges"] = "bytes";
rhd["Content-Range"] = `bytes */${filelen}`;
const regexmatch = req.headers["range"].match(
/bytes=([0-9]*)-([0-9]*)/
);
if (!regexmatch) {
res.error(416, rhd);
} else {
// Process the partial content request
const beginOrig = regexmatch[1];
const endOrig = regexmatch[2];
const maxEnd =
filelen -
1 +
(ext == "html" ? res.head.length + res.foot.length : 0);
let begin = 0;
let end = maxEnd;
if (beginOrig == "" && endOrig == "") {
res.error(416, rhd);
return;
} else if (beginOrig == "") {
begin = end - parseInt(endOrig) + 1;
} else {
begin = parseInt(beginOrig);
if (endOrig != "") end = parseInt(endOrig);
}
if (begin > end || begin < 0 || begin > maxEnd) {
res.error(416, rhd);
return;
}
if (end > maxEnd) end = maxEnd;
rhd["Content-Range"] =
"bytes " + begin + "-" + end + "/" + filelen;
rhd["Content-Length"] = end - begin + 1;
delete rhd["Content-Type"];
const mtype = mime.contentType(ext);
if (mtype && ext != "") rhd["Content-Type"] = mtype;
if (fileETag) rhd["ETag"] = fileETag;
if (req.method != "HEAD") {
if (
ext == "html" &&
begin < res.head.length &&
end - begin < res.head.length
) {
res.writeHead(206, statusCodes[206], rhd);
res.end(res.head.substring(begin, end + 1));
return;
} else if (
ext == "html" &&
begin >= res.head.length + filelen
) {
res.writeHead(206, statusCodes[206], rhd);
res.end(
res.foot.substring(
begin - res.head.length - filelen,
end - res.head.length - filelen + 1
)
);
return;
}
let readStream = fs.createReadStream(readFrom, {
start:
ext == "html"
? Math.max(0, begin - res.head.length)
: begin,
end:
ext == "html"
? Math.min(filelen, end - res.head.length)
: end
});
readStream
.on("error", (err) => {
if (err.code == "ENOENT") {
res.error(404);
logFacilities.errmessage("Resource not found.");
} else if (err.code == "ENOTDIR") {
res.error(404); // Assume that file doesn't exist.
logFacilities.errmessage("Resource not found.");
} else if (err.code == "EACCES") {
res.error(403);
logFacilities.errmessage("Access denied.");
} else if (err.code == "ENAMETOOLONG") {
res.error(414);
} else if (err.code == "EMFILE") {
res.error(503);
} else if (err.code == "ELOOP") {
res.error(508); // The symbolic link loop is detected during file system operations.
logFacilities.errmessage("Symbolic link loop detected.");
} else {
res.error(500, err);
}
})
.on("open", () => {
try {
if (ext == "html") {
const afterWriteCallback = () => {
if (
res.foot.length > 0 &&
end > res.head.length + filelen
) {
readStream.on("end", () => {
res.end(
res.foot.substring(
0,
end - res.head.length - filelen + 1
)
);
});
}
readStream.pipe(res, {
end: !(
res.foot.length > 0 &&
end > res.head.length + filelen
)
});
};
res.writeHead(206, statusCodes[206], rhd);
if (res.head.length == 0 || begin > res.head.length) {
afterWriteCallback();
} else if (
!res.write(
res.head.substring(begin, res.head.length - begin)
)
) {
res.on("drain", afterWriteCallback);
} else {
process.nextTick(afterWriteCallback);
}
} else {
res.writeHead(206, statusCodes[206], rhd);
readStream.pipe(res);
}
logFacilities.resmessage(
"Client successfully received content."
);
} catch (err) {
res.error(500, err);
}
});
} else {
res.writeHead(206, statusCodes[206], rhd);
res.end();
}
}
} catch (err) {
res.error(500, err);
}
} else {
// Helper function to check if compression is allowed for the file
const canCompress = (path, list) => {
let canCompress = true;
for (let i = 0; i < list.length; i++) {
if (createRegex(list[i], true).test(path)) {
canCompress = false;
break;
}
}
return canCompress;
};
let useBrotli =
ext != "br" &&
filelen > 256 &&
zlib.createBrotliCompress &&
acceptEncoding.match(/\bbr\b/);
let useDeflate =
ext != "zip" &&
filelen > 256 &&
acceptEncoding.match(/\bdeflate\b/);
let useGzip =
ext != "gz" && filelen > 256 && acceptEncoding.match(/\bgzip\b/);
let isCompressable = true;
try {
// Check for files not to compressed and compression enabling setting. Also check for browser quirks and adjust compression accordingly
if (
(!useBrotli && !useDeflate && !useGzip) ||
config.enableCompression !== true ||
!canCompress(href, config.dontCompress)
) {
isCompressable = false; // Compression is disabled
} else if (
ext != "html" &&
ext != "htm" &&
ext != "xhtml" &&
ext != "xht" &&
ext != "shtml"
) {
if (
/^Mozilla\/4\.[0-9]+(( *\[[^)]*\] *| *)\([^)\]]*\))? *$/.test(
req.headers["user-agent"]
) &&
!/https?:\/\/|[bB][oO][tT]|[sS][pP][iI][dD][eE][rR]|[sS][uU][rR][vV][eE][yY]|MSIE/.test(
req.headers["user-agent"]
)
) {
isCompressable = false; // Netscape 4.x doesn't handle compressed data properly outside of HTML documents.
} else if (/^w3m\/[^ ]*$/.test(req.headers["user-agent"])) {
isCompressable = false; // w3m doesn't handle compressed data properly outside of HTML documents.
}
} else {
if (
/^Mozilla\/4\.0[6-8](( *\[[^)]*\] *| *)\([^)\]]*\))? *$/.test(
req.headers["user-agent"]
) &&
!/https?:\/\/|[bB][oO][tT]|[sS][pP][iI][dD][eE][rR]|[sS][uU][rR][vV][eE][yY]|MSIE/.test(
req.headers["user-agent"]
)
) {
isCompressable = false; // Netscape 4.06-4.08 doesn't handle compressed data properly.
}
}
} catch (err) {
res.error(500, err);
return;
}
// Bun 1.1 has definition for zlib.createBrotliCompress, but throws an error while invoking the function.
if (process.isBun && useBrotli && isCompressable) {
try {
zlib.createBrotliCompress();
// eslint-disable-next-line no-unused-vars
} catch (err) {
useBrotli = false;
}
}
try {
let hdhds = {};
if (useBrotli && isCompressable) {
hdhds["Content-Encoding"] = "br";
} else if (useDeflate && isCompressable) {
hdhds["Content-Encoding"] = "deflate";
} else if (useGzip && isCompressable) {
hdhds["Content-Encoding"] = "gzip";
} else {
if (ext == "html") {
hdhds["Content-Length"] =
res.head.length + filelen + res.foot.length;
} else {
hdhds["Content-Length"] = filelen;
}
}
hdhds["Accept-Ranges"] = "bytes";
delete hdhds["Content-Type"];
const mtype = mime.contentType(ext);
if (mtype && ext != "") hdhds["Content-Type"] = mtype;
if (fileETag) hdhds["ETag"] = fileETag;
if (req.method != "HEAD") {
let readStream = fs.createReadStream(readFrom);
readStream
.on("error", (err) => {
if (err.code == "ENOENT") {
res.error(404);
logFacilities.errmessage("Resource not found.");
} else if (err.code == "ENOTDIR") {
res.error(404); // Assume that file doesn't exist.
logFacilities.errmessage("Resource not found.");
} else if (err.code == "EACCES") {
res.error(403);
logFacilities.errmessage("Access denied.");
} else if (err.code == "ENAMETOOLONG") {
res.error(414);
} else if (err.code == "EMFILE") {
res.error(503);
} else if (err.code == "ELOOP") {
res.error(508); // The symbolic link loop is detected during file system operations.
logFacilities.errmessage("Symbolic link loop detected.");
} else {
res.error(500, err);
}
})
.on("open", () => {
try {
let resStream = {};
if (useBrotli && isCompressable) {
resStream = zlib.createBrotliCompress();
resStream.pipe(res);
} else if (useDeflate && isCompressable) {
resStream = zlib.createDeflateRaw();
resStream.pipe(res);
} else if (useGzip && isCompressable) {
resStream = zlib.createGzip();
resStream.pipe(res);
} else {
resStream = res;
}
if (ext == "html") {
const afterWriteCallback = () => {
if (res.foot.length > 0) {
readStream.on("end", () => {
resStream.end(res.foot);
});
}
readStream.pipe(resStream, {
end: res.foot.length == 0
});
};
res.writeHead(200, statusCodes[200], hdhds);
if (res.head.length == 0) {
afterWriteCallback();
} else if (!resStream.write(res.head)) {
resStream.on("drain", afterWriteCallback);
} else {
process.nextTick(afterWriteCallback);
}
} else {
res.writeHead(200, statusCodes[200], hdhds);
readStream.pipe(resStream);
}
logFacilities.resmessage(
"Client successfully received content."
);
} catch (err) {
res.error(500, err);
}
});
} else {
res.writeHead(200, statusCodes[200], hdhds);
res.end();
logFacilities.resmessage("Client successfully received content.");
}
} catch (err) {
res.error(500, err);
}
}
} else if (stats.isDirectory()) {
// Check if directory listing is enabled in the configuration
if (
checkForEnabledDirectoryListing(
req.headers.host,
req.socket ? req.socket.localAddress : undefined
)
) {
let customDirListingHeader = "";
let customDirListingFooter = "";
const getCustomDirListingHeader = (callback) => {
fs.readFile(
(config.wwwroot + dHref + "/.dirhead").replace(/\/+/g, "/"),
(err, data) => {
if (err) {
if (err.code == "ENOENT" || err.code == "EISDIR") {
if (os.platform != "win32" || href != "/") {
fs.readFile(
(config.wwwroot + dHref + "/HEAD.html").replace(
/\/+/g,
"/"
),
(err, data) => {
if (err) {
if (err.code == "ENOENT" || err.code == "EISDIR") {
callback();
} else {
res.error(500, err);
}
} else {
customDirListingHeader = data.toString();
callback();
}
}
);
} else {
callback();
}
} else {
res.error(500, err);
}
} else {
customDirListingHeader = data.toString();
callback();
}
}
);
};
const getCustomDirListingFooter = (callback) => {
fs.readFile(
(config.wwwroot + dHref + "/.dirfoot").replace(/\/+/g, "/"),
(err, data) => {
if (err) {
if (err.code == "ENOENT" || err.code == "EISDIR") {
if (os.platform != "win32" || href != "/") {
fs.readFile(
(config.wwwroot + dHref + "/FOOT.html").replace(
/\/+/g,
"/"
),
(err, data) => {
if (err) {
if (err.code == "ENOENT" || err.code == "EISDIR") {
callback();
} else {
res.error(500, err);
}
} else {
customDirListingFooter = data.toString();
callback();
}
}
);
} else {
callback();
}
} else {
res.error(500, err);
}
} else {
customDirListingFooter = data.toString();
callback();
}
}
);
};
// Read custom header and footer content (if available)
getCustomDirListingHeader(() => {
getCustomDirListingFooter(() => {
// Check if custom header has HTML tag
const headerHasHTMLTag = customDirListingHeader
.replace(/<!--(?:(?:(?!-->)[\s\S])*|)(?:-->|$)/g, "")
.match(
/<html(?![a-zA-Z0-9])(?:"(?:\\(?:[\s\S]|$)|[^\\"])*(?:"|$)|'(?:\\(?:[\s\S]|$)|[^\\'])*(?:'|$)|[^'">])*(?:>|$)/i
);
// Generate HTML head and footer based on configuration and custom content
let htmlHead = `${
(!config.enableDirectoryListingWithDefaultHead || res.head == ""
? !headerHasHTMLTag
? "<!DOCTYPE html><html><head><title>Directory: " +
decodeURIComponent(origHref)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;") +
'</title><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><style>' +
defaultPageCSS +
"</style></head><body>"
: customDirListingHeader.replace(
/<head>/i,
"<head><title>Directory: " +
decodeURIComponent(origHref)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;") +
"</title>"
)
: res.head.replace(
/<head>/i,
"<head><title>Directory: " +
decodeURIComponent(origHref)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;") +
"</title>"
)) + (!headerHasHTMLTag ? customDirListingHeader : "")
}<h1>Directory: ${decodeURIComponent(origHref)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(
/>/g,
"&gt;"
)}</h1><table id="directoryListing"> <tr> <th></th> <th>Filename</th> <th>Size</th> <th>Date</th> </tr>${
checkPathLevel(decodeURIComponent(origHref)) < 1
? ""
: '<tr><td style="width: 24px;"><img src="/.dirimages/return.png" width="24px" height="24px" alt="[RET]" /></td><td style="word-wrap: break-word; word-break: break-word; overflow-wrap: break-word;"><a href="' +
origHref.replace(/\/+/g, "/").replace(/\/[^/]*\/?$/, "/") +
'">Return</a></td><td></td><td></td></tr>'
}`;
let htmlFoot = `</table><p><i>${config
.generateServerString()
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")}${
req.headers.host == undefined
? ""
: " on " +
String(req.headers.host)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
}</i></p>${customDirListingFooter}${
!config.enableDirectoryListingWithDefaultHead || res.foot == ""
? "</body></html>"
: res.foot
}`;
if (
fs.existsSync(
config.wwwroot +
decodeURIComponent(href) +
"/.maindesc".replace(/\/+/g, "/")
)
) {
htmlFoot =
"</table><hr/>" +
fs.readFileSync(
config.wwwroot +
decodeURIComponent(href) +
"/.maindesc".replace(/\/+/g, "/")
) +
htmlFoot;
}
fs.readdir(readFrom, (err, list) => {
try {
if (err) throw err;
list = list.sort();
// Function to get stats for all files in the directory
const getStatsForAllFilesI = (
fileList,
callback,
prefix,
pushArray,
index
) => {
if (fileList.length == 0) {
callback(pushArray);
return;
}
fs.stat(
(prefix + "/" + fileList[index]).replace(/\/+/g, "/"),
(err, stats) => {
if (err) {
fs.lstat(
(prefix + "/" + fileList[index]).replace(
/\/+/g,
"/"
),
(err, stats) => {
pushArray.push({
name: fileList[index],
stats: err ? null : stats,
errored: true
});
if (index < fileList.length - 1) {
getStatsForAllFilesI(
fileList,
callback,
prefix,
pushArray,
index + 1
);
} else {
callback(pushArray);
}
}
);
} else {
pushArray.push({
name: fileList[index],
stats: stats,
errored: false
});
if (index < fileList.length - 1) {
getStatsForAllFilesI(
fileList,
callback,
prefix,
pushArray,
index + 1
);
} else {
callback(pushArray);
}
}
}
);
};
// Wrapper function to get stats for all files
const getStatsForAllFiles = (fileList, prefix, callback) => {
if (!prefix) prefix = "";
getStatsForAllFilesI(fileList, callback, prefix, [], 0);
};
// Get stats for all files in the directory and generate the listing
getStatsForAllFiles(list, readFrom, (filelist) => {
let directoryListingRows = [];
for (let i = 0; i < filelist.length; i++) {
if (filelist[i].name[0] !== ".") {
const estats = filelist[i].stats;
const ename = filelist[i].name;
let eext = ename.match(/\.([^.]+)$/);
eext = eext ? eext[1] : "";
const emime = eext ? mime.contentType(eext) : false;
if (filelist[i].errored) {
directoryListingRows.push(
`<tr><td style="width: 24px;"><img src="/.dirimages/bad.png" alt="[BAD]" width="24px" height="24px" /></td><td style="word-wrap: break-word; word-break: break-word; overflow-wrap: break-word;"><a href="${(
href +
"/" +
encodeURI(ename)
).replace(/\/+/g, "/")}">${ename
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(
/>/g,
"&gt;"
)}</a></td><td>-</td><td>${estats ? estats.mtime.toDateString() : "-"}</td></tr>\r\n`
);
} else {
let entry = `<tr><td style="width: 24px;"><img src="[img]" alt="[alt]" width="24px" height="24px" /></td><td style="word-wrap: break-word; word-break: break-word; overflow-wrap: break-word;"><a href="${(
origHref +
"/" +
encodeURIComponent(ename)
).replace(
/\/+/g,
"/"
)}${estats.isDirectory() ? "/" : ""}">${ename
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")}</a></td><td>${
estats.isDirectory() ? "-" : sizify(estats.size)
}</td><td>${estats.mtime.toDateString()}</td></tr>\r\n`;
// Determine the file type and set the appropriate image and alt text
if (estats.isDirectory()) {
entry = entry
.replace("[img]", "/.dirimages/directory.png")
.replace("[alt]", "[DIR]");
} else if (!estats.isFile()) {
entry = `<tr><td style="width: 24px;"><img src="[img]" alt="[alt]" width="24px" height="24px" /></td><td style="word-wrap: break-word; word-break: break-word; overflow-wrap: break-word;"><a href="${(
origHref +
"/" +
encodeURIComponent(ename)
).replace(/\/+/g, "/")}">${ename
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(
/>/g,
"&gt;"
)}</a></td><td>-</td><td>${estats.mtime.toDateString()}</td></tr>\r\n`;
// Determine the special file types (block device, character device, etc.)
if (estats.isBlockDevice()) {
entry = entry
.replace("[img]", "/.dirimages/hwdevice.png")
.replace("[alt]", "[BLK]");
} else if (estats.isCharacterDevice()) {
entry = entry
.replace("[img]", "/.dirimages/hwdevice.png")
.replace("[alt]", "[CHR]");
} else if (estats.isFIFO()) {
entry = entry
.replace("[img]", "/.dirimages/fifo.png")
.replace("[alt]", "[FIF]");
} else if (estats.isSocket()) {
entry = entry
.replace("[img]", "/.dirimages/socket.png")
.replace("[alt]", "[SCK]");
}
} else if (ename.match(/README|LICEN[SC]E/i)) {
entry = entry
.replace("[img]", "/.dirimages/important.png")
.replace("[alt]", "[IMP]");
} else if (eext.match(/^(?:[xs]?html?|xml)$/i)) {
entry = entry
.replace("[img]", "/.dirimages/html.png")
.replace(
"[alt]",
eext == "xml" ? "[XML]" : "[HTM]"
);
} else if (eext == "js") {
entry = entry
.replace("[img]", "/.dirimages/javascript.png")
.replace("[alt]", "[JS ]");
} else if (eext == "php") {
entry = entry
.replace("[img]", "/.dirimages/php.png")
.replace("[alt]", "[PHP]");
} else if (eext == "css") {
entry = entry
.replace("[img]", "/.dirimages/css.png")
.replace("[alt]", "[CSS]");
} else if (emime && emime.split("/")[0] == "image") {
entry = entry
.replace("[img]", "/.dirimages/image.png")
.replace(
"[alt]",
eext == "ico" ? "[ICO]" : "[IMG]"
);
} else if (emime && emime.split("/")[0] == "font") {
entry = entry
.replace("[img]", "/.dirimages/font.png")
.replace("[alt]", "[FON]");
} else if (emime && emime.split("/")[0] == "audio") {
entry = entry
.replace("[img]", "/.dirimages/audio.png")
.replace("[alt]", "[AUD]");
} else if (
(emime && emime.split("/")[0] == "text") ||
eext == "json"
) {
entry = entry
.replace("[img]", "/.dirimages/text.png")
.replace(
"[alt]",
eext == "json" ? "[JSO]" : "[TXT]"
);
} else if (emime && emime.split("/")[0] == "video") {
entry = entry
.replace("[img]", "/.dirimages/video.png")
.replace("[alt]", "[VID]");
} else if (
eext.match(/^(?:zip|rar|bz2|[gb7x]z|lzma|tar)$/i)
) {
entry = entry
.replace("[img]", "/.dirimages/archive.png")
.replace("[alt]", "[ARC]");
} else if (eext.match(/^(?:[id]mg|iso|flp)$/i)) {
entry = entry
.replace("[img]", "/.dirimages/diskimage.png")
.replace("[alt]", "[DSK]");
} else {
entry = entry
.replace("[img]", "/.dirimages/other.png")
.replace("[alt]", "[OTH]");
}
directoryListingRows.push(entry);
}
}
}
// Push the information about empty directory
if (directoryListingRows.length == 0) {
directoryListingRows.push(
"<tr><td></td><td>No files found</td><td></td><td></td></tr>"
);
}
// Send the directory listing response
res.writeHead(200, statusCodes[200], {
"Content-Type": "text/html; charset=utf-8"
});
res.end(
htmlHead + directoryListingRows.join("") + htmlFoot
);
logFacilities.resmessage(
"Client successfully received content."
);
});
} catch (err) {
if (err.code == "ENOENT") {
res.error(404);
logFacilities.errmessage("Resource not found.");
} else if (err.code == "ENOTDIR") {
res.error(404); // Assume that file doesn't exist.
logFacilities.errmessage("Resource not found.");
} else if (err.code == "EACCES") {
res.error(403);
logFacilities.errmessage("Access denied.");
} else if (err.code == "ENAMETOOLONG") {
res.error(414);
} else if (err.code == "EMFILE") {
res.error(503);
} else if (err.code == "ELOOP") {
res.error(508); // The symbolic link loop is detected during file system operations.
logFacilities.errmessage("Symbolic link loop detected.");
} else {
res.error(500, err);
}
}
});
});
});
} else {
// Directory listing is disabled, call 403 Forbidden error
res.error(403);
logFacilities.errmessage("Directory listing is disabled.");
}
} else {
res.error(501);
logFacilities.errmessage(
`${name} doesn't support block devices, character devices, FIFOs nor sockets.`
);
return;
}
};
// Check if index file exists
if (!dirImagesMissing && (req.url == "/" || stats.isDirectory())) {
fs.stat((readFrom + "/index.html").replace(/\/+/g, "/"), (e, s) => {
if (e || !s.isFile()) {
fs.stat((readFrom + "/index.htm").replace(/\/+/g, "/"), (e, s) => {
if (e || !s.isFile()) {
fs.stat(
(readFrom + "/index.xhtml").replace(/\/+/g, "/"),
(e, s) => {
if (e || !s.isFile()) {
properDirectoryListingAndStaticFileServe();
} else {
stats = s;
ext = "xhtml";
readFrom = (readFrom + "/index.xhtml").replace(/\/+/g, "/");
properDirectoryListingAndStaticFileServe();
}
}
);
} else {
stats = s;
ext = "htm";
readFrom = (readFrom + "/index.htm").replace(/\/+/g, "/");
properDirectoryListingAndStaticFileServe();
}
});
} else {
stats = s;
ext = "html";
readFrom = (readFrom + "/index.html").replace(/\/+/g, "/");
properDirectoryListingAndStaticFileServe();
}
});
} else if (dirImagesMissing) {
fs.stat(readFrom, (e, s) => {
if (e || !s.isFile()) {
properDirectoryListingAndStaticFileServe();
} else {
stats = s;
properDirectoryListingAndStaticFileServe();
}
});
} else {
properDirectoryListingAndStaticFileServe();
}
});
};
module.exports.proxySafe = true;

View file

@ -1,117 +0,0 @@
const os = require("os");
const defaultPageCSS = require("../res/defaultPageCSS.js");
const sizify = require("../utils/sizify.js");
const statusCodes = require("../res/statusCodes.js");
const svrjsInfo = require("../../svrjs.json");
const { name } = svrjsInfo;
module.exports = (req, res, logFacilities, config, next) => {
if (
config.allowStatus &&
(req.parsedURL.pathname == "/svrjsstatus.svr" ||
(os.platform() == "win32" &&
req.parsedURL.pathname.toLowerCase() == "/svrjsstatus.svr"))
) {
const formatRelativeTime = (relativeTime) => {
const days = Math.floor(relativeTime / 60 / (60 * 24));
const dateDiff = new Date(relativeTime * 1000);
return (
days +
" days, " +
dateDiff.getUTCHours() +
" hours, " +
dateDiff.getUTCMinutes() +
" minutes, " +
dateDiff.getUTCSeconds() +
" seconds"
);
};
let statusBody = "";
statusBody +=
"Server version: " + config.generateServerString() + "<br/><hr/>";
//Those entries are just dates and numbers converted/formatted to strings, so no escaping is needed.
statusBody += `Current time: ${new Date().toString()}<br/>Thread start time: ${new Date(new Date() - process.uptime() * 1000).toString()}<br/>Thread uptime: ${formatRelativeTime(Math.floor(process.uptime()))}<br/>`;
statusBody += `OS uptime: ${formatRelativeTime(os.uptime())}<br/>`;
statusBody += `Total request count: ${process.reqcounter}<br/>`;
statusBody += `Average request rate: ${Math.round((process.reqcounter / process.uptime()) * 100) / 100} requests/s<br/>`;
statusBody += `Client errors (4xx): ${process.err4xxcounter}<br/>`;
statusBody += `Server errors (5xx): ${process.err5xxcounter}<br/>`;
statusBody += `Average error rate: ${
Math.round(
((process.err4xxcounter + process.err5xxcounter) / process.reqcounter) *
10000
) / 100
}%<br/>`;
statusBody += `Malformed HTTP requests: ${process.malformedcounter}`;
if (process.memoryUsage)
statusBody += `<br/>Memory usage of thread: ${sizify(process.memoryUsage().rss, true)}B`;
if (process.cpuUsage)
statusBody += `<br/>Total CPU usage by thread: u${process.cpuUsage().user / 1000}ms s${process.cpuUsage().system / 1000}ms - ${
Math.round(
((process.cpuUsage().user + process.cpuUsage().system) /
1000000 /
process.uptime()) *
1000
) / 1000
}%`;
statusBody += `<br/>Thread PID: ${process.pid}<br/>`;
res.writeHead(200, statusCodes[200], {
"Content-Type": "text/html; charset=utf-8"
});
res.end(
`${
res.head == ""
? "<!DOCTYPE html><html><head><title>" +
name
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;") +
" status" +
(req.headers.host == undefined
? ""
: " for " +
String(req.headers.host)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")) +
'</title><meta name="viewport" content="width=device-width, initial-scale=1.0" /><style>' +
defaultPageCSS +
"</style></head><body>"
: res.head.replace(
/<head>/i,
"<head><title>" +
name
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;") +
" status" +
(req.headers.host == undefined
? ""
: " for " +
String(req.headers.host)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")) +
"</title>"
)
}<h1>${name
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")} status${
req.headers.host == undefined
? ""
: " for " +
String(req.headers.host)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
}</h1>${statusBody}${res.foot == "" ? "</body></html>" : res.foot}`
);
return;
}
next();
};
module.exports.proxySafe = true;

View file

@ -1,56 +0,0 @@
const sanitizeURL = require("../utils/urlSanitizer.js");
const parseURL = require("../utils/urlParser.js");
module.exports = (req, res, logFacilities, config, next) => {
// Sanitize URL
let sanitizedHref = sanitizeURL(
req.parsedURL.pathname,
config.allowDoubleSlashes
);
let preparedReqUrl =
req.parsedURL.pathname +
(req.parsedURL.search ? req.parsedURL.search : "") +
(req.parsedURL.hash ? req.parsedURL.hash : "");
// Check if URL is "dirty"
if (req.parsedURL.pathname != sanitizedHref && !req.isProxy) {
let sanitizedURL =
sanitizedHref +
(req.parsedURL.search ? req.parsedURL.search : "") +
(req.parsedURL.hash ? req.parsedURL.hash : "");
logFacilities.resmessage(`URL sanitized: ${req.url} => ${sanitizedURL}`);
if (config.rewriteDirtyURLs) {
req.url = sanitizedURL;
try {
req.parsedURL = parseURL(
req.url,
`http${req.socket.encrypted ? "s" : ""}://${
req.headers.host
? req.headers.host
: config.domain
? config.domain
: "unknown.invalid"
}`
);
} catch (err) {
res.error(400, err);
return;
}
} else {
res.redirect(sanitizedURL, false);
return;
}
} else if (req.url != preparedReqUrl && !req.isProxy) {
logFacilities.resmessage(`URL sanitized: ${req.url} => ${preparedReqUrl}`);
if (config.rewriteDirtyURLs) {
req.url = preparedReqUrl;
} else {
res.redirect(preparedReqUrl, false);
return;
}
}
next();
};
module.exports.proxySafe = true;

View file

@ -1,147 +0,0 @@
const createRegex = require("../utils/createRegex.js");
const ipMatch = require("../utils/ipMatch.js");
const sanitizeURL = require("../utils/urlSanitizer.js");
const parseURL = require("../utils/urlParser.js");
module.exports = (req, res, logFacilities, config, next) => {
const matchHostname = (hostname) => {
if (typeof hostname == "undefined" || hostname == "*") {
return true;
} else if (
req.headers.host &&
hostname.indexOf("*.") == 0 &&
hostname != "*."
) {
const hostnamesRoot = hostname.substring(2);
if (
req.headers.host == hostnamesRoot ||
(req.headers.host.length > hostnamesRoot.length &&
req.headers.host.indexOf("." + hostnamesRoot) ==
req.headers.host.length - hostnamesRoot.length - 1)
) {
return true;
}
} else if (req.headers.host && req.headers.host == hostname) {
return true;
}
return false;
};
// Add web root postfixes
if (!req.isProxy) {
let preparedReqUrl3 = config.allowPostfixDoubleSlashes
? req.parsedURL.pathname.replace(/\/+/, "/") +
(req.parsedURL.search ? req.parsedURL.search : "") +
(req.parsedURL.hash ? req.parsedURL.hash : "")
: req.url;
let urlWithPostfix = preparedReqUrl3;
let postfixPrefix = "";
config.wwwrootPostfixPrefixesVHost.every((currentPostfixPrefix) => {
if (preparedReqUrl3.indexOf(currentPostfixPrefix) == 0) {
if (currentPostfixPrefix.match(/\/+$/))
postfixPrefix = currentPostfixPrefix.replace(/\/+$/, "");
else if (
urlWithPostfix.length == currentPostfixPrefix.length ||
urlWithPostfix[currentPostfixPrefix.length] == "?" ||
urlWithPostfix[currentPostfixPrefix.length] == "/" ||
urlWithPostfix[currentPostfixPrefix.length] == "#"
)
postfixPrefix = currentPostfixPrefix;
else return true;
urlWithPostfix = urlWithPostfix.substring(postfixPrefix.length);
return false;
} else {
return true;
}
});
config.wwwrootPostfixesVHost.every((postfixEntry) => {
if (
matchHostname(postfixEntry.host) &&
ipMatch(
postfixEntry.ip,
req.socket ? req.socket.localAddress : undefined
) &&
!(
postfixEntry.skipRegex &&
preparedReqUrl3.match(createRegex(postfixEntry.skipRegex))
)
) {
urlWithPostfix =
postfixPrefix + "/" + postfixEntry.postfix + urlWithPostfix;
return false;
} else {
return true;
}
});
if (urlWithPostfix != preparedReqUrl3) {
logFacilities.resmessage(
"Added web root postfix: " + req.url + " => " + urlWithPostfix
);
req.url = urlWithPostfix;
try {
req.parsedURL = parseURL(
req.url,
`http${req.socket.encrypted ? "s" : ""}://${
req.headers.host
? req.headers.host
: config.domain
? config.domain
: "unknown.invalid"
}`
);
} catch (err) {
res.error(400, err);
return;
}
const sHref = sanitizeURL(
req.parsedURL.pathname,
config.allowDoubleSlashes
);
const preparedReqUrl2 =
req.parsedURL.pathname +
(req.parsedURL.search ? req.parsedURL.search : "") +
(req.parsedURL.hash ? req.parsedURL.hash : "");
if (
req.url != preparedReqUrl2 ||
sHref !=
req.parsedURL.pathname
.replace(/\/\.(?=\/|$)/g, "/")
.replace(/\/+/g, "/")
) {
res.error(403);
logFacilities.errmessage("Content blocked.");
return;
} else if (sHref != req.parsedURL.pathname) {
let rewrittenAgainURL =
sHref +
(req.parsedURL.search ? req.parsedURL.search : "") +
(req.parsedURL.hash ? req.parsedURL.hash : "");
logFacilities.resmessage(
`URL sanitized: ${req.url} => ${rewrittenAgainURL}`
);
req.url = rewrittenAgainURL;
try {
req.parsedURL = parseURL(
req.url,
`http${req.socket.encrypted ? "s" : ""}://${
req.headers.host
? req.headers.host
: config.domain
? config.domain
: "unknown.invalid"
}`
);
} catch (err) {
res.error(400, err);
return;
}
}
}
}
next();
};
module.exports.proxySafe = true;

View file

@ -1,5 +0,0 @@
// Default CSS used for the pages generated by SVR.JS
const defaultPageCSS =
"html{background-color:#ffffff;color:#000000;font-family:Poppins, FreeSans, Helvetica, Tahoma, Verdana, Arial, sans-serif;margin:0.75em}body{padding:0.5em 0.5em 0.1em;margin:0.5em auto;width:90%;max-width:800px}h1{text-align:center;font-size:2.25em;margin:0.3em 0 0.5em}code{background-color:#f9f9fa;border:1px solid #e4e4e7;border-radius:7px;display:block;padding:0.5em;margin:auto;width:95%;max-width:600px}table{width:95%;border-collapse:collapse;margin:auto;overflow-wrap:break-word;word-wrap:break-word;word-break:break-all;word-break:break-word}table tbody{background-color:#ffffff;color:#000000}table img{margin:0;display:inline}th,tr{padding:0.15em;text-align:center}th{background-color:#e2e2e4}th a{color:#ffffff}td,th{padding:0.225em}td{text-align:left}tr:nth-child(odd){background-color:#f9f9fa}hr{color:#ffffff}@media screen and (prefers-color-scheme: dark){html{background-color:#0c0a09;color:#ffffff}code{background-color:#191817;border-color:#27272a}a{color:#ffffff}a:hover{color:#ffffff}table tbody{background-color:#0c0a09;color:#ffffff}th{background-color:#2a2829}tr:nth-child(odd){background-color:#191817}}";
module.exports = defaultPageCSS;

View file

@ -1,52 +0,0 @@
// HTTP error descriptions
const serverHTTPErrorDescs = {
200: "The request succeeded! :)",
201: "A new resource has been created.",
202: "The request has been accepted for processing, but the processing has not been completed.",
400: "The request you made is invalid.",
401: "You need to authenticate yourself in order to access the requested file.",
402: "You need to pay in order to access the requested file.",
403: "You don't have access to the requested file.",
404: "The requested file doesn't exist. If you have typed the URL manually, then please check the spelling.",
405: "Method used to access the requested file isn't allowed.",
406: "The request is capable of generating only unacceptable content.",
407: "You need to authenticate yourself in order to use the proxy.",
408: "You have timed out.",
409: "The request you sent conflicts with the current state of the server.",
410: "The requested file is permanently deleted.",
411: "Content-Length property is required.",
412: "The server doesn't meet the preconditions you put in the request.",
413: "The request you sent is too large.",
414: "The URL you sent is too long.",
415: "The media type of request you sent isn't supported by the server.",
416: "The requested content range (Content-Range header) you sent is unsatisfiable.",
417: "The expectation specified in the Expect property couldn't be satisfied.",
418: "The server (teapot) can't brew any coffee! ;)",
421: "The request you made isn't intended for this server.",
422: "The server couldn't process content sent by you.",
423: "The requested file is locked.",
424: "The request depends on another failed request.",
425: "The server is unwilling to risk processing a request that might be replayed.",
426: "You need to upgrade the protocols you use to request a file.",
428: "The request you sent needs to be conditional, but it isn't.",
429: "You sent too many requests to the server.",
431: "The request you sent contains headers that are too large.",
451: "The requested file isn't accessible for legal reasons.",
497: "You sent a non-TLS request to the HTTPS server.",
500: "The server had an unexpected error. Below, the error stack is shown: </p><code>{stack}</code><p>You may need to contact the server administrator at <i>{contact}</i>.",
501: "The request requires the use of a function, which isn't currently implemented by the server.",
502: "The server had an error while it was acting as a gateway.</p><p>You may need to contact the server administrator at <i>{contact}</i>.",
503: "The service provided by the server is currently unavailable, possibly due to maintenance downtime or capacity problems. Please try again later.</p><p>You may need to contact the server administrator at <i>{contact}</i>.",
504: "The server couldn't get a response in time while it was acting as a gateway.</p><p>You may need to contact the server administrator at <i>{contact}</i>.",
505: "The server doesn't support the HTTP version used in the request.",
506: "The Variant header is configured to be engaged in content negotiation.</p><p>You may need to contact the server administrator at <i>{contact}</i>.",
507: "The server ran out of disk space necessary to complete the request.",
508: "The server detected an infinite loop while processing the request.",
509: "The server has its bandwidth limit exceeded.</p><p>You may need to contact the server administrator at <i>{contact}</i>.",
510: "The server requires an extended HTTP request. The request you made isn't an extended HTTP request.",
511: "You need to authenticate yourself in order to get network access.",
598: "The server couldn't get a response in time while it was acting as a proxy.",
599: "The server couldn't connect in time while it was acting as a proxy."
};
module.exports = serverHTTPErrorDescs;

View file

@ -1,45 +0,0 @@
// ASCII art SVR.JS logo ;)
const logo = [
"",
"",
"\x1b[0m \x1b[38;5;255m@@@@@@@@@\x1b[0m",
"\x1b[0m ,\x1b[38;5;255m@@@\x1b[38;5;254m&\x1b[38;5;041m/////////\x1b[38;5;254m@\x1b[38;5;255m@@@\x1b[0m,",
"\x1b[0m &\x1b[38;5;255m@@@\x1b[38;5;078m#\x1b[38;5;041m/////////////////\x1b[38;5;115m#\x1b[38;5;255m@@@\x1b[0m%",
"\x1b[0m \x1b[38;5;255m@@@@\x1b[38;5;041m///////////////////////////\x1b[38;5;255m@@@@\x1b[0m",
"\x1b[0m *\x1b[38;5;255m@@@\x1b[38;5;254m&\x1b[38;5;041m///////////////////////////////////\x1b[38;5;255m@@@@\x1b[0m*",
"\x1b[0m &\x1b[38;5;255m@@@\x1b[38;5;078m#\x1b[38;5;041m///////////////////////////////////////////\x1b[38;5;115m#\x1b[38;5;255m@@@\x1b[0m%",
"\x1b[0m \x1b[38;5;255m@@@@\x1b[38;5;041m/////////////////////////////////////////////////////\x1b[38;5;255m@@@@\x1b[0m",
"\x1b[0m /\x1b[38;5;255m@@@\x1b[38;5;254m@\x1b[38;5;041m/////////////////////////////////////////////////////////////\x1b[38;5;255m@@@@\x1b[0m,",
"\x1b[0m &\x1b[38;5;255m@@@\x1b[38;5;114m#\x1b[38;5;041m/////////////////////////////////////////////////////////////////////\x1b[38;5;115m#\x1b[38;5;255m@@@\x1b[0m%",
"\x1b[0m#\x1b[38;5;255m@\x1b[38;5;188m&\x1b[38;5;235m.\x1b[38;5;255m@@@@\x1b[38;5;041m/////////////////////////////////////////////////////////////////\x1b[38;5;255m@@@@\x1b[38;5;235m.\x1b[38;5;188m&\x1b[38;5;255m@",
"\x1b[0m#\x1b[38;5;255m@\x1b[38;5;188m&\x1b[38;5;235m.....\x1b[38;5;151m%\x1b[38;5;255m@@@\x1b[38;5;115m#\x1b[38;5;041m///////////////////////////////////////////////////////\x1b[38;5;151m%\x1b[38;5;255m@@@\x1b[38;5;109m#\x1b[38;5;235m.....\x1b[38;5;188m&\x1b[38;5;255m@",
"\x1b[0m#\x1b[38;5;255m@\x1b[38;5;188m&\x1b[38;5;235m.........\x1b[38;5;238m,\x1b[38;5;255m@@@@\x1b[38;5;041m///////////////////////////////////////////////\x1b[38;5;255m@@@@\x1b[38;5;236m,\x1b[38;5;235m.........\x1b[38;5;188m&\x1b[38;5;255m@",
"\x1b[0m#\x1b[38;5;255m@\x1b[38;5;188m&\x1b[38;5;235m..............\x1b[38;5;255m@@@@\x1b[38;5;041m//////////////////////////////////////\x1b[38;5;077m/\x1b[38;5;255m@@@@\x1b[38;5;235m..............\x1b[38;5;188m&\x1b[38;5;255m@",
"\x1b[0m#\x1b[38;5;255m@\x1b[38;5;188m&\x1b[38;5;235m..................\x1b[38;5;151m%\x1b[38;5;255m@@@\x1b[38;5;115m#\x1b[38;5;041m/////////////////////////////\x1b[38;5;151m%\x1b[38;5;255m@@@\x1b[38;5;248m#\x1b[38;5;235m..................\x1b[38;5;188m&\x1b[38;5;255m@",
"\x1b[0m#\x1b[38;5;255m@\x1b[38;5;188m&\x1b[38;5;235m......................\x1b[38;5;238m,\x1b[38;5;255m@@@\x1b[38;5;254m@\x1b[38;5;041m/////////////////////\x1b[38;5;255m@@@@\x1b[38;5;237m,\x1b[38;5;235m.............\x1b[38;5;041m///\x1b[38;5;236m.\x1b[38;5;235m.....\x1b[38;5;188m&\x1b[38;5;255m@",
"\x1b[0m#\x1b[38;5;255m@\x1b[38;5;254m&\x1b[38;5;235m...........................\x1b[38;5;255m@@@@\x1b[38;5;041m////////////\x1b[38;5;077m/\x1b[38;5;255m@@@@\x1b[38;5;235m................\x1b[38;5;041m/////\x1b[38;5;035m*\x1b[38;5;235m.....\x1b[38;5;188m&\x1b[38;5;255m@",
"\x1b[0m#\x1b[38;5;255m@\x1b[38;5;188m&\x1b[38;5;235m...............................\x1b[38;5;007m%\x1b[38;5;255m@@@\x1b[38;5;114m#\x1b[38;5;041m///\x1b[38;5;115m#\x1b[38;5;255m@@@\x1b[38;5;249m#\x1b[38;5;235m...................\x1b[38;5;041m/////\x1b[38;5;035m/\x1b[38;5;235m......\x1b[38;5;188m&\x1b[38;5;255m@",
"\x1b[0m#\x1b[38;5;255m@\x1b[38;5;188m&\x1b[38;5;235m...................................\x1b[38;5;237m,\x1b[38;5;255m@@@\x1b[38;5;237m,\x1b[38;5;235m.......................\x1b[38;5;041m///\x1b[38;5;035m/\x1b[38;5;235m........\x1b[38;5;188m&\x1b[38;5;255m@",
"\x1b[0m,\x1b[38;5;255m@@@\x1b[38;5;251m%\x1b[38;5;235m..................................\x1b[38;5;251m%\x1b[38;5;255m@\x1b[38;5;242m/\x1b[38;5;235m.........\x1b[38;5;035m*\x1b[38;5;041m///\x1b[38;5;035m/\x1b[38;5;235m....................\x1b[38;5;253m&\x1b[38;5;255m@@@",
"\x1b[0m \x1b[38;5;255m@@@@\x1b[38;5;235m..............................\x1b[38;5;251m%\x1b[38;5;255m@\x1b[38;5;242m/\x1b[38;5;235m........\x1b[38;5;041m/////\x1b[38;5;029m*\x1b[38;5;235m................\x1b[38;5;255m@@@@\x1b[0m",
"\x1b[0m \x1b[38;5;254m@\x1b[38;5;255m@@@\x1b[38;5;238m,\x1b[38;5;235m.........................\x1b[38;5;251m%\x1b[38;5;255m@\x1b[38;5;242m/\x1b[38;5;235m.......\x1b[38;5;041m/////\x1b[38;5;029m*\x1b[38;5;235m............\x1b[38;5;240m*\x1b[38;5;255m@@@\x1b[0m&",
"\x1b[0m *\x1b[38;5;255m@@@\x1b[38;5;251m%\x1b[38;5;235m.....................\x1b[38;5;251m%\x1b[38;5;255m@\x1b[38;5;242m/\x1b[38;5;235m.......\x1b[38;5;029m*\x1b[38;5;041m//\x1b[38;5;236m.\x1b[38;5;235m..........\x1b[38;5;252m&\x1b[38;5;255m@@@\x1b[0m/",
"\x1b[0m //\x1b[38;5;255m@@@@\x1b[38;5;235m.................\x1b[38;5;251m%\x1b[38;5;255m@\x1b[38;5;242m/\x1b[38;5;235m.................\x1b[38;5;255m@@@@\x1b[0m//",
"\x1b[0m //////////&\x1b[38;5;255m@@@\x1b[38;5;238m,\x1b[38;5;235m............\x1b[38;5;251m%\x1b[38;5;255m@\x1b[38;5;242m/\x1b[38;5;235m............\x1b[38;5;239m,\x1b[38;5;255m@@@\x1b[0m&//////////",
"\x1b[0m .//////////////////(\x1b[38;5;255m@@@\x1b[38;5;251m%\x1b[38;5;235m........\x1b[38;5;251m%\x1b[38;5;255m@\x1b[38;5;242m/\x1b[38;5;235m........\x1b[38;5;253m&\x1b[38;5;255m@@@\x1b[0m(//////////////////,",
"\x1b[0m ///////////////////////////\x1b[38;5;255m@@@@\x1b[38;5;235m....\x1b[38;5;251m%\x1b[38;5;255m@\x1b[38;5;242m/\x1b[38;5;235m....\x1b[38;5;255m@@@@\x1b[0m///////////////////////////",
"\x1b[0m ///////////////////////////////&\x1b[38;5;255m@@@\x1b[38;5;252m&\x1b[38;5;255m@\x1b[38;5;246m(\x1b[38;5;255m@@@\x1b[0m&///////////////////////////////",
"\x1b[0m /////////////////////////////////////////////////////////////////////",
"\x1b[0m .///////////////////////////////////////////////////////////.",
"\x1b[0m ///////////////////////////////////////////////////",
"\x1b[0m ///////////////////////////////////////////",
"\x1b[0m ./////////////////////////////////,",
"\x1b[0m /////////////////////////",
"\x1b[0m /////////////////",
"\x1b[0m ,///////,",
"",
"",
"\x1b[0m"
];
module.exports = logo;

View file

@ -1,36 +0,0 @@
// Server error descriptions
const serverErrorDescs = {
EADDRINUSE: "Address is already in use by another process.",
EADDRNOTAVAIL: "Address is not available on this machine.",
EACCES:
"Permission denied. You may not have sufficient privileges to access the requested address.",
EAFNOSUPPORT:
"Address family not supported. The address family (IPv4 or IPv6) of the requested address is not supported.",
EALREADY:
"Operation already in progress. The server is already in the process of establishing a connection on the requested address.",
ECONNABORTED:
"Connection aborted. The connection to the server was terminated abruptly.",
ECONNREFUSED:
"Connection refused. The server refused the connection attempt.",
ECONNRESET:
"Connection reset by peer. The connection to the server was reset by the remote host.",
EDESTADDRREQ:
"Destination address required. The destination address must be specified.",
EINVAL: "Invalid argument (invalid IP address?).",
ENETDOWN:
"Network is down. The network interface used for the connection is not available.",
ENETUNREACH:
"Network is unreachable. The network destination is not reachable from this host.",
ENOBUFS:
"No buffer space available. Insufficient buffer space is available for the server to process the request.",
ENOTFOUND: "Domain name doesn't exist (invalid IP address?).",
ENOTSOCK: "Not a socket. The file descriptor provided is not a valid socket.",
EPROTO: "Protocol error. An unspecified protocol error occurred.",
EPROTONOSUPPORT:
"Protocol not supported. The requested network protocol is not supported.",
ETIMEDOUT:
"Connection timed out. The server did not respond within the specified timeout period.",
UNKNOWN: "There was an unknown error with the server."
};
module.exports = serverErrorDescs;

View file

@ -1,73 +0,0 @@
const http = require("http");
const statusCodes = {
...http.STATUS_CODES,
100: "Continue",
101: "Switching Protocols",
102: "Processing",
103: "Early Hints",
200: "OK",
201: "Created",
202: "Accepted",
203: "Non-Authoritative Information",
204: "No Content",
205: "Reset Content",
206: "Partial Content",
207: "Multi-Status",
208: "Already Reported",
226: "IM Used",
300: "Multiple Choices",
301: "Moved Permanently",
302: "Found",
303: "See Other",
304: "Not Modified",
305: "Use Proxy",
307: "Temporary Redirect",
308: "Permanent Redirect",
400: "Bad Request",
401: "Unauthorized",
402: "Payment Required",
403: "Forbidden",
404: "Not Found",
405: "Method Not Allowed",
406: "Not Acceptable",
407: "Proxy Authentication Required",
408: "Request Timeout",
409: "Conflict",
410: "Gone",
411: "Length Required",
412: "Precondition Failed",
413: "Payload Too Large",
414: "URI Too Long",
415: "Unsupported Media Type",
416: "Range Not Satisfiable",
417: "Expectation Failed",
418: "I'm a Teapot",
421: "Misdirected Request",
422: "Unprocessable Entity",
423: "Locked",
424: "Failed Dependency",
425: "Too Early",
426: "Upgrade Required",
428: "Precondition Required",
429: "Too Many Requests",
431: "Request Header Fields Too Large",
451: "Unavailable For Legal Reasons",
497: "HTTP Request Sent to HTTPS Port",
500: "Internal Server Error",
501: "Not Implemented",
502: "Bad Gateway",
503: "Service Unavailable",
504: "Gateway Timeout",
505: "HTTP Version Not Supported",
506: "Variant Also Negotiates",
507: "Insufficient Storage",
508: "Loop Detected",
509: "Bandwidth Limit Exceeded",
510: "Not Extended",
511: "Network Authentication Required",
598: "Network Read Timeout Error",
599: "Network Connect Timeout Error"
};
module.exports = statusCodes;

View file

@ -1,231 +0,0 @@
const net = require("net");
const os = require("os");
const path = require("path");
let cluster = {};
if (!process.singleThreaded) {
try {
// Import cluster module
cluster = require("cluster");
// eslint-disable-next-line no-unused-vars
} catch (err) {
// Clustering is not supported!
}
// Cluster & IPC shim for Bun and Deno
cluster.shim = () => {
cluster.isMaster = !process.env.NODE_UNIQUE_ID;
cluster.isPrimary = cluster.isMaster;
cluster.isWorker = !cluster.isMaster;
cluster.__shimmed__ = true;
if (cluster.isWorker) {
// Shim the cluster.worker object for worker processes
cluster.worker = {
id: parseInt(process.env.NODE_UNIQUE_ID),
process: process,
isDead: () => false,
send: (message, ...params) => {
process.send(message, ...params);
}
};
if (!process.send) {
// Shim the process.send function for worker processes
// Create a fake IPC server to receive messages
let fakeIPCServer = net.createServer((socket) => {
let receivedData = "";
socket.on("data", (data) => {
receivedData += data.toString();
});
socket.on("end", () => {
process.emit("message", receivedData);
});
});
fakeIPCServer.listen(
os.platform() === "win32"
? path.join(
"\\\\?\\pipe",
process.dirname,
"temp/.W" + process.pid + ".ipc"
)
: process.dirname + "/temp/.W" + process.pid + ".ipc"
);
process.send = (message) => {
// Create a fake IPC connection to send messages
let fakeIPCConnection = net.createConnection(
os.platform() === "win32"
? path.join(
"\\\\?\\pipe",
process.dirname,
"temp/.P" + process.pid + ".ipc"
)
: process.dirname + "/temp/.P" + process.pid + ".ipc",
() => {
fakeIPCConnection.end(message);
}
);
};
process.removeFakeIPC = () => {
// Close IPC server
process.send = () => {};
fakeIPCServer.close();
};
}
}
// Custom implementation for cluster.fork()
cluster._workersCounter = 1;
cluster.workers = {};
cluster.fork = (env) => {
const child_process = require("child_process");
let newEnvironment = Object.assign({}, env ? env : process.env);
newEnvironment.NODE_UNIQUE_ID = cluster._workersCounter;
let newArguments = [...process.argv];
let command = newArguments.shift();
let newWorker = child_process.spawn(command, newArguments, {
env: newEnvironment,
stdio: ["inherit", "inherit", "inherit", "ipc"]
});
newWorker.process = newWorker;
newWorker.isDead = () => newWorker.exitCode !== null || newWorker.killed;
newWorker.id = newEnvironment.NODE_UNIQUE_ID;
function checkSendImplementation(worker) {
let sendImplemented = true;
if (
!(
process.versions &&
process.versions.bun &&
process.versions.bun[0] != "0"
)
) {
if (!worker.send) {
sendImplemented = false;
}
let oldLog = console.log;
console.log = (...params) => {
if (
params[0] ==
"ChildProcess.prototype.send() - Sorry! Not implemented yet"
) {
throw new Error("NOT IMPLEMENTED");
} else {
oldLog(...params);
}
};
try {
if (process.isBun) worker.send(undefined);
} catch (err) {
if (err.message === "NOT IMPLEMENTED") {
sendImplemented = false;
}
console.log(err);
}
console.log = oldLog;
}
return sendImplemented;
}
if (!checkSendImplementation(newWorker)) {
// Create a fake IPC server for worker process to receive messages
let fakeWorkerIPCServer = net.createServer((socket) => {
let receivedData = "";
socket.on("data", (data) => {
receivedData += data.toString();
});
socket.on("end", () => {
newWorker.emit("message", receivedData);
});
});
fakeWorkerIPCServer.listen(
os.platform() === "win32"
? path.join(
"\\\\?\\pipe",
process.dirname,
"temp/.P" + newWorker.process.pid + ".ipc"
)
: process.dirname + "/temp/.P" + newWorker.process.pid + ".ipc"
);
// Cleanup when worker process exits
newWorker.on("exit", () => {
fakeWorkerIPCServer.close();
delete cluster.workers[newWorker.id];
});
newWorker.send = function (
message,
fakeParam2,
fakeParam3,
fakeParam4,
tries
) {
if (!tries) tries = 0;
try {
// Create a fake IPC connection to send messages to worker process
let fakeWorkerIPCConnection = net.createConnection(
os.platform() === "win32"
? path.join(
"\\\\?\\pipe",
process.dirname,
"temp/.W" + newWorker.process.pid + ".ipc"
)
: process.dirname + "/temp/.W" + newWorker.process.pid + ".ipc",
() => {
fakeWorkerIPCConnection.end(message);
}
);
} catch (err) {
if (tries > 50) throw err;
newWorker.send(
message,
fakeParam2,
fakeParam3,
fakeParam4,
tries + 1
);
}
};
} else {
newWorker.on("exit", () => {
delete cluster.workers[newWorker.id];
});
}
cluster.workers[newWorker.id] = newWorker;
cluster._workersCounter++;
return newWorker;
};
};
if (
(process.isBun || (process.versions && process.versions.deno)) &&
(cluster.isMaster === undefined ||
(cluster.isMaster && process.env.NODE_UNIQUE_ID))
) {
cluster.shim();
}
// Shim cluster.isPrimary field
if (cluster.isPrimary === undefined && cluster.isMaster !== undefined)
cluster.isPrimary = cluster.isMaster;
}
module.exports = cluster;

View file

@ -1,17 +0,0 @@
const os = require("os");
function createRegex(regex, isPath) {
// The new regular expression supports single unescaped "/" within [], but not two unescaped "/".
// We needed to do it, because it's very hard to create the regex that matches two unescaped "/" within "[]" without ReDoS.
const regexStrMatch = regex.match(
/^\/((?:\\.|\/+(?:(?:\\.|[^\]\\/])*\])|[^/\\])*)\/([a-zA-Z0-9]*)$/
);
if (!regexStrMatch) throw new Error("Invalid regular expression: " + regex);
const searchString = regexStrMatch[1];
let modifiers = regexStrMatch[2];
if (isPath && !modifiers.match(/i/i) && os.platform() == "win32")
modifiers += "i";
return new RegExp(searchString, modifiers);
}
module.exports = createRegex;

View file

@ -1,49 +0,0 @@
// Function to deep clone an object or array
function deepClone(obj, isFullObject) {
if (typeof obj !== "object" || obj === null) {
return obj;
}
const objectsArray = [];
const clonesArray = [];
const recurse = (obj) => {
let objectsArrayIndex = -1;
for (let i = 0; i < objectsArray.length; i++) {
if (objectsArray[i] == obj) {
objectsArrayIndex = i;
break;
}
}
if (objectsArrayIndex != -1) {
return clonesArray[objectsArrayIndex];
}
if (Array.isArray(obj)) {
const clone = [];
objectsArray.push(obj);
clonesArray.push(clone);
obj.forEach((item, index) => {
clone[index] =
typeof item !== "object" || item === null ? item : recurse(item);
});
return clone;
} else {
const clone = isFullObject ? {} : Object.create(null);
objectsArray.push(obj);
clonesArray.push(clone);
Object.keys(obj).forEach((key) => {
clone[key] =
typeof obj[key] !== "object" || obj[key] === null
? obj[key]
: recurse(obj[key]);
});
return clone;
}
};
return recurse(obj, objectsArray, clonesArray);
}
module.exports = deepClone;

View file

@ -1,19 +0,0 @@
const fs = require("fs");
function deleteFolderRecursive(path) {
if (fs.existsSync(path)) {
fs.readdirSync(path).forEach((file) => {
const curPath = path + "/" + file;
if (fs.statSync(curPath).isDirectory()) {
// recurse
deleteFolderRecursive(curPath);
} else {
// delete file
fs.unlinkSync(curPath);
}
});
fs.rmdirSync(path);
}
}
module.exports = deleteFolderRecursive;

View file

@ -1,85 +0,0 @@
const os = require("os");
const path = require("path");
// Function to get URL path for use in forbidden path adding.
function getInitializePath(to) {
const isWin32 = os.platform() == "win32";
const cwd = process.cwd();
if (isWin32) {
to = to.replace(/\//g, "\\");
if (to[0] == "\\") to = cwd.split("\\")[0] + to;
}
const absoluteTo = path.isAbsolute(to)
? to
: process.dirname + (isWin32 ? "\\" : "/") + to;
if (isWin32 && cwd[0] != absoluteTo[0]) return "";
const relative = path.relative(cwd, absoluteTo);
if (isWin32) {
return "/" + relative.replace(/\\/g, "/");
} else {
return "/" + relative;
}
}
function isForbiddenPath(decodedHref, match) {
const forbiddenPath = forbiddenPaths[match];
if (!forbiddenPath) return false;
const isWin32 = os.platform() === "win32";
const decodedHrefLower = isWin32 ? decodedHref.toLowerCase() : null;
if (typeof forbiddenPath === "string") {
return isWin32
? decodedHrefLower === forbiddenPath.toLowerCase()
: decodedHref === forbiddenPath;
}
if (typeof forbiddenPath === "object") {
return isWin32
? forbiddenPath.some((path) => decodedHrefLower === path.toLowerCase())
: forbiddenPath.includes(decodedHref);
}
return false;
}
function isIndexOfForbiddenPath(decodedHref, match) {
const forbiddenPath = forbiddenPaths[match];
if (!forbiddenPath) return false;
const isWin32 = os.platform() === "win32";
const decodedHrefLower = isWin32 ? decodedHref.toLowerCase() : null;
if (typeof forbiddenPath === "string") {
const forbiddenPathLower = isWin32 ? forbiddenPath.toLowerCase() : null;
return isWin32
? decodedHrefLower === forbiddenPathLower ||
decodedHrefLower.indexOf(forbiddenPathLower + "/") == 0
: decodedHref === forbiddenPath ||
decodedHref.indexOf(forbiddenPath + "/") == 0;
}
if (typeof forbiddenPath === "object") {
return isWin32
? forbiddenPath.some(
(path) =>
decodedHrefLower === path.toLowerCase() ||
decodedHrefLower.indexOf(path.toLowerCase() + "/") == 0
)
: forbiddenPath.some(
(path) => decodedHref === path || decodedHref.indexOf(path + "/") == 0
);
}
return false;
}
// Set up forbidden paths
let forbiddenPaths = {};
module.exports = {
getInitializePath: getInitializePath,
isForbiddenPath: isForbiddenPath,
isIndexOfForbiddenPath: isIndexOfForbiddenPath,
forbiddenPaths: forbiddenPaths
};

View file

@ -1,49 +0,0 @@
// Generate V8-style error stack from Error object.
function generateErrorStack(errorObject) {
// Split the error stack by newlines.
const errorStack = errorObject.stack ? errorObject.stack.split("\n") : [];
// If the error stack starts with the error name, return the original stack (it is V8-style then).
if (
errorStack.some(
(errorStackLine) => errorStackLine.indexOf(errorObject.name) == 0
)
) {
return errorObject.stack;
}
// Create a new error stack with the error name and code (if available).
let newErrorStack = [
errorObject.name +
(errorObject.code ? ": " + errorObject.code : "") +
(errorObject.message == "" ? "" : ": " + errorObject.message)
];
// Process each line of the original error stack.
errorStack.forEach((errorStackLine) => {
if (errorStackLine != "") {
// Split the line into function and location parts (if available).
let errorFrame = errorStackLine.split("@");
let location = "";
if (errorFrame.length > 1 && errorFrame[0] == "global code")
errorFrame.shift();
if (errorFrame.length > 1) location = errorFrame.pop();
const func = errorFrame.join("@");
// Build the new error stack entry with function and location information.
newErrorStack.push(
" at " +
(func == ""
? !location || location == ""
? "<anonymous>"
: location
: func + (!location || location == "" ? "" : " (" + location + ")"))
);
}
});
// Join the new error stack entries with newlines and return the final stack.
return newErrorStack.join("\n");
}
module.exports = generateErrorStack;

Some files were not shown because too many files have changed in this diff Show more