Compare commits
1 commit
main
...
simpleserv
Author | SHA1 | Date | |
---|---|---|---|
1285fb6bce |
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 8 KiB After Width: | Height: | Size: 8 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 7 KiB After Width: | Height: | Size: 7 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
19
.github/workflows/main.yml
vendored
|
@ -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
|
@ -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
|
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
npx --no -- commitlint --edit "$1"
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
npx lint-staged
|
|
21
LICENSE
|
@ -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
|
@ -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 distribution’s 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.
|
|
|
@ -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.
|
|
|
@ -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
|
|
||||||
}
|
|
Before Width: | Height: | Size: 15 KiB |
BIN
assets/logo.png
Before Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 19 KiB |
|
@ -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
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
Copyright © 2020-2024 SVR.JS
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -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>
|
|
|
@ -1,6 +0,0 @@
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
Copyright © 2020-2024 SVR.JS
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -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>
|
|
|
@ -1,3 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
extends: ["@commitlint/config-conventional"]
|
|
||||||
};
|
|
4
config.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"port": 80,
|
||||||
|
"exposeServerVersion": true
|
||||||
|
}
|
|
@ -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;
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -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
After Width: | Height: | Size: 15 KiB |
36
index.html
Normal 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>
|
|
@ -1,5 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
testEnvironment: 'node',
|
|
||||||
testMatch: ['**/tests/**/*.test.js'],
|
|
||||||
verbose: true,
|
|
||||||
};
|
|
|
@ -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
After Width: | Height: | Size: 25 KiB |
26
node_modules/.package-lock.json
generated
vendored
Normal 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
|
@ -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
|
@ -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
|
@ -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
12
node_modules/mime-db/index.js
generated
vendored
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"watch": [
|
|
||||||
"dist/svr.js",
|
|
||||||
"dist/config.json"
|
|
||||||
]
|
|
||||||
}
|
|
9939
package-lock.json
generated
56
package.json
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
|
@ -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]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
|
@ -1 +0,0 @@
|
||||||
require(__dirname + "/svr.js");
|
|
|
@ -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
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
)
|
|
||||||
.replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode])
|
|
||||||
.replace(
|
|
||||||
/{stack}/g,
|
|
||||||
stack
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.replace(/\r\n/g, "<br/>")
|
|
||||||
.replace(/\n/g, "<br/>")
|
|
||||||
.replace(/\r/g, "<br/>")
|
|
||||||
.replace(/ {2}/g, " ")
|
|
||||||
)
|
|
||||||
.replace(
|
|
||||||
/{server}/g,
|
|
||||||
(
|
|
||||||
config.generateServerString() +
|
|
||||||
(!config.exposeModsInErrorPages || extName == undefined
|
|
||||||
? ""
|
|
||||||
: " " + extName)
|
|
||||||
)
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
)
|
|
||||||
.replace(
|
|
||||||
/{contact}/g,
|
|
||||||
config.serverAdministratorEmail
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.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, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
)
|
|
||||||
.replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode])
|
|
||||||
.replace(
|
|
||||||
/{stack}/g,
|
|
||||||
stack
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.replace(/\r\n/g, "<br/>")
|
|
||||||
.replace(/\n/g, "<br/>")
|
|
||||||
.replace(/\r/g, "<br/>")
|
|
||||||
.replace(/ {2}/g, " ")
|
|
||||||
)
|
|
||||||
.replace(
|
|
||||||
/{server}/g,
|
|
||||||
(
|
|
||||||
config.generateServerString() +
|
|
||||||
(!config.exposeModsInErrorPages || extName == undefined
|
|
||||||
? ""
|
|
||||||
: " " + extName)
|
|
||||||
)
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
)
|
|
||||||
.replace(
|
|
||||||
/{contact}/g,
|
|
||||||
config.serverAdministratorEmail
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.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, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
)
|
|
||||||
.replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode])
|
|
||||||
.replace(
|
|
||||||
/{stack}/g,
|
|
||||||
stack
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.replace(/\r\n/g, "<br/>")
|
|
||||||
.replace(/\n/g, "<br/>")
|
|
||||||
.replace(/\r/g, "<br/>")
|
|
||||||
.replace(/ {2}/g, " ")
|
|
||||||
)
|
|
||||||
.replace(
|
|
||||||
/{server}/g,
|
|
||||||
(
|
|
||||||
config.generateServerString() +
|
|
||||||
(!config.exposeModsInErrorPages || extName == undefined
|
|
||||||
? ""
|
|
||||||
: " " + extName)
|
|
||||||
)
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
)
|
|
||||||
.replace(
|
|
||||||
/{contact}/g,
|
|
||||||
config.serverAdministratorEmail
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.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;
|
|
||||||
};
|
|
|
@ -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;
|
|
||||||
};
|
|
|
@ -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;
|
|
||||||
};
|
|
|
@ -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, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
)
|
|
||||||
.replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode])
|
|
||||||
.replace(
|
|
||||||
/{stack}/g,
|
|
||||||
stack
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.replace(/\r\n/g, "<br/>")
|
|
||||||
.replace(/\n/g, "<br/>")
|
|
||||||
.replace(/\r/g, "<br/>")
|
|
||||||
.replace(/ {2}/g, " ")
|
|
||||||
)
|
|
||||||
.replace(
|
|
||||||
/{path}/g,
|
|
||||||
req.url
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
)
|
|
||||||
.replace(
|
|
||||||
/{server}/g,
|
|
||||||
"" +
|
|
||||||
(
|
|
||||||
config.generateServerString() +
|
|
||||||
(!config.exposeModsInErrorPages || extName == undefined
|
|
||||||
? ""
|
|
||||||
: " " + extName)
|
|
||||||
)
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">") +
|
|
||||||
(req.headers.host == undefined || req.isProxy
|
|
||||||
? ""
|
|
||||||
: " on " +
|
|
||||||
String(req.headers.host)
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">"))
|
|
||||||
)
|
|
||||||
.replace(
|
|
||||||
/{contact}/g,
|
|
||||||
config.serverAdministratorEmail
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.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, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
)
|
|
||||||
.replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode])
|
|
||||||
.replace(
|
|
||||||
/{stack}/g,
|
|
||||||
stack
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.replace(/\r\n/g, "<br/>")
|
|
||||||
.replace(/\n/g, "<br/>")
|
|
||||||
.replace(/\r/g, "<br/>")
|
|
||||||
.replace(/ {2}/g, " ")
|
|
||||||
)
|
|
||||||
.replace(
|
|
||||||
/{path}/g,
|
|
||||||
req.url
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
)
|
|
||||||
.replace(
|
|
||||||
/{server}/g,
|
|
||||||
"" +
|
|
||||||
(
|
|
||||||
config.generateServerString() +
|
|
||||||
(!config.exposeModsInErrorPages || extName == undefined
|
|
||||||
? ""
|
|
||||||
: " " + extName)
|
|
||||||
)
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">") +
|
|
||||||
(req.headers.host == undefined || req.isProxy
|
|
||||||
? ""
|
|
||||||
: " on " +
|
|
||||||
String(req.headers.host)
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">"))
|
|
||||||
)
|
|
||||||
.replace(
|
|
||||||
/{contact}/g,
|
|
||||||
config.serverAdministratorEmail
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.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;
|
|
||||||
};
|
|
|
@ -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;
|
|
||||||
};
|
|
2553
src/index.js
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(
|
|
||||||
/>/g,
|
|
||||||
">"
|
|
||||||
)} 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, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")} as a proxy.</p><p><i>${config
|
|
||||||
.generateServerString()
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")}</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;
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">") +
|
|
||||||
'</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, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">") +
|
|
||||||
"</title>"
|
|
||||||
)
|
|
||||||
: res.head.replace(
|
|
||||||
/<head>/i,
|
|
||||||
"<head><title>Directory: " +
|
|
||||||
decodeURIComponent(origHref)
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">") +
|
|
||||||
"</title>"
|
|
||||||
)) + (!headerHasHTMLTag ? customDirListingHeader : "")
|
|
||||||
}<h1>Directory: ${decodeURIComponent(origHref)
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(
|
|
||||||
/>/g,
|
|
||||||
">"
|
|
||||||
)}</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, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")}${
|
|
||||||
req.headers.host == undefined
|
|
||||||
? ""
|
|
||||||
: " on " +
|
|
||||||
String(req.headers.host)
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
}</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, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(
|
|
||||||
/>/g,
|
|
||||||
">"
|
|
||||||
)}</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, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")}</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, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(
|
|
||||||
/>/g,
|
|
||||||
">"
|
|
||||||
)}</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;
|
|
|
@ -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, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">") +
|
|
||||||
" status" +
|
|
||||||
(req.headers.host == undefined
|
|
||||||
? ""
|
|
||||||
: " for " +
|
|
||||||
String(req.headers.host)
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")) +
|
|
||||||
'</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, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">") +
|
|
||||||
" status" +
|
|
||||||
(req.headers.host == undefined
|
|
||||||
? ""
|
|
||||||
: " for " +
|
|
||||||
String(req.headers.host)
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")) +
|
|
||||||
"</title>"
|
|
||||||
)
|
|
||||||
}<h1>${name
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")} status${
|
|
||||||
req.headers.host == undefined
|
|
||||||
? ""
|
|
||||||
: " for " +
|
|
||||||
String(req.headers.host)
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
}</h1>${statusBody}${res.foot == "" ? "</body></html>" : res.foot}`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.proxySafe = true;
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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
|
|
||||||
};
|
|
|
@ -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;
|
|