Projektstart

This commit is contained in:
2026-01-22 15:49:12 +01:00
parent 7212eb6f7a
commit 57e5f652f8
10637 changed files with 2598792 additions and 64 deletions

View File

@@ -0,0 +1,13 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
open-pull-requests-limit: 10
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10

View File

@@ -0,0 +1,18 @@
name: CI
on:
push:
paths-ignore:
- 'docs/**'
- '*.md'
pull_request:
paths-ignore:
- 'docs/**'
- '*.md'
jobs:
test:
uses: fastify/workflows/.github/workflows/plugins-ci.yml@v3
with:
license-check: true
lint: true

2
backend/node_modules/@fastify/send/.taprc generated vendored Normal file
View File

@@ -0,0 +1,2 @@
files:
- test/**/*.test.js

521
backend/node_modules/@fastify/send/HISTORY.md generated vendored Normal file
View File

@@ -0,0 +1,521 @@
0.18.0 / 2022-03-23
===================
* Fix emitted 416 error missing headers property
* Limit the headers removed for 304 response
* deps: depd@2.0.0
- Replace internal `eval` usage with `Function` constructor
- Use instance methods on `process` to check for listeners
* deps: destroy@1.2.0
* deps: http-errors@2.0.0
- deps: depd@2.0.0
- deps: statuses@2.0.1
* deps: on-finished@2.4.1
* deps: statuses@2.0.1
0.17.2 / 2021-12-11
===================
* pref: ignore empty http tokens
* deps: http-errors@1.8.1
- deps: inherits@2.0.4
- deps: toidentifier@1.0.1
- deps: setprototypeof@1.2.0
* deps: ms@2.1.3
0.17.1 / 2019-05-10
===================
* Set stricter CSP header in redirect & error responses
* deps: range-parser@~1.2.1
0.17.0 / 2019-05-03
===================
* deps: http-errors@~1.7.2
- Set constructor name when possible
- Use `toidentifier` module to make class names
- deps: depd@~1.1.2
- deps: setprototypeof@1.1.1
- deps: statuses@'>= 1.5.0 < 2'
* deps: mime@1.6.0
- Add extensions for JPEG-2000 images
- Add new `font/*` types from IANA
- Add WASM mapping
- Update `.bdoc` to `application/bdoc`
- Update `.bmp` to `image/bmp`
- Update `.m4a` to `audio/mp4`
- Update `.rtf` to `application/rtf`
- Update `.wav` to `audio/wav`
- Update `.xml` to `application/xml`
- Update generic extensions to `application/octet-stream`:
`.deb`, `.dll`, `.dmg`, `.exe`, `.iso`, `.msi`
- Use mime-score module to resolve extension conflicts
* deps: ms@2.1.1
- Add `week`/`w` support
- Fix negative number handling
* deps: statuses@~1.5.0
* perf: remove redundant `path.normalize` call
0.16.2 / 2018-02-07
===================
* Fix incorrect end tag in default error & redirects
* deps: depd@~1.1.2
- perf: remove argument reassignment
* deps: encodeurl@~1.0.2
- Fix encoding `%` as last character
* deps: statuses@~1.4.0
0.16.1 / 2017-09-29
===================
* Fix regression in edge-case behavior for empty `path`
0.16.0 / 2017-09-27
===================
* Add `immutable` option
* Fix missing `</html>` in default error & redirects
* Use instance methods on steam to check for listeners
* deps: mime@1.4.1
- Add 70 new types for file extensions
- Set charset as "UTF-8" for .js and .json
* perf: improve path validation speed
0.15.6 / 2017-09-22
===================
* deps: debug@2.6.9
* perf: improve `If-Match` token parsing
0.15.5 / 2017-09-20
===================
* deps: etag@~1.8.1
- perf: replace regular expression with substring
* deps: fresh@0.5.2
- Fix handling of modified headers with invalid dates
- perf: improve ETag match loop
- perf: improve `If-None-Match` token parsing
0.15.4 / 2017-08-05
===================
* deps: debug@2.6.8
* deps: depd@~1.1.1
- Remove unnecessary `Buffer` loading
* deps: http-errors@~1.6.2
- deps: depd@1.1.1
0.15.3 / 2017-05-16
===================
* deps: debug@2.6.7
- deps: ms@2.0.0
* deps: ms@2.0.0
0.15.2 / 2017-04-26
===================
* deps: debug@2.6.4
- Fix `DEBUG_MAX_ARRAY_LENGTH`
- deps: ms@0.7.3
* deps: ms@1.0.0
0.15.1 / 2017-03-04
===================
* Fix issue when `Date.parse` does not return `NaN` on invalid date
* Fix strict violation in broken environments
0.15.0 / 2017-02-25
===================
* Support `If-Match` and `If-Unmodified-Since` headers
* Add `res` and `path` arguments to `directory` event
* Remove usage of `res._headers` private field
- Improves compatibility with Node.js 8 nightly
* Send complete HTML document in redirect & error responses
* Set default CSP header in redirect & error responses
* Use `res.getHeaderNames()` when available
* Use `res.headersSent` when available
* deps: debug@2.6.1
- Allow colors in workers
- Deprecated `DEBUG_FD` environment variable set to `3` or higher
- Fix error when running under React Native
- Use same color for same namespace
- deps: ms@0.7.2
* deps: etag@~1.8.0
* deps: fresh@0.5.0
- Fix false detection of `no-cache` request directive
- Fix incorrect result when `If-None-Match` has both `*` and ETags
- Fix weak `ETag` matching to match spec
- perf: delay reading header values until needed
- perf: enable strict mode
- perf: hoist regular expressions
- perf: remove duplicate conditional
- perf: remove unnecessary boolean coercions
- perf: skip checking modified time if ETag check failed
- perf: skip parsing `If-None-Match` when no `ETag` header
- perf: use `Date.parse` instead of `new Date`
* deps: http-errors@~1.6.1
- Make `message` property enumerable for `HttpError`s
- deps: setprototypeof@1.0.3
0.14.2 / 2017-01-23
===================
* deps: http-errors@~1.5.1
- deps: inherits@2.0.3
- deps: setprototypeof@1.0.2
- deps: statuses@'>= 1.3.1 < 2'
* deps: ms@0.7.2
* deps: statuses@~1.3.1
0.14.1 / 2016-06-09
===================
* Fix redirect error when `path` contains raw non-URL characters
* Fix redirect when `path` starts with multiple forward slashes
0.14.0 / 2016-06-06
===================
* Add `acceptRanges` option
* Add `cacheControl` option
* Attempt to combine multiple ranges into single range
* Correctly inherit from `Stream` class
* Fix `Content-Range` header in 416 responses when using `start`/`end` options
* Fix `Content-Range` header missing from default 416 responses
* Ignore non-byte `Range` headers
* deps: http-errors@~1.5.0
- Add `HttpError` export, for `err instanceof createError.HttpError`
- Support new code `421 Misdirected Request`
- Use `setprototypeof` module to replace `__proto__` setting
- deps: inherits@2.0.1
- deps: statuses@'>= 1.3.0 < 2'
- perf: enable strict mode
* deps: range-parser@~1.2.0
- Fix incorrectly returning -1 when there is at least one valid range
- perf: remove internal function
* deps: statuses@~1.3.0
- Add `421 Misdirected Request`
- perf: enable strict mode
* perf: remove argument reassignment
0.13.2 / 2016-03-05
===================
* Fix invalid `Content-Type` header when `send.mime.default_type` unset
0.13.1 / 2016-01-16
===================
* deps: depd@~1.1.0
- Support web browser loading
- perf: enable strict mode
* deps: destroy@~1.0.4
- perf: enable strict mode
* deps: escape-html@~1.0.3
- perf: enable strict mode
- perf: optimize string replacement
- perf: use faster string coercion
* deps: range-parser@~1.0.3
- perf: enable strict mode
0.13.0 / 2015-06-16
===================
* Allow Node.js HTTP server to set `Date` response header
* Fix incorrectly removing `Content-Location` on 304 response
* Improve the default redirect response headers
* Send appropriate headers on default error response
* Use `http-errors` for standard emitted errors
* Use `statuses` instead of `http` module for status messages
* deps: escape-html@1.0.2
* deps: etag@~1.7.0
- Improve stat performance by removing hashing
* deps: fresh@0.3.0
- Add weak `ETag` matching support
* deps: on-finished@~2.3.0
- Add defined behavior for HTTP `CONNECT` requests
- Add defined behavior for HTTP `Upgrade` requests
- deps: ee-first@1.1.1
* perf: enable strict mode
* perf: remove unnecessary array allocations
0.12.3 / 2015-05-13
===================
* deps: debug@~2.2.0
- deps: ms@0.7.1
* deps: depd@~1.0.1
* deps: etag@~1.6.0
- Improve support for JXcore
- Support "fake" stats objects in environments without `fs`
* deps: ms@0.7.1
- Prevent extraordinarily long inputs
* deps: on-finished@~2.2.1
0.12.2 / 2015-03-13
===================
* Throw errors early for invalid `extensions` or `index` options
* deps: debug@~2.1.3
- Fix high intensity foreground color for bold
- deps: ms@0.7.0
0.12.1 / 2015-02-17
===================
* Fix regression sending zero-length files
0.12.0 / 2015-02-16
===================
* Always read the stat size from the file
* Fix mutating passed-in `options`
* deps: mime@1.3.4
0.11.1 / 2015-01-20
===================
* Fix `root` path disclosure
0.11.0 / 2015-01-05
===================
* deps: debug@~2.1.1
* deps: etag@~1.5.1
- deps: crc@3.2.1
* deps: ms@0.7.0
- Add `milliseconds`
- Add `msecs`
- Add `secs`
- Add `mins`
- Add `hrs`
- Add `yrs`
* deps: on-finished@~2.2.0
0.10.1 / 2014-10-22
===================
* deps: on-finished@~2.1.1
- Fix handling of pipelined requests
0.10.0 / 2014-10-15
===================
* deps: debug@~2.1.0
- Implement `DEBUG_FD` env variable support
* deps: depd@~1.0.0
* deps: etag@~1.5.0
- Improve string performance
- Slightly improve speed for weak ETags over 1KB
0.9.3 / 2014-09-24
==================
* deps: etag@~1.4.0
- Support "fake" stats objects
0.9.2 / 2014-09-15
==================
* deps: depd@0.4.5
* deps: etag@~1.3.1
* deps: range-parser@~1.0.2
0.9.1 / 2014-09-07
==================
* deps: fresh@0.2.4
0.9.0 / 2014-09-07
==================
* Add `lastModified` option
* Use `etag` to generate `ETag` header
* deps: debug@~2.0.0
0.8.5 / 2014-09-04
==================
* Fix malicious path detection for empty string path
0.8.4 / 2014-09-04
==================
* Fix a path traversal issue when using `root`
0.8.3 / 2014-08-16
==================
* deps: destroy@1.0.3
- renamed from dethroy
* deps: on-finished@2.1.0
0.8.2 / 2014-08-14
==================
* Work around `fd` leak in Node.js 0.10 for `fs.ReadStream`
* deps: dethroy@1.0.2
0.8.1 / 2014-08-05
==================
* Fix `extensions` behavior when file already has extension
0.8.0 / 2014-08-05
==================
* Add `extensions` option
0.7.4 / 2014-08-04
==================
* Fix serving index files without root dir
0.7.3 / 2014-07-29
==================
* Fix incorrect 403 on Windows and Node.js 0.11
0.7.2 / 2014-07-27
==================
* deps: depd@0.4.4
- Work-around v8 generating empty stack traces
0.7.1 / 2014-07-26
==================
* deps: depd@0.4.3
- Fix exception when global `Error.stackTraceLimit` is too low
0.7.0 / 2014-07-20
==================
* Deprecate `hidden` option; use `dotfiles` option
* Add `dotfiles` option
* deps: debug@1.0.4
* deps: depd@0.4.2
- Add `TRACE_DEPRECATION` environment variable
- Remove non-standard grey color from color output
- Support `--no-deprecation` argument
- Support `--trace-deprecation` argument
0.6.0 / 2014-07-11
==================
* Deprecate `from` option; use `root` option
* Deprecate `send.etag()` -- use `etag` in `options`
* Deprecate `send.hidden()` -- use `hidden` in `options`
* Deprecate `send.index()` -- use `index` in `options`
* Deprecate `send.maxage()` -- use `maxAge` in `options`
* Deprecate `send.root()` -- use `root` in `options`
* Cap `maxAge` value to 1 year
* deps: debug@1.0.3
- Add support for multiple wildcards in namespaces
0.5.0 / 2014-06-28
==================
* Accept string for `maxAge` (converted by `ms`)
* Add `headers` event
* Include link in default redirect response
* Use `EventEmitter.listenerCount` to count listeners
0.4.3 / 2014-06-11
==================
* Do not throw un-catchable error on file open race condition
* Use `escape-html` for HTML escaping
* deps: debug@1.0.2
- fix some debugging output colors on node.js 0.8
* deps: finished@1.2.2
* deps: fresh@0.2.2
0.4.2 / 2014-06-09
==================
* fix "event emitter leak" warnings
* deps: debug@1.0.1
* deps: finished@1.2.1
0.4.1 / 2014-06-02
==================
* Send `max-age` in `Cache-Control` in correct format
0.4.0 / 2014-05-27
==================
* Calculate ETag with md5 for reduced collisions
* Fix wrong behavior when index file matches directory
* Ignore stream errors after request ends
- Goodbye `EBADF, read`
* Skip directories in index file search
* deps: debug@0.8.1
0.3.0 / 2014-04-24
==================
* Fix sending files with dots without root set
* Coerce option types
* Accept API options in options object
* Set etags to "weak"
* Include file path in etag
* Make "Can't set headers after they are sent." catchable
* Send full entity-body for multi range requests
* Default directory access to 403 when index disabled
* Support multiple index paths
* Support "If-Range" header
* Control whether to generate etags
* deps: mime@1.2.11
0.2.0 / 2014-01-29
==================
* update range-parser and fresh
0.1.4 / 2013-08-11
==================
* update fresh
0.1.3 / 2013-07-08
==================
* Revert "Fix fd leak"
0.1.2 / 2013-07-03
==================
* Fix fd leak
0.1.0 / 2012-08-25
==================
* add options parameter to send() that is passed to fs.createReadStream() [kanongil]
0.0.4 / 2012-08-16
==================
* allow custom "Accept-Ranges" definition
0.0.3 / 2012-07-16
==================
* fix normalization of the root directory. Closes #3
0.0.2 / 2012-07-09
==================
* add passing of req explicitly for now (YUCK)
0.0.1 / 2010-01-03
==================
* Initial release

