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
|
||||
/dist/
|
||||
/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
|
||||
temp
|
||||
log
|
||||
|
|
|
@ -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;
|