forked from svrjs/svrjs
Add tests for middleware, add ".js" file extensions inside the require functions in tests for utility functions, make error handling in URL rewriting middleware better, and lint out static file serving and directory listing middleware
This commit is contained in:
parent
57ce4018dc
commit
0450094c68
26 changed files with 1527 additions and 29 deletions
234
package-lock.json
generated
234
package-lock.json
generated
|
@ -25,6 +25,7 @@
|
||||||
"eslint-plugin-prettier": "^5.2.1",
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
"globals": "^15.9.0",
|
"globals": "^15.9.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
|
"node-mocks-http": "^1.15.1",
|
||||||
"prettier": "^3.3.3"
|
"prettier": "^3.3.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1753,6 +1754,49 @@
|
||||||
"@babel/types": "^7.20.7"
|
"@babel/types": "^7.20.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/body-parser": {
|
||||||
|
"version": "1.19.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
|
||||||
|
"integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/connect": "*",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/connect": {
|
||||||
|
"version": "3.4.38",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
|
||||||
|
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/express": {
|
||||||
|
"version": "4.17.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
|
||||||
|
"integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/body-parser": "*",
|
||||||
|
"@types/express-serve-static-core": "^4.17.33",
|
||||||
|
"@types/qs": "*",
|
||||||
|
"@types/serve-static": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/express-serve-static-core": {
|
||||||
|
"version": "4.19.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz",
|
||||||
|
"integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*",
|
||||||
|
"@types/qs": "*",
|
||||||
|
"@types/range-parser": "*",
|
||||||
|
"@types/send": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/graceful-fs": {
|
"node_modules/@types/graceful-fs": {
|
||||||
"version": "4.1.9",
|
"version": "4.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
|
||||||
|
@ -1762,6 +1806,12 @@
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/http-errors": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/istanbul-lib-coverage": {
|
"node_modules/@types/istanbul-lib-coverage": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
|
||||||
|
@ -1786,6 +1836,12 @@
|
||||||
"@types/istanbul-lib-report": "*"
|
"@types/istanbul-lib-report": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/mime": {
|
||||||
|
"version": "1.3.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
|
||||||
|
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.5.0",
|
"version": "22.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.0.tgz",
|
||||||
|
@ -1795,6 +1851,39 @@
|
||||||
"undici-types": "~6.19.2"
|
"undici-types": "~6.19.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/qs": {
|
||||||
|
"version": "6.9.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz",
|
||||||
|
"integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@types/range-parser": {
|
||||||
|
"version": "1.2.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
|
||||||
|
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@types/send": {
|
||||||
|
"version": "0.17.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
|
||||||
|
"integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/mime": "^1",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/serve-static": {
|
||||||
|
"version": "1.15.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
|
||||||
|
"integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/http-errors": "*",
|
||||||
|
"@types/node": "*",
|
||||||
|
"@types/send": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/stack-utils": {
|
"node_modules/@types/stack-utils": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
|
||||||
|
@ -1961,6 +2050,19 @@
|
||||||
"node": ">=6.5"
|
"node": ">=6.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/accepts": {
|
||||||
|
"version": "1.3.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||||
|
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"mime-types": "~2.1.34",
|
||||||
|
"negotiator": "0.6.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
"version": "8.12.1",
|
"version": "8.12.1",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
|
||||||
|
@ -2659,6 +2761,18 @@
|
||||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/content-disposition": {
|
||||||
|
"version": "0.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||||
|
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "5.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/convert-source-map": {
|
"node_modules/convert-source-map": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||||
|
@ -2793,6 +2907,15 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/depd": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/detect-newline": {
|
"node_modules/detect-newline": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
|
||||||
|
@ -3473,6 +3596,15 @@
|
||||||
"url": "https://ko-fi.com/tunnckoCore/commissions"
|
"url": "https://ko-fi.com/tunnckoCore/commissions"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fresh": {
|
||||||
|
"version": "0.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||||
|
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fs-extra": {
|
"node_modules/fs-extra": {
|
||||||
"version": "10.1.0",
|
"version": "10.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
|
||||||
|
@ -4905,6 +5037,24 @@
|
||||||
"tmpl": "1.0.5"
|
"tmpl": "1.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/media-typer": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/merge-descriptors": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/merge-stream": {
|
"node_modules/merge-stream": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||||
|
@ -4920,6 +5070,15 @@
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/methods": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/micromatch": {
|
"node_modules/micromatch": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||||
|
@ -4933,6 +5092,18 @@
|
||||||
"node": ">=8.6"
|
"node": ">=8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mime": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"mime": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mime-db": {
|
"node_modules/mime-db": {
|
||||||
"version": "1.52.0",
|
"version": "1.52.0",
|
||||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
@ -5038,12 +5209,44 @@
|
||||||
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
|
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/negotiator": {
|
||||||
|
"version": "0.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||||
|
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-int64": {
|
"node_modules/node-int64": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
|
||||||
"integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
|
"integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/node-mocks-http": {
|
||||||
|
"version": "1.15.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-mocks-http/-/node-mocks-http-1.15.1.tgz",
|
||||||
|
"integrity": "sha512-X/GpUpNNiPDYUeUD183W8V4OW6OHYWI29w/QDyb+c/GzOfVEAlo6HjbW9++eXT2aV2lGg+uS+XqTD2q0pNREQA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/express": "^4.17.21",
|
||||||
|
"@types/node": "*",
|
||||||
|
"accepts": "^1.3.7",
|
||||||
|
"content-disposition": "^0.5.3",
|
||||||
|
"depd": "^1.1.0",
|
||||||
|
"fresh": "^0.5.2",
|
||||||
|
"merge-descriptors": "^1.0.1",
|
||||||
|
"methods": "^1.1.2",
|
||||||
|
"mime": "^1.3.4",
|
||||||
|
"parseurl": "^1.3.3",
|
||||||
|
"range-parser": "^1.2.0",
|
||||||
|
"type-is": "^1.6.18"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-releases": {
|
"node_modules/node-releases": {
|
||||||
"version": "2.0.18",
|
"version": "2.0.18",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
|
||||||
|
@ -5214,6 +5417,15 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/parseurl": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||||
|
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/path-exists": {
|
"node_modules/path-exists": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||||
|
@ -5524,6 +5736,15 @@
|
||||||
"integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==",
|
"integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/range-parser": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||||
|
@ -6214,6 +6435,19 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/type-is": {
|
||||||
|
"version": "1.6.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||||
|
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"media-typer": "0.3.0",
|
||||||
|
"mime-types": "~2.1.24"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.5.4",
|
"version": "5.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
|
||||||
|
|
|
@ -8,7 +8,9 @@
|
||||||
"lint": "eslint --no-error-on-unmatched-pattern src/**/*.js src/*.js tests/**/*.test.js tests/**/*.js tests/*.test.js tests/*.js utils/**/*.js utils/*.js",
|
"lint": "eslint --no-error-on-unmatched-pattern src/**/*.js src/*.js tests/**/*.test.js tests/**/*.js tests/*.test.js tests/*.js utils/**/*.js utils/*.js",
|
||||||
"lint:fix": "npm run lint -- --fix",
|
"lint:fix": "npm run lint -- --fix",
|
||||||
"start": "node dist/svr.js",
|
"start": "node dist/svr.js",
|
||||||
"test": "jest"
|
"test": "jest",
|
||||||
|
"test:middleware": "jest tests/middleware",
|
||||||
|
"test:utils": "jest tests/utils"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.9.0",
|
"@eslint/js": "^9.9.0",
|
||||||
|
@ -22,6 +24,7 @@
|
||||||
"eslint-plugin-prettier": "^5.2.1",
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
"globals": "^15.9.0",
|
"globals": "^15.9.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
|
"node-mocks-http": "^1.15.1",
|
||||||
"prettier": "^3.3.3"
|
"prettier": "^3.3.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -51,18 +51,18 @@ module.exports = (req, res, logFacilities, config, next) => {
|
||||||
address = address.replace(/\/+/g, "/");
|
address = address.replace(/\/+/g, "/");
|
||||||
tempRewrittenURL = address;
|
tempRewrittenURL = address;
|
||||||
}
|
}
|
||||||
if (
|
try {
|
||||||
matchHostname(mapEntry.host, req.headers.host) &&
|
if (
|
||||||
ipMatch(
|
matchHostname(mapEntry.host, req.headers.host) &&
|
||||||
mapEntry.ip,
|
ipMatch(
|
||||||
req.socket ? req.socket.localAddress : undefined,
|
mapEntry.ip,
|
||||||
) &&
|
req.socket ? req.socket.localAddress : undefined,
|
||||||
address.match(createRegex(mapEntry.definingRegex)) &&
|
) &&
|
||||||
!(mapEntry.isNotDirectory && _fileState == 2) &&
|
address.match(createRegex(mapEntry.definingRegex)) &&
|
||||||
!(mapEntry.isNotFile && _fileState == 1)
|
!(mapEntry.isNotDirectory && _fileState == 2) &&
|
||||||
) {
|
!(mapEntry.isNotFile && _fileState == 1)
|
||||||
rewrittenURL = tempRewrittenURL;
|
) {
|
||||||
try {
|
rewrittenURL = tempRewrittenURL;
|
||||||
mapEntry.replacements.forEach((replacement) => {
|
mapEntry.replacements.forEach((replacement) => {
|
||||||
rewrittenURL = rewrittenURL.replace(
|
rewrittenURL = rewrittenURL.replace(
|
||||||
createRegex(replacement.regex),
|
createRegex(replacement.regex),
|
||||||
|
@ -70,10 +70,11 @@ module.exports = (req, res, logFacilities, config, next) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
if (mapEntry.append) rewrittenURL += mapEntry.append;
|
if (mapEntry.append) rewrittenURL += mapEntry.append;
|
||||||
} catch (err) {
|
break;
|
||||||
doCallback = false;
|
|
||||||
callback(err, null);
|
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
doCallback = false;
|
||||||
|
callback(err, null);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -771,9 +771,7 @@ module.exports = (req, res, logFacilities, config, next) => {
|
||||||
.replace(/&/g, "&")
|
.replace(/&/g, "&")
|
||||||
.replace(/</g, "<")
|
.replace(/</g, "<")
|
||||||
.replace(/>/g, ">")}</a></td><td>${
|
.replace(/>/g, ">")}</a></td><td>${
|
||||||
estats.isDirectory()
|
estats.isDirectory() ? "-" : sizify(estats.size)
|
||||||
? "-"
|
|
||||||
: sizify(estats.size)
|
|
||||||
}</td><td>${estats.mtime.toDateString()}</td></tr>\r\n`;
|
}</td><td>${estats.mtime.toDateString()}</td></tr>\r\n`;
|
||||||
|
|
||||||
// Determine the file type and set the appropriate image and alt text
|
// Determine the file type and set the appropriate image and alt text
|
||||||
|
|
97
tests/middleware/blocklist.test.js
Normal file
97
tests/middleware/blocklist.test.js
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
const middleware = require("../../src/utils/ipBlockList.js");
|
||||||
|
const cluster = require("../../src/utils/clusterBunShim.js");
|
||||||
|
|
||||||
|
jest.mock("../../src/utils/ipBlockList.js");
|
||||||
|
jest.mock("../../src/utils/clusterBunShim.js");
|
||||||
|
|
||||||
|
const ipBlockListAdd = jest.fn();
|
||||||
|
const ipBlockListCheck = jest.fn();
|
||||||
|
const ipBlockListRemove = jest.fn();
|
||||||
|
|
||||||
|
middleware.mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
check: ipBlockListCheck,
|
||||||
|
add: ipBlockListAdd,
|
||||||
|
remove: ipBlockListRemove,
|
||||||
|
raw: [],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
process.serverConfig = {
|
||||||
|
blacklist: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const blocklistMiddleware = require("../../src/middleware/blocklist");
|
||||||
|
|
||||||
|
describe("Blocklist middleware", () => {
|
||||||
|
let req, res, logFacilities, config, next;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
req = {
|
||||||
|
socket: {
|
||||||
|
realRemoteAddress: "127.0.0.1",
|
||||||
|
remoteAddress: "127.0.0.1",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
res = {
|
||||||
|
error: jest.fn(),
|
||||||
|
};
|
||||||
|
logFacilities = {
|
||||||
|
errmessage: jest.fn(),
|
||||||
|
};
|
||||||
|
config = {};
|
||||||
|
next = jest.fn();
|
||||||
|
|
||||||
|
cluster.isPrimary = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should call next if the IP is not in the blocklist", () => {
|
||||||
|
middleware().check.mockReturnValue(false);
|
||||||
|
|
||||||
|
blocklistMiddleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
expect(res.error).not.toHaveBeenCalled();
|
||||||
|
expect(logFacilities.errmessage).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should call res.error if the IP is in the blocklist", () => {
|
||||||
|
middleware().check.mockReturnValue(true);
|
||||||
|
|
||||||
|
blocklistMiddleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(next).not.toHaveBeenCalled();
|
||||||
|
expect(res.error).toHaveBeenCalledWith(403);
|
||||||
|
expect(logFacilities.errmessage).toHaveBeenCalledWith(
|
||||||
|
"Client is in the block list.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should block an IP", () => {
|
||||||
|
middleware().check.mockReturnValue(false);
|
||||||
|
|
||||||
|
const ip = ["192.168.1.1"];
|
||||||
|
const log = jest.fn();
|
||||||
|
const passCommand = jest.fn();
|
||||||
|
|
||||||
|
blocklistMiddleware.commands.block(ip, log, passCommand);
|
||||||
|
|
||||||
|
expect(ipBlockListAdd).toHaveBeenCalledWith("::ffff:192.168.1.1");
|
||||||
|
expect(process.serverConfig.blacklist).toEqual(middleware().raw);
|
||||||
|
expect(log).toHaveBeenCalledWith("IPs successfully blocked.");
|
||||||
|
expect(passCommand).toHaveBeenCalledWith(ip, log);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should unblock an IP", () => {
|
||||||
|
const ip = ["192.168.1.1"];
|
||||||
|
const log = jest.fn();
|
||||||
|
const passCommand = jest.fn();
|
||||||
|
|
||||||
|
blocklistMiddleware.commands.unblock(ip, log, passCommand);
|
||||||
|
|
||||||
|
expect(ipBlockListRemove).toHaveBeenCalledWith("::ffff:192.168.1.1");
|
||||||
|
expect(process.serverConfig.blacklist).toEqual(middleware().raw);
|
||||||
|
expect(log).toHaveBeenCalledWith("IPs successfully unblocked.");
|
||||||
|
expect(passCommand).toHaveBeenCalledWith(ip, log);
|
||||||
|
});
|
||||||
|
});
|
67
tests/middleware/checkForbiddenPaths.test.js
Normal file
67
tests/middleware/checkForbiddenPaths.test.js
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
const forbiddenPaths = require("../../src/utils/forbiddenPaths.js");
|
||||||
|
|
||||||
|
jest.mock("../../src/utils/forbiddenPaths.js", () => ({
|
||||||
|
getInitializePath: jest.fn(() => "/forbidden"),
|
||||||
|
isForbiddenPath: jest.fn((path) => path === "/forbidden"),
|
||||||
|
isIndexOfForbiddenPath: jest.fn((path) => path.includes("/forbidden")),
|
||||||
|
forbiddenPaths: {
|
||||||
|
config: "/forbidden",
|
||||||
|
certificates: [],
|
||||||
|
svrjs: "/forbidden",
|
||||||
|
serverSideScripts: ["/forbidden"],
|
||||||
|
serverSideScriptDirectories: ["/forbidden"],
|
||||||
|
temp: "/forbidden",
|
||||||
|
log: "/forbidden",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
process.serverConfig = {
|
||||||
|
secure: true,
|
||||||
|
sni: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
process.dirname = "/usr/lib/mocksvrjs";
|
||||||
|
process.filename = "/usr/lib/mocksvrjs/svr.js";
|
||||||
|
|
||||||
|
const middleware = require("../../src/middleware/checkForbiddenPaths.js");
|
||||||
|
|
||||||
|
describe("Forbidden path checking middleware", () => {
|
||||||
|
let req, res, logFacilities, config, next;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
req = {
|
||||||
|
parsedURL: { pathname: "/forbidden" },
|
||||||
|
isProxy: false,
|
||||||
|
};
|
||||||
|
res = {
|
||||||
|
error: jest.fn(),
|
||||||
|
};
|
||||||
|
logFacilities = {
|
||||||
|
errmessage: jest.fn(),
|
||||||
|
};
|
||||||
|
config = {
|
||||||
|
enableLogging: true,
|
||||||
|
enableRemoteLogBrowsing: false,
|
||||||
|
exposeServerVersion: false,
|
||||||
|
disableServerSideScriptExpose: true,
|
||||||
|
};
|
||||||
|
next = jest.fn();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should deny access to forbidden paths", () => {
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(res.error).toHaveBeenCalledWith(403);
|
||||||
|
expect(logFacilities.errmessage).toHaveBeenCalled();
|
||||||
|
expect(next).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should allow access to non-forbidden paths", () => {
|
||||||
|
req.parsedURL.pathname = "/allowed";
|
||||||
|
forbiddenPaths.isForbiddenPath.mockReturnValue(false);
|
||||||
|
forbiddenPaths.isIndexOfForbiddenPath.mockReturnValue(false);
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(res.error).not.toHaveBeenCalled();
|
||||||
|
expect(logFacilities.errmessage).not.toHaveBeenCalled();
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
54
tests/middleware/defaultHandlerChecks.test.js
Normal file
54
tests/middleware/defaultHandlerChecks.test.js
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
const middleware = require("../../src/middleware/defaultHandlerChecks.js");
|
||||||
|
const httpMocks = require("node-mocks-http");
|
||||||
|
|
||||||
|
describe("Default handler checks middleware", () => {
|
||||||
|
let req, res, logFacilities, config, next;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
req = httpMocks.createRequest();
|
||||||
|
res = httpMocks.createResponse();
|
||||||
|
logFacilities = {
|
||||||
|
errmessage: jest.fn(),
|
||||||
|
};
|
||||||
|
config = {
|
||||||
|
getCustomHeaders: jest.fn(() => ({})),
|
||||||
|
generateServerString: jest.fn(() => "Server String"),
|
||||||
|
};
|
||||||
|
next = jest.fn();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return 501 and log error message if req.isProxy is true", () => {
|
||||||
|
req.isProxy = true;
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(res._getStatusCode()).toBe(501);
|
||||||
|
expect(logFacilities.errmessage).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining("doesn't support proxy without proxy mod."),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return 204 if req.method is OPTIONS", () => {
|
||||||
|
req.method = "OPTIONS";
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(res._getStatusCode()).toBe(204);
|
||||||
|
expect(res._getHeaders()).toHaveProperty(
|
||||||
|
"allow",
|
||||||
|
"GET, POST, HEAD, OPTIONS",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should call res.error with 405 and log error message if req.method is not GET, POST, or HEAD", () => {
|
||||||
|
req.method = "PUT";
|
||||||
|
res.error = jest.fn();
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(res.error).toHaveBeenCalledWith(405);
|
||||||
|
expect(logFacilities.errmessage).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining("Invaild method: PUT"),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should call next if req.method is GET, POST, or HEAD and req.isProxy is false", () => {
|
||||||
|
req.method = "GET";
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
281
tests/middleware/nonStandardCodesAndHttpAuthentication.test.js
Normal file
281
tests/middleware/nonStandardCodesAndHttpAuthentication.test.js
Normal file
|
@ -0,0 +1,281 @@
|
||||||
|
const sha256 = require("../../src/utils/sha256.js");
|
||||||
|
const ipMatch = require("../../src/utils/ipMatch.js");
|
||||||
|
const matchHostname = require("../../src/utils/matchHostname.js");
|
||||||
|
const ipBlockList = require("../../src/utils/ipBlockList.js");
|
||||||
|
const cluster = require("../../src/utils/clusterBunShim.js");
|
||||||
|
|
||||||
|
jest.mock("../../src/utils/sha256.js");
|
||||||
|
jest.mock("../../src/utils/ipMatch.js");
|
||||||
|
jest.mock("../../src/utils/matchHostname.js");
|
||||||
|
jest.mock("../../src/utils/ipBlockList.js");
|
||||||
|
jest.mock("../../src/utils/clusterBunShim.js");
|
||||||
|
|
||||||
|
let mockScryptHash = "mocked-scrypt-hash";
|
||||||
|
let mockPbkdf2Hash = "mocked-pbkdf2-hash";
|
||||||
|
|
||||||
|
jest.mock("crypto", () => {
|
||||||
|
return {
|
||||||
|
scrypt: jest.fn((password, salt, keylen, callback) => {
|
||||||
|
// Mock implementation for crypto.scrypt
|
||||||
|
callback(null, Buffer.from(mockScryptHash));
|
||||||
|
}),
|
||||||
|
pbkdf2: jest.fn((password, salt, iterations, keylen, digest, callback) => {
|
||||||
|
// Mock implementation for crypto.pbkdf2
|
||||||
|
callback(null, Buffer.from(mockPbkdf2Hash));
|
||||||
|
}),
|
||||||
|
// Add other properties or methods of crypto module if needed
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
process.serverConfig = {
|
||||||
|
nonStandardCodes: [
|
||||||
|
{
|
||||||
|
host: "example.com",
|
||||||
|
ip: "192.168.1.1",
|
||||||
|
url: "/test/path",
|
||||||
|
scode: 403,
|
||||||
|
users: ["127.0.0.1"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
host: "example.com",
|
||||||
|
ip: "192.168.1.1",
|
||||||
|
url: "/test/path2",
|
||||||
|
scode: 401,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
process.messageEventListeners = [];
|
||||||
|
|
||||||
|
process.send = undefined;
|
||||||
|
|
||||||
|
const middleware = require("../../src/middleware/nonStandardCodesAndHttpAuthentication.js");
|
||||||
|
|
||||||
|
describe("Non-standard codes and HTTP authentication middleware", () => {
|
||||||
|
let req, res, logFacilities, config, next;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
req = {
|
||||||
|
socket: {
|
||||||
|
realRemoteAddress: "127.0.0.1",
|
||||||
|
localAddress: "192.168.1.1",
|
||||||
|
},
|
||||||
|
parsedURL: {
|
||||||
|
pathname: "/test/path",
|
||||||
|
},
|
||||||
|
url: "/test/path",
|
||||||
|
headers: {
|
||||||
|
host: "example.com",
|
||||||
|
},
|
||||||
|
isProxy: false,
|
||||||
|
};
|
||||||
|
res = {
|
||||||
|
error: jest.fn(),
|
||||||
|
redirect: jest.fn(),
|
||||||
|
};
|
||||||
|
logFacilities = {
|
||||||
|
errmessage: jest.fn(),
|
||||||
|
reqmessage: jest.fn(),
|
||||||
|
};
|
||||||
|
config = {
|
||||||
|
getCustomHeaders: jest.fn(),
|
||||||
|
users: [],
|
||||||
|
};
|
||||||
|
next = jest.fn();
|
||||||
|
process.serverConfig = {
|
||||||
|
nonStandardCodes: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
cluster.isPrimary = true;
|
||||||
|
config.getCustomHeaders.mockReturnValue({});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle non-standard codes", () => {
|
||||||
|
ipBlockList.mockReturnValue({
|
||||||
|
check: jest.fn().mockReturnValue(true),
|
||||||
|
});
|
||||||
|
matchHostname.mockReturnValue(true);
|
||||||
|
ipMatch.mockReturnValue(true);
|
||||||
|
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(res.error).toHaveBeenCalledWith(403);
|
||||||
|
expect(logFacilities.errmessage).toHaveBeenCalledWith("Content blocked.");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle HTTP authentication", () => {
|
||||||
|
req.parsedURL.pathname = "/test/path2";
|
||||||
|
req.url = "/test/path2";
|
||||||
|
matchHostname.mockReturnValue(true);
|
||||||
|
ipMatch.mockReturnValue(true);
|
||||||
|
config.users = [
|
||||||
|
{
|
||||||
|
name: "test",
|
||||||
|
pass: "test",
|
||||||
|
salt: "test",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
sha256.mockReturnValue("test");
|
||||||
|
req.headers.authorization = "Basic dGVzdDp0ZXN0";
|
||||||
|
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
expect(logFacilities.reqmessage).toHaveBeenCalledWith(
|
||||||
|
'Client is logged in as "test".',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle brute force protection", () => {
|
||||||
|
req.parsedURL.pathname = "/test/path2";
|
||||||
|
req.url = "/test/path2";
|
||||||
|
req.socket.realRemoteAddress = "127.0.0.2";
|
||||||
|
matchHostname.mockReturnValue(true);
|
||||||
|
ipMatch.mockReturnValue(true);
|
||||||
|
config.users = [
|
||||||
|
{
|
||||||
|
name: "test",
|
||||||
|
pass: "test2",
|
||||||
|
salt: "test",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
sha256.mockReturnValue("test");
|
||||||
|
req.headers.authorization = "Basic dGVzdDp0ZXN0";
|
||||||
|
|
||||||
|
// Maximum 10 login attempts by default
|
||||||
|
for (let i = 0; i < 11; i++) {
|
||||||
|
logFacilities.errmessage.mockClear();
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(next).not.toHaveBeenCalled();
|
||||||
|
expect(logFacilities.errmessage).toHaveBeenCalledWith(
|
||||||
|
"Brute force limit reached!",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle HTTP authentication with scrypt", () => {
|
||||||
|
req.parsedURL.pathname = "/test/path2";
|
||||||
|
req.url = "/test/path2";
|
||||||
|
matchHostname.mockReturnValue(true);
|
||||||
|
ipMatch.mockReturnValue(true);
|
||||||
|
config.users = [
|
||||||
|
{
|
||||||
|
name: "test",
|
||||||
|
pass: "74657374", // "test" converted to hex
|
||||||
|
salt: "test",
|
||||||
|
scrypt: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
mockScryptHash = "test";
|
||||||
|
req.headers.authorization = "Basic dGVzdDp0ZXN0";
|
||||||
|
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
expect(logFacilities.reqmessage).toHaveBeenCalledWith(
|
||||||
|
'Client is logged in as "test".',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle HTTP authentication with PBKDF2", () => {
|
||||||
|
req.parsedURL.pathname = "/test/path2";
|
||||||
|
req.url = "/test/path2";
|
||||||
|
matchHostname.mockReturnValue(true);
|
||||||
|
ipMatch.mockReturnValue(true);
|
||||||
|
config.users = [
|
||||||
|
{
|
||||||
|
name: "test",
|
||||||
|
pass: "74657374", // "test" converted to hex
|
||||||
|
salt: "test",
|
||||||
|
pbkdf2: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
mockPbkdf2Hash = "test";
|
||||||
|
req.headers.authorization = "Basic dGVzdDp0ZXN0";
|
||||||
|
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
expect(logFacilities.reqmessage).toHaveBeenCalledWith(
|
||||||
|
'Client is logged in as "test".',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should call next if no non-standard codes or HTTP authentication is needed", () => {
|
||||||
|
req.parsedURL.pathname = "/test/path3";
|
||||||
|
req.url = "/test/path3";
|
||||||
|
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle HTTP authentication with clustering", () => {
|
||||||
|
cluster.isPrimary = false;
|
||||||
|
req.parsedURL.pathname = "/test/path2";
|
||||||
|
req.url = "/test/path2";
|
||||||
|
matchHostname.mockReturnValue(true);
|
||||||
|
ipMatch.mockReturnValue(true);
|
||||||
|
config.users = [
|
||||||
|
{
|
||||||
|
name: "test",
|
||||||
|
pass: "test",
|
||||||
|
salt: "test",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
sha256.mockReturnValue("test");
|
||||||
|
req.headers.authorization = "Basic dGVzdDp0ZXN0";
|
||||||
|
let mockHandlers = [];
|
||||||
|
process.on = (eventType, eventListener) => {
|
||||||
|
if (eventType == "message") {
|
||||||
|
mockHandlers.push(eventListener);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
process.once = (eventType, eventListener) => {
|
||||||
|
const wrap = (...params) => {
|
||||||
|
eventListener(...params);
|
||||||
|
process.removeListener(eventType, wrap);
|
||||||
|
};
|
||||||
|
process.on(eventType, wrap);
|
||||||
|
};
|
||||||
|
process.addListener = process.on;
|
||||||
|
process.removeListener = (eventType, eventListener) => {
|
||||||
|
if (eventType == "message") {
|
||||||
|
let indexOfListener = mockHandlers.indexOf(eventListener);
|
||||||
|
if (indexOfListener != -1) mockHandlers.splice(indexOfListener, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
process.removeAllListeners = (eventType) => {
|
||||||
|
if (eventType == "message") {
|
||||||
|
mockHandlers = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
process.send = (message) => {
|
||||||
|
const mockWorker = {
|
||||||
|
send: (msg) => {
|
||||||
|
mockHandlers.forEach((handler) => handler(msg));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const mockServerConsole = {
|
||||||
|
climessage: () => {},
|
||||||
|
reqmessage: () => {},
|
||||||
|
resmessage: () => {},
|
||||||
|
errmessage: () => {},
|
||||||
|
locerrmessage: () => {},
|
||||||
|
locwarnmessage: () => {},
|
||||||
|
locmessage: () => {},
|
||||||
|
};
|
||||||
|
process.messageEventListeners.forEach((listenerWrapper) =>
|
||||||
|
listenerWrapper(mockWorker, mockServerConsole)(message),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
expect(logFacilities.reqmessage).toHaveBeenCalledWith(
|
||||||
|
'Client is logged in as "test".',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
91
tests/middleware/redirectTrailingSlashes.test.js
Normal file
91
tests/middleware/redirectTrailingSlashes.test.js
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
const fs = require("fs");
|
||||||
|
const middleware = require("../../src/middleware/redirectTrailingSlashes.js");
|
||||||
|
|
||||||
|
jest.mock("fs");
|
||||||
|
|
||||||
|
describe("Trailing slash redirection middleware", () => {
|
||||||
|
let req, res, logFacilities, config, next;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
req = {
|
||||||
|
isProxy: false,
|
||||||
|
parsedURL: { pathname: "/test", search: "?query=1", hash: "#hash" },
|
||||||
|
originalParsedURL: { pathname: "/test" },
|
||||||
|
};
|
||||||
|
res = {
|
||||||
|
redirect: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
};
|
||||||
|
logFacilities = {};
|
||||||
|
config = { disableTrailingSlashRedirects: false };
|
||||||
|
next = jest.fn();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should redirect if pathname does not end with a slash", () => {
|
||||||
|
fs.stat.mockImplementation((path, cb) => {
|
||||||
|
cb(null, { isDirectory: () => true });
|
||||||
|
});
|
||||||
|
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(res.redirect).toHaveBeenCalledWith("/test/?query=1#hash");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should not redirect if pathname ends with a slash", () => {
|
||||||
|
req.parsedURL.pathname = "/test/";
|
||||||
|
req.originalParsedURL.pathname = "/test/";
|
||||||
|
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should not redirect if disableTrailingSlashRedirects is true", () => {
|
||||||
|
config.disableTrailingSlashRedirects = true;
|
||||||
|
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should not redirect if isProxy is true", () => {
|
||||||
|
req.isProxy = true;
|
||||||
|
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should call next if fs.stat returns an error", () => {
|
||||||
|
fs.stat.mockImplementation((path, cb) => {
|
||||||
|
cb(new Error("File does not exist"));
|
||||||
|
});
|
||||||
|
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should call next if fs.stat returns a file that is not a directory", () => {
|
||||||
|
fs.stat.mockImplementation((path, cb) => {
|
||||||
|
cb(null, { isDirectory: () => false });
|
||||||
|
});
|
||||||
|
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should call res.error if next throws an error", () => {
|
||||||
|
fs.stat.mockImplementation((path, cb) => {
|
||||||
|
cb(null, { isDirectory: () => false });
|
||||||
|
});
|
||||||
|
next.mockImplementation(() => {
|
||||||
|
throw new Error("Next error");
|
||||||
|
});
|
||||||
|
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(res.error).toHaveBeenCalledWith(500, new Error("Next error"));
|
||||||
|
});
|
||||||
|
});
|
52
tests/middleware/redirects.test.js
Normal file
52
tests/middleware/redirects.test.js
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
const middleware = require("../../src/middleware/redirects.js");
|
||||||
|
|
||||||
|
describe("Redirects middleware", () => {
|
||||||
|
let req, res, logFacilities, config, next;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
req = {
|
||||||
|
headers: {},
|
||||||
|
socket: { encrypted: false, remoteAddress: "8.8.8.8" },
|
||||||
|
isProxy: false,
|
||||||
|
url: "/test",
|
||||||
|
};
|
||||||
|
res = {
|
||||||
|
redirect: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
};
|
||||||
|
logFacilities = {
|
||||||
|
errmessage: jest.fn(),
|
||||||
|
};
|
||||||
|
config = {
|
||||||
|
secure: true,
|
||||||
|
disableNonEncryptedServer: false,
|
||||||
|
disableToHTTPSRedirect: false,
|
||||||
|
port: 80,
|
||||||
|
sport: 443,
|
||||||
|
spubport: 8443,
|
||||||
|
wwwredirect: true,
|
||||||
|
domain: "example.com",
|
||||||
|
};
|
||||||
|
next = jest.fn();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should redirect to HTTPS if config.secure is true and connection is not encrypted", () => {
|
||||||
|
req.headers.host = "www.example.com";
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(res.redirect).toHaveBeenCalledWith("https://www.example.com/test");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should not redirect if connection is encrypted", () => {
|
||||||
|
req.headers.host = "www.example.com";
|
||||||
|
req.socket.encrypted = true;
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(res.redirect).not.toHaveBeenCalled();
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should redirect to www subdomain if config.wwwredirect is true and host does not start with www", () => {
|
||||||
|
req.headers.host = "example.com";
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(res.redirect).toHaveBeenCalledWith("https://example.com/test");
|
||||||
|
});
|
||||||
|
});
|
48
tests/middleware/responseHeaders.test.js
Normal file
48
tests/middleware/responseHeaders.test.js
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
const middleware = require("../../src/middleware/responseHeaders.js");
|
||||||
|
|
||||||
|
describe("Response header setting middleware", () => {
|
||||||
|
let req, res, next, config, logFacilities;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
req = { isProxy: false };
|
||||||
|
res = { setHeader: jest.fn() };
|
||||||
|
next = jest.fn();
|
||||||
|
config = {
|
||||||
|
getCustomHeaders: jest.fn(() => ({ "X-Custom-Header": "custom-value" })),
|
||||||
|
};
|
||||||
|
logFacilities = {};
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should set custom headers if req.isProxy is false", () => {
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(res.setHeader).toHaveBeenCalledWith(
|
||||||
|
"X-Custom-Header",
|
||||||
|
"custom-value",
|
||||||
|
);
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should not set custom headers if req.isProxy is true", () => {
|
||||||
|
req.isProxy = true;
|
||||||
|
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(res.setHeader).not.toHaveBeenCalled();
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should call next even if an error occurs while setting headers", () => {
|
||||||
|
res.setHeader.mockImplementation(() => {
|
||||||
|
throw new Error("test error");
|
||||||
|
});
|
||||||
|
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should have proxySafe property set to true", () => {
|
||||||
|
expect(middleware.proxySafe).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
143
tests/middleware/rewriteURL.test.js
Normal file
143
tests/middleware/rewriteURL.test.js
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
const middleware = require("../../src/middleware/rewriteURL.js");
|
||||||
|
const createRegex = require("../../src/utils/createRegex.js");
|
||||||
|
const sanitizeURL = require("../../src/utils/urlSanitizer.js");
|
||||||
|
const parseURL = require("../../src/utils/urlParser.js");
|
||||||
|
|
||||||
|
jest.mock("fs");
|
||||||
|
jest.mock("../../src/utils/urlSanitizer.js");
|
||||||
|
jest.mock("../../src/utils/urlParser.js");
|
||||||
|
jest.mock("../../src/utils/createRegex.js");
|
||||||
|
|
||||||
|
describe("rewriteURL middleware", () => {
|
||||||
|
let req, res, logFacilities, config, next;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
req = {
|
||||||
|
parsedURL: {
|
||||||
|
pathname: "/test",
|
||||||
|
search: "",
|
||||||
|
hash: "",
|
||||||
|
},
|
||||||
|
url: "/test",
|
||||||
|
headers: {
|
||||||
|
host: "test.com",
|
||||||
|
},
|
||||||
|
socket: {
|
||||||
|
encrypted: false,
|
||||||
|
localAddress: "127.0.0.1",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
res = {
|
||||||
|
error: jest.fn(),
|
||||||
|
};
|
||||||
|
logFacilities = {
|
||||||
|
resmessage: jest.fn(),
|
||||||
|
errmessage: jest.fn(),
|
||||||
|
};
|
||||||
|
config = {
|
||||||
|
rewriteMap: [],
|
||||||
|
domain: "test.com",
|
||||||
|
allowDoubleSlashes: false,
|
||||||
|
};
|
||||||
|
next = jest.fn();
|
||||||
|
|
||||||
|
// Make mocks call actual functions
|
||||||
|
createRegex.mockImplementation((...params) =>
|
||||||
|
jest.requireActual("../../src/utils/createRegex.js")(...params),
|
||||||
|
);
|
||||||
|
parseURL.mockImplementation((...params) =>
|
||||||
|
jest.requireActual("../../src/utils/urlParser.js")(...params),
|
||||||
|
);
|
||||||
|
sanitizeURL.mockImplementation((...params) =>
|
||||||
|
jest.requireActual("../../src/utils/urlSanitizer.js")(...params),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should call next if URL is not rewritten", () => {
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return 400 if URL decoding fails", () => {
|
||||||
|
req.parsedURL.pathname = "%";
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(res.error).toHaveBeenCalledWith(400);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return 500 if rewriteURL callback returns an error", () => {
|
||||||
|
config.rewriteMap = [
|
||||||
|
{
|
||||||
|
host: "test.com",
|
||||||
|
definingRegex: "/.*/",
|
||||||
|
replacements: [
|
||||||
|
{
|
||||||
|
regex: "/.*/",
|
||||||
|
replacement: "error",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
createRegex.mockImplementation(() => {
|
||||||
|
throw new Error("Test error");
|
||||||
|
});
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(res.error).toHaveBeenCalledWith(500, expect.any(Error));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return 400 if parsedURL is invalid", () => {
|
||||||
|
config.rewriteMap = [
|
||||||
|
{
|
||||||
|
host: "test.com",
|
||||||
|
definingRegex: "/.*/",
|
||||||
|
replacements: [
|
||||||
|
{
|
||||||
|
regex: "/.*/",
|
||||||
|
replacement: "/new",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
parseURL.mockImplementation(() => {
|
||||||
|
throw new Error("Test error");
|
||||||
|
});
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(res.error).toHaveBeenCalledWith(400, expect.any(Error));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return 403 if URL is sanitized", () => {
|
||||||
|
config.rewriteMap = [
|
||||||
|
{
|
||||||
|
host: "test.com",
|
||||||
|
definingRegex: "/.*/",
|
||||||
|
replacements: [
|
||||||
|
{
|
||||||
|
regex: "/.*/",
|
||||||
|
replacement: "/new",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
sanitizeURL.mockReturnValue("/sanitized");
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(res.error).toHaveBeenCalledWith(403);
|
||||||
|
expect(logFacilities.errmessage).toHaveBeenCalledWith("Content blocked.");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should call next if URL is rewritten successfully", () => {
|
||||||
|
config.rewriteMap = [
|
||||||
|
{
|
||||||
|
host: "test.com",
|
||||||
|
definingRegex: "/.*/",
|
||||||
|
replacements: [
|
||||||
|
{
|
||||||
|
regex: "/.*/",
|
||||||
|
replacement: "/new",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
185
tests/middleware/staticFileServingAndDirectoryListing.test.js
Normal file
185
tests/middleware/staticFileServingAndDirectoryListing.test.js
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
const middleware = require("../../src/middleware/staticFileServingAndDirectoryListings.js");
|
||||||
|
const fs = require("fs");
|
||||||
|
const http = require("http");
|
||||||
|
const httpMocks = require("node-mocks-http");
|
||||||
|
|
||||||
|
jest.mock("fs");
|
||||||
|
|
||||||
|
describe("Static file serving and directory listings middleware", () => {
|
||||||
|
let req, res, logFacilities, config, next;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
req = httpMocks.createRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "/",
|
||||||
|
headers: {
|
||||||
|
host: "example.com",
|
||||||
|
"accept-encoding": "gzip, deflate, br",
|
||||||
|
"user-agent": "Mozilla/5.0",
|
||||||
|
},
|
||||||
|
socket: {
|
||||||
|
localAddress: "127.0.0.1",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
req.parsedURL = {
|
||||||
|
pathname: "/",
|
||||||
|
};
|
||||||
|
req.originalParsedURL = {
|
||||||
|
pathname: "/",
|
||||||
|
};
|
||||||
|
res = httpMocks.createResponse({
|
||||||
|
eventEmitter: require("events").EventEmitter,
|
||||||
|
});
|
||||||
|
res.error = (statusCode) => {
|
||||||
|
// Very simple replacement of res.error
|
||||||
|
res.writeHead(statusCode, { "Content-Type": "text/plain" });
|
||||||
|
res.end(statusCode + " " + http.STATUS_CODES[statusCode]);
|
||||||
|
};
|
||||||
|
logFacilities = {
|
||||||
|
errmessage: jest.fn(),
|
||||||
|
resmessage: jest.fn(),
|
||||||
|
};
|
||||||
|
config = {
|
||||||
|
enableDirectoryListing: true,
|
||||||
|
enableDirectoryListingVHost: [],
|
||||||
|
enableCompression: true,
|
||||||
|
dontCompress: [],
|
||||||
|
generateServerString: jest.fn().mockReturnValue("Server"),
|
||||||
|
};
|
||||||
|
next = jest.fn();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return 404 if file does not exist", async () => {
|
||||||
|
fs.stat.mockImplementation((path, cb) => {
|
||||||
|
cb({ code: "ENOENT" });
|
||||||
|
});
|
||||||
|
|
||||||
|
await middleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(404);
|
||||||
|
expect(logFacilities.errmessage).toHaveBeenCalledWith(
|
||||||
|
"Resource not found.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return 403 if directory listing is disabled", async () => {
|
||||||
|
config.enableDirectoryListing = false;
|
||||||
|
fs.stat.mockImplementation((path, cb) => {
|
||||||
|
cb(null, { isDirectory: () => true, isFile: () => false });
|
||||||
|
});
|
||||||
|
|
||||||
|
await middleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(403);
|
||||||
|
expect(logFacilities.errmessage).toHaveBeenCalledWith(
|
||||||
|
"Directory listing is disabled.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return 403 if access is denied", async () => {
|
||||||
|
fs.stat.mockImplementation((path, cb) => {
|
||||||
|
cb({ code: "EACCES" });
|
||||||
|
});
|
||||||
|
|
||||||
|
await middleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(403);
|
||||||
|
expect(logFacilities.errmessage).toHaveBeenCalledWith("Access denied.");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return 414 if the URI is too long", async () => {
|
||||||
|
fs.stat.mockImplementation((path, cb) => {
|
||||||
|
cb({ code: "ENAMETOOLONG" });
|
||||||
|
});
|
||||||
|
|
||||||
|
await middleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(414);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return 503 if the server is unable to handle the request", async () => {
|
||||||
|
fs.stat.mockImplementation((path, cb) => {
|
||||||
|
cb({ code: "EMFILE" });
|
||||||
|
});
|
||||||
|
|
||||||
|
await middleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(503);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return 508 if a loop is detected in symbolic links", async () => {
|
||||||
|
fs.stat.mockImplementation((path, cb) => {
|
||||||
|
cb({ code: "ELOOP" });
|
||||||
|
});
|
||||||
|
|
||||||
|
await middleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(508);
|
||||||
|
expect(logFacilities.errmessage).toHaveBeenCalledWith(
|
||||||
|
"Symbolic link loop detected.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return 500 if an unknown error occurs", async () => {
|
||||||
|
fs.stat.mockImplementation((path, cb) => {
|
||||||
|
cb(new Error("Unknown error"));
|
||||||
|
});
|
||||||
|
|
||||||
|
await middleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(500);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return 501 if the file is a block device, character device, FIFO, or socket", async () => {
|
||||||
|
fs.stat.mockImplementation((path, cb) => {
|
||||||
|
cb(null, {
|
||||||
|
isDirectory: () => false,
|
||||||
|
isFile: () => false,
|
||||||
|
isBlockDevice: () => true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await middleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(501);
|
||||||
|
expect(logFacilities.errmessage).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining("doesn't support block devices"),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return a directory listing if the path is a directory and directory listing is enabled", async () => {
|
||||||
|
fs.readdir.mockImplementation((path, cb) => {
|
||||||
|
cb(null, ["file1.txt", "file2.txt"]);
|
||||||
|
});
|
||||||
|
fs.readFile.mockImplementation((path, cb) => {
|
||||||
|
if (path.match(/(?:^|\/)file[12]\.txt$/)) {
|
||||||
|
cb(null, Buffer.from("test"));
|
||||||
|
} else {
|
||||||
|
cb({ code: "ENOENT" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fs.stat.mockImplementation((path, cb) => {
|
||||||
|
if (!path.match(/(?:^|\/)file[12]\.txt$/)) {
|
||||||
|
cb(null, { isDirectory: () => true, isFile: () => false });
|
||||||
|
} else {
|
||||||
|
cb(null, {
|
||||||
|
isDirectory: () => false,
|
||||||
|
isFile: () => true,
|
||||||
|
size: 1024,
|
||||||
|
mtime: new Date(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await middleware(req, res, logFacilities, config, next);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
expect(res._getData()).toContain("Directory: /");
|
||||||
|
expect(res._getData()).toContain("file1.txt");
|
||||||
|
expect(res._getData()).toContain("file2.txt");
|
||||||
|
});
|
||||||
|
});
|
77
tests/middleware/status.test.js
Normal file
77
tests/middleware/status.test.js
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
const middleware = require("../../src/middleware/status.js");
|
||||||
|
const http = require("http");
|
||||||
|
const os = require("os");
|
||||||
|
|
||||||
|
describe("Status middleware", () => {
|
||||||
|
let req, res, logFacilities, config, next;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
req = {
|
||||||
|
parsedURL: { pathname: "/svrjsstatus.svr" },
|
||||||
|
headers: { host: "localhost" },
|
||||||
|
};
|
||||||
|
res = {
|
||||||
|
writeHead: jest.fn(),
|
||||||
|
end: jest.fn(),
|
||||||
|
head: "",
|
||||||
|
foot: "",
|
||||||
|
};
|
||||||
|
logFacilities = {};
|
||||||
|
config = {
|
||||||
|
allowStatus: true,
|
||||||
|
generateServerString: () => "Test Server",
|
||||||
|
};
|
||||||
|
next = jest.fn();
|
||||||
|
process.reqcounter = 100;
|
||||||
|
process.err4xxcounter = 10;
|
||||||
|
process.err5xxcounter = 5;
|
||||||
|
process.malformedcounter = 2;
|
||||||
|
process.uptime = jest.fn(() => 1000);
|
||||||
|
process.memoryUsage = jest.fn(() => ({ rss: 1024 }));
|
||||||
|
process.cpuUsage = jest.fn(() => ({ user: 500000, system: 500000 }));
|
||||||
|
process.pid = 1234;
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should set response headers and body when conditions are met", () => {
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(res.writeHead).toHaveBeenCalledWith(200, http.STATUS_CODES[200], {
|
||||||
|
"Content-Type": "text/html; charset=utf-8",
|
||||||
|
});
|
||||||
|
expect(res.end).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should call next function when conditions are not met", () => {
|
||||||
|
req.parsedURL.pathname = "/";
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle case insensitivity on Windows", () => {
|
||||||
|
req.parsedURL.pathname = "/SvrJsStatus.Svr";
|
||||||
|
jest.spyOn(os, "platform").mockReturnValue("win32");
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(res.writeHead).toHaveBeenCalledWith(200, http.STATUS_CODES[200], {
|
||||||
|
"Content-Type": "text/html; charset=utf-8",
|
||||||
|
});
|
||||||
|
expect(res.end).toHaveBeenCalled();
|
||||||
|
os.platform.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle undefined host header", () => {
|
||||||
|
req.headers.host = undefined;
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(res.writeHead).toHaveBeenCalledWith(200, http.STATUS_CODES[200], {
|
||||||
|
"Content-Type": "text/html; charset=utf-8",
|
||||||
|
});
|
||||||
|
expect(res.end).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle custom head and foot", () => {
|
||||||
|
const headContents = "<style>body { background-color: red; }</style>";
|
||||||
|
res.head = `<head>${headContents}</head>`;
|
||||||
|
res.foot = "<footer>Copyright 2022</footer>";
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(res.end).toHaveBeenCalledWith(expect.stringContaining(headContents));
|
||||||
|
expect(res.end).toHaveBeenCalledWith(expect.stringContaining(res.foot));
|
||||||
|
});
|
||||||
|
});
|
99
tests/middleware/urlSanitizer.test.js
Normal file
99
tests/middleware/urlSanitizer.test.js
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
const middleware = require("../../src/middleware/urlSanitizer.js");
|
||||||
|
const sanitizeURL = require("../../src/utils/urlSanitizer.js");
|
||||||
|
const parseURL = require("../../src/utils/urlParser.js");
|
||||||
|
|
||||||
|
jest.mock("../../src/utils/urlSanitizer.js");
|
||||||
|
jest.mock("../../src/utils/urlParser.js");
|
||||||
|
|
||||||
|
describe("Path sanitizer middleware", () => {
|
||||||
|
let req, res, logFacilities, config, next;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
req = {
|
||||||
|
parsedURL: {
|
||||||
|
pathname: "/test",
|
||||||
|
search: "?query=test",
|
||||||
|
hash: "#hash",
|
||||||
|
},
|
||||||
|
url: "/test?query=test#hash",
|
||||||
|
isProxy: false,
|
||||||
|
headers: {
|
||||||
|
host: "test.com",
|
||||||
|
},
|
||||||
|
socket: {
|
||||||
|
encrypted: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
res = {
|
||||||
|
redirect: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
};
|
||||||
|
logFacilities = {
|
||||||
|
resmessage: jest.fn(),
|
||||||
|
};
|
||||||
|
config = {
|
||||||
|
allowDoubleSlashes: false,
|
||||||
|
rewriteDirtyURLs: false,
|
||||||
|
domain: "test.com",
|
||||||
|
};
|
||||||
|
next = jest.fn();
|
||||||
|
|
||||||
|
sanitizeURL.mockImplementation((url) => url);
|
||||||
|
parseURL.mockImplementation((url) => ({ pathname: url }));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should call next if URL is not dirty", () => {
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should redirect if URL is dirty and rewriteDirtyURLs is false", () => {
|
||||||
|
req.parsedURL.pathname = "/dirty%20url";
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(res.redirect).toHaveBeenCalledWith(
|
||||||
|
"/dirty%20url?query=test#hash",
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
expect(next).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should rewrite URL if URL is dirty and rewriteDirtyURLs is true", () => {
|
||||||
|
req.parsedURL.pathname = "/dirty%20url";
|
||||||
|
config.rewriteDirtyURLs = true;
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(req.url).toBe("/dirty%20url?query=test#hash");
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should redirect if URL is dirty (sanitized via sanitizeURL) and rewriteDirtyURLs is false", () => {
|
||||||
|
req.parsedURL.pathname = "/dirty%20url";
|
||||||
|
sanitizeURL.mockImplementation((url) => url.replace(/dirty/g, "clean"));
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(res.redirect).toHaveBeenCalledWith(
|
||||||
|
"/clean%20url?query=test#hash",
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
expect(next).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should rewrite URL if URL is dirty (sanitized via sanitizeURL) and rewriteDirtyURLs is true", () => {
|
||||||
|
req.parsedURL.pathname = "/dirty%20url";
|
||||||
|
config.rewriteDirtyURLs = true;
|
||||||
|
sanitizeURL.mockImplementation((url) => url.replace(/dirty/g, "clean"));
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(req.url).toBe("/clean%20url?query=test#hash");
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle parseURL errors", () => {
|
||||||
|
req.parsedURL.pathname = "/dirty%20url";
|
||||||
|
config.rewriteDirtyURLs = true;
|
||||||
|
sanitizeURL.mockImplementation((url) => url.replace(/dirty/g, "clean"));
|
||||||
|
parseURL.mockImplementation(() => {
|
||||||
|
throw new Error("Parse error");
|
||||||
|
});
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(res.error).toHaveBeenCalledWith(400, new Error("Parse error"));
|
||||||
|
expect(next).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
68
tests/middleware/webRootPrefixes.test.js
Normal file
68
tests/middleware/webRootPrefixes.test.js
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
const middleware = require("../../src/middleware/webRootPostfixes.js");
|
||||||
|
const createRegex = require("../../src/utils/createRegex.js");
|
||||||
|
const ipMatch = require("../../src/utils/ipMatch.js");
|
||||||
|
const sanitizeURL = require("../../src/utils/urlSanitizer.js");
|
||||||
|
const parseURL = require("../../src/utils/urlParser.js");
|
||||||
|
|
||||||
|
jest.mock("../../src/utils/createRegex.js");
|
||||||
|
jest.mock("../../src/utils/ipMatch.js");
|
||||||
|
jest.mock("../../src/utils/urlSanitizer.js");
|
||||||
|
jest.mock("../../src/utils/urlParser.js");
|
||||||
|
|
||||||
|
describe("Web root postfixes middleware", () => {
|
||||||
|
let req, res, logFacilities, config, next;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
req = {
|
||||||
|
isProxy: false,
|
||||||
|
url: "/test",
|
||||||
|
parsedURL: { pathname: "/test" },
|
||||||
|
headers: { host: "test.com" },
|
||||||
|
socket: { localAddress: "127.0.0.1" },
|
||||||
|
};
|
||||||
|
res = { error: jest.fn() };
|
||||||
|
logFacilities = { resmessage: jest.fn(), errmessage: jest.fn() };
|
||||||
|
config = {
|
||||||
|
allowPostfixDoubleSlashes: true,
|
||||||
|
wwwrootPostfixPrefixesVHost: [],
|
||||||
|
wwwrootPostfixesVHost: [
|
||||||
|
{ host: "test.com", ip: "127.0.0.1", postfix: "postfix" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
next = jest.fn();
|
||||||
|
|
||||||
|
createRegex.mockReturnValue(new RegExp());
|
||||||
|
ipMatch.mockReturnValue(true);
|
||||||
|
sanitizeURL.mockImplementation((url) => url);
|
||||||
|
parseURL.mockImplementation((url) => ({ pathname: url }));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should add web root postfix", () => {
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(req.url).toBe("/postfix/test");
|
||||||
|
expect(logFacilities.resmessage).toHaveBeenCalledWith(
|
||||||
|
"Added web root postfix: /test => /postfix/test",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should not add web root postfix if req.isProxy is true", () => {
|
||||||
|
req.isProxy = true;
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(req.url).toBe("/test");
|
||||||
|
expect(logFacilities.resmessage).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should not add web root postfix if no matching config is found", () => {
|
||||||
|
config.wwwrootPostfixesVHost = [
|
||||||
|
{ host: "example.com", ip: "127.0.0.1", postfix: "postfix" },
|
||||||
|
];
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(req.url).toBe("/test");
|
||||||
|
expect(logFacilities.resmessage).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should call next function", () => {
|
||||||
|
middleware(req, res, logFacilities, config, next);
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,4 +1,4 @@
|
||||||
const createRegex = require("../../src/utils/createRegex");
|
const createRegex = require("../../src/utils/createRegex.js");
|
||||||
const os = require("os");
|
const os = require("os");
|
||||||
|
|
||||||
jest.mock("os", () => ({
|
jest.mock("os", () => ({
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const deepClone = require("../../src/utils/deepClone");
|
const deepClone = require("../../src/utils/deepClone.js");
|
||||||
|
|
||||||
describe("Deep cloning function", () => {
|
describe("Deep cloning function", () => {
|
||||||
test("should clone a simple object", () => {
|
test("should clone a simple object", () => {
|
||||||
|
|
|
@ -3,7 +3,7 @@ const {
|
||||||
isForbiddenPath,
|
isForbiddenPath,
|
||||||
isIndexOfForbiddenPath,
|
isIndexOfForbiddenPath,
|
||||||
forbiddenPaths,
|
forbiddenPaths,
|
||||||
} = require("../../src/utils/forbiddenPaths");
|
} = require("../../src/utils/forbiddenPaths.js");
|
||||||
const os = require("os");
|
const os = require("os");
|
||||||
|
|
||||||
jest.mock("os", () => ({
|
jest.mock("os", () => ({
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const generateErrorStack = require("../../src/utils/generateErrorStack");
|
const generateErrorStack = require("../../src/utils/generateErrorStack.js");
|
||||||
|
|
||||||
describe("Error stack generation function", () => {
|
describe("Error stack generation function", () => {
|
||||||
test("should return the original stack if it is V8-style", () => {
|
test("should return the original stack if it is V8-style", () => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const ipBlockList = require("../../src/utils/ipBlockList");
|
const ipBlockList = require("../../src/utils/ipBlockList.js");
|
||||||
|
|
||||||
describe("IP block list functionality", () => {
|
describe("IP block list functionality", () => {
|
||||||
let blockList;
|
let blockList;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const ipMatch = require("../../src/utils/ipMatch");
|
const ipMatch = require("../../src/utils/ipMatch.js");
|
||||||
|
|
||||||
describe("IP address matching function", () => {
|
describe("IP address matching function", () => {
|
||||||
test("should return true if IP1 is empty", () => {
|
test("should return true if IP1 is empty", () => {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const {
|
const {
|
||||||
calculateBroadcastIPv4FromCidr,
|
calculateBroadcastIPv4FromCidr,
|
||||||
calculateNetworkIPv4FromCidr,
|
calculateNetworkIPv4FromCidr,
|
||||||
} = require("../../src/utils/ipSubnetUtils");
|
} = require("../../src/utils/ipSubnetUtils.js");
|
||||||
|
|
||||||
describe("IPv4 subnet utilties", () => {
|
describe("IPv4 subnet utilties", () => {
|
||||||
describe("calculateBroadcastIPv4FromCidr", () => {
|
describe("calculateBroadcastIPv4FromCidr", () => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const matchHostname = require("../../src/utils/matchHostname");
|
const matchHostname = require("../../src/utils/matchHostname.js");
|
||||||
|
|
||||||
describe("Hostname matching function", () => {
|
describe("Hostname matching function", () => {
|
||||||
test("should return true if hostname is undefined", () => {
|
test("should return true if hostname is undefined", () => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const sha256 = require("../../src/utils/sha256");
|
const sha256 = require("../../src/utils/sha256.js");
|
||||||
const crypto = require("crypto");
|
const crypto = require("crypto");
|
||||||
|
|
||||||
// Mock the crypto module to simulate the absence of crypto support
|
// Mock the crypto module to simulate the absence of crypto support
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const sizify = require("../../src/utils/sizify");
|
const sizify = require("../../src/utils/sizify.js");
|
||||||
|
|
||||||
describe('"sizify" function', () => {
|
describe('"sizify" function', () => {
|
||||||
test('should return "0" for 0 bytes', () => {
|
test('should return "0" for 0 bytes', () => {
|
||||||
|
|
Reference in a new issue