23
backend/node_modules/@fastify/send/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,23 @@
MIT License
Copyright (c) 2012 TJ Holowaychuk
Copyright (c) 2014-2022 Douglas Christopher Wilson
Copyright (c) 2023 The Fastify Team
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.

311
backend/node_modules/@fastify/send/README.md generated vendored Normal file
View File

@@ -0,0 +1,311 @@
# @fastify/send
![CI](https://github.com/fastify/send/workflows/CI/badge.svg)
[![NPM version](https://img.shields.io/npm/v/@fastify/send.svg?style=flat)](https://www.npmjs.com/package/@fastify/send)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/)
Send is a library for streaming files from the file system as an HTTP response
supporting partial responses (Ranges), conditional-GET negotiation (If-Match,
If-Unmodified-Since, If-None-Match, If-Modified-Since), high test coverage,
and granular events which may be leveraged to take appropriate actions in your
application or framework.
## Installation
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):
```bash
$ npm install @fastify/send
```
## API
```js
var send = require('@fastify/send')
```
### send(req, path, [options])
Create a new `SendStream` for the given path to send to a `res`. The `req` is
the Node.js HTTP request and the `path` is a urlencoded path to send (urlencoded,
not the actual file-system path).
#### Options
##### acceptRanges
Enable or disable accepting ranged requests, defaults to true.
Disabling this will not send `Accept-Ranges` and ignore the contents
of the `Range` request header.
##### cacheControl
Enable or disable setting `Cache-Control` response header, defaults to
true. Disabling this will ignore the `immutable` and `maxAge` options.
##### dotfiles
Set how "dotfiles" are treated when encountered. A dotfile is a file
or directory that begins with a dot ("."). Note this check is done on
the path itself without checking if the path exists on the
disk. If `root` is specified, only the dotfiles above the root are
checked (i.e. the root itself can be within a dotfile when set
to "deny").
- `'allow'` No special treatment for dotfiles.
- `'deny'` Send a 403 for any request for a dotfile.
- `'ignore'` Pretend like the dotfile does not exist and 404.
The default value is _similar_ to `'ignore'`, with the exception that
this default will not ignore the files within a directory that begins
with a dot, for backward-compatibility.
##### end
Byte offset at which the stream ends, defaults to the length of the file
minus 1. The end is inclusive in the stream, meaning `end: 3` will include
the 4th byte in the stream.
##### etag
Enable or disable etag generation, defaults to true.
##### extensions
If a given file doesn't exist, try appending one of the given extensions,
in the given order. By default, this is disabled (set to `false`). An
example value that will serve extension-less HTML files: `['html', 'htm']`.
This is skipped if the requested file already has an extension.
##### immutable
Enable or disable the `immutable` directive in the `Cache-Control` response
header, defaults to `false`. If set to `true`, the `maxAge` option should
also be specified to enable caching. The `immutable` directive will prevent
supported clients from making conditional requests during the life of the
`maxAge` option to check if the file has changed.
##### index
By default send supports "index.html" files, to disable this
set `false` or to supply a new index pass a string or an array
in preferred order.
##### lastModified
Enable or disable `Last-Modified` header, defaults to true. Uses the file
system's last modified value.
##### maxAge
Provide a max-age in milliseconds for HTTP caching, defaults to 0.
This can also be a string accepted by the
[ms](https://www.npmjs.org/package/ms#readme) module.
##### root
Serve files relative to `path`.
##### start
Byte offset at which the stream starts, defaults to 0. The start is inclusive,
meaning `start: 2` will include the 3rd byte in the stream.
#### Events
The `SendStream` is an event emitter and will emit the following events:
- `error` an error occurred `(err)`
- `directory` a directory was requested `(res, path)`
- `file` a file was requested `(path, stat)`
- `headers` the headers are about to be set on a file `(res, path, stat)`
- `stream` file streaming has started `(stream)`
- `end` streaming has completed
#### .pipe
The `pipe` method is used to pipe the response into the Node.js HTTP response
object, typically `send(req, path, options).pipe(res)`.
### .mime
The `mime` export is the global instance of the
[`mime` npm module](https://www.npmjs.com/package/mime).
This is used to configure the MIME types that are associated with file extensions
as well as other options for how to resolve the MIME type of a file (like the
default type to use for an unknown file extension).
## Error-handling
By default when no `error` listeners are present an automatic response will be
made, otherwise you have full control over the response, aka you may show a 5xx
page etc.
## Caching
It does _not_ perform internal caching, you should use a reverse proxy cache
such as Varnish for this, or those fancy things called CDNs. If your
application is small enough that it would benefit from single-node memory
caching, it's small enough that it does not need caching at all ;).
## Debugging
To enable `debug()` instrumentation output export __NODE_DEBUG__:
```
$ NODE_DEBUG=send node app
```
## Running tests
```
$ npm install
$ npm test
```
## Examples
### Serve a specific file
This simple example will send a specific file to all requests.
```js
var http = require('http')
var send = require('send')
var server = http.createServer(function onRequest (req, res) {
send(req, '/path/to/index.html')
.pipe(res)
})
server.listen(3000)
```
### Serve all files from a directory
This simple example will just serve up all the files in a
given directory as the top-level. For example, a request
`GET /foo.txt` will send back `/www/public/foo.txt`.
```js
var http = require('http')
var parseUrl = require('parseurl')
var send = require('@fastify/send')
var server = http.createServer(function onRequest (req, res) {
send(req, parseUrl(req).pathname, { root: '/www/public' })
.pipe(res)
})
server.listen(3000)
```
### Custom file types
```js
var http = require('http')
var parseUrl = require('parseurl')
var send = require('@fastify/send')
// Default unknown types to text/plain
send.mime.default_type = 'text/plain'
// Add a custom type
send.mime.define({
'application/x-my-type': ['x-mt', 'x-mtt']
})
var server = http.createServer(function onRequest (req, res) {
send(req, parseUrl(req).pathname, { root: '/www/public' })
.pipe(res)
})
server.listen(3000)
```
### Custom directory index view
This is an example of serving up a structure of directories with a
custom function to render a listing of a directory.
```js
var http = require('http')
var fs = require('fs')
var parseUrl = require('parseurl')
var send = require('@fastify/send')
// Transfer arbitrary files from within /www/example.com/public/*
// with a custom handler for directory listing
var server = http.createServer(function onRequest (req, res) {
send(req, parseUrl(req).pathname, { index: false, root: '/www/public' })
.once('directory', directory)
.pipe(res)
})
server.listen(3000)
// Custom directory handler
function directory (res, path) {
var stream = this
// redirect to trailing slash for consistent url
if (!stream.hasTrailingSlash()) {
return stream.redirect(path)
}
// get directory list
fs.readdir(path, function onReaddir (err, list) {
if (err) return stream.error(err)
// render an index for the directory
res.setHeader('Content-Type', 'text/plain; charset=UTF-8')
res.end(list.join('\n') + '\n')
})
}
```
### Serving from a root directory with custom error-handling
```js
var http = require('http')
var parseUrl = require('parseurl')
var send = require('@fastify/send')
var server = http.createServer(function onRequest (req, res) {
// your custom error-handling logic:
function error (err) {
res.statusCode = err.status || 500
res.end(err.message)
}
// your custom headers
function headers (res, path, stat) {
// serve all files for download
res.setHeader('Content-Disposition', 'attachment')
}
// your custom directory handling logic:
function redirect () {
res.statusCode = 301
res.setHeader('Location', req.url + '/')
res.end('Redirecting to ' + req.url + '/')
}
// transfer arbitrary files from within
// /www/example.com/public/*
send(req, parseUrl(req).pathname, { root: '/www/public' })
.on('error', error)
.on('directory', redirect)
.on('headers', headers)
.pipe(res)
})
server.listen(3000)
```
## License
[MIT](LICENSE)

View File

@@ -0,0 +1,13 @@
'use strict'
const benchmark = require('benchmark')
const collapseLeadingSlashes = require('../lib/collapseLeadingSlashes').collapseLeadingSlashes
const nonLeading = 'bla.json'
const hasLeading = '///./json'
new benchmark.Suite()
.add(nonLeading, function () { collapseLeadingSlashes(nonLeading) }, { minSamples: 100 })
.add(hasLeading, function () { collapseLeadingSlashes(hasLeading) }, { minSamples: 100 })
.on('cycle', function onCycle (event) { console.log(String(event.target)) })
.run({ async: false })

View File

@@ -0,0 +1,15 @@
'use strict'
const benchmark = require('benchmark')
const { containsDotFile } = require('../lib/containsDotFile')
const hasDotFileSimple = '.github'.split('/')
const hasDotFile = './.github'.split('/')
const noDotFile = './index.html'.split('/')
new benchmark.Suite()
.add(hasDotFileSimple.join('/'), function () { containsDotFile(hasDotFileSimple) }, { minSamples: 100 })
.add(noDotFile.join('/'), function () { containsDotFile(noDotFile) }, { minSamples: 100 })
.add(hasDotFile.join('/'), function () { containsDotFile(hasDotFile) }, { minSamples: 100 })
.on('cycle', function onCycle (event) { console.log(String(event.target)) })
.run({ async: false })

View File

@@ -0,0 +1,23 @@
'use strict'
const benchmark = require('benchmark')
const isUtf8MimeType = require('../lib/isUtf8MimeType').isUtf8MimeType
const applicationJson = 'application/json'
const applicationJavascript = 'application/javascript'
const textJson = 'text/json'
const textHtml = 'text/html'
const textJavascript = 'text/javascript'
const imagePng = 'image/png'
new benchmark.Suite()
.add('isUtf8MimeType', function () {
isUtf8MimeType(applicationJson)
isUtf8MimeType(applicationJavascript)
isUtf8MimeType(imagePng)
isUtf8MimeType(textJson)
isUtf8MimeType(textHtml)
isUtf8MimeType(textJavascript)
}, { minSamples: 100 })
.on('cycle', function onCycle (event) { console.log(String(event.target)) })
.run({ async: false })

View File

@@ -0,0 +1,14 @@
'use strict'
const benchmark = require('benchmark')
const { normalizeList } = require('../lib/normalizeList')
const validSingle = 'a'
const validArray = ['a', 'b', 'c']
new benchmark.Suite()
.add('false', function () { normalizeList(false) }, { minSamples: 100 })
.add('valid single', function () { normalizeList(validSingle) }, { minSamples: 100 })
.add('valid array', function () { normalizeList(validArray) }, { minSamples: 100 })
.on('cycle', function onCycle (event) { console.log(String(event.target)) })
.run({ async: false })

View File

@@ -0,0 +1,15 @@
'use strict'
const benchmark = require('benchmark')
const { parseBytesRange } = require('../lib/parseBytesRange')
const size150 = 150
const rangeSingle = 'bytes=0-100'
const rangeMultiple = 'bytes=0-4,90-99,5-75,100-199,101-102'
new benchmark.Suite()
.add('size: 150, bytes=0-100', function () { parseBytesRange(size150, rangeSingle) }, { minSamples: 100 })
.add('size: 150, bytes=0-4,90-99,5-75,100-199,101-102', function () { parseBytesRange(size150, rangeMultiple) }, { minSamples: 100 })
.on('cycle', function onCycle (event) { console.log(String(event.target)) })
.run({ async: false })

View File

@@ -0,0 +1 @@
<p>Hello, World</p>

14
backend/node_modules/@fastify/send/examples/simple.js generated vendored Normal file
View File

@@ -0,0 +1,14 @@
'use strict'
const http = require('http')
const send = require('..')
const path = require('path')
const indexPath = path.join(__dirname, 'index.html')
const server = http.createServer(function onRequest (req, res) {
send(req, indexPath)
.pipe(res)
})
server.listen(3000)

43
backend/node_modules/@fastify/send/index.js generated vendored Normal file
View File

@@ -0,0 +1,43 @@
/*!
* send
* Copyright(c) 2012 TJ Holowaychuk
* Copyright(c) 2014-2022 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict'
/**
* Module dependencies.
* @private
*/
const isUtf8MimeType = require('./lib/isUtf8MimeType').isUtf8MimeType
const mime = require('mime')
const SendStream = require('./lib/SendStream')
/**
* Return a `SendStream` for `req` and `path`.
*
* @param {object} req
* @param {string} path
* @param {object} [options]
* @return {SendStream}
* @public
*/
function send (req, path, options) {
return new SendStream(req, path, options)
}
/**
* Module exports.
* @public
*/
module.exports = send
module.exports.default = send
module.exports.send = send
module.exports.SendStream = SendStream
module.exports.isUtf8MimeType = isUtf8MimeType
module.exports.mime = mime

903
backend/node_modules/@fastify/send/lib/SendStream.js generated vendored Normal file
View File

@@ -0,0 +1,903 @@
/*!
* send
* Copyright(c) 2012 TJ Holowaychuk
* Copyright(c) 2014-2022 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict'
const fs = require('node:fs')
const path = require('node:path')
const Stream = require('node:stream')
const util = require('node:util')
const debug = require('node:util').debuglog('send')
const decode = require('fast-decode-uri-component')
const escapeHtml = require('escape-html')
const mime = require('mime')
const ms = require('@lukeed/ms')
const { clearHeaders } = require('./clearHeaders')
const { collapseLeadingSlashes } = require('./collapseLeadingSlashes')
const { containsDotFile } = require('./containsDotFile')
const { contentRange } = require('./contentRange')
const { createHtmlDocument } = require('./createHtmlDocument')
const { createHttpError } = require('./createHttpError')
const { isUtf8MimeType } = require('./isUtf8MimeType')
const { normalizeList } = require('./normalizeList')
const { parseBytesRange } = require('./parseBytesRange')
const { parseTokenList } = require('./parseTokenList')
const { setHeaders } = require('./setHeaders')
/**
* Path function references.
* @private
*/
const extname = path.extname
const join = path.join
const normalize = path.normalize
const resolve = path.resolve
const sep = path.sep
/**
* Regular expression for identifying a bytes Range header.
* @private
*/
const BYTES_RANGE_REGEXP = /^ *bytes=/
/**
* Maximum value allowed for the max age.
* @private
*/
const MAX_MAXAGE = 60 * 60 * 24 * 365 * 1000 // 1 year
/**
* Regular expression to match a path with a directory up component.
* @private
*/
const UP_PATH_REGEXP = /(?:^|[\\/])\.\.(?:[\\/]|$)/
const ERROR_RESPONSES = {
400: createHtmlDocument('Error', 'Bad Request'),
403: createHtmlDocument('Error', 'Forbidden'),
404: createHtmlDocument('Error', 'Not Found'),
412: createHtmlDocument('Error', 'Precondition Failed'),
416: createHtmlDocument('Error', 'Range Not Satisfiable'),
500: createHtmlDocument('Error', 'Internal Server Error')
}
const validDotFilesOptions = [
'allow',
'ignore',
'deny'
]
/**
* Initialize a `SendStream` with the given `path`.
*
* @param {Request} req
* @param {String} path
* @param {object} [options]
* @private
*/
function SendStream (req, path, options) {
if (!new.target) {
return new SendStream(req, path, options)
}
Stream.call(this)
const opts = options || {}
this.options = opts
this.path = path
this.req = req
this._acceptRanges = opts.acceptRanges !== undefined
? Boolean(opts.acceptRanges)
: true
this._cacheControl = opts.cacheControl !== undefined
? Boolean(opts.cacheControl)
: true
this._etag = opts.etag !== undefined
? Boolean(opts.etag)
: true
this._dotfiles = opts.dotfiles !== undefined
? validDotFilesOptions.indexOf(opts.dotfiles)
: 1 // 'ignore'
if (this._dotfiles === -1) {
throw new TypeError('dotfiles option must be "allow", "deny", or "ignore"')
}
this._extensions = opts.extensions !== undefined
? normalizeList(opts.extensions, 'extensions option')
: []
this._immutable = opts.immutable !== undefined
? Boolean(opts.immutable)
: false
this._index = opts.index !== undefined
? normalizeList(opts.index, 'index option')
: ['index.html']
this._lastModified = opts.lastModified !== undefined
? Boolean(opts.lastModified)
: true
this._maxage = opts.maxAge || opts.maxage
this._maxage = typeof this._maxage === 'string'
? ms.parse(this._maxage)
: Number(this._maxage)
// eslint-disable-next-line no-self-compare
this._maxage = this._maxage === this._maxage // fast path of isNaN(number)
? Math.min(Math.max(0, this._maxage), MAX_MAXAGE)
: 0
this._root = opts.root
? resolve(opts.root)
: null
}
/**
* Inherits from `Stream`.
*/
util.inherits(SendStream, Stream)
/**
* Set root `path`.
*
* @param {String} path
* @return {SendStream}
* @api private
*/
SendStream.prototype.root = function root (path) {
this._root = resolve(String(path))
debug('root %s', this._root)
return this
}
/**
* Emit error with `status`.
*
* @memberof SendStream
* @param {number} status
* @param {Error} [err]
* @this {Stream}
* @private
*/
SendStream.prototype.error = function error (status, err) {
// emit if listeners instead of responding
if (this.listenerCount('error') > 0) {
return this.emit('error', createHttpError(status, err))
}
const res = this.res
// clear existing headers
clearHeaders(res)
// add error headers
if (err && err.headers) {
setHeaders(res, err.headers)
}
const doc = ERROR_RESPONSES[status]
// send basic response
res.statusCode = status
res.setHeader('Content-Type', 'text/html; charset=UTF-8')
res.setHeader('Content-Length', doc[1])
res.setHeader('Content-Security-Policy', "default-src 'none'")
res.setHeader('X-Content-Type-Options', 'nosniff')
res.end(doc[0])
}
/**
* Check if the pathname ends with "/".
*
* @return {boolean}
* @private
*/
SendStream.prototype.hasTrailingSlash = function hasTrailingSlash () {
return this.path[this.path.length - 1] === '/'
}
/**
* Check if this is a conditional GET request.
*
* @return {Boolean}
* @api private
*/
SendStream.prototype.isConditionalGET = function isConditionalGET () {
return this.req.headers['if-match'] ||
this.req.headers['if-unmodified-since'] ||
this.req.headers['if-none-match'] ||
this.req.headers['if-modified-since']
}
SendStream.prototype.isNotModifiedFailure = function isNotModifiedFailure () {
const req = this.req
const res = this.res
// Always return stale when Cache-Control: no-cache
// to support end-to-end reload requests
// https://tools.ietf.org/html/rfc2616#section-14.9.4
if (
'cache-control' in req.headers &&
req.headers['cache-control'].indexOf('no-cache') !== -1
) {
return false
}
// if-none-match
if ('if-none-match' in req.headers) {
const ifNoneMatch = req.headers['if-none-match']
if (ifNoneMatch === '*') {
return true
}
const etag = res.getHeader('etag')
if (typeof etag !== 'string') {
return false
}
const etagL = etag.length
const isMatching = parseTokenList(ifNoneMatch, function (match) {
const mL = match.length
if (
(etagL === mL && match === etag) ||
(etagL > mL && 'W/' + match === etag)
) {
return true
}
})
if (isMatching) {
return true
}
/**
* A recipient MUST ignore If-Modified-Since if the request contains an
* If-None-Match header field; the condition in If-None-Match is considered
* to be a more accurate replacement for the condition in If-Modified-Since,
* and the two are only combined for the sake of interoperating with older
* intermediaries that might not implement If-None-Match.
*
* @see RFC 9110 section 13.1.3
*/
return false
}
// if-modified-since
if ('if-modified-since' in req.headers) {
const ifModifiedSince = req.headers['if-modified-since']
const lastModified = res.getHeader('last-modified')
if (!lastModified || (Date.parse(lastModified) <= Date.parse(ifModifiedSince))) {
return true
}
}
return false
}
/**
* Check if the request preconditions failed.
*
* @return {boolean}
* @private
*/
SendStream.prototype.isPreconditionFailure = function isPreconditionFailure () {
const req = this.req
const res = this.res
// if-match
const ifMatch = req.headers['if-match']
if (ifMatch) {
const etag = res.getHeader('ETag')
if (ifMatch !== '*') {
const isMatching = parseTokenList(ifMatch, function (match) {
if (
match === etag ||
'W/' + match === etag
) {
return true
}
}) || false
if (isMatching !== true) {
return true
}
}
}
// if-unmodified-since
if ('if-unmodified-since' in req.headers) {
const ifUnmodifiedSince = req.headers['if-unmodified-since']
const unmodifiedSince = Date.parse(ifUnmodifiedSince)
// eslint-disable-next-line no-self-compare
if (unmodifiedSince === unmodifiedSince) { // fast path of isNaN(number)
const lastModified = Date.parse(res.getHeader('Last-Modified'))
if (
// eslint-disable-next-line no-self-compare
lastModified !== lastModified ||// fast path of isNaN(number)
lastModified > unmodifiedSince
) {
return true
}
}
}
return false
}
/**
* Strip various content header fields for a change in entity.
*
* @private
*/
SendStream.prototype.removeContentHeaderFields = function removeContentHeaderFields () {
const res = this.res
res.removeHeader('Content-Encoding')
res.removeHeader('Content-Language')
res.removeHeader('Content-Length')
res.removeHeader('Content-Range')
res.removeHeader('Content-Type')
}
/**
* Respond with 304 not modified.
*
* @api private
*/
SendStream.prototype.notModified = function notModified () {
const res = this.res
debug('not modified')
this.removeContentHeaderFields()
res.statusCode = 304
res.end()
}
/**
* Raise error that headers already sent.
*
* @api private
*/
SendStream.prototype.headersAlreadySent = function headersAlreadySent () {
const err = new Error('Can\'t set headers after they are sent.')
debug('headers already sent')
this.error(500, err)
}
/**
* Check if the request is cacheable, aka
* responded with 2xx or 304 (see RFC 2616 section 14.2{5,6}).
*
* @return {Boolean}
* @api private
*/
SendStream.prototype.isCachable = function isCachable () {
const statusCode = this.res.statusCode
return (statusCode >= 200 && statusCode < 300) ||
statusCode === 304
}
/**
* Handle stat() error.
*
* @param {Error} error
* @private
*/
SendStream.prototype.onStatError = function onStatError (error) {
// POSIX throws ENAMETOOLONG and ENOTDIR, Windows only ENOENT
/* istanbul ignore next */
switch (error.code) {
case 'ENAMETOOLONG':
case 'ENOTDIR':
case 'ENOENT':
this.error(404, error)
break
default:
this.error(500, error)
break
}
}
/**
* Check if the range is fresh.
*
* @return {Boolean}
* @api private
*/
SendStream.prototype.isRangeFresh = function isRangeFresh () {
if (!('if-range' in this.req.headers)) {
return true
}
const ifRange = this.req.headers['if-range']
// if-range as etag
if (ifRange.indexOf('"') !== -1) {
const etag = this.res.getHeader('ETag')
return (etag && ifRange.indexOf(etag) !== -1) || false
}
const ifRangeTimestamp = Date.parse(ifRange)
// eslint-disable-next-line no-self-compare
if (ifRangeTimestamp !== ifRangeTimestamp) { // fast path of isNaN(number)
return false
}
// if-range as modified date
const lastModified = Date.parse(this.res.getHeader('Last-Modified'))
return (
// eslint-disable-next-line no-self-compare
lastModified !== lastModified || // fast path of isNaN(number)
lastModified <= ifRangeTimestamp
)
}
/**
* Redirect to path.
*
* @param {string} path
* @private
*/
SendStream.prototype.redirect = function redirect (path) {
const res = this.res
if (this.listenerCount('directory') > 0) {
this.emit('directory', res, path)
return
}
if (this.hasTrailingSlash()) {
this.error(403)
return
}
const loc = encodeURI(collapseLeadingSlashes(this.path + '/'))
const doc = createHtmlDocument('Redirecting', 'Redirecting to <a href="' + escapeHtml(loc) + '">' +
escapeHtml(loc) + '</a>')
// redirect
res.statusCode = 301
res.setHeader('Content-Type', 'text/html; charset=UTF-8')
res.setHeader('Content-Length', doc[1])
res.setHeader('Content-Security-Policy', "default-src 'none'")
res.setHeader('X-Content-Type-Options', 'nosniff')
res.setHeader('Location', loc)
res.end(doc[0])
}
/**
* Pipe to `res.
*
* @param {Stream} res
* @return {Stream} res
* @api public
*/
SendStream.prototype.pipe = function pipe (res) {
// root path
const root = this._root
// references
this.res = res
// decode the path
let path = decode(this.path)
if (path === null) {
this.error(400)
return res
}
// null byte(s)
if (~path.indexOf('\0')) {
this.error(400)
return res
}
let parts
if (root !== null) {
// normalize
if (path) {
path = normalize('.' + sep + path)
}
// malicious path
if (UP_PATH_REGEXP.test(path)) {
debug('malicious path "%s"', path)
this.error(403)
return res
}
// explode path parts
parts = path.split(sep)
// join / normalize from optional root dir
path = normalize(join(root, path))
} else {
// ".." is malicious without "root"
if (UP_PATH_REGEXP.test(path)) {
debug('malicious path "%s"', path)
this.error(403)
return res
}
// explode path parts
parts = normalize(path).split(sep)
// resolve the path
path = resolve(path)
}
// dotfile handling
if (
(
debug.enabled || // if debugging is enabled, then check for all cases to log allow case
this._dotfiles !== 0 // if debugging is not enabled, then only check if 'deny' or 'ignore' is set
) &&
containsDotFile(parts)
) {
switch (this._dotfiles) {
/* istanbul ignore next: unreachable, because NODE_DEBUG can not be set after process is running */
case 0: // 'allow'
debug('allow dotfile "%s"', path)
break
case 2: // 'deny'
debug('deny dotfile "%s"', path)
this.error(403)
return res
case 1: // 'ignore'
default:
debug('ignore dotfile "%s"', path)
this.error(404)
return res
}
}
// index file support
if (this._index.length && this.hasTrailingSlash()) {
this.sendIndex(path)
return res
}
this.sendFile(path)
return res
}
/**
* Transfer `path`.
*
* @param {String} path
* @api public
*/
SendStream.prototype.send = function send (path, stat) {
let len = stat.size
const options = this.options
const opts = {}
const res = this.res
const req = this.req
let offset = options.start || 0
if (res.headersSent) {
// impossible to send now
this.headersAlreadySent()
return
}
debug('pipe "%s"', path)
// set header fields
this.setHeader(path, stat)
// set content-type
this.type(path)
// conditional GET support
if (this.isConditionalGET()) {
if (this.isPreconditionFailure()) {
this.error(412)
return
}
if (this.isCachable() && this.isNotModifiedFailure()) {
this.notModified()
return
}
}
// adjust len to start/end options
len = Math.max(0, len - offset)
if (options.end !== undefined) {
const bytes = options.end - offset + 1
if (len > bytes) len = bytes
}
// Range support
if (this._acceptRanges) {
const rangeHeader = req.headers.range
if (
rangeHeader !== undefined &&
BYTES_RANGE_REGEXP.test(rangeHeader)
) {
// If-Range support
if (this.isRangeFresh()) {
// parse
const ranges = parseBytesRange(len, rangeHeader)
// unsatisfiable
if (ranges.length === 0) {
debug('range unsatisfiable')
// Content-Range
res.setHeader('Content-Range', contentRange('bytes', len))
// 416 Requested Range Not Satisfiable
return this.error(416, {
headers: { 'Content-Range': res.getHeader('Content-Range') }
})
// valid (syntactically invalid/multiple ranges are treated as a regular response)
} else if (ranges.length === 1) {
debug('range %j', ranges)
// Content-Range
res.statusCode = 206
res.setHeader('Content-Range', contentRange('bytes', len, ranges[0]))
// adjust for requested range
offset += ranges[0].start
len = ranges[0].end - ranges[0].start + 1
}
} else {
debug('range stale')
}
}
}
// clone options
for (const prop in options) {
opts[prop] = options[prop]
}
// set read options
opts.start = offset
opts.end = Math.max(offset, offset + len - 1)
// content-length
res.setHeader('Content-Length', len)
// HEAD support
if (req.method === 'HEAD') {
res.end()
return
}
this.stream(path, opts)
}
/**
* Transfer file for `path`.
*
* @param {String} path
* @api private
*/
SendStream.prototype.sendFile = function sendFile (path) {
let i = 0
const self = this
debug('stat "%s"', path)
fs.stat(path, function onstat (err, stat) {
if (err && err.code === 'ENOENT' && !extname(path) && path[path.length - 1] !== sep) {
// not found, check extensions
return next(err)
}
if (err) return self.onStatError(err)
if (stat.isDirectory()) return self.redirect(path)
self.emit('file', path, stat)
self.send(path, stat)
})
function next (err) {
if (self._extensions.length <= i) {
return err
? self.onStatError(err)
: self.error(404)
}
const p = path + '.' + self._extensions[i++]
debug('stat "%s"', p)
fs.stat(p, function (err, stat) {
if (err) return next(err)
if (stat.isDirectory()) return next()
self.emit('file', p, stat)
self.send(p, stat)
})
}
}
/**
* Transfer index for `path`.
*
* @param {String} path
* @api private
*/
SendStream.prototype.sendIndex = function sendIndex (path) {
let i = -1
const self = this
function next (err) {
if (++i >= self._index.length) {
if (err) return self.onStatError(err)
return self.error(404)
}
const p = join(path, self._index[i])
debug('stat "%s"', p)
fs.stat(p, function (err, stat) {
if (err) return next(err)
if (stat.isDirectory()) return next()
self.emit('file', p, stat)
self.send(p, stat)
})
}
next()
}
/**
* Stream `path` to the response.
*
* @param {String} path
* @param {Object} options
* @api private
*/
SendStream.prototype.stream = function stream (path, options) {
const self = this
const res = this.res
// pipe
const stream = fs.createReadStream(path, options)
this.emit('stream', stream)
stream.pipe(res)
let destroyed = false
// destroy piped stream
function destroy () {
if (destroyed) {
return
}
destroyed = true
stream.destroy()
}
res.once('finish', destroy)
// error handling
stream.on('error', function onerror (err) {
// clean up stream early
destroy()
// error
self.onStatError(err)
})
// end
stream.on('end', function onend () {
self.emit('end')
})
}
/**
* Set content-type based on `path`
* if it hasn't been explicitly set.
*
* @param {String} path
* @api private
*/
SendStream.prototype.type = function type (path) {
const res = this.res
if (res.getHeader('Content-Type')) return
const type = mime.getType(path) || mime.default_type
if (!type) {
debug('no content-type')
return
}
debug('content-type %s', type)
if (isUtf8MimeType(type)) {
res.setHeader('Content-Type', type + '; charset=UTF-8')
} else {
res.setHeader('Content-Type', type)
}
}
/**
* Set response header fields, most
* fields may be pre-defined.
*
* @param {String} path
* @param {Object} stat
* @api private
*/
SendStream.prototype.setHeader = function setHeader (path, stat) {
const res = this.res
this.emit('headers', res, path, stat)
if (this._acceptRanges && !res.getHeader('Accept-Ranges')) {
debug('accept ranges')
res.setHeader('Accept-Ranges', 'bytes')
}
if (this._cacheControl && !res.getHeader('Cache-Control')) {
let cacheControl = 'public, max-age=' + Math.floor(this._maxage / 1000)
if (this._immutable) {
cacheControl += ', immutable'
}
debug('cache-control %s', cacheControl)
res.setHeader('Cache-Control', cacheControl)
}
if (this._lastModified && !res.getHeader('Last-Modified')) {
const modified = stat.mtime.toUTCString()
debug('modified %s', modified)
res.setHeader('Last-Modified', modified)
}
if (this._etag && !res.getHeader('ETag')) {
const etag = 'W/"' + stat.size.toString(16) + '-' + stat.mtime.getTime().toString(16) + '"'
debug('etag %s', etag)
res.setHeader('ETag', etag)
}
}
/**
* Module exports.
* @public
*/
module.exports = SendStream

21
backend/node_modules/@fastify/send/lib/clearHeaders.js generated vendored Normal file
View File

@@ -0,0 +1,21 @@
/*!
* send
* Copyright(c) 2012 TJ Holowaychuk
* Copyright(c) 2014-2022 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict'
/**
* Clear all headers from a response.
*
* @param {object} res
* @private
*/
function clearHeaders (res) {
const headers = res.getHeaderNames()
for (let i = 0; i < headers.length; i++) {
res.removeHeader(headers[i])
}
}
exports.clearHeaders = clearHeaders

View File

@@ -0,0 +1,24 @@
'use strict'
/**
* Collapse all leading slashes into a single slash
*
* @param {string} str
* @private
*/
function collapseLeadingSlashes (str) {
if (
str[0] !== '/' ||
str[1] !== '/'
) {
return str
}
for (let i = 2, il = str.length; i < il; ++i) {
if (str[i] !== '/') {
return str.slice(i - 1)
}
}
}
module.exports.collapseLeadingSlashes = collapseLeadingSlashes

View File

@@ -0,0 +1,23 @@
/*!
* send
* Copyright(c) 2012 TJ Holowaychuk
* Copyright(c) 2014-2022 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict'
/**
* Determine if path parts contain a dotfile.
*
* @api private
*/
function containsDotFile (parts) {
for (let i = 0, il = parts.length; i < il; ++i) {
if (parts[i].length !== 1 && parts[i][0] === '.') {
return true
}
}
return false
}
module.exports.containsDotFile = containsDotFile

18
backend/node_modules/@fastify/send/lib/contentRange.js generated vendored Normal file
View File

@@ -0,0 +1,18 @@
/*!
* send
* Copyright(c) 2012 TJ Holowaychuk
* Copyright(c) 2014-2022 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict'
/**
* Create a Content-Range header.
*
* @param {string} type
* @param {number} size
* @param {array} [range]
*/
function contentRange (type, size, range) {
return type + ' ' + (range ? range.start + '-' + range.end : '*') + '/' + size
}
exports.contentRange = contentRange

View File

@@ -0,0 +1,29 @@
/*!
* send
* Copyright(c) 2012 TJ Holowaychuk
* Copyright(c) 2014-2022 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict'
/**
* Create a minimal HTML document.
*
* @param {string} title
* @param {string} body
* @private
*/
function createHtmlDocument (title, body) {
const html = '<!DOCTYPE html>\n' +
'<html lang="en">\n' +
'<head>\n' +
'<meta charset="utf-8">\n' +
'<title>' + title + '</title>\n' +
'</head>\n' +
'<body>\n' +
'<pre>' + body + '</pre>\n' +
'</body>\n' +
'</html>\n'
return [html, Buffer.byteLength(html)]
}
exports.createHtmlDocument = createHtmlDocument

View File

@@ -0,0 +1,23 @@
'use strict'
const createError = require('http-errors')
/**
* Create a HttpError object from simple arguments.
*
* @param {number} status
* @param {Error|object} err
* @private
*/
function createHttpError (status, err) {
if (!err) {
return createError(status)
}
return err instanceof Error
? createError(status, err, { expose: false })
: createError(status, err)
}
module.exports.createHttpError = createHttpError

View File

@@ -0,0 +1,12 @@
'use strict'
function isUtf8MimeType (value) {
const len = value.length
return (
(len > 21 && value.indexOf('application/javascript') === 0) ||
(len > 14 && value.indexOf('application/json') === 0) ||
(len > 5 && value.indexOf('text/') === 0)
)
}
module.exports.isUtf8MimeType = isUtf8MimeType

View File

@@ -0,0 +1,28 @@
'use strict'
/**
* Normalize the index option into an array.
*
* @param {boolean|string|array} val
* @param {string} name
* @private
*/
function normalizeList (val, name) {
if (typeof val === 'string') {
return [val]
} else if (val === false) {
return []
} else if (Array.isArray(val)) {
for (let i = 0, il = val.length; i < il; ++i) {
if (typeof val[i] !== 'string') {
throw new TypeError(name + ' must be array of strings or false')
}
}
return val
} else {
throw new TypeError(name + ' must be array of strings or false')
}
}
module.exports.normalizeList = normalizeList

View File

@@ -0,0 +1,133 @@
'use strict'
/*!
* Based on range-parser
*
* Copyright(c) 2012-2014 TJ Holowaychuk
* Copyright(c) 2015-2016 Douglas Christopher Wilson
* MIT Licensed
*/
/**
* Parse "Range" header `str` relative to the given file `size`.
*
* @param {Number} size
* @param {String} str
* @return {Array}
* @public
*/
function parseBytesRange (size, str) {
// split the range string
const values = str.slice(str.indexOf('=') + 1)
const ranges = []
const len = values.length
let i = 0
let il = 0
let j = 0
let start
let end
let commaIdx = values.indexOf(',')
let dashIdx = values.indexOf('-')
let prevIdx = -1
// parse all ranges
while (true) {
commaIdx === -1 && (commaIdx = len)
start = parseInt(values.slice(prevIdx + 1, dashIdx), 10)
end = parseInt(values.slice(dashIdx + 1, commaIdx), 10)
// -nnn
// eslint-disable-next-line no-self-compare
if (start !== start) { // fast path of isNaN(number)
start = size - end
end = size - 1
// nnn-
// eslint-disable-next-line no-self-compare
} else if (end !== end) { // fast path of isNaN(number)
end = size - 1
// limit last-byte-pos to current length
} else if (end > size - 1) {
end = size - 1
}
// add range only on valid ranges
if (
// eslint-disable-next-line no-self-compare
start === start && // fast path of isNaN(number)
// eslint-disable-next-line no-self-compare
end === end && // fast path of isNaN(number)
start > -1 &&
start <= end
) {
// add range
ranges.push({
start,
end,
index: j++
})
}
if (commaIdx === len) {
break
}
prevIdx = commaIdx++
dashIdx = values.indexOf('-', commaIdx)
commaIdx = values.indexOf(',', commaIdx)
}
// unsatisfiable
if (
j < 2
) {
return ranges
}
ranges.sort(sortByRangeStart)
il = j
j = 0
i = 1
while (i < il) {
const range = ranges[i++]
const current = ranges[j]
if (range.start > current.end + 1) {
// next range
ranges[++j] = range
} else if (range.end > current.end) {
// extend range
current.end = range.end
current.index > range.index && (current.index = range.index)
}
}
// trim ordered array
ranges.length = j + 1
// generate combined range
ranges.sort(sortByRangeIndex)
return ranges
}
/**
* Sort function to sort ranges by index.
* @private
*/
function sortByRangeIndex (a, b) {
return a.index - b.index
}
/**
* Sort function to sort ranges by start position.
* @private
*/
function sortByRangeStart (a, b) {
return a.start - b.start
}
module.exports.parseBytesRange = parseBytesRange

View File

@@ -0,0 +1,46 @@
'use strict'
/**
* Parse a HTTP token list.
*
* @param {string} str
* @private
*/
const slice = String.prototype.slice
function parseTokenList (str, cb) {
let end = 0
let start = 0
let result
// gather tokens
for (let i = 0, len = str.length; i < len; i++) {
switch (str.charCodeAt(i)) {
case 0x20: /* */
if (start === end) {
start = end = i + 1
}
break
case 0x2c: /* , */
if (start !== end) {
result = cb(slice.call(str, start, end))
if (result !== undefined) {
return result
}
}
start = end = i + 1
break
default:
end = i + 1
break
}
}
// final token
if (start !== end) {
return cb(slice.call(str, start, end))
}
}
module.exports.parseTokenList = parseTokenList

20
backend/node_modules/@fastify/send/lib/setHeaders.js generated vendored Normal file
View File

@@ -0,0 +1,20 @@
'use strict'
/**
* Set an object of headers on a response.
*
* @param {object} res
* @param {object} headers
* @private
*/
function setHeaders (res, headers) {
const keys = Object.keys(headers)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
res.setHeader(key, headers[key])
}
}
module.exports.setHeaders = setHeaders

53
backend/node_modules/@fastify/send/package.json generated vendored Normal file
View File

@@ -0,0 +1,53 @@
{
"name": "@fastify/send",
"description": "Better streaming static file server with Range and conditional-GET support",
"version": "2.1.0",
"author": "TJ Holowaychuk <tj@vision-media.ca>",
"contributors": [
"Douglas Christopher Wilson <doug@somethingdoug.com>",
"James Wyatt Cready <jcready@gmail.com>",
"Jesús Leganés Combarro <piranna@gmail.com>"
],
"main": "index.js",
"types": "types/index.d.ts",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/fastify/send.git"
},
"keywords": [
"static",
"file",
"server"
],
"dependencies": {
"escape-html": "~1.0.3",
"fast-decode-uri-component": "^1.0.1",
"http-errors": "2.0.0",
"mime": "^3.0.0",
"@lukeed/ms": "^2.0.1"
},
"devDependencies": {
"@fastify/pre-commit": "^2.0.2",
"@types/node": "^18.11.18",
"after": "0.8.2",
"benchmark": "^2.1.4",
"snazzy": "^9.0.0",
"standard": "^17.0.0",
"supertest": "6.3.3",
"tap": "^16.3.3",
"tsd": "^0.28.0"
},
"scripts": {
"lint": "standard | snazzy",
"lint:fix": "standard --fix | snazzy",
"test": "npm run test:unit && npm run test:typescript",
"test:coverage": "tap --coverage-report=html",
"test:typescript": "tsd",
"test:unit": "tap"
},
"pre-commit": [
"lint",
"test"
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,628 @@
'use strict'
const { test } = require('tap')
const fs = require('fs')
const http = require('http')
const path = require('path')
const request = require('supertest')
const SendStream = require('..').SendStream
const { shouldNotHaveHeader, createServer } = require('./utils')
// test server
const fixtures = path.join(__dirname, 'fixtures')
test('SendStream(file, options)', function (t) {
t.plan(10)
t.test('acceptRanges', function (t) {
t.plan(2)
t.test('should support disabling accept-ranges', function (t) {
t.plan(2)
request(createServer({ acceptRanges: false, root: fixtures }))
.get('/nums.txt')
.expect(shouldNotHaveHeader('Accept-Ranges', t))
.expect(200, err => t.error(err))
})
t.test('should ignore requested range', function (t) {
t.plan(3)
request(createServer({ acceptRanges: false, root: fixtures }))
.get('/nums.txt')
.set('Range', 'bytes=0-2')
.expect(shouldNotHaveHeader('Accept-Ranges', t))
.expect(shouldNotHaveHeader('Content-Range', t))
.expect(200, '123456789', err => t.error(err))
})
})
t.test('cacheControl', function (t) {
t.plan(2)
t.test('should support disabling cache-control', function (t) {
t.plan(2)
request(createServer({ cacheControl: false, root: fixtures }))
.get('/name.txt')
.expect(shouldNotHaveHeader('Cache-Control', t))
.expect(200, err => t.error(err))
})
t.test('should ignore maxAge option', function (t) {
t.plan(2)
request(createServer({ cacheControl: false, maxAge: 1000, root: fixtures }))
.get('/name.txt')
.expect(shouldNotHaveHeader('Cache-Control', t))
.expect(200, err => t.error(err))
})
})
t.test('etag', function (t) {
t.plan(1)
t.test('should support disabling etags', function (t) {
t.plan(2)
request(createServer({ etag: false, root: fixtures }))
.get('/name.txt')
.expect(shouldNotHaveHeader('ETag', t))
.expect(200, err => t.error(err))
})
})
t.test('extensions', function (t) {
t.plan(9)
t.test('should reject numbers', function (t) {
t.plan(1)
request(createServer({ extensions: 42, root: fixtures }))
.get('/pets/')
.expect(500, /TypeError: extensions option/, err => t.error(err))
})
t.test('should reject true', function (t) {
t.plan(1)
request(createServer({ extensions: true, root: fixtures }))
.get('/pets/')
.expect(500, /TypeError: extensions option/, err => t.error(err))
})
t.test('should be not be enabled by default', function (t) {
t.plan(1)
request(createServer({ root: fixtures }))
.get('/tobi')
.expect(404, err => t.error(err))
})
t.test('should be configurable', function (t) {
t.plan(1)
request(createServer({ extensions: 'txt', root: fixtures }))
.get('/name')
.expect(200, 'tobi', err => t.error(err))
})
t.test('should support disabling extensions', function (t) {
t.plan(1)
request(createServer({ extensions: false, root: fixtures }))
.get('/name')
.expect(404, err => t.error(err))
})
t.test('should support fallbacks', function (t) {
t.plan(1)
request(createServer({ extensions: ['htm', 'html', 'txt'], root: fixtures }))
.get('/name')
.expect(200, '<p>tobi</p>', err => t.error(err))
})
t.test('should 404 if nothing found', function (t) {
t.plan(1)
request(createServer({ extensions: ['htm', 'html', 'txt'], root: fixtures }))
.get('/bob')
.expect(404, err => t.error(err))
})
t.test('should skip directories', function (t) {
t.plan(1)
request(createServer({ extensions: ['file', 'dir'], root: fixtures }))
.get('/name')
.expect(404, err => t.error(err))
})
t.test('should not search if file has extension', function (t) {
t.plan(1)
request(createServer({ extensions: 'html', root: fixtures }))
.get('/thing.html')
.expect(404, err => t.error(err))
})
})
t.test('lastModified', function (t) {
t.plan(1)
t.test('should support disabling last-modified', function (t) {
t.plan(2)
request(createServer({ lastModified: false, root: fixtures }))
.get('/name.txt')
.expect(shouldNotHaveHeader('Last-Modified', t))
.expect(200, err => t.error(err))
})
})
t.test('dotfiles', function (t) {
t.plan(5)
t.test('should default to "ignore"', function (t) {
t.plan(1)
request(createServer({ root: fixtures }))
.get('/.hidden.txt')
.expect(404, err => t.error(err))
})
t.test('should reject bad value', function (t) {
t.plan(1)
request(createServer({ dotfiles: 'bogus' }))
.get('/name.txt')
.expect(500, /dotfiles/, err => t.error(err))
})
t.test('when "allow"', function (t) {
t.plan(3)
t.test('should SendStream dotfile', function (t) {
t.plan(1)
request(createServer({ dotfiles: 'allow', root: fixtures }))
.get('/.hidden.txt')
.expect(200, 'secret', err => t.error(err))
})
t.test('should SendStream within dotfile directory', function (t) {
t.plan(1)
request(createServer({ dotfiles: 'allow', root: fixtures }))
.get('/.mine/name.txt')
.expect(200, /tobi/, err => t.error(err))
})
t.test('should 404 for non-existent dotfile', function (t) {
t.plan(1)
request(createServer({ dotfiles: 'allow', root: fixtures }))
.get('/.nothere')
.expect(404, err => t.error(err))
})
})
t.test('when "deny"', function (t) {
t.plan(10)
t.test('should 403 for dotfile', function (t) {
t.plan(1)
request(createServer({ dotfiles: 'deny', root: fixtures }))
.get('/.hidden.txt')
.expect(403, err => t.error(err))
})
t.test('should 403 for dotfile directory', function (t) {
t.plan(1)
request(createServer({ dotfiles: 'deny', root: fixtures }))
.get('/.mine')
.expect(403, err => t.error(err))
})
t.test('should 403 for dotfile directory with trailing slash', function (t) {
t.plan(1)
request(createServer({ dotfiles: 'deny', root: fixtures }))
.get('/.mine/')
.expect(403, err => t.error(err))
})
t.test('should 403 for file within dotfile directory', function (t) {
t.plan(1)
request(createServer({ dotfiles: 'deny', root: fixtures }))
.get('/.mine/name.txt')
.expect(403, err => t.error(err))
})
t.test('should 403 for non-existent dotfile', function (t) {
t.plan(1)
request(createServer({ dotfiles: 'deny', root: fixtures }))
.get('/.nothere')
.expect(403, err => t.error(err))
})
t.test('should 403 for non-existent dotfile directory', function (t) {
t.plan(1)
request(createServer({ dotfiles: 'deny', root: fixtures }))
.get('/.what/name.txt')
.expect(403, err => t.error(err))
})
t.test('should 403 for dotfile in directory', function (t) {
t.plan(1)
request(createServer({ dotfiles: 'deny', root: fixtures }))
.get('/pets/.hidden')
.expect(403, err => t.error(err))
})
t.test('should 403 for dotfile in dotfile directory', function (t) {
t.plan(1)
request(createServer({ dotfiles: 'deny', root: fixtures }))
.get('/.mine/.hidden')
.expect(403, err => t.error(err))
})
t.test('should SendStream files in root dotfile directory', function (t) {
t.plan(1)
request(createServer({ dotfiles: 'deny', root: path.join(fixtures, '.mine') }))
.get('/name.txt')
.expect(200, /tobi/, err => t.error(err))
})
t.test('should 403 for dotfile without root', function (t) {
t.plan(1)
const server = http.createServer(function onRequest (req, res) {
new SendStream(req, fixtures + '/.mine' + req.url, { dotfiles: 'deny' }).pipe(res)
})
request(server)
.get('/name.txt')
.expect(403, err => t.error(err))
})
})
t.test('when "ignore"', function (t) {
t.plan(8)
t.test('should 404 for dotfile', function (t) {
t.plan(1)
request(createServer({ dotfiles: 'ignore', root: fixtures }))
.get('/.hidden.txt')
.expect(404, err => t.error(err))
})
t.test('should 404 for dotfile directory', function (t) {
t.plan(1)
request(createServer({ dotfiles: 'ignore', root: fixtures }))
.get('/.mine')
.expect(404, err => t.error(err))
})
t.test('should 404 for dotfile directory with trailing slash', function (t) {
t.plan(1)
request(createServer({ dotfiles: 'ignore', root: fixtures }))
.get('/.mine/')
.expect(404, err => t.error(err))
})
t.test('should 404 for file within dotfile directory', function (t) {
t.plan(1)
request(createServer({ dotfiles: 'ignore', root: fixtures }))
.get('/.mine/name.txt')
.expect(404, err => t.error(err))
})
t.test('should 404 for non-existent dotfile', function (t) {
t.plan(1)
request(createServer({ dotfiles: 'ignore', root: fixtures }))
.get('/.nothere')
.expect(404, err => t.error(err))
})
t.test('should 404 for non-existent dotfile directory', function (t) {
t.plan(1)
request(createServer({ dotfiles: 'ignore', root: fixtures }))
.get('/.what/name.txt')
.expect(404, err => t.error(err))
})
t.test('should SendStream files in root dotfile directory', function (t) {
t.plan(1)
request(createServer({ dotfiles: 'ignore', root: path.join(fixtures, '.mine') }))
.get('/name.txt')
.expect(200, /tobi/, err => t.error(err))
})
t.test('should 404 for dotfile without root', function (t) {
t.plan(1)
const server = http.createServer(function onRequest (req, res) {
new SendStream(req, fixtures + '/.mine' + req.url, { dotfiles: 'ignore' }).pipe(res)
})
request(server)
.get('/name.txt')
.expect(404, err => t.error(err))
})
})
})
t.test('immutable', function (t) {
t.plan(2)
t.test('should default to false', function (t) {
t.plan(1)
request(createServer({ root: fixtures }))
.get('/name.txt')
.expect('Cache-Control', 'public, max-age=0', err => t.error(err))
})
t.test('should set immutable directive in Cache-Control', function (t) {
t.plan(1)
request(createServer({ immutable: true, maxAge: '1h', root: fixtures }))
.get('/name.txt')
.expect('Cache-Control', 'public, max-age=3600, immutable', err => t.error(err))
})
})
t.test('maxAge', function (t) {
t.plan(4)
t.test('should default to 0', function (t) {
t.plan(1)
request(createServer({ root: fixtures }))
.get('/name.txt')
.expect('Cache-Control', 'public, max-age=0', err => t.error(err))
})
t.test('should floor to integer', function (t) {
t.plan(1)
request(createServer({ maxAge: 123956, root: fixtures }))
.get('/name.txt')
.expect('Cache-Control', 'public, max-age=123', err => t.error(err))
})
t.test('should accept string', function (t) {
t.plan(1)
request(createServer({ maxAge: '30d', root: fixtures }))
.get('/name.txt')
.expect('Cache-Control', 'public, max-age=2592000', err => t.error(err))
})
t.test('should max at 1 year', function (t) {
t.plan(1)
request(createServer({ maxAge: '2y', root: fixtures }))
.get('/name.txt')
.expect('Cache-Control', 'public, max-age=31536000', err => t.error(err))
})
})
t.test('index', function (t) {
t.plan(10)
t.test('should reject numbers', function (t) {
t.plan(1)
request(createServer({ root: fixtures, index: 42 }))
.get('/pets/')
.expect(500, /TypeError: index option/, err => t.error(err))
})
t.test('should reject true', function (t) {
t.plan(1)
request(createServer({ root: fixtures, index: true }))
.get('/pets/')
.expect(500, /TypeError: index option/, err => t.error(err))
})
t.test('should default to index.html', function (t) {
t.plan(1)
request(createServer({ root: fixtures }))
.get('/pets/')
.expect(fs.readFileSync(path.join(fixtures, 'pets', 'index.html'), 'utf8'), err => t.error(err))
})
t.test('should be configurable', function (t) {
t.plan(1)
request(createServer({ root: fixtures, index: 'tobi.html' }))
.get('/')
.expect(200, '<p>tobi</p>', err => t.error(err))
})
t.test('should support disabling', function (t) {
t.plan(1)
request(createServer({ root: fixtures, index: false }))
.get('/pets/')
.expect(403, err => t.error(err))
})
t.test('should support fallbacks', function (t) {
t.plan(1)
request(createServer({ root: fixtures, index: ['default.htm', 'index.html'] }))
.get('/pets/')
.expect(200, fs.readFileSync(path.join(fixtures, 'pets', 'index.html'), 'utf8'), err => t.error(err))
})
t.test('should 404 if no index file found (file)', function (t) {
t.plan(1)
request(createServer({ root: fixtures, index: 'default.htm' }))
.get('/pets/')
.expect(404, err => t.error(err))
})
t.test('should 404 if no index file found (dir)', function (t) {
t.plan(1)
request(createServer({ root: fixtures, index: 'pets' }))
.get('/')
.expect(404, err => t.error(err))
})
t.test('should not follow directories', function (t) {
t.plan(1)
request(createServer({ root: fixtures, index: ['pets', 'name.txt'] }))
.get('/')
.expect(200, 'tobi', err => t.error(err))
})
t.test('should work without root', function (t) {
t.plan(1)
const server = http.createServer(function (req, res) {
const p = path.join(fixtures, 'pets').replace(/\\/g, '/') + '/'
new SendStream(req, p, { index: ['index.html'] })
.pipe(res)
})
request(server)
.get('/')
.expect(200, /tobi/, err => t.error(err))
})
})
t.test('root', function (t) {
t.plan(2)
t.test('when given', function (t) {
t.plan(8)
t.test('should join root', function (t) {
t.plan(1)
request(createServer({ root: fixtures }))
.get('/pets/../name.txt')
.expect(200, 'tobi', err => t.error(err))
})
t.test('should work with trailing slash', function (t) {
t.plan(1)
const app = http.createServer(function (req, res) {
new SendStream(req, req.url, { root: fixtures + '/' })
.pipe(res)
})
request(app)
.get('/name.txt')
.expect(200, 'tobi', err => t.error(err))
})
t.test('should work with empty path', function (t) {
t.plan(1)
const app = http.createServer(function (req, res) {
new SendStream(req, '', { root: fixtures })
.pipe(res)
})
request(app)
.get('/name.txt')
.expect(301, /Redirecting to/, err => t.error(err))
})
//
// NOTE: This is not a real part of the API, but
// over time this has become something users
// are doing, so this will prevent unseen
// regressions around this use-case.
//
t.test('should try as file with empty path', function (t) {
t.plan(1)
const app = http.createServer(function (req, res) {
new SendStream(req, '', { root: path.join(fixtures, 'name.txt') })
.pipe(res)
})
request(app)
.get('/')
.expect(200, 'tobi', err => t.error(err))
})
t.test('should restrict paths to within root', function (t) {
t.plan(1)
request(createServer({ root: fixtures }))
.get('/pets/../../SendStream.js')
.expect(403, err => t.error(err))
})
t.test('should allow .. in root', function (t) {
t.plan(1)
const app = http.createServer(function (req, res) {
new SendStream(req, req.url, { root: fixtures + '/../fixtures' })
.pipe(res)
})
request(app)
.get('/pets/../../SendStream.js')
.expect(403, err => t.error(err))
})
t.test('should not allow root transversal', function (t) {
t.plan(1)
request(createServer({ root: path.join(fixtures, 'name.d') }))
.get('/../name.dir/name.txt')
.expect(403, err => t.error(err))
})
t.test('should not allow root path disclosure', function (t) {
t.plan(1)
request(createServer({ root: fixtures }))
.get('/pets/../../fixtures/name.txt')
.expect(403, err => t.error(err))
})
})
t.test('when missing', function (t) {
t.plan(2)
t.test('should consider .. malicious', function (t) {
t.plan(1)
const app = http.createServer(function (req, res) {
new SendStream(req, fixtures + req.url)
.pipe(res)
})
request(app)
.get('/../SendStream.js')
.expect(403, err => t.error(err))
})
t.test('should still serve files with dots in name', function (t) {
t.plan(1)
const app = http.createServer(function (req, res) {
new SendStream(req, fixtures + req.url)
.pipe(res)
})
request(app)
.get('/do..ts.txt')
.expect(200, '...', err => t.error(err))
})
})
})
})

View File

@@ -0,0 +1,22 @@
'use strict'
const { test } = require('tap')
const { collapseLeadingSlashes } = require('../lib/collapseLeadingSlashes')
test('collapseLeadingSlashes', function (t) {
const testCases = [
['abcd', 'abcd'],
['text/json', 'text/json'],
['/text/json', '/text/json'],
['//text/json', '/text/json'],
['///text/json', '/text/json'],
['/.//text/json', '/.//text/json'],
['//./text/json', '/./text/json'],
['///./text/json', '/./text/json']
]
t.plan(testCases.length)
for (let i = 0; i < testCases.length; ++i) {
t.strictSame(collapseLeadingSlashes(testCases[i][0]), testCases[i][1])
}
})

View File

@@ -0,0 +1,13 @@
'use strict'
const { test } = require('tap')
const SendStream = require('../index').SendStream
test('constructor', function (t) {
t.plan(1)
t.test('SendStream without new returns SendStream instance', function (t) {
t.plan(1)
t.ok(SendStream({}, '/', {}) instanceof SendStream)
})
})

View File

@@ -0,0 +1,18 @@
'use strict'
const { test } = require('tap')
const { containsDotFile } = require('../lib/containsDotFile')
test('containsDotFile', function (t) {
const testCases = [
['/.github', true],
['.github', true],
['index.html', false],
['./index.html', false]
]
t.plan(testCases.length)
for (let i = 0; i < testCases.length; ++i) {
t.strictSame(containsDotFile(testCases[i][0].split('/')), testCases[i][1], testCases[i][0])
}
})

View File

@@ -0,0 +1 @@
secret

View File

@@ -0,0 +1 @@
secret

View File

@@ -0,0 +1 @@
tobi

View File

@@ -0,0 +1 @@
...

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 B

View File

@@ -0,0 +1 @@
loki

View File

@@ -0,0 +1 @@
tobi

View File

@@ -0,0 +1 @@
<p>tobi</p>

View File

@@ -0,0 +1 @@
tobi

View File

@@ -0,0 +1 @@
foobar

View File

@@ -0,0 +1 @@
123456789

View File

@@ -0,0 +1 @@
secret

View File

@@ -0,0 +1,3 @@
tobi
loki
jane

View File

View File

@@ -0,0 +1 @@
hey

View File

@@ -0,0 +1 @@
<p>trap!</p>

View File

@@ -0,0 +1 @@
<p>tobi</p>

View File

@@ -0,0 +1,22 @@
'use strict'
const { test } = require('tap')
const { isUtf8MimeType } = require('../lib/isUtf8MimeType')
test('isUtf8MimeType', function (t) {
const testCases = [
['application/json', true],
['text/json', true],
['application/javascript', true],
['text/javascript', true],
['application/json+v5', true],
['text/xml', true],
['text/html', true],
['image/png', false]
]
t.plan(testCases.length)
for (let i = 0; i < testCases.length; ++i) {
t.strictSame(isUtf8MimeType(testCases[i][0], 'test'), testCases[i][1])
}
})

59
backend/node_modules/@fastify/send/test/mime.test.js generated vendored Normal file
View File

@@ -0,0 +1,59 @@
'use strict'
const { test } = require('tap')
const path = require('path')
const request = require('supertest')
const send = require('..')
const { shouldNotHaveHeader, createServer } = require('./utils')
const fixtures = path.join(__dirname, 'fixtures')
test('send.mime', function (t) {
t.plan(2)
t.test('should be exposed', function (t) {
t.plan(1)
t.ok(send.mime)
})
t.test('.default_type', function (t) {
t.plan(3)
t.before(function () {
this.default_type = send.mime.default_type
})
t.afterEach(function () {
send.mime.default_type = this.default_type
})
t.test('should change the default type', function (t) {
t.plan(1)
send.mime.default_type = 'text/plain'
request(createServer({ root: fixtures }))
.get('/no_ext')
.expect('Content-Type', 'text/plain; charset=UTF-8')
.expect(200, err => t.error(err))
})
t.test('should not add Content-Type for undefined default', function (t) {
t.plan(2)
send.mime.default_type = undefined
request(createServer({ root: fixtures }))
.get('/no_ext')
.expect(shouldNotHaveHeader('Content-Type', t))
.expect(200, err => t.error(err))
})
t.test('should return Content-Type without charset', function (t) {
t.plan(1)
request(createServer({ root: fixtures }))
.get('/images/node-js.png')
.expect('Content-Type', 'image/png')
.expect(200, err => t.error(err))
})
})
})

View File

@@ -0,0 +1,28 @@
'use strict'
const { test } = require('tap')
const { normalizeList } = require('../lib/normalizeList')
test('normalizeList', function (t) {
const testCases = [
[undefined, new Error('test must be array of strings or false')],
[false, []],
[[], []],
['', ['']],
[[''], ['']],
[['a'], ['a']],
['a', ['a']],
[true, new Error('test must be array of strings or false')],
[1, new Error('test must be array of strings or false')],
[[1], new Error('test must be array of strings or false')]
]
t.plan(testCases.length)
for (let i = 0; i < testCases.length; ++i) {
if (testCases[i][1] instanceof Error) {
t.throws(() => normalizeList(testCases[i][0], 'test'), testCases[i][1])
} else {
t.strictSame(normalizeList(testCases[i][0], 'test'), testCases[i][1])
}
}
})

View File

@@ -0,0 +1,103 @@
'use strict'
const { test } = require('tap')
const { parseBytesRange } = require('../lib/parseBytesRange')
test('parseBytesRange', function (t) {
t.plan(13)
t.test('should return empty array if all specified ranges are invalid', function (t) {
t.plan(3)
t.strictSame(parseBytesRange(200, 'bytes=500-20'), [])
t.strictSame(parseBytesRange(200, 'bytes=500-999'), [])
t.strictSame(parseBytesRange(200, 'bytes=500-999,1000-1499'), [])
})
t.test('should parse str', function (t) {
t.plan(2)
const range = parseBytesRange(1000, 'bytes=0-499')
t.equal(range.length, 1)
t.strictSame(range[0], { start: 0, end: 499, index: 0 })
})
t.test('should cap end at size', function (t) {
t.plan(2)
const range = parseBytesRange(200, 'bytes=0-499')
t.equal(range.length, 1)
t.strictSame(range[0], { start: 0, end: 199, index: 0 })
})
t.test('should parse str', function (t) {
t.plan(2)
const range = parseBytesRange(1000, 'bytes=40-80')
t.equal(range.length, 1)
t.strictSame(range[0], { start: 40, end: 80, index: 0 })
})
t.test('should parse str asking for last n bytes', function (t) {
t.plan(2)
const range = parseBytesRange(1000, 'bytes=-400')
t.equal(range.length, 1)
t.strictSame(range[0], { start: 600, end: 999, index: 0 })
})
t.test('should parse str with only start', function (t) {
t.plan(2)
const range = parseBytesRange(1000, 'bytes=400-')
t.equal(range.length, 1)
t.strictSame(range[0], { start: 400, end: 999, index: 0 })
})
t.test('should parse "bytes=0-"', function (t) {
t.plan(2)
const range = parseBytesRange(1000, 'bytes=0-')
t.equal(range.length, 1)
t.strictSame(range[0], { start: 0, end: 999, index: 0 })
})
t.test('should parse str with no bytes', function (t) {
t.plan(2)
const range = parseBytesRange(1000, 'bytes=0-0')
t.equal(range.length, 1)
t.strictSame(range[0], { start: 0, end: 0, index: 0 })
})
t.test('should parse str asking for last byte', function (t) {
t.plan(2)
const range = parseBytesRange(1000, 'bytes=-1')
t.equal(range.length, 1)
t.strictSame(range[0], { start: 999, end: 999, index: 0 })
})
t.test('should parse str with some invalid ranges', function (t) {
t.plan(2)
const range = parseBytesRange(200, 'bytes=0-499,1000-,500-999')
t.equal(range.length, 1)
t.strictSame(range[0], { start: 0, end: 199, index: 0 })
})
t.test('should combine overlapping ranges', function (t) {
t.plan(3)
const range = parseBytesRange(150, 'bytes=0-4,90-99,5-75,100-199,101-102')
t.equal(range.length, 2)
t.strictSame(range[0], { start: 0, end: 75, index: 0 })
t.strictSame(range[1], { start: 90, end: 149, index: 1 })
})
t.test('should retain original order /1', function (t) {
t.plan(3)
const range = parseBytesRange(150, 'bytes=90-99,5-75,100-199,101-102,0-4')
t.equal(range.length, 2)
t.strictSame(range[0], { start: 90, end: 149, index: 0 })
t.strictSame(range[1], { start: 0, end: 75, index: 1 })
})
t.test('should retain original order /2', function (t) {
t.plan(4)
const range = parseBytesRange(150, 'bytes=-1,20-100,0-1,101-120')
t.equal(range.length, 3)
t.strictSame(range[0], { start: 149, end: 149, index: 0 })
t.strictSame(range[1], { start: 20, end: 120, index: 1 })
t.strictSame(range[2], { start: 0, end: 1, index: 2 })
})
})

28
backend/node_modules/@fastify/send/test/utils.js generated vendored Normal file
View File

@@ -0,0 +1,28 @@
'use strict'
const http = require('http')
const send = require('..')
module.exports.shouldNotHaveHeader = function shouldNotHaveHeader (header, t) {
return function (res) {
t.ok(!(header.toLowerCase() in res.headers), 'should not have header ' + header)
}
}
module.exports.createServer = function createServer (opts, fn) {
return http.createServer(function onRequest (req, res) {
try {
fn && fn(req, res)
send(req, req.url, opts).pipe(res)
} catch (err) {
res.statusCode = 500
res.end(String(err))
}
})
}
module.exports.shouldNotHaveBody = function shouldNotHaveBody (t) {
return function (res) {
t.ok(res.text === '' || res.text === undefined)
}
}

215
backend/node_modules/@fastify/send/types/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,215 @@
// Definitions by: Mike Jerred <https://github.com/MikeJerred>
// Piotr Błażejewicz <https://github.com/peterblazejewicz>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
/// <reference types="node" />
import * as stream from "stream";
import * as fs from "fs";
/**
* Create a new SendStream for the given path to send to a res.
* The req is the Node.js HTTP request and the path is a urlencoded path to send (urlencoded, not the actual file-system path).
*/
declare function send(req: stream.Readable, path: string, options?: send.SendOptions): send.SendStream;
type Send = typeof send;
declare class Mime {
constructor(typeMap: TypeMap, ...mimes: TypeMap[]);
getType(path: string): string | null;
getExtension(mime: string): string | null;
define(typeMap: TypeMap, force?: boolean): void;
}
interface TypeMap {
[key: string]: string[];
}
declare namespace send {
export const mime: Mime;
export const isUtf8MimeType: (value: string) => boolean;
export interface SendOptions {
/**
* Enable or disable accepting ranged requests, defaults to true.
* Disabling this will not send Accept-Ranges and ignore the contents of the Range request header.
*/
acceptRanges?: boolean | undefined;
/**
* Enable or disable setting Cache-Control response header, defaults to true.
* Disabling this will ignore the maxAge option.
*/
cacheControl?: boolean | undefined;
/**
* Set how "dotfiles" are treated when encountered.
* A dotfile is a file or directory that begins with a dot (".").
* Note this check is done on the path itself without checking if the path actually exists on the disk.
* If root is specified, only the dotfiles above the root are checked (i.e. the root itself can be within a dotfile when when set to "deny").
* 'allow' No special treatment for dotfiles.
* 'deny' Send a 403 for any request for a dotfile.
* 'ignore' Pretend like the dotfile does not exist and 404.
* The default value is similar to 'ignore', with the exception that this default will not ignore the files within a directory that begins with a dot, for backward-compatibility.
*/
dotfiles?: "allow" | "deny" | "ignore" | undefined;
/**
* Byte offset at which the stream ends, defaults to the length of the file minus 1.
* The end is inclusive in the stream, meaning end: 3 will include the 4th byte in the stream.
*/
end?: number | undefined;
/**
* Enable or disable etag generation, defaults to true.
*/
etag?: boolean | undefined;
/**
* If a given file doesn't exist, try appending one of the given extensions, in the given order.
* By default, this is disabled (set to false).
* An example value that will serve extension-less HTML files: ['html', 'htm'].
* This is skipped if the requested file already has an extension.
*/
extensions?: string[] | string | boolean | undefined;
/**
* Enable or disable the immutable directive in the Cache-Control response header, defaults to false.
* If set to true, the maxAge option should also be specified to enable caching.
* The immutable directive will prevent supported clients from making conditional requests during the life of the maxAge option to check if the file has changed.
* @default false
*/
immutable?: boolean | undefined;
/**
* By default send supports "index.html" files, to disable this set false or to supply a new index pass a string or an array in preferred order.
*/
index?: string[] | string | boolean | undefined;
/**
* Enable or disable Last-Modified header, defaults to true.
* Uses the file system's last modified value.
*/
lastModified?: boolean | undefined;
/**
* Provide a max-age in milliseconds for http caching, defaults to 0.
* This can also be a string accepted by the ms module.
*/
maxAge?: string | number | undefined;
/**
* Serve files relative to path.
*/
root?: string | undefined;
/**
* Byte offset at which the stream starts, defaults to 0.
* The start is inclusive, meaning start: 2 will include the 3rd byte in the stream.
*/
start?: number | undefined;
}
export class SendStream extends stream.Stream {
constructor(req: stream.Readable, path: string, options?: SendOptions);
/**
* Emit error with `status`.
*/
error(status: number, error?: Error): void;
/**
* Check if the pathname ends with "/".
*/
hasTrailingSlash(): boolean;
/**
* Check if this is a conditional GET request.
*/
isConditionalGET(): boolean;
/**
* Strip content-* header fields.
*/
removeContentHeaderFields(): void;
/**
* Respond with 304 not modified.
*/
notModified(): void;
/**
* Raise error that headers already sent.
*/
headersAlreadySent(): void;
/**
* Check if the request is cacheable, aka responded with 2xx or 304 (see RFC 2616 section 14.2{5,6}).
*/
isCachable(): boolean;
/**
* Handle stat() error.
*/
onStatError(error: Error): void;
/**
* Check if the cache is fresh.
*/
isFresh(): boolean;
/**
* Check if the range is fresh.
*/
isRangeFresh(): boolean;
/**
* Redirect to path.
*/
redirect(path: string): void;
/**
* Pipe to `res`.
*/
pipe<T extends NodeJS.WritableStream>(res: T): T;
/**
* Transfer `path`.
*/
send(path: string, stat?: fs.Stats): void;
/**
* Transfer file for `path`.
*/
sendFile(path: string): void;
/**
* Transfer index for `path`.
*/
sendIndex(path: string): void;
/**
* Transfer index for `path`.
*/
stream(path: string, options?: {}): void;
/**
* Set content-type based on `path` if it hasn't been explicitly set.
*/
type(path: string): void;
/**
* Set response header fields, most fields may be pre-defined.
*/
setHeader(path: string, stat: fs.Stats): void;
}
export const send: Send
export { send as default }
}
export = send;

View File

@@ -0,0 +1,36 @@
import { expectType } from 'tsd'
import send from '..'
import { SendStream } from '..';
send.mime.define({
'application/x-my-type': ['x-mt', 'x-mtt']
});
expectType<(value: string) => boolean>(send.isUtf8MimeType)
expectType<boolean>(send.isUtf8MimeType('application/json'))
const req: any = {}
const res: any = {}
send(req, '/test.html', {
immutable: true,
maxAge: 0,
root: __dirname + '/wwwroot'
}).pipe(res);
send(req, '/test.html', { maxAge: 0, root: __dirname + '/wwwroot' })
.on('error', (err: any) => {
res.statusCode = err.status || 500;
res.end(err.message);
})
.on('directory', () => {
res.statusCode = 301;
res.setHeader('Location', req.url + '/');
res.end(`Redirecting to ${req.url}/`);
})
.on('headers', (res: any, path: string, stat: any) => {
res.setHeader('Content-Disposition', 'attachment');
})
.pipe(res);
const test = new SendStream(req, '/test.html', { maxAge: 0, root: __dirname + '/wwwroot' });