mirror of
https://github.com/static-web-server/static-web-server.git
synced 2026-01-25 05:06:33 +00:00
feat: disable symlinks option via --disable-symlinks (#454)
* feat: disable symlinks option --disable-symlinks[=<DISABLE_SYMLINKS>] Prevent following files or directories if any path name component is a symbolic link [env: SERVER_DISABLE_SYMLINKS=] [default: false] [possible values: true, false] * chore: add tests * docs: feature page [skip ci]
This commit is contained in:
@@ -16,262 +16,89 @@ Usage: static-web-server [OPTIONS]
|
||||
|
||||
Options:
|
||||
-a, --host <HOST>
|
||||
Host address (E.g 127.0.0.1 or ::1)
|
||||
|
||||
[env: SERVER_HOST=]
|
||||
[default: ::]
|
||||
|
||||
Host address (E.g 127.0.0.1 or ::1) [env: SERVER_HOST=] [default: ::]
|
||||
-p, --port <PORT>
|
||||
Host port
|
||||
|
||||
[env: SERVER_PORT=]
|
||||
[default: 80]
|
||||
|
||||
Host port [env: SERVER_PORT=] [default: 80]
|
||||
-f, --fd <FD>
|
||||
Instead of binding to a TCP port, accept incoming connections to an already-bound TCP socket listener on the specified file descriptor number (usually zero). Requires that the parent process (e.g. inetd, launchd, or systemd) binds an address and port on behalf of static-web-server, before arranging for the resulting file descriptor to be inherited by static-web-server. Cannot be used in conjunction with the port and host arguments. The included systemd unit file utilises this feature to increase security by allowing the static-web-server to be sandboxed more completely
|
||||
|
||||
[env: SERVER_LISTEN_FD=]
|
||||
|
||||
Instead of binding to a TCP port, accept incoming connections to an already-bound TCP socket listener on the specified file descriptor number (usually zero). Requires that the parent process (e.g. inetd, launchd, or systemd) binds an address and port on behalf of static-web-server, before arranging for the resulting file descriptor to be inherited by static-web-server. Cannot be used in conjunction with the port and host arguments. The included systemd unit file utilises this feature to increase security by allowing the static-web-server to be sandboxed more completely [env: SERVER_LISTEN_FD=]
|
||||
-n, --threads-multiplier <THREADS_MULTIPLIER>
|
||||
Number of worker threads multiplier that'll be multiplied by the number of system CPUs using the formula: `worker threads = number of CPUs * n` where `n` is the value that changes here. When multiplier value is 0 or 1 then one thread per core is used. Number of worker threads result should be a number between 1 and 32,768 though it is advised to keep this value on the smaller side
|
||||
|
||||
[env: SERVER_THREADS_MULTIPLIER=]
|
||||
[default: 1]
|
||||
|
||||
Number of worker threads multiplier that'll be multiplied by the number of system CPUs using the formula: `worker threads = number of CPUs * n` where `n` is the value that changes here. When multiplier value is 0 or 1 then one thread per core is used. Number of worker threads result should be a number between 1 and 32,768 though it is advised to keep this value on the smaller side [env: SERVER_THREADS_MULTIPLIER=] [default: 1]
|
||||
-b, --max-blocking-threads <MAX_BLOCKING_THREADS>
|
||||
Maximum number of blocking threads
|
||||
|
||||
[env: SERVER_MAX_BLOCKING_THREADS=]
|
||||
[default: 512]
|
||||
|
||||
Maximum number of blocking threads [env: SERVER_MAX_BLOCKING_THREADS=] [default: 512]
|
||||
-d, --root <ROOT>
|
||||
Root directory path of static files
|
||||
|
||||
[env: SERVER_ROOT=]
|
||||
[default: ./public]
|
||||
|
||||
Root directory path of static files [env: SERVER_ROOT=] [default: ./public]
|
||||
--page50x <PAGE50X>
|
||||
HTML file path for 50x errors. If the path is not specified or simply doesn't exist then the server will use a generic HTML error message. If a relative path is used then it will be resolved under the root directory
|
||||
|
||||
[env: SERVER_ERROR_PAGE_50X=]
|
||||
[default: ./50x.html]
|
||||
|
||||
HTML file path for 50x errors. If the path is not specified or simply doesn't exist then the server will use a generic HTML error message. If a relative path is used then it will be resolved under the root directory [env: SERVER_ERROR_PAGE_50X=] [default: ./50x.html]
|
||||
--page404 <PAGE404>
|
||||
HTML file path for 404 errors. If the path is not specified or simply doesn't exist then the server will use a generic HTML error message. If a relative path is used then it will be resolved under the root directory
|
||||
|
||||
[env: SERVER_ERROR_PAGE_404=]
|
||||
[default: ./404.html]
|
||||
|
||||
HTML file path for 404 errors. If the path is not specified or simply doesn't exist then the server will use a generic HTML error message. If a relative path is used then it will be resolved under the root directory [env: SERVER_ERROR_PAGE_404=] [default: ./404.html]
|
||||
--page-fallback <PAGE_FALLBACK>
|
||||
HTML file path that is used for GET requests when the requested path doesn't exist. The fallback page is served with a 200 status code, useful when using client routers. If the path is not specified or simply doesn't exist then this feature will not be active
|
||||
|
||||
[env: SERVER_FALLBACK_PAGE=]
|
||||
[default: ]
|
||||
|
||||
HTML file path that is used for GET requests when the requested path doesn't exist. The fallback page is served with a 200 status code, useful when using client routers. If the path is not specified or simply doesn't exist then this feature will not be active [env: SERVER_FALLBACK_PAGE=] [default: ]
|
||||
-g, --log-level <LOG_LEVEL>
|
||||
Specify a logging level in lower case. Values: error, warn, info, debug or trace
|
||||
|
||||
[env: SERVER_LOG_LEVEL=]
|
||||
[default: error]
|
||||
|
||||
Specify a logging level in lower case. Values: error, warn, info, debug or trace [env: SERVER_LOG_LEVEL=] [default: error]
|
||||
-c, --cors-allow-origins <CORS_ALLOW_ORIGINS>
|
||||
Specify an optional CORS list of allowed origin hosts separated by commas. Host ports or protocols aren't being checked. Use an asterisk (*) to allow any host
|
||||
|
||||
[env: SERVER_CORS_ALLOW_ORIGINS=]
|
||||
[default: ]
|
||||
|
||||
Specify an optional CORS list of allowed origin hosts separated by commas. Host ports or protocols aren't being checked. Use an asterisk (*) to allow any host [env: SERVER_CORS_ALLOW_ORIGINS=] [default: ]
|
||||
-j, --cors-allow-headers <CORS_ALLOW_HEADERS>
|
||||
Specify an optional CORS list of allowed headers separated by commas. Default "origin, content-type". It requires `--cors-allow-origins` to be used along with
|
||||
|
||||
[env: SERVER_CORS_ALLOW_HEADERS=]
|
||||
[default: "origin, content-type, authorization"]
|
||||
|
||||
Specify an optional CORS list of allowed headers separated by commas. Default "origin, content-type". It requires `--cors-allow-origins` to be used along with [env: SERVER_CORS_ALLOW_HEADERS=] [default: "origin, content-type, authorization"]
|
||||
--cors-expose-headers <CORS_EXPOSE_HEADERS>
|
||||
Specify an optional CORS list of exposed headers separated by commas. Default "origin, content-type". It requires `--cors-expose-origins` to be used along with
|
||||
|
||||
[env: SERVER_CORS_EXPOSE_HEADERS=]
|
||||
[default: "origin, content-type"]
|
||||
|
||||
Specify an optional CORS list of exposed headers separated by commas. Default "origin, content-type". It requires `--cors-expose-origins` to be used along with [env: SERVER_CORS_EXPOSE_HEADERS=] [default: "origin, content-type"]
|
||||
-t, --http2[=<HTTP2>]
|
||||
Enable HTTP/2 with TLS support
|
||||
|
||||
[env: SERVER_HTTP2_TLS=]
|
||||
[default: false]
|
||||
[possible values: true, false]
|
||||
|
||||
Enable HTTP/2 with TLS support [env: SERVER_HTTP2_TLS=] [default: false] [possible values: true, false]
|
||||
--http2-tls-cert <HTTP2_TLS_CERT>
|
||||
Specify the file path to read the certificate
|
||||
|
||||
[env: SERVER_HTTP2_TLS_CERT=]
|
||||
|
||||
Specify the file path to read the certificate [env: SERVER_HTTP2_TLS_CERT=]
|
||||
--http2-tls-key <HTTP2_TLS_KEY>
|
||||
Specify the file path to read the private key
|
||||
|
||||
[env: SERVER_HTTP2_TLS_KEY=]
|
||||
|
||||
Specify the file path to read the private key [env: SERVER_HTTP2_TLS_KEY=]
|
||||
--https-redirect[=<HTTPS_REDIRECT>]
|
||||
Redirect all requests with scheme "http" to "https" for the current server instance. It depends on "http2" to be enabled
|
||||
|
||||
[env: SERVER_HTTPS_REDIRECT=]
|
||||
[default: false]
|
||||
[possible values: true, false]
|
||||
|
||||
Redirect all requests with scheme "http" to "https" for the current server instance. It depends on "http2" to be enabled [env: SERVER_HTTPS_REDIRECT=] [default: false] [possible values: true, false]
|
||||
--https-redirect-host <HTTPS_REDIRECT_HOST>
|
||||
Canonical host name or IP of the HTTPS (HTTPS/2) server. It depends on "https_redirect" to be enabled
|
||||
|
||||
[env: SERVER_HTTPS_REDIRECT_HOST=]
|
||||
[default: localhost]
|
||||
|
||||
Canonical host name or IP of the HTTPS (HTTPS/2) server. It depends on "https_redirect" to be enabled [env: SERVER_HTTPS_REDIRECT_HOST=] [default: localhost]
|
||||
--https-redirect-from-port <HTTPS_REDIRECT_FROM_PORT>
|
||||
HTTP host port where the redirect server will listen for requests to redirect them to HTTPS. It depends on "https_redirect" to be enabled
|
||||
|
||||
[env: SERVER_HTTPS_REDIRECT_FROM_PORT=]
|
||||
[default: 80]
|
||||
|
||||
HTTP host port where the redirect server will listen for requests to redirect them to HTTPS. It depends on "https_redirect" to be enabled [env: SERVER_HTTPS_REDIRECT_FROM_PORT=] [default: 80]
|
||||
--https-redirect-from-hosts <HTTPS_REDIRECT_FROM_HOSTS>
|
||||
List of host names or IPs allowed to redirect from. HTTP requests must contain the HTTP 'Host' header and match against this list. It depends on "https_redirect" to be enabled
|
||||
|
||||
[env: SERVER_HTTPS_REDIRECT_FROM_HOSTS=]
|
||||
[default: localhost]
|
||||
|
||||
List of host names or IPs allowed to redirect from. HTTP requests must contain the HTTP 'Host' header and match against this list. It depends on "https_redirect" to be enabled [env: SERVER_HTTPS_REDIRECT_FROM_HOSTS=] [default: localhost]
|
||||
--index-files <INDEX_FILES>
|
||||
List of files that will be used as an index for requests ending with the slash character (‘/’). Files are checked in the specified order
|
||||
|
||||
[env: SERVER_INDEX_FILES=]
|
||||
[default: index.html]
|
||||
|
||||
List of files that will be used as an index for requests ending with the slash character (‘/’). Files are checked in the specified order [env: SERVER_INDEX_FILES=] [default: index.html]
|
||||
-x, --compression[=<COMPRESSION>]
|
||||
Gzip, Deflate, Brotli or Zstd compression on demand determined by the Accept-Encoding header and applied to text-based web file types only
|
||||
|
||||
[env: SERVER_COMPRESSION=]
|
||||
[default: true]
|
||||
[possible values: true, false]
|
||||
|
||||
Gzip, Deflate, Brotli or Zstd compression on demand determined by the Accept-Encoding header and applied to text-based web file types only [env: SERVER_COMPRESSION=] [default: true] [possible values: true, false]
|
||||
--compression-level <COMPRESSION_LEVEL>
|
||||
Compression level to apply for Gzip, Deflate, Brotli or Zstd compression
|
||||
|
||||
[env: SERVER_COMPRESSION_LEVEL=]
|
||||
[default: default]
|
||||
|
||||
Possible values:
|
||||
- fastest: Fastest execution at the expense of larger file sizes
|
||||
- best: Smallest file size but potentially slow
|
||||
- default: Algorithm-specific default compression level setting
|
||||
|
||||
Compression level to apply for Gzip, Deflate, Brotli or Zstd compression [env: SERVER_COMPRESSION_LEVEL=] [default: default] [possible values: fastest, best, default]
|
||||
--compression-static[=<COMPRESSION_STATIC>]
|
||||
Look up the pre-compressed file variant (`.gz`, `.br` or `.zst`) on disk of a requested file and serves it directly if available. The compression type is determined by the `Accept-Encoding` header
|
||||
|
||||
[env: SERVER_COMPRESSION_STATIC=]
|
||||
[default: false]
|
||||
[possible values: true, false]
|
||||
|
||||
Look up the pre-compressed file variant (`.gz`, `.br` or `.zst`) on disk of a requested file and serves it directly if available. The compression type is determined by the `Accept-Encoding` header [env: SERVER_COMPRESSION_STATIC=] [default: false] [possible values: true, false]
|
||||
-z, --directory-listing[=<DIRECTORY_LISTING>]
|
||||
Enable directory listing for all requests ending with the slash character (‘/’)
|
||||
|
||||
[env: SERVER_DIRECTORY_LISTING=]
|
||||
[default: false]
|
||||
[possible values: true, false]
|
||||
|
||||
Enable directory listing for all requests ending with the slash character (‘/’) [env: SERVER_DIRECTORY_LISTING=] [default: false] [possible values: true, false]
|
||||
--directory-listing-order <DIRECTORY_LISTING_ORDER>
|
||||
Specify a default code number to order directory listing entries per `Name`, `Last modified` or `Size` attributes (columns). Code numbers supported: 0 (Name asc), 1 (Name desc), 2 (Last modified asc), 3 (Last modified desc), 4 (Size asc), 5 (Size desc). Default 6 (unordered)
|
||||
|
||||
[env: SERVER_DIRECTORY_LISTING_ORDER=]
|
||||
[default: 6]
|
||||
|
||||
Specify a default code number to order directory listing entries per `Name`, `Last modified` or `Size` attributes (columns). Code numbers supported: 0 (Name asc), 1 (Name desc), 2 (Last modified asc), 3 (Last modified desc), 4 (Size asc), 5 (Size desc). Default 6 (unordered) [env: SERVER_DIRECTORY_LISTING_ORDER=] [default: 6]
|
||||
--directory-listing-format <DIRECTORY_LISTING_FORMAT>
|
||||
Specify a content format for directory listing entries. Formats supported: "html" or "json". Default "html"
|
||||
|
||||
[env: SERVER_DIRECTORY_LISTING_FORMAT=]
|
||||
[default: html]
|
||||
|
||||
Possible values:
|
||||
- html: HTML format to display (default)
|
||||
- json: JSON format to display
|
||||
|
||||
Specify a content format for directory listing entries. Formats supported: "html" or "json". Default "html" [env: SERVER_DIRECTORY_LISTING_FORMAT=] [default: html] [possible values: html, json]
|
||||
--security-headers[=<SECURITY_HEADERS>]
|
||||
Enable security headers by default when HTTP/2 feature is activated. Headers included: "Strict-Transport-Security: max-age=63072000; includeSubDomains; preload" (2 years max-age), "X-Frame-Options: DENY" and "Content-Security-Policy: frame-ancestors 'self'"
|
||||
|
||||
[env: SERVER_SECURITY_HEADERS=]
|
||||
[default: false]
|
||||
[possible values: true, false]
|
||||
|
||||
Enable security headers by default when HTTP/2 feature is activated. Headers included: "Strict-Transport-Security: max-age=63072000; includeSubDomains; preload" (2 years max-age), "X-Frame-Options: DENY" and "Content-Security-Policy: frame-ancestors 'self'" [env: SERVER_SECURITY_HEADERS=] [default: false] [possible values: true, false]
|
||||
-e, --cache-control-headers[=<CACHE_CONTROL_HEADERS>]
|
||||
Enable cache control headers for incoming requests based on a set of file types. The file type list can be found on `src/control_headers.rs` file
|
||||
|
||||
[env: SERVER_CACHE_CONTROL_HEADERS=]
|
||||
[default: true]
|
||||
[possible values: true, false]
|
||||
|
||||
Enable cache control headers for incoming requests based on a set of file types. The file type list can be found on `src/control_headers.rs` file [env: SERVER_CACHE_CONTROL_HEADERS=] [default: true] [possible values: true, false]
|
||||
--basic-auth <BASIC_AUTH>
|
||||
It provides The "Basic" HTTP Authentication scheme using credentials as "user-id:password" pairs. Password must be encoded using the "BCrypt" password-hashing function
|
||||
|
||||
[env: SERVER_BASIC_AUTH=]
|
||||
[default: ]
|
||||
|
||||
It provides The "Basic" HTTP Authentication scheme using credentials as "user-id:password" pairs. Password must be encoded using the "BCrypt" password-hashing function [env: SERVER_BASIC_AUTH=] [default: ]
|
||||
-q, --grace-period <GRACE_PERIOD>
|
||||
Defines a grace period in seconds after a `SIGTERM` signal is caught which will delay the server before to shut it down gracefully. The maximum value is 255 seconds
|
||||
|
||||
[env: SERVER_GRACE_PERIOD=]
|
||||
[default: 0]
|
||||
|
||||
Defines a grace period in seconds after a `SIGTERM` signal is caught which will delay the server before to shut it down gracefully. The maximum value is 255 seconds [env: SERVER_GRACE_PERIOD=] [default: 0]
|
||||
-w, --config-file <CONFIG_FILE>
|
||||
Server TOML configuration file path
|
||||
|
||||
[env: SERVER_CONFIG_FILE=]
|
||||
[default: ./config.toml]
|
||||
|
||||
Server TOML configuration file path [env: SERVER_CONFIG_FILE=] [default: ./config.toml]
|
||||
--log-remote-address[=<LOG_REMOTE_ADDRESS>]
|
||||
Log incoming requests information along with its remote address if available using the `info` log level
|
||||
|
||||
[env: SERVER_LOG_REMOTE_ADDRESS=]
|
||||
[default: false]
|
||||
[possible values: true, false]
|
||||
|
||||
Log incoming requests information along with its remote address if available using the `info` log level [env: SERVER_LOG_REMOTE_ADDRESS=] [default: false] [possible values: true, false]
|
||||
--redirect-trailing-slash[=<REDIRECT_TRAILING_SLASH>]
|
||||
Check for a trailing slash in the requested directory URI and redirect permanently (308) to the same path with a trailing slash suffix if it is missing
|
||||
|
||||
[env: SERVER_REDIRECT_TRAILING_SLASH=]
|
||||
[default: true]
|
||||
[possible values: true, false]
|
||||
|
||||
Check for a trailing slash in the requested directory URI and redirect permanently (308) to the same path with a trailing slash suffix if it is missing [env: SERVER_REDIRECT_TRAILING_SLASH=] [default: true] [possible values: true, false]
|
||||
--ignore-hidden-files[=<IGNORE_HIDDEN_FILES>]
|
||||
Ignore hidden files/directories (dotfiles), preventing them to be served and being included in auto HTML index pages (directory listing)
|
||||
|
||||
[env: SERVER_IGNORE_HIDDEN_FILES=]
|
||||
[default: false]
|
||||
[possible values: true, false]
|
||||
|
||||
Ignore hidden files/directories (dotfiles), preventing them to be served and being included in auto HTML index pages (directory listing) [env: SERVER_IGNORE_HIDDEN_FILES=] [default: false] [possible values: true, false]
|
||||
--disable-symlinks[=<DISABLE_SYMLINKS>]
|
||||
Prevent following files or directories if any path name component is a symbolic link [env: SERVER_DISABLE_SYMLINKS=] [default: false] [possible values: true, false]
|
||||
--health[=<HEALTH>]
|
||||
Add a /health endpoint that doesn't generate any log entry and returns a 200 status code. This is especially useful with Kubernetes liveness and readiness probes
|
||||
|
||||
[env: SERVER_HEALTH=]
|
||||
[default: false]
|
||||
[possible values: true, false]
|
||||
|
||||
Add a /health endpoint that doesn't generate any log entry and returns a 200 status code. This is especially useful with Kubernetes liveness and readiness probes [env: SERVER_HEALTH=] [default: false] [possible values: true, false]
|
||||
--maintenance-mode[=<MAINTENANCE_MODE>]
|
||||
Enable the server's maintenance mode functionality
|
||||
|
||||
[env: SERVER_MAINTENANCE_MODE=]
|
||||
[default: false]
|
||||
[possible values: true, false]
|
||||
|
||||
Enable the server's maintenance mode functionality [env: SERVER_MAINTENANCE_MODE=] [default: false] [possible values: true, false]
|
||||
--maintenance-mode-status <MAINTENANCE_MODE_STATUS>
|
||||
Provide a custom HTTP status code when entering into maintenance mode. Default 503
|
||||
|
||||
[env: SERVER_MAINTENANCE_MODE_STATUS=]
|
||||
[default: 503]
|
||||
|
||||
Provide a custom HTTP status code when entering into maintenance mode. Default 503 [env: SERVER_MAINTENANCE_MODE_STATUS=] [default: 503]
|
||||
--maintenance-mode-file <MAINTENANCE_MODE_FILE>
|
||||
Provide a custom maintenance mode HTML file. If not provided then a generic message will be displayed
|
||||
|
||||
[env: SERVER_MAINTENANCE_MODE_FILE=]
|
||||
[default: ]
|
||||
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Provide a custom maintenance mode HTML file. If not provided then a generic message will be displayed [env: SERVER_MAINTENANCE_MODE_FILE=] [default: ]
|
||||
-V, --version
|
||||
Print version
|
||||
Print version info and exit
|
||||
-h, --help
|
||||
Print help (see more with '--help')
|
||||
```
|
||||
|
||||
## Windows
|
||||
|
||||
@@ -7,13 +7,13 @@ The server can be configured via the following environment variables.
|
||||
- [Command-line arguments](./command-line-arguments.md) take precedence over their equivalent environment variables.
|
||||
|
||||
### SERVER_HOST
|
||||
The address of the host (E.g 127.0.0.1). Default `[::]`.
|
||||
The address of the host (e.g. 127.0.0.1). Default `[::]`.
|
||||
|
||||
### SERVER_PORT
|
||||
The port of the host. Default `80`.
|
||||
|
||||
### SERVER_LISTEN_FD
|
||||
Optional file descriptor number (e.g. `0`) to inherit an already-opened TCP listener on (instead of using `SERVER_HOST` and/or `SERVER_PORT`). Default empty (disabled).
|
||||
Optional file descriptor number (e.g. `0`) to inherit an already-opened TCP listener (instead of using `SERVER_HOST` and/or `SERVER_PORT`). Default empty (disabled).
|
||||
|
||||
### SERVER_ROOT
|
||||
Relative or absolute root directory path of static files. Default `./public`.
|
||||
@@ -25,10 +25,10 @@ The Server configuration file path is in TOML format. See [The TOML Configuratio
|
||||
Defines a grace period in seconds after a `SIGTERM` signal is caught which will delay the server before shutting it down gracefully. The maximum value is `255` seconds. The default value is `0` (no delay).
|
||||
|
||||
### SERVER_LOG_LEVEL
|
||||
Specify a logging level in lower case. Possible values are `error`, `warn`, `info`, `debug` or `trace`. Default `error`.
|
||||
Specify a logging level in lowercase. Possible values are `error`, `warn`, `info`, `debug` or `trace`. Default `error`.
|
||||
|
||||
### SERVER_LOG_REMOTE_ADDRESS
|
||||
Log incoming requests information along with its Remote Address (IP) if available using the `info` log level. Default `false`.
|
||||
Log incoming request information along with its Remote Address (IP) if available using the `info` log level. Default `false`.
|
||||
|
||||
### SERVER_ERROR_PAGE_404
|
||||
HTML file path for 404 errors. If the path is not specified or simply doesn't exist then the server will use a generic HTML error message.
|
||||
@@ -39,10 +39,10 @@ HTML file path for 50x errors. If the path is not specified or simply doesn't ex
|
||||
If a relative path is used then it will be resolved under the root directory. Default `./50x.html`
|
||||
|
||||
### SERVER_FALLBACK_PAGE
|
||||
HTML file path that is used for `GET` requests when the requested path doesn't exist. The fallback page is served with a `200` status code, useful when using client routers (E.g `React Router`). If the path is not specified or simply doesn't exist then this feature will not be active.
|
||||
HTML file path that is used for `GET` requests when the requested path doesn't exist. The fallback page is served with a `200` status code, useful when using client routers (e.g. `React Router``). If the path is not specified or simply doesn't exist then this feature will not be active.
|
||||
|
||||
### SERVER_THREADS_MULTIPLIER
|
||||
The number of worker threads multiplier that'll be multiplied by the number of system CPUs using the formula: `worker threads = number of CPUs * n` where `n` is the value that changes here. When the multiplier value is 0 or 1 then the `number of CPUs` is used. The number of worker threads result should be a number between 1 and 32,768 though it is advised to keep this value on the smaller side. Default one thread per core.
|
||||
The number of worker threads multiplier will be multiplied by the number of system CPUs using the formula: `worker threads = number of CPUs * n` where `n` is the value that changes here. When the multiplier value is 0 or 1 then the `number of CPUs` is used. The number of worker threads result should be a number between 1 and 32,768 though it is advised to keep this value on the smaller side. Default one thread per core.
|
||||
|
||||
### SERVER_MAX_BLOCKING_THREADS
|
||||
Maximum number of blocking threads.
|
||||
@@ -84,7 +84,7 @@ Specify an optional CORS list of exposed HTTP headers separated by commas. It re
|
||||
Supported values are `fastest` (fast compression but larger resulting files), `best` (smallest file size but potentially slow) and `default` (algorithm-specific balanced compression level). Default is `default`.
|
||||
|
||||
### SERVER_COMPRESSION_STATIC
|
||||
Look up the pre-compressed file variant (`.gz`, `.br` or `.zst`) on disk of a requested file and serves it directly if available. Default `false` (disabled). The compression type is determined by the `Accept-Encoding` header.
|
||||
Look up the pre-compressed file variant (`.gz`, `.br` or `.zst`) on the disk of a requested file and serve it directly if available. Default `false` (disabled). The compression type is determined by the `Accept-Encoding` header.
|
||||
|
||||
### SERVER_DIRECTORY_LISTING
|
||||
Enable directory listing for all requests ending with the slash character (‘/’). Default `false` (disabled).
|
||||
@@ -96,10 +96,10 @@ Specify a default code number to order directory listing entries per `Name`, `La
|
||||
Specify a content format for the directory listing entries. Formats supported: `html` or `json`. Default `html`.
|
||||
|
||||
### SERVER_SECURITY_HEADERS
|
||||
Enable security headers by default when HTTP/2 feature is activated. Headers included: `Strict-Transport-Security: max-age=63072000; includeSubDomains; preload` (2 years max-age), `X-Frame-Options: DENY` and `Content-Security-Policy: frame-ancestors 'self'`. Default `false` (disabled).
|
||||
Enable security headers by default when the HTTP/2 feature is activated. Headers included: `Strict-Transport-Security: max-age=63072000; includeSubDomains; preload` (2 years max-age), `X-Frame-Options: DENY` and `Content-Security-Policy: frame-ancestors 'self'`. Default `false` (disabled).
|
||||
|
||||
### SERVER_CACHE_CONTROL_HEADERS
|
||||
Enable cache control headers for incoming requests based on a set of file types. The file type list can be found on [`src/control_headers.rs`](https://github.com/static-web-server/static-web-server/blob/master//src/control_headers.rs) file. Default `true` (enabled).
|
||||
Enable cache control headers for incoming requests based on a set of file types. The file type list can be found in [`src/control_headers.rs`](https://github.com/static-web-server/static-web-server/blob/master//src/control_headers.rs) file. Default `true` (enabled).
|
||||
|
||||
### SERVER_BASIC_AUTH
|
||||
It provides [The "Basic" HTTP Authentication Scheme](https://datatracker.ietf.org/doc/html/rfc7617) using credentials as `user-id:password` pairs, encoded using `Base64`. Password must be encoded using the [BCrypt](https://en.wikipedia.org/wiki/Bcrypt) password-hashing function. Default empty (disabled).
|
||||
@@ -108,7 +108,10 @@ It provides [The "Basic" HTTP Authentication Scheme](https://datatracker.ietf.or
|
||||
Check for a trailing slash in the requested directory URI and redirect permanent (308) to the same path with a trailing slash suffix if it is missing. Default `true` (enabled).
|
||||
|
||||
### SERVER_IGNORE_HIDDEN_FILES
|
||||
Ignore hidden files/directories (dotfiles), preventing them to be served and being included in auto HTML index pages (directory listing).
|
||||
Ignore hidden files/directories (dotfiles), preventing them from being served and being included in auto HTML index pages (directory listing).
|
||||
|
||||
### SERVER_DISABLE_SYMLINKS
|
||||
Prevent following files or directories if any path name component is a symbolic link.
|
||||
|
||||
### SERVER_HEALTH
|
||||
Activate the health endpoint.
|
||||
|
||||
15
docs/content/features/disable-symlinks.md
Normal file
15
docs/content/features/disable-symlinks.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Disable Symlinks
|
||||
|
||||
**`SWS`** does follow symlinks by default. However, it's possible to disable all symlinks (deny access) by preventing to following files or directories if any path name component is a symbolic link. This applies to direct requests (URL) or those using the directory listing.
|
||||
As a result, SWS will respond with a `403 Forbidden` status.
|
||||
|
||||
This feature is disabled by default and can be controlled by the boolean `--disable-symlinks` option or the equivalent [SERVER_DISABLE_SYMLINKS](./../configuration/environment-variables.md#server_disable_symlinks) env.
|
||||
|
||||
Here is an example of how to ignore hidden files:
|
||||
|
||||
```sh
|
||||
static-web-server \
|
||||
-p=8787 -d=./public -g=trace \
|
||||
--directory-listing \
|
||||
--disable-symlinks
|
||||
```
|
||||
@@ -160,6 +160,7 @@ nav:
|
||||
- 'Windows Service': 'features/windows-service.md'
|
||||
- 'Trailing Slash Redirect': 'features/trailing-slash-redirect.md'
|
||||
- 'Ignore Files': 'features/ignore-files.md'
|
||||
- 'Disable Symlinks': 'features/disable-symlinks.md'
|
||||
- 'Health endpoint': 'features/health-endpoint.md'
|
||||
- 'Virtual Hosting': 'features/virtual-hosting.md'
|
||||
- 'Multiple Index Files': 'features/multiple-index-files.md'
|
||||
|
||||
@@ -56,6 +56,8 @@ pub struct DirListOpts<'a> {
|
||||
pub dir_listing_format: &'a DirListFmt,
|
||||
/// Ignore hidden files (dotfiles).
|
||||
pub ignore_hidden_files: bool,
|
||||
/// Prevent following symlinks for files and directories.
|
||||
pub disable_symlinks: bool,
|
||||
}
|
||||
|
||||
/// Initializes directory listings.
|
||||
@@ -89,16 +91,17 @@ pub fn auto_index(
|
||||
|
||||
match std::fs::read_dir(parent) {
|
||||
Ok(dir_reader) => Either::Left(async move {
|
||||
let is_head = opts.method.is_head();
|
||||
match read_dir_entries(
|
||||
let dir_opts = DirEntryOpts {
|
||||
dir_reader,
|
||||
opts.current_path,
|
||||
opts.uri_query,
|
||||
is_head,
|
||||
opts.dir_listing_order,
|
||||
opts.dir_listing_format,
|
||||
opts.ignore_hidden_files,
|
||||
) {
|
||||
base_path: opts.current_path,
|
||||
uri_query: opts.uri_query,
|
||||
is_head: opts.method.is_head(),
|
||||
order_code: opts.dir_listing_order,
|
||||
content_format: opts.dir_listing_format,
|
||||
ignore_hidden_files: opts.ignore_hidden_files,
|
||||
disable_symlinks: opts.disable_symlinks,
|
||||
};
|
||||
match read_dir_entries(dir_opts) {
|
||||
Ok(resp) => Ok(resp),
|
||||
Err(err) => {
|
||||
tracing::error!("error after try to read directory entries: {:?}", err);
|
||||
@@ -175,22 +178,26 @@ struct SortingAttr<'a> {
|
||||
size: &'a str,
|
||||
}
|
||||
|
||||
/// Defines read directory entries.
|
||||
struct DirEntryOpts<'a> {
|
||||
dir_reader: std::fs::ReadDir,
|
||||
base_path: &'a str,
|
||||
uri_query: Option<&'a str>,
|
||||
is_head: bool,
|
||||
order_code: u8,
|
||||
content_format: &'a DirListFmt,
|
||||
ignore_hidden_files: bool,
|
||||
disable_symlinks: bool,
|
||||
}
|
||||
|
||||
/// It reads a list of directory entries and create an index page content.
|
||||
/// Otherwise it returns a status error.
|
||||
fn read_dir_entries(
|
||||
dir_reader: std::fs::ReadDir,
|
||||
base_path: &str,
|
||||
uri_query: Option<&str>,
|
||||
is_head: bool,
|
||||
mut order_code: u8,
|
||||
content_format: &DirListFmt,
|
||||
ignore_hidden_files: bool,
|
||||
) -> Result<Response<Body>> {
|
||||
fn read_dir_entries(mut opt: DirEntryOpts<'_>) -> Result<Response<Body>> {
|
||||
let mut dirs_count: usize = 0;
|
||||
let mut files_count: usize = 0;
|
||||
let mut file_entries: Vec<FileEntry> = vec![];
|
||||
|
||||
for dir_entry in dir_reader {
|
||||
for dir_entry in opt.dir_reader {
|
||||
let dir_entry = dir_entry.with_context(|| "unable to read directory entry")?;
|
||||
let meta = match dir_entry.metadata() {
|
||||
Ok(m) => m,
|
||||
@@ -206,7 +213,7 @@ fn read_dir_entries(
|
||||
let name = dir_entry.file_name();
|
||||
|
||||
// Check and ignore the current hidden file/directory (dotfile) if feature enabled
|
||||
if ignore_hidden_files && name.as_encoded_bytes().first().is_some_and(|c| *c == b'.') {
|
||||
if opt.ignore_hidden_files && name.as_encoded_bytes().first().is_some_and(|c| *c == b'.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -216,7 +223,7 @@ fn read_dir_entries(
|
||||
} else if meta.is_file() {
|
||||
files_count += 1;
|
||||
(FileType::File, Some(meta.len()))
|
||||
} else if meta.file_type().is_symlink() {
|
||||
} else if !opt.disable_symlinks && meta.file_type().is_symlink() {
|
||||
// NOTE: we resolve the symlink path below to just know if is a directory or not.
|
||||
// However, we are still showing the symlink name but not the resolved name.
|
||||
|
||||
@@ -264,11 +271,12 @@ fn read_dir_entries(
|
||||
// entries should contain the "parent/entry-name" as a link format.
|
||||
// Otherwise, we just use the "entry-name" as a link (default behavior).
|
||||
// Note that in both cases, we add a trailing slash if the entry is a directory.
|
||||
let mut uri = if !base_path.ends_with('/') && !base_path.is_empty() {
|
||||
let parent = base_path
|
||||
let mut uri = if !opt.base_path.ends_with('/') && !opt.base_path.is_empty() {
|
||||
let parent = opt
|
||||
.base_path
|
||||
.rsplit_once('/')
|
||||
.map(|(_, parent)| parent)
|
||||
.unwrap_or(base_path);
|
||||
.unwrap_or(opt.base_path);
|
||||
format!("{parent}/{name_encoded}")
|
||||
} else {
|
||||
name_encoded
|
||||
@@ -280,24 +288,25 @@ fn read_dir_entries(
|
||||
|
||||
let mtime = meta.modified().ok().map(DateTime::<Local>::from);
|
||||
|
||||
file_entries.push(FileEntry {
|
||||
let entry = FileEntry {
|
||||
name,
|
||||
mtime,
|
||||
size,
|
||||
r#type,
|
||||
uri,
|
||||
});
|
||||
};
|
||||
file_entries.push(entry);
|
||||
}
|
||||
|
||||
// Check the query request uri for a sorting type. E.g https://blah/?sort=5
|
||||
if let Some(q) = uri_query {
|
||||
if let Some(q) = opt.uri_query {
|
||||
let mut parts = form_urlencoded::parse(q.as_bytes());
|
||||
if parts.count() > 0 {
|
||||
// NOTE: we just pick up the first value (pair)
|
||||
if let Some(sort) = parts.next() {
|
||||
if sort.0 == "sort" && !sort.1.trim().is_empty() {
|
||||
match sort.1.parse::<u8>() {
|
||||
Ok(code) => order_code = code,
|
||||
Ok(code) => opt.order_code = code,
|
||||
Err(err) => {
|
||||
tracing::error!(
|
||||
"sorting: query value error when converting to u8: {:?}",
|
||||
@@ -313,13 +322,13 @@ fn read_dir_entries(
|
||||
let mut resp = Response::new(Body::empty());
|
||||
|
||||
// Handle directory listing content format
|
||||
let content = match content_format {
|
||||
let content = match opt.content_format {
|
||||
DirListFmt::Json => {
|
||||
// JSON
|
||||
resp.headers_mut()
|
||||
.typed_insert(ContentType::from(mime::APPLICATION_JSON));
|
||||
|
||||
json_auto_index(&mut file_entries, order_code)?
|
||||
json_auto_index(&mut file_entries, opt.order_code)?
|
||||
}
|
||||
// HTML (default)
|
||||
_ => {
|
||||
@@ -327,11 +336,11 @@ fn read_dir_entries(
|
||||
.typed_insert(ContentType::from(mime::TEXT_HTML_UTF_8));
|
||||
|
||||
html_auto_index(
|
||||
base_path,
|
||||
opt.base_path,
|
||||
dirs_count,
|
||||
files_count,
|
||||
&mut file_entries,
|
||||
order_code,
|
||||
opt.order_code,
|
||||
)
|
||||
}
|
||||
};
|
||||
@@ -340,7 +349,7 @@ fn read_dir_entries(
|
||||
.typed_insert(ContentLength(content.len() as u64));
|
||||
|
||||
// We skip the body for HEAD requests
|
||||
if is_head {
|
||||
if opt.is_head {
|
||||
return Ok(resp);
|
||||
}
|
||||
|
||||
|
||||
@@ -95,6 +95,8 @@ pub struct RequestHandlerOpts {
|
||||
pub redirect_trailing_slash: bool,
|
||||
/// Ignore hidden files feature.
|
||||
pub ignore_hidden_files: bool,
|
||||
/// Prevent following symlinks for files and directories.
|
||||
pub disable_symlinks: bool,
|
||||
/// Health endpoint feature.
|
||||
pub health: bool,
|
||||
/// Metrics endpoint feature (experimental).
|
||||
@@ -144,6 +146,7 @@ impl Default for RequestHandlerOpts {
|
||||
log_remote_address: false,
|
||||
redirect_trailing_slash: true,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
health: false,
|
||||
#[cfg(all(unix, feature = "experimental"))]
|
||||
experimental_metrics: false,
|
||||
@@ -178,6 +181,7 @@ impl RequestHandler {
|
||||
let redirect_trailing_slash = self.opts.redirect_trailing_slash;
|
||||
let compression_static = self.opts.compression_static;
|
||||
let ignore_hidden_files = self.opts.ignore_hidden_files;
|
||||
let disable_symlinks = self.opts.disable_symlinks;
|
||||
let index_files: Vec<&str> = self.opts.index_files.iter().map(|s| s.as_str()).collect();
|
||||
|
||||
log_addr::pre_process(&self.opts, req, remote_addr);
|
||||
@@ -260,6 +264,7 @@ impl RequestHandler {
|
||||
compression_static,
|
||||
ignore_hidden_files,
|
||||
index_files,
|
||||
disable_symlinks,
|
||||
})
|
||||
.await
|
||||
{
|
||||
|
||||
@@ -232,6 +232,10 @@ impl Server {
|
||||
let ignore_hidden_files = general.ignore_hidden_files;
|
||||
server_info!("ignore hidden files: enabled={}", ignore_hidden_files);
|
||||
|
||||
// Disable symlinks option
|
||||
let disable_symlinks = general.disable_symlinks;
|
||||
server_info!("disable symlinks: enabled={}", disable_symlinks);
|
||||
|
||||
// Grace period option
|
||||
let grace_period = general.grace_period;
|
||||
server_info!("grace period before graceful shutdown: {}s", grace_period);
|
||||
@@ -255,6 +259,7 @@ impl Server {
|
||||
log_remote_address,
|
||||
redirect_trailing_slash,
|
||||
ignore_hidden_files,
|
||||
disable_symlinks,
|
||||
index_files,
|
||||
advanced_opts,
|
||||
..Default::default()
|
||||
|
||||
@@ -438,6 +438,18 @@ pub struct General {
|
||||
/// Ignore hidden files/directories (dotfiles), preventing them to be served and being included in auto HTML index pages (directory listing).
|
||||
pub ignore_hidden_files: bool,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
default_value = "false",
|
||||
default_missing_value("true"),
|
||||
num_args(0..=1),
|
||||
require_equals(true),
|
||||
action = clap::ArgAction::Set,
|
||||
env = "SERVER_DISABLE_SYMLINKS",
|
||||
)]
|
||||
/// Prevent following files or directories if any path name component is a symbolic link.
|
||||
pub disable_symlinks: bool,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
default_value = "false",
|
||||
|
||||
@@ -344,6 +344,9 @@ pub struct General {
|
||||
/// Ignore hidden files feature.
|
||||
pub ignore_hidden_files: Option<bool>,
|
||||
|
||||
/// Prevent following symbolic links of files or directories.
|
||||
pub disable_symlinks: Option<bool>,
|
||||
|
||||
/// Health endpoint feature.
|
||||
pub health: Option<bool>,
|
||||
|
||||
|
||||
@@ -180,6 +180,7 @@ impl Settings {
|
||||
let mut log_remote_address = opts.log_remote_address;
|
||||
let mut redirect_trailing_slash = opts.redirect_trailing_slash;
|
||||
let mut ignore_hidden_files = opts.ignore_hidden_files;
|
||||
let mut disable_symlinks = opts.disable_symlinks;
|
||||
let mut index_files = opts.index_files;
|
||||
let mut health = opts.health;
|
||||
|
||||
@@ -349,6 +350,9 @@ impl Settings {
|
||||
if let Some(v) = general.ignore_hidden_files {
|
||||
ignore_hidden_files = v
|
||||
}
|
||||
if let Some(v) = general.disable_symlinks {
|
||||
disable_symlinks = v
|
||||
}
|
||||
if let Some(v) = general.health {
|
||||
health = v
|
||||
}
|
||||
@@ -628,6 +632,7 @@ impl Settings {
|
||||
log_remote_address,
|
||||
redirect_trailing_slash,
|
||||
ignore_hidden_files,
|
||||
disable_symlinks,
|
||||
index_files,
|
||||
health,
|
||||
#[cfg(all(unix, feature = "experimental"))]
|
||||
|
||||
@@ -76,6 +76,8 @@ pub struct HandleOpts<'a> {
|
||||
pub compression_static: bool,
|
||||
/// Ignore hidden files feature.
|
||||
pub ignore_hidden_files: bool,
|
||||
/// Prevent following symlinks for files and directories.
|
||||
pub disable_symlinks: bool,
|
||||
}
|
||||
|
||||
/// Static file response type with additional data.
|
||||
@@ -110,6 +112,7 @@ pub async fn handle<'a>(opts: &HandleOpts<'a>) -> Result<StaticFileResponse, Sta
|
||||
headers_opt,
|
||||
opts.compression_static,
|
||||
opts.index_files,
|
||||
opts.disable_symlinks,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -171,6 +174,7 @@ pub async fn handle<'a>(opts: &HandleOpts<'a>) -> Result<StaticFileResponse, Sta
|
||||
dir_listing_order: opts.dir_listing_order,
|
||||
dir_listing_format: opts.dir_listing_format,
|
||||
ignore_hidden_files: opts.ignore_hidden_files,
|
||||
disable_symlinks: opts.disable_symlinks,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -221,9 +225,20 @@ async fn get_composed_file_metadata<'a>(
|
||||
_headers: &'a HeaderMap<HeaderValue>,
|
||||
_compression_static: bool,
|
||||
mut index_files: &'a [&'a str],
|
||||
disable_symlinks: bool,
|
||||
) -> Result<FileMetadata<'a>, StatusCode> {
|
||||
tracing::trace!("getting metadata for file {}", file_path.display());
|
||||
|
||||
// Prevent symlinks access if option is enabled
|
||||
if disable_symlinks && file_path.is_symlink() {
|
||||
tracing::warn!(
|
||||
"file path {} is a symlink, access denied",
|
||||
file_path.display()
|
||||
);
|
||||
return Err(StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
// Try to find the file path on the file system
|
||||
match try_metadata(file_path) {
|
||||
Ok((mut metadata, is_dir)) => {
|
||||
if is_dir {
|
||||
|
||||
@@ -96,6 +96,7 @@ pub mod fixtures {
|
||||
log_remote_address: general.log_remote_address,
|
||||
redirect_trailing_slash: general.redirect_trailing_slash,
|
||||
ignore_hidden_files: general.ignore_hidden_files,
|
||||
disable_symlinks: general.disable_symlinks,
|
||||
index_files: vec![general.index_files],
|
||||
health: general.health,
|
||||
#[cfg(all(unix, feature = "experimental"))]
|
||||
|
||||
@@ -62,6 +62,7 @@ mod tests {
|
||||
))]
|
||||
compression_static: true,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -132,6 +133,7 @@ mod tests {
|
||||
))]
|
||||
compression_static: true,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -199,6 +201,7 @@ mod tests {
|
||||
))]
|
||||
compression_static: true,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -253,6 +256,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: true,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -295,6 +299,7 @@ mod tests {
|
||||
))]
|
||||
compression_static: true,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
|
||||
@@ -49,6 +49,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -81,6 +82,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -123,6 +125,7 @@ mod tests {
|
||||
redirect_trailing_slash: false,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -165,6 +168,7 @@ mod tests {
|
||||
redirect_trailing_slash: false,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -197,6 +201,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -250,6 +255,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: true,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -266,10 +272,10 @@ mod tests {
|
||||
|
||||
if method == Method::GET {
|
||||
let entries: Vec<FileEntry> = serde_json::from_str(body_str).unwrap();
|
||||
assert_eq!(entries.len(), 5);
|
||||
assert_eq!(entries.len(), 6);
|
||||
|
||||
let first_entry = entries.first().unwrap();
|
||||
assert_eq!(first_entry.name, "spécial-directöry.net");
|
||||
assert_eq!(first_entry.name, "symlink");
|
||||
assert_eq!(first_entry.typed, "directory");
|
||||
assert!(!first_entry.mtime.is_empty());
|
||||
assert!(first_entry.size.is_none());
|
||||
@@ -321,6 +327,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -365,6 +372,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: true,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
|
||||
1
tests/fixtures/public/symlink
vendored
Symbolic link
1
tests/fixtures/public/symlink
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
./spécial-directöry.net
|
||||
@@ -29,6 +29,17 @@ mod tests {
|
||||
PathBuf::from("docker/public/")
|
||||
}
|
||||
|
||||
const METHODS: [Method; 8] = [
|
||||
Method::CONNECT,
|
||||
Method::DELETE,
|
||||
Method::GET,
|
||||
Method::HEAD,
|
||||
Method::PATCH,
|
||||
Method::POST,
|
||||
Method::PUT,
|
||||
Method::TRACE,
|
||||
];
|
||||
|
||||
#[tokio::test]
|
||||
async fn handle_file() {
|
||||
let result = static_files::handle(&HandleOpts {
|
||||
@@ -46,6 +57,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -89,6 +101,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -133,6 +146,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -164,6 +178,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -197,6 +212,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -229,6 +245,7 @@ mod tests {
|
||||
redirect_trailing_slash: false,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -266,6 +283,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -318,6 +336,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -352,6 +371,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -388,6 +408,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -425,6 +446,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -465,6 +487,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -503,6 +526,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -539,6 +563,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -574,6 +599,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -597,17 +623,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn handle_file_allowed_disallowed_methods() {
|
||||
let methods = [
|
||||
Method::CONNECT,
|
||||
Method::DELETE,
|
||||
Method::GET,
|
||||
Method::HEAD,
|
||||
Method::PATCH,
|
||||
Method::POST,
|
||||
Method::PUT,
|
||||
Method::TRACE,
|
||||
];
|
||||
for method in methods {
|
||||
for method in METHODS {
|
||||
match static_files::handle(&HandleOpts {
|
||||
method: &method,
|
||||
headers: &HeaderMap::new(),
|
||||
@@ -623,6 +639,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -706,6 +723,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -776,6 +794,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -825,6 +844,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -874,6 +894,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -924,6 +945,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -966,6 +988,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -1018,6 +1041,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -1067,6 +1091,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -1116,6 +1141,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -1168,6 +1194,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -1210,6 +1237,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -1251,6 +1279,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -1307,6 +1336,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: false,
|
||||
ignore_hidden_files: false,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -1355,6 +1385,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: true,
|
||||
ignore_hidden_files: true,
|
||||
disable_symlinks: false,
|
||||
index_files: &[],
|
||||
})
|
||||
.await
|
||||
@@ -1394,6 +1425,7 @@ mod tests {
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: true,
|
||||
ignore_hidden_files: true,
|
||||
disable_symlinks: false,
|
||||
index_files: &["index.html", "index.htm"],
|
||||
})
|
||||
.await
|
||||
@@ -1413,4 +1445,79 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handle_disable_symlinks() {
|
||||
let root_dir = PathBuf::from("tests/fixtures/public/");
|
||||
let headers = HeaderMap::new();
|
||||
|
||||
for method in METHODS {
|
||||
match static_files::handle(&HandleOpts {
|
||||
method: &method,
|
||||
headers: &headers,
|
||||
base_path: &root_dir,
|
||||
uri_path: "/symlink",
|
||||
uri_query: None,
|
||||
#[cfg(feature = "directory-listing")]
|
||||
dir_listing: false,
|
||||
#[cfg(feature = "directory-listing")]
|
||||
dir_listing_order: 6,
|
||||
#[cfg(feature = "directory-listing")]
|
||||
dir_listing_format: &DirListFmt::Html,
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: true,
|
||||
ignore_hidden_files: true,
|
||||
disable_symlinks: true,
|
||||
index_files: &["index.html", "index.htm"],
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => panic!("unexpected successful response rather than an error"),
|
||||
Err(err) => {
|
||||
match method {
|
||||
// The handle only accepts HEAD or GET request methods
|
||||
Method::GET | Method::HEAD => assert_eq!(err, StatusCode::FORBIDDEN),
|
||||
_ => assert_eq!(err, StatusCode::METHOD_NOT_ALLOWED),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for method in METHODS {
|
||||
match static_files::handle(&HandleOpts {
|
||||
method: &method,
|
||||
headers: &headers,
|
||||
base_path: &root_dir,
|
||||
uri_path: "/symlink/spécial file.txt~",
|
||||
uri_query: None,
|
||||
#[cfg(feature = "directory-listing")]
|
||||
dir_listing: false,
|
||||
#[cfg(feature = "directory-listing")]
|
||||
dir_listing_order: 6,
|
||||
#[cfg(feature = "directory-listing")]
|
||||
dir_listing_format: &DirListFmt::Html,
|
||||
redirect_trailing_slash: true,
|
||||
compression_static: true,
|
||||
ignore_hidden_files: true,
|
||||
disable_symlinks: false,
|
||||
index_files: &["index.html", "index.htm"],
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(result) => {
|
||||
let res = result.resp;
|
||||
assert_eq!(res.status(), 200);
|
||||
}
|
||||
Err(err) => {
|
||||
match method {
|
||||
// The handle only accepts HEAD or GET request methods
|
||||
Method::GET | Method::HEAD => {
|
||||
panic!("unexpected an error response {}", err)
|
||||
}
|
||||
_ => assert_eq!(err, StatusCode::METHOD_NOT_ALLOWED),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user