diff --git a/.github/workflows/test-core-linux.yml b/.github/workflows/test-core-linux.yml index 6d85ec66b..e5cf03aed 100644 --- a/.github/workflows/test-core-linux.yml +++ b/.github/workflows/test-core-linux.yml @@ -65,7 +65,7 @@ jobs: curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor | sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/ubuntu `lsb_release -cs` nginx" | sudo tee /etc/apt/sources.list.d/nginx.list sudo apt update - sudo apt install -y nginx=1.24.0-1~jammy + sudo apt install -y nginx=1.26.0-1~jammy - name: Fix version without a starting number if: inputs.RELEASE == 'testing' || inputs.RELEASE == 'dev' run: echo "force-bad-version" | sudo tee -a /etc/dpkg/dpkg.cfg diff --git a/.github/workflows/tests-ui-linux.yml b/.github/workflows/tests-ui-linux.yml index 3128b4f14..47bec5bdd 100644 --- a/.github/workflows/tests-ui-linux.yml +++ b/.github/workflows/tests-ui-linux.yml @@ -65,7 +65,7 @@ jobs: curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor | sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/ubuntu `lsb_release -cs` nginx" | sudo tee /etc/apt/sources.list.d/nginx.list sudo apt update - sudo apt install -y nginx=1.24.0-1~jammy + sudo apt install -y nginx=1.26.0-1~jammy - name: Fix version without a starting number if: inputs.RELEASE == 'testing' || inputs.RELEASE == 'dev' || inputs.RELEASE == 'ui' run: echo "force-bad-version" | sudo tee -a /etc/dpkg/dpkg.cfg diff --git a/.gitleaksignore b/.gitleaksignore index 03e06d422..71dcd76db 100644 --- a/.gitleaksignore +++ b/.gitleaksignore @@ -11,3 +11,5 @@ src/ui/templates/settings_plugins.html:hashicorp-tf-password:106 src/ui/templates/account.html:hashicorp-tf-password:154 src/ui/templates/account.html:hashicorp-tf-password:162 src/common/core/errors/files/error.html:aws-access-token:20 +src/deps/src/nginx/src/event/quic/ngx_event_quic_protection.c:generic-api-key:164 +src/deps/src/nginx/src/event/quic/ngx_event_quic_protection.c:generic-api-key:165 diff --git a/README.md b/README.md index c537b098b..929e550f9 100644 --- a/README.md +++ b/README.md @@ -283,9 +283,6 @@ Repositories of Linux packages for BunkerWeb are available on [PackageCloud](htt You will find more information in the [Linux section](https://docs.bunkerweb.io/1.5.8/integrations/?utm_campaign=self&utm_source=github#linux) of the documentation. -> [!IMPORTANT] -> As of Ubuntu 24.04, the `nginx` package is not available in the official repository. You will need to use the `jammy` repository to install NGINX 1.24.0. Also we do not yet run automated tests on Ubuntu 24.04, so please consider this version as experimental. - ## Microsoft Azure

diff --git a/docs/integrations.md b/docs/integrations.md index 063675404..25a4c9df2 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -320,7 +320,7 @@ Supported Linux distributions for BunkerWeb (amd64/x86_64 and arm64/aarch64 arch - Red Hat Enterprise Linux (RHEL) 8.9 - Red Hat Enterprise Linux (RHEL) 9.4 -Please ensure that you have **NGINX 1.24.0 installed before installing BunkerWeb**. For all distributions, except Fedora, it is mandatory to use prebuilt packages from the [official NGINX repository](https://nginx.org/en/linux_packages.html). Compiling NGINX from source or using packages from different repositories will not work with the official prebuilt packages of BunkerWeb. However, you have the option to build BunkerWeb from source. +Please ensure that you have **NGINX 1.26.0 installed before installing BunkerWeb**. For all distributions, except Fedora, it is mandatory to use prebuilt packages from the [official NGINX repository](https://nginx.org/en/linux_packages.html). Compiling NGINX from source or using packages from different repositories will not work with the official prebuilt packages of BunkerWeb. However, you have the option to build BunkerWeb from source. To simplify the installation process, Linux package repositories for BunkerWeb are available on [PackageCloud](https://packagecloud.io/bunkerity/bunkerweb). They provide a bash script that automatically adds and trusts the repository. You can follow the provided script for automatic setup, or opt for [manual installation](https://packagecloud.io/bunkerity/bunkerweb/install) instructions if you prefer. @@ -337,11 +337,11 @@ To simplify the installation process, Linux package repositories for BunkerWeb a | sudo tee /etc/apt/sources.list.d/nginx.list ``` - You should now be able to install NGINX 1.24.0 : + You should now be able to install NGINX 1.26.0 : ```shell sudo apt update && \ - sudo apt install -y nginx=1.24.0-1~$(lsb_release -cs) + sudo apt install -y nginx=1.26.0-1~$(lsb_release -cs) ``` !!! warning "Testing/dev version" @@ -373,11 +373,6 @@ To simplify the installation process, Linux package repositories for BunkerWeb a === "Ubuntu" - !!! example "Specifications for Ubuntu 24.04" - As of Ubuntu 24.04, the `nginx` package is not available in the official repository. You will need to use the `jammy` repository to install NGINX 1.24.0. - - Also we do not yet run automated tests on Ubuntu 24.04, so please consider this version as experimental. - The first step is to add NGINX official repository : ```shell @@ -385,15 +380,15 @@ To simplify the installation process, Linux package repositories for BunkerWeb a curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \ | sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null && \ echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \ - http://nginx.org/packages/ubuntu jammy nginx" \ + http://nginx.org/packages/ubuntu `lsb_release -cs` nginx" \ | sudo tee /etc/apt/sources.list.d/nginx.list ``` - You should now be able to install NGINX 1.24.0 : + You should now be able to install NGINX 1.26.0 : ```shell sudo apt update && \ - sudo apt install -y nginx=1.24.0-1~jammy + sudo apt install -y nginx=1.26.0-1~$(lsb_release -cs) ``` !!! warning "Testing/dev version" @@ -425,10 +420,10 @@ To simplify the installation process, Linux package repositories for BunkerWeb a === "Fedora" - Fedora already provides NGINX 1.24.0 that we support : + Fedora already provides NGINX 1.26.0 that we support : ```shell - sudo dnf install -y nginx-1.24.0 + sudo dnf install -y nginx-1.26.0 ``` Optional step : if you want to automatically enable the [setup wizard](web-ui.md#setup-wizard) when BunkerWeb is installed, export the following variable : @@ -476,10 +471,10 @@ To simplify the installation process, Linux package repositories for BunkerWeb a module_hotfixes=true ``` - You should now be able to install NGINX 1.24.0 : + You should now be able to install NGINX 1.26.0 : ```shell - sudo dnf install nginx-1.24.0 + sudo dnf install nginx-1.26.0 ``` Optional step : if you want to automatically enable the [setup wizard](web-ui.md#setup-wizard) when BunkerWeb is installed, export the following variable : diff --git a/docs/security-tuning.md b/docs/security-tuning.md index a48d010f0..a88170019 100644 --- a/docs/security-tuning.md +++ b/docs/security-tuning.md @@ -137,8 +137,15 @@ Besides the HTTPS / SSL/TLS configuration, the following settings related to HTT | `AUTO_REDIRECT_HTTP_TO_HTTPS` | `yes` | When set to `yes`, will redirect every HTTP request to HTTPS only if BunkerWeb is configured with HTTPS. | | `SSL_PROTOCOLS` | `TLSv1.2 TLSv1.3` | List of supported SSL/TLS protocols when SSL is enabled. | | `HTTP2` | `yes` | When set to `yes`, will enable HTTP2 protocol support when using HTTPS. | +| `HTTP3` | `yes` | When set to `yes`, will enable HTTP3 protocol support when using HTTPS. | +| `HTTP3_ALT_SVC_PORT` | `443` | HTTP3 alternate service port. This value will be used as part of the Alt-Svc header. | | `LISTEN_HTTP` | `yes` | When set to `no`, BunkerWeb will not listen for HTTP requests. Useful if you want HTTPS only for example. | +!!! example "About HTTP3" + HTTP/3 is the next version of the HTTP protocol. It is based on Google's QUIC protocol which is a transport layer protocol that provides security and reliability features. HTTP/3 is designed to improve the performance of websites and web applications. + + **Remember that NGINX's support for HTTP/3 is still experimental and may not be suitable for all use cases.** + ### Let's Encrypt STREAM support :white_check_mark: diff --git a/docs/settings.md b/docs/settings.md index baee99334..b2821c0a0 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -433,6 +433,8 @@ Miscellaneous settings. |`ROOT_FOLDER` | |multisite|no |Root folder containing files to serve (/var/www/html/{server_name} if unset). | |`SSL_PROTOCOLS` |`TLSv1.2 TLSv1.3` |multisite|no |The supported version of TLS. We recommend the default value TLSv1.2 TLSv1.3 for compatibility reasons. | |`HTTP2` |`yes` |multisite|no |Support HTTP2 protocol when HTTPS is enabled. | +|`HTTP3` |`no` |multisite|no |Support HTTP3 protocol when HTTPS is enabled. | +|`HTTP3_ALT_SVC_PORT` |`443` |multisite|no |HTTP3 alternate service port. This value will be used as part of the Alt-Svc header. | |`LISTEN_HTTP` |`yes` |multisite|no |Respond to (insecure) HTTP requests. | |`USE_OPEN_FILE_CACHE` |`no` |multisite|no |Enable open file cache feature | |`OPEN_FILE_CACHE` |`max=1000 inactive=20s`|multisite|no |Open file cache directive | diff --git a/src/bw/Dockerfile b/src/bw/Dockerfile index b53ce6395..ce7a79406 100644 --- a/src/bw/Dockerfile +++ b/src/bw/Dockerfile @@ -1,4 +1,4 @@ -FROM nginx:1.24.0-alpine-slim@sha256:927eec798eb41b53f9e446aef26482ce4ade9008645ff13608c682cfe66b9503 AS builder +FROM nginx:1.26.0-alpine-slim@sha256:be13c98f606eef87521627d5c794a98ac1e5a8fcb085e75acdc0c9d66a28666c AS builder # Install temporary requirements for the dependencies RUN apk add --no-cache bash autoconf libtool automake geoip-dev g++ gcc curl-dev libxml2-dev pcre-dev make linux-headers musl-dev gd-dev gnupg brotli-dev openssl-dev patch readline-dev yajl yajl-dev yajl-tools py3-pip @@ -22,8 +22,8 @@ COPY src/common/gen/requirements.txt deps/requirements-gen.txt # Install python requirements RUN export MAKEFLAGS="-j$(nproc)" && \ - pip install --no-cache-dir --require-hashes --ignore-installed -r /tmp/requirements-deps.txt && \ - pip install --no-cache-dir --require-hashes --target deps/python -r deps/requirements-gen.txt + pip install --break-system-packages --no-cache-dir --require-hashes --ignore-installed -r /tmp/requirements-deps.txt && \ + pip install --break-system-packages --no-cache-dir --require-hashes --target deps/python -r deps/requirements-gen.txt # Copy files # can't exclude deps from . so we are copying everything by hand @@ -42,7 +42,7 @@ COPY src/common/utils utils COPY src/VERSION VERSION COPY misc/*.ascii misc/ -FROM nginx:1.24.0-alpine-slim@sha256:927eec798eb41b53f9e446aef26482ce4ade9008645ff13608c682cfe66b9503 +FROM nginx:1.26.0-alpine-slim@sha256:be13c98f606eef87521627d5c794a98ac1e5a8fcb085e75acdc0c9d66a28666c # Set default umask to prevent huge recursive chmod increasing the final image size RUN umask 027 @@ -68,7 +68,7 @@ RUN apk add --no-cache openssl pcre bash python3 yajl geoip libxml2 libgd curl & ln -s /proc/1/fd/1 /var/log/bunkerweb/access.log # Fix CVEs -RUN apk add --no-cache "busybox>=1.35.0-r30" "busybox-binsh>=1.35.0-r30" "ssl_client>=1.35.0-r30" # CVE-2023-42366 +RUN apk add --no-cache "busybox>=1.36.1-r17" "busybox-binsh>=1.36.1-r17" "ssl_client>=1.36.1-r17" # CVE-2023-42363 CVE-2023-42366 LABEL maintainer "Bunkerity " LABEL version "1.5.8" @@ -76,7 +76,7 @@ LABEL url "https://www.bunkerweb.io" LABEL bunkerweb.type "bunkerweb" LABEL bunkerweb.INSTANCE "bunkerweb" -EXPOSE 8080/tcp 8443/tcp +EXPOSE 8080/tcp 8443/tcp 8443/udp USER nginx:nginx diff --git a/src/common/confs/default-server-http.conf b/src/common/confs/default-server-http.conf index af4653a97..2d09efee9 100644 --- a/src/common/confs/default-server-http.conf +++ b/src/common/confs/default-server-http.conf @@ -29,9 +29,21 @@ server { {% endif %} ssl_certificate /var/cache/bunkerweb/misc/default-server-cert.pem; ssl_certificate_key /var/cache/bunkerweb/misc/default-server-cert.key; - listen 0.0.0.0:{{ HTTPS_PORT }} ssl {% if HTTP2 == "yes" %}http2{% endif %} default_server {% if USE_PROXY_PROTOCOL == "yes" %}proxy_protocol{% endif %}; + {% if HTTP2 == "yes" %} + http2 on; + {% endif %} + listen 0.0.0.0:{{ HTTPS_PORT }} ssl default_server {% if USE_PROXY_PROTOCOL == "yes" %}proxy_protocol{% endif %}; {% if USE_IPV6 == "yes" +%} - listen [::]:{{ HTTPS_PORT }} ssl {% if HTTP2 == "yes" %}http2{% endif %} default_server {% if USE_PROXY_PROTOCOL == "yes" %}proxy_protocol{% endif %}; + listen [::]:{{ HTTPS_PORT }} ssl default_server {% if USE_PROXY_PROTOCOL == "yes" %}proxy_protocol{% endif %}; + {% endif %} + + {% if HTTP3 == "yes" %} + http3 on; + listen 0.0.0.0:{{ HTTPS_PORT }} quic default_server {% if USE_PROXY_PROTOCOL == "yes" %}proxy_protocol{% endif %} reuseport; + {% if USE_IPV6 == "yes" +%} + listen [::]:{{ HTTPS_PORT }} quic default_server {% if USE_PROXY_PROTOCOL == "yes" %}proxy_protocol{% endif %} reuseport; + {% endif %} + add_header Alt-Svc 'h3=":{{ HTTP3_ALT_SVC_PORT }}"; ma=86400'; {% endif %} {% endif %} diff --git a/src/common/confs/server-http/ssl-certificate-lua.conf b/src/common/confs/server-http/ssl-certificate-lua.conf index 79e1c4d9e..e4da0ce9a 100644 --- a/src/common/confs/server-http/ssl-certificate-lua.conf +++ b/src/common/confs/server-http/ssl-certificate-lua.conf @@ -10,9 +10,21 @@ ssl_dhparam /etc/nginx/dhparam; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; {% endif %} -listen 0.0.0.0:{{ HTTPS_PORT }} ssl {% if HTTP2 == "yes" %}http2{% endif %} {% if USE_PROXY_PROTOCOL == "yes" %}proxy_protocol{% endif %}; +{% if HTTP2 == "yes" %} +http2 on; +{% endif %} +listen 0.0.0.0:{{ HTTPS_PORT }} ssl {% if USE_PROXY_PROTOCOL == "yes" %}proxy_protocol{% endif %}; {% if USE_IPV6 == "yes" +%} -listen [::]:{{ HTTPS_PORT }} ssl {% if HTTP2 == "yes" %}http2{% endif %} {% if USE_PROXY_PROTOCOL == "yes" %}proxy_protocol{% endif %}; +listen [::]:{{ HTTPS_PORT }} ssl {% if USE_PROXY_PROTOCOL == "yes" %}proxy_protocol{% endif %}; +{% endif %} + +{% if HTTP3 == "yes" %} +http3 on; +listen 0.0.0.0:{{ HTTPS_PORT }} quic {% if USE_PROXY_PROTOCOL == "yes" %}proxy_protocol{% endif %}; +{% if USE_IPV6 == "yes" +%} +listen [::]:{{ HTTPS_PORT }} quic {% if USE_PROXY_PROTOCOL == "yes" %}proxy_protocol{% endif %}; +{% endif %} +add_header Alt-Svc 'h3=":{{ HTTP3_ALT_SVC_PORT }}"; ma=86400'; {% endif %} ssl_certificate_by_lua_block { diff --git a/src/common/core/misc/plugin.json b/src/common/core/misc/plugin.json index 97013f241..c813b0a57 100644 --- a/src/common/core/misc/plugin.json +++ b/src/common/core/misc/plugin.json @@ -95,6 +95,24 @@ "regex": "^(yes|no)$", "type": "check" }, + "HTTP3": { + "context": "multisite", + "default": "no", + "help": "Support HTTP3 protocol when HTTPS is enabled.", + "id": "http3", + "label": "HTTP3", + "regex": "^(yes|no)$", + "type": "check" + }, + "HTTP3_ALT_SVC_PORT": { + "context": "multisite", + "default": "443", + "help": "HTTP3 alternate service port. This value will be used as part of the Alt-Svc header.", + "id": "http3-alt-svc-port", + "label": "HTTP3 Alt-Svc port", + "regex": "^\\d+$", + "type": "text" + }, "LISTEN_HTTP": { "context": "multisite", "default": "yes", diff --git a/src/common/db/Database.py b/src/common/db/Database.py index b27129d7e..743482102 100644 --- a/src/common/db/Database.py +++ b/src/common/db/Database.py @@ -220,6 +220,7 @@ class Database: def test_write(self): """Test the write access to the database""" + self.logger.debug("Testing write access to the database ...") with self.__db_session() as session: table_name = uuid4().hex session.execute(text(f"CREATE TABLE IF NOT EXISTS test_{table_name} (id INT)")) @@ -229,6 +230,8 @@ class Database: def retry_connection(self, *, readonly: bool = False, fallback: bool = False, **kwargs) -> None: """Retry the connection to the database""" + self.logger.debug(f"Retrying the connection to the database {'in read-only mode' if readonly else ''}{' with fallback' if fallback else ''} ...") + assert self.sql_engine is not None if fallback and not self.database_uri_readonly: diff --git a/src/common/gen/main.py b/src/common/gen/main.py index 61f794a7d..7334a8817 100644 --- a/src/common/gen/main.py +++ b/src/common/gen/main.py @@ -39,7 +39,6 @@ if __name__ == "__main__": parser.add_argument("--output", default=join(sep, "etc", "nginx"), type=str, help="where to write the rendered files") parser.add_argument("--target", default=join(sep, "etc", "nginx"), type=str, help="where nginx will search for configurations files") parser.add_argument("--variables", type=str, help="path to the file containing environment variables") - parser.add_argument("--no-linux-reload", action="store_true", help="disable linux reload") args = parser.parse_args() settings_path = Path(args.settings) diff --git a/src/deps/deps.json b/src/deps/deps.json index 665f4812c..7cd943fa4 100644 --- a/src/deps/deps.json +++ b/src/deps/deps.json @@ -29,9 +29,9 @@ }, { "id": "nginx", - "name": "Nginx v1.24.0", + "name": "Nginx v1.26.0", "url": "https://github.com/nginx/nginx.git", - "commit": "84cd7217778b8b3a2e395194b641ea14050344f1" + "commit": "361f6bf4b1cd5057d8f6365d1c0a94f3d72824e4" }, { "id": "ngx_brotli", @@ -225,9 +225,9 @@ }, { "id": "stream-lua-nginx-module", - "name": "stream-lua-nginx-module v0.0.14", + "name": "stream-lua-nginx-module v0.0.14+ (10 commits after to include nginx 1.26.0 fixes)", "url": "https://github.com/openresty/stream-lua-nginx-module.git", - "commit": "cafa6f55333541d1c78767a286fa434c97574a4c" + "commit": "bea8a0c0de94cede71554f53818ac0267d675d63" }, { "id": "zlib", diff --git a/src/deps/src/nginx/.hgtags b/src/deps/src/nginx/.hgtags index e50ceb9cf..4a7ee2678 100644 --- a/src/deps/src/nginx/.hgtags +++ b/src/deps/src/nginx/.hgtags @@ -472,3 +472,9 @@ a63d0a70afea96813ba6667997bc7d68b5863f0d release-1.23.1 aa901551a7ebad1e8b0f8c11cb44e3424ba29707 release-1.23.2 ff3afd1ce6a6b65057741df442adfaa71a0e2588 release-1.23.3 ac779115ed6ee4f3039e9aea414a54e560450ee2 release-1.23.4 +12dcf92b0c2c68552398f19644ce3104459807d7 release-1.25.0 +f8134640e8615448205785cf00b0bc810489b495 release-1.25.1 +1d839f05409d1a50d0f15a2bf36547001f99ae40 release-1.25.2 +294a3d07234f8f65d7b0e0b0e2c5b05c12c5da0a release-1.25.3 +173a0a7dbce569adbb70257c6ec4f0f6bc585009 release-1.25.4 +8618e4d900cc71082fbe7dc72af087937d64faf5 release-1.25.5 diff --git a/src/deps/src/nginx/auto/install b/src/deps/src/nginx/auto/install index c764fdd2f..7f73e4bff 100644 --- a/src/deps/src/nginx/auto/install +++ b/src/deps/src/nginx/auto/install @@ -112,7 +112,7 @@ install: build $NGX_INSTALL_PERL_MODULES test ! -f '\$(DESTDIR)$NGX_SBIN_PATH' \\ || mv '\$(DESTDIR)$NGX_SBIN_PATH' \\ '\$(DESTDIR)$NGX_SBIN_PATH.old' - cp $NGX_OBJS/nginx '\$(DESTDIR)$NGX_SBIN_PATH' + cp $NGX_OBJS/nginx$ngx_binext '\$(DESTDIR)$NGX_SBIN_PATH' test -d '\$(DESTDIR)$NGX_CONF_PREFIX' \\ || mkdir -p '\$(DESTDIR)$NGX_CONF_PREFIX' diff --git a/src/deps/src/nginx/auto/lib/geoip/conf b/src/deps/src/nginx/auto/lib/geoip/conf index 8302aae17..47165b15b 100644 --- a/src/deps/src/nginx/auto/lib/geoip/conf +++ b/src/deps/src/nginx/auto/lib/geoip/conf @@ -64,6 +64,23 @@ if [ $ngx_found = no ]; then fi +if [ $ngx_found = no ]; then + + # Homebrew on Apple Silicon + + ngx_feature="GeoIP library in /opt/homebrew/" + ngx_feature_path="/opt/homebrew/include" + + if [ $NGX_RPATH = YES ]; then + ngx_feature_libs="-R/opt/homebrew/lib -L/opt/homebrew/lib -lGeoIP" + else + ngx_feature_libs="-L/opt/homebrew/lib -lGeoIP" + fi + + . auto/feature +fi + + if [ $ngx_found = yes ]; then CORE_INCS="$CORE_INCS $ngx_feature_path" diff --git a/src/deps/src/nginx/auto/lib/google-perftools/conf b/src/deps/src/nginx/auto/lib/google-perftools/conf index 7f1a91139..94dadac62 100644 --- a/src/deps/src/nginx/auto/lib/google-perftools/conf +++ b/src/deps/src/nginx/auto/lib/google-perftools/conf @@ -46,6 +46,22 @@ if [ $ngx_found = no ]; then fi +if [ $ngx_found = no ]; then + + # Homebrew on Apple Silicon + + ngx_feature="Google perftools in /opt/homebrew/" + + if [ $NGX_RPATH = YES ]; then + ngx_feature_libs="-R/opt/homebrew/lib -L/opt/homebrew/lib -lprofiler" + else + ngx_feature_libs="-L/opt/homebrew/lib -lprofiler" + fi + + . auto/feature +fi + + if [ $ngx_found = yes ]; then CORE_LIBS="$CORE_LIBS $ngx_feature_libs" diff --git a/src/deps/src/nginx/auto/lib/libgd/conf b/src/deps/src/nginx/auto/lib/libgd/conf index 678639767..07f565677 100644 --- a/src/deps/src/nginx/auto/lib/libgd/conf +++ b/src/deps/src/nginx/auto/lib/libgd/conf @@ -65,6 +65,23 @@ if [ $ngx_found = no ]; then fi +if [ $ngx_found = no ]; then + + # Homebrew on Apple Silicon + + ngx_feature="GD library in /opt/homebrew/" + ngx_feature_path="/opt/homebrew/include" + + if [ $NGX_RPATH = YES ]; then + ngx_feature_libs="-R/opt/homebrew/lib -L/opt/homebrew/lib -lgd" + else + ngx_feature_libs="-L/opt/homebrew/lib -lgd" + fi + + . auto/feature +fi + + if [ $ngx_found = yes ]; then CORE_INCS="$CORE_INCS $ngx_feature_path" diff --git a/src/deps/src/nginx/auto/lib/openssl/conf b/src/deps/src/nginx/auto/lib/openssl/conf index 4fb52df7f..fdf430dff 100644 --- a/src/deps/src/nginx/auto/lib/openssl/conf +++ b/src/deps/src/nginx/auto/lib/openssl/conf @@ -5,12 +5,19 @@ if [ $OPENSSL != NONE ]; then + have=NGX_OPENSSL . auto/have + have=NGX_SSL . auto/have + + have=NGX_OPENSSL_NO_CONFIG . auto/have + + if [ $USE_OPENSSL_QUIC = YES ]; then + have=NGX_QUIC . auto/have + have=NGX_QUIC_OPENSSL_COMPAT . auto/have + fi + case "$CC" in cl | bcc32) - have=NGX_OPENSSL . auto/have - have=NGX_SSL . auto/have - CFLAGS="$CFLAGS -DNO_SYS_TYPES_H" CORE_INCS="$CORE_INCS $OPENSSL/openssl/include" @@ -33,9 +40,6 @@ if [ $OPENSSL != NONE ]; then ;; *) - have=NGX_OPENSSL . auto/have - have=NGX_SSL . auto/have - CORE_INCS="$CORE_INCS $OPENSSL/.openssl/include" CORE_DEPS="$CORE_DEPS $OPENSSL/.openssl/include/openssl/ssl.h" CORE_LIBS="$CORE_LIBS $OPENSSL/.openssl/lib/libssl.a" @@ -118,11 +122,58 @@ else . auto/feature fi + if [ $ngx_found = no ]; then + + # Homebrew on Apple Silicon + + ngx_feature="OpenSSL library in /opt/homebrew/" + ngx_feature_path="/opt/homebrew/include" + + if [ $NGX_RPATH = YES ]; then + ngx_feature_libs="-R/opt/homebrew/lib -L/opt/homebrew/lib -lssl -lcrypto" + else + ngx_feature_libs="-L/opt/homebrew/lib -lssl -lcrypto" + fi + + ngx_feature_libs="$ngx_feature_libs $NGX_LIBDL $NGX_LIBPTHREAD" + + . auto/feature + fi + if [ $ngx_found = yes ]; then have=NGX_SSL . auto/have CORE_INCS="$CORE_INCS $ngx_feature_path" CORE_LIBS="$CORE_LIBS $ngx_feature_libs" OPENSSL=YES + + if [ $USE_OPENSSL_QUIC = YES ]; then + + ngx_feature="OpenSSL QUIC support" + ngx_feature_name="NGX_QUIC" + ngx_feature_test="SSL_set_quic_method(NULL, NULL)" + . auto/feature + + if [ $ngx_found = no ]; then + have=NGX_QUIC_OPENSSL_COMPAT . auto/have + + ngx_feature="OpenSSL QUIC compatibility" + ngx_feature_test="SSL_CTX_add_custom_ext(NULL, 0, 0, + NULL, NULL, NULL, NULL, NULL)" + . auto/feature + fi + + if [ $ngx_found = no ]; then +cat << END + +$0: error: certain modules require OpenSSL QUIC support. +You can either do not enable the modules, or install the OpenSSL library with +QUIC support into the system, or build the OpenSSL library with QUIC support +statically from the source with nginx by using --with-openssl= option. + +END + exit 1 + fi + fi fi fi diff --git a/src/deps/src/nginx/auto/lib/pcre/conf b/src/deps/src/nginx/auto/lib/pcre/conf index 20c1cafbe..cdf1809f5 100644 --- a/src/deps/src/nginx/auto/lib/pcre/conf +++ b/src/deps/src/nginx/auto/lib/pcre/conf @@ -182,6 +182,22 @@ else . auto/feature fi + if [ $ngx_found = no ]; then + + # Homebrew on Apple Silicon + + ngx_feature="PCRE library in /opt/homebrew/" + ngx_feature_path="/opt/homebrew/include" + + if [ $NGX_RPATH = YES ]; then + ngx_feature_libs="-R/opt/homebrew/lib -L/opt/homebrew/lib -lpcre" + else + ngx_feature_libs="-L/opt/homebrew/lib -lpcre" + fi + + . auto/feature + fi + if [ $ngx_found = yes ]; then CORE_INCS="$CORE_INCS $ngx_feature_path" CORE_LIBS="$CORE_LIBS $ngx_feature_libs" diff --git a/src/deps/src/nginx/auto/make b/src/deps/src/nginx/auto/make index ef7c9f694..25ee3fb56 100644 --- a/src/deps/src/nginx/auto/make +++ b/src/deps/src/nginx/auto/make @@ -6,9 +6,10 @@ echo "creating $NGX_MAKEFILE" mkdir -p $NGX_OBJS/src/core $NGX_OBJS/src/event $NGX_OBJS/src/event/modules \ + $NGX_OBJS/src/event/quic \ $NGX_OBJS/src/os/unix $NGX_OBJS/src/os/win32 \ - $NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/modules \ - $NGX_OBJS/src/http/modules/perl \ + $NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/v3 \ + $NGX_OBJS/src/http/modules $NGX_OBJS/src/http/modules/perl \ $NGX_OBJS/src/mail \ $NGX_OBJS/src/stream \ $NGX_OBJS/src/misc diff --git a/src/deps/src/nginx/auto/modules b/src/deps/src/nginx/auto/modules index 94867bfc0..1a5e4212d 100644 --- a/src/deps/src/nginx/auto/modules +++ b/src/deps/src/nginx/auto/modules @@ -102,7 +102,7 @@ if [ $HTTP = YES ]; then fi - if [ $HTTP_V2 = YES ]; then + if [ $HTTP_V2 = YES -o $HTTP_V3 = YES ]; then HTTP_SRCS="$HTTP_SRCS $HTTP_HUFF_SRCS" fi @@ -124,6 +124,7 @@ if [ $HTTP = YES ]; then # ngx_http_header_filter # ngx_http_chunked_filter # ngx_http_v2_filter + # ngx_http_v3_filter # ngx_http_range_header_filter # ngx_http_gzip_filter # ngx_http_postpone_filter @@ -156,6 +157,7 @@ if [ $HTTP = YES ]; then ngx_http_header_filter_module \ ngx_http_chunked_filter_module \ ngx_http_v2_filter_module \ + ngx_http_v3_filter_module \ ngx_http_range_header_filter_module \ ngx_http_gzip_filter_module \ ngx_http_postpone_filter_module \ @@ -217,6 +219,17 @@ if [ $HTTP = YES ]; then . auto/module fi + if [ $HTTP_V3 = YES ]; then + ngx_module_name=ngx_http_v3_filter_module + ngx_module_incs= + ngx_module_deps= + ngx_module_srcs=src/http/v3/ngx_http_v3_filter_module.c + ngx_module_libs= + ngx_module_link=$HTTP_V3 + + . auto/module + fi + if :; then ngx_module_name=ngx_http_range_header_filter_module ngx_module_incs= @@ -410,7 +423,6 @@ if [ $HTTP = YES ]; then if [ $HTTP_V2 = YES ]; then have=NGX_HTTP_V2 . auto/have - have=NGX_HTTP_HEADERS . auto/have ngx_module_name=ngx_http_v2_module ngx_module_incs=src/http/v2 @@ -426,6 +438,32 @@ if [ $HTTP = YES ]; then . auto/module fi + if [ $HTTP_V3 = YES ]; then + USE_OPENSSL_QUIC=YES + HTTP_SSL=YES + + have=NGX_HTTP_V3 . auto/have + + ngx_module_name=ngx_http_v3_module + ngx_module_incs=src/http/v3 + ngx_module_deps="src/http/v3/ngx_http_v3.h \ + src/http/v3/ngx_http_v3_encode.h \ + src/http/v3/ngx_http_v3_parse.h \ + src/http/v3/ngx_http_v3_table.h \ + src/http/v3/ngx_http_v3_uni.h" + ngx_module_srcs="src/http/v3/ngx_http_v3.c \ + src/http/v3/ngx_http_v3_encode.c \ + src/http/v3/ngx_http_v3_parse.c \ + src/http/v3/ngx_http_v3_table.c \ + src/http/v3/ngx_http_v3_uni.c \ + src/http/v3/ngx_http_v3_request.c \ + src/http/v3/ngx_http_v3_module.c" + ngx_module_libs= + ngx_module_link=$HTTP_V3 + + . auto/module + fi + if :; then ngx_module_name=ngx_http_static_module ngx_module_incs= @@ -1128,6 +1166,16 @@ if [ $STREAM != NO ]; then . auto/module fi + if [ $STREAM_PASS = YES ]; then + ngx_module_name=ngx_stream_pass_module + ngx_module_deps= + ngx_module_srcs=src/stream/ngx_stream_pass_module.c + ngx_module_libs= + ngx_module_link=$STREAM_PASS + + . auto/module + fi + if [ $STREAM_SET = YES ]; then ngx_module_name=ngx_stream_set_module ngx_module_deps= @@ -1272,6 +1320,63 @@ if [ $USE_OPENSSL = YES ]; then fi +if [ $USE_OPENSSL_QUIC = YES ]; then + ngx_module_type=CORE + ngx_module_name=ngx_quic_module + ngx_module_incs= + ngx_module_deps="src/event/quic/ngx_event_quic.h \ + src/event/quic/ngx_event_quic_transport.h \ + src/event/quic/ngx_event_quic_protection.h \ + src/event/quic/ngx_event_quic_connection.h \ + src/event/quic/ngx_event_quic_frames.h \ + src/event/quic/ngx_event_quic_connid.h \ + src/event/quic/ngx_event_quic_migration.h \ + src/event/quic/ngx_event_quic_streams.h \ + src/event/quic/ngx_event_quic_ssl.h \ + src/event/quic/ngx_event_quic_tokens.h \ + src/event/quic/ngx_event_quic_ack.h \ + src/event/quic/ngx_event_quic_output.h \ + src/event/quic/ngx_event_quic_socket.h \ + src/event/quic/ngx_event_quic_openssl_compat.h" + ngx_module_srcs="src/event/quic/ngx_event_quic.c \ + src/event/quic/ngx_event_quic_udp.c \ + src/event/quic/ngx_event_quic_transport.c \ + src/event/quic/ngx_event_quic_protection.c \ + src/event/quic/ngx_event_quic_frames.c \ + src/event/quic/ngx_event_quic_connid.c \ + src/event/quic/ngx_event_quic_migration.c \ + src/event/quic/ngx_event_quic_streams.c \ + src/event/quic/ngx_event_quic_ssl.c \ + src/event/quic/ngx_event_quic_tokens.c \ + src/event/quic/ngx_event_quic_ack.c \ + src/event/quic/ngx_event_quic_output.c \ + src/event/quic/ngx_event_quic_socket.c \ + src/event/quic/ngx_event_quic_openssl_compat.c" + + ngx_module_libs= + ngx_module_link=YES + ngx_module_order= + + . auto/module + + if [ $QUIC_BPF = YES -a $SO_COOKIE_FOUND = YES ]; then + ngx_module_type=CORE + ngx_module_name=ngx_quic_bpf_module + ngx_module_incs= + ngx_module_deps= + ngx_module_srcs="src/event/quic/ngx_event_quic_bpf.c \ + src/event/quic/ngx_event_quic_bpf_code.c" + ngx_module_libs= + ngx_module_link=YES + ngx_module_order= + + . auto/module + + have=NGX_QUIC_BPF . auto/have + fi +fi + + if [ $USE_PCRE = YES ]; then ngx_module_type=CORE ngx_module_name=ngx_regex_module diff --git a/src/deps/src/nginx/auto/options b/src/deps/src/nginx/auto/options index 48f3a1a42..6a6e990a0 100644 --- a/src/deps/src/nginx/auto/options +++ b/src/deps/src/nginx/auto/options @@ -45,6 +45,8 @@ USE_THREADS=NO NGX_FILE_AIO=NO +QUIC_BPF=NO + HTTP=YES NGX_HTTP_LOG_PATH= @@ -59,6 +61,7 @@ HTTP_CHARSET=YES HTTP_GZIP=YES HTTP_SSL=NO HTTP_V2=NO +HTTP_V3=NO HTTP_SSI=YES HTTP_REALIP=NO HTTP_XSLT=NO @@ -124,6 +127,7 @@ STREAM_GEOIP=NO STREAM_MAP=YES STREAM_SPLIT_CLIENTS=YES STREAM_RETURN=YES +STREAM_PASS=YES STREAM_SET=YES STREAM_UPSTREAM_HASH=YES STREAM_UPSTREAM_LEAST_CONN=YES @@ -149,6 +153,7 @@ PCRE_JIT=NO PCRE2=YES USE_OPENSSL=NO +USE_OPENSSL_QUIC=NO OPENSSL=NONE USE_ZLIB=NO @@ -166,6 +171,8 @@ USE_GEOIP=NO NGX_GOOGLE_PERFTOOLS=NO NGX_CPP_TEST=NO +SO_COOKIE_FOUND=NO + NGX_LIBATOMIC=NO NGX_CPU_CACHE_LINE= @@ -211,6 +218,8 @@ do --with-file-aio) NGX_FILE_AIO=YES ;; + --without-quic_bpf_module) QUIC_BPF=NONE ;; + --with-ipv6) NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG $0: warning: the \"--with-ipv6\" option is deprecated" @@ -228,6 +237,7 @@ $0: warning: the \"--with-ipv6\" option is deprecated" --with-http_ssl_module) HTTP_SSL=YES ;; --with-http_v2_module) HTTP_V2=YES ;; + --with-http_v3_module) HTTP_V3=YES ;; --with-http_realip_module) HTTP_REALIP=YES ;; --with-http_addition_module) HTTP_ADDITION=YES ;; --with-http_xslt_module) HTTP_XSLT=YES ;; @@ -328,6 +338,7 @@ use the \"--with-mail_ssl_module\" option instead" --without-stream_split_clients_module) STREAM_SPLIT_CLIENTS=NO ;; --without-stream_return_module) STREAM_RETURN=NO ;; + --without-stream_pass_module) STREAM_PASS=NO ;; --without-stream_set_module) STREAM_SET=NO ;; --without-stream_upstream_hash_module) STREAM_UPSTREAM_HASH=NO ;; @@ -443,8 +454,11 @@ cat << END --with-file-aio enable file AIO support + --without-quic_bpf_module disable ngx_quic_bpf_module + --with-http_ssl_module enable ngx_http_ssl_module --with-http_v2_module enable ngx_http_v2_module + --with-http_v3_module enable ngx_http_v3_module --with-http_realip_module enable ngx_http_realip_module --with-http_addition_module enable ngx_http_addition_module --with-http_xslt_module enable ngx_http_xslt_module @@ -544,6 +558,7 @@ cat << END --without-stream_split_clients_module disable ngx_stream_split_clients_module --without-stream_return_module disable ngx_stream_return_module + --without-stream_pass_module disable ngx_stream_pass_module --without-stream_set_module disable ngx_stream_set_module --without-stream_upstream_hash_module disable ngx_stream_upstream_hash_module diff --git a/src/deps/src/nginx/auto/os/conf b/src/deps/src/nginx/auto/os/conf index d7f6e0382..bb0ce4ef2 100644 --- a/src/deps/src/nginx/auto/os/conf +++ b/src/deps/src/nginx/auto/os/conf @@ -115,6 +115,21 @@ case "$NGX_MACHINE" in NGX_MACH_CACHE_LINE=64 ;; + ppc64* | powerpc64*) + have=NGX_ALIGNMENT value=16 . auto/define + NGX_MACH_CACHE_LINE=128 + ;; + + riscv64) + have=NGX_ALIGNMENT value=16 . auto/define + NGX_MACH_CACHE_LINE=64 + ;; + + s390x) + have=NGX_ALIGNMENT value=16 . auto/define + NGX_MACH_CACHE_LINE=256 + ;; + *) have=NGX_ALIGNMENT value=16 . auto/define NGX_MACH_CACHE_LINE=32 diff --git a/src/deps/src/nginx/auto/os/linux b/src/deps/src/nginx/auto/os/linux index eb6702679..bc0556b3a 100644 --- a/src/deps/src/nginx/auto/os/linux +++ b/src/deps/src/nginx/auto/os/linux @@ -228,10 +228,58 @@ ngx_feature_test="struct crypt_data cd; crypt_r(\"key\", \"salt\", &cd);" . auto/feature +if [ $ngx_found = yes ]; then + CRYPT_LIB="-lcrypt" +fi + ngx_include="sys/vfs.h"; . auto/include +# BPF sockhash + +ngx_feature="BPF sockhash" +ngx_feature_name="NGX_HAVE_BPF" +ngx_feature_run=no +ngx_feature_incs="#include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="union bpf_attr attr = { 0 }; + + attr.map_flags = 0; + attr.map_type = BPF_MAP_TYPE_SOCKHASH; + + syscall(__NR_bpf, 0, &attr, 0);" +. auto/feature + +if [ $ngx_found = yes ]; then + CORE_SRCS="$CORE_SRCS src/core/ngx_bpf.c" + CORE_DEPS="$CORE_DEPS src/core/ngx_bpf.h" + + if [ $QUIC_BPF != NONE ]; then + QUIC_BPF=YES + fi +fi + + +ngx_feature="SO_COOKIE" +ngx_feature_name="NGX_HAVE_SO_COOKIE" +ngx_feature_run=no +ngx_feature_incs="#include + $NGX_INCLUDE_INTTYPES_H" +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="socklen_t optlen = sizeof(uint64_t); + uint64_t cookie; + getsockopt(0, SOL_SOCKET, SO_COOKIE, &cookie, &optlen)" +. auto/feature + +if [ $ngx_found = yes ]; then + SO_COOKIE_FOUND=YES +fi + + # UDP segmentation offloading ngx_feature="UDP_SEGMENT" diff --git a/src/deps/src/nginx/auto/os/win32 b/src/deps/src/nginx/auto/os/win32 index b821ae6d8..bce764b54 100644 --- a/src/deps/src/nginx/auto/os/win32 +++ b/src/deps/src/nginx/auto/os/win32 @@ -18,7 +18,7 @@ ngx_binext=".exe" case "$NGX_CC_NAME" in - gcc) + clang | gcc) CORE_LIBS="$CORE_LIBS -ladvapi32 -lws2_32" MAIN_LINK="$MAIN_LINK -Wl,--export-all-symbols" MAIN_LINK="$MAIN_LINK -Wl,--out-implib=$NGX_OBJS/libnginx.a" diff --git a/src/deps/src/nginx/auto/sources b/src/deps/src/nginx/auto/sources index a539093ce..46408ee53 100644 --- a/src/deps/src/nginx/auto/sources +++ b/src/deps/src/nginx/auto/sources @@ -83,7 +83,7 @@ CORE_SRCS="src/core/nginx.c \ EVENT_MODULES="ngx_events_module ngx_event_core_module" -EVENT_INCS="src/event src/event/modules" +EVENT_INCS="src/event src/event/modules src/event/quic" EVENT_DEPS="src/event/ngx_event.h \ src/event/ngx_event_timer.h \ diff --git a/src/deps/src/nginx/auto/unix b/src/deps/src/nginx/auto/unix index 867101982..f29e69c61 100644 --- a/src/deps/src/nginx/auto/unix +++ b/src/deps/src/nginx/auto/unix @@ -448,6 +448,54 @@ ngx_feature_test="setsockopt(0, IPPROTO_IPV6, IPV6_RECVPKTINFO, NULL, 0)" . auto/feature +# IP packet fragmentation + +ngx_feature="IP_MTU_DISCOVER" +ngx_feature_name="NGX_HAVE_IP_MTU_DISCOVER" +ngx_feature_run=no +ngx_feature_incs="#include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="(void) IP_PMTUDISC_DO; + setsockopt(0, IPPROTO_IP, IP_MTU_DISCOVER, NULL, 0)" +. auto/feature + + +ngx_feature="IPV6_MTU_DISCOVER" +ngx_feature_name="NGX_HAVE_IPV6_MTU_DISCOVER" +ngx_feature_run=no +ngx_feature_incs="#include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="(void) IPV6_PMTUDISC_DO; + setsockopt(0, IPPROTO_IPV6, IPV6_MTU_DISCOVER, NULL, 0)" +. auto/feature + + +ngx_feature="IP_DONTFRAG" +ngx_feature_name="NGX_HAVE_IP_DONTFRAG" +ngx_feature_run=no +ngx_feature_incs="#include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="setsockopt(0, IPPROTO_IP, IP_DONTFRAG, NULL, 0)" +. auto/feature + + +ngx_feature="IPV6_DONTFRAG" +ngx_feature_name="NGX_HAVE_IPV6_DONTFRAG" +ngx_feature_run=no +ngx_feature_incs="#include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="setsockopt(0, IPPROTO_IP, IPV6_DONTFRAG, NULL, 0)" +. auto/feature + + ngx_feature="TCP_DEFER_ACCEPT" ngx_feature_name="NGX_HAVE_DEFERRED_ACCEPT" ngx_feature_run=no diff --git a/src/deps/src/nginx/contrib/vim/syntax/nginx.vim b/src/deps/src/nginx/contrib/vim/syntax/nginx.vim index 7d587fc48..29eef7a23 100644 --- a/src/deps/src/nginx/contrib/vim/syntax/nginx.vim +++ b/src/deps/src/nginx/contrib/vim/syntax/nginx.vim @@ -65,12 +65,12 @@ syn match ngxListenComment '#.*$' \ contained \ nextgroup=@ngxListenParams skipwhite skipempty syn keyword ngxListenOptions contained - \ default_server ssl http2 proxy_protocol + \ default_server ssl quic proxy_protocol \ setfib fastopen backlog rcvbuf sndbuf accept_filter deferred bind \ ipv6only reuseport so_keepalive \ nextgroup=@ngxListenParams skipwhite skipempty syn keyword ngxListenOptionsDeprecated contained - \ spdy + \ http2 \ nextgroup=@ngxListenParams skipwhite skipempty syn cluster ngxListenParams \ contains=ngxListenParam,ngxListenString,ngxListenComment @@ -90,7 +90,6 @@ syn keyword ngxDirectiveBlock contained if syn keyword ngxDirectiveBlock contained geo syn keyword ngxDirectiveBlock contained map syn keyword ngxDirectiveBlock contained split_clients -syn keyword ngxDirectiveBlock contained match syn keyword ngxDirectiveImportant contained include syn keyword ngxDirectiveImportant contained root @@ -113,7 +112,6 @@ syn keyword ngxDirectiveError contained post_action syn keyword ngxDirectiveDeprecated contained proxy_downstream_buffer syn keyword ngxDirectiveDeprecated contained proxy_upstream_buffer -syn keyword ngxDirectiveDeprecated contained ssl syn keyword ngxDirectiveDeprecated contained http2_idle_timeout syn keyword ngxDirectiveDeprecated contained http2_max_field_size syn keyword ngxDirectiveDeprecated contained http2_max_header_size @@ -136,7 +134,6 @@ syn keyword ngxDirective contained alias syn keyword ngxDirective contained allow syn keyword ngxDirective contained ancient_browser syn keyword ngxDirective contained ancient_browser_value -syn keyword ngxDirective contained api syn keyword ngxDirective contained auth_basic syn keyword ngxDirective contained auth_basic_user_file syn keyword ngxDirective contained auth_delay @@ -144,15 +141,6 @@ syn keyword ngxDirective contained auth_http syn keyword ngxDirective contained auth_http_header syn keyword ngxDirective contained auth_http_pass_client_cert syn keyword ngxDirective contained auth_http_timeout -syn keyword ngxDirective contained auth_jwt -syn keyword ngxDirective contained auth_jwt_claim_set -syn keyword ngxDirective contained auth_jwt_header_set -syn keyword ngxDirective contained auth_jwt_key_cache -syn keyword ngxDirective contained auth_jwt_key_file -syn keyword ngxDirective contained auth_jwt_key_request -syn keyword ngxDirective contained auth_jwt_leeway -syn keyword ngxDirective contained auth_jwt_require -syn keyword ngxDirective contained auth_jwt_type syn keyword ngxDirective contained auth_request syn keyword ngxDirective contained auth_request_set syn keyword ngxDirective contained autoindex @@ -193,8 +181,6 @@ syn keyword ngxDirective contained error_log syn keyword ngxDirective contained etag syn keyword ngxDirective contained eventport_events syn keyword ngxDirective contained expires -syn keyword ngxDirective contained f4f -syn keyword ngxDirective contained f4f_buffer_size syn keyword ngxDirective contained fastcgi_bind syn keyword ngxDirective contained fastcgi_buffer_size syn keyword ngxDirective contained fastcgi_buffering @@ -211,7 +197,6 @@ syn keyword ngxDirective contained fastcgi_cache_max_range_offset syn keyword ngxDirective contained fastcgi_cache_methods syn keyword ngxDirective contained fastcgi_cache_min_uses syn keyword ngxDirective contained fastcgi_cache_path -syn keyword ngxDirective contained fastcgi_cache_purge syn keyword ngxDirective contained fastcgi_cache_revalidate syn keyword ngxDirective contained fastcgi_cache_use_stale syn keyword ngxDirective contained fastcgi_cache_valid @@ -295,14 +280,7 @@ syn keyword ngxDirective contained gzip_types syn keyword ngxDirective contained gzip_vary syn keyword ngxDirective contained gzip_window syn keyword ngxDirective contained hash -syn keyword ngxDirective contained health_check -syn keyword ngxDirective contained health_check_timeout -syn keyword ngxDirective contained hls -syn keyword ngxDirective contained hls_buffers -syn keyword ngxDirective contained hls_forward_args -syn keyword ngxDirective contained hls_fragment -syn keyword ngxDirective contained hls_mp4_buffer_size -syn keyword ngxDirective contained hls_mp4_max_buffer_size +syn keyword ngxDirective contained http2 syn keyword ngxDirective contained http2_body_preread_size syn keyword ngxDirective contained http2_chunk_size syn keyword ngxDirective contained http2_max_concurrent_pushes @@ -312,6 +290,10 @@ syn keyword ngxDirective contained http2_push syn keyword ngxDirective contained http2_push_preload syn keyword ngxDirective contained http2_recv_buffer_size syn keyword ngxDirective contained http2_streams_index_size +syn keyword ngxDirective contained http3 +syn keyword ngxDirective contained http3_hq +syn keyword ngxDirective contained http3_max_concurrent_streams +syn keyword ngxDirective contained http3_stream_buffer_size syn keyword ngxDirective contained if_modified_since syn keyword ngxDirective contained ignore_invalid_headers syn keyword ngxDirective contained image_filter @@ -342,21 +324,20 @@ syn keyword ngxDirective contained js_filter syn keyword ngxDirective contained js_header_filter syn keyword ngxDirective contained js_import syn keyword ngxDirective contained js_path +syn keyword ngxDirective contained js_preload_object syn keyword ngxDirective contained js_preread syn keyword ngxDirective contained js_set +syn keyword ngxDirective contained js_shared_dict_zone syn keyword ngxDirective contained js_var syn keyword ngxDirective contained keepalive syn keyword ngxDirective contained keepalive_disable syn keyword ngxDirective contained keepalive_requests syn keyword ngxDirective contained keepalive_time syn keyword ngxDirective contained keepalive_timeout -syn keyword ngxDirective contained keyval -syn keyword ngxDirective contained keyval_zone syn keyword ngxDirective contained kqueue_changes syn keyword ngxDirective contained kqueue_events syn keyword ngxDirective contained large_client_header_buffers syn keyword ngxDirective contained least_conn -syn keyword ngxDirective contained least_time syn keyword ngxDirective contained limit_conn syn keyword ngxDirective contained limit_conn_dry_run syn keyword ngxDirective contained limit_conn_log_level @@ -400,14 +381,11 @@ syn keyword ngxDirective contained modern_browser syn keyword ngxDirective contained modern_browser_value syn keyword ngxDirective contained mp4 syn keyword ngxDirective contained mp4_buffer_size -syn keyword ngxDirective contained mp4_limit_rate -syn keyword ngxDirective contained mp4_limit_rate_after syn keyword ngxDirective contained mp4_max_buffer_size syn keyword ngxDirective contained mp4_start_key_frame syn keyword ngxDirective contained msie_padding syn keyword ngxDirective contained msie_refresh syn keyword ngxDirective contained multi_accept -syn keyword ngxDirective contained ntlm syn keyword ngxDirective contained open_file_cache syn keyword ngxDirective contained open_file_cache_errors syn keyword ngxDirective contained open_file_cache_events @@ -450,7 +428,6 @@ syn keyword ngxDirective contained proxy_cache_max_range_offset syn keyword ngxDirective contained proxy_cache_methods syn keyword ngxDirective contained proxy_cache_min_uses syn keyword ngxDirective contained proxy_cache_path -syn keyword ngxDirective contained proxy_cache_purge syn keyword ngxDirective contained proxy_cache_revalidate syn keyword ngxDirective contained proxy_cache_use_stale syn keyword ngxDirective contained proxy_cache_valid @@ -488,7 +465,6 @@ syn keyword ngxDirective contained proxy_requests syn keyword ngxDirective contained proxy_responses syn keyword ngxDirective contained proxy_send_lowat syn keyword ngxDirective contained proxy_send_timeout -syn keyword ngxDirective contained proxy_session_drop syn keyword ngxDirective contained proxy_set_body syn keyword ngxDirective contained proxy_set_header syn keyword ngxDirective contained proxy_smtp_auth @@ -513,7 +489,11 @@ syn keyword ngxDirective contained proxy_temp_file_write_size syn keyword ngxDirective contained proxy_temp_path syn keyword ngxDirective contained proxy_timeout syn keyword ngxDirective contained proxy_upload_rate -syn keyword ngxDirective contained queue +syn keyword ngxDirective contained quic_active_connection_id_limit +syn keyword ngxDirective contained quic_bpf +syn keyword ngxDirective contained quic_gso +syn keyword ngxDirective contained quic_host_key +syn keyword ngxDirective contained quic_retry syn keyword ngxDirective contained random syn keyword ngxDirective contained random_index syn keyword ngxDirective contained read_ahead @@ -544,7 +524,6 @@ syn keyword ngxDirective contained scgi_cache_max_range_offset syn keyword ngxDirective contained scgi_cache_methods syn keyword ngxDirective contained scgi_cache_min_uses syn keyword ngxDirective contained scgi_cache_path -syn keyword ngxDirective contained scgi_cache_purge syn keyword ngxDirective contained scgi_cache_revalidate syn keyword ngxDirective contained scgi_cache_use_stale syn keyword ngxDirective contained scgi_cache_valid @@ -583,9 +562,6 @@ syn keyword ngxDirective contained server_name_in_redirect syn keyword ngxDirective contained server_names_hash_bucket_size syn keyword ngxDirective contained server_names_hash_max_size syn keyword ngxDirective contained server_tokens -syn keyword ngxDirective contained session_log -syn keyword ngxDirective contained session_log_format -syn keyword ngxDirective contained session_log_zone syn keyword ngxDirective contained set_real_ip_from syn keyword ngxDirective contained slice syn keyword ngxDirective contained smtp_auth @@ -633,11 +609,6 @@ syn keyword ngxDirective contained ssl_trusted_certificate syn keyword ngxDirective contained ssl_verify_client syn keyword ngxDirective contained ssl_verify_depth syn keyword ngxDirective contained starttls -syn keyword ngxDirective contained state -syn keyword ngxDirective contained status -syn keyword ngxDirective contained status_format -syn keyword ngxDirective contained status_zone -syn keyword ngxDirective contained sticky syn keyword ngxDirective contained stub_status syn keyword ngxDirective contained sub_filter syn keyword ngxDirective contained sub_filter_last_modified @@ -680,7 +651,6 @@ syn keyword ngxDirective contained uwsgi_cache_max_range_offset syn keyword ngxDirective contained uwsgi_cache_methods syn keyword ngxDirective contained uwsgi_cache_min_uses syn keyword ngxDirective contained uwsgi_cache_path -syn keyword ngxDirective contained uwsgi_cache_purge syn keyword ngxDirective contained uwsgi_cache_revalidate syn keyword ngxDirective contained uwsgi_cache_use_stale syn keyword ngxDirective contained uwsgi_cache_valid @@ -744,6 +714,62 @@ syn keyword ngxDirective contained xslt_string_param syn keyword ngxDirective contained xslt_stylesheet syn keyword ngxDirective contained xslt_types syn keyword ngxDirective contained zone + +" nginx-plus commercial extensions directives + +syn keyword ngxDirectiveBlock contained match +syn keyword ngxDirectiveBlock contained otel_exporter + +syn keyword ngxDirective contained api +syn keyword ngxDirective contained auth_jwt +syn keyword ngxDirective contained auth_jwt_claim_set +syn keyword ngxDirective contained auth_jwt_header_set +syn keyword ngxDirective contained auth_jwt_key_cache +syn keyword ngxDirective contained auth_jwt_key_file +syn keyword ngxDirective contained auth_jwt_key_request +syn keyword ngxDirective contained auth_jwt_leeway +syn keyword ngxDirective contained auth_jwt_require +syn keyword ngxDirective contained auth_jwt_type +syn keyword ngxDirective contained f4f +syn keyword ngxDirective contained f4f_buffer_size +syn keyword ngxDirective contained fastcgi_cache_purge +syn keyword ngxDirective contained health_check +syn keyword ngxDirective contained health_check_timeout +syn keyword ngxDirective contained hls +syn keyword ngxDirective contained hls_buffers +syn keyword ngxDirective contained hls_forward_args +syn keyword ngxDirective contained hls_fragment +syn keyword ngxDirective contained hls_mp4_buffer_size +syn keyword ngxDirective contained hls_mp4_max_buffer_size +syn keyword ngxDirective contained internal_redirect +syn keyword ngxDirective contained keyval +syn keyword ngxDirective contained keyval_zone +syn keyword ngxDirective contained least_time +syn keyword ngxDirective contained mp4_limit_rate +syn keyword ngxDirective contained mp4_limit_rate_after +syn keyword ngxDirective contained mqtt +syn keyword ngxDirective contained mqtt_preread +syn keyword ngxDirective contained mqtt_rewrite_buffer_size +syn keyword ngxDirective contained mqtt_set_connect +syn keyword ngxDirective contained ntlm +syn keyword ngxDirective contained otel_service_name +syn keyword ngxDirective contained otel_span_attr +syn keyword ngxDirective contained otel_span_name +syn keyword ngxDirective contained otel_trace +syn keyword ngxDirective contained otel_trace_context +syn keyword ngxDirective contained proxy_cache_purge +syn keyword ngxDirective contained proxy_session_drop +syn keyword ngxDirective contained queue +syn keyword ngxDirective contained scgi_cache_purge +syn keyword ngxDirective contained session_log +syn keyword ngxDirective contained session_log_format +syn keyword ngxDirective contained session_log_zone +syn keyword ngxDirective contained state +syn keyword ngxDirective contained status +syn keyword ngxDirective contained status_format +syn keyword ngxDirective contained status_zone +syn keyword ngxDirective contained sticky +syn keyword ngxDirective contained uwsgi_cache_purge syn keyword ngxDirective contained zone_sync syn keyword ngxDirective contained zone_sync_buffers syn keyword ngxDirective contained zone_sync_connect_retry_interval @@ -766,7 +792,6 @@ syn keyword ngxDirective contained zone_sync_ssl_verify syn keyword ngxDirective contained zone_sync_ssl_verify_depth syn keyword ngxDirective contained zone_sync_timeout - " 3rd party modules list taken from " https://github.com/freebsd/freebsd-ports/blob/main/www/nginx-devel/Makefile.extmod " ---------------------------------------------------------------------------------- @@ -837,52 +862,6 @@ syn keyword ngxDirectiveThirdParty contained brotli_window " https://github.com/torden/ngx_cache_purge syn keyword ngxDirectiveThirdParty contained cache_purge_response_type -" https://github.com/nginx-clojure/nginx-clojure -syn keyword ngxDirectiveThirdParty contained access_handler_code -syn keyword ngxDirectiveThirdParty contained access_handler_name -syn keyword ngxDirectiveThirdParty contained access_handler_property -syn keyword ngxDirectiveThirdParty contained access_handler_type -syn keyword ngxDirectiveThirdParty contained always_read_body -syn keyword ngxDirectiveThirdParty contained auto_upgrade_ws -syn keyword ngxDirectiveThirdParty contained body_filter_code -syn keyword ngxDirectiveThirdParty contained body_filter_name -syn keyword ngxDirectiveThirdParty contained body_filter_property -syn keyword ngxDirectiveThirdParty contained body_filter_type -syn keyword ngxDirectiveThirdParty contained content_handler_code -syn keyword ngxDirectiveThirdParty contained content_handler_name -syn keyword ngxDirectiveThirdParty contained content_handler_property -syn keyword ngxDirectiveThirdParty contained content_handler_type -syn keyword ngxDirectiveThirdParty contained handler_code -syn keyword ngxDirectiveThirdParty contained handler_name -syn keyword ngxDirectiveThirdParty contained handler_type -syn keyword ngxDirectiveThirdParty contained handlers_lazy_init -syn keyword ngxDirectiveThirdParty contained header_filter_code -syn keyword ngxDirectiveThirdParty contained header_filter_name -syn keyword ngxDirectiveThirdParty contained header_filter_property -syn keyword ngxDirectiveThirdParty contained header_filter_type -syn keyword ngxDirectiveThirdParty contained jvm_classpath -syn keyword ngxDirectiveThirdParty contained jvm_classpath_check -syn keyword ngxDirectiveThirdParty contained jvm_exit_handler_code -syn keyword ngxDirectiveThirdParty contained jvm_exit_handler_name -syn keyword ngxDirectiveThirdParty contained jvm_handler_type -syn keyword ngxDirectiveThirdParty contained jvm_init_handler_code -syn keyword ngxDirectiveThirdParty contained jvm_init_handler_name -syn keyword ngxDirectiveThirdParty contained jvm_options -syn keyword ngxDirectiveThirdParty contained jvm_path -syn keyword ngxDirectiveThirdParty contained jvm_var -syn keyword ngxDirectiveThirdParty contained jvm_workers -syn keyword ngxDirectiveThirdParty contained log_handler_code -syn keyword ngxDirectiveThirdParty contained log_handler_name -syn keyword ngxDirectiveThirdParty contained log_handler_property -syn keyword ngxDirectiveThirdParty contained log_handler_type -syn keyword ngxDirectiveThirdParty contained max_balanced_tcp_connections -syn keyword ngxDirectiveThirdParty contained rewrite_handler_code -syn keyword ngxDirectiveThirdParty contained rewrite_handler_name -syn keyword ngxDirectiveThirdParty contained rewrite_handler_property -syn keyword ngxDirectiveThirdParty contained rewrite_handler_type -syn keyword ngxDirectiveThirdParty contained shared_map -syn keyword ngxDirectiveThirdParty contained write_page_size - " https://github.com/AirisX/nginx_cookie_flag_module syn keyword ngxDirectiveThirdParty contained set_cookie_flag @@ -932,29 +911,6 @@ syn keyword ngxDirectiveThirdParty contained dns_update syn keyword ngxDirectiveThirdParty contained dynamic_state_file syn keyword ngxDirectiveThirdParty contained dynamic_upstream -" https://github.com/ZigzagAK/ngx_dynamic_healthcheck -syn keyword ngxDirectiveThirdParty contained check -syn keyword ngxDirectiveThirdParty contained check_disable_host -syn keyword ngxDirectiveThirdParty contained check_exclude_host -syn keyword ngxDirectiveThirdParty contained check_persistent -syn keyword ngxDirectiveThirdParty contained check_request_body -syn keyword ngxDirectiveThirdParty contained check_request_headers -syn keyword ngxDirectiveThirdParty contained check_request_uri -syn keyword ngxDirectiveThirdParty contained check_response_body -syn keyword ngxDirectiveThirdParty contained check_response_codes -syn keyword ngxDirectiveThirdParty contained healthcheck -syn keyword ngxDirectiveThirdParty contained healthcheck_buffer_size -syn keyword ngxDirectiveThirdParty contained healthcheck_disable_host -syn keyword ngxDirectiveThirdParty contained healthcheck_get -syn keyword ngxDirectiveThirdParty contained healthcheck_persistent -syn keyword ngxDirectiveThirdParty contained healthcheck_request_body -syn keyword ngxDirectiveThirdParty contained healthcheck_request_headers -syn keyword ngxDirectiveThirdParty contained healthcheck_request_uri -syn keyword ngxDirectiveThirdParty contained healthcheck_response_body -syn keyword ngxDirectiveThirdParty contained healthcheck_response_codes -syn keyword ngxDirectiveThirdParty contained healthcheck_status -syn keyword ngxDirectiveThirdParty contained healthcheck_update - " https://github.com/openresty/encrypted-session-nginx-module syn keyword ngxDirectiveThirdParty contained encrypted_session_expires syn keyword ngxDirectiveThirdParty contained encrypted_session_iv @@ -1004,6 +960,7 @@ syn keyword ngxDirectiveThirdParty contained auth_gss_map_to_local syn keyword ngxDirectiveThirdParty contained auth_gss_realm syn keyword ngxDirectiveThirdParty contained auth_gss_service_ccache syn keyword ngxDirectiveThirdParty contained auth_gss_service_name +syn keyword ngxDirectiveThirdParty contained auth_gss_zone_name " https://github.com/kvspb/nginx-auth-ldap syn keyword ngxDirectiveThirdParty contained auth_ldap @@ -1033,6 +990,7 @@ syn keyword ngxDirectiveThirdParty contained eval_subrequest_in_memory " https://github.com/aperezdc/ngx-fancyindex syn keyword ngxDirectiveThirdParty contained fancyindex +syn keyword ngxDirectiveThirdParty contained fancyindex_case_sensitive syn keyword ngxDirectiveThirdParty contained fancyindex_css_href syn keyword ngxDirectiveThirdParty contained fancyindex_default_sort syn keyword ngxDirectiveThirdParty contained fancyindex_directories_first @@ -1121,6 +1079,7 @@ syn keyword ngxDirectiveThirdParty contained nchan_publisher_upstream_request syn keyword ngxDirectiveThirdParty contained nchan_pubsub syn keyword ngxDirectiveThirdParty contained nchan_pubsub_channel_id syn keyword ngxDirectiveThirdParty contained nchan_pubsub_location +syn keyword ngxDirectiveThirdParty contained nchan_redis_accurate_subscriber_count syn keyword ngxDirectiveThirdParty contained nchan_redis_cluster_check_interval syn keyword ngxDirectiveThirdParty contained nchan_redis_cluster_check_interval_backoff syn keyword ngxDirectiveThirdParty contained nchan_redis_cluster_check_interval_jitter @@ -1138,6 +1097,11 @@ syn keyword ngxDirectiveThirdParty contained nchan_redis_connect_timeout syn keyword ngxDirectiveThirdParty contained nchan_redis_discovered_ip_range_blacklist syn keyword ngxDirectiveThirdParty contained nchan_redis_fakesub_timer_interval syn keyword ngxDirectiveThirdParty contained nchan_redis_idle_channel_cache_timeout +syn keyword ngxDirectiveThirdParty contained nchan_redis_idle_channel_keepalive_backoff +syn keyword ngxDirectiveThirdParty contained nchan_redis_idle_channel_keepalive_jitter +syn keyword ngxDirectiveThirdParty contained nchan_redis_idle_channel_keepalive_max +syn keyword ngxDirectiveThirdParty contained nchan_redis_idle_channel_keepalive_min +syn keyword ngxDirectiveThirdParty contained nchan_redis_idle_channel_keepalive_safety_margin syn keyword ngxDirectiveThirdParty contained nchan_redis_load_scripts_unconditionally syn keyword ngxDirectiveThirdParty contained nchan_redis_namespace syn keyword ngxDirectiveThirdParty contained nchan_redis_node_connect_timeout @@ -1173,6 +1137,9 @@ syn keyword ngxDirectiveThirdParty contained nchan_redis_tls_server_name syn keyword ngxDirectiveThirdParty contained nchan_redis_tls_trusted_certificate syn keyword ngxDirectiveThirdParty contained nchan_redis_tls_trusted_certificate_path syn keyword ngxDirectiveThirdParty contained nchan_redis_tls_verify_certificate +syn keyword ngxDirectiveThirdParty contained nchan_redis_upstream_stats +syn keyword ngxDirectiveThirdParty contained nchan_redis_upstream_stats_disconnected_timeout +syn keyword ngxDirectiveThirdParty contained nchan_redis_upstream_stats_enabled syn keyword ngxDirectiveThirdParty contained nchan_redis_url syn keyword ngxDirectiveThirdParty contained nchan_redis_username syn keyword ngxDirectiveThirdParty contained nchan_redis_wait_after_connecting @@ -1323,6 +1290,7 @@ syn keyword ngxDirectiveThirdParty contained upload_progress_jsonp_parameter syn keyword ngxDirectiveThirdParty contained upload_progress_template " https://github.com/yaoweibin/nginx_upstream_check_module +syn keyword ngxDirectiveThirdParty contained check syn keyword ngxDirectiveThirdParty contained check_fastcgi_param syn keyword ngxDirectiveThirdParty contained check_http_expect_alive syn keyword ngxDirectiveThirdParty contained check_http_send @@ -1335,6 +1303,7 @@ syn keyword ngxDirectiveThirdParty contained fair syn keyword ngxDirectiveThirdParty contained upstream_fair_shm_size " https://github.com/ayty-adrianomartins/nginx-sticky-module-ng +syn keyword ngxDirectiveThirdParty contained sticky_hide_cookie syn keyword ngxDirectiveThirdParty contained sticky_no_fallback " https://github.com/Novetta/nginx-video-thumbextractor-module @@ -1421,6 +1390,8 @@ syn keyword ngxDirectiveThirdParty contained lua_socket_pool_size syn keyword ngxDirectiveThirdParty contained lua_socket_read_timeout syn keyword ngxDirectiveThirdParty contained lua_socket_send_lowat syn keyword ngxDirectiveThirdParty contained lua_socket_send_timeout +syn keyword ngxDirectiveThirdParty contained lua_ssl_certificate +syn keyword ngxDirectiveThirdParty contained lua_ssl_certificate_key syn keyword ngxDirectiveThirdParty contained lua_ssl_ciphers syn keyword ngxDirectiveThirdParty contained lua_ssl_conf_command syn keyword ngxDirectiveThirdParty contained lua_ssl_crl @@ -1834,16 +1805,6 @@ syn keyword ngxDirectiveThirdParty contained slowfs_cache_purge syn keyword ngxDirectiveThirdParty contained slowfs_cache_valid syn keyword ngxDirectiveThirdParty contained slowfs_temp_path -" https://github.com/kawakibi/ngx_small_light -syn keyword ngxDirectiveThirdParty contained small_light -syn keyword ngxDirectiveThirdParty contained small_light_buffer -syn keyword ngxDirectiveThirdParty contained small_light_getparam_mode -syn keyword ngxDirectiveThirdParty contained small_light_imlib2_temp_dir -syn keyword ngxDirectiveThirdParty contained small_light_material_dir -syn keyword ngxDirectiveThirdParty contained small_light_pattern_define -syn keyword ngxDirectiveThirdParty contained small_light_radius_max -syn keyword ngxDirectiveThirdParty contained small_light_sigma_max - " https://github.com/openresty/srcache-nginx-module syn keyword ngxDirectiveThirdParty contained srcache_buffer syn keyword ngxDirectiveThirdParty contained srcache_default_expire @@ -1980,6 +1941,14 @@ syn keyword ngxDirectiveThirdParty contained websockify_pass syn keyword ngxDirectiveThirdParty contained websockify_read_timeout syn keyword ngxDirectiveThirdParty contained websockify_send_timeout +" https://github.com/vozlt/nginx-module-sts +syn keyword ngxDirectiveThirdParty contained stream_server_traffic_status +syn keyword ngxDirectiveThirdParty contained stream_server_traffic_status_average_method +syn keyword ngxDirectiveThirdParty contained stream_server_traffic_status_display +syn keyword ngxDirectiveThirdParty contained stream_server_traffic_status_display_format +syn keyword ngxDirectiveThirdParty contained stream_server_traffic_status_display_jsonp +syn keyword ngxDirectiveThirdParty contained stream_server_traffic_status_zone + " highlight hi def link ngxComment Comment diff --git a/src/deps/src/nginx/docs/text/LICENSE b/src/deps/src/nginx/docs/text/LICENSE index fdedcb746..985470ef9 100644 --- a/src/deps/src/nginx/docs/text/LICENSE +++ b/src/deps/src/nginx/docs/text/LICENSE @@ -1,6 +1,6 @@ /* * Copyright (C) 2002-2021 Igor Sysoev - * Copyright (C) 2011-2022 Nginx, Inc. + * Copyright (C) 2011-2024 Nginx, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/src/deps/src/nginx/docs/xml/nginx/changes.xml b/src/deps/src/nginx/docs/xml/nginx/changes.xml index cb5e1d92e..99c3cf272 100644 --- a/src/deps/src/nginx/docs/xml/nginx/changes.xml +++ b/src/deps/src/nginx/docs/xml/nginx/changes.xml @@ -5,14 +5,364 @@ - + -Стабильная ветка 1.24.x. +Стабильная ветка 1.26.x. -1.24.x stable branch. +1.26.x stable branch. + + + + + + + + + + +виртуальные сервера в модуле stream. + + +virtual servers in the stream module. + + + + + +модуль ngx_stream_pass_module. + + +the ngx_stream_pass_module. + + + + + +параметры deferred, accept_filter и setfib директивы listen в модуле stream. + + +the "deferred", "accept_filter", and "setfib" parameters of the "listen" +directive in the stream module. + + + + + +определение размера строки кеша процессора для некоторых архитектур.
+Спасибо Piotr Sikora. +
+ +cache line size detection for some architectures.
+Thanks to Piotr Sikora. +
+
+ + + +поддержка Homebrew на Apple Silicon.
+Спасибо Piotr Sikora. +
+ +support for Homebrew on Apple Silicon.
+Thanks to Piotr Sikora. +
+
+ + + +улучшения и исправления кросс-компиляции для Windows.
+Спасибо Piotr Sikora. +
+ +Windows cross-compilation bugfixes and improvements.
+Thanks to Piotr Sikora. +
+
+ + + +неожиданное закрытие соединения при использовании 0-RTT в QUIC.
+Спасибо Владимиру Хомутову. +
+ +unexpected connection closure while using 0-RTT in QUIC.
+Thanks to Vladimir Khomutov. +
+
+ +
+ + + + + + +при использовании HTTP/3 в рабочем процессе мог произойти segmentation fault +во время обработки специально созданной QUIC-сессии +(CVE-2024-24989, CVE-2024-24990). + + +when using HTTP/3 a segmentation fault might occur in a worker process +while processing a specially crafted QUIC session +(CVE-2024-24989, CVE-2024-24990). + + + + + +соединения с незавершенными AIO-операциями могли закрываться преждевременно +во время плавного завершения старых рабочих процессов. + + +connections with pending AIO operations might be closed prematurely +during graceful shutdown of old worker processes. + + + + + +теперь nginx не пишет в лог сообщения об утечке сокетов, +если во время плавного завершения старых рабочих процессов +было запрошено быстрое завершение. + + +socket leak alerts no longer logged when fast shutdown +was requested after graceful shutdown of old worker processes. + + + + + +при использовании AIO в подзапросе могла происходить +ошибка на сокете, утечка сокетов, +либо segmentation fault в рабочем процессе (при SSL-проксировании). + + +a socket descriptor error, a socket leak, +or a segmentation fault in a worker process (for SSL proxying) +might occur if AIO was used in a subrequest. + + + + + +в рабочем процессе мог произойти segmentation fault, +если использовалось SSL-проксирование и директива image_filter, +а ошибки с кодом 415 перенаправлялись с помощью директивы error_page. + + +a segmentation fault might occur in a worker process +if SSL proxying was used along with the "image_filter" directive +and errors with code 415 were redirected with the "error_page" directive. + + + + + +Исправления и улучшения в HTTP/3. + + +Bugfixes and improvements in HTTP/3. + + + + + + + + + + +улучшено детектирование некорректного поведения клиентов +при использовании HTTP/2. + + +improved detection of misbehaving clients +when using HTTP/2. + + + + + +уменьшение времени запуска +при использовании большого количества location'ов.
+Спасибо Yusuke Nojima. +
+ +startup speedup +when using a large number of locations.
+Thanks to Yusuke Nojima. +
+
+ + + +при использовании HTTP/2 без SSL +в рабочем процессе мог произойти segmentation fault; +ошибка появилась в 1.25.1. + + +a segmentation fault might occur in a worker process +when using HTTP/2 without SSL; +the bug had appeared in 1.25.1. + + + + + +строка "Status" в заголовке ответа бэкенда с пустой поясняющей фразой +обрабатывалась некорректно. + + +the "Status" backend response header line with an empty reason phrase +was handled incorrectly. + + + + + +утечки памяти во время переконфигурации +при использовании библиотеки PCRE2.
+Спасибо ZhenZhong Wu. +
+ +memory leak during reconfiguration +when using the PCRE2 library.
+Thanks to ZhenZhong Wu. +
+
+ + + +Исправления и улучшения в HTTP/3. + + +Bugfixes and improvements in HTTP/3. + + + +
+ + + + + + +path MTU discovery при использовании HTTP/3. + + +path MTU discovery when using HTTP/3. + + + + + +поддержка шифра TLS_AES_128_CCM_SHA256 при использовании HTTP/3. + + +TLS_AES_128_CCM_SHA256 cipher suite support when using HTTP/3. + + + + + +теперь при загрузке конфигурации OpenSSL +nginx использует appname "nginx". + + +now nginx uses appname "nginx" +when loading OpenSSL configuration. + + + + + +теперь nginx не пытается загружать конфигурацию OpenSSL, +если для сборки OpenSSL использовался параметр --with-openssl +и переменная окружения OPENSSL_CONF не установлена. + + +now nginx does not try to load OpenSSL configuration +if the --with-openssl option was used to built OpenSSL +and the OPENSSL_CONF environment variable is not set. + + + + + +в переменной $body_bytes_sent при использовании HTTP/3. + + +in the $body_bytes_sent variable when using HTTP/3. + + + + + +в HTTP/3. + + +in HTTP/3. + + + + + + + + + + +директива http2, позволяющая включать HTTP/2 в отдельных блоках server; +параметр http2 директивы listen объявлен устаревшим. + + +the "http2" directive, which enables HTTP/2 on a per-server basis; +the "http2" parameter of the "listen" directive is now deprecated. + + + + + +поддержка HTTP/2 server push упразднена. + + +HTTP/2 server push support has been removed. + + + + + +устаревшая директива ssl больше не поддерживается. + + +the deprecated "ssl" directive is not supported anymore. + + + + + +в HTTP/3 при использовании OpenSSL. + + +in HTTP/3 when using OpenSSL. + + + + + + + + + + +экспериментальная поддержка HTTP/3. + + +experimental HTTP/3 support. diff --git a/src/deps/src/nginx/misc/GNUmakefile b/src/deps/src/nginx/misc/GNUmakefile index a5c4a1953..dca0e2637 100644 --- a/src/deps/src/nginx/misc/GNUmakefile +++ b/src/deps/src/nginx/misc/GNUmakefile @@ -6,8 +6,8 @@ TEMP = tmp CC = cl OBJS = objs.msvc8 -OPENSSL = openssl-1.1.1t -ZLIB = zlib-1.2.13 +OPENSSL = openssl-3.0.13 +ZLIB = zlib-1.3.1 PCRE = pcre2-10.39 @@ -75,6 +75,8 @@ win32: --with-http_slice_module \ --with-mail \ --with-stream \ + --with-stream_realip_module \ + --with-stream_ssl_preread_module \ --with-openssl=$(OBJS)/lib/$(OPENSSL) \ --with-openssl-opt="no-asm no-tests -D_WIN32_WINNT=0x0501" \ --with-http_ssl_module \ @@ -103,7 +105,7 @@ zip: export $(MAKE) -f docs/GNUmakefile changes mv $(TEMP)/$(NGINX)/CHANGES* $(TEMP)/$(NGINX)/docs/ - cp -p $(OBJS)/lib/$(OPENSSL)/LICENSE \ + cp -p $(OBJS)/lib/$(OPENSSL)/LICENSE.txt \ $(TEMP)/$(NGINX)/docs/OpenSSL.LICENSE cp -p $(OBJS)/lib/$(PCRE)/LICENCE \ diff --git a/src/deps/src/nginx/src/core/nginx.c b/src/deps/src/nginx/src/core/nginx.c index 48a20e9fd..0deb27b7f 100644 --- a/src/deps/src/nginx/src/core/nginx.c +++ b/src/deps/src/nginx/src/core/nginx.c @@ -13,6 +13,7 @@ static void ngx_show_version_info(void); static ngx_int_t ngx_add_inherited_sockets(ngx_cycle_t *cycle); static void ngx_cleanup_environment(void *data); +static void ngx_cleanup_environment_variable(void *data); static ngx_int_t ngx_get_options(int argc, char *const *argv); static ngx_int_t ngx_process_options(ngx_cycle_t *cycle); static ngx_int_t ngx_save_argv(ngx_cycle_t *cycle, int argc, char *const *argv); @@ -518,7 +519,8 @@ ngx_add_inherited_sockets(ngx_cycle_t *cycle) char ** ngx_set_environment(ngx_cycle_t *cycle, ngx_uint_t *last) { - char **p, **env; + char **p, **env, *str; + size_t len; ngx_str_t *var; ngx_uint_t i, n; ngx_core_conf_t *ccf; @@ -600,7 +602,31 @@ tz_found: for (i = 0; i < ccf->env.nelts; i++) { if (var[i].data[var[i].len] == '=') { - env[n++] = (char *) var[i].data; + + if (last) { + env[n++] = (char *) var[i].data; + continue; + } + + cln = ngx_pool_cleanup_add(cycle->pool, 0); + if (cln == NULL) { + return NULL; + } + + len = ngx_strlen(var[i].data) + 1; + + str = ngx_alloc(len, cycle->log); + if (str == NULL) { + return NULL; + } + + ngx_memcpy(str, var[i].data, len); + + cln->handler = ngx_cleanup_environment_variable; + cln->data = str; + + env[n++] = str; + continue; } @@ -645,6 +671,29 @@ ngx_cleanup_environment(void *data) } +static void +ngx_cleanup_environment_variable(void *data) +{ + char *var = data; + + char **p; + + for (p = environ; *p; p++) { + + /* + * if an environment variable is still used, as it happens on exit, + * the only option is to leak it + */ + + if (*p == var) { + return; + } + } + + ngx_free(var); +} + + ngx_pid_t ngx_exec_new_binary(ngx_cycle_t *cycle, char *const *argv) { @@ -680,6 +729,9 @@ ngx_exec_new_binary(ngx_cycle_t *cycle, char *const *argv) ls = cycle->listening.elts; for (i = 0; i < cycle->listening.nelts; i++) { + if (ls[i].ignore) { + continue; + } p = ngx_sprintf(p, "%ud;", ls[i].fd); } diff --git a/src/deps/src/nginx/src/core/nginx.h b/src/deps/src/nginx/src/core/nginx.h index 832baaae0..7ef5cfb13 100644 --- a/src/deps/src/nginx/src/core/nginx.h +++ b/src/deps/src/nginx/src/core/nginx.h @@ -9,8 +9,8 @@ #define _NGINX_H_INCLUDED_ -#define nginx_version 1024000 -#define NGINX_VERSION "1.24.0" +#define nginx_version 1026000 +#define NGINX_VERSION "1.26.0" #define NGINX_VER "nginx/" NGINX_VERSION #ifdef NGX_BUILD diff --git a/src/deps/src/nginx/src/core/ngx_bpf.c b/src/deps/src/nginx/src/core/ngx_bpf.c new file mode 100644 index 000000000..363a02c7d --- /dev/null +++ b/src/deps/src/nginx/src/core/ngx_bpf.c @@ -0,0 +1,143 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + +#define NGX_BPF_LOGBUF_SIZE (16 * 1024) + + +static ngx_inline int +ngx_bpf(enum bpf_cmd cmd, union bpf_attr *attr, unsigned int size) +{ + return syscall(__NR_bpf, cmd, attr, size); +} + + +void +ngx_bpf_program_link(ngx_bpf_program_t *program, const char *symbol, int fd) +{ + ngx_uint_t i; + ngx_bpf_reloc_t *rl; + + rl = program->relocs; + + for (i = 0; i < program->nrelocs; i++) { + if (ngx_strcmp(rl[i].name, symbol) == 0) { + program->ins[rl[i].offset].src_reg = 1; + program->ins[rl[i].offset].imm = fd; + } + } +} + + +int +ngx_bpf_load_program(ngx_log_t *log, ngx_bpf_program_t *program) +{ + int fd; + union bpf_attr attr; +#if (NGX_DEBUG) + char buf[NGX_BPF_LOGBUF_SIZE]; +#endif + + ngx_memzero(&attr, sizeof(union bpf_attr)); + + attr.license = (uintptr_t) program->license; + attr.prog_type = program->type; + attr.insns = (uintptr_t) program->ins; + attr.insn_cnt = program->nins; + +#if (NGX_DEBUG) + /* for verifier errors */ + attr.log_buf = (uintptr_t) buf; + attr.log_size = NGX_BPF_LOGBUF_SIZE; + attr.log_level = 1; +#endif + + fd = ngx_bpf(BPF_PROG_LOAD, &attr, sizeof(attr)); + if (fd < 0) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + "failed to load BPF program"); + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, + "bpf verifier: %s", buf); + + return -1; + } + + return fd; +} + + +int +ngx_bpf_map_create(ngx_log_t *log, enum bpf_map_type type, int key_size, + int value_size, int max_entries, uint32_t map_flags) +{ + int fd; + union bpf_attr attr; + + ngx_memzero(&attr, sizeof(union bpf_attr)); + + attr.map_type = type; + attr.key_size = key_size; + attr.value_size = value_size; + attr.max_entries = max_entries; + attr.map_flags = map_flags; + + fd = ngx_bpf(BPF_MAP_CREATE, &attr, sizeof(attr)); + if (fd < 0) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + "failed to create BPF map"); + return NGX_ERROR; + } + + return fd; +} + + +int +ngx_bpf_map_update(int fd, const void *key, const void *value, uint64_t flags) +{ + union bpf_attr attr; + + ngx_memzero(&attr, sizeof(union bpf_attr)); + + attr.map_fd = fd; + attr.key = (uintptr_t) key; + attr.value = (uintptr_t) value; + attr.flags = flags; + + return ngx_bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)); +} + + +int +ngx_bpf_map_delete(int fd, const void *key) +{ + union bpf_attr attr; + + ngx_memzero(&attr, sizeof(union bpf_attr)); + + attr.map_fd = fd; + attr.key = (uintptr_t) key; + + return ngx_bpf(BPF_MAP_DELETE_ELEM, &attr, sizeof(attr)); +} + + +int +ngx_bpf_map_lookup(int fd, const void *key, void *value) +{ + union bpf_attr attr; + + ngx_memzero(&attr, sizeof(union bpf_attr)); + + attr.map_fd = fd; + attr.key = (uintptr_t) key; + attr.value = (uintptr_t) value; + + return ngx_bpf(BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr)); +} diff --git a/src/deps/src/nginx/src/core/ngx_bpf.h b/src/deps/src/nginx/src/core/ngx_bpf.h new file mode 100644 index 000000000..f62a36e11 --- /dev/null +++ b/src/deps/src/nginx/src/core/ngx_bpf.h @@ -0,0 +1,43 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_BPF_H_INCLUDED_ +#define _NGX_BPF_H_INCLUDED_ + + +#include +#include + +#include + + +typedef struct { + char *name; + int offset; +} ngx_bpf_reloc_t; + +typedef struct { + char *license; + enum bpf_prog_type type; + struct bpf_insn *ins; + size_t nins; + ngx_bpf_reloc_t *relocs; + size_t nrelocs; +} ngx_bpf_program_t; + + +void ngx_bpf_program_link(ngx_bpf_program_t *program, const char *symbol, + int fd); +int ngx_bpf_load_program(ngx_log_t *log, ngx_bpf_program_t *program); + +int ngx_bpf_map_create(ngx_log_t *log, enum bpf_map_type type, int key_size, + int value_size, int max_entries, uint32_t map_flags); +int ngx_bpf_map_update(int fd, const void *key, const void *value, + uint64_t flags); +int ngx_bpf_map_delete(int fd, const void *key); +int ngx_bpf_map_lookup(int fd, const void *key, void *value); + +#endif /* _NGX_BPF_H_INCLUDED_ */ diff --git a/src/deps/src/nginx/src/core/ngx_connection.c b/src/deps/src/nginx/src/core/ngx_connection.c index 36823451a..75809d9ad 100644 --- a/src/deps/src/nginx/src/core/ngx_connection.c +++ b/src/deps/src/nginx/src/core/ngx_connection.c @@ -1013,6 +1013,78 @@ ngx_configure_listening_sockets(ngx_cycle_t *cycle) } } +#endif + +#if (NGX_HAVE_IP_MTU_DISCOVER) + + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET) { + value = IP_PMTUDISC_DO; + + if (setsockopt(ls[i].fd, IPPROTO_IP, IP_MTU_DISCOVER, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "setsockopt(IP_MTU_DISCOVER) " + "for %V failed, ignored", + &ls[i].addr_text); + } + } + +#elif (NGX_HAVE_IP_DONTFRAG) + + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET) { + value = 1; + + if (setsockopt(ls[i].fd, IPPROTO_IP, IP_DONTFRAG, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "setsockopt(IP_DONTFRAG) " + "for %V failed, ignored", + &ls[i].addr_text); + } + } + +#endif + +#if (NGX_HAVE_INET6) + +#if (NGX_HAVE_IPV6_MTU_DISCOVER) + + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET6) { + value = IPV6_PMTUDISC_DO; + + if (setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "setsockopt(IPV6_MTU_DISCOVER) " + "for %V failed, ignored", + &ls[i].addr_text); + } + } + +#elif (NGX_HAVE_IP_DONTFRAG) + + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET6) { + value = 1; + + if (setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_DONTFRAG, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "setsockopt(IPV6_DONTFRAG) " + "for %V failed, ignored", + &ls[i].addr_text); + } + } + +#endif + #endif } @@ -1037,6 +1109,12 @@ ngx_close_listening_sockets(ngx_cycle_t *cycle) ls = cycle->listening.elts; for (i = 0; i < cycle->listening.nelts; i++) { +#if (NGX_QUIC) + if (ls[i].quic) { + continue; + } +#endif + c = ls[i].connection; if (c) { @@ -1505,6 +1583,10 @@ ngx_connection_error(ngx_connection_t *c, ngx_err_t err, char *text) } #endif + if (err == NGX_EMSGSIZE && c->log_error == NGX_ERROR_IGNORE_EMSGSIZE) { + return 0; + } + if (err == 0 || err == NGX_ECONNRESET #if (NGX_WIN32) @@ -1522,6 +1604,7 @@ ngx_connection_error(ngx_connection_t *c, ngx_err_t err, char *text) { switch (c->log_error) { + case NGX_ERROR_IGNORE_EMSGSIZE: case NGX_ERROR_IGNORE_EINVAL: case NGX_ERROR_IGNORE_ECONNRESET: case NGX_ERROR_INFO: diff --git a/src/deps/src/nginx/src/core/ngx_connection.h b/src/deps/src/nginx/src/core/ngx_connection.h index 36e1be27c..84dd80442 100644 --- a/src/deps/src/nginx/src/core/ngx_connection.h +++ b/src/deps/src/nginx/src/core/ngx_connection.h @@ -73,6 +73,7 @@ struct ngx_listening_s { unsigned reuseport:1; unsigned add_reuseport:1; unsigned keepalive:2; + unsigned quic:1; unsigned deferred_accept:1; unsigned delete_deferred:1; @@ -96,7 +97,8 @@ typedef enum { NGX_ERROR_ERR, NGX_ERROR_INFO, NGX_ERROR_IGNORE_ECONNRESET, - NGX_ERROR_IGNORE_EINVAL + NGX_ERROR_IGNORE_EINVAL, + NGX_ERROR_IGNORE_EMSGSIZE } ngx_connection_log_error_e; @@ -147,6 +149,10 @@ struct ngx_connection_s { ngx_proxy_protocol_t *proxy_protocol; +#if (NGX_QUIC || NGX_COMPAT) + ngx_quic_stream_t *quic; +#endif + #if (NGX_SSL || NGX_COMPAT) ngx_ssl_connection_t *ssl; #endif diff --git a/src/deps/src/nginx/src/core/ngx_core.h b/src/deps/src/nginx/src/core/ngx_core.h index 7ecdca0cb..88db7dc98 100644 --- a/src/deps/src/nginx/src/core/ngx_core.h +++ b/src/deps/src/nginx/src/core/ngx_core.h @@ -27,6 +27,7 @@ typedef struct ngx_connection_s ngx_connection_t; typedef struct ngx_thread_task_s ngx_thread_task_t; typedef struct ngx_ssl_s ngx_ssl_t; typedef struct ngx_proxy_protocol_s ngx_proxy_protocol_t; +typedef struct ngx_quic_stream_s ngx_quic_stream_t; typedef struct ngx_ssl_connection_s ngx_ssl_connection_t; typedef struct ngx_udp_connection_s ngx_udp_connection_t; @@ -82,6 +83,9 @@ typedef void (*ngx_connection_handler_pt)(ngx_connection_t *c); #include #if (NGX_OPENSSL) #include +#if (NGX_QUIC) +#include +#endif #endif #include #include @@ -91,6 +95,9 @@ typedef void (*ngx_connection_handler_pt)(ngx_connection_t *c); #include #include #include +#if (NGX_HAVE_BPF) +#include +#endif #define LF (u_char) '\n' diff --git a/src/deps/src/nginx/src/core/ngx_inet.c b/src/deps/src/nginx/src/core/ngx_inet.c index 4228504ad..acb2ef48a 100644 --- a/src/deps/src/nginx/src/core/ngx_inet.c +++ b/src/deps/src/nginx/src/core/ngx_inet.c @@ -507,7 +507,7 @@ ngx_cidr_match(struct sockaddr *sa, ngx_array_t *cidrs) p = inaddr6->s6_addr; - inaddr = p[12] << 24; + inaddr = (in_addr_t) p[12] << 24; inaddr += p[13] << 16; inaddr += p[14] << 8; inaddr += p[15]; diff --git a/src/deps/src/nginx/src/core/ngx_module.h b/src/deps/src/nginx/src/core/ngx_module.h index 6fb455426..a415cd6d9 100644 --- a/src/deps/src/nginx/src/core/ngx_module.h +++ b/src/deps/src/nginx/src/core/ngx_module.h @@ -107,7 +107,12 @@ #endif #define NGX_MODULE_SIGNATURE_17 "0" + +#if (NGX_QUIC || NGX_COMPAT) +#define NGX_MODULE_SIGNATURE_18 "1" +#else #define NGX_MODULE_SIGNATURE_18 "0" +#endif #if (NGX_HAVE_OPENAT) #define NGX_MODULE_SIGNATURE_19 "1" diff --git a/src/deps/src/nginx/src/core/ngx_queue.c b/src/deps/src/nginx/src/core/ngx_queue.c index 3cacaf3a8..3d1d58988 100644 --- a/src/deps/src/nginx/src/core/ngx_queue.c +++ b/src/deps/src/nginx/src/core/ngx_queue.c @@ -9,6 +9,10 @@ #include +static void ngx_queue_merge(ngx_queue_t *queue, ngx_queue_t *tail, + ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *)); + + /* * find the middle queue element if the queue has odd number of elements * or the first element of the queue's second part otherwise @@ -45,13 +49,13 @@ ngx_queue_middle(ngx_queue_t *queue) } -/* the stable insertion sort */ +/* the stable merge sort */ void ngx_queue_sort(ngx_queue_t *queue, ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *)) { - ngx_queue_t *q, *prev, *next; + ngx_queue_t *q, tail; q = ngx_queue_head(queue); @@ -59,22 +63,44 @@ ngx_queue_sort(ngx_queue_t *queue, return; } - for (q = ngx_queue_next(q); q != ngx_queue_sentinel(queue); q = next) { + q = ngx_queue_middle(queue); - prev = ngx_queue_prev(q); - next = ngx_queue_next(q); + ngx_queue_split(queue, q, &tail); - ngx_queue_remove(q); + ngx_queue_sort(queue, cmp); + ngx_queue_sort(&tail, cmp); - do { - if (cmp(prev, q) <= 0) { - break; - } + ngx_queue_merge(queue, &tail, cmp); +} - prev = ngx_queue_prev(prev); - } while (prev != ngx_queue_sentinel(queue)); +static void +ngx_queue_merge(ngx_queue_t *queue, ngx_queue_t *tail, + ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *)) +{ + ngx_queue_t *q1, *q2; - ngx_queue_insert_after(prev, q); + q1 = ngx_queue_head(queue); + q2 = ngx_queue_head(tail); + + for ( ;; ) { + if (q1 == ngx_queue_sentinel(queue)) { + ngx_queue_add(queue, tail); + break; + } + + if (q2 == ngx_queue_sentinel(tail)) { + break; + } + + if (cmp(q1, q2) <= 0) { + q1 = ngx_queue_next(q1); + continue; + } + + ngx_queue_remove(q2); + ngx_queue_insert_before(q1, q2); + + q2 = ngx_queue_head(tail); } } diff --git a/src/deps/src/nginx/src/core/ngx_queue.h b/src/deps/src/nginx/src/core/ngx_queue.h index 038bf1211..0f82f173e 100644 --- a/src/deps/src/nginx/src/core/ngx_queue.h +++ b/src/deps/src/nginx/src/core/ngx_queue.h @@ -47,6 +47,9 @@ struct ngx_queue_s { (h)->prev = x +#define ngx_queue_insert_before ngx_queue_insert_tail + + #define ngx_queue_head(h) \ (h)->next diff --git a/src/deps/src/nginx/src/core/ngx_regex.c b/src/deps/src/nginx/src/core/ngx_regex.c index bebf3b6a8..5b13c5db3 100644 --- a/src/deps/src/nginx/src/core/ngx_regex.c +++ b/src/deps/src/nginx/src/core/ngx_regex.c @@ -600,6 +600,8 @@ ngx_regex_cleanup(void *data) * the new cycle, these will be re-allocated. */ + ngx_regex_malloc_init(NULL); + if (ngx_regex_compile_context) { pcre2_compile_context_free(ngx_regex_compile_context); ngx_regex_compile_context = NULL; @@ -611,6 +613,8 @@ ngx_regex_cleanup(void *data) ngx_regex_match_data_size = 0; } + ngx_regex_malloc_done(); + #endif } @@ -706,9 +710,6 @@ ngx_regex_module_init(ngx_cycle_t *cycle) ngx_regex_malloc_done(); ngx_regex_studies = NULL; -#if (NGX_PCRE2) - ngx_regex_compile_context = NULL; -#endif return NGX_OK; } @@ -732,14 +733,14 @@ ngx_regex_create_conf(ngx_cycle_t *cycle) return NULL; } - cln->handler = ngx_regex_cleanup; - cln->data = rcf; - rcf->studies = ngx_list_create(cycle->pool, 8, sizeof(ngx_regex_elt_t)); if (rcf->studies == NULL) { return NULL; } + cln->handler = ngx_regex_cleanup; + cln->data = rcf; + ngx_regex_studies = rcf->studies; return rcf; diff --git a/src/deps/src/nginx/src/event/ngx_event.c b/src/deps/src/nginx/src/event/ngx_event.c index d81547af4..ef525d93b 100644 --- a/src/deps/src/nginx/src/event/ngx_event.c +++ b/src/deps/src/nginx/src/event/ngx_event.c @@ -267,6 +267,18 @@ ngx_process_events_and_timers(ngx_cycle_t *cycle) ngx_int_t ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags) { +#if (NGX_QUIC) + + ngx_connection_t *c; + + c = rev->data; + + if (c->quic) { + return NGX_OK; + } + +#endif + if (ngx_event_flags & NGX_USE_CLEAR_EVENT) { /* kqueue, epoll */ @@ -337,9 +349,15 @@ ngx_handle_write_event(ngx_event_t *wev, size_t lowat) { ngx_connection_t *c; - if (lowat) { - c = wev->data; + c = wev->data; +#if (NGX_QUIC) + if (c->quic) { + return NGX_OK; + } +#endif + + if (lowat) { if (ngx_send_lowat(c, lowat) == NGX_ERROR) { return NGX_ERROR; } @@ -873,8 +891,16 @@ ngx_event_process_init(ngx_cycle_t *cycle) #else - rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept - : ngx_event_recvmsg; + if (c->type == SOCK_STREAM) { + rev->handler = ngx_event_accept; + +#if (NGX_QUIC) + } else if (ls[i].quic) { + rev->handler = ngx_quic_recvmsg; +#endif + } else { + rev->handler = ngx_event_recvmsg; + } #if (NGX_HAVE_REUSEPORT) diff --git a/src/deps/src/nginx/src/event/ngx_event_openssl.c b/src/deps/src/nginx/src/event/ngx_event_openssl.c index 104e8daf7..89f277fe5 100644 --- a/src/deps/src/nginx/src/event/ngx_event_openssl.c +++ b/src/deps/src/nginx/src/event/ngx_event_openssl.c @@ -33,9 +33,6 @@ static int ngx_ssl_new_client_session(ngx_ssl_conn_t *ssl_conn, #ifdef SSL_READ_EARLY_DATA_SUCCESS static ngx_int_t ngx_ssl_try_early_data(ngx_connection_t *c); #endif -#if (NGX_DEBUG) -static void ngx_ssl_handshake_log(ngx_connection_t *c); -#endif static void ngx_ssl_handshake_handler(ngx_event_t *ev); #ifdef SSL_READ_EARLY_DATA_SUCCESS static ssize_t ngx_ssl_recv_early(ngx_connection_t *c, u_char *buf, @@ -143,13 +140,42 @@ int ngx_ssl_stapling_index; ngx_int_t ngx_ssl_init(ngx_log_t *log) { -#if OPENSSL_VERSION_NUMBER >= 0x10100003L +#if (OPENSSL_INIT_LOAD_CONFIG && !defined LIBRESSL_VERSION_NUMBER) - if (OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, NULL) == 0) { + uint64_t opts; + OPENSSL_INIT_SETTINGS *init; + + opts = OPENSSL_INIT_LOAD_CONFIG; + +#if (NGX_OPENSSL_NO_CONFIG) + + if (getenv("OPENSSL_CONF") == NULL) { + opts = OPENSSL_INIT_NO_LOAD_CONFIG; + } + +#endif + + init = OPENSSL_INIT_new(); + if (init == NULL) { + ngx_ssl_error(NGX_LOG_ALERT, log, 0, "OPENSSL_INIT_new() failed"); + return NGX_ERROR; + } + +#ifndef OPENSSL_NO_STDIO + if (OPENSSL_INIT_set_config_appname(init, "nginx") == 0) { + ngx_ssl_error(NGX_LOG_ALERT, log, 0, + "OPENSSL_INIT_set_config_appname() failed"); + return NGX_ERROR; + } +#endif + + if (OPENSSL_init_ssl(opts, init) == 0) { ngx_ssl_error(NGX_LOG_ALERT, log, 0, "OPENSSL_init_ssl() failed"); return NGX_ERROR; } + OPENSSL_INIT_free(init); + /* * OPENSSL_init_ssl() may leave errors in the error queue * while returning success @@ -159,7 +185,15 @@ ngx_ssl_init(ngx_log_t *log) #else - OPENSSL_config(NULL); +#if (NGX_OPENSSL_NO_CONFIG) + + if (getenv("OPENSSL_CONF") == NULL) { + OPENSSL_no_config(); + } + +#endif + + OPENSSL_config("nginx"); SSL_library_init(); SSL_load_error_strings(); @@ -1071,7 +1105,8 @@ ngx_ssl_info_callback(const ngx_ssl_conn_t *ssl_conn, int where, int ret) BIO *rbio, *wbio; ngx_connection_t *c; -#ifndef SSL_OP_NO_RENEGOTIATION +#if (!defined SSL_OP_NO_RENEGOTIATION \ + && !defined SSL_OP_NO_CLIENT_RENEGOTIATION) if ((where & SSL_CB_HANDSHAKE_START) && SSL_is_server((ngx_ssl_conn_t *) ssl_conn)) @@ -1804,9 +1839,10 @@ ngx_ssl_handshake(ngx_connection_t *c) c->read->ready = 1; c->write->ready = 1; -#ifndef SSL_OP_NO_RENEGOTIATION -#if OPENSSL_VERSION_NUMBER < 0x10100000L -#ifdef SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS +#if (!defined SSL_OP_NO_RENEGOTIATION \ + && !defined SSL_OP_NO_CLIENT_RENEGOTIATION \ + && defined SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS \ + && OPENSSL_VERSION_NUMBER < 0x10100000L) /* initial handshake done, disable renegotiation (CVE-2009-3555) */ if (c->ssl->connection->s3 && SSL_is_server(c->ssl->connection)) { @@ -1814,8 +1850,6 @@ ngx_ssl_handshake(ngx_connection_t *c) } #endif -#endif -#endif #if (defined BIO_get_ktls_send && !NGX_WIN32) @@ -2052,7 +2086,7 @@ ngx_ssl_try_early_data(ngx_connection_t *c) #if (NGX_DEBUG) -static void +void ngx_ssl_handshake_log(ngx_connection_t *c) { char buf[129], *s, *d; @@ -2449,7 +2483,8 @@ ngx_ssl_handle_recv(ngx_connection_t *c, int n) int sslerr; ngx_err_t err; -#ifndef SSL_OP_NO_RENEGOTIATION +#if (!defined SSL_OP_NO_RENEGOTIATION \ + && !defined SSL_OP_NO_CLIENT_RENEGOTIATION) if (c->ssl->renegotiation) { /* @@ -3202,6 +3237,13 @@ ngx_ssl_shutdown(ngx_connection_t *c) ngx_err_t err; ngx_uint_t tries; +#if (NGX_QUIC) + if (c->quic) { + /* QUIC streams inherit SSL object */ + return NGX_OK; + } +#endif + rc = NGX_OK; ngx_ssl_ocsp_cleanup(c); @@ -5145,6 +5187,9 @@ ngx_ssl_get_curves(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) } curves = ngx_palloc(pool, n * sizeof(int)); + if (curves == NULL) { + return NGX_ERROR; + } n = SSL_get1_curves(c->ssl->connection, curves); len = 0; diff --git a/src/deps/src/nginx/src/event/ngx_event_openssl.h b/src/deps/src/nginx/src/event/ngx_event_openssl.h index 860ea26dd..ebb2c35bf 100644 --- a/src/deps/src/nginx/src/event/ngx_event_openssl.h +++ b/src/deps/src/nginx/src/event/ngx_event_openssl.h @@ -24,6 +24,14 @@ #include #endif #include +#if (NGX_QUIC) +#ifdef OPENSSL_IS_BORINGSSL +#include +#include +#else +#include +#endif +#endif #include #ifndef OPENSSL_NO_OCSP #include @@ -37,7 +45,7 @@ #if (defined LIBRESSL_VERSION_NUMBER && OPENSSL_VERSION_NUMBER == 0x20000000L) #undef OPENSSL_VERSION_NUMBER -#if (LIBRESSL_VERSION_NUMBER >= 0x2080000fL) +#if (LIBRESSL_VERSION_NUMBER >= 0x3050000fL) #define OPENSSL_VERSION_NUMBER 0x1010000fL #else #define OPENSSL_VERSION_NUMBER 0x1000107fL @@ -302,6 +310,9 @@ ngx_int_t ngx_ssl_get_client_v_remain(ngx_connection_t *c, ngx_pool_t *pool, ngx_int_t ngx_ssl_handshake(ngx_connection_t *c); +#if (NGX_DEBUG) +void ngx_ssl_handshake_log(ngx_connection_t *c); +#endif ssize_t ngx_ssl_recv(ngx_connection_t *c, u_char *buf, size_t size); ssize_t ngx_ssl_write(ngx_connection_t *c, u_char *data, size_t size); ssize_t ngx_ssl_recv_chain(ngx_connection_t *c, ngx_chain_t *cl, off_t limit); diff --git a/src/deps/src/nginx/src/event/ngx_event_openssl_stapling.c b/src/deps/src/nginx/src/event/ngx_event_openssl_stapling.c index e3fa8c4e2..e9bb8354e 100644 --- a/src/deps/src/nginx/src/event/ngx_event_openssl_stapling.c +++ b/src/deps/src/nginx/src/event/ngx_event_openssl_stapling.c @@ -893,7 +893,7 @@ ngx_ssl_ocsp_validate(ngx_connection_t *c) ocsp->cert_status = V_OCSP_CERTSTATUS_GOOD; ocsp->conf = ocf; -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER) +#if OPENSSL_VERSION_NUMBER >= 0x10100000L ocsp->certs = SSL_get0_verified_chain(c->ssl->connection); diff --git a/src/deps/src/nginx/src/event/ngx_event_pipe.c b/src/deps/src/nginx/src/event/ngx_event_pipe.c index 54412e130..d774903ec 100644 --- a/src/deps/src/nginx/src/event/ngx_event_pipe.c +++ b/src/deps/src/nginx/src/event/ngx_event_pipe.c @@ -57,7 +57,9 @@ ngx_event_pipe(ngx_event_pipe_t *p, ngx_int_t do_write) do_write = 1; } - if (p->upstream->fd != (ngx_socket_t) -1) { + if (p->upstream + && p->upstream->fd != (ngx_socket_t) -1) + { rev = p->upstream->read; flags = (rev->eof || rev->error) ? NGX_CLOSE_EVENT : 0; @@ -108,7 +110,9 @@ ngx_event_pipe_read_upstream(ngx_event_pipe_t *p) ngx_msec_t delay; ngx_chain_t *chain, *cl, *ln; - if (p->upstream_eof || p->upstream_error || p->upstream_done) { + if (p->upstream_eof || p->upstream_error || p->upstream_done + || p->upstream == NULL) + { return NGX_OK; } diff --git a/src/deps/src/nginx/src/event/ngx_event_udp.c b/src/deps/src/nginx/src/event/ngx_event_udp.c index d7a94c76e..43fa621b0 100644 --- a/src/deps/src/nginx/src/event/ngx_event_udp.c +++ b/src/deps/src/nginx/src/event/ngx_event_udp.c @@ -12,13 +12,6 @@ #if !(NGX_WIN32) -struct ngx_udp_connection_s { - ngx_rbtree_node_t node; - ngx_connection_t *connection; - ngx_buf_t *buffer; -}; - - static void ngx_close_accepted_udp_connection(ngx_connection_t *c); static ssize_t ngx_udp_shared_recv(ngx_connection_t *c, u_char *buf, size_t size); @@ -424,8 +417,8 @@ ngx_udp_rbtree_insert_value(ngx_rbtree_node_t *temp, udpt = (ngx_udp_connection_t *) temp; ct = udpt->connection; - rc = ngx_cmp_sockaddr(c->sockaddr, c->socklen, - ct->sockaddr, ct->socklen, 1); + rc = ngx_memn2cmp(udp->key.data, udpt->key.data, + udp->key.len, udpt->key.len); if (rc == 0 && c->listening->wildcard) { rc = ngx_cmp_sockaddr(c->local_sockaddr, c->local_socklen, @@ -478,6 +471,8 @@ ngx_insert_udp_connection(ngx_connection_t *c) ngx_crc32_final(hash); udp->node.key = hash; + udp->key.data = (u_char *) c->sockaddr; + udp->key.len = c->socklen; cln = ngx_pool_cleanup_add(c->pool, 0); if (cln == NULL) { diff --git a/src/deps/src/nginx/src/event/ngx_event_udp.h b/src/deps/src/nginx/src/event/ngx_event_udp.h index 51ca665be..e5ddf1b31 100644 --- a/src/deps/src/nginx/src/event/ngx_event_udp.h +++ b/src/deps/src/nginx/src/event/ngx_event_udp.h @@ -23,6 +23,14 @@ #endif +struct ngx_udp_connection_s { + ngx_rbtree_node_t node; + ngx_connection_t *connection; + ngx_buf_t *buffer; + ngx_str_t key; +}; + + #if (NGX_HAVE_ADDRINFO_CMSG) typedef union { diff --git a/src/deps/src/nginx/src/event/quic/bpf/bpfgen.sh b/src/deps/src/nginx/src/event/quic/bpf/bpfgen.sh new file mode 100644 index 000000000..78cbdac4d --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/bpf/bpfgen.sh @@ -0,0 +1,113 @@ +#!/bin/bash + +export LANG=C + +set -e + +if [ $# -lt 1 ]; then + echo "Usage: PROGNAME=foo LICENSE=bar $0 " + exit 1 +fi + + +self=$0 +filename=$1 +funcname=$PROGNAME + +generate_head() +{ + cat << END +/* AUTO-GENERATED, DO NOT EDIT. */ + +#include +#include + +#include "ngx_bpf.h" + + +END +} + +generate_tail() +{ + cat << END + +ngx_bpf_program_t $PROGNAME = { + .relocs = bpf_reloc_prog_$funcname, + .nrelocs = sizeof(bpf_reloc_prog_$funcname) + / sizeof(bpf_reloc_prog_$funcname[0]), + .ins = bpf_insn_prog_$funcname, + .nins = sizeof(bpf_insn_prog_$funcname) + / sizeof(bpf_insn_prog_$funcname[0]), + .license = "$LICENSE", + .type = BPF_PROG_TYPE_SK_REUSEPORT, +}; + +END +} + +process_relocations() +{ + echo "static ngx_bpf_reloc_t bpf_reloc_prog_$funcname[] = {" + + objdump -r $filename | awk '{ + + if (enabled && $NF > 0) { + off = strtonum(sprintf("0x%s", $1)); + name = $3; + + printf(" { \"%s\", %d },\n", name, off/8); + } + + if ($1 == "OFFSET") { + enabled=1; + } +}' + echo "};" + echo +} + +process_section() +{ + echo "static struct bpf_insn bpf_insn_prog_$funcname[] = {" + echo " /* opcode dst src offset imm */" + + section_info=$(objdump -h $filename --section=$funcname | grep "1 $funcname") + + # dd doesn't know hex + length=$(printf "%d" 0x$(echo $section_info | cut -d ' ' -f3)) + offset=$(printf "%d" 0x$(echo $section_info | cut -d ' ' -f6)) + + for ins in $(dd if="$filename" bs=1 count=$length skip=$offset status=none | xxd -p -c 8) + do + opcode=0x${ins:0:2} + srcdst=0x${ins:2:2} + + # bytes are dumped in LE order + offset=0x${ins:6:2}${ins:4:2} # short + immedi=0x${ins:14:2}${ins:12:2}${ins:10:2}${ins:8:2} # int + + dst="$(($srcdst & 0xF))" + src="$(($srcdst & 0xF0))" + src="$(($src >> 4))" + + opcode=$(printf "0x%x" $opcode) + dst=$(printf "BPF_REG_%d" $dst) + src=$(printf "BPF_REG_%d" $src) + offset=$(printf "%d" $offset) + immedi=$(printf "0x%x" $immedi) + + printf " { %4s, %11s, %11s, (int16_t) %6s, %10s },\n" $opcode $dst $src $offset $immedi + done + +cat << END +}; + +END +} + +generate_head +process_relocations +process_section +generate_tail + diff --git a/src/deps/src/nginx/src/event/quic/bpf/makefile b/src/deps/src/nginx/src/event/quic/bpf/makefile new file mode 100644 index 000000000..b4d758f33 --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/bpf/makefile @@ -0,0 +1,30 @@ +CFLAGS=-O2 -Wall + +LICENSE=BSD + +PROGNAME=ngx_quic_reuseport_helper +RESULT=ngx_event_quic_bpf_code +DEST=../$(RESULT).c + +all: $(RESULT) + +$(RESULT): $(PROGNAME).o + LICENSE=$(LICENSE) PROGNAME=$(PROGNAME) bash ./bpfgen.sh $< > $@ + +DEFS=-DPROGNAME=\"$(PROGNAME)\" \ + -DLICENSE_$(LICENSE) \ + -DLICENSE=\"$(LICENSE)\" \ + +$(PROGNAME).o: $(PROGNAME).c + clang $(CFLAGS) $(DEFS) -target bpf -c $< -o $@ + +install: $(RESULT) + cp $(RESULT) $(DEST) + +clean: + @rm -f $(RESULT) *.o + +debug: $(PROGNAME).o + llvm-objdump -S -no-show-raw-insn $< + +.DELETE_ON_ERROR: diff --git a/src/deps/src/nginx/src/event/quic/bpf/ngx_quic_reuseport_helper.c b/src/deps/src/nginx/src/event/quic/bpf/ngx_quic_reuseport_helper.c new file mode 100644 index 000000000..999e7607c --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/bpf/ngx_quic_reuseport_helper.c @@ -0,0 +1,140 @@ +#include +#include +#include +#include +/* + * the bpf_helpers.h is not included into linux-headers, only available + * with kernel sources in "tools/lib/bpf/bpf_helpers.h" or in libbpf. + */ +#include + + +#if !defined(SEC) +#define SEC(NAME) __attribute__((section(NAME), used)) +#endif + + +#if defined(LICENSE_GPL) + +/* + * To see debug: + * + * echo 1 > /sys/kernel/debug/tracing/events/bpf_trace/enable + * cat /sys/kernel/debug/tracing/trace_pipe + * echo 0 > /sys/kernel/debug/tracing/events/bpf_trace/enable + */ + +#define debugmsg(fmt, ...) \ +do { \ + char __buf[] = fmt; \ + bpf_trace_printk(__buf, sizeof(__buf), ##__VA_ARGS__); \ +} while (0) + +#else + +#define debugmsg(fmt, ...) + +#endif + +char _license[] SEC("license") = LICENSE; + +/*****************************************************************************/ + +#define NGX_QUIC_PKT_LONG 0x80 /* header form */ +#define NGX_QUIC_SERVER_CID_LEN 20 + + +#define advance_data(nbytes) \ + offset += nbytes; \ + if (start + offset > end) { \ + debugmsg("cannot read %ld bytes at offset %ld", nbytes, offset); \ + goto failed; \ + } \ + data = start + offset - 1; + + +#define ngx_quic_parse_uint64(p) \ + (((__u64)(p)[0] << 56) | \ + ((__u64)(p)[1] << 48) | \ + ((__u64)(p)[2] << 40) | \ + ((__u64)(p)[3] << 32) | \ + ((__u64)(p)[4] << 24) | \ + ((__u64)(p)[5] << 16) | \ + ((__u64)(p)[6] << 8) | \ + ((__u64)(p)[7])) + +/* + * actual map object is created by the "bpf" system call, + * all pointers to this variable are replaced by the bpf loader + */ +struct bpf_map_def SEC("maps") ngx_quic_sockmap; + + +SEC(PROGNAME) +int ngx_quic_select_socket_by_dcid(struct sk_reuseport_md *ctx) +{ + int rc; + __u64 key; + size_t len, offset; + unsigned char *start, *end, *data, *dcid; + + start = ctx->data; + end = (unsigned char *) ctx->data_end; + offset = 0; + + advance_data(sizeof(struct udphdr)); /* data at UDP header */ + advance_data(1); /* data at QUIC flags */ + + if (data[0] & NGX_QUIC_PKT_LONG) { + + advance_data(4); /* data at QUIC version */ + advance_data(1); /* data at DCID len */ + + len = data[0]; /* read DCID length */ + + if (len < 8) { + /* it's useless to search for key in such short DCID */ + return SK_PASS; + } + + } else { + len = NGX_QUIC_SERVER_CID_LEN; + } + + dcid = &data[1]; + advance_data(len); /* we expect the packet to have full DCID */ + + /* make verifier happy */ + if (dcid + sizeof(__u64) > end) { + goto failed; + } + + key = ngx_quic_parse_uint64(dcid); + + rc = bpf_sk_select_reuseport(ctx, &ngx_quic_sockmap, &key, 0); + + switch (rc) { + case 0: + debugmsg("nginx quic socket selected by key 0x%llx", key); + return SK_PASS; + + /* kernel returns positive error numbers, errno.h defines positive */ + case -ENOENT: + debugmsg("nginx quic default route for key 0x%llx", key); + /* let the default reuseport logic decide which socket to choose */ + return SK_PASS; + + default: + debugmsg("nginx quic bpf_sk_select_reuseport err: %d key 0x%llx", + rc, key); + goto failed; + } + +failed: + /* + * SK_DROP will generate ICMP, but we may want to process "invalid" packet + * in userspace quic to investigate further and finally react properly + * (maybe ignore, maybe send something in response or close connection) + */ + return SK_PASS; +} diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic.c b/src/deps/src/nginx/src/event/quic/ngx_event_quic.c new file mode 100644 index 000000000..e4690f7dd --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic.c @@ -0,0 +1,1452 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c, + ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_handle_stateless_reset(ngx_connection_t *c, + ngx_quic_header_t *pkt); +static void ngx_quic_input_handler(ngx_event_t *rev); +static void ngx_quic_close_handler(ngx_event_t *ev); + +static ngx_int_t ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b, + ngx_quic_conf_t *conf); +static ngx_int_t ngx_quic_handle_packet(ngx_connection_t *c, + ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_handle_payload(ngx_connection_t *c, + ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_check_csid(ngx_quic_connection_t *qc, + ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_handle_frames(ngx_connection_t *c, + ngx_quic_header_t *pkt); + +static void ngx_quic_push_handler(ngx_event_t *ev); + + +static ngx_core_module_t ngx_quic_module_ctx = { + ngx_string("quic"), + NULL, + NULL +}; + + +ngx_module_t ngx_quic_module = { + NGX_MODULE_V1, + &ngx_quic_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +#if (NGX_DEBUG) + +void +ngx_quic_connstate_dbg(ngx_connection_t *c) +{ + u_char *p, *last; + ngx_quic_connection_t *qc; + u_char buf[NGX_MAX_ERROR_STR]; + + p = buf; + last = p + sizeof(buf); + + qc = ngx_quic_get_connection(c); + + p = ngx_slprintf(p, last, "state:"); + + if (qc) { + + if (qc->error != (ngx_uint_t) -1) { + p = ngx_slprintf(p, last, "%s", qc->error_app ? " app" : ""); + p = ngx_slprintf(p, last, " error:%ui", qc->error); + + if (qc->error_reason) { + p = ngx_slprintf(p, last, " \"%s\"", qc->error_reason); + } + } + + p = ngx_slprintf(p, last, "%s", qc->shutdown ? " shutdown" : ""); + p = ngx_slprintf(p, last, "%s", qc->closing ? " closing" : ""); + p = ngx_slprintf(p, last, "%s", qc->draining ? " draining" : ""); + p = ngx_slprintf(p, last, "%s", qc->key_phase ? " kp" : ""); + + } else { + p = ngx_slprintf(p, last, " early"); + } + + if (c->read->timer_set) { + p = ngx_slprintf(p, last, + qc && qc->send_timer_set ? " send:%M" : " read:%M", + c->read->timer.key - ngx_current_msec); + } + + if (qc) { + + if (qc->push.timer_set) { + p = ngx_slprintf(p, last, " push:%M", + qc->push.timer.key - ngx_current_msec); + } + + if (qc->pto.timer_set) { + p = ngx_slprintf(p, last, " pto:%M", + qc->pto.timer.key - ngx_current_msec); + } + + if (qc->close.timer_set) { + p = ngx_slprintf(p, last, " close:%M", + qc->close.timer.key - ngx_current_msec); + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic %*s", p - buf, buf); +} + +#endif + + +ngx_int_t +ngx_quic_apply_transport_params(ngx_connection_t *c, ngx_quic_tp_t *ctp) +{ + ngx_str_t scid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + scid.data = qc->path->cid->id; + scid.len = qc->path->cid->len; + + if (scid.len != ctp->initial_scid.len + || ngx_memcmp(scid.data, ctp->initial_scid.data, scid.len) != 0) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic client initial_source_connection_id mismatch"); + return NGX_ERROR; + } + + if (ctp->max_udp_payload_size < NGX_QUIC_MIN_INITIAL_SIZE + || ctp->max_udp_payload_size > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) + { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "invalid maximum packet size"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic maximum packet size is invalid"); + return NGX_ERROR; + } + + if (ctp->active_connection_id_limit < 2) { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "invalid active_connection_id_limit"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic active_connection_id_limit is invalid"); + return NGX_ERROR; + } + + if (ctp->ack_delay_exponent > 20) { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "invalid ack_delay_exponent"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic ack_delay_exponent is invalid"); + return NGX_ERROR; + } + + if (ctp->max_ack_delay >= 16384) { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "invalid max_ack_delay"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic max_ack_delay is invalid"); + return NGX_ERROR; + } + + if (ctp->max_idle_timeout > 0 + && ctp->max_idle_timeout < qc->tp.max_idle_timeout) + { + qc->tp.max_idle_timeout = ctp->max_idle_timeout; + } + + qc->streams.server_max_streams_bidi = ctp->initial_max_streams_bidi; + qc->streams.server_max_streams_uni = ctp->initial_max_streams_uni; + + ngx_memcpy(&qc->ctp, ctp, sizeof(ngx_quic_tp_t)); + + return NGX_OK; +} + + +void +ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf) +{ + ngx_int_t rc; + ngx_quic_connection_t *qc; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic run"); + + rc = ngx_quic_handle_datagram(c, c->buffer, conf); + if (rc != NGX_OK) { + ngx_quic_close_connection(c, rc); + return; + } + + /* quic connection is now created */ + qc = ngx_quic_get_connection(c); + + ngx_add_timer(c->read, qc->tp.max_idle_timeout); + + if (!qc->streams.initialized) { + ngx_add_timer(&qc->close, qc->conf->handshake_timeout); + } + + ngx_quic_connstate_dbg(c); + + c->read->handler = ngx_quic_input_handler; + + return; +} + + +static ngx_quic_connection_t * +ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, + ngx_quic_header_t *pkt) +{ + ngx_uint_t i; + ngx_quic_tp_t *ctp; + ngx_quic_connection_t *qc; + + qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); + if (qc == NULL) { + return NULL; + } + + qc->keys = ngx_pcalloc(c->pool, sizeof(ngx_quic_keys_t)); + if (qc->keys == NULL) { + return NULL; + } + + qc->version = pkt->version; + + ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel, + ngx_quic_rbtree_insert_stream); + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ngx_queue_init(&qc->send_ctx[i].frames); + ngx_queue_init(&qc->send_ctx[i].sending); + ngx_queue_init(&qc->send_ctx[i].sent); + qc->send_ctx[i].largest_pn = NGX_QUIC_UNSET_PN; + qc->send_ctx[i].largest_ack = NGX_QUIC_UNSET_PN; + qc->send_ctx[i].largest_range = NGX_QUIC_UNSET_PN; + qc->send_ctx[i].pending_ack = NGX_QUIC_UNSET_PN; + } + + qc->send_ctx[0].level = ssl_encryption_initial; + qc->send_ctx[1].level = ssl_encryption_handshake; + qc->send_ctx[2].level = ssl_encryption_application; + + ngx_queue_init(&qc->free_frames); + + ngx_quic_init_rtt(qc); + + qc->pto.log = c->log; + qc->pto.data = c; + qc->pto.handler = ngx_quic_pto_handler; + + qc->push.log = c->log; + qc->push.data = c; + qc->push.handler = ngx_quic_push_handler; + + qc->close.log = c->log; + qc->close.data = c; + qc->close.handler = ngx_quic_close_handler; + + qc->path_validation.log = c->log; + qc->path_validation.data = c; + qc->path_validation.handler = ngx_quic_path_handler; + + qc->key_update.log = c->log; + qc->key_update.data = c; + qc->key_update.handler = ngx_quic_keys_update; + + qc->conf = conf; + + if (ngx_quic_init_transport_params(&qc->tp, conf) != NGX_OK) { + return NULL; + } + + ctp = &qc->ctp; + + /* defaults to be used before actual client parameters are received */ + ctp->max_udp_payload_size = NGX_QUIC_MAX_UDP_PAYLOAD_SIZE; + ctp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT; + ctp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY; + ctp->active_connection_id_limit = 2; + + ngx_queue_init(&qc->streams.uninitialized); + ngx_queue_init(&qc->streams.free); + + qc->streams.recv_max_data = qc->tp.initial_max_data; + qc->streams.recv_window = qc->streams.recv_max_data; + + qc->streams.client_max_streams_uni = qc->tp.initial_max_streams_uni; + qc->streams.client_max_streams_bidi = qc->tp.initial_max_streams_bidi; + + qc->congestion.window = ngx_min(10 * qc->tp.max_udp_payload_size, + ngx_max(2 * qc->tp.max_udp_payload_size, + 14720)); + qc->congestion.ssthresh = (size_t) -1; + qc->congestion.recovery_start = ngx_current_msec; + + if (pkt->validated && pkt->retried) { + qc->tp.retry_scid.len = pkt->dcid.len; + qc->tp.retry_scid.data = ngx_pstrdup(c->pool, &pkt->dcid); + if (qc->tp.retry_scid.data == NULL) { + return NULL; + } + } + + if (ngx_quic_keys_set_initial_secret(qc->keys, &pkt->dcid, c->log) + != NGX_OK) + { + return NULL; + } + + qc->validated = pkt->validated; + + if (ngx_quic_open_sockets(c, qc, pkt) != NGX_OK) { + ngx_quic_keys_cleanup(qc->keys); + return NULL; + } + + c->idle = 1; + ngx_reusable_connection(c, 1); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic connection created"); + + return qc; +} + + +static ngx_int_t +ngx_quic_handle_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + u_char *tail, ch; + ngx_uint_t i; + ngx_queue_t *q; + ngx_quic_client_id_t *cid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + /* A stateless reset uses an entire UDP datagram */ + if (!pkt->first) { + return NGX_DECLINED; + } + + tail = pkt->raw->last - NGX_QUIC_SR_TOKEN_LEN; + + for (q = ngx_queue_head(&qc->client_ids); + q != ngx_queue_sentinel(&qc->client_ids); + q = ngx_queue_next(q)) + { + cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); + + if (cid->seqnum == 0 || !cid->used) { + /* + * No stateless reset token in initial connection id. + * Don't accept a token from an unused connection id. + */ + continue; + } + + /* constant time comparison */ + + for (ch = 0, i = 0; i < NGX_QUIC_SR_TOKEN_LEN; i++) { + ch |= tail[i] ^ cid->sr_token[i]; + } + + if (ch == 0) { + return NGX_OK; + } + } + + return NGX_DECLINED; +} + + +static void +ngx_quic_input_handler(ngx_event_t *rev) +{ + ngx_int_t rc; + ngx_buf_t *b; + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic input handler"); + + c = rev->data; + qc = ngx_quic_get_connection(c); + + c->log->action = "handling quic input"; + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, + "quic client timed out"); + ngx_quic_close_connection(c, NGX_DONE); + return; + } + + if (c->close) { + c->close = 0; + + if (!ngx_exiting || !qc->streams.initialized) { + qc->error = NGX_QUIC_ERR_NO_ERROR; + qc->error_reason = "graceful shutdown"; + ngx_quic_close_connection(c, NGX_ERROR); + return; + } + + if (!qc->closing && qc->conf->shutdown) { + qc->conf->shutdown(c); + } + + return; + } + + b = c->udp->buffer; + if (b == NULL) { + return; + } + + rc = ngx_quic_handle_datagram(c, b, NULL); + + if (rc == NGX_ERROR) { + ngx_quic_close_connection(c, NGX_ERROR); + return; + } + + if (rc == NGX_DONE) { + return; + } + + /* rc == NGX_OK */ + + qc->send_timer_set = 0; + ngx_add_timer(rev, qc->tp.max_idle_timeout); + + ngx_quic_connstate_dbg(c); +} + + +void +ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) +{ + ngx_uint_t i; + ngx_pool_t *pool; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (qc == NULL) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet rejected rc:%i, cleanup connection", rc); + goto quic_done; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic close %s rc:%i", + qc->closing ? "resumed": "initiated", rc); + + if (!qc->closing) { + + /* drop packets from retransmit queues, no ack is expected */ + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ngx_quic_free_frames(c, &qc->send_ctx[i].frames); + ngx_quic_free_frames(c, &qc->send_ctx[i].sent); + } + + if (qc->close.timer_set) { + ngx_del_timer(&qc->close); + } + + if (rc == NGX_DONE) { + + /* + * RFC 9000, 10.1. Idle Timeout + * + * If a max_idle_timeout is specified by either endpoint in its + * transport parameters (Section 18.2), the connection is silently + * closed and its state is discarded when it remains idle + */ + + /* this case also handles some errors from ngx_quic_run() */ + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic close silent drain:%d timedout:%d", + qc->draining, c->read->timedout); + } else { + + /* + * RFC 9000, 10.2. Immediate Close + * + * An endpoint sends a CONNECTION_CLOSE frame (Section 19.19) + * to terminate the connection immediately. + */ + + if (qc->error == (ngx_uint_t) -1) { + qc->error = NGX_QUIC_ERR_INTERNAL_ERROR; + qc->error_app = 0; + } + + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic close immediate term:%d drain:%d " + "%serror:%ui \"%s\"", + rc == NGX_ERROR ? 1 : 0, qc->draining, + qc->error_app ? "app " : "", qc->error, + qc->error_reason ? qc->error_reason : ""); + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ctx = &qc->send_ctx[i]; + + if (!ngx_quic_keys_available(qc->keys, ctx->level, 1)) { + continue; + } + + qc->error_level = ctx->level; + (void) ngx_quic_send_cc(c); + + if (rc == NGX_OK) { + ngx_add_timer(&qc->close, 3 * ngx_quic_pto(c, ctx)); + } + } + } + + qc->closing = 1; + } + + if (rc == NGX_ERROR && qc->close.timer_set) { + /* do not wait for timer in case of fatal error */ + ngx_del_timer(&qc->close); + } + + if (ngx_quic_close_streams(c, qc) == NGX_AGAIN) { + return; + } + + if (qc->push.timer_set) { + ngx_del_timer(&qc->push); + } + + if (qc->pto.timer_set) { + ngx_del_timer(&qc->pto); + } + + if (qc->path_validation.timer_set) { + ngx_del_timer(&qc->path_validation); + } + + if (qc->push.posted) { + ngx_delete_posted_event(&qc->push); + } + + if (qc->key_update.posted) { + ngx_delete_posted_event(&qc->key_update); + } + + if (qc->close.timer_set) { + return; + } + + if (qc->close.posted) { + ngx_delete_posted_event(&qc->close); + } + + ngx_quic_close_sockets(c); + + ngx_quic_keys_cleanup(qc->keys); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic close completed"); + + /* may be tested from SSL callback during SSL shutdown */ + c->udp = NULL; + +quic_done: + + if (c->ssl) { + (void) ngx_ssl_shutdown(c); + } + + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, -1); +#endif + + c->destroyed = 1; + + pool = c->pool; + + ngx_close_connection(c); + + ngx_destroy_pool(pool); +} + + +void +ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err, + const char *reason) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (qc->closing) { + return; + } + + qc->error = err; + qc->error_reason = reason; + qc->error_app = 1; + qc->error_ftype = 0; + + ngx_post_event(&qc->close, &ngx_posted_events); +} + + +void +ngx_quic_shutdown_connection(ngx_connection_t *c, ngx_uint_t err, + const char *reason) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + qc->shutdown = 1; + qc->shutdown_code = err; + qc->shutdown_reason = reason; + + ngx_quic_shutdown_quic(c); +} + + +static void +ngx_quic_close_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic close handler"); + + c = ev->data; + + ngx_quic_close_connection(c, NGX_OK); +} + + +static ngx_int_t +ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b, + ngx_quic_conf_t *conf) +{ + size_t size; + u_char *p, *start; + ngx_int_t rc; + ngx_uint_t good; + ngx_quic_path_t *path; + ngx_quic_header_t pkt; + ngx_quic_connection_t *qc; + + good = 0; + path = NULL; + + size = b->last - b->pos; + + p = start = b->pos; + + while (p < b->last) { + + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + pkt.raw = b; + pkt.data = p; + pkt.len = b->last - p; + pkt.log = c->log; + pkt.first = (p == start) ? 1 : 0; + pkt.path = path; + pkt.flags = p[0]; + pkt.raw->pos++; + + rc = ngx_quic_handle_packet(c, conf, &pkt); + +#if (NGX_DEBUG) + if (pkt.parsed) { + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet done rc:%i level:%s" + " decr:%d pn:%L perr:%ui", + rc, ngx_quic_level_name(pkt.level), + pkt.decrypted, pkt.pn, pkt.error); + } else { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet done rc:%i parse failed", rc); + } +#endif + + if (rc == NGX_ERROR || rc == NGX_DONE) { + return rc; + } + + if (rc == NGX_OK) { + good = 1; + } + + path = pkt.path; /* preserve packet path from 1st packet */ + + /* NGX_OK || NGX_DECLINED */ + + /* + * we get NGX_DECLINED when there are no keys [yet] available + * to decrypt packet. + * Instead of queueing it, we ignore it and rely on the sender's + * retransmission: + * + * RFC 9000, 12.2. Coalescing Packets + * + * For example, if decryption fails (because the keys are + * not available or for any other reason), the receiver MAY either + * discard or buffer the packet for later processing and MUST + * attempt to process the remaining packets. + * + * We also skip packets that don't match connection state + * or cannot be parsed properly. + */ + + /* b->pos is at header end, adjust by actual packet length */ + b->pos = pkt.data + pkt.len; + + p = b->pos; + } + + if (!good) { + return NGX_DONE; + } + + qc = ngx_quic_get_connection(c); + + if (qc) { + qc->received += size; + + if ((uint64_t) (c->sent + qc->received) / 8 > + (qc->streams.sent + qc->streams.recv_last) + 1048576) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic flood detected"); + + qc->error = NGX_QUIC_ERR_NO_ERROR; + qc->error_reason = "QUIC flood detected"; + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_handle_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, + ngx_quic_header_t *pkt) +{ + ngx_int_t rc; + ngx_quic_socket_t *qsock; + ngx_quic_connection_t *qc; + + c->log->action = "parsing quic packet"; + + rc = ngx_quic_parse_packet(pkt); + + if (rc == NGX_ERROR) { + return NGX_DECLINED; + } + + pkt->parsed = 1; + + c->log->action = "handling quic packet"; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet rx dcid len:%uz %xV", + pkt->dcid.len, &pkt->dcid); + +#if (NGX_DEBUG) + if (pkt->level != ssl_encryption_application) { + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet rx scid len:%uz %xV", + pkt->scid.len, &pkt->scid); + } + + if (pkt->level == ssl_encryption_initial) { + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic address validation token len:%uz %xV", + pkt->token.len, &pkt->token); + } +#endif + + qc = ngx_quic_get_connection(c); + + if (qc) { + + if (rc == NGX_ABORT) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic unsupported version: 0x%xD", pkt->version); + return NGX_DECLINED; + } + + if (pkt->level != ssl_encryption_application) { + + if (pkt->version != qc->version) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic version mismatch: 0x%xD", pkt->version); + return NGX_DECLINED; + } + + if (pkt->first) { + qsock = ngx_quic_get_socket(c); + + if (ngx_cmp_sockaddr(&qsock->sockaddr.sockaddr, qsock->socklen, + qc->path->sockaddr, qc->path->socklen, 1) + != NGX_OK) + { + /* packet comes from unknown path, possibly migration */ + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic too early migration attempt"); + return NGX_DONE; + } + } + + if (ngx_quic_check_csid(qc, pkt) != NGX_OK) { + return NGX_DECLINED; + } + + } + + rc = ngx_quic_handle_payload(c, pkt); + + if (rc == NGX_DECLINED && pkt->level == ssl_encryption_application) { + if (ngx_quic_handle_stateless_reset(c, pkt) == NGX_OK) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic stateless reset packet detected"); + + qc->draining = 1; + ngx_post_event(&qc->close, &ngx_posted_events); + + return NGX_OK; + } + } + + return rc; + } + + /* packet does not belong to a connection */ + + if (rc == NGX_ABORT) { + return ngx_quic_negotiate_version(c, pkt); + } + + if (pkt->level == ssl_encryption_application) { + return ngx_quic_send_stateless_reset(c, conf, pkt); + } + + if (pkt->level != ssl_encryption_initial) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic expected initial, got handshake"); + return NGX_ERROR; + } + + c->log->action = "handling initial packet"; + + if (pkt->dcid.len < NGX_QUIC_CID_LEN_MIN) { + /* RFC 9000, 7.2. Negotiating Connection IDs */ + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic too short dcid in initial" + " packet: len:%i", pkt->dcid.len); + return NGX_ERROR; + } + + /* process retry and initialize connection IDs */ + + if (pkt->token.len) { + + rc = ngx_quic_validate_token(c, conf->av_token_key, pkt); + + if (rc == NGX_ERROR) { + /* internal error */ + return NGX_ERROR; + + } else if (rc == NGX_ABORT) { + /* token cannot be decrypted */ + return ngx_quic_send_early_cc(c, pkt, + NGX_QUIC_ERR_INVALID_TOKEN, + "cannot decrypt token"); + } else if (rc == NGX_DECLINED) { + /* token is invalid */ + + if (pkt->retried) { + /* invalid address validation token */ + return ngx_quic_send_early_cc(c, pkt, + NGX_QUIC_ERR_INVALID_TOKEN, + "invalid address validation token"); + } else if (conf->retry) { + /* invalid NEW_TOKEN */ + return ngx_quic_send_retry(c, conf, pkt); + } + } + + /* NGX_OK */ + + } else if (conf->retry) { + return ngx_quic_send_retry(c, conf, pkt); + + } else { + pkt->odcid = pkt->dcid; + } + + if (ngx_terminate || ngx_exiting) { + if (conf->retry) { + return ngx_quic_send_retry(c, conf, pkt); + } + + return NGX_ERROR; + } + + c->log->action = "creating quic connection"; + + qc = ngx_quic_new_connection(c, conf, pkt); + if (qc == NULL) { + return NGX_ERROR; + } + + return ngx_quic_handle_payload(c, pkt); +} + + +static ngx_int_t +ngx_quic_handle_payload(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + ngx_int_t rc; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + qc = ngx_quic_get_connection(c); + + qc->error = (ngx_uint_t) -1; + qc->error_reason = 0; + + c->log->action = "decrypting packet"; + + if (!ngx_quic_keys_available(qc->keys, pkt->level, 0)) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic no %s keys, ignoring packet", + ngx_quic_level_name(pkt->level)); + return NGX_DECLINED; + } + +#if !defined (OPENSSL_IS_BORINGSSL) + /* OpenSSL provides read keys for an application level before it's ready */ + + if (pkt->level == ssl_encryption_application && !c->ssl->handshaked) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic no %s keys ready, ignoring packet", + ngx_quic_level_name(pkt->level)); + return NGX_DECLINED; + } +#endif + + pkt->keys = qc->keys; + pkt->key_phase = qc->key_phase; + pkt->plaintext = buf; + + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + rc = ngx_quic_decrypt(pkt, &ctx->largest_pn); + if (rc != NGX_OK) { + qc->error = pkt->error; + qc->error_reason = "failed to decrypt packet"; + return rc; + } + + pkt->decrypted = 1; + + c->log->action = "handling decrypted packet"; + + if (pkt->path == NULL) { + rc = ngx_quic_set_path(c, pkt); + if (rc != NGX_OK) { + return rc; + } + } + + if (c->ssl == NULL) { + if (ngx_quic_init_connection(c) != NGX_OK) { + return NGX_ERROR; + } + } + + if (pkt->level == ssl_encryption_handshake) { + /* + * RFC 9001, 4.9.1. Discarding Initial Keys + * + * The successful use of Handshake packets indicates + * that no more Initial packets need to be exchanged + */ + ngx_quic_discard_ctx(c, ssl_encryption_initial); + + if (!qc->path->validated) { + qc->path->validated = 1; + ngx_quic_path_dbg(c, "in handshake", qc->path); + ngx_post_event(&qc->push, &ngx_posted_events); + } + } + + if (qc->closing) { + /* + * RFC 9000, 10.2. Immediate Close + * + * ... delayed or reordered packets are properly discarded. + * + * In the closing state, an endpoint retains only enough information + * to generate a packet containing a CONNECTION_CLOSE frame and to + * identify packets as belonging to the connection. + */ + + qc->error_level = pkt->level; + qc->error = NGX_QUIC_ERR_NO_ERROR; + qc->error_reason = "connection is closing, packet discarded"; + qc->error_ftype = 0; + qc->error_app = 0; + + return ngx_quic_send_cc(c); + } + + pkt->received = ngx_current_msec; + + c->log->action = "handling payload"; + + if (pkt->level != ssl_encryption_application) { + return ngx_quic_handle_frames(c, pkt); + } + + if (!pkt->key_update) { + return ngx_quic_handle_frames(c, pkt); + } + + /* switch keys and generate next on Key Phase change */ + + qc->key_phase ^= 1; + ngx_quic_keys_switch(c, qc->keys); + + rc = ngx_quic_handle_frames(c, pkt); + if (rc != NGX_OK) { + return rc; + } + + ngx_post_event(&qc->key_update, &ngx_posted_events); + + return NGX_OK; +} + + +void +ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level) +{ + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_socket_t *qsock; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (!ngx_quic_keys_available(qc->keys, level, 0) + && !ngx_quic_keys_available(qc->keys, level, 1)) + { + return; + } + + ngx_quic_keys_discard(qc->keys, level); + + qc->pto_count = 0; + + ctx = ngx_quic_get_send_ctx(qc, level); + + ngx_quic_free_buffer(c, &ctx->crypto); + + while (!ngx_queue_empty(&ctx->sent)) { + q = ngx_queue_head(&ctx->sent); + ngx_queue_remove(q); + + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + ngx_quic_congestion_ack(c, f); + ngx_quic_free_frame(c, f); + } + + while (!ngx_queue_empty(&ctx->frames)) { + q = ngx_queue_head(&ctx->frames); + ngx_queue_remove(q); + + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + ngx_quic_free_frame(c, f); + } + + if (level == ssl_encryption_initial) { + /* close temporary listener with initial dcid */ + qsock = ngx_quic_find_socket(c, NGX_QUIC_UNSET_PN); + if (qsock) { + ngx_quic_close_socket(c, qsock); + } + } + + ctx->send_ack = 0; + + ngx_quic_set_lost_timer(c); +} + + +static ngx_int_t +ngx_quic_check_csid(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt) +{ + ngx_queue_t *q; + ngx_quic_client_id_t *cid; + + for (q = ngx_queue_head(&qc->client_ids); + q != ngx_queue_sentinel(&qc->client_ids); + q = ngx_queue_next(q)) + { + cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); + + if (pkt->scid.len == cid->len + && ngx_memcmp(pkt->scid.data, cid->id, cid->len) == 0) + { + return NGX_OK; + } + } + + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic scid"); + return NGX_ERROR; +} + + +static ngx_int_t +ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + u_char *end, *p; + ssize_t len; + ngx_buf_t buf; + ngx_uint_t do_close, nonprobing; + ngx_chain_t chain; + ngx_quic_frame_t frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + p = pkt->payload.data; + end = p + pkt->payload.len; + + do_close = 0; + nonprobing = 0; + + while (p < end) { + + c->log->action = "parsing frames"; + + ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); + ngx_memzero(&buf, sizeof(ngx_buf_t)); + buf.temporary = 1; + + chain.buf = &buf; + chain.next = NULL; + frame.data = &chain; + + len = ngx_quic_parse_frame(pkt, p, end, &frame); + + if (len < 0) { + qc->error = pkt->error; + return NGX_ERROR; + } + + ngx_quic_log_frame(c->log, &frame, 0); + + c->log->action = "handling frames"; + + p += len; + + switch (frame.type) { + /* probing frames */ + case NGX_QUIC_FT_PADDING: + case NGX_QUIC_FT_PATH_CHALLENGE: + case NGX_QUIC_FT_PATH_RESPONSE: + case NGX_QUIC_FT_NEW_CONNECTION_ID: + break; + + /* non-probing frames */ + default: + nonprobing = 1; + break; + } + + switch (frame.type) { + + case NGX_QUIC_FT_ACK: + if (ngx_quic_handle_ack_frame(c, pkt, &frame) != NGX_OK) { + return NGX_ERROR; + } + + continue; + + case NGX_QUIC_FT_PADDING: + /* no action required */ + continue; + + case NGX_QUIC_FT_CONNECTION_CLOSE: + case NGX_QUIC_FT_CONNECTION_CLOSE_APP: + do_close = 1; + continue; + } + + /* got there with ack-eliciting packet */ + pkt->need_ack = 1; + + switch (frame.type) { + + case NGX_QUIC_FT_CRYPTO: + + if (ngx_quic_handle_crypto_frame(c, pkt, &frame) != NGX_OK) { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_PING: + break; + + case NGX_QUIC_FT_STREAM: + + if (ngx_quic_handle_stream_frame(c, pkt, &frame) != NGX_OK) { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_MAX_DATA: + + if (ngx_quic_handle_max_data_frame(c, &frame.u.max_data) != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_STREAMS_BLOCKED: + case NGX_QUIC_FT_STREAMS_BLOCKED2: + + if (ngx_quic_handle_streams_blocked_frame(c, pkt, + &frame.u.streams_blocked) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_DATA_BLOCKED: + + if (ngx_quic_handle_data_blocked_frame(c, pkt, + &frame.u.data_blocked) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_STREAM_DATA_BLOCKED: + + if (ngx_quic_handle_stream_data_blocked_frame(c, pkt, + &frame.u.stream_data_blocked) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_MAX_STREAM_DATA: + + if (ngx_quic_handle_max_stream_data_frame(c, pkt, + &frame.u.max_stream_data) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_RESET_STREAM: + + if (ngx_quic_handle_reset_stream_frame(c, pkt, + &frame.u.reset_stream) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_STOP_SENDING: + + if (ngx_quic_handle_stop_sending_frame(c, pkt, + &frame.u.stop_sending) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_MAX_STREAMS: + case NGX_QUIC_FT_MAX_STREAMS2: + + if (ngx_quic_handle_max_streams_frame(c, pkt, &frame.u.max_streams) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_PATH_CHALLENGE: + + if (ngx_quic_handle_path_challenge_frame(c, pkt, + &frame.u.path_challenge) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_PATH_RESPONSE: + + if (ngx_quic_handle_path_response_frame(c, &frame.u.path_response) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_NEW_CONNECTION_ID: + + if (ngx_quic_handle_new_connection_id_frame(c, &frame.u.ncid) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_RETIRE_CONNECTION_ID: + + if (ngx_quic_handle_retire_connection_id_frame(c, + &frame.u.retire_cid) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + default: + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic missing frame handler"); + return NGX_ERROR; + } + } + + if (p != end) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic trailing garbage in payload:%ui bytes", end - p); + + qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + return NGX_ERROR; + } + + if (do_close) { + qc->draining = 1; + ngx_post_event(&qc->close, &ngx_posted_events); + } + + if (pkt->path != qc->path && nonprobing) { + + /* + * RFC 9000, 9.2. Initiating Connection Migration + * + * An endpoint can migrate a connection to a new local + * address by sending packets containing non-probing frames + * from that address. + */ + if (ngx_quic_handle_migration(c, pkt) != NGX_OK) { + return NGX_ERROR; + } + } + + if (ngx_quic_ack_packet(c, pkt) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static void +ngx_quic_push_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic push handler"); + + c = ev->data; + + if (ngx_quic_output(c) != NGX_OK) { + ngx_quic_close_connection(c, NGX_ERROR); + return; + } + + ngx_quic_connstate_dbg(c); +} + + +void +ngx_quic_shutdown_quic(ngx_connection_t *c) +{ + ngx_quic_connection_t *qc; + + if (c->reusable) { + qc = ngx_quic_get_connection(c); + ngx_quic_finalize_connection(c, qc->shutdown_code, qc->shutdown_reason); + } +} diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic.h b/src/deps/src/nginx/src/event/quic/ngx_event_quic.h new file mode 100644 index 000000000..15201671d --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic.h @@ -0,0 +1,129 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_H_INCLUDED_ +#define _NGX_EVENT_QUIC_H_INCLUDED_ + + +#include +#include + + +#define NGX_QUIC_MAX_UDP_PAYLOAD_SIZE 65527 + +#define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3 +#define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25 +#define NGX_QUIC_DEFAULT_HOST_KEY_LEN 32 +#define NGX_QUIC_SR_KEY_LEN 32 +#define NGX_QUIC_AV_KEY_LEN 32 + +#define NGX_QUIC_SR_TOKEN_LEN 16 + +#define NGX_QUIC_MIN_INITIAL_SIZE 1200 + +#define NGX_QUIC_STREAM_SERVER_INITIATED 0x01 +#define NGX_QUIC_STREAM_UNIDIRECTIONAL 0x02 + + +typedef ngx_int_t (*ngx_quic_init_pt)(ngx_connection_t *c); +typedef void (*ngx_quic_shutdown_pt)(ngx_connection_t *c); + + +typedef enum { + NGX_QUIC_STREAM_SEND_READY = 0, + NGX_QUIC_STREAM_SEND_SEND, + NGX_QUIC_STREAM_SEND_DATA_SENT, + NGX_QUIC_STREAM_SEND_DATA_RECVD, + NGX_QUIC_STREAM_SEND_RESET_SENT, + NGX_QUIC_STREAM_SEND_RESET_RECVD +} ngx_quic_stream_send_state_e; + + +typedef enum { + NGX_QUIC_STREAM_RECV_RECV = 0, + NGX_QUIC_STREAM_RECV_SIZE_KNOWN, + NGX_QUIC_STREAM_RECV_DATA_RECVD, + NGX_QUIC_STREAM_RECV_DATA_READ, + NGX_QUIC_STREAM_RECV_RESET_RECVD, + NGX_QUIC_STREAM_RECV_RESET_READ +} ngx_quic_stream_recv_state_e; + + +typedef struct { + uint64_t size; + uint64_t offset; + uint64_t last_offset; + ngx_chain_t *chain; + ngx_chain_t *last_chain; +} ngx_quic_buffer_t; + + +typedef struct { + ngx_ssl_t *ssl; + + ngx_flag_t retry; + ngx_flag_t gso_enabled; + ngx_flag_t disable_active_migration; + ngx_msec_t handshake_timeout; + ngx_msec_t idle_timeout; + ngx_str_t host_key; + size_t stream_buffer_size; + ngx_uint_t max_concurrent_streams_bidi; + ngx_uint_t max_concurrent_streams_uni; + ngx_uint_t active_connection_id_limit; + ngx_int_t stream_close_code; + ngx_int_t stream_reject_code_uni; + ngx_int_t stream_reject_code_bidi; + + ngx_quic_init_pt init; + ngx_quic_shutdown_pt shutdown; + + u_char av_token_key[NGX_QUIC_AV_KEY_LEN]; + u_char sr_token_key[NGX_QUIC_SR_KEY_LEN]; +} ngx_quic_conf_t; + + +struct ngx_quic_stream_s { + ngx_rbtree_node_t node; + ngx_queue_t queue; + ngx_connection_t *parent; + ngx_connection_t *connection; + uint64_t id; + uint64_t sent; + uint64_t acked; + uint64_t send_max_data; + uint64_t send_offset; + uint64_t send_final_size; + uint64_t recv_max_data; + uint64_t recv_offset; + uint64_t recv_window; + uint64_t recv_last; + uint64_t recv_final_size; + ngx_quic_buffer_t send; + ngx_quic_buffer_t recv; + ngx_quic_stream_send_state_e send_state; + ngx_quic_stream_recv_state_e recv_state; + unsigned cancelable:1; + unsigned fin_acked:1; +}; + + +void ngx_quic_recvmsg(ngx_event_t *ev); +void ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf); +ngx_connection_t *ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi); +void ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err, + const char *reason); +void ngx_quic_shutdown_connection(ngx_connection_t *c, ngx_uint_t err, + const char *reason); +ngx_int_t ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err); +ngx_int_t ngx_quic_shutdown_stream(ngx_connection_t *c, int how); +void ngx_quic_cancelable_stream(ngx_connection_t *c); +ngx_int_t ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t len, + ngx_str_t *dcid); +ngx_int_t ngx_quic_derive_key(ngx_log_t *log, const char *label, + ngx_str_t *secret, ngx_str_t *salt, u_char *out, size_t len); + +#endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */ diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_ack.c b/src/deps/src/nginx/src/event/quic/ngx_event_quic_ack.c new file mode 100644 index 000000000..c7ffd44dd --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_ack.c @@ -0,0 +1,1188 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +#define NGX_QUIC_MAX_ACK_GAP 2 + +/* RFC 9002, 6.1.1. Packet Threshold: kPacketThreshold */ +#define NGX_QUIC_PKT_THR 3 /* packets */ +/* RFC 9002, 6.1.2. Time Threshold: kGranularity */ +#define NGX_QUIC_TIME_GRANULARITY 1 /* ms */ + +/* RFC 9002, 7.6.1. Duration: kPersistentCongestionThreshold */ +#define NGX_QUIC_PERSISTENT_CONGESTION_THR 3 + + +/* send time of ACK'ed packets */ +typedef struct { + ngx_msec_t max_pn; + ngx_msec_t oldest; + ngx_msec_t newest; +} ngx_quic_ack_stat_t; + + +static ngx_inline ngx_msec_t ngx_quic_lost_threshold(ngx_quic_connection_t *qc); +static void ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, + enum ssl_encryption_level_t level, ngx_msec_t send_time); +static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, uint64_t min, uint64_t max, + ngx_quic_ack_stat_t *st); +static void ngx_quic_drop_ack_ranges(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, uint64_t pn); +static ngx_int_t ngx_quic_detect_lost(ngx_connection_t *c, + ngx_quic_ack_stat_t *st); +static ngx_msec_t ngx_quic_pcg_duration(ngx_connection_t *c); +static void ngx_quic_persistent_congestion(ngx_connection_t *c); +static void ngx_quic_congestion_lost(ngx_connection_t *c, + ngx_quic_frame_t *frame); +static void ngx_quic_lost_handler(ngx_event_t *ev); + + +/* RFC 9002, 6.1.2. Time Threshold: kTimeThreshold, kGranularity */ +static ngx_inline ngx_msec_t +ngx_quic_lost_threshold(ngx_quic_connection_t *qc) +{ + ngx_msec_t thr; + + thr = ngx_max(qc->latest_rtt, qc->avg_rtt); + thr += thr >> 3; + + return ngx_max(thr, NGX_QUIC_TIME_GRANULARITY); +} + + +ngx_int_t +ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, + ngx_quic_frame_t *f) +{ + ssize_t n; + u_char *pos, *end; + uint64_t min, max, gap, range; + ngx_uint_t i; + ngx_quic_ack_stat_t send_time; + ngx_quic_send_ctx_t *ctx; + ngx_quic_ack_frame_t *ack; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_handle_ack_frame level:%d", pkt->level); + + ack = &f->u.ack; + + /* + * RFC 9000, 19.3.1. ACK Ranges + * + * If any computed packet number is negative, an endpoint MUST + * generate a connection error of type FRAME_ENCODING_ERROR. + */ + + if (ack->first_range > ack->largest) { + qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic invalid first range in ack frame"); + return NGX_ERROR; + } + + min = ack->largest - ack->first_range; + max = ack->largest; + + send_time.oldest = NGX_TIMER_INFINITE; + send_time.newest = NGX_TIMER_INFINITE; + + if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time) + != NGX_OK) + { + return NGX_ERROR; + } + + /* RFC 9000, 13.2.4. Limiting Ranges by Tracking ACK Frames */ + if (ctx->largest_ack < max || ctx->largest_ack == NGX_QUIC_UNSET_PN) { + ctx->largest_ack = max; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic updated largest received ack:%uL", max); + + /* + * RFC 9002, 5.1. Generating RTT Samples + * + * An endpoint generates an RTT sample on receiving an + * ACK frame that meets the following two conditions: + * + * - the largest acknowledged packet number is newly acknowledged + * - at least one of the newly acknowledged packets was ack-eliciting. + */ + + if (send_time.max_pn != NGX_TIMER_INFINITE) { + ngx_quic_rtt_sample(c, ack, pkt->level, send_time.max_pn); + } + } + + if (f->data) { + pos = f->data->buf->pos; + end = f->data->buf->last; + + } else { + pos = NULL; + end = NULL; + } + + for (i = 0; i < ack->range_count; i++) { + + n = ngx_quic_parse_ack_range(pkt->log, pos, end, &gap, &range); + if (n == NGX_ERROR) { + return NGX_ERROR; + } + pos += n; + + if (gap + 2 > min) { + qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic invalid range:%ui in ack frame", i); + return NGX_ERROR; + } + + max = min - gap - 2; + + if (range > max) { + qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic invalid range:%ui in ack frame", i); + return NGX_ERROR; + } + + min = max - range; + + if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time) + != NGX_OK) + { + return NGX_ERROR; + } + } + + return ngx_quic_detect_lost(c, &send_time); +} + + +static void +ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, + enum ssl_encryption_level_t level, ngx_msec_t send_time) +{ + ngx_msec_t latest_rtt, ack_delay, adjusted_rtt, rttvar_sample; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + latest_rtt = ngx_current_msec - send_time; + qc->latest_rtt = latest_rtt; + + if (qc->min_rtt == NGX_TIMER_INFINITE) { + qc->min_rtt = latest_rtt; + qc->avg_rtt = latest_rtt; + qc->rttvar = latest_rtt / 2; + qc->first_rtt = ngx_current_msec; + + } else { + qc->min_rtt = ngx_min(qc->min_rtt, latest_rtt); + + ack_delay = (ack->delay << qc->ctp.ack_delay_exponent) / 1000; + + if (c->ssl->handshaked) { + ack_delay = ngx_min(ack_delay, qc->ctp.max_ack_delay); + } + + adjusted_rtt = latest_rtt; + + if (qc->min_rtt + ack_delay < latest_rtt) { + adjusted_rtt -= ack_delay; + } + + rttvar_sample = ngx_abs((ngx_msec_int_t) (qc->avg_rtt - adjusted_rtt)); + qc->rttvar += (rttvar_sample >> 2) - (qc->rttvar >> 2); + qc->avg_rtt += (adjusted_rtt >> 3) - (qc->avg_rtt >> 3); + } + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic rtt sample latest:%M min:%M avg:%M var:%M", + latest_rtt, qc->min_rtt, qc->avg_rtt, qc->rttvar); +} + + +static ngx_int_t +ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t min, uint64_t max, ngx_quic_ack_stat_t *st) +{ + ngx_uint_t found; + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (ctx->level == ssl_encryption_application) { + if (ngx_quic_handle_path_mtu(c, qc->path, min, max) != NGX_OK) { + return NGX_ERROR; + } + } + + st->max_pn = NGX_TIMER_INFINITE; + found = 0; + + q = ngx_queue_head(&ctx->sent); + + while (q != ngx_queue_sentinel(&ctx->sent)) { + + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + q = ngx_queue_next(q); + + if (f->pnum > max) { + break; + } + + if (f->pnum >= min) { + ngx_quic_congestion_ack(c, f); + + switch (f->type) { + case NGX_QUIC_FT_ACK: + case NGX_QUIC_FT_ACK_ECN: + ngx_quic_drop_ack_ranges(c, ctx, f->u.ack.largest); + break; + + case NGX_QUIC_FT_STREAM: + case NGX_QUIC_FT_RESET_STREAM: + ngx_quic_handle_stream_ack(c, f); + break; + } + + if (f->pnum == max) { + st->max_pn = f->send_time; + } + + /* save earliest and latest send times of frames ack'ed */ + if (st->oldest == NGX_TIMER_INFINITE || f->send_time < st->oldest) { + st->oldest = f->send_time; + } + + if (st->newest == NGX_TIMER_INFINITE || f->send_time > st->newest) { + st->newest = f->send_time; + } + + ngx_queue_remove(&f->queue); + ngx_quic_free_frame(c, f); + found = 1; + } + } + + if (!found) { + + if (max < ctx->pnum) { + /* duplicate ACK or ACK for non-ack-eliciting frame */ + return NGX_OK; + } + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic ACK for the packet not sent"); + + qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + qc->error_ftype = NGX_QUIC_FT_ACK; + qc->error_reason = "unknown packet number"; + + return NGX_ERROR; + } + + if (!qc->push.timer_set) { + ngx_post_event(&qc->push, &ngx_posted_events); + } + + qc->pto_count = 0; + + return NGX_OK; +} + + +void +ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f) +{ + ngx_uint_t blocked; + ngx_msec_t timer; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + + if (f->plen == 0) { + return; + } + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + + if (f->pnum < qc->rst_pnum) { + return; + } + + blocked = (cg->in_flight >= cg->window) ? 1 : 0; + + cg->in_flight -= f->plen; + + timer = f->send_time - cg->recovery_start; + + if ((ngx_msec_int_t) timer <= 0) { + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion ack recovery win:%uz ss:%z if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + + goto done; + } + + if (cg->window < cg->ssthresh) { + cg->window += f->plen; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion slow start win:%uz ss:%z if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + + } else { + cg->window += qc->tp.max_udp_payload_size * f->plen / cg->window; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion avoidance win:%uz ss:%z if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + } + + /* prevent recovery_start from wrapping */ + + timer = cg->recovery_start - ngx_current_msec + qc->tp.max_idle_timeout * 2; + + if ((ngx_msec_int_t) timer < 0) { + cg->recovery_start = ngx_current_msec - qc->tp.max_idle_timeout * 2; + } + +done: + + if (blocked && cg->in_flight < cg->window) { + ngx_post_event(&qc->push, &ngx_posted_events); + } +} + + +static void +ngx_quic_drop_ack_ranges(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t pn) +{ + uint64_t base; + ngx_uint_t i, smallest, largest; + ngx_quic_ack_range_t *r; + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_drop_ack_ranges pn:%uL largest:%uL" + " fr:%uL nranges:%ui", pn, ctx->largest_range, + ctx->first_range, ctx->nranges); + + base = ctx->largest_range; + + if (base == NGX_QUIC_UNSET_PN) { + return; + } + + if (ctx->pending_ack != NGX_QUIC_UNSET_PN && pn >= ctx->pending_ack) { + ctx->pending_ack = NGX_QUIC_UNSET_PN; + } + + largest = base; + smallest = largest - ctx->first_range; + + if (pn >= largest) { + ctx->largest_range = NGX_QUIC_UNSET_PN; + ctx->first_range = 0; + ctx->nranges = 0; + return; + } + + if (pn >= smallest) { + ctx->first_range = largest - pn - 1; + ctx->nranges = 0; + return; + } + + for (i = 0; i < ctx->nranges; i++) { + r = &ctx->ranges[i]; + + largest = smallest - r->gap - 2; + smallest = largest - r->range; + + if (pn >= largest) { + ctx->nranges = i; + return; + } + if (pn >= smallest) { + r->range = largest - pn - 1; + ctx->nranges = i + 1; + return; + } + } +} + + +static ngx_int_t +ngx_quic_detect_lost(ngx_connection_t *c, ngx_quic_ack_stat_t *st) +{ + ngx_uint_t i, nlost; + ngx_msec_t now, wait, thr, oldest, newest; + ngx_queue_t *q; + ngx_quic_frame_t *start; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + now = ngx_current_msec; + thr = ngx_quic_lost_threshold(qc); + + /* send time of lost packets across all send contexts */ + oldest = NGX_TIMER_INFINITE; + newest = NGX_TIMER_INFINITE; + + nlost = 0; + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + + ctx = &qc->send_ctx[i]; + + if (ctx->largest_ack == NGX_QUIC_UNSET_PN) { + continue; + } + + while (!ngx_queue_empty(&ctx->sent)) { + + q = ngx_queue_head(&ctx->sent); + start = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (start->pnum > ctx->largest_ack) { + break; + } + + wait = start->send_time + thr - now; + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic detect_lost pnum:%uL thr:%M wait:%i level:%d", + start->pnum, thr, (ngx_int_t) wait, start->level); + + if ((ngx_msec_int_t) wait > 0 + && ctx->largest_ack - start->pnum < NGX_QUIC_PKT_THR) + { + break; + } + + if (start->send_time > qc->first_rtt) { + + if (oldest == NGX_TIMER_INFINITE || start->send_time < oldest) { + oldest = start->send_time; + } + + if (newest == NGX_TIMER_INFINITE || start->send_time > newest) { + newest = start->send_time; + } + + nlost++; + } + + ngx_quic_resend_frames(c, ctx); + } + } + + + /* RFC 9002, 7.6.2. Establishing Persistent Congestion */ + + /* + * Once acknowledged, packets are no longer tracked. Thus no send time + * information is available for such packets. This limits persistent + * congestion algorithm to packets mentioned within ACK ranges of the + * latest ACK frame. + */ + + if (st && nlost >= 2 && (st->newest < oldest || st->oldest > newest)) { + + if (newest - oldest > ngx_quic_pcg_duration(c)) { + ngx_quic_persistent_congestion(c); + } + } + + ngx_quic_set_lost_timer(c); + + return NGX_OK; +} + + +static ngx_msec_t +ngx_quic_pcg_duration(ngx_connection_t *c) +{ + ngx_msec_t duration; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + duration = qc->avg_rtt; + duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY); + duration += qc->ctp.max_ack_delay; + duration *= NGX_QUIC_PERSISTENT_CONGESTION_THR; + + return duration; +} + + +static void +ngx_quic_persistent_congestion(ngx_connection_t *c) +{ + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + + cg->recovery_start = ngx_current_msec; + cg->window = qc->tp.max_udp_payload_size * 2; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic persistent congestion win:%uz", cg->window); +} + + +void +ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + uint64_t pnum; + ngx_queue_t *q; + ngx_quic_frame_t *f, *start; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + q = ngx_queue_head(&ctx->sent); + start = ngx_queue_data(q, ngx_quic_frame_t, queue); + pnum = start->pnum; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic resend packet pnum:%uL", start->pnum); + + ngx_quic_congestion_lost(c, start); + + do { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (f->pnum != pnum) { + break; + } + + q = ngx_queue_next(q); + + ngx_queue_remove(&f->queue); + + switch (f->type) { + case NGX_QUIC_FT_ACK: + case NGX_QUIC_FT_ACK_ECN: + if (ctx->level == ssl_encryption_application) { + /* force generation of most recent acknowledgment */ + ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; + } + + ngx_quic_free_frame(c, f); + break; + + case NGX_QUIC_FT_PING: + case NGX_QUIC_FT_PATH_CHALLENGE: + case NGX_QUIC_FT_PATH_RESPONSE: + case NGX_QUIC_FT_CONNECTION_CLOSE: + ngx_quic_free_frame(c, f); + break; + + case NGX_QUIC_FT_MAX_DATA: + f->u.max_data.max_data = qc->streams.recv_max_data; + ngx_quic_queue_frame(qc, f); + break; + + case NGX_QUIC_FT_MAX_STREAMS: + case NGX_QUIC_FT_MAX_STREAMS2: + f->u.max_streams.limit = f->u.max_streams.bidi + ? qc->streams.client_max_streams_bidi + : qc->streams.client_max_streams_uni; + ngx_quic_queue_frame(qc, f); + break; + + case NGX_QUIC_FT_MAX_STREAM_DATA: + qs = ngx_quic_find_stream(&qc->streams.tree, + f->u.max_stream_data.id); + if (qs == NULL) { + ngx_quic_free_frame(c, f); + break; + } + + f->u.max_stream_data.limit = qs->recv_max_data; + ngx_quic_queue_frame(qc, f); + break; + + case NGX_QUIC_FT_STREAM: + qs = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); + + if (qs) { + if (qs->send_state == NGX_QUIC_STREAM_SEND_RESET_SENT + || qs->send_state == NGX_QUIC_STREAM_SEND_RESET_RECVD) + { + ngx_quic_free_frame(c, f); + break; + } + } + + /* fall through */ + + default: + ngx_queue_insert_tail(&ctx->frames, &f->queue); + } + + } while (q != ngx_queue_sentinel(&ctx->sent)); + + if (qc->closing) { + return; + } + + ngx_post_event(&qc->push, &ngx_posted_events); +} + + +static void +ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *f) +{ + ngx_uint_t blocked; + ngx_msec_t timer; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + + if (f->plen == 0) { + return; + } + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + + if (f->pnum < qc->rst_pnum) { + return; + } + + blocked = (cg->in_flight >= cg->window) ? 1 : 0; + + cg->in_flight -= f->plen; + f->plen = 0; + + timer = f->send_time - cg->recovery_start; + + if ((ngx_msec_int_t) timer <= 0) { + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion lost recovery win:%uz ss:%z if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + + goto done; + } + + cg->recovery_start = ngx_current_msec; + cg->window /= 2; + + if (cg->window < qc->tp.max_udp_payload_size * 2) { + cg->window = qc->tp.max_udp_payload_size * 2; + } + + cg->ssthresh = cg->window; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion lost win:%uz ss:%z if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + +done: + + if (blocked && cg->in_flight < cg->window) { + ngx_post_event(&qc->push, &ngx_posted_events); + } +} + + +void +ngx_quic_set_lost_timer(ngx_connection_t *c) +{ + ngx_uint_t i; + ngx_msec_t now; + ngx_queue_t *q; + ngx_msec_int_t lost, pto, w; + ngx_quic_frame_t *f; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + now = ngx_current_msec; + + lost = -1; + pto = -1; + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ctx = &qc->send_ctx[i]; + + if (ngx_queue_empty(&ctx->sent)) { + continue; + } + + if (ctx->largest_ack != NGX_QUIC_UNSET_PN) { + q = ngx_queue_head(&ctx->sent); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + w = (ngx_msec_int_t) + (f->send_time + ngx_quic_lost_threshold(qc) - now); + + if (f->pnum <= ctx->largest_ack) { + if (w < 0 || ctx->largest_ack - f->pnum >= NGX_QUIC_PKT_THR) { + w = 0; + } + + if (lost == -1 || w < lost) { + lost = w; + } + } + } + + q = ngx_queue_last(&ctx->sent); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + w = (ngx_msec_int_t) + (f->send_time + (ngx_quic_pto(c, ctx) << qc->pto_count) - now); + + if (w < 0) { + w = 0; + } + + if (pto == -1 || w < pto) { + pto = w; + } + } + + if (qc->pto.timer_set) { + ngx_del_timer(&qc->pto); + } + + if (lost != -1) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic lost timer lost:%M", lost); + + qc->pto.handler = ngx_quic_lost_handler; + ngx_add_timer(&qc->pto, lost); + return; + } + + if (pto != -1) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic lost timer pto:%M", pto); + + qc->pto.handler = ngx_quic_pto_handler; + ngx_add_timer(&qc->pto, pto); + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic lost timer unset"); +} + + +ngx_msec_t +ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + ngx_msec_t duration; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + /* RFC 9002, Appendix A.8. Setting the Loss Detection Timer */ + + duration = qc->avg_rtt; + duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY); + + if (ctx->level == ssl_encryption_application && c->ssl->handshaked) { + duration += qc->ctp.max_ack_delay; + } + + return duration; +} + + +static +void ngx_quic_lost_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic lost timer"); + + c = ev->data; + + if (ngx_quic_detect_lost(c, NULL) != NGX_OK) { + ngx_quic_close_connection(c, NGX_ERROR); + return; + } + + ngx_quic_connstate_dbg(c); +} + + +void +ngx_quic_pto_handler(ngx_event_t *ev) +{ + ngx_uint_t i, n; + ngx_msec_t now; + ngx_queue_t *q; + ngx_msec_int_t w; + ngx_connection_t *c; + ngx_quic_frame_t *f; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic pto timer"); + + c = ev->data; + qc = ngx_quic_get_connection(c); + now = ngx_current_msec; + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + + ctx = &qc->send_ctx[i]; + + if (ngx_queue_empty(&ctx->sent)) { + continue; + } + + q = ngx_queue_last(&ctx->sent); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + w = (ngx_msec_int_t) + (f->send_time + (ngx_quic_pto(c, ctx) << qc->pto_count) - now); + + if (f->pnum <= ctx->largest_ack + && ctx->largest_ack != NGX_QUIC_UNSET_PN) + { + continue; + } + + if (w > 0) { + continue; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic pto %s pto_count:%ui", + ngx_quic_level_name(ctx->level), qc->pto_count); + + for (n = 0; n < 2; n++) { + + f = ngx_quic_alloc_frame(c); + if (f == NULL) { + goto failed; + } + + f->level = ctx->level; + f->type = NGX_QUIC_FT_PING; + f->ignore_congestion = 1; + + if (ngx_quic_frame_sendto(c, f, 0, qc->path) == NGX_ERROR) { + goto failed; + } + } + } + + qc->pto_count++; + + ngx_quic_set_lost_timer(c); + + ngx_quic_connstate_dbg(c); + + return; + +failed: + + ngx_quic_close_connection(c, NGX_ERROR); + return; +} + + +ngx_int_t +ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + uint64_t base, largest, smallest, gs, ge, gap, range, pn; + uint64_t prev_pending; + ngx_uint_t i, nr; + ngx_quic_send_ctx_t *ctx; + ngx_quic_ack_range_t *r; + ngx_quic_connection_t *qc; + + c->log->action = "preparing ack"; + + qc = ngx_quic_get_connection(c); + + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_ack_packet pn:%uL largest %L fr:%uL" + " nranges:%ui", pkt->pn, (int64_t) ctx->largest_range, + ctx->first_range, ctx->nranges); + + if (!ngx_quic_keys_available(qc->keys, ctx->level, 1)) { + return NGX_OK; + } + + prev_pending = ctx->pending_ack; + + if (pkt->need_ack) { + + ngx_post_event(&qc->push, &ngx_posted_events); + + if (ctx->send_ack == 0) { + ctx->ack_delay_start = ngx_current_msec; + } + + ctx->send_ack++; + + if (ctx->pending_ack == NGX_QUIC_UNSET_PN + || ctx->pending_ack < pkt->pn) + { + ctx->pending_ack = pkt->pn; + } + } + + base = ctx->largest_range; + pn = pkt->pn; + + if (base == NGX_QUIC_UNSET_PN) { + ctx->largest_range = pn; + ctx->largest_received = pkt->received; + return NGX_OK; + } + + if (base == pn) { + return NGX_OK; + } + + largest = base; + smallest = largest - ctx->first_range; + + if (pn > base) { + + if (pn - base == 1) { + ctx->first_range++; + ctx->largest_range = pn; + ctx->largest_received = pkt->received; + + return NGX_OK; + + } else { + /* new gap in front of current largest */ + + /* no place for new range, send current range as is */ + if (ctx->nranges == NGX_QUIC_MAX_RANGES) { + + if (prev_pending != NGX_QUIC_UNSET_PN) { + if (ngx_quic_send_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + } + + if (prev_pending == ctx->pending_ack || !pkt->need_ack) { + ctx->pending_ack = NGX_QUIC_UNSET_PN; + } + } + + gap = pn - base - 2; + range = ctx->first_range; + + ctx->first_range = 0; + ctx->largest_range = pn; + ctx->largest_received = pkt->received; + + /* packet is out of order, force send */ + if (pkt->need_ack) { + ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; + } + + i = 0; + + goto insert; + } + } + + /* pn < base, perform lookup in existing ranges */ + + /* packet is out of order */ + if (pkt->need_ack) { + ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; + } + + if (pn >= smallest && pn <= largest) { + return NGX_OK; + } + +#if (NGX_SUPPRESS_WARN) + r = NULL; +#endif + + for (i = 0; i < ctx->nranges; i++) { + r = &ctx->ranges[i]; + + ge = smallest - 1; + gs = ge - r->gap; + + if (pn >= gs && pn <= ge) { + + if (gs == ge) { + /* gap size is exactly one packet, now filled */ + + /* data moves to previous range, current is removed */ + + if (i == 0) { + ctx->first_range += r->range + 2; + + } else { + ctx->ranges[i - 1].range += r->range + 2; + } + + nr = ctx->nranges - i - 1; + if (nr) { + ngx_memmove(&ctx->ranges[i], &ctx->ranges[i + 1], + sizeof(ngx_quic_ack_range_t) * nr); + } + + ctx->nranges--; + + } else if (pn == gs) { + /* current gap shrinks from tail (current range grows) */ + r->gap--; + r->range++; + + } else if (pn == ge) { + /* current gap shrinks from head (previous range grows) */ + r->gap--; + + if (i == 0) { + ctx->first_range++; + + } else { + ctx->ranges[i - 1].range++; + } + + } else { + /* current gap is split into two parts */ + + gap = ge - pn - 1; + range = 0; + + if (ctx->nranges == NGX_QUIC_MAX_RANGES) { + if (prev_pending != NGX_QUIC_UNSET_PN) { + if (ngx_quic_send_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + } + + if (prev_pending == ctx->pending_ack || !pkt->need_ack) { + ctx->pending_ack = NGX_QUIC_UNSET_PN; + } + } + + r->gap = pn - gs - 1; + goto insert; + } + + return NGX_OK; + } + + largest = smallest - r->gap - 2; + smallest = largest - r->range; + + if (pn >= smallest && pn <= largest) { + /* this packet number is already known */ + return NGX_OK; + } + + } + + if (pn == smallest - 1) { + /* extend first or last range */ + + if (i == 0) { + ctx->first_range++; + + } else { + r->range++; + } + + return NGX_OK; + } + + /* nothing found, add new range at the tail */ + + if (ctx->nranges == NGX_QUIC_MAX_RANGES) { + /* packet is too old to keep it */ + + if (pkt->need_ack) { + return ngx_quic_send_ack_range(c, ctx, pn, pn); + } + + return NGX_OK; + } + + gap = smallest - 2 - pn; + range = 0; + +insert: + + if (ctx->nranges < NGX_QUIC_MAX_RANGES) { + ctx->nranges++; + } + + ngx_memmove(&ctx->ranges[i + 1], &ctx->ranges[i], + sizeof(ngx_quic_ack_range_t) * (ctx->nranges - i - 1)); + + ctx->ranges[i].gap = gap; + ctx->ranges[i].range = range; + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_generate_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + ngx_msec_t delay; + ngx_quic_connection_t *qc; + + if (!ctx->send_ack) { + return NGX_OK; + } + + if (ctx->level == ssl_encryption_application) { + + delay = ngx_current_msec - ctx->ack_delay_start; + qc = ngx_quic_get_connection(c); + + if (ngx_queue_empty(&ctx->frames) + && ctx->send_ack < NGX_QUIC_MAX_ACK_GAP + && delay < qc->tp.max_ack_delay) + { + if (!qc->push.timer_set && !qc->closing) { + ngx_add_timer(&qc->push, + qc->tp.max_ack_delay - delay); + } + + return NGX_OK; + } + } + + if (ngx_quic_send_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + + ctx->send_ack = 0; + + return NGX_OK; +} diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_ack.h b/src/deps/src/nginx/src/event/quic/ngx_event_quic_ack.h new file mode 100644 index 000000000..56920c2a5 --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_ack.h @@ -0,0 +1,30 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_ACK_H_INCLUDED_ +#define _NGX_EVENT_QUIC_ACK_H_INCLUDED_ + + +#include +#include + + +ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_frame_t *f); + +void ngx_quic_congestion_ack(ngx_connection_t *c, + ngx_quic_frame_t *frame); +void ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); +void ngx_quic_set_lost_timer(ngx_connection_t *c); +void ngx_quic_pto_handler(ngx_event_t *ev); +ngx_msec_t ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); + +ngx_int_t ngx_quic_ack_packet(ngx_connection_t *c, + ngx_quic_header_t *pkt); +ngx_int_t ngx_quic_generate_ack(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx); + +#endif /* _NGX_EVENT_QUIC_ACK_H_INCLUDED_ */ diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_bpf.c b/src/deps/src/nginx/src/event/quic/ngx_event_quic_bpf.c new file mode 100644 index 000000000..ab024ad56 --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_bpf.c @@ -0,0 +1,657 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +#define NGX_QUIC_BPF_VARNAME "NGINX_BPF_MAPS" +#define NGX_QUIC_BPF_VARSEP ';' +#define NGX_QUIC_BPF_ADDRSEP '#' + + +#define ngx_quic_bpf_get_conf(cycle) \ + (ngx_quic_bpf_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_quic_bpf_module) + +#define ngx_quic_bpf_get_old_conf(cycle) \ + cycle->old_cycle->conf_ctx ? ngx_quic_bpf_get_conf(cycle->old_cycle) \ + : NULL + +#define ngx_core_get_conf(cycle) \ + (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module) + + +typedef struct { + ngx_queue_t queue; + int map_fd; + + struct sockaddr *sockaddr; + socklen_t socklen; + ngx_uint_t unused; /* unsigned unused:1; */ +} ngx_quic_sock_group_t; + + +typedef struct { + ngx_flag_t enabled; + ngx_uint_t map_size; + ngx_queue_t groups; /* of ngx_quic_sock_group_t */ +} ngx_quic_bpf_conf_t; + + +static void *ngx_quic_bpf_create_conf(ngx_cycle_t *cycle); +static ngx_int_t ngx_quic_bpf_module_init(ngx_cycle_t *cycle); + +static void ngx_quic_bpf_cleanup(void *data); +static ngx_inline void ngx_quic_bpf_close(ngx_log_t *log, int fd, + const char *name); + +static ngx_quic_sock_group_t *ngx_quic_bpf_find_group(ngx_quic_bpf_conf_t *bcf, + ngx_listening_t *ls); +static ngx_quic_sock_group_t *ngx_quic_bpf_alloc_group(ngx_cycle_t *cycle, + struct sockaddr *sa, socklen_t socklen); +static ngx_quic_sock_group_t *ngx_quic_bpf_create_group(ngx_cycle_t *cycle, + ngx_listening_t *ls); +static ngx_quic_sock_group_t *ngx_quic_bpf_get_group(ngx_cycle_t *cycle, + ngx_listening_t *ls); +static ngx_int_t ngx_quic_bpf_group_add_socket(ngx_cycle_t *cycle, + ngx_listening_t *ls); +static uint64_t ngx_quic_bpf_socket_key(ngx_fd_t fd, ngx_log_t *log); + +static ngx_int_t ngx_quic_bpf_export_maps(ngx_cycle_t *cycle); +static ngx_int_t ngx_quic_bpf_import_maps(ngx_cycle_t *cycle); + +extern ngx_bpf_program_t ngx_quic_reuseport_helper; + + +static ngx_command_t ngx_quic_bpf_commands[] = { + + { ngx_string("quic_bpf"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + 0, + offsetof(ngx_quic_bpf_conf_t, enabled), + NULL }, + + ngx_null_command +}; + + +static ngx_core_module_t ngx_quic_bpf_module_ctx = { + ngx_string("quic_bpf"), + ngx_quic_bpf_create_conf, + NULL +}; + + +ngx_module_t ngx_quic_bpf_module = { + NGX_MODULE_V1, + &ngx_quic_bpf_module_ctx, /* module context */ + ngx_quic_bpf_commands, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + ngx_quic_bpf_module_init, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_quic_bpf_create_conf(ngx_cycle_t *cycle) +{ + ngx_quic_bpf_conf_t *bcf; + + bcf = ngx_pcalloc(cycle->pool, sizeof(ngx_quic_bpf_conf_t)); + if (bcf == NULL) { + return NULL; + } + + bcf->enabled = NGX_CONF_UNSET; + bcf->map_size = NGX_CONF_UNSET_UINT; + + ngx_queue_init(&bcf->groups); + + return bcf; +} + + +static ngx_int_t +ngx_quic_bpf_module_init(ngx_cycle_t *cycle) +{ + ngx_uint_t i; + ngx_listening_t *ls; + ngx_core_conf_t *ccf; + ngx_pool_cleanup_t *cln; + ngx_quic_bpf_conf_t *bcf; + + if (ngx_test_config) { + /* + * during config test, SO_REUSEPORT socket option is + * not set, thus making further processing meaningless + */ + return NGX_OK; + } + + ccf = ngx_core_get_conf(cycle); + bcf = ngx_quic_bpf_get_conf(cycle); + + ngx_conf_init_value(bcf->enabled, 0); + + bcf->map_size = ccf->worker_processes * 4; + + cln = ngx_pool_cleanup_add(cycle->pool, 0); + if (cln == NULL) { + goto failed; + } + + cln->data = bcf; + cln->handler = ngx_quic_bpf_cleanup; + + if (ngx_inherited && ngx_is_init_cycle(cycle->old_cycle)) { + if (ngx_quic_bpf_import_maps(cycle) != NGX_OK) { + goto failed; + } + } + + ls = cycle->listening.elts; + + for (i = 0; i < cycle->listening.nelts; i++) { + if (ls[i].quic && ls[i].reuseport) { + if (ngx_quic_bpf_group_add_socket(cycle, &ls[i]) != NGX_OK) { + goto failed; + } + } + } + + if (ngx_quic_bpf_export_maps(cycle) != NGX_OK) { + goto failed; + } + + return NGX_OK; + +failed: + + if (ngx_is_init_cycle(cycle->old_cycle)) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "ngx_quic_bpf_module failed to initialize, check limits"); + + /* refuse to start */ + return NGX_ERROR; + } + + /* + * returning error now will lead to master process exiting immediately + * leaving worker processes orphaned, what is really unexpected. + * Instead, just issue a not about failed initialization and try + * to cleanup a bit. Still program can be already loaded to kernel + * for some reuseport groups, and there is no way to revert, so + * behaviour may be inconsistent. + */ + + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "ngx_quic_bpf_module failed to initialize properly, ignored." + "please check limits and note that nginx state now " + "can be inconsistent and restart may be required"); + + return NGX_OK; +} + + +static void +ngx_quic_bpf_cleanup(void *data) +{ + ngx_quic_bpf_conf_t *bcf = (ngx_quic_bpf_conf_t *) data; + + ngx_queue_t *q; + ngx_quic_sock_group_t *grp; + + for (q = ngx_queue_head(&bcf->groups); + q != ngx_queue_sentinel(&bcf->groups); + q = ngx_queue_next(q)) + { + grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue); + + ngx_quic_bpf_close(ngx_cycle->log, grp->map_fd, "map"); + } +} + + +static ngx_inline void +ngx_quic_bpf_close(ngx_log_t *log, int fd, const char *name) +{ + if (close(fd) != -1) { + return; + } + + ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, + "quic bpf close %s fd:%d failed", name, fd); +} + + +static ngx_quic_sock_group_t * +ngx_quic_bpf_find_group(ngx_quic_bpf_conf_t *bcf, ngx_listening_t *ls) +{ + ngx_queue_t *q; + ngx_quic_sock_group_t *grp; + + for (q = ngx_queue_head(&bcf->groups); + q != ngx_queue_sentinel(&bcf->groups); + q = ngx_queue_next(q)) + { + grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue); + + if (ngx_cmp_sockaddr(ls->sockaddr, ls->socklen, + grp->sockaddr, grp->socklen, 1) + == NGX_OK) + { + return grp; + } + } + + return NULL; +} + + +static ngx_quic_sock_group_t * +ngx_quic_bpf_alloc_group(ngx_cycle_t *cycle, struct sockaddr *sa, + socklen_t socklen) +{ + ngx_quic_bpf_conf_t *bcf; + ngx_quic_sock_group_t *grp; + + bcf = ngx_quic_bpf_get_conf(cycle); + + grp = ngx_pcalloc(cycle->pool, sizeof(ngx_quic_sock_group_t)); + if (grp == NULL) { + return NULL; + } + + grp->socklen = socklen; + grp->sockaddr = ngx_palloc(cycle->pool, socklen); + if (grp->sockaddr == NULL) { + return NULL; + } + ngx_memcpy(grp->sockaddr, sa, socklen); + + ngx_queue_insert_tail(&bcf->groups, &grp->queue); + + return grp; +} + + +static ngx_quic_sock_group_t * +ngx_quic_bpf_create_group(ngx_cycle_t *cycle, ngx_listening_t *ls) +{ + int progfd, failed, flags, rc; + ngx_quic_bpf_conf_t *bcf; + ngx_quic_sock_group_t *grp; + + bcf = ngx_quic_bpf_get_conf(cycle); + + if (!bcf->enabled) { + return NULL; + } + + grp = ngx_quic_bpf_alloc_group(cycle, ls->sockaddr, ls->socklen); + if (grp == NULL) { + return NULL; + } + + grp->map_fd = ngx_bpf_map_create(cycle->log, BPF_MAP_TYPE_SOCKHASH, + sizeof(uint64_t), sizeof(uint64_t), + bcf->map_size, 0); + if (grp->map_fd == -1) { + goto failed; + } + + flags = fcntl(grp->map_fd, F_GETFD); + if (flags == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, errno, + "quic bpf getfd failed"); + goto failed; + } + + /* need to inherit map during binary upgrade after exec */ + flags &= ~FD_CLOEXEC; + + rc = fcntl(grp->map_fd, F_SETFD, flags); + if (rc == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, errno, + "quic bpf setfd failed"); + goto failed; + } + + ngx_bpf_program_link(&ngx_quic_reuseport_helper, + "ngx_quic_sockmap", grp->map_fd); + + progfd = ngx_bpf_load_program(cycle->log, &ngx_quic_reuseport_helper); + if (progfd < 0) { + goto failed; + } + + failed = 0; + + if (setsockopt(ls->fd, SOL_SOCKET, SO_ATTACH_REUSEPORT_EBPF, + &progfd, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno, + "quic bpf setsockopt(SO_ATTACH_REUSEPORT_EBPF) failed"); + failed = 1; + } + + ngx_quic_bpf_close(cycle->log, progfd, "program"); + + if (failed) { + goto failed; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, + "quic bpf sockmap created fd:%d", grp->map_fd); + return grp; + +failed: + + if (grp->map_fd != -1) { + ngx_quic_bpf_close(cycle->log, grp->map_fd, "map"); + } + + ngx_queue_remove(&grp->queue); + + return NULL; +} + + +static ngx_quic_sock_group_t * +ngx_quic_bpf_get_group(ngx_cycle_t *cycle, ngx_listening_t *ls) +{ + ngx_quic_bpf_conf_t *bcf, *old_bcf; + ngx_quic_sock_group_t *grp, *ogrp; + + bcf = ngx_quic_bpf_get_conf(cycle); + + grp = ngx_quic_bpf_find_group(bcf, ls); + if (grp) { + return grp; + } + + old_bcf = ngx_quic_bpf_get_old_conf(cycle); + + if (old_bcf == NULL) { + return ngx_quic_bpf_create_group(cycle, ls); + } + + ogrp = ngx_quic_bpf_find_group(old_bcf, ls); + if (ogrp == NULL) { + return ngx_quic_bpf_create_group(cycle, ls); + } + + grp = ngx_quic_bpf_alloc_group(cycle, ls->sockaddr, ls->socklen); + if (grp == NULL) { + return NULL; + } + + grp->map_fd = dup(ogrp->map_fd); + if (grp->map_fd == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, + "quic bpf failed to duplicate bpf map descriptor"); + + ngx_queue_remove(&grp->queue); + + return NULL; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0, + "quic bpf sockmap fd duplicated old:%d new:%d", + ogrp->map_fd, grp->map_fd); + + return grp; +} + + +static ngx_int_t +ngx_quic_bpf_group_add_socket(ngx_cycle_t *cycle, ngx_listening_t *ls) +{ + uint64_t cookie; + ngx_quic_bpf_conf_t *bcf; + ngx_quic_sock_group_t *grp; + + bcf = ngx_quic_bpf_get_conf(cycle); + + grp = ngx_quic_bpf_get_group(cycle, ls); + + if (grp == NULL) { + if (!bcf->enabled) { + return NGX_OK; + } + + return NGX_ERROR; + } + + grp->unused = 0; + + cookie = ngx_quic_bpf_socket_key(ls->fd, cycle->log); + if (cookie == (uint64_t) NGX_ERROR) { + return NGX_ERROR; + } + + /* map[cookie] = socket; for use in kernel helper */ + if (ngx_bpf_map_update(grp->map_fd, &cookie, &ls->fd, BPF_ANY) == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, + "quic bpf failed to update socket map key=%xL", cookie); + return NGX_ERROR; + } + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, cycle->log, 0, + "quic bpf sockmap fd:%d add socket:%d cookie:0x%xL worker:%ui", + grp->map_fd, ls->fd, cookie, ls->worker); + + /* do not inherit this socket */ + ls->ignore = 1; + + return NGX_OK; +} + + +static uint64_t +ngx_quic_bpf_socket_key(ngx_fd_t fd, ngx_log_t *log) +{ + uint64_t cookie; + socklen_t optlen; + + optlen = sizeof(cookie); + + if (getsockopt(fd, SOL_SOCKET, SO_COOKIE, &cookie, &optlen) == -1) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno, + "quic bpf getsockopt(SO_COOKIE) failed"); + + return (ngx_uint_t) NGX_ERROR; + } + + return cookie; +} + + +static ngx_int_t +ngx_quic_bpf_export_maps(ngx_cycle_t *cycle) +{ + u_char *p, *buf; + size_t len; + ngx_str_t *var; + ngx_queue_t *q; + ngx_core_conf_t *ccf; + ngx_quic_bpf_conf_t *bcf; + ngx_quic_sock_group_t *grp; + + ccf = ngx_core_get_conf(cycle); + bcf = ngx_quic_bpf_get_conf(cycle); + + len = sizeof(NGX_QUIC_BPF_VARNAME) + 1; + + q = ngx_queue_head(&bcf->groups); + + while (q != ngx_queue_sentinel(&bcf->groups)) { + + grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue); + + q = ngx_queue_next(q); + + if (grp->unused) { + /* + * map was inherited, but it is not used in this configuration; + * do not pass such map further and drop the group to prevent + * interference with changes during reload + */ + + ngx_quic_bpf_close(cycle->log, grp->map_fd, "map"); + ngx_queue_remove(&grp->queue); + + continue; + } + + len += NGX_INT32_LEN + 1 + NGX_SOCKADDR_STRLEN + 1; + } + + len++; + + buf = ngx_palloc(cycle->pool, len); + if (buf == NULL) { + return NGX_ERROR; + } + + p = ngx_cpymem(buf, NGX_QUIC_BPF_VARNAME "=", + sizeof(NGX_QUIC_BPF_VARNAME)); + + for (q = ngx_queue_head(&bcf->groups); + q != ngx_queue_sentinel(&bcf->groups); + q = ngx_queue_next(q)) + { + grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue); + + p = ngx_sprintf(p, "%ud", grp->map_fd); + + *p++ = NGX_QUIC_BPF_ADDRSEP; + + p += ngx_sock_ntop(grp->sockaddr, grp->socklen, p, + NGX_SOCKADDR_STRLEN, 1); + + *p++ = NGX_QUIC_BPF_VARSEP; + } + + *p = '\0'; + + var = ngx_array_push(&ccf->env); + if (var == NULL) { + return NGX_ERROR; + } + + var->data = buf; + var->len = sizeof(NGX_QUIC_BPF_VARNAME) - 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_bpf_import_maps(ngx_cycle_t *cycle) +{ + int s; + u_char *inherited, *p, *v; + ngx_uint_t in_fd; + ngx_addr_t tmp; + ngx_quic_bpf_conf_t *bcf; + ngx_quic_sock_group_t *grp; + + inherited = (u_char *) getenv(NGX_QUIC_BPF_VARNAME); + + if (inherited == NULL) { + return NGX_OK; + } + + bcf = ngx_quic_bpf_get_conf(cycle); + +#if (NGX_SUPPRESS_WARN) + s = -1; +#endif + + in_fd = 1; + + for (p = inherited, v = p; *p; p++) { + + switch (*p) { + + case NGX_QUIC_BPF_ADDRSEP: + + if (!in_fd) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "quic bpf failed to parse inherited env"); + return NGX_ERROR; + } + in_fd = 0; + + s = ngx_atoi(v, p - v); + if (s == NGX_ERROR) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "quic bpf failed to parse inherited map fd"); + return NGX_ERROR; + } + + v = p + 1; + break; + + case NGX_QUIC_BPF_VARSEP: + + if (in_fd) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "quic bpf failed to parse inherited env"); + return NGX_ERROR; + } + in_fd = 1; + + grp = ngx_pcalloc(cycle->pool, + sizeof(ngx_quic_sock_group_t)); + if (grp == NULL) { + return NGX_ERROR; + } + + grp->map_fd = s; + + if (ngx_parse_addr_port(cycle->pool, &tmp, v, p - v) + != NGX_OK) + { + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "quic bpf failed to parse inherited" + " address '%*s'", p - v , v); + + ngx_quic_bpf_close(cycle->log, s, "inherited map"); + + return NGX_ERROR; + } + + grp->sockaddr = tmp.sockaddr; + grp->socklen = tmp.socklen; + + grp->unused = 1; + + ngx_queue_insert_tail(&bcf->groups, &grp->queue); + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0, + "quic bpf sockmap inherited with " + "fd:%d address:%*s", + grp->map_fd, p - v, v); + v = p + 1; + break; + + default: + break; + } + } + + return NGX_OK; +} diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_bpf_code.c b/src/deps/src/nginx/src/event/quic/ngx_event_quic_bpf_code.c new file mode 100644 index 000000000..5c9dea1c1 --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_bpf_code.c @@ -0,0 +1,88 @@ +/* AUTO-GENERATED, DO NOT EDIT. */ + +#include +#include + +#include "ngx_bpf.h" + + +static ngx_bpf_reloc_t bpf_reloc_prog_ngx_quic_reuseport_helper[] = { + { "ngx_quic_sockmap", 55 }, +}; + +static struct bpf_insn bpf_insn_prog_ngx_quic_reuseport_helper[] = { + /* opcode dst src offset imm */ + { 0x79, BPF_REG_4, BPF_REG_1, (int16_t) 0, 0x0 }, + { 0x79, BPF_REG_3, BPF_REG_1, (int16_t) 8, 0x0 }, + { 0xbf, BPF_REG_2, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x7, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0x8 }, + { 0x2d, BPF_REG_2, BPF_REG_3, (int16_t) 54, 0x0 }, + { 0xbf, BPF_REG_5, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x9 }, + { 0x2d, BPF_REG_5, BPF_REG_3, (int16_t) 51, 0x0 }, + { 0xb7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x14 }, + { 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x9 }, + { 0x71, BPF_REG_6, BPF_REG_2, (int16_t) 0, 0x0 }, + { 0x67, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x38 }, + { 0xc7, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x38 }, + { 0x65, BPF_REG_6, BPF_REG_0, (int16_t) 10, 0xffffffff }, + { 0xbf, BPF_REG_2, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x7, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0xd }, + { 0x2d, BPF_REG_2, BPF_REG_3, (int16_t) 42, 0x0 }, + { 0xbf, BPF_REG_5, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0xe }, + { 0x2d, BPF_REG_5, BPF_REG_3, (int16_t) 39, 0x0 }, + { 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0xe }, + { 0x71, BPF_REG_5, BPF_REG_2, (int16_t) 0, 0x0 }, + { 0xb7, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x8 }, + { 0x2d, BPF_REG_6, BPF_REG_5, (int16_t) 35, 0x0 }, + { 0xf, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x0 }, + { 0xf, BPF_REG_4, BPF_REG_5, (int16_t) 0, 0x0 }, + { 0x2d, BPF_REG_4, BPF_REG_3, (int16_t) 32, 0x0 }, + { 0xbf, BPF_REG_4, BPF_REG_2, (int16_t) 0, 0x0 }, + { 0x7, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x9 }, + { 0x2d, BPF_REG_4, BPF_REG_3, (int16_t) 29, 0x0 }, + { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 1, 0x0 }, + { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x38 }, + { 0x71, BPF_REG_3, BPF_REG_2, (int16_t) 2, 0x0 }, + { 0x67, BPF_REG_3, BPF_REG_0, (int16_t) 0, 0x30 }, + { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 3, 0x0 }, + { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x28 }, + { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 4, 0x0 }, + { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x20 }, + { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 5, 0x0 }, + { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x18 }, + { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 6, 0x0 }, + { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x10 }, + { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 7, 0x0 }, + { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x8 }, + { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x71, BPF_REG_2, BPF_REG_2, (int16_t) 8, 0x0 }, + { 0x4f, BPF_REG_3, BPF_REG_2, (int16_t) 0, 0x0 }, + { 0x7b, BPF_REG_10, BPF_REG_3, (int16_t) 65528, 0x0 }, + { 0xbf, BPF_REG_3, BPF_REG_10, (int16_t) 0, 0x0 }, + { 0x7, BPF_REG_3, BPF_REG_0, (int16_t) 0, 0xfffffff8 }, + { 0x18, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0x0 }, + { 0x0, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x0 }, + { 0xb7, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x0 }, + { 0x85, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x52 }, + { 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x1 }, + { 0x95, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x0 }, +}; + + +ngx_bpf_program_t ngx_quic_reuseport_helper = { + .relocs = bpf_reloc_prog_ngx_quic_reuseport_helper, + .nrelocs = sizeof(bpf_reloc_prog_ngx_quic_reuseport_helper) + / sizeof(bpf_reloc_prog_ngx_quic_reuseport_helper[0]), + .ins = bpf_insn_prog_ngx_quic_reuseport_helper, + .nins = sizeof(bpf_insn_prog_ngx_quic_reuseport_helper) + / sizeof(bpf_insn_prog_ngx_quic_reuseport_helper[0]), + .license = "BSD", + .type = BPF_PROG_TYPE_SK_REUSEPORT, +}; diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_connection.h b/src/deps/src/nginx/src/event/quic/ngx_event_quic_connection.h new file mode 100644 index 000000000..824c92b57 --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_connection.h @@ -0,0 +1,305 @@ +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_ +#define _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_ + + +#include +#include +#include + + +/* #define NGX_QUIC_DEBUG_PACKETS */ /* dump packet contents */ +/* #define NGX_QUIC_DEBUG_FRAMES */ /* dump frames contents */ +/* #define NGX_QUIC_DEBUG_ALLOC */ /* log frames and bufs alloc */ +/* #define NGX_QUIC_DEBUG_CRYPTO */ + +typedef struct ngx_quic_connection_s ngx_quic_connection_t; +typedef struct ngx_quic_server_id_s ngx_quic_server_id_t; +typedef struct ngx_quic_client_id_s ngx_quic_client_id_t; +typedef struct ngx_quic_send_ctx_s ngx_quic_send_ctx_t; +typedef struct ngx_quic_socket_s ngx_quic_socket_t; +typedef struct ngx_quic_path_s ngx_quic_path_t; +typedef struct ngx_quic_keys_s ngx_quic_keys_t; + +#if (NGX_QUIC_OPENSSL_COMPAT) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* RFC 9002, 6.2.2. Handshakes and New Paths: kInitialRtt */ +#define NGX_QUIC_INITIAL_RTT 333 /* ms */ + +#define NGX_QUIC_UNSET_PN (uint64_t) -1 + +#define NGX_QUIC_SEND_CTX_LAST (NGX_QUIC_ENCRYPTION_LAST - 1) + +/* 0-RTT and 1-RTT data exist in the same packet number space, + * so we have 3 packet number spaces: + * + * 0 - Initial + * 1 - Handshake + * 2 - 0-RTT and 1-RTT + */ +#define ngx_quic_get_send_ctx(qc, level) \ + ((level) == ssl_encryption_initial) ? &((qc)->send_ctx[0]) \ + : (((level) == ssl_encryption_handshake) ? &((qc)->send_ctx[1]) \ + : &((qc)->send_ctx[2])) + +#define ngx_quic_get_connection(c) \ + (((c)->udp) ? (((ngx_quic_socket_t *)((c)->udp))->quic) : NULL) + +#define ngx_quic_get_socket(c) ((ngx_quic_socket_t *)((c)->udp)) + +#define ngx_quic_init_rtt(qc) \ + (qc)->avg_rtt = NGX_QUIC_INITIAL_RTT; \ + (qc)->rttvar = NGX_QUIC_INITIAL_RTT / 2; \ + (qc)->min_rtt = NGX_TIMER_INFINITE; \ + (qc)->first_rtt = NGX_TIMER_INFINITE; \ + (qc)->latest_rtt = 0; + + +typedef enum { + NGX_QUIC_PATH_IDLE = 0, + NGX_QUIC_PATH_VALIDATING, + NGX_QUIC_PATH_WAITING, + NGX_QUIC_PATH_MTUD +} ngx_quic_path_state_e; + + +struct ngx_quic_client_id_s { + ngx_queue_t queue; + uint64_t seqnum; + size_t len; + u_char id[NGX_QUIC_CID_LEN_MAX]; + u_char sr_token[NGX_QUIC_SR_TOKEN_LEN]; + ngx_uint_t used; /* unsigned used:1; */ +}; + + +struct ngx_quic_server_id_s { + uint64_t seqnum; + size_t len; + u_char id[NGX_QUIC_CID_LEN_MAX]; +}; + + +struct ngx_quic_path_s { + ngx_queue_t queue; + struct sockaddr *sockaddr; + ngx_sockaddr_t sa; + socklen_t socklen; + ngx_quic_client_id_t *cid; + ngx_quic_path_state_e state; + ngx_msec_t expires; + ngx_uint_t tries; + ngx_uint_t tag; + size_t mtu; + size_t mtud; + size_t max_mtu; + off_t sent; + off_t received; + u_char challenge[2][8]; + uint64_t seqnum; + uint64_t mtu_pnum[NGX_QUIC_PATH_RETRIES]; + ngx_str_t addr_text; + u_char text[NGX_SOCKADDR_STRLEN]; + unsigned validated:1; + unsigned mtu_unvalidated:1; +}; + + +struct ngx_quic_socket_s { + ngx_udp_connection_t udp; + ngx_quic_connection_t *quic; + ngx_queue_t queue; + ngx_quic_server_id_t sid; + ngx_sockaddr_t sockaddr; + socklen_t socklen; + ngx_uint_t used; /* unsigned used:1; */ +}; + + +typedef struct { + ngx_rbtree_t tree; + ngx_rbtree_node_t sentinel; + + ngx_queue_t uninitialized; + ngx_queue_t free; + + uint64_t sent; + uint64_t recv_offset; + uint64_t recv_window; + uint64_t recv_last; + uint64_t recv_max_data; + uint64_t send_offset; + uint64_t send_max_data; + + uint64_t server_max_streams_uni; + uint64_t server_max_streams_bidi; + uint64_t server_streams_uni; + uint64_t server_streams_bidi; + + uint64_t client_max_streams_uni; + uint64_t client_max_streams_bidi; + uint64_t client_streams_uni; + uint64_t client_streams_bidi; + + ngx_uint_t initialized; + /* unsigned initialized:1; */ +} ngx_quic_streams_t; + + +typedef struct { + size_t in_flight; + size_t window; + size_t ssthresh; + ngx_msec_t recovery_start; +} ngx_quic_congestion_t; + + +/* + * RFC 9000, 12.3. Packet Numbers + * + * Conceptually, a packet number space is the context in which a packet + * can be processed and acknowledged. Initial packets can only be sent + * with Initial packet protection keys and acknowledged in packets that + * are also Initial packets. + */ +struct ngx_quic_send_ctx_s { + enum ssl_encryption_level_t level; + + ngx_quic_buffer_t crypto; + uint64_t crypto_sent; + + uint64_t pnum; /* to be sent */ + uint64_t largest_ack; /* received from peer */ + uint64_t largest_pn; /* received from peer */ + + ngx_queue_t frames; /* generated frames */ + ngx_queue_t sending; /* frames assigned to pkt */ + ngx_queue_t sent; /* frames waiting ACK */ + + uint64_t pending_ack; /* non sent ack-eliciting */ + uint64_t largest_range; + uint64_t first_range; + ngx_msec_t largest_received; + ngx_msec_t ack_delay_start; + ngx_uint_t nranges; + ngx_quic_ack_range_t ranges[NGX_QUIC_MAX_RANGES]; + ngx_uint_t send_ack; +}; + + +struct ngx_quic_connection_s { + uint32_t version; + + ngx_quic_path_t *path; + + ngx_queue_t sockets; + ngx_queue_t paths; + ngx_queue_t client_ids; + ngx_queue_t free_sockets; + ngx_queue_t free_paths; + ngx_queue_t free_client_ids; + + ngx_uint_t nsockets; + ngx_uint_t nclient_ids; + uint64_t max_retired_seqnum; + uint64_t client_seqnum; + uint64_t server_seqnum; + uint64_t path_seqnum; + + ngx_quic_tp_t tp; + ngx_quic_tp_t ctp; + + ngx_quic_send_ctx_t send_ctx[NGX_QUIC_SEND_CTX_LAST]; + + ngx_quic_keys_t *keys; + + ngx_quic_conf_t *conf; + + ngx_event_t push; + ngx_event_t pto; + ngx_event_t close; + ngx_event_t path_validation; + ngx_event_t key_update; + + ngx_msec_t last_cc; + + ngx_msec_t first_rtt; + ngx_msec_t latest_rtt; + ngx_msec_t avg_rtt; + ngx_msec_t min_rtt; + ngx_msec_t rttvar; + + ngx_uint_t pto_count; + + ngx_queue_t free_frames; + ngx_buf_t *free_bufs; + ngx_buf_t *free_shadow_bufs; + + ngx_uint_t nframes; +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_uint_t nbufs; + ngx_uint_t nshadowbufs; +#endif + +#if (NGX_QUIC_OPENSSL_COMPAT) + ngx_quic_compat_t *compat; +#endif + + ngx_quic_streams_t streams; + ngx_quic_congestion_t congestion; + + uint64_t rst_pnum; /* first on validated path */ + + off_t received; + + ngx_uint_t error; + enum ssl_encryption_level_t error_level; + ngx_uint_t error_ftype; + const char *error_reason; + + ngx_uint_t shutdown_code; + const char *shutdown_reason; + + unsigned error_app:1; + unsigned send_timer_set:1; + unsigned closing:1; + unsigned shutdown:1; + unsigned draining:1; + unsigned key_phase:1; + unsigned validated:1; + unsigned client_tp_done:1; +}; + + +ngx_int_t ngx_quic_apply_transport_params(ngx_connection_t *c, + ngx_quic_tp_t *ctp); +void ngx_quic_discard_ctx(ngx_connection_t *c, + enum ssl_encryption_level_t level); +void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc); +void ngx_quic_shutdown_quic(ngx_connection_t *c); + +#if (NGX_DEBUG) +void ngx_quic_connstate_dbg(ngx_connection_t *c); +#else +#define ngx_quic_connstate_dbg(c) +#endif + +#endif /* _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_ */ diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_connid.c b/src/deps/src/nginx/src/event/quic/ngx_event_quic_connid.c new file mode 100644 index 000000000..f50868205 --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_connid.c @@ -0,0 +1,502 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + +#define NGX_QUIC_MAX_SERVER_IDS 8 + + +#if (NGX_QUIC_BPF) +static ngx_int_t ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id); +#endif +static ngx_int_t ngx_quic_retire_client_id(ngx_connection_t *c, + ngx_quic_client_id_t *cid); +static ngx_quic_client_id_t *ngx_quic_alloc_client_id(ngx_connection_t *c, + ngx_quic_connection_t *qc); +static ngx_int_t ngx_quic_send_server_id(ngx_connection_t *c, + ngx_quic_server_id_t *sid); + + +ngx_int_t +ngx_quic_create_server_id(ngx_connection_t *c, u_char *id) +{ + if (RAND_bytes(id, NGX_QUIC_SERVER_CID_LEN) != 1) { + return NGX_ERROR; + } + +#if (NGX_QUIC_BPF) + if (ngx_quic_bpf_attach_id(c, id) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "quic bpf failed to generate socket key"); + /* ignore error, things still may work */ + } +#endif + + return NGX_OK; +} + + +#if (NGX_QUIC_BPF) + +static ngx_int_t +ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id) +{ + int fd; + uint64_t cookie; + socklen_t optlen; + + fd = c->listening->fd; + + optlen = sizeof(cookie); + + if (getsockopt(fd, SOL_SOCKET, SO_COOKIE, &cookie, &optlen) == -1) { + ngx_log_error(NGX_LOG_ERR, c->log, ngx_socket_errno, + "quic getsockopt(SO_COOKIE) failed"); + + return NGX_ERROR; + } + + ngx_quic_dcid_encode_key(id, cookie); + + return NGX_OK; +} + +#endif + + +ngx_int_t +ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, + ngx_quic_new_conn_id_frame_t *f) +{ + ngx_str_t id; + ngx_queue_t *q; + ngx_quic_frame_t *frame; + ngx_quic_client_id_t *cid, *item; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (f->seqnum < qc->max_retired_seqnum) { + /* + * RFC 9000, 19.15. NEW_CONNECTION_ID Frame + * + * An endpoint that receives a NEW_CONNECTION_ID frame with + * a sequence number smaller than the Retire Prior To field + * of a previously received NEW_CONNECTION_ID frame MUST send + * a corresponding RETIRE_CONNECTION_ID frame that retires + * the newly received connection ID, unless it has already + * done so for that sequence number. + */ + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID; + frame->u.retire_cid.sequence_number = f->seqnum; + + ngx_quic_queue_frame(qc, frame); + + goto retire; + } + + cid = NULL; + + for (q = ngx_queue_head(&qc->client_ids); + q != ngx_queue_sentinel(&qc->client_ids); + q = ngx_queue_next(q)) + { + item = ngx_queue_data(q, ngx_quic_client_id_t, queue); + + if (item->seqnum == f->seqnum) { + cid = item; + break; + } + } + + if (cid) { + /* + * Transmission errors, timeouts, and retransmissions might cause the + * same NEW_CONNECTION_ID frame to be received multiple times. + */ + + if (cid->len != f->len + || ngx_strncmp(cid->id, f->cid, f->len) != 0 + || ngx_strncmp(cid->sr_token, f->srt, NGX_QUIC_SR_TOKEN_LEN) != 0) + { + /* + * ..if a sequence number is used for different connection IDs, + * the endpoint MAY treat that receipt as a connection error + * of type PROTOCOL_VIOLATION. + */ + qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + qc->error_reason = "seqnum refers to different connection id/token"; + return NGX_ERROR; + } + + } else { + + id.data = f->cid; + id.len = f->len; + + if (ngx_quic_create_client_id(c, &id, f->seqnum, f->srt) == NULL) { + return NGX_ERROR; + } + } + +retire: + + if (qc->max_retired_seqnum && f->retire <= qc->max_retired_seqnum) { + /* + * Once a sender indicates a Retire Prior To value, smaller values sent + * in subsequent NEW_CONNECTION_ID frames have no effect. A receiver + * MUST ignore any Retire Prior To fields that do not increase the + * largest received Retire Prior To value. + */ + goto done; + } + + qc->max_retired_seqnum = f->retire; + + q = ngx_queue_head(&qc->client_ids); + + while (q != ngx_queue_sentinel(&qc->client_ids)) { + + cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); + q = ngx_queue_next(q); + + if (cid->seqnum >= f->retire) { + continue; + } + + if (ngx_quic_retire_client_id(c, cid) != NGX_OK) { + return NGX_ERROR; + } + } + +done: + + if (qc->nclient_ids > qc->tp.active_connection_id_limit) { + /* + * RFC 9000, 5.1.1. Issuing Connection IDs + * + * After processing a NEW_CONNECTION_ID frame and + * adding and retiring active connection IDs, if the number of active + * connection IDs exceeds the value advertised in its + * active_connection_id_limit transport parameter, an endpoint MUST + * close the connection with an error of type CONNECTION_ID_LIMIT_ERROR. + */ + qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR; + qc->error_reason = "too many connection ids received"; + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_retire_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid) +{ + ngx_queue_t *q; + ngx_quic_path_t *path; + ngx_quic_client_id_t *new_cid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (!cid->used) { + return ngx_quic_free_client_id(c, cid); + } + + /* we are going to retire client id which is in use */ + + q = ngx_queue_head(&qc->paths); + + while (q != ngx_queue_sentinel(&qc->paths)) { + + path = ngx_queue_data(q, ngx_quic_path_t, queue); + q = ngx_queue_next(q); + + if (path->cid != cid) { + continue; + } + + if (path == qc->path) { + /* this is the active path: update it with new CID */ + new_cid = ngx_quic_next_client_id(c); + if (new_cid == NULL) { + return NGX_ERROR; + } + + qc->path->cid = new_cid; + new_cid->used = 1; + + return ngx_quic_free_client_id(c, cid); + } + + return ngx_quic_free_path(c, path); + } + + return NGX_OK; +} + + +static ngx_quic_client_id_t * +ngx_quic_alloc_client_id(ngx_connection_t *c, ngx_quic_connection_t *qc) +{ + ngx_queue_t *q; + ngx_quic_client_id_t *cid; + + if (!ngx_queue_empty(&qc->free_client_ids)) { + + q = ngx_queue_head(&qc->free_client_ids); + cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); + + ngx_queue_remove(&cid->queue); + + ngx_memzero(cid, sizeof(ngx_quic_client_id_t)); + + } else { + + cid = ngx_pcalloc(c->pool, sizeof(ngx_quic_client_id_t)); + if (cid == NULL) { + return NULL; + } + } + + return cid; +} + + +ngx_quic_client_id_t * +ngx_quic_create_client_id(ngx_connection_t *c, ngx_str_t *id, + uint64_t seqnum, u_char *token) +{ + ngx_quic_client_id_t *cid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + cid = ngx_quic_alloc_client_id(c, qc); + if (cid == NULL) { + return NULL; + } + + cid->seqnum = seqnum; + + cid->len = id->len; + ngx_memcpy(cid->id, id->data, id->len); + + if (token) { + ngx_memcpy(cid->sr_token, token, NGX_QUIC_SR_TOKEN_LEN); + } + + ngx_queue_insert_tail(&qc->client_ids, &cid->queue); + qc->nclient_ids++; + + if (seqnum > qc->client_seqnum) { + qc->client_seqnum = seqnum; + } + + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic cid seq:%uL received id:%uz:%xV:%*xs", + cid->seqnum, id->len, id, + (size_t) NGX_QUIC_SR_TOKEN_LEN, cid->sr_token); + + return cid; +} + + +ngx_quic_client_id_t * +ngx_quic_next_client_id(ngx_connection_t *c) +{ + ngx_queue_t *q; + ngx_quic_client_id_t *cid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + for (q = ngx_queue_head(&qc->client_ids); + q != ngx_queue_sentinel(&qc->client_ids); + q = ngx_queue_next(q)) + { + cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); + + if (!cid->used) { + return cid; + } + } + + return NULL; +} + + +ngx_int_t +ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, + ngx_quic_retire_cid_frame_t *f) +{ + ngx_quic_socket_t *qsock; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (f->sequence_number >= qc->server_seqnum) { + /* + * RFC 9000, 19.16. + * + * Receipt of a RETIRE_CONNECTION_ID frame containing a sequence + * number greater than any previously sent to the peer MUST be + * treated as a connection error of type PROTOCOL_VIOLATION. + */ + qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + qc->error_reason = "sequence number of id to retire was never issued"; + + return NGX_ERROR; + } + + qsock = ngx_quic_get_socket(c); + + if (qsock->sid.seqnum == f->sequence_number) { + + /* + * RFC 9000, 19.16. + * + * The sequence number specified in a RETIRE_CONNECTION_ID frame MUST + * NOT refer to the Destination Connection ID field of the packet in + * which the frame is contained. The peer MAY treat this as a + * connection error of type PROTOCOL_VIOLATION. + */ + + qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + qc->error_reason = "sequence number of id to retire refers DCID"; + + return NGX_ERROR; + } + + qsock = ngx_quic_find_socket(c, f->sequence_number); + if (qsock == NULL) { + return NGX_OK; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic socket seq:%uL is retired", qsock->sid.seqnum); + + ngx_quic_close_socket(c, qsock); + + /* restore socket count up to a limit after deletion */ + if (ngx_quic_create_sockets(c) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_create_sockets(ngx_connection_t *c) +{ + ngx_uint_t n; + ngx_quic_socket_t *qsock; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + n = ngx_min(NGX_QUIC_MAX_SERVER_IDS, qc->ctp.active_connection_id_limit); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic create sockets has:%ui max:%ui", qc->nsockets, n); + + while (qc->nsockets < n) { + + qsock = ngx_quic_create_socket(c, qc); + if (qsock == NULL) { + return NGX_ERROR; + } + + if (ngx_quic_listen(c, qc, qsock) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_quic_send_server_id(c, &qsock->sid) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_send_server_id(ngx_connection_t *c, ngx_quic_server_id_t *sid) +{ + ngx_str_t dcid; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + dcid.len = sid->len; + dcid.data = sid->id; + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_NEW_CONNECTION_ID; + frame->u.ncid.seqnum = sid->seqnum; + frame->u.ncid.retire = 0; + frame->u.ncid.len = NGX_QUIC_SERVER_CID_LEN; + ngx_memcpy(frame->u.ncid.cid, sid->id, NGX_QUIC_SERVER_CID_LEN); + + if (ngx_quic_new_sr_token(c, &dcid, qc->conf->sr_token_key, + frame->u.ncid.srt) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_free_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid) +{ + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID; + frame->u.retire_cid.sequence_number = cid->seqnum; + + ngx_quic_queue_frame(qc, frame); + + /* we are no longer going to use this client id */ + + ngx_queue_remove(&cid->queue); + ngx_queue_insert_head(&qc->free_client_ids, &cid->queue); + + qc->nclient_ids--; + + return NGX_OK; +} diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_connid.h b/src/deps/src/nginx/src/event/quic/ngx_event_quic_connid.h new file mode 100644 index 000000000..33e9c65b9 --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_connid.h @@ -0,0 +1,29 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_CONNID_H_INCLUDED_ +#define _NGX_EVENT_QUIC_CONNID_H_INCLUDED_ + + +#include +#include + + +ngx_int_t ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, + ngx_quic_retire_cid_frame_t *f); +ngx_int_t ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, + ngx_quic_new_conn_id_frame_t *f); + +ngx_int_t ngx_quic_create_sockets(ngx_connection_t *c); +ngx_int_t ngx_quic_create_server_id(ngx_connection_t *c, u_char *id); + +ngx_quic_client_id_t *ngx_quic_create_client_id(ngx_connection_t *c, + ngx_str_t *id, uint64_t seqnum, u_char *token); +ngx_quic_client_id_t *ngx_quic_next_client_id(ngx_connection_t *c); +ngx_int_t ngx_quic_free_client_id(ngx_connection_t *c, + ngx_quic_client_id_t *cid); + +#endif /* _NGX_EVENT_QUIC_CONNID_H_INCLUDED_ */ diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_frames.c b/src/deps/src/nginx/src/event/quic/ngx_event_quic_frames.c new file mode 100644 index 000000000..42b7d9f41 --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_frames.c @@ -0,0 +1,894 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +#define NGX_QUIC_BUFFER_SIZE 4096 + +#define ngx_quic_buf_refs(b) (b)->shadow->num +#define ngx_quic_buf_inc_refs(b) ngx_quic_buf_refs(b)++ +#define ngx_quic_buf_dec_refs(b) ngx_quic_buf_refs(b)-- +#define ngx_quic_buf_set_refs(b, v) ngx_quic_buf_refs(b) = v + + +static ngx_buf_t *ngx_quic_alloc_buf(ngx_connection_t *c); +static void ngx_quic_free_buf(ngx_connection_t *c, ngx_buf_t *b); +static ngx_buf_t *ngx_quic_clone_buf(ngx_connection_t *c, ngx_buf_t *b); +static ngx_int_t ngx_quic_split_chain(ngx_connection_t *c, ngx_chain_t *cl, + off_t offset); + + +static ngx_buf_t * +ngx_quic_alloc_buf(ngx_connection_t *c) +{ + u_char *p; + ngx_buf_t *b; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + b = qc->free_bufs; + + if (b) { + qc->free_bufs = b->shadow; + p = b->start; + + } else { + b = qc->free_shadow_bufs; + + if (b) { + qc->free_shadow_bufs = b->shadow; + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic use shadow buffer n:%ui %ui", + ++qc->nbufs, --qc->nshadowbufs); +#endif + + } else { + b = ngx_palloc(c->pool, sizeof(ngx_buf_t)); + if (b == NULL) { + return NULL; + } + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic new buffer n:%ui", ++qc->nbufs); +#endif + } + + p = ngx_pnalloc(c->pool, NGX_QUIC_BUFFER_SIZE); + if (p == NULL) { + return NULL; + } + } + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic alloc buffer %p", b); +#endif + + ngx_memzero(b, sizeof(ngx_buf_t)); + + b->tag = (ngx_buf_tag_t) &ngx_quic_alloc_buf; + b->temporary = 1; + b->shadow = b; + + b->start = p; + b->pos = p; + b->last = p; + b->end = p + NGX_QUIC_BUFFER_SIZE; + + ngx_quic_buf_set_refs(b, 1); + + return b; +} + + +static void +ngx_quic_free_buf(ngx_connection_t *c, ngx_buf_t *b) +{ + ngx_buf_t *shadow; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ngx_quic_buf_dec_refs(b); + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic free buffer %p r:%ui", + b, (ngx_uint_t) ngx_quic_buf_refs(b)); +#endif + + shadow = b->shadow; + + if (ngx_quic_buf_refs(b) == 0) { + shadow->shadow = qc->free_bufs; + qc->free_bufs = shadow; + } + + if (b != shadow) { + b->shadow = qc->free_shadow_bufs; + qc->free_shadow_bufs = b; + } + +} + + +static ngx_buf_t * +ngx_quic_clone_buf(ngx_connection_t *c, ngx_buf_t *b) +{ + ngx_buf_t *nb; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + nb = qc->free_shadow_bufs; + + if (nb) { + qc->free_shadow_bufs = nb->shadow; + + } else { + nb = ngx_palloc(c->pool, sizeof(ngx_buf_t)); + if (nb == NULL) { + return NULL; + } + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic new shadow buffer n:%ui", ++qc->nshadowbufs); +#endif + } + + *nb = *b; + + ngx_quic_buf_inc_refs(b); + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic clone buffer %p %p r:%ui", + b, nb, (ngx_uint_t) ngx_quic_buf_refs(b)); +#endif + + return nb; +} + + +static ngx_int_t +ngx_quic_split_chain(ngx_connection_t *c, ngx_chain_t *cl, off_t offset) +{ + ngx_buf_t *b, *tb; + ngx_chain_t *tail; + + b = cl->buf; + + tail = ngx_alloc_chain_link(c->pool); + if (tail == NULL) { + return NGX_ERROR; + } + + tb = ngx_quic_clone_buf(c, b); + if (tb == NULL) { + return NGX_ERROR; + } + + tail->buf = tb; + + tb->pos += offset; + + b->last = tb->pos; + b->last_buf = 0; + + tail->next = cl->next; + cl->next = tail; + + return NGX_OK; +} + + +ngx_quic_frame_t * +ngx_quic_alloc_frame(ngx_connection_t *c) +{ + ngx_queue_t *q; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (!ngx_queue_empty(&qc->free_frames)) { + + q = ngx_queue_head(&qc->free_frames); + frame = ngx_queue_data(q, ngx_quic_frame_t, queue); + + ngx_queue_remove(&frame->queue); + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic reuse frame n:%ui", qc->nframes); +#endif + + } else if (qc->nframes < 10000) { + frame = ngx_palloc(c->pool, sizeof(ngx_quic_frame_t)); + if (frame == NULL) { + return NULL; + } + + ++qc->nframes; + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic alloc frame n:%ui", qc->nframes); +#endif + + } else { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic flood detected"); + return NULL; + } + + ngx_memzero(frame, sizeof(ngx_quic_frame_t)); + + return frame; +} + + +void +ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (frame->data) { + ngx_quic_free_chain(c, frame->data); + } + + ngx_queue_insert_head(&qc->free_frames, &frame->queue); + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic free frame n:%ui", qc->nframes); +#endif +} + + +void +ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in) +{ + ngx_chain_t *cl; + + while (in) { + cl = in; + in = in->next; + + ngx_quic_free_buf(c, cl->buf); + ngx_free_chain(c->pool, cl); + } +} + + +void +ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames) +{ + ngx_queue_t *q; + ngx_quic_frame_t *f; + + do { + q = ngx_queue_head(frames); + + if (q == ngx_queue_sentinel(frames)) { + break; + } + + ngx_queue_remove(q); + + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + ngx_quic_free_frame(c, f); + } while (1); +} + + +void +ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) +{ + ngx_quic_send_ctx_t *ctx; + + ctx = ngx_quic_get_send_ctx(qc, frame->level); + + ngx_queue_insert_tail(&ctx->frames, &frame->queue); + + frame->len = ngx_quic_create_frame(NULL, frame); + /* always succeeds */ + + if (qc->closing) { + return; + } + + ngx_post_event(&qc->push, &ngx_posted_events); +} + + +ngx_int_t +ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len) +{ + size_t shrink; + ngx_chain_t *out; + ngx_quic_frame_t *nf; + ngx_quic_buffer_t qb; + ngx_quic_ordered_frame_t *of, *onf; + + switch (f->type) { + case NGX_QUIC_FT_CRYPTO: + case NGX_QUIC_FT_STREAM: + break; + + default: + return NGX_DECLINED; + } + + if ((size_t) f->len <= len) { + return NGX_OK; + } + + shrink = f->len - len; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic split frame now:%uz need:%uz shrink:%uz", + f->len, len, shrink); + + of = &f->u.ord; + + if (of->length <= shrink) { + return NGX_DECLINED; + } + + of->length -= shrink; + f->len = ngx_quic_create_frame(NULL, f); + + if ((size_t) f->len > len) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, "could not split QUIC frame"); + return NGX_ERROR; + } + + ngx_memzero(&qb, sizeof(ngx_quic_buffer_t)); + qb.chain = f->data; + + out = ngx_quic_read_buffer(c, &qb, of->length); + if (out == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + f->data = out; + + nf = ngx_quic_alloc_frame(c); + if (nf == NULL) { + return NGX_ERROR; + } + + *nf = *f; + onf = &nf->u.ord; + onf->offset += of->length; + onf->length = shrink; + nf->len = ngx_quic_create_frame(NULL, nf); + nf->data = qb.chain; + + if (f->type == NGX_QUIC_FT_STREAM) { + f->u.stream.fin = 0; + } + + ngx_queue_insert_after(&f->queue, &nf->queue); + + return NGX_OK; +} + + +ngx_chain_t * +ngx_quic_copy_buffer(ngx_connection_t *c, u_char *data, size_t len) +{ + ngx_buf_t buf; + ngx_chain_t cl, *out; + ngx_quic_buffer_t qb; + + ngx_memzero(&buf, sizeof(ngx_buf_t)); + + buf.pos = data; + buf.last = buf.pos + len; + buf.temporary = 1; + + cl.buf = &buf; + cl.next = NULL; + + ngx_memzero(&qb, sizeof(ngx_quic_buffer_t)); + + if (ngx_quic_write_buffer(c, &qb, &cl, len, 0) == NGX_CHAIN_ERROR) { + return NGX_CHAIN_ERROR; + } + + out = ngx_quic_read_buffer(c, &qb, len); + if (out == NGX_CHAIN_ERROR) { + return NGX_CHAIN_ERROR; + } + + ngx_quic_free_buffer(c, &qb); + + return out; +} + + +ngx_chain_t * +ngx_quic_read_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, uint64_t limit) +{ + uint64_t n; + ngx_buf_t *b; + ngx_chain_t *out, **ll; + + out = qb->chain; + + for (ll = &out; *ll; ll = &(*ll)->next) { + b = (*ll)->buf; + + if (b->sync) { + /* hole */ + break; + } + + if (limit == 0) { + break; + } + + n = b->last - b->pos; + + if (n > limit) { + if (ngx_quic_split_chain(c, *ll, limit) != NGX_OK) { + return NGX_CHAIN_ERROR; + } + + n = limit; + } + + limit -= n; + qb->offset += n; + } + + if (qb->offset >= qb->last_offset) { + qb->last_chain = NULL; + } + + qb->chain = *ll; + *ll = NULL; + + return out; +} + + +void +ngx_quic_skip_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, + uint64_t offset) +{ + size_t n; + ngx_buf_t *b; + ngx_chain_t *cl; + + while (qb->chain) { + if (qb->offset >= offset) { + break; + } + + cl = qb->chain; + b = cl->buf; + n = b->last - b->pos; + + if (qb->offset + n > offset) { + n = offset - qb->offset; + b->pos += n; + qb->offset += n; + break; + } + + qb->offset += n; + qb->chain = cl->next; + + cl->next = NULL; + ngx_quic_free_chain(c, cl); + } + + if (qb->chain == NULL) { + qb->offset = offset; + } + + if (qb->offset >= qb->last_offset) { + qb->last_chain = NULL; + } +} + + +ngx_chain_t * +ngx_quic_alloc_chain(ngx_connection_t *c) +{ + ngx_chain_t *cl; + + cl = ngx_alloc_chain_link(c->pool); + if (cl == NULL) { + return NULL; + } + + cl->buf = ngx_quic_alloc_buf(c); + if (cl->buf == NULL) { + return NULL; + } + + return cl; +} + + +ngx_chain_t * +ngx_quic_write_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, + ngx_chain_t *in, uint64_t limit, uint64_t offset) +{ + u_char *p; + uint64_t n, base; + ngx_buf_t *b; + ngx_chain_t *cl, **chain; + + if (qb->last_chain && offset >= qb->last_offset) { + base = qb->last_offset; + chain = &qb->last_chain; + + } else { + base = qb->offset; + chain = &qb->chain; + } + + while (in && limit) { + + if (offset < base) { + n = ngx_min((uint64_t) (in->buf->last - in->buf->pos), + ngx_min(base - offset, limit)); + + in->buf->pos += n; + offset += n; + limit -= n; + + if (in->buf->pos == in->buf->last) { + in = in->next; + } + + continue; + } + + cl = *chain; + + if (cl == NULL) { + cl = ngx_quic_alloc_chain(c); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + cl->buf->last = cl->buf->end; + cl->buf->sync = 1; /* hole */ + cl->next = NULL; + *chain = cl; + } + + b = cl->buf; + n = b->last - b->pos; + + if (base + n <= offset) { + base += n; + chain = &cl->next; + continue; + } + + if (b->sync && offset > base) { + if (ngx_quic_split_chain(c, cl, offset - base) != NGX_OK) { + return NGX_CHAIN_ERROR; + } + + continue; + } + + p = b->pos + (offset - base); + + while (in) { + + if (!ngx_buf_in_memory(in->buf) || in->buf->pos == in->buf->last) { + in = in->next; + continue; + } + + if (p == b->last || limit == 0) { + break; + } + + n = ngx_min(b->last - p, in->buf->last - in->buf->pos); + n = ngx_min(n, limit); + + if (b->sync) { + ngx_memcpy(p, in->buf->pos, n); + qb->size += n; + } + + p += n; + in->buf->pos += n; + offset += n; + limit -= n; + } + + if (b->sync && p == b->last) { + b->sync = 0; + continue; + } + + if (b->sync && p != b->pos) { + if (ngx_quic_split_chain(c, cl, p - b->pos) != NGX_OK) { + return NGX_CHAIN_ERROR; + } + + b->sync = 0; + } + } + + qb->last_offset = base; + qb->last_chain = *chain; + + return in; +} + + +void +ngx_quic_free_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb) +{ + ngx_quic_free_chain(c, qb->chain); + + qb->chain = NULL; +} + + +#if (NGX_DEBUG) + +void +ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) +{ + u_char *p, *last, *pos, *end; + ssize_t n; + uint64_t gap, range, largest, smallest; + ngx_uint_t i; + u_char buf[NGX_MAX_ERROR_STR]; + + p = buf; + last = buf + sizeof(buf); + + switch (f->type) { + + case NGX_QUIC_FT_CRYPTO: + p = ngx_slprintf(p, last, "CRYPTO len:%uL off:%uL", + f->u.crypto.length, f->u.crypto.offset); + +#ifdef NGX_QUIC_DEBUG_FRAMES + { + ngx_chain_t *cl; + + p = ngx_slprintf(p, last, " data:"); + + for (cl = f->data; cl; cl = cl->next) { + p = ngx_slprintf(p, last, "%*xs", + cl->buf->last - cl->buf->pos, cl->buf->pos); + } + } +#endif + + break; + + case NGX_QUIC_FT_PADDING: + p = ngx_slprintf(p, last, "PADDING"); + break; + + case NGX_QUIC_FT_ACK: + case NGX_QUIC_FT_ACK_ECN: + + p = ngx_slprintf(p, last, "ACK n:%ui delay:%uL ", + f->u.ack.range_count, f->u.ack.delay); + + if (f->data) { + pos = f->data->buf->pos; + end = f->data->buf->last; + + } else { + pos = NULL; + end = NULL; + } + + largest = f->u.ack.largest; + smallest = f->u.ack.largest - f->u.ack.first_range; + + if (largest == smallest) { + p = ngx_slprintf(p, last, "%uL", largest); + + } else { + p = ngx_slprintf(p, last, "%uL-%uL", largest, smallest); + } + + for (i = 0; i < f->u.ack.range_count; i++) { + n = ngx_quic_parse_ack_range(log, pos, end, &gap, &range); + if (n == NGX_ERROR) { + break; + } + + pos += n; + + largest = smallest - gap - 2; + smallest = largest - range; + + if (largest == smallest) { + p = ngx_slprintf(p, last, " %uL", largest); + + } else { + p = ngx_slprintf(p, last, " %uL-%uL", largest, smallest); + } + } + + if (f->type == NGX_QUIC_FT_ACK_ECN) { + p = ngx_slprintf(p, last, " ECN counters ect0:%uL ect1:%uL ce:%uL", + f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce); + } + break; + + case NGX_QUIC_FT_PING: + p = ngx_slprintf(p, last, "PING"); + break; + + case NGX_QUIC_FT_NEW_CONNECTION_ID: + p = ngx_slprintf(p, last, + "NEW_CONNECTION_ID seq:%uL retire:%uL len:%ud", + f->u.ncid.seqnum, f->u.ncid.retire, f->u.ncid.len); + break; + + case NGX_QUIC_FT_RETIRE_CONNECTION_ID: + p = ngx_slprintf(p, last, "RETIRE_CONNECTION_ID seqnum:%uL", + f->u.retire_cid.sequence_number); + break; + + case NGX_QUIC_FT_CONNECTION_CLOSE: + case NGX_QUIC_FT_CONNECTION_CLOSE_APP: + p = ngx_slprintf(p, last, "CONNECTION_CLOSE%s err:%ui", + f->type == NGX_QUIC_FT_CONNECTION_CLOSE ? "" : "_APP", + f->u.close.error_code); + + if (f->u.close.reason.len) { + p = ngx_slprintf(p, last, " %V", &f->u.close.reason); + } + + if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { + p = ngx_slprintf(p, last, " ft:%ui", f->u.close.frame_type); + } + + break; + + case NGX_QUIC_FT_STREAM: + p = ngx_slprintf(p, last, "STREAM id:0x%xL", f->u.stream.stream_id); + + if (f->u.stream.off) { + p = ngx_slprintf(p, last, " off:%uL", f->u.stream.offset); + } + + if (f->u.stream.len) { + p = ngx_slprintf(p, last, " len:%uL", f->u.stream.length); + } + + if (f->u.stream.fin) { + p = ngx_slprintf(p, last, " fin:1"); + } + +#ifdef NGX_QUIC_DEBUG_FRAMES + { + ngx_chain_t *cl; + + p = ngx_slprintf(p, last, " data:"); + + for (cl = f->data; cl; cl = cl->next) { + p = ngx_slprintf(p, last, "%*xs", + cl->buf->last - cl->buf->pos, cl->buf->pos); + } + } +#endif + + break; + + case NGX_QUIC_FT_MAX_DATA: + p = ngx_slprintf(p, last, "MAX_DATA max_data:%uL on recv", + f->u.max_data.max_data); + break; + + case NGX_QUIC_FT_RESET_STREAM: + p = ngx_slprintf(p, last, "RESET_STREAM" + " id:0x%xL error_code:0x%xL final_size:0x%xL", + f->u.reset_stream.id, f->u.reset_stream.error_code, + f->u.reset_stream.final_size); + break; + + case NGX_QUIC_FT_STOP_SENDING: + p = ngx_slprintf(p, last, "STOP_SENDING id:0x%xL err:0x%xL", + f->u.stop_sending.id, f->u.stop_sending.error_code); + break; + + case NGX_QUIC_FT_STREAMS_BLOCKED: + case NGX_QUIC_FT_STREAMS_BLOCKED2: + p = ngx_slprintf(p, last, "STREAMS_BLOCKED limit:%uL bidi:%ui", + f->u.streams_blocked.limit, f->u.streams_blocked.bidi); + break; + + case NGX_QUIC_FT_MAX_STREAMS: + case NGX_QUIC_FT_MAX_STREAMS2: + p = ngx_slprintf(p, last, "MAX_STREAMS limit:%uL bidi:%ui", + f->u.max_streams.limit, f->u.max_streams.bidi); + break; + + case NGX_QUIC_FT_MAX_STREAM_DATA: + p = ngx_slprintf(p, last, "MAX_STREAM_DATA id:0x%xL limit:%uL", + f->u.max_stream_data.id, f->u.max_stream_data.limit); + break; + + + case NGX_QUIC_FT_DATA_BLOCKED: + p = ngx_slprintf(p, last, "DATA_BLOCKED limit:%uL", + f->u.data_blocked.limit); + break; + + case NGX_QUIC_FT_STREAM_DATA_BLOCKED: + p = ngx_slprintf(p, last, "STREAM_DATA_BLOCKED id:0x%xL limit:%uL", + f->u.stream_data_blocked.id, + f->u.stream_data_blocked.limit); + break; + + case NGX_QUIC_FT_PATH_CHALLENGE: + p = ngx_slprintf(p, last, "PATH_CHALLENGE data:0x%*xs", + sizeof(f->u.path_challenge.data), + f->u.path_challenge.data); + break; + + case NGX_QUIC_FT_PATH_RESPONSE: + p = ngx_slprintf(p, last, "PATH_RESPONSE data:0x%*xs", + sizeof(f->u.path_challenge.data), + f->u.path_challenge.data); + break; + + case NGX_QUIC_FT_NEW_TOKEN: + p = ngx_slprintf(p, last, "NEW_TOKEN"); + +#ifdef NGX_QUIC_DEBUG_FRAMES + { + ngx_chain_t *cl; + + p = ngx_slprintf(p, last, " token:"); + + for (cl = f->data; cl; cl = cl->next) { + p = ngx_slprintf(p, last, "%*xs", + cl->buf->last - cl->buf->pos, cl->buf->pos); + } + } +#endif + + break; + + case NGX_QUIC_FT_HANDSHAKE_DONE: + p = ngx_slprintf(p, last, "HANDSHAKE DONE"); + break; + + default: + p = ngx_slprintf(p, last, "unknown type 0x%xi", f->type); + break; + } + + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, log, 0, "quic frame %s %s:%uL %*s", + tx ? "tx" : "rx", ngx_quic_level_name(f->level), f->pnum, + p - buf, buf); +} + +#endif diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_frames.h b/src/deps/src/nginx/src/event/quic/ngx_event_quic_frames.h new file mode 100644 index 000000000..2d55af9de --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_frames.h @@ -0,0 +1,45 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_ +#define _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_ + + +#include +#include + + +typedef ngx_int_t (*ngx_quic_frame_handler_pt)(ngx_connection_t *c, + ngx_quic_frame_t *frame, void *data); + + +ngx_quic_frame_t *ngx_quic_alloc_frame(ngx_connection_t *c); +void ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame); +void ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames); +void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame); +ngx_int_t ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, + size_t len); + +ngx_chain_t *ngx_quic_alloc_chain(ngx_connection_t *c); +void ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in); + +ngx_chain_t *ngx_quic_copy_buffer(ngx_connection_t *c, u_char *data, + size_t len); +ngx_chain_t *ngx_quic_read_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, + uint64_t limit); +ngx_chain_t *ngx_quic_write_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, + ngx_chain_t *in, uint64_t limit, uint64_t offset); +void ngx_quic_skip_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, + uint64_t offset); +void ngx_quic_free_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb); + +#if (NGX_DEBUG) +void ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx); +#else +#define ngx_quic_log_frame(log, f, tx) +#endif + +#endif /* _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_ */ diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_migration.c b/src/deps/src/nginx/src/event/quic/ngx_event_quic_migration.c new file mode 100644 index 000000000..2d1467e14 --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_migration.c @@ -0,0 +1,1003 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +#define NGX_QUIC_PATH_MTU_DELAY 100 +#define NGX_QUIC_PATH_MTU_PRECISION 16 + + +static void ngx_quic_set_connection_path(ngx_connection_t *c, + ngx_quic_path_t *path); +static ngx_int_t ngx_quic_validate_path(ngx_connection_t *c, + ngx_quic_path_t *path); +static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c, + ngx_quic_path_t *path); +static void ngx_quic_set_path_timer(ngx_connection_t *c); +static ngx_int_t ngx_quic_expire_path_validation(ngx_connection_t *c, + ngx_quic_path_t *path); +static ngx_int_t ngx_quic_expire_path_mtu_delay(ngx_connection_t *c, + ngx_quic_path_t *path); +static ngx_int_t ngx_quic_expire_path_mtu_discovery(ngx_connection_t *c, + ngx_quic_path_t *path); +static ngx_quic_path_t *ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag); +static ngx_int_t ngx_quic_send_path_mtu_probe(ngx_connection_t *c, + ngx_quic_path_t *path); + + +ngx_int_t +ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f) +{ + size_t min; + ngx_quic_frame_t *fp; + ngx_quic_connection_t *qc; + + if (pkt->level != ssl_encryption_application || pkt->path_challenged) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ignoring PATH_CHALLENGE"); + return NGX_OK; + } + + pkt->path_challenged = 1; + + qc = ngx_quic_get_connection(c); + + fp = ngx_quic_alloc_frame(c); + if (fp == NULL) { + return NGX_ERROR; + } + + fp->level = ssl_encryption_application; + fp->type = NGX_QUIC_FT_PATH_RESPONSE; + fp->u.path_response = *f; + + /* + * RFC 9000, 8.2.2. Path Validation Responses + * + * A PATH_RESPONSE frame MUST be sent on the network path where the + * PATH_CHALLENGE frame was received. + */ + + /* + * An endpoint MUST expand datagrams that contain a PATH_RESPONSE frame + * to at least the smallest allowed maximum datagram size of 1200 bytes. + * ... + * However, an endpoint MUST NOT expand the datagram containing the + * PATH_RESPONSE if the resulting data exceeds the anti-amplification limit. + */ + + min = (ngx_quic_path_limit(c, pkt->path, 1200) < 1200) ? 0 : 1200; + + if (ngx_quic_frame_sendto(c, fp, min, pkt->path) == NGX_ERROR) { + return NGX_ERROR; + } + + if (pkt->path == qc->path) { + /* + * RFC 9000, 9.3.3. Off-Path Packet Forwarding + * + * An endpoint that receives a PATH_CHALLENGE on an active path SHOULD + * send a non-probing packet in response. + */ + + fp = ngx_quic_alloc_frame(c); + if (fp == NULL) { + return NGX_ERROR; + } + + fp->level = ssl_encryption_application; + fp->type = NGX_QUIC_FT_PING; + + ngx_quic_queue_frame(qc, fp); + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_path_response_frame(ngx_connection_t *c, + ngx_quic_path_challenge_frame_t *f) +{ + ngx_uint_t rst; + ngx_queue_t *q; + ngx_quic_path_t *path, *prev; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + /* + * RFC 9000, 8.2.3. Successful Path Validation + * + * A PATH_RESPONSE frame received on any network path validates the path + * on which the PATH_CHALLENGE was sent. + */ + + for (q = ngx_queue_head(&qc->paths); + q != ngx_queue_sentinel(&qc->paths); + q = ngx_queue_next(q)) + { + path = ngx_queue_data(q, ngx_quic_path_t, queue); + + if (path->state != NGX_QUIC_PATH_VALIDATING) { + continue; + } + + if (ngx_memcmp(path->challenge[0], f->data, sizeof(f->data)) == 0 + || ngx_memcmp(path->challenge[1], f->data, sizeof(f->data)) == 0) + { + goto valid; + } + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stale PATH_RESPONSE ignored"); + + return NGX_OK; + +valid: + + /* + * RFC 9000, 9.4. Loss Detection and Congestion Control + * + * On confirming a peer's ownership of its new address, + * an endpoint MUST immediately reset the congestion controller + * and round-trip time estimator for the new path to initial values + * unless the only change in the peer's address is its port number. + */ + + rst = 1; + + prev = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP); + + if (prev != NULL) { + + if (ngx_cmp_sockaddr(prev->sockaddr, prev->socklen, + path->sockaddr, path->socklen, 0) + == NGX_OK) + { + /* address did not change */ + rst = 0; + + path->mtu = prev->mtu; + path->max_mtu = prev->max_mtu; + path->mtu_unvalidated = 0; + } + } + + if (rst) { + /* prevent old path packets contribution to congestion control */ + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + qc->rst_pnum = ctx->pnum; + + ngx_memzero(&qc->congestion, sizeof(ngx_quic_congestion_t)); + + qc->congestion.window = ngx_min(10 * qc->tp.max_udp_payload_size, + ngx_max(2 * qc->tp.max_udp_payload_size, + 14720)); + qc->congestion.ssthresh = (size_t) -1; + qc->congestion.recovery_start = ngx_current_msec; + + ngx_quic_init_rtt(qc); + } + + path->validated = 1; + + if (path->mtu_unvalidated) { + path->mtu_unvalidated = 0; + return ngx_quic_validate_path(c, path); + } + + /* + * RFC 9000, 9.3. Responding to Connection Migration + * + * After verifying a new client address, the server SHOULD + * send new address validation tokens (Section 8) to the client. + */ + + if (ngx_quic_send_new_token(c, path) != NGX_OK) { + return NGX_ERROR; + } + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic path seq:%uL addr:%V successfully validated", + path->seqnum, &path->addr_text); + + ngx_quic_path_dbg(c, "is validated", path); + + ngx_quic_discover_path_mtu(c, path); + + return NGX_OK; +} + + +ngx_quic_path_t * +ngx_quic_new_path(ngx_connection_t *c, + struct sockaddr *sockaddr, socklen_t socklen, ngx_quic_client_id_t *cid) +{ + ngx_queue_t *q; + ngx_quic_path_t *path; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (!ngx_queue_empty(&qc->free_paths)) { + + q = ngx_queue_head(&qc->free_paths); + path = ngx_queue_data(q, ngx_quic_path_t, queue); + + ngx_queue_remove(&path->queue); + + ngx_memzero(path, sizeof(ngx_quic_path_t)); + + } else { + + path = ngx_pcalloc(c->pool, sizeof(ngx_quic_path_t)); + if (path == NULL) { + return NULL; + } + } + + ngx_queue_insert_tail(&qc->paths, &path->queue); + + path->cid = cid; + cid->used = 1; + + path->seqnum = qc->path_seqnum++; + + path->sockaddr = &path->sa.sockaddr; + path->socklen = socklen; + ngx_memcpy(path->sockaddr, sockaddr, socklen); + + path->addr_text.data = path->text; + path->addr_text.len = ngx_sock_ntop(sockaddr, socklen, path->text, + NGX_SOCKADDR_STRLEN, 1); + + path->mtu = NGX_QUIC_MIN_INITIAL_SIZE; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path seq:%uL created addr:%V", + path->seqnum, &path->addr_text); + return path; +} + + +static ngx_quic_path_t * +ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag) +{ + ngx_queue_t *q; + ngx_quic_path_t *path; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + for (q = ngx_queue_head(&qc->paths); + q != ngx_queue_sentinel(&qc->paths); + q = ngx_queue_next(q)) + { + path = ngx_queue_data(q, ngx_quic_path_t, queue); + + if (path->tag == tag) { + return path; + } + } + + return NULL; +} + + +ngx_int_t +ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + off_t len; + ngx_queue_t *q; + ngx_quic_path_t *path, *probe; + ngx_quic_socket_t *qsock; + ngx_quic_send_ctx_t *ctx; + ngx_quic_client_id_t *cid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + qsock = ngx_quic_get_socket(c); + + len = pkt->raw->last - pkt->raw->start; + + if (c->udp->buffer == NULL) { + /* first ever packet in connection, path already exists */ + path = qc->path; + goto update; + } + + probe = NULL; + + for (q = ngx_queue_head(&qc->paths); + q != ngx_queue_sentinel(&qc->paths); + q = ngx_queue_next(q)) + { + path = ngx_queue_data(q, ngx_quic_path_t, queue); + + if (ngx_cmp_sockaddr(&qsock->sockaddr.sockaddr, qsock->socklen, + path->sockaddr, path->socklen, 1) + == NGX_OK) + { + goto update; + } + + if (path->tag == NGX_QUIC_PATH_PROBE) { + probe = path; + } + } + + /* packet from new path, drop current probe, if any */ + + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + /* + * only accept highest-numbered packets to prevent connection id + * exhaustion by excessive probing packets from unknown paths + */ + if (pkt->pn != ctx->largest_pn) { + return NGX_DONE; + } + + if (probe && ngx_quic_free_path(c, probe) != NGX_OK) { + return NGX_ERROR; + } + + /* new path requires new client id */ + cid = ngx_quic_next_client_id(c); + if (cid == NULL) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic no available client ids for new path"); + /* stop processing of this datagram */ + return NGX_DONE; + } + + path = ngx_quic_new_path(c, &qsock->sockaddr.sockaddr, qsock->socklen, cid); + if (path == NULL) { + return NGX_ERROR; + } + + path->tag = NGX_QUIC_PATH_PROBE; + + /* + * client arrived using new path and previously seen DCID, + * this indicates NAT rebinding (or bad client) + */ + if (qsock->used) { + pkt->rebound = 1; + } + +update: + + qsock->used = 1; + pkt->path = path; + + /* TODO: this may be too late in some cases; + * for example, if error happens during decrypt(), we cannot + * send CC, if error happens in 1st packet, due to amplification + * limit, because path->received = 0 + * + * should we account garbage as received or only decrypting packets? + */ + path->received += len; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet len:%O via sock seq:%L path seq:%uL", + len, (int64_t) qsock->sid.seqnum, path->seqnum); + ngx_quic_path_dbg(c, "status", path); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_free_path(ngx_connection_t *c, ngx_quic_path_t *path) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ngx_queue_remove(&path->queue); + ngx_queue_insert_head(&qc->free_paths, &path->queue); + + /* + * invalidate CID that is no longer usable for any other path; + * this also requests new CIDs from client + */ + if (path->cid) { + if (ngx_quic_free_client_id(c, path->cid) != NGX_OK) { + return NGX_ERROR; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path seq:%uL addr:%V retired", + path->seqnum, &path->addr_text); + + return NGX_OK; +} + + +static void +ngx_quic_set_connection_path(ngx_connection_t *c, ngx_quic_path_t *path) +{ + ngx_memcpy(c->sockaddr, path->sockaddr, path->socklen); + c->socklen = path->socklen; + + if (c->addr_text.data) { + c->addr_text.len = ngx_sock_ntop(c->sockaddr, c->socklen, + c->addr_text.data, + c->listening->addr_text_max_len, 0); + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic send path set to seq:%uL addr:%V", + path->seqnum, &path->addr_text); +} + + +ngx_int_t +ngx_quic_handle_migration(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + ngx_quic_path_t *next, *bkp; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + /* got non-probing packet via non-active path */ + + qc = ngx_quic_get_connection(c); + + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + /* + * RFC 9000, 9.3. Responding to Connection Migration + * + * An endpoint only changes the address to which it sends packets in + * response to the highest-numbered non-probing packet. + */ + if (pkt->pn != ctx->largest_pn) { + return NGX_OK; + } + + next = pkt->path; + + /* + * RFC 9000, 9.3.3: + * + * In response to an apparent migration, endpoints MUST validate the + * previously active path using a PATH_CHALLENGE frame. + */ + if (pkt->rebound) { + + /* NAT rebinding: client uses new path with old SID */ + if (ngx_quic_validate_path(c, qc->path) != NGX_OK) { + return NGX_ERROR; + } + } + + if (qc->path->validated) { + + if (next->tag != NGX_QUIC_PATH_BACKUP) { + /* can delete backup path, if any */ + bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP); + + if (bkp && ngx_quic_free_path(c, bkp) != NGX_OK) { + return NGX_ERROR; + } + } + + qc->path->tag = NGX_QUIC_PATH_BACKUP; + ngx_quic_path_dbg(c, "is now backup", qc->path); + + } else { + if (ngx_quic_free_path(c, qc->path) != NGX_OK) { + return NGX_ERROR; + } + } + + /* switch active path to migrated */ + qc->path = next; + qc->path->tag = NGX_QUIC_PATH_ACTIVE; + + ngx_quic_set_connection_path(c, next); + + if (!next->validated && next->state != NGX_QUIC_PATH_VALIDATING) { + if (ngx_quic_validate_path(c, next) != NGX_OK) { + return NGX_ERROR; + } + } + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic migrated to path seq:%uL addr:%V", + qc->path->seqnum, &qc->path->addr_text); + + ngx_quic_path_dbg(c, "is now active", qc->path); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_path_t *path) +{ + ngx_msec_t pto; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic initiated validation of path seq:%uL", path->seqnum); + + path->tries = 0; + + if (RAND_bytes((u_char *) path->challenge, sizeof(path->challenge)) != 1) { + return NGX_ERROR; + } + + (void) ngx_quic_send_path_challenge(c, path); + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + pto = ngx_max(ngx_quic_pto(c, ctx), 1000); + + path->expires = ngx_current_msec + pto; + path->state = NGX_QUIC_PATH_VALIDATING; + + ngx_quic_set_path_timer(c); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path) +{ + size_t min; + ngx_uint_t n; + ngx_quic_frame_t *frame; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path seq:%uL send path_challenge tries:%ui", + path->seqnum, path->tries); + + for (n = 0; n < 2; n++) { + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_PATH_CHALLENGE; + + ngx_memcpy(frame->u.path_challenge.data, path->challenge[n], 8); + + /* + * RFC 9000, 8.2.1. Initiating Path Validation + * + * An endpoint MUST expand datagrams that contain a PATH_CHALLENGE frame + * to at least the smallest allowed maximum datagram size of 1200 bytes, + * unless the anti-amplification limit for the path does not permit + * sending a datagram of this size. + */ + + if (path->mtu_unvalidated + || ngx_quic_path_limit(c, path, 1200) < 1200) + { + min = 0; + path->mtu_unvalidated = 1; + + } else { + min = 1200; + } + + if (ngx_quic_frame_sendto(c, frame, min, path) == NGX_ERROR) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +void +ngx_quic_discover_path_mtu(ngx_connection_t *c, ngx_quic_path_t *path) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (path->max_mtu) { + if (path->max_mtu - path->mtu <= NGX_QUIC_PATH_MTU_PRECISION) { + path->state = NGX_QUIC_PATH_IDLE; + ngx_quic_set_path_timer(c); + return; + } + + path->mtud = (path->mtu + path->max_mtu) / 2; + + } else { + path->mtud = path->mtu * 2; + + if (path->mtud >= qc->ctp.max_udp_payload_size) { + path->mtud = qc->ctp.max_udp_payload_size; + path->max_mtu = qc->ctp.max_udp_payload_size; + } + } + + path->state = NGX_QUIC_PATH_WAITING; + path->expires = ngx_current_msec + NGX_QUIC_PATH_MTU_DELAY; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path seq:%uL schedule mtu:%uz", + path->seqnum, path->mtud); + + ngx_quic_set_path_timer(c); +} + + +static void +ngx_quic_set_path_timer(ngx_connection_t *c) +{ + ngx_msec_t now; + ngx_queue_t *q; + ngx_msec_int_t left, next; + ngx_quic_path_t *path; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + now = ngx_current_msec; + next = -1; + + for (q = ngx_queue_head(&qc->paths); + q != ngx_queue_sentinel(&qc->paths); + q = ngx_queue_next(q)) + { + path = ngx_queue_data(q, ngx_quic_path_t, queue); + + if (path->state == NGX_QUIC_PATH_IDLE) { + continue; + } + + left = path->expires - now; + left = ngx_max(left, 1); + + if (next == -1 || left < next) { + next = left; + } + } + + if (next != -1) { + ngx_add_timer(&qc->path_validation, next); + + } else if (qc->path_validation.timer_set) { + ngx_del_timer(&qc->path_validation); + } +} + + +void +ngx_quic_path_handler(ngx_event_t *ev) +{ + ngx_msec_t now; + ngx_queue_t *q; + ngx_msec_int_t left; + ngx_quic_path_t *path; + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + c = ev->data; + qc = ngx_quic_get_connection(c); + + now = ngx_current_msec; + + q = ngx_queue_head(&qc->paths); + + while (q != ngx_queue_sentinel(&qc->paths)) { + + path = ngx_queue_data(q, ngx_quic_path_t, queue); + q = ngx_queue_next(q); + + if (path->state == NGX_QUIC_PATH_IDLE) { + continue; + } + + left = path->expires - now; + + if (left > 0) { + continue; + } + + switch (path->state) { + case NGX_QUIC_PATH_VALIDATING: + if (ngx_quic_expire_path_validation(c, path) != NGX_OK) { + goto failed; + } + + break; + + case NGX_QUIC_PATH_WAITING: + if (ngx_quic_expire_path_mtu_delay(c, path) != NGX_OK) { + goto failed; + } + + break; + + case NGX_QUIC_PATH_MTUD: + if (ngx_quic_expire_path_mtu_discovery(c, path) != NGX_OK) { + goto failed; + } + + break; + + default: + break; + } + } + + ngx_quic_set_path_timer(c); + + return; + +failed: + + ngx_quic_close_connection(c, NGX_ERROR); +} + + +static ngx_int_t +ngx_quic_expire_path_validation(ngx_connection_t *c, ngx_quic_path_t *path) +{ + ngx_msec_int_t pto; + ngx_quic_path_t *bkp; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + + if (++path->tries < NGX_QUIC_PATH_RETRIES) { + pto = ngx_max(ngx_quic_pto(c, ctx), 1000) << path->tries; + path->expires = ngx_current_msec + pto; + + (void) ngx_quic_send_path_challenge(c, path); + + return NGX_OK; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path seq:%uL validation failed", path->seqnum); + + /* found expired path */ + + path->validated = 0; + + + /* RFC 9000, 9.3.2. On-Path Address Spoofing + * + * To protect the connection from failing due to such a spurious + * migration, an endpoint MUST revert to using the last validated + * peer address when validation of a new peer address fails. + */ + + if (qc->path == path) { + /* active path validation failed */ + + bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP); + + if (bkp == NULL) { + qc->error = NGX_QUIC_ERR_NO_VIABLE_PATH; + qc->error_reason = "no viable path"; + return NGX_ERROR; + } + + qc->path = bkp; + qc->path->tag = NGX_QUIC_PATH_ACTIVE; + + ngx_quic_set_connection_path(c, qc->path); + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic path seq:%uL addr:%V is restored from backup", + qc->path->seqnum, &qc->path->addr_text); + + ngx_quic_path_dbg(c, "is active", qc->path); + } + + return ngx_quic_free_path(c, path); +} + + +static ngx_int_t +ngx_quic_expire_path_mtu_delay(ngx_connection_t *c, ngx_quic_path_t *path) +{ + ngx_int_t rc; + ngx_uint_t i; + ngx_msec_t pto; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + + path->tries = 0; + + for ( ;; ) { + + for (i = 0; i < NGX_QUIC_PATH_RETRIES; i++) { + path->mtu_pnum[i] = NGX_QUIC_UNSET_PN; + } + + rc = ngx_quic_send_path_mtu_probe(c, path); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_OK) { + pto = ngx_quic_pto(c, ctx); + path->expires = ngx_current_msec + pto; + path->state = NGX_QUIC_PATH_MTUD; + return NGX_OK; + } + + /* rc == NGX_DECLINED */ + + path->max_mtu = path->mtud; + + if (path->max_mtu - path->mtu <= NGX_QUIC_PATH_MTU_PRECISION) { + path->state = NGX_QUIC_PATH_IDLE; + return NGX_OK; + } + + path->mtud = (path->mtu + path->max_mtu) / 2; + } +} + + +static ngx_int_t +ngx_quic_expire_path_mtu_discovery(ngx_connection_t *c, ngx_quic_path_t *path) +{ + ngx_int_t rc; + ngx_msec_int_t pto; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + + if (++path->tries < NGX_QUIC_PATH_RETRIES) { + rc = ngx_quic_send_path_mtu_probe(c, path); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_OK) { + pto = ngx_quic_pto(c, ctx) << path->tries; + path->expires = ngx_current_msec + pto; + return NGX_OK; + } + + /* rc == NGX_DECLINED */ + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path seq:%uL expired mtu:%uz", + path->seqnum, path->mtud); + + path->max_mtu = path->mtud; + + ngx_quic_discover_path_mtu(c, path); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_send_path_mtu_probe(ngx_connection_t *c, ngx_quic_path_t *path) +{ + size_t mtu; + uint64_t pnum; + ngx_int_t rc; + ngx_uint_t log_error; + ngx_quic_frame_t *frame; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_PING; + + qc = ngx_quic_get_connection(c); + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + pnum = ctx->pnum; + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path seq:%uL send probe " + "mtu:%uz pnum:%uL tries:%ui", + path->seqnum, path->mtud, ctx->pnum, path->tries); + + log_error = c->log_error; + c->log_error = NGX_ERROR_IGNORE_EMSGSIZE; + + mtu = path->mtu; + path->mtu = path->mtud; + + rc = ngx_quic_frame_sendto(c, frame, path->mtud, path); + + path->mtu = mtu; + c->log_error = log_error; + + if (rc == NGX_OK) { + path->mtu_pnum[path->tries] = pnum; + return NGX_OK; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path seq:%uL rejected mtu:%uz", + path->seqnum, path->mtud); + + if (rc == NGX_ERROR) { + if (c->write->error) { + c->write->error = 0; + return NGX_DECLINED; + } + + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_path_mtu(ngx_connection_t *c, ngx_quic_path_t *path, + uint64_t min, uint64_t max) +{ + uint64_t pnum; + ngx_uint_t i; + + if (path->state != NGX_QUIC_PATH_MTUD) { + return NGX_OK; + } + + for (i = 0; i < NGX_QUIC_PATH_RETRIES; i++) { + pnum = path->mtu_pnum[i]; + + if (pnum == NGX_QUIC_UNSET_PN) { + continue; + } + + if (pnum < min || pnum > max) { + continue; + } + + path->mtu = path->mtud; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path seq:%uL ack mtu:%uz", + path->seqnum, path->mtu); + + ngx_quic_discover_path_mtu(c, path); + + break; + } + + return NGX_OK; +} diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_migration.h b/src/deps/src/nginx/src/event/quic/ngx_event_quic_migration.h new file mode 100644 index 000000000..270c572ed --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_migration.h @@ -0,0 +1,45 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_ +#define _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_ + + +#include +#include + +#define NGX_QUIC_PATH_RETRIES 3 + +#define NGX_QUIC_PATH_PROBE 0 +#define NGX_QUIC_PATH_ACTIVE 1 +#define NGX_QUIC_PATH_BACKUP 2 + +#define ngx_quic_path_dbg(c, msg, path) \ + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, \ + "quic path seq:%uL %s tx:%O rx:%O valid:%d st:%d mtu:%uz", \ + path->seqnum, msg, path->sent, path->received, \ + path->validated, path->state, path->mtu); + +ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f); +ngx_int_t ngx_quic_handle_path_response_frame(ngx_connection_t *c, + ngx_quic_path_challenge_frame_t *f); + +ngx_quic_path_t *ngx_quic_new_path(ngx_connection_t *c, + struct sockaddr *sockaddr, socklen_t socklen, ngx_quic_client_id_t *cid); +ngx_int_t ngx_quic_free_path(ngx_connection_t *c, ngx_quic_path_t *path); + +ngx_int_t ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt); +ngx_int_t ngx_quic_handle_migration(ngx_connection_t *c, + ngx_quic_header_t *pkt); + +void ngx_quic_path_handler(ngx_event_t *ev); + +void ngx_quic_discover_path_mtu(ngx_connection_t *c, ngx_quic_path_t *path); +ngx_int_t ngx_quic_handle_path_mtu(ngx_connection_t *c, + ngx_quic_path_t *path, uint64_t min, uint64_t max); + +#endif /* _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_ */ diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_openssl_compat.c b/src/deps/src/nginx/src/event/quic/ngx_event_quic_openssl_compat.c new file mode 100644 index 000000000..c7412e82b --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_openssl_compat.c @@ -0,0 +1,651 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +#if (NGX_QUIC_OPENSSL_COMPAT) + +#define NGX_QUIC_COMPAT_RECORD_SIZE 1024 + +#define NGX_QUIC_COMPAT_SSL_TP_EXT 0x39 + +#define NGX_QUIC_COMPAT_CLIENT_HANDSHAKE "CLIENT_HANDSHAKE_TRAFFIC_SECRET" +#define NGX_QUIC_COMPAT_SERVER_HANDSHAKE "SERVER_HANDSHAKE_TRAFFIC_SECRET" +#define NGX_QUIC_COMPAT_CLIENT_APPLICATION "CLIENT_TRAFFIC_SECRET_0" +#define NGX_QUIC_COMPAT_SERVER_APPLICATION "SERVER_TRAFFIC_SECRET_0" + + +typedef struct { + ngx_quic_secret_t secret; + ngx_uint_t cipher; +} ngx_quic_compat_keys_t; + + +typedef struct { + ngx_log_t *log; + + u_char type; + ngx_str_t payload; + uint64_t number; + ngx_quic_compat_keys_t *keys; + + enum ssl_encryption_level_t level; +} ngx_quic_compat_record_t; + + +struct ngx_quic_compat_s { + const SSL_QUIC_METHOD *method; + + enum ssl_encryption_level_t write_level; + + uint64_t read_record; + ngx_quic_compat_keys_t keys; + + ngx_str_t tp; + ngx_str_t ctp; +}; + + +static void ngx_quic_compat_keylog_callback(const SSL *ssl, const char *line); +static ngx_int_t ngx_quic_compat_set_encryption_secret(ngx_connection_t *c, + ngx_quic_compat_keys_t *keys, enum ssl_encryption_level_t level, + const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len); +static void ngx_quic_compat_cleanup_encryption_secret(void *data); +static int ngx_quic_compat_add_transport_params_callback(SSL *ssl, + unsigned int ext_type, unsigned int context, const unsigned char **out, + size_t *outlen, X509 *x, size_t chainidx, int *al, void *add_arg); +static int ngx_quic_compat_parse_transport_params_callback(SSL *ssl, + unsigned int ext_type, unsigned int context, const unsigned char *in, + size_t inlen, X509 *x, size_t chainidx, int *al, void *parse_arg); +static void ngx_quic_compat_message_callback(int write_p, int version, + int content_type, const void *buf, size_t len, SSL *ssl, void *arg); +static size_t ngx_quic_compat_create_header(ngx_quic_compat_record_t *rec, + u_char *out, ngx_uint_t plain); +static ngx_int_t ngx_quic_compat_create_record(ngx_quic_compat_record_t *rec, + ngx_str_t *res); + + +ngx_int_t +ngx_quic_compat_init(ngx_conf_t *cf, SSL_CTX *ctx) +{ + SSL_CTX_set_keylog_callback(ctx, ngx_quic_compat_keylog_callback); + + if (SSL_CTX_has_client_custom_ext(ctx, NGX_QUIC_COMPAT_SSL_TP_EXT)) { + return NGX_OK; + } + + if (SSL_CTX_add_custom_ext(ctx, NGX_QUIC_COMPAT_SSL_TP_EXT, + SSL_EXT_CLIENT_HELLO + |SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS, + ngx_quic_compat_add_transport_params_callback, + NULL, + NULL, + ngx_quic_compat_parse_transport_params_callback, + NULL) + == 0) + { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "SSL_CTX_add_custom_ext() failed"); + return NGX_ERROR; + } + + return NGX_OK; +} + + +static void +ngx_quic_compat_keylog_callback(const SSL *ssl, const char *line) +{ + u_char ch, *p, *start, value; + size_t n; + ngx_uint_t write; + const SSL_CIPHER *cipher; + ngx_quic_compat_t *com; + ngx_connection_t *c; + ngx_quic_connection_t *qc; + enum ssl_encryption_level_t level; + u_char secret[EVP_MAX_MD_SIZE]; + + c = ngx_ssl_get_connection(ssl); + if (c->type != SOCK_DGRAM) { + return; + } + + p = (u_char *) line; + + for (start = p; *p && *p != ' '; p++); + + n = p - start; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat secret %*s", n, start); + + if (n == sizeof(NGX_QUIC_COMPAT_CLIENT_HANDSHAKE) - 1 + && ngx_strncmp(start, NGX_QUIC_COMPAT_CLIENT_HANDSHAKE, n) == 0) + { + level = ssl_encryption_handshake; + write = 0; + + } else if (n == sizeof(NGX_QUIC_COMPAT_SERVER_HANDSHAKE) - 1 + && ngx_strncmp(start, NGX_QUIC_COMPAT_SERVER_HANDSHAKE, n) == 0) + { + level = ssl_encryption_handshake; + write = 1; + + } else if (n == sizeof(NGX_QUIC_COMPAT_CLIENT_APPLICATION) - 1 + && ngx_strncmp(start, NGX_QUIC_COMPAT_CLIENT_APPLICATION, n) + == 0) + { + level = ssl_encryption_application; + write = 0; + + } else if (n == sizeof(NGX_QUIC_COMPAT_SERVER_APPLICATION) - 1 + && ngx_strncmp(start, NGX_QUIC_COMPAT_SERVER_APPLICATION, n) + == 0) + { + level = ssl_encryption_application; + write = 1; + + } else { + return; + } + + if (*p++ == '\0') { + return; + } + + for ( /* void */ ; *p && *p != ' '; p++); + + if (*p++ == '\0') { + return; + } + + for (n = 0, start = p; *p; p++) { + ch = *p; + + if (ch >= '0' && ch <= '9') { + value = ch - '0'; + goto next; + } + + ch = (u_char) (ch | 0x20); + + if (ch >= 'a' && ch <= 'f') { + value = ch - 'a' + 10; + goto next; + } + + ngx_log_error(NGX_LOG_EMERG, c->log, 0, + "invalid OpenSSL QUIC secret format"); + + return; + + next: + + if ((p - start) % 2) { + secret[n++] += value; + + } else { + if (n >= EVP_MAX_MD_SIZE) { + ngx_log_error(NGX_LOG_EMERG, c->log, 0, + "too big OpenSSL QUIC secret"); + return; + } + + secret[n] = (value << 4); + } + } + + qc = ngx_quic_get_connection(c); + com = qc->compat; + cipher = SSL_get_current_cipher(ssl); + + if (write) { + com->method->set_write_secret((SSL *) ssl, level, cipher, secret, n); + com->write_level = level; + + } else { + com->method->set_read_secret((SSL *) ssl, level, cipher, secret, n); + com->read_record = 0; + + (void) ngx_quic_compat_set_encryption_secret(c, &com->keys, level, + cipher, secret, n); + } + + ngx_explicit_memzero(secret, n); +} + + +static ngx_int_t +ngx_quic_compat_set_encryption_secret(ngx_connection_t *c, + ngx_quic_compat_keys_t *keys, enum ssl_encryption_level_t level, + const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len) +{ + ngx_int_t key_len; + ngx_str_t secret_str; + ngx_uint_t i; + ngx_quic_md_t key; + ngx_quic_hkdf_t seq[2]; + ngx_quic_secret_t *peer_secret; + ngx_quic_ciphers_t ciphers; + ngx_pool_cleanup_t *cln; + + peer_secret = &keys->secret; + + keys->cipher = SSL_CIPHER_get_id(cipher); + + key_len = ngx_quic_ciphers(keys->cipher, &ciphers); + + if (key_len == NGX_ERROR) { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher"); + return NGX_ERROR; + } + + key.len = key_len; + + peer_secret->iv.len = NGX_QUIC_IV_LEN; + + secret_str.len = secret_len; + secret_str.data = (u_char *) secret; + + ngx_quic_hkdf_set(&seq[0], "tls13 key", &key, &secret_str); + ngx_quic_hkdf_set(&seq[1], "tls13 iv", &peer_secret->iv, &secret_str); + + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { + if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, c->log) != NGX_OK) { + return NGX_ERROR; + } + } + + /* register cleanup handler once */ + + if (peer_secret->ctx) { + ngx_quic_crypto_cleanup(peer_secret); + + } else { + cln = ngx_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + return NGX_ERROR; + } + + cln->handler = ngx_quic_compat_cleanup_encryption_secret; + cln->data = peer_secret; + } + + if (ngx_quic_crypto_init(ciphers.c, peer_secret, &key, 1, c->log) + == NGX_ERROR) + { + return NGX_ERROR; + } + + ngx_explicit_memzero(key.data, key.len); + + return NGX_OK; +} + + +static void +ngx_quic_compat_cleanup_encryption_secret(void *data) +{ + ngx_quic_secret_t *secret = data; + + ngx_quic_crypto_cleanup(secret); +} + + +static int +ngx_quic_compat_add_transport_params_callback(SSL *ssl, unsigned int ext_type, + unsigned int context, const unsigned char **out, size_t *outlen, X509 *x, + size_t chainidx, int *al, void *add_arg) +{ + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection(ssl); + if (c->type != SOCK_DGRAM) { + return 0; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat add transport params"); + + qc = ngx_quic_get_connection(c); + com = qc->compat; + + *out = com->tp.data; + *outlen = com->tp.len; + + return 1; +} + + +static int +ngx_quic_compat_parse_transport_params_callback(SSL *ssl, unsigned int ext_type, + unsigned int context, const unsigned char *in, size_t inlen, X509 *x, + size_t chainidx, int *al, void *parse_arg) +{ + u_char *p; + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection(ssl); + if (c->type != SOCK_DGRAM) { + return 0; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat parse transport params"); + + qc = ngx_quic_get_connection(c); + com = qc->compat; + + p = ngx_pnalloc(c->pool, inlen); + if (p == NULL) { + return 0; + } + + ngx_memcpy(p, in, inlen); + + com->ctp.data = p; + com->ctp.len = inlen; + + return 1; +} + + +int +SSL_set_quic_method(SSL *ssl, const SSL_QUIC_METHOD *quic_method) +{ + BIO *rbio, *wbio; + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection(ssl); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic compat set method"); + + qc = ngx_quic_get_connection(c); + + qc->compat = ngx_pcalloc(c->pool, sizeof(ngx_quic_compat_t)); + if (qc->compat == NULL) { + return 0; + } + + com = qc->compat; + com->method = quic_method; + + rbio = BIO_new(BIO_s_mem()); + if (rbio == NULL) { + return 0; + } + + wbio = BIO_new(BIO_s_null()); + if (wbio == NULL) { + return 0; + } + + SSL_set_bio(ssl, rbio, wbio); + + SSL_set_msg_callback(ssl, ngx_quic_compat_message_callback); + + /* early data is not supported */ + SSL_set_max_early_data(ssl, 0); + + return 1; +} + + +static void +ngx_quic_compat_message_callback(int write_p, int version, int content_type, + const void *buf, size_t len, SSL *ssl, void *arg) +{ + ngx_uint_t alert; + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + enum ssl_encryption_level_t level; + + if (!write_p) { + return; + } + + c = ngx_ssl_get_connection(ssl); + qc = ngx_quic_get_connection(c); + + if (qc == NULL) { + /* closing */ + return; + } + + com = qc->compat; + level = com->write_level; + + switch (content_type) { + + case SSL3_RT_HANDSHAKE: + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat tx %s len:%uz ", + ngx_quic_level_name(level), len); + + if (com->method->add_handshake_data(ssl, level, buf, len) != 1) { + goto failed; + } + + break; + + case SSL3_RT_ALERT: + if (len >= 2) { + alert = ((u_char *) buf)[1]; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat %s alert:%ui len:%uz ", + ngx_quic_level_name(level), alert, len); + + if (com->method->send_alert(ssl, level, alert) != 1) { + goto failed; + } + } + + break; + } + + return; + +failed: + + ngx_post_event(&qc->close, &ngx_posted_events); +} + + +int +SSL_provide_quic_data(SSL *ssl, enum ssl_encryption_level_t level, + const uint8_t *data, size_t len) +{ + BIO *rbio; + size_t n; + u_char *p; + ngx_str_t res; + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + ngx_quic_compat_record_t rec; + u_char in[NGX_QUIC_COMPAT_RECORD_SIZE + 1]; + u_char out[NGX_QUIC_COMPAT_RECORD_SIZE + 1 + + SSL3_RT_HEADER_LENGTH + + NGX_QUIC_TAG_LEN]; + + c = ngx_ssl_get_connection(ssl); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic compat rx %s len:%uz", + ngx_quic_level_name(level), len); + + qc = ngx_quic_get_connection(c); + com = qc->compat; + rbio = SSL_get_rbio(ssl); + + while (len) { + ngx_memzero(&rec, sizeof(ngx_quic_compat_record_t)); + + rec.type = SSL3_RT_HANDSHAKE; + rec.log = c->log; + rec.number = com->read_record++; + rec.keys = &com->keys; + rec.level = level; + + if (level == ssl_encryption_initial) { + n = ngx_min(len, 65535); + + rec.payload.len = n; + rec.payload.data = (u_char *) data; + + ngx_quic_compat_create_header(&rec, out, 1); + + BIO_write(rbio, out, SSL3_RT_HEADER_LENGTH); + BIO_write(rbio, data, n); + +#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS) + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat record len:%uz %*xs%*xs", + n + SSL3_RT_HEADER_LENGTH, + (size_t) SSL3_RT_HEADER_LENGTH, out, n, data); +#endif + + } else { + n = ngx_min(len, NGX_QUIC_COMPAT_RECORD_SIZE); + + p = ngx_cpymem(in, data, n); + *p++ = SSL3_RT_HANDSHAKE; + + rec.payload.len = p - in; + rec.payload.data = in; + + res.data = out; + + if (ngx_quic_compat_create_record(&rec, &res) != NGX_OK) { + return 0; + } + +#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS) + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat record len:%uz %xV", res.len, &res); +#endif + + BIO_write(rbio, res.data, res.len); + } + + data += n; + len -= n; + } + + return 1; +} + + +static size_t +ngx_quic_compat_create_header(ngx_quic_compat_record_t *rec, u_char *out, + ngx_uint_t plain) +{ + u_char type; + size_t len; + + len = rec->payload.len; + + if (plain) { + type = rec->type; + + } else { + type = SSL3_RT_APPLICATION_DATA; + len += NGX_QUIC_TAG_LEN; + } + + out[0] = type; + out[1] = 0x03; + out[2] = 0x03; + out[3] = (len >> 8); + out[4] = len; + + return 5; +} + + +static ngx_int_t +ngx_quic_compat_create_record(ngx_quic_compat_record_t *rec, ngx_str_t *res) +{ + ngx_str_t ad, out; + ngx_quic_secret_t *secret; + u_char nonce[NGX_QUIC_IV_LEN]; + + ad.data = res->data; + ad.len = ngx_quic_compat_create_header(rec, ad.data, 0); + + out.len = rec->payload.len + NGX_QUIC_TAG_LEN; + out.data = res->data + ad.len; + +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, rec->log, 0, + "quic compat ad len:%uz %xV", ad.len, &ad); +#endif + + secret = &rec->keys->secret; + + ngx_memcpy(nonce, secret->iv.data, secret->iv.len); + ngx_quic_compute_nonce(nonce, sizeof(nonce), rec->number); + + if (ngx_quic_crypto_seal(secret, &out, nonce, &rec->payload, &ad, rec->log) + != NGX_OK) + { + return NGX_ERROR; + } + + res->len = ad.len + out.len; + + return NGX_OK; +} + + +int +SSL_set_quic_transport_params(SSL *ssl, const uint8_t *params, + size_t params_len) +{ + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection(ssl); + qc = ngx_quic_get_connection(c); + com = qc->compat; + + com->tp.len = params_len; + com->tp.data = (u_char *) params; + + return 1; +} + + +void +SSL_get_peer_quic_transport_params(const SSL *ssl, const uint8_t **out_params, + size_t *out_params_len) +{ + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection(ssl); + qc = ngx_quic_get_connection(c); + com = qc->compat; + + *out_params = com->ctp.data; + *out_params_len = com->ctp.len; +} + +#endif /* NGX_QUIC_OPENSSL_COMPAT */ diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_openssl_compat.h b/src/deps/src/nginx/src/event/quic/ngx_event_quic_openssl_compat.h new file mode 100644 index 000000000..77cc3cb0d --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_openssl_compat.h @@ -0,0 +1,59 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_ +#define _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_ + +#if defined SSL_R_MISSING_QUIC_TRANSPORT_PARAMETERS_EXTENSION \ + || defined LIBRESSL_VERSION_NUMBER +#undef NGX_QUIC_OPENSSL_COMPAT +#else + + +#include +#include + + +typedef struct ngx_quic_compat_s ngx_quic_compat_t; + + +enum ssl_encryption_level_t { + ssl_encryption_initial = 0, + ssl_encryption_early_data, + ssl_encryption_handshake, + ssl_encryption_application +}; + + +typedef struct ssl_quic_method_st { + int (*set_read_secret)(SSL *ssl, enum ssl_encryption_level_t level, + const SSL_CIPHER *cipher, + const uint8_t *rsecret, size_t secret_len); + int (*set_write_secret)(SSL *ssl, enum ssl_encryption_level_t level, + const SSL_CIPHER *cipher, + const uint8_t *wsecret, size_t secret_len); + int (*add_handshake_data)(SSL *ssl, enum ssl_encryption_level_t level, + const uint8_t *data, size_t len); + int (*flush_flight)(SSL *ssl); + int (*send_alert)(SSL *ssl, enum ssl_encryption_level_t level, + uint8_t alert); +} SSL_QUIC_METHOD; + + +ngx_int_t ngx_quic_compat_init(ngx_conf_t *cf, SSL_CTX *ctx); + +int SSL_set_quic_method(SSL *ssl, const SSL_QUIC_METHOD *quic_method); +int SSL_provide_quic_data(SSL *ssl, enum ssl_encryption_level_t level, + const uint8_t *data, size_t len); +int SSL_set_quic_transport_params(SSL *ssl, const uint8_t *params, + size_t params_len); +void SSL_get_peer_quic_transport_params(const SSL *ssl, + const uint8_t **out_params, size_t *out_params_len); + + +#endif /* TLSEXT_TYPE_quic_transport_parameters */ + +#endif /* _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_ */ diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_output.c b/src/deps/src/nginx/src/event/quic/ngx_event_quic_output.c new file mode 100644 index 000000000..ce6aaab22 --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_output.c @@ -0,0 +1,1319 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +#define NGX_QUIC_MAX_UDP_SEGMENT_BUF 65487 /* 65K - IPv6 header */ +#define NGX_QUIC_MAX_SEGMENTS 64 /* UDP_MAX_SEGMENTS */ + +#define NGX_QUIC_RETRY_TOKEN_LIFETIME 3 /* seconds */ +#define NGX_QUIC_NEW_TOKEN_LIFETIME 600 /* seconds */ +#define NGX_QUIC_RETRY_BUFFER_SIZE 256 + /* 1 flags + 4 version + 3 x (1 + 20) s/o/dcid + itag + token(64) */ + +/* + * RFC 9000, 10.3. Stateless Reset + * + * Endpoints MUST discard packets that are too small to be valid QUIC + * packets. With the set of AEAD functions defined in [QUIC-TLS], + * short header packets that are smaller than 21 bytes are never valid. + */ +#define NGX_QUIC_MIN_PKT_LEN 21 + +#define NGX_QUIC_MIN_SR_PACKET 43 /* 5 rand + 16 srt + 22 padding */ +#define NGX_QUIC_MAX_SR_PACKET 1200 + +#define NGX_QUIC_CC_MIN_INTERVAL 1000 /* 1s */ + +#define NGX_QUIC_SOCKET_RETRY_DELAY 10 /* ms, for NGX_AGAIN on write */ + + +#define ngx_quic_log_packet(log, pkt) \ + ngx_log_debug6(NGX_LOG_DEBUG_EVENT, log, 0, \ + "quic packet tx %s bytes:%ui need_ack:%d" \ + " number:%L encoded nl:%d trunc:0x%xD", \ + ngx_quic_level_name((pkt)->level), (pkt)->payload.len, \ + (pkt)->need_ack, (pkt)->number, (pkt)->num_len, \ + (pkt)->trunc); + + +static ngx_int_t ngx_quic_create_datagrams(ngx_connection_t *c); +static void ngx_quic_commit_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); +static void ngx_quic_revert_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t pnum); +#if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL)) +static ngx_uint_t ngx_quic_allow_segmentation(ngx_connection_t *c); +static ngx_int_t ngx_quic_create_segments(ngx_connection_t *c); +static ssize_t ngx_quic_send_segments(ngx_connection_t *c, u_char *buf, + size_t len, struct sockaddr *sockaddr, socklen_t socklen, size_t segment); +#endif +static ssize_t ngx_quic_output_packet(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, u_char *data, size_t max, size_t min); +static void ngx_quic_init_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + ngx_quic_header_t *pkt, ngx_quic_path_t *path); +static ngx_uint_t ngx_quic_get_padding_level(ngx_connection_t *c); +static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len, + struct sockaddr *sockaddr, socklen_t socklen); +static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt, + ngx_quic_send_ctx_t *ctx); + + +ngx_int_t +ngx_quic_output(ngx_connection_t *c) +{ + size_t in_flight; + ngx_int_t rc; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + + c->log->action = "sending frames"; + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + + in_flight = cg->in_flight; + +#if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL)) + if (ngx_quic_allow_segmentation(c)) { + rc = ngx_quic_create_segments(c); + } else +#endif + { + rc = ngx_quic_create_datagrams(c); + } + + if (rc != NGX_OK) { + return NGX_ERROR; + } + + if (in_flight == cg->in_flight || qc->closing) { + /* no ack-eliciting data was sent or we are done */ + return NGX_OK; + } + + if (!qc->send_timer_set) { + qc->send_timer_set = 1; + ngx_add_timer(c->read, qc->tp.max_idle_timeout); + } + + ngx_quic_set_lost_timer(c); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_create_datagrams(ngx_connection_t *c) +{ + size_t len, min; + ssize_t n; + u_char *p; + uint64_t preserved_pnum[NGX_QUIC_SEND_CTX_LAST]; + ngx_uint_t i, pad; + ngx_quic_path_t *path; + ngx_quic_send_ctx_t *ctx; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + path = qc->path; + + while (cg->in_flight < cg->window) { + + p = dst; + + len = ngx_quic_path_limit(c, path, path->mtu); + + pad = ngx_quic_get_padding_level(c); + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + + ctx = &qc->send_ctx[i]; + + preserved_pnum[i] = ctx->pnum; + + if (ngx_quic_generate_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + + min = (i == pad && p - dst < NGX_QUIC_MIN_INITIAL_SIZE) + ? NGX_QUIC_MIN_INITIAL_SIZE - (p - dst) : 0; + + if (min > len) { + /* padding can't be applied - avoid sending the packet */ + + while (i-- > 0) { + ctx = &qc->send_ctx[i]; + ngx_quic_revert_send(c, ctx, preserved_pnum[i]); + } + + return NGX_OK; + } + + n = ngx_quic_output_packet(c, ctx, p, len, min); + if (n == NGX_ERROR) { + return NGX_ERROR; + } + + p += n; + len -= n; + } + + len = p - dst; + if (len == 0) { + break; + } + + n = ngx_quic_send(c, dst, len, path->sockaddr, path->socklen); + + if (n == NGX_ERROR) { + return NGX_ERROR; + } + + if (n == NGX_AGAIN) { + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ngx_quic_revert_send(c, &qc->send_ctx[i], preserved_pnum[i]); + } + + ngx_add_timer(&qc->push, NGX_QUIC_SOCKET_RETRY_DELAY); + break; + } + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ngx_quic_commit_send(c, &qc->send_ctx[i]); + } + + path->sent += len; + } + + return NGX_OK; +} + + +static void +ngx_quic_commit_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + cg = &qc->congestion; + + while (!ngx_queue_empty(&ctx->sending)) { + + q = ngx_queue_head(&ctx->sending); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + ngx_queue_remove(q); + + if (f->pkt_need_ack && !qc->closing) { + ngx_queue_insert_tail(&ctx->sent, q); + + cg->in_flight += f->plen; + + } else { + ngx_quic_free_frame(c, f); + } + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion send if:%uz", cg->in_flight); +} + + +static void +ngx_quic_revert_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t pnum) +{ + ngx_queue_t *q; + + while (!ngx_queue_empty(&ctx->sending)) { + + q = ngx_queue_last(&ctx->sending); + ngx_queue_remove(q); + ngx_queue_insert_head(&ctx->frames, q); + } + + ctx->pnum = pnum; +} + + +#if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL)) + +static ngx_uint_t +ngx_quic_allow_segmentation(ngx_connection_t *c) +{ + size_t bytes, len; + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (!qc->conf->gso_enabled) { + return 0; + } + + if (!qc->path->validated) { + /* don't even try to be faster on non-validated paths */ + return 0; + } + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial); + if (!ngx_queue_empty(&ctx->frames)) { + return 0; + } + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); + if (!ngx_queue_empty(&ctx->frames)) { + return 0; + } + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + + bytes = 0; + len = ngx_min(qc->path->mtu, NGX_QUIC_MAX_UDP_SEGMENT_BUF); + + for (q = ngx_queue_head(&ctx->frames); + q != ngx_queue_sentinel(&ctx->frames); + q = ngx_queue_next(q)) + { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + bytes += f->len; + + if (bytes > len * 3) { + /* require at least ~3 full packets to batch */ + return 1; + } + } + + return 0; +} + + +static ngx_int_t +ngx_quic_create_segments(ngx_connection_t *c) +{ + size_t len, segsize; + ssize_t n; + u_char *p, *end; + uint64_t preserved_pnum; + ngx_uint_t nseg; + ngx_quic_path_t *path; + ngx_quic_send_ctx_t *ctx; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + static u_char dst[NGX_QUIC_MAX_UDP_SEGMENT_BUF]; + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + path = qc->path; + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + + if (ngx_quic_generate_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + + segsize = ngx_min(path->mtu, NGX_QUIC_MAX_UDP_SEGMENT_BUF); + p = dst; + end = dst + sizeof(dst); + + nseg = 0; + + preserved_pnum = ctx->pnum; + + for ( ;; ) { + + len = ngx_min(segsize, (size_t) (end - p)); + + if (len && cg->in_flight + (p - dst) < cg->window) { + + n = ngx_quic_output_packet(c, ctx, p, len, len); + if (n == NGX_ERROR) { + return NGX_ERROR; + } + + if (n) { + p += n; + nseg++; + } + + } else { + n = 0; + } + + if (p == dst) { + break; + } + + if (n == 0 || nseg == NGX_QUIC_MAX_SEGMENTS) { + n = ngx_quic_send_segments(c, dst, p - dst, path->sockaddr, + path->socklen, segsize); + if (n == NGX_ERROR) { + return NGX_ERROR; + } + + if (n == NGX_AGAIN) { + ngx_quic_revert_send(c, ctx, preserved_pnum); + + ngx_add_timer(&qc->push, NGX_QUIC_SOCKET_RETRY_DELAY); + break; + } + + ngx_quic_commit_send(c, ctx); + + path->sent += n; + + p = dst; + nseg = 0; + preserved_pnum = ctx->pnum; + } + } + + return NGX_OK; +} + + +static ssize_t +ngx_quic_send_segments(ngx_connection_t *c, u_char *buf, size_t len, + struct sockaddr *sockaddr, socklen_t socklen, size_t segment) +{ + size_t clen; + ssize_t n; + uint16_t *valp; + struct iovec iov; + struct msghdr msg; + struct cmsghdr *cmsg; + +#if (NGX_HAVE_ADDRINFO_CMSG) + char msg_control[CMSG_SPACE(sizeof(uint16_t)) + + CMSG_SPACE(sizeof(ngx_addrinfo_t))]; +#else + char msg_control[CMSG_SPACE(sizeof(uint16_t))]; +#endif + + ngx_memzero(&msg, sizeof(struct msghdr)); + ngx_memzero(msg_control, sizeof(msg_control)); + + iov.iov_len = len; + iov.iov_base = buf; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + msg.msg_name = sockaddr; + msg.msg_namelen = socklen; + + msg.msg_control = msg_control; + msg.msg_controllen = sizeof(msg_control); + + cmsg = CMSG_FIRSTHDR(&msg); + + cmsg->cmsg_level = SOL_UDP; + cmsg->cmsg_type = UDP_SEGMENT; + cmsg->cmsg_len = CMSG_LEN(sizeof(uint16_t)); + + clen = CMSG_SPACE(sizeof(uint16_t)); + + valp = (void *) CMSG_DATA(cmsg); + *valp = segment; + +#if (NGX_HAVE_ADDRINFO_CMSG) + if (c->listening && c->listening->wildcard && c->local_sockaddr) { + cmsg = CMSG_NXTHDR(&msg, cmsg); + clen += ngx_set_srcaddr_cmsg(cmsg, c->local_sockaddr); + } +#endif + + msg.msg_controllen = clen; + + n = ngx_sendmsg(c, &msg, 0); + if (n < 0) { + return n; + } + + c->sent += n; + + return n; +} + +#endif + + + +static ngx_uint_t +ngx_quic_get_padding_level(ngx_connection_t *c) +{ + ngx_uint_t i; + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + /* + * RFC 9000, 14.1. Initial Datagram Size + * + * Similarly, a server MUST expand the payload of all UDP datagrams + * carrying ack-eliciting Initial packets to at least the smallest + * allowed maximum datagram size of 1200 bytes. + */ + + qc = ngx_quic_get_connection(c); + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial); + + for (q = ngx_queue_head(&ctx->frames); + q != ngx_queue_sentinel(&ctx->frames); + q = ngx_queue_next(q)) + { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (f->need_ack) { + for (i = 0; i + 1 < NGX_QUIC_SEND_CTX_LAST; i++) { + ctx = &qc->send_ctx[i + 1]; + + if (ngx_queue_empty(&ctx->frames)) { + break; + } + } + + return i; + } + } + + return NGX_QUIC_SEND_CTX_LAST; +} + + +static ssize_t +ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + u_char *data, size_t max, size_t min) +{ + size_t len, pad, min_payload, max_payload; + u_char *p; + ssize_t flen; + ngx_str_t res; + ngx_int_t rc; + ngx_uint_t nframes; + ngx_msec_t now; + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_header_t pkt; + ngx_quic_connection_t *qc; + static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + if (ngx_queue_empty(&ctx->frames)) { + return 0; + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic output %s packet max:%uz min:%uz", + ngx_quic_level_name(ctx->level), max, min); + + qc = ngx_quic_get_connection(c); + + if (!ngx_quic_keys_available(qc->keys, ctx->level, 1)) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, "quic %s write keys discarded", + ngx_quic_level_name(ctx->level)); + + while (!ngx_queue_empty(&ctx->frames)) { + q = ngx_queue_head(&ctx->frames); + ngx_queue_remove(q); + + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + ngx_quic_free_frame(c, f); + } + + return 0; + } + + ngx_quic_init_packet(c, ctx, &pkt, qc->path); + + min_payload = ngx_quic_payload_size(&pkt, min); + max_payload = ngx_quic_payload_size(&pkt, max); + + /* RFC 9001, 5.4.2. Header Protection Sample */ + pad = 4 - pkt.num_len; + min_payload = ngx_max(min_payload, pad); + + if (min_payload > max_payload) { + return 0; + } + + now = ngx_current_msec; + nframes = 0; + p = src; + len = 0; + + for (q = ngx_queue_head(&ctx->frames); + q != ngx_queue_sentinel(&ctx->frames); + q = ngx_queue_next(q)) + { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (len >= max_payload) { + break; + } + + if (len + f->len > max_payload) { + rc = ngx_quic_split_frame(c, f, max_payload - len); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_DECLINED) { + break; + } + } + + if (f->need_ack) { + pkt.need_ack = 1; + } + + f->pnum = ctx->pnum; + f->send_time = now; + f->plen = 0; + + ngx_quic_log_frame(c->log, f, 1); + + flen = ngx_quic_create_frame(p, f); + if (flen == -1) { + return NGX_ERROR; + } + + len += flen; + p += flen; + + nframes++; + } + + if (nframes == 0) { + return 0; + } + + if (len < min_payload) { + ngx_memset(p, NGX_QUIC_FT_PADDING, min_payload - len); + len = min_payload; + } + + pkt.payload.data = src; + pkt.payload.len = len; + + res.data = data; + + ngx_quic_log_packet(c->log, &pkt); + + if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { + return NGX_ERROR; + } + + ctx->pnum++; + + if (pkt.need_ack) { + q = ngx_queue_head(&ctx->frames); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + f->plen = res.len; + } + + while (nframes--) { + q = ngx_queue_head(&ctx->frames); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + f->pkt_need_ack = pkt.need_ack; + + ngx_queue_remove(q); + ngx_queue_insert_tail(&ctx->sending, q); + } + + return res.len; +} + + +static void +ngx_quic_init_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + ngx_quic_header_t *pkt, ngx_quic_path_t *path) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ngx_memzero(pkt, sizeof(ngx_quic_header_t)); + + pkt->flags = NGX_QUIC_PKT_FIXED_BIT; + + if (ctx->level == ssl_encryption_initial) { + pkt->flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_INITIAL; + + } else if (ctx->level == ssl_encryption_handshake) { + pkt->flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_HANDSHAKE; + + } else { + if (qc->key_phase) { + pkt->flags |= NGX_QUIC_PKT_KPHASE; + } + } + + pkt->dcid.data = path->cid->id; + pkt->dcid.len = path->cid->len; + + pkt->scid = qc->tp.initial_scid; + + pkt->version = qc->version; + pkt->log = c->log; + pkt->level = ctx->level; + + pkt->keys = qc->keys; + + ngx_quic_set_packet_number(pkt, ctx); +} + + +static ssize_t +ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len, + struct sockaddr *sockaddr, socklen_t socklen) +{ + ssize_t n; + struct iovec iov; + struct msghdr msg; +#if (NGX_HAVE_ADDRINFO_CMSG) + struct cmsghdr *cmsg; + char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))]; +#endif + + ngx_memzero(&msg, sizeof(struct msghdr)); + + iov.iov_len = len; + iov.iov_base = buf; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + msg.msg_name = sockaddr; + msg.msg_namelen = socklen; + +#if (NGX_HAVE_ADDRINFO_CMSG) + if (c->listening && c->listening->wildcard && c->local_sockaddr) { + + msg.msg_control = msg_control; + msg.msg_controllen = sizeof(msg_control); + ngx_memzero(msg_control, sizeof(msg_control)); + + cmsg = CMSG_FIRSTHDR(&msg); + + msg.msg_controllen = ngx_set_srcaddr_cmsg(cmsg, c->local_sockaddr); + } +#endif + + n = ngx_sendmsg(c, &msg, 0); + if (n < 0) { + return n; + } + + c->sent += n; + + return n; +} + + +static void +ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx) +{ + uint64_t delta; + + delta = ctx->pnum - ctx->largest_ack; + pkt->number = ctx->pnum; + + if (delta <= 0x7F) { + pkt->num_len = 1; + pkt->trunc = ctx->pnum & 0xff; + + } else if (delta <= 0x7FFF) { + pkt->num_len = 2; + pkt->flags |= 0x1; + pkt->trunc = ctx->pnum & 0xffff; + + } else if (delta <= 0x7FFFFF) { + pkt->num_len = 3; + pkt->flags |= 0x2; + pkt->trunc = ctx->pnum & 0xffffff; + + } else { + pkt->num_len = 4; + pkt->flags |= 0x3; + pkt->trunc = ctx->pnum & 0xffffffff; + } +} + + +ngx_int_t +ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt) +{ + size_t len; + ngx_quic_header_t pkt; + static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "sending version negotiation packet"); + + pkt.log = c->log; + pkt.flags = NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_FIXED_BIT; + pkt.dcid = inpkt->scid; + pkt.scid = inpkt->dcid; + + len = ngx_quic_create_version_negotiation(&pkt, buf); + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic vnego packet to send len:%uz %*xs", len, len, buf); +#endif + + (void) ngx_quic_send(c, buf, len, c->sockaddr, c->socklen); + + return NGX_DONE; +} + + +ngx_int_t +ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, + ngx_quic_header_t *pkt) +{ + u_char *token; + size_t len, max; + uint16_t rndbytes; + u_char buf[NGX_QUIC_MAX_SR_PACKET]; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic handle stateless reset output"); + + if (pkt->len <= NGX_QUIC_MIN_PKT_LEN) { + return NGX_DECLINED; + } + + if (pkt->len <= NGX_QUIC_MIN_SR_PACKET) { + len = pkt->len - 1; + + } else { + max = ngx_min(NGX_QUIC_MAX_SR_PACKET, pkt->len * 3); + + if (RAND_bytes((u_char *) &rndbytes, sizeof(rndbytes)) != 1) { + return NGX_ERROR; + } + + len = (rndbytes % (max - NGX_QUIC_MIN_SR_PACKET + 1)) + + NGX_QUIC_MIN_SR_PACKET; + } + + if (RAND_bytes(buf, len - NGX_QUIC_SR_TOKEN_LEN) != 1) { + return NGX_ERROR; + } + + buf[0] &= ~NGX_QUIC_PKT_LONG; + buf[0] |= NGX_QUIC_PKT_FIXED_BIT; + + token = &buf[len - NGX_QUIC_SR_TOKEN_LEN]; + + if (ngx_quic_new_sr_token(c, &pkt->dcid, conf->sr_token_key, token) + != NGX_OK) + { + return NGX_ERROR; + } + + (void) ngx_quic_send(c, buf, len, c->sockaddr, c->socklen); + + return NGX_DECLINED; +} + + +ngx_int_t +ngx_quic_send_cc(ngx_connection_t *c) +{ + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (qc->draining) { + return NGX_OK; + } + + if (qc->closing + && ngx_current_msec - qc->last_cc < NGX_QUIC_CC_MIN_INTERVAL) + { + /* dot not send CC too often */ + return NGX_OK; + } + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = qc->error_level; + frame->type = qc->error_app ? NGX_QUIC_FT_CONNECTION_CLOSE_APP + : NGX_QUIC_FT_CONNECTION_CLOSE; + frame->u.close.error_code = qc->error; + frame->u.close.frame_type = qc->error_ftype; + + if (qc->error_reason) { + frame->u.close.reason.len = ngx_strlen(qc->error_reason); + frame->u.close.reason.data = (u_char *) qc->error_reason; + } + + frame->ignore_congestion = 1; + + qc->last_cc = ngx_current_msec; + + return ngx_quic_frame_sendto(c, frame, 0, qc->path); +} + + +ngx_int_t +ngx_quic_send_early_cc(ngx_connection_t *c, ngx_quic_header_t *inpkt, + ngx_uint_t err, const char *reason) +{ + ssize_t len; + ngx_str_t res; + ngx_quic_keys_t keys; + ngx_quic_frame_t frame; + ngx_quic_header_t pkt; + + static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + + frame.level = inpkt->level; + frame.type = NGX_QUIC_FT_CONNECTION_CLOSE; + frame.u.close.error_code = err; + + frame.u.close.reason.data = (u_char *) reason; + frame.u.close.reason.len = ngx_strlen(reason); + + ngx_quic_log_frame(c->log, &frame, 1); + + len = ngx_quic_create_frame(NULL, &frame); + if (len > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) { + return NGX_ERROR; + } + + len = ngx_quic_create_frame(src, &frame); + if (len == -1) { + return NGX_ERROR; + } + + ngx_memzero(&keys, sizeof(ngx_quic_keys_t)); + + pkt.keys = &keys; + + if (ngx_quic_keys_set_initial_secret(pkt.keys, &inpkt->dcid, c->log) + != NGX_OK) + { + return NGX_ERROR; + } + + pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG + | NGX_QUIC_PKT_INITIAL; + + pkt.num_len = 1; + /* + * pkt.num = 0; + * pkt.trunc = 0; + */ + + pkt.version = inpkt->version; + pkt.log = c->log; + pkt.level = inpkt->level; + pkt.dcid = inpkt->scid; + pkt.scid = inpkt->dcid; + pkt.payload.data = src; + pkt.payload.len = len; + + res.data = dst; + + ngx_quic_log_packet(c->log, &pkt); + + if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { + ngx_quic_keys_cleanup(pkt.keys); + return NGX_ERROR; + } + + if (ngx_quic_send(c, res.data, res.len, c->sockaddr, c->socklen) < 0) { + ngx_quic_keys_cleanup(pkt.keys); + return NGX_ERROR; + } + + ngx_quic_keys_cleanup(pkt.keys); + + return NGX_DONE; +} + + +ngx_int_t +ngx_quic_send_retry(ngx_connection_t *c, ngx_quic_conf_t *conf, + ngx_quic_header_t *inpkt) +{ + time_t expires; + ssize_t len; + ngx_str_t res, token; + ngx_quic_header_t pkt; + + u_char buf[NGX_QUIC_RETRY_BUFFER_SIZE]; + u_char dcid[NGX_QUIC_SERVER_CID_LEN]; + u_char tbuf[NGX_QUIC_TOKEN_BUF_SIZE]; + + expires = ngx_time() + NGX_QUIC_RETRY_TOKEN_LIFETIME; + + token.data = tbuf; + token.len = NGX_QUIC_TOKEN_BUF_SIZE; + + if (ngx_quic_new_token(c->log, c->sockaddr, c->socklen, conf->av_token_key, + &token, &inpkt->dcid, expires, 1) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_RETRY; + pkt.version = inpkt->version; + pkt.log = c->log; + + pkt.odcid = inpkt->dcid; + pkt.dcid = inpkt->scid; + + /* TODO: generate routable dcid */ + if (RAND_bytes(dcid, NGX_QUIC_SERVER_CID_LEN) != 1) { + return NGX_ERROR; + } + + pkt.scid.len = NGX_QUIC_SERVER_CID_LEN; + pkt.scid.data = dcid; + + pkt.token = token; + + res.data = buf; + + if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { + return NGX_ERROR; + } + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet to send len:%uz %xV", res.len, &res); +#endif + + len = ngx_quic_send(c, res.data, res.len, c->sockaddr, c->socklen); + if (len < 0) { + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic retry packet sent to %xV", &pkt.dcid); + + /* + * RFC 9000, 17.2.5.1. Sending a Retry Packet + * + * A server MUST NOT send more than one Retry + * packet in response to a single UDP datagram. + * NGX_DONE will stop quic_input() from processing further + */ + return NGX_DONE; +} + + +ngx_int_t +ngx_quic_send_new_token(ngx_connection_t *c, ngx_quic_path_t *path) +{ + time_t expires; + ngx_str_t token; + ngx_chain_t *out; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + u_char tbuf[NGX_QUIC_TOKEN_BUF_SIZE]; + + qc = ngx_quic_get_connection(c); + + expires = ngx_time() + NGX_QUIC_NEW_TOKEN_LIFETIME; + + token.data = tbuf; + token.len = NGX_QUIC_TOKEN_BUF_SIZE; + + if (ngx_quic_new_token(c->log, path->sockaddr, path->socklen, + qc->conf->av_token_key, &token, NULL, expires, 0) + != NGX_OK) + { + return NGX_ERROR; + } + + out = ngx_quic_copy_buffer(c, token.data, token.len); + if (out == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_NEW_TOKEN; + frame->data = out; + frame->u.token.length = token.len; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + size_t len, left; + uint64_t ack_delay; + ngx_buf_t *b; + ngx_uint_t i; + ngx_chain_t *cl, **ll; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ack_delay = ngx_current_msec - ctx->largest_received; + ack_delay *= 1000; + ack_delay >>= qc->tp.ack_delay_exponent; + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + ll = &frame->data; + b = NULL; + + for (i = 0; i < ctx->nranges; i++) { + len = ngx_quic_create_ack_range(NULL, ctx->ranges[i].gap, + ctx->ranges[i].range); + + left = b ? b->end - b->last : 0; + + if (left < len) { + cl = ngx_quic_alloc_chain(c); + if (cl == NULL) { + return NGX_ERROR; + } + + *ll = cl; + ll = &cl->next; + + b = cl->buf; + left = b->end - b->last; + + if (left < len) { + return NGX_ERROR; + } + } + + b->last += ngx_quic_create_ack_range(b->last, ctx->ranges[i].gap, + ctx->ranges[i].range); + + frame->u.ack.ranges_length += len; + } + + *ll = NULL; + + frame->level = ctx->level; + frame->type = NGX_QUIC_FT_ACK; + frame->u.ack.largest = ctx->largest_range; + frame->u.ack.delay = ack_delay; + frame->u.ack.range_count = ctx->nranges; + frame->u.ack.first_range = ctx->first_range; + frame->len = ngx_quic_create_frame(NULL, frame); + + ngx_queue_insert_head(&ctx->frames, &frame->queue); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t smallest, uint64_t largest) +{ + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ctx->level; + frame->type = NGX_QUIC_FT_ACK; + frame->u.ack.largest = largest; + frame->u.ack.delay = 0; + frame->u.ack.range_count = 0; + frame->u.ack.first_range = largest - smallest; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, + size_t min, ngx_quic_path_t *path) +{ + size_t max, max_payload, min_payload, pad; + ssize_t len, sent; + ngx_str_t res; + ngx_msec_t now; + ngx_quic_header_t pkt; + ngx_quic_send_ctx_t *ctx; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + + static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + ctx = ngx_quic_get_send_ctx(qc, frame->level); + + now = ngx_current_msec; + + max = ngx_quic_path_limit(c, path, path->mtu); + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic sendto %s packet max:%uz min:%uz", + ngx_quic_level_name(ctx->level), max, min); + + if (cg->in_flight >= cg->window && !frame->ignore_congestion) { + ngx_quic_free_frame(c, frame); + return NGX_AGAIN; + } + + ngx_quic_init_packet(c, ctx, &pkt, path); + + min_payload = ngx_quic_payload_size(&pkt, min); + max_payload = ngx_quic_payload_size(&pkt, max); + + /* RFC 9001, 5.4.2. Header Protection Sample */ + pad = 4 - pkt.num_len; + min_payload = ngx_max(min_payload, pad); + + if (min_payload > max_payload) { + ngx_quic_free_frame(c, frame); + return NGX_AGAIN; + } + +#if (NGX_DEBUG) + frame->pnum = pkt.number; +#endif + + ngx_quic_log_frame(c->log, frame, 1); + + len = ngx_quic_create_frame(NULL, frame); + if ((size_t) len > max_payload) { + ngx_quic_free_frame(c, frame); + return NGX_AGAIN; + } + + len = ngx_quic_create_frame(src, frame); + if (len == -1) { + ngx_quic_free_frame(c, frame); + return NGX_ERROR; + } + + if (len < (ssize_t) min_payload) { + ngx_memset(src + len, NGX_QUIC_FT_PADDING, min_payload - len); + len = min_payload; + } + + pkt.payload.data = src; + pkt.payload.len = len; + + res.data = dst; + + ngx_quic_log_packet(c->log, &pkt); + + if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { + ngx_quic_free_frame(c, frame); + return NGX_ERROR; + } + + frame->pnum = ctx->pnum; + frame->send_time = now; + frame->plen = res.len; + + ctx->pnum++; + + sent = ngx_quic_send(c, res.data, res.len, path->sockaddr, path->socklen); + if (sent < 0) { + ngx_quic_free_frame(c, frame); + return sent; + } + + path->sent += sent; + + if (frame->need_ack && !qc->closing) { + ngx_queue_insert_tail(&ctx->sent, &frame->queue); + + cg->in_flight += frame->plen; + + } else { + ngx_quic_free_frame(c, frame); + return NGX_OK; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion send if:%uz", cg->in_flight); + + if (!qc->send_timer_set) { + qc->send_timer_set = 1; + ngx_add_timer(c->read, qc->tp.max_idle_timeout); + } + + ngx_quic_set_lost_timer(c); + + return NGX_OK; +} + + +size_t +ngx_quic_path_limit(ngx_connection_t *c, ngx_quic_path_t *path, size_t size) +{ + off_t max; + + if (!path->validated) { + max = path->received * 3; + max = (path->sent >= max) ? 0 : max - path->sent; + + if ((off_t) size > max) { + return max; + } + } + + return size; +} diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_output.h b/src/deps/src/nginx/src/event/quic/ngx_event_quic_output.h new file mode 100644 index 000000000..52d8a374f --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_output.h @@ -0,0 +1,40 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_ +#define _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_ + + +#include +#include + + +ngx_int_t ngx_quic_output(ngx_connection_t *c); + +ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c, + ngx_quic_header_t *inpkt); + +ngx_int_t ngx_quic_send_stateless_reset(ngx_connection_t *c, + ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +ngx_int_t ngx_quic_send_cc(ngx_connection_t *c); +ngx_int_t ngx_quic_send_early_cc(ngx_connection_t *c, + ngx_quic_header_t *inpkt, ngx_uint_t err, const char *reason); + +ngx_int_t ngx_quic_send_retry(ngx_connection_t *c, + ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +ngx_int_t ngx_quic_send_new_token(ngx_connection_t *c, ngx_quic_path_t *path); + +ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx); +ngx_int_t ngx_quic_send_ack_range(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, uint64_t smallest, uint64_t largest); + +ngx_int_t ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, + size_t min, ngx_quic_path_t *path); +size_t ngx_quic_path_limit(ngx_connection_t *c, ngx_quic_path_t *path, + size_t size); + +#endif /* _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_ */ diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_protection.c b/src/deps/src/nginx/src/event/quic/ngx_event_quic_protection.c new file mode 100644 index 000000000..8223626b6 --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_protection.c @@ -0,0 +1,1243 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +/* RFC 9001, 5.4.1. Header Protection Application: 5-byte mask */ +#define NGX_QUIC_HP_LEN 5 + +#define NGX_QUIC_AES_128_KEY_LEN 16 + +#define NGX_QUIC_INITIAL_CIPHER TLS1_3_CK_AES_128_GCM_SHA256 + + +static ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len, + const EVP_MD *digest, const u_char *prk, size_t prk_len, + const u_char *info, size_t info_len); +static ngx_int_t ngx_hkdf_extract(u_char *out_key, size_t *out_len, + const EVP_MD *digest, const u_char *secret, size_t secret_len, + const u_char *salt, size_t salt_len); + +static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask, + uint64_t *largest_pn); + +static ngx_int_t ngx_quic_crypto_open(ngx_quic_secret_t *s, ngx_str_t *out, + u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log); +#ifndef OPENSSL_IS_BORINGSSL +static ngx_int_t ngx_quic_crypto_common(ngx_quic_secret_t *s, ngx_str_t *out, + u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log); +#endif + +static ngx_int_t ngx_quic_crypto_hp_init(const EVP_CIPHER *cipher, + ngx_quic_secret_t *s, ngx_log_t *log); +static ngx_int_t ngx_quic_crypto_hp(ngx_quic_secret_t *s, + u_char *out, u_char *in, ngx_log_t *log); +static void ngx_quic_crypto_hp_cleanup(ngx_quic_secret_t *s); + +static ngx_int_t ngx_quic_create_packet(ngx_quic_header_t *pkt, + ngx_str_t *res); +static ngx_int_t ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, + ngx_str_t *res); + + +ngx_int_t +ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers) +{ + ngx_int_t len; + + switch (id) { + + case TLS1_3_CK_AES_128_GCM_SHA256: +#ifdef OPENSSL_IS_BORINGSSL + ciphers->c = EVP_aead_aes_128_gcm(); +#else + ciphers->c = EVP_aes_128_gcm(); +#endif + ciphers->hp = EVP_aes_128_ctr(); + ciphers->d = EVP_sha256(); + len = 16; + break; + + case TLS1_3_CK_AES_256_GCM_SHA384: +#ifdef OPENSSL_IS_BORINGSSL + ciphers->c = EVP_aead_aes_256_gcm(); +#else + ciphers->c = EVP_aes_256_gcm(); +#endif + ciphers->hp = EVP_aes_256_ctr(); + ciphers->d = EVP_sha384(); + len = 32; + break; + + case TLS1_3_CK_CHACHA20_POLY1305_SHA256: +#ifdef OPENSSL_IS_BORINGSSL + ciphers->c = EVP_aead_chacha20_poly1305(); +#else + ciphers->c = EVP_chacha20_poly1305(); +#endif +#ifdef OPENSSL_IS_BORINGSSL + ciphers->hp = (const EVP_CIPHER *) EVP_aead_chacha20_poly1305(); +#else + ciphers->hp = EVP_chacha20(); +#endif + ciphers->d = EVP_sha256(); + len = 32; + break; + +#ifndef OPENSSL_IS_BORINGSSL + case TLS1_3_CK_AES_128_CCM_SHA256: + ciphers->c = EVP_aes_128_ccm(); + ciphers->hp = EVP_aes_128_ctr(); + ciphers->d = EVP_sha256(); + len = 16; + break; +#endif + + default: + return NGX_ERROR; + } + + return len; +} + + +ngx_int_t +ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys, ngx_str_t *secret, + ngx_log_t *log) +{ + size_t is_len; + uint8_t is[SHA256_DIGEST_LENGTH]; + ngx_str_t iss; + ngx_uint_t i; + const EVP_MD *digest; + ngx_quic_md_t client_key, server_key; + ngx_quic_hkdf_t seq[8]; + ngx_quic_secret_t *client, *server; + ngx_quic_ciphers_t ciphers; + + static const uint8_t salt[20] = + "\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17" + "\x9a\xe6\xa4\xc8\x0c\xad\xcc\xbb\x7f\x0a"; + + client = &keys->secrets[ssl_encryption_initial].client; + server = &keys->secrets[ssl_encryption_initial].server; + + /* + * RFC 9001, section 5. Packet Protection + * + * Initial packets use AEAD_AES_128_GCM. The hash function + * for HKDF when deriving initial secrets and keys is SHA-256. + */ + + digest = EVP_sha256(); + is_len = SHA256_DIGEST_LENGTH; + + if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len, + salt, sizeof(salt)) + != NGX_OK) + { + return NGX_ERROR; + } + + iss.len = is_len; + iss.data = is; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, + "quic ngx_quic_set_initial_secret"); +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, log, 0, + "quic salt len:%uz %*xs", sizeof(salt), sizeof(salt), salt); + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, log, 0, + "quic initial secret len:%uz %*xs", is_len, is_len, is); +#endif + + client->secret.len = SHA256_DIGEST_LENGTH; + server->secret.len = SHA256_DIGEST_LENGTH; + + client_key.len = NGX_QUIC_AES_128_KEY_LEN; + server_key.len = NGX_QUIC_AES_128_KEY_LEN; + + client->hp.len = NGX_QUIC_AES_128_KEY_LEN; + server->hp.len = NGX_QUIC_AES_128_KEY_LEN; + + client->iv.len = NGX_QUIC_IV_LEN; + server->iv.len = NGX_QUIC_IV_LEN; + + /* labels per RFC 9001, 5.1. Packet Protection Keys */ + ngx_quic_hkdf_set(&seq[0], "tls13 client in", &client->secret, &iss); + ngx_quic_hkdf_set(&seq[1], "tls13 quic key", &client_key, &client->secret); + ngx_quic_hkdf_set(&seq[2], "tls13 quic iv", &client->iv, &client->secret); + ngx_quic_hkdf_set(&seq[3], "tls13 quic hp", &client->hp, &client->secret); + ngx_quic_hkdf_set(&seq[4], "tls13 server in", &server->secret, &iss); + ngx_quic_hkdf_set(&seq[5], "tls13 quic key", &server_key, &server->secret); + ngx_quic_hkdf_set(&seq[6], "tls13 quic iv", &server->iv, &server->secret); + ngx_quic_hkdf_set(&seq[7], "tls13 quic hp", &server->hp, &server->secret); + + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { + if (ngx_quic_hkdf_expand(&seq[i], digest, log) != NGX_OK) { + return NGX_ERROR; + } + } + + if (ngx_quic_ciphers(NGX_QUIC_INITIAL_CIPHER, &ciphers) == NGX_ERROR) { + return NGX_ERROR; + } + + if (ngx_quic_crypto_init(ciphers.c, client, &client_key, 0, log) + == NGX_ERROR) + { + return NGX_ERROR; + } + + if (ngx_quic_crypto_init(ciphers.c, server, &server_key, 1, log) + == NGX_ERROR) + { + goto failed; + } + + if (ngx_quic_crypto_hp_init(ciphers.hp, client, log) == NGX_ERROR) { + goto failed; + } + + if (ngx_quic_crypto_hp_init(ciphers.hp, server, log) == NGX_ERROR) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_quic_keys_cleanup(keys); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_quic_hkdf_expand(ngx_quic_hkdf_t *h, const EVP_MD *digest, ngx_log_t *log) +{ + size_t info_len; + uint8_t *p; + uint8_t info[20]; + + info_len = 2 + 1 + h->label_len + 1; + + info[0] = 0; + info[1] = h->out_len; + info[2] = h->label_len; + + p = ngx_cpymem(&info[3], h->label, h->label_len); + *p = '\0'; + + if (ngx_hkdf_expand(h->out, h->out_len, digest, + h->prk, h->prk_len, info, info_len) + != NGX_OK) + { + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "ngx_hkdf_expand(%*s) failed", h->label_len, h->label); + return NGX_ERROR; + } + +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, log, 0, + "quic expand \"%*s\" len:%uz %*xs", + h->label_len, h->label, h->out_len, h->out_len, h->out); +#endif + + return NGX_OK; +} + + +static ngx_int_t +ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest, + const uint8_t *prk, size_t prk_len, const u_char *info, size_t info_len) +{ +#ifdef OPENSSL_IS_BORINGSSL + + if (HKDF_expand(out_key, out_len, digest, prk, prk_len, info, info_len) + == 0) + { + return NGX_ERROR; + } + + return NGX_OK; + +#else + + EVP_PKEY_CTX *pctx; + + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + if (pctx == NULL) { + return NGX_ERROR; + } + + if (EVP_PKEY_derive_init(pctx) <= 0) { + goto failed; + } + + if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) <= 0) { + goto failed; + } + + if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) { + goto failed; + } + + if (EVP_PKEY_CTX_set1_hkdf_key(pctx, prk, prk_len) <= 0) { + goto failed; + } + + if (EVP_PKEY_CTX_add1_hkdf_info(pctx, info, info_len) <= 0) { + goto failed; + } + + if (EVP_PKEY_derive(pctx, out_key, &out_len) <= 0) { + goto failed; + } + + EVP_PKEY_CTX_free(pctx); + + return NGX_OK; + +failed: + + EVP_PKEY_CTX_free(pctx); + + return NGX_ERROR; + +#endif +} + + +static ngx_int_t +ngx_hkdf_extract(u_char *out_key, size_t *out_len, const EVP_MD *digest, + const u_char *secret, size_t secret_len, const u_char *salt, + size_t salt_len) +{ +#ifdef OPENSSL_IS_BORINGSSL + + if (HKDF_extract(out_key, out_len, digest, secret, secret_len, salt, + salt_len) + == 0) + { + return NGX_ERROR; + } + + return NGX_OK; + +#else + + EVP_PKEY_CTX *pctx; + + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + if (pctx == NULL) { + return NGX_ERROR; + } + + if (EVP_PKEY_derive_init(pctx) <= 0) { + goto failed; + } + + if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) <= 0) { + goto failed; + } + + if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) { + goto failed; + } + + if (EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, secret_len) <= 0) { + goto failed; + } + + if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, salt_len) <= 0) { + goto failed; + } + + if (EVP_PKEY_derive(pctx, out_key, out_len) <= 0) { + goto failed; + } + + EVP_PKEY_CTX_free(pctx); + + return NGX_OK; + +failed: + + EVP_PKEY_CTX_free(pctx); + + return NGX_ERROR; + +#endif +} + + +ngx_int_t +ngx_quic_crypto_init(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, + ngx_quic_md_t *key, ngx_int_t enc, ngx_log_t *log) +{ + +#ifdef OPENSSL_IS_BORINGSSL + EVP_AEAD_CTX *ctx; + + ctx = EVP_AEAD_CTX_new(cipher, key->data, key->len, + EVP_AEAD_DEFAULT_TAG_LENGTH); + if (ctx == NULL) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_new() failed"); + return NGX_ERROR; + } +#else + EVP_CIPHER_CTX *ctx; + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CIPHER_CTX_new() failed"); + return NGX_ERROR; + } + + if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, enc) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CipherInit_ex() failed"); + return NGX_ERROR; + } + + if (EVP_CIPHER_mode(cipher) == EVP_CIPH_CCM_MODE + && EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, NGX_QUIC_TAG_LEN, + NULL) + == 0) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_AEAD_SET_TAG) failed"); + return NGX_ERROR; + } + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, s->iv.len, NULL) + == 0) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_AEAD_SET_IVLEN) failed"); + return NGX_ERROR; + } + + if (EVP_CipherInit_ex(ctx, NULL, NULL, key->data, NULL, enc) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CipherInit_ex() failed"); + return NGX_ERROR; + } +#endif + + s->ctx = ctx; + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_crypto_open(ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, + ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log) +{ +#ifdef OPENSSL_IS_BORINGSSL + if (EVP_AEAD_CTX_open(s->ctx, out->data, &out->len, out->len, nonce, + s->iv.len, in->data, in->len, ad->data, ad->len) + != 1) + { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_open() failed"); + return NGX_ERROR; + } + + return NGX_OK; +#else + return ngx_quic_crypto_common(s, out, nonce, in, ad, log); +#endif +} + + +ngx_int_t +ngx_quic_crypto_seal(ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, + ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log) +{ +#ifdef OPENSSL_IS_BORINGSSL + if (EVP_AEAD_CTX_seal(s->ctx, out->data, &out->len, out->len, nonce, + s->iv.len, in->data, in->len, ad->data, ad->len) + != 1) + { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_seal() failed"); + return NGX_ERROR; + } + + return NGX_OK; +#else + return ngx_quic_crypto_common(s, out, nonce, in, ad, log); +#endif +} + + +#ifndef OPENSSL_IS_BORINGSSL + +static ngx_int_t +ngx_quic_crypto_common(ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, + ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log) +{ + int len, enc; + ngx_quic_crypto_ctx_t *ctx; + + ctx = s->ctx; + enc = EVP_CIPHER_CTX_encrypting(ctx); + + if (EVP_CipherInit_ex(ctx, NULL, NULL, NULL, nonce, enc) != 1) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CipherInit_ex() failed"); + return NGX_ERROR; + } + + if (enc == 0) { + in->len -= NGX_QUIC_TAG_LEN; + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, NGX_QUIC_TAG_LEN, + in->data + in->len) + == 0) + { + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_AEAD_SET_TAG) failed"); + return NGX_ERROR; + } + } + + if (EVP_CIPHER_mode(EVP_CIPHER_CTX_cipher(ctx)) == EVP_CIPH_CCM_MODE + && EVP_CipherUpdate(ctx, NULL, &len, NULL, in->len) != 1) + { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CipherUpdate() failed"); + return NGX_ERROR; + } + + if (EVP_CipherUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CipherUpdate() failed"); + return NGX_ERROR; + } + + if (EVP_CipherUpdate(ctx, out->data, &len, in->data, in->len) != 1) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CipherUpdate() failed"); + return NGX_ERROR; + } + + out->len = len; + + if (EVP_CipherFinal_ex(ctx, out->data + out->len, &len) <= 0) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CipherFinal_ex failed"); + return NGX_ERROR; + } + + out->len += len; + + if (enc == 1) { + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, NGX_QUIC_TAG_LEN, + out->data + out->len) + == 0) + { + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_AEAD_GET_TAG) failed"); + return NGX_ERROR; + } + + out->len += NGX_QUIC_TAG_LEN; + } + + return NGX_OK; +} + +#endif + + +void +ngx_quic_crypto_cleanup(ngx_quic_secret_t *s) +{ + if (s->ctx) { +#ifdef OPENSSL_IS_BORINGSSL + EVP_AEAD_CTX_free(s->ctx); +#else + EVP_CIPHER_CTX_free(s->ctx); +#endif + s->ctx = NULL; + } +} + + +static ngx_int_t +ngx_quic_crypto_hp_init(const EVP_CIPHER *cipher, ngx_quic_secret_t *s, + ngx_log_t *log) +{ + EVP_CIPHER_CTX *ctx; + +#ifdef OPENSSL_IS_BORINGSSL + if (cipher == (EVP_CIPHER *) EVP_aead_chacha20_poly1305()) { + /* no EVP interface */ + s->hp_ctx = NULL; + return NGX_OK; + } +#endif + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CIPHER_CTX_new() failed"); + return NGX_ERROR; + } + + if (EVP_EncryptInit_ex(ctx, cipher, NULL, s->hp.data, NULL) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); + return NGX_ERROR; + } + + s->hp_ctx = ctx; + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_crypto_hp(ngx_quic_secret_t *s, u_char *out, u_char *in, + ngx_log_t *log) +{ + int outlen; + EVP_CIPHER_CTX *ctx; + u_char zero[NGX_QUIC_HP_LEN] = {0}; + + ctx = s->hp_ctx; + +#ifdef OPENSSL_IS_BORINGSSL + uint32_t cnt; + + if (ctx == NULL) { + ngx_memcpy(&cnt, in, sizeof(uint32_t)); + CRYPTO_chacha_20(out, zero, NGX_QUIC_HP_LEN, s->hp.data, &in[4], cnt); + return NGX_OK; + } +#endif + + if (EVP_EncryptInit_ex(ctx, NULL, NULL, NULL, in) != 1) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); + return NGX_ERROR; + } + + if (!EVP_EncryptUpdate(ctx, out, &outlen, zero, NGX_QUIC_HP_LEN)) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed"); + return NGX_ERROR; + } + + if (!EVP_EncryptFinal_ex(ctx, out + NGX_QUIC_HP_LEN, &outlen)) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptFinal_Ex() failed"); + return NGX_ERROR; + } + + return NGX_OK; +} + + +static void +ngx_quic_crypto_hp_cleanup(ngx_quic_secret_t *s) +{ + if (s->hp_ctx) { + EVP_CIPHER_CTX_free(s->hp_ctx); + s->hp_ctx = NULL; + } +} + + +ngx_int_t +ngx_quic_keys_set_encryption_secret(ngx_log_t *log, ngx_uint_t is_write, + ngx_quic_keys_t *keys, enum ssl_encryption_level_t level, + const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len) +{ + ngx_int_t key_len; + ngx_str_t secret_str; + ngx_uint_t i; + ngx_quic_md_t key; + ngx_quic_hkdf_t seq[3]; + ngx_quic_secret_t *peer_secret; + ngx_quic_ciphers_t ciphers; + + peer_secret = is_write ? &keys->secrets[level].server + : &keys->secrets[level].client; + + keys->cipher = SSL_CIPHER_get_id(cipher); + + key_len = ngx_quic_ciphers(keys->cipher, &ciphers); + + if (key_len == NGX_ERROR) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "unexpected cipher"); + return NGX_ERROR; + } + + if (sizeof(peer_secret->secret.data) < secret_len) { + ngx_log_error(NGX_LOG_ALERT, log, 0, + "unexpected secret len: %uz", secret_len); + return NGX_ERROR; + } + + peer_secret->secret.len = secret_len; + ngx_memcpy(peer_secret->secret.data, secret, secret_len); + + key.len = key_len; + peer_secret->iv.len = NGX_QUIC_IV_LEN; + peer_secret->hp.len = key_len; + + secret_str.len = secret_len; + secret_str.data = (u_char *) secret; + + ngx_quic_hkdf_set(&seq[0], "tls13 quic key", &key, &secret_str); + ngx_quic_hkdf_set(&seq[1], "tls13 quic iv", &peer_secret->iv, &secret_str); + ngx_quic_hkdf_set(&seq[2], "tls13 quic hp", &peer_secret->hp, &secret_str); + + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { + if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, log) != NGX_OK) { + return NGX_ERROR; + } + } + + if (ngx_quic_crypto_init(ciphers.c, peer_secret, &key, is_write, log) + == NGX_ERROR) + { + return NGX_ERROR; + } + + if (ngx_quic_crypto_hp_init(ciphers.hp, peer_secret, log) == NGX_ERROR) { + return NGX_ERROR; + } + + ngx_explicit_memzero(key.data, key.len); + + return NGX_OK; +} + + +ngx_uint_t +ngx_quic_keys_available(ngx_quic_keys_t *keys, + enum ssl_encryption_level_t level, ngx_uint_t is_write) +{ + if (is_write == 0) { + return keys->secrets[level].client.ctx != NULL; + } + + return keys->secrets[level].server.ctx != NULL; +} + + +void +ngx_quic_keys_discard(ngx_quic_keys_t *keys, + enum ssl_encryption_level_t level) +{ + ngx_quic_secret_t *client, *server; + + client = &keys->secrets[level].client; + server = &keys->secrets[level].server; + + ngx_quic_crypto_cleanup(client); + ngx_quic_crypto_cleanup(server); + + ngx_quic_crypto_hp_cleanup(client); + ngx_quic_crypto_hp_cleanup(server); + + ngx_explicit_memzero(client->secret.data, client->secret.len); + ngx_explicit_memzero(server->secret.data, server->secret.len); +} + + +void +ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys) +{ + ngx_quic_secrets_t *current, *next, tmp; + + current = &keys->secrets[ssl_encryption_application]; + next = &keys->next_key; + + ngx_quic_crypto_cleanup(¤t->client); + ngx_quic_crypto_cleanup(¤t->server); + + tmp = *current; + *current = *next; + *next = tmp; +} + + +void +ngx_quic_keys_update(ngx_event_t *ev) +{ + ngx_int_t key_len; + ngx_uint_t i; + ngx_quic_md_t client_key, server_key; + ngx_quic_hkdf_t seq[6]; + ngx_quic_keys_t *keys; + ngx_connection_t *c; + ngx_quic_ciphers_t ciphers; + ngx_quic_secrets_t *current, *next; + ngx_quic_connection_t *qc; + + c = ev->data; + qc = ngx_quic_get_connection(c); + keys = qc->keys; + + current = &keys->secrets[ssl_encryption_application]; + next = &keys->next_key; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic key update"); + + c->log->action = "updating keys"; + + key_len = ngx_quic_ciphers(keys->cipher, &ciphers); + + if (key_len == NGX_ERROR) { + goto failed; + } + + client_key.len = key_len; + server_key.len = key_len; + + next->client.secret.len = current->client.secret.len; + next->client.iv.len = NGX_QUIC_IV_LEN; + next->client.hp = current->client.hp; + next->client.hp_ctx = current->client.hp_ctx; + + next->server.secret.len = current->server.secret.len; + next->server.iv.len = NGX_QUIC_IV_LEN; + next->server.hp = current->server.hp; + next->server.hp_ctx = current->server.hp_ctx; + + ngx_quic_hkdf_set(&seq[0], "tls13 quic ku", + &next->client.secret, ¤t->client.secret); + ngx_quic_hkdf_set(&seq[1], "tls13 quic key", + &client_key, &next->client.secret); + ngx_quic_hkdf_set(&seq[2], "tls13 quic iv", + &next->client.iv, &next->client.secret); + ngx_quic_hkdf_set(&seq[3], "tls13 quic ku", + &next->server.secret, ¤t->server.secret); + ngx_quic_hkdf_set(&seq[4], "tls13 quic key", + &server_key, &next->server.secret); + ngx_quic_hkdf_set(&seq[5], "tls13 quic iv", + &next->server.iv, &next->server.secret); + + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { + if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, c->log) != NGX_OK) { + goto failed; + } + } + + if (ngx_quic_crypto_init(ciphers.c, &next->client, &client_key, 0, c->log) + == NGX_ERROR) + { + goto failed; + } + + if (ngx_quic_crypto_init(ciphers.c, &next->server, &server_key, 1, c->log) + == NGX_ERROR) + { + goto failed; + } + + ngx_explicit_memzero(current->client.secret.data, + current->client.secret.len); + ngx_explicit_memzero(current->server.secret.data, + current->server.secret.len); + + ngx_explicit_memzero(client_key.data, client_key.len); + ngx_explicit_memzero(server_key.data, server_key.len); + + return; + +failed: + + ngx_quic_close_connection(c, NGX_ERROR); +} + + +void +ngx_quic_keys_cleanup(ngx_quic_keys_t *keys) +{ + ngx_uint_t i; + ngx_quic_secrets_t *next; + + for (i = 0; i < NGX_QUIC_ENCRYPTION_LAST; i++) { + ngx_quic_keys_discard(keys, i); + } + + next = &keys->next_key; + + ngx_quic_crypto_cleanup(&next->client); + ngx_quic_crypto_cleanup(&next->server); + + ngx_explicit_memzero(next->client.secret.data, + next->client.secret.len); + ngx_explicit_memzero(next->server.secret.data, + next->server.secret.len); +} + + +static ngx_int_t +ngx_quic_create_packet(ngx_quic_header_t *pkt, ngx_str_t *res) +{ + u_char *pnp, *sample; + ngx_str_t ad, out; + ngx_uint_t i; + ngx_quic_secret_t *secret; + u_char nonce[NGX_QUIC_IV_LEN], mask[NGX_QUIC_HP_LEN]; + + ad.data = res->data; + ad.len = ngx_quic_create_header(pkt, ad.data, &pnp); + + out.len = pkt->payload.len + NGX_QUIC_TAG_LEN; + out.data = res->data + ad.len; + +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic ad len:%uz %xV", ad.len, &ad); +#endif + + secret = &pkt->keys->secrets[pkt->level].server; + + ngx_memcpy(nonce, secret->iv.data, secret->iv.len); + ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number); + + if (ngx_quic_crypto_seal(secret, &out, nonce, &pkt->payload, &ad, pkt->log) + != NGX_OK) + { + return NGX_ERROR; + } + + sample = &out.data[4 - pkt->num_len]; + if (ngx_quic_crypto_hp(secret, mask, sample, pkt->log) != NGX_OK) { + return NGX_ERROR; + } + + /* RFC 9001, 5.4.1. Header Protection Application */ + ad.data[0] ^= mask[0] & ngx_quic_pkt_hp_mask(pkt->flags); + + for (i = 0; i < pkt->num_len; i++) { + pnp[i] ^= mask[i + 1]; + } + + res->len = ad.len + out.len; + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res) +{ + u_char *start; + ngx_str_t ad, itag; + ngx_quic_md_t key; + ngx_quic_secret_t secret; + ngx_quic_ciphers_t ciphers; + + /* 5.8. Retry Packet Integrity */ + static u_char key_data[16] = + "\xbe\x0c\x69\x0b\x9f\x66\x57\x5a\x1d\x76\x6b\x54\xe3\x68\xc8\x4e"; + static u_char nonce[NGX_QUIC_IV_LEN] = + "\x46\x15\x99\xd3\x5d\x63\x2b\xf2\x23\x98\x25\xbb"; + static ngx_str_t in = ngx_string(""); + + ad.data = res->data; + ad.len = ngx_quic_create_retry_itag(pkt, ad.data, &start); + + itag.data = ad.data + ad.len; + itag.len = NGX_QUIC_TAG_LEN; + +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic retry itag len:%uz %xV", ad.len, &ad); +#endif + + if (ngx_quic_ciphers(NGX_QUIC_INITIAL_CIPHER, &ciphers) == NGX_ERROR) { + return NGX_ERROR; + } + + key.len = sizeof(key_data); + ngx_memcpy(key.data, key_data, sizeof(key_data)); + secret.iv.len = NGX_QUIC_IV_LEN; + + if (ngx_quic_crypto_init(ciphers.c, &secret, &key, 1, pkt->log) + == NGX_ERROR) + { + return NGX_ERROR; + } + + if (ngx_quic_crypto_seal(&secret, &itag, nonce, &in, &ad, pkt->log) + != NGX_OK) + { + ngx_quic_crypto_cleanup(&secret); + return NGX_ERROR; + } + + ngx_quic_crypto_cleanup(&secret); + + res->len = itag.data + itag.len - start; + res->data = start; + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_derive_key(ngx_log_t *log, const char *label, ngx_str_t *secret, + ngx_str_t *salt, u_char *out, size_t len) +{ + size_t is_len, info_len; + uint8_t *p; + const EVP_MD *digest; + + uint8_t is[SHA256_DIGEST_LENGTH]; + uint8_t info[20]; + + digest = EVP_sha256(); + is_len = SHA256_DIGEST_LENGTH; + + if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len, + salt->data, salt->len) + != NGX_OK) + { + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "ngx_hkdf_extract(%s) failed", label); + return NGX_ERROR; + } + + info[0] = 0; + info[1] = len; + info[2] = ngx_strlen(label); + + info_len = 2 + 1 + info[2] + 1; + + if (info_len >= 20) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "ngx_quic_create_key label \"%s\" too long", label); + return NGX_ERROR; + } + + p = ngx_cpymem(&info[3], label, info[2]); + *p = '\0'; + + if (ngx_hkdf_expand(out, len, digest, is, is_len, info, info_len) != NGX_OK) + { + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "ngx_hkdf_expand(%s) failed", label); + return NGX_ERROR; + } + + return NGX_OK; +} + + +static uint64_t +ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask, + uint64_t *largest_pn) +{ + u_char *p; + uint64_t truncated_pn, expected_pn, candidate_pn; + uint64_t pn_nbits, pn_win, pn_hwin, pn_mask; + + pn_nbits = ngx_min(len * 8, 62); + + p = *pos; + truncated_pn = *p++ ^ *mask++; + + while (--len) { + truncated_pn = (truncated_pn << 8) + (*p++ ^ *mask++); + } + + *pos = p; + + expected_pn = *largest_pn + 1; + pn_win = 1ULL << pn_nbits; + pn_hwin = pn_win / 2; + pn_mask = pn_win - 1; + + candidate_pn = (expected_pn & ~pn_mask) | truncated_pn; + + if ((int64_t) candidate_pn <= (int64_t) (expected_pn - pn_hwin) + && candidate_pn < (1ULL << 62) - pn_win) + { + candidate_pn += pn_win; + + } else if (candidate_pn > expected_pn + pn_hwin + && candidate_pn >= pn_win) + { + candidate_pn -= pn_win; + } + + *largest_pn = ngx_max((int64_t) *largest_pn, (int64_t) candidate_pn); + + return candidate_pn; +} + + +void +ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn) +{ + nonce[len - 8] ^= (pn >> 56) & 0x3f; + nonce[len - 7] ^= (pn >> 48) & 0xff; + nonce[len - 6] ^= (pn >> 40) & 0xff; + nonce[len - 5] ^= (pn >> 32) & 0xff; + nonce[len - 4] ^= (pn >> 24) & 0xff; + nonce[len - 3] ^= (pn >> 16) & 0xff; + nonce[len - 2] ^= (pn >> 8) & 0xff; + nonce[len - 1] ^= pn & 0xff; +} + + +ngx_int_t +ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res) +{ + if (ngx_quic_pkt_retry(pkt->flags)) { + return ngx_quic_create_retry_packet(pkt, res); + } + + return ngx_quic_create_packet(pkt, res); +} + + +ngx_int_t +ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) +{ + u_char *p, *sample; + size_t len; + uint64_t pn, lpn; + ngx_int_t pnl; + ngx_str_t in, ad; + ngx_uint_t key_phase; + ngx_quic_secret_t *secret; + uint8_t nonce[NGX_QUIC_IV_LEN], mask[NGX_QUIC_HP_LEN]; + + secret = &pkt->keys->secrets[pkt->level].client; + + p = pkt->raw->pos; + len = pkt->data + pkt->len - p; + + /* + * RFC 9001, 5.4.2. Header Protection Sample + * 5.4.3. AES-Based Header Protection + * 5.4.4. ChaCha20-Based Header Protection + * + * the Packet Number field is assumed to be 4 bytes long + * AES and ChaCha20 algorithms sample 16 bytes + */ + + if (len < NGX_QUIC_TAG_LEN + 4) { + return NGX_DECLINED; + } + + sample = p + 4; + + /* header protection */ + + if (ngx_quic_crypto_hp(secret, mask, sample, pkt->log) != NGX_OK) { + return NGX_DECLINED; + } + + pkt->flags ^= mask[0] & ngx_quic_pkt_hp_mask(pkt->flags); + + if (ngx_quic_short_pkt(pkt->flags)) { + key_phase = (pkt->flags & NGX_QUIC_PKT_KPHASE) != 0; + + if (key_phase != pkt->key_phase) { + if (pkt->keys->next_key.client.ctx != NULL) { + secret = &pkt->keys->next_key.client; + pkt->key_update = 1; + + } else { + /* + * RFC 9001, 6.3. Timing of Receive Key Generation. + * + * Trial decryption to avoid timing side-channel. + */ + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic next key missing"); + } + } + } + + lpn = *largest_pn; + + pnl = (pkt->flags & 0x03) + 1; + pn = ngx_quic_parse_pn(&p, pnl, &mask[1], &lpn); + + pkt->pn = pn; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet rx clearflags:%xd", pkt->flags); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet rx number:%uL len:%xi", pn, pnl); + + /* packet protection */ + + in.data = p; + in.len = len - pnl; + + ad.len = p - pkt->data; + ad.data = pkt->plaintext; + + ngx_memcpy(ad.data, pkt->data, ad.len); + ad.data[0] = pkt->flags; + + do { + ad.data[ad.len - pnl] = pn >> (8 * (pnl - 1)) % 256; + } while (--pnl); + + ngx_memcpy(nonce, secret->iv.data, secret->iv.len); + ngx_quic_compute_nonce(nonce, sizeof(nonce), pn); + +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic ad len:%uz %xV", ad.len, &ad); +#endif + + pkt->payload.len = in.len - NGX_QUIC_TAG_LEN; + pkt->payload.data = pkt->plaintext + ad.len; + + if (ngx_quic_crypto_open(secret, &pkt->payload, nonce, &in, &ad, pkt->log) + != NGX_OK) + { + return NGX_DECLINED; + } + + if (pkt->payload.len == 0) { + /* + * RFC 9000, 12.4. Frames and Frame Types + * + * An endpoint MUST treat receipt of a packet containing no + * frames as a connection error of type PROTOCOL_VIOLATION. + */ + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic zero-length packet"); + pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + return NGX_ERROR; + } + + if (pkt->flags & ngx_quic_pkt_rb_mask(pkt->flags)) { + /* + * RFC 9000, Reserved Bits + * + * An endpoint MUST treat receipt of a packet that has + * a non-zero value for these bits, after removing both + * packet and header protection, as a connection error + * of type PROTOCOL_VIOLATION. + */ + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic reserved bit set in packet"); + pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + return NGX_ERROR; + } + +#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS) + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet payload len:%uz %xV", + pkt->payload.len, &pkt->payload); +#endif + + *largest_pn = lpn; + + return NGX_OK; +} diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_protection.h b/src/deps/src/nginx/src/event/quic/ngx_event_quic_protection.h new file mode 100644 index 000000000..34cfee61b --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_protection.h @@ -0,0 +1,120 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ +#define _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ + + +#include +#include + +#include + + +#define NGX_QUIC_ENCRYPTION_LAST ((ssl_encryption_application) + 1) + +/* RFC 5116, 5.1/5.3 and RFC 8439, 2.3/2.5 for all supported ciphers */ +#define NGX_QUIC_IV_LEN 12 +#define NGX_QUIC_TAG_LEN 16 + +/* largest hash used in TLS is SHA-384 */ +#define NGX_QUIC_MAX_MD_SIZE 48 + + +#ifdef OPENSSL_IS_BORINGSSL +#define ngx_quic_cipher_t EVP_AEAD +#define ngx_quic_crypto_ctx_t EVP_AEAD_CTX +#else +#define ngx_quic_cipher_t EVP_CIPHER +#define ngx_quic_crypto_ctx_t EVP_CIPHER_CTX +#endif + + +typedef struct { + size_t len; + u_char data[NGX_QUIC_MAX_MD_SIZE]; +} ngx_quic_md_t; + + +typedef struct { + size_t len; + u_char data[NGX_QUIC_IV_LEN]; +} ngx_quic_iv_t; + + +typedef struct { + ngx_quic_md_t secret; + ngx_quic_iv_t iv; + ngx_quic_md_t hp; + ngx_quic_crypto_ctx_t *ctx; + EVP_CIPHER_CTX *hp_ctx; +} ngx_quic_secret_t; + + +typedef struct { + ngx_quic_secret_t client; + ngx_quic_secret_t server; +} ngx_quic_secrets_t; + + +struct ngx_quic_keys_s { + ngx_quic_secrets_t secrets[NGX_QUIC_ENCRYPTION_LAST]; + ngx_quic_secrets_t next_key; + ngx_uint_t cipher; +}; + + +typedef struct { + const ngx_quic_cipher_t *c; + const EVP_CIPHER *hp; + const EVP_MD *d; +} ngx_quic_ciphers_t; + + +typedef struct { + size_t out_len; + u_char *out; + + size_t prk_len; + const uint8_t *prk; + + size_t label_len; + const u_char *label; +} ngx_quic_hkdf_t; + +#define ngx_quic_hkdf_set(seq, _label, _out, _prk) \ + (seq)->out_len = (_out)->len; (seq)->out = (_out)->data; \ + (seq)->prk_len = (_prk)->len, (seq)->prk = (_prk)->data, \ + (seq)->label_len = (sizeof(_label) - 1); (seq)->label = (u_char *)(_label); + + +ngx_int_t ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys, + ngx_str_t *secret, ngx_log_t *log); +ngx_int_t ngx_quic_keys_set_encryption_secret(ngx_log_t *log, + ngx_uint_t is_write, ngx_quic_keys_t *keys, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *secret, size_t secret_len); +ngx_uint_t ngx_quic_keys_available(ngx_quic_keys_t *keys, + enum ssl_encryption_level_t level, ngx_uint_t is_write); +void ngx_quic_keys_discard(ngx_quic_keys_t *keys, + enum ssl_encryption_level_t level); +void ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys); +void ngx_quic_keys_update(ngx_event_t *ev); +void ngx_quic_keys_cleanup(ngx_quic_keys_t *keys); +ngx_int_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res); +ngx_int_t ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn); +void ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn); +ngx_int_t ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers); +ngx_int_t ngx_quic_crypto_init(const ngx_quic_cipher_t *cipher, + ngx_quic_secret_t *s, ngx_quic_md_t *key, ngx_int_t enc, ngx_log_t *log); +ngx_int_t ngx_quic_crypto_seal(ngx_quic_secret_t *s, ngx_str_t *out, + u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log); +void ngx_quic_crypto_cleanup(ngx_quic_secret_t *s); +ngx_int_t ngx_quic_hkdf_expand(ngx_quic_hkdf_t *hkdf, const EVP_MD *digest, + ngx_log_t *log); + + +#endif /* _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ */ diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_socket.c b/src/deps/src/nginx/src/event/quic/ngx_event_quic_socket.c new file mode 100644 index 000000000..c2bc822a5 --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_socket.c @@ -0,0 +1,237 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +ngx_int_t +ngx_quic_open_sockets(ngx_connection_t *c, ngx_quic_connection_t *qc, + ngx_quic_header_t *pkt) +{ + ngx_quic_socket_t *qsock, *tmp; + ngx_quic_client_id_t *cid; + + /* + * qc->path = NULL + * + * qc->nclient_ids = 0 + * qc->nsockets = 0 + * qc->max_retired_seqnum = 0 + * qc->client_seqnum = 0 + */ + + ngx_queue_init(&qc->sockets); + ngx_queue_init(&qc->free_sockets); + + ngx_queue_init(&qc->paths); + ngx_queue_init(&qc->free_paths); + + ngx_queue_init(&qc->client_ids); + ngx_queue_init(&qc->free_client_ids); + + qc->tp.original_dcid.len = pkt->odcid.len; + qc->tp.original_dcid.data = ngx_pstrdup(c->pool, &pkt->odcid); + if (qc->tp.original_dcid.data == NULL) { + return NGX_ERROR; + } + + /* socket to use for further processing (id auto-generated) */ + qsock = ngx_quic_create_socket(c, qc); + if (qsock == NULL) { + return NGX_ERROR; + } + + /* socket is listening at new server id */ + if (ngx_quic_listen(c, qc, qsock) != NGX_OK) { + return NGX_ERROR; + } + + qsock->used = 1; + + qc->tp.initial_scid.len = qsock->sid.len; + qc->tp.initial_scid.data = ngx_pnalloc(c->pool, qsock->sid.len); + if (qc->tp.initial_scid.data == NULL) { + goto failed; + } + ngx_memcpy(qc->tp.initial_scid.data, qsock->sid.id, qsock->sid.len); + + /* for all packets except first, this is set at udp layer */ + c->udp = &qsock->udp; + + /* ngx_quic_get_connection(c) macro is now usable */ + + /* we have a client identified by scid */ + cid = ngx_quic_create_client_id(c, &pkt->scid, 0, NULL); + if (cid == NULL) { + goto failed; + } + + /* path of the first packet is our initial active path */ + qc->path = ngx_quic_new_path(c, c->sockaddr, c->socklen, cid); + if (qc->path == NULL) { + goto failed; + } + + qc->path->tag = NGX_QUIC_PATH_ACTIVE; + + if (pkt->validated) { + qc->path->validated = 1; + } + + ngx_quic_path_dbg(c, "set active", qc->path); + + tmp = ngx_pcalloc(c->pool, sizeof(ngx_quic_socket_t)); + if (tmp == NULL) { + goto failed; + } + + tmp->sid.seqnum = NGX_QUIC_UNSET_PN; /* temporary socket */ + + ngx_memcpy(tmp->sid.id, pkt->dcid.data, pkt->dcid.len); + tmp->sid.len = pkt->dcid.len; + + if (ngx_quic_listen(c, qc, tmp) != NGX_OK) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node); + c->udp = NULL; + + return NGX_ERROR; +} + + +ngx_quic_socket_t * +ngx_quic_create_socket(ngx_connection_t *c, ngx_quic_connection_t *qc) +{ + ngx_queue_t *q; + ngx_quic_socket_t *sock; + + if (!ngx_queue_empty(&qc->free_sockets)) { + + q = ngx_queue_head(&qc->free_sockets); + sock = ngx_queue_data(q, ngx_quic_socket_t, queue); + + ngx_queue_remove(&sock->queue); + + ngx_memzero(sock, sizeof(ngx_quic_socket_t)); + + } else { + + sock = ngx_pcalloc(c->pool, sizeof(ngx_quic_socket_t)); + if (sock == NULL) { + return NULL; + } + } + + sock->sid.len = NGX_QUIC_SERVER_CID_LEN; + if (ngx_quic_create_server_id(c, sock->sid.id) != NGX_OK) { + return NULL; + } + + sock->sid.seqnum = qc->server_seqnum++; + + return sock; +} + + +void +ngx_quic_close_socket(ngx_connection_t *c, ngx_quic_socket_t *qsock) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ngx_queue_remove(&qsock->queue); + ngx_queue_insert_head(&qc->free_sockets, &qsock->queue); + + ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node); + qc->nsockets--; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic socket seq:%L closed nsock:%ui", + (int64_t) qsock->sid.seqnum, qc->nsockets); +} + + +ngx_int_t +ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc, + ngx_quic_socket_t *qsock) +{ + ngx_str_t id; + ngx_quic_server_id_t *sid; + + sid = &qsock->sid; + + id.data = sid->id; + id.len = sid->len; + + qsock->udp.connection = c; + qsock->udp.node.key = ngx_crc32_long(id.data, id.len); + qsock->udp.key = id; + + ngx_rbtree_insert(&c->listening->rbtree, &qsock->udp.node); + + ngx_queue_insert_tail(&qc->sockets, &qsock->queue); + + qc->nsockets++; + qsock->quic = qc; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic socket seq:%L listening at sid:%xV nsock:%ui", + (int64_t) sid->seqnum, &id, qc->nsockets); + + return NGX_OK; +} + + +void +ngx_quic_close_sockets(ngx_connection_t *c) +{ + ngx_queue_t *q; + ngx_quic_socket_t *qsock; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + while (!ngx_queue_empty(&qc->sockets)) { + q = ngx_queue_head(&qc->sockets); + qsock = ngx_queue_data(q, ngx_quic_socket_t, queue); + + ngx_quic_close_socket(c, qsock); + } +} + + +ngx_quic_socket_t * +ngx_quic_find_socket(ngx_connection_t *c, uint64_t seqnum) +{ + ngx_queue_t *q; + ngx_quic_socket_t *qsock; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + for (q = ngx_queue_head(&qc->sockets); + q != ngx_queue_sentinel(&qc->sockets); + q = ngx_queue_next(q)) + { + qsock = ngx_queue_data(q, ngx_quic_socket_t, queue); + + if (qsock->sid.seqnum == seqnum) { + return qsock; + } + } + + return NULL; +} diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_socket.h b/src/deps/src/nginx/src/event/quic/ngx_event_quic_socket.h new file mode 100644 index 000000000..68ecc063d --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_socket.h @@ -0,0 +1,28 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_ +#define _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_ + + +#include +#include + + +ngx_int_t ngx_quic_open_sockets(ngx_connection_t *c, + ngx_quic_connection_t *qc, ngx_quic_header_t *pkt); +void ngx_quic_close_sockets(ngx_connection_t *c); + +ngx_quic_socket_t *ngx_quic_create_socket(ngx_connection_t *c, + ngx_quic_connection_t *qc); +ngx_int_t ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc, + ngx_quic_socket_t *qsock); +void ngx_quic_close_socket(ngx_connection_t *c, ngx_quic_socket_t *qsock); + +ngx_quic_socket_t *ngx_quic_find_socket(ngx_connection_t *c, uint64_t seqnum); + + +#endif /* _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_ */ diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_ssl.c b/src/deps/src/nginx/src/event/quic/ngx_event_quic_ssl.c new file mode 100644 index 000000000..7872783f8 --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_ssl.c @@ -0,0 +1,590 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +#if defined OPENSSL_IS_BORINGSSL \ + || defined LIBRESSL_VERSION_NUMBER \ + || NGX_QUIC_OPENSSL_COMPAT +#define NGX_QUIC_BORINGSSL_API 1 +#endif + + +/* + * RFC 9000, 7.5. Cryptographic Message Buffering + * + * Implementations MUST support buffering at least 4096 bytes of data + */ +#define NGX_QUIC_MAX_BUFFERED 65535 + + +#if (NGX_QUIC_BORINGSSL_API) +static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *secret, size_t secret_len); +static int ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *secret, size_t secret_len); +#else +static int ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *read_secret, + const uint8_t *write_secret, size_t secret_len); +#endif + +static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *data, size_t len); +static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn); +static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, uint8_t alert); +static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data, + enum ssl_encryption_level_t level); + + +#if (NGX_QUIC_BORINGSSL_API) + +static int +ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *rsecret, size_t secret_len) +{ + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = ngx_quic_get_connection(c); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_set_read_secret() level:%d", level); +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic read secret len:%uz %*xs", secret_len, + secret_len, rsecret); +#endif + + if (ngx_quic_keys_set_encryption_secret(c->log, 0, qc->keys, level, + cipher, rsecret, secret_len) + != NGX_OK) + { + return 0; + } + + return 1; +} + + +static int +ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *wsecret, size_t secret_len) +{ + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = ngx_quic_get_connection(c); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_set_write_secret() level:%d", level); +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic write secret len:%uz %*xs", secret_len, + secret_len, wsecret); +#endif + + if (ngx_quic_keys_set_encryption_secret(c->log, 1, qc->keys, level, + cipher, wsecret, secret_len) + != NGX_OK) + { + return 0; + } + + return 1; +} + +#else + +static int +ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *rsecret, + const uint8_t *wsecret, size_t secret_len) +{ + ngx_connection_t *c; + const SSL_CIPHER *cipher; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = ngx_quic_get_connection(c); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_set_encryption_secrets() level:%d", level); +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic read secret len:%uz %*xs", secret_len, + secret_len, rsecret); +#endif + + cipher = SSL_get_current_cipher(ssl_conn); + + if (ngx_quic_keys_set_encryption_secret(c->log, 0, qc->keys, level, + cipher, rsecret, secret_len) + != NGX_OK) + { + return 0; + } + + if (level == ssl_encryption_early_data) { + return 1; + } + +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic write secret len:%uz %*xs", secret_len, + secret_len, wsecret); +#endif + + if (ngx_quic_keys_set_encryption_secret(c->log, 1, qc->keys, level, + cipher, wsecret, secret_len) + != NGX_OK) + { + return 0; + } + + return 1; +} + +#endif + + +static int +ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *data, size_t len) +{ + u_char *p, *end; + size_t client_params_len; + ngx_chain_t *out; + const uint8_t *client_params; + ngx_quic_tp_t ctp; + ngx_quic_frame_t *frame; + ngx_connection_t *c; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; +#if defined(TLSEXT_TYPE_application_layer_protocol_negotiation) + unsigned int alpn_len; + const unsigned char *alpn_data; +#endif + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = ngx_quic_get_connection(c); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_add_handshake_data"); + + if (!qc->client_tp_done) { + /* + * things to do once during handshake: check ALPN and transport + * parameters; we want to break handshake if something is wrong + * here; + */ + +#if defined(TLSEXT_TYPE_application_layer_protocol_negotiation) + + SSL_get0_alpn_selected(ssl_conn, &alpn_data, &alpn_len); + + if (alpn_len == 0) { + qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_NO_APPLICATION_PROTOCOL); + qc->error_reason = "unsupported protocol in ALPN extension"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic unsupported protocol in ALPN extension"); + return 0; + } + +#endif + + SSL_get_peer_quic_transport_params(ssl_conn, &client_params, + &client_params_len); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic SSL_get_peer_quic_transport_params():" + " params_len:%ui", client_params_len); + + if (client_params_len == 0) { + /* RFC 9001, 8.2. QUIC Transport Parameters Extension */ + qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_MISSING_EXTENSION); + qc->error_reason = "missing transport parameters"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "missing transport parameters"); + return 0; + } + + p = (u_char *) client_params; + end = p + client_params_len; + + /* defaults for parameters not sent by client */ + ngx_memcpy(&ctp, &qc->ctp, sizeof(ngx_quic_tp_t)); + + if (ngx_quic_parse_transport_params(p, end, &ctp, c->log) + != NGX_OK) + { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "failed to process transport parameters"; + + return 0; + } + + if (ngx_quic_apply_transport_params(c, &ctp) != NGX_OK) { + return 0; + } + + qc->client_tp_done = 1; + } + + ctx = ngx_quic_get_send_ctx(qc, level); + + out = ngx_quic_copy_buffer(c, (u_char *) data, len); + if (out == NGX_CHAIN_ERROR) { + return 0; + } + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return 0; + } + + frame->data = out; + frame->level = level; + frame->type = NGX_QUIC_FT_CRYPTO; + frame->u.crypto.offset = ctx->crypto_sent; + frame->u.crypto.length = len; + + ctx->crypto_sent += len; + + ngx_quic_queue_frame(qc, frame); + + return 1; +} + + +static int +ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn) +{ +#if (NGX_DEBUG) + ngx_connection_t *c; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_flush_flight()"); +#endif + return 1; +} + + +static int +ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, + uint8_t alert) +{ + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_send_alert() level:%s alert:%d", + ngx_quic_level_name(level), (int) alert); + + /* already closed on regular shutdown */ + + qc = ngx_quic_get_connection(c); + if (qc == NULL) { + return 1; + } + + qc->error = NGX_QUIC_ERR_CRYPTO(alert); + qc->error_reason = "handshake failed"; + + return 1; +} + + +ngx_int_t +ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, + ngx_quic_frame_t *frame) +{ + uint64_t last; + ngx_chain_t *cl; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + ngx_quic_crypto_frame_t *f; + + qc = ngx_quic_get_connection(c); + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + f = &frame->u.crypto; + + /* no overflow since both values are 62-bit */ + last = f->offset + f->length; + + if (last > ctx->crypto.offset + NGX_QUIC_MAX_BUFFERED) { + qc->error = NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED; + return NGX_ERROR; + } + + if (last <= ctx->crypto.offset) { + if (pkt->level == ssl_encryption_initial) { + /* speeding up handshake completion */ + + if (!ngx_queue_empty(&ctx->sent)) { + ngx_quic_resend_frames(c, ctx); + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); + while (!ngx_queue_empty(&ctx->sent)) { + ngx_quic_resend_frames(c, ctx); + } + } + } + + return NGX_OK; + } + + if (f->offset == ctx->crypto.offset) { + if (ngx_quic_crypto_input(c, frame->data, pkt->level) != NGX_OK) { + return NGX_ERROR; + } + + ngx_quic_skip_buffer(c, &ctx->crypto, last); + + } else { + if (ngx_quic_write_buffer(c, &ctx->crypto, frame->data, f->length, + f->offset) + == NGX_CHAIN_ERROR) + { + return NGX_ERROR; + } + } + + cl = ngx_quic_read_buffer(c, &ctx->crypto, (uint64_t) -1); + + if (cl) { + if (ngx_quic_crypto_input(c, cl, pkt->level) != NGX_OK) { + return NGX_ERROR; + } + + ngx_quic_free_chain(c, cl); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data, + enum ssl_encryption_level_t level) +{ + int n, sslerr; + ngx_buf_t *b; + ngx_chain_t *cl; + ngx_ssl_conn_t *ssl_conn; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ssl_conn = c->ssl->connection; + + for (cl = data; cl; cl = cl->next) { + b = cl->buf; + + if (!SSL_provide_quic_data(ssl_conn, level, b->pos, b->last - b->pos)) { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "SSL_provide_quic_data() failed"); + return NGX_ERROR; + } + } + + n = SSL_do_handshake(ssl_conn); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); + + if (n <= 0) { + sslerr = SSL_get_error(ssl_conn, n); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", + sslerr); + + if (sslerr != SSL_ERROR_WANT_READ) { + + if (c->ssl->handshake_rejected) { + ngx_connection_error(c, 0, "handshake rejected"); + ERR_clear_error(); + + return NGX_ERROR; + } + + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); + return NGX_ERROR; + } + } + + if (n <= 0 || SSL_in_init(ssl_conn)) { + if (ngx_quic_keys_available(qc->keys, ssl_encryption_early_data, 0) + && qc->client_tp_done) + { + if (ngx_quic_init_streams(c) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; + } + +#if (NGX_DEBUG) + ngx_ssl_handshake_log(c); +#endif + + c->ssl->handshaked = 1; + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_HANDSHAKE_DONE; + ngx_quic_queue_frame(qc, frame); + + if (qc->conf->retry) { + if (ngx_quic_send_new_token(c, qc->path) != NGX_OK) { + return NGX_ERROR; + } + } + + /* + * RFC 9001, 9.5. Header Protection Timing Side Channels + * + * Generating next keys before a key update is received. + */ + + ngx_post_event(&qc->key_update, &ngx_posted_events); + + /* + * RFC 9001, 4.9.2. Discarding Handshake Keys + * + * An endpoint MUST discard its Handshake keys + * when the TLS handshake is confirmed. + */ + ngx_quic_discard_ctx(c, ssl_encryption_handshake); + + ngx_quic_discover_path_mtu(c, qc->path); + + /* start accepting clients on negotiated number of server ids */ + if (ngx_quic_create_sockets(c) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_quic_init_streams(c) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_init_connection(ngx_connection_t *c) +{ + u_char *p; + size_t clen; + ssize_t len; + ngx_str_t dcid; + ngx_ssl_conn_t *ssl_conn; + ngx_quic_socket_t *qsock; + ngx_quic_connection_t *qc; + static SSL_QUIC_METHOD quic_method; + + qc = ngx_quic_get_connection(c); + + if (ngx_ssl_create_connection(qc->conf->ssl, c, 0) != NGX_OK) { + return NGX_ERROR; + } + + c->ssl->no_wait_shutdown = 1; + + ssl_conn = c->ssl->connection; + + if (!quic_method.send_alert) { +#if (NGX_QUIC_BORINGSSL_API) + quic_method.set_read_secret = ngx_quic_set_read_secret; + quic_method.set_write_secret = ngx_quic_set_write_secret; +#else + quic_method.set_encryption_secrets = ngx_quic_set_encryption_secrets; +#endif + quic_method.add_handshake_data = ngx_quic_add_handshake_data; + quic_method.flush_flight = ngx_quic_flush_flight; + quic_method.send_alert = ngx_quic_send_alert; + } + + if (SSL_set_quic_method(ssl_conn, &quic_method) == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic SSL_set_quic_method() failed"); + return NGX_ERROR; + } + +#ifdef OPENSSL_INFO_QUIC + if (SSL_CTX_get_max_early_data(qc->conf->ssl->ctx)) { + SSL_set_quic_early_data_enabled(ssl_conn, 1); + } +#endif + + qsock = ngx_quic_get_socket(c); + + dcid.data = qsock->sid.id; + dcid.len = qsock->sid.len; + + if (ngx_quic_new_sr_token(c, &dcid, qc->conf->sr_token_key, qc->tp.sr_token) + != NGX_OK) + { + return NGX_ERROR; + } + + len = ngx_quic_create_transport_params(NULL, NULL, &qc->tp, &clen); + /* always succeeds */ + + p = ngx_pnalloc(c->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + len = ngx_quic_create_transport_params(p, p + len, &qc->tp, NULL); + if (len < 0) { + return NGX_ERROR; + } + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic transport parameters len:%uz %*xs", len, len, p); +#endif + + if (SSL_set_quic_transport_params(ssl_conn, p, len) == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic SSL_set_quic_transport_params() failed"); + return NGX_ERROR; + } + +#ifdef OPENSSL_IS_BORINGSSL + if (SSL_set_quic_early_data_context(ssl_conn, p, clen) == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic SSL_set_quic_early_data_context() failed"); + return NGX_ERROR; + } +#endif + + return NGX_OK; +} diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_ssl.h b/src/deps/src/nginx/src/event/quic/ngx_event_quic_ssl.h new file mode 100644 index 000000000..ee0aa07c9 --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_ssl.h @@ -0,0 +1,19 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_SSL_H_INCLUDED_ +#define _NGX_EVENT_QUIC_SSL_H_INCLUDED_ + + +#include +#include + +ngx_int_t ngx_quic_init_connection(ngx_connection_t *c); + +ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_frame_t *frame); + +#endif /* _NGX_EVENT_QUIC_SSL_H_INCLUDED_ */ diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_streams.c b/src/deps/src/nginx/src/event/quic/ngx_event_quic_streams.c new file mode 100644 index 000000000..178b805e4 --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_streams.c @@ -0,0 +1,1820 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +#define NGX_QUIC_STREAM_GONE (void *) -1 + + +static ngx_int_t ngx_quic_do_reset_stream(ngx_quic_stream_t *qs, + ngx_uint_t err); +static ngx_int_t ngx_quic_shutdown_stream_send(ngx_connection_t *c); +static ngx_int_t ngx_quic_shutdown_stream_recv(ngx_connection_t *c); +static ngx_quic_stream_t *ngx_quic_get_stream(ngx_connection_t *c, uint64_t id); +static ngx_int_t ngx_quic_reject_stream(ngx_connection_t *c, uint64_t id); +static void ngx_quic_init_stream_handler(ngx_event_t *ev); +static void ngx_quic_init_streams_handler(ngx_connection_t *c); +static ngx_int_t ngx_quic_do_init_streams(ngx_connection_t *c); +static ngx_quic_stream_t *ngx_quic_create_stream(ngx_connection_t *c, + uint64_t id); +static void ngx_quic_empty_handler(ngx_event_t *ev); +static ssize_t ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, + size_t size); +static ssize_t ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, + size_t size); +static ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c, + ngx_chain_t *in, off_t limit); +static ngx_int_t ngx_quic_stream_flush(ngx_quic_stream_t *qs); +static void ngx_quic_stream_cleanup_handler(void *data); +static ngx_int_t ngx_quic_close_stream(ngx_quic_stream_t *qs); +static ngx_int_t ngx_quic_can_shutdown(ngx_connection_t *c); +static ngx_int_t ngx_quic_control_flow(ngx_quic_stream_t *qs, uint64_t last); +static ngx_int_t ngx_quic_update_flow(ngx_quic_stream_t *qs, uint64_t last); +static ngx_int_t ngx_quic_update_max_stream_data(ngx_quic_stream_t *qs); +static ngx_int_t ngx_quic_update_max_data(ngx_connection_t *c); +static void ngx_quic_set_event(ngx_event_t *ev); + + +ngx_connection_t * +ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) +{ + uint64_t id; + ngx_connection_t *pc, *sc; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + pc = c->quic ? c->quic->parent : c; + qc = ngx_quic_get_connection(pc); + + if (qc->closing) { + return NULL; + } + + if (bidi) { + if (qc->streams.server_streams_bidi + >= qc->streams.server_max_streams_bidi) + { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic too many server bidi streams:%uL", + qc->streams.server_streams_bidi); + return NULL; + } + + id = (qc->streams.server_streams_bidi << 2) + | NGX_QUIC_STREAM_SERVER_INITIATED; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic creating server bidi stream" + " streams:%uL max:%uL id:0x%xL", + qc->streams.server_streams_bidi, + qc->streams.server_max_streams_bidi, id); + + qc->streams.server_streams_bidi++; + + } else { + if (qc->streams.server_streams_uni + >= qc->streams.server_max_streams_uni) + { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic too many server uni streams:%uL", + qc->streams.server_streams_uni); + return NULL; + } + + id = (qc->streams.server_streams_uni << 2) + | NGX_QUIC_STREAM_SERVER_INITIATED + | NGX_QUIC_STREAM_UNIDIRECTIONAL; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic creating server uni stream" + " streams:%uL max:%uL id:0x%xL", + qc->streams.server_streams_uni, + qc->streams.server_max_streams_uni, id); + + qc->streams.server_streams_uni++; + } + + qs = ngx_quic_create_stream(pc, id); + if (qs == NULL) { + return NULL; + } + + sc = qs->connection; + + sc->write->active = 1; + sc->write->ready = 1; + + if (bidi) { + sc->read->active = 1; + } + + return sc; +} + + +void +ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) +{ + ngx_rbtree_node_t **p; + ngx_quic_stream_t *qn, *qnt; + + for ( ;; ) { + qn = (ngx_quic_stream_t *) node; + qnt = (ngx_quic_stream_t *) temp; + + p = (qn->id < qnt->id) ? &temp->left : &temp->right; + + if (*p == sentinel) { + break; + } + + temp = *p; + } + + *p = node; + node->parent = temp; + node->left = sentinel; + node->right = sentinel; + ngx_rbt_red(node); +} + + +ngx_quic_stream_t * +ngx_quic_find_stream(ngx_rbtree_t *rbtree, uint64_t id) +{ + ngx_rbtree_node_t *node, *sentinel; + ngx_quic_stream_t *qn; + + node = rbtree->root; + sentinel = rbtree->sentinel; + + while (node != sentinel) { + qn = (ngx_quic_stream_t *) node; + + if (id == qn->id) { + return qn; + } + + node = (id < qn->id) ? node->left : node->right; + } + + return NULL; +} + + +ngx_int_t +ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) +{ + ngx_pool_t *pool; + ngx_queue_t *q; + ngx_rbtree_t *tree; + ngx_connection_t *sc; + ngx_rbtree_node_t *node; + ngx_quic_stream_t *qs; + + while (!ngx_queue_empty(&qc->streams.uninitialized)) { + q = ngx_queue_head(&qc->streams.uninitialized); + ngx_queue_remove(q); + + qs = ngx_queue_data(q, ngx_quic_stream_t, queue); + pool = qs->connection->pool; + + ngx_close_connection(qs->connection); + ngx_destroy_pool(pool); + } + + tree = &qc->streams.tree; + + if (tree->root == tree->sentinel) { + return NGX_OK; + } + + node = ngx_rbtree_min(tree->root, tree->sentinel); + + while (node) { + qs = (ngx_quic_stream_t *) node; + node = ngx_rbtree_next(tree, node); + sc = qs->connection; + + qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_RECVD; + qs->send_state = NGX_QUIC_STREAM_SEND_RESET_SENT; + + if (sc == NULL) { + ngx_quic_close_stream(qs); + continue; + } + + sc->read->error = 1; + sc->write->error = 1; + + ngx_quic_set_event(sc->read); + ngx_quic_set_event(sc->write); + + sc->close = 1; + sc->read->handler(sc->read); + } + + if (tree->root == tree->sentinel) { + return NGX_OK; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic connection has active streams"); + + return NGX_AGAIN; +} + + +ngx_int_t +ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err) +{ + return ngx_quic_do_reset_stream(c->quic, err); +} + + +static ngx_int_t +ngx_quic_do_reset_stream(ngx_quic_stream_t *qs, ngx_uint_t err) +{ + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + if (qs->send_state == NGX_QUIC_STREAM_SEND_DATA_RECVD + || qs->send_state == NGX_QUIC_STREAM_SEND_RESET_SENT + || qs->send_state == NGX_QUIC_STREAM_SEND_RESET_RECVD) + { + return NGX_OK; + } + + qs->send_state = NGX_QUIC_STREAM_SEND_RESET_SENT; + qs->send_final_size = qs->send_offset; + + if (qs->connection) { + qs->connection->write->error = 1; + } + + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL reset", qs->id); + + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_RESET_STREAM; + frame->u.reset_stream.id = qs->id; + frame->u.reset_stream.error_code = err; + frame->u.reset_stream.final_size = qs->send_offset; + + ngx_quic_queue_frame(qc, frame); + + ngx_quic_free_buffer(pc, &qs->send); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_shutdown_stream(ngx_connection_t *c, int how) +{ + if (how == NGX_RDWR_SHUTDOWN || how == NGX_WRITE_SHUTDOWN) { + if (ngx_quic_shutdown_stream_send(c) != NGX_OK) { + return NGX_ERROR; + } + } + + if (how == NGX_RDWR_SHUTDOWN || how == NGX_READ_SHUTDOWN) { + if (ngx_quic_shutdown_stream_recv(c) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_shutdown_stream_send(ngx_connection_t *c) +{ + ngx_quic_stream_t *qs; + + qs = c->quic; + + if (qs->send_state != NGX_QUIC_STREAM_SEND_READY + && qs->send_state != NGX_QUIC_STREAM_SEND_SEND) + { + return NGX_OK; + } + + qs->send_state = NGX_QUIC_STREAM_SEND_SEND; + qs->send_final_size = c->sent; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, qs->parent->log, 0, + "quic stream id:0x%xL send shutdown", qs->id); + + return ngx_quic_stream_flush(qs); +} + + +static ngx_int_t +ngx_quic_shutdown_stream_recv(ngx_connection_t *c) +{ + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qs = c->quic; + + if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV + && qs->recv_state != NGX_QUIC_STREAM_RECV_SIZE_KNOWN) + { + return NGX_OK; + } + + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + if (qc->conf->stream_close_code == 0) { + return NGX_OK; + } + + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL recv shutdown", qs->id); + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_STOP_SENDING; + frame->u.stop_sending.id = qs->id; + frame->u.stop_sending.error_code = qc->conf->stream_close_code; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +static ngx_quic_stream_t * +ngx_quic_get_stream(ngx_connection_t *c, uint64_t id) +{ + uint64_t min_id; + ngx_event_t *rev; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + qs = ngx_quic_find_stream(&qc->streams.tree, id); + + if (qs) { + return qs; + } + + if (qc->shutdown || qc->closing) { + return NGX_QUIC_STREAM_GONE; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL is missing", id); + + if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + + if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + if ((id >> 2) < qc->streams.server_streams_uni) { + return NGX_QUIC_STREAM_GONE; + } + + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NULL; + } + + if ((id >> 2) < qc->streams.client_streams_uni) { + return NGX_QUIC_STREAM_GONE; + } + + if ((id >> 2) >= qc->streams.client_max_streams_uni) { + qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR; + return NULL; + } + + min_id = (qc->streams.client_streams_uni << 2) + | NGX_QUIC_STREAM_UNIDIRECTIONAL; + qc->streams.client_streams_uni = (id >> 2) + 1; + + } else { + + if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + if ((id >> 2) < qc->streams.server_streams_bidi) { + return NGX_QUIC_STREAM_GONE; + } + + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NULL; + } + + if ((id >> 2) < qc->streams.client_streams_bidi) { + return NGX_QUIC_STREAM_GONE; + } + + if ((id >> 2) >= qc->streams.client_max_streams_bidi) { + qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR; + return NULL; + } + + min_id = (qc->streams.client_streams_bidi << 2); + qc->streams.client_streams_bidi = (id >> 2) + 1; + } + + /* + * RFC 9000, 2.1. Stream Types and Identifiers + * + * successive streams of each type are created with numerically increasing + * stream IDs. A stream ID that is used out of order results in all + * streams of that type with lower-numbered stream IDs also being opened. + */ + +#if (NGX_SUPPRESS_WARN) + qs = NULL; +#endif + + for ( /* void */ ; min_id <= id; min_id += 0x04) { + + qs = ngx_quic_create_stream(c, min_id); + + if (qs == NULL) { + if (ngx_quic_reject_stream(c, min_id) != NGX_OK) { + return NULL; + } + + continue; + } + + ngx_queue_insert_tail(&qc->streams.uninitialized, &qs->queue); + + rev = qs->connection->read; + rev->handler = ngx_quic_init_stream_handler; + + if (qc->streams.initialized) { + ngx_post_event(rev, &ngx_posted_events); + + if (qc->push.posted) { + /* + * The posted stream can produce output immediately. + * By postponing the push event, we coalesce the stream + * output with queued frames in one UDP datagram. + */ + + ngx_delete_posted_event(&qc->push); + ngx_post_event(&qc->push, &ngx_posted_events); + } + } + } + + if (qs == NULL) { + return NGX_QUIC_STREAM_GONE; + } + + return qs; +} + + +static ngx_int_t +ngx_quic_reject_stream(ngx_connection_t *c, uint64_t id) +{ + uint64_t code; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + code = (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + ? qc->conf->stream_reject_code_uni + : qc->conf->stream_reject_code_bidi; + + if (code == 0) { + return NGX_DECLINED; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL reject err:0x%xL", id, code); + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_RESET_STREAM; + frame->u.reset_stream.id = id; + frame->u.reset_stream.error_code = code; + frame->u.reset_stream.final_size = 0; + + ngx_quic_queue_frame(qc, frame); + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_STOP_SENDING; + frame->u.stop_sending.id = id; + frame->u.stop_sending.error_code = code; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +static void +ngx_quic_init_stream_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + ngx_quic_stream_t *qs; + + c = ev->data; + qs = c->quic; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic init stream"); + + if ((qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { + c->write->active = 1; + c->write->ready = 1; + } + + c->read->active = 1; + + ngx_queue_remove(&qs->queue); + + c->listening->handler(c); +} + + +ngx_int_t +ngx_quic_init_streams(ngx_connection_t *c) +{ + ngx_int_t rc; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (qc->streams.initialized) { + return NGX_OK; + } + + rc = ngx_ssl_ocsp_validate(c); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_AGAIN) { + c->ssl->handler = ngx_quic_init_streams_handler; + return NGX_OK; + } + + return ngx_quic_do_init_streams(c); +} + + +static void +ngx_quic_init_streams_handler(ngx_connection_t *c) +{ + if (ngx_quic_do_init_streams(c) != NGX_OK) { + ngx_quic_close_connection(c, NGX_ERROR); + } +} + + +static ngx_int_t +ngx_quic_do_init_streams(ngx_connection_t *c) +{ + ngx_queue_t *q; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic init streams"); + + qc = ngx_quic_get_connection(c); + + if (qc->conf->init) { + if (qc->conf->init(c) != NGX_OK) { + return NGX_ERROR; + } + } + + for (q = ngx_queue_head(&qc->streams.uninitialized); + q != ngx_queue_sentinel(&qc->streams.uninitialized); + q = ngx_queue_next(q)) + { + qs = ngx_queue_data(q, ngx_quic_stream_t, queue); + ngx_post_event(qs->connection->read, &ngx_posted_events); + } + + qc->streams.initialized = 1; + + if (!qc->closing && qc->close.timer_set) { + ngx_del_timer(&qc->close); + } + + return NGX_OK; +} + + +static ngx_quic_stream_t * +ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) +{ + ngx_str_t addr_text; + ngx_log_t *log; + ngx_pool_t *pool; + ngx_uint_t reusable; + ngx_queue_t *q; + struct sockaddr *sockaddr; + ngx_connection_t *sc; + ngx_quic_stream_t *qs; + ngx_pool_cleanup_t *cln; + ngx_quic_connection_t *qc; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL create", id); + + qc = ngx_quic_get_connection(c); + + if (!ngx_queue_empty(&qc->streams.free)) { + q = ngx_queue_head(&qc->streams.free); + qs = ngx_queue_data(q, ngx_quic_stream_t, queue); + ngx_queue_remove(&qs->queue); + + } else { + /* + * the number of streams is limited by transport + * parameters and application requirements + */ + + qs = ngx_palloc(c->pool, sizeof(ngx_quic_stream_t)); + if (qs == NULL) { + return NULL; + } + } + + ngx_memzero(qs, sizeof(ngx_quic_stream_t)); + + qs->node.key = id; + qs->parent = c; + qs->id = id; + qs->send_final_size = (uint64_t) -1; + qs->recv_final_size = (uint64_t) -1; + + pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log); + if (pool == NULL) { + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + return NULL; + } + + log = ngx_palloc(pool, sizeof(ngx_log_t)); + if (log == NULL) { + ngx_destroy_pool(pool); + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + return NULL; + } + + *log = *c->log; + pool->log = log; + + sockaddr = ngx_palloc(pool, c->socklen); + if (sockaddr == NULL) { + ngx_destroy_pool(pool); + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + return NULL; + } + + ngx_memcpy(sockaddr, c->sockaddr, c->socklen); + + if (c->addr_text.data) { + addr_text.data = ngx_pnalloc(pool, c->addr_text.len); + if (addr_text.data == NULL) { + ngx_destroy_pool(pool); + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + return NULL; + } + + ngx_memcpy(addr_text.data, c->addr_text.data, c->addr_text.len); + addr_text.len = c->addr_text.len; + + } else { + addr_text.len = 0; + addr_text.data = NULL; + } + + reusable = c->reusable; + ngx_reusable_connection(c, 0); + + sc = ngx_get_connection(c->fd, log); + if (sc == NULL) { + ngx_destroy_pool(pool); + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + ngx_reusable_connection(c, reusable); + return NULL; + } + + qs->connection = sc; + + sc->quic = qs; + sc->shared = 1; + sc->type = SOCK_STREAM; + sc->pool = pool; + sc->ssl = c->ssl; + sc->sockaddr = sockaddr; + sc->socklen = c->socklen; + sc->listening = c->listening; + sc->addr_text = addr_text; + sc->local_sockaddr = c->local_sockaddr; + sc->local_socklen = c->local_socklen; + sc->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); + sc->start_time = c->start_time; + sc->tcp_nodelay = NGX_TCP_NODELAY_DISABLED; + + sc->recv = ngx_quic_stream_recv; + sc->send = ngx_quic_stream_send; + sc->send_chain = ngx_quic_stream_send_chain; + + sc->read->log = log; + sc->write->log = log; + + sc->read->handler = ngx_quic_empty_handler; + sc->write->handler = ngx_quic_empty_handler; + + log->connection = sc->number; + + if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + qs->send_max_data = qc->ctp.initial_max_stream_data_uni; + qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_READ; + qs->send_state = NGX_QUIC_STREAM_SEND_READY; + + } else { + qs->recv_max_data = qc->tp.initial_max_stream_data_uni; + qs->recv_state = NGX_QUIC_STREAM_RECV_RECV; + qs->send_state = NGX_QUIC_STREAM_SEND_DATA_RECVD; + } + + } else { + if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + qs->send_max_data = qc->ctp.initial_max_stream_data_bidi_remote; + qs->recv_max_data = qc->tp.initial_max_stream_data_bidi_local; + + } else { + qs->send_max_data = qc->ctp.initial_max_stream_data_bidi_local; + qs->recv_max_data = qc->tp.initial_max_stream_data_bidi_remote; + } + + qs->recv_state = NGX_QUIC_STREAM_RECV_RECV; + qs->send_state = NGX_QUIC_STREAM_SEND_READY; + } + + qs->recv_window = qs->recv_max_data; + + cln = ngx_pool_cleanup_add(pool, 0); + if (cln == NULL) { + ngx_close_connection(sc); + ngx_destroy_pool(pool); + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + ngx_reusable_connection(c, reusable); + return NULL; + } + + cln->handler = ngx_quic_stream_cleanup_handler; + cln->data = sc; + + ngx_rbtree_insert(&qc->streams.tree, &qs->node); + + return qs; +} + + +void +ngx_quic_cancelable_stream(ngx_connection_t *c) +{ + ngx_connection_t *pc; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qs = c->quic; + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + if (!qs->cancelable) { + qs->cancelable = 1; + + if (ngx_quic_can_shutdown(pc) == NGX_OK) { + ngx_reusable_connection(pc, 1); + + if (qc->shutdown) { + ngx_quic_shutdown_quic(pc); + } + } + } +} + + +static void +ngx_quic_empty_handler(ngx_event_t *ev) +{ +} + + +static ssize_t +ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) +{ + ssize_t len; + ngx_buf_t *b; + ngx_chain_t *cl, *in; + ngx_event_t *rev; + ngx_connection_t *pc; + ngx_quic_stream_t *qs; + + qs = c->quic; + pc = qs->parent; + rev = c->read; + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_RECVD + || qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_READ) + { + qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_READ; + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL recv buf:%uz", qs->id, size); + + if (size == 0) { + return 0; + } + + in = ngx_quic_read_buffer(pc, &qs->recv, size); + if (in == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + len = 0; + + for (cl = in; cl; cl = cl->next) { + b = cl->buf; + len += b->last - b->pos; + buf = ngx_cpymem(buf, b->pos, b->last - b->pos); + } + + ngx_quic_free_chain(pc, in); + + if (len == 0) { + rev->ready = 0; + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_DATA_RECVD + && qs->recv_offset == qs->recv_final_size) + { + qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_READ; + } + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_DATA_READ) { + rev->eof = 1; + return 0; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL recv() not ready", qs->id); + return NGX_AGAIN; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL recv len:%z", qs->id, len); + + if (ngx_quic_update_flow(qs, qs->recv_offset + len) != NGX_OK) { + return NGX_ERROR; + } + + return len; +} + + +static ssize_t +ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) +{ + ngx_buf_t b; + ngx_chain_t cl; + + ngx_memzero(&b, sizeof(ngx_buf_t)); + + b.memory = 1; + b.pos = buf; + b.last = buf + size; + + cl.buf = &b; + cl.next = NULL; + + if (ngx_quic_stream_send_chain(c, &cl, 0) == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + if (b.pos == buf) { + return NGX_AGAIN; + } + + return b.pos - buf; +} + + +static ngx_chain_t * +ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) +{ + uint64_t n, flow; + ngx_event_t *wev; + ngx_connection_t *pc; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qs = c->quic; + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + wev = c->write; + + if (qs->send_state != NGX_QUIC_STREAM_SEND_READY + && qs->send_state != NGX_QUIC_STREAM_SEND_SEND) + { + wev->error = 1; + return NGX_CHAIN_ERROR; + } + + qs->send_state = NGX_QUIC_STREAM_SEND_SEND; + + flow = qs->acked + qc->conf->stream_buffer_size - qs->sent; + + if (flow == 0) { + wev->ready = 0; + return in; + } + + if (limit == 0 || limit > (off_t) flow) { + limit = flow; + } + + n = qs->send.size; + + in = ngx_quic_write_buffer(pc, &qs->send, in, limit, qs->sent); + if (in == NGX_CHAIN_ERROR) { + return NGX_CHAIN_ERROR; + } + + n = qs->send.size - n; + c->sent += n; + qs->sent += n; + qc->streams.sent += n; + + if (flow == n) { + wev->ready = 0; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic send_chain sent:%uL", n); + + if (ngx_quic_stream_flush(qs) != NGX_OK) { + return NGX_CHAIN_ERROR; + } + + return in; +} + + +static ngx_int_t +ngx_quic_stream_flush(ngx_quic_stream_t *qs) +{ + off_t limit, len; + ngx_uint_t last; + ngx_chain_t *out; + ngx_quic_frame_t *frame; + ngx_connection_t *pc; + ngx_quic_connection_t *qc; + + if (qs->send_state != NGX_QUIC_STREAM_SEND_SEND) { + return NGX_OK; + } + + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + if (qc->streams.send_max_data == 0) { + qc->streams.send_max_data = qc->ctp.initial_max_data; + } + + limit = ngx_min(qc->streams.send_max_data - qc->streams.send_offset, + qs->send_max_data - qs->send_offset); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL flush limit:%O", qs->id, limit); + + len = qs->send.offset; + + out = ngx_quic_read_buffer(pc, &qs->send, limit); + if (out == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + len = qs->send.offset - len; + last = 0; + + if (qs->send_final_size != (uint64_t) -1 + && qs->send_final_size == qs->send.offset) + { + qs->send_state = NGX_QUIC_STREAM_SEND_DATA_SENT; + last = 1; + } + + if (len == 0 && !last) { + return NGX_OK; + } + + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_STREAM; + frame->data = out; + + frame->u.stream.off = 1; + frame->u.stream.len = 1; + frame->u.stream.fin = last; + + frame->u.stream.stream_id = qs->id; + frame->u.stream.offset = qs->send_offset; + frame->u.stream.length = len; + + ngx_quic_queue_frame(qc, frame); + + qs->send_offset += len; + qc->streams.send_offset += len; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL flush len:%O last:%ui", + qs->id, len, last); + + if (qs->connection == NULL) { + return ngx_quic_close_stream(qs); + } + + return NGX_OK; +} + + +static void +ngx_quic_stream_cleanup_handler(void *data) +{ + ngx_connection_t *c = data; + + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qs = c->quic; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, qs->parent->log, 0, + "quic stream id:0x%xL cleanup", qs->id); + + if (ngx_quic_shutdown_stream(c, NGX_RDWR_SHUTDOWN) != NGX_OK) { + qs->connection = NULL; + goto failed; + } + + qs->connection = NULL; + + if (ngx_quic_close_stream(qs) != NGX_OK) { + goto failed; + } + + return; + +failed: + + qc = ngx_quic_get_connection(qs->parent); + qc->error = NGX_QUIC_ERR_INTERNAL_ERROR; + + ngx_post_event(&qc->close, &ngx_posted_events); +} + + +static ngx_int_t +ngx_quic_close_stream(ngx_quic_stream_t *qs) +{ + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + if (!qc->closing) { + /* make sure everything is sent and final size is received */ + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_RECV) { + return NGX_OK; + } + + if (qs->send_state != NGX_QUIC_STREAM_SEND_DATA_RECVD + && qs->send_state != NGX_QUIC_STREAM_SEND_RESET_RECVD) + { + return NGX_OK; + } + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL close", qs->id); + + ngx_quic_free_buffer(pc, &qs->send); + ngx_quic_free_buffer(pc, &qs->recv); + + ngx_rbtree_delete(&qc->streams.tree, &qs->node); + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + + if (qc->closing) { + /* schedule handler call to continue ngx_quic_close_connection() */ + ngx_post_event(&qc->close, &ngx_posted_events); + return NGX_OK; + } + + if (!pc->reusable && ngx_quic_can_shutdown(pc) == NGX_OK) { + ngx_reusable_connection(pc, 1); + } + + if (qc->shutdown) { + ngx_quic_shutdown_quic(pc); + return NGX_OK; + } + + if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) { + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_MAX_STREAMS; + + if (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + frame->u.max_streams.limit = ++qc->streams.client_max_streams_uni; + frame->u.max_streams.bidi = 0; + + } else { + frame->u.max_streams.limit = ++qc->streams.client_max_streams_bidi; + frame->u.max_streams.bidi = 1; + } + + ngx_quic_queue_frame(qc, frame); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_can_shutdown(ngx_connection_t *c) +{ + ngx_rbtree_t *tree; + ngx_rbtree_node_t *node; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + tree = &qc->streams.tree; + + if (tree->root != tree->sentinel) { + for (node = ngx_rbtree_min(tree->root, tree->sentinel); + node; + node = ngx_rbtree_next(tree, node)) + { + qs = (ngx_quic_stream_t *) node; + + if (!qs->cancelable) { + return NGX_DECLINED; + } + } + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, + ngx_quic_frame_t *frame) +{ + uint64_t last; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + ngx_quic_stream_frame_t *f; + + qc = ngx_quic_get_connection(c); + f = &frame->u.stream; + + if ((f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->stream_id & NGX_QUIC_STREAM_SERVER_INITIATED)) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + /* no overflow since both values are 62-bit */ + last = f->offset + f->length; + + qs = ngx_quic_get_stream(c, f->stream_id); + + if (qs == NULL) { + return NGX_ERROR; + } + + if (qs == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV + && qs->recv_state != NGX_QUIC_STREAM_RECV_SIZE_KNOWN) + { + return NGX_OK; + } + + if (ngx_quic_control_flow(qs, last) != NGX_OK) { + return NGX_ERROR; + } + + if (qs->recv_final_size != (uint64_t) -1 && last > qs->recv_final_size) { + qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; + return NGX_ERROR; + } + + if (last < qs->recv_offset) { + return NGX_OK; + } + + if (f->fin) { + if (qs->recv_final_size != (uint64_t) -1 && qs->recv_final_size != last) + { + qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; + return NGX_ERROR; + } + + if (qs->recv_last > last) { + qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; + return NGX_ERROR; + } + + qs->recv_final_size = last; + qs->recv_state = NGX_QUIC_STREAM_RECV_SIZE_KNOWN; + } + + if (ngx_quic_write_buffer(c, &qs->recv, frame->data, f->length, f->offset) + == NGX_CHAIN_ERROR) + { + return NGX_ERROR; + } + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_SIZE_KNOWN + && qs->recv.size == qs->recv_final_size) + { + qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_RECVD; + } + + if (qs->connection == NULL) { + return ngx_quic_close_stream(qs); + } + + if (f->offset <= qs->recv_offset) { + ngx_quic_set_event(qs->connection->read); + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_max_data_frame(ngx_connection_t *c, + ngx_quic_max_data_frame_t *f) +{ + ngx_rbtree_t *tree; + ngx_rbtree_node_t *node; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + tree = &qc->streams.tree; + + if (f->max_data <= qc->streams.send_max_data) { + return NGX_OK; + } + + if (tree->root == tree->sentinel + || qc->streams.send_offset < qc->streams.send_max_data) + { + /* not blocked on MAX_DATA */ + qc->streams.send_max_data = f->max_data; + return NGX_OK; + } + + qc->streams.send_max_data = f->max_data; + node = ngx_rbtree_min(tree->root, tree->sentinel); + + while (node && qc->streams.send_offset < qc->streams.send_max_data) { + + qs = (ngx_quic_stream_t *) node; + node = ngx_rbtree_next(tree, node); + + if (ngx_quic_stream_flush(qs) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f) +{ + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_data_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_data_blocked_frame_t *f) +{ + return ngx_quic_update_max_data(c); +} + + +ngx_int_t +ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f) +{ + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED)) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + qs = ngx_quic_get_stream(c, f->id); + + if (qs == NULL) { + return NGX_ERROR; + } + + if (qs == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + return ngx_quic_update_max_stream_data(qs); +} + + +ngx_int_t +ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f) +{ + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + qs = ngx_quic_get_stream(c, f->id); + + if (qs == NULL) { + return NGX_ERROR; + } + + if (qs == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + if (f->limit <= qs->send_max_data) { + return NGX_OK; + } + + if (qs->send_offset < qs->send_max_data) { + /* not blocked on MAX_STREAM_DATA */ + qs->send_max_data = f->limit; + return NGX_OK; + } + + qs->send_max_data = f->limit; + + return ngx_quic_stream_flush(qs); +} + + +ngx_int_t +ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f) +{ + ngx_event_t *rev; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED)) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + qs = ngx_quic_get_stream(c, f->id); + + if (qs == NULL) { + return NGX_ERROR; + } + + if (qs == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_RECVD + || qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_READ) + { + return NGX_OK; + } + + qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_RECVD; + + if (ngx_quic_control_flow(qs, f->final_size) != NGX_OK) { + return NGX_ERROR; + } + + if (qs->recv_final_size != (uint64_t) -1 + && qs->recv_final_size != f->final_size) + { + qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; + return NGX_ERROR; + } + + if (qs->recv_last > f->final_size) { + qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; + return NGX_ERROR; + } + + qs->recv_final_size = f->final_size; + + if (ngx_quic_update_flow(qs, qs->recv_final_size) != NGX_OK) { + return NGX_ERROR; + } + + if (qs->connection == NULL) { + return ngx_quic_close_stream(qs); + } + + rev = qs->connection->read; + rev->error = 1; + + ngx_quic_set_event(rev); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f) +{ + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + qs = ngx_quic_get_stream(c, f->id); + + if (qs == NULL) { + return NGX_ERROR; + } + + if (qs == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + if (ngx_quic_do_reset_stream(qs, f->error_code) != NGX_OK) { + return NGX_ERROR; + } + + if (qs->connection == NULL) { + return ngx_quic_close_stream(qs); + } + + ngx_quic_set_event(qs->connection->write); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_max_streams_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_max_streams_frame_t *f) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (f->bidi) { + if (qc->streams.server_max_streams_bidi < f->limit) { + qc->streams.server_max_streams_bidi = f->limit; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic max_streams_bidi:%uL", f->limit); + } + + } else { + if (qc->streams.server_max_streams_uni < f->limit) { + qc->streams.server_max_streams_uni = f->limit; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic max_streams_uni:%uL", f->limit); + } + } + + return NGX_OK; +} + + +void +ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) +{ + uint64_t acked; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + switch (f->type) { + + case NGX_QUIC_FT_RESET_STREAM: + + qs = ngx_quic_find_stream(&qc->streams.tree, f->u.reset_stream.id); + if (qs == NULL) { + return; + } + + qs->send_state = NGX_QUIC_STREAM_SEND_RESET_RECVD; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL ack reset final_size:%uL", + qs->id, f->u.reset_stream.final_size); + + break; + + case NGX_QUIC_FT_STREAM: + + qs = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); + if (qs == NULL) { + return; + } + + acked = qs->acked; + qs->acked += f->u.stream.length; + + if (f->u.stream.fin) { + qs->fin_acked = 1; + } + + if (qs->send_state == NGX_QUIC_STREAM_SEND_DATA_SENT + && qs->acked == qs->sent && qs->fin_acked) + { + qs->send_state = NGX_QUIC_STREAM_SEND_DATA_RECVD; + } + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL ack len:%uL fin:%d unacked:%uL", + qs->id, f->u.stream.length, f->u.stream.fin, + qs->sent - qs->acked); + + if (qs->connection + && qs->sent - acked == qc->conf->stream_buffer_size + && f->u.stream.length > 0) + { + ngx_quic_set_event(qs->connection->write); + } + + break; + + default: + return; + } + + if (qs->connection == NULL) { + ngx_quic_close_stream(qs); + } +} + + +static ngx_int_t +ngx_quic_control_flow(ngx_quic_stream_t *qs, uint64_t last) +{ + uint64_t len; + ngx_connection_t *pc; + ngx_quic_connection_t *qc; + + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + if (last <= qs->recv_last) { + return NGX_OK; + } + + len = last - qs->recv_last; + + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL flow control msd:%uL/%uL md:%uL/%uL", + qs->id, last, qs->recv_max_data, qc->streams.recv_last + len, + qc->streams.recv_max_data); + + qs->recv_last += len; + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_RECV + && qs->recv_last > qs->recv_max_data) + { + qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; + return NGX_ERROR; + } + + qc->streams.recv_last += len; + + if (qc->streams.recv_last > qc->streams.recv_max_data) { + qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_update_flow(ngx_quic_stream_t *qs, uint64_t last) +{ + uint64_t len; + ngx_connection_t *pc; + ngx_quic_connection_t *qc; + + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + if (last <= qs->recv_offset) { + return NGX_OK; + } + + len = last - qs->recv_offset; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL flow update %uL", qs->id, last); + + qs->recv_offset += len; + + if (qs->recv_max_data <= qs->recv_offset + qs->recv_window / 2) { + if (ngx_quic_update_max_stream_data(qs) != NGX_OK) { + return NGX_ERROR; + } + } + + qc->streams.recv_offset += len; + + if (qc->streams.recv_max_data + <= qc->streams.recv_offset + qc->streams.recv_window / 2) + { + if (ngx_quic_update_max_data(pc) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_update_max_stream_data(ngx_quic_stream_t *qs) +{ + uint64_t recv_max_data; + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV) { + return NGX_OK; + } + + recv_max_data = qs->recv_offset + qs->recv_window; + + if (qs->recv_max_data == recv_max_data) { + return NGX_OK; + } + + qs->recv_max_data = recv_max_data; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL flow update msd:%uL", + qs->id, qs->recv_max_data); + + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_MAX_STREAM_DATA; + frame->u.max_stream_data.id = qs->id; + frame->u.max_stream_data.limit = qs->recv_max_data; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_update_max_data(ngx_connection_t *c) +{ + uint64_t recv_max_data; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + recv_max_data = qc->streams.recv_offset + qc->streams.recv_window; + + if (qc->streams.recv_max_data == recv_max_data) { + return NGX_OK; + } + + qc->streams.recv_max_data = recv_max_data; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic flow update md:%uL", qc->streams.recv_max_data); + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_MAX_DATA; + frame->u.max_data.max_data = qc->streams.recv_max_data; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +static void +ngx_quic_set_event(ngx_event_t *ev) +{ + ev->ready = 1; + + if (ev->active) { + ngx_post_event(ev, &ngx_posted_events); + } +} diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_streams.h b/src/deps/src/nginx/src/event/quic/ngx_event_quic_streams.h new file mode 100644 index 000000000..fb6dbbd8f --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_streams.h @@ -0,0 +1,44 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_ +#define _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_ + + +#include +#include + + +ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_frame_t *frame); +void ngx_quic_handle_stream_ack(ngx_connection_t *c, + ngx_quic_frame_t *f); +ngx_int_t ngx_quic_handle_max_data_frame(ngx_connection_t *c, + ngx_quic_max_data_frame_t *f); +ngx_int_t ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f); +ngx_int_t ngx_quic_handle_data_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_data_blocked_frame_t *f); +ngx_int_t ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f); +ngx_int_t ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f); +ngx_int_t ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f); +ngx_int_t ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f); +ngx_int_t ngx_quic_handle_max_streams_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_max_streams_frame_t *f); + +ngx_int_t ngx_quic_init_streams(ngx_connection_t *c); +void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); +ngx_quic_stream_t *ngx_quic_find_stream(ngx_rbtree_t *rbtree, + uint64_t id); +ngx_int_t ngx_quic_close_streams(ngx_connection_t *c, + ngx_quic_connection_t *qc); + +#endif /* _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_ */ diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_tokens.c b/src/deps/src/nginx/src/event/quic/ngx_event_quic_tokens.c new file mode 100644 index 000000000..c1da0d472 --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_tokens.c @@ -0,0 +1,309 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include +#include + + +static void ngx_quic_address_hash(struct sockaddr *sockaddr, socklen_t socklen, + ngx_uint_t no_port, u_char buf[20]); + + +ngx_int_t +ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, u_char *secret, + u_char *token) +{ + ngx_str_t tmp; + + tmp.data = secret; + tmp.len = NGX_QUIC_SR_KEY_LEN; + + if (ngx_quic_derive_key(c->log, "sr_token_key", &tmp, cid, token, + NGX_QUIC_SR_TOKEN_LEN) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stateless reset token %*xs", + (size_t) NGX_QUIC_SR_TOKEN_LEN, token); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_new_token(ngx_log_t *log, struct sockaddr *sockaddr, + socklen_t socklen, u_char *key, ngx_str_t *token, ngx_str_t *odcid, + time_t exp, ngx_uint_t is_retry) +{ + int len, iv_len; + u_char *p, *iv; + EVP_CIPHER_CTX *ctx; + const EVP_CIPHER *cipher; + + u_char in[NGX_QUIC_MAX_TOKEN_SIZE]; + + ngx_quic_address_hash(sockaddr, socklen, !is_retry, in); + + p = in + 20; + + p = ngx_cpymem(p, &exp, sizeof(time_t)); + + *p++ = is_retry ? 1 : 0; + + if (odcid) { + *p++ = odcid->len; + p = ngx_cpymem(p, odcid->data, odcid->len); + + } else { + *p++ = 0; + } + + len = p - in; + + cipher = EVP_aes_256_gcm(); + iv_len = NGX_QUIC_AES_256_GCM_IV_LEN; + + if ((size_t) (iv_len + len + NGX_QUIC_AES_256_GCM_TAG_LEN) > token->len) { + ngx_log_error(NGX_LOG_ALERT, log, 0, "quic token buffer is too small"); + return NGX_ERROR; + } + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + return NGX_ERROR; + } + + iv = token->data; + + if (RAND_bytes(iv, iv_len) <= 0 + || !EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv)) + { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + token->len = iv_len; + + if (EVP_EncryptUpdate(ctx, token->data + token->len, &len, in, len) != 1) { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + token->len += len; + + if (EVP_EncryptFinal_ex(ctx, token->data + token->len, &len) <= 0) { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + token->len += len; + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, + NGX_QUIC_AES_256_GCM_TAG_LEN, + token->data + token->len) + == 0) + { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + token->len += NGX_QUIC_AES_256_GCM_TAG_LEN; + + EVP_CIPHER_CTX_free(ctx); + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0, + "quic new token len:%uz %xV", token->len, token); +#endif + + return NGX_OK; +} + + +static void +ngx_quic_address_hash(struct sockaddr *sockaddr, socklen_t socklen, + ngx_uint_t no_port, u_char buf[20]) +{ + size_t len; + u_char *data; + ngx_sha1_t sha1; + struct sockaddr_in *sin; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + + len = (size_t) socklen; + data = (u_char *) sockaddr; + + if (no_port) { + switch (sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) sockaddr; + + len = sizeof(struct in6_addr); + data = sin6->sin6_addr.s6_addr; + + break; +#endif + + case AF_INET: + sin = (struct sockaddr_in *) sockaddr; + + len = sizeof(in_addr_t); + data = (u_char *) &sin->sin_addr; + + break; + } + } + + ngx_sha1_init(&sha1); + ngx_sha1_update(&sha1, data, len); + ngx_sha1_final(buf, &sha1); +} + + +ngx_int_t +ngx_quic_validate_token(ngx_connection_t *c, u_char *key, + ngx_quic_header_t *pkt) +{ + int len, tlen, iv_len; + u_char *iv, *p; + time_t now, exp; + size_t total; + ngx_str_t odcid; + EVP_CIPHER_CTX *ctx; + const EVP_CIPHER *cipher; + + u_char addr_hash[20]; + u_char tdec[NGX_QUIC_MAX_TOKEN_SIZE]; + +#if NGX_SUPPRESS_WARN + ngx_str_null(&odcid); +#endif + + /* Retry token or NEW_TOKEN in a previous connection */ + + cipher = EVP_aes_256_gcm(); + iv = pkt->token.data; + iv_len = NGX_QUIC_AES_256_GCM_IV_LEN; + + /* sanity checks */ + + if (pkt->token.len < (size_t) iv_len + NGX_QUIC_AES_256_GCM_TAG_LEN) { + goto garbage; + } + + if (pkt->token.len > (size_t) iv_len + NGX_QUIC_MAX_TOKEN_SIZE + + NGX_QUIC_AES_256_GCM_TAG_LEN) + { + goto garbage; + } + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + return NGX_ERROR; + } + + if (!EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv)) { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + p = pkt->token.data + iv_len; + len = pkt->token.len - iv_len - NGX_QUIC_AES_256_GCM_TAG_LEN; + + if (EVP_DecryptUpdate(ctx, tdec, &tlen, p, len) != 1) { + EVP_CIPHER_CTX_free(ctx); + goto garbage; + } + total = tlen; + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, + NGX_QUIC_AES_256_GCM_TAG_LEN, p + len) + == 0) + { + EVP_CIPHER_CTX_free(ctx); + goto garbage; + } + + if (EVP_DecryptFinal_ex(ctx, tdec + tlen, &tlen) <= 0) { + EVP_CIPHER_CTX_free(ctx); + goto garbage; + } + total += tlen; + + EVP_CIPHER_CTX_free(ctx); + + if (total < (20 + sizeof(time_t) + 2)) { + goto garbage; + } + + p = tdec + 20; + + ngx_memcpy(&exp, p, sizeof(time_t)); + p += sizeof(time_t); + + pkt->retried = (*p++ == 1); + + ngx_quic_address_hash(c->sockaddr, c->socklen, !pkt->retried, addr_hash); + + if (ngx_memcmp(tdec, addr_hash, 20) != 0) { + goto bad_token; + } + + odcid.len = *p++; + if (odcid.len) { + if (odcid.len > NGX_QUIC_MAX_CID_LEN) { + goto bad_token; + } + + if ((size_t)(tdec + total - p) < odcid.len) { + goto bad_token; + } + + odcid.data = p; + } + + now = ngx_time(); + + if (now > exp) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic expired token"); + return NGX_DECLINED; + } + + if (odcid.len) { + pkt->odcid.len = odcid.len; + pkt->odcid.data = pkt->odcid_buf; + ngx_memcpy(pkt->odcid.data, odcid.data, odcid.len); + + } else { + pkt->odcid = pkt->dcid; + } + + pkt->validated = 1; + + return NGX_OK; + +garbage: + + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic garbage token"); + + return NGX_ABORT; + +bad_token: + + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid token"); + + return NGX_DECLINED; +} diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_tokens.h b/src/deps/src/nginx/src/event/quic/ngx_event_quic_tokens.h new file mode 100644 index 000000000..ee3fe5b9a --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_tokens.h @@ -0,0 +1,34 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_ +#define _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_ + + +#include +#include + + +#define NGX_QUIC_MAX_TOKEN_SIZE 64 + /* SHA-1(addr)=20 + sizeof(time_t) + retry(1) + odcid.len(1) + odcid */ + +#define NGX_QUIC_AES_256_GCM_IV_LEN 12 +#define NGX_QUIC_AES_256_GCM_TAG_LEN 16 + +#define NGX_QUIC_TOKEN_BUF_SIZE (NGX_QUIC_AES_256_GCM_IV_LEN \ + + NGX_QUIC_MAX_TOKEN_SIZE \ + + NGX_QUIC_AES_256_GCM_TAG_LEN) + + +ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, + u_char *secret, u_char *token); +ngx_int_t ngx_quic_new_token(ngx_log_t *log, struct sockaddr *sockaddr, + socklen_t socklen, u_char *key, ngx_str_t *token, ngx_str_t *odcid, + time_t expires, ngx_uint_t is_retry); +ngx_int_t ngx_quic_validate_token(ngx_connection_t *c, + u_char *key, ngx_quic_header_t *pkt); + +#endif /* _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_ */ diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_transport.c b/src/deps/src/nginx/src/event/quic/ngx_event_quic_transport.c new file mode 100644 index 000000000..19670a6b1 --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_transport.c @@ -0,0 +1,2202 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +#define NGX_QUIC_LONG_DCID_LEN_OFFSET 5 +#define NGX_QUIC_LONG_DCID_OFFSET 6 +#define NGX_QUIC_SHORT_DCID_OFFSET 1 + +#define NGX_QUIC_STREAM_FRAME_FIN 0x01 +#define NGX_QUIC_STREAM_FRAME_LEN 0x02 +#define NGX_QUIC_STREAM_FRAME_OFF 0x04 + + +#if (NGX_HAVE_NONALIGNED) + +#define ngx_quic_parse_uint16(p) ntohs(*(uint16_t *) (p)) +#define ngx_quic_parse_uint32(p) ntohl(*(uint32_t *) (p)) + +#define ngx_quic_write_uint16 ngx_quic_write_uint16_aligned +#define ngx_quic_write_uint32 ngx_quic_write_uint32_aligned + +#else + +#define ngx_quic_parse_uint16(p) ((p)[0] << 8 | (p)[1]) +#define ngx_quic_parse_uint32(p) \ + ((uint32_t) (p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3]) + +#define ngx_quic_write_uint16(p, s) \ + ((p)[0] = (u_char) ((s) >> 8), \ + (p)[1] = (u_char) (s), \ + (p) + sizeof(uint16_t)) + +#define ngx_quic_write_uint32(p, s) \ + ((p)[0] = (u_char) ((s) >> 24), \ + (p)[1] = (u_char) ((s) >> 16), \ + (p)[2] = (u_char) ((s) >> 8), \ + (p)[3] = (u_char) (s), \ + (p) + sizeof(uint32_t)) + +#endif + +#define ngx_quic_write_uint64(p, s) \ + ((p)[0] = (u_char) ((s) >> 56), \ + (p)[1] = (u_char) ((s) >> 48), \ + (p)[2] = (u_char) ((s) >> 40), \ + (p)[3] = (u_char) ((s) >> 32), \ + (p)[4] = (u_char) ((s) >> 24), \ + (p)[5] = (u_char) ((s) >> 16), \ + (p)[6] = (u_char) ((s) >> 8), \ + (p)[7] = (u_char) (s), \ + (p) + sizeof(uint64_t)) + +#define ngx_quic_write_uint24(p, s) \ + ((p)[0] = (u_char) ((s) >> 16), \ + (p)[1] = (u_char) ((s) >> 8), \ + (p)[2] = (u_char) (s), \ + (p) + 3) + +#define ngx_quic_write_uint16_aligned(p, s) \ + (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t)) + +#define ngx_quic_write_uint32_aligned(p, s) \ + (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t)) + +#define ngx_quic_build_int_set(p, value, len, bits) \ + (*(p)++ = ((value >> ((len) * 8)) & 0xff) | ((bits) << 6)) + + +static u_char *ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out); +static ngx_uint_t ngx_quic_varint_len(uint64_t value); +static void ngx_quic_build_int(u_char **pos, uint64_t value); + +static u_char *ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value); +static u_char *ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value); +static u_char *ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len, + u_char **out); +static u_char *ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len, + u_char *dst); + +static ngx_int_t ngx_quic_parse_short_header(ngx_quic_header_t *pkt, + size_t dcid_len); +static ngx_int_t ngx_quic_parse_long_header(ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_supported_version(uint32_t version); +static ngx_int_t ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt); + +static size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, + u_char **pnp); +static size_t ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, + u_char **pnp); + +static ngx_int_t ngx_quic_frame_allowed(ngx_quic_header_t *pkt, + ngx_uint_t frame_type); +static size_t ngx_quic_create_ping(u_char *p); +static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack, + ngx_chain_t *ranges); +static size_t ngx_quic_create_reset_stream(u_char *p, + ngx_quic_reset_stream_frame_t *rs); +static size_t ngx_quic_create_stop_sending(u_char *p, + ngx_quic_stop_sending_frame_t *ss); +static size_t ngx_quic_create_crypto(u_char *p, + ngx_quic_crypto_frame_t *crypto, ngx_chain_t *data); +static size_t ngx_quic_create_hs_done(u_char *p); +static size_t ngx_quic_create_new_token(u_char *p, + ngx_quic_new_token_frame_t *token, ngx_chain_t *data); +static size_t ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf, + ngx_chain_t *data); +static size_t ngx_quic_create_max_streams(u_char *p, + ngx_quic_max_streams_frame_t *ms); +static size_t ngx_quic_create_max_stream_data(u_char *p, + ngx_quic_max_stream_data_frame_t *ms); +static size_t ngx_quic_create_max_data(u_char *p, + ngx_quic_max_data_frame_t *md); +static size_t ngx_quic_create_path_challenge(u_char *p, + ngx_quic_path_challenge_frame_t *pc); +static size_t ngx_quic_create_path_response(u_char *p, + ngx_quic_path_challenge_frame_t *pc); +static size_t ngx_quic_create_new_connection_id(u_char *p, + ngx_quic_new_conn_id_frame_t *rcid); +static size_t ngx_quic_create_retire_connection_id(u_char *p, + ngx_quic_retire_cid_frame_t *rcid); +static size_t ngx_quic_create_close(u_char *p, ngx_quic_frame_t *f); + +static ngx_int_t ngx_quic_parse_transport_param(u_char *p, u_char *end, + uint16_t id, ngx_quic_tp_t *dst); + + +uint32_t ngx_quic_versions[] = { + /* QUICv1 */ + 0x00000001, +}; + +#define NGX_QUIC_NVERSIONS \ + (sizeof(ngx_quic_versions) / sizeof(ngx_quic_versions[0])) + + +static ngx_inline u_char * +ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out) +{ + u_char *p; + uint64_t value; + ngx_uint_t len; + + if (pos >= end) { + return NULL; + } + + p = pos; + len = 1 << (*p >> 6); + + value = *p++ & 0x3f; + + if ((size_t)(end - p) < (len - 1)) { + return NULL; + } + + while (--len) { + value = (value << 8) + *p++; + } + + *out = value; + + return p; +} + + +static ngx_inline u_char * +ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value) +{ + if ((size_t)(end - pos) < 1) { + return NULL; + } + + *value = *pos; + + return pos + 1; +} + + +static ngx_inline u_char * +ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value) +{ + if ((size_t)(end - pos) < sizeof(uint32_t)) { + return NULL; + } + + *value = ngx_quic_parse_uint32(pos); + + return pos + sizeof(uint32_t); +} + + +static ngx_inline u_char * +ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len, u_char **out) +{ + if ((size_t)(end - pos) < len) { + return NULL; + } + + *out = pos; + + return pos + len; +} + + +static u_char * +ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len, u_char *dst) +{ + if ((size_t)(end - pos) < len) { + return NULL; + } + + ngx_memcpy(dst, pos, len); + + return pos + len; +} + + +static ngx_inline ngx_uint_t +ngx_quic_varint_len(uint64_t value) +{ + if (value < (1 << 6)) { + return 1; + } + + if (value < (1 << 14)) { + return 2; + } + + if (value < (1 << 30)) { + return 4; + } + + return 8; +} + + +static ngx_inline void +ngx_quic_build_int(u_char **pos, uint64_t value) +{ + u_char *p; + + p = *pos; + + if (value < (1 << 6)) { + ngx_quic_build_int_set(p, value, 0, 0); + + } else if (value < (1 << 14)) { + ngx_quic_build_int_set(p, value, 1, 1); + ngx_quic_build_int_set(p, value, 0, 0); + + } else if (value < (1 << 30)) { + ngx_quic_build_int_set(p, value, 3, 2); + ngx_quic_build_int_set(p, value, 2, 0); + ngx_quic_build_int_set(p, value, 1, 0); + ngx_quic_build_int_set(p, value, 0, 0); + + } else { + ngx_quic_build_int_set(p, value, 7, 3); + ngx_quic_build_int_set(p, value, 6, 0); + ngx_quic_build_int_set(p, value, 5, 0); + ngx_quic_build_int_set(p, value, 4, 0); + ngx_quic_build_int_set(p, value, 3, 0); + ngx_quic_build_int_set(p, value, 2, 0); + ngx_quic_build_int_set(p, value, 1, 0); + ngx_quic_build_int_set(p, value, 0, 0); + } + + *pos = p; +} + + +ngx_int_t +ngx_quic_parse_packet(ngx_quic_header_t *pkt) +{ + if (!ngx_quic_long_pkt(pkt->flags)) { + pkt->level = ssl_encryption_application; + + if (ngx_quic_parse_short_header(pkt, NGX_QUIC_SERVER_CID_LEN) != NGX_OK) + { + return NGX_ERROR; + } + + return NGX_OK; + } + + if (ngx_quic_parse_long_header(pkt) != NGX_OK) { + return NGX_ERROR; + } + + if (!ngx_quic_supported_version(pkt->version)) { + return NGX_ABORT; + } + + if (ngx_quic_parse_long_header_v1(pkt) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_parse_short_header(ngx_quic_header_t *pkt, size_t dcid_len) +{ + u_char *p, *end; + + p = pkt->raw->pos; + end = pkt->data + pkt->len; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet rx short flags:%xd", pkt->flags); + + if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set"); + return NGX_ERROR; + } + + pkt->dcid.len = dcid_len; + + p = ngx_quic_read_bytes(p, end, dcid_len, &pkt->dcid.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet is too small to read dcid"); + return NGX_ERROR; + } + + pkt->raw->pos = p; + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_parse_long_header(ngx_quic_header_t *pkt) +{ + u_char *p, *end; + uint8_t idlen; + + p = pkt->raw->pos; + end = pkt->data + pkt->len; + + p = ngx_quic_read_uint32(p, end, &pkt->version); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet is too small to read version"); + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet rx long flags:%xd version:%xD", + pkt->flags, pkt->version); + + if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set"); + return NGX_ERROR; + } + + p = ngx_quic_read_uint8(p, end, &idlen); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet is too small to read dcid len"); + return NGX_ERROR; + } + + if (idlen > NGX_QUIC_CID_LEN_MAX) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet dcid is too long"); + return NGX_ERROR; + } + + pkt->dcid.len = idlen; + + p = ngx_quic_read_bytes(p, end, idlen, &pkt->dcid.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet is too small to read dcid"); + return NGX_ERROR; + } + + p = ngx_quic_read_uint8(p, end, &idlen); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet is too small to read scid len"); + return NGX_ERROR; + } + + if (idlen > NGX_QUIC_CID_LEN_MAX) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet scid is too long"); + return NGX_ERROR; + } + + pkt->scid.len = idlen; + + p = ngx_quic_read_bytes(p, end, idlen, &pkt->scid.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet is too small to read scid"); + return NGX_ERROR; + } + + pkt->raw->pos = p; + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_supported_version(uint32_t version) +{ + ngx_uint_t i; + + for (i = 0; i < NGX_QUIC_NVERSIONS; i++) { + if (ngx_quic_versions[i] == version) { + return 1; + } + } + + return 0; +} + + +static ngx_int_t +ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt) +{ + u_char *p, *end; + uint64_t varint; + + p = pkt->raw->pos; + end = pkt->raw->last; + + pkt->log->action = "parsing quic long header"; + + if (ngx_quic_pkt_in(pkt->flags)) { + + if (pkt->len < NGX_QUIC_MIN_INITIAL_SIZE) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic UDP datagram is too small for initial packet"); + return NGX_DECLINED; + } + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic failed to parse token length"); + return NGX_ERROR; + } + + pkt->token.len = varint; + + p = ngx_quic_read_bytes(p, end, pkt->token.len, &pkt->token.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet too small to read token data"); + return NGX_ERROR; + } + + pkt->level = ssl_encryption_initial; + + } else if (ngx_quic_pkt_zrtt(pkt->flags)) { + pkt->level = ssl_encryption_early_data; + + } else if (ngx_quic_pkt_hs(pkt->flags)) { + pkt->level = ssl_encryption_handshake; + + } else { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic bad packet type"); + return NGX_DECLINED; + } + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic bad packet length"); + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet rx %s len:%uL", + ngx_quic_level_name(pkt->level), varint); + + if (varint > (uint64_t) ((pkt->data + pkt->len) - p)) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic truncated %s packet", + ngx_quic_level_name(pkt->level)); + return NGX_ERROR; + } + + pkt->raw->pos = p; + pkt->len = p + varint - pkt->data; + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t n, + ngx_str_t *dcid) +{ + size_t len, offset; + + if (n == 0) { + goto failed; + } + + if (ngx_quic_long_pkt(*data)) { + if (n < NGX_QUIC_LONG_DCID_LEN_OFFSET + 1) { + goto failed; + } + + len = data[NGX_QUIC_LONG_DCID_LEN_OFFSET]; + offset = NGX_QUIC_LONG_DCID_OFFSET; + + } else { + len = NGX_QUIC_SERVER_CID_LEN; + offset = NGX_QUIC_SHORT_DCID_OFFSET; + } + + if (n < len + offset) { + goto failed; + } + + dcid->len = len; + dcid->data = &data[offset]; + + return NGX_OK; + +failed: + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, "quic malformed packet"); + + return NGX_ERROR; +} + + +size_t +ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out) +{ + u_char *p, *start; + ngx_uint_t i; + + p = start = out; + + *p++ = pkt->flags; + + /* + * The Version field of a Version Negotiation packet + * MUST be set to 0x00000000 + */ + p = ngx_quic_write_uint32(p, 0); + + *p++ = pkt->dcid.len; + p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); + + *p++ = pkt->scid.len; + p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); + + for (i = 0; i < NGX_QUIC_NVERSIONS; i++) { + p = ngx_quic_write_uint32(p, ngx_quic_versions[i]); + } + + return p - start; +} + + +/* returns the amount of payload quic packet of "pkt_len" size may fit or 0 */ +size_t +ngx_quic_payload_size(ngx_quic_header_t *pkt, size_t pkt_len) +{ + size_t len; + + if (ngx_quic_short_pkt(pkt->flags)) { + + len = 1 + pkt->dcid.len + pkt->num_len + NGX_QUIC_TAG_LEN; + if (len > pkt_len) { + return 0; + } + + return pkt_len - len; + } + + /* flags, version, dcid and scid with lengths and zero-length token */ + len = 5 + 2 + pkt->dcid.len + pkt->scid.len + + (pkt->level == ssl_encryption_initial ? 1 : 0); + + if (len > pkt_len) { + return 0; + } + + /* (pkt_len - len) is 'remainder' packet length (see RFC 9000, 17.2) */ + len += ngx_quic_varint_len(pkt_len - len) + + pkt->num_len + NGX_QUIC_TAG_LEN; + + if (len > pkt_len) { + return 0; + } + + return pkt_len - len; +} + + +size_t +ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out, u_char **pnp) +{ + return ngx_quic_short_pkt(pkt->flags) + ? ngx_quic_create_short_header(pkt, out, pnp) + : ngx_quic_create_long_header(pkt, out, pnp); +} + + +static size_t +ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, + u_char **pnp) +{ + size_t rem_len; + u_char *p, *start; + + rem_len = pkt->num_len + pkt->payload.len + NGX_QUIC_TAG_LEN; + + if (out == NULL) { + return 5 + 2 + pkt->dcid.len + pkt->scid.len + + ngx_quic_varint_len(rem_len) + pkt->num_len + + (pkt->level == ssl_encryption_initial ? 1 : 0); + } + + p = start = out; + + *p++ = pkt->flags; + + p = ngx_quic_write_uint32(p, pkt->version); + + *p++ = pkt->dcid.len; + p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); + + *p++ = pkt->scid.len; + p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); + + if (pkt->level == ssl_encryption_initial) { + ngx_quic_build_int(&p, 0); + } + + ngx_quic_build_int(&p, rem_len); + + *pnp = p; + + switch (pkt->num_len) { + case 1: + *p++ = pkt->trunc; + break; + case 2: + p = ngx_quic_write_uint16(p, pkt->trunc); + break; + case 3: + p = ngx_quic_write_uint24(p, pkt->trunc); + break; + case 4: + p = ngx_quic_write_uint32(p, pkt->trunc); + break; + } + + return p - start; +} + + +static size_t +ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, + u_char **pnp) +{ + u_char *p, *start; + + if (out == NULL) { + return 1 + pkt->dcid.len + pkt->num_len; + } + + p = start = out; + + *p++ = pkt->flags; + + p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); + + *pnp = p; + + switch (pkt->num_len) { + case 1: + *p++ = pkt->trunc; + break; + case 2: + p = ngx_quic_write_uint16(p, pkt->trunc); + break; + case 3: + p = ngx_quic_write_uint24(p, pkt->trunc); + break; + case 4: + p = ngx_quic_write_uint32(p, pkt->trunc); + break; + } + + return p - start; +} + + +size_t +ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out, + u_char **start) +{ + u_char *p; + + p = out; + + *p++ = pkt->odcid.len; + p = ngx_cpymem(p, pkt->odcid.data, pkt->odcid.len); + + *start = p; + + *p++ = 0xff; + + p = ngx_quic_write_uint32(p, pkt->version); + + *p++ = pkt->dcid.len; + p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); + + *p++ = pkt->scid.len; + p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); + + p = ngx_cpymem(p, pkt->token.data, pkt->token.len); + + return p - out; +} + + +ssize_t +ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, + ngx_quic_frame_t *f) +{ + u_char *p; + uint64_t varint; + ngx_buf_t *b; + ngx_uint_t i; + + b = f->data->buf; + + p = start; + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic failed to obtain quic frame type"); + return NGX_ERROR; + } + + if (varint > NGX_QUIC_FT_LAST) { + pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic unknown frame type 0x%xL", varint); + return NGX_ERROR; + } + + f->type = varint; + + if (ngx_quic_frame_allowed(pkt, f->type) != NGX_OK) { + pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + return NGX_ERROR; + } + + switch (f->type) { + + case NGX_QUIC_FT_CRYPTO: + + p = ngx_quic_parse_int(p, end, &f->u.crypto.offset); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.crypto.length); + if (p == NULL) { + goto error; + } + + p = ngx_quic_read_bytes(p, end, f->u.crypto.length, &b->pos); + if (p == NULL) { + goto error; + } + + b->last = p; + + break; + + case NGX_QUIC_FT_PADDING: + + while (p < end && *p == NGX_QUIC_FT_PADDING) { + p++; + } + + break; + + case NGX_QUIC_FT_ACK: + case NGX_QUIC_FT_ACK_ECN: + + p = ngx_quic_parse_int(p, end, &f->u.ack.largest); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.ack.delay); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.ack.range_count); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.ack.first_range); + if (p == NULL) { + goto error; + } + + b->pos = p; + + /* process all ranges to get bounds, values are ignored */ + for (i = 0; i < f->u.ack.range_count; i++) { + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + goto error; + } + } + + b->last = p; + + f->u.ack.ranges_length = b->last - b->pos; + + if (f->type == NGX_QUIC_FT_ACK_ECN) { + + p = ngx_quic_parse_int(p, end, &f->u.ack.ect0); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.ack.ect1); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.ack.ce); + if (p == NULL) { + goto error; + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic ACK ECN counters ect0:%uL ect1:%uL ce:%uL", + f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce); + } + + break; + + case NGX_QUIC_FT_PING: + break; + + case NGX_QUIC_FT_NEW_CONNECTION_ID: + + p = ngx_quic_parse_int(p, end, &f->u.ncid.seqnum); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.ncid.retire); + if (p == NULL) { + goto error; + } + + if (f->u.ncid.retire > f->u.ncid.seqnum) { + goto error; + } + + p = ngx_quic_read_uint8(p, end, &f->u.ncid.len); + if (p == NULL) { + goto error; + } + + if (f->u.ncid.len < 1 || f->u.ncid.len > NGX_QUIC_CID_LEN_MAX) { + goto error; + } + + p = ngx_quic_copy_bytes(p, end, f->u.ncid.len, f->u.ncid.cid); + if (p == NULL) { + goto error; + } + + p = ngx_quic_copy_bytes(p, end, NGX_QUIC_SR_TOKEN_LEN, f->u.ncid.srt); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_RETIRE_CONNECTION_ID: + + p = ngx_quic_parse_int(p, end, &f->u.retire_cid.sequence_number); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_CONNECTION_CLOSE: + case NGX_QUIC_FT_CONNECTION_CLOSE_APP: + + p = ngx_quic_parse_int(p, end, &f->u.close.error_code); + if (p == NULL) { + goto error; + } + + if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { + p = ngx_quic_parse_int(p, end, &f->u.close.frame_type); + if (p == NULL) { + goto error; + } + } + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + goto error; + } + + f->u.close.reason.len = varint; + + p = ngx_quic_read_bytes(p, end, f->u.close.reason.len, + &f->u.close.reason.data); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_STREAM: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + + f->u.stream.fin = (f->type & NGX_QUIC_STREAM_FRAME_FIN) ? 1 : 0; + + p = ngx_quic_parse_int(p, end, &f->u.stream.stream_id); + if (p == NULL) { + goto error; + } + + if (f->type & NGX_QUIC_STREAM_FRAME_OFF) { + f->u.stream.off = 1; + + p = ngx_quic_parse_int(p, end, &f->u.stream.offset); + if (p == NULL) { + goto error; + } + + } else { + f->u.stream.off = 0; + f->u.stream.offset = 0; + } + + if (f->type & NGX_QUIC_STREAM_FRAME_LEN) { + f->u.stream.len = 1; + + p = ngx_quic_parse_int(p, end, &f->u.stream.length); + if (p == NULL) { + goto error; + } + + } else { + f->u.stream.len = 0; + f->u.stream.length = end - p; /* up to packet end */ + } + + p = ngx_quic_read_bytes(p, end, f->u.stream.length, &b->pos); + if (p == NULL) { + goto error; + } + + b->last = p; + + f->type = NGX_QUIC_FT_STREAM; + break; + + case NGX_QUIC_FT_MAX_DATA: + + p = ngx_quic_parse_int(p, end, &f->u.max_data.max_data); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_RESET_STREAM: + + p = ngx_quic_parse_int(p, end, &f->u.reset_stream.id); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.reset_stream.error_code); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.reset_stream.final_size); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_STOP_SENDING: + + p = ngx_quic_parse_int(p, end, &f->u.stop_sending.id); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.stop_sending.error_code); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_STREAMS_BLOCKED: + case NGX_QUIC_FT_STREAMS_BLOCKED2: + + p = ngx_quic_parse_int(p, end, &f->u.streams_blocked.limit); + if (p == NULL) { + goto error; + } + + if (f->u.streams_blocked.limit > 0x1000000000000000) { + goto error; + } + + f->u.streams_blocked.bidi = + (f->type == NGX_QUIC_FT_STREAMS_BLOCKED) ? 1 : 0; + break; + + case NGX_QUIC_FT_MAX_STREAMS: + case NGX_QUIC_FT_MAX_STREAMS2: + + p = ngx_quic_parse_int(p, end, &f->u.max_streams.limit); + if (p == NULL) { + goto error; + } + + if (f->u.max_streams.limit > 0x1000000000000000) { + goto error; + } + + f->u.max_streams.bidi = (f->type == NGX_QUIC_FT_MAX_STREAMS) ? 1 : 0; + + break; + + case NGX_QUIC_FT_MAX_STREAM_DATA: + + p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.id); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.limit); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_DATA_BLOCKED: + + p = ngx_quic_parse_int(p, end, &f->u.data_blocked.limit); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_STREAM_DATA_BLOCKED: + + p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.id); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.limit); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_PATH_CHALLENGE: + + p = ngx_quic_copy_bytes(p, end, 8, f->u.path_challenge.data); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_PATH_RESPONSE: + + p = ngx_quic_copy_bytes(p, end, 8, f->u.path_response.data); + if (p == NULL) { + goto error; + } + + break; + + default: + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic unknown frame type 0x%xi", f->type); + return NGX_ERROR; + } + + f->level = pkt->level; +#if (NGX_DEBUG) + f->pnum = pkt->pn; +#endif + + return p - start; + +error: + + pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic failed to parse frame type:0x%xi", f->type); + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type) +{ + uint8_t ptype; + + /* + * RFC 9000, 12.4. Frames and Frame Types: Table 3 + * + * Frame permissions per packet: 4 bits: IH01 + */ + static uint8_t ngx_quic_frame_masks[] = { + /* PADDING */ 0xF, + /* PING */ 0xF, + /* ACK */ 0xD, + /* ACK_ECN */ 0xD, + /* RESET_STREAM */ 0x3, + /* STOP_SENDING */ 0x3, + /* CRYPTO */ 0xD, + /* NEW_TOKEN */ 0x0, /* only sent by server */ + /* STREAM */ 0x3, + /* STREAM1 */ 0x3, + /* STREAM2 */ 0x3, + /* STREAM3 */ 0x3, + /* STREAM4 */ 0x3, + /* STREAM5 */ 0x3, + /* STREAM6 */ 0x3, + /* STREAM7 */ 0x3, + /* MAX_DATA */ 0x3, + /* MAX_STREAM_DATA */ 0x3, + /* MAX_STREAMS */ 0x3, + /* MAX_STREAMS2 */ 0x3, + /* DATA_BLOCKED */ 0x3, + /* STREAM_DATA_BLOCKED */ 0x3, + /* STREAMS_BLOCKED */ 0x3, + /* STREAMS_BLOCKED2 */ 0x3, + /* NEW_CONNECTION_ID */ 0x3, + /* RETIRE_CONNECTION_ID */ 0x3, + /* PATH_CHALLENGE */ 0x3, + /* PATH_RESPONSE */ 0x1, + /* CONNECTION_CLOSE */ 0xF, + /* CONNECTION_CLOSE2 */ 0x3, + /* HANDSHAKE_DONE */ 0x0, /* only sent by server */ + }; + + if (ngx_quic_long_pkt(pkt->flags)) { + + if (ngx_quic_pkt_in(pkt->flags)) { + ptype = 8; /* initial */ + + } else if (ngx_quic_pkt_hs(pkt->flags)) { + ptype = 4; /* handshake */ + + } else { + ptype = 2; /* zero-rtt */ + } + + } else { + ptype = 1; /* application data */ + } + + if (ptype & ngx_quic_frame_masks[frame_type]) { + return NGX_OK; + } + + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic frame type 0x%xi is not " + "allowed in packet with flags 0x%xd", + frame_type, pkt->flags); + + return NGX_DECLINED; +} + + +ssize_t +ngx_quic_parse_ack_range(ngx_log_t *log, u_char *start, u_char *end, + uint64_t *gap, uint64_t *range) +{ + u_char *p; + + p = start; + + p = ngx_quic_parse_int(p, end, gap); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic failed to parse ack frame gap"); + return NGX_ERROR; + } + + p = ngx_quic_parse_int(p, end, range); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic failed to parse ack frame range"); + return NGX_ERROR; + } + + return p - start; +} + + +size_t +ngx_quic_create_ack_range(u_char *p, uint64_t gap, uint64_t range) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(gap); + len += ngx_quic_varint_len(range); + return len; + } + + start = p; + + ngx_quic_build_int(&p, gap); + ngx_quic_build_int(&p, range); + + return p - start; +} + + +ssize_t +ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) +{ + /* + * RFC 9002, 2. Conventions and Definitions + * + * Ack-eliciting frames: All frames other than ACK, PADDING, and + * CONNECTION_CLOSE are considered ack-eliciting. + */ + f->need_ack = 1; + + switch (f->type) { + case NGX_QUIC_FT_PING: + return ngx_quic_create_ping(p); + + case NGX_QUIC_FT_ACK: + f->need_ack = 0; + return ngx_quic_create_ack(p, &f->u.ack, f->data); + + case NGX_QUIC_FT_RESET_STREAM: + return ngx_quic_create_reset_stream(p, &f->u.reset_stream); + + case NGX_QUIC_FT_STOP_SENDING: + return ngx_quic_create_stop_sending(p, &f->u.stop_sending); + + case NGX_QUIC_FT_CRYPTO: + return ngx_quic_create_crypto(p, &f->u.crypto, f->data); + + case NGX_QUIC_FT_HANDSHAKE_DONE: + return ngx_quic_create_hs_done(p); + + case NGX_QUIC_FT_NEW_TOKEN: + return ngx_quic_create_new_token(p, &f->u.token, f->data); + + case NGX_QUIC_FT_STREAM: + return ngx_quic_create_stream(p, &f->u.stream, f->data); + + case NGX_QUIC_FT_CONNECTION_CLOSE: + case NGX_QUIC_FT_CONNECTION_CLOSE_APP: + f->need_ack = 0; + return ngx_quic_create_close(p, f); + + case NGX_QUIC_FT_MAX_STREAMS: + return ngx_quic_create_max_streams(p, &f->u.max_streams); + + case NGX_QUIC_FT_MAX_STREAM_DATA: + return ngx_quic_create_max_stream_data(p, &f->u.max_stream_data); + + case NGX_QUIC_FT_MAX_DATA: + return ngx_quic_create_max_data(p, &f->u.max_data); + + case NGX_QUIC_FT_PATH_CHALLENGE: + return ngx_quic_create_path_challenge(p, &f->u.path_challenge); + + case NGX_QUIC_FT_PATH_RESPONSE: + return ngx_quic_create_path_response(p, &f->u.path_response); + + case NGX_QUIC_FT_NEW_CONNECTION_ID: + return ngx_quic_create_new_connection_id(p, &f->u.ncid); + + case NGX_QUIC_FT_RETIRE_CONNECTION_ID: + return ngx_quic_create_retire_connection_id(p, &f->u.retire_cid); + + default: + /* BUG: unsupported frame type generated */ + return NGX_ERROR; + } +} + + +static size_t +ngx_quic_create_ping(u_char *p) +{ + u_char *start; + + if (p == NULL) { + return ngx_quic_varint_len(NGX_QUIC_FT_PING); + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_PING); + + return p - start; +} + + +static size_t +ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack, ngx_chain_t *ranges) +{ + size_t len; + u_char *start; + ngx_buf_t *b; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_ACK); + len += ngx_quic_varint_len(ack->largest); + len += ngx_quic_varint_len(ack->delay); + len += ngx_quic_varint_len(ack->range_count); + len += ngx_quic_varint_len(ack->first_range); + len += ack->ranges_length; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_ACK); + ngx_quic_build_int(&p, ack->largest); + ngx_quic_build_int(&p, ack->delay); + ngx_quic_build_int(&p, ack->range_count); + ngx_quic_build_int(&p, ack->first_range); + + while (ranges) { + b = ranges->buf; + p = ngx_cpymem(p, b->pos, b->last - b->pos); + ranges = ranges->next; + } + + return p - start; +} + + +static size_t +ngx_quic_create_reset_stream(u_char *p, ngx_quic_reset_stream_frame_t *rs) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_RESET_STREAM); + len += ngx_quic_varint_len(rs->id); + len += ngx_quic_varint_len(rs->error_code); + len += ngx_quic_varint_len(rs->final_size); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_RESET_STREAM); + ngx_quic_build_int(&p, rs->id); + ngx_quic_build_int(&p, rs->error_code); + ngx_quic_build_int(&p, rs->final_size); + + return p - start; +} + + +static size_t +ngx_quic_create_stop_sending(u_char *p, ngx_quic_stop_sending_frame_t *ss) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_STOP_SENDING); + len += ngx_quic_varint_len(ss->id); + len += ngx_quic_varint_len(ss->error_code); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_STOP_SENDING); + ngx_quic_build_int(&p, ss->id); + ngx_quic_build_int(&p, ss->error_code); + + return p - start; +} + + +static size_t +ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto, + ngx_chain_t *data) +{ + size_t len; + u_char *start; + ngx_buf_t *b; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_CRYPTO); + len += ngx_quic_varint_len(crypto->offset); + len += ngx_quic_varint_len(crypto->length); + len += crypto->length; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_CRYPTO); + ngx_quic_build_int(&p, crypto->offset); + ngx_quic_build_int(&p, crypto->length); + + while (data) { + b = data->buf; + p = ngx_cpymem(p, b->pos, b->last - b->pos); + data = data->next; + } + + return p - start; +} + + +static size_t +ngx_quic_create_hs_done(u_char *p) +{ + u_char *start; + + if (p == NULL) { + return ngx_quic_varint_len(NGX_QUIC_FT_HANDSHAKE_DONE); + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_HANDSHAKE_DONE); + + return p - start; +} + + +static size_t +ngx_quic_create_new_token(u_char *p, ngx_quic_new_token_frame_t *token, + ngx_chain_t *data) +{ + size_t len; + u_char *start; + ngx_buf_t *b; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_NEW_TOKEN); + len += ngx_quic_varint_len(token->length); + len += token->length; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_NEW_TOKEN); + ngx_quic_build_int(&p, token->length); + + while (data) { + b = data->buf; + p = ngx_cpymem(p, b->pos, b->last - b->pos); + data = data->next; + } + + return p - start; +} + + +static size_t +ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf, + ngx_chain_t *data) +{ + size_t len; + u_char *start, type; + ngx_buf_t *b; + + type = NGX_QUIC_FT_STREAM; + + if (sf->off) { + type |= NGX_QUIC_STREAM_FRAME_OFF; + } + + if (sf->len) { + type |= NGX_QUIC_STREAM_FRAME_LEN; + } + + if (sf->fin) { + type |= NGX_QUIC_STREAM_FRAME_FIN; + } + + if (p == NULL) { + len = ngx_quic_varint_len(type); + len += ngx_quic_varint_len(sf->stream_id); + + if (sf->off) { + len += ngx_quic_varint_len(sf->offset); + } + + if (sf->len) { + len += ngx_quic_varint_len(sf->length); + } + + len += sf->length; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, type); + ngx_quic_build_int(&p, sf->stream_id); + + if (sf->off) { + ngx_quic_build_int(&p, sf->offset); + } + + if (sf->len) { + ngx_quic_build_int(&p, sf->length); + } + + while (data) { + b = data->buf; + p = ngx_cpymem(p, b->pos, b->last - b->pos); + data = data->next; + } + + return p - start; +} + + +static size_t +ngx_quic_create_max_streams(u_char *p, ngx_quic_max_streams_frame_t *ms) +{ + size_t len; + u_char *start; + ngx_uint_t type; + + type = ms->bidi ? NGX_QUIC_FT_MAX_STREAMS : NGX_QUIC_FT_MAX_STREAMS2; + + if (p == NULL) { + len = ngx_quic_varint_len(type); + len += ngx_quic_varint_len(ms->limit); + return len; + } + + start = p; + + ngx_quic_build_int(&p, type); + ngx_quic_build_int(&p, ms->limit); + + return p - start; +} + + +static ngx_int_t +ngx_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id, + ngx_quic_tp_t *dst) +{ + uint64_t varint; + ngx_str_t str; + + varint = 0; + ngx_str_null(&str); + + switch (id) { + + case NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION: + /* zero-length option */ + if (end - p != 0) { + return NGX_ERROR; + } + dst->disable_active_migration = 1; + return NGX_OK; + + case NGX_QUIC_TP_MAX_IDLE_TIMEOUT: + case NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE: + case NGX_QUIC_TP_INITIAL_MAX_DATA: + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL: + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE: + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI: + case NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI: + case NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI: + case NGX_QUIC_TP_ACK_DELAY_EXPONENT: + case NGX_QUIC_TP_MAX_ACK_DELAY: + case NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT: + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + return NGX_ERROR; + } + break; + + case NGX_QUIC_TP_INITIAL_SCID: + + str.len = end - p; + str.data = p; + break; + + default: + return NGX_DECLINED; + } + + switch (id) { + + case NGX_QUIC_TP_MAX_IDLE_TIMEOUT: + dst->max_idle_timeout = varint; + break; + + case NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE: + dst->max_udp_payload_size = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_DATA: + dst->initial_max_data = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL: + dst->initial_max_stream_data_bidi_local = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE: + dst->initial_max_stream_data_bidi_remote = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI: + dst->initial_max_stream_data_uni = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI: + dst->initial_max_streams_bidi = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI: + dst->initial_max_streams_uni = varint; + break; + + case NGX_QUIC_TP_ACK_DELAY_EXPONENT: + dst->ack_delay_exponent = varint; + break; + + case NGX_QUIC_TP_MAX_ACK_DELAY: + dst->max_ack_delay = varint; + break; + + case NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT: + dst->active_connection_id_limit = varint; + break; + + case NGX_QUIC_TP_INITIAL_SCID: + dst->initial_scid = str; + break; + + default: + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, + ngx_log_t *log) +{ + uint64_t id, len; + ngx_int_t rc; + + while (p < end) { + p = ngx_quic_parse_int(p, end, &id); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic failed to parse transport param id"); + return NGX_ERROR; + } + + switch (id) { + case NGX_QUIC_TP_ORIGINAL_DCID: + case NGX_QUIC_TP_PREFERRED_ADDRESS: + case NGX_QUIC_TP_RETRY_SCID: + case NGX_QUIC_TP_SR_TOKEN: + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic client sent forbidden transport param" + " id:0x%xL", id); + return NGX_ERROR; + } + + p = ngx_quic_parse_int(p, end, &len); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic failed to parse" + " transport param id:0x%xL length", id); + return NGX_ERROR; + } + + rc = ngx_quic_parse_transport_param(p, p + len, id, tp); + + if (rc == NGX_ERROR) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic failed to parse" + " transport param id:0x%xL data", id); + return NGX_ERROR; + } + + if (rc == NGX_DECLINED) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic %s transport param id:0x%xL, skipped", + (id % 31 == 27) ? "reserved" : "unknown", id); + } + + p += len; + } + + if (p != end) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic trailing garbage in" + " transport parameters: bytes:%ui", + end - p); + return NGX_ERROR; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, + "quic transport parameters parsed ok"); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp disable active migration: %ui", + tp->disable_active_migration); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp idle_timeout:%ui", + tp->max_idle_timeout); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp max_udp_payload_size:%ui", + tp->max_udp_payload_size); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_data:%ui", + tp->initial_max_data); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp max_stream_data_bidi_local:%ui", + tp->initial_max_stream_data_bidi_local); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp max_stream_data_bidi_remote:%ui", + tp->initial_max_stream_data_bidi_remote); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp max_stream_data_uni:%ui", + tp->initial_max_stream_data_uni); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp initial_max_streams_bidi:%ui", + tp->initial_max_streams_bidi); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp initial_max_streams_uni:%ui", + tp->initial_max_streams_uni); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp ack_delay_exponent:%ui", + tp->ack_delay_exponent); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_ack_delay:%ui", + tp->max_ack_delay); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp active_connection_id_limit:%ui", + tp->active_connection_id_limit); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp initial source_connection_id len:%uz %xV", + tp->initial_scid.len, &tp->initial_scid); + + return NGX_OK; +} + + +static size_t +ngx_quic_create_max_stream_data(u_char *p, ngx_quic_max_stream_data_frame_t *ms) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_MAX_STREAM_DATA); + len += ngx_quic_varint_len(ms->id); + len += ngx_quic_varint_len(ms->limit); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_MAX_STREAM_DATA); + ngx_quic_build_int(&p, ms->id); + ngx_quic_build_int(&p, ms->limit); + + return p - start; +} + + +static size_t +ngx_quic_create_max_data(u_char *p, ngx_quic_max_data_frame_t *md) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_MAX_DATA); + len += ngx_quic_varint_len(md->max_data); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_MAX_DATA); + ngx_quic_build_int(&p, md->max_data); + + return p - start; +} + + +static size_t +ngx_quic_create_path_challenge(u_char *p, ngx_quic_path_challenge_frame_t *pc) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_PATH_CHALLENGE); + len += sizeof(pc->data); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_PATH_CHALLENGE); + p = ngx_cpymem(p, &pc->data, sizeof(pc->data)); + + return p - start; +} + + +static size_t +ngx_quic_create_path_response(u_char *p, ngx_quic_path_challenge_frame_t *pc) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_PATH_RESPONSE); + len += sizeof(pc->data); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_PATH_RESPONSE); + p = ngx_cpymem(p, &pc->data, sizeof(pc->data)); + + return p - start; +} + + +static size_t +ngx_quic_create_new_connection_id(u_char *p, ngx_quic_new_conn_id_frame_t *ncid) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_NEW_CONNECTION_ID); + len += ngx_quic_varint_len(ncid->seqnum); + len += ngx_quic_varint_len(ncid->retire); + len++; + len += ncid->len; + len += NGX_QUIC_SR_TOKEN_LEN; + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_NEW_CONNECTION_ID); + ngx_quic_build_int(&p, ncid->seqnum); + ngx_quic_build_int(&p, ncid->retire); + *p++ = ncid->len; + p = ngx_cpymem(p, ncid->cid, ncid->len); + p = ngx_cpymem(p, ncid->srt, NGX_QUIC_SR_TOKEN_LEN); + + return p - start; +} + + +static size_t +ngx_quic_create_retire_connection_id(u_char *p, + ngx_quic_retire_cid_frame_t *rcid) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_RETIRE_CONNECTION_ID); + len += ngx_quic_varint_len(rcid->sequence_number); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_RETIRE_CONNECTION_ID); + ngx_quic_build_int(&p, rcid->sequence_number); + + return p - start; +} + + +ngx_int_t +ngx_quic_init_transport_params(ngx_quic_tp_t *tp, ngx_quic_conf_t *qcf) +{ + ngx_uint_t nstreams; + + ngx_memzero(tp, sizeof(ngx_quic_tp_t)); + + /* + * set by ngx_memzero(): + * + * tp->disable_active_migration = 0; + * tp->original_dcid = { 0, NULL }; + * tp->initial_scid = { 0, NULL }; + * tp->retry_scid = { 0, NULL }; + * tp->sr_token = { 0 } + * tp->sr_enabled = 0 + * tp->preferred_address = NULL + */ + + tp->max_idle_timeout = qcf->idle_timeout; + + tp->max_udp_payload_size = NGX_QUIC_MAX_UDP_PAYLOAD_SIZE; + + nstreams = qcf->max_concurrent_streams_bidi + + qcf->max_concurrent_streams_uni; + + tp->initial_max_data = nstreams * qcf->stream_buffer_size; + tp->initial_max_stream_data_bidi_local = qcf->stream_buffer_size; + tp->initial_max_stream_data_bidi_remote = qcf->stream_buffer_size; + tp->initial_max_stream_data_uni = qcf->stream_buffer_size; + + tp->initial_max_streams_bidi = qcf->max_concurrent_streams_bidi; + tp->initial_max_streams_uni = qcf->max_concurrent_streams_uni; + + tp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY; + tp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT; + + tp->active_connection_id_limit = qcf->active_connection_id_limit; + tp->disable_active_migration = qcf->disable_active_migration; + + return NGX_OK; +} + + +ssize_t +ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, + size_t *clen) +{ + u_char *p; + size_t len; + +#define ngx_quic_tp_len(id, value) \ + ngx_quic_varint_len(id) \ + + ngx_quic_varint_len(value) \ + + ngx_quic_varint_len(ngx_quic_varint_len(value)) + +#define ngx_quic_tp_vint(id, value) \ + do { \ + ngx_quic_build_int(&p, id); \ + ngx_quic_build_int(&p, ngx_quic_varint_len(value)); \ + ngx_quic_build_int(&p, value); \ + } while (0) + +#define ngx_quic_tp_strlen(id, value) \ + ngx_quic_varint_len(id) \ + + ngx_quic_varint_len(value.len) \ + + value.len + +#define ngx_quic_tp_str(id, value) \ + do { \ + ngx_quic_build_int(&p, id); \ + ngx_quic_build_int(&p, value.len); \ + p = ngx_cpymem(p, value.data, value.len); \ + } while (0) + + len = ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_DATA, tp->initial_max_data); + + len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI, + tp->initial_max_streams_uni); + + len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI, + tp->initial_max_streams_bidi); + + len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, + tp->initial_max_stream_data_bidi_local); + + len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, + tp->initial_max_stream_data_bidi_remote); + + len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI, + tp->initial_max_stream_data_uni); + + len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_IDLE_TIMEOUT, + tp->max_idle_timeout); + + len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE, + tp->max_udp_payload_size); + + if (tp->disable_active_migration) { + len += ngx_quic_varint_len(NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION); + len += ngx_quic_varint_len(0); + } + + len += ngx_quic_tp_len(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, + tp->active_connection_id_limit); + + /* transport parameters listed above will be saved in 0-RTT context */ + if (clen) { + *clen = len; + } + + len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_ACK_DELAY, + tp->max_ack_delay); + + len += ngx_quic_tp_len(NGX_QUIC_TP_ACK_DELAY_EXPONENT, + tp->ack_delay_exponent); + + len += ngx_quic_tp_strlen(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); + len += ngx_quic_tp_strlen(NGX_QUIC_TP_INITIAL_SCID, tp->initial_scid); + + if (tp->retry_scid.len) { + len += ngx_quic_tp_strlen(NGX_QUIC_TP_RETRY_SCID, tp->retry_scid); + } + + len += ngx_quic_varint_len(NGX_QUIC_TP_SR_TOKEN); + len += ngx_quic_varint_len(NGX_QUIC_SR_TOKEN_LEN); + len += NGX_QUIC_SR_TOKEN_LEN; + + if (pos == NULL) { + return len; + } + + p = pos; + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_DATA, + tp->initial_max_data); + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI, + tp->initial_max_streams_uni); + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI, + tp->initial_max_streams_bidi); + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, + tp->initial_max_stream_data_bidi_local); + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, + tp->initial_max_stream_data_bidi_remote); + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI, + tp->initial_max_stream_data_uni); + + ngx_quic_tp_vint(NGX_QUIC_TP_MAX_IDLE_TIMEOUT, + tp->max_idle_timeout); + + ngx_quic_tp_vint(NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE, + tp->max_udp_payload_size); + + if (tp->disable_active_migration) { + ngx_quic_build_int(&p, NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION); + ngx_quic_build_int(&p, 0); + } + + ngx_quic_tp_vint(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, + tp->active_connection_id_limit); + + ngx_quic_tp_vint(NGX_QUIC_TP_MAX_ACK_DELAY, + tp->max_ack_delay); + + ngx_quic_tp_vint(NGX_QUIC_TP_ACK_DELAY_EXPONENT, + tp->ack_delay_exponent); + + ngx_quic_tp_str(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); + ngx_quic_tp_str(NGX_QUIC_TP_INITIAL_SCID, tp->initial_scid); + + if (tp->retry_scid.len) { + ngx_quic_tp_str(NGX_QUIC_TP_RETRY_SCID, tp->retry_scid); + } + + ngx_quic_build_int(&p, NGX_QUIC_TP_SR_TOKEN); + ngx_quic_build_int(&p, NGX_QUIC_SR_TOKEN_LEN); + p = ngx_cpymem(p, tp->sr_token, NGX_QUIC_SR_TOKEN_LEN); + + return p - pos; +} + + +static size_t +ngx_quic_create_close(u_char *p, ngx_quic_frame_t *f) +{ + size_t len; + u_char *start; + ngx_quic_close_frame_t *cl; + + cl = &f->u.close; + + if (p == NULL) { + len = ngx_quic_varint_len(f->type); + len += ngx_quic_varint_len(cl->error_code); + + if (f->type != NGX_QUIC_FT_CONNECTION_CLOSE_APP) { + len += ngx_quic_varint_len(cl->frame_type); + } + + len += ngx_quic_varint_len(cl->reason.len); + len += cl->reason.len; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, f->type); + ngx_quic_build_int(&p, cl->error_code); + + if (f->type != NGX_QUIC_FT_CONNECTION_CLOSE_APP) { + ngx_quic_build_int(&p, cl->frame_type); + } + + ngx_quic_build_int(&p, cl->reason.len); + p = ngx_cpymem(p, cl->reason.data, cl->reason.len); + + return p - start; +} + + +void +ngx_quic_dcid_encode_key(u_char *dcid, uint64_t key) +{ + (void) ngx_quic_write_uint64(dcid, key); +} diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_transport.h b/src/deps/src/nginx/src/event/quic/ngx_event_quic_transport.h new file mode 100644 index 000000000..3e320391a --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_transport.h @@ -0,0 +1,397 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_ +#define _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_ + + +#include +#include + + +/* + * RFC 9000, 17.2. Long Header Packets + * 17.3. Short Header Packets + * + * QUIC flags in first byte + */ +#define NGX_QUIC_PKT_LONG 0x80 /* header form */ +#define NGX_QUIC_PKT_FIXED_BIT 0x40 +#define NGX_QUIC_PKT_TYPE 0x30 /* in long packet */ +#define NGX_QUIC_PKT_KPHASE 0x04 /* in short packet */ + +#define ngx_quic_long_pkt(flags) ((flags) & NGX_QUIC_PKT_LONG) +#define ngx_quic_short_pkt(flags) (((flags) & NGX_QUIC_PKT_LONG) == 0) + +/* Long packet types */ +#define NGX_QUIC_PKT_INITIAL 0x00 +#define NGX_QUIC_PKT_ZRTT 0x10 +#define NGX_QUIC_PKT_HANDSHAKE 0x20 +#define NGX_QUIC_PKT_RETRY 0x30 + +#define ngx_quic_pkt_in(flags) \ + (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_INITIAL) +#define ngx_quic_pkt_zrtt(flags) \ + (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_ZRTT) +#define ngx_quic_pkt_hs(flags) \ + (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_HANDSHAKE) +#define ngx_quic_pkt_retry(flags) \ + (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_RETRY) + +#define ngx_quic_pkt_rb_mask(flags) \ + (ngx_quic_long_pkt(flags) ? 0x0C : 0x18) +#define ngx_quic_pkt_hp_mask(flags) \ + (ngx_quic_long_pkt(flags) ? 0x0F : 0x1F) + +#define ngx_quic_level_name(lvl) \ + (lvl == ssl_encryption_application) ? "app" \ + : (lvl == ssl_encryption_initial) ? "init" \ + : (lvl == ssl_encryption_handshake) ? "hs" : "early" + +#define NGX_QUIC_MAX_CID_LEN 20 +#define NGX_QUIC_SERVER_CID_LEN NGX_QUIC_MAX_CID_LEN + +/* 12.4. Frames and Frame Types */ +#define NGX_QUIC_FT_PADDING 0x00 +#define NGX_QUIC_FT_PING 0x01 +#define NGX_QUIC_FT_ACK 0x02 +#define NGX_QUIC_FT_ACK_ECN 0x03 +#define NGX_QUIC_FT_RESET_STREAM 0x04 +#define NGX_QUIC_FT_STOP_SENDING 0x05 +#define NGX_QUIC_FT_CRYPTO 0x06 +#define NGX_QUIC_FT_NEW_TOKEN 0x07 +#define NGX_QUIC_FT_STREAM 0x08 +#define NGX_QUIC_FT_STREAM1 0x09 +#define NGX_QUIC_FT_STREAM2 0x0A +#define NGX_QUIC_FT_STREAM3 0x0B +#define NGX_QUIC_FT_STREAM4 0x0C +#define NGX_QUIC_FT_STREAM5 0x0D +#define NGX_QUIC_FT_STREAM6 0x0E +#define NGX_QUIC_FT_STREAM7 0x0F +#define NGX_QUIC_FT_MAX_DATA 0x10 +#define NGX_QUIC_FT_MAX_STREAM_DATA 0x11 +#define NGX_QUIC_FT_MAX_STREAMS 0x12 +#define NGX_QUIC_FT_MAX_STREAMS2 0x13 +#define NGX_QUIC_FT_DATA_BLOCKED 0x14 +#define NGX_QUIC_FT_STREAM_DATA_BLOCKED 0x15 +#define NGX_QUIC_FT_STREAMS_BLOCKED 0x16 +#define NGX_QUIC_FT_STREAMS_BLOCKED2 0x17 +#define NGX_QUIC_FT_NEW_CONNECTION_ID 0x18 +#define NGX_QUIC_FT_RETIRE_CONNECTION_ID 0x19 +#define NGX_QUIC_FT_PATH_CHALLENGE 0x1A +#define NGX_QUIC_FT_PATH_RESPONSE 0x1B +#define NGX_QUIC_FT_CONNECTION_CLOSE 0x1C +#define NGX_QUIC_FT_CONNECTION_CLOSE_APP 0x1D +#define NGX_QUIC_FT_HANDSHAKE_DONE 0x1E + +#define NGX_QUIC_FT_LAST NGX_QUIC_FT_HANDSHAKE_DONE + +/* 22.5. QUIC Transport Error Codes Registry */ +#define NGX_QUIC_ERR_NO_ERROR 0x00 +#define NGX_QUIC_ERR_INTERNAL_ERROR 0x01 +#define NGX_QUIC_ERR_CONNECTION_REFUSED 0x02 +#define NGX_QUIC_ERR_FLOW_CONTROL_ERROR 0x03 +#define NGX_QUIC_ERR_STREAM_LIMIT_ERROR 0x04 +#define NGX_QUIC_ERR_STREAM_STATE_ERROR 0x05 +#define NGX_QUIC_ERR_FINAL_SIZE_ERROR 0x06 +#define NGX_QUIC_ERR_FRAME_ENCODING_ERROR 0x07 +#define NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR 0x08 +#define NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR 0x09 +#define NGX_QUIC_ERR_PROTOCOL_VIOLATION 0x0A +#define NGX_QUIC_ERR_INVALID_TOKEN 0x0B +#define NGX_QUIC_ERR_APPLICATION_ERROR 0x0C +#define NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED 0x0D +#define NGX_QUIC_ERR_KEY_UPDATE_ERROR 0x0E +#define NGX_QUIC_ERR_AEAD_LIMIT_REACHED 0x0F +#define NGX_QUIC_ERR_NO_VIABLE_PATH 0x10 + +#define NGX_QUIC_ERR_CRYPTO_ERROR 0x100 + +#define NGX_QUIC_ERR_CRYPTO(e) (NGX_QUIC_ERR_CRYPTO_ERROR + (e)) + + +/* 22.3. QUIC Transport Parameters Registry */ +#define NGX_QUIC_TP_ORIGINAL_DCID 0x00 +#define NGX_QUIC_TP_MAX_IDLE_TIMEOUT 0x01 +#define NGX_QUIC_TP_SR_TOKEN 0x02 +#define NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE 0x03 +#define NGX_QUIC_TP_INITIAL_MAX_DATA 0x04 +#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL 0x05 +#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE 0x06 +#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI 0x07 +#define NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI 0x08 +#define NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI 0x09 +#define NGX_QUIC_TP_ACK_DELAY_EXPONENT 0x0A +#define NGX_QUIC_TP_MAX_ACK_DELAY 0x0B +#define NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION 0x0C +#define NGX_QUIC_TP_PREFERRED_ADDRESS 0x0D +#define NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT 0x0E +#define NGX_QUIC_TP_INITIAL_SCID 0x0F +#define NGX_QUIC_TP_RETRY_SCID 0x10 + +#define NGX_QUIC_CID_LEN_MIN 8 +#define NGX_QUIC_CID_LEN_MAX 20 + +#define NGX_QUIC_MAX_RANGES 10 + + +typedef struct { + uint64_t gap; + uint64_t range; +} ngx_quic_ack_range_t; + + +typedef struct { + uint64_t largest; + uint64_t delay; + uint64_t range_count; + uint64_t first_range; + uint64_t ect0; + uint64_t ect1; + uint64_t ce; + uint64_t ranges_length; +} ngx_quic_ack_frame_t; + + +typedef struct { + uint64_t seqnum; + uint64_t retire; + uint8_t len; + u_char cid[NGX_QUIC_CID_LEN_MAX]; + u_char srt[NGX_QUIC_SR_TOKEN_LEN]; +} ngx_quic_new_conn_id_frame_t; + + +typedef struct { + uint64_t length; +} ngx_quic_new_token_frame_t; + +/* + * common layout for CRYPTO and STREAM frames; + * conceptually, CRYPTO frame is also a stream + * frame lacking some properties + */ +typedef struct { + uint64_t offset; + uint64_t length; +} ngx_quic_ordered_frame_t; + +typedef ngx_quic_ordered_frame_t ngx_quic_crypto_frame_t; + + +typedef struct { + /* initial fields same as in ngx_quic_ordered_frame_t */ + uint64_t offset; + uint64_t length; + + uint64_t stream_id; + unsigned off:1; + unsigned len:1; + unsigned fin:1; +} ngx_quic_stream_frame_t; + + +typedef struct { + uint64_t max_data; +} ngx_quic_max_data_frame_t; + + +typedef struct { + uint64_t error_code; + uint64_t frame_type; + ngx_str_t reason; +} ngx_quic_close_frame_t; + + +typedef struct { + uint64_t id; + uint64_t error_code; + uint64_t final_size; +} ngx_quic_reset_stream_frame_t; + + +typedef struct { + uint64_t id; + uint64_t error_code; +} ngx_quic_stop_sending_frame_t; + + +typedef struct { + uint64_t limit; + ngx_uint_t bidi; /* unsigned: bidi:1 */ +} ngx_quic_streams_blocked_frame_t; + + +typedef struct { + uint64_t limit; + ngx_uint_t bidi; /* unsigned: bidi:1 */ +} ngx_quic_max_streams_frame_t; + + +typedef struct { + uint64_t id; + uint64_t limit; +} ngx_quic_max_stream_data_frame_t; + + +typedef struct { + uint64_t limit; +} ngx_quic_data_blocked_frame_t; + + +typedef struct { + uint64_t id; + uint64_t limit; +} ngx_quic_stream_data_blocked_frame_t; + + +typedef struct { + uint64_t sequence_number; +} ngx_quic_retire_cid_frame_t; + + +typedef struct { + u_char data[8]; +} ngx_quic_path_challenge_frame_t; + + +typedef struct ngx_quic_frame_s ngx_quic_frame_t; + +struct ngx_quic_frame_s { + ngx_uint_t type; + enum ssl_encryption_level_t level; + ngx_queue_t queue; + uint64_t pnum; + size_t plen; + ngx_msec_t send_time; + ssize_t len; + unsigned need_ack:1; + unsigned pkt_need_ack:1; + unsigned ignore_congestion:1; + + ngx_chain_t *data; + union { + ngx_quic_ack_frame_t ack; + ngx_quic_crypto_frame_t crypto; + ngx_quic_ordered_frame_t ord; + ngx_quic_new_conn_id_frame_t ncid; + ngx_quic_new_token_frame_t token; + ngx_quic_stream_frame_t stream; + ngx_quic_max_data_frame_t max_data; + ngx_quic_close_frame_t close; + ngx_quic_reset_stream_frame_t reset_stream; + ngx_quic_stop_sending_frame_t stop_sending; + ngx_quic_streams_blocked_frame_t streams_blocked; + ngx_quic_max_streams_frame_t max_streams; + ngx_quic_max_stream_data_frame_t max_stream_data; + ngx_quic_data_blocked_frame_t data_blocked; + ngx_quic_stream_data_blocked_frame_t stream_data_blocked; + ngx_quic_retire_cid_frame_t retire_cid; + ngx_quic_path_challenge_frame_t path_challenge; + ngx_quic_path_challenge_frame_t path_response; + } u; +}; + + +typedef struct { + ngx_log_t *log; + ngx_quic_path_t *path; + + ngx_quic_keys_t *keys; + + ngx_msec_t received; + uint64_t number; + uint8_t num_len; + uint32_t trunc; + uint8_t flags; + uint32_t version; + ngx_str_t token; + enum ssl_encryption_level_t level; + ngx_uint_t error; + + /* filled in by parser */ + ngx_buf_t *raw; /* udp datagram */ + + u_char *data; /* quic packet */ + size_t len; + + /* cleartext fields */ + ngx_str_t odcid; /* retry packet tag */ + u_char odcid_buf[NGX_QUIC_MAX_CID_LEN]; + ngx_str_t dcid; + ngx_str_t scid; + uint64_t pn; + u_char *plaintext; + ngx_str_t payload; /* decrypted data */ + + unsigned need_ack:1; + unsigned key_phase:1; + unsigned key_update:1; + unsigned parsed:1; + unsigned decrypted:1; + unsigned validated:1; + unsigned retried:1; + unsigned first:1; + unsigned rebound:1; + unsigned path_challenged:1; +} ngx_quic_header_t; + + +typedef struct { + ngx_msec_t max_idle_timeout; + ngx_msec_t max_ack_delay; + + size_t max_udp_payload_size; + size_t initial_max_data; + size_t initial_max_stream_data_bidi_local; + size_t initial_max_stream_data_bidi_remote; + size_t initial_max_stream_data_uni; + ngx_uint_t initial_max_streams_bidi; + ngx_uint_t initial_max_streams_uni; + ngx_uint_t ack_delay_exponent; + ngx_uint_t active_connection_id_limit; + ngx_flag_t disable_active_migration; + + ngx_str_t original_dcid; + ngx_str_t initial_scid; + ngx_str_t retry_scid; + u_char sr_token[NGX_QUIC_SR_TOKEN_LEN]; + + /* TODO */ + void *preferred_address; +} ngx_quic_tp_t; + + +ngx_int_t ngx_quic_parse_packet(ngx_quic_header_t *pkt); + +size_t ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out); + +size_t ngx_quic_payload_size(ngx_quic_header_t *pkt, size_t pkt_len); + +size_t ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out, + u_char **pnp); + +size_t ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out, + u_char **start); + +ssize_t ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, + ngx_quic_frame_t *frame); +ssize_t ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f); + +ssize_t ngx_quic_parse_ack_range(ngx_log_t *log, u_char *start, + u_char *end, uint64_t *gap, uint64_t *range); +size_t ngx_quic_create_ack_range(u_char *p, uint64_t gap, uint64_t range); + +ngx_int_t ngx_quic_init_transport_params(ngx_quic_tp_t *tp, + ngx_quic_conf_t *qcf); +ngx_int_t ngx_quic_parse_transport_params(u_char *p, u_char *end, + ngx_quic_tp_t *tp, ngx_log_t *log); +ssize_t ngx_quic_create_transport_params(u_char *p, u_char *end, + ngx_quic_tp_t *tp, size_t *clen); + +void ngx_quic_dcid_encode_key(u_char *dcid, uint64_t key); + +#endif /* _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_ */ diff --git a/src/deps/src/nginx/src/event/quic/ngx_event_quic_udp.c b/src/deps/src/nginx/src/event/quic/ngx_event_quic_udp.c new file mode 100644 index 000000000..15b54bc82 --- /dev/null +++ b/src/deps/src/nginx/src/event/quic/ngx_event_quic_udp.c @@ -0,0 +1,420 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +static void ngx_quic_close_accepted_connection(ngx_connection_t *c); +static ngx_connection_t *ngx_quic_lookup_connection(ngx_listening_t *ls, + ngx_str_t *key, struct sockaddr *local_sockaddr, socklen_t local_socklen); + + +void +ngx_quic_recvmsg(ngx_event_t *ev) +{ + ssize_t n; + ngx_str_t key; + ngx_buf_t buf; + ngx_log_t *log; + ngx_err_t err; + socklen_t socklen, local_socklen; + ngx_event_t *rev, *wev; + struct iovec iov[1]; + struct msghdr msg; + ngx_sockaddr_t sa, lsa; + struct sockaddr *sockaddr, *local_sockaddr; + ngx_listening_t *ls; + ngx_event_conf_t *ecf; + ngx_connection_t *c, *lc; + ngx_quic_socket_t *qsock; + static u_char buffer[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + +#if (NGX_HAVE_ADDRINFO_CMSG) + u_char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))]; +#endif + + if (ev->timedout) { + if (ngx_enable_accept_events((ngx_cycle_t *) ngx_cycle) != NGX_OK) { + return; + } + + ev->timedout = 0; + } + + ecf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_core_module); + + if (!(ngx_event_flags & NGX_USE_KQUEUE_EVENT)) { + ev->available = ecf->multi_accept; + } + + lc = ev->data; + ls = lc->listening; + ev->ready = 0; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0, + "quic recvmsg on %V, ready: %d", + &ls->addr_text, ev->available); + + do { + ngx_memzero(&msg, sizeof(struct msghdr)); + + iov[0].iov_base = (void *) buffer; + iov[0].iov_len = sizeof(buffer); + + msg.msg_name = &sa; + msg.msg_namelen = sizeof(ngx_sockaddr_t); + msg.msg_iov = iov; + msg.msg_iovlen = 1; + +#if (NGX_HAVE_ADDRINFO_CMSG) + if (ls->wildcard) { + msg.msg_control = &msg_control; + msg.msg_controllen = sizeof(msg_control); + + ngx_memzero(&msg_control, sizeof(msg_control)); + } +#endif + + n = recvmsg(lc->fd, &msg, 0); + + if (n == -1) { + err = ngx_socket_errno; + + if (err == NGX_EAGAIN) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, err, + "quic recvmsg() not ready"); + return; + } + + ngx_log_error(NGX_LOG_ALERT, ev->log, err, "quic recvmsg() failed"); + + return; + } + +#if (NGX_HAVE_ADDRINFO_CMSG) + if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) { + ngx_log_error(NGX_LOG_ALERT, ev->log, 0, + "quic recvmsg() truncated data"); + continue; + } +#endif + + sockaddr = msg.msg_name; + socklen = msg.msg_namelen; + + if (socklen > (socklen_t) sizeof(ngx_sockaddr_t)) { + socklen = sizeof(ngx_sockaddr_t); + } + +#if (NGX_HAVE_UNIX_DOMAIN) + + if (sockaddr->sa_family == AF_UNIX) { + struct sockaddr_un *saun = (struct sockaddr_un *) sockaddr; + + if (socklen <= (socklen_t) offsetof(struct sockaddr_un, sun_path) + || saun->sun_path[0] == '\0') + { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, + "unbound unix socket"); + goto next; + } + } + +#endif + + local_sockaddr = ls->sockaddr; + local_socklen = ls->socklen; + +#if (NGX_HAVE_ADDRINFO_CMSG) + + if (ls->wildcard) { + struct cmsghdr *cmsg; + + ngx_memcpy(&lsa, local_sockaddr, local_socklen); + local_sockaddr = &lsa.sockaddr; + + for (cmsg = CMSG_FIRSTHDR(&msg); + cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) + { + if (ngx_get_srcaddr_cmsg(cmsg, local_sockaddr) == NGX_OK) { + break; + } + } + } + +#endif + + if (ngx_quic_get_packet_dcid(ev->log, buffer, n, &key) != NGX_OK) { + goto next; + } + + c = ngx_quic_lookup_connection(ls, &key, local_sockaddr, local_socklen); + + if (c) { + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + ngx_log_handler_pt handler; + + handler = c->log->handler; + c->log->handler = NULL; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic recvmsg: fd:%d n:%z", c->fd, n); + + c->log->handler = handler; + } +#endif + + ngx_memzero(&buf, sizeof(ngx_buf_t)); + + buf.pos = buffer; + buf.last = buffer + n; + buf.start = buf.pos; + buf.end = buffer + sizeof(buffer); + + qsock = ngx_quic_get_socket(c); + + ngx_memcpy(&qsock->sockaddr, sockaddr, socklen); + qsock->socklen = socklen; + + c->udp->buffer = &buf; + + rev = c->read; + rev->ready = 1; + rev->active = 0; + + rev->handler(rev); + + if (c->udp) { + c->udp->buffer = NULL; + } + + rev->ready = 0; + rev->active = 1; + + goto next; + } + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_accepted, 1); +#endif + + ngx_accept_disabled = ngx_cycle->connection_n / 8 + - ngx_cycle->free_connection_n; + + c = ngx_get_connection(lc->fd, ev->log); + if (c == NULL) { + return; + } + + c->shared = 1; + c->type = SOCK_DGRAM; + c->socklen = socklen; + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, 1); +#endif + + c->pool = ngx_create_pool(ls->pool_size, ev->log); + if (c->pool == NULL) { + ngx_quic_close_accepted_connection(c); + return; + } + + c->sockaddr = ngx_palloc(c->pool, NGX_SOCKADDRLEN); + if (c->sockaddr == NULL) { + ngx_quic_close_accepted_connection(c); + return; + } + + ngx_memcpy(c->sockaddr, sockaddr, socklen); + + log = ngx_palloc(c->pool, sizeof(ngx_log_t)); + if (log == NULL) { + ngx_quic_close_accepted_connection(c); + return; + } + + *log = ls->log; + + c->log = log; + c->pool->log = log; + c->listening = ls; + + if (local_sockaddr == &lsa.sockaddr) { + local_sockaddr = ngx_palloc(c->pool, local_socklen); + if (local_sockaddr == NULL) { + ngx_quic_close_accepted_connection(c); + return; + } + + ngx_memcpy(local_sockaddr, &lsa, local_socklen); + } + + c->local_sockaddr = local_sockaddr; + c->local_socklen = local_socklen; + + c->buffer = ngx_create_temp_buf(c->pool, n); + if (c->buffer == NULL) { + ngx_quic_close_accepted_connection(c); + return; + } + + c->buffer->last = ngx_cpymem(c->buffer->last, buffer, n); + + rev = c->read; + wev = c->write; + + rev->active = 1; + wev->ready = 1; + + rev->log = log; + wev->log = log; + + /* + * TODO: MT: - ngx_atomic_fetch_add() + * or protection by critical section or light mutex + * + * TODO: MP: - allocated in a shared memory + * - ngx_atomic_fetch_add() + * or protection by critical section or light mutex + */ + + c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); + + c->start_time = ngx_current_msec; + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_handled, 1); +#endif + + if (ls->addr_ntop) { + c->addr_text.data = ngx_pnalloc(c->pool, ls->addr_text_max_len); + if (c->addr_text.data == NULL) { + ngx_quic_close_accepted_connection(c); + return; + } + + c->addr_text.len = ngx_sock_ntop(c->sockaddr, c->socklen, + c->addr_text.data, + ls->addr_text_max_len, 0); + if (c->addr_text.len == 0) { + ngx_quic_close_accepted_connection(c); + return; + } + } + +#if (NGX_DEBUG) + { + ngx_str_t addr; + u_char text[NGX_SOCKADDR_STRLEN]; + + ngx_debug_accepted_connection(ecf, c); + + if (log->log_level & NGX_LOG_DEBUG_EVENT) { + addr.data = text; + addr.len = ngx_sock_ntop(c->sockaddr, c->socklen, text, + NGX_SOCKADDR_STRLEN, 1); + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0, + "*%uA quic recvmsg: %V fd:%d n:%z", + c->number, &addr, c->fd, n); + } + + } +#endif + + log->data = NULL; + log->handler = NULL; + + ls->handler(c); + + next: + + if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { + ev->available -= n; + } + + } while (ev->available); +} + + +static void +ngx_quic_close_accepted_connection(ngx_connection_t *c) +{ + ngx_free_connection(c); + + c->fd = (ngx_socket_t) -1; + + if (c->pool) { + ngx_destroy_pool(c->pool); + } + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, -1); +#endif +} + + +static ngx_connection_t * +ngx_quic_lookup_connection(ngx_listening_t *ls, ngx_str_t *key, + struct sockaddr *local_sockaddr, socklen_t local_socklen) +{ + uint32_t hash; + ngx_int_t rc; + ngx_connection_t *c; + ngx_rbtree_node_t *node, *sentinel; + ngx_quic_socket_t *qsock; + + if (key->len == 0) { + return NULL; + } + + node = ls->rbtree.root; + sentinel = ls->rbtree.sentinel; + hash = ngx_crc32_long(key->data, key->len); + + while (node != sentinel) { + + if (hash < node->key) { + node = node->left; + continue; + } + + if (hash > node->key) { + node = node->right; + continue; + } + + /* hash == node->key */ + + qsock = (ngx_quic_socket_t *) node; + + rc = ngx_memn2cmp(key->data, qsock->sid.id, key->len, qsock->sid.len); + + c = qsock->udp.connection; + + if (rc == 0 && ls->wildcard) { + rc = ngx_cmp_sockaddr(local_sockaddr, local_socklen, + c->local_sockaddr, c->local_socklen, 1); + } + + if (rc == 0) { + c->udp = &qsock->udp; + return c; + } + + node = (rc < 0) ? node->left : node->right; + } + + return NULL; +} diff --git a/src/deps/src/nginx/src/http/modules/ngx_http_access_module.c b/src/deps/src/nginx/src/http/modules/ngx_http_access_module.c index 7355de9e7..ea755200d 100644 --- a/src/deps/src/nginx/src/http/modules/ngx_http_access_module.c +++ b/src/deps/src/nginx/src/http/modules/ngx_http_access_module.c @@ -148,7 +148,7 @@ ngx_http_access_handler(ngx_http_request_t *r) p = sin6->sin6_addr.s6_addr; if (alcf->rules && IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { - addr = p[12] << 24; + addr = (in_addr_t) p[12] << 24; addr += p[13] << 16; addr += p[14] << 8; addr += p[15]; diff --git a/src/deps/src/nginx/src/http/modules/ngx_http_fastcgi_module.c b/src/deps/src/nginx/src/http/modules/ngx_http_fastcgi_module.c index 2d9a18f90..b9890833d 100644 --- a/src/deps/src/nginx/src/http/modules/ngx_http_fastcgi_module.c +++ b/src/deps/src/nginx/src/http/modules/ngx_http_fastcgi_module.c @@ -2048,7 +2048,10 @@ ngx_http_fastcgi_process_header(ngx_http_request_t *r) } u->headers_in.status_n = status; - u->headers_in.status_line = *status_line; + + if (status_line->len > 3) { + u->headers_in.status_line = *status_line; + } } else if (u->headers_in.location) { u->headers_in.status_n = 302; diff --git a/src/deps/src/nginx/src/http/modules/ngx_http_geo_module.c b/src/deps/src/nginx/src/http/modules/ngx_http_geo_module.c index ef4e9b84a..75c03978a 100644 --- a/src/deps/src/nginx/src/http/modules/ngx_http_geo_module.c +++ b/src/deps/src/nginx/src/http/modules/ngx_http_geo_module.c @@ -199,7 +199,7 @@ ngx_http_geo_cidr_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, p = inaddr6->s6_addr; if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { - inaddr = p[12] << 24; + inaddr = (in_addr_t) p[12] << 24; inaddr += p[13] << 16; inaddr += p[14] << 8; inaddr += p[15]; @@ -272,7 +272,7 @@ ngx_http_geo_range_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { p = inaddr6->s6_addr; - inaddr = p[12] << 24; + inaddr = (in_addr_t) p[12] << 24; inaddr += p[13] << 16; inaddr += p[14] << 8; inaddr += p[15]; @@ -1259,7 +1259,7 @@ ngx_http_geo_value(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, return gvvn->value; } - val = ngx_palloc(ctx->pool, sizeof(ngx_http_variable_value_t)); + val = ngx_pcalloc(ctx->pool, sizeof(ngx_http_variable_value_t)); if (val == NULL) { return NULL; } @@ -1271,8 +1271,6 @@ ngx_http_geo_value(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, } val->valid = 1; - val->no_cacheable = 0; - val->not_found = 0; gvvn = ngx_palloc(ctx->temp_pool, sizeof(ngx_http_geo_variable_value_node_t)); diff --git a/src/deps/src/nginx/src/http/modules/ngx_http_geoip_module.c b/src/deps/src/nginx/src/http/modules/ngx_http_geoip_module.c index eaf98764f..5b8b11fc7 100644 --- a/src/deps/src/nginx/src/http/modules/ngx_http_geoip_module.c +++ b/src/deps/src/nginx/src/http/modules/ngx_http_geoip_module.c @@ -266,7 +266,7 @@ ngx_http_geoip_addr(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf) if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { p = inaddr6->s6_addr; - inaddr = p[12] << 24; + inaddr = (in_addr_t) p[12] << 24; inaddr += p[13] << 16; inaddr += p[14] << 8; inaddr += p[15]; diff --git a/src/deps/src/nginx/src/http/modules/ngx_http_referer_module.c b/src/deps/src/nginx/src/http/modules/ngx_http_referer_module.c index 599dd3a12..11a681b84 100644 --- a/src/deps/src/nginx/src/http/modules/ngx_http_referer_module.c +++ b/src/deps/src/nginx/src/http/modules/ngx_http_referer_module.c @@ -631,7 +631,7 @@ ngx_http_add_regex_referer(ngx_conf_t *cf, ngx_http_referer_conf_t *rlcf, #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "the using of the regex \"%V\" requires PCRE library", + "using regex \"%V\" requires PCRE library", name); return NGX_ERROR; diff --git a/src/deps/src/nginx/src/http/modules/ngx_http_rewrite_module.c b/src/deps/src/nginx/src/http/modules/ngx_http_rewrite_module.c index 0e6d4df64..ff3b68716 100644 --- a/src/deps/src/nginx/src/http/modules/ngx_http_rewrite_module.c +++ b/src/deps/src/nginx/src/http/modules/ngx_http_rewrite_module.c @@ -489,6 +489,7 @@ ngx_http_rewrite_return(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } if (cf->args->nelts == 2) { + ngx_str_set(&ret->text.value, ""); return NGX_CONF_OK; } diff --git a/src/deps/src/nginx/src/http/modules/ngx_http_scgi_module.c b/src/deps/src/nginx/src/http/modules/ngx_http_scgi_module.c index 9fc18dcd3..3acea87b7 100644 --- a/src/deps/src/nginx/src/http/modules/ngx_http_scgi_module.c +++ b/src/deps/src/nginx/src/http/modules/ngx_http_scgi_module.c @@ -1153,7 +1153,10 @@ ngx_http_scgi_process_header(ngx_http_request_t *r) } u->headers_in.status_n = status; - u->headers_in.status_line = *status_line; + + if (status_line->len > 3) { + u->headers_in.status_line = *status_line; + } } else if (u->headers_in.location) { u->headers_in.status_n = 302; diff --git a/src/deps/src/nginx/src/http/modules/ngx_http_ssi_filter_module.c b/src/deps/src/nginx/src/http/modules/ngx_http_ssi_filter_module.c index e7601b83e..0b84bd322 100644 --- a/src/deps/src/nginx/src/http/modules/ngx_http_ssi_filter_module.c +++ b/src/deps/src/nginx/src/http/modules/ngx_http_ssi_filter_module.c @@ -2001,7 +2001,7 @@ ngx_http_ssi_regex_match(ngx_http_request_t *r, ngx_str_t *pattern, #else ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, - "the using of the regex \"%V\" in SSI requires PCRE library", + "using regex \"%V\" in SSI requires PCRE library", pattern); return NGX_HTTP_SSI_ERROR; diff --git a/src/deps/src/nginx/src/http/modules/ngx_http_ssl_module.c b/src/deps/src/nginx/src/http/modules/ngx_http_ssl_module.c index 4c4a598b1..1c92d9fa8 100644 --- a/src/deps/src/nginx/src/http/modules/ngx_http_ssl_module.c +++ b/src/deps/src/nginx/src/http/modules/ngx_http_ssl_module.c @@ -9,6 +9,10 @@ #include #include +#if (NGX_QUIC_OPENSSL_COMPAT) +#include +#endif + typedef ngx_int_t (*ngx_ssl_variable_handler_pt)(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s); @@ -39,8 +43,6 @@ static char *ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, static ngx_int_t ngx_http_ssl_compile_certificates(ngx_conf_t *cf, ngx_http_ssl_srv_conf_t *conf); -static char *ngx_http_ssl_enable(ngx_conf_t *cf, ngx_command_t *cmd, - void *conf); static char *ngx_http_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, @@ -52,6 +54,10 @@ static char *ngx_http_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data); static ngx_int_t ngx_http_ssl_init(ngx_conf_t *cf); +#if (NGX_QUIC_OPENSSL_COMPAT) +static ngx_int_t ngx_http_ssl_quic_compat_init(ngx_conf_t *cf, + ngx_http_conf_addr_t *addr); +#endif static ngx_conf_bitmask_t ngx_http_ssl_protocols[] = { @@ -82,24 +88,12 @@ static ngx_conf_enum_t ngx_http_ssl_ocsp[] = { }; -static ngx_conf_deprecated_t ngx_http_ssl_deprecated = { - ngx_conf_deprecated, "ssl", "listen ... ssl" -}; - - static ngx_conf_post_t ngx_http_ssl_conf_command_post = { ngx_http_ssl_conf_command_check }; static ngx_command_t ngx_http_ssl_commands[] = { - { ngx_string("ssl"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, - ngx_http_ssl_enable, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_ssl_srv_conf_t, enable), - &ngx_http_ssl_deprecated }, - { ngx_string("ssl_certificate"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_array_slot, @@ -419,16 +413,22 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg) { - unsigned int srvlen; - unsigned char *srv; + unsigned int srvlen; + unsigned char *srv; #if (NGX_DEBUG) - unsigned int i; + unsigned int i; +#endif +#if (NGX_HTTP_V2 || NGX_HTTP_V3) + ngx_http_connection_t *hc; #endif #if (NGX_HTTP_V2) - ngx_http_connection_t *hc; + ngx_http_v2_srv_conf_t *h2scf; #endif -#if (NGX_HTTP_V2 || NGX_DEBUG) - ngx_connection_t *c; +#if (NGX_HTTP_V3) + ngx_http_v3_srv_conf_t *h3scf; +#endif +#if (NGX_HTTP_V2 || NGX_HTTP_V3 || NGX_DEBUG) + ngx_connection_t *c; c = ngx_ssl_get_connection(ssl_conn); #endif @@ -441,17 +441,49 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, } #endif -#if (NGX_HTTP_V2) +#if (NGX_HTTP_V2 || NGX_HTTP_V3) hc = c->data; +#endif + +#if (NGX_HTTP_V3) + if (hc->addr_conf->quic) { + + h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); + + if (h3scf->enable && h3scf->enable_hq) { + srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO + NGX_HTTP_V3_HQ_ALPN_PROTO; + srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO NGX_HTTP_V3_HQ_ALPN_PROTO) + - 1; + + } else if (h3scf->enable_hq) { + srv = (unsigned char *) NGX_HTTP_V3_HQ_ALPN_PROTO; + srvlen = sizeof(NGX_HTTP_V3_HQ_ALPN_PROTO) - 1; + + } else if (h3scf->enable) { + srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO; + srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO) - 1; + + } else { + return SSL_TLSEXT_ERR_ALERT_FATAL; + } - if (hc->addr_conf->http2) { - srv = (unsigned char *) NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS; - srvlen = sizeof(NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS) - 1; } else #endif { - srv = (unsigned char *) NGX_HTTP_ALPN_PROTOS; - srvlen = sizeof(NGX_HTTP_ALPN_PROTOS) - 1; +#if (NGX_HTTP_V2) + h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); + + if (h2scf->enable || hc->addr_conf->http2) { + srv = (unsigned char *) NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS; + srvlen = sizeof(NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS) - 1; + + } else +#endif + { + srv = (unsigned char *) NGX_HTTP_ALPN_PROTOS; + srvlen = sizeof(NGX_HTTP_ALPN_PROTOS) - 1; + } } if (SSL_select_next_proto((unsigned char **) out, outlen, srv, srvlen, @@ -579,7 +611,6 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t *cf) * sscf->stapling_responder = { 0, NULL }; */ - sscf->enable = NGX_CONF_UNSET; sscf->prefer_server_ciphers = NGX_CONF_UNSET; sscf->early_data = NGX_CONF_UNSET; sscf->reject_handshake = NGX_CONF_UNSET; @@ -611,17 +642,6 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_pool_cleanup_t *cln; - if (conf->enable == NGX_CONF_UNSET) { - if (prev->enable == NGX_CONF_UNSET) { - conf->enable = 0; - - } else { - conf->enable = prev->enable; - conf->file = prev->file; - conf->line = prev->line; - } - } - ngx_conf_merge_value(conf->session_timeout, prev->session_timeout, 300); @@ -676,37 +696,7 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) conf->ssl.log = cf->log; - if (conf->enable) { - - if (conf->certificates) { - if (conf->certificate_keys == NULL) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no \"ssl_certificate_key\" is defined for " - "the \"ssl\" directive in %s:%ui", - conf->file, conf->line); - return NGX_CONF_ERROR; - } - - if (conf->certificate_keys->nelts < conf->certificates->nelts) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no \"ssl_certificate_key\" is defined " - "for certificate \"%V\" and " - "the \"ssl\" directive in %s:%ui", - ((ngx_str_t *) conf->certificates->elts) - + conf->certificates->nelts - 1, - conf->file, conf->line); - return NGX_CONF_ERROR; - } - - } else if (!conf->reject_handshake) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no \"ssl_certificate\" is defined for " - "the \"ssl\" directive in %s:%ui", - conf->file, conf->line); - return NGX_CONF_ERROR; - } - - } else if (conf->certificates) { + if (conf->certificates) { if (conf->certificate_keys == NULL || conf->certificate_keys->nelts < conf->certificates->nelts) @@ -992,26 +982,6 @@ found: } -static char * -ngx_http_ssl_enable(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) -{ - ngx_http_ssl_srv_conf_t *sscf = conf; - - char *rv; - - rv = ngx_conf_set_flag_slot(cf, cmd, conf); - - if (rv != NGX_CONF_OK) { - return rv; - } - - sscf->file = cf->conf_file->file.name.data; - sscf->line = cf->conf_file->line; - - return NGX_CONF_OK; -} - - static char * ngx_http_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { @@ -1241,6 +1211,7 @@ static ngx_int_t ngx_http_ssl_init(ngx_conf_t *cf) { ngx_uint_t a, p, s; + const char *name; ngx_http_conf_addr_t *addr; ngx_http_conf_port_t *port; ngx_http_ssl_srv_conf_t *sscf; @@ -1290,22 +1261,44 @@ ngx_http_ssl_init(ngx_conf_t *cf) addr = port[p].addrs.elts; for (a = 0; a < port[p].addrs.nelts; a++) { - if (!addr[a].opt.ssl) { + if (!addr[a].opt.ssl && !addr[a].opt.quic) { continue; } + if (addr[a].opt.quic) { + name = "quic"; + +#if (NGX_QUIC_OPENSSL_COMPAT) + if (ngx_http_ssl_quic_compat_init(cf, &addr[a]) != NGX_OK) { + return NGX_ERROR; + } +#endif + + } else { + name = "ssl"; + } + cscf = addr[a].default_server; sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; if (sscf->certificates) { + + if (addr[a].opt.quic && !(sscf->protocols & NGX_SSL_TLSv1_3)) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "\"ssl_protocols\" must enable TLSv1.3 for " + "the \"listen ... %s\" directive in %s:%ui", + name, cscf->file_name, cscf->line); + return NGX_ERROR; + } + continue; } if (!sscf->reject_handshake) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "no \"ssl_certificate\" is defined for " - "the \"listen ... ssl\" directive in %s:%ui", - cscf->file_name, cscf->line); + "the \"listen ... %s\" directive in %s:%ui", + name, cscf->file_name, cscf->line); return NGX_ERROR; } @@ -1326,8 +1319,8 @@ ngx_http_ssl_init(ngx_conf_t *cf) ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "no \"ssl_certificate\" is defined for " - "the \"listen ... ssl\" directive in %s:%ui", - cscf->file_name, cscf->line); + "the \"listen ... %s\" directive in %s:%ui", + name, cscf->file_name, cscf->line); return NGX_ERROR; } } @@ -1335,3 +1328,31 @@ ngx_http_ssl_init(ngx_conf_t *cf) return NGX_OK; } + + +#if (NGX_QUIC_OPENSSL_COMPAT) + +static ngx_int_t +ngx_http_ssl_quic_compat_init(ngx_conf_t *cf, ngx_http_conf_addr_t *addr) +{ + ngx_uint_t s; + ngx_http_ssl_srv_conf_t *sscf; + ngx_http_core_srv_conf_t **cscfp, *cscf; + + cscfp = addr->servers.elts; + for (s = 0; s < addr->servers.nelts; s++) { + + cscf = cscfp[s]; + sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; + + if (sscf->certificates || sscf->reject_handshake) { + if (ngx_quic_compat_init(cf, sscf->ssl.ctx) != NGX_OK) { + return NGX_ERROR; + } + } + } + + return NGX_OK; +} + +#endif diff --git a/src/deps/src/nginx/src/http/modules/ngx_http_ssl_module.h b/src/deps/src/nginx/src/http/modules/ngx_http_ssl_module.h index 7ab0f7eae..c69c8ffd2 100644 --- a/src/deps/src/nginx/src/http/modules/ngx_http_ssl_module.h +++ b/src/deps/src/nginx/src/http/modules/ngx_http_ssl_module.h @@ -15,8 +15,6 @@ typedef struct { - ngx_flag_t enable; - ngx_ssl_t ssl; ngx_flag_t prefer_server_ciphers; @@ -64,9 +62,6 @@ typedef struct { ngx_flag_t stapling_verify; ngx_str_t stapling_file; ngx_str_t stapling_responder; - - u_char *file; - ngx_uint_t line; } ngx_http_ssl_srv_conf_t; diff --git a/src/deps/src/nginx/src/http/modules/ngx_http_uwsgi_module.c b/src/deps/src/nginx/src/http/modules/ngx_http_uwsgi_module.c index e4f721bb0..c1731ff48 100644 --- a/src/deps/src/nginx/src/http/modules/ngx_http_uwsgi_module.c +++ b/src/deps/src/nginx/src/http/modules/ngx_http_uwsgi_module.c @@ -1381,7 +1381,10 @@ ngx_http_uwsgi_process_header(ngx_http_request_t *r) } u->headers_in.status_n = status; - u->headers_in.status_line = *status_line; + + if (status_line->len > 3) { + u->headers_in.status_line = *status_line; + } } else if (u->headers_in.location) { u->headers_in.status_n = 302; diff --git a/src/deps/src/nginx/src/http/ngx_http.c b/src/deps/src/nginx/src/http/ngx_http.c index 93e1cd435..d835f896e 100644 --- a/src/deps/src/nginx/src/http/ngx_http.c +++ b/src/deps/src/nginx/src/http/ngx_http.c @@ -1200,7 +1200,10 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, port = cmcf->ports->elts; for (i = 0; i < cmcf->ports->nelts; i++) { - if (p != port[i].port || sa->sa_family != port[i].family) { + if (p != port[i].port + || lsopt->type != port[i].type + || sa->sa_family != port[i].family) + { continue; } @@ -1217,6 +1220,7 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, } port->family = sa->sa_family; + port->type = lsopt->type; port->port = p; port->addrs.elts = NULL; @@ -1237,6 +1241,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, #if (NGX_HTTP_V2) ngx_uint_t http2; #endif +#if (NGX_HTTP_V3) + ngx_uint_t quic; +#endif /* * we cannot compare whole sockaddr struct's as kernel @@ -1278,6 +1285,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, protocols |= lsopt->http2 << 2; protocols_prev |= addr[i].opt.http2 << 2; #endif +#if (NGX_HTTP_V3) + quic = lsopt->quic || addr[i].opt.quic; +#endif if (lsopt->set) { @@ -1365,6 +1375,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, #if (NGX_HTTP_V2) addr[i].opt.http2 = http2; #endif +#if (NGX_HTTP_V3) + addr[i].opt.quic = quic; +#endif return NGX_OK; } @@ -1831,6 +1844,7 @@ ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr) } #endif + ls->type = addr->opt.type; ls->backlog = addr->opt.backlog; ls->rcvbuf = addr->opt.rcvbuf; ls->sndbuf = addr->opt.sndbuf; @@ -1866,6 +1880,12 @@ ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr) ls->reuseport = addr->opt.reuseport; #endif + ls->wildcard = addr->opt.wildcard; + +#if (NGX_HTTP_V3) + ls->quic = addr->opt.quic; +#endif + return ls; } @@ -1897,6 +1917,9 @@ ngx_http_add_addrs(ngx_conf_t *cf, ngx_http_port_t *hport, #endif #if (NGX_HTTP_V2) addrs[i].conf.http2 = addr[i].opt.http2; +#endif +#if (NGX_HTTP_V3) + addrs[i].conf.quic = addr[i].opt.quic; #endif addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; @@ -1962,6 +1985,9 @@ ngx_http_add_addrs6(ngx_conf_t *cf, ngx_http_port_t *hport, #endif #if (NGX_HTTP_V2) addrs6[i].conf.http2 = addr[i].opt.http2; +#endif +#if (NGX_HTTP_V3) + addrs6[i].conf.quic = addr[i].opt.quic; #endif addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; diff --git a/src/deps/src/nginx/src/http/ngx_http.h b/src/deps/src/nginx/src/http/ngx_http.h index a7f312f41..e06464ebd 100644 --- a/src/deps/src/nginx/src/http/ngx_http.h +++ b/src/deps/src/nginx/src/http/ngx_http.h @@ -20,6 +20,8 @@ typedef struct ngx_http_file_cache_s ngx_http_file_cache_t; typedef struct ngx_http_log_ctx_s ngx_http_log_ctx_t; typedef struct ngx_http_chunked_s ngx_http_chunked_t; typedef struct ngx_http_v2_stream_s ngx_http_v2_stream_t; +typedef struct ngx_http_v3_parse_s ngx_http_v3_parse_t; +typedef struct ngx_http_v3_session_s ngx_http_v3_session_t; typedef ngx_int_t (*ngx_http_header_handler_pt)(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); @@ -38,6 +40,9 @@ typedef u_char *(*ngx_http_log_handler_pt)(ngx_http_request_t *r, #if (NGX_HTTP_V2) #include #endif +#if (NGX_HTTP_V3) +#include +#endif #if (NGX_HTTP_CACHE) #include #endif @@ -124,6 +129,11 @@ void ngx_http_handler(ngx_http_request_t *r); void ngx_http_run_posted_requests(ngx_connection_t *c); ngx_int_t ngx_http_post_request(ngx_http_request_t *r, ngx_http_posted_request_t *pr); +ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r, + ngx_str_t *host); +ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, + ngx_uint_t alloc); +void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc); void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc); void ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc); @@ -167,7 +177,7 @@ ngx_uint_t ngx_http_degraded(ngx_http_request_t *); #endif -#if (NGX_HTTP_V2) +#if (NGX_HTTP_V2 || NGX_HTTP_V3) ngx_int_t ngx_http_huff_decode(u_char *state, u_char *src, size_t len, u_char **dst, ngx_uint_t last, ngx_log_t *log); size_t ngx_http_huff_encode(u_char *src, size_t len, u_char *dst, diff --git a/src/deps/src/nginx/src/http/ngx_http_copy_filter_module.c b/src/deps/src/nginx/src/http/ngx_http_copy_filter_module.c index bd3028bc7..8e5de2bf7 100644 --- a/src/deps/src/nginx/src/http/ngx_http_copy_filter_module.c +++ b/src/deps/src/nginx/src/http/ngx_http_copy_filter_module.c @@ -170,6 +170,8 @@ ngx_http_copy_aio_handler(ngx_output_chain_ctx_t *ctx, ngx_file_t *file) file->aio->data = r; file->aio->handler = ngx_http_copy_aio_event_handler; + ngx_add_timer(&file->aio->event, 60000); + r->main->blocked++; r->aio = 1; ctx->aio = 1; @@ -192,12 +194,32 @@ ngx_http_copy_aio_event_handler(ngx_event_t *ev) ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http aio: \"%V?%V\"", &r->uri, &r->args); + if (ev->timedout) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "aio operation took too long"); + ev->timedout = 0; + return; + } + + if (ev->timer_set) { + ngx_del_timer(ev); + } + r->main->blocked--; r->aio = 0; - r->write_event_handler(r); + if (r->main->terminated) { + /* + * trigger connection event handler if the request was + * terminated + */ - ngx_http_run_posted_requests(c); + c->write->handler(c->write); + + } else { + r->write_event_handler(r); + ngx_http_run_posted_requests(c); + } } #endif @@ -264,6 +286,8 @@ ngx_http_copy_thread_handler(ngx_thread_task_t *task, ngx_file_t *file) return NGX_ERROR; } + ngx_add_timer(&task->event, 60000); + r->main->blocked++; r->aio = 1; @@ -288,6 +312,17 @@ ngx_http_copy_thread_event_handler(ngx_event_t *ev) ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http thread: \"%V?%V\"", &r->uri, &r->args); + if (ev->timedout) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "thread operation took too long"); + ev->timedout = 0; + return; + } + + if (ev->timer_set) { + ngx_del_timer(ev); + } + r->main->blocked--; r->aio = 0; @@ -305,11 +340,11 @@ ngx_http_copy_thread_event_handler(ngx_event_t *ev) #endif - if (r->done) { + if (r->done || r->main->terminated) { /* * trigger connection event handler if the subrequest was - * already finalized; this can happen if the handler is used - * for sendfile() in threads + * already finalized (this can happen if the handler is used + * for sendfile() in threads), or if the request was terminated */ c->write->handler(c->write); diff --git a/src/deps/src/nginx/src/http/ngx_http_core_module.c b/src/deps/src/nginx/src/http/ngx_http_core_module.c index 2140e0627..033a3bf64 100644 --- a/src/deps/src/nginx/src/http/ngx_http_core_module.c +++ b/src/deps/src/nginx/src/http/ngx_http_core_module.c @@ -3005,6 +3005,7 @@ ngx_http_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy) lsopt.socklen = sizeof(struct sockaddr_in); lsopt.backlog = NGX_LISTEN_BACKLOG; + lsopt.type = SOCK_STREAM; lsopt.rcvbuf = -1; lsopt.sndbuf = -1; #if (NGX_HAVE_SETFIB) @@ -3960,7 +3961,7 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_str_t *value, size; ngx_url_t u; - ngx_uint_t n, i; + ngx_uint_t n, i, backlog; ngx_http_listen_opt_t lsopt; cscf->listen = 1; @@ -3986,6 +3987,7 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t)); lsopt.backlog = NGX_LISTEN_BACKLOG; + lsopt.type = SOCK_STREAM; lsopt.rcvbuf = -1; lsopt.sndbuf = -1; #if (NGX_HAVE_SETFIB) @@ -3998,6 +4000,8 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) lsopt.ipv6only = 1; #endif + backlog = 0; + for (n = 2; n < cf->args->nelts; n++) { if (ngx_strcmp(value[n].data, "default_server") == 0 @@ -4056,6 +4060,8 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } + backlog = 1; + continue; } @@ -4174,6 +4180,11 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (ngx_strcmp(value[n].data, "http2") == 0) { #if (NGX_HTTP_V2) + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "the \"listen ... http2\" directive " + "is deprecated, use " + "the \"http2\" directive instead"); + lsopt.http2 = 1; continue; #else @@ -4184,6 +4195,19 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) #endif } + if (ngx_strcmp(value[n].data, "quic") == 0) { +#if (NGX_HTTP_V3) + lsopt.quic = 1; + lsopt.type = SOCK_DGRAM; + continue; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the \"quic\" parameter requires " + "ngx_http_v3_module"); + return NGX_CONF_ERROR; +#endif + } + if (ngx_strncmp(value[n].data, "so_keepalive=", 13) == 0) { if (ngx_strcmp(&value[n].data[13], "on") == 0) { @@ -4285,6 +4309,50 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } + if (lsopt.quic) { +#if (NGX_HAVE_TCP_FASTOPEN) + if (lsopt.fastopen != -1) { + return "\"fastopen\" parameter is incompatible with \"quic\""; + } +#endif + + if (backlog) { + return "\"backlog\" parameter is incompatible with \"quic\""; + } + +#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) + if (lsopt.accept_filter) { + return "\"accept_filter\" parameter is incompatible with \"quic\""; + } +#endif + +#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT) + if (lsopt.deferred_accept) { + return "\"deferred\" parameter is incompatible with \"quic\""; + } +#endif + +#if (NGX_HTTP_SSL) + if (lsopt.ssl) { + return "\"ssl\" parameter is incompatible with \"quic\""; + } +#endif + +#if (NGX_HTTP_V2) + if (lsopt.http2) { + return "\"http2\" parameter is incompatible with \"quic\""; + } +#endif + + if (lsopt.so_keepalive) { + return "\"so_keepalive\" parameter is incompatible with \"quic\""; + } + + if (lsopt.proxy_protocol) { + return "\"proxy_protocol\" parameter is incompatible with \"quic\""; + } + } + for (n = 0; n < u.naddrs; n++) { for (i = 0; i < n; i++) { diff --git a/src/deps/src/nginx/src/http/ngx_http_core_module.h b/src/deps/src/nginx/src/http/ngx_http_core_module.h index e41bc68d5..765e7ff60 100644 --- a/src/deps/src/nginx/src/http/ngx_http_core_module.h +++ b/src/deps/src/nginx/src/http/ngx_http_core_module.h @@ -75,6 +75,7 @@ typedef struct { unsigned wildcard:1; unsigned ssl:1; unsigned http2:1; + unsigned quic:1; #if (NGX_HAVE_INET6) unsigned ipv6only:1; #endif @@ -86,6 +87,7 @@ typedef struct { int backlog; int rcvbuf; int sndbuf; + int type; #if (NGX_HAVE_SETFIB) int setfib; #endif @@ -237,6 +239,7 @@ struct ngx_http_addr_conf_s { unsigned ssl:1; unsigned http2:1; + unsigned quic:1; unsigned proxy_protocol:1; }; @@ -266,6 +269,7 @@ typedef struct { typedef struct { ngx_int_t family; + ngx_int_t type; in_port_t port; ngx_array_t addrs; /* array of ngx_http_conf_addr_t */ } ngx_http_conf_port_t; diff --git a/src/deps/src/nginx/src/http/ngx_http_file_cache.c b/src/deps/src/nginx/src/http/ngx_http_file_cache.c index aa5fd1917..5209f003b 100644 --- a/src/deps/src/nginx/src/http/ngx_http_file_cache.c +++ b/src/deps/src/nginx/src/http/ngx_http_file_cache.c @@ -14,7 +14,7 @@ static ngx_int_t ngx_http_file_cache_lock(ngx_http_request_t *r, ngx_http_cache_t *c); static void ngx_http_file_cache_lock_wait_handler(ngx_event_t *ev); -static void ngx_http_file_cache_lock_wait(ngx_http_request_t *r, +static ngx_int_t ngx_http_file_cache_lock_wait(ngx_http_request_t *r, ngx_http_cache_t *c); static ngx_int_t ngx_http_file_cache_read(ngx_http_request_t *r, ngx_http_cache_t *c); @@ -463,6 +463,7 @@ ngx_http_file_cache_lock(ngx_http_request_t *r, ngx_http_cache_t *c) static void ngx_http_file_cache_lock_wait_handler(ngx_event_t *ev) { + ngx_int_t rc; ngx_connection_t *c; ngx_http_request_t *r; @@ -474,13 +475,31 @@ ngx_http_file_cache_lock_wait_handler(ngx_event_t *ev) ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http file cache wait: \"%V?%V\"", &r->uri, &r->args); - ngx_http_file_cache_lock_wait(r, r->cache); + rc = ngx_http_file_cache_lock_wait(r, r->cache); - ngx_http_run_posted_requests(c); + if (rc == NGX_AGAIN) { + return; + } + + r->cache->waiting = 0; + r->main->blocked--; + + if (r->main->terminated) { + /* + * trigger connection event handler if the request was + * terminated + */ + + c->write->handler(c->write); + + } else { + r->write_event_handler(r); + ngx_http_run_posted_requests(c); + } } -static void +static ngx_int_t ngx_http_file_cache_lock_wait(ngx_http_request_t *r, ngx_http_cache_t *c) { ngx_uint_t wait; @@ -495,7 +514,7 @@ ngx_http_file_cache_lock_wait(ngx_http_request_t *r, ngx_http_cache_t *c) ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "cache lock timeout"); c->lock_timeout = 0; - goto wakeup; + return NGX_OK; } cache = c->file_cache; @@ -513,14 +532,10 @@ ngx_http_file_cache_lock_wait(ngx_http_request_t *r, ngx_http_cache_t *c) if (wait) { ngx_add_timer(&c->wait_event, (timer > 500) ? 500 : timer); - return; + return NGX_AGAIN; } -wakeup: - - c->waiting = 0; - r->main->blocked--; - r->write_event_handler(r); + return NGX_OK; } @@ -690,6 +705,8 @@ ngx_http_file_cache_aio_read(ngx_http_request_t *r, ngx_http_cache_t *c) c->file.aio->data = r; c->file.aio->handler = ngx_http_cache_aio_event_handler; + ngx_add_timer(&c->file.aio->event, 60000); + r->main->blocked++; r->aio = 1; @@ -737,12 +754,32 @@ ngx_http_cache_aio_event_handler(ngx_event_t *ev) ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http file cache aio: \"%V?%V\"", &r->uri, &r->args); + if (ev->timedout) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "aio operation took too long"); + ev->timedout = 0; + return; + } + + if (ev->timer_set) { + ngx_del_timer(ev); + } + r->main->blocked--; r->aio = 0; - r->write_event_handler(r); + if (r->main->terminated) { + /* + * trigger connection event handler if the request was + * terminated + */ - ngx_http_run_posted_requests(c); + c->write->handler(c->write); + + } else { + r->write_event_handler(r); + ngx_http_run_posted_requests(c); + } } #endif @@ -786,6 +823,8 @@ ngx_http_cache_thread_handler(ngx_thread_task_t *task, ngx_file_t *file) return NGX_ERROR; } + ngx_add_timer(&task->event, 60000); + r->main->blocked++; r->aio = 1; @@ -807,12 +846,32 @@ ngx_http_cache_thread_event_handler(ngx_event_t *ev) ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http file cache thread: \"%V?%V\"", &r->uri, &r->args); + if (ev->timedout) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "thread operation took too long"); + ev->timedout = 0; + return; + } + + if (ev->timer_set) { + ngx_del_timer(ev); + } + r->main->blocked--; r->aio = 0; - r->write_event_handler(r); + if (r->main->terminated) { + /* + * trigger connection event handler if the request was + * terminated + */ - ngx_http_run_posted_requests(c); + c->write->handler(c->write); + + } else { + r->write_event_handler(r); + ngx_http_run_posted_requests(c); + } } #endif diff --git a/src/deps/src/nginx/src/http/ngx_http_huff_decode.c b/src/deps/src/nginx/src/http/ngx_http_huff_decode.c index 14b7b7896..a65c3c329 100644 --- a/src/deps/src/nginx/src/http/ngx_http_huff_decode.c +++ b/src/deps/src/nginx/src/http/ngx_http_huff_decode.c @@ -2657,7 +2657,7 @@ ngx_http_huff_decode(u_char *state, u_char *src, size_t len, u_char **dst, != NGX_OK) { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, - "http2 huffman decoding error at state %d: " + "http huffman decoding error at state %d: " "bad code 0x%Xd", *state, ch >> 4); return NGX_ERROR; @@ -2667,7 +2667,7 @@ ngx_http_huff_decode(u_char *state, u_char *src, size_t len, u_char **dst, != NGX_OK) { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, - "http2 huffman decoding error at state %d: " + "http huffman decoding error at state %d: " "bad code 0x%Xd", *state, ch & 0xf); return NGX_ERROR; @@ -2677,7 +2677,7 @@ ngx_http_huff_decode(u_char *state, u_char *src, size_t len, u_char **dst, if (last) { if (!ending) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, - "http2 huffman decoding error: " + "http huffman decoding error: " "incomplete code 0x%Xd", ch); return NGX_ERROR; diff --git a/src/deps/src/nginx/src/http/ngx_http_parse.c b/src/deps/src/nginx/src/http/ngx_http_parse.c index d4f2dae87..f7e50388f 100644 --- a/src/deps/src/nginx/src/http/ngx_http_parse.c +++ b/src/deps/src/nginx/src/http/ngx_http_parse.c @@ -451,19 +451,16 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) switch (ch) { case '/': - r->port_end = p; r->uri_start = p; state = sw_after_slash_in_uri; break; case '?': - r->port_end = p; r->uri_start = p; r->args_start = p + 1; r->empty_path_in_uri = 1; state = sw_uri; break; case ' ': - r->port_end = p; /* * use single "/" from request line to preserve pointers, * if request line will be copied to large client buffer diff --git a/src/deps/src/nginx/src/http/ngx_http_request.c b/src/deps/src/nginx/src/http/ngx_http_request.c index 5e0340b28..3cca57cf5 100644 --- a/src/deps/src/nginx/src/http/ngx_http_request.c +++ b/src/deps/src/nginx/src/http/ngx_http_request.c @@ -29,10 +29,6 @@ static ngx_int_t ngx_http_process_connection(ngx_http_request_t *r, static ngx_int_t ngx_http_process_user_agent(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); -static ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, - ngx_uint_t alloc); -static ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r, - ngx_str_t *host); static ngx_int_t ngx_http_find_virtual_server(ngx_connection_t *c, ngx_http_virtual_names_t *virtual_names, ngx_str_t *host, ngx_http_request_t *r, ngx_http_core_srv_conf_t **cscfp); @@ -50,7 +46,6 @@ static void ngx_http_keepalive_handler(ngx_event_t *ev); static void ngx_http_set_lingering_close(ngx_connection_t *c); static void ngx_http_lingering_close_handler(ngx_event_t *ev); static ngx_int_t ngx_http_post_action(ngx_http_request_t *r); -static void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t error); static void ngx_http_log_request(ngx_http_request_t *r); static u_char *ngx_http_log_error(ngx_log_t *log, u_char *buf, size_t len); @@ -323,24 +318,19 @@ ngx_http_init_connection(ngx_connection_t *c) rev->handler = ngx_http_wait_request_handler; c->write->handler = ngx_http_empty_handler; -#if (NGX_HTTP_V2) - if (hc->addr_conf->http2) { - rev->handler = ngx_http_v2_init; +#if (NGX_HTTP_V3) + if (hc->addr_conf->quic) { + ngx_http_v3_init_stream(c); + return; } #endif #if (NGX_HTTP_SSL) - { - ngx_http_ssl_srv_conf_t *sscf; - - sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module); - - if (sscf->enable || hc->addr_conf->ssl) { + if (hc->addr_conf->ssl) { hc->ssl = 1; c->log->action = "SSL handshaking"; rev->handler = ngx_http_ssl_handshake; } - } #endif if (hc->addr_conf->proxy_protocol) { @@ -381,6 +371,9 @@ ngx_http_wait_request_handler(ngx_event_t *rev) ngx_buf_t *b; ngx_connection_t *c; ngx_http_connection_t *hc; +#if (NGX_HTTP_V2) + ngx_http_v2_srv_conf_t *h2scf; +#endif ngx_http_core_srv_conf_t *cscf; c = rev->data; @@ -427,6 +420,8 @@ ngx_http_wait_request_handler(ngx_event_t *rev) b->end = b->last + size; } + size = b->end - b->last; + n = c->recv(c, b->last, size); if (n == NGX_AGAIN) { @@ -441,12 +436,16 @@ ngx_http_wait_request_handler(ngx_event_t *rev) return; } - /* - * We are trying to not hold c->buffer's memory for an idle connection. - */ + if (b->pos == b->last) { - if (ngx_pfree(c->pool, b->start) == NGX_OK) { - b->start = NULL; + /* + * We are trying to not hold c->buffer's memory for an + * idle connection. + */ + + if (ngx_pfree(c->pool, b->start) == NGX_OK) { + b->start = NULL; + } } return; @@ -487,6 +486,29 @@ ngx_http_wait_request_handler(ngx_event_t *rev) } } +#if (NGX_HTTP_V2) + + h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); + + if (!hc->ssl && (h2scf->enable || hc->addr_conf->http2)) { + + size = ngx_min(sizeof(NGX_HTTP_V2_PREFACE) - 1, + (size_t) (b->last - b->pos)); + + if (ngx_memcmp(b->pos, NGX_HTTP_V2_PREFACE, size) == 0) { + + if (size == sizeof(NGX_HTTP_V2_PREFACE) - 1) { + ngx_http_v2_init(rev); + return; + } + + ngx_post_event(rev, &ngx_posted_events); + return; + } + } + +#endif + c->log->action = "reading client request line"; ngx_reusable_connection(c, 0); @@ -806,13 +828,16 @@ ngx_http_ssl_handshake_handler(ngx_connection_t *c) #if (NGX_HTTP_V2 \ && defined TLSEXT_TYPE_application_layer_protocol_negotiation) { - unsigned int len; - const unsigned char *data; - ngx_http_connection_t *hc; + unsigned int len; + const unsigned char *data; + ngx_http_connection_t *hc; + ngx_http_v2_srv_conf_t *h2scf; hc = c->data; - if (hc->addr_conf->http2) { + h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); + + if (h2scf->enable || hc->addr_conf->http2) { SSL_get0_alpn_selected(c->ssl->connection, &data, &len); @@ -949,6 +974,14 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) #ifdef SSL_OP_NO_RENEGOTIATION SSL_set_options(ssl_conn, SSL_OP_NO_RENEGOTIATION); +#endif + +#ifdef SSL_OP_ENABLE_MIDDLEBOX_COMPAT +#if (NGX_HTTP_V3) + if (c->listening->quic) { + SSL_clear_options(ssl_conn, SSL_OP_ENABLE_MIDDLEBOX_COMPAT); + } +#endif #endif } @@ -1685,14 +1718,23 @@ ngx_http_alloc_large_header_buffer(ngx_http_request_t *r, r->request_end = new + (r->request_end - old); } - r->method_end = new + (r->method_end - old); + if (r->method_end) { + r->method_end = new + (r->method_end - old); + } - r->uri_start = new + (r->uri_start - old); - r->uri_end = new + (r->uri_end - old); + if (r->uri_start) { + r->uri_start = new + (r->uri_start - old); + } + + if (r->uri_end) { + r->uri_end = new + (r->uri_end - old); + } if (r->schema_start) { r->schema_start = new + (r->schema_start - old); - r->schema_end = new + (r->schema_end - old); + if (r->schema_end) { + r->schema_end = new + (r->schema_end - old); + } } if (r->host_start) { @@ -1702,11 +1744,6 @@ ngx_http_alloc_large_header_buffer(ngx_http_request_t *r, } } - if (r->port_start) { - r->port_start = new + (r->port_start - old); - r->port_end = new + (r->port_end - old); - } - if (r->uri_ext) { r->uri_ext = new + (r->uri_ext - old); } @@ -1721,9 +1758,18 @@ ngx_http_alloc_large_header_buffer(ngx_http_request_t *r, } else { r->header_name_start = new; - r->header_name_end = new + (r->header_name_end - old); - r->header_start = new + (r->header_start - old); - r->header_end = new + (r->header_end - old); + + if (r->header_name_end) { + r->header_name_end = new + (r->header_name_end - old); + } + + if (r->header_start) { + r->header_start = new + (r->header_start - old); + } + + if (r->header_end) { + r->header_end = new + (r->header_end - old); + } } r->header_in = b; @@ -2095,7 +2141,7 @@ ngx_http_process_request(ngx_http_request_t *r) } -static ngx_int_t +ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc) { u_char *h, ch; @@ -2187,7 +2233,7 @@ ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc) } -static ngx_int_t +ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r, ngx_str_t *host) { ngx_int_t rc; @@ -2648,6 +2694,8 @@ ngx_http_terminate_request(ngx_http_request_t *r, ngx_int_t rc) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http terminate request count:%d", mr->count); + mr->terminated = 1; + if (rc > 0 && (mr->headers_out.status == 0 || mr->connection->sent == 0)) { mr->headers_out.status = rc; } @@ -2670,8 +2718,11 @@ ngx_http_terminate_request(ngx_http_request_t *r, ngx_int_t rc) if (mr->write_event_handler) { if (mr->blocked) { + r = r->connection->data; + r->connection->error = 1; r->write_event_handler = ngx_http_request_finalizer; + return; } @@ -2710,6 +2761,13 @@ ngx_http_finalize_connection(ngx_http_request_t *r) } #endif +#if (NGX_HTTP_V3) + if (r->connection->quic) { + ngx_http_close_request(r, 0); + return; + } +#endif + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (r->main->count != 1) { @@ -2925,6 +2983,20 @@ ngx_http_test_reading(ngx_http_request_t *r) #endif +#if (NGX_HTTP_V3) + + if (c->quic) { + if (rev->error) { + c->error = 1; + err = 0; + goto closed; + } + + return; + } + +#endif + #if (NGX_HAVE_KQUEUE) if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { @@ -3590,7 +3662,7 @@ ngx_http_post_action(ngx_http_request_t *r) } -static void +void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc) { ngx_connection_t *c; @@ -3677,7 +3749,12 @@ ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc) log->action = "closing request"; - if (r->connection->timedout) { + if (r->connection->timedout +#if (NGX_HTTP_V3) + && r->connection->quic == NULL +#endif + ) + { clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (clcf->reset_timedout_connection) { @@ -3750,6 +3827,12 @@ ngx_http_close_connection(ngx_connection_t *c) #endif +#if (NGX_HTTP_V3) + if (c->quic) { + ngx_http_v3_reset_stream(c); + } +#endif + #if (NGX_STAT_STUB) (void) ngx_atomic_fetch_add(ngx_stat_active, -1); #endif diff --git a/src/deps/src/nginx/src/http/ngx_http_request.h b/src/deps/src/nginx/src/http/ngx_http_request.h index 8c9eed249..65c8333f8 100644 --- a/src/deps/src/nginx/src/http/ngx_http_request.h +++ b/src/deps/src/nginx/src/http/ngx_http_request.h @@ -24,6 +24,7 @@ #define NGX_HTTP_VERSION_10 1000 #define NGX_HTTP_VERSION_11 1001 #define NGX_HTTP_VERSION_20 2000 +#define NGX_HTTP_VERSION_30 3000 #define NGX_HTTP_UNKNOWN 0x00000001 #define NGX_HTTP_GET 0x00000002 @@ -451,6 +452,7 @@ struct ngx_http_request_s { ngx_http_connection_t *http_connection; ngx_http_v2_stream_t *stream; + ngx_http_v3_parse_t *v3_parse; ngx_http_log_handler_pt log_handler; @@ -543,10 +545,12 @@ struct ngx_http_request_s { unsigned request_complete:1; unsigned request_output:1; unsigned header_sent:1; + unsigned response_sent:1; unsigned expect_tested:1; unsigned root_tested:1; unsigned done:1; unsigned logged:1; + unsigned terminated:1; unsigned buffered:4; @@ -594,8 +598,6 @@ struct ngx_http_request_s { u_char *schema_end; u_char *host_start; u_char *host_end; - u_char *port_start; - u_char *port_end; unsigned http_minor:16; unsigned http_major:16; diff --git a/src/deps/src/nginx/src/http/ngx_http_request_body.c b/src/deps/src/nginx/src/http/ngx_http_request_body.c index ad3549f98..afb042395 100644 --- a/src/deps/src/nginx/src/http/ngx_http_request_body.c +++ b/src/deps/src/nginx/src/http/ngx_http_request_body.c @@ -92,6 +92,13 @@ ngx_http_read_client_request_body(ngx_http_request_t *r, } #endif +#if (NGX_HTTP_V3) + if (r->http_version == NGX_HTTP_VERSION_30) { + rc = ngx_http_v3_read_request_body(r); + goto done; + } +#endif + preread = r->header_in->last - r->header_in->pos; if (preread) { @@ -238,6 +245,18 @@ ngx_http_read_unbuffered_request_body(ngx_http_request_t *r) } #endif +#if (NGX_HTTP_V3) + if (r->http_version == NGX_HTTP_VERSION_30) { + rc = ngx_http_v3_read_unbuffered_request_body(r); + + if (rc == NGX_OK) { + r->reading_body = 0; + } + + return rc; + } +#endif + if (r->connection->read->timedout) { r->connection->timedout = 1; return NGX_HTTP_REQUEST_TIME_OUT; @@ -625,6 +644,12 @@ ngx_http_discard_request_body(ngx_http_request_t *r) } #endif +#if (NGX_HTTP_V3) + if (r->http_version == NGX_HTTP_VERSION_30) { + return NGX_OK; + } +#endif + if (ngx_http_test_expect(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } @@ -920,6 +945,9 @@ ngx_http_test_expect(ngx_http_request_t *r) || r->http_version < NGX_HTTP_VERSION_11 #if (NGX_HTTP_V2) || r->stream != NULL +#endif +#if (NGX_HTTP_V3) + || r->connection->quic != NULL #endif ) { diff --git a/src/deps/src/nginx/src/http/ngx_http_upstream.c b/src/deps/src/nginx/src/http/ngx_http_upstream.c index 3ae822bb8..2ce9f2114 100644 --- a/src/deps/src/nginx/src/http/ngx_http_upstream.c +++ b/src/deps/src/nginx/src/http/ngx_http_upstream.c @@ -521,6 +521,13 @@ ngx_http_upstream_init(ngx_http_request_t *r) } #endif +#if (NGX_HTTP_V3) + if (c->quic) { + ngx_http_upstream_init_request(r); + return; + } +#endif + if (c->read->timer_set) { ngx_del_timer(c->read); } @@ -1354,6 +1361,19 @@ ngx_http_upstream_check_broken_connection(ngx_http_request_t *r, } #endif +#if (NGX_HTTP_V3) + + if (c->quic) { + if (c->write->error) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_CLIENT_CLOSED_REQUEST); + } + + return; + } + +#endif + #if (NGX_HAVE_KQUEUE) if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { @@ -3929,6 +3949,8 @@ ngx_http_upstream_thread_handler(ngx_thread_task_t *task, ngx_file_t *file) r->aio = 1; p->aio = 1; + ngx_add_timer(&task->event, 60000); + return NGX_OK; } @@ -3947,6 +3969,17 @@ ngx_http_upstream_thread_event_handler(ngx_event_t *ev) ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream thread: \"%V?%V\"", &r->uri, &r->args); + if (ev->timedout) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "thread operation took too long"); + ev->timedout = 0; + return; + } + + if (ev->timer_set) { + ngx_del_timer(ev); + } + r->main->blocked--; r->aio = 0; @@ -3964,11 +3997,11 @@ ngx_http_upstream_thread_event_handler(ngx_event_t *ev) #endif - if (r->done) { + if (r->done || r->main->terminated) { /* * trigger connection event handler if the subrequest was - * already finalized; this can happen if the handler is used - * for sendfile() in threads + * already finalized (this can happen if the handler is used + * for sendfile() in threads), or if the request was terminated */ c->write->handler(c->write); @@ -4541,6 +4574,10 @@ ngx_http_upstream_finalize_request(ngx_http_request_t *r, u->peer.connection = NULL; + if (u->pipe) { + u->pipe->upstream = NULL; + } + if (u->pipe && u->pipe->temp_file) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http upstream temp fd: %d", diff --git a/src/deps/src/nginx/src/http/ngx_http_variables.c b/src/deps/src/nginx/src/http/ngx_http_variables.c index 16ffda3fe..4f0bd0e4b 100644 --- a/src/deps/src/nginx/src/http/ngx_http_variables.c +++ b/src/deps/src/nginx/src/http/ngx_http_variables.c @@ -828,7 +828,7 @@ ngx_http_variable_headers_internal(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data, u_char sep) { size_t len; - u_char *p; + u_char *p, *end; ngx_table_elt_t *h, *th; h = *(ngx_table_elt_t **) ((char *) r + data); @@ -870,6 +870,8 @@ ngx_http_variable_headers_internal(ngx_http_request_t *r, v->len = len; v->data = p; + end = p + len; + for (th = h; th; th = th->next) { if (th->hash == 0) { @@ -878,7 +880,7 @@ ngx_http_variable_headers_internal(ngx_http_request_t *r, p = ngx_copy(p, th->value.data, th->value.len); - if (th->next == NULL) { + if (p == end) { break; } diff --git a/src/deps/src/nginx/src/http/ngx_http_write_filter_module.c b/src/deps/src/nginx/src/http/ngx_http_write_filter_module.c index 89ab49e12..9188ee948 100644 --- a/src/deps/src/nginx/src/http/ngx_http_write_filter_module.c +++ b/src/deps/src/nginx/src/http/ngx_http_write_filter_module.c @@ -240,6 +240,10 @@ ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in) r->out = NULL; c->buffered &= ~NGX_HTTP_WRITE_BUFFERED; + if (last) { + r->response_sent = 1; + } + return NGX_OK; } @@ -346,6 +350,10 @@ ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in) c->buffered &= ~NGX_HTTP_WRITE_BUFFERED; + if (last) { + r->response_sent = 1; + } + if ((c->buffered & NGX_LOWLEVEL_BUFFERED) && r->postponed == NULL) { return NGX_AGAIN; } diff --git a/src/deps/src/nginx/src/http/v2/ngx_http_v2.c b/src/deps/src/nginx/src/http/v2/ngx_http_v2.c index ea3f27c07..0f5bd3de8 100644 --- a/src/deps/src/nginx/src/http/v2/ngx_http_v2.c +++ b/src/deps/src/nginx/src/http/v2/ngx_http_v2.c @@ -11,14 +11,6 @@ #include -typedef struct { - ngx_str_t name; - ngx_uint_t offset; - ngx_uint_t hash; - ngx_http_header_t *hh; -} ngx_http_v2_parse_header_t; - - /* errors */ #define NGX_HTTP_V2_NO_ERROR 0x0 #define NGX_HTTP_V2_PROTOCOL_ERROR 0x1 @@ -63,8 +55,6 @@ static void ngx_http_v2_handle_connection(ngx_http_v2_connection_t *h2c); static void ngx_http_v2_lingering_close(ngx_connection_t *c); static void ngx_http_v2_lingering_close_handler(ngx_event_t *rev); -static u_char *ngx_http_v2_state_proxy_protocol(ngx_http_v2_connection_t *h2c, - u_char *pos, u_char *end); static u_char *ngx_http_v2_state_preface(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_preface_end(ngx_http_v2_connection_t *h2c, @@ -128,7 +118,7 @@ static ngx_int_t ngx_http_v2_parse_int(ngx_http_v2_connection_t *h2c, u_char **pos, u_char *end, ngx_uint_t prefix); static ngx_http_v2_stream_t *ngx_http_v2_create_stream( - ngx_http_v2_connection_t *h2c, ngx_uint_t push); + ngx_http_v2_connection_t *h2c); static ngx_http_v2_node_t *ngx_http_v2_get_node_by_id( ngx_http_v2_connection_t *h2c, ngx_uint_t sid, ngx_uint_t alloc); static ngx_http_v2_node_t *ngx_http_v2_get_closed_node( @@ -164,14 +154,11 @@ static ngx_int_t ngx_http_v2_parse_scheme(ngx_http_request_t *r, ngx_str_t *value); static ngx_int_t ngx_http_v2_parse_authority(ngx_http_request_t *r, ngx_str_t *value); -static ngx_int_t ngx_http_v2_parse_header(ngx_http_request_t *r, - ngx_http_v2_parse_header_t *header, ngx_str_t *value); static ngx_int_t ngx_http_v2_construct_request_line(ngx_http_request_t *r); static ngx_int_t ngx_http_v2_cookie(ngx_http_request_t *r, ngx_http_v2_header_t *header); static ngx_int_t ngx_http_v2_construct_cookie_header(ngx_http_request_t *r); static void ngx_http_v2_run_request(ngx_http_request_t *r); -static void ngx_http_v2_run_request_handler(ngx_event_t *ev); static ngx_int_t ngx_http_v2_process_request_body(ngx_http_request_t *r, u_char *pos, size_t size, ngx_uint_t last, ngx_uint_t flush); static ngx_int_t ngx_http_v2_filter_request_body(ngx_http_request_t *r); @@ -212,26 +199,10 @@ static ngx_http_v2_handler_pt ngx_http_v2_frame_states[] = { (sizeof(ngx_http_v2_frame_states) / sizeof(ngx_http_v2_handler_pt)) -static ngx_http_v2_parse_header_t ngx_http_v2_parse_headers[] = { - { ngx_string("host"), - offsetof(ngx_http_headers_in_t, host), 0, NULL }, - - { ngx_string("accept-encoding"), - offsetof(ngx_http_headers_in_t, accept_encoding), 0, NULL }, - - { ngx_string("accept-language"), - offsetof(ngx_http_headers_in_t, accept_language), 0, NULL }, - - { ngx_string("user-agent"), - offsetof(ngx_http_headers_in_t, user_agent), 0, NULL }, - - { ngx_null_string, 0, 0, NULL } -}; - - void ngx_http_v2_init(ngx_event_t *rev) { + u_char *p, *end; ngx_connection_t *c; ngx_pool_cleanup_t *cln; ngx_http_connection_t *hc; @@ -276,7 +247,6 @@ ngx_http_v2_init(ngx_event_t *rev) h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); - h2c->concurrent_pushes = h2scf->concurrent_pushes; h2c->priority_limit = ngx_max(h2scf->concurrent_streams, 100); h2c->pool = ngx_create_pool(h2scf->pool_size, h2c->connection->log); @@ -314,8 +284,7 @@ ngx_http_v2_init(ngx_event_t *rev) return; } - h2c->state.handler = hc->proxy_protocol ? ngx_http_v2_state_proxy_protocol - : ngx_http_v2_state_preface; + h2c->state.handler = ngx_http_v2_state_preface; ngx_queue_init(&h2c->waiting); ngx_queue_init(&h2c->dependencies); @@ -335,6 +304,23 @@ ngx_http_v2_init(ngx_event_t *rev) c->idle = 1; ngx_reusable_connection(c, 0); + if (c->buffer) { + p = c->buffer->pos; + end = c->buffer->last; + + do { + p = h2c->state.handler(h2c, p, end); + + if (p == NULL) { + return; + } + + } while (p != end); + + h2c->total_bytes += p - c->buffer->pos; + c->buffer->pos = p; + } + ngx_http_v2_read_handler(rev); } @@ -361,6 +347,7 @@ ngx_http_v2_read_handler(ngx_event_t *rev) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http2 read handler"); h2c->blocked = 1; + h2c->new_streams = 0; if (c->close) { c->close = 0; @@ -370,7 +357,7 @@ ngx_http_v2_read_handler(ngx_event_t *rev) return; } - if (!h2c->processing && !h2c->pushing) { + if (!h2c->processing) { ngx_http_v2_finalize_connection(h2c, NGX_HTTP_V2_NO_ERROR); return; } @@ -399,13 +386,11 @@ ngx_http_v2_read_handler(ngx_event_t *rev) h2mcf = ngx_http_get_module_main_conf(h2c->http_connection->conf_ctx, ngx_http_v2_module); - available = h2mcf->recv_buffer_size - 2 * NGX_HTTP_V2_STATE_BUFFER_SIZE; + available = h2mcf->recv_buffer_size - NGX_HTTP_V2_STATE_BUFFER_SIZE; do { p = h2mcf->recv_buffer; - - ngx_memcpy(p, h2c->state.buffer, NGX_HTTP_V2_STATE_BUFFER_SIZE); - end = p + h2c->state.buffer_used; + end = ngx_cpymem(p, h2c->state.buffer, h2c->state.buffer_used); n = c->recv(c, end, available); @@ -413,9 +398,7 @@ ngx_http_v2_read_handler(ngx_event_t *rev) break; } - if (n == 0 - && (h2c->state.incomplete || h2c->processing || h2c->pushing)) - { + if (n == 0 && (h2c->state.incomplete || h2c->processing)) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "client prematurely closed connection"); } @@ -638,7 +621,7 @@ ngx_http_v2_handle_connection(ngx_http_v2_connection_t *h2c) ngx_connection_t *c; ngx_http_core_loc_conf_t *clcf; - if (h2c->last_out || h2c->processing || h2c->pushing) { + if (h2c->last_out || h2c->processing) { return; } @@ -846,32 +829,11 @@ ngx_http_v2_lingering_close_handler(ngx_event_t *rev) } -static u_char * -ngx_http_v2_state_proxy_protocol(ngx_http_v2_connection_t *h2c, u_char *pos, - u_char *end) -{ - ngx_log_t *log; - - log = h2c->connection->log; - log->action = "reading PROXY protocol"; - - pos = ngx_proxy_protocol_read(h2c->connection, pos, end); - - log->action = "processing HTTP/2 connection"; - - if (pos == NULL) { - return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); - } - - return ngx_http_v2_state_preface(h2c, pos, end); -} - - static u_char * ngx_http_v2_state_preface(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) { - static const u_char preface[] = "PRI * HTTP/2.0\r\n"; + static const u_char preface[] = NGX_HTTP_V2_PREFACE_START; if ((size_t) (end - pos) < sizeof(preface) - 1) { return ngx_http_v2_state_save(h2c, pos, end, ngx_http_v2_state_preface); @@ -892,7 +854,7 @@ static u_char * ngx_http_v2_state_preface_end(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) { - static const u_char preface[] = "\r\nSM\r\n\r\n"; + static const u_char preface[] = NGX_HTTP_V2_PREFACE_END; if ((size_t) (end - pos) < sizeof(preface) - 1) { return ngx_http_v2_state_save(h2c, pos, end, @@ -1321,6 +1283,14 @@ ngx_http_v2_state_headers(ngx_http_v2_connection_t *h2c, u_char *pos, goto rst_stream; } + if (h2c->new_streams++ >= 2 * h2scf->concurrent_streams) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent too many streams at once"); + + status = NGX_HTTP_V2_REFUSED_STREAM; + goto rst_stream; + } + if (!h2c->settings_ack && !(h2c->state.flags & NGX_HTTP_V2_END_STREAM_FLAG) && h2scf->preread_size < NGX_HTTP_V2_DEFAULT_WINDOW) @@ -1344,7 +1314,7 @@ ngx_http_v2_state_headers(ngx_http_v2_connection_t *h2c, u_char *pos, h2c->closed_nodes--; } - stream = ngx_http_v2_create_stream(h2c, 0); + stream = ngx_http_v2_create_stream(h2c); if (stream == NULL) { return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); } @@ -1386,6 +1356,12 @@ ngx_http_v2_state_headers(ngx_http_v2_connection_t *h2c, u_char *pos, rst_stream: + if (h2c->refused_streams++ > ngx_max(h2scf->concurrent_streams, 100)) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent too many refused streams"); + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_NO_ERROR); + } + if (ngx_http_v2_send_rst_stream(h2c, h2c->state.sid, status) != NGX_OK) { return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); } @@ -2133,11 +2109,6 @@ ngx_http_v2_state_rst_stream(ngx_http_v2_connection_t *h2c, u_char *pos, "client canceled stream %ui", h2c->state.sid); break; - case NGX_HTTP_V2_REFUSED_STREAM: - ngx_log_error(NGX_LOG_INFO, fc->log, 0, - "client refused stream %ui", h2c->state.sid); - break; - case NGX_HTTP_V2_INTERNAL_ERROR: ngx_log_error(NGX_LOG_INFO, fc->log, 0, "client terminated stream %ui due to internal error", @@ -2205,7 +2176,6 @@ ngx_http_v2_state_settings_params(ngx_http_v2_connection_t *h2c, u_char *pos, { ssize_t window_delta; ngx_uint_t id, value; - ngx_http_v2_srv_conf_t *h2scf; ngx_http_v2_out_frame_t *frame; window_delta = 0; @@ -2267,14 +2237,6 @@ ngx_http_v2_state_settings_params(ngx_http_v2_connection_t *h2c, u_char *pos, NGX_HTTP_V2_PROTOCOL_ERROR); } - h2c->push_disabled = !value; - break; - - case NGX_HTTP_V2_MAX_STREAMS_SETTING: - h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, - ngx_http_v2_module); - - h2c->concurrent_pushes = ngx_min(value, h2scf->concurrent_pushes); break; case NGX_HTTP_V2_HEADER_TABLE_SIZE_SETTING: @@ -2628,7 +2590,7 @@ ngx_http_v2_state_save(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end, return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); } - ngx_memcpy(h2c->state.buffer, pos, NGX_HTTP_V2_STATE_BUFFER_SIZE); + ngx_memcpy(h2c->state.buffer, pos, size); h2c->state.buffer_used = size; h2c->state.handler = handler; @@ -2729,163 +2691,6 @@ ngx_http_v2_parse_int(ngx_http_v2_connection_t *h2c, u_char **pos, u_char *end, } -ngx_http_v2_stream_t * -ngx_http_v2_push_stream(ngx_http_v2_stream_t *parent, ngx_str_t *path) -{ - ngx_int_t rc; - ngx_str_t value; - ngx_pool_t *pool; - ngx_uint_t index; - ngx_table_elt_t **h; - ngx_connection_t *fc; - ngx_http_request_t *r; - ngx_http_v2_node_t *node; - ngx_http_v2_stream_t *stream; - ngx_http_v2_srv_conf_t *h2scf; - ngx_http_v2_connection_t *h2c; - ngx_http_v2_parse_header_t *header; - - h2c = parent->connection; - - pool = ngx_create_pool(1024, h2c->connection->log); - if (pool == NULL) { - goto rst_stream; - } - - node = ngx_http_v2_get_node_by_id(h2c, h2c->last_push, 1); - - if (node == NULL) { - ngx_destroy_pool(pool); - goto rst_stream; - } - - stream = ngx_http_v2_create_stream(h2c, 1); - if (stream == NULL) { - - if (node->parent == NULL) { - h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, - ngx_http_v2_module); - - index = ngx_http_v2_index(h2scf, h2c->last_push); - h2c->streams_index[index] = node->index; - - ngx_queue_insert_tail(&h2c->closed, &node->reuse); - h2c->closed_nodes++; - } - - ngx_destroy_pool(pool); - goto rst_stream; - } - - if (node->parent) { - ngx_queue_remove(&node->reuse); - h2c->closed_nodes--; - } - - stream->pool = pool; - - r = stream->request; - fc = r->connection; - - stream->in_closed = 1; - stream->node = node; - - node->stream = stream; - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, - "http2 push stream sid:%ui " - "depends on %ui excl:0 weight:16", - h2c->last_push, parent->node->id); - - node->weight = NGX_HTTP_V2_DEFAULT_WEIGHT; - ngx_http_v2_set_dependency(h2c, node, parent->node->id, 0); - - r->method_name = ngx_http_core_get_method; - r->method = NGX_HTTP_GET; - - r->schema.data = ngx_pstrdup(pool, &parent->request->schema); - if (r->schema.data == NULL) { - goto close; - } - - r->schema.len = parent->request->schema.len; - - value.data = ngx_pstrdup(pool, path); - if (value.data == NULL) { - goto close; - } - - value.len = path->len; - - rc = ngx_http_v2_parse_path(r, &value); - - if (rc != NGX_OK) { - goto error; - } - - for (header = ngx_http_v2_parse_headers; header->name.len; header++) { - h = (ngx_table_elt_t **) - ((char *) &parent->request->headers_in + header->offset); - - if (*h == NULL) { - continue; - } - - value.len = (*h)->value.len; - - value.data = ngx_pnalloc(pool, value.len + 1); - if (value.data == NULL) { - goto close; - } - - ngx_memcpy(value.data, (*h)->value.data, value.len); - value.data[value.len] = '\0'; - - rc = ngx_http_v2_parse_header(r, header, &value); - - if (rc != NGX_OK) { - goto error; - } - } - - fc->write->handler = ngx_http_v2_run_request_handler; - ngx_post_event(fc->write, &ngx_posted_events); - - return stream; - -error: - - if (rc == NGX_ABORT) { - /* header handler has already finalized request */ - ngx_http_run_posted_requests(fc); - return NULL; - } - - if (rc == NGX_DECLINED) { - ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); - ngx_http_run_posted_requests(fc); - return NULL; - } - -close: - - ngx_http_v2_close_stream(stream, NGX_HTTP_INTERNAL_SERVER_ERROR); - - return NULL; - -rst_stream: - - if (ngx_http_v2_send_rst_stream(h2c, h2c->last_push, - NGX_HTTP_INTERNAL_SERVER_ERROR) - != NGX_OK) - { - h2c->connection->error = 1; - } - - return NULL; -} - - static ngx_int_t ngx_http_v2_send_settings(ngx_http_v2_connection_t *h2c) { @@ -3157,7 +2962,7 @@ ngx_http_v2_frame_handler(ngx_http_v2_connection_t *h2c, static ngx_http_v2_stream_t * -ngx_http_v2_create_stream(ngx_http_v2_connection_t *h2c, ngx_uint_t push) +ngx_http_v2_create_stream(ngx_http_v2_connection_t *h2c) { ngx_log_t *log; ngx_event_t *rev, *wev; @@ -3212,13 +3017,7 @@ ngx_http_v2_create_stream(ngx_http_v2_connection_t *h2c, ngx_uint_t push) ngx_memcpy(log, h2c->connection->log, sizeof(ngx_log_t)); log->data = ctx; - - if (push) { - log->action = "processing pushed request headers"; - - } else { - log->action = "reading client request headers"; - } + log->action = "reading client request headers"; ngx_memzero(rev, sizeof(ngx_event_t)); @@ -3290,12 +3089,7 @@ ngx_http_v2_create_stream(ngx_http_v2_connection_t *h2c, ngx_uint_t push) stream->send_window = h2c->init_window; stream->recv_window = h2scf->preread_size; - if (push) { - h2c->pushing++; - - } else { - h2c->processing++; - } + h2c->processing++; h2c->priority_limit += h2scf->concurrent_streams; @@ -3717,46 +3511,42 @@ ngx_http_v2_parse_scheme(ngx_http_request_t *r, ngx_str_t *value) static ngx_int_t ngx_http_v2_parse_authority(ngx_http_request_t *r, ngx_str_t *value) -{ - return ngx_http_v2_parse_header(r, &ngx_http_v2_parse_headers[0], value); -} - - -static ngx_int_t -ngx_http_v2_parse_header(ngx_http_request_t *r, - ngx_http_v2_parse_header_t *header, ngx_str_t *value) { ngx_table_elt_t *h; + ngx_http_header_t *hh; ngx_http_core_main_conf_t *cmcf; + static ngx_str_t host = ngx_string("host"); + h = ngx_list_push(&r->headers_in.headers); if (h == NULL) { return NGX_ERROR; } - h->key.len = header->name.len; - h->key.data = header->name.data; - h->lowcase_key = header->name.data; + h->hash = ngx_hash(ngx_hash(ngx_hash('h', 'o'), 's'), 't'); - if (header->hh == NULL) { - header->hash = ngx_hash_key(header->name.data, header->name.len); - - cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); - - header->hh = ngx_hash_find(&cmcf->headers_in_hash, header->hash, - h->lowcase_key, h->key.len); - if (header->hh == NULL) { - return NGX_ERROR; - } - } - - h->hash = header->hash; + h->key.len = host.len; + h->key.data = host.data; h->value.len = value->len; h->value.data = value->data; - if (header->hh->handler(r, h, header->hh->offset) != NGX_OK) { - /* header handler has already finalized request */ + h->lowcase_key = host.data; + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh == NULL) { + return NGX_ERROR; + } + + if (hh->handler(r, h, hh->offset) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_host() + */ return NGX_ABORT; } @@ -3943,10 +3733,22 @@ static void ngx_http_v2_run_request(ngx_http_request_t *r) { ngx_connection_t *fc; + ngx_http_v2_srv_conf_t *h2scf; ngx_http_v2_connection_t *h2c; fc = r->connection; + h2scf = ngx_http_get_module_srv_conf(r, ngx_http_v2_module); + + if (!h2scf->enable && !r->http_connection->addr_conf->http2) { + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client attempted to request the server name " + "for which the negotiated protocol is disabled"); + + ngx_http_finalize_request(r, NGX_HTTP_MISDIRECTED_REQUEST); + goto failed; + } + if (ngx_http_v2_construct_request_line(r) != NGX_OK) { goto failed; } @@ -3987,22 +3789,6 @@ failed: } -static void -ngx_http_v2_run_request_handler(ngx_event_t *ev) -{ - ngx_connection_t *fc; - ngx_http_request_t *r; - - fc = ev->data; - r = fc->data; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 run request handler"); - - ngx_http_v2_run_request(r); -} - - ngx_int_t ngx_http_v2_read_request_body(ngx_http_request_t *r) { @@ -4606,7 +4392,6 @@ void ngx_http_v2_close_stream(ngx_http_v2_stream_t *stream, ngx_int_t rc) { ngx_pool_t *pool; - ngx_uint_t push; ngx_event_t *ev; ngx_connection_t *fc; ngx_http_v2_node_t *node; @@ -4615,10 +4400,9 @@ ngx_http_v2_close_stream(ngx_http_v2_stream_t *stream, ngx_int_t rc) h2c = stream->connection; node = stream->node; - ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, - "http2 close stream %ui, queued %ui, " - "processing %ui, pushing %ui", - node->id, stream->queued, h2c->processing, h2c->pushing); + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 close stream %ui, queued %ui, processing %ui", + node->id, stream->queued, h2c->processing); fc = stream->request->connection; @@ -4653,8 +4437,6 @@ ngx_http_v2_close_stream(ngx_http_v2_stream_t *stream, ngx_int_t rc) h2c->state.stream = NULL; } - push = stream->node->id % 2 == 0; - node->stream = NULL; ngx_queue_insert_tail(&h2c->closed, &node->reuse); @@ -4704,14 +4486,9 @@ ngx_http_v2_close_stream(ngx_http_v2_stream_t *stream, ngx_int_t rc) fc->data = h2c->free_fake_connections; h2c->free_fake_connections = fc; - if (push) { - h2c->pushing--; + h2c->processing--; - } else { - h2c->processing--; - } - - if (h2c->processing || h2c->pushing || h2c->blocked) { + if (h2c->processing || h2c->blocked) { return; } @@ -4887,7 +4664,7 @@ ngx_http_v2_finalize_connection(ngx_http_v2_connection_t *h2c, } } - if (!h2c->processing && !h2c->pushing) { + if (!h2c->processing) { goto done; } @@ -4935,7 +4712,7 @@ ngx_http_v2_finalize_connection(ngx_http_v2_connection_t *h2c, h2c->blocked = 0; - if (h2c->processing || h2c->pushing) { + if (h2c->processing) { c->error = 1; return; } diff --git a/src/deps/src/nginx/src/http/v2/ngx_http_v2.h b/src/deps/src/nginx/src/http/v2/ngx_http_v2.h index 4e252931c..6751b3026 100644 --- a/src/deps/src/nginx/src/http/v2/ngx_http_v2.h +++ b/src/deps/src/nginx/src/http/v2/ngx_http_v2.h @@ -24,8 +24,6 @@ #define NGX_HTTP_V2_MAX_FIELD \ (127 + (1 << (NGX_HTTP_V2_INT_OCTETS - 1) * 7) - 1) -#define NGX_HTTP_V2_STREAM_ID_SIZE 4 - #define NGX_HTTP_V2_FRAME_HEADER_SIZE 9 /* frame types */ @@ -63,6 +61,15 @@ typedef u_char *(*ngx_http_v2_handler_pt) (ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); +typedef struct { + ngx_flag_t enable; + size_t pool_size; + ngx_uint_t concurrent_streams; + size_t preread_size; + ngx_uint_t streams_index_mask; +} ngx_http_v2_srv_conf_t; + + typedef struct { ngx_str_t name; ngx_str_t value; @@ -124,11 +131,10 @@ struct ngx_http_v2_connection_s { ngx_uint_t processing; ngx_uint_t frames; ngx_uint_t idle; + ngx_uint_t new_streams; + ngx_uint_t refused_streams; ngx_uint_t priority_limit; - ngx_uint_t pushing; - ngx_uint_t concurrent_pushes; - size_t send_window; size_t recv_window; size_t init_window; @@ -155,7 +161,6 @@ struct ngx_http_v2_connection_s { ngx_uint_t closed_nodes; ngx_uint_t last_sid; - ngx_uint_t last_push; time_t lingering_time; @@ -163,7 +168,6 @@ struct ngx_http_v2_connection_s { unsigned table_update:1; unsigned blocked:1; unsigned goaway:1; - unsigned push_disabled:1; }; @@ -293,9 +297,6 @@ void ngx_http_v2_init(ngx_event_t *rev); ngx_int_t ngx_http_v2_read_request_body(ngx_http_request_t *r); ngx_int_t ngx_http_v2_read_unbuffered_request_body(ngx_http_request_t *r); -ngx_http_v2_stream_t *ngx_http_v2_push_stream(ngx_http_v2_stream_t *parent, - ngx_str_t *path); - void ngx_http_v2_close_stream(ngx_http_v2_stream_t *stream, ngx_int_t rc); ngx_int_t ngx_http_v2_send_output_queue(ngx_http_v2_connection_t *h2c); @@ -397,20 +398,25 @@ ngx_int_t ngx_http_v2_table_size(ngx_http_v2_connection_t *h2c, size_t size); #define NGX_HTTP_V2_STATUS_404_INDEX 13 #define NGX_HTTP_V2_STATUS_500_INDEX 14 -#define NGX_HTTP_V2_ACCEPT_ENCODING_INDEX 16 -#define NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX 17 #define NGX_HTTP_V2_CONTENT_LENGTH_INDEX 28 #define NGX_HTTP_V2_CONTENT_TYPE_INDEX 31 #define NGX_HTTP_V2_DATE_INDEX 33 #define NGX_HTTP_V2_LAST_MODIFIED_INDEX 44 #define NGX_HTTP_V2_LOCATION_INDEX 46 #define NGX_HTTP_V2_SERVER_INDEX 54 -#define NGX_HTTP_V2_USER_AGENT_INDEX 58 #define NGX_HTTP_V2_VARY_INDEX 59 +#define NGX_HTTP_V2_PREFACE_START "PRI * HTTP/2.0\r\n" +#define NGX_HTTP_V2_PREFACE_END "\r\nSM\r\n\r\n" +#define NGX_HTTP_V2_PREFACE NGX_HTTP_V2_PREFACE_START \ + NGX_HTTP_V2_PREFACE_END + u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, u_char *tmp, ngx_uint_t lower); +extern ngx_module_t ngx_http_v2_module; + + #endif /* _NGX_HTTP_V2_H_INCLUDED_ */ diff --git a/src/deps/src/nginx/src/http/v2/ngx_http_v2_filter_module.c b/src/deps/src/nginx/src/http/v2/ngx_http_v2_filter_module.c index 5f8626a40..1e2cafaf1 100644 --- a/src/deps/src/nginx/src/http/v2/ngx_http_v2_filter_module.c +++ b/src/deps/src/nginx/src/http/v2/ngx_http_v2_filter_module.c @@ -27,39 +27,8 @@ #define NGX_HTTP_V2_NO_TRAILERS (ngx_http_v2_out_frame_t *) -1 -typedef struct { - ngx_str_t name; - u_char index; - ngx_uint_t offset; -} ngx_http_v2_push_header_t; - - -static ngx_http_v2_push_header_t ngx_http_v2_push_headers[] = { - { ngx_string(":authority"), NGX_HTTP_V2_AUTHORITY_INDEX, - offsetof(ngx_http_headers_in_t, host) }, - - { ngx_string("accept-encoding"), NGX_HTTP_V2_ACCEPT_ENCODING_INDEX, - offsetof(ngx_http_headers_in_t, accept_encoding) }, - - { ngx_string("accept-language"), NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX, - offsetof(ngx_http_headers_in_t, accept_language) }, - - { ngx_string("user-agent"), NGX_HTTP_V2_USER_AGENT_INDEX, - offsetof(ngx_http_headers_in_t, user_agent) }, -}; - -#define NGX_HTTP_V2_PUSH_HEADERS \ - (sizeof(ngx_http_v2_push_headers) / sizeof(ngx_http_v2_push_header_t)) - - -static ngx_int_t ngx_http_v2_push_resources(ngx_http_request_t *r); -static ngx_int_t ngx_http_v2_push_resource(ngx_http_request_t *r, - ngx_str_t *path, ngx_str_t *binary); - static ngx_http_v2_out_frame_t *ngx_http_v2_create_headers_frame( ngx_http_request_t *r, u_char *pos, u_char *end, ngx_uint_t fin); -static ngx_http_v2_out_frame_t *ngx_http_v2_create_push_frame( - ngx_http_request_t *r, u_char *pos, u_char *end); static ngx_http_v2_out_frame_t *ngx_http_v2_create_trailers_frame( ngx_http_request_t *r); @@ -82,8 +51,6 @@ static ngx_inline ngx_int_t ngx_http_v2_filter_send( static ngx_int_t ngx_http_v2_headers_frame_handler( ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame); -static ngx_int_t ngx_http_v2_push_frame_handler( - ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame); static ngx_int_t ngx_http_v2_data_frame_handler( ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame); static ngx_inline void ngx_http_v2_handle_frame( @@ -244,15 +211,6 @@ ngx_http_v2_header_filter(ngx_http_request_t *r) h2c = stream->connection; - if (!h2c->push_disabled && !h2c->goaway - && stream->node->id % 2 == 1 - && r->method != NGX_HTTP_HEAD) - { - if (ngx_http_v2_push_resources(r) != NGX_OK) { - return NGX_ERROR; - } - } - len = h2c->table_update ? 1 : 0; len += status ? 1 : 1 + ngx_http_v2_literal_size("418"); @@ -653,7 +611,7 @@ ngx_http_v2_header_filter(ngx_http_request_t *r) ngx_http_v2_queue_blocked_frame(h2c, frame); - stream->queued++; + stream->queued = 1; cln = ngx_http_cleanup_add(r, 0); if (cln == NULL) { @@ -671,409 +629,6 @@ ngx_http_v2_header_filter(ngx_http_request_t *r) } -static ngx_int_t -ngx_http_v2_push_resources(ngx_http_request_t *r) -{ - u_char *start, *end, *last; - ngx_int_t rc; - ngx_str_t path; - ngx_uint_t i, push; - ngx_table_elt_t *h; - ngx_http_v2_loc_conf_t *h2lcf; - ngx_http_complex_value_t *pushes; - ngx_str_t binary[NGX_HTTP_V2_PUSH_HEADERS]; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http2 push resources"); - - ngx_memzero(binary, NGX_HTTP_V2_PUSH_HEADERS * sizeof(ngx_str_t)); - - h2lcf = ngx_http_get_module_loc_conf(r, ngx_http_v2_module); - - if (h2lcf->pushes) { - pushes = h2lcf->pushes->elts; - - for (i = 0; i < h2lcf->pushes->nelts; i++) { - - if (ngx_http_complex_value(r, &pushes[i], &path) != NGX_OK) { - return NGX_ERROR; - } - - if (path.len == 0) { - continue; - } - - if (path.len == 3 && ngx_strncmp(path.data, "off", 3) == 0) { - continue; - } - - rc = ngx_http_v2_push_resource(r, &path, binary); - - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - - if (rc == NGX_ABORT) { - return NGX_OK; - } - - /* NGX_OK, NGX_DECLINED */ - } - } - - if (!h2lcf->push_preload) { - return NGX_OK; - } - - for (h = r->headers_out.link; h; h = h->next) { - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http2 parse link: \"%V\"", &h->value); - - start = h->value.data; - end = h->value.data + h->value.len; - - next_link: - - while (start < end && *start == ' ') { start++; } - - if (start == end || *start++ != '<') { - continue; - } - - while (start < end && *start == ' ') { start++; } - - for (last = start; last < end && *last != '>'; last++) { - /* void */ - } - - if (last == start || last == end) { - continue; - } - - path.len = last - start; - path.data = start; - - start = last + 1; - - while (start < end && *start == ' ') { start++; } - - if (start == end) { - continue; - } - - if (*start == ',') { - start++; - goto next_link; - } - - if (*start++ != ';') { - continue; - } - - last = ngx_strlchr(start, end, ','); - - if (last == NULL) { - last = end; - } - - push = 0; - - for ( ;; ) { - - while (start < last && *start == ' ') { start++; } - - if (last - start >= 6 - && ngx_strncasecmp(start, (u_char *) "nopush", 6) == 0) - { - start += 6; - - if (start == last || *start == ' ' || *start == ';') { - push = 0; - break; - } - - goto next_param; - } - - if (last - start >= 11 - && ngx_strncasecmp(start, (u_char *) "rel=preload", 11) == 0) - { - start += 11; - - if (start == last || *start == ' ' || *start == ';') { - push = 1; - } - - goto next_param; - } - - if (last - start >= 4 - && ngx_strncasecmp(start, (u_char *) "rel=", 4) == 0) - { - start += 4; - - while (start < last && *start == ' ') { start++; } - - if (start == last || *start++ != '"') { - goto next_param; - } - - for ( ;; ) { - - while (start < last && *start == ' ') { start++; } - - if (last - start >= 7 - && ngx_strncasecmp(start, (u_char *) "preload", 7) == 0) - { - start += 7; - - if (start < last && (*start == ' ' || *start == '"')) { - push = 1; - break; - } - } - - while (start < last && *start != ' ' && *start != '"') { - start++; - } - - if (start == last) { - break; - } - - if (*start == '"') { - break; - } - - start++; - } - } - - next_param: - - start = ngx_strlchr(start, last, ';'); - - if (start == NULL) { - break; - } - - start++; - } - - if (push) { - while (path.len && path.data[path.len - 1] == ' ') { - path.len--; - } - } - - if (push && path.len - && !(path.len > 1 && path.data[0] == '/' && path.data[1] == '/')) - { - rc = ngx_http_v2_push_resource(r, &path, binary); - - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - - if (rc == NGX_ABORT) { - return NGX_OK; - } - - /* NGX_OK, NGX_DECLINED */ - } - - if (last < end) { - start = last + 1; - goto next_link; - } - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path, - ngx_str_t *binary) -{ - u_char *start, *pos, *tmp; - size_t len; - ngx_str_t *value; - ngx_uint_t i; - ngx_table_elt_t **h; - ngx_connection_t *fc; - ngx_http_v2_stream_t *stream; - ngx_http_v2_out_frame_t *frame; - ngx_http_v2_connection_t *h2c; - ngx_http_v2_push_header_t *ph; - - fc = r->connection; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push resource"); - - stream = r->stream; - h2c = stream->connection; - - if (!ngx_path_separator(path->data[0])) { - ngx_log_error(NGX_LOG_WARN, fc->log, 0, - "non-absolute path \"%V\" not pushed", path); - return NGX_DECLINED; - } - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, - "http2 pushing:%ui limit:%ui", - h2c->pushing, h2c->concurrent_pushes); - - if (h2c->pushing >= h2c->concurrent_pushes) { - return NGX_ABORT; - } - - if (h2c->last_push == 0x7ffffffe) { - return NGX_ABORT; - } - - if (path->len > NGX_HTTP_V2_MAX_FIELD) { - return NGX_DECLINED; - } - - if (r->headers_in.host == NULL) { - return NGX_ABORT; - } - - ph = ngx_http_v2_push_headers; - - len = ngx_max(r->schema.len, path->len); - - if (binary[0].len) { - tmp = ngx_palloc(r->pool, len); - if (tmp == NULL) { - return NGX_ERROR; - } - - } else { - for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) { - h = (ngx_table_elt_t **) ((char *) &r->headers_in + ph[i].offset); - - if (*h) { - len = ngx_max(len, (*h)->value.len); - } - } - - tmp = ngx_palloc(r->pool, len); - if (tmp == NULL) { - return NGX_ERROR; - } - - for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) { - h = (ngx_table_elt_t **) ((char *) &r->headers_in + ph[i].offset); - - if (*h == NULL) { - continue; - } - - value = &(*h)->value; - - len = 1 + NGX_HTTP_V2_INT_OCTETS + value->len; - - pos = ngx_pnalloc(r->pool, len); - if (pos == NULL) { - return NGX_ERROR; - } - - binary[i].data = pos; - - *pos++ = ngx_http_v2_inc_indexed(ph[i].index); - pos = ngx_http_v2_write_value(pos, value->data, value->len, tmp); - - binary[i].len = pos - binary[i].data; - } - } - - len = (h2c->table_update ? 1 : 0) - + 1 - + 1 + NGX_HTTP_V2_INT_OCTETS + path->len - + 1 + NGX_HTTP_V2_INT_OCTETS + r->schema.len; - - for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) { - len += binary[i].len; - } - - pos = ngx_pnalloc(r->pool, len); - if (pos == NULL) { - return NGX_ERROR; - } - - start = pos; - - if (h2c->table_update) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 table size update: 0"); - *pos++ = (1 << 5) | 0; - h2c->table_update = 0; - } - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 push header: \":method: GET\""); - - *pos++ = ngx_http_v2_indexed(NGX_HTTP_V2_METHOD_GET_INDEX); - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 push header: \":path: %V\"", path); - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX); - pos = ngx_http_v2_write_value(pos, path->data, path->len, tmp); - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 push header: \":scheme: %V\"", &r->schema); - - if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { - *pos++ = ngx_http_v2_indexed(NGX_HTTP_V2_SCHEME_HTTPS_INDEX); - - } else if (r->schema.len == 4 - && ngx_strncmp(r->schema.data, "http", 4) == 0) - { - *pos++ = ngx_http_v2_indexed(NGX_HTTP_V2_SCHEME_HTTP_INDEX); - - } else { - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_SCHEME_HTTP_INDEX); - pos = ngx_http_v2_write_value(pos, r->schema.data, r->schema.len, tmp); - } - - for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) { - h = (ngx_table_elt_t **) ((char *) &r->headers_in + ph[i].offset); - - if (*h == NULL) { - continue; - } - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 push header: \"%V: %V\"", - &ph[i].name, &(*h)->value); - - pos = ngx_cpymem(pos, binary[i].data, binary[i].len); - } - - frame = ngx_http_v2_create_push_frame(r, start, pos); - if (frame == NULL) { - return NGX_ERROR; - } - - ngx_http_v2_queue_blocked_frame(h2c, frame); - - stream->queued++; - - stream = ngx_http_v2_push_stream(stream, path); - - if (stream) { - stream->request->request_length = pos - start; - return NGX_OK; - } - - return NGX_ERROR; -} - - static ngx_http_v2_out_frame_t * ngx_http_v2_create_headers_frame(ngx_http_request_t *r, u_char *pos, u_char *end, ngx_uint_t fin) @@ -1179,125 +734,6 @@ ngx_http_v2_create_headers_frame(ngx_http_request_t *r, u_char *pos, } -static ngx_http_v2_out_frame_t * -ngx_http_v2_create_push_frame(ngx_http_request_t *r, u_char *pos, u_char *end) -{ - u_char type, flags; - size_t rest, frame_size, len; - ngx_buf_t *b; - ngx_chain_t *cl, **ll; - ngx_http_v2_stream_t *stream; - ngx_http_v2_out_frame_t *frame; - ngx_http_v2_connection_t *h2c; - - stream = r->stream; - h2c = stream->connection; - rest = NGX_HTTP_V2_STREAM_ID_SIZE + (end - pos); - - frame = ngx_palloc(r->pool, sizeof(ngx_http_v2_out_frame_t)); - if (frame == NULL) { - return NULL; - } - - frame->handler = ngx_http_v2_push_frame_handler; - frame->stream = stream; - frame->length = rest; - frame->blocked = 1; - frame->fin = 0; - - ll = &frame->first; - - type = NGX_HTTP_V2_PUSH_PROMISE_FRAME; - flags = NGX_HTTP_V2_NO_FLAG; - frame_size = h2c->frame_size; - - for ( ;; ) { - if (rest <= frame_size) { - frame_size = rest; - flags |= NGX_HTTP_V2_END_HEADERS_FLAG; - } - - b = ngx_create_temp_buf(r->pool, - NGX_HTTP_V2_FRAME_HEADER_SIZE - + ((type == NGX_HTTP_V2_PUSH_PROMISE_FRAME) - ? NGX_HTTP_V2_STREAM_ID_SIZE : 0)); - if (b == NULL) { - return NULL; - } - - b->last = ngx_http_v2_write_len_and_type(b->last, frame_size, type); - *b->last++ = flags; - b->last = ngx_http_v2_write_sid(b->last, stream->node->id); - - b->tag = (ngx_buf_tag_t) &ngx_http_v2_module; - - if (type == NGX_HTTP_V2_PUSH_PROMISE_FRAME) { - h2c->last_push += 2; - - b->last = ngx_http_v2_write_sid(b->last, h2c->last_push); - len = frame_size - NGX_HTTP_V2_STREAM_ID_SIZE; - - } else { - len = frame_size; - } - - cl = ngx_alloc_chain_link(r->pool); - if (cl == NULL) { - return NULL; - } - - cl->buf = b; - - *ll = cl; - ll = &cl->next; - - b = ngx_calloc_buf(r->pool); - if (b == NULL) { - return NULL; - } - - b->pos = pos; - - pos += len; - - b->last = pos; - b->start = b->pos; - b->end = b->last; - b->temporary = 1; - - cl = ngx_alloc_chain_link(r->pool); - if (cl == NULL) { - return NULL; - } - - cl->buf = b; - - *ll = cl; - ll = &cl->next; - - rest -= frame_size; - - if (rest) { - frame->length += NGX_HTTP_V2_FRAME_HEADER_SIZE; - - type = NGX_HTTP_V2_CONTINUATION_FRAME; - continue; - } - - cl->next = NULL; - frame->last = cl; - - ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http2:%ui create PUSH_PROMISE frame %p: " - "sid:%ui len:%uz", - stream->node->id, frame, h2c->last_push, - frame->length); - - return frame; - } -} - - static ngx_http_v2_out_frame_t * ngx_http_v2_create_trailers_frame(ngx_http_request_t *r) { @@ -1901,62 +1337,6 @@ ngx_http_v2_headers_frame_handler(ngx_http_v2_connection_t *h2c, } -static ngx_int_t -ngx_http_v2_push_frame_handler(ngx_http_v2_connection_t *h2c, - ngx_http_v2_out_frame_t *frame) -{ - ngx_chain_t *cl, *ln; - ngx_http_v2_stream_t *stream; - - stream = frame->stream; - cl = frame->first; - - for ( ;; ) { - if (cl->buf->pos != cl->buf->last) { - frame->first = cl; - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, - "http2:%ui PUSH_PROMISE frame %p was sent partially", - stream->node->id, frame); - - return NGX_AGAIN; - } - - ln = cl->next; - - if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_module) { - cl->next = stream->free_frame_headers; - stream->free_frame_headers = cl; - - } else { - cl->next = stream->free_bufs; - stream->free_bufs = cl; - } - - if (cl == frame->last) { - break; - } - - cl = ln; - } - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, - "http2:%ui PUSH_PROMISE frame %p was sent", - stream->node->id, frame); - - stream->request->header_size += NGX_HTTP_V2_FRAME_HEADER_SIZE - + frame->length; - - h2c->payload_bytes += frame->length; - - ngx_http_v2_handle_frame(stream, frame); - - ngx_http_v2_handle_stream(h2c, stream); - - return NGX_OK; -} - - static ngx_int_t ngx_http_v2_data_frame_handler(ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame) diff --git a/src/deps/src/nginx/src/http/v2/ngx_http_v2_module.c b/src/deps/src/nginx/src/http/v2/ngx_http_v2_module.c index 005088611..d64488c20 100644 --- a/src/deps/src/nginx/src/http/v2/ngx_http_v2_module.c +++ b/src/deps/src/nginx/src/http/v2/ngx_http_v2_module.c @@ -27,8 +27,6 @@ static void *ngx_http_v2_create_loc_conf(ngx_conf_t *cf); static char *ngx_http_v2_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); -static char *ngx_http_v2_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); - static char *ngx_http_v2_recv_buffer_size(ngx_conf_t *cf, void *post, void *data); static char *ngx_http_v2_pool_size(ngx_conf_t *cf, void *post, void *data); @@ -75,6 +73,13 @@ static ngx_conf_post_t ngx_http_v2_chunk_size_post = static ngx_command_t ngx_http_v2_commands[] = { + { ngx_string("http2"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v2_srv_conf_t, enable), + NULL }, + { ngx_string("http2_recv_buffer_size"), NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, @@ -98,9 +103,9 @@ static ngx_command_t ngx_http_v2_commands[] = { { ngx_string("http2_max_concurrent_pushes"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v2_srv_conf_t, concurrent_pushes), + ngx_http_v2_obsolete, + 0, + 0, NULL }, { ngx_string("http2_max_requests"), @@ -161,15 +166,15 @@ static ngx_command_t ngx_http_v2_commands[] = { { ngx_string("http2_push_preload"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, - ngx_conf_set_flag_slot, - NGX_HTTP_LOC_CONF_OFFSET, - offsetof(ngx_http_v2_loc_conf_t, push_preload), + ngx_http_v2_obsolete, + 0, + 0, NULL }, { ngx_string("http2_push"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, - ngx_http_v2_push, - NGX_HTTP_LOC_CONF_OFFSET, + ngx_http_v2_obsolete, + 0, 0, NULL }, @@ -314,10 +319,11 @@ ngx_http_v2_create_srv_conf(ngx_conf_t *cf) return NULL; } + h2scf->enable = NGX_CONF_UNSET; + h2scf->pool_size = NGX_CONF_UNSET_SIZE; h2scf->concurrent_streams = NGX_CONF_UNSET_UINT; - h2scf->concurrent_pushes = NGX_CONF_UNSET_UINT; h2scf->preread_size = NGX_CONF_UNSET_SIZE; @@ -333,12 +339,12 @@ ngx_http_v2_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_http_v2_srv_conf_t *prev = parent; ngx_http_v2_srv_conf_t *conf = child; + ngx_conf_merge_value(conf->enable, prev->enable, 0); + ngx_conf_merge_size_value(conf->pool_size, prev->pool_size, 4096); ngx_conf_merge_uint_value(conf->concurrent_streams, prev->concurrent_streams, 128); - ngx_conf_merge_uint_value(conf->concurrent_pushes, - prev->concurrent_pushes, 10); ngx_conf_merge_size_value(conf->preread_size, prev->preread_size, 65536); @@ -359,17 +365,8 @@ ngx_http_v2_create_loc_conf(ngx_conf_t *cf) return NULL; } - /* - * set by ngx_pcalloc(): - * - * h2lcf->pushes = NULL; - */ - h2lcf->chunk_size = NGX_CONF_UNSET_SIZE; - h2lcf->push_preload = NGX_CONF_UNSET; - h2lcf->push = NGX_CONF_UNSET; - return h2lcf; } @@ -382,72 +379,6 @@ ngx_http_v2_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_size_value(conf->chunk_size, prev->chunk_size, 8 * 1024); - ngx_conf_merge_value(conf->push, prev->push, 1); - - if (conf->push && conf->pushes == NULL) { - conf->pushes = prev->pushes; - } - - ngx_conf_merge_value(conf->push_preload, prev->push_preload, 0); - - return NGX_CONF_OK; -} - - -static char * -ngx_http_v2_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) -{ - ngx_http_v2_loc_conf_t *h2lcf = conf; - - ngx_str_t *value; - ngx_http_complex_value_t *cv; - ngx_http_compile_complex_value_t ccv; - - value = cf->args->elts; - - if (ngx_strcmp(value[1].data, "off") == 0) { - - if (h2lcf->pushes) { - return "\"off\" parameter cannot be used with URI"; - } - - if (h2lcf->push == 0) { - return "is duplicate"; - } - - h2lcf->push = 0; - return NGX_CONF_OK; - } - - if (h2lcf->push == 0) { - return "URI cannot be used with \"off\" parameter"; - } - - h2lcf->push = 1; - - if (h2lcf->pushes == NULL) { - h2lcf->pushes = ngx_array_create(cf->pool, 1, - sizeof(ngx_http_complex_value_t)); - if (h2lcf->pushes == NULL) { - return NGX_CONF_ERROR; - } - } - - cv = ngx_array_push(h2lcf->pushes); - if (cv == NULL) { - return NGX_CONF_ERROR; - } - - ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); - - ccv.cf = cf; - ccv.value = &value[1]; - ccv.complex_value = cv; - - if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { - return NGX_CONF_ERROR; - } - return NGX_CONF_OK; } @@ -457,7 +388,7 @@ ngx_http_v2_recv_buffer_size(ngx_conf_t *cf, void *post, void *data) { size_t *sp = data; - if (*sp <= 2 * NGX_HTTP_V2_STATE_BUFFER_SIZE) { + if (*sp <= NGX_HTTP_V2_STATE_BUFFER_SIZE) { return "value is too small"; } @@ -551,10 +482,17 @@ ngx_http_v2_obsolete(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_conf_deprecated_t *d = cmd->post; - ngx_conf_log_error(NGX_LOG_WARN, cf, 0, - "the \"%s\" directive is obsolete, " - "use the \"%s\" directive instead", - d->old_name, d->new_name); + if (d) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "the \"%s\" directive is obsolete, " + "use the \"%s\" directive instead", + d->old_name, d->new_name); + + } else { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "the \"%V\" directive is obsolete, ignored", + &cmd->name); + } return NGX_CONF_OK; } diff --git a/src/deps/src/nginx/src/http/v2/ngx_http_v2_module.h b/src/deps/src/nginx/src/http/v2/ngx_http_v2_module.h index ca4a0bfc5..07a595cfd 100644 --- a/src/deps/src/nginx/src/http/v2/ngx_http_v2_module.h +++ b/src/deps/src/nginx/src/http/v2/ngx_http_v2_module.h @@ -20,26 +20,9 @@ typedef struct { } ngx_http_v2_main_conf_t; -typedef struct { - size_t pool_size; - ngx_uint_t concurrent_streams; - ngx_uint_t concurrent_pushes; - size_t preread_size; - ngx_uint_t streams_index_mask; -} ngx_http_v2_srv_conf_t; - - typedef struct { size_t chunk_size; - - ngx_flag_t push_preload; - - ngx_flag_t push; - ngx_array_t *pushes; } ngx_http_v2_loc_conf_t; -extern ngx_module_t ngx_http_v2_module; - - #endif /* _NGX_HTTP_V2_MODULE_H_INCLUDED_ */ diff --git a/src/deps/src/nginx/src/http/v3/ngx_http_v3.c b/src/deps/src/nginx/src/http/v3/ngx_http_v3.c new file mode 100644 index 000000000..8db229b29 --- /dev/null +++ b/src/deps/src/nginx/src/http/v3/ngx_http_v3.c @@ -0,0 +1,111 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +static void ngx_http_v3_keepalive_handler(ngx_event_t *ev); +static void ngx_http_v3_cleanup_session(void *data); + + +ngx_int_t +ngx_http_v3_init_session(ngx_connection_t *c) +{ + ngx_pool_cleanup_t *cln; + ngx_http_connection_t *hc; + ngx_http_v3_session_t *h3c; + + hc = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init session"); + + h3c = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_session_t)); + if (h3c == NULL) { + goto failed; + } + + h3c->http_connection = hc; + + ngx_queue_init(&h3c->blocked); + + h3c->keepalive.log = c->log; + h3c->keepalive.data = c; + h3c->keepalive.handler = ngx_http_v3_keepalive_handler; + + h3c->table.send_insert_count.log = c->log; + h3c->table.send_insert_count.data = c; + h3c->table.send_insert_count.handler = ngx_http_v3_inc_insert_count_handler; + + cln = ngx_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + goto failed; + } + + cln->handler = ngx_http_v3_cleanup_session; + cln->data = h3c; + + c->data = h3c; + + return NGX_OK; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create http3 session"); + return NGX_ERROR; +} + + +static void +ngx_http_v3_keepalive_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + + c = ev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 keepalive handler"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, + "keepalive timeout"); +} + + +static void +ngx_http_v3_cleanup_session(void *data) +{ + ngx_http_v3_session_t *h3c = data; + + ngx_http_v3_cleanup_table(h3c); + + if (h3c->keepalive.timer_set) { + ngx_del_timer(&h3c->keepalive); + } + + if (h3c->table.send_insert_count.posted) { + ngx_delete_posted_event(&h3c->table.send_insert_count); + } +} + + +ngx_int_t +ngx_http_v3_check_flood(ngx_connection_t *c) +{ + ngx_http_v3_session_t *h3c; + + h3c = ngx_http_v3_get_session(c); + + if (h3c->total_bytes / 8 > h3c->payload_bytes + 1048576) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "http3 flood detected"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, + "HTTP/3 flood detected"); + return NGX_ERROR; + } + + return NGX_OK; +} diff --git a/src/deps/src/nginx/src/http/v3/ngx_http_v3.h b/src/deps/src/nginx/src/http/v3/ngx_http_v3.h new file mode 100644 index 000000000..9dcb5e6a7 --- /dev/null +++ b/src/deps/src/nginx/src/http/v3/ngx_http_v3.h @@ -0,0 +1,160 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_V3_H_INCLUDED_ +#define _NGX_HTTP_V3_H_INCLUDED_ + + +#include +#include +#include + +#include +#include +#include +#include + + +#define NGX_HTTP_V3_ALPN_PROTO "\x02h3" +#define NGX_HTTP_V3_HQ_ALPN_PROTO "\x0Ahq-interop" +#define NGX_HTTP_V3_HQ_PROTO "hq-interop" + +#define NGX_HTTP_V3_VARLEN_INT_LEN 4 +#define NGX_HTTP_V3_PREFIX_INT_LEN 11 + +#define NGX_HTTP_V3_STREAM_CONTROL 0x00 +#define NGX_HTTP_V3_STREAM_PUSH 0x01 +#define NGX_HTTP_V3_STREAM_ENCODER 0x02 +#define NGX_HTTP_V3_STREAM_DECODER 0x03 + +#define NGX_HTTP_V3_FRAME_DATA 0x00 +#define NGX_HTTP_V3_FRAME_HEADERS 0x01 +#define NGX_HTTP_V3_FRAME_CANCEL_PUSH 0x03 +#define NGX_HTTP_V3_FRAME_SETTINGS 0x04 +#define NGX_HTTP_V3_FRAME_PUSH_PROMISE 0x05 +#define NGX_HTTP_V3_FRAME_GOAWAY 0x07 +#define NGX_HTTP_V3_FRAME_MAX_PUSH_ID 0x0d + +#define NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY 0x01 +#define NGX_HTTP_V3_PARAM_MAX_FIELD_SECTION_SIZE 0x06 +#define NGX_HTTP_V3_PARAM_BLOCKED_STREAMS 0x07 + +#define NGX_HTTP_V3_MAX_TABLE_CAPACITY 4096 + +#define NGX_HTTP_V3_STREAM_CLIENT_CONTROL 0 +#define NGX_HTTP_V3_STREAM_SERVER_CONTROL 1 +#define NGX_HTTP_V3_STREAM_CLIENT_ENCODER 2 +#define NGX_HTTP_V3_STREAM_SERVER_ENCODER 3 +#define NGX_HTTP_V3_STREAM_CLIENT_DECODER 4 +#define NGX_HTTP_V3_STREAM_SERVER_DECODER 5 +#define NGX_HTTP_V3_MAX_KNOWN_STREAM 6 +#define NGX_HTTP_V3_MAX_UNI_STREAMS 3 + +/* HTTP/3 errors */ +#define NGX_HTTP_V3_ERR_NO_ERROR 0x100 +#define NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR 0x101 +#define NGX_HTTP_V3_ERR_INTERNAL_ERROR 0x102 +#define NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR 0x103 +#define NGX_HTTP_V3_ERR_CLOSED_CRITICAL_STREAM 0x104 +#define NGX_HTTP_V3_ERR_FRAME_UNEXPECTED 0x105 +#define NGX_HTTP_V3_ERR_FRAME_ERROR 0x106 +#define NGX_HTTP_V3_ERR_EXCESSIVE_LOAD 0x107 +#define NGX_HTTP_V3_ERR_ID_ERROR 0x108 +#define NGX_HTTP_V3_ERR_SETTINGS_ERROR 0x109 +#define NGX_HTTP_V3_ERR_MISSING_SETTINGS 0x10a +#define NGX_HTTP_V3_ERR_REQUEST_REJECTED 0x10b +#define NGX_HTTP_V3_ERR_REQUEST_CANCELLED 0x10c +#define NGX_HTTP_V3_ERR_REQUEST_INCOMPLETE 0x10d +#define NGX_HTTP_V3_ERR_CONNECT_ERROR 0x10f +#define NGX_HTTP_V3_ERR_VERSION_FALLBACK 0x110 + +/* QPACK errors */ +#define NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED 0x200 +#define NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR 0x201 +#define NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR 0x202 + + +#define ngx_http_v3_get_session(c) \ + ((ngx_http_v3_session_t *) ((c)->quic ? (c)->quic->parent->data \ + : (c)->data)) + +#define ngx_http_quic_get_connection(c) \ + (ngx_http_v3_get_session(c)->http_connection) + +#define ngx_http_v3_get_module_loc_conf(c, module) \ + ngx_http_get_module_loc_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ + module) + +#define ngx_http_v3_get_module_srv_conf(c, module) \ + ngx_http_get_module_srv_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ + module) + +#define ngx_http_v3_finalize_connection(c, code, reason) \ + ngx_quic_finalize_connection((c)->quic ? (c)->quic->parent : (c), \ + code, reason) + +#define ngx_http_v3_shutdown_connection(c, code, reason) \ + ngx_quic_shutdown_connection((c)->quic ? (c)->quic->parent : (c), \ + code, reason) + + +typedef struct { + ngx_flag_t enable; + ngx_flag_t enable_hq; + size_t max_table_capacity; + ngx_uint_t max_blocked_streams; + ngx_uint_t max_concurrent_streams; + ngx_quic_conf_t quic; +} ngx_http_v3_srv_conf_t; + + +struct ngx_http_v3_parse_s { + size_t header_limit; + ngx_http_v3_parse_headers_t headers; + ngx_http_v3_parse_data_t body; + ngx_array_t *cookies; +}; + + +struct ngx_http_v3_session_s { + ngx_http_connection_t *http_connection; + + ngx_http_v3_dynamic_table_t table; + + ngx_event_t keepalive; + ngx_uint_t nrequests; + + ngx_queue_t blocked; + ngx_uint_t nblocked; + + uint64_t next_request_id; + + off_t total_bytes; + off_t payload_bytes; + + unsigned goaway:1; + unsigned hq:1; + + ngx_connection_t *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM]; +}; + + +void ngx_http_v3_init_stream(ngx_connection_t *c); +void ngx_http_v3_reset_stream(ngx_connection_t *c); +ngx_int_t ngx_http_v3_init_session(ngx_connection_t *c); +ngx_int_t ngx_http_v3_check_flood(ngx_connection_t *c); +ngx_int_t ngx_http_v3_init(ngx_connection_t *c); +void ngx_http_v3_shutdown(ngx_connection_t *c); + +ngx_int_t ngx_http_v3_read_request_body(ngx_http_request_t *r); +ngx_int_t ngx_http_v3_read_unbuffered_request_body(ngx_http_request_t *r); + + +extern ngx_module_t ngx_http_v3_module; + + +#endif /* _NGX_HTTP_V3_H_INCLUDED_ */ diff --git a/src/deps/src/nginx/src/http/v3/ngx_http_v3_encode.c b/src/deps/src/nginx/src/http/v3/ngx_http_v3_encode.c new file mode 100644 index 000000000..fb089c413 --- /dev/null +++ b/src/deps/src/nginx/src/http/v3/ngx_http_v3_encode.c @@ -0,0 +1,304 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +uintptr_t +ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value) +{ + if (value <= 63) { + if (p == NULL) { + return 1; + } + + *p++ = value; + return (uintptr_t) p; + } + + if (value <= 16383) { + if (p == NULL) { + return 2; + } + + *p++ = 0x40 | (value >> 8); + *p++ = value; + return (uintptr_t) p; + } + + if (value <= 1073741823) { + if (p == NULL) { + return 4; + } + + *p++ = 0x80 | (value >> 24); + *p++ = (value >> 16); + *p++ = (value >> 8); + *p++ = value; + return (uintptr_t) p; + } + + if (p == NULL) { + return 8; + } + + *p++ = 0xc0 | (value >> 56); + *p++ = (value >> 48); + *p++ = (value >> 40); + *p++ = (value >> 32); + *p++ = (value >> 24); + *p++ = (value >> 16); + *p++ = (value >> 8); + *p++ = value; + return (uintptr_t) p; +} + + +uintptr_t +ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, ngx_uint_t prefix) +{ + ngx_uint_t thresh, n; + + thresh = (1 << prefix) - 1; + + if (value < thresh) { + if (p == NULL) { + return 1; + } + + *p++ |= value; + return (uintptr_t) p; + } + + value -= thresh; + + if (p == NULL) { + for (n = 2; value >= 128; n++) { + value >>= 7; + } + + return n; + } + + *p++ |= thresh; + + while (value >= 128) { + *p++ = 0x80 | value; + value >>= 7; + } + + *p++ = value; + + return (uintptr_t) p; +} + + +uintptr_t +ngx_http_v3_encode_field_section_prefix(u_char *p, ngx_uint_t insert_count, + ngx_uint_t sign, ngx_uint_t delta_base) +{ + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, insert_count, 8) + + ngx_http_v3_encode_prefix_int(NULL, delta_base, 7); + } + + *p = 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, insert_count, 8); + + *p = sign ? 0x80 : 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, delta_base, 7); + + return (uintptr_t) p; +} + + +uintptr_t +ngx_http_v3_encode_field_ri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index) +{ + /* Indexed Field Line */ + + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, index, 6); + } + + *p = dynamic ? 0x80 : 0xc0; + + return ngx_http_v3_encode_prefix_int(p, index, 6); +} + + +uintptr_t +ngx_http_v3_encode_field_lri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index, + u_char *data, size_t len) +{ + size_t hlen; + u_char *p1, *p2; + + /* Literal Field Line With Name Reference */ + + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, index, 4) + + ngx_http_v3_encode_prefix_int(NULL, len, 7) + + len; + } + + *p = dynamic ? 0x40 : 0x50; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 4); + + p1 = p; + *p = 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7); + + if (data) { + p2 = p; + hlen = ngx_http_huff_encode(data, len, p, 0); + + if (hlen) { + p = p1; + *p = 0x80; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7); + + if (p != p2) { + ngx_memmove(p, p2, hlen); + } + + p += hlen; + + } else { + p = ngx_cpymem(p, data, len); + } + } + + return (uintptr_t) p; +} + + +uintptr_t +ngx_http_v3_encode_field_l(u_char *p, ngx_str_t *name, ngx_str_t *value) +{ + size_t hlen; + u_char *p1, *p2; + + /* Literal Field Line With Literal Name */ + + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, name->len, 3) + + name->len + + ngx_http_v3_encode_prefix_int(NULL, value->len, 7) + + value->len; + } + + p1 = p; + *p = 0x20; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, name->len, 3); + + p2 = p; + hlen = ngx_http_huff_encode(name->data, name->len, p, 1); + + if (hlen) { + p = p1; + *p = 0x28; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 3); + + if (p != p2) { + ngx_memmove(p, p2, hlen); + } + + p += hlen; + + } else { + ngx_strlow(p, name->data, name->len); + p += name->len; + } + + p1 = p; + *p = 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, value->len, 7); + + p2 = p; + hlen = ngx_http_huff_encode(value->data, value->len, p, 0); + + if (hlen) { + p = p1; + *p = 0x80; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7); + + if (p != p2) { + ngx_memmove(p, p2, hlen); + } + + p += hlen; + + } else { + p = ngx_cpymem(p, value->data, value->len); + } + + return (uintptr_t) p; +} + + +uintptr_t +ngx_http_v3_encode_field_pbi(u_char *p, ngx_uint_t index) +{ + /* Indexed Field Line With Post-Base Index */ + + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, index, 4); + } + + *p = 0x10; + + return ngx_http_v3_encode_prefix_int(p, index, 4); +} + + +uintptr_t +ngx_http_v3_encode_field_lpbi(u_char *p, ngx_uint_t index, u_char *data, + size_t len) +{ + size_t hlen; + u_char *p1, *p2; + + /* Literal Field Line With Post-Base Name Reference */ + + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, index, 3) + + ngx_http_v3_encode_prefix_int(NULL, len, 7) + + len; + } + + *p = 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 3); + + p1 = p; + *p = 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7); + + if (data) { + p2 = p; + hlen = ngx_http_huff_encode(data, len, p, 0); + + if (hlen) { + p = p1; + *p = 0x80; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7); + + if (p != p2) { + ngx_memmove(p, p2, hlen); + } + + p += hlen; + + } else { + p = ngx_cpymem(p, data, len); + } + } + + return (uintptr_t) p; +} diff --git a/src/deps/src/nginx/src/http/v3/ngx_http_v3_encode.h b/src/deps/src/nginx/src/http/v3/ngx_http_v3_encode.h new file mode 100644 index 000000000..fca376da5 --- /dev/null +++ b/src/deps/src/nginx/src/http/v3/ngx_http_v3_encode.h @@ -0,0 +1,34 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_V3_ENCODE_H_INCLUDED_ +#define _NGX_HTTP_V3_ENCODE_H_INCLUDED_ + + +#include +#include +#include + + +uintptr_t ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value); +uintptr_t ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, + ngx_uint_t prefix); + +uintptr_t ngx_http_v3_encode_field_section_prefix(u_char *p, + ngx_uint_t insert_count, ngx_uint_t sign, ngx_uint_t delta_base); +uintptr_t ngx_http_v3_encode_field_ri(u_char *p, ngx_uint_t dynamic, + ngx_uint_t index); +uintptr_t ngx_http_v3_encode_field_lri(u_char *p, ngx_uint_t dynamic, + ngx_uint_t index, u_char *data, size_t len); +uintptr_t ngx_http_v3_encode_field_l(u_char *p, ngx_str_t *name, + ngx_str_t *value); +uintptr_t ngx_http_v3_encode_field_pbi(u_char *p, ngx_uint_t index); +uintptr_t ngx_http_v3_encode_field_lpbi(u_char *p, ngx_uint_t index, + u_char *data, size_t len); + + +#endif /* _NGX_HTTP_V3_ENCODE_H_INCLUDED_ */ diff --git a/src/deps/src/nginx/src/http/v3/ngx_http_v3_filter_module.c b/src/deps/src/nginx/src/http/v3/ngx_http_v3_filter_module.c new file mode 100644 index 000000000..4d2276dc0 --- /dev/null +++ b/src/deps/src/nginx/src/http/v3/ngx_http_v3_filter_module.c @@ -0,0 +1,852 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +/* static table indices */ +#define NGX_HTTP_V3_HEADER_AUTHORITY 0 +#define NGX_HTTP_V3_HEADER_PATH_ROOT 1 +#define NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO 4 +#define NGX_HTTP_V3_HEADER_DATE 6 +#define NGX_HTTP_V3_HEADER_LAST_MODIFIED 10 +#define NGX_HTTP_V3_HEADER_LOCATION 12 +#define NGX_HTTP_V3_HEADER_METHOD_GET 17 +#define NGX_HTTP_V3_HEADER_SCHEME_HTTP 22 +#define NGX_HTTP_V3_HEADER_SCHEME_HTTPS 23 +#define NGX_HTTP_V3_HEADER_STATUS_200 25 +#define NGX_HTTP_V3_HEADER_ACCEPT_ENCODING 31 +#define NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN 53 +#define NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING 59 +#define NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE 72 +#define NGX_HTTP_V3_HEADER_SERVER 92 +#define NGX_HTTP_V3_HEADER_USER_AGENT 95 + + +typedef struct { + ngx_chain_t *free; + ngx_chain_t *busy; +} ngx_http_v3_filter_ctx_t; + + +static ngx_int_t ngx_http_v3_header_filter(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_body_filter(ngx_http_request_t *r, + ngx_chain_t *in); +static ngx_chain_t *ngx_http_v3_create_trailers(ngx_http_request_t *r, + ngx_http_v3_filter_ctx_t *ctx); +static ngx_int_t ngx_http_v3_filter_init(ngx_conf_t *cf); + + +static ngx_http_module_t ngx_http_v3_filter_module_ctx = { + NULL, /* preconfiguration */ + ngx_http_v3_filter_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_http_v3_filter_module = { + NGX_MODULE_V1, + &ngx_http_v3_filter_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_output_header_filter_pt ngx_http_next_header_filter; +static ngx_http_output_body_filter_pt ngx_http_next_body_filter; + + +static ngx_int_t +ngx_http_v3_header_filter(ngx_http_request_t *r) +{ + u_char *p; + size_t len, n; + ngx_buf_t *b; + ngx_str_t host, location; + ngx_uint_t i, port; + ngx_chain_t *out, *hl, *cl, **ll; + ngx_list_part_t *part; + ngx_table_elt_t *header; + ngx_connection_t *c; + ngx_http_v3_session_t *h3c; + ngx_http_v3_filter_ctx_t *ctx; + ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + u_char addr[NGX_SOCKADDR_STRLEN]; + + if (r->http_version != NGX_HTTP_VERSION_30) { + return ngx_http_next_header_filter(r); + } + + if (r->header_sent) { + return NGX_OK; + } + + r->header_sent = 1; + + if (r != r->main) { + return NGX_OK; + } + + h3c = ngx_http_v3_get_session(r->connection); + + if (r->method == NGX_HTTP_HEAD) { + r->header_only = 1; + } + + if (r->headers_out.last_modified_time != -1) { + if (r->headers_out.status != NGX_HTTP_OK + && r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT + && r->headers_out.status != NGX_HTTP_NOT_MODIFIED) + { + r->headers_out.last_modified_time = -1; + r->headers_out.last_modified = NULL; + } + } + + if (r->headers_out.status == NGX_HTTP_NO_CONTENT) { + r->header_only = 1; + ngx_str_null(&r->headers_out.content_type); + r->headers_out.last_modified_time = -1; + r->headers_out.last_modified = NULL; + r->headers_out.content_length = NULL; + r->headers_out.content_length_n = -1; + } + + if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) { + r->header_only = 1; + } + + c = r->connection; + + out = NULL; + ll = &out; + + len = ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); + + if (r->headers_out.status == NGX_HTTP_OK) { + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_STATUS_200); + + } else { + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_STATUS_200, + NULL, 3); + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (r->headers_out.server == NULL) { + if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { + n = sizeof(NGINX_VER) - 1; + + } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { + n = sizeof(NGINX_VER_BUILD) - 1; + + } else { + n = sizeof("nginx") - 1; + } + + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_SERVER, + NULL, n); + } + + if (r->headers_out.date == NULL) { + len += ngx_http_v3_encode_field_lri(NULL, 0, NGX_HTTP_V3_HEADER_DATE, + NULL, ngx_cached_http_time.len); + } + + if (r->headers_out.content_type.len) { + n = r->headers_out.content_type.len; + + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + n += sizeof("; charset=") - 1 + r->headers_out.charset.len; + } + + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN, + NULL, n); + } + + if (r->headers_out.content_length == NULL) { + if (r->headers_out.content_length_n > 0) { + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO, + NULL, NGX_OFF_T_LEN); + + } else if (r->headers_out.content_length_n == 0) { + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO); + } + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_LAST_MODIFIED, NULL, + sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1); + } + + if (r->headers_out.location && r->headers_out.location->value.len) { + + if (r->headers_out.location->value.data[0] == '/' + && clcf->absolute_redirect) + { + if (clcf->server_name_in_redirect) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + host = cscf->server_name; + + } else if (r->headers_in.server.len) { + host = r->headers_in.server; + + } else { + host.len = NGX_SOCKADDR_STRLEN; + host.data = addr; + + if (ngx_connection_local_sockaddr(c, &host, 0) != NGX_OK) { + return NGX_ERROR; + } + } + + port = ngx_inet_get_port(c->local_sockaddr); + + location.len = sizeof("https://") - 1 + host.len + + r->headers_out.location->value.len; + + if (clcf->port_in_redirect) { + port = (port == 443) ? 0 : port; + + } else { + port = 0; + } + + if (port) { + location.len += sizeof(":65535") - 1; + } + + location.data = ngx_pnalloc(r->pool, location.len); + if (location.data == NULL) { + return NGX_ERROR; + } + + p = ngx_cpymem(location.data, "https://", sizeof("https://") - 1); + p = ngx_cpymem(p, host.data, host.len); + + if (port) { + p = ngx_sprintf(p, ":%ui", port); + } + + p = ngx_cpymem(p, r->headers_out.location->value.data, + r->headers_out.location->value.len); + + /* update r->headers_out.location->value for possible logging */ + + r->headers_out.location->value.len = p - location.data; + r->headers_out.location->value.data = location.data; + ngx_str_set(&r->headers_out.location->key, "Location"); + } + + r->headers_out.location->hash = 0; + + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_LOCATION, NULL, + r->headers_out.location->value.len); + } + +#if (NGX_HTTP_GZIP) + if (r->gzip_vary) { + if (clcf->gzip_vary) { + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING); + + } else { + r->gzip_vary = 0; + } + } +#endif + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + len += ngx_http_v3_encode_field_l(NULL, &header[i].key, + &header[i].value); + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 header len:%uz", len); + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_ERROR; + } + + b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last, + 0, 0, 0); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \":status: %03ui\"", + r->headers_out.status); + + if (r->headers_out.status == NGX_HTTP_OK) { + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, + NGX_HTTP_V3_HEADER_STATUS_200); + + } else { + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_STATUS_200, + NULL, 3); + b->last = ngx_sprintf(b->last, "%03ui", r->headers_out.status); + } + + if (r->headers_out.server == NULL) { + if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { + p = (u_char *) NGINX_VER; + n = sizeof(NGINX_VER) - 1; + + } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { + p = (u_char *) NGINX_VER_BUILD; + n = sizeof(NGINX_VER_BUILD) - 1; + + } else { + p = (u_char *) "nginx"; + n = sizeof("nginx") - 1; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"server: %*s\"", n, p); + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_SERVER, + p, n); + } + + if (r->headers_out.date == NULL) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"date: %V\"", + &ngx_cached_http_time); + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_DATE, + ngx_cached_http_time.data, + ngx_cached_http_time.len); + } + + if (r->headers_out.content_type.len) { + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + n = r->headers_out.content_type.len + sizeof("; charset=") - 1 + + r->headers_out.charset.len; + + p = ngx_pnalloc(r->pool, n); + if (p == NULL) { + return NGX_ERROR; + } + + p = ngx_cpymem(p, r->headers_out.content_type.data, + r->headers_out.content_type.len); + + p = ngx_cpymem(p, "; charset=", sizeof("; charset=") - 1); + + p = ngx_cpymem(p, r->headers_out.charset.data, + r->headers_out.charset.len); + + /* updated r->headers_out.content_type is also needed for logging */ + + r->headers_out.content_type.len = n; + r->headers_out.content_type.data = p - n; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"content-type: %V\"", + &r->headers_out.content_type); + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN, + r->headers_out.content_type.data, + r->headers_out.content_type.len); + } + + if (r->headers_out.content_length == NULL + && r->headers_out.content_length_n >= 0) + { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"content-length: %O\"", + r->headers_out.content_length_n); + + if (r->headers_out.content_length_n > 0) { + p = ngx_sprintf(b->last, "%O", r->headers_out.content_length_n); + n = p - b->last; + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO, + NULL, n); + + b->last = ngx_sprintf(b->last, "%O", + r->headers_out.content_length_n); + + } else { + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, + NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO); + } + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + n = sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1; + + p = ngx_pnalloc(r->pool, n); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_http_time(p, r->headers_out.last_modified_time); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"last-modified: %*s\"", n, p); + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_LAST_MODIFIED, + p, n); + } + + if (r->headers_out.location && r->headers_out.location->value.len) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"location: %V\"", + &r->headers_out.location->value); + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_LOCATION, + r->headers_out.location->value.data, + r->headers_out.location->value.len); + } + +#if (NGX_HTTP_GZIP) + if (r->gzip_vary) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"vary: Accept-Encoding\""); + + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, + NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING); + } +#endif + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"%V: %V\"", + &header[i].key, &header[i].value); + + b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, + &header[i].key, + &header[i].value); + } + + if (r->header_only) { + b->last_buf = 1; + } + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = b; + cl->next = NULL; + + n = b->last - b->pos; + + h3c->payload_bytes += n; + + len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_HEADERS) + + ngx_http_v3_encode_varlen_int(NULL, n); + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_ERROR; + } + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, + NGX_HTTP_V3_FRAME_HEADERS); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); + + hl = ngx_alloc_chain_link(r->pool); + if (hl == NULL) { + return NGX_ERROR; + } + + hl->buf = b; + hl->next = cl; + + *ll = hl; + ll = &cl->next; + + if (r->headers_out.content_length_n >= 0 + && !r->header_only && !r->expect_trailers) + { + len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_DATA) + + ngx_http_v3_encode_varlen_int(NULL, + r->headers_out.content_length_n); + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_ERROR; + } + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, + NGX_HTTP_V3_FRAME_DATA); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, + r->headers_out.content_length_n); + + h3c->payload_bytes += r->headers_out.content_length_n; + h3c->total_bytes += r->headers_out.content_length_n; + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = b; + cl->next = NULL; + + *ll = cl; + + } else { + ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_filter_ctx_t)); + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_http_set_ctx(r, ctx, ngx_http_v3_filter_module); + } + + for (cl = out; cl; cl = cl->next) { + h3c->total_bytes += cl->buf->last - cl->buf->pos; + r->header_size += cl->buf->last - cl->buf->pos; + } + + return ngx_http_write_filter(r, out); +} + + +static ngx_int_t +ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in) +{ + u_char *chunk; + off_t size; + ngx_int_t rc; + ngx_buf_t *b; + ngx_chain_t *out, *cl, *tl, **ll; + ngx_http_v3_session_t *h3c; + ngx_http_v3_filter_ctx_t *ctx; + + if (in == NULL) { + return ngx_http_next_body_filter(r, in); + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_v3_filter_module); + if (ctx == NULL) { + return ngx_http_next_body_filter(r, in); + } + + h3c = ngx_http_v3_get_session(r->connection); + + out = NULL; + ll = &out; + + size = 0; + cl = in; + + for ( ;; ) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 chunk: %O", ngx_buf_size(cl->buf)); + + size += ngx_buf_size(cl->buf); + + if (cl->buf->flush + || cl->buf->sync + || ngx_buf_in_memory(cl->buf) + || cl->buf->in_file) + { + tl = ngx_alloc_chain_link(r->pool); + if (tl == NULL) { + return NGX_ERROR; + } + + tl->buf = cl->buf; + *ll = tl; + ll = &tl->next; + } + + if (cl->next == NULL) { + break; + } + + cl = cl->next; + } + + if (size) { + tl = ngx_chain_get_free_buf(r->pool, &ctx->free); + if (tl == NULL) { + return NGX_ERROR; + } + + b = tl->buf; + chunk = b->start; + + if (chunk == NULL) { + chunk = ngx_palloc(r->pool, NGX_HTTP_V3_VARLEN_INT_LEN * 2); + if (chunk == NULL) { + return NGX_ERROR; + } + + b->start = chunk; + b->end = chunk + NGX_HTTP_V3_VARLEN_INT_LEN * 2; + } + + b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module; + b->memory = 0; + b->temporary = 1; + b->pos = chunk; + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(chunk, + NGX_HTTP_V3_FRAME_DATA); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, size); + + tl->next = out; + out = tl; + + h3c->payload_bytes += size; + } + + if (cl->buf->last_buf) { + tl = ngx_http_v3_create_trailers(r, ctx); + if (tl == NULL) { + return NGX_ERROR; + } + + cl->buf->last_buf = 0; + + *ll = tl; + + } else { + *ll = NULL; + } + + for (cl = out; cl; cl = cl->next) { + h3c->total_bytes += cl->buf->last - cl->buf->pos; + } + + rc = ngx_http_next_body_filter(r, out); + + ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out, + (ngx_buf_tag_t) &ngx_http_v3_filter_module); + + return rc; +} + + +static ngx_chain_t * +ngx_http_v3_create_trailers(ngx_http_request_t *r, + ngx_http_v3_filter_ctx_t *ctx) +{ + size_t len, n; + u_char *p; + ngx_buf_t *b; + ngx_uint_t i; + ngx_chain_t *cl, *hl; + ngx_list_part_t *part; + ngx_table_elt_t *header; + ngx_http_v3_session_t *h3c; + + h3c = ngx_http_v3_get_session(r->connection); + + len = 0; + + part = &r->headers_out.trailers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + len += ngx_http_v3_encode_field_l(NULL, &header[i].key, + &header[i].value); + } + + cl = ngx_chain_get_free_buf(r->pool, &ctx->free); + if (cl == NULL) { + return NULL; + } + + b = cl->buf; + + b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module; + b->memory = 0; + b->last_buf = 1; + + if (len == 0) { + b->temporary = 0; + b->pos = b->last = NULL; + return cl; + } + + b->temporary = 1; + + len += ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); + + b->pos = ngx_palloc(r->pool, len); + if (b->pos == NULL) { + return NULL; + } + + b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->pos, + 0, 0, 0); + + part = &r->headers_out.trailers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 output trailer: \"%V: %V\"", + &header[i].key, &header[i].value); + + b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, + &header[i].key, + &header[i].value); + } + + n = b->last - b->pos; + + h3c->payload_bytes += n; + + hl = ngx_chain_get_free_buf(r->pool, &ctx->free); + if (hl == NULL) { + return NULL; + } + + b = hl->buf; + p = b->start; + + if (p == NULL) { + p = ngx_palloc(r->pool, NGX_HTTP_V3_VARLEN_INT_LEN * 2); + if (p == NULL) { + return NULL; + } + + b->start = p; + b->end = p + NGX_HTTP_V3_VARLEN_INT_LEN * 2; + } + + b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module; + b->memory = 0; + b->temporary = 1; + b->pos = p; + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(p, + NGX_HTTP_V3_FRAME_HEADERS); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); + + hl->next = cl; + + return hl; +} + + +static ngx_int_t +ngx_http_v3_filter_init(ngx_conf_t *cf) +{ + ngx_http_next_header_filter = ngx_http_top_header_filter; + ngx_http_top_header_filter = ngx_http_v3_header_filter; + + ngx_http_next_body_filter = ngx_http_top_body_filter; + ngx_http_top_body_filter = ngx_http_v3_body_filter; + + return NGX_OK; +} diff --git a/src/deps/src/nginx/src/http/v3/ngx_http_v3_module.c b/src/deps/src/nginx/src/http/v3/ngx_http_v3_module.c new file mode 100644 index 000000000..139bd65f3 --- /dev/null +++ b/src/deps/src/nginx/src/http/v3/ngx_http_v3_module.c @@ -0,0 +1,393 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include + + +static ngx_int_t ngx_http_v3_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_v3_add_variables(ngx_conf_t *cf); +static void *ngx_http_v3_create_srv_conf(ngx_conf_t *cf); +static char *ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, + void *child); +static char *ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + + +static ngx_command_t ngx_http_v3_commands[] = { + + { ngx_string("http3"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, enable), + NULL }, + + { ngx_string("http3_hq"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, enable_hq), + NULL }, + + { ngx_string("http3_max_concurrent_streams"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, max_concurrent_streams), + NULL }, + + { ngx_string("http3_stream_buffer_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.stream_buffer_size), + NULL }, + + { ngx_string("quic_retry"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.retry), + NULL }, + + { ngx_string("quic_gso"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.gso_enabled), + NULL }, + + { ngx_string("quic_host_key"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_http_quic_host_key, + NGX_HTTP_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("quic_active_connection_id_limit"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.active_connection_id_limit), + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_v3_module_ctx = { + ngx_http_v3_add_variables, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + ngx_http_v3_create_srv_conf, /* create server configuration */ + ngx_http_v3_merge_srv_conf, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_http_v3_module = { + NGX_MODULE_V1, + &ngx_http_v3_module_ctx, /* module context */ + ngx_http_v3_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_variable_t ngx_http_v3_vars[] = { + + { ngx_string("http3"), NULL, ngx_http_v3_variable, 0, 0, 0 }, + + ngx_http_null_variable +}; + +static ngx_str_t ngx_http_quic_salt = ngx_string("ngx_quic"); + + +static ngx_int_t +ngx_http_v3_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_http_v3_session_t *h3c; + + if (r->connection->quic) { + h3c = ngx_http_v3_get_session(r->connection); + + if (h3c->hq) { + v->len = sizeof("hq") - 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) "hq"; + + return NGX_OK; + } + + v->len = sizeof("h3") - 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) "h3"; + + return NGX_OK; + } + + *v = ngx_http_variable_null_value; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_add_variables(ngx_conf_t *cf) +{ + ngx_http_variable_t *var, *v; + + for (v = ngx_http_v3_vars; v->name.len; v++) { + var = ngx_http_add_variable(cf, &v->name, v->flags); + if (var == NULL) { + return NGX_ERROR; + } + + var->get_handler = v->get_handler; + var->data = v->data; + } + + return NGX_OK; +} + + +static void * +ngx_http_v3_create_srv_conf(ngx_conf_t *cf) +{ + ngx_http_v3_srv_conf_t *h3scf; + + h3scf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_srv_conf_t)); + if (h3scf == NULL) { + return NULL; + } + + /* + * set by ngx_pcalloc(): + * + * h3scf->quic.host_key = { 0, NULL } + * h3scf->quic.stream_reject_code_uni = 0; + * h3scf->quic.disable_active_migration = 0; + * h3scf->quic.idle_timeout = 0; + * h3scf->max_blocked_streams = 0; + */ + + h3scf->enable = NGX_CONF_UNSET; + h3scf->enable_hq = NGX_CONF_UNSET; + h3scf->max_table_capacity = NGX_HTTP_V3_MAX_TABLE_CAPACITY; + h3scf->max_concurrent_streams = NGX_CONF_UNSET_UINT; + + h3scf->quic.stream_buffer_size = NGX_CONF_UNSET_SIZE; + h3scf->quic.max_concurrent_streams_bidi = NGX_CONF_UNSET_UINT; + h3scf->quic.max_concurrent_streams_uni = NGX_HTTP_V3_MAX_UNI_STREAMS; + h3scf->quic.retry = NGX_CONF_UNSET; + h3scf->quic.gso_enabled = NGX_CONF_UNSET; + h3scf->quic.stream_close_code = NGX_HTTP_V3_ERR_NO_ERROR; + h3scf->quic.stream_reject_code_bidi = NGX_HTTP_V3_ERR_REQUEST_REJECTED; + h3scf->quic.active_connection_id_limit = NGX_CONF_UNSET_UINT; + + h3scf->quic.init = ngx_http_v3_init; + h3scf->quic.shutdown = ngx_http_v3_shutdown; + + return h3scf; +} + + +static char * +ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_v3_srv_conf_t *prev = parent; + ngx_http_v3_srv_conf_t *conf = child; + + ngx_http_ssl_srv_conf_t *sscf; + ngx_http_core_srv_conf_t *cscf; + + ngx_conf_merge_value(conf->enable, prev->enable, 1); + + ngx_conf_merge_value(conf->enable_hq, prev->enable_hq, 0); + + ngx_conf_merge_uint_value(conf->max_concurrent_streams, + prev->max_concurrent_streams, 128); + + conf->max_blocked_streams = conf->max_concurrent_streams; + + ngx_conf_merge_size_value(conf->quic.stream_buffer_size, + prev->quic.stream_buffer_size, + 65536); + + conf->quic.max_concurrent_streams_bidi = conf->max_concurrent_streams; + + ngx_conf_merge_value(conf->quic.retry, prev->quic.retry, 0); + ngx_conf_merge_value(conf->quic.gso_enabled, prev->quic.gso_enabled, 0); + + ngx_conf_merge_str_value(conf->quic.host_key, prev->quic.host_key, ""); + + ngx_conf_merge_uint_value(conf->quic.active_connection_id_limit, + prev->quic.active_connection_id_limit, + 2); + + if (conf->quic.host_key.len == 0) { + + conf->quic.host_key.len = NGX_QUIC_DEFAULT_HOST_KEY_LEN; + conf->quic.host_key.data = ngx_palloc(cf->pool, + conf->quic.host_key.len); + if (conf->quic.host_key.data == NULL) { + return NGX_CONF_ERROR; + } + + if (RAND_bytes(conf->quic.host_key.data, NGX_QUIC_DEFAULT_HOST_KEY_LEN) + <= 0) + { + return NGX_CONF_ERROR; + } + } + + if (ngx_quic_derive_key(cf->log, "av_token_key", + &conf->quic.host_key, &ngx_http_quic_salt, + conf->quic.av_token_key, NGX_QUIC_AV_KEY_LEN) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + if (ngx_quic_derive_key(cf->log, "sr_token_key", + &conf->quic.host_key, &ngx_http_quic_salt, + conf->quic.sr_token_key, NGX_QUIC_SR_KEY_LEN) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + cscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_core_module); + conf->quic.handshake_timeout = cscf->client_header_timeout; + + sscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_ssl_module); + conf->quic.ssl = &sscf->ssl; + + return NGX_CONF_OK; +} + + +static char * +ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_v3_srv_conf_t *h3scf = conf; + + u_char *buf; + size_t size; + ssize_t n; + ngx_str_t *value; + ngx_file_t file; + ngx_file_info_t fi; + ngx_quic_conf_t *qcf; + + qcf = &h3scf->quic; + + if (qcf->host_key.len) { + return "is duplicate"; + } + + buf = NULL; +#if (NGX_SUPPRESS_WARN) + size = 0; +#endif + + value = cf->args->elts; + + if (ngx_conf_full_name(cf->cycle, &value[1], 1) != NGX_OK) { + return NGX_CONF_ERROR; + } + + ngx_memzero(&file, sizeof(ngx_file_t)); + file.name = value[1]; + file.log = cf->log; + + file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); + + if (file.fd == NGX_INVALID_FILE) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno, + ngx_open_file_n " \"%V\" failed", &file.name); + return NGX_CONF_ERROR; + } + + if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, + ngx_fd_info_n " \"%V\" failed", &file.name); + goto failed; + } + + size = ngx_file_size(&fi); + + if (size == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"%V\" zero key size", &file.name); + goto failed; + } + + buf = ngx_pnalloc(cf->pool, size); + if (buf == NULL) { + goto failed; + } + + n = ngx_read_file(&file, buf, size, 0); + + if (n == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, + ngx_read_file_n " \"%V\" failed", &file.name); + goto failed; + } + + if ((size_t) n != size) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, 0, + ngx_read_file_n " \"%V\" returned only " + "%z bytes instead of %uz", &file.name, n, size); + goto failed; + } + + qcf->host_key.data = buf; + qcf->host_key.len = n; + + if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, + ngx_close_file_n " \"%V\" failed", &file.name); + } + + return NGX_CONF_OK; + +failed: + + if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, + ngx_close_file_n " \"%V\" failed", &file.name); + } + + if (buf) { + ngx_explicit_memzero(buf, size); + } + + return NGX_CONF_ERROR; +} diff --git a/src/deps/src/nginx/src/http/v3/ngx_http_v3_parse.c b/src/deps/src/nginx/src/http/v3/ngx_http_v3_parse.c new file mode 100644 index 000000000..568816323 --- /dev/null +++ b/src/deps/src/nginx/src/http/v3/ngx_http_v3_parse.c @@ -0,0 +1,1933 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#define ngx_http_v3_is_v2_frame(type) \ + ((type) == 0x02 || (type) == 0x06 || (type) == 0x08 || (type) == 0x09) + + +static void ngx_http_v3_parse_start_local(ngx_buf_t *b, ngx_buf_t *loc, + ngx_uint_t n); +static void ngx_http_v3_parse_end_local(ngx_buf_t *b, ngx_buf_t *loc, + ngx_uint_t *n); +static ngx_int_t ngx_http_v3_parse_skip(ngx_buf_t *b, ngx_uint_t *length); + +static ngx_int_t ngx_http_v3_parse_varlen_int(ngx_connection_t *c, + ngx_http_v3_parse_varlen_int_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_prefix_int(ngx_connection_t *c, + ngx_http_v3_parse_prefix_int_t *st, ngx_uint_t prefix, ngx_buf_t *b); + +static ngx_int_t ngx_http_v3_parse_field_section_prefix(ngx_connection_t *c, + ngx_http_v3_parse_field_section_prefix_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_field_rep(ngx_connection_t *c, + ngx_http_v3_parse_field_rep_t *st, ngx_uint_t base, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_literal(ngx_connection_t *c, + ngx_http_v3_parse_literal_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_field_ri(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_field_lri(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_field_l(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_field_pbi(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_field_lpbi(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b); + +static ngx_int_t ngx_http_v3_parse_control(ngx_connection_t *c, + ngx_http_v3_parse_control_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_settings(ngx_connection_t *c, + ngx_http_v3_parse_settings_t *st, ngx_buf_t *b); + +static ngx_int_t ngx_http_v3_parse_encoder(ngx_connection_t *c, + ngx_http_v3_parse_encoder_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_field_inr(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_field_iln(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b); + +static ngx_int_t ngx_http_v3_parse_decoder(ngx_connection_t *c, + ngx_http_v3_parse_decoder_t *st, ngx_buf_t *b); + +static ngx_int_t ngx_http_v3_parse_lookup(ngx_connection_t *c, + ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *name, ngx_str_t *value); + + +static void +ngx_http_v3_parse_start_local(ngx_buf_t *b, ngx_buf_t *loc, ngx_uint_t n) +{ + *loc = *b; + + if ((size_t) (loc->last - loc->pos) > n) { + loc->last = loc->pos + n; + } +} + + +static void +ngx_http_v3_parse_end_local(ngx_buf_t *b, ngx_buf_t *loc, ngx_uint_t *pn) +{ + *pn -= loc->pos - b->pos; + b->pos = loc->pos; +} + + +static ngx_int_t +ngx_http_v3_parse_skip(ngx_buf_t *b, ngx_uint_t *length) +{ + if ((size_t) (b->last - b->pos) < *length) { + *length -= b->last - b->pos; + b->pos = b->last; + return NGX_AGAIN; + } + + b->pos += *length; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_varlen_int(ngx_connection_t *c, + ngx_http_v3_parse_varlen_int_t *st, ngx_buf_t *b) +{ + u_char ch; + enum { + sw_start = 0, + sw_length_2, + sw_length_3, + sw_length_4, + sw_length_5, + sw_length_6, + sw_length_7, + sw_length_8 + }; + + for ( ;; ) { + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos++; + + switch (st->state) { + + case sw_start: + + st->value = ch; + if (st->value & 0xc0) { + st->state = sw_length_2; + break; + } + + goto done; + + case sw_length_2: + + st->value = (st->value << 8) + ch; + if ((st->value & 0xc000) == 0x4000) { + st->value &= 0x3fff; + goto done; + } + + st->state = sw_length_3; + break; + + case sw_length_4: + + st->value = (st->value << 8) + ch; + if ((st->value & 0xc0000000) == 0x80000000) { + st->value &= 0x3fffffff; + goto done; + } + + st->state = sw_length_5; + break; + + case sw_length_3: + case sw_length_5: + case sw_length_6: + case sw_length_7: + + st->value = (st->value << 8) + ch; + st->state++; + break; + + case sw_length_8: + + st->value = (st->value << 8) + ch; + st->value &= 0x3fffffffffffffff; + goto done; + } + } + +done: + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse varlen int %uL", st->value); + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_prefix_int(ngx_connection_t *c, + ngx_http_v3_parse_prefix_int_t *st, ngx_uint_t prefix, ngx_buf_t *b) +{ + u_char ch; + ngx_uint_t mask; + enum { + sw_start = 0, + sw_value + }; + + for ( ;; ) { + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos++; + + switch (st->state) { + + case sw_start: + + mask = (1 << prefix) - 1; + st->value = ch & mask; + + if (st->value != mask) { + goto done; + } + + st->shift = 0; + st->state = sw_value; + break; + + case sw_value: + + st->value += (uint64_t) (ch & 0x7f) << st->shift; + + if (st->shift == 56 + && ((ch & 0x80) || (st->value & 0xc000000000000000))) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client exceeded integer size limit"); + return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD; + } + + if (ch & 0x80) { + st->shift += 7; + break; + } + + goto done; + } + } + +done: + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse prefix int %uL", st->value); + + st->state = sw_start; + return NGX_DONE; +} + + +ngx_int_t +ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, + ngx_buf_t *b) +{ + ngx_buf_t loc; + ngx_int_t rc; + enum { + sw_start = 0, + sw_type, + sw_length, + sw_skip, + sw_prefix, + sw_verify, + sw_field_rep, + sw_done + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse headers"); + + st->state = sw_type; + + /* fall through */ + + case sw_type: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + st->type = st->vlint.value; + + if (ngx_http_v3_is_v2_frame(st->type) + || st->type == NGX_HTTP_V3_FRAME_DATA + || st->type == NGX_HTTP_V3_FRAME_GOAWAY + || st->type == NGX_HTTP_V3_FRAME_SETTINGS + || st->type == NGX_HTTP_V3_FRAME_MAX_PUSH_ID + || st->type == NGX_HTTP_V3_FRAME_CANCEL_PUSH + || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE) + { + return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; + } + + st->state = sw_length; + break; + + case sw_length: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + st->length = st->vlint.value; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse headers type:%ui, len:%ui", + st->type, st->length); + + if (st->type != NGX_HTTP_V3_FRAME_HEADERS) { + st->state = st->length > 0 ? sw_skip : sw_type; + break; + } + + if (st->length == 0) { + return NGX_HTTP_V3_ERR_FRAME_ERROR; + } + + st->state = sw_prefix; + break; + + case sw_skip: + + rc = ngx_http_v3_parse_skip(b, &st->length); + if (rc != NGX_DONE) { + return rc; + } + + st->state = sw_type; + break; + + case sw_prefix: + + ngx_http_v3_parse_start_local(b, &loc, st->length); + + rc = ngx_http_v3_parse_field_section_prefix(c, &st->prefix, &loc); + + ngx_http_v3_parse_end_local(b, &loc, &st->length); + + if (st->length == 0 && rc == NGX_AGAIN) { + return NGX_HTTP_V3_ERR_FRAME_ERROR; + } + + if (rc != NGX_DONE) { + return rc; + } + + st->state = sw_verify; + break; + + case sw_verify: + + rc = ngx_http_v3_check_insert_count(c, st->prefix.insert_count); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_field_rep; + + /* fall through */ + + case sw_field_rep: + + ngx_http_v3_parse_start_local(b, &loc, st->length); + + rc = ngx_http_v3_parse_field_rep(c, &st->field_rep, st->prefix.base, + &loc); + + ngx_http_v3_parse_end_local(b, &loc, &st->length); + + if (st->length == 0 && rc == NGX_AGAIN) { + return NGX_HTTP_V3_ERR_FRAME_ERROR; + } + + if (rc != NGX_DONE) { + return rc; + } + + if (st->length == 0) { + goto done; + } + + return NGX_OK; + } + } + +done: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse headers done"); + + if (st->prefix.insert_count > 0) { + if (ngx_http_v3_send_ack_section(c, c->quic->id) != NGX_OK) { + return NGX_ERROR; + } + + ngx_http_v3_ack_insert_count(c, st->prefix.insert_count); + } + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_field_section_prefix(ngx_connection_t *c, + ngx_http_v3_parse_field_section_prefix_t *st, ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_req_insert_count, + sw_delta_base, + sw_read_delta_base + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field section prefix"); + + st->state = sw_req_insert_count; + + /* fall through */ + + case sw_req_insert_count: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 8, b); + if (rc != NGX_DONE) { + return rc; + } + + st->insert_count = st->pint.value; + st->state = sw_delta_base; + break; + + case sw_delta_base: + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->sign = (ch & 0x80) ? 1 : 0; + st->state = sw_read_delta_base; + + /* fall through */ + + case sw_read_delta_base: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } + + st->delta_base = st->pint.value; + goto done; + } + } + +done: + + rc = ngx_http_v3_decode_insert_count(c, &st->insert_count); + if (rc != NGX_OK) { + return rc; + } + + if (st->sign) { + if (st->insert_count <= st->delta_base) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent negative base"); + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + st->base = st->insert_count - st->delta_base - 1; + + } else { + st->base = st->insert_count + st->delta_base; + } + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field section prefix done " + "insert_count:%ui, sign:%ui, delta_base:%ui, base:%ui", + st->insert_count, st->sign, st->delta_base, st->base); + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_field_rep(ngx_connection_t *c, + ngx_http_v3_parse_field_rep_t *st, ngx_uint_t base, ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_field_ri, + sw_field_lri, + sw_field_l, + sw_field_pbi, + sw_field_lpbi + }; + + if (st->state == sw_start) { + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field representation"); + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + ngx_memzero(&st->field, sizeof(ngx_http_v3_parse_field_t)); + + st->field.base = base; + + if (ch & 0x80) { + /* Indexed Field Line */ + + st->state = sw_field_ri; + + } else if (ch & 0x40) { + /* Literal Field Line With Name Reference */ + + st->state = sw_field_lri; + + } else if (ch & 0x20) { + /* Literal Field Line With Literal Name */ + + st->state = sw_field_l; + + } else if (ch & 0x10) { + /* Indexed Field Line With Post-Base Index */ + + st->state = sw_field_pbi; + + } else { + /* Literal Field Line With Post-Base Name Reference */ + + st->state = sw_field_lpbi; + } + } + + switch (st->state) { + + case sw_field_ri: + rc = ngx_http_v3_parse_field_ri(c, &st->field, b); + break; + + case sw_field_lri: + rc = ngx_http_v3_parse_field_lri(c, &st->field, b); + break; + + case sw_field_l: + rc = ngx_http_v3_parse_field_l(c, &st->field, b); + break; + + case sw_field_pbi: + rc = ngx_http_v3_parse_field_pbi(c, &st->field, b); + break; + + case sw_field_lpbi: + rc = ngx_http_v3_parse_field_lpbi(c, &st->field, b); + break; + + default: + rc = NGX_OK; + } + + if (rc != NGX_DONE) { + return rc; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field representation done"); + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st, + ngx_buf_t *b) +{ + u_char ch; + ngx_uint_t n; + ngx_http_core_srv_conf_t *cscf; + enum { + sw_start = 0, + sw_value + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse literal huff:%ui, len:%ui", + st->huffman, st->length); + + n = st->length; + + cscf = ngx_http_v3_get_module_srv_conf(c, ngx_http_core_module); + + if (n > cscf->large_client_header_buffers.size) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent too large field line"); + return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD; + } + + if (st->huffman) { + n = n * 8 / 5; + st->huffstate = 0; + } + + st->last = ngx_pnalloc(c->pool, n + 1); + if (st->last == NULL) { + return NGX_ERROR; + } + + st->value.data = st->last; + st->state = sw_value; + + /* fall through */ + + case sw_value: + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos++; + + if (st->huffman) { + if (ngx_http_huff_decode(&st->huffstate, &ch, 1, &st->last, + st->length == 1, c->log) + != NGX_OK) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent invalid encoded field line"); + return NGX_ERROR; + } + + } else { + *st->last++ = ch; + } + + if (--st->length) { + break; + } + + st->value.len = st->last - st->value.data; + *st->last = '\0'; + goto done; + } + } + +done: + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse literal done \"%V\"", &st->value); + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_field_ri(ngx_connection_t *c, ngx_http_v3_parse_field_t *st, + ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_index + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field ri"); + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->dynamic = (ch & 0x40) ? 0 : 1; + st->state = sw_index; + + /* fall through */ + + case sw_index: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b); + if (rc != NGX_DONE) { + return rc; + } + + st->index = st->pint.value; + goto done; + } + } + +done: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field ri done %s%ui]", + st->dynamic ? "dynamic[-" : "static[", st->index); + + if (st->dynamic) { + st->index = st->base - st->index - 1; + } + + rc = ngx_http_v3_parse_lookup(c, st->dynamic, st->index, &st->name, + &st->value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_field_lri(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_index, + sw_value_len, + sw_read_value_len, + sw_value + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field lri"); + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->dynamic = (ch & 0x10) ? 0 : 1; + st->state = sw_index; + + /* fall through */ + + case sw_index: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 4, b); + if (rc != NGX_DONE) { + return rc; + } + + st->index = st->pint.value; + st->state = sw_value_len; + break; + + case sw_value_len: + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->literal.huffman = (ch & 0x80) ? 1 : 0; + st->state = sw_read_value_len; + + /* fall through */ + + case sw_read_value_len: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + goto done; + } + + st->state = sw_value; + break; + + case sw_value: + + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } + + st->value = st->literal.value; + goto done; + } + } + +done: + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field lri done %s%ui] \"%V\"", + st->dynamic ? "dynamic[-" : "static[", + st->index, &st->value); + + if (st->dynamic) { + st->index = st->base - st->index - 1; + } + + rc = ngx_http_v3_parse_lookup(c, st->dynamic, st->index, &st->name, NULL); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_field_l(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_name_len, + sw_name, + sw_value_len, + sw_read_value_len, + sw_value + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field l"); + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->literal.huffman = (ch & 0x08) ? 1 : 0; + st->state = sw_name_len; + + /* fall through */ + + case sw_name_len: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 3, b); + if (rc != NGX_DONE) { + return rc; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + return NGX_ERROR; + } + + st->state = sw_name; + break; + + case sw_name: + + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } + + st->name = st->literal.value; + st->state = sw_value_len; + break; + + case sw_value_len: + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->literal.huffman = (ch & 0x80) ? 1 : 0; + st->state = sw_read_value_len; + + /* fall through */ + + case sw_read_value_len: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + goto done; + } + + st->state = sw_value; + break; + + case sw_value: + + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } + + st->value = st->literal.value; + goto done; + } + } + +done: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field l done \"%V\" \"%V\"", + &st->name, &st->value); + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_field_pbi(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b) +{ + ngx_int_t rc; + enum { + sw_start = 0, + sw_index + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field pbi"); + + st->state = sw_index; + + /* fall through */ + + case sw_index: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 4, b); + if (rc != NGX_DONE) { + return rc; + } + + st->index = st->pint.value; + goto done; + } + } + +done: + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field pbi done dynamic[+%ui]", st->index); + + rc = ngx_http_v3_parse_lookup(c, 1, st->base + st->index, &st->name, + &st->value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_field_lpbi(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_index, + sw_value_len, + sw_read_value_len, + sw_value + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field lpbi"); + + st->state = sw_index; + + /* fall through */ + + case sw_index: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 3, b); + if (rc != NGX_DONE) { + return rc; + } + + st->index = st->pint.value; + st->state = sw_value_len; + break; + + case sw_value_len: + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->literal.huffman = (ch & 0x80) ? 1 : 0; + st->state = sw_read_value_len; + + /* fall through */ + + case sw_read_value_len: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + goto done; + } + + st->state = sw_value; + break; + + case sw_value: + + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } + + st->value = st->literal.value; + goto done; + } + } + +done: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field lpbi done dynamic[+%ui] \"%V\"", + st->index, &st->value); + + rc = ngx_http_v3_parse_lookup(c, 1, st->base + st->index, &st->name, NULL); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_lookup(ngx_connection_t *c, ngx_uint_t dynamic, + ngx_uint_t index, ngx_str_t *name, ngx_str_t *value) +{ + u_char *p; + + if (!dynamic) { + if (ngx_http_v3_lookup_static(c, index, name, value) != NGX_OK) { + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + return NGX_OK; + } + + if (ngx_http_v3_lookup(c, index, name, value) != NGX_OK) { + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + if (name) { + p = ngx_pnalloc(c->pool, name->len + 1); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(p, name->data, name->len); + p[name->len] = '\0'; + name->data = p; + } + + if (value) { + p = ngx_pnalloc(c->pool, value->len + 1); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(p, value->data, value->len); + p[value->len] = '\0'; + value->data = p; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_parse_control(ngx_connection_t *c, ngx_http_v3_parse_control_t *st, + ngx_buf_t *b) +{ + ngx_buf_t loc; + ngx_int_t rc; + enum { + sw_start = 0, + sw_first_type, + sw_type, + sw_length, + sw_settings, + sw_skip + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse control"); + + st->state = sw_first_type; + + /* fall through */ + + case sw_first_type: + case sw_type: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + st->type = st->vlint.value; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse frame type:%ui", st->type); + + if (st->state == sw_first_type + && st->type != NGX_HTTP_V3_FRAME_SETTINGS) + { + return NGX_HTTP_V3_ERR_MISSING_SETTINGS; + } + + if (st->state != sw_first_type + && st->type == NGX_HTTP_V3_FRAME_SETTINGS) + { + return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; + } + + if (ngx_http_v3_is_v2_frame(st->type) + || st->type == NGX_HTTP_V3_FRAME_DATA + || st->type == NGX_HTTP_V3_FRAME_HEADERS + || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE) + { + return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; + } + + if (st->type == NGX_HTTP_V3_FRAME_CANCEL_PUSH) { + return NGX_HTTP_V3_ERR_ID_ERROR; + } + + st->state = sw_length; + break; + + case sw_length: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse frame len:%uL", st->vlint.value); + + st->length = st->vlint.value; + if (st->length == 0) { + st->state = sw_type; + break; + } + + switch (st->type) { + + case NGX_HTTP_V3_FRAME_SETTINGS: + st->state = sw_settings; + break; + + default: + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse skip unknown frame"); + st->state = sw_skip; + } + + break; + + case sw_settings: + + ngx_http_v3_parse_start_local(b, &loc, st->length); + + rc = ngx_http_v3_parse_settings(c, &st->settings, &loc); + + ngx_http_v3_parse_end_local(b, &loc, &st->length); + + if (st->length == 0 && rc == NGX_AGAIN) { + return NGX_HTTP_V3_ERR_SETTINGS_ERROR; + } + + if (rc != NGX_DONE) { + return rc; + } + + if (st->length == 0) { + st->state = sw_type; + } + + break; + + case sw_skip: + + rc = ngx_http_v3_parse_skip(b, &st->length); + if (rc != NGX_DONE) { + return rc; + } + + st->state = sw_type; + break; + } + } +} + + +static ngx_int_t +ngx_http_v3_parse_settings(ngx_connection_t *c, + ngx_http_v3_parse_settings_t *st, ngx_buf_t *b) +{ + ngx_int_t rc; + enum { + sw_start = 0, + sw_id, + sw_value + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse settings"); + + st->state = sw_id; + + /* fall through */ + + case sw_id: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + st->id = st->vlint.value; + st->state = sw_value; + break; + + case sw_value: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + if (ngx_http_v3_set_param(c, st->id, st->vlint.value) != NGX_OK) { + return NGX_HTTP_V3_ERR_SETTINGS_ERROR; + } + + goto done; + } + } + +done: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse settings done"); + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_encoder(ngx_connection_t *c, ngx_http_v3_parse_encoder_t *st, + ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_inr, + sw_iln, + sw_capacity, + sw_duplicate + }; + + for ( ;; ) { + + if (st->state == sw_start) { + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse encoder instruction"); + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + if (ch & 0x80) { + /* Insert With Name Reference */ + + st->state = sw_inr; + + } else if (ch & 0x40) { + /* Insert With Literal Name */ + + st->state = sw_iln; + + } else if (ch & 0x20) { + /* Set Dynamic Table Capacity */ + + st->state = sw_capacity; + + } else { + /* Duplicate */ + + st->state = sw_duplicate; + } + } + + switch (st->state) { + + case sw_inr: + + rc = ngx_http_v3_parse_field_inr(c, &st->field, b); + if (rc != NGX_DONE) { + return rc; + } + + st->state = sw_start; + break; + + case sw_iln: + + rc = ngx_http_v3_parse_field_iln(c, &st->field, b); + if (rc != NGX_DONE) { + return rc; + } + + st->state = sw_start; + break; + + case sw_capacity: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, b); + if (rc != NGX_DONE) { + return rc; + } + + rc = ngx_http_v3_set_capacity(c, st->pint.value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + break; + + default: /* sw_duplicate */ + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, b); + if (rc != NGX_DONE) { + return rc; + } + + rc = ngx_http_v3_duplicate(c, st->pint.value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + break; + } + } +} + + +static ngx_int_t +ngx_http_v3_parse_field_inr(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_name_index, + sw_value_len, + sw_read_value_len, + sw_value + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field inr"); + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->dynamic = (ch & 0x40) ? 0 : 1; + st->state = sw_name_index; + + /* fall through */ + + case sw_name_index: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b); + if (rc != NGX_DONE) { + return rc; + } + + st->index = st->pint.value; + st->state = sw_value_len; + break; + + case sw_value_len: + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->literal.huffman = (ch & 0x80) ? 1 : 0; + st->state = sw_read_value_len; + + /* fall through */ + + case sw_read_value_len: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + st->value.len = 0; + goto done; + } + + st->state = sw_value; + break; + + case sw_value: + + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } + + st->value = st->literal.value; + goto done; + } + } + +done: + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field inr done %s[%ui] \"%V\"", + st->dynamic ? "dynamic" : "static", + st->index, &st->value); + + rc = ngx_http_v3_ref_insert(c, st->dynamic, st->index, &st->value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_field_iln(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_name_len, + sw_name, + sw_value_len, + sw_read_value_len, + sw_value + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field iln"); + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->literal.huffman = (ch & 0x20) ? 1 : 0; + st->state = sw_name_len; + + /* fall through */ + + case sw_name_len: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, b); + if (rc != NGX_DONE) { + return rc; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + return NGX_ERROR; + } + + st->state = sw_name; + break; + + case sw_name: + + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } + + st->name = st->literal.value; + st->state = sw_value_len; + break; + + case sw_value_len: + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->literal.huffman = (ch & 0x80) ? 1 : 0; + st->state = sw_read_value_len; + + /* fall through */ + + case sw_read_value_len: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + st->value.len = 0; + goto done; + } + + st->state = sw_value; + break; + + case sw_value: + + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } + + st->value = st->literal.value; + goto done; + } + } + +done: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field iln done \"%V\":\"%V\"", + &st->name, &st->value); + + rc = ngx_http_v3_insert(c, &st->name, &st->value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_decoder(ngx_connection_t *c, ngx_http_v3_parse_decoder_t *st, + ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_ack_section, + sw_cancel_stream, + sw_inc_insert_count + }; + + for ( ;; ) { + + if (st->state == sw_start) { + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse decoder instruction"); + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + if (ch & 0x80) { + /* Section Acknowledgment */ + + st->state = sw_ack_section; + + } else if (ch & 0x40) { + /* Stream Cancellation */ + + st->state = sw_cancel_stream; + + } else { + /* Insert Count Increment */ + + st->state = sw_inc_insert_count; + } + } + + switch (st->state) { + + case sw_ack_section: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } + + rc = ngx_http_v3_ack_section(c, st->pint.value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + break; + + case sw_cancel_stream: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b); + if (rc != NGX_DONE) { + return rc; + } + + rc = ngx_http_v3_cancel_stream(c, st->pint.value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + break; + + case sw_inc_insert_count: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b); + if (rc != NGX_DONE) { + return rc; + } + + rc = ngx_http_v3_inc_insert_count(c, st->pint.value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + break; + } + } +} + + +ngx_int_t +ngx_http_v3_parse_data(ngx_connection_t *c, ngx_http_v3_parse_data_t *st, + ngx_buf_t *b) +{ + ngx_int_t rc; + enum { + sw_start = 0, + sw_type, + sw_length, + sw_skip + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse data"); + + st->state = sw_type; + + /* fall through */ + + case sw_type: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + st->type = st->vlint.value; + + if (st->type == NGX_HTTP_V3_FRAME_HEADERS) { + /* trailers */ + goto done; + } + + if (ngx_http_v3_is_v2_frame(st->type) + || st->type == NGX_HTTP_V3_FRAME_GOAWAY + || st->type == NGX_HTTP_V3_FRAME_SETTINGS + || st->type == NGX_HTTP_V3_FRAME_MAX_PUSH_ID + || st->type == NGX_HTTP_V3_FRAME_CANCEL_PUSH + || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE) + { + return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; + } + + st->state = sw_length; + break; + + case sw_length: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + st->length = st->vlint.value; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse data type:%ui, len:%ui", + st->type, st->length); + + if (st->type != NGX_HTTP_V3_FRAME_DATA && st->length > 0) { + st->state = sw_skip; + break; + } + + st->state = sw_type; + return NGX_OK; + + case sw_skip: + + rc = ngx_http_v3_parse_skip(b, &st->length); + if (rc != NGX_DONE) { + return rc; + } + + st->state = sw_type; + break; + } + } + +done: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse data done"); + + st->state = sw_start; + return NGX_DONE; +} + + +ngx_int_t +ngx_http_v3_parse_uni(ngx_connection_t *c, ngx_http_v3_parse_uni_t *st, + ngx_buf_t *b) +{ + ngx_int_t rc; + enum { + sw_start = 0, + sw_type, + sw_control, + sw_encoder, + sw_decoder, + sw_unknown + }; + + for ( ;; ) { + + switch (st->state) { + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse uni"); + + st->state = sw_type; + + /* fall through */ + + case sw_type: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + rc = ngx_http_v3_register_uni_stream(c, st->vlint.value); + if (rc != NGX_OK) { + return rc; + } + + switch (st->vlint.value) { + case NGX_HTTP_V3_STREAM_CONTROL: + st->state = sw_control; + break; + + case NGX_HTTP_V3_STREAM_ENCODER: + st->state = sw_encoder; + break; + + case NGX_HTTP_V3_STREAM_DECODER: + st->state = sw_decoder; + break; + + default: + st->state = sw_unknown; + } + + break; + + case sw_control: + + return ngx_http_v3_parse_control(c, &st->u.control, b); + + case sw_encoder: + + return ngx_http_v3_parse_encoder(c, &st->u.encoder, b); + + case sw_decoder: + + return ngx_http_v3_parse_decoder(c, &st->u.decoder, b); + + case sw_unknown: + + b->pos = b->last; + return NGX_AGAIN; + } + } +} diff --git a/src/deps/src/nginx/src/http/v3/ngx_http_v3_parse.h b/src/deps/src/nginx/src/http/v3/ngx_http_v3_parse.h new file mode 100644 index 000000000..ba004db5d --- /dev/null +++ b/src/deps/src/nginx/src/http/v3/ngx_http_v3_parse.h @@ -0,0 +1,146 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_V3_PARSE_H_INCLUDED_ +#define _NGX_HTTP_V3_PARSE_H_INCLUDED_ + + +#include +#include +#include + + +typedef struct { + ngx_uint_t state; + uint64_t value; +} ngx_http_v3_parse_varlen_int_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t shift; + uint64_t value; +} ngx_http_v3_parse_prefix_int_t; + + +typedef struct { + ngx_uint_t state; + uint64_t id; + ngx_http_v3_parse_varlen_int_t vlint; +} ngx_http_v3_parse_settings_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t insert_count; + ngx_uint_t delta_base; + ngx_uint_t sign; + ngx_uint_t base; + ngx_http_v3_parse_prefix_int_t pint; +} ngx_http_v3_parse_field_section_prefix_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t length; + ngx_uint_t huffman; + ngx_str_t value; + u_char *last; + u_char huffstate; +} ngx_http_v3_parse_literal_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t index; + ngx_uint_t base; + ngx_uint_t dynamic; + + ngx_str_t name; + ngx_str_t value; + + ngx_http_v3_parse_prefix_int_t pint; + ngx_http_v3_parse_literal_t literal; +} ngx_http_v3_parse_field_t; + + +typedef struct { + ngx_uint_t state; + ngx_http_v3_parse_field_t field; +} ngx_http_v3_parse_field_rep_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t type; + ngx_uint_t length; + ngx_http_v3_parse_varlen_int_t vlint; + ngx_http_v3_parse_field_section_prefix_t prefix; + ngx_http_v3_parse_field_rep_t field_rep; +} ngx_http_v3_parse_headers_t; + + +typedef struct { + ngx_uint_t state; + ngx_http_v3_parse_field_t field; + ngx_http_v3_parse_prefix_int_t pint; +} ngx_http_v3_parse_encoder_t; + + +typedef struct { + ngx_uint_t state; + ngx_http_v3_parse_prefix_int_t pint; +} ngx_http_v3_parse_decoder_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t type; + ngx_uint_t length; + ngx_http_v3_parse_varlen_int_t vlint; + ngx_http_v3_parse_settings_t settings; +} ngx_http_v3_parse_control_t; + + +typedef struct { + ngx_uint_t state; + ngx_http_v3_parse_varlen_int_t vlint; + union { + ngx_http_v3_parse_encoder_t encoder; + ngx_http_v3_parse_decoder_t decoder; + ngx_http_v3_parse_control_t control; + } u; +} ngx_http_v3_parse_uni_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t type; + ngx_uint_t length; + ngx_http_v3_parse_varlen_int_t vlint; +} ngx_http_v3_parse_data_t; + + +/* + * Parse functions return codes: + * NGX_DONE - parsing done + * NGX_OK - sub-element done + * NGX_AGAIN - more data expected + * NGX_BUSY - waiting for external event + * NGX_ERROR - internal error + * NGX_HTTP_V3_ERROR_XXX - HTTP/3 or QPACK error + */ + +ngx_int_t ngx_http_v3_parse_headers(ngx_connection_t *c, + ngx_http_v3_parse_headers_t *st, ngx_buf_t *b); +ngx_int_t ngx_http_v3_parse_data(ngx_connection_t *c, + ngx_http_v3_parse_data_t *st, ngx_buf_t *b); +ngx_int_t ngx_http_v3_parse_uni(ngx_connection_t *c, + ngx_http_v3_parse_uni_t *st, ngx_buf_t *b); + + +#endif /* _NGX_HTTP_V3_PARSE_H_INCLUDED_ */ diff --git a/src/deps/src/nginx/src/http/v3/ngx_http_v3_request.c b/src/deps/src/nginx/src/http/v3/ngx_http_v3_request.c new file mode 100644 index 000000000..87f5f3214 --- /dev/null +++ b/src/deps/src/nginx/src/http/v3/ngx_http_v3_request.c @@ -0,0 +1,1710 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +static void ngx_http_v3_init_request_stream(ngx_connection_t *c); +static void ngx_http_v3_wait_request_handler(ngx_event_t *rev); +static void ngx_http_v3_cleanup_connection(void *data); +static void ngx_http_v3_cleanup_request(void *data); +static void ngx_http_v3_process_request(ngx_event_t *rev); +static ngx_int_t ngx_http_v3_process_header(ngx_http_request_t *r, + ngx_str_t *name, ngx_str_t *value); +static ngx_int_t ngx_http_v3_validate_header(ngx_http_request_t *r, + ngx_str_t *name, ngx_str_t *value); +static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, + ngx_str_t *name, ngx_str_t *value); +static ngx_int_t ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_process_request_header(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_cookie(ngx_http_request_t *r, ngx_str_t *value); +static ngx_int_t ngx_http_v3_construct_cookie_header(ngx_http_request_t *r); +static void ngx_http_v3_read_client_request_body_handler(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_request_body_filter(ngx_http_request_t *r, + ngx_chain_t *in); + + +static const struct { + ngx_str_t name; + ngx_uint_t method; +} ngx_http_v3_methods[] = { + + { ngx_string("GET"), NGX_HTTP_GET }, + { ngx_string("POST"), NGX_HTTP_POST }, + { ngx_string("HEAD"), NGX_HTTP_HEAD }, + { ngx_string("OPTIONS"), NGX_HTTP_OPTIONS }, + { ngx_string("PROPFIND"), NGX_HTTP_PROPFIND }, + { ngx_string("PUT"), NGX_HTTP_PUT }, + { ngx_string("MKCOL"), NGX_HTTP_MKCOL }, + { ngx_string("DELETE"), NGX_HTTP_DELETE }, + { ngx_string("COPY"), NGX_HTTP_COPY }, + { ngx_string("MOVE"), NGX_HTTP_MOVE }, + { ngx_string("PROPPATCH"), NGX_HTTP_PROPPATCH }, + { ngx_string("LOCK"), NGX_HTTP_LOCK }, + { ngx_string("UNLOCK"), NGX_HTTP_UNLOCK }, + { ngx_string("PATCH"), NGX_HTTP_PATCH }, + { ngx_string("TRACE"), NGX_HTTP_TRACE }, + { ngx_string("CONNECT"), NGX_HTTP_CONNECT } +}; + + +void +ngx_http_v3_init_stream(ngx_connection_t *c) +{ + ngx_http_connection_t *hc, *phc; + ngx_http_v3_srv_conf_t *h3scf; + ngx_http_core_loc_conf_t *clcf; + + hc = c->data; + + hc->ssl = 1; + + clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); + + if (c->quic == NULL) { + h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); + h3scf->quic.idle_timeout = clcf->keepalive_timeout; + + ngx_quic_run(c, &h3scf->quic); + return; + } + + phc = ngx_http_quic_get_connection(c); + + if (phc->ssl_servername) { + hc->ssl_servername = phc->ssl_servername; +#if (NGX_PCRE) + hc->ssl_servername_regex = phc->ssl_servername_regex; +#endif + hc->conf_ctx = phc->conf_ctx; + + ngx_set_connection_log(c, clcf->error_log); + } + + if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + ngx_http_v3_init_uni_stream(c); + + } else { + ngx_http_v3_init_request_stream(c); + } +} + + +ngx_int_t +ngx_http_v3_init(ngx_connection_t *c) +{ + unsigned int len; + const unsigned char *data; + ngx_http_v3_session_t *h3c; + ngx_http_v3_srv_conf_t *h3scf; + ngx_http_core_loc_conf_t *clcf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init"); + + if (ngx_http_v3_init_session(c) != NGX_OK) { + return NGX_ERROR; + } + + h3c = ngx_http_v3_get_session(c); + clcf = ngx_http_v3_get_module_loc_conf(c, ngx_http_core_module); + ngx_add_timer(&h3c->keepalive, clcf->keepalive_timeout); + + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + + if (h3scf->enable_hq) { + if (!h3scf->enable) { + h3c->hq = 1; + return NGX_OK; + } + + SSL_get0_alpn_selected(c->ssl->connection, &data, &len); + + if (len == sizeof(NGX_HTTP_V3_HQ_PROTO) - 1 + && ngx_strncmp(data, NGX_HTTP_V3_HQ_PROTO, len) == 0) + { + h3c->hq = 1; + return NGX_OK; + } + } + + return ngx_http_v3_send_settings(c); +} + + +void +ngx_http_v3_shutdown(ngx_connection_t *c) +{ + ngx_http_v3_session_t *h3c; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 shutdown"); + + h3c = ngx_http_v3_get_session(c); + + if (h3c == NULL) { + ngx_quic_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, + "connection shutdown"); + return; + } + + if (!h3c->goaway) { + h3c->goaway = 1; + + if (!h3c->hq) { + (void) ngx_http_v3_send_goaway(c, h3c->next_request_id); + } + + ngx_http_v3_shutdown_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, + "connection shutdown"); + } +} + + +static void +ngx_http_v3_init_request_stream(ngx_connection_t *c) +{ + uint64_t n; + ngx_event_t *rev; + ngx_pool_cleanup_t *cln; + ngx_http_connection_t *hc; + ngx_http_v3_session_t *h3c; + ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init request stream"); + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, 1); +#endif + + hc = c->data; + + clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); + + n = c->quic->id >> 2; + + if (n >= clcf->keepalive_requests * 2) { + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "too many requests per connection"); + ngx_http_close_connection(c); + return; + } + + h3c = ngx_http_v3_get_session(c); + + if (h3c->goaway) { + c->close = 1; + ngx_http_close_connection(c); + return; + } + + h3c->next_request_id = c->quic->id + 0x04; + + if (n + 1 == clcf->keepalive_requests + || ngx_current_msec - c->start_time > clcf->keepalive_time) + { + h3c->goaway = 1; + + if (!h3c->hq) { + if (ngx_http_v3_send_goaway(c, h3c->next_request_id) != NGX_OK) { + ngx_http_close_connection(c); + return; + } + } + + ngx_http_v3_shutdown_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, + "reached maximum number of requests"); + } + + cln = ngx_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + ngx_http_close_connection(c); + return; + } + + cln->handler = ngx_http_v3_cleanup_connection; + cln->data = c; + + h3c->nrequests++; + + if (h3c->keepalive.timer_set) { + ngx_del_timer(&h3c->keepalive); + } + + rev = c->read; + + if (!h3c->hq) { + rev->handler = ngx_http_v3_wait_request_handler; + c->write->handler = ngx_http_empty_handler; + } + + if (rev->ready) { + rev->handler(rev); + return; + } + + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); + + ngx_add_timer(rev, cscf->client_header_timeout); + ngx_reusable_connection(c, 1); + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_close_connection(c); + return; + } +} + + +static void +ngx_http_v3_wait_request_handler(ngx_event_t *rev) +{ + size_t size; + ssize_t n; + ngx_buf_t *b; + ngx_connection_t *c; + ngx_pool_cleanup_t *cln; + ngx_http_request_t *r; + ngx_http_connection_t *hc; + ngx_http_core_srv_conf_t *cscf; + + c = rev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 wait request handler"); + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + c->timedout = 1; + ngx_http_close_connection(c); + return; + } + + if (c->close) { + ngx_http_close_connection(c); + return; + } + + hc = c->data; + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); + + size = cscf->client_header_buffer_size; + + b = c->buffer; + + if (b == NULL) { + b = ngx_create_temp_buf(c->pool, size); + if (b == NULL) { + ngx_http_close_connection(c); + return; + } + + c->buffer = b; + + } else if (b->start == NULL) { + + b->start = ngx_palloc(c->pool, size); + if (b->start == NULL) { + ngx_http_close_connection(c); + return; + } + + b->pos = b->start; + b->last = b->start; + b->end = b->last + size; + } + + n = c->recv(c, b->last, size); + + if (n == NGX_AGAIN) { + + if (!rev->timer_set) { + ngx_add_timer(rev, cscf->client_header_timeout); + ngx_reusable_connection(c, 1); + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_close_connection(c); + return; + } + + /* + * We are trying to not hold c->buffer's memory for an idle connection. + */ + + if (ngx_pfree(c->pool, b->start) == NGX_OK) { + b->start = NULL; + } + + return; + } + + if (n == NGX_ERROR) { + ngx_http_close_connection(c); + return; + } + + if (n == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client closed connection"); + ngx_http_close_connection(c); + return; + } + + b->last += n; + + c->log->action = "reading client request"; + + ngx_reusable_connection(c, 0); + + r = ngx_http_create_request(c); + if (r == NULL) { + ngx_http_close_connection(c); + return; + } + + r->http_version = NGX_HTTP_VERSION_30; + + r->v3_parse = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_parse_t)); + if (r->v3_parse == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + r->v3_parse->header_limit = cscf->large_client_header_buffers.size + * cscf->large_client_header_buffers.num; + + c->data = r; + c->requests = (c->quic->id >> 2) + 1; + + cln = ngx_pool_cleanup_add(r->pool, 0); + if (cln == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + cln->handler = ngx_http_v3_cleanup_request; + cln->data = r; + + rev->handler = ngx_http_v3_process_request; + ngx_http_v3_process_request(rev); +} + + +void +ngx_http_v3_reset_stream(ngx_connection_t *c) +{ + ngx_http_v3_session_t *h3c; + ngx_http_v3_srv_conf_t *h3scf; + + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + + h3c = ngx_http_v3_get_session(c); + + if (h3scf->max_table_capacity > 0 && !c->read->eof && !h3c->hq + && (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) + { + (void) ngx_http_v3_send_cancel_stream(c, c->quic->id); + } + + if (c->timedout) { + ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR); + + } else if (c->close) { + ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_REQUEST_REJECTED); + + } else if (c->requests == 0 || c->error) { + ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR); + } +} + + +static void +ngx_http_v3_cleanup_connection(void *data) +{ + ngx_connection_t *c = data; + + ngx_http_v3_session_t *h3c; + ngx_http_core_loc_conf_t *clcf; + + h3c = ngx_http_v3_get_session(c); + + if (--h3c->nrequests == 0) { + clcf = ngx_http_v3_get_module_loc_conf(c, ngx_http_core_module); + ngx_add_timer(&h3c->keepalive, clcf->keepalive_timeout); + } +} + + +static void +ngx_http_v3_cleanup_request(void *data) +{ + ngx_http_request_t *r = data; + + if (!r->response_sent) { + r->connection->error = 1; + } +} + + +static void +ngx_http_v3_process_request(ngx_event_t *rev) +{ + u_char *p; + ssize_t n; + ngx_buf_t *b; + ngx_int_t rc; + ngx_connection_t *c; + ngx_http_request_t *r; + ngx_http_v3_session_t *h3c; + ngx_http_core_srv_conf_t *cscf; + ngx_http_v3_parse_headers_t *st; + + c = rev->data; + r = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http3 process request"); + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + c->timedout = 1; + ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT); + return; + } + + h3c = ngx_http_v3_get_session(c); + + st = &r->v3_parse->headers; + + b = r->header_in; + + for ( ;; ) { + + if (b->pos == b->last) { + + if (rev->ready) { + n = c->recv(c, b->start, b->end - b->start); + + } else { + n = NGX_AGAIN; + } + + if (n == NGX_AGAIN) { + if (!rev->timer_set) { + cscf = ngx_http_get_module_srv_conf(r, + ngx_http_core_module); + ngx_add_timer(rev, cscf->client_header_timeout); + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + break; + } + + if (n == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client prematurely closed connection"); + } + + if (n == 0 || n == NGX_ERROR) { + c->error = 1; + c->log->action = "reading client request"; + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + break; + } + + b->pos = b->start; + b->last = b->start + n; + } + + p = b->pos; + + rc = ngx_http_v3_parse_headers(c, st, b); + + if (rc > 0) { + ngx_quic_reset_stream(c, rc); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client sent invalid header"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + break; + } + + if (rc == NGX_ERROR) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + break; + } + + r->request_length += b->pos - p; + h3c->total_bytes += b->pos - p; + + if (ngx_http_v3_check_flood(c) != NGX_OK) { + ngx_http_close_request(r, NGX_HTTP_CLOSE); + break; + } + + if (rc == NGX_BUSY) { + if (rev->error) { + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + break; + } + + if (!rev->timer_set) { + cscf = ngx_http_get_module_srv_conf(r, + ngx_http_core_module); + ngx_add_timer(rev, cscf->client_header_timeout); + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + break; + } + + if (rc == NGX_AGAIN) { + continue; + } + + /* rc == NGX_OK || rc == NGX_DONE */ + + h3c->payload_bytes += ngx_http_v3_encode_field_l(NULL, + &st->field_rep.field.name, + &st->field_rep.field.value); + + if (ngx_http_v3_process_header(r, &st->field_rep.field.name, + &st->field_rep.field.value) + != NGX_OK) + { + break; + } + + if (rc == NGX_DONE) { + if (ngx_http_v3_process_request_header(r) != NGX_OK) { + break; + } + + ngx_http_process_request(r); + break; + } + } + + ngx_http_run_posted_requests(c); + + return; +} + + +static ngx_int_t +ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, + ngx_str_t *value) +{ + size_t len; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_srv_conf_t *cscf; + ngx_http_core_main_conf_t *cmcf; + + static ngx_str_t cookie = ngx_string("cookie"); + + len = name->len + value->len; + + if (len > r->v3_parse->header_limit) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent too large header"); + ngx_http_finalize_request(r, NGX_HTTP_REQUEST_HEADER_TOO_LARGE); + return NGX_ERROR; + } + + r->v3_parse->header_limit -= len; + + if (ngx_http_v3_validate_header(r, name, value) != NGX_OK) { + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + if (r->invalid_header) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + if (cscf->ignore_invalid_headers) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header: \"%V\"", name); + + return NGX_OK; + } + } + + if (name->len && name->data[0] == ':') { + return ngx_http_v3_process_pseudo_header(r, name, value); + } + + if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) { + return NGX_ERROR; + } + + if (name->len == cookie.len + && ngx_memcmp(name->data, cookie.data, cookie.len) == 0) + { + if (ngx_http_v3_cookie(r, value) != NGX_OK) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + } else { + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + h->key = *name; + h->value = *value; + h->lowcase_key = h->key.data; + h->hash = ngx_hash_key(h->key.data, h->key.len); + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { + return NGX_ERROR; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 header: \"%V: %V\"", name, value); + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_validate_header(ngx_http_request_t *r, ngx_str_t *name, + ngx_str_t *value) +{ + u_char ch; + ngx_uint_t i; + ngx_http_core_srv_conf_t *cscf; + + r->invalid_header = 0; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + for (i = (name->data[0] == ':'); i != name->len; i++) { + ch = name->data[i]; + + if ((ch >= 'a' && ch <= 'z') + || (ch == '-') + || (ch >= '0' && ch <= '9') + || (ch == '_' && cscf->underscores_in_headers)) + { + continue; + } + + if (ch <= 0x20 || ch == 0x7f || ch == ':' + || (ch >= 'A' && ch <= 'Z')) + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header name: \"%V\"", name); + + return NGX_ERROR; + } + + r->invalid_header = 1; + } + + for (i = 0; i != value->len; i++) { + ch = value->data[i]; + + if (ch == '\0' || ch == LF || ch == CR) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent header \"%V\" with " + "invalid value: \"%V\"", name, value); + + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, + ngx_str_t *value) +{ + u_char ch, c; + ngx_uint_t i; + + if (r->request_line.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent out of order pseudo-headers"); + goto failed; + } + + if (name->len == 7 && ngx_strncmp(name->data, ":method", 7) == 0) { + + if (r->method_name.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate \":method\" header"); + goto failed; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty \":method\" header"); + goto failed; + } + + r->method_name = *value; + + for (i = 0; i < sizeof(ngx_http_v3_methods) + / sizeof(ngx_http_v3_methods[0]); i++) + { + if (value->len == ngx_http_v3_methods[i].name.len + && ngx_strncmp(value->data, + ngx_http_v3_methods[i].name.data, value->len) + == 0) + { + r->method = ngx_http_v3_methods[i].method; + break; + } + } + + for (i = 0; i < value->len; i++) { + ch = value->data[i]; + + if ((ch < 'A' || ch > 'Z') && ch != '_' && ch != '-') { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid method: \"%V\"", value); + goto failed; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 method \"%V\" %ui", value, r->method); + return NGX_OK; + } + + if (name->len == 5 && ngx_strncmp(name->data, ":path", 5) == 0) { + + if (r->uri_start) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate \":path\" header"); + goto failed; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty \":path\" header"); + goto failed; + } + + r->uri_start = value->data; + r->uri_end = value->data + value->len; + + if (ngx_http_parse_uri(r) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid \":path\" header: \"%V\"", + value); + goto failed; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 path \"%V\"", value); + return NGX_OK; + } + + if (name->len == 7 && ngx_strncmp(name->data, ":scheme", 7) == 0) { + + if (r->schema.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate \":scheme\" header"); + goto failed; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty \":scheme\" header"); + goto failed; + } + + for (i = 0; i < value->len; i++) { + ch = value->data[i]; + + c = (u_char) (ch | 0x20); + if (c >= 'a' && c <= 'z') { + continue; + } + + if (((ch >= '0' && ch <= '9') + || ch == '+' || ch == '-' || ch == '.') + && i > 0) + { + continue; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid \":scheme\" header: \"%V\"", + value); + goto failed; + } + + r->schema = *value; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 schema \"%V\"", value); + return NGX_OK; + } + + if (name->len == 10 && ngx_strncmp(name->data, ":authority", 10) == 0) { + + if (r->host_start) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate \":authority\" header"); + goto failed; + } + + r->host_start = value->data; + r->host_end = value->data + value->len; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 authority \"%V\"", value); + return NGX_OK; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent unknown pseudo-header \"%V\"", name); + +failed: + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; +} + + +static ngx_int_t +ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r) +{ + size_t len; + u_char *p; + ngx_int_t rc; + ngx_str_t host; + + if (r->request_line.len) { + return NGX_OK; + } + + if (r->method_name.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no \":method\" header"); + goto failed; + } + + if (r->schema.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no \":scheme\" header"); + goto failed; + } + + if (r->uri_start == NULL) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no \":path\" header"); + goto failed; + } + + len = r->method_name.len + 1 + + (r->uri_end - r->uri_start) + 1 + + sizeof("HTTP/3.0") - 1; + + p = ngx_pnalloc(r->pool, len); + if (p == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + r->request_line.data = p; + + p = ngx_cpymem(p, r->method_name.data, r->method_name.len); + *p++ = ' '; + p = ngx_cpymem(p, r->uri_start, r->uri_end - r->uri_start); + *p++ = ' '; + p = ngx_cpymem(p, "HTTP/3.0", sizeof("HTTP/3.0") - 1); + + r->request_line.len = p - r->request_line.data; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 request line: \"%V\"", &r->request_line); + + ngx_str_set(&r->http_protocol, "HTTP/3.0"); + + if (ngx_http_process_request_uri(r) != NGX_OK) { + return NGX_ERROR; + } + + if (r->host_end) { + + host.len = r->host_end - r->host_start; + host.data = r->host_start; + + rc = ngx_http_validate_host(&host, r->pool, 0); + + if (rc == NGX_DECLINED) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid host in request line"); + goto failed; + } + + if (rc == NGX_ERROR) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) { + return NGX_ERROR; + } + + r->headers_in.server = host; + } + + if (ngx_list_init(&r->headers_in.headers, r->pool, 20, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + return NGX_OK; + +failed: + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; +} + + +static ngx_int_t +ngx_http_v3_process_request_header(ngx_http_request_t *r) +{ + ssize_t n; + ngx_buf_t *b; + ngx_connection_t *c; + ngx_http_v3_session_t *h3c; + ngx_http_v3_srv_conf_t *h3scf; + + c = r->connection; + + if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) { + return NGX_ERROR; + } + + h3c = ngx_http_v3_get_session(c); + h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); + + if ((h3c->hq && !h3scf->enable_hq) || (!h3c->hq && !h3scf->enable)) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client attempted to request the server name " + "for which the negotiated protocol is disabled"); + ngx_http_finalize_request(r, NGX_HTTP_MISDIRECTED_REQUEST); + return NGX_ERROR; + } + + if (ngx_http_v3_construct_cookie_header(r) != NGX_OK) { + return NGX_ERROR; + } + + if (r->headers_in.server.len == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent neither \":authority\" nor \"Host\" header"); + goto failed; + } + + if (r->headers_in.host) { + if (r->headers_in.host->value.len != r->headers_in.server.len + || ngx_memcmp(r->headers_in.host->value.data, + r->headers_in.server.data, + r->headers_in.server.len) + != 0) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent \":authority\" and \"Host\" headers " + "with different values"); + goto failed; + } + } + + if (r->headers_in.content_length) { + r->headers_in.content_length_n = + ngx_atoof(r->headers_in.content_length->value.data, + r->headers_in.content_length->value.len); + + if (r->headers_in.content_length_n == NGX_ERROR) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent invalid \"Content-Length\" header"); + goto failed; + } + + } else { + b = r->header_in; + n = b->last - b->pos; + + if (n == 0) { + n = c->recv(c, b->start, b->end - b->start); + + if (n == NGX_ERROR) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + if (n > 0) { + b->pos = b->start; + b->last = b->start + n; + } + } + + if (n != 0) { + r->headers_in.chunked = 1; + } + } + + if (r->method == NGX_HTTP_CONNECT) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent CONNECT method"); + ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED); + return NGX_ERROR; + } + + if (r->method == NGX_HTTP_TRACE) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent TRACE method"); + ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED); + return NGX_ERROR; + } + + return NGX_OK; + +failed: + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; +} + + +static ngx_int_t +ngx_http_v3_cookie(ngx_http_request_t *r, ngx_str_t *value) +{ + ngx_str_t *val; + ngx_array_t *cookies; + + cookies = r->v3_parse->cookies; + + if (cookies == NULL) { + cookies = ngx_array_create(r->pool, 2, sizeof(ngx_str_t)); + if (cookies == NULL) { + return NGX_ERROR; + } + + r->v3_parse->cookies = cookies; + } + + val = ngx_array_push(cookies); + if (val == NULL) { + return NGX_ERROR; + } + + *val = *value; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_construct_cookie_header(ngx_http_request_t *r) +{ + u_char *buf, *p, *end; + size_t len; + ngx_str_t *vals; + ngx_uint_t i; + ngx_array_t *cookies; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_main_conf_t *cmcf; + + static ngx_str_t cookie = ngx_string("cookie"); + + cookies = r->v3_parse->cookies; + + if (cookies == NULL) { + return NGX_OK; + } + + vals = cookies->elts; + + i = 0; + len = 0; + + do { + len += vals[i].len + 2; + } while (++i != cookies->nelts); + + len -= 2; + + buf = ngx_pnalloc(r->pool, len + 1); + if (buf == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + p = buf; + end = buf + len; + + for (i = 0; /* void */ ; i++) { + + p = ngx_cpymem(p, vals[i].data, vals[i].len); + + if (p == end) { + *p = '\0'; + break; + } + + *p++ = ';'; *p++ = ' '; + } + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash( + ngx_hash('c', 'o'), 'o'), 'k'), 'i'), 'e'); + + h->key.len = cookie.len; + h->key.data = cookie.data; + + h->value.len = len; + h->value.data = buf; + + h->lowcase_key = cookie.data; + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + if (hh->handler(r, h, hh->offset) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_multi_header_lines() + */ + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_read_request_body(ngx_http_request_t *r) +{ + size_t preread; + ngx_int_t rc; + ngx_chain_t *cl, out; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + rb = r->request_body; + + preread = r->header_in->last - r->header_in->pos; + + if (preread) { + + /* there is the pre-read part of the request body */ + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 client request body preread %uz", preread); + + out.buf = r->header_in; + out.next = NULL; + cl = &out; + + } else { + cl = NULL; + } + + rc = ngx_http_v3_request_body_filter(r, cl); + if (rc != NGX_OK) { + return rc; + } + + if (rb->rest == 0 && rb->last_saved) { + /* the whole request body was pre-read */ + r->request_body_no_buffering = 0; + rb->post_handler(r); + return NGX_OK; + } + + if (rb->rest < 0) { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + "negative request body rest"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + rb->buf = ngx_create_temp_buf(r->pool, clcf->client_body_buffer_size); + if (rb->buf == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + r->read_event_handler = ngx_http_v3_read_client_request_body_handler; + r->write_event_handler = ngx_http_request_empty_handler; + + return ngx_http_v3_do_read_client_request_body(r); +} + + +static void +ngx_http_v3_read_client_request_body_handler(ngx_http_request_t *r) +{ + ngx_int_t rc; + + if (r->connection->read->timedout) { + r->connection->timedout = 1; + ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT); + return; + } + + rc = ngx_http_v3_do_read_client_request_body(r); + + if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { + ngx_http_finalize_request(r, rc); + } +} + + +ngx_int_t +ngx_http_v3_read_unbuffered_request_body(ngx_http_request_t *r) +{ + ngx_int_t rc; + + if (r->connection->read->timedout) { + r->connection->timedout = 1; + return NGX_HTTP_REQUEST_TIME_OUT; + } + + rc = ngx_http_v3_do_read_client_request_body(r); + + if (rc == NGX_OK) { + r->reading_body = 0; + } + + return rc; +} + + +static ngx_int_t +ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r) +{ + off_t rest; + size_t size; + ssize_t n; + ngx_int_t rc; + ngx_uint_t flush; + ngx_chain_t out; + ngx_connection_t *c; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + c = r->connection; + rb = r->request_body; + flush = 1; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 read client request body"); + + for ( ;; ) { + for ( ;; ) { + if (rb->rest == 0) { + break; + } + + if (rb->buf->last == rb->buf->end) { + + /* update chains */ + + rc = ngx_http_v3_request_body_filter(r, NULL); + + if (rc != NGX_OK) { + return rc; + } + + if (rb->busy != NULL) { + if (r->request_body_no_buffering) { + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + return NGX_AGAIN; + } + + if (rb->filter_need_buffering) { + clcf = ngx_http_get_module_loc_conf(r, + ngx_http_core_module); + ngx_add_timer(c->read, clcf->client_body_timeout); + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + return NGX_AGAIN; + } + + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "busy buffers after request body flush"); + + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + flush = 0; + rb->buf->pos = rb->buf->start; + rb->buf->last = rb->buf->start; + } + + size = rb->buf->end - rb->buf->last; + rest = rb->rest - (rb->buf->last - rb->buf->pos); + + if ((off_t) size > rest) { + size = (size_t) rest; + } + + if (size == 0) { + break; + } + + n = c->recv(c, rb->buf->last, size); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 client request body recv %z", n); + + if (n == NGX_AGAIN) { + break; + } + + if (n == 0) { + rb->buf->last_buf = 1; + } + + if (n == NGX_ERROR) { + c->error = 1; + return NGX_HTTP_BAD_REQUEST; + } + + rb->buf->last += n; + + /* pass buffer to request body filter chain */ + + flush = 0; + out.buf = rb->buf; + out.next = NULL; + + rc = ngx_http_v3_request_body_filter(r, &out); + + if (rc != NGX_OK) { + return rc; + } + + if (rb->rest == 0) { + break; + } + + if (rb->buf->last < rb->buf->end) { + break; + } + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 client request body rest %O", rb->rest); + + if (flush) { + rc = ngx_http_v3_request_body_filter(r, NULL); + + if (rc != NGX_OK) { + return rc; + } + } + + if (rb->rest == 0 && rb->last_saved) { + break; + } + + if (!c->read->ready || rb->rest == 0) { + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + ngx_add_timer(c->read, clcf->client_body_timeout); + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + return NGX_AGAIN; + } + } + + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + + if (!r->request_body_no_buffering) { + r->read_event_handler = ngx_http_block_reading; + rb->post_handler(r); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) +{ + off_t max; + size_t size; + u_char *p; + ngx_int_t rc; + ngx_buf_t *b; + ngx_uint_t last; + ngx_chain_t *cl, *out, *tl, **ll; + ngx_http_v3_session_t *h3c; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + ngx_http_v3_parse_data_t *st; + + rb = r->request_body; + st = &r->v3_parse->body; + + h3c = ngx_http_v3_get_session(r->connection); + + if (rb->rest == -1) { + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 request body filter"); + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + rb->rest = cscf->large_client_header_buffers.size; + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + max = r->headers_in.content_length_n; + + if (max == -1 && clcf->client_max_body_size) { + max = clcf->client_max_body_size; + } + + out = NULL; + ll = &out; + last = 0; + + for (cl = in; cl; cl = cl->next) { + + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0, + "http3 body buf " + "t:%d f:%d %p, pos %p, size: %z file: %O, size: %O", + cl->buf->temporary, cl->buf->in_file, + cl->buf->start, cl->buf->pos, + cl->buf->last - cl->buf->pos, + cl->buf->file_pos, + cl->buf->file_last - cl->buf->file_pos); + + if (cl->buf->last_buf) { + last = 1; + } + + b = NULL; + + while (cl->buf->pos < cl->buf->last) { + + if (st->length == 0) { + p = cl->buf->pos; + + rc = ngx_http_v3_parse_data(r->connection, st, cl->buf); + + r->request_length += cl->buf->pos - p; + h3c->total_bytes += cl->buf->pos - p; + + if (ngx_http_v3_check_flood(r->connection) != NGX_OK) { + return NGX_HTTP_CLOSE; + } + + if (rc == NGX_AGAIN) { + continue; + } + + if (rc == NGX_DONE) { + last = 1; + goto done; + } + + if (rc > 0) { + ngx_quic_reset_stream(r->connection, rc); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client sent invalid body"); + return NGX_HTTP_BAD_REQUEST; + } + + if (rc == NGX_ERROR) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + /* rc == NGX_OK */ + + if (max != -1 && (uint64_t) (max - rb->received) < st->length) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client intended to send too large " + "body: %O+%ui bytes", + rb->received, st->length); + + return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE; + } + + continue; + } + + if (b + && st->length <= 128 + && (uint64_t) (cl->buf->last - cl->buf->pos) >= st->length) + { + rb->received += st->length; + r->request_length += st->length; + h3c->total_bytes += st->length; + h3c->payload_bytes += st->length; + + if (st->length < 8) { + + while (st->length) { + *b->last++ = *cl->buf->pos++; + st->length--; + } + + } else { + ngx_memmove(b->last, cl->buf->pos, st->length); + b->last += st->length; + cl->buf->pos += st->length; + st->length = 0; + } + + continue; + } + + tl = ngx_chain_get_free_buf(r->pool, &rb->free); + if (tl == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + b = tl->buf; + + ngx_memzero(b, sizeof(ngx_buf_t)); + + b->temporary = 1; + b->tag = (ngx_buf_tag_t) &ngx_http_read_client_request_body; + b->start = cl->buf->pos; + b->pos = cl->buf->pos; + b->last = cl->buf->last; + b->end = cl->buf->end; + b->flush = r->request_body_no_buffering; + + *ll = tl; + ll = &tl->next; + + size = cl->buf->last - cl->buf->pos; + + if (size > st->length) { + cl->buf->pos += (size_t) st->length; + rb->received += st->length; + r->request_length += st->length; + h3c->total_bytes += st->length; + h3c->payload_bytes += st->length; + st->length = 0; + + } else { + st->length -= size; + rb->received += size; + r->request_length += size; + h3c->total_bytes += size; + h3c->payload_bytes += size; + cl->buf->pos = cl->buf->last; + } + + b->last = cl->buf->pos; + } + } + +done: + + if (last) { + + if (st->length > 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client prematurely closed stream"); + r->connection->error = 1; + return NGX_HTTP_BAD_REQUEST; + } + + if (r->headers_in.content_length_n == -1) { + r->headers_in.content_length_n = rb->received; + + } else if (r->headers_in.content_length_n != rb->received) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent less body data than expected: " + "%O out of %O bytes of request body received", + rb->received, r->headers_in.content_length_n); + return NGX_HTTP_BAD_REQUEST; + } + + rb->rest = 0; + + tl = ngx_chain_get_free_buf(r->pool, &rb->free); + if (tl == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + b = tl->buf; + + ngx_memzero(b, sizeof(ngx_buf_t)); + + b->last_buf = 1; + + *ll = tl; + + } else { + + /* set rb->rest, amount of data we want to see next time */ + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + rb->rest = (off_t) cscf->large_client_header_buffers.size; + } + + rc = ngx_http_top_request_body_filter(r, out); + + ngx_chain_update_chains(r->pool, &rb->free, &rb->busy, &out, + (ngx_buf_tag_t) &ngx_http_read_client_request_body); + + return rc; +} diff --git a/src/deps/src/nginx/src/http/v3/ngx_http_v3_table.c b/src/deps/src/nginx/src/http/v3/ngx_http_v3_table.c new file mode 100644 index 000000000..f49a8fc5e --- /dev/null +++ b/src/deps/src/nginx/src/http/v3/ngx_http_v3_table.c @@ -0,0 +1,715 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#define ngx_http_v3_table_entry_size(n, v) ((n)->len + (v)->len + 32) + + +static ngx_int_t ngx_http_v3_evict(ngx_connection_t *c, size_t target); +static void ngx_http_v3_unblock(void *data); +static ngx_int_t ngx_http_v3_new_entry(ngx_connection_t *c); + + +typedef struct { + ngx_queue_t queue; + ngx_connection_t *connection; + ngx_uint_t *nblocked; +} ngx_http_v3_block_t; + + +static ngx_http_v3_field_t ngx_http_v3_static_table[] = { + + { ngx_string(":authority"), ngx_string("") }, + { ngx_string(":path"), ngx_string("/") }, + { ngx_string("age"), ngx_string("0") }, + { ngx_string("content-disposition"), ngx_string("") }, + { ngx_string("content-length"), ngx_string("0") }, + { ngx_string("cookie"), ngx_string("") }, + { ngx_string("date"), ngx_string("") }, + { ngx_string("etag"), ngx_string("") }, + { ngx_string("if-modified-since"), ngx_string("") }, + { ngx_string("if-none-match"), ngx_string("") }, + { ngx_string("last-modified"), ngx_string("") }, + { ngx_string("link"), ngx_string("") }, + { ngx_string("location"), ngx_string("") }, + { ngx_string("referer"), ngx_string("") }, + { ngx_string("set-cookie"), ngx_string("") }, + { ngx_string(":method"), ngx_string("CONNECT") }, + { ngx_string(":method"), ngx_string("DELETE") }, + { ngx_string(":method"), ngx_string("GET") }, + { ngx_string(":method"), ngx_string("HEAD") }, + { ngx_string(":method"), ngx_string("OPTIONS") }, + { ngx_string(":method"), ngx_string("POST") }, + { ngx_string(":method"), ngx_string("PUT") }, + { ngx_string(":scheme"), ngx_string("http") }, + { ngx_string(":scheme"), ngx_string("https") }, + { ngx_string(":status"), ngx_string("103") }, + { ngx_string(":status"), ngx_string("200") }, + { ngx_string(":status"), ngx_string("304") }, + { ngx_string(":status"), ngx_string("404") }, + { ngx_string(":status"), ngx_string("503") }, + { ngx_string("accept"), ngx_string("*/*") }, + { ngx_string("accept"), + ngx_string("application/dns-message") }, + { ngx_string("accept-encoding"), ngx_string("gzip, deflate, br") }, + { ngx_string("accept-ranges"), ngx_string("bytes") }, + { ngx_string("access-control-allow-headers"), + ngx_string("cache-control") }, + { ngx_string("access-control-allow-headers"), + ngx_string("content-type") }, + { ngx_string("access-control-allow-origin"), + ngx_string("*") }, + { ngx_string("cache-control"), ngx_string("max-age=0") }, + { ngx_string("cache-control"), ngx_string("max-age=2592000") }, + { ngx_string("cache-control"), ngx_string("max-age=604800") }, + { ngx_string("cache-control"), ngx_string("no-cache") }, + { ngx_string("cache-control"), ngx_string("no-store") }, + { ngx_string("cache-control"), + ngx_string("public, max-age=31536000") }, + { ngx_string("content-encoding"), ngx_string("br") }, + { ngx_string("content-encoding"), ngx_string("gzip") }, + { ngx_string("content-type"), + ngx_string("application/dns-message") }, + { ngx_string("content-type"), + ngx_string("application/javascript") }, + { ngx_string("content-type"), ngx_string("application/json") }, + { ngx_string("content-type"), + ngx_string("application/x-www-form-urlencoded") }, + { ngx_string("content-type"), ngx_string("image/gif") }, + { ngx_string("content-type"), ngx_string("image/jpeg") }, + { ngx_string("content-type"), ngx_string("image/png") }, + { ngx_string("content-type"), ngx_string("text/css") }, + { ngx_string("content-type"), + ngx_string("text/html;charset=utf-8") }, + { ngx_string("content-type"), ngx_string("text/plain") }, + { ngx_string("content-type"), + ngx_string("text/plain;charset=utf-8") }, + { ngx_string("range"), ngx_string("bytes=0-") }, + { ngx_string("strict-transport-security"), + ngx_string("max-age=31536000") }, + { ngx_string("strict-transport-security"), + ngx_string("max-age=31536000;includesubdomains") }, + { ngx_string("strict-transport-security"), + ngx_string("max-age=31536000;includesubdomains;preload") }, + { ngx_string("vary"), ngx_string("accept-encoding") }, + { ngx_string("vary"), ngx_string("origin") }, + { ngx_string("x-content-type-options"), + ngx_string("nosniff") }, + { ngx_string("x-xss-protection"), ngx_string("1;mode=block") }, + { ngx_string(":status"), ngx_string("100") }, + { ngx_string(":status"), ngx_string("204") }, + { ngx_string(":status"), ngx_string("206") }, + { ngx_string(":status"), ngx_string("302") }, + { ngx_string(":status"), ngx_string("400") }, + { ngx_string(":status"), ngx_string("403") }, + { ngx_string(":status"), ngx_string("421") }, + { ngx_string(":status"), ngx_string("425") }, + { ngx_string(":status"), ngx_string("500") }, + { ngx_string("accept-language"), ngx_string("") }, + { ngx_string("access-control-allow-credentials"), + ngx_string("FALSE") }, + { ngx_string("access-control-allow-credentials"), + ngx_string("TRUE") }, + { ngx_string("access-control-allow-headers"), + ngx_string("*") }, + { ngx_string("access-control-allow-methods"), + ngx_string("get") }, + { ngx_string("access-control-allow-methods"), + ngx_string("get, post, options") }, + { ngx_string("access-control-allow-methods"), + ngx_string("options") }, + { ngx_string("access-control-expose-headers"), + ngx_string("content-length") }, + { ngx_string("access-control-request-headers"), + ngx_string("content-type") }, + { ngx_string("access-control-request-method"), + ngx_string("get") }, + { ngx_string("access-control-request-method"), + ngx_string("post") }, + { ngx_string("alt-svc"), ngx_string("clear") }, + { ngx_string("authorization"), ngx_string("") }, + { ngx_string("content-security-policy"), + ngx_string("script-src 'none';object-src 'none';base-uri 'none'") }, + { ngx_string("early-data"), ngx_string("1") }, + { ngx_string("expect-ct"), ngx_string("") }, + { ngx_string("forwarded"), ngx_string("") }, + { ngx_string("if-range"), ngx_string("") }, + { ngx_string("origin"), ngx_string("") }, + { ngx_string("purpose"), ngx_string("prefetch") }, + { ngx_string("server"), ngx_string("") }, + { ngx_string("timing-allow-origin"), ngx_string("*") }, + { ngx_string("upgrade-insecure-requests"), + ngx_string("1") }, + { ngx_string("user-agent"), ngx_string("") }, + { ngx_string("x-forwarded-for"), ngx_string("") }, + { ngx_string("x-frame-options"), ngx_string("deny") }, + { ngx_string("x-frame-options"), ngx_string("sameorigin") } +}; + + +ngx_int_t +ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, + ngx_uint_t index, ngx_str_t *value) +{ + ngx_str_t name; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + if (dynamic) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 ref insert dynamic[%ui] \"%V\"", index, value); + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (dt->base + dt->nelts <= index) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + index = dt->base + dt->nelts - 1 - index; + + if (ngx_http_v3_lookup(c, index, &name, NULL) != NGX_OK) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + } else { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 ref insert static[%ui] \"%V\"", index, value); + + if (ngx_http_v3_lookup_static(c, index, &name, NULL) != NGX_OK) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + } + + return ngx_http_v3_insert(c, &name, value); +} + + +ngx_int_t +ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value) +{ + u_char *p; + size_t size; + ngx_http_v3_field_t *field; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + size = ngx_http_v3_table_entry_size(name, value); + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (size > dt->capacity) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "not enough dynamic table capacity"); + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 insert [%ui] \"%V\":\"%V\", size:%uz", + dt->base + dt->nelts, name, value, size); + + p = ngx_alloc(sizeof(ngx_http_v3_field_t) + name->len + value->len, + c->log); + if (p == NULL) { + return NGX_ERROR; + } + + field = (ngx_http_v3_field_t *) p; + + field->name.data = p + sizeof(ngx_http_v3_field_t); + field->name.len = name->len; + field->value.data = ngx_cpymem(field->name.data, name->data, name->len); + field->value.len = value->len; + ngx_memcpy(field->value.data, value->data, value->len); + + dt->elts[dt->nelts++] = field; + dt->size += size; + + dt->insert_count++; + + if (ngx_http_v3_evict(c, dt->capacity) != NGX_OK) { + return NGX_ERROR; + } + + ngx_post_event(&dt->send_insert_count, &ngx_posted_events); + + if (ngx_http_v3_new_entry(c) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +void +ngx_http_v3_inc_insert_count_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + c = ev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 inc insert count handler"); + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (dt->insert_count > dt->ack_insert_count) { + if (ngx_http_v3_send_inc_insert_count(c, + dt->insert_count - dt->ack_insert_count) + != NGX_OK) + { + return; + } + + dt->ack_insert_count = dt->insert_count; + } +} + + +ngx_int_t +ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) +{ + ngx_uint_t max, prev_max; + ngx_http_v3_field_t **elts; + ngx_http_v3_session_t *h3c; + ngx_http_v3_srv_conf_t *h3scf; + ngx_http_v3_dynamic_table_t *dt; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 set capacity %ui", capacity); + + h3c = ngx_http_v3_get_session(c); + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + + if (capacity > h3scf->max_table_capacity) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client exceeded http3_max_table_capacity limit"); + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + if (ngx_http_v3_evict(c, capacity) != NGX_OK) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + dt = &h3c->table; + max = capacity / 32; + prev_max = dt->capacity / 32; + + if (max > prev_max) { + elts = ngx_alloc(max * sizeof(void *), c->log); + if (elts == NULL) { + return NGX_ERROR; + } + + if (dt->elts) { + ngx_memcpy(elts, dt->elts, dt->nelts * sizeof(void *)); + ngx_free(dt->elts); + } + + dt->elts = elts; + } + + dt->capacity = capacity; + + return NGX_OK; +} + + +void +ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c) +{ + ngx_uint_t n; + ngx_http_v3_dynamic_table_t *dt; + + dt = &h3c->table; + + if (dt->elts == NULL) { + return; + } + + for (n = 0; n < dt->nelts; n++) { + ngx_free(dt->elts[n]); + } + + ngx_free(dt->elts); +} + + +static ngx_int_t +ngx_http_v3_evict(ngx_connection_t *c, size_t target) +{ + size_t size; + ngx_uint_t n; + ngx_http_v3_field_t *field; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + n = 0; + + while (dt->size > target) { + field = dt->elts[n++]; + size = ngx_http_v3_table_entry_size(&field->name, &field->value); + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 evict [%ui] \"%V\":\"%V\" size:%uz", + dt->base, &field->name, &field->value, size); + + ngx_free(field); + dt->size -= size; + } + + if (n) { + dt->nelts -= n; + dt->base += n; + ngx_memmove(dt->elts, &dt->elts[n], dt->nelts * sizeof(void *)); + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index) +{ + ngx_str_t name, value; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 duplicate %ui", index); + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (dt->base + dt->nelts <= index) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + index = dt->base + dt->nelts - 1 - index; + + if (ngx_http_v3_lookup(c, index, &name, &value) != NGX_OK) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + return ngx_http_v3_insert(c, &name, &value); +} + + +ngx_int_t +ngx_http_v3_ack_section(ngx_connection_t *c, ngx_uint_t stream_id) +{ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 ack section %ui", stream_id); + + /* we do not use dynamic tables */ + + return NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR; +} + + +ngx_int_t +ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) +{ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 increment insert count %ui", inc); + + /* we do not use dynamic tables */ + + return NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR; +} + + +ngx_int_t +ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index, + ngx_str_t *name, ngx_str_t *value) +{ + ngx_uint_t nelts; + ngx_http_v3_field_t *field; + + nelts = sizeof(ngx_http_v3_static_table) + / sizeof(ngx_http_v3_static_table[0]); + + if (index >= nelts) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 static[%ui] lookup out of bounds: %ui", + index, nelts); + return NGX_ERROR; + } + + field = &ngx_http_v3_static_table[index]; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 static[%ui] lookup \"%V\":\"%V\"", + index, &field->name, &field->value); + + if (name) { + *name = field->name; + } + + if (value) { + *value = field->value; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, ngx_str_t *name, + ngx_str_t *value) +{ + ngx_http_v3_field_t *field; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (index < dt->base || index - dt->base >= dt->nelts) { + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 dynamic[%ui] lookup out of bounds: [%ui,%ui]", + index, dt->base, dt->base + dt->nelts); + return NGX_ERROR; + } + + field = dt->elts[index - dt->base]; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 dynamic[%ui] lookup \"%V\":\"%V\"", + index, &field->name, &field->value); + + if (name) { + *name = field->name; + } + + if (value) { + *value = field->value; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_decode_insert_count(ngx_connection_t *c, ngx_uint_t *insert_count) +{ + ngx_uint_t max_entries, full_range, max_value, + max_wrapped, req_insert_count; + ngx_http_v3_srv_conf_t *h3scf; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + /* QPACK 4.5.1.1. Required Insert Count */ + + if (*insert_count == 0) { + return NGX_OK; + } + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + + max_entries = h3scf->max_table_capacity / 32; + full_range = 2 * max_entries; + + if (*insert_count > full_range) { + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + max_value = dt->base + dt->nelts + max_entries; + max_wrapped = (max_value / full_range) * full_range; + req_insert_count = max_wrapped + *insert_count - 1; + + if (req_insert_count > max_value) { + if (req_insert_count <= full_range) { + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + req_insert_count -= full_range; + } + + if (req_insert_count == 0) { + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 decode insert_count %ui -> %ui", + *insert_count, req_insert_count); + + *insert_count = req_insert_count; + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count) +{ + size_t n; + ngx_pool_cleanup_t *cln; + ngx_http_v3_block_t *block; + ngx_http_v3_session_t *h3c; + ngx_http_v3_srv_conf_t *h3scf; + ngx_http_v3_dynamic_table_t *dt; + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + n = dt->base + dt->nelts; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 check insert count req:%ui, have:%ui", + insert_count, n); + + if (n >= insert_count) { + return NGX_OK; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 block stream"); + + block = NULL; + + for (cln = c->pool->cleanup; cln; cln = cln->next) { + if (cln->handler == ngx_http_v3_unblock) { + block = cln->data; + break; + } + } + + if (block == NULL) { + cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_http_v3_block_t)); + if (cln == NULL) { + return NGX_ERROR; + } + + cln->handler = ngx_http_v3_unblock; + + block = cln->data; + block->queue.prev = NULL; + block->connection = c; + block->nblocked = &h3c->nblocked; + } + + if (block->queue.prev == NULL) { + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + + if (h3c->nblocked == h3scf->max_blocked_streams) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client exceeded http3_max_blocked_streams limit"); + + ngx_http_v3_finalize_connection(c, + NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED, + "too many blocked streams"); + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + h3c->nblocked++; + ngx_queue_insert_tail(&h3c->blocked, &block->queue); + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 blocked:%ui", h3c->nblocked); + + return NGX_BUSY; +} + + +void +ngx_http_v3_ack_insert_count(ngx_connection_t *c, uint64_t insert_count) +{ + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (dt->ack_insert_count < insert_count) { + dt->ack_insert_count = insert_count; + } +} + + +static void +ngx_http_v3_unblock(void *data) +{ + ngx_http_v3_block_t *block = data; + + if (block->queue.prev) { + ngx_queue_remove(&block->queue); + block->queue.prev = NULL; + (*block->nblocked)--; + } +} + + +static ngx_int_t +ngx_http_v3_new_entry(ngx_connection_t *c) +{ + ngx_queue_t *q; + ngx_connection_t *bc; + ngx_http_v3_block_t *block; + ngx_http_v3_session_t *h3c; + + h3c = ngx_http_v3_get_session(c); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 new dynamic entry, blocked:%ui", h3c->nblocked); + + while (!ngx_queue_empty(&h3c->blocked)) { + q = ngx_queue_head(&h3c->blocked); + block = (ngx_http_v3_block_t *) q; + bc = block->connection; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, bc->log, 0, "http3 unblock stream"); + + ngx_http_v3_unblock(block); + ngx_post_event(bc->read, &ngx_posted_events); + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, uint64_t value) +{ + switch (id) { + + case NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY: + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 param QPACK_MAX_TABLE_CAPACITY:%uL", value); + break; + + case NGX_HTTP_V3_PARAM_MAX_FIELD_SECTION_SIZE: + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 param SETTINGS_MAX_FIELD_SECTION_SIZE:%uL", + value); + break; + + case NGX_HTTP_V3_PARAM_BLOCKED_STREAMS: + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 param QPACK_BLOCKED_STREAMS:%uL", value); + break; + + default: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 param #%uL:%uL", id, value); + } + + return NGX_OK; +} diff --git a/src/deps/src/nginx/src/http/v3/ngx_http_v3_table.h b/src/deps/src/nginx/src/http/v3/ngx_http_v3_table.h new file mode 100644 index 000000000..1c2fb17b9 --- /dev/null +++ b/src/deps/src/nginx/src/http/v3/ngx_http_v3_table.h @@ -0,0 +1,58 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_V3_TABLE_H_INCLUDED_ +#define _NGX_HTTP_V3_TABLE_H_INCLUDED_ + + +#include +#include +#include + + +typedef struct { + ngx_str_t name; + ngx_str_t value; +} ngx_http_v3_field_t; + + +typedef struct { + ngx_http_v3_field_t **elts; + ngx_uint_t nelts; + ngx_uint_t base; + size_t size; + size_t capacity; + uint64_t insert_count; + uint64_t ack_insert_count; + ngx_event_t send_insert_count; +} ngx_http_v3_dynamic_table_t; + + +void ngx_http_v3_inc_insert_count_handler(ngx_event_t *ev); +void ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c); +ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, + ngx_uint_t index, ngx_str_t *value); +ngx_int_t ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, + ngx_str_t *value); +ngx_int_t ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity); +ngx_int_t ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index); +ngx_int_t ngx_http_v3_ack_section(ngx_connection_t *c, ngx_uint_t stream_id); +ngx_int_t ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc); +ngx_int_t ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index, + ngx_str_t *name, ngx_str_t *value); +ngx_int_t ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, + ngx_str_t *name, ngx_str_t *value); +ngx_int_t ngx_http_v3_decode_insert_count(ngx_connection_t *c, + ngx_uint_t *insert_count); +ngx_int_t ngx_http_v3_check_insert_count(ngx_connection_t *c, + ngx_uint_t insert_count); +void ngx_http_v3_ack_insert_count(ngx_connection_t *c, uint64_t insert_count); +ngx_int_t ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, + uint64_t value); + + +#endif /* _NGX_HTTP_V3_TABLE_H_INCLUDED_ */ diff --git a/src/deps/src/nginx/src/http/v3/ngx_http_v3_uni.c b/src/deps/src/nginx/src/http/v3/ngx_http_v3_uni.c new file mode 100644 index 000000000..2fc5b07a4 --- /dev/null +++ b/src/deps/src/nginx/src/http/v3/ngx_http_v3_uni.c @@ -0,0 +1,624 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +typedef struct { + ngx_http_v3_parse_uni_t parse; + ngx_int_t index; +} ngx_http_v3_uni_stream_t; + + +static void ngx_http_v3_close_uni_stream(ngx_connection_t *c); +static void ngx_http_v3_uni_read_handler(ngx_event_t *rev); +static void ngx_http_v3_uni_dummy_read_handler(ngx_event_t *wev); +static void ngx_http_v3_uni_dummy_write_handler(ngx_event_t *wev); +static ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c, + ngx_uint_t type); + + +void +ngx_http_v3_init_uni_stream(ngx_connection_t *c) +{ + uint64_t n; + ngx_http_v3_session_t *h3c; + ngx_http_v3_uni_stream_t *us; + + h3c = ngx_http_v3_get_session(c); + if (h3c->hq) { + ngx_http_v3_finalize_connection(c, + NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, + "uni stream in hq mode"); + c->data = NULL; + ngx_http_v3_close_uni_stream(c); + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init uni stream"); + + n = c->quic->id >> 2; + + if (n >= NGX_HTTP_V3_MAX_UNI_STREAMS) { + ngx_http_v3_finalize_connection(c, + NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, + "reached maximum number of uni streams"); + c->data = NULL; + ngx_http_v3_close_uni_stream(c); + return; + } + + ngx_quic_cancelable_stream(c); + + us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t)); + if (us == NULL) { + ngx_http_v3_finalize_connection(c, + NGX_HTTP_V3_ERR_INTERNAL_ERROR, + "memory allocation error"); + c->data = NULL; + ngx_http_v3_close_uni_stream(c); + return; + } + + us->index = -1; + + c->data = us; + + c->read->handler = ngx_http_v3_uni_read_handler; + c->write->handler = ngx_http_v3_uni_dummy_write_handler; + + ngx_http_v3_uni_read_handler(c->read); +} + + +static void +ngx_http_v3_close_uni_stream(ngx_connection_t *c) +{ + ngx_pool_t *pool; + ngx_http_v3_session_t *h3c; + ngx_http_v3_uni_stream_t *us; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 close stream"); + + us = c->data; + + if (us && us->index >= 0) { + h3c = ngx_http_v3_get_session(c); + h3c->known_streams[us->index] = NULL; + } + + c->destroyed = 1; + + pool = c->pool; + + ngx_close_connection(c); + + ngx_destroy_pool(pool); +} + + +ngx_int_t +ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type) +{ + ngx_int_t index; + ngx_http_v3_session_t *h3c; + ngx_http_v3_uni_stream_t *us; + + h3c = ngx_http_v3_get_session(c); + + switch (type) { + + case NGX_HTTP_V3_STREAM_ENCODER: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 encoder stream"); + index = NGX_HTTP_V3_STREAM_CLIENT_ENCODER; + break; + + case NGX_HTTP_V3_STREAM_DECODER: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 decoder stream"); + index = NGX_HTTP_V3_STREAM_CLIENT_DECODER; + break; + + case NGX_HTTP_V3_STREAM_CONTROL: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 control stream"); + index = NGX_HTTP_V3_STREAM_CLIENT_CONTROL; + + break; + + default: + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 stream 0x%02xL", type); + + if (h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_ENCODER] == NULL + || h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_DECODER] == NULL + || h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_CONTROL] == NULL) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "missing mandatory stream"); + return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR; + } + + index = -1; + } + + if (index >= 0) { + if (h3c->known_streams[index]) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "stream exists"); + return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR; + } + + h3c->known_streams[index] = c; + + us = c->data; + us->index = index; + } + + return NGX_OK; +} + + +static void +ngx_http_v3_uni_read_handler(ngx_event_t *rev) +{ + u_char buf[128]; + ssize_t n; + ngx_buf_t b; + ngx_int_t rc; + ngx_connection_t *c; + ngx_http_v3_session_t *h3c; + ngx_http_v3_uni_stream_t *us; + + c = rev->data; + us = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read handler"); + + if (c->close) { + ngx_http_v3_close_uni_stream(c); + return; + } + + ngx_memzero(&b, sizeof(ngx_buf_t)); + + while (rev->ready) { + + n = c->recv(c, buf, sizeof(buf)); + + if (n == NGX_ERROR) { + rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR; + goto failed; + } + + if (n == 0) { + if (us->index >= 0) { + rc = NGX_HTTP_V3_ERR_CLOSED_CRITICAL_STREAM; + goto failed; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read eof"); + ngx_http_v3_close_uni_stream(c); + return; + } + + if (n == NGX_AGAIN) { + break; + } + + b.pos = buf; + b.last = buf + n; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (ngx_http_v3_check_flood(c) != NGX_OK) { + ngx_http_v3_close_uni_stream(c); + return; + } + + rc = ngx_http_v3_parse_uni(c, &us->parse, &b); + + if (rc == NGX_DONE) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 read done"); + ngx_http_v3_close_uni_stream(c); + return; + } + + if (rc > 0) { + goto failed; + } + + if (rc != NGX_AGAIN) { + rc = NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR; + goto failed; + } + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR; + goto failed; + } + + return; + +failed: + + ngx_http_v3_finalize_connection(c, rc, "stream error"); + ngx_http_v3_close_uni_stream(c); +} + + +static void +ngx_http_v3_uni_dummy_read_handler(ngx_event_t *rev) +{ + u_char ch; + ngx_connection_t *c; + + c = rev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy read handler"); + + if (c->close) { + ngx_http_v3_close_uni_stream(c); + return; + } + + if (rev->ready) { + if (c->recv(c, &ch, 1) != 0) { + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, NULL); + ngx_http_v3_close_uni_stream(c); + return; + } + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, + NULL); + ngx_http_v3_close_uni_stream(c); + } +} + + +static void +ngx_http_v3_uni_dummy_write_handler(ngx_event_t *wev) +{ + ngx_connection_t *c; + + c = wev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy write handler"); + + if (ngx_handle_write_event(wev, 0) != NGX_OK) { + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, + NULL); + ngx_http_v3_close_uni_stream(c); + } +} + + +static ngx_connection_t * +ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) +{ + u_char buf[NGX_HTTP_V3_VARLEN_INT_LEN]; + size_t n; + ngx_int_t index; + ngx_connection_t *sc; + ngx_http_v3_session_t *h3c; + ngx_http_v3_uni_stream_t *us; + + switch (type) { + case NGX_HTTP_V3_STREAM_ENCODER: + index = NGX_HTTP_V3_STREAM_SERVER_ENCODER; + break; + case NGX_HTTP_V3_STREAM_DECODER: + index = NGX_HTTP_V3_STREAM_SERVER_DECODER; + break; + case NGX_HTTP_V3_STREAM_CONTROL: + index = NGX_HTTP_V3_STREAM_SERVER_CONTROL; + break; + default: + index = -1; + } + + h3c = ngx_http_v3_get_session(c); + + if (index >= 0) { + if (h3c->known_streams[index]) { + return h3c->known_streams[index]; + } + } + + sc = ngx_quic_open_stream(c, 0); + if (sc == NULL) { + goto failed; + } + + ngx_quic_cancelable_stream(sc); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 create uni stream, type:%ui", type); + + us = ngx_pcalloc(sc->pool, sizeof(ngx_http_v3_uni_stream_t)); + if (us == NULL) { + goto failed; + } + + us->index = index; + + sc->data = us; + + sc->read->handler = ngx_http_v3_uni_dummy_read_handler; + sc->write->handler = ngx_http_v3_uni_dummy_write_handler; + + if (index >= 0) { + h3c->known_streams[index] = sc; + } + + n = (u_char *) ngx_http_v3_encode_varlen_int(buf, type) - buf; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (sc->send(sc, buf, n) != (ssize_t) n) { + goto failed; + } + + ngx_post_event(sc->read, &ngx_posted_events); + + return sc; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create server stream"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, + "failed to create server stream"); + if (sc) { + ngx_http_v3_close_uni_stream(sc); + } + + return NULL; +} + + +ngx_int_t +ngx_http_v3_send_settings(ngx_connection_t *c) +{ + u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 6]; + size_t n; + ngx_connection_t *cc; + ngx_http_v3_session_t *h3c; + ngx_http_v3_srv_conf_t *h3scf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send settings"); + + cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL); + if (cc == NULL) { + return NGX_ERROR; + } + + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + + n = ngx_http_v3_encode_varlen_int(NULL, + NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY); + n += ngx_http_v3_encode_varlen_int(NULL, h3scf->max_table_capacity); + n += ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_PARAM_BLOCKED_STREAMS); + n += ngx_http_v3_encode_varlen_int(NULL, h3scf->max_blocked_streams); + + p = (u_char *) ngx_http_v3_encode_varlen_int(buf, + NGX_HTTP_V3_FRAME_SETTINGS); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, n); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, + NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->max_table_capacity); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, + NGX_HTTP_V3_PARAM_BLOCKED_STREAMS); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->max_blocked_streams); + n = p - buf; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (cc->send(cc, buf, n) != (ssize_t) n) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send settings"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "failed to send settings"); + ngx_http_v3_close_uni_stream(cc); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id) +{ + u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 3]; + size_t n; + ngx_connection_t *cc; + ngx_http_v3_session_t *h3c; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send goaway %uL", id); + + cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL); + if (cc == NULL) { + return NGX_ERROR; + } + + n = ngx_http_v3_encode_varlen_int(NULL, id); + p = (u_char *) ngx_http_v3_encode_varlen_int(buf, NGX_HTTP_V3_FRAME_GOAWAY); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, n); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, id); + n = p - buf; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (cc->send(cc, buf, n) != (ssize_t) n) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send goaway"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "failed to send goaway"); + ngx_http_v3_close_uni_stream(cc); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_http_v3_send_ack_section(ngx_connection_t *c, ngx_uint_t stream_id) +{ + u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; + size_t n; + ngx_connection_t *dc; + ngx_http_v3_session_t *h3c; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 send section acknowledgement %ui", stream_id); + + dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); + if (dc == NULL) { + return NGX_ERROR; + } + + buf[0] = 0x80; + n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 7) - buf; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (dc->send(dc, buf, n) != (ssize_t) n) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "failed to send section acknowledgement"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "failed to send section acknowledgement"); + ngx_http_v3_close_uni_stream(dc); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_http_v3_send_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) +{ + u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; + size_t n; + ngx_connection_t *dc; + ngx_http_v3_session_t *h3c; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 send stream cancellation %ui", stream_id); + + dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); + if (dc == NULL) { + return NGX_ERROR; + } + + buf[0] = 0x40; + n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 6) - buf; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (dc->send(dc, buf, n) != (ssize_t) n) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send stream cancellation"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "failed to send stream cancellation"); + ngx_http_v3_close_uni_stream(dc); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_http_v3_send_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) +{ + u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; + size_t n; + ngx_connection_t *dc; + ngx_http_v3_session_t *h3c; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 send insert count increment %ui", inc); + + dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); + if (dc == NULL) { + return NGX_ERROR; + } + + buf[0] = 0; + n = (u_char *) ngx_http_v3_encode_prefix_int(buf, inc, 6) - buf; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (dc->send(dc, buf, n) != (ssize_t) n) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "failed to send insert count increment"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "failed to send insert count increment"); + ngx_http_v3_close_uni_stream(dc); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) +{ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 cancel stream %ui", stream_id); + + /* we do not use dynamic tables */ + + return NGX_OK; +} diff --git a/src/deps/src/nginx/src/http/v3/ngx_http_v3_uni.h b/src/deps/src/nginx/src/http/v3/ngx_http_v3_uni.h new file mode 100644 index 000000000..911e153d7 --- /dev/null +++ b/src/deps/src/nginx/src/http/v3/ngx_http_v3_uni.h @@ -0,0 +1,32 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_V3_UNI_H_INCLUDED_ +#define _NGX_HTTP_V3_UNI_H_INCLUDED_ + + +#include +#include +#include + + +void ngx_http_v3_init_uni_stream(ngx_connection_t *c); +ngx_int_t ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type); + +ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id); + +ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c); +ngx_int_t ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id); +ngx_int_t ngx_http_v3_send_ack_section(ngx_connection_t *c, + ngx_uint_t stream_id); +ngx_int_t ngx_http_v3_send_cancel_stream(ngx_connection_t *c, + ngx_uint_t stream_id); +ngx_int_t ngx_http_v3_send_inc_insert_count(ngx_connection_t *c, + ngx_uint_t inc); + + +#endif /* _NGX_HTTP_V3_UNI_H_INCLUDED_ */ diff --git a/src/deps/src/nginx/src/mail/ngx_mail_core_module.c b/src/deps/src/nginx/src/mail/ngx_mail_core_module.c index 487c5de8d..228a8d0a8 100644 --- a/src/deps/src/nginx/src/mail/ngx_mail_core_module.c +++ b/src/deps/src/nginx/src/mail/ngx_mail_core_module.c @@ -441,7 +441,7 @@ ngx_mail_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) continue; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "bind ipv6only is not supported " + "ipv6only is not supported " "on this platform"); return NGX_CONF_ERROR; #endif @@ -564,7 +564,7 @@ ngx_mail_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "the invalid \"%V\" parameter", &value[i]); + "invalid parameter \"%V\"", &value[i]); return NGX_CONF_ERROR; } diff --git a/src/deps/src/nginx/src/mail/ngx_mail_handler.c b/src/deps/src/nginx/src/mail/ngx_mail_handler.c index 246ba97cf..1167df3fb 100644 --- a/src/deps/src/nginx/src/mail/ngx_mail_handler.c +++ b/src/deps/src/nginx/src/mail/ngx_mail_handler.c @@ -283,11 +283,11 @@ ngx_mail_init_session_handler(ngx_event_t *rev) s = c->data; - sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module); - - if (sslcf->enable || s->ssl) { + if (s->ssl) { c->log->action = "SSL handshaking"; + sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module); + ngx_mail_ssl_init_connection(&sslcf->ssl, c); return; } diff --git a/src/deps/src/nginx/src/mail/ngx_mail_ssl_module.c b/src/deps/src/nginx/src/mail/ngx_mail_ssl_module.c index 28737ac4e..aebb4ccb6 100644 --- a/src/deps/src/nginx/src/mail/ngx_mail_ssl_module.c +++ b/src/deps/src/nginx/src/mail/ngx_mail_ssl_module.c @@ -23,8 +23,6 @@ static int ngx_mail_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, static void *ngx_mail_ssl_create_conf(ngx_conf_t *cf); static char *ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child); -static char *ngx_mail_ssl_enable(ngx_conf_t *cf, ngx_command_t *cmd, - void *conf); static char *ngx_mail_ssl_starttls(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_mail_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, @@ -65,24 +63,12 @@ static ngx_conf_enum_t ngx_mail_ssl_verify[] = { }; -static ngx_conf_deprecated_t ngx_mail_ssl_deprecated = { - ngx_conf_deprecated, "ssl", "listen ... ssl" -}; - - static ngx_conf_post_t ngx_mail_ssl_conf_command_post = { ngx_mail_ssl_conf_command_check }; static ngx_command_t ngx_mail_ssl_commands[] = { - { ngx_string("ssl"), - NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_FLAG, - ngx_mail_ssl_enable, - NGX_MAIL_SRV_CONF_OFFSET, - offsetof(ngx_mail_ssl_conf_t, enable), - &ngx_mail_ssl_deprecated }, - { ngx_string("starttls"), NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1, ngx_mail_ssl_starttls, @@ -322,7 +308,6 @@ ngx_mail_ssl_create_conf(ngx_conf_t *cf) * scf->shm_zone = NULL; */ - scf->enable = NGX_CONF_UNSET; scf->starttls = NGX_CONF_UNSET_UINT; scf->certificates = NGX_CONF_UNSET_PTR; scf->certificate_keys = NGX_CONF_UNSET_PTR; @@ -349,7 +334,6 @@ ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) char *mode; ngx_pool_cleanup_t *cln; - ngx_conf_merge_value(conf->enable, prev->enable, 0); ngx_conf_merge_uint_value(conf->starttls, prev->starttls, NGX_MAIL_STARTTLS_OFF); @@ -394,9 +378,6 @@ ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) if (conf->listen) { mode = "listen ... ssl"; - } else if (conf->enable) { - mode = "ssl"; - } else if (conf->starttls != NGX_MAIL_STARTTLS_OFF) { mode = "starttls"; @@ -545,34 +526,6 @@ ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) } -static char * -ngx_mail_ssl_enable(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) -{ - ngx_mail_ssl_conf_t *scf = conf; - - char *rv; - - rv = ngx_conf_set_flag_slot(cf, cmd, conf); - - if (rv != NGX_CONF_OK) { - return rv; - } - - if (scf->enable && (ngx_int_t) scf->starttls > NGX_MAIL_STARTTLS_OFF) { - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "\"starttls\" directive conflicts with \"ssl on\""); - return NGX_CONF_ERROR; - } - - if (!scf->listen) { - scf->file = cf->conf_file->file.name.data; - scf->line = cf->conf_file->line; - } - - return NGX_CONF_OK; -} - - static char * ngx_mail_ssl_starttls(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { @@ -586,12 +539,6 @@ ngx_mail_ssl_starttls(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return rv; } - if (scf->enable == 1 && (ngx_int_t) scf->starttls > NGX_MAIL_STARTTLS_OFF) { - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "\"ssl\" directive conflicts with \"starttls\""); - return NGX_CONF_ERROR; - } - if (!scf->listen) { scf->file = cf->conf_file->file.name.data; scf->line = cf->conf_file->line; diff --git a/src/deps/src/nginx/src/mail/ngx_mail_ssl_module.h b/src/deps/src/nginx/src/mail/ngx_mail_ssl_module.h index a0a611317..c0eb6a38f 100644 --- a/src/deps/src/nginx/src/mail/ngx_mail_ssl_module.h +++ b/src/deps/src/nginx/src/mail/ngx_mail_ssl_module.h @@ -20,7 +20,6 @@ typedef struct { - ngx_flag_t enable; ngx_flag_t prefer_server_ciphers; ngx_ssl_t ssl; diff --git a/src/deps/src/nginx/src/os/unix/ngx_darwin_init.c b/src/deps/src/nginx/src/os/unix/ngx_darwin_init.c index aabe02ff9..70748ee57 100644 --- a/src/deps/src/nginx/src/os/unix/ngx_darwin_init.c +++ b/src/deps/src/nginx/src/os/unix/ngx_darwin_init.c @@ -9,11 +9,12 @@ #include -char ngx_darwin_kern_ostype[16]; -char ngx_darwin_kern_osrelease[128]; -int ngx_darwin_hw_ncpu; -int ngx_darwin_kern_ipc_somaxconn; -u_long ngx_darwin_net_inet_tcp_sendspace; +char ngx_darwin_kern_ostype[16]; +char ngx_darwin_kern_osrelease[128]; +int ngx_darwin_hw_ncpu; +int ngx_darwin_kern_ipc_somaxconn; +u_long ngx_darwin_net_inet_tcp_sendspace; +int64_t ngx_darwin_hw_cachelinesize; ngx_uint_t ngx_debug_malloc; @@ -56,6 +57,10 @@ sysctl_t sysctls[] = { &ngx_darwin_kern_ipc_somaxconn, sizeof(ngx_darwin_kern_ipc_somaxconn), 0 }, + { "hw.cachelinesize", + &ngx_darwin_hw_cachelinesize, + sizeof(ngx_darwin_hw_cachelinesize), 0 }, + { NULL, NULL, 0, 0 } }; @@ -155,6 +160,7 @@ ngx_os_specific_init(ngx_log_t *log) return NGX_ERROR; } + ngx_cacheline_size = ngx_darwin_hw_cachelinesize; ngx_ncpu = ngx_darwin_hw_ncpu; if (ngx_darwin_kern_ipc_somaxconn > 32767) { diff --git a/src/deps/src/nginx/src/os/unix/ngx_errno.h b/src/deps/src/nginx/src/os/unix/ngx_errno.h index 7d6ca764d..07fa8ced1 100644 --- a/src/deps/src/nginx/src/os/unix/ngx_errno.h +++ b/src/deps/src/nginx/src/os/unix/ngx_errno.h @@ -54,6 +54,7 @@ typedef int ngx_err_t; #define NGX_ENOMOREFILES 0 #define NGX_ELOOP ELOOP #define NGX_EBADF EBADF +#define NGX_EMSGSIZE EMSGSIZE #if (NGX_HAVE_OPENAT) #define NGX_EMLINK EMLINK diff --git a/src/deps/src/nginx/src/os/unix/ngx_files.c b/src/deps/src/nginx/src/os/unix/ngx_files.c index 1c82a8ead..2fec1ece1 100644 --- a/src/deps/src/nginx/src/os/unix/ngx_files.c +++ b/src/deps/src/nginx/src/os/unix/ngx_files.c @@ -110,6 +110,8 @@ ngx_thread_read(ngx_file_t *file, u_char *buf, size_t size, off_t offset, return NGX_ERROR; } + task->event.log = file->log; + file->thread_task = task; } @@ -493,6 +495,8 @@ ngx_thread_write_chain_to_file(ngx_file_t *file, ngx_chain_t *cl, off_t offset, return NGX_ERROR; } + task->event.log = file->log; + file->thread_task = task; } diff --git a/src/deps/src/nginx/src/os/unix/ngx_linux_sendfile_chain.c b/src/deps/src/nginx/src/os/unix/ngx_linux_sendfile_chain.c index 101d91a9a..603f91722 100644 --- a/src/deps/src/nginx/src/os/unix/ngx_linux_sendfile_chain.c +++ b/src/deps/src/nginx/src/os/unix/ngx_linux_sendfile_chain.c @@ -332,6 +332,7 @@ ngx_linux_sendfile_thread(ngx_connection_t *c, ngx_buf_t *file, size_t size) return NGX_ERROR; } + task->event.log = c->log; task->handler = ngx_linux_sendfile_thread_handler; c->sendfile_task = task; diff --git a/src/deps/src/nginx/src/os/unix/ngx_posix_init.c b/src/deps/src/nginx/src/os/unix/ngx_posix_init.c index 7824735d0..6ac931c0c 100644 --- a/src/deps/src/nginx/src/os/unix/ngx_posix_init.c +++ b/src/deps/src/nginx/src/os/unix/ngx_posix_init.c @@ -51,7 +51,10 @@ ngx_os_init(ngx_log_t *log) } ngx_pagesize = getpagesize(); - ngx_cacheline_size = NGX_CPU_CACHE_LINE; + + if (ngx_cacheline_size == 0) { + ngx_cacheline_size = NGX_CPU_CACHE_LINE; + } for (n = ngx_pagesize; n >>= 1; ngx_pagesize_shift++) { /* void */ } diff --git a/src/deps/src/nginx/src/os/unix/ngx_process_cycle.c b/src/deps/src/nginx/src/os/unix/ngx_process_cycle.c index 98d2dd29b..5bc5ce979 100644 --- a/src/deps/src/nginx/src/os/unix/ngx_process_cycle.c +++ b/src/deps/src/nginx/src/os/unix/ngx_process_cycle.c @@ -948,7 +948,7 @@ ngx_worker_process_exit(ngx_cycle_t *cycle) } } - if (ngx_exiting) { + if (ngx_exiting && !ngx_terminate) { c = cycle->connections; for (i = 0; i < cycle->connection_n; i++) { if (c[i].fd != -1 @@ -963,11 +963,11 @@ ngx_worker_process_exit(ngx_cycle_t *cycle) ngx_debug_quit = 1; } } + } - if (ngx_debug_quit) { - ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "aborting"); - ngx_debug_point(); - } + if (ngx_debug_quit) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "aborting"); + ngx_debug_point(); } /* diff --git a/src/deps/src/nginx/src/os/unix/ngx_socket.h b/src/deps/src/nginx/src/os/unix/ngx_socket.h index ec66a6f83..4480adca2 100644 --- a/src/deps/src/nginx/src/os/unix/ngx_socket.h +++ b/src/deps/src/nginx/src/os/unix/ngx_socket.h @@ -13,6 +13,8 @@ #define NGX_WRITE_SHUTDOWN SHUT_WR +#define NGX_READ_SHUTDOWN SHUT_RD +#define NGX_RDWR_SHUTDOWN SHUT_RDWR typedef int ngx_socket_t; diff --git a/src/deps/src/nginx/src/os/win32/ngx_errno.h b/src/deps/src/nginx/src/os/win32/ngx_errno.h index 255a39d53..1e73a832b 100644 --- a/src/deps/src/nginx/src/os/win32/ngx_errno.h +++ b/src/deps/src/nginx/src/os/win32/ngx_errno.h @@ -57,6 +57,7 @@ typedef DWORD ngx_err_t; #define NGX_EILSEQ ERROR_NO_UNICODE_TRANSLATION #define NGX_ELOOP 0 #define NGX_EBADF WSAEBADF +#define NGX_EMSGSIZE WSAEMSGSIZE #define NGX_EALREADY WSAEALREADY #define NGX_EINVAL WSAEINVAL diff --git a/src/deps/src/nginx/src/os/win32/ngx_files.h b/src/deps/src/nginx/src/os/win32/ngx_files.h index 6e59a6fc9..78ba13a0e 100644 --- a/src/deps/src/nginx/src/os/win32/ngx_files.h +++ b/src/deps/src/nginx/src/os/win32/ngx_files.h @@ -154,7 +154,8 @@ ngx_int_t ngx_file_info(u_char *filename, ngx_file_info_t *fi); (((off_t) (fi)->nFileSizeHigh << 32) | (fi)->nFileSizeLow) #define ngx_file_fs_size(fi) ngx_file_size(fi) -#define ngx_file_uniq(fi) (*(ngx_file_uniq_t *) &(fi)->nFileIndexHigh) +#define ngx_file_uniq(fi) \ + (((ngx_file_uniq_t) (fi)->nFileIndexHigh << 32) | (fi)->nFileIndexLow) /* 116444736000000000 is commented in src/os/win32/ngx_time.c */ diff --git a/src/deps/src/nginx/src/os/win32/ngx_process_cycle.c b/src/deps/src/nginx/src/os/win32/ngx_process_cycle.c index 0c848eff4..a39335fd1 100644 --- a/src/deps/src/nginx/src/os/win32/ngx_process_cycle.c +++ b/src/deps/src/nginx/src/os/win32/ngx_process_cycle.c @@ -834,7 +834,7 @@ ngx_worker_process_exit(ngx_cycle_t *cycle) } } - if (ngx_exiting) { + if (ngx_exiting && !ngx_terminate) { c = cycle->connections; for (i = 0; i < cycle->connection_n; i++) { if (c[i].fd != (ngx_socket_t) -1 diff --git a/src/deps/src/nginx/src/os/win32/ngx_socket.h b/src/deps/src/nginx/src/os/win32/ngx_socket.h index ab56bc8b3..5b6138944 100644 --- a/src/deps/src/nginx/src/os/win32/ngx_socket.h +++ b/src/deps/src/nginx/src/os/win32/ngx_socket.h @@ -14,6 +14,8 @@ #define NGX_WRITE_SHUTDOWN SD_SEND +#define NGX_READ_SHUTDOWN SD_RECEIVE +#define NGX_RDWR_SHUTDOWN SD_BOTH typedef SOCKET ngx_socket_t; diff --git a/src/deps/src/nginx/src/os/win32/ngx_win32_config.h b/src/deps/src/nginx/src/os/win32/ngx_win32_config.h index 406003a78..704561355 100644 --- a/src/deps/src/nginx/src/os/win32/ngx_win32_config.h +++ b/src/deps/src/nginx/src/os/win32/ngx_win32_config.h @@ -280,7 +280,11 @@ typedef int sig_atomic_t; #define NGX_HAVE_GETADDRINFO 1 -#define ngx_random rand +#define ngx_random() \ + ((long) (0x7fffffff & ( ((uint32_t) rand() << 16) \ + ^ ((uint32_t) rand() << 8) \ + ^ ((uint32_t) rand()) ))) + #define ngx_debug_init() diff --git a/src/deps/src/nginx/src/stream/ngx_stream.c b/src/deps/src/nginx/src/stream/ngx_stream.c index 3304c843c..b6eeb23af 100644 --- a/src/deps/src/nginx/src/stream/ngx_stream.c +++ b/src/deps/src/nginx/src/stream/ngx_stream.c @@ -16,16 +16,34 @@ static ngx_int_t ngx_stream_init_phases(ngx_conf_t *cf, ngx_stream_core_main_conf_t *cmcf); static ngx_int_t ngx_stream_init_phase_handlers(ngx_conf_t *cf, ngx_stream_core_main_conf_t *cmcf); -static ngx_int_t ngx_stream_add_ports(ngx_conf_t *cf, ngx_array_t *ports, - ngx_stream_listen_t *listen); -static char *ngx_stream_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports); + +static ngx_int_t ngx_stream_add_addresses(ngx_conf_t *cf, + ngx_stream_core_srv_conf_t *cscf, ngx_stream_conf_port_t *port, + ngx_stream_listen_opt_t *lsopt); +static ngx_int_t ngx_stream_add_address(ngx_conf_t *cf, + ngx_stream_core_srv_conf_t *cscf, ngx_stream_conf_port_t *port, + ngx_stream_listen_opt_t *lsopt); +static ngx_int_t ngx_stream_add_server(ngx_conf_t *cf, + ngx_stream_core_srv_conf_t *cscf, ngx_stream_conf_addr_t *addr); + +static ngx_int_t ngx_stream_optimize_servers(ngx_conf_t *cf, + ngx_stream_core_main_conf_t *cmcf, ngx_array_t *ports); +static ngx_int_t ngx_stream_server_names(ngx_conf_t *cf, + ngx_stream_core_main_conf_t *cmcf, ngx_stream_conf_addr_t *addr); +static ngx_int_t ngx_stream_cmp_conf_addrs(const void *one, const void *two); +static int ngx_libc_cdecl ngx_stream_cmp_dns_wildcards(const void *one, + const void *two); + +static ngx_int_t ngx_stream_init_listening(ngx_conf_t *cf, + ngx_stream_conf_port_t *port); +static ngx_listening_t *ngx_stream_add_listening(ngx_conf_t *cf, + ngx_stream_conf_addr_t *addr); static ngx_int_t ngx_stream_add_addrs(ngx_conf_t *cf, ngx_stream_port_t *stport, ngx_stream_conf_addr_t *addr); #if (NGX_HAVE_INET6) static ngx_int_t ngx_stream_add_addrs6(ngx_conf_t *cf, ngx_stream_port_t *stport, ngx_stream_conf_addr_t *addr); #endif -static ngx_int_t ngx_stream_cmp_conf_addrs(const void *one, const void *two); ngx_uint_t ngx_stream_max_module; @@ -74,10 +92,8 @@ static char * ngx_stream_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { char *rv; - ngx_uint_t i, m, mi, s; + ngx_uint_t mi, m, s; ngx_conf_t pcf; - ngx_array_t ports; - ngx_stream_listen_t *listen; ngx_stream_module_t *module; ngx_stream_conf_ctx_t *ctx; ngx_stream_core_srv_conf_t **cscfp; @@ -251,21 +267,13 @@ ngx_stream_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } - if (ngx_array_init(&ports, cf->temp_pool, 4, sizeof(ngx_stream_conf_port_t)) - != NGX_OK) - { + /* optimize the lists of ports, addresses and server names */ + + if (ngx_stream_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK) { return NGX_CONF_ERROR; } - listen = cmcf->listen.elts; - - for (i = 0; i < cmcf->listen.nelts; i++) { - if (ngx_stream_add_ports(cf, &ports, &listen[i]) != NGX_OK) { - return NGX_CONF_ERROR; - } - } - - return ngx_stream_optimize_servers(cf, &ports); + return NGX_CONF_OK; } @@ -377,73 +385,295 @@ ngx_stream_init_phase_handlers(ngx_conf_t *cf, } -static ngx_int_t -ngx_stream_add_ports(ngx_conf_t *cf, ngx_array_t *ports, - ngx_stream_listen_t *listen) +ngx_int_t +ngx_stream_add_listen(ngx_conf_t *cf, ngx_stream_core_srv_conf_t *cscf, + ngx_stream_listen_opt_t *lsopt) { - in_port_t p; - ngx_uint_t i; - struct sockaddr *sa; - ngx_stream_conf_port_t *port; - ngx_stream_conf_addr_t *addr; + in_port_t p; + ngx_uint_t i; + struct sockaddr *sa; + ngx_stream_conf_port_t *port; + ngx_stream_core_main_conf_t *cmcf; - sa = listen->sockaddr; + cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); + + if (cmcf->ports == NULL) { + cmcf->ports = ngx_array_create(cf->temp_pool, 2, + sizeof(ngx_stream_conf_port_t)); + if (cmcf->ports == NULL) { + return NGX_ERROR; + } + } + + sa = lsopt->sockaddr; p = ngx_inet_get_port(sa); - port = ports->elts; - for (i = 0; i < ports->nelts; i++) { + port = cmcf->ports->elts; + for (i = 0; i < cmcf->ports->nelts; i++) { - if (p == port[i].port - && listen->type == port[i].type - && sa->sa_family == port[i].family) + if (p != port[i].port + || lsopt->type != port[i].type + || sa->sa_family != port[i].family) { - /* a port is already in the port list */ - - port = &port[i]; - goto found; + continue; } + + /* a port is already in the port list */ + + return ngx_stream_add_addresses(cf, cscf, &port[i], lsopt); } /* add a port to the port list */ - port = ngx_array_push(ports); + port = ngx_array_push(cmcf->ports); if (port == NULL) { return NGX_ERROR; } port->family = sa->sa_family; - port->type = listen->type; + port->type = lsopt->type; port->port = p; + port->addrs.elts = NULL; - if (ngx_array_init(&port->addrs, cf->temp_pool, 2, - sizeof(ngx_stream_conf_addr_t)) - != NGX_OK) - { - return NGX_ERROR; + return ngx_stream_add_address(cf, cscf, port, lsopt); +} + + +static ngx_int_t +ngx_stream_add_addresses(ngx_conf_t *cf, ngx_stream_core_srv_conf_t *cscf, + ngx_stream_conf_port_t *port, ngx_stream_listen_opt_t *lsopt) +{ + ngx_uint_t i, default_server, proxy_protocol, + protocols, protocols_prev; + ngx_stream_conf_addr_t *addr; +#if (NGX_STREAM_SSL) + ngx_uint_t ssl; +#endif + + /* + * we cannot compare whole sockaddr struct's as kernel + * may fill some fields in inherited sockaddr struct's + */ + + addr = port->addrs.elts; + + for (i = 0; i < port->addrs.nelts; i++) { + + if (ngx_cmp_sockaddr(lsopt->sockaddr, lsopt->socklen, + addr[i].opt.sockaddr, + addr[i].opt.socklen, 0) + != NGX_OK) + { + continue; + } + + /* the address is already in the address list */ + + if (ngx_stream_add_server(cf, cscf, &addr[i]) != NGX_OK) { + return NGX_ERROR; + } + + /* preserve default_server bit during listen options overwriting */ + default_server = addr[i].opt.default_server; + + proxy_protocol = lsopt->proxy_protocol || addr[i].opt.proxy_protocol; + protocols = lsopt->proxy_protocol; + protocols_prev = addr[i].opt.proxy_protocol; + +#if (NGX_STREAM_SSL) + ssl = lsopt->ssl || addr[i].opt.ssl; + protocols |= lsopt->ssl << 1; + protocols_prev |= addr[i].opt.ssl << 1; +#endif + + if (lsopt->set) { + + if (addr[i].opt.set) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "duplicate listen options for %V", + &addr[i].opt.addr_text); + return NGX_ERROR; + } + + addr[i].opt = *lsopt; + } + + /* check the duplicate "default" server for this address:port */ + + if (lsopt->default_server) { + + if (default_server) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "a duplicate default server for %V", + &addr[i].opt.addr_text); + return NGX_ERROR; + } + + default_server = 1; + addr[i].default_server = cscf; + } + + /* check for conflicting protocol options */ + + if ((protocols | protocols_prev) != protocols_prev) { + + /* options added */ + + if ((addr[i].opt.set && !lsopt->set) + || addr[i].protocols_changed + || (protocols | protocols_prev) != protocols) + { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "protocol options redefined for %V", + &addr[i].opt.addr_text); + } + + addr[i].protocols = protocols_prev; + addr[i].protocols_set = 1; + addr[i].protocols_changed = 1; + + } else if ((protocols_prev | protocols) != protocols) { + + /* options removed */ + + if (lsopt->set + || (addr[i].protocols_set && protocols != addr[i].protocols)) + { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "protocol options redefined for %V", + &addr[i].opt.addr_text); + } + + addr[i].protocols = protocols; + addr[i].protocols_set = 1; + addr[i].protocols_changed = 1; + + } else { + + /* the same options */ + + if ((lsopt->set && addr[i].protocols_changed) + || (addr[i].protocols_set && protocols != addr[i].protocols)) + { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "protocol options redefined for %V", + &addr[i].opt.addr_text); + } + + addr[i].protocols = protocols; + addr[i].protocols_set = 1; + } + + addr[i].opt.default_server = default_server; + addr[i].opt.proxy_protocol = proxy_protocol; +#if (NGX_STREAM_SSL) + addr[i].opt.ssl = ssl; +#endif + + return NGX_OK; } -found: + /* add the address to the addresses list that bound to this port */ + + return ngx_stream_add_address(cf, cscf, port, lsopt); +} + + +/* + * add the server address, the server names and the server core module + * configurations to the port list + */ + +static ngx_int_t +ngx_stream_add_address(ngx_conf_t *cf, ngx_stream_core_srv_conf_t *cscf, + ngx_stream_conf_port_t *port, ngx_stream_listen_opt_t *lsopt) +{ + ngx_stream_conf_addr_t *addr; + + if (port->addrs.elts == NULL) { + if (ngx_array_init(&port->addrs, cf->temp_pool, 4, + sizeof(ngx_stream_conf_addr_t)) + != NGX_OK) + { + return NGX_ERROR; + } + } addr = ngx_array_push(&port->addrs); if (addr == NULL) { return NGX_ERROR; } - addr->opt = *listen; + addr->opt = *lsopt; + addr->protocols = 0; + addr->protocols_set = 0; + addr->protocols_changed = 0; + addr->hash.buckets = NULL; + addr->hash.size = 0; + addr->wc_head = NULL; + addr->wc_tail = NULL; +#if (NGX_PCRE) + addr->nregex = 0; + addr->regex = NULL; +#endif + addr->default_server = cscf; + addr->servers.elts = NULL; + + return ngx_stream_add_server(cf, cscf, addr); +} + + +/* add the server core module configuration to the address:port */ + +static ngx_int_t +ngx_stream_add_server(ngx_conf_t *cf, ngx_stream_core_srv_conf_t *cscf, + ngx_stream_conf_addr_t *addr) +{ + ngx_uint_t i; + ngx_stream_core_srv_conf_t **server; + + if (addr->servers.elts == NULL) { + if (ngx_array_init(&addr->servers, cf->temp_pool, 4, + sizeof(ngx_stream_core_srv_conf_t *)) + != NGX_OK) + { + return NGX_ERROR; + } + + } else { + server = addr->servers.elts; + for (i = 0; i < addr->servers.nelts; i++) { + if (server[i] == cscf) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "a duplicate listen %V", + &addr->opt.addr_text); + return NGX_ERROR; + } + } + } + + server = ngx_array_push(&addr->servers); + if (server == NULL) { + return NGX_ERROR; + } + + *server = cscf; return NGX_OK; } -static char * -ngx_stream_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports) +static ngx_int_t +ngx_stream_optimize_servers(ngx_conf_t *cf, ngx_stream_core_main_conf_t *cmcf, + ngx_array_t *ports) { - ngx_uint_t i, p, last, bind_wildcard; - ngx_listening_t *ls; - ngx_stream_port_t *stport; - ngx_stream_conf_port_t *port; - ngx_stream_conf_addr_t *addr; - ngx_stream_core_srv_conf_t *cscf; + ngx_uint_t p, a; + ngx_stream_conf_port_t *port; + ngx_stream_conf_addr_t *addr; + + if (ports == NULL) { + return NGX_OK; + } port = ports->elts; for (p = 0; p < ports->nelts; p++) { @@ -451,176 +681,192 @@ ngx_stream_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports) ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts, sizeof(ngx_stream_conf_addr_t), ngx_stream_cmp_conf_addrs); - addr = port[p].addrs.elts; - last = port[p].addrs.nelts; - /* - * if there is the binding to the "*:port" then we need to bind() - * to the "*:port" only and ignore the other bindings + * check whether all name-based servers have the same + * configuration as a default server for given address:port */ - if (addr[last - 1].opt.wildcard) { - addr[last - 1].opt.bind = 1; - bind_wildcard = 1; + addr = port[p].addrs.elts; + for (a = 0; a < port[p].addrs.nelts; a++) { - } else { - bind_wildcard = 0; + if (addr[a].servers.nelts > 1 +#if (NGX_PCRE) + || addr[a].default_server->captures +#endif + ) + { + if (ngx_stream_server_names(cf, cmcf, &addr[a]) != NGX_OK) { + return NGX_ERROR; + } + } } - i = 0; + if (ngx_stream_init_listening(cf, &port[p]) != NGX_OK) { + return NGX_ERROR; + } + } - while (i < last) { + return NGX_OK; +} - if (bind_wildcard && !addr[i].opt.bind) { - i++; + +static ngx_int_t +ngx_stream_server_names(ngx_conf_t *cf, ngx_stream_core_main_conf_t *cmcf, + ngx_stream_conf_addr_t *addr) +{ + ngx_int_t rc; + ngx_uint_t n, s; + ngx_hash_init_t hash; + ngx_hash_keys_arrays_t ha; + ngx_stream_server_name_t *name; + ngx_stream_core_srv_conf_t **cscfp; +#if (NGX_PCRE) + ngx_uint_t regex, i; + + regex = 0; +#endif + + ngx_memzero(&ha, sizeof(ngx_hash_keys_arrays_t)); + + ha.temp_pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log); + if (ha.temp_pool == NULL) { + return NGX_ERROR; + } + + ha.pool = cf->pool; + + if (ngx_hash_keys_array_init(&ha, NGX_HASH_LARGE) != NGX_OK) { + goto failed; + } + + cscfp = addr->servers.elts; + + for (s = 0; s < addr->servers.nelts; s++) { + + name = cscfp[s]->server_names.elts; + + for (n = 0; n < cscfp[s]->server_names.nelts; n++) { + +#if (NGX_PCRE) + if (name[n].regex) { + regex++; continue; } +#endif - ls = ngx_create_listening(cf, addr[i].opt.sockaddr, - addr[i].opt.socklen); - if (ls == NULL) { - return NGX_CONF_ERROR; + rc = ngx_hash_add_key(&ha, &name[n].name, name[n].server, + NGX_HASH_WILDCARD_KEY); + + if (rc == NGX_ERROR) { + goto failed; } - ls->addr_ntop = 1; - ls->handler = ngx_stream_init_connection; - ls->pool_size = 256; - ls->type = addr[i].opt.type; - - cscf = addr->opt.ctx->srv_conf[ngx_stream_core_module.ctx_index]; - - ls->logp = cscf->error_log; - ls->log.data = &ls->addr_text; - ls->log.handler = ngx_accept_log_error; - - ls->backlog = addr[i].opt.backlog; - ls->rcvbuf = addr[i].opt.rcvbuf; - ls->sndbuf = addr[i].opt.sndbuf; - - ls->wildcard = addr[i].opt.wildcard; - - ls->keepalive = addr[i].opt.so_keepalive; -#if (NGX_HAVE_KEEPALIVE_TUNABLE) - ls->keepidle = addr[i].opt.tcp_keepidle; - ls->keepintvl = addr[i].opt.tcp_keepintvl; - ls->keepcnt = addr[i].opt.tcp_keepcnt; -#endif - -#if (NGX_HAVE_INET6) - ls->ipv6only = addr[i].opt.ipv6only; -#endif - -#if (NGX_HAVE_TCP_FASTOPEN) - ls->fastopen = addr[i].opt.fastopen; -#endif - -#if (NGX_HAVE_REUSEPORT) - ls->reuseport = addr[i].opt.reuseport; -#endif - - stport = ngx_palloc(cf->pool, sizeof(ngx_stream_port_t)); - if (stport == NULL) { - return NGX_CONF_ERROR; + if (rc == NGX_DECLINED) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "invalid server name or wildcard \"%V\" on %V", + &name[n].name, &addr->opt.addr_text); + goto failed; } - ls->servers = stport; - - stport->naddrs = i + 1; - - switch (ls->sockaddr->sa_family) { -#if (NGX_HAVE_INET6) - case AF_INET6: - if (ngx_stream_add_addrs6(cf, stport, addr) != NGX_OK) { - return NGX_CONF_ERROR; - } - break; -#endif - default: /* AF_INET */ - if (ngx_stream_add_addrs(cf, stport, addr) != NGX_OK) { - return NGX_CONF_ERROR; - } - break; + if (rc == NGX_BUSY) { + ngx_log_error(NGX_LOG_WARN, cf->log, 0, + "conflicting server name \"%V\" on %V, ignored", + &name[n].name, &addr->opt.addr_text); } - - addr++; - last--; } } - return NGX_CONF_OK; -} + hash.key = ngx_hash_key_lc; + hash.max_size = cmcf->server_names_hash_max_size; + hash.bucket_size = cmcf->server_names_hash_bucket_size; + hash.name = "server_names_hash"; + hash.pool = cf->pool; + if (ha.keys.nelts) { + hash.hash = &addr->hash; + hash.temp_pool = NULL; -static ngx_int_t -ngx_stream_add_addrs(ngx_conf_t *cf, ngx_stream_port_t *stport, - ngx_stream_conf_addr_t *addr) -{ - ngx_uint_t i; - struct sockaddr_in *sin; - ngx_stream_in_addr_t *addrs; + if (ngx_hash_init(&hash, ha.keys.elts, ha.keys.nelts) != NGX_OK) { + goto failed; + } + } - stport->addrs = ngx_pcalloc(cf->pool, - stport->naddrs * sizeof(ngx_stream_in_addr_t)); - if (stport->addrs == NULL) { + if (ha.dns_wc_head.nelts) { + + ngx_qsort(ha.dns_wc_head.elts, (size_t) ha.dns_wc_head.nelts, + sizeof(ngx_hash_key_t), ngx_stream_cmp_dns_wildcards); + + hash.hash = NULL; + hash.temp_pool = ha.temp_pool; + + if (ngx_hash_wildcard_init(&hash, ha.dns_wc_head.elts, + ha.dns_wc_head.nelts) + != NGX_OK) + { + goto failed; + } + + addr->wc_head = (ngx_hash_wildcard_t *) hash.hash; + } + + if (ha.dns_wc_tail.nelts) { + + ngx_qsort(ha.dns_wc_tail.elts, (size_t) ha.dns_wc_tail.nelts, + sizeof(ngx_hash_key_t), ngx_stream_cmp_dns_wildcards); + + hash.hash = NULL; + hash.temp_pool = ha.temp_pool; + + if (ngx_hash_wildcard_init(&hash, ha.dns_wc_tail.elts, + ha.dns_wc_tail.nelts) + != NGX_OK) + { + goto failed; + } + + addr->wc_tail = (ngx_hash_wildcard_t *) hash.hash; + } + + ngx_destroy_pool(ha.temp_pool); + +#if (NGX_PCRE) + + if (regex == 0) { + return NGX_OK; + } + + addr->nregex = regex; + addr->regex = ngx_palloc(cf->pool, + regex * sizeof(ngx_stream_server_name_t)); + if (addr->regex == NULL) { return NGX_ERROR; } - addrs = stport->addrs; + i = 0; - for (i = 0; i < stport->naddrs; i++) { + for (s = 0; s < addr->servers.nelts; s++) { - sin = (struct sockaddr_in *) addr[i].opt.sockaddr; - addrs[i].addr = sin->sin_addr.s_addr; + name = cscfp[s]->server_names.elts; - addrs[i].conf.ctx = addr[i].opt.ctx; -#if (NGX_STREAM_SSL) - addrs[i].conf.ssl = addr[i].opt.ssl; -#endif - addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; - addrs[i].conf.addr_text = addr[i].opt.addr_text; + for (n = 0; n < cscfp[s]->server_names.nelts; n++) { + if (name[n].regex) { + addr->regex[i++] = name[n]; + } + } } +#endif + return NGX_OK; + +failed: + + ngx_destroy_pool(ha.temp_pool); + + return NGX_ERROR; } -#if (NGX_HAVE_INET6) - -static ngx_int_t -ngx_stream_add_addrs6(ngx_conf_t *cf, ngx_stream_port_t *stport, - ngx_stream_conf_addr_t *addr) -{ - ngx_uint_t i; - struct sockaddr_in6 *sin6; - ngx_stream_in6_addr_t *addrs6; - - stport->addrs = ngx_pcalloc(cf->pool, - stport->naddrs * sizeof(ngx_stream_in6_addr_t)); - if (stport->addrs == NULL) { - return NGX_ERROR; - } - - addrs6 = stport->addrs; - - for (i = 0; i < stport->naddrs; i++) { - - sin6 = (struct sockaddr_in6 *) addr[i].opt.sockaddr; - addrs6[i].addr6 = sin6->sin6_addr; - - addrs6[i].conf.ctx = addr[i].opt.ctx; -#if (NGX_STREAM_SSL) - addrs6[i].conf.ssl = addr[i].opt.ssl; -#endif - addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; - addrs6[i].conf.addr_text = addr[i].opt.addr_text; - } - - return NGX_OK; -} - -#endif - - static ngx_int_t ngx_stream_cmp_conf_addrs(const void *one, const void *two) { @@ -630,12 +876,12 @@ ngx_stream_cmp_conf_addrs(const void *one, const void *two) second = (ngx_stream_conf_addr_t *) two; if (first->opt.wildcard) { - /* a wildcard must be the last resort, shift it to the end */ + /* a wildcard address must be the last resort, shift it to the end */ return 1; } if (second->opt.wildcard) { - /* a wildcard must be the last resort, shift it to the end */ + /* a wildcard address must be the last resort, shift it to the end */ return -1; } @@ -653,3 +899,277 @@ ngx_stream_cmp_conf_addrs(const void *one, const void *two) return 0; } + + +static int ngx_libc_cdecl +ngx_stream_cmp_dns_wildcards(const void *one, const void *two) +{ + ngx_hash_key_t *first, *second; + + first = (ngx_hash_key_t *) one; + second = (ngx_hash_key_t *) two; + + return ngx_dns_strcmp(first->key.data, second->key.data); +} + + +static ngx_int_t +ngx_stream_init_listening(ngx_conf_t *cf, ngx_stream_conf_port_t *port) +{ + ngx_uint_t i, last, bind_wildcard; + ngx_listening_t *ls; + ngx_stream_port_t *stport; + ngx_stream_conf_addr_t *addr; + + addr = port->addrs.elts; + last = port->addrs.nelts; + + /* + * If there is a binding to an "*:port" then we need to bind() to + * the "*:port" only and ignore other implicit bindings. The bindings + * have been already sorted: explicit bindings are on the start, then + * implicit bindings go, and wildcard binding is in the end. + */ + + if (addr[last - 1].opt.wildcard) { + addr[last - 1].opt.bind = 1; + bind_wildcard = 1; + + } else { + bind_wildcard = 0; + } + + i = 0; + + while (i < last) { + + if (bind_wildcard && !addr[i].opt.bind) { + i++; + continue; + } + + ls = ngx_stream_add_listening(cf, &addr[i]); + if (ls == NULL) { + return NGX_ERROR; + } + + stport = ngx_pcalloc(cf->pool, sizeof(ngx_stream_port_t)); + if (stport == NULL) { + return NGX_ERROR; + } + + ls->servers = stport; + + stport->naddrs = i + 1; + + switch (ls->sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + if (ngx_stream_add_addrs6(cf, stport, addr) != NGX_OK) { + return NGX_ERROR; + } + break; +#endif + default: /* AF_INET */ + if (ngx_stream_add_addrs(cf, stport, addr) != NGX_OK) { + return NGX_ERROR; + } + break; + } + + addr++; + last--; + } + + return NGX_OK; +} + + +static ngx_listening_t * +ngx_stream_add_listening(ngx_conf_t *cf, ngx_stream_conf_addr_t *addr) +{ + ngx_listening_t *ls; + ngx_stream_core_srv_conf_t *cscf; + + ls = ngx_create_listening(cf, addr->opt.sockaddr, addr->opt.socklen); + if (ls == NULL) { + return NULL; + } + + ls->addr_ntop = 1; + + ls->handler = ngx_stream_init_connection; + + ls->pool_size = 256; + + cscf = addr->default_server; + + ls->logp = cscf->error_log; + ls->log.data = &ls->addr_text; + ls->log.handler = ngx_accept_log_error; + + ls->type = addr->opt.type; + ls->backlog = addr->opt.backlog; + ls->rcvbuf = addr->opt.rcvbuf; + ls->sndbuf = addr->opt.sndbuf; + + ls->keepalive = addr->opt.so_keepalive; +#if (NGX_HAVE_KEEPALIVE_TUNABLE) + ls->keepidle = addr->opt.tcp_keepidle; + ls->keepintvl = addr->opt.tcp_keepintvl; + ls->keepcnt = addr->opt.tcp_keepcnt; +#endif + +#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) + ls->accept_filter = addr->opt.accept_filter; +#endif + +#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT) + ls->deferred_accept = addr->opt.deferred_accept; +#endif + +#if (NGX_HAVE_INET6) + ls->ipv6only = addr->opt.ipv6only; +#endif + +#if (NGX_HAVE_SETFIB) + ls->setfib = addr->opt.setfib; +#endif + +#if (NGX_HAVE_TCP_FASTOPEN) + ls->fastopen = addr->opt.fastopen; +#endif + +#if (NGX_HAVE_REUSEPORT) + ls->reuseport = addr->opt.reuseport; +#endif + + ls->wildcard = addr->opt.wildcard; + + return ls; +} + + +static ngx_int_t +ngx_stream_add_addrs(ngx_conf_t *cf, ngx_stream_port_t *stport, + ngx_stream_conf_addr_t *addr) +{ + ngx_uint_t i; + struct sockaddr_in *sin; + ngx_stream_in_addr_t *addrs; + ngx_stream_virtual_names_t *vn; + + stport->addrs = ngx_pcalloc(cf->pool, + stport->naddrs * sizeof(ngx_stream_in_addr_t)); + if (stport->addrs == NULL) { + return NGX_ERROR; + } + + addrs = stport->addrs; + + for (i = 0; i < stport->naddrs; i++) { + + sin = (struct sockaddr_in *) addr[i].opt.sockaddr; + addrs[i].addr = sin->sin_addr.s_addr; + addrs[i].conf.default_server = addr[i].default_server; +#if (NGX_STREAM_SSL) + addrs[i].conf.ssl = addr[i].opt.ssl; +#endif + addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; + + if (addr[i].hash.buckets == NULL + && (addr[i].wc_head == NULL + || addr[i].wc_head->hash.buckets == NULL) + && (addr[i].wc_tail == NULL + || addr[i].wc_tail->hash.buckets == NULL) +#if (NGX_PCRE) + && addr[i].nregex == 0 +#endif + ) + { + continue; + } + + vn = ngx_palloc(cf->pool, sizeof(ngx_stream_virtual_names_t)); + if (vn == NULL) { + return NGX_ERROR; + } + + addrs[i].conf.virtual_names = vn; + + vn->names.hash = addr[i].hash; + vn->names.wc_head = addr[i].wc_head; + vn->names.wc_tail = addr[i].wc_tail; +#if (NGX_PCRE) + vn->nregex = addr[i].nregex; + vn->regex = addr[i].regex; +#endif + } + + return NGX_OK; +} + + +#if (NGX_HAVE_INET6) + +static ngx_int_t +ngx_stream_add_addrs6(ngx_conf_t *cf, ngx_stream_port_t *stport, + ngx_stream_conf_addr_t *addr) +{ + ngx_uint_t i; + struct sockaddr_in6 *sin6; + ngx_stream_in6_addr_t *addrs6; + ngx_stream_virtual_names_t *vn; + + stport->addrs = ngx_pcalloc(cf->pool, + stport->naddrs * sizeof(ngx_stream_in6_addr_t)); + if (stport->addrs == NULL) { + return NGX_ERROR; + } + + addrs6 = stport->addrs; + + for (i = 0; i < stport->naddrs; i++) { + + sin6 = (struct sockaddr_in6 *) addr[i].opt.sockaddr; + addrs6[i].addr6 = sin6->sin6_addr; + addrs6[i].conf.default_server = addr[i].default_server; +#if (NGX_STREAM_SSL) + addrs6[i].conf.ssl = addr[i].opt.ssl; +#endif + addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; + + if (addr[i].hash.buckets == NULL + && (addr[i].wc_head == NULL + || addr[i].wc_head->hash.buckets == NULL) + && (addr[i].wc_tail == NULL + || addr[i].wc_tail->hash.buckets == NULL) +#if (NGX_PCRE) + && addr[i].nregex == 0 +#endif + ) + { + continue; + } + + vn = ngx_palloc(cf->pool, sizeof(ngx_stream_virtual_names_t)); + if (vn == NULL) { + return NGX_ERROR; + } + + addrs6[i].conf.virtual_names = vn; + + vn->names.hash = addr[i].hash; + vn->names.wc_head = addr[i].wc_head; + vn->names.wc_tail = addr[i].wc_tail; +#if (NGX_PCRE) + vn->nregex = addr[i].nregex; + vn->regex = addr[i].regex; +#endif + } + + return NGX_OK; +} + +#endif diff --git a/src/deps/src/nginx/src/stream/ngx_stream.h b/src/deps/src/nginx/src/stream/ngx_stream.h index 46c362296..dc05dc5ba 100644 --- a/src/deps/src/nginx/src/stream/ngx_stream.h +++ b/src/deps/src/nginx/src/stream/ngx_stream.h @@ -45,74 +45,39 @@ typedef struct { socklen_t socklen; ngx_str_t addr_text; - /* server ctx */ - ngx_stream_conf_ctx_t *ctx; - + unsigned set:1; + unsigned default_server:1; unsigned bind:1; unsigned wildcard:1; unsigned ssl:1; #if (NGX_HAVE_INET6) unsigned ipv6only:1; #endif + unsigned deferred_accept:1; unsigned reuseport:1; unsigned so_keepalive:2; unsigned proxy_protocol:1; + + int backlog; + int rcvbuf; + int sndbuf; + int type; +#if (NGX_HAVE_SETFIB) + int setfib; +#endif +#if (NGX_HAVE_TCP_FASTOPEN) + int fastopen; +#endif #if (NGX_HAVE_KEEPALIVE_TUNABLE) int tcp_keepidle; int tcp_keepintvl; int tcp_keepcnt; #endif - int backlog; - int rcvbuf; - int sndbuf; -#if (NGX_HAVE_TCP_FASTOPEN) - int fastopen; + +#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) + char *accept_filter; #endif - int type; -} ngx_stream_listen_t; - - -typedef struct { - ngx_stream_conf_ctx_t *ctx; - ngx_str_t addr_text; - unsigned ssl:1; - unsigned proxy_protocol:1; -} ngx_stream_addr_conf_t; - -typedef struct { - in_addr_t addr; - ngx_stream_addr_conf_t conf; -} ngx_stream_in_addr_t; - - -#if (NGX_HAVE_INET6) - -typedef struct { - struct in6_addr addr6; - ngx_stream_addr_conf_t conf; -} ngx_stream_in6_addr_t; - -#endif - - -typedef struct { - /* ngx_stream_in_addr_t or ngx_stream_in6_addr_t */ - void *addrs; - ngx_uint_t naddrs; -} ngx_stream_port_t; - - -typedef struct { - int family; - int type; - in_port_t port; - ngx_array_t addrs; /* array of ngx_stream_conf_addr_t */ -} ngx_stream_conf_port_t; - - -typedef struct { - ngx_stream_listen_t opt; -} ngx_stream_conf_addr_t; +} ngx_stream_listen_opt_t; typedef enum { @@ -153,7 +118,6 @@ typedef struct { typedef struct { ngx_array_t servers; /* ngx_stream_core_srv_conf_t */ - ngx_array_t listen; /* ngx_stream_listen_t */ ngx_stream_phase_engine_t phase_engine; @@ -163,16 +127,24 @@ typedef struct { ngx_array_t prefix_variables; /* ngx_stream_variable_t */ ngx_uint_t ncaptures; + ngx_uint_t server_names_hash_max_size; + ngx_uint_t server_names_hash_bucket_size; + ngx_uint_t variables_hash_max_size; ngx_uint_t variables_hash_bucket_size; ngx_hash_keys_arrays_t *variables_keys; + ngx_array_t *ports; + ngx_stream_phase_t phases[NGX_STREAM_LOG_PHASE + 1]; } ngx_stream_core_main_conf_t; typedef struct { + /* array of the ngx_stream_server_name_t, "server_name" directive */ + ngx_array_t server_names; + ngx_stream_content_handler_pt handler; ngx_stream_conf_ctx_t *ctx; @@ -180,6 +152,8 @@ typedef struct { u_char *file_name; ngx_uint_t line; + ngx_str_t server_name; + ngx_flag_t tcp_nodelay; size_t preread_buffer_size; ngx_msec_t preread_timeout; @@ -191,10 +165,98 @@ typedef struct { ngx_msec_t proxy_protocol_timeout; - ngx_uint_t listen; /* unsigned listen:1; */ + unsigned listen:1; +#if (NGX_PCRE) + unsigned captures:1; +#endif } ngx_stream_core_srv_conf_t; +/* list of structures to find core_srv_conf quickly at run time */ + + +typedef struct { +#if (NGX_PCRE) + ngx_stream_regex_t *regex; +#endif + ngx_stream_core_srv_conf_t *server; /* virtual name server conf */ + ngx_str_t name; +} ngx_stream_server_name_t; + + +typedef struct { + ngx_hash_combined_t names; + + ngx_uint_t nregex; + ngx_stream_server_name_t *regex; +} ngx_stream_virtual_names_t; + + +typedef struct { + /* the default server configuration for this address:port */ + ngx_stream_core_srv_conf_t *default_server; + + ngx_stream_virtual_names_t *virtual_names; + + unsigned ssl:1; + unsigned proxy_protocol:1; +} ngx_stream_addr_conf_t; + + +typedef struct { + in_addr_t addr; + ngx_stream_addr_conf_t conf; +} ngx_stream_in_addr_t; + + +#if (NGX_HAVE_INET6) + +typedef struct { + struct in6_addr addr6; + ngx_stream_addr_conf_t conf; +} ngx_stream_in6_addr_t; + +#endif + + +typedef struct { + /* ngx_stream_in_addr_t or ngx_stream_in6_addr_t */ + void *addrs; + ngx_uint_t naddrs; +} ngx_stream_port_t; + + +typedef struct { + int family; + int type; + in_port_t port; + ngx_array_t addrs; /* array of ngx_stream_conf_addr_t */ +} ngx_stream_conf_port_t; + + +typedef struct { + ngx_stream_listen_opt_t opt; + + unsigned protocols:3; + unsigned protocols_set:1; + unsigned protocols_changed:1; + + ngx_hash_t hash; + ngx_hash_wildcard_t *wc_head; + ngx_hash_wildcard_t *wc_tail; + +#if (NGX_PCRE) + ngx_uint_t nregex; + ngx_stream_server_name_t *regex; +#endif + + /* the default server configuration for this address:port */ + ngx_stream_core_srv_conf_t *default_server; + ngx_array_t servers; + /* array of ngx_stream_core_srv_conf_t */ +} ngx_stream_conf_addr_t; + + struct ngx_stream_session_s { uint32_t signature; /* "STRM" */ @@ -210,6 +272,8 @@ struct ngx_stream_session_s { void **main_conf; void **srv_conf; + ngx_stream_virtual_names_t *virtual_names; + ngx_stream_upstream_t *upstream; ngx_array_t *upstream_states; /* of ngx_stream_upstream_state_t */ @@ -283,6 +347,9 @@ typedef struct { #define NGX_STREAM_WRITE_BUFFERED 0x10 +ngx_int_t ngx_stream_add_listen(ngx_conf_t *cf, + ngx_stream_core_srv_conf_t *cscf, ngx_stream_listen_opt_t *lsopt); + void ngx_stream_core_run_phases(ngx_stream_session_t *s); ngx_int_t ngx_stream_core_generic_phase(ngx_stream_session_t *s, ngx_stream_phase_handler_t *ph); @@ -291,6 +358,10 @@ ngx_int_t ngx_stream_core_preread_phase(ngx_stream_session_t *s, ngx_int_t ngx_stream_core_content_phase(ngx_stream_session_t *s, ngx_stream_phase_handler_t *ph); +ngx_int_t ngx_stream_validate_host(ngx_str_t *host, ngx_pool_t *pool, + ngx_uint_t alloc); +ngx_int_t ngx_stream_find_virtual_server(ngx_stream_session_t *s, + ngx_str_t *host, ngx_stream_core_srv_conf_t **cscfp); void ngx_stream_init_connection(ngx_connection_t *c); void ngx_stream_session_handler(ngx_event_t *rev); diff --git a/src/deps/src/nginx/src/stream/ngx_stream_access_module.c b/src/deps/src/nginx/src/stream/ngx_stream_access_module.c index a3020d4fb..070c22614 100644 --- a/src/deps/src/nginx/src/stream/ngx_stream_access_module.c +++ b/src/deps/src/nginx/src/stream/ngx_stream_access_module.c @@ -144,7 +144,7 @@ ngx_stream_access_handler(ngx_stream_session_t *s) p = sin6->sin6_addr.s6_addr; if (ascf->rules && IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { - addr = p[12] << 24; + addr = (in_addr_t) p[12] << 24; addr += p[13] << 16; addr += p[14] << 8; addr += p[15]; diff --git a/src/deps/src/nginx/src/stream/ngx_stream_core_module.c b/src/deps/src/nginx/src/stream/ngx_stream_core_module.c index f0b79341d..3093963b7 100644 --- a/src/deps/src/nginx/src/stream/ngx_stream_core_module.c +++ b/src/deps/src/nginx/src/stream/ngx_stream_core_module.c @@ -10,6 +10,11 @@ #include +static ngx_uint_t ngx_stream_preread_can_peek(ngx_connection_t *c); +static ngx_int_t ngx_stream_preread_peek(ngx_stream_session_t *s, + ngx_stream_phase_handler_t *ph); +static ngx_int_t ngx_stream_preread(ngx_stream_session_t *s, + ngx_stream_phase_handler_t *ph); static ngx_int_t ngx_stream_core_preconfiguration(ngx_conf_t *cf); static void *ngx_stream_core_create_main_conf(ngx_conf_t *cf); static char *ngx_stream_core_init_main_conf(ngx_conf_t *cf, void *conf); @@ -22,6 +27,8 @@ static char *ngx_stream_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_stream_core_server_name(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); static char *ngx_stream_core_resolver(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); @@ -42,6 +49,20 @@ static ngx_command_t ngx_stream_core_commands[] = { offsetof(ngx_stream_core_main_conf_t, variables_hash_bucket_size), NULL }, + { ngx_string("server_names_hash_max_size"), + NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_STREAM_MAIN_CONF_OFFSET, + offsetof(ngx_stream_core_main_conf_t, server_names_hash_max_size), + NULL }, + + { ngx_string("server_names_hash_bucket_size"), + NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_STREAM_MAIN_CONF_OFFSET, + offsetof(ngx_stream_core_main_conf_t, server_names_hash_bucket_size), + NULL }, + { ngx_string("server"), NGX_STREAM_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, ngx_stream_core_server, @@ -56,6 +77,13 @@ static ngx_command_t ngx_stream_core_commands[] = { 0, NULL }, + { ngx_string("server_name"), + NGX_STREAM_SRV_CONF|NGX_CONF_1MORE, + ngx_stream_core_server_name, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + { ngx_string("error_log"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE, ngx_stream_core_error_log, @@ -203,8 +231,6 @@ ngx_int_t ngx_stream_core_preread_phase(ngx_stream_session_t *s, ngx_stream_phase_handler_t *ph) { - size_t size; - ssize_t n; ngx_int_t rc; ngx_connection_t *c; ngx_stream_core_srv_conf_t *cscf; @@ -217,56 +243,33 @@ ngx_stream_core_preread_phase(ngx_stream_session_t *s, if (c->read->timedout) { rc = NGX_STREAM_OK; + goto done; + } - } else if (c->read->timer_set) { - rc = NGX_AGAIN; + if (!c->read->timer_set) { + rc = ph->handler(s); + + if (rc != NGX_AGAIN) { + goto done; + } + } + + if (c->buffer == NULL) { + c->buffer = ngx_create_temp_buf(c->pool, cscf->preread_buffer_size); + if (c->buffer == NULL) { + rc = NGX_ERROR; + goto done; + } + } + + if (ngx_stream_preread_can_peek(c)) { + rc = ngx_stream_preread_peek(s, ph); } else { - rc = ph->handler(s); + rc = ngx_stream_preread(s, ph); } - while (rc == NGX_AGAIN) { - - if (c->buffer == NULL) { - c->buffer = ngx_create_temp_buf(c->pool, cscf->preread_buffer_size); - if (c->buffer == NULL) { - rc = NGX_ERROR; - break; - } - } - - size = c->buffer->end - c->buffer->last; - - if (size == 0) { - ngx_log_error(NGX_LOG_ERR, c->log, 0, "preread buffer full"); - rc = NGX_STREAM_BAD_REQUEST; - break; - } - - if (c->read->eof) { - rc = NGX_STREAM_OK; - break; - } - - if (!c->read->ready) { - break; - } - - n = c->recv(c, c->buffer->last, size); - - if (n == NGX_ERROR || n == 0) { - rc = NGX_STREAM_OK; - break; - } - - if (n == NGX_AGAIN) { - break; - } - - c->buffer->last += n; - - rc = ph->handler(s); - } +done: if (rc == NGX_AGAIN) { if (ngx_handle_read_event(c->read, 0) != NGX_OK) { @@ -311,6 +314,129 @@ ngx_stream_core_preread_phase(ngx_stream_session_t *s, } +static ngx_uint_t +ngx_stream_preread_can_peek(ngx_connection_t *c) +{ +#if (NGX_STREAM_SSL) + if (c->ssl) { + return 0; + } +#endif + + if ((ngx_event_flags & NGX_USE_CLEAR_EVENT) == 0) { + return 0; + } + +#if (NGX_HAVE_KQUEUE) + if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { + return 1; + } +#endif + +#if (NGX_HAVE_EPOLLRDHUP) + if ((ngx_event_flags & NGX_USE_EPOLL_EVENT) && ngx_use_epoll_rdhup) { + return 1; + } +#endif + + return 0; +} + + +static ngx_int_t +ngx_stream_preread_peek(ngx_stream_session_t *s, ngx_stream_phase_handler_t *ph) +{ + ssize_t n; + ngx_int_t rc; + ngx_err_t err; + ngx_connection_t *c; + + c = s->connection; + + n = recv(c->fd, (char *) c->buffer->last, + c->buffer->end - c->buffer->last, MSG_PEEK); + + err = ngx_socket_errno; + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, "stream recv(): %z", n); + + if (n == -1) { + if (err == NGX_EAGAIN) { + c->read->ready = 0; + return NGX_AGAIN; + } + + ngx_connection_error(c, err, "recv() failed"); + return NGX_STREAM_OK; + } + + if (n == 0) { + return NGX_STREAM_OK; + } + + c->buffer->last += n; + + rc = ph->handler(s); + + if (rc != NGX_AGAIN) { + c->buffer->last = c->buffer->pos; + return rc; + } + + if (c->buffer->last == c->buffer->end) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, "preread buffer full"); + return NGX_STREAM_BAD_REQUEST; + } + + if (c->read->pending_eof) { + return NGX_STREAM_OK; + } + + c->buffer->last = c->buffer->pos; + + return NGX_AGAIN; +} + + +static ngx_int_t +ngx_stream_preread(ngx_stream_session_t *s, ngx_stream_phase_handler_t *ph) +{ + ssize_t n; + ngx_int_t rc; + ngx_connection_t *c; + + c = s->connection; + + while (c->read->ready) { + + n = c->recv(c, c->buffer->last, c->buffer->end - c->buffer->last); + + if (n == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (n == NGX_ERROR || n == 0) { + return NGX_STREAM_OK; + } + + c->buffer->last += n; + + rc = ph->handler(s); + + if (rc != NGX_AGAIN) { + return rc; + } + + if (c->buffer->last == c->buffer->end) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, "preread buffer full"); + return NGX_STREAM_BAD_REQUEST; + } + } + + return NGX_AGAIN; +} + + ngx_int_t ngx_stream_core_content_phase(ngx_stream_session_t *s, ngx_stream_phase_handler_t *ph) @@ -338,6 +464,149 @@ ngx_stream_core_content_phase(ngx_stream_session_t *s, } +ngx_int_t +ngx_stream_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc) +{ + u_char *h, ch; + size_t i, dot_pos, host_len; + + enum { + sw_usual = 0, + sw_literal, + sw_rest + } state; + + dot_pos = host->len; + host_len = host->len; + + h = host->data; + + state = sw_usual; + + for (i = 0; i < host->len; i++) { + ch = h[i]; + + switch (ch) { + + case '.': + if (dot_pos == i - 1) { + return NGX_DECLINED; + } + dot_pos = i; + break; + + case ':': + if (state == sw_usual) { + host_len = i; + state = sw_rest; + } + break; + + case '[': + if (i == 0) { + state = sw_literal; + } + break; + + case ']': + if (state == sw_literal) { + host_len = i + 1; + state = sw_rest; + } + break; + + default: + + if (ngx_path_separator(ch)) { + return NGX_DECLINED; + } + + if (ch <= 0x20 || ch == 0x7f) { + return NGX_DECLINED; + } + + if (ch >= 'A' && ch <= 'Z') { + alloc = 1; + } + + break; + } + } + + if (dot_pos == host_len - 1) { + host_len--; + } + + if (host_len == 0) { + return NGX_DECLINED; + } + + if (alloc) { + host->data = ngx_pnalloc(pool, host_len); + if (host->data == NULL) { + return NGX_ERROR; + } + + ngx_strlow(host->data, h, host_len); + } + + host->len = host_len; + + return NGX_OK; +} + + +ngx_int_t +ngx_stream_find_virtual_server(ngx_stream_session_t *s, + ngx_str_t *host, ngx_stream_core_srv_conf_t **cscfp) +{ + ngx_stream_core_srv_conf_t *cscf; + + if (s->virtual_names == NULL) { + return NGX_DECLINED; + } + + cscf = ngx_hash_find_combined(&s->virtual_names->names, + ngx_hash_key(host->data, host->len), + host->data, host->len); + + if (cscf) { + *cscfp = cscf; + return NGX_OK; + } + +#if (NGX_PCRE) + + if (host->len && s->virtual_names->nregex) { + ngx_int_t n; + ngx_uint_t i; + ngx_stream_server_name_t *sn; + + sn = s->virtual_names->regex; + + for (i = 0; i < s->virtual_names->nregex; i++) { + + n = ngx_stream_regex_exec(s, sn[i].regex, host); + + if (n == NGX_DECLINED) { + continue; + } + + if (n == NGX_OK) { + *cscfp = sn[i].server; + return NGX_OK; + } + + return NGX_ERROR; + } + } + +#endif /* NGX_PCRE */ + + return NGX_DECLINED; +} + + static ngx_int_t ngx_stream_core_preconfiguration(ngx_conf_t *cf) { @@ -362,11 +631,8 @@ ngx_stream_core_create_main_conf(ngx_conf_t *cf) return NULL; } - if (ngx_array_init(&cmcf->listen, cf->pool, 4, sizeof(ngx_stream_listen_t)) - != NGX_OK) - { - return NULL; - } + cmcf->server_names_hash_max_size = NGX_CONF_UNSET_UINT; + cmcf->server_names_hash_bucket_size = NGX_CONF_UNSET_UINT; cmcf->variables_hash_max_size = NGX_CONF_UNSET_UINT; cmcf->variables_hash_bucket_size = NGX_CONF_UNSET_UINT; @@ -380,6 +646,14 @@ ngx_stream_core_init_main_conf(ngx_conf_t *cf, void *conf) { ngx_stream_core_main_conf_t *cmcf = conf; + ngx_conf_init_uint_value(cmcf->server_names_hash_max_size, 512); + ngx_conf_init_uint_value(cmcf->server_names_hash_bucket_size, + ngx_cacheline_size); + + cmcf->server_names_hash_bucket_size = + ngx_align(cmcf->server_names_hash_bucket_size, ngx_cacheline_size); + + ngx_conf_init_uint_value(cmcf->variables_hash_max_size, 1024); ngx_conf_init_uint_value(cmcf->variables_hash_bucket_size, 64); @@ -411,6 +685,13 @@ ngx_stream_core_create_srv_conf(ngx_conf_t *cf) * cscf->error_log = NULL; */ + if (ngx_array_init(&cscf->server_names, cf->temp_pool, 4, + sizeof(ngx_stream_server_name_t)) + != NGX_OK) + { + return NULL; + } + cscf->file_name = cf->conf_file->file.name.data; cscf->line = cf->conf_file->line; cscf->resolver_timeout = NGX_CONF_UNSET_MSEC; @@ -429,6 +710,9 @@ ngx_stream_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_stream_core_srv_conf_t *prev = parent; ngx_stream_core_srv_conf_t *conf = child; + ngx_str_t name; + ngx_stream_server_name_t *sn; + ngx_conf_merge_msec_value(conf->resolver_timeout, prev->resolver_timeout, 30000); @@ -476,6 +760,37 @@ ngx_stream_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_msec_value(conf->preread_timeout, prev->preread_timeout, 30000); + if (conf->server_names.nelts == 0) { + /* the array has 4 empty preallocated elements, so push cannot fail */ + sn = ngx_array_push(&conf->server_names); +#if (NGX_PCRE) + sn->regex = NULL; +#endif + sn->server = conf; + ngx_str_set(&sn->name, ""); + } + + sn = conf->server_names.elts; + name = sn[0].name; + +#if (NGX_PCRE) + if (sn->regex) { + name.len++; + name.data--; + } else +#endif + + if (name.data[0] == '.') { + name.len--; + name.data++; + } + + conf->server_name.len = name.len; + conf->server_name.data = ngx_pstrdup(cf->pool, &name); + if (conf->server_name.data == NULL) { + return NGX_CONF_ERROR; + } + return NGX_CONF_OK; } @@ -575,11 +890,10 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_stream_core_srv_conf_t *cscf = conf; - ngx_str_t *value, size; - ngx_url_t u; - ngx_uint_t i, n, backlog; - ngx_stream_listen_t *ls, *als, *nls; - ngx_stream_core_main_conf_t *cmcf; + ngx_str_t *value, size; + ngx_url_t u; + ngx_uint_t n, i, backlog; + ngx_stream_listen_opt_t lsopt; cscf->listen = 1; @@ -600,51 +914,67 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } - cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); + ngx_memzero(&lsopt, sizeof(ngx_stream_listen_opt_t)); - ls = ngx_array_push(&cmcf->listen); - if (ls == NULL) { - return NGX_CONF_ERROR; - } - - ngx_memzero(ls, sizeof(ngx_stream_listen_t)); - - ls->backlog = NGX_LISTEN_BACKLOG; - ls->rcvbuf = -1; - ls->sndbuf = -1; - ls->type = SOCK_STREAM; - ls->ctx = cf->ctx; - -#if (NGX_HAVE_TCP_FASTOPEN) - ls->fastopen = -1; + lsopt.backlog = NGX_LISTEN_BACKLOG; + lsopt.type = SOCK_STREAM; + lsopt.rcvbuf = -1; + lsopt.sndbuf = -1; +#if (NGX_HAVE_SETFIB) + lsopt.setfib = -1; +#endif +#if (NGX_HAVE_TCP_FASTOPEN) + lsopt.fastopen = -1; #endif - #if (NGX_HAVE_INET6) - ls->ipv6only = 1; + lsopt.ipv6only = 1; #endif backlog = 0; for (i = 2; i < cf->args->nelts; i++) { + if (ngx_strcmp(value[i].data, "default_server") == 0) { + lsopt.default_server = 1; + continue; + } + #if !(NGX_WIN32) if (ngx_strcmp(value[i].data, "udp") == 0) { - ls->type = SOCK_DGRAM; + lsopt.type = SOCK_DGRAM; continue; } #endif if (ngx_strcmp(value[i].data, "bind") == 0) { - ls->bind = 1; + lsopt.set = 1; + lsopt.bind = 1; continue; } +#if (NGX_HAVE_SETFIB) + if (ngx_strncmp(value[i].data, "setfib=", 7) == 0) { + lsopt.setfib = ngx_atoi(value[i].data + 7, value[i].len - 7); + lsopt.set = 1; + lsopt.bind = 1; + + if (lsopt.setfib == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid setfib \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + continue; + } +#endif + #if (NGX_HAVE_TCP_FASTOPEN) if (ngx_strncmp(value[i].data, "fastopen=", 9) == 0) { - ls->fastopen = ngx_atoi(value[i].data + 9, value[i].len - 9); - ls->bind = 1; + lsopt.fastopen = ngx_atoi(value[i].data + 9, value[i].len - 9); + lsopt.set = 1; + lsopt.bind = 1; - if (ls->fastopen == NGX_ERROR) { + if (lsopt.fastopen == NGX_ERROR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid fastopen \"%V\"", &value[i]); return NGX_CONF_ERROR; @@ -655,10 +985,11 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) #endif if (ngx_strncmp(value[i].data, "backlog=", 8) == 0) { - ls->backlog = ngx_atoi(value[i].data + 8, value[i].len - 8); - ls->bind = 1; + lsopt.backlog = ngx_atoi(value[i].data + 8, value[i].len - 8); + lsopt.set = 1; + lsopt.bind = 1; - if (ls->backlog == NGX_ERROR || ls->backlog == 0) { + if (lsopt.backlog == NGX_ERROR || lsopt.backlog == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid backlog \"%V\"", &value[i]); return NGX_CONF_ERROR; @@ -673,10 +1004,11 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) size.len = value[i].len - 7; size.data = value[i].data + 7; - ls->rcvbuf = ngx_parse_size(&size); - ls->bind = 1; + lsopt.rcvbuf = ngx_parse_size(&size); + lsopt.set = 1; + lsopt.bind = 1; - if (ls->rcvbuf == NGX_ERROR) { + if (lsopt.rcvbuf == NGX_ERROR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid rcvbuf \"%V\"", &value[i]); return NGX_CONF_ERROR; @@ -689,10 +1021,11 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) size.len = value[i].len - 7; size.data = value[i].data + 7; - ls->sndbuf = ngx_parse_size(&size); - ls->bind = 1; + lsopt.sndbuf = ngx_parse_size(&size); + lsopt.set = 1; + lsopt.bind = 1; - if (ls->sndbuf == NGX_ERROR) { + if (lsopt.sndbuf == NGX_ERROR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid sndbuf \"%V\"", &value[i]); return NGX_CONF_ERROR; @@ -701,13 +1034,40 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) continue; } + if (ngx_strncmp(value[i].data, "accept_filter=", 14) == 0) { +#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) + lsopt.accept_filter = (char *) &value[i].data[14]; + lsopt.set = 1; + lsopt.bind = 1; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "accept filters \"%V\" are not supported " + "on this platform, ignored", + &value[i]); +#endif + continue; + } + + if (ngx_strcmp(value[i].data, "deferred") == 0) { +#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT) + lsopt.deferred_accept = 1; + lsopt.set = 1; + lsopt.bind = 1; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the deferred accept is not supported " + "on this platform, ignored"); +#endif + continue; + } + if (ngx_strncmp(value[i].data, "ipv6only=o", 10) == 0) { #if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) if (ngx_strcmp(&value[i].data[10], "n") == 0) { - ls->ipv6only = 1; + lsopt.ipv6only = 1; } else if (ngx_strcmp(&value[i].data[10], "ff") == 0) { - ls->ipv6only = 0; + lsopt.ipv6only = 0; } else { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, @@ -716,11 +1076,13 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } - ls->bind = 1; + lsopt.set = 1; + lsopt.bind = 1; + continue; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "bind ipv6only is not supported " + "ipv6only is not supported " "on this platform"); return NGX_CONF_ERROR; #endif @@ -728,8 +1090,9 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (ngx_strcmp(value[i].data, "reuseport") == 0) { #if (NGX_HAVE_REUSEPORT) - ls->reuseport = 1; - ls->bind = 1; + lsopt.reuseport = 1; + lsopt.set = 1; + lsopt.bind = 1; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "reuseport is not supported " @@ -740,17 +1103,7 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (ngx_strcmp(value[i].data, "ssl") == 0) { #if (NGX_STREAM_SSL) - ngx_stream_ssl_conf_t *sslcf; - - sslcf = ngx_stream_conf_get_module_srv_conf(cf, - ngx_stream_ssl_module); - - sslcf->listen = 1; - sslcf->file = cf->conf_file->file.name.data; - sslcf->line = cf->conf_file->line; - - ls->ssl = 1; - + lsopt.ssl = 1; continue; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, @@ -763,10 +1116,10 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (ngx_strncmp(value[i].data, "so_keepalive=", 13) == 0) { if (ngx_strcmp(&value[i].data[13], "on") == 0) { - ls->so_keepalive = 1; + lsopt.so_keepalive = 1; } else if (ngx_strcmp(&value[i].data[13], "off") == 0) { - ls->so_keepalive = 2; + lsopt.so_keepalive = 2; } else { @@ -785,8 +1138,8 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (p > s.data) { s.len = p - s.data; - ls->tcp_keepidle = ngx_parse_time(&s, 1); - if (ls->tcp_keepidle == (time_t) NGX_ERROR) { + lsopt.tcp_keepidle = ngx_parse_time(&s, 1); + if (lsopt.tcp_keepidle == (time_t) NGX_ERROR) { goto invalid_so_keepalive; } } @@ -801,8 +1154,8 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (p > s.data) { s.len = p - s.data; - ls->tcp_keepintvl = ngx_parse_time(&s, 1); - if (ls->tcp_keepintvl == (time_t) NGX_ERROR) { + lsopt.tcp_keepintvl = ngx_parse_time(&s, 1); + if (lsopt.tcp_keepintvl == (time_t) NGX_ERROR) { goto invalid_so_keepalive; } } @@ -812,19 +1165,19 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (s.data < end) { s.len = end - s.data; - ls->tcp_keepcnt = ngx_atoi(s.data, s.len); - if (ls->tcp_keepcnt == NGX_ERROR) { + lsopt.tcp_keepcnt = ngx_atoi(s.data, s.len); + if (lsopt.tcp_keepcnt == NGX_ERROR) { goto invalid_so_keepalive; } } - if (ls->tcp_keepidle == 0 && ls->tcp_keepintvl == 0 - && ls->tcp_keepcnt == 0) + if (lsopt.tcp_keepidle == 0 && lsopt.tcp_keepintvl == 0 + && lsopt.tcp_keepcnt == 0) { goto invalid_so_keepalive; } - ls->so_keepalive = 1; + lsopt.so_keepalive = 1; #else @@ -836,7 +1189,8 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) #endif } - ls->bind = 1; + lsopt.set = 1; + lsopt.bind = 1; continue; @@ -851,39 +1205,51 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } if (ngx_strcmp(value[i].data, "proxy_protocol") == 0) { - ls->proxy_protocol = 1; + lsopt.proxy_protocol = 1; continue; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "the invalid \"%V\" parameter", &value[i]); + "invalid parameter \"%V\"", &value[i]); return NGX_CONF_ERROR; } - if (ls->type == SOCK_DGRAM) { + if (lsopt.type == SOCK_DGRAM) { +#if (NGX_HAVE_TCP_FASTOPEN) + if (lsopt.fastopen != -1) { + return "\"fastopen\" parameter is incompatible with \"udp\""; + } +#endif + if (backlog) { return "\"backlog\" parameter is incompatible with \"udp\""; } +#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) + if (lsopt.accept_filter) { + return "\"accept_filter\" parameter is incompatible with \"udp\""; + } +#endif + +#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT) + if (lsopt.deferred_accept) { + return "\"deferred\" parameter is incompatible with \"udp\""; + } +#endif + #if (NGX_STREAM_SSL) - if (ls->ssl) { + if (lsopt.ssl) { return "\"ssl\" parameter is incompatible with \"udp\""; } #endif - if (ls->so_keepalive) { + if (lsopt.so_keepalive) { return "\"so_keepalive\" parameter is incompatible with \"udp\""; } - if (ls->proxy_protocol) { + if (lsopt.proxy_protocol) { return "\"proxy_protocol\" parameter is incompatible with \"udp\""; } - -#if (NGX_HAVE_TCP_FASTOPEN) - if (ls->fastopen != -1) { - return "\"fastopen\" parameter is incompatible with \"udp\""; - } -#endif } for (n = 0; n < u.naddrs; n++) { @@ -897,40 +1263,12 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } } - if (n != 0) { - nls = ngx_array_push(&cmcf->listen); - if (nls == NULL) { - return NGX_CONF_ERROR; - } + lsopt.sockaddr = u.addrs[n].sockaddr; + lsopt.socklen = u.addrs[n].socklen; + lsopt.addr_text = u.addrs[n].name; + lsopt.wildcard = ngx_inet_wildcard(lsopt.sockaddr); - *nls = *ls; - - } else { - nls = ls; - } - - nls->sockaddr = u.addrs[n].sockaddr; - nls->socklen = u.addrs[n].socklen; - nls->addr_text = u.addrs[n].name; - nls->wildcard = ngx_inet_wildcard(nls->sockaddr); - - als = cmcf->listen.elts; - - for (i = 0; i < cmcf->listen.nelts - 1; i++) { - if (nls->type != als[i].type) { - continue; - } - - if (ngx_cmp_sockaddr(als[i].sockaddr, als[i].socklen, - nls->sockaddr, nls->socklen, 1) - != NGX_OK) - { - continue; - } - - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "duplicate \"%V\" address and port pair", - &nls->addr_text); + if (ngx_stream_add_listen(cf, cscf, &lsopt) != NGX_OK) { return NGX_CONF_ERROR; } @@ -942,6 +1280,107 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } +static char * +ngx_stream_core_server_name(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_core_srv_conf_t *cscf = conf; + + u_char ch; + ngx_str_t *value; + ngx_uint_t i; + ngx_stream_server_name_t *sn; + + value = cf->args->elts; + + for (i = 1; i < cf->args->nelts; i++) { + + ch = value[i].data[0]; + + if ((ch == '*' && (value[i].len < 3 || value[i].data[1] != '.')) + || (ch == '.' && value[i].len < 2)) + { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "server name \"%V\" is invalid", &value[i]); + return NGX_CONF_ERROR; + } + + if (ngx_strchr(value[i].data, '/')) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "server name \"%V\" has suspicious symbols", + &value[i]); + } + + sn = ngx_array_push(&cscf->server_names); + if (sn == NULL) { + return NGX_CONF_ERROR; + } + +#if (NGX_PCRE) + sn->regex = NULL; +#endif + sn->server = cscf; + + if (ngx_strcasecmp(value[i].data, (u_char *) "$hostname") == 0) { + sn->name = cf->cycle->hostname; + + } else { + sn->name = value[i]; + } + + if (value[i].data[0] != '~') { + ngx_strlow(sn->name.data, sn->name.data, sn->name.len); + continue; + } + +#if (NGX_PCRE) + { + u_char *p; + ngx_regex_compile_t rc; + u_char errstr[NGX_MAX_CONF_ERRSTR]; + + if (value[i].len == 1) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "empty regex in server name \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + value[i].len--; + value[i].data++; + + ngx_memzero(&rc, sizeof(ngx_regex_compile_t)); + + rc.pattern = value[i]; + rc.err.len = NGX_MAX_CONF_ERRSTR; + rc.err.data = errstr; + + for (p = value[i].data; p < value[i].data + value[i].len; p++) { + if (*p >= 'A' && *p <= 'Z') { + rc.options = NGX_REGEX_CASELESS; + break; + } + } + + sn->regex = ngx_stream_regex_compile(cf, &rc); + if (sn->regex == NULL) { + return NGX_CONF_ERROR; + } + + sn->name = value[i]; + cscf->captures = (rc.captures > 0); + } +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "using regex \"%V\" " + "requires PCRE library", &value[i]); + + return NGX_CONF_ERROR; +#endif + } + + return NGX_CONF_OK; +} + + static char * ngx_stream_core_resolver(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { diff --git a/src/deps/src/nginx/src/stream/ngx_stream_geo_module.c b/src/deps/src/nginx/src/stream/ngx_stream_geo_module.c index 4b4cad8fc..2324bef0d 100644 --- a/src/deps/src/nginx/src/stream/ngx_stream_geo_module.c +++ b/src/deps/src/nginx/src/stream/ngx_stream_geo_module.c @@ -190,7 +190,7 @@ ngx_stream_geo_cidr_variable(ngx_stream_session_t *s, p = inaddr6->s6_addr; if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { - inaddr = p[12] << 24; + inaddr = (in_addr_t) p[12] << 24; inaddr += p[13] << 16; inaddr += p[14] << 8; inaddr += p[15]; @@ -263,7 +263,7 @@ ngx_stream_geo_range_variable(ngx_stream_session_t *s, if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { p = inaddr6->s6_addr; - inaddr = p[12] << 24; + inaddr = (in_addr_t) p[12] << 24; inaddr += p[13] << 16; inaddr += p[14] << 8; inaddr += p[15]; @@ -1209,7 +1209,7 @@ ngx_stream_geo_value(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx, return gvvn->value; } - val = ngx_palloc(ctx->pool, sizeof(ngx_stream_variable_value_t)); + val = ngx_pcalloc(ctx->pool, sizeof(ngx_stream_variable_value_t)); if (val == NULL) { return NULL; } @@ -1221,8 +1221,6 @@ ngx_stream_geo_value(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx, } val->valid = 1; - val->no_cacheable = 0; - val->not_found = 0; gvvn = ngx_palloc(ctx->temp_pool, sizeof(ngx_stream_geo_variable_value_node_t)); diff --git a/src/deps/src/nginx/src/stream/ngx_stream_geoip_module.c b/src/deps/src/nginx/src/stream/ngx_stream_geoip_module.c index 6507b716e..3ee8f0e33 100644 --- a/src/deps/src/nginx/src/stream/ngx_stream_geoip_module.c +++ b/src/deps/src/nginx/src/stream/ngx_stream_geoip_module.c @@ -236,7 +236,7 @@ ngx_stream_geoip_addr(ngx_stream_session_t *s, ngx_stream_geoip_conf_t *gcf) if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { p = inaddr6->s6_addr; - inaddr = p[12] << 24; + inaddr = (in_addr_t) p[12] << 24; inaddr += p[13] << 16; inaddr += p[14] << 8; inaddr += p[15]; diff --git a/src/deps/src/nginx/src/stream/ngx_stream_handler.c b/src/deps/src/nginx/src/stream/ngx_stream_handler.c index 669b6a18d..a7ffc6e61 100644 --- a/src/deps/src/nginx/src/stream/ngx_stream_handler.c +++ b/src/deps/src/nginx/src/stream/ngx_stream_handler.c @@ -30,6 +30,7 @@ ngx_stream_init_connection(ngx_connection_t *c) struct sockaddr_in *sin; ngx_stream_in_addr_t *addr; ngx_stream_session_t *s; + ngx_stream_conf_ctx_t *ctx; ngx_stream_addr_conf_t *addr_conf; #if (NGX_HAVE_INET6) struct sockaddr_in6 *sin6; @@ -121,9 +122,12 @@ ngx_stream_init_connection(ngx_connection_t *c) return; } + ctx = addr_conf->default_server->ctx; + s->signature = NGX_STREAM_MODULE; - s->main_conf = addr_conf->ctx->main_conf; - s->srv_conf = addr_conf->ctx->srv_conf; + s->main_conf = ctx->main_conf; + s->srv_conf = ctx->srv_conf; + s->virtual_names = addr_conf->virtual_names; #if (NGX_STREAM_SSL) s->ssl = addr_conf->ssl; @@ -144,7 +148,7 @@ ngx_stream_init_connection(ngx_connection_t *c) ngx_log_error(NGX_LOG_INFO, c->log, 0, "*%uA %sclient %*s connected to %V", c->number, c->type == SOCK_DGRAM ? "udp " : "", - len, text, &addr_conf->addr_text); + len, text, &c->listening->addr_text); c->log->connection = c->number; c->log->handler = ngx_stream_log_error; diff --git a/src/deps/src/nginx/src/stream/ngx_stream_pass_module.c b/src/deps/src/nginx/src/stream/ngx_stream_pass_module.c new file mode 100644 index 000000000..2c1c60c6a --- /dev/null +++ b/src/deps/src/nginx/src/stream/ngx_stream_pass_module.c @@ -0,0 +1,327 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#define NGX_STREAM_PASS_MAX_PASSES 10 + + +typedef struct { + ngx_addr_t *addr; + ngx_stream_complex_value_t *addr_value; +} ngx_stream_pass_srv_conf_t; + + +static void ngx_stream_pass_handler(ngx_stream_session_t *s); +static ngx_int_t ngx_stream_pass_check_cycle(ngx_connection_t *c); +static void ngx_stream_pass_cleanup(void *data); +static ngx_int_t ngx_stream_pass_match(ngx_listening_t *ls, ngx_addr_t *addr); +static void *ngx_stream_pass_create_srv_conf(ngx_conf_t *cf); +static char *ngx_stream_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); + + +static ngx_command_t ngx_stream_pass_commands[] = { + + { ngx_string("pass"), + NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_stream_pass, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_stream_module_t ngx_stream_pass_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + ngx_stream_pass_create_srv_conf, /* create server configuration */ + NULL /* merge server configuration */ +}; + + +ngx_module_t ngx_stream_pass_module = { + NGX_MODULE_V1, + &ngx_stream_pass_module_ctx, /* module context */ + ngx_stream_pass_commands, /* module directives */ + NGX_STREAM_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void +ngx_stream_pass_handler(ngx_stream_session_t *s) +{ + ngx_url_t u; + ngx_str_t url; + ngx_addr_t *addr; + ngx_uint_t i; + ngx_listening_t *ls; + ngx_connection_t *c; + ngx_stream_pass_srv_conf_t *pscf; + + c = s->connection; + + c->log->action = "passing connection to port"; + + if (c->buffer && c->buffer->pos != c->buffer->last) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "cannot pass connection with preread data"); + goto failed; + } + + pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_pass_module); + + addr = pscf->addr; + + if (addr == NULL) { + if (ngx_stream_complex_value(s, pscf->addr_value, &url) != NGX_OK) { + goto failed; + } + + ngx_memzero(&u, sizeof(ngx_url_t)); + + u.url = url; + u.no_resolve = 1; + + if (ngx_parse_url(c->pool, &u) != NGX_OK) { + if (u.err) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "%s in pass \"%V\"", u.err, &u.url); + } + + goto failed; + } + + if (u.naddrs == 0) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "no addresses in pass \"%V\"", &u.url); + goto failed; + } + + if (u.no_port) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "no port in pass \"%V\"", &u.url); + goto failed; + } + + addr = &u.addrs[0]; + } + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "stream pass addr: \"%V\"", &addr->name); + + if (ngx_stream_pass_check_cycle(c) != NGX_OK) { + goto failed; + } + + ls = ngx_cycle->listening.elts; + + for (i = 0; i < ngx_cycle->listening.nelts; i++) { + + if (ngx_stream_pass_match(&ls[i], addr) != NGX_OK) { + continue; + } + + c->listening = &ls[i]; + + c->data = NULL; + c->buffer = NULL; + + *c->log = c->listening->log; + c->log->handler = NULL; + c->log->data = NULL; + + c->local_sockaddr = addr->sockaddr; + c->local_socklen = addr->socklen; + + c->listening->handler(c); + + return; + } + + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "port not found for \"%V\"", &addr->name); + + ngx_stream_finalize_session(s, NGX_STREAM_OK); + + return; + +failed: + + ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR); +} + + +static ngx_int_t +ngx_stream_pass_check_cycle(ngx_connection_t *c) +{ + ngx_uint_t *num; + ngx_pool_cleanup_t *cln; + + for (cln = c->pool->cleanup; cln; cln = cln->next) { + if (cln->handler != ngx_stream_pass_cleanup) { + continue; + } + + num = cln->data; + + if (++(*num) > NGX_STREAM_PASS_MAX_PASSES) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, "stream pass cycle"); + return NGX_ERROR; + } + + return NGX_OK; + } + + cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_uint_t)); + if (cln == NULL) { + return NGX_ERROR; + } + + cln->handler = ngx_stream_pass_cleanup; + + num = cln->data; + *num = 1; + + return NGX_OK; +} + + +static void +ngx_stream_pass_cleanup(void *data) +{ + return; +} + + +static ngx_int_t +ngx_stream_pass_match(ngx_listening_t *ls, ngx_addr_t *addr) +{ + if (!ls->wildcard) { + return ngx_cmp_sockaddr(ls->sockaddr, ls->socklen, + addr->sockaddr, addr->socklen, 1); + } + + if (ls->sockaddr->sa_family == addr->sockaddr->sa_family + && ngx_inet_get_port(ls->sockaddr) == ngx_inet_get_port(addr->sockaddr)) + { + return NGX_OK; + } + + return NGX_DECLINED; +} + + +static void * +ngx_stream_pass_create_srv_conf(ngx_conf_t *cf) +{ + ngx_stream_pass_srv_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_pass_srv_conf_t)); + if (conf == NULL) { + return NULL; + } + + /* + * set by ngx_pcalloc(): + * + * conf->addr = NULL; + * conf->addr_value = NULL; + */ + + return conf; +} + + +static char * +ngx_stream_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_pass_srv_conf_t *pscf = conf; + + ngx_url_t u; + ngx_str_t *value, *url; + ngx_stream_complex_value_t cv; + ngx_stream_core_srv_conf_t *cscf; + ngx_stream_compile_complex_value_t ccv; + + if (pscf->addr || pscf->addr_value) { + return "is duplicate"; + } + + cscf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_core_module); + + cscf->handler = ngx_stream_pass_handler; + + value = cf->args->elts; + + url = &value[1]; + + ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = url; + ccv.complex_value = &cv; + + if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + if (cv.lengths) { + pscf->addr_value = ngx_palloc(cf->pool, + sizeof(ngx_stream_complex_value_t)); + if (pscf->addr_value == NULL) { + return NGX_CONF_ERROR; + } + + *pscf->addr_value = cv; + + return NGX_CONF_OK; + } + + ngx_memzero(&u, sizeof(ngx_url_t)); + + u.url = *url; + u.no_resolve = 1; + + if (ngx_parse_url(cf->pool, &u) != NGX_OK) { + if (u.err) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "%s in \"%V\" of the \"pass\" directive", + u.err, &u.url); + } + + return NGX_CONF_ERROR; + } + + if (u.naddrs == 0) { + return "has no addresses"; + } + + if (u.no_port) { + return "has no port"; + } + + pscf->addr = &u.addrs[0]; + + return NGX_CONF_OK; +} diff --git a/src/deps/src/nginx/src/stream/ngx_stream_ssl_module.c b/src/deps/src/nginx/src/stream/ngx_stream_ssl_module.c index 1ba1825ce..ba444776a 100644 --- a/src/deps/src/nginx/src/stream/ngx_stream_ssl_module.c +++ b/src/deps/src/nginx/src/stream/ngx_stream_ssl_module.c @@ -40,12 +40,12 @@ static ngx_int_t ngx_stream_ssl_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_stream_ssl_add_variables(ngx_conf_t *cf); -static void *ngx_stream_ssl_create_conf(ngx_conf_t *cf); -static char *ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, +static void *ngx_stream_ssl_create_srv_conf(ngx_conf_t *cf); +static char *ngx_stream_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child); static ngx_int_t ngx_stream_ssl_compile_certificates(ngx_conf_t *cf, - ngx_stream_ssl_conf_t *conf); + ngx_stream_ssl_srv_conf_t *conf); static char *ngx_stream_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); @@ -90,21 +90,21 @@ static ngx_command_t ngx_stream_ssl_commands[] = { NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, handshake_timeout), + offsetof(ngx_stream_ssl_srv_conf_t, handshake_timeout), NULL }, { ngx_string("ssl_certificate"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_array_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, certificates), + offsetof(ngx_stream_ssl_srv_conf_t, certificates), NULL }, { ngx_string("ssl_certificate_key"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_array_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, certificate_keys), + offsetof(ngx_stream_ssl_srv_conf_t, certificate_keys), NULL }, { ngx_string("ssl_password_file"), @@ -118,63 +118,63 @@ static ngx_command_t ngx_stream_ssl_commands[] = { NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, dhparam), + offsetof(ngx_stream_ssl_srv_conf_t, dhparam), NULL }, { ngx_string("ssl_ecdh_curve"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, ecdh_curve), + offsetof(ngx_stream_ssl_srv_conf_t, ecdh_curve), NULL }, { ngx_string("ssl_protocols"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, protocols), + offsetof(ngx_stream_ssl_srv_conf_t, protocols), &ngx_stream_ssl_protocols }, { ngx_string("ssl_ciphers"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, ciphers), + offsetof(ngx_stream_ssl_srv_conf_t, ciphers), NULL }, { ngx_string("ssl_verify_client"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_enum_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, verify), + offsetof(ngx_stream_ssl_srv_conf_t, verify), &ngx_stream_ssl_verify }, { ngx_string("ssl_verify_depth"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, verify_depth), + offsetof(ngx_stream_ssl_srv_conf_t, verify_depth), NULL }, { ngx_string("ssl_client_certificate"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, client_certificate), + offsetof(ngx_stream_ssl_srv_conf_t, client_certificate), NULL }, { ngx_string("ssl_trusted_certificate"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, trusted_certificate), + offsetof(ngx_stream_ssl_srv_conf_t, trusted_certificate), NULL }, { ngx_string("ssl_prefer_server_ciphers"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, prefer_server_ciphers), + offsetof(ngx_stream_ssl_srv_conf_t, prefer_server_ciphers), NULL }, { ngx_string("ssl_session_cache"), @@ -188,37 +188,44 @@ static ngx_command_t ngx_stream_ssl_commands[] = { NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, session_tickets), + offsetof(ngx_stream_ssl_srv_conf_t, session_tickets), NULL }, { ngx_string("ssl_session_ticket_key"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_array_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, session_ticket_keys), + offsetof(ngx_stream_ssl_srv_conf_t, session_ticket_keys), NULL }, { ngx_string("ssl_session_timeout"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_sec_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, session_timeout), + offsetof(ngx_stream_ssl_srv_conf_t, session_timeout), NULL }, { ngx_string("ssl_crl"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, crl), + offsetof(ngx_stream_ssl_srv_conf_t, crl), NULL }, { ngx_string("ssl_conf_command"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE2, ngx_conf_set_keyval_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, conf_commands), + offsetof(ngx_stream_ssl_srv_conf_t, conf_commands), &ngx_stream_ssl_conf_command_post }, + { ngx_string("ssl_reject_handshake"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_srv_conf_t, reject_handshake), + NULL }, + { ngx_string("ssl_alpn"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE, ngx_stream_ssl_alpn, @@ -237,8 +244,8 @@ static ngx_stream_module_t ngx_stream_ssl_module_ctx = { NULL, /* create main configuration */ NULL, /* init main configuration */ - ngx_stream_ssl_create_conf, /* create server configuration */ - ngx_stream_ssl_merge_conf /* merge server configuration */ + ngx_stream_ssl_create_srv_conf, /* create server configuration */ + ngx_stream_ssl_merge_srv_conf /* merge server configuration */ }; @@ -332,11 +339,11 @@ static ngx_str_t ngx_stream_ssl_sess_id_ctx = ngx_string("STREAM"); static ngx_int_t ngx_stream_ssl_handler(ngx_stream_session_t *s) { - long rc; - X509 *cert; - ngx_int_t rv; - ngx_connection_t *c; - ngx_stream_ssl_conf_t *sslcf; + long rc; + X509 *cert; + ngx_int_t rv; + ngx_connection_t *c; + ngx_stream_ssl_srv_conf_t *sscf; if (!s->ssl) { return NGX_OK; @@ -344,23 +351,23 @@ ngx_stream_ssl_handler(ngx_stream_session_t *s) c = s->connection; - sslcf = ngx_stream_get_module_srv_conf(s, ngx_stream_ssl_module); + sscf = ngx_stream_get_module_srv_conf(s, ngx_stream_ssl_module); if (c->ssl == NULL) { c->log->action = "SSL handshaking"; - rv = ngx_stream_ssl_init_connection(&sslcf->ssl, c); + rv = ngx_stream_ssl_init_connection(&sscf->ssl, c); if (rv != NGX_OK) { return rv; } } - if (sslcf->verify) { + if (sscf->verify) { rc = SSL_get_verify_result(c->ssl->connection); if (rc != X509_V_OK - && (sslcf->verify != 3 || !ngx_ssl_verify_error_optional(rc))) + && (sscf->verify != 3 || !ngx_ssl_verify_error_optional(rc))) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "client SSL certificate verify error: (%l:%s)", @@ -371,7 +378,7 @@ ngx_stream_ssl_handler(ngx_stream_session_t *s) return NGX_ERROR; } - if (sslcf->verify == 1) { + if (sscf->verify == 1) { cert = SSL_get_peer_certificate(c->ssl->connection); if (cert == NULL) { @@ -396,7 +403,7 @@ ngx_stream_ssl_init_connection(ngx_ssl_t *ssl, ngx_connection_t *c) { ngx_int_t rc; ngx_stream_session_t *s; - ngx_stream_ssl_conf_t *sslcf; + ngx_stream_ssl_srv_conf_t *sscf; ngx_stream_core_srv_conf_t *cscf; s = c->data; @@ -418,9 +425,9 @@ ngx_stream_ssl_init_connection(ngx_ssl_t *ssl, ngx_connection_t *c) } if (rc == NGX_AGAIN) { - sslcf = ngx_stream_get_module_srv_conf(s, ngx_stream_ssl_module); + sscf = ngx_stream_get_module_srv_conf(s, ngx_stream_ssl_module); - ngx_add_timer(c->read, sslcf->handshake_timeout); + ngx_add_timer(c->read, sscf->handshake_timeout); c->ssl->handler = ngx_stream_ssl_handshake_handler; @@ -458,7 +465,112 @@ ngx_stream_ssl_handshake_handler(ngx_connection_t *c) static int ngx_stream_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) { + ngx_int_t rc; + ngx_str_t host; + const char *servername; + ngx_connection_t *c; + ngx_stream_session_t *s; + ngx_stream_ssl_srv_conf_t *sscf; + ngx_stream_core_srv_conf_t *cscf; + + c = ngx_ssl_get_connection(ssl_conn); + + if (c->ssl->handshaked) { + *ad = SSL_AD_NO_RENEGOTIATION; + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + s = c->data; + + servername = SSL_get_servername(ssl_conn, TLSEXT_NAMETYPE_host_name); + + if (servername == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, + "SSL server name: null"); + goto done; + } + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "SSL server name: \"%s\"", servername); + + host.len = ngx_strlen(servername); + + if (host.len == 0) { + goto done; + } + + host.data = (u_char *) servername; + + rc = ngx_stream_validate_host(&host, c->pool, 1); + + if (rc == NGX_ERROR) { + goto error; + } + + if (rc == NGX_DECLINED) { + goto done; + } + + rc = ngx_stream_find_virtual_server(s, &host, &cscf); + + if (rc == NGX_ERROR) { + goto error; + } + + if (rc == NGX_DECLINED) { + goto done; + } + + s->srv_conf = cscf->ctx->srv_conf; + + ngx_set_connection_log(c, cscf->error_log); + + sscf = ngx_stream_get_module_srv_conf(s, ngx_stream_ssl_module); + + if (sscf->ssl.ctx) { + if (SSL_set_SSL_CTX(ssl_conn, sscf->ssl.ctx) == NULL) { + goto error; + } + + /* + * SSL_set_SSL_CTX() only changes certs as of 1.0.0d + * adjust other things we care about + */ + + SSL_set_verify(ssl_conn, SSL_CTX_get_verify_mode(sscf->ssl.ctx), + SSL_CTX_get_verify_callback(sscf->ssl.ctx)); + + SSL_set_verify_depth(ssl_conn, SSL_CTX_get_verify_depth(sscf->ssl.ctx)); + +#if OPENSSL_VERSION_NUMBER >= 0x009080dfL + /* only in 0.9.8m+ */ + SSL_clear_options(ssl_conn, SSL_get_options(ssl_conn) & + ~SSL_CTX_get_options(sscf->ssl.ctx)); +#endif + + SSL_set_options(ssl_conn, SSL_CTX_get_options(sscf->ssl.ctx)); + +#ifdef SSL_OP_NO_RENEGOTIATION + SSL_set_options(ssl_conn, SSL_OP_NO_RENEGOTIATION); +#endif + } + +done: + + sscf = ngx_stream_get_module_srv_conf(s, ngx_stream_ssl_module); + + if (sscf->reject_handshake) { + c->ssl->handshake_rejected = 1; + *ad = SSL_AD_UNRECOGNIZED_NAME; + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + return SSL_TLSEXT_ERR_OK; + +error: + + *ad = SSL_AD_INTERNAL_ERROR; + return SSL_TLSEXT_ERR_ALERT_FATAL; } #endif @@ -513,7 +625,7 @@ ngx_stream_ssl_certificate(ngx_ssl_conn_t *ssl_conn, void *arg) ngx_uint_t i, nelts; ngx_connection_t *c; ngx_stream_session_t *s; - ngx_stream_ssl_conf_t *sslcf; + ngx_stream_ssl_srv_conf_t *sscf; ngx_stream_complex_value_t *certs, *keys; c = ngx_ssl_get_connection(ssl_conn); @@ -524,11 +636,11 @@ ngx_stream_ssl_certificate(ngx_ssl_conn_t *ssl_conn, void *arg) s = c->data; - sslcf = arg; + sscf = arg; - nelts = sslcf->certificate_values->nelts; - certs = sslcf->certificate_values->elts; - keys = sslcf->certificate_key_values->elts; + nelts = sscf->certificate_values->nelts; + certs = sscf->certificate_values->elts; + keys = sscf->certificate_key_values->elts; for (i = 0; i < nelts; i++) { @@ -547,7 +659,7 @@ ngx_stream_ssl_certificate(ngx_ssl_conn_t *ssl_conn, void *arg) "ssl key: \"%s\"", key.data); if (ngx_ssl_connection_certificate(c, c->pool, &cert, &key, - sslcf->passwords) + sscf->passwords) != NGX_OK) { return 0; @@ -643,53 +755,53 @@ ngx_stream_ssl_add_variables(ngx_conf_t *cf) static void * -ngx_stream_ssl_create_conf(ngx_conf_t *cf) +ngx_stream_ssl_create_srv_conf(ngx_conf_t *cf) { - ngx_stream_ssl_conf_t *scf; + ngx_stream_ssl_srv_conf_t *sscf; - scf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_ssl_conf_t)); - if (scf == NULL) { + sscf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_ssl_srv_conf_t)); + if (sscf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * - * scf->listen = 0; - * scf->protocols = 0; - * scf->certificate_values = NULL; - * scf->dhparam = { 0, NULL }; - * scf->ecdh_curve = { 0, NULL }; - * scf->client_certificate = { 0, NULL }; - * scf->trusted_certificate = { 0, NULL }; - * scf->crl = { 0, NULL }; - * scf->alpn = { 0, NULL }; - * scf->ciphers = { 0, NULL }; - * scf->shm_zone = NULL; + * sscf->protocols = 0; + * sscf->certificate_values = NULL; + * sscf->dhparam = { 0, NULL }; + * sscf->ecdh_curve = { 0, NULL }; + * sscf->client_certificate = { 0, NULL }; + * sscf->trusted_certificate = { 0, NULL }; + * sscf->crl = { 0, NULL }; + * sscf->alpn = { 0, NULL }; + * sscf->ciphers = { 0, NULL }; + * sscf->shm_zone = NULL; */ - scf->handshake_timeout = NGX_CONF_UNSET_MSEC; - scf->certificates = NGX_CONF_UNSET_PTR; - scf->certificate_keys = NGX_CONF_UNSET_PTR; - scf->passwords = NGX_CONF_UNSET_PTR; - scf->conf_commands = NGX_CONF_UNSET_PTR; - scf->prefer_server_ciphers = NGX_CONF_UNSET; - scf->verify = NGX_CONF_UNSET_UINT; - scf->verify_depth = NGX_CONF_UNSET_UINT; - scf->builtin_session_cache = NGX_CONF_UNSET; - scf->session_timeout = NGX_CONF_UNSET; - scf->session_tickets = NGX_CONF_UNSET; - scf->session_ticket_keys = NGX_CONF_UNSET_PTR; + sscf->handshake_timeout = NGX_CONF_UNSET_MSEC; + sscf->certificates = NGX_CONF_UNSET_PTR; + sscf->certificate_keys = NGX_CONF_UNSET_PTR; + sscf->passwords = NGX_CONF_UNSET_PTR; + sscf->conf_commands = NGX_CONF_UNSET_PTR; + sscf->prefer_server_ciphers = NGX_CONF_UNSET; + sscf->reject_handshake = NGX_CONF_UNSET; + sscf->verify = NGX_CONF_UNSET_UINT; + sscf->verify_depth = NGX_CONF_UNSET_UINT; + sscf->builtin_session_cache = NGX_CONF_UNSET; + sscf->session_timeout = NGX_CONF_UNSET; + sscf->session_tickets = NGX_CONF_UNSET; + sscf->session_ticket_keys = NGX_CONF_UNSET_PTR; - return scf; + return sscf; } static char * -ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) +ngx_stream_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) { - ngx_stream_ssl_conf_t *prev = parent; - ngx_stream_ssl_conf_t *conf = child; + ngx_stream_ssl_srv_conf_t *prev = parent; + ngx_stream_ssl_srv_conf_t *conf = child; ngx_pool_cleanup_t *cln; @@ -702,6 +814,8 @@ ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_value(conf->prefer_server_ciphers, prev->prefer_server_ciphers, 0); + ngx_conf_merge_value(conf->reject_handshake, prev->reject_handshake, 0); + ngx_conf_merge_bitmask_value(conf->protocols, prev->protocols, (NGX_CONF_BITMASK_SET |NGX_SSL_TLSv1|NGX_SSL_TLSv1_1 @@ -735,37 +849,23 @@ ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) conf->ssl.log = cf->log; - if (!conf->listen) { + if (conf->certificates) { + + if (conf->certificate_keys == NULL + || conf->certificate_keys->nelts < conf->certificates->nelts) + { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no \"ssl_certificate_key\" is defined " + "for certificate \"%V\"", + ((ngx_str_t *) conf->certificates->elts) + + conf->certificates->nelts - 1); + return NGX_CONF_ERROR; + } + + } else if (!conf->reject_handshake) { return NGX_CONF_OK; } - if (conf->certificates == NULL) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no \"ssl_certificate\" is defined for " - "the \"listen ... ssl\" directive in %s:%ui", - conf->file, conf->line); - return NGX_CONF_ERROR; - } - - if (conf->certificate_keys == NULL) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no \"ssl_certificate_key\" is defined for " - "the \"listen ... ssl\" directive in %s:%ui", - conf->file, conf->line); - return NGX_CONF_ERROR; - } - - if (conf->certificate_keys->nelts < conf->certificates->nelts) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no \"ssl_certificate_key\" is defined " - "for certificate \"%V\" and " - "the \"listen ... ssl\" directive in %s:%ui", - ((ngx_str_t *) conf->certificates->elts) - + conf->certificates->nelts - 1, - conf->file, conf->line); - return NGX_CONF_ERROR; - } - if (ngx_ssl_create(&conf->ssl, conf->protocols, NULL) != NGX_OK) { return NGX_CONF_ERROR; } @@ -818,7 +918,7 @@ ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) return NGX_CONF_ERROR; #endif - } else { + } else if (conf->certificates) { /* configure certificates */ @@ -910,13 +1010,17 @@ ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) static ngx_int_t ngx_stream_ssl_compile_certificates(ngx_conf_t *cf, - ngx_stream_ssl_conf_t *conf) + ngx_stream_ssl_srv_conf_t *conf) { ngx_str_t *cert, *key; ngx_uint_t i, nelts; ngx_stream_complex_value_t *cv; ngx_stream_compile_complex_value_t ccv; + if (conf->certificates == NULL) { + return NGX_OK; + } + cert = conf->certificates->elts; key = conf->certificate_keys->elts; nelts = conf->certificates->nelts; @@ -995,19 +1099,19 @@ found: static char * ngx_stream_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { - ngx_stream_ssl_conf_t *scf = conf; + ngx_stream_ssl_srv_conf_t *sscf = conf; ngx_str_t *value; - if (scf->passwords != NGX_CONF_UNSET_PTR) { + if (sscf->passwords != NGX_CONF_UNSET_PTR) { return "is duplicate"; } value = cf->args->elts; - scf->passwords = ngx_ssl_read_password_file(cf, &value[1]); + sscf->passwords = ngx_ssl_read_password_file(cf, &value[1]); - if (scf->passwords == NULL) { + if (sscf->passwords == NULL) { return NGX_CONF_ERROR; } @@ -1018,7 +1122,7 @@ ngx_stream_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) static char * ngx_stream_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { - ngx_stream_ssl_conf_t *scf = conf; + ngx_stream_ssl_srv_conf_t *sscf = conf; size_t len; ngx_str_t *value, name, size; @@ -1030,17 +1134,17 @@ ngx_stream_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) for (i = 1; i < cf->args->nelts; i++) { if (ngx_strcmp(value[i].data, "off") == 0) { - scf->builtin_session_cache = NGX_SSL_NO_SCACHE; + sscf->builtin_session_cache = NGX_SSL_NO_SCACHE; continue; } if (ngx_strcmp(value[i].data, "none") == 0) { - scf->builtin_session_cache = NGX_SSL_NONE_SCACHE; + sscf->builtin_session_cache = NGX_SSL_NONE_SCACHE; continue; } if (ngx_strcmp(value[i].data, "builtin") == 0) { - scf->builtin_session_cache = NGX_SSL_DFLT_BUILTIN_SCACHE; + sscf->builtin_session_cache = NGX_SSL_DFLT_BUILTIN_SCACHE; continue; } @@ -1055,7 +1159,7 @@ ngx_stream_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) goto invalid; } - scf->builtin_session_cache = n; + sscf->builtin_session_cache = n; continue; } @@ -1098,13 +1202,13 @@ ngx_stream_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } - scf->shm_zone = ngx_shared_memory_add(cf, &name, n, + sscf->shm_zone = ngx_shared_memory_add(cf, &name, n, &ngx_stream_ssl_module); - if (scf->shm_zone == NULL) { + if (sscf->shm_zone == NULL) { return NGX_CONF_ERROR; } - scf->shm_zone->init = ngx_ssl_session_cache_init; + sscf->shm_zone->init = ngx_ssl_session_cache_init; continue; } @@ -1112,8 +1216,8 @@ ngx_stream_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) goto invalid; } - if (scf->shm_zone && scf->builtin_session_cache == NGX_CONF_UNSET) { - scf->builtin_session_cache = NGX_SSL_NO_BUILTIN_SCACHE; + if (sscf->shm_zone && sscf->builtin_session_cache == NGX_CONF_UNSET) { + sscf->builtin_session_cache = NGX_SSL_NO_BUILTIN_SCACHE; } return NGX_CONF_OK; @@ -1132,14 +1236,14 @@ ngx_stream_ssl_alpn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { #ifdef TLSEXT_TYPE_application_layer_protocol_negotiation - ngx_stream_ssl_conf_t *scf = conf; + ngx_stream_ssl_srv_conf_t *sscf = conf; u_char *p; size_t len; ngx_str_t *value; ngx_uint_t i; - if (scf->alpn.len) { + if (sscf->alpn.len) { return "is duplicate"; } @@ -1156,19 +1260,19 @@ ngx_stream_ssl_alpn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) len += value[i].len + 1; } - scf->alpn.data = ngx_pnalloc(cf->pool, len); - if (scf->alpn.data == NULL) { + sscf->alpn.data = ngx_pnalloc(cf->pool, len); + if (sscf->alpn.data == NULL) { return NGX_CONF_ERROR; } - p = scf->alpn.data; + p = sscf->alpn.data; for (i = 1; i < cf->args->nelts; i++) { *p++ = value[i].len; p = ngx_cpymem(p, value[i].data, value[i].len); } - scf->alpn.len = len; + sscf->alpn.len = len; return NGX_CONF_OK; @@ -1195,8 +1299,13 @@ ngx_stream_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data) static ngx_int_t ngx_stream_ssl_init(ngx_conf_t *cf) { - ngx_stream_handler_pt *h; - ngx_stream_core_main_conf_t *cmcf; + ngx_uint_t a, p, s; + ngx_stream_handler_pt *h; + ngx_stream_conf_addr_t *addr; + ngx_stream_conf_port_t *port; + ngx_stream_ssl_srv_conf_t *sscf; + ngx_stream_core_srv_conf_t **cscfp, *cscf; + ngx_stream_core_main_conf_t *cmcf; cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); @@ -1207,5 +1316,58 @@ ngx_stream_ssl_init(ngx_conf_t *cf) *h = ngx_stream_ssl_handler; + if (cmcf->ports == NULL) { + return NGX_OK; + } + + port = cmcf->ports->elts; + for (p = 0; p < cmcf->ports->nelts; p++) { + + addr = port[p].addrs.elts; + for (a = 0; a < port[p].addrs.nelts; a++) { + + if (!addr[a].opt.ssl) { + continue; + } + + cscf = addr[a].default_server; + sscf = cscf->ctx->srv_conf[ngx_stream_ssl_module.ctx_index]; + + if (sscf->certificates) { + continue; + } + + if (!sscf->reject_handshake) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no \"ssl_certificate\" is defined for " + "the \"listen ... ssl\" directive in %s:%ui", + cscf->file_name, cscf->line); + return NGX_ERROR; + } + + /* + * if no certificates are defined in the default server, + * check all non-default server blocks + */ + + cscfp = addr[a].servers.elts; + for (s = 0; s < addr[a].servers.nelts; s++) { + + cscf = cscfp[s]; + sscf = cscf->ctx->srv_conf[ngx_stream_ssl_module.ctx_index]; + + if (sscf->certificates || sscf->reject_handshake) { + continue; + } + + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no \"ssl_certificate\" is defined for " + "the \"listen ... ssl\" directive in %s:%ui", + cscf->file_name, cscf->line); + return NGX_ERROR; + } + } + } + return NGX_OK; } diff --git a/src/deps/src/nginx/src/stream/ngx_stream_ssl_module.h b/src/deps/src/nginx/src/stream/ngx_stream_ssl_module.h index e7c825e9b..6f6d9aec3 100644 --- a/src/deps/src/nginx/src/stream/ngx_stream_ssl_module.h +++ b/src/deps/src/nginx/src/stream/ngx_stream_ssl_module.h @@ -18,10 +18,10 @@ typedef struct { ngx_msec_t handshake_timeout; ngx_flag_t prefer_server_ciphers; + ngx_flag_t reject_handshake; ngx_ssl_t ssl; - ngx_uint_t listen; ngx_uint_t protocols; ngx_uint_t verify; @@ -53,10 +53,7 @@ typedef struct { ngx_flag_t session_tickets; ngx_array_t *session_ticket_keys; - - u_char *file; - ngx_uint_t line; -} ngx_stream_ssl_conf_t; +} ngx_stream_ssl_srv_conf_t; extern ngx_module_t ngx_stream_ssl_module; diff --git a/src/deps/src/nginx/src/stream/ngx_stream_ssl_preread_module.c b/src/deps/src/nginx/src/stream/ngx_stream_ssl_preread_module.c index a236fc555..bc96adeee 100644 --- a/src/deps/src/nginx/src/stream/ngx_stream_ssl_preread_module.c +++ b/src/deps/src/nginx/src/stream/ngx_stream_ssl_preread_module.c @@ -33,6 +33,8 @@ typedef struct { static ngx_int_t ngx_stream_ssl_preread_handler(ngx_stream_session_t *s); static ngx_int_t ngx_stream_ssl_preread_parse_record( ngx_stream_ssl_preread_ctx_t *ctx, u_char *pos, u_char *last); +static ngx_int_t ngx_stream_ssl_preread_servername(ngx_stream_session_t *s, + ngx_str_t *servername); static ngx_int_t ngx_stream_ssl_preread_protocol_variable( ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_stream_ssl_preread_server_name_variable( @@ -187,6 +189,10 @@ ngx_stream_ssl_preread_handler(ngx_stream_session_t *s) return NGX_DECLINED; } + if (rc == NGX_OK) { + return ngx_stream_ssl_preread_servername(s, &ctx->host); + } + if (rc != NGX_AGAIN) { return rc; } @@ -404,9 +410,6 @@ ngx_stream_ssl_preread_parse_record(ngx_stream_ssl_preread_ctx_t *ctx, case sw_sni_host: ctx->host.len = (p[1] << 8) + p[2]; - ngx_log_debug1(NGX_LOG_DEBUG_STREAM, ctx->log, 0, - "ssl preread: SNI hostname \"%V\"", &ctx->host); - state = sw_ext; dst = NULL; size = ext; @@ -496,6 +499,54 @@ ngx_stream_ssl_preread_parse_record(ngx_stream_ssl_preread_ctx_t *ctx, } +static ngx_int_t +ngx_stream_ssl_preread_servername(ngx_stream_session_t *s, + ngx_str_t *servername) +{ + ngx_int_t rc; + ngx_str_t host; + ngx_connection_t *c; + ngx_stream_core_srv_conf_t *cscf; + + c = s->connection; + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "SSL preread server name: \"%V\"", servername); + + if (servername->len == 0) { + return NGX_OK; + } + + host = *servername; + + rc = ngx_stream_validate_host(&host, c->pool, 1); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_DECLINED) { + return NGX_OK; + } + + rc = ngx_stream_find_virtual_server(s, &host, &cscf); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_DECLINED) { + return NGX_OK; + } + + s->srv_conf = cscf->ctx->srv_conf; + + ngx_set_connection_log(c, cscf->error_log); + + return NGX_OK; +} + + static ngx_int_t ngx_stream_ssl_preread_protocol_variable(ngx_stream_session_t *s, ngx_variable_value_t *v, uintptr_t data) diff --git a/src/deps/src/nginx/src/stream/ngx_stream_variables.c b/src/deps/src/nginx/src/stream/ngx_stream_variables.c index 812689098..4658104e1 100644 --- a/src/deps/src/nginx/src/stream/ngx_stream_variables.c +++ b/src/deps/src/nginx/src/stream/ngx_stream_variables.c @@ -29,6 +29,8 @@ static ngx_int_t ngx_stream_variable_server_addr(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_stream_variable_server_port(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_variable_server_name(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_stream_variable_bytes(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_stream_variable_session_time(ngx_stream_session_t *s, @@ -91,6 +93,9 @@ static ngx_stream_variable_t ngx_stream_core_variables[] = { { ngx_string("server_port"), NULL, ngx_stream_variable_server_port, 0, 0, 0 }, + { ngx_string("server_name"), NULL, ngx_stream_variable_server_name, + 0, 0, 0 }, + { ngx_string("bytes_sent"), NULL, ngx_stream_variable_bytes, 0, 0, 0 }, @@ -721,6 +726,24 @@ ngx_stream_variable_server_port(ngx_stream_session_t *s, } +static ngx_int_t +ngx_stream_variable_server_name(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + ngx_stream_core_srv_conf_t *cscf; + + cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_core_module); + + v->len = cscf->server_name.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = cscf->server_name.data; + + return NGX_OK; +} + + static ngx_int_t ngx_stream_variable_bytes(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data) diff --git a/src/deps/src/stream-lua-nginx-module/config b/src/deps/src/stream-lua-nginx-module/config index 8db90628e..e1470b7ad 100644 --- a/src/deps/src/stream-lua-nginx-module/config +++ b/src/deps/src/stream-lua-nginx-module/config @@ -405,45 +405,6 @@ fi # ---------------------------------------- -if [ $USE_PCRE = YES -o $PCRE != NONE ] && [ $PCRE != NO -a $PCRE != YES ] && [ $PCRE2 != YES ]; then - # force pcre_version symbol to be required when PCRE is statically linked - case "$NGX_PLATFORM" in - Darwin:*) - ngx_feature="require defined symbols (-u)" - ngx_feature_name= - ngx_feature_path= - ngx_feature_libs="-Wl,-u,_strerror" - ngx_feature_run=no - ngx_feature_incs="#include " - ngx_feature_test='printf("hello");' - - . auto/feature - - if [ $ngx_found = yes ]; then - CORE_LIBS="-Wl,-u,_pcre_version $CORE_LIBS" - fi - ;; - - *) - ngx_feature="require defined symbols (--require-defined)" - ngx_feature_name= - ngx_feature_path= - ngx_feature_libs="-Wl,--require-defined=strerror" - ngx_feature_run=no - ngx_feature_incs="#include " - ngx_feature_test='printf("hello");' - - . auto/feature - - if [ $ngx_found = yes ]; then - CORE_LIBS="-Wl,--require-defined=pcre_version $CORE_LIBS" - fi - ;; - esac -fi - -# ---------------------------------------- - USE_MD5=YES USE_SHA1=YES diff --git a/src/deps/src/stream-lua-nginx-module/src/ngx_stream_lua_module.c b/src/deps/src/stream-lua-nginx-module/src/ngx_stream_lua_module.c index f7dca968f..5c9024e75 100644 --- a/src/deps/src/stream-lua-nginx-module/src/ngx_stream_lua_module.c +++ b/src/deps/src/stream-lua-nginx-module/src/ngx_stream_lua_module.c @@ -864,12 +864,20 @@ ngx_stream_lua_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_stream_lua_srv_conf_t *conf = child; #if (NGX_STREAM_SSL) +#if defined(nginx_version) && nginx_version >= 1025005 + ngx_stream_ssl_srv_conf_t *sscf; +#else ngx_stream_ssl_conf_t *sscf; +#endif dd("merge srv conf"); sscf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_ssl_module); +#if defined(nginx_version) && nginx_version >= 1025005 + if (sscf && sscf->ssl.ctx) { +#else if (sscf && sscf->listen) { +#endif if (conf->srv.ssl_client_hello_src.len == 0) { conf->srv.ssl_client_hello_src = prev->srv.ssl_client_hello_src; conf->srv.ssl_client_hello_src_key = diff --git a/src/deps/src/stream-lua-nginx-module/src/ngx_stream_lua_regex.c b/src/deps/src/stream-lua-nginx-module/src/ngx_stream_lua_regex.c index e32744ec6..080e5dd28 100644 --- a/src/deps/src/stream-lua-nginx-module/src/ngx_stream_lua_regex.c +++ b/src/deps/src/stream-lua-nginx-module/src/ngx_stream_lua_regex.c @@ -598,7 +598,11 @@ ngx_stream_lua_ffi_compile_regex(const unsigned char *pat, size_t pat_len, re_comp.captures = 0; } else { +#if (NGX_PCRE2) + ovecsize = (re_comp.captures + 1) * 2; +#else ovecsize = (re_comp.captures + 1) * 3; +#endif } dd("allocating cap with size: %d", (int) ovecsize); @@ -691,21 +695,21 @@ ngx_stream_lua_ffi_exec_regex(ngx_stream_lua_regex_t *re, int flags, { int rc, exec_opts = 0; size_t *ov; - ngx_uint_t ovecsize, n, i; + ngx_uint_t ovecpair, n, i; ngx_pool_t *old_pool; if (flags & NGX_LUA_RE_MODE_DFA) { - ovecsize = 2; + ovecpair = 1; re->ncaptures = 0; } else { - ovecsize = (re->ncaptures + 1) * 3; + ovecpair = re->ncaptures + 1; } old_pool = ngx_stream_lua_pcre_malloc_init(NULL); if (ngx_regex_match_data == NULL - || ovecsize > ngx_regex_match_data_size) + || ovecpair > ngx_regex_match_data_size) { /* * Allocate a match data if not yet allocated or smaller than @@ -716,8 +720,8 @@ ngx_stream_lua_ffi_exec_regex(ngx_stream_lua_regex_t *re, int flags, pcre2_match_data_free(ngx_regex_match_data); } - ngx_regex_match_data_size = ovecsize; - ngx_regex_match_data = pcre2_match_data_create(ovecsize / 3, NULL); + ngx_regex_match_data_size = ovecpair; + ngx_regex_match_data = pcre2_match_data_create(ovecpair, NULL); if (ngx_regex_match_data == NULL) { rc = PCRE2_ERROR_NOMEMORY; @@ -747,7 +751,7 @@ ngx_stream_lua_ffi_exec_regex(ngx_stream_lua_regex_t *re, int flags, #if (NGX_DEBUG) ngx_log_debug4(NGX_LOG_DEBUG_STREAM, ngx_cycle->log, 0, "pcre2_match failed: flags 0x%05Xd, options 0x%08Xd, rc %d, " - "ovecsize %ui", flags, exec_opts, rc, ovecsize); + "ovecpair %ui", flags, exec_opts, rc, ovecpair); #endif goto failed; @@ -759,11 +763,11 @@ ngx_stream_lua_ffi_exec_regex(ngx_stream_lua_regex_t *re, int flags, #if (NGX_DEBUG) ngx_log_debug5(NGX_LOG_DEBUG_STREAM, ngx_cycle->log, 0, "pcre2_match: flags 0x%05Xd, options 0x%08Xd, rc %d, " - "n %ui, ovecsize %ui", flags, exec_opts, rc, n, ovecsize); + "n %ui, ovecpair %ui", flags, exec_opts, rc, n, ovecpair); #endif - if (!(flags & NGX_LUA_RE_MODE_DFA) && n > ovecsize / 3) { - n = ovecsize / 3; + if (n > ovecpair) { + n = ovecpair; } for (i = 0; i < n; i++) { @@ -796,6 +800,21 @@ ngx_stream_lua_ffi_exec_regex(ngx_stream_lua_regex_t *re, int flags, re->ncaptures = 0; } else { + /* How pcre_exec() returns captured substrings + * The first two-thirds of the vector is used to pass back captured + * substrings, each substring using a pair of integers. The remaining + * third of the vector is used as workspace by pcre_exec() while + * matching capturing subpatterns, and is not available for passing + * back information. The number passed in ovecsize should always be a + * multiple of three. If it is not, it is rounded down. + * + * When a match is successful, information about captured substrings is + * returned in pairs of integers, starting at the beginning of ovector, + * and continuing up to two-thirds of its length at the most. The first + * element of each pair is set to the byte offset of the first character + * in a substring, and the second is set to the byte offset of the first + * character after the end of a substring. + */ ovecsize = (re->ncaptures + 1) * 3; } diff --git a/src/deps/src/stream-lua-nginx-module/src/ngx_stream_lua_socket_tcp.c b/src/deps/src/stream-lua-nginx-module/src/ngx_stream_lua_socket_tcp.c index 57f389d0d..9d5472a20 100644 --- a/src/deps/src/stream-lua-nginx-module/src/ngx_stream_lua_socket_tcp.c +++ b/src/deps/src/stream-lua-nginx-module/src/ngx_stream_lua_socket_tcp.c @@ -5250,6 +5250,34 @@ ngx_stream_lua_socket_tcp_setkeepalive(lua_State *L) luaL_checktype(L, 1, LUA_TTABLE); + r = ngx_stream_lua_get_req(L); + if (r == NULL) { + return luaL_error(L, "no request found"); + } + + llcf = ngx_stream_lua_get_module_loc_conf(r, ngx_stream_lua_module); + + /* luaL_checkinteger will throw error if the argument is not a number. + * e.g.: bad argument \#2 to '?' (number expected, got string) + * + * We should check the argument in advance; otherwise, + * throwing an exception in the middle can compromise data integrity. + * e.g.: set pc->connection to NULL without following cleanup. + */ + if (n >= 2 && !lua_isnil(L, 2)) { + timeout = (ngx_msec_t) luaL_checkinteger(L, 2); + + } else { + timeout = llcf->keepalive_timeout; + } + + if (n >= 3 && !lua_isnil(L, 3)) { + pool_size = luaL_checkinteger(L, 3); + + } else { + pool_size = llcf->pool_size; + } + lua_rawgeti(L, 1, SOCKET_CTX_INDEX); u = lua_touserdata(L, -1); lua_pop(L, 1); @@ -5271,11 +5299,6 @@ ngx_stream_lua_socket_tcp_setkeepalive(lua_State *L) return 2; } - r = ngx_stream_lua_get_req(L); - if (r == NULL) { - return luaL_error(L, "no request found"); - } - if (u->request != r) { return luaL_error(L, "bad request"); } @@ -5349,18 +5372,9 @@ ngx_stream_lua_socket_tcp_setkeepalive(lua_State *L) /* stack: obj timeout? size? pools cache_key */ - llcf = ngx_stream_lua_get_module_loc_conf(r, ngx_stream_lua_module); - if (spool == NULL) { /* create a new socket pool for the current peer key */ - if (n >= 3 && !lua_isnil(L, 3)) { - pool_size = luaL_checkinteger(L, 3); - - } else { - pool_size = llcf->pool_size; - } - if (pool_size <= 0) { msg = lua_pushfstring(L, "bad \"pool_size\" option value: %i", pool_size); @@ -5425,13 +5439,6 @@ ngx_stream_lua_socket_tcp_setkeepalive(lua_State *L) ngx_del_timer(c->write); } - if (n >= 2 && !lua_isnil(L, 2)) { - timeout = (ngx_msec_t) luaL_checkinteger(L, 2); - - } else { - timeout = llcf->keepalive_timeout; - } - #if (NGX_DEBUG) if (timeout == 0) { ngx_log_debug0(NGX_LOG_DEBUG_STREAM, r->connection->log, 0, diff --git a/src/deps/src/stream-lua-nginx-module/src/ngx_stream_lua_ssl_certby.c b/src/deps/src/stream-lua-nginx-module/src/ngx_stream_lua_ssl_certby.c index e7733ae4b..3ac8c7aa7 100644 --- a/src/deps/src/stream-lua-nginx-module/src/ngx_stream_lua_ssl_certby.c +++ b/src/deps/src/stream-lua-nginx-module/src/ngx_stream_lua_ssl_certby.c @@ -986,8 +986,8 @@ ngx_stream_lua_ffi_ssl_raw_client_addr(ngx_stream_lua_request_t *r, char **addr, int -ngx_stream_lua_ffi_cert_pem_to_der(const u_char *pem, size_t pem_len, u_char *der, - char **err) +ngx_stream_lua_ffi_cert_pem_to_der(const u_char *pem, size_t pem_len, + u_char *der, char **err) { int total, len; BIO *bio; @@ -1079,7 +1079,7 @@ ngx_stream_lua_ffi_priv_key_pem_to_der(const u_char *pem, size_t pem_len, return NGX_ERROR; } - pkey = PEM_read_bio_PrivateKey(in, NULL, NULL, (void *)passphrase); + pkey = PEM_read_bio_PrivateKey(in, NULL, NULL, (void *) passphrase); if (pkey == NULL) { BIO_free(in); *err = "PEM_read_bio_PrivateKey() failed"; @@ -1186,6 +1186,75 @@ ngx_stream_lua_ffi_parse_pem_cert(const u_char *pem, size_t pem_len, } +void * +ngx_stream_lua_ffi_parse_der_cert(const char *data, size_t len, + char **err) +{ + BIO *bio; + X509 *x509; + STACK_OF(X509) *chain; + + bio = BIO_new_mem_buf((char *) data, len); + if (bio == NULL) { + *err = "BIO_new_mem_buf() failed"; + ERR_clear_error(); + return NULL; + } + + x509 = d2i_X509_bio(bio, NULL); + if (x509 == NULL) { + *err = "d2i_X509_bio() failed"; + BIO_free(bio); + ERR_clear_error(); + return NULL; + } + + chain = sk_X509_new_null(); + if (chain == NULL) { + *err = "sk_X509_new_null() failed"; + X509_free(x509); + BIO_free(bio); + ERR_clear_error(); + return NULL; + } + + if (sk_X509_push(chain, x509) == 0) { + *err = "sk_X509_push() failed"; + sk_X509_free(chain); + X509_free(x509); + BIO_free(bio); + ERR_clear_error(); + return NULL; + } + + /* read rest of the chain */ + + while (!BIO_eof(bio)) { + x509 = d2i_X509_bio(bio, NULL); + if (x509 == NULL) { + *err = "d2i_X509_bio() failed in rest of chain"; + sk_X509_pop_free(chain, X509_free); + BIO_free(bio); + ERR_clear_error(); + return NULL; + } + + if (sk_X509_push(chain, x509) == 0) { + *err = "sk_X509_push() failed in rest of chain"; + sk_X509_pop_free(chain, X509_free); + X509_free(x509); + BIO_free(bio); + ERR_clear_error(); + return NULL; + } + } + + BIO_free(bio); + + return chain; +} + + void ngx_stream_lua_ffi_free_cert(void *cdata) { @@ -1223,6 +1292,35 @@ ngx_stream_lua_ffi_parse_pem_priv_key(const u_char *pem, size_t pem_len, } +void * +ngx_stream_lua_ffi_parse_der_priv_key(const char *data, size_t len, + char **err) +{ + BIO *bio = NULL; + EVP_PKEY *pkey = NULL; + + bio = BIO_new_mem_buf((char *) data, len); + if (bio == NULL) { + *err = "BIO_new_mem_buf() failed"; + BIO_free(bio); + ERR_clear_error(); + return NULL; + } + + pkey = d2i_PrivateKey_bio(bio, NULL); + if (pkey == NULL) { + *err = "d2i_PrivateKey_bio() failed"; + BIO_free(bio); + ERR_clear_error(); + return NULL; + } + + BIO_free(bio); + + return pkey; +} + + void ngx_stream_lua_ffi_free_priv_key(void *cdata) { @@ -1385,7 +1483,11 @@ ngx_stream_lua_ffi_ssl_verify_client(ngx_stream_lua_request_t *r, ngx_stream_lua_ctx_t *ctx; ngx_ssl_conn_t *ssl_conn; +#if defined(nginx_version) && nginx_version >= 1025005 + ngx_stream_ssl_srv_conf_t *sscf; +#else ngx_stream_ssl_conf_t *sscf; +#endif STACK_OF(X509) *chain = ca_certs; STACK_OF(X509_NAME) *name_chain = NULL; X509 *x509 = NULL; @@ -1505,4 +1607,27 @@ failed: } +int +ngx_stream_lua_ffi_ssl_client_random(ngx_stream_lua_request_t *r, + unsigned char *out, size_t *outlen, char **err) +{ + ngx_ssl_conn_t *ssl_conn; + + if (r->connection == NULL || r->connection->ssl == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + ssl_conn = r->connection->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NGX_ERROR; + } + + *outlen = SSL_get_client_random(ssl_conn, out, *outlen); + + return NGX_OK; +} + + #endif /* NGX_STREAM_SSL */ diff --git a/src/deps/src/stream-lua-nginx-module/t/048-match-dfa.t b/src/deps/src/stream-lua-nginx-module/t/048-match-dfa.t index 0544ae3c8..c2f4100d5 100644 --- a/src/deps/src/stream-lua-nginx-module/t/048-match-dfa.t +++ b/src/deps/src/stream-lua-nginx-module/t/048-match-dfa.t @@ -19,7 +19,7 @@ __DATA__ === TEST 1: matched with d --- stream_server_config content_by_lua_block { - m = ngx.re.match("hello", "(he|hell)", "d") + local m = ngx.re.match("hello", "(he|hell)", "d") if m then ngx.say(m[0]) else @@ -34,7 +34,7 @@ hell === TEST 2: matched with d + j --- stream_server_config content_by_lua_block { - m = ngx.re.match("hello", "(he|hell)", "jd") + local m = ngx.re.match("hello", "(he|hell)", "jd") if m then ngx.say(m[0]) else @@ -49,7 +49,7 @@ hell === TEST 3: not matched with j --- stream_server_config content_by_lua_block { - m = ngx.re.match("world", "(he|hell)", "d") + local m = ngx.re.match("world", "(he|hell)", "d") if m then ngx.say(m[0]) else @@ -64,7 +64,7 @@ not matched! === TEST 4: matched with do --- stream_server_config content_by_lua_block { - m = ngx.re.match("hello", "he|hell", "do") + local m = ngx.re.match("hello", "he|hell", "do") if m then ngx.say(m[0]) ngx.say(m[1]) @@ -83,7 +83,7 @@ nil === TEST 5: not matched with do --- stream_server_config content_by_lua_block { - m = ngx.re.match("world", "([0-9]+)", "do") + local m = ngx.re.match("world", "([0-9]+)", "do") if m then ngx.say(m[0]) else @@ -152,3 +152,26 @@ exec opts: 0 你 --- no_error_log [error] + + + +=== TEST 8: matched dfa after nfa +--- stream_server_config + content_by_lua_block { + local m1 = ngx.re.match("hello", "(a)(a)(a)") + if m1 then + ngx.say(m1[0]) + else + ngx.say("not matched!") + end + + local m2 = ngx.re.match("world", "w", "d") + if m2 then + ngx.say(m2[0]) + else + ngx.say("not matched!") + end + } +--- stream_response +not matched! +w diff --git a/src/deps/src/stream-lua-nginx-module/t/068-socket-keepalive.t b/src/deps/src/stream-lua-nginx-module/t/068-socket-keepalive.t index 1d2669b62..9588aa76d 100644 --- a/src/deps/src/stream-lua-nginx-module/t/068-socket-keepalive.t +++ b/src/deps/src/stream-lua-nginx-module/t/068-socket-keepalive.t @@ -2354,3 +2354,141 @@ ok lua tcp socket abort queueing --- no_error_log [error] + + + +=== TEST 53: wrong first argument of setkeepalive() +--- config + location /foo { + server_tokens off; + keepalive_timeout 60s; + echo foo; + } +--- stream_server_config + content_by_lua_block { + local sock = ngx.socket.tcp() + sock:settimeouts(1000, 1000, 1000) + + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local req = "GET /foo HTTP/1.1\r\nHost: localhost\r\nConnection: keepalive\r\n\r\n" + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + + ngx.say("request sent: ", bytes) + + local reader = sock:receiveuntil("\r\n0\r\n\r\n") + local data, err = reader() + if not data then + ngx.say("failed to receive response body: ", err) + return + end + + ngx.say("received response of ", #data, " bytes") + + local ok, err = sock:setkeepalive() + if not ok then + ngx.say("failed to set reusable: ", err) + return + end + + ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + local ok, err = sock:setkeepalive("not a number") + if not ok then + ngx.say("failed to set reusable: ", err) + return + end + } +--- stream_response +connected: 1 +request sent: 61 +received response of 156 bytes +--- error_log eval +qr/\Qbad argument #1 to 'setkeepalive' (number expected, got string)\E/ +--- no_error_log +[crit] +--- timeout: 4 + + + +=== TEST 54: wrong second argument of setkeepalive() +--- config + location /foo { + server_tokens off; + keepalive_timeout 60s; + echo foo; + } +--- stream_server_config + content_by_lua_block { + local sock = ngx.socket.tcp() + sock:settimeouts(1000, 1000, 1000) + + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local req = "GET /foo HTTP/1.1\r\nHost: localhost\r\nConnection: keepalive\r\n\r\n" + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + + ngx.say("request sent: ", bytes) + + local reader = sock:receiveuntil("\r\n0\r\n\r\n") + local data, err = reader() + if not data then + ngx.say("failed to receive response body: ", err) + return + end + + ngx.say("received response of ", #data, " bytes") + + local ok, err = sock:setkeepalive() + if not ok then + ngx.say("failed to set reusable: ", err) + return + end + + ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + local ok, err = sock:setkeepalive(10, "not a number") + if not ok then + ngx.say("failed to set reusable: ", err) + return + end + } +--- stream_response +connected: 1 +request sent: 61 +received response of 156 bytes +--- error_log eval +qr/\Qbad argument #2 to 'setkeepalive' (number expected, got string)\E/ +--- no_error_log +[crit] +--- timeout: 4 diff --git a/src/deps/src/stream-lua-nginx-module/t/140-ssl-c-api.t b/src/deps/src/stream-lua-nginx-module/t/140-ssl-c-api.t index e7cbea6bf..4ddae0edc 100644 --- a/src/deps/src/stream-lua-nginx-module/t/140-ssl-c-api.t +++ b/src/deps/src/stream-lua-nginx-module/t/140-ssl-c-api.t @@ -48,9 +48,15 @@ ffi.cdef[[ void *ngx_stream_lua_ffi_parse_pem_cert(const unsigned char *pem, size_t pem_len, char **err); + void *ngx_stream_lua_ffi_parse_der_cert(const unsigned char *der, + size_t der_len, char **err); + void *ngx_stream_lua_ffi_parse_pem_priv_key(const unsigned char *pem, size_t pem_len, char **err); + void *ngx_stream_lua_ffi_parse_der_priv_key(const unsigned char *der, + size_t der_len, char **err); + int ngx_stream_lua_ffi_set_cert(void *r, void *cdata, char **err); @@ -63,6 +69,9 @@ ffi.cdef[[ int ngx_stream_lua_ffi_ssl_verify_client(void *r, void *cdata, int depth, char **err); + int ngx_stream_lua_ffi_ssl_client_random(ngx_stream_lua_request_t *r, + unsigned char *out, size_t *outlen, char **err); + ]] _EOC_ } @@ -990,3 +999,240 @@ lua ssl server name: "test.com" --- no_error_log [error] [alert] + + + +=== TEST 10: DER cert + private key cdata +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate_by_lua_block { + collectgarbage() + + local ffi = require "ffi" + require "defines" + + local errmsg = ffi.new("char *[1]") + + local r = require "resty.core.base" .get_request() + if not r then + ngx.log(ngx.ERR, "no request found") + return + end + + ffi.C.ngx_stream_lua_ffi_ssl_clear_certs(r, errmsg) + + local f = assert(io.open("t/cert/test_der.crt", "rb")) + local cert_data = f:read("*all") + f:close() + + local cert = ffi.C.ngx_stream_lua_ffi_parse_der_cert(cert_data, #cert_data, errmsg) + if not cert then + ngx.log(ngx.ERR, "failed to parse DER cert: ", + ffi.string(errmsg[0])) + return + end + + local rc = ffi.C.ngx_stream_lua_ffi_set_cert(r, cert, errmsg) + if rc ~= 0 then + ngx.log(ngx.ERR, "failed to set cdata cert: ", + ffi.string(errmsg[0])) + return + end + + ffi.C.ngx_stream_lua_ffi_free_cert(cert) + + f = assert(io.open("t/cert/test_der.key", "rb")) + local pkey_data = f:read("*all") + f:close() + + local pkey = ffi.C.ngx_stream_lua_ffi_parse_der_priv_key(pkey_data, #pkey_data, errmsg) + if pkey == nil then + ngx.log(ngx.ERR, "failed to parse DER priv key: ", + ffi.string(errmsg[0])) + return + end + + local rc = ffi.C.ngx_stream_lua_ffi_set_priv_key(r, pkey, errmsg) + if rc ~= 0 then + ngx.log(ngx.ERR, "failed to set cdata priv key: ", + ffi.string(errmsg[0])) + return + end + + ffi.C.ngx_stream_lua_ffi_free_priv_key(pkey) + } + + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + return 'it works!\n'; + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/test.crt; + + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + +--- stream_response +connected: 1 +ssl handshake: userdata +received: it works! +close: 1 nil + +--- error_log +lua ssl server name: "test.com" + +--- no_error_log +[error] +[alert] + + + +=== TEST 11: client random +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate_by_lua_block { + collectgarbage() + + local ffi = require "ffi" + require "defines" + + local errmsg = ffi.new("char *[1]") + + local r = require "resty.core.base" .get_request() + if not r then + ngx.log(ngx.ERR, "no request found") + return + end + + -- test client random length + local out = ffi.new("unsigned char[?]", 0) + local sizep = ffi.new("size_t[1]", 0) + + local rc = ffi.C.ngx_stream_lua_ffi_ssl_client_random(r, out, sizep, errmsg) + if rc ~= 0 then + ngx.log(ngx.ERR, "failed to get client random length: ", + ffi.string(errmsg[0])) + return + end + + if tonumber(sizep[0]) ~= 32 then + ngx.log(ngx.ERR, "client random length does not equal 32") + return + end + + -- test client random value + out = ffi.new("unsigned char[?]", 50) + sizep = ffi.new("size_t[1]", 50) + + rc = ffi.C.ngx_stream_lua_ffi_ssl_client_random(r, out, sizep, errmsg) + if rc ~= 0 then + ngx.log(ngx.ERR, "failed to get client random: ", + ffi.string(errmsg[0])) + return + end + + local init_v = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + if ffi.string(out, sizep[0]) == init_v then + ngx.log(ngx.ERR, "maybe the client random value is incorrect") + return + end + } + + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + return 'it works!\n'; + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/test.crt; + + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + +--- stream_response +connected: 1 +ssl handshake: userdata +received: it works! +close: 1 nil + +--- error_log +lua ssl server name: "test.com" + +--- no_error_log +[error] +[alert] diff --git a/src/deps/src/stream-lua-nginx-module/t/cert/test_der.crt b/src/deps/src/stream-lua-nginx-module/t/cert/test_der.crt new file mode 100644 index 000000000..0b6ef6344 Binary files /dev/null and b/src/deps/src/stream-lua-nginx-module/t/cert/test_der.crt differ diff --git a/src/deps/src/stream-lua-nginx-module/t/cert/test_der.key b/src/deps/src/stream-lua-nginx-module/t/cert/test_der.key new file mode 100644 index 000000000..767df379a Binary files /dev/null and b/src/deps/src/stream-lua-nginx-module/t/cert/test_der.key differ diff --git a/src/linux/Dockerfile-centos b/src/linux/Dockerfile-centos index 0dfdb1d15..4a975b35b 100644 --- a/src/linux/Dockerfile-centos +++ b/src/linux/Dockerfile-centos @@ -1,7 +1,7 @@ FROM quay.io/centos/centos:stream8@sha256:7b56a6667ca1e57935a055307bca430e1c3d9d328365240c69e21a225f507a5f as builder ENV OS=centos -ENV NGINX_VERSION 1.24.0 +ENV NGINX_VERSION 1.26.0 # Copy centos repo COPY src/linux/centos.repo /etc/yum.repos.d/centos.repo @@ -23,7 +23,7 @@ RUN dnf update -y && \ yum-utils redhat-lsb-core && \ dnf module -y reset ruby && dnf module -y enable ruby:2.6 && dnf module -y install ruby:2.6/common && \ gem install fpm && \ - # TODO: find a way to install nginx-1.24.0 as it's not yet available in centos 8 + # TODO: find a way to install nginx-1.26.0 as it's not yet available in centos 8 dnf install nginx-${NGINX_VERSION} -y WORKDIR /tmp/bunkerweb/deps diff --git a/src/linux/Dockerfile-debian b/src/linux/Dockerfile-debian index a24f68b84..44aed0e74 100644 --- a/src/linux/Dockerfile-debian +++ b/src/linux/Dockerfile-debian @@ -1,7 +1,7 @@ FROM debian:bookworm-slim@sha256:d02c76d82364cedca16ba3ed6f9102406fa9fa8833076a609cabf14270f43dfc as builder ENV OS=debian -ENV NGINX_VERSION 1.24.0 +ENV NGINX_VERSION 1.26.0 # Install Nginx and dependencies RUN apt update && \ diff --git a/src/linux/Dockerfile-fedora b/src/linux/Dockerfile-fedora index 28e85a3c2..26d3b3262 100644 --- a/src/linux/Dockerfile-fedora +++ b/src/linux/Dockerfile-fedora @@ -1,7 +1,7 @@ FROM fedora:39@sha256:ee042eb5527cfe117d279af0fff4461ad0c5441a8ef36deea9d98a2eb0130a90 as builder ENV OS=fedora -ENV NGINX_VERSION 1.24.0 +ENV NGINX_VERSION 1.26.0 # Install Nginx, fpm and dependencies RUN dnf update -y && \ diff --git a/src/linux/Dockerfile-rhel b/src/linux/Dockerfile-rhel index e43c8aa99..b495823bd 100644 --- a/src/linux/Dockerfile-rhel +++ b/src/linux/Dockerfile-rhel @@ -1,7 +1,7 @@ FROM redhat/ubi8:8.10@sha256:a424544997de1960a93466b57d12f1f3fac62be0f4cd35482435bae305a6ca27 as builder ENV OS=rhel -ENV NGINX_VERSION 1.24.0 +ENV NGINX_VERSION 1.26.0 # Copy centos repo COPY src/linux/centos.repo /etc/yum.repos.d/centos.repo diff --git a/src/linux/Dockerfile-rhel9 b/src/linux/Dockerfile-rhel9 index 1b0ecfe79..f32c1f797 100644 --- a/src/linux/Dockerfile-rhel9 +++ b/src/linux/Dockerfile-rhel9 @@ -1,7 +1,7 @@ FROM redhat/ubi9:9.4@sha256:ed84f34cd929ea6b0c247b6daef54dd79602804a32480a052951021caf429494 as builder ENV OS=rhel -ENV NGINX_VERSION 1.24.0 +ENV NGINX_VERSION 1.26.0 # Copy centos repo COPY src/linux/centos-9.repo /etc/yum.repos.d/centos.repo diff --git a/src/linux/Dockerfile-ubuntu b/src/linux/Dockerfile-ubuntu index 90b756970..f2948d54c 100644 --- a/src/linux/Dockerfile-ubuntu +++ b/src/linux/Dockerfile-ubuntu @@ -1,7 +1,7 @@ FROM ubuntu:22.04@sha256:f9d633ff6640178c2d0525017174a688e2c1aef28f0a0130b26bd5554491f0da as builder ENV OS=ubuntu -ENV NGINX_VERSION 1.24.0 +ENV NGINX_VERSION 1.26.0 # Install Nginx and dependencies RUN apt update && \ diff --git a/src/linux/Dockerfile-ubuntu-noble b/src/linux/Dockerfile-ubuntu-noble index ef06f573e..d79ef49eb 100644 --- a/src/linux/Dockerfile-ubuntu-noble +++ b/src/linux/Dockerfile-ubuntu-noble @@ -1,17 +1,17 @@ FROM ubuntu:24.04@sha256:562456a05a0dbd62a671c1854868862a4687bf979a96d48ae8e766642cd911e8 as builder ENV OS=ubuntu -ENV NGINX_VERSION 1.24.0 +ENV NGINX_VERSION 1.26.0 # Install Nginx and dependencies RUN apt update && \ apt install -y --no-install-recommends curl gnupg2 ca-certificates lsb-release ubuntu-keyring software-properties-common \ bash libssl-dev git zlib1g-dev libyajl2 libyajl-dev yajl-tools pkgconf libcurl4-openssl-dev libgeoip-dev liblmdb-dev apt-utils build-essential autoconf libtool automake g++ gcc libxml2-dev make musl-dev gnupg patch libreadline-dev libpcre3-dev libgd-dev python3 python3-dev python3-pip -y && \ - echo "deb https://nginx.org/packages/ubuntu/ jammy nginx" > /etc/apt/sources.list.d/nginx.list && \ - echo "deb-src https://nginx.org/packages/ubuntu/ jammy nginx" >> /etc/apt/sources.list.d/nginx.list && \ + echo "deb https://nginx.org/packages/ubuntu/ noble nginx" > /etc/apt/sources.list.d/nginx.list && \ + echo "deb-src https://nginx.org/packages/ubuntu/ noble nginx" >> /etc/apt/sources.list.d/nginx.list && \ apt-key adv --keyserver keyserver.ubuntu.com --recv-keys ABF5BD827BD9BF62 && \ apt-get update && \ - apt-get install -y --no-install-recommends nginx=${NGINX_VERSION}-1~jammy + apt-get install -y --no-install-recommends nginx=${NGINX_VERSION}-1~noble WORKDIR /tmp/bunkerweb/deps diff --git a/src/linux/fpm-centos b/src/linux/fpm-centos index 9562529e5..0c97b89c5 100644 --- a/src/linux/fpm-centos +++ b/src/linux/fpm-centos @@ -3,7 +3,7 @@ --license agpl3 --version %VERSION% --architecture x86_64 ---depends bash --depends epel-release --depends python39 --depends 'nginx = 1:1.24.0-1.el8.ngx' --depends libcurl-devel --depends libxml2 --depends yajl --depends lmdb-libs --depends GeoIP-devel --depends file-libs --depends net-tools --depends gd --depends sudo --depends procps --depends lsof --depends brotli --depends openssl --depends libpq --depends mysql --depends postgresql --depends sqlite --depends unzip +--depends bash --depends epel-release --depends python39 --depends 'nginx = 1:1.26.0-1.el8.ngx' --depends libcurl-devel --depends libxml2 --depends yajl --depends lmdb-libs --depends GeoIP-devel --depends file-libs --depends net-tools --depends gd --depends sudo --depends procps --depends lsof --depends brotli --depends openssl --depends libpq --depends mysql --depends postgresql --depends sqlite --depends unzip --description "BunkerWeb %VERSION% for CentOS Stream 8" --url "https://www.bunkerweb.io" --maintainer "Bunkerity " diff --git a/src/linux/fpm-debian b/src/linux/fpm-debian index 8251d5348..da83a1e0c 100644 --- a/src/linux/fpm-debian +++ b/src/linux/fpm-debian @@ -3,7 +3,7 @@ --license agpl3 --version %VERSION% --architecture %ARCH% ---depends bash --depends python3 --depends procps --depends python3-pip --depends 'nginx = 1.24.0-1~bookworm' --depends libcurl4 --depends libgeoip-dev --depends libxml2 --depends libyajl2 --depends libmagic1 --depends net-tools --depends sudo --depends lsof --depends libpq5 --depends libpcre3 --depends libcap2-bin --depends logrotate --depends mariadb-client --depends postgresql-client --depends sqlite3 --depends unzip +--depends bash --depends python3 --depends procps --depends python3-pip --depends 'nginx = 1.26.0-1~bookworm' --depends libcurl4 --depends libgeoip-dev --depends libxml2 --depends libyajl2 --depends libmagic1 --depends net-tools --depends sudo --depends lsof --depends libpq5 --depends libpcre3 --depends libcap2-bin --depends logrotate --depends mariadb-client --depends postgresql-client --depends sqlite3 --depends unzip --description "BunkerWeb %VERSION% for Debian 12" --url "https://www.bunkerweb.io" --maintainer "Bunkerity " diff --git a/src/linux/fpm-fedora b/src/linux/fpm-fedora index 0b51fe22b..d022e0fb1 100644 --- a/src/linux/fpm-fedora +++ b/src/linux/fpm-fedora @@ -3,7 +3,7 @@ --license agpl3 --version %VERSION% --architecture %ARCH% ---depends bash --depends python3 --depends 'nginx >= 1:1.24.0' --depends 'nginx < 1:1.25.0' --depends libcurl-devel --depends libxml2 --depends yajl --depends lmdb-libs --depends geoip-devel --depends gd --depends sudo --depends procps --depends lsof --depends nginx-mod-stream --depends pcre --depends libpq --depends libcap --depends openssl --depends logrotate --depends mysql --depends postgresql --depends sqlite3 --depends unzip +--depends bash --depends python3 --depends 'nginx >= 1:1.26.0' --depends 'nginx < 1:1.27.0' --depends libcurl-devel --depends libxml2 --depends yajl --depends lmdb-libs --depends geoip-devel --depends gd --depends sudo --depends procps --depends lsof --depends nginx-mod-stream --depends pcre --depends libpq --depends libcap --depends openssl --depends logrotate --depends mysql --depends postgresql --depends sqlite3 --depends unzip --description "BunkerWeb %VERSION% for Fedora 39" --url "https://www.bunkerweb.io" --maintainer "Bunkerity " diff --git a/src/linux/fpm-rhel b/src/linux/fpm-rhel index d39f67d8b..f3cc795f5 100644 --- a/src/linux/fpm-rhel +++ b/src/linux/fpm-rhel @@ -3,7 +3,7 @@ --license agpl3 --version %VERSION% --architecture %ARCH% ---depends bash --depends python39 --depends 'nginx >= 1:1.24.0' --depends 'nginx < 1:1.25.0' --depends libcurl-devel --depends libxml2 --depends yajl --depends file-libs --depends net-tools --depends gd --depends sudo --depends procps --depends lsof --depends geoip --depends libpq --depends libcap --depends openssl --depends sqlite --depends unzip +--depends bash --depends python39 --depends 'nginx >= 1:1.26.0' --depends 'nginx < 1:1.27.0' --depends libcurl-devel --depends libxml2 --depends yajl --depends file-libs --depends net-tools --depends gd --depends sudo --depends procps --depends lsof --depends geoip --depends libpq --depends libcap --depends openssl --depends sqlite --depends unzip --description "BunkerWeb %VERSION% for RHEL 8" --url "https://www.bunkerweb.io" --maintainer "Bunkerity " diff --git a/src/linux/fpm-rhel9 b/src/linux/fpm-rhel9 index 99dae9a8c..91b30f0aa 100644 --- a/src/linux/fpm-rhel9 +++ b/src/linux/fpm-rhel9 @@ -3,7 +3,7 @@ --license agpl3 --version %VERSION% --architecture %ARCH% ---depends bash --depends python39 --depends 'nginx >= 1:1.24.0' --depends 'nginx < 1:1.25.0' --depends libcurl-devel --depends libxml2 --depends yajl --depends file-libs --depends net-tools --depends gd --depends sudo --depends procps --depends lsof --depends libmaxminddb --depends libpq --depends libcap --depends openssl --depends mysql --depends postgresql --depends sqlite --depends unzip +--depends bash --depends python39 --depends 'nginx >= 1:1.26.0' --depends 'nginx < 1:1.27.0' --depends libcurl-devel --depends libxml2 --depends yajl --depends file-libs --depends net-tools --depends gd --depends sudo --depends procps --depends lsof --depends libmaxminddb --depends libpq --depends libcap --depends openssl --depends mysql --depends postgresql --depends sqlite --depends unzip --description "BunkerWeb %VERSION% for RHEL 9" --url "https://www.bunkerweb.io" --maintainer "Bunkerity " diff --git a/src/linux/fpm-ubuntu b/src/linux/fpm-ubuntu index 11dbeeffe..b30173fe5 100644 --- a/src/linux/fpm-ubuntu +++ b/src/linux/fpm-ubuntu @@ -3,7 +3,7 @@ --license agpl3 --version %VERSION% --architecture %ARCH% ---depends bash --depends python3 --depends python3-pip --depends 'nginx = 1.24.0-1~jammy' --depends libcurl4 --depends libgeoip-dev --depends libxml2 --depends libyajl2 --depends libmagic1 --depends net-tools --depends sudo --depends procps --depends lsof --depends libpq5 --depends libcap2-bin --depends logrotate --depends mariadb-client --depends postgresql-client --depends sqlite3 --depends unzip +--depends bash --depends python3 --depends python3-pip --depends 'nginx = 1.26.0-1~jammy' --depends libcurl4 --depends libgeoip-dev --depends libxml2 --depends libyajl2 --depends libmagic1 --depends net-tools --depends sudo --depends procps --depends lsof --depends libpq5 --depends libcap2-bin --depends logrotate --depends mariadb-client --depends postgresql-client --depends sqlite3 --depends unzip --description "BunkerWeb %VERSION% for Ubuntu 22.04" --url "https://www.bunkerweb.io" --maintainer "Bunkerity " diff --git a/src/linux/fpm-ubuntu-noble b/src/linux/fpm-ubuntu-noble index 545a18b1f..595aeb742 100644 --- a/src/linux/fpm-ubuntu-noble +++ b/src/linux/fpm-ubuntu-noble @@ -3,7 +3,7 @@ --license agpl3 --version %VERSION% --architecture %ARCH% ---depends bash --depends python3 --depends python3-pip --depends 'nginx = 1.24.0-1~jammy' --depends libcurl4 --depends libgeoip-dev --depends libxml2 --depends libyajl2 --depends libmagic1 --depends net-tools --depends sudo --depends procps --depends lsof --depends libpq5 --depends libcap2-bin --depends logrotate --depends mariadb-client --depends postgresql-client --depends sqlite3 --depends unzip --depends libpcre3 +--depends bash --depends python3 --depends python3-pip --depends 'nginx = 1.26.0-1~noble' --depends libcurl4 --depends libgeoip-dev --depends libxml2 --depends libyajl2 --depends libmagic1 --depends net-tools --depends sudo --depends procps --depends lsof --depends libpq5 --depends libcap2-bin --depends logrotate --depends mariadb-client --depends postgresql-client --depends sqlite3 --depends unzip --depends libpcre3 --description "BunkerWeb %VERSION% for Ubuntu 24.04" --url "https://www.bunkerweb.io" --maintainer "Bunkerity " diff --git a/src/linux/scripts/start.sh b/src/linux/scripts/start.sh index 4165f4c9f..a1512924e 100644 --- a/src/linux/scripts/start.sh +++ b/src/linux/scripts/start.sh @@ -169,7 +169,7 @@ function start() { MODSECURITY_CRS_VERSION="3" fi sudo -E -u nginx -g nginx /bin/bash -c "echo -ne 'IS_LOADING=yes\nUSE_BUNKERNET=no\nSEND_ANONYMOUS_REPORT=no\nSERVER_NAME=\nMODSECURITY_CRS_VERSION=${MODSECURITY_CRS_VERSION}\nDNS_RESOLVERS=${DNS_RESOLVERS}\nAPI_HTTP_PORT=${API_HTTP_PORT}\nAPI_LISTEN_IP=${API_LISTEN_IP}\nAPI_SERVER_NAME=${API_SERVER_NAME}\nAPI_WHITELIST_IP=${API_WHITELIST_IP}\nUSE_REAL_IP=${USE_REAL_IP}\nUSE_PROXY_PROTOCOL=${USE_PROXY_PROTOCOL}\nREAL_IP_FROM=${REAL_IP_FROM}\nREAL_IP_HEADER=${REAL_IP_HEADER}\nHTTP_PORT=${HTTP_PORT}\nHTTPS_PORT=${HTTPS_PORT}\n' > /var/tmp/bunkerweb/tmp.env" - sudo -E -u nginx -g nginx /bin/bash -c "PYTHONPATH=/usr/share/bunkerweb/deps/python/ /usr/share/bunkerweb/gen/main.py --variables /var/tmp/bunkerweb/tmp.env --no-linux-reload" + sudo -E -u nginx -g nginx /bin/bash -c "PYTHONPATH=/usr/share/bunkerweb/deps/python/ /usr/share/bunkerweb/gen/main.py --variables /var/tmp/bunkerweb/tmp.env" # shellcheck disable=SC2181 if [ $? -ne 0 ] ; then log "SYSTEMCTL" "❌" "Error while generating config from /var/tmp/bunkerweb/tmp.env" diff --git a/src/scheduler/main.py b/src/scheduler/main.py index 4ab7431ba..86ef97658 100644 --- a/src/scheduler/main.py +++ b/src/scheduler/main.py @@ -100,6 +100,10 @@ signal(SIGTERM, handle_stop) def handle_reload(signum, frame): try: if SCHEDULER is not None and RUN: + if SCHEDULER.db.readonly: + LOGGER.warning("The database is read-only, no need to save the changes in the configuration as they will not be saved") + return + # run the config saver proc = subprocess_run( [ @@ -468,21 +472,24 @@ if __name__ == "__main__": or not nginx_variables_path.exists() or (tmp_variables_path.read_text(encoding="utf-8") != nginx_variables_path.read_text(encoding="utf-8")) ): - # run the config saver - proc = subprocess_run( - [ - "python3", - join(sep, "usr", "share", "bunkerweb", "gen", "save_config.py"), - "--settings", - join(sep, "usr", "share", "bunkerweb", "settings.json"), - ] - + (["--variables", str(tmp_variables_path)] if args.variables else []), - stdin=DEVNULL, - stderr=STDOUT, - check=False, - ) - if proc.returncode != 0: - LOGGER.error("Config saver failed, configuration will not work as expected...") + if SCHEDULER.db.readonly: + LOGGER.warning("The database is read-only, no need to save the changes in the configuration as they will not be saved") + else: + # run the config saver + proc = subprocess_run( + [ + "python3", + join(sep, "usr", "share", "bunkerweb", "gen", "save_config.py"), + "--settings", + join(sep, "usr", "share", "bunkerweb", "settings.json"), + ] + + (["--variables", str(tmp_variables_path)] if args.variables else []), + stdin=DEVNULL, + stderr=STDOUT, + check=False, + ) + if proc.returncode != 0: + LOGGER.error("Config saver failed, configuration will not work as expected...") ready = False while not ready: @@ -637,21 +644,24 @@ if __name__ == "__main__": for thread in threads: thread.join() - # run the config saver to save potential ignored external plugins settings - LOGGER.info("Running config saver to save potential ignored external plugins settings ...") - proc = subprocess_run( - [ - "python3", - join(sep, "usr", "share", "bunkerweb", "gen", "save_config.py"), - "--settings", - join(sep, "usr", "share", "bunkerweb", "settings.json"), - ], - stdin=DEVNULL, - stderr=STDOUT, - check=False, - ) - if proc.returncode != 0: - LOGGER.error("Config saver failed, configuration will not work as expected...") + if SCHEDULER.db.readonly: + LOGGER.warning("The database is read-only, no need to look for changes in the plugins settings as they will not be saved") + else: + # run the config saver to save potential ignored external plugins settings + LOGGER.info("Running config saver to save potential ignored external plugins settings ...") + proc = subprocess_run( + [ + "python3", + join(sep, "usr", "share", "bunkerweb", "gen", "save_config.py"), + "--settings", + join(sep, "usr", "share", "bunkerweb", "settings.json"), + ], + stdin=DEVNULL, + stderr=STDOUT, + check=False, + ) + if proc.returncode != 0: + LOGGER.error("Config saver failed, configuration will not work as expected...") SCHEDULER.update_jobs() env = SCHEDULER.db.get_config() @@ -696,21 +706,23 @@ if __name__ == "__main__": content += f"{k}={v}\n" SCHEDULER_TMP_ENV_PATH.write_text(content) # run the generator - args = [ - "python3", - join(sep, "usr", "share", "bunkerweb", "gen", "main.py"), - "--settings", - join(sep, "usr", "share", "bunkerweb", "settings.json"), - "--templates", - join(sep, "usr", "share", "bunkerweb", "confs"), - "--output", - join(sep, "etc", "nginx"), - "--variables", - str(SCHEDULER_TMP_ENV_PATH), - ] - if MASTER_MODE: - args.append("--no-linux-reload") - proc = subprocess_run(args, stdin=DEVNULL, stderr=STDOUT, check=False) + proc = subprocess_run( + [ + "python3", + join(sep, "usr", "share", "bunkerweb", "gen", "main.py"), + "--settings", + join(sep, "usr", "share", "bunkerweb", "settings.json"), + "--templates", + join(sep, "usr", "share", "bunkerweb", "confs"), + "--output", + join(sep, "etc", "nginx"), + "--variables", + str(SCHEDULER_TMP_ENV_PATH), + ], + stdin=DEVNULL, + stderr=STDOUT, + check=False, + ) if proc.returncode != 0: LOGGER.error("Config generator failed, configuration will not work as expected...") @@ -739,7 +751,7 @@ if __name__ == "__main__": LOGGER.info("Successfully reloaded nginx") else: LOGGER.error("Error while reloading nginx") - elif INTEGRATION == "Linux": + elif INTEGRATION == "Linux" and not MASTER_MODE: # Reload nginx LOGGER.info("Reloading nginx ...") proc = subprocess_run( diff --git a/src/ui/main.py b/src/ui/main.py index b3f7a6733..6cdc63e0f 100755 --- a/src/ui/main.py +++ b/src/ui/main.py @@ -111,14 +111,6 @@ PLUGIN_KEYS = ["id", "name", "description", "version", "stream", "settings"] db = Database(app.logger, ui=True, log=False) -USER = "Error" -while USER == "Error": - with suppress(Exception): - USER = db.get_ui_user() - -if USER: - USER = User(**USER) - USER_PASSWORD_RX = re_compile(r"^(?=.*?\p{Lowercase_Letter})(?=.*?\p{Uppercase_Letter})(?=.*?\d)(?=.*?[ !\"#$%&'()*+,./:;<=>?@[\\\]^_`{|}~-]).{8,}$") bw_version = get_version() @@ -132,7 +124,6 @@ try: CONFIG=Config(db), CONFIGFILES=ConfigFiles(), WTF_CSRF_SSL_STRICT=False, - USER=USER, SEND_FILE_MAX_AGE_DEFAULT=86400, SCRIPT_NONCE=sha256(urandom(32)).hexdigest(), DB=db, @@ -405,18 +396,23 @@ def set_csp_header(response): + " style-src 'self' 'unsafe-inline';" + " img-src 'self' data: https://assets.bunkerity.com;" + " font-src 'self' data:;" - + " connect-src *;" + " base-uri 'self';" + + (" connect-src *;" if request.path.startswith(("/check", "/setup")) else "") ) + if app.config["DB"].readonly: + flash("Database connection is in read-only mode : no modification possible.", "error") + return response @login_manager.user_loader def load_user(user_id): - if not app.config["USER"]: + admin_user = app.config["DB"].get_ui_user() + if not admin_user: app.logger.warning("Couldn't get the admin user from the database.") return None - return app.config["USER"] if user_id == app.config["USER"].get_id() else None + admin_user = User(**admin_user) + return admin_user if user_id == admin_user.get_id() else None @app.errorhandler(CSRFError) @@ -430,61 +426,57 @@ def handle_csrf_error(_): session.clear() logout_user() flash("Wrong CSRF token !", "error") - if not app.config["USER"]: + if not current_user: return render_template("setup.html"), 403 return render_template("login.html", is_totp=current_user.is_two_factor_enabled), 403 @app.before_request def before_request(): - if not app.config["DB"].readonly: - try: - app.config["DB"].test_write() - except BaseException: - app.config["DB"].readonly = True - - db_user = app.config["DB"].get_ui_user() - if db_user: - app.config["USER"] = User(**db_user) - app.config["SCRIPT_NONCE"] = sha256(urandom(32)).hexdigest() - if current_user.is_authenticated: - passed = True + if not request.path.startswith(("/css", "/images", "/js", "/json", "/webfonts")): + if app.config["DB"].database_uri and app.config["DB"].readonly: + try: + app.config["DB"].retry_connection(pool_timeout=1) + app.config["DB"].readonly = False + app.logger.info("The database is no longer read-only, defaulting to read-write mode") + except BaseException: + try: + app.config["DB"].retry_connection(readonly=True, pool_timeout=1) + except BaseException: + if app.config["DB"].database_uri_readonly: + with suppress(BaseException): + app.config["DB"].retry_connection(fallback=True, pool_timeout=1) + app.config["DB"].readonly = True - # Go back from totp to login - if ( - not session.get("totp_validated", False) - and current_user.is_two_factor_enabled - and "/totp" not in request.path - and not request.path.startswith(("/css", "/images", "/js", "/json", "/webfonts")) - and request.path.endswith("/login") - ): - logout_user() - session.clear() - return redirect(url_for("login")) + if not app.config["DB"].readonly and request.method == "POST" and not ("/totp" in request.path or "/login" in request.path): + try: + app.config["DB"].test_write() + except BaseException: + app.config["DB"].readonly = True - # Case not login page, keep on 2FA before any other access - if ( - not session.get("totp_validated", False) - and current_user.is_two_factor_enabled - and "/totp" not in request.path - and not request.path.startswith(("/css", "/images", "/js", "/json", "/webfonts")) - ): - return redirect(url_for("totp", next=request.form.get("next"))) - elif session.get("ip") != request.remote_addr: - passed = False - elif session.get("user_agent") != request.headers.get("User-Agent"): - passed = False + if current_user.is_authenticated: + passed = True - if not passed: - logout_user() - session.clear() + # Case not login page, keep on 2FA before any other access + if not session.get("totp_validated", False) and current_user.is_two_factor_enabled and "/totp" not in request.path: + if not request.path.endswith("/login"): + return redirect(url_for("totp", next=request.form.get("next"))) + passed = False + elif session.get("ip") != request.remote_addr: + passed = False + elif session.get("user_agent") != request.headers.get("User-Agent"): + passed = False + + if not passed: + logout_user() + session.clear() @app.route("/", strict_slashes=False) def index(): - if app.config["USER"]: + if app.config["DB"].get_ui_user(): if current_user.is_authenticated: # type: ignore return redirect(url_for("home")) return redirect(url_for("login"), 301) @@ -511,6 +503,10 @@ def setup(): if db_config.get(f"{server_name}_USE_UI", "no") == "yes": return redirect(url_for("login"), 301) + admin_user = app.config["DB"].get_ui_user() + if admin_user: + admin_user = User(**admin_user) + if request.method == "POST": if app.config["DB"].readonly: return redirect_flash_error("Database is in read-only mode", "setup") @@ -518,13 +514,13 @@ def setup(): is_request_form("setup") required_keys = ["server_name", "ui_host", "ui_url"] - if not app.config["USER"]: + if not admin_user: required_keys.extend(["admin_username", "admin_password", "admin_password_check"]) if not any(key in request.form for key in required_keys): return redirect_flash_error(f"Missing either one of the following parameters: {', '.join(required_keys)}.", "setup") - if not app.config["USER"]: + if not admin_user: if len(request.form["admin_username"]) > 256: return redirect_flash_error("The admin username is too long. It must be less than 256 characters.", "setup") @@ -548,10 +544,10 @@ def setup(): if not REVERSE_PROXY_PATH.match(request.form["ui_host"]): return redirect_flash_error("The hostname is not valid.", "setup") - if not app.config["USER"]: - app.config["USER"] = User(request.form["admin_username"], request.form["admin_password"], method="ui") + if not admin_user: + admin_user = User(request.form["admin_username"], request.form["admin_password"], method="ui") - ret = app.config["DB"].create_ui_user(request.form["admin_username"], app.config["USER"].password_hash, method="ui") + ret = app.config["DB"].create_ui_user(request.form["admin_username"], admin_user.password_hash, method="ui") if ret: return redirect_flash_error(f"Couldn't create the admin user in the database: {ret}", "setup", False, "error") @@ -590,7 +586,7 @@ def setup(): return render_template( "setup.html", - ui_user=app.config["USER"], + ui_user=admin_user, username=getenv("ADMIN_USERNAME", ""), password=getenv("ADMIN_PASSWORD", ""), ui_host=db_config.get("UI_HOST", getenv("UI_HOST", "")), @@ -2315,7 +2311,8 @@ def jobs_download(): @app.route("/login", methods=["GET", "POST"]) def login(): - if not app.config["USER"]: + admin_user = app.config["DB"].get_ui_user() + if not admin_user: return redirect(url_for("setup")) elif current_user.is_authenticated: # type: ignore return redirect(url_for("home")) @@ -2323,16 +2320,15 @@ def login(): fail = False if request.method == "POST" and "username" in request.form and "password" in request.form: app.logger.warning(f"Login attempt from {request.remote_addr} with username \"{request.form['username']}\"") - if not app.config["USER"]: - app.logger.error("Couldn't get user from database") - stop(1) - if app.config["USER"].get_id() == request.form["username"] and app.config["USER"].check_password(request.form["password"]): + admin_user = User(**admin_user) + + if admin_user.get_id() == request.form["username"] and admin_user.check_password(request.form["password"]): # log the user in session["ip"] = request.remote_addr session["user_agent"] = request.headers.get("User-Agent") session["totp_validated"] = False - login_user(app.config["USER"], duration=timedelta(hours=1), force=True) + login_user(admin_user, duration=timedelta(hours=8), force=True) # redirect him to the page he originally wanted or to the home page return redirect(url_for("loading", next=request.form.get("next") or url_for("home"))) diff --git a/src/ui/static/css/dashboard.css b/src/ui/static/css/dashboard.css index 302717b03..4e6f4a0fa 100644 --- a/src/ui/static/css/dashboard.css +++ b/src/ui/static/css/dashboard.css @@ -1 +1 @@ -/*! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e9ecef}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Open Sans;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#ced4da}input::placeholder,textarea::placeholder{opacity:1;color:#ced4da}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#5e72e480;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.\!container{width:100%!important;margin-right:auto!important;margin-left:auto!important;padding-right:1.5rem!important;padding-left:1.5rem!important}.container{width:100%;margin-right:auto;margin-left:auto;padding-right:1.5rem;padding-left:1.5rem}@media (min-width:340px){.\!container{max-width:340px!important}.container{max-width:340px}}@media (min-width:576px){.\!container{max-width:576px!important}.container{max-width:576px}}@media (min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media (min-width:992px){.\!container{max-width:992px!important}.container{max-width:992px}}@media (min-width:1200px){.\!container{max-width:1200px!important}.container{max-width:1200px}}@media (min-width:1320px){.\!container{max-width:1320px!important}.container{max-width:1320px}}@media (min-width:1920px){.\!container{max-width:1920px!important}.container{max-width:1920px}}a{letter-spacing:-.025rem}hr{margin:1rem 0;border:0;opacity:.25}img{max-width:none}label{display:inline-block}p{line-height:1.625;font-weight:400;margin-bottom:1rem}small{font-size:.875em}svg{display:inline}table{border-collapse:inherit}h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;color:#344767}h1,h2,h3,h4{letter-spacing:-.05rem}h1,h2,h3{font-weight:700}h4,h5,h6{font-weight:600}h1{font-size:3rem;line-height:1.25}h2{font-size:2.25rem;line-height:1.3}h3{font-size:1.875rem}h3,h4{line-height:1.375}h4{font-size:1.5rem}h5{font-size:1.25rem;line-height:1.375}h6{font-size:1rem;line-height:1.625}.sr-only{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border-width:0!important}.pointer-events-none{pointer-events:none!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}.collapse{visibility:collapse!important}.static{position:static!important}.fixed{position:fixed!important}.absolute{position:absolute!important}.relative{position:relative!important}.inset-y-0{top:0!important;bottom:0!important}.-left-full{left:-100%!important}.-right-0{right:0!important}.-right-1{right:-.25rem!important}.bottom-0{bottom:0!important}.bottom-1{bottom:.25rem!important}.bottom-1\.5{bottom:.375rem!important}.bottom-2{bottom:.5rem!important}.bottom-24{bottom:6rem!important}.bottom-3{bottom:.75rem!important}.bottom-4{bottom:1rem!important}.bottom-6{bottom:1.5rem!important}.bottom-7{bottom:1.75rem!important}.bottom-8{bottom:2rem!important}.left-0{left:0!important}.left-1{left:.25rem!important}.left-16{left:4rem!important}.left-20{left:5rem!important}.left-24{left:6rem!important}.left-32{left:8rem!important}.left-4{left:1rem!important}.left-40{left:10rem!important}.left-48{left:12rem!important}.left-8{left:2rem!important}.left-auto{left:auto!important}.left-full{left:100%!important}.right-0{right:0!important}.right-12{right:3rem!important}.right-2{right:.5rem!important}.right-20{right:5rem!important}.right-4{right:1rem!important}.right-5{right:1.25rem!important}.right-6{right:1.5rem!important}.right-7{right:1.75rem!important}.right-8{right:2rem!important}.right-\[3\.25rem\]{right:3.25rem!important}.top-0{top:0!important}.top-1{top:.25rem!important}.top-1\.5{top:.375rem!important}.top-1\/2{top:50%!important}.top-10{top:2.5rem!important}.top-12{top:3rem!important}.top-14{top:3.5rem!important}.top-16{top:4rem!important}.top-2{top:.5rem!important}.top-24{top:6rem!important}.top-3{top:.75rem!important}.top-36{top:9rem!important}.top-4{top:1rem!important}.top-6{top:1.5rem!important}.top-7{top:1.75rem!important}.top-8{top:2rem!important}.top-\[38\%\]{top:38%!important}.top-\[4\.5rem\]{top:4.5rem!important}.top-\[52\%\]{top:52%!important}.top-\[55\%\]{top:55%!important}.top-\[8\.2rem\]{top:8.2rem!important}.-z-10{z-index:-10!important}.z-0{z-index:0!important}.z-10{z-index:10!important}.z-100{z-index:100!important}.z-110{z-index:110!important}.z-20{z-index:20!important}.z-990{z-index:990!important}.z-\[10000\]{z-index:10000!important}.z-\[1000\]{z-index:1000!important}.z-\[1001\]{z-index:1001!important}.z-\[20\]{z-index:20!important}.z-sticky{z-index:1020!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.col-span-1{grid-column:span 1/span 1!important}.col-span-10{grid-column:span 10/span 10!important}.col-span-12{grid-column:span 12/span 12!important}.col-span-2{grid-column:span 2/span 2!important}.col-span-3{grid-column:span 3/span 3!important}.col-span-4{grid-column:span 4/span 4!important}.col-span-5{grid-column:span 5/span 5!important}.col-span-6{grid-column:span 6/span 6!important}.col-span-9{grid-column:span 9/span 9!important}.float-right{float:right!important}.float-left{float:left!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.mx-0{margin-left:0!important;margin-right:0!important}.mx-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-1\.5{margin-left:.375rem!important;margin-right:.375rem!important}.mx-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-2\.5{margin-left:.625rem!important;margin-right:.625rem!important}.mx-4{margin-left:1rem!important;margin-right:1rem!important}.mx-auto{margin-left:auto!important;margin-right:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:.75rem!important;margin-bottom:.75rem!important}.my-4{margin-top:1rem!important;margin-bottom:1rem!important}.mb-0{margin-bottom:0!important}.mb-0\.5{margin-bottom:.125rem!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-2\.5{margin-bottom:.625rem!important}.mb-3{margin-bottom:.75rem!important}.mb-4{margin-bottom:1rem!important}.mb-6{margin-bottom:1.5rem!important}.mb-7{margin-bottom:1.75rem!important}.mb-8{margin-bottom:2rem!important}.ml-0{margin-left:0!important}.ml-1{margin-left:.25rem!important}.ml-12{margin-left:3rem!important}.ml-2{margin-left:.5rem!important}.ml-3{margin-left:.75rem!important}.ml-4{margin-left:1rem!important}.mr-1{margin-right:.25rem!important}.mr-12{margin-right:3rem!important}.mr-2{margin-right:.5rem!important}.mr-3{margin-right:.75rem!important}.mr-4{margin-right:1rem!important}.mt-0{margin-top:0!important}.mt-0\.5{margin-top:.125rem!important}.mt-1{margin-top:.25rem!important}.mt-10{margin-top:2.5rem!important}.mt-12{margin-top:3rem!important}.mt-16{margin-top:4rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:.75rem!important}.mt-4{margin-top:1rem!important}.mt-5{margin-top:1.25rem!important}.mt-6{margin-top:1.5rem!important}.mt-8{margin-top:2rem!important}.mt-\[15vh\]{margin-top:15vh!important}.mt-\[4\.5rem\]{margin-top:4.5rem!important}.block{display:block!important}.inline-block{display:inline-block!important}.inline{display:inline!important}.flex{display:flex!important}.table{display:table!important}.grid{display:grid!important}.list-item{display:list-item!important}.\!hidden,.hidden{display:none!important}.h-1{height:.25rem!important}.h-12{height:3rem!important}.h-14{height:3.5rem!important}.h-3{height:.75rem!important}.h-32{height:8rem!important}.h-4{height:1rem!important}.h-4\.5{height:1.125rem!important}.h-40{height:10rem!important}.h-5{height:1.25rem!important}.h-5\.5{height:1.375rem!important}.h-6{height:1.5rem!important}.h-7{height:1.75rem!important}.h-8{height:2rem!important}.h-96{height:24rem!important}.h-\[2\.5rem\]{height:2.5rem!important}.h-\[250px\]{height:250px!important}.h-\[3\.5rem\]{height:3.5rem!important}.h-\[4rem\]{height:4rem!important}.h-\[90vh\]{height:90vh!important}.h-fit{height:-moz-fit-content!important;height:fit-content!important}.h-full{height:100%!important}.h-px{height:1px!important}.h-screen{height:100vh!important}.max-h-100{max-height:25rem!important}.max-h-30{max-height:7.5rem!important}.max-h-\[200px\]{max-height:200px!important}.max-h-\[250px\]{max-height:250px!important}.max-h-\[350px\]{max-height:350px!important}.max-h-\[400px\]{max-height:400px!important}.max-h-\[70vh\]{max-height:70vh!important}.max-h-\[90vh\]{max-height:90vh!important}.max-h-\[95vh\]{max-height:95vh!important}.max-h-screen{max-height:100vh!important}.min-h-20{min-height:5rem!important}.min-h-52{min-height:13rem!important}.min-h-6{min-height:1.5rem!important}.min-h-\[100px\]{min-height:100px!important}.min-h-\[200px\]{min-height:200px!important}.min-h-\[350px\]{min-height:350px!important}.min-h-\[400px\]{min-height:400px!important}.min-h-\[75px\]{min-height:75px!important}.min-h-\[85vh\]{min-height:85vh!important}.min-h-screen{min-height:100vh!important}.w-1{width:.25rem!important}.w-10{width:2.5rem!important}.w-11\/12{width:91.666667%!important}.w-28{width:7rem!important}.w-3{width:.75rem!important}.w-32{width:8rem!important}.w-4{width:1rem!important}.w-4\.5{width:1.125rem!important}.w-40{width:10rem!important}.w-48{width:12rem!important}.w-5{width:1.25rem!important}.w-5\.5{width:1.375rem!important}.w-50{width:12.5rem!important}.w-6{width:1.5rem!important}.w-7{width:1.75rem!important}.w-8{width:2rem!important}.w-80{width:20rem!important}.w-90{width:22.5rem!important}.w-\[2\.5rem\]{width:2.5rem!important}.w-\[50vw\]{width:50vw!important}.w-auto{width:auto!important}.w-fit{width:-moz-fit-content!important;width:fit-content!important}.w-full{width:100%!important}.w-screen{width:100vw!important}.min-w-0{min-width:0!important}.min-w-4{min-width:1rem!important}.min-w-\[1150px\]{min-width:1150px!important}.min-w-\[1300px\]{min-width:1300px!important}.min-w-\[200px\]{min-width:200px!important}.min-w-\[300px\]{min-width:300px!important}.min-w-\[600px\]{min-width:600px!important}.min-w-\[800px\]{min-width:800px!important}.min-w-\[900px\]{min-width:900px!important}.min-w-fit{min-width:-moz-fit-content!important;min-width:fit-content!important}.max-w-40{max-width:10rem!important}.max-w-60{max-width:15rem!important}.max-w-64{max-width:16rem!important}.max-w-\[1000px\]{max-width:1000px!important}.max-w-\[150px\]{max-width:150px!important}.max-w-\[1920px\]{max-width:1920px!important}.max-w-\[300px\]{max-width:300px!important}.max-w-\[400px\]{max-width:400px!important}.max-w-\[450px\]{max-width:450px!important}.max-w-\[550px\]{max-width:550px!important}.max-w-\[650px\]{max-width:650px!important}.max-w-\[700px\]{max-width:700px!important}.max-w-full{max-width:100%!important}.max-w-none{max-width:none!important}.max-w-screen-lg{max-width:992px!important}.flex-auto{flex:1 1 auto!important}.grow{flex-grow:1!important}.basis-full{flex-basis:100%!important}.border-collapse{border-collapse:collapse!important}.-translate-x-1{--tw-translate-x:-0.25rem!important}.-translate-x-1,.-translate-x-1\.5{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.-translate-x-1\.5{--tw-translate-x:-0.375rem!important}.-translate-x-36{--tw-translate-x:-9rem!important}.-translate-x-36,.-translate-x-40{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.-translate-x-40{--tw-translate-x:-10rem!important}.-translate-x-48{--tw-translate-x:-12rem!important}.-translate-x-48,.-translate-x-52{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.-translate-x-52{--tw-translate-x:-13rem!important}.-translate-x-56{--tw-translate-x:-14rem!important}.-translate-x-56,.-translate-x-60{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.-translate-x-60{--tw-translate-x:-15rem!important}.-translate-x-64{--tw-translate-x:-16rem!important}.-translate-x-64,.-translate-x-72{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.-translate-x-72{--tw-translate-x:-18rem!important}.-translate-x-full{--tw-translate-x:-100%!important}.-translate-x-full,.-translate-y-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.-translate-y-0{--tw-translate-y:-0px!important}.-translate-y-0\.4{--tw-translate-y:-0.1rem!important}.-translate-y-0\.4,.-translate-y-0\.5{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.-translate-y-0\.5{--tw-translate-y:-0.125rem!important}.-translate-y-1{--tw-translate-y:-0.25rem!important}.-translate-y-1,.-translate-y-12{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.-translate-y-12{--tw-translate-y:-3rem!important}.-translate-y-16{--tw-translate-y:-4rem!important}.-translate-y-16,.-translate-y-2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.-translate-y-2{--tw-translate-y:-0.5rem!important}.-translate-y-20{--tw-translate-y:-5rem!important}.-translate-y-20,.-translate-y-24{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.-translate-y-24{--tw-translate-y:-6rem!important}.-translate-y-28{--tw-translate-y:-7rem!important}.-translate-y-28,.-translate-y-36{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.-translate-y-36{--tw-translate-y:-9rem!important}.-translate-y-4{--tw-translate-y:-1rem!important}.-translate-y-4,.-translate-y-6{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.-translate-y-6{--tw-translate-y:-1.5rem!important}.-translate-y-8{--tw-translate-y:-2rem!important}.-translate-y-8,.translate-x-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.translate-x-0{--tw-translate-x:0px!important}.translate-x-0\.5{--tw-translate-x:0.125rem!important}.translate-x-0\.5,.translate-x-1{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.translate-x-1{--tw-translate-x:0.25rem!important}.translate-x-16{--tw-translate-x:4rem!important}.translate-x-16,.translate-x-2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.translate-x-2{--tw-translate-x:0.5rem!important}.translate-x-24{--tw-translate-x:6rem!important}.translate-x-24,.translate-x-3{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.translate-x-3{--tw-translate-x:0.75rem!important}.translate-x-32{--tw-translate-x:8rem!important}.translate-x-32,.translate-x-4{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.translate-x-4{--tw-translate-x:1rem!important}.translate-x-40{--tw-translate-x:10rem!important}.translate-x-40,.translate-x-48{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.translate-x-48{--tw-translate-x:12rem!important}.translate-x-52{--tw-translate-x:13rem!important}.translate-x-52,.translate-x-56{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.translate-x-56{--tw-translate-x:14rem!important}.translate-x-60{--tw-translate-x:15rem!important}.translate-x-60,.translate-x-90{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.translate-x-90{--tw-translate-x:22.5rem!important}.translate-x-\[3rem\]{--tw-translate-x:3rem!important}.translate-x-\[3rem\],.translate-y-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.translate-y-0{--tw-translate-y:0px!important}.translate-y-0\.5{--tw-translate-y:0.125rem!important}.translate-y-0\.5,.translate-y-1{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.translate-y-1{--tw-translate-y:0.25rem!important}.translate-y-16{--tw-translate-y:4rem!important}.translate-y-16,.translate-y-2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.translate-y-2{--tw-translate-y:0.5rem!important}.-rotate-12{--tw-rotate:-12deg!important}.-rotate-12,.rotate-12{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.rotate-12{--tw-rotate:12deg!important}.rotate-180{--tw-rotate:180deg!important}.rotate-180,.rotate-90{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.rotate-90{--tw-rotate:90deg!important}.scale-105{--tw-scale-x:1.05!important;--tw-scale-y:1.05!important}.scale-105,.scale-110{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.scale-110{--tw-scale-x:1.1!important;--tw-scale-y:1.1!important}.scale-50{--tw-scale-x:.5!important;--tw-scale-y:.5!important}.scale-50,.scale-75{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.scale-75{--tw-scale-x:.75!important;--tw-scale-y:.75!important}.scale-90{--tw-scale-x:.9!important;--tw-scale-y:.9!important}.scale-90,.scale-\[0\.6\]{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.scale-\[0\.6\]{--tw-scale-x:0.6!important;--tw-scale-y:0.6!important}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.cursor-not-allowed{cursor:not-allowed!important}.cursor-pointer{cursor:pointer!important}.cursor-text{cursor:text!important}.select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.resize{resize:both!important}.list-none{list-style-type:none!important}.appearance-none{-webkit-appearance:none!important;-moz-appearance:none!important;appearance:none!important}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))!important}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))!important}.flex-row{flex-direction:row!important}.flex-col{flex-direction:column!important}.flex-wrap{flex-wrap:wrap!important}.items-start{align-items:flex-start!important}.items-end{align-items:flex-end!important}.items-center{align-items:center!important}.justify-start{justify-content:flex-start!important}.justify-end{justify-content:flex-end!important}.justify-center{justify-content:center!important}.justify-between{justify-content:space-between!important}.justify-items-center{justify-items:center!important}.gap-2{gap:.5rem!important}.gap-3{gap:.75rem!important}.gap-4{gap:1rem!important}.gap-8{gap:2rem!important}.gap-x-4{-moz-column-gap:1rem!important;column-gap:1rem!important}.gap-y-2{row-gap:.5rem!important}.gap-y-3{row-gap:.75rem!important}.gap-y-4{row-gap:1rem!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-x-auto{overflow-x:auto!important}.overflow-y-auto{overflow-y:auto!important}.overflow-x-hidden{overflow-x:hidden!important}.truncate{overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}.whitespace-normal{white-space:normal!important}.whitespace-nowrap{white-space:nowrap!important}.break-words{overflow-wrap:break-word!important}.break-all{word-break:break-all!important}.rounded,.rounded-1{border-radius:.25rem!important}.rounded-1\.4{border-radius:.35rem!important}.rounded-10{border-radius:2.5rem!important}.rounded-2xl{border-radius:1rem!important}.rounded-circle{border-radius:50%!important}.rounded-full{border-radius:9999px!important}.rounded-lg{border-radius:.5rem!important}.rounded-none{border-radius:0!important}.rounded-xl{border-radius:.75rem!important}.rounded-b{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-b-lg{border-bottom-left-radius:.5rem!important}.rounded-b-lg,.rounded-r-lg{border-bottom-right-radius:.5rem!important}.rounded-r-lg{border-top-right-radius:.5rem!important}.rounded-t{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-t-2xl{border-top-left-radius:1rem!important;border-top-right-radius:1rem!important}.rounded-t-lg{border-top-left-radius:.5rem!important;border-top-right-radius:.5rem!important}.border{border-width:1px!important}.border-0{border-width:0!important}.border-2{border-width:2px!important}.border-b{border-bottom-width:1px!important}.border-b-0{border-bottom-width:0!important}.border-l{border-left-width:1px!important}.border-r{border-right-width:1px!important}.border-t{border-top-width:1px!important}.border-solid{border-style:solid!important}.border-dashed{border-style:dashed!important}.border-gray-100{--tw-border-opacity:1!important;border-color:rgb(235 239 244/var(--tw-border-opacity))!important}.border-gray-100\/50{border-color:#ebeff480!important}.border-gray-200{--tw-border-opacity:1!important;border-color:rgb(233 236 239/var(--tw-border-opacity))!important}.border-gray-300{--tw-border-opacity:1!important;border-color:rgb(210 214 218/var(--tw-border-opacity))!important}.border-gray-400{--tw-border-opacity:1!important;border-color:rgb(206 212 218/var(--tw-border-opacity))!important}.border-gray-700{--tw-border-opacity:1!important;border-color:rgb(73 80 87/var(--tw-border-opacity))!important}.border-primary{--tw-border-opacity:1!important;border-color:rgb(11 85 119/var(--tw-border-opacity))!important}.bg-blue-500{--tw-bg-opacity:1!important;background-color:rgb(94 114 228/var(--tw-bg-opacity))!important}.bg-emerald-500{--tw-bg-opacity:1!important;background-color:rgb(45 206 137/var(--tw-bg-opacity))!important}.bg-emerald-500\/80{background-color:#2dce89cc!important}.bg-gray-100{background-color:rgb(235 239 244/var(--tw-bg-opacity))!important}.bg-gray-100,.bg-gray-200{--tw-bg-opacity:1!important}.bg-gray-200{background-color:rgb(233 236 239/var(--tw-bg-opacity))!important}.bg-gray-300{background-color:rgb(210 214 218/var(--tw-bg-opacity))!important}.bg-gray-300,.bg-gray-50{--tw-bg-opacity:1!important}.bg-gray-50{background-color:rgb(248 249 250/var(--tw-bg-opacity))!important}.bg-gray-50\/10{background-color:#f8f9fa1a!important}.bg-gray-500{--tw-bg-opacity:1!important;background-color:rgb(173 181 189/var(--tw-bg-opacity))!important}.bg-gray-500\/80{background-color:#adb5bdcc!important}.bg-gray-600{--tw-bg-opacity:1!important;background-color:rgb(108 117 125/var(--tw-bg-opacity))!important}.bg-gray-600\/50{background-color:#6c757d80!important}.bg-gray-600\/80{background-color:#6c757dcc!important}.bg-green-500{--tw-bg-opacity:1!important;background-color:rgb(34 197 94/var(--tw-bg-opacity))!important}.bg-green-500\/80{background-color:#22c55ecc!important}.bg-orange-500{--tw-bg-opacity:1!important;background-color:rgb(251 99 64/var(--tw-bg-opacity))!important}.bg-orange-500\/80{background-color:#fb6340cc!important}.bg-primary{--tw-bg-opacity:1!important;background-color:rgb(11 85 119/var(--tw-bg-opacity))!important}.bg-primary\/10{background-color:#0b55771a!important}.bg-red-500{--tw-bg-opacity:1!important;background-color:rgb(245 57 57/var(--tw-bg-opacity))!important}.bg-red-500\/80{background-color:#f53939cc!important}.bg-secondary{background-color:rgb(46 172 104/var(--tw-bg-opacity))!important}.bg-secondary,.bg-sky-500{--tw-bg-opacity:1!important}.bg-sky-500{background-color:rgb(14 165 233/var(--tw-bg-opacity))!important}.bg-sky-500\/80{background-color:#0ea5e9cc!important}.bg-slate-800\/10{background-color:#3a416f1a!important}.bg-transparent{background-color:initial!important}.bg-white{background-color:rgb(255 255 255/var(--tw-bg-opacity))!important}.bg-white,.bg-yellow-400{--tw-bg-opacity:1!important}.bg-yellow-400{background-color:rgb(251 207 51/var(--tw-bg-opacity))!important}.bg-yellow-400\/80{background-color:#fbcf33cc!important}.bg-yellow-500{--tw-bg-opacity:1!important;background-color:rgb(251 177 64/var(--tw-bg-opacity))!important}.bg-yellow-500\/80{background-color:#fbb140cc!important}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))!important}.bg-gradient-to-tl{background-image:linear-gradient(to top left,var(--tw-gradient-stops))!important}.bg-none{background-image:none!important}.from-\[\#075577\]{--tw-gradient-from:#075577 var(--tw-gradient-from-position)!important;--tw-gradient-to:#07557700 var(--tw-gradient-to-position)!important;--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)!important}.from-\[\#0b5577\]{--tw-gradient-from:#0b5577 var(--tw-gradient-from-position)!important;--tw-gradient-to:#0b557700 var(--tw-gradient-to-position)!important;--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)!important}.from-transparent{--tw-gradient-from:#0000 var(--tw-gradient-from-position)!important;--tw-gradient-to:#0000 var(--tw-gradient-to-position)!important;--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)!important}.via-black\/40{--tw-gradient-to:#0000 var(--tw-gradient-to-position)!important;--tw-gradient-stops:var(--tw-gradient-from),#0006 var(--tw-gradient-via-position),var(--tw-gradient-to)!important}.to-\[\#116D70\]{--tw-gradient-to:#116d70 var(--tw-gradient-to-position)!important}.to-transparent{--tw-gradient-to:#0000 var(--tw-gradient-to-position)!important}.bg-150{background-size:150%!important}.bg-contain{background-size:contain!important}.bg-clip-border{background-clip:initial!important}.bg-clip-padding{background-clip:padding-box!important}.bg-center{background-position:50%!important}.bg-left{background-position:0!important}.bg-x-25{background-position:25% 0!important}.bg-no-repeat{background-repeat:no-repeat!important}.fill-blue-500{fill:#5e72e4!important}.fill-gray-500{fill:#adb5bd!important}.fill-gray-600{fill:#6c757d!important}.fill-gray-700{fill:#495057!important}.fill-green-500{fill:#22c55e!important}.fill-primary{fill:#0b5577!important}.fill-red-500{fill:#f53939!important}.fill-sky-500{fill:#0ea5e9!important}.fill-slate-800{fill:#3a416f!important}.fill-white{fill:#fff!important}.fill-yellow-500{fill:#fbb140!important}.stroke-amber-500{stroke:#f59e0b!important}.stroke-blue-400{stroke:#60a5fa!important}.stroke-blue-500{stroke:#5e72e4!important}.stroke-emerald-600{stroke:#059669!important}.stroke-gray-100{stroke:#ebeff4!important}.stroke-gray-100\/50{stroke:#ebeff480!important}.stroke-gray-600{stroke:#6c757d!important}.stroke-gray-700{stroke:#495057!important}.stroke-gray-800{stroke:#252f40!important}.stroke-green-700{stroke:#15803d!important}.stroke-orange-500{stroke:#fb6340!important}.stroke-pink-600{stroke:#db2777!important}.stroke-red-500{stroke:#f53939!important}.stroke-sky-500{stroke:#0ea5e9!important}.stroke-stone-500{stroke:#78716c!important}.stroke-white{stroke:#fff!important}.stroke-yellow-400{stroke:#fbcf33!important}.stroke-yellow-500{stroke:#fbb140!important}.stroke-0{stroke-width:0!important}.object-cover{-o-object-fit:cover!important;object-fit:cover!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:.75rem!important}.p-4{padding:1rem!important}.px-0{padding-left:0!important;padding-right:0!important}.px-0\.5{padding-left:.125rem!important;padding-right:.125rem!important}.px-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-1\.5{padding-left:.375rem!important;padding-right:.375rem!important}.px-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-28{padding-left:7rem!important;padding-right:7rem!important}.px-3{padding-left:.75rem!important;padding-right:.75rem!important}.px-4{padding-left:1rem!important;padding-right:1rem!important}.px-6{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-8{padding-left:2rem!important;padding-right:2rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-0\.5{padding-top:.125rem!important;padding-bottom:.125rem!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-1\.5{padding-top:.375rem!important;padding-bottom:.375rem!important}.py-10{padding-top:2.5rem!important;padding-bottom:2.5rem!important}.py-12{padding-top:3rem!important;padding-bottom:3rem!important}.py-16{padding-top:4rem!important;padding-bottom:4rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-2\.5{padding-top:.625rem!important;padding-bottom:.625rem!important}.py-3{padding-top:.75rem!important;padding-bottom:.75rem!important}.py-4{padding-top:1rem!important;padding-bottom:1rem!important}.py-8{padding-top:2rem!important;padding-bottom:2rem!important}.pb-0{padding-bottom:0!important}.pb-10{padding-bottom:2.5rem!important}.pb-16{padding-bottom:4rem!important}.pb-2{padding-bottom:.5rem!important}.pb-24{padding-bottom:6rem!important}.pb-28{padding-bottom:7rem!important}.pb-4{padding-bottom:1rem!important}.pb-6{padding-bottom:1.5rem!important}.pb-8{padding-bottom:2rem!important}.pl-0{padding-left:0!important}.pl-2{padding-left:.5rem!important}.pl-3{padding-left:.75rem!important}.pl-6{padding-left:1.5rem!important}.pt-1{padding-top:.25rem!important}.pt-10{padding-top:2.5rem!important}.pt-2{padding-top:.5rem!important}.pt-20{padding-top:5rem!important}.pt-3{padding-top:.75rem!important}.pt-4{padding-top:1rem!important}.pt-6{padding-top:1.5rem!important}.pt-8{padding-top:2rem!important}.pt-9{padding-top:2.25rem!important}.text-left{text-align:left!important}.text-center{text-align:center!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.font-sans{font-family:Open Sans!important}.text-2xl{font-size:1.5rem!important;line-height:2rem!important}.text-3xl{font-size:1.875rem!important;line-height:2.25rem!important}.text-4xl{font-size:2.25rem!important;line-height:2.5rem!important}.text-5xl{font-size:3rem!important}.text-5xl,.text-6xl{line-height:1!important}.text-6xl{font-size:3.75rem!important}.text-7xl{font-size:4.5rem!important}.text-7xl,.text-9xl{line-height:1!important}.text-9xl{font-size:6rem!important}.text-\[0\.7rem\]{font-size:.7rem!important}.text-\[1\.1rem\]{font-size:1.1rem!important}.text-base{font-size:1rem!important;line-height:1.5rem!important}.text-lg{font-size:1.125rem!important;line-height:1.75rem!important}.text-sm{font-size:.875rem!important;line-height:1.5rem!important}.text-xl{font-size:1.25rem!important;line-height:1.75rem!important}.text-xs{font-size:.75rem!important;line-height:1rem!important}.font-bold{font-weight:700!important}.font-medium{font-weight:500!important}.font-normal{font-weight:400!important}.font-semibold{font-weight:600!important}.uppercase{text-transform:uppercase!important}.capitalize{text-transform:capitalize!important}.italic{font-style:italic!important}.ordinal{--tw-ordinal:ordinal!important;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)!important}.leading-5{line-height:1.25rem!important}.leading-5\.6{line-height:1.4rem!important}.leading-6{line-height:1.5rem!important}.leading-default{line-height:1.6!important}.leading-none{line-height:1!important}.leading-normal{line-height:1.5!important}.leading-tight{line-height:1.25!important}.tracking-\[0\.20rem\]{letter-spacing:.2rem!important}.tracking-normal{letter-spacing:0!important}.tracking-tight-rem{letter-spacing:-.025rem!important}.tracking-wide{letter-spacing:.025em!important}.tracking-wider{letter-spacing:.05em!important}.tracking-widest{letter-spacing:.1em!important}.text-blue-500{--tw-text-opacity:1!important;color:rgb(94 114 228/var(--tw-text-opacity))!important}.text-gray-100{--tw-text-opacity:1!important;color:rgb(235 239 244/var(--tw-text-opacity))!important}.text-gray-100\/50{color:#ebeff480!important}.text-gray-300{--tw-text-opacity:1!important;color:rgb(210 214 218/var(--tw-text-opacity))!important}.text-gray-50{--tw-text-opacity:1!important;color:rgb(248 249 250/var(--tw-text-opacity))!important}.text-gray-500{--tw-text-opacity:1!important;color:rgb(173 181 189/var(--tw-text-opacity))!important}.text-gray-600{--tw-text-opacity:1!important;color:rgb(108 117 125/var(--tw-text-opacity))!important}.text-gray-700{--tw-text-opacity:1!important;color:rgb(73 80 87/var(--tw-text-opacity))!important}.text-gray-700\/80{color:#495057cc!important}.text-gray-800{--tw-text-opacity:1!important;color:rgb(37 47 64/var(--tw-text-opacity))!important}.text-green-500{--tw-text-opacity:1!important;color:rgb(34 197 94/var(--tw-text-opacity))!important}.text-primary{color:rgb(11 85 119/var(--tw-text-opacity))!important}.text-primary,.text-red-500{--tw-text-opacity:1!important}.text-red-500{color:rgb(245 57 57/var(--tw-text-opacity))!important}.text-secondary{--tw-text-opacity:1!important;color:rgb(46 172 104/var(--tw-text-opacity))!important}.text-sky-500{--tw-text-opacity:1!important;color:rgb(14 165 233/var(--tw-text-opacity))!important}.text-slate-500{--tw-text-opacity:1!important;color:rgb(103 116 142/var(--tw-text-opacity))!important}.text-slate-700{color:rgb(52 71 103/var(--tw-text-opacity))!important}.text-slate-700,.text-white{--tw-text-opacity:1!important}.text-white{color:rgb(255 255 255/var(--tw-text-opacity))!important}.text-yellow-400{--tw-text-opacity:1!important;color:rgb(251 207 51/var(--tw-text-opacity))!important}.text-yellow-500{--tw-text-opacity:1!important;color:rgb(251 177 64/var(--tw-text-opacity))!important}.underline{text-decoration-line:underline!important}.antialiased{-webkit-font-smoothing:antialiased!important;-moz-osx-font-smoothing:grayscale!important}.opacity-0{opacity:0!important}.opacity-100{opacity:1!important}.opacity-50{opacity:.5!important}.opacity-60{opacity:.6!important}.shadow-3xl{--tw-shadow:0 8px 26px -4px #14141426,0 8px 9px -5px #1414140f!important;--tw-shadow-colored:0 8px 26px -4px var(--tw-shadow-color),0 8px 9px -5px var(--tw-shadow-color)!important}.shadow-3xl,.shadow-\[8px_8px_12px_rgb\(0\2c 0\2c 0\2c 0\.2\)\]{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)!important}.shadow-\[8px_8px_12px_rgb\(0\2c 0\2c 0\2c 0\.2\)\]{--tw-shadow:8px 8px 12px #0003!important;--tw-shadow-colored:8px 8px 12px var(--tw-shadow-color)!important}.shadow-md{--tw-shadow:0 4px 6px #32325d1a,0 1px 3px #00000014!important;--tw-shadow-colored:0 4px 6px var(--tw-shadow-color),0 1px 3px var(--tw-shadow-color)!important}.shadow-md,.shadow-none{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)!important}.shadow-none{--tw-shadow:0 0 #0000!important;--tw-shadow-colored:0 0 #0000!important}.shadow-sm{--tw-shadow:0 .25rem .375rem -.0625rem #1414141f,0 .125rem .25rem -.0625rem #14141412!important;--tw-shadow-colored:0 .25rem .375rem -.0625rem var(--tw-shadow-color),0 .125rem .25rem -.0625rem var(--tw-shadow-color)!important}.shadow-sm,.shadow-xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)!important}.shadow-xl{--tw-shadow:0 0 2rem 0 #8898aa26!important;--tw-shadow-colored:0 0 2rem 0 var(--tw-shadow-color)!important}.shadow-xs{--tw-shadow:0 7px 14px #32325d1a,0 3px 6px #00000014!important;--tw-shadow-colored:0 7px 14px var(--tw-shadow-color),0 3px 6px var(--tw-shadow-color)!important;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)!important}.outline-none{outline:2px solid #0000!important;outline-offset:2px!important}.outline{outline-style:solid!important}.outline-secondary{outline-color:#2eac68!important}.blur{--tw-blur:blur(8px)!important}.blur,.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter!important;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter!important;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter!important;transition-timing-function:ease!important;transition-duration:.15s!important}.transition-all{transition-property:all!important;transition-timing-function:ease!important;transition-duration:.15s!important}.transition-transform{transition-property:transform!important;transition-timing-function:ease!important;transition-duration:.15s!important}.delay-200{transition-delay:.2s!important}.duration-200{transition-duration:.2s!important}.duration-250{transition-duration:.25s!important}.duration-300{transition-duration:.3s!important}.duration-700{transition-duration:.7s!important}.ease-in{transition-timing-function:ease-in!important}.ease-in-out{transition-timing-function:ease-in-out!important}.flex-wrap-inherit{flex-wrap:inherit!important}@font-face{font-family:Open Sans;src:url(../webfonts/OpenSans.ttf)}*{font-family:Open Sans,sans-serif}.ace_editor,.ace_editor *{font-family:Monaco,Menlo,Ubuntu Mono,Droid Sans Mono,Consolas,monospace!important;font-weight:400!important;letter-spacing:0!important}.sr-only{display:none}.separator{margin:.75rem 0 .5rem;height:1px;background-color:initial;--tw-gradient-from:#0000 var(--tw-gradient-from-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to);--tw-gradient-stops:var(--tw-gradient-from),#0006 var(--tw-gradient-via-position),var(--tw-gradient-to);--tw-gradient-to:#0000 var(--tw-gradient-to-position)}.separator,:is(.dark .separator){background-image:linear-gradient(to right,var(--tw-gradient-stops))}:is(.dark .separator){--tw-gradient-from:#0000 var(--tw-gradient-from-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to);--tw-gradient-to:#fff0 var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),#fff var(--tw-gradient-via-position),var(--tw-gradient-to);--tw-gradient-to:#0000 var(--tw-gradient-to-position)}.close-btn{display:inline-block;cursor:pointer;border-radius:.5rem;border-width:1px;--tw-border-opacity:1;border-color:rgb(245 57 57/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));padding:.5rem 1rem;text-align:center;vertical-align:middle;font-weight:700;text-transform:uppercase;line-height:1.5;letter-spacing:-.025rem;--tw-text-opacity:1;color:rgb(245 57 57/var(--tw-text-opacity));--tw-shadow:0 7px 14px #32325d1a,0 3px 6px #00000014;--tw-shadow-colored:0 7px 14px var(--tw-shadow-color),0 3px 6px var(--tw-shadow-color);transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.close-btn,.close-btn:hover{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.close-btn:hover{--tw-translate-y:-1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-shadow:0 4px 6px #32325d1a,0 1px 3px #00000014;--tw-shadow-colored:0 4px 6px var(--tw-shadow-color),0 1px 3px var(--tw-shadow-color)}.close-btn:focus,.close-btn:hover{background-color:#fffc}.close-btn:active{opacity:.85}.close-btn:disabled{cursor:not-allowed;--tw-bg-opacity:1;--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity))}.close-btn:disabled,.close-btn:hover:disabled{border-color:#ced4da00;background-color:rgb(206 212 218/var(--tw-bg-opacity))}.close-btn:hover:disabled{--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-bg-opacity:1}:is(.dark .close-btn){--tw-bg-opacity:1;background-color:rgb(233 236 239/var(--tw-bg-opacity));--tw-brightness:brightness(.9)}:is(.dark .close-btn),:is(.dark .close-btn:hover){filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}:is(.dark .close-btn:hover){--tw-brightness:brightness(.75)}:is(.dark .close-btn:disabled){--tw-bg-opacity:1;--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}:is(.dark .close-btn:disabled),:is(.dark .close-btn:hover:disabled){border-color:#49505700;background-color:rgb(73 80 87/var(--tw-bg-opacity))}:is(.dark .close-btn:hover:disabled){--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-bg-opacity:1}@media (min-width:768px){.close-btn{padding:.625rem 1.25rem}}.valid-btn{display:inline-block;cursor:pointer;border-radius:.5rem;--tw-bg-opacity:1;background-color:rgb(34 197 94/var(--tw-bg-opacity));padding:.5rem 1rem;text-align:center;vertical-align:middle;font-weight:700;text-transform:uppercase;line-height:1.5;letter-spacing:.025em;--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity));--tw-shadow:0 7px 14px #32325d1a,0 3px 6px #00000014;--tw-shadow-colored:0 7px 14px var(--tw-shadow-color),0 3px 6px var(--tw-shadow-color);transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.valid-btn,.valid-btn:hover{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.valid-btn:hover{--tw-translate-y:-1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-shadow:0 4px 6px #32325d1a,0 1px 3px #00000014;--tw-shadow-colored:0 4px 6px var(--tw-shadow-color),0 1px 3px var(--tw-shadow-color)}.valid-btn:focus,.valid-btn:hover{background-color:#22c55ecc}.valid-btn:active{opacity:.85}.valid-btn:disabled{cursor:not-allowed;--tw-bg-opacity:1;--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity))}.valid-btn:disabled,.valid-btn:hover:disabled{border-color:#ced4da00;background-color:rgb(206 212 218/var(--tw-bg-opacity))}.valid-btn:hover:disabled{--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-bg-opacity:1}:is(.dark .valid-btn){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}:is(.dark .valid-btn:disabled){--tw-bg-opacity:1;--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}:is(.dark .valid-btn:disabled),:is(.dark .valid-btn:hover:disabled){border-color:#49505700;background-color:rgb(73 80 87/var(--tw-bg-opacity))}:is(.dark .valid-btn:hover:disabled){--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-bg-opacity:1}@media (min-width:768px){.valid-btn{padding:.625rem 1.25rem}}.delete-btn{display:inline-block;cursor:pointer;border-radius:.5rem;--tw-bg-opacity:1;background-color:rgb(245 57 57/var(--tw-bg-opacity));padding:.5rem 1rem;text-align:center;vertical-align:middle;font-weight:700;text-transform:uppercase;line-height:1.5;letter-spacing:.025em;--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity));--tw-shadow:0 7px 14px #32325d1a,0 3px 6px #00000014;--tw-shadow-colored:0 7px 14px var(--tw-shadow-color),0 3px 6px var(--tw-shadow-color);transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.delete-btn,.delete-btn:hover{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.delete-btn:hover{--tw-translate-y:-1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-shadow:0 4px 6px #32325d1a,0 1px 3px #00000014;--tw-shadow-colored:0 4px 6px var(--tw-shadow-color),0 1px 3px var(--tw-shadow-color)}.delete-btn:focus,.delete-btn:hover{background-color:#f53939cc}.delete-btn:active{opacity:.85}.delete-btn:disabled{cursor:not-allowed;--tw-bg-opacity:1;--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity))}.delete-btn:disabled,.delete-btn:hover:disabled{border-color:#ced4da00;background-color:rgb(206 212 218/var(--tw-bg-opacity))}.delete-btn:hover:disabled{--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-bg-opacity:1}:is(.dark .delete-btn){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}:is(.dark .delete-btn:disabled){border-color:#49505700;--tw-bg-opacity:1;background-color:rgb(73 80 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}:is(.dark .delete-btn:hover:disabled){--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-color:#49505700;--tw-bg-opacity:1;background-color:rgb(73 80 87/var(--tw-bg-opacity))}@media (min-width:768px){.delete-btn{padding:.625rem 1.25rem}}.edit-btn{display:inline-block;cursor:pointer;border-radius:.5rem;--tw-bg-opacity:1;background-color:rgb(251 177 64/var(--tw-bg-opacity));padding:.5rem 1rem;text-align:center;vertical-align:middle;font-weight:700;text-transform:uppercase;line-height:1.5;letter-spacing:.025em;--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity));--tw-shadow:0 7px 14px #32325d1a,0 3px 6px #00000014;--tw-shadow-colored:0 7px 14px var(--tw-shadow-color),0 3px 6px var(--tw-shadow-color);transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.edit-btn,.edit-btn:hover{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.edit-btn:hover{--tw-translate-y:-1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-shadow:0 4px 6px #32325d1a,0 1px 3px #00000014;--tw-shadow-colored:0 4px 6px var(--tw-shadow-color),0 1px 3px var(--tw-shadow-color)}.edit-btn:focus,.edit-btn:hover{background-color:#fbb140cc}.edit-btn:active{opacity:.85}.edit-btn:disabled{cursor:not-allowed;--tw-bg-opacity:1;--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity))}.edit-btn:disabled,.edit-btn:hover:disabled{border-color:#ced4da00;background-color:rgb(206 212 218/var(--tw-bg-opacity))}.edit-btn:hover:disabled{--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-bg-opacity:1}:is(.dark .edit-btn){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}:is(.dark .edit-btn:disabled){--tw-bg-opacity:1;--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}:is(.dark .edit-btn:disabled),:is(.dark .edit-btn:hover:disabled){border-color:#49505700;background-color:rgb(73 80 87/var(--tw-bg-opacity))}:is(.dark .edit-btn:hover:disabled){--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-bg-opacity:1}@media (min-width:768px){.edit-btn{padding:.625rem 1.25rem}}.info-btn{display:inline-block;cursor:pointer;border-radius:.5rem;--tw-bg-opacity:1;background-color:rgb(14 165 233/var(--tw-bg-opacity));padding:.5rem 1rem;text-align:center;vertical-align:middle;font-weight:700;text-transform:uppercase;line-height:1.5;letter-spacing:.025em;--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity));--tw-shadow:0 7px 14px #32325d1a,0 3px 6px #00000014;--tw-shadow-colored:0 7px 14px var(--tw-shadow-color),0 3px 6px var(--tw-shadow-color);transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.info-btn,.info-btn:hover{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.info-btn:hover{--tw-translate-y:-1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-shadow:0 4px 6px #32325d1a,0 1px 3px #00000014;--tw-shadow-colored:0 4px 6px var(--tw-shadow-color),0 1px 3px var(--tw-shadow-color)}.info-btn:focus,.info-btn:hover{background-color:#0ea5e9cc}.info-btn:active{opacity:.85}.info-btn:disabled{cursor:not-allowed;--tw-bg-opacity:1;--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity))}.info-btn:disabled,.info-btn:hover:disabled{border-color:#ced4da00;background-color:rgb(206 212 218/var(--tw-bg-opacity))}.info-btn:hover:disabled{--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-bg-opacity:1}:is(.dark .info-btn){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}:is(.dark .info-btn:disabled){--tw-bg-opacity:1;--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}:is(.dark .info-btn:disabled),:is(.dark .info-btn:hover:disabled){border-color:#49505700;background-color:rgb(73 80 87/var(--tw-bg-opacity))}:is(.dark .info-btn:hover:disabled){--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-bg-opacity:1}@media (min-width:768px){.info-btn{padding:.625rem 1.25rem}}.btn-disabled-style:disabled{cursor:not-allowed;--tw-bg-opacity:1;--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity))}.btn-disabled-style:disabled,.btn-disabled-style:hover:disabled{border-color:#ced4da00;background-color:rgb(206 212 218/var(--tw-bg-opacity))}.btn-disabled-style:hover:disabled{--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-bg-opacity:1}:is(.dark .btn-disabled-style:disabled){border-color:#49505700;--tw-bg-opacity:1;background-color:rgb(73 80 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}:is(.dark .btn-disabled-style:hover:disabled){--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-color:#49505700;--tw-bg-opacity:1;background-color:rgb(73 80 87/var(--tw-bg-opacity))}.checkbox{position:relative;z-index:10;float:left;margin-top:.25rem;height:1.25rem;width:1.25rem;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.35rem;border-width:1px;border-color:rgb(210 214 218/var(--tw-border-opacity));background-color:rgb(255 255 255/var(--tw-bg-opacity));background-size:contain;background-position:50%;background-repeat:no-repeat;vertical-align:top;font-size:1rem;line-height:1.5rem;transition-property:none;transition-property:all;transition-timing-function:ease;transition-duration:.25s}.checkbox,.checkbox:disabled{--tw-border-opacity:1;--tw-bg-opacity:1}.checkbox:disabled{cursor:default;cursor:not-allowed;border-color:rgb(206 212 218/var(--tw-border-opacity));background-color:rgb(206 212 218/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity))}.checkbox[data-checked=true]{z-index:0;--tw-border-opacity:1;border-color:rgb(11 85 119/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(11 85 119/var(--tw-bg-opacity))}.checkbox:disabled[data-checked=true]{--tw-border-opacity:1;border-color:rgb(206 212 218/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(206 212 218/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity))}:is(.dark .checkbox){--tw-border-opacity:1;border-color:rgb(98 117 148/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(52 71 103/var(--tw-bg-opacity))}:is(.dark .checkbox:disabled){--tw-border-opacity:1;border-color:rgb(37 47 64/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(37 47 64/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}:is(.dark .checkbox[data-checked=true]){--tw-border-opacity:1;border-color:rgb(11 85 119/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(11 85 119/var(--tw-bg-opacity))}:is(.dark .checkbox:disabled[data-checked=true]){--tw-border-opacity:1;border-color:rgb(37 47 64/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(37 47 64/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}.custom-select-btn{display:flex;min-height:38px;width:100%;align-items:center;justify-content:space-between;border-radius:.5rem;border-width:1px;border-style:solid;--tw-border-opacity:1;border-color:rgb(210 214 218/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));background-clip:padding-box;padding:.25rem .375rem;text-align:left;vertical-align:middle;font-size:.875rem;font-weight:400;line-height:1.4rem;--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity));transition-property:all;transition-timing-function:ease;transition-duration:.3s;transition-timing-function:ease-in-out}.custom-select-btn::-moz-placeholder{--tw-text-opacity:1;color:rgb(173 181 189/var(--tw-text-opacity))}.custom-select-btn::placeholder{--tw-text-opacity:1;color:rgb(173 181 189/var(--tw-text-opacity))}.custom-select-btn:focus{--tw-border-opacity:1;border-color:rgb(11 85 119/var(--tw-border-opacity))}.custom-select-btn:disabled{cursor:not-allowed;--tw-border-opacity:1;border-color:rgb(206 212 218/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(206 212 218/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity));opacity:.75}:is(.dark .custom-select-btn){--tw-border-opacity:1;border-color:rgb(98 117 148/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(52 71 103/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(233 236 239/var(--tw-text-opacity))}:is(.dark .custom-select-btn:disabled){--tw-border-opacity:1;border-color:rgb(37 47 64/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(37 47 64/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}@media (min-width:768px){.custom-select-btn{padding:.5rem .75rem}}.custom-dropdown-btn{position:relative;margin-top:0;margin-bottom:0;min-height:38px;cursor:pointer;border-radius:0;border-bottom-width:1px;border-left-width:1px;border-right-width:1px;--tw-border-opacity:1;border-color:rgb(210 214 218/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));padding:.5rem 1.5rem;text-align:center;vertical-align:middle;font-size:.875rem;line-height:1.5rem;line-height:1.5;letter-spacing:-.025rem;--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity));transition-property:none;transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.custom-dropdown-btn:hover{--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}:is(.dark .custom-dropdown-btn){--tw-border-opacity:1;border-color:rgb(98 117 148/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(52 71 103/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(233 236 239/var(--tw-text-opacity))}.active.custom-dropdown-btn{position:relative;margin-top:0;margin-bottom:0;min-height:38px;cursor:pointer;border-radius:0;border-bottom-width:1px;border-left-width:1px;border-right-width:1px;--tw-border-opacity:1;border-color:rgb(210 214 218/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(11 85 119/var(--tw-bg-opacity));padding:.5rem 1.5rem;text-align:center;vertical-align:middle;font-size:.875rem;line-height:1.5rem;line-height:1.5;letter-spacing:-.025rem;--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity));transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.active.custom-dropdown-btn:hover{--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}:is(.dark .active.custom-dropdown-btn){border-color:rgb(98 117 148/var(--tw-border-opacity));background-color:rgb(11 85 119/var(--tw-bg-opacity));color:rgb(233 236 239/var(--tw-text-opacity))}.regular-input,:is(.dark .active.custom-dropdown-btn){--tw-border-opacity:1;--tw-bg-opacity:1;--tw-text-opacity:1}.regular-input{display:block;width:100%;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.5rem;border-width:1px;border-style:solid;border-color:rgb(210 214 218/var(--tw-border-opacity));background-color:rgb(255 255 255/var(--tw-bg-opacity));background-clip:padding-box;padding:.25rem .375rem;font-size:.875rem;font-weight:400;line-height:1.4rem;color:rgb(73 80 87/var(--tw-text-opacity));outline:2px solid #0000;outline-offset:2px;transition-property:none;transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.regular-input::-moz-placeholder{--tw-text-opacity:1;color:rgb(173 181 189/var(--tw-text-opacity))}.regular-input::placeholder{--tw-text-opacity:1;color:rgb(173 181 189/var(--tw-text-opacity))}.regular-input:focus{border-color:#d2d6da00;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.regular-input:valid:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(34 197 94/var(--tw-ring-opacity))}.regular-input:invalid:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(245 57 57/var(--tw-ring-opacity))}.regular-input:disabled{cursor:not-allowed;--tw-bg-opacity:1;background-color:rgb(206 212 218/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity));opacity:.75}:is(.dark .regular-input){--tw-border-opacity:1;border-color:rgb(98 117 148/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(52 71 103/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(233 236 239/var(--tw-text-opacity))}:is(.dark .regular-input:disabled){--tw-border-opacity:1;border-color:rgb(37 47 64/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(37 47 64/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}@media (min-width:768px){.regular-input{padding:.5rem .75rem}}.invalid.regular-input{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)!important;--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)!important;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)!important;--tw-ring-opacity:1!important;--tw-ring-color:rgb(245 57 57/var(--tw-ring-opacity))!important}.input-title{margin:0;font-size:.875rem;line-height:1.5rem;font-weight:700;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.3s;transition-timing-function:ease-in-out}:is(.dark .input-title){--tw-text-opacity:1;color:rgb(233 236 239/var(--tw-text-opacity))}.popover-settings-container{position:fixed;z-index:1000;height:-moz-fit-content;height:fit-content;max-width:250px;--tw-translate-y:-1.75rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-radius:.375rem;padding:.75rem;transition-property:all;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.5s}:is(.dark .popover-settings-container){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.info.popover-settings-container{--tw-bg-opacity:1;background-color:rgb(94 114 228/var(--tw-bg-opacity))}.multisite.popover-settings-container{--tw-bg-opacity:1;background-color:rgb(251 99 64/var(--tw-bg-opacity))}.popover-tab{position:absolute;left:0;bottom:0;z-index:50;--tw-translate-y:-1.75rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-radius:.375rem;--tw-bg-opacity:1;background-color:rgb(94 114 228/var(--tw-bg-opacity));padding:.75rem;transition-property:all;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.5s}:is(.dark .popover-tab){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.popover-settings-text{margin:0;font-size:.875rem;line-height:1.5rem;font-weight:700;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.3s;transition-timing-function:ease-in-out}.popover-settings-text,:is(.dark .popover-settings-text){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.popover-settings-svg{margin-left:.5rem;height:1.25rem;width:1.25rem;cursor:pointer;fill:#5e72e4}.popover-settings-svg:hover{--tw-brightness:brightness(.75);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.popover-settings-svg-multiple{margin-left:.5rem;height:1.375rem;width:1.375rem;cursor:pointer;fill:#fb6340;stroke:#495057}.popover-settings-svg-multiple:hover{--tw-brightness:brightness(.75);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}:is(.dark .popover-settings-svg-multiple){stroke:#adb5bd}.hidden-multiple{display:none!important}.active.tabs-tab-btn,.active.tabs-tab-btn:hover{--tw-bg-opacity:1;background-color:rgb(233 236 239/var(--tw-bg-opacity))}:is(.dark .active.tabs-tab-btn),:is(.dark .active.tabs-tab-btn:hover){--tw-bg-opacity:1;background-color:rgb(73 80 87/var(--tw-bg-opacity))}.tabs-tab-btn{position:relative;margin-top:.25rem;margin-bottom:.25rem;cursor:pointer;border-radius:0;border-width:1px;--tw-border-opacity:1;border-color:rgb(11 85 119/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));padding:.75rem;text-align:center;vertical-align:middle;font-size:.875rem;line-height:1.5rem;font-weight:700;text-transform:uppercase;line-height:1.5;letter-spacing:-.025rem;--tw-shadow:0 7px 14px #32325d1a,0 3px 6px #00000014;--tw-shadow-colored:0 7px 14px var(--tw-shadow-color),0 3px 6px var(--tw-shadow-color);transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.tabs-tab-btn,.tabs-tab-btn:hover{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.tabs-tab-btn:hover{--tw-bg-opacity:1;background-color:rgb(235 239 244/var(--tw-bg-opacity));--tw-shadow:0 4px 6px #32325d1a,0 1px 3px #00000014;--tw-shadow-colored:0 4px 6px var(--tw-shadow-color),0 1px 3px var(--tw-shadow-color)}:is(.dark .tabs-tab-btn){--tw-border-opacity:1;border-color:rgb(98 117 148/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(52 71 103/var(--tw-bg-opacity))}:is(.dark .tabs-tab-btn:hover){--tw-bg-opacity:1;background-color:rgb(58 65 111/var(--tw-bg-opacity))}.tabs-name{padding-left:.75rem;padding-right:.5rem;--tw-text-opacity:1;color:rgb(11 85 119/var(--tw-text-opacity));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.3s;transition-timing-function:ease-in-out}:is(.dark .tabs-name){--tw-text-opacity:1;color:rgb(235 239 244/var(--tw-text-opacity))}.tabs-popover-container{position:absolute;top:60px;left:0;z-index:50;min-width:150px;border-radius:.375rem;--tw-bg-opacity:1;background-color:rgb(94 114 228/var(--tw-bg-opacity));padding:.75rem;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.15s}:is(.dark .tabs-popover-container){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.tabs-popover-text{margin:0;font-size:.875rem;line-height:1.5rem;font-weight:700;--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.settings-tabs-select-btn{margin-top:.25rem;margin-bottom:.25rem;display:flex;width:100%;cursor:pointer;align-items:center;justify-content:space-between;border-radius:.5rem;border-width:1px;--tw-border-opacity:1;border-color:rgb(11 85 119/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));padding:.5rem 1rem;text-align:center;vertical-align:middle;font-size:.875rem;line-height:1.5rem;font-weight:700;text-transform:uppercase;line-height:1.5;letter-spacing:-.025rem;--tw-shadow:0 7px 14px #32325d1a,0 3px 6px #00000014;--tw-shadow-colored:0 7px 14px var(--tw-shadow-color),0 3px 6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.settings-tabs-select-btn:hover{--tw-translate-y:-1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-bg-opacity:1;background-color:rgb(248 249 250/var(--tw-bg-opacity));--tw-shadow:0 4px 6px #32325d1a,0 1px 3px #00000014;--tw-shadow-colored:0 4px 6px var(--tw-shadow-color),0 1px 3px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}:is(.dark .settings-tabs-select-btn){--tw-border-opacity:1;border-color:rgb(98 117 148/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(52 71 103/var(--tw-bg-opacity))}:is(.dark .settings-tabs-select-btn:hover){--tw-brightness:brightness(.95);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}@media (min-width:768px){.settings-tabs-select-btn{padding:.75rem 1.5rem}}.settings-tabs-select-btn-text{word-break:break-all;--tw-text-opacity:1;color:rgb(11 85 119/var(--tw-text-opacity));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.3s;transition-timing-function:ease-in-out}:is(.dark .settings-tabs-select-btn-text){--tw-text-opacity:1;color:rgb(206 212 218/var(--tw-text-opacity))}.settings-tabs-select-btn-svg{margin-left:1rem;height:1rem;width:1rem;min-width:1rem;fill:#0b5577;transition-property:transform;transition-timing-function:ease;transition-duration:.15s}:is(.dark .settings-tabs-select-btn-svg){fill:#d2d6da}.active.settings-tabs-select-dropdown-btn{position:relative;z-index:1000;margin-top:0;margin-bottom:0;cursor:pointer;border-radius:0;--tw-border-opacity:1;border-color:rgb(210 214 218/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(11 85 119/var(--tw-bg-opacity));padding:.5rem 1.5rem;text-align:center;vertical-align:middle;font-size:.875rem;line-height:1.5rem;line-height:1.5;letter-spacing:-.025rem;--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity));transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.active.settings-tabs-select-dropdown-btn:hover{--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}:is(.dark .active.settings-tabs-select-dropdown-btn){--tw-border-opacity:1;border-color:rgb(98 117 148/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(11 85 119/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(233 236 239/var(--tw-text-opacity))}:is(.dark .active.settings-tabs-select-dropdown-btn:hover){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.first.settings-tabs-select-dropdown-btn{border-top-left-radius:.25rem;border-top-right-radius:.25rem;border-width:1px}.last.settings-tabs-select-dropdown-btn{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.settings-tabs-select-dropdown-btn{position:relative;margin-top:0;margin-bottom:0;display:flex;cursor:pointer;justify-content:space-between;word-break:break-all;border-radius:0;border-bottom-width:1px;border-left-width:1px;border-right-width:1px;--tw-border-opacity:1;border-color:rgb(210 214 218/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));padding:.5rem 1.5rem;text-align:left;vertical-align:middle;font-size:.875rem;line-height:1.5rem;line-height:1.5;letter-spacing:-.025rem;--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity));transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.settings-tabs-select-dropdown-btn:hover{--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}:is(.dark .settings-tabs-select-dropdown-btn){--tw-border-opacity:1;border-color:rgb(98 117 148/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(52 71 103/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(233 236 239/var(--tw-text-opacity))}:is(.dark .settings-tabs-select-dropdown-btn:hover){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.home-card{grid-column:span 12/span 12;display:flex;width:100%;justify-content:space-between;overflow-wrap:break-word;word-break:break-all;border-radius:1rem;--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));background-clip:initial;padding:1rem;--tw-shadow:0 4px 6px #32325d1a,0 1px 3px #00000014;--tw-shadow-colored:0 4px 6px var(--tw-shadow-color),0 1px 3px var(--tw-shadow-color);transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.15s}.home-card,:is(.dark .home-card){box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}:is(.dark .home-card){--tw-bg-opacity:1;background-color:rgb(17 28 68/var(--tw-bg-opacity));--tw-shadow:0 2px 2px 0 #00000024,0 3px 1px -2px #0003,0 1px 5px 0 #0000001f;--tw-shadow-colored:0 2px 2px 0 var(--tw-shadow-color),0 3px 1px -2px var(--tw-shadow-color),0 1px 5px 0 var(--tw-shadow-color);--tw-brightness:brightness(1.1);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}@media (min-width:576px){.home-card{max-height:7rem}}@media (min-width:768px){.home-card{grid-column:span 6/span 6}}@media (min-width:1320px){.home-card{grid-column:span 4/span 4}}.home-card-name{margin-bottom:0;font-family:Open Sans;font-size:.875rem;line-height:1.5rem;font-weight:600;text-transform:uppercase;line-height:1.5}:is(.dark .home-card-name){--tw-text-opacity:1;color:rgb(206 212 218/var(--tw-text-opacity))}.home-card-title{margin-bottom:.25rem;font-weight:700}:is(.dark .home-card-title){color:#ffffffe6}.home-card-subtitle{margin-left:.125rem;margin-right:.125rem;margin-bottom:0;font-size:.875rem;line-height:1.5rem;font-weight:700;line-height:1.5}.info.home-card-subtitle{--tw-text-opacity:1;color:rgb(14 165 233/var(--tw-text-opacity))}.error.home-card-subtitle{--tw-text-opacity:1;color:rgb(245 57 57/var(--tw-text-opacity))}.success.home-card-subtitle{--tw-text-opacity:1;color:rgb(34 197 94/var(--tw-text-opacity))}.warning.home-card-subtitle{--tw-text-opacity:1;color:rgb(251 177 64/var(--tw-text-opacity))}.home-card-svg-container{display:inline-block;height:3rem;width:3rem;min-width:3rem;border-radius:50%;text-align:center}:is(.dark .home-card-svg-container){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.version.home-card-svg-container{--tw-bg-opacity:1;background-color:rgb(251 177 64/var(--tw-bg-opacity))}.version-number.home-card-svg-container{--tw-bg-opacity:1;background-color:rgb(45 206 137/var(--tw-bg-opacity))}.instances.home-card-svg-container{--tw-bg-opacity:1;background-color:rgb(108 117 125/var(--tw-bg-opacity))}.services.home-card-svg-container{--tw-bg-opacity:1;background-color:rgb(251 99 64/var(--tw-bg-opacity))}.plugins.home-card-svg-container{--tw-bg-opacity:1;background-color:rgb(251 207 51/var(--tw-bg-opacity))}.card-detail-container{margin-top:1rem;margin-bottom:1.5rem;margin-left:.25rem;display:grid;grid-template-columns:repeat(1,minmax(0,1fr));gap:.5rem}.card-detail-item{grid-column:span 1/span 1;display:flex;align-items:center;padding-top:.25rem;padding-bottom:.25rem}@media (min-width:576px){.card-detail-item{padding-top:0;padding-bottom:0}}.card-detail-item-title{margin-bottom:0;min-width:-moz-fit-content;min-width:fit-content;font-family:Open Sans;font-size:.875rem;line-height:1.5rem;font-weight:700;text-transform:uppercase;line-height:1.5;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.3s;transition-timing-function:ease-in-out}:is(.dark .card-detail-item-title){--tw-text-opacity:1;color:rgb(173 181 189/var(--tw-text-opacity))}.card-detail-item-subtitle{grid-column:span 1/span 1;margin-bottom:0;min-width:2rem;word-break:break-all;padding-left:.5rem;font-family:Open Sans;font-size:.875rem;line-height:1.5rem;font-weight:600;text-transform:uppercase;line-height:1.5;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.3s;transition-timing-function:ease-in-out}:is(.dark .card-detail-item-subtitle){--tw-text-opacity:1;color:rgb(235 239 244/var(--tw-text-opacity))}.core-layout{grid-column:span 12/span 12;display:grid;grid-template-columns:repeat(12,minmax(0,1fr))}.core-card{position:relative;grid-column:span 12/span 12;margin:.5rem;height:-moz-fit-content;height:fit-content;min-width:0;overflow-wrap:break-word;border-radius:1rem;--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));background-clip:initial;padding:1rem;--tw-shadow:0 0 2rem 0 #8898aa26;--tw-shadow-colored:0 0 2rem 0 var(--tw-shadow-color);transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.15s}.core-card,:is(.dark .core-card){box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}:is(.dark .core-card){--tw-bg-opacity:1;background-color:rgb(17 28 68/var(--tw-bg-opacity));--tw-shadow:0 2px 2px 0 #00000024,0 3px 1px -2px #0003,0 1px 5px 0 #0000001f;--tw-shadow-colored:0 2px 2px 0 var(--tw-shadow-color),0 3px 1px -2px var(--tw-shadow-color),0 1px 5px 0 var(--tw-shadow-color);--tw-brightness:brightness(1.1);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}@media (min-width:768px){.core-card{grid-column:span 6/span 6}}@media (min-width:1320px){.core-card{grid-column:span 4/span 4}}@media (min-width:1920px){.core-card{grid-column:span 3/span 3}}.core-card-list-large{position:relative;grid-column:span 12/span 12;margin:.5rem;height:-moz-fit-content;height:fit-content;min-width:0;overflow-x:auto;overflow-y:hidden;overflow-wrap:break-word;border-radius:1rem;--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));background-clip:initial;padding:1rem;--tw-shadow:0 0 2rem 0 #8898aa26;--tw-shadow-colored:0 0 2rem 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.15s}:is(.dark .core-card-list-large){--tw-bg-opacity:1;background-color:rgb(17 28 68/var(--tw-bg-opacity));--tw-shadow:0 2px 2px 0 #00000024,0 3px 1px -2px #0003,0 1px 5px 0 #0000001f;--tw-shadow-colored:0 2px 2px 0 var(--tw-shadow-color),0 3px 1px -2px var(--tw-shadow-color),0 1px 5px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);--tw-brightness:brightness(1.1);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}@media (min-width:992px){.core-card-list-large{grid-column:span 6/span 6;margin-bottom:0;height:100%}}.core-card-filter{position:relative;grid-column:span 12/span 12;display:flex;min-width:0;flex-direction:column;overflow-wrap:break-word;border-radius:1rem;--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));background-clip:initial;padding:1rem;--tw-shadow:0 0 2rem 0 #8898aa26;--tw-shadow-colored:0 0 2rem 0 var(--tw-shadow-color)}.core-card-filter,:is(.dark .core-card-filter){box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}:is(.dark .core-card-filter){--tw-bg-opacity:1;background-color:rgb(17 28 68/var(--tw-bg-opacity));--tw-shadow:0 2px 2px 0 #00000024,0 3px 1px -2px #0003,0 1px 5px 0 #0000001f;--tw-shadow-colored:0 2px 2px 0 var(--tw-shadow-color),0 3px 1px -2px var(--tw-shadow-color),0 1px 5px 0 var(--tw-shadow-color)}@media (min-width:768px){.core-card-filter{grid-column:span 6/span 6}}@media (min-width:1320px){.core-card-filter{grid-column:span 4/span 4}}.core-card-lg{position:relative;grid-column:span 12/span 12;margin:.5rem;height:-moz-fit-content;height:fit-content;min-width:0;overflow-wrap:break-word;border-radius:1rem;--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));background-clip:initial;padding:1rem;--tw-shadow:0 0 2rem 0 #8898aa26;--tw-shadow-colored:0 0 2rem 0 var(--tw-shadow-color);transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.15s}.core-card-lg,:is(.dark .core-card-lg){box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}:is(.dark .core-card-lg){--tw-bg-opacity:1;background-color:rgb(17 28 68/var(--tw-bg-opacity));--tw-shadow:0 2px 2px 0 #00000024,0 3px 1px -2px #0003,0 1px 5px 0 #0000001f;--tw-shadow-colored:0 2px 2px 0 var(--tw-shadow-color),0 3px 1px -2px var(--tw-shadow-color),0 1px 5px 0 var(--tw-shadow-color);--tw-brightness:brightness(1.1);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}@media (min-width:768px){.core-card-lg{grid-column:span 6/span 6}}.core-card-wrap{display:flex;justify-content:space-between}.core-card-wrap-logo{display:flex;align-items:center;justify-content:flex-start}.core-card-text{margin-bottom:0;font-family:Open Sans;font-size:.875rem;line-height:1.5rem;line-height:1.5;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.3s;transition-timing-function:ease-in-out}:is(.dark .core-card-text){--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}.core-card-text-doc{margin-top:1rem;margin-bottom:.5rem;padding-left:.25rem;padding-right:.25rem;font-family:Open Sans;font-size:.875rem;line-height:1.5rem;line-height:1.5;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.3s;transition-timing-function:ease-in-out}:is(.dark .core-card-text-doc){--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}.core-card-text-doc-link{margin-top:.5rem;cursor:pointer;--tw-text-opacity:1;color:rgb(14 165 233/var(--tw-text-opacity));text-decoration-line:underline}.core-card-text-doc-link:hover{--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.center.core-card-text{text-align:center}.core-card-title{margin-bottom:.5rem;font-weight:700}:is(.dark .core-card-title){color:#ffffffe6}.core-card-svg-container{display:inline-block;height:3rem;width:3rem;border-radius:50%;text-align:center}:is(.dark .core-card-svg-container){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.core-card-deactivated-title{font-weight:700}:is(.dark .core-card-deactivated-title){color:#ffffffe6}.core-card-deactivated-svg{position:relative;--tw-translate-y:-8px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));fill:#fbb140;stroke:#fff;font-size:1.125rem;line-height:1.75rem;line-height:1}.core-card-text-container{margin:.75rem .25rem;display:flex;align-items:center;justify-content:flex-start}.core-card-status{position:relative;grid-column:span 12/span 12;margin:.5rem;height:-moz-fit-content;height:fit-content;min-width:0;overflow-wrap:break-word;border-radius:1rem;--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));background-clip:initial;padding:1rem;--tw-shadow:0 0 2rem 0 #8898aa26;--tw-shadow-colored:0 0 2rem 0 var(--tw-shadow-color);transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.15s}.core-card-status,:is(.dark .core-card-status){box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}:is(.dark .core-card-status){--tw-bg-opacity:1;background-color:rgb(17 28 68/var(--tw-bg-opacity));--tw-shadow:0 2px 2px 0 #00000024,0 3px 1px -2px #0003,0 1px 5px 0 #0000001f;--tw-shadow-colored:0 2px 2px 0 var(--tw-shadow-color),0 3px 1px -2px var(--tw-shadow-color),0 1px 5px 0 var(--tw-shadow-color);--tw-brightness:brightness(1.1);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}@media (min-width:768px){.core-card-status{grid-column:span 6/span 6}}@media (min-width:1320px){.core-card-status{grid-column:span 3/span 3}}@media (min-width:1920px){.core-card-status{grid-column:span 2/span 2}}.core-card-status-container{display:flex;align-items:center;justify-content:space-between}.core-card-status-title{margin-bottom:0;margin-right:1rem;font-weight:700}:is(.dark .core-card-status-title){color:#ffffffe6}.core-card-status-svg{height:1.5rem;width:1.5rem}.info.core-card-status-svg{fill:#0ea5e9}.error.core-card-status-svg{fill:#f53939}.success.core-card-status-svg{fill:#22c55e}.core-layout-separator{grid-column:span 12/span 12}.core-card-list{position:relative;grid-column:span 12/span 12;margin:.5rem;display:grid;max-height:25rem;grid-template-columns:repeat(12,minmax(0,1fr));align-content:flex-start;overflow-y:auto;overflow-x:hidden;overflow-wrap:break-word;border-radius:1rem;--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));background-clip:initial;padding:1rem;--tw-shadow:0 0 2rem 0 #8898aa26;--tw-shadow-colored:0 0 2rem 0 var(--tw-shadow-color)}.core-card-list,:is(.dark .core-card-list){box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}:is(.dark .core-card-list){--tw-bg-opacity:1;background-color:rgb(17 28 68/var(--tw-bg-opacity));--tw-shadow:0 2px 2px 0 #00000024,0 3px 1px -2px #0003,0 1px 5px 0 #0000001f;--tw-shadow-colored:0 2px 2px 0 var(--tw-shadow-color),0 3px 1px -2px var(--tw-shadow-color),0 1px 5px 0 var(--tw-shadow-color);--tw-brightness:brightness(1.1);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}@media (min-width:576px){.core-card-list{max-height:31.25rem}}@media (min-width:768px){.core-card-list{grid-column:span 6/span 6}}.core-card-list.no-data{place-content:stretch}.core-card-list-no-data{margin-bottom:0;padding-bottom:2rem;text-align:center;font-size:1.5rem;line-height:2rem}@media (min-width:768px){.w-small.core-card-list{max-width:300px}.w-medium.core-card-list{max-width:400px}.w-large.core-card-list{max-width:550px}}.core-card-list-title-container{grid-column:span 12/span 12;display:flex}.core-card-list-title{margin-bottom:1rem;font-weight:700}:is(.dark .core-card-list-title){color:#ffffffe6}.core-card-list-container{grid-column:span 12/span 12;overflow-x:auto;overflow-y:auto}.core-card-list-header{margin:0;height:2rem;border-bottom-width:1px;--tw-border-opacity:1;border-color:rgb(206 212 218/var(--tw-border-opacity));padding-bottom:.5rem;font-size:.875rem;line-height:1.5rem;font-weight:700}:is(.dark .core-card-list-header){--tw-text-opacity:1;color:rgb(233 236 239/var(--tw-text-opacity))}.core-card-list-item{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));align-items:center;border-bottom-width:1px;--tw-border-opacity:1;border-color:rgb(210 214 218/var(--tw-border-opacity));padding-top:.625rem;padding-bottom:.625rem}.core-card-list-item-content{margin:.25rem 0;font-size:.875rem;line-height:1.5rem}:is(.dark .core-card-list-item-content){--tw-text-opacity:1;color:rgb(206 212 218/var(--tw-text-opacity))}.core-card-list-wrap{display:grid;width:100%;grid-template-columns:repeat(12,minmax(0,1fr));border-radius:.25rem;padding:.5rem}.w-small.core-card-list-wrap{min-width:200px}.w-medium.core-card-list-wrap{min-width:300px}.w-large.core-card-list-wrap{min-width:450px}.core-card-metrics{grid-column:span 12/span 12;margin:.5rem;display:flex;height:-moz-fit-content;height:fit-content;justify-content:space-between;overflow-wrap:break-word;border-radius:1rem;--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));background-clip:initial;padding:1rem;--tw-shadow:0 0 2rem 0 #8898aa26;--tw-shadow-colored:0 0 2rem 0 var(--tw-shadow-color);transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.15s}.core-card-metrics,:is(.dark .core-card-metrics){box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}:is(.dark .core-card-metrics){--tw-bg-opacity:1;background-color:rgb(17 28 68/var(--tw-bg-opacity));--tw-shadow:0 2px 2px 0 #00000024,0 3px 1px -2px #0003,0 1px 5px 0 #0000001f;--tw-shadow-colored:0 2px 2px 0 var(--tw-shadow-color),0 3px 1px -2px var(--tw-shadow-color),0 1px 5px 0 var(--tw-shadow-color);--tw-brightness:brightness(1.1);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}@media (min-width:576px){.core-card-metrics{max-height:7rem}}@media (min-width:768px){.core-card-metrics{grid-column:span 6/span 6}}@media (min-width:1320px){.core-card-metrics{grid-column:span 4/span 4}}.core-card-metrics-name{margin-bottom:.5rem;font-family:Open Sans;font-size:.875rem;line-height:1.5rem;font-weight:600;text-transform:uppercase;line-height:1.5}:is(.dark .core-card-metrics-name){--tw-text-opacity:1;color:rgb(206 212 218/var(--tw-text-opacity))}.core-card-metrics-subtitle{margin-bottom:0}:is(.dark .core-card-metrics-subtitle){--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}.core-card-metrics-subtitle-content{margin-left:.125rem;margin-right:.125rem;font-size:.875rem;line-height:1.5rem;font-weight:700;line-height:1.5}.error.core-card-metrics-subtitle-content{--tw-text-opacity:1;color:rgb(245 57 57/var(--tw-text-opacity))}.success.core-card-metrics-subtitle-content{--tw-text-opacity:1;color:rgb(34 197 94/var(--tw-text-opacity))}.warning.core-card-metrics-subtitle-content{--tw-text-opacity:1;color:rgb(251 177 64/var(--tw-text-opacity))}.info.core-card-metrics-subtitle-content{--tw-text-opacity:1;color:rgb(14 165 233/var(--tw-text-opacity))}.core-card-metrics-svg{position:relative;fill:#fff;font-size:1.125rem;line-height:1.75rem;line-height:1}.size-small.core-card-metrics-svg{--tw-scale-x:0.5;--tw-scale-y:0.5}.size-medium.core-card-metrics-svg,.size-small.core-card-metrics-svg{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.size-medium.core-card-metrics-svg{--tw-scale-x:0.6;--tw-scale-y:0.6}.size-base.core-card-metrics-svg{--tw-scale-x:0.75;--tw-scale-y:0.75;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.purple.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(147 51 234/var(--tw-bg-opacity))}.green.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(21 128 61/var(--tw-bg-opacity))}.red.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(185 28 28/var(--tw-bg-opacity))}.orange.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(245 96 54/var(--tw-bg-opacity))}.blue.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(33 82 255/var(--tw-bg-opacity))}.yellow.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(202 138 4/var(--tw-bg-opacity))}.gray.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(108 117 125/var(--tw-bg-opacity))}.dark.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(98 117 148/var(--tw-bg-opacity))}.amber.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(217 119 6/var(--tw-bg-opacity))}.emerald.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(5 150 105/var(--tw-bg-opacity))}.teal.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(13 148 136/var(--tw-bg-opacity))}.indigo.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity))}.cyan.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(8 145 178/var(--tw-bg-opacity))}.sky.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(3 105 161/var(--tw-bg-opacity))}.pink.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(219 39 119/var(--tw-bg-opacity))}.lime.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(101 163 13/var(--tw-bg-opacity))}.core-separator{margin:.75rem 0 .5rem;height:1px;background-color:initial;--tw-gradient-from:#0000 var(--tw-gradient-from-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to);--tw-gradient-stops:var(--tw-gradient-from),#0006 var(--tw-gradient-via-position),var(--tw-gradient-to);--tw-gradient-to:#0000 var(--tw-gradient-to-position)}.core-separator,:is(.dark .core-separator){background-image:linear-gradient(to right,var(--tw-gradient-stops))}:is(.dark .core-separator){--tw-gradient-from:#0000 var(--tw-gradient-from-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to);--tw-gradient-to:#fff0 var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),#fff var(--tw-gradient-via-position),var(--tw-gradient-to);--tw-gradient-to:#0000 var(--tw-gradient-to-position)}.core-card-test-container{margin-top:1rem;display:flex;justify-content:center}.core-card-test-btn{display:inline-block;cursor:pointer;border-radius:.5rem;--tw-bg-opacity:1;background-color:rgb(251 177 64/var(--tw-bg-opacity));padding:.75rem 1.5rem;text-align:center;vertical-align:middle;font-size:.875rem;line-height:1.5rem;font-weight:700;text-transform:uppercase;line-height:1.5;letter-spacing:.025em;--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity));--tw-shadow:0 7px 14px #32325d1a,0 3px 6px #00000014;--tw-shadow-colored:0 7px 14px var(--tw-shadow-color),0 3px 6px var(--tw-shadow-color);transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.core-card-test-btn,.core-card-test-btn:hover{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.core-card-test-btn:hover{--tw-translate-y:-1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));background-color:#fbb140cc;--tw-shadow:0 4px 6px #32325d1a,0 1px 3px #00000014;--tw-shadow-colored:0 4px 6px var(--tw-shadow-color),0 1px 3px var(--tw-shadow-color)}.core-card-test-btn:focus{background-color:#fbb140cc}.core-card-test-btn:active{opacity:.85}.core-card-test-btn:disabled{cursor:not-allowed;--tw-bg-opacity:1;--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity))}.core-card-test-btn:disabled,.core-card-test-btn:hover:disabled{border-color:#ced4da00;background-color:rgb(206 212 218/var(--tw-bg-opacity))}.core-card-test-btn:hover:disabled{--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-bg-opacity:1}:is(.dark .core-card-test-btn){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}:is(.dark .core-card-test-btn:disabled){border-color:#49505700;--tw-bg-opacity:1;background-color:rgb(73 80 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}:is(.dark .core-card-test-btn:hover:disabled){--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-color:#49505700;--tw-bg-opacity:1;background-color:rgb(73 80 87/var(--tw-bg-opacity))}.core-card-upload-btn{display:inline-block;width:-moz-fit-content;width:fit-content;cursor:pointer;border-radius:.5rem;--tw-bg-opacity:1;background-color:rgb(11 85 119/var(--tw-bg-opacity));background-image:linear-gradient(to top left,var(--tw-gradient-stops));background-size:150%;background-position:25% 0;padding:.75rem 1.5rem;text-align:center;vertical-align:middle;font-size:.75rem;line-height:1rem;font-weight:700;text-transform:uppercase;line-height:1.5;letter-spacing:.025em;--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity));--tw-shadow:0 7px 14px #32325d1a,0 3px 6px #00000014;--tw-shadow-colored:0 7px 14px var(--tw-shadow-color),0 3px 6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.core-card-upload-btn:hover{--tw-translate-y:-1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-shadow:0 4px 6px #32325d1a,0 1px 3px #00000014;--tw-shadow-colored:0 4px 6px var(--tw-shadow-color),0 1px 3px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);--tw-brightness:brightness(.75);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.core-card-upload-btn:active{opacity:.85}.core-card-upload-btn:disabled{cursor:not-allowed;--tw-border-opacity:1;border-color:rgb(206 212 218/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(206 212 218/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity));opacity:.75}.core-card-upload-btn:hover:disabled{--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}:is(.dark .core-card-upload-btn){--tw-brightness:brightness(1.25);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}:is(.dark .core-card-upload-btn:disabled){--tw-border-opacity:1;border-color:rgb(37 47 64/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(37 47 64/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}.core-card-test-status-container{margin-left:.25rem;margin-right:.25rem;display:flex;align-items:center;justify-content:center}.core-card-test-status-svg{margin-right:.5rem;height:1.5rem;width:1.5rem}.success.core-card-test-status-svg{fill:#22c55e}.error.core-card-test-status-svg{fill:#f53939}.info.core-card-test-status-svg{fill:#0ea5e9}.core-img-default{margin-right:1rem;height:3rem;width:3rem}.core-img-hor{margin-right:1rem;height:4rem;width:6rem}.core-card-detail{position:relative;grid-column:span 12/span 12;height:-moz-fit-content;height:fit-content;overflow:auto;overflow-wrap:break-word;border-radius:1rem;--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));background-clip:initial;padding:1rem;--tw-shadow:0 0 2rem 0 #8898aa26;--tw-shadow-colored:0 0 2rem 0 var(--tw-shadow-color)}.core-card-detail,:is(.dark .core-card-detail){box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}:is(.dark .core-card-detail){--tw-bg-opacity:1;background-color:rgb(17 28 68/var(--tw-bg-opacity));--tw-shadow:0 2px 2px 0 #00000024,0 3px 1px -2px #0003,0 1px 5px 0 #0000001f;--tw-shadow-colored:0 2px 2px 0 var(--tw-shadow-color),0 3px 1px -2px var(--tw-shadow-color),0 1px 5px 0 var(--tw-shadow-color)}@media (min-width:992px){.core-card-detail{grid-column:span 6/span 6}}@media (min-width:1200px){.core-card-detail{grid-column:span 6/span 6}}@media (min-width:1920px){.core-card-detail{grid-column:span 3/span 3}}.core-card-detail-title{margin-bottom:.5rem;font-weight:700}:is(.dark .core-card-detail-title){color:#ffffffe6}.core-card-detail-items-container{margin-top:1rem;margin-bottom:1.5rem;margin-left:.25rem;display:grid;grid-template-columns:repeat(1,minmax(0,1fr));gap:.5rem}.core-card-detail-item{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));padding-top:.25rem;padding-bottom:.25rem}@media (min-width:576px){.core-card-detail-item{padding-top:0;padding-bottom:0}}.core-card-detail-item-title{grid-column:span 4/span 4;margin-bottom:0;width:100%;min-width:-moz-fit-content;min-width:fit-content;word-break:break-all;font-family:Open Sans;font-size:.875rem;line-height:1.5rem;font-weight:700;text-transform:uppercase;line-height:1.5;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.3s;transition-timing-function:ease-in-out}:is(.dark .core-card-detail-item-title){--tw-text-opacity:1;color:rgb(173 181 189/var(--tw-text-opacity))}.core-card-detail-item-subtitle{grid-column:span 8/span 8;margin-bottom:0;width:100%;min-width:2rem;word-break:break-all;padding-left:.5rem;font-family:Open Sans;font-size:.875rem;line-height:1.5rem;font-weight:600;text-transform:uppercase;line-height:1.5;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.3s;transition-timing-function:ease-in-out}:is(.dark .core-card-detail-item-subtitle){--tw-text-opacity:1;color:rgb(235 239 244/var(--tw-text-opacity))}.file-manager-actions-item-btn{position:relative;margin:.25rem;cursor:pointer;white-space:nowrap;border-radius:.25rem;border-width:1px;--tw-border-opacity:1;border-color:rgb(11 85 119/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));padding:.75rem 1.25rem .625rem 1rem;text-align:center;vertical-align:middle;font-size:.875rem;line-height:1.5rem;font-weight:700;text-transform:uppercase;line-height:1.5;letter-spacing:-.025rem;--tw-text-opacity:1;color:rgb(11 85 119/var(--tw-text-opacity));--tw-shadow:0 7px 14px #32325d1a,0 3px 6px #00000014;--tw-shadow-colored:0 7px 14px var(--tw-shadow-color),0 3px 6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.file-manager-actions-item-btn:hover{--tw-bg-opacity:1;background-color:rgb(235 239 244/var(--tw-bg-opacity));--tw-shadow:0 4px 6px #32325d1a,0 1px 3px #00000014;--tw-shadow-colored:0 4px 6px var(--tw-shadow-color),0 1px 3px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.file-manager-actions-item-btn:disabled{cursor:not-allowed;border-color:#ced4da00;--tw-bg-opacity:1;background-color:rgb(206 212 218/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity))}.file-manager-actions-item-btn:hover:disabled{--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-color:#ced4da00;--tw-bg-opacity:1;background-color:rgb(206 212 218/var(--tw-bg-opacity))}:is(.dark .file-manager-actions-item-btn){--tw-border-opacity:1;border-color:rgb(98 117 148/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(52 71 103/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(233 236 239/var(--tw-text-opacity))}:is(.dark .file-manager-actions-item-btn:hover){--tw-bg-opacity:1;background-color:rgb(58 65 111/var(--tw-bg-opacity))}:is(.dark .file-manager-actions-item-btn:disabled){border-color:#49505700;--tw-bg-opacity:1;background-color:rgb(73 80 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}:is(.dark .file-manager-actions-item-btn:hover:disabled){--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-color:#49505700;--tw-bg-opacity:1;background-color:rgb(73 80 87/var(--tw-bg-opacity))}@media (min-width:768px){.file-manager-actions-item-btn{display:block}}.plugins-list-container{position:relative;grid-column:span 12/span 12;width:100%;overflow:auto;overflow-wrap:break-word;border-radius:1rem;--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));background-clip:initial;padding:1rem;--tw-shadow:0 0 2rem 0 #8898aa26;--tw-shadow-colored:0 0 2rem 0 var(--tw-shadow-color)}.plugins-list-container,:is(.dark .plugins-list-container){box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}:is(.dark .plugins-list-container){--tw-bg-opacity:1;background-color:rgb(17 28 68/var(--tw-bg-opacity));--tw-shadow:0 2px 2px 0 #00000024,0 3px 1px -2px #0003,0 1px 5px 0 #0000001f;--tw-shadow-colored:0 2px 2px 0 var(--tw-shadow-color),0 3px 1px -2px var(--tw-shadow-color),0 1px 5px 0 var(--tw-shadow-color)}.plugins-list-container-title-container{grid-column:span 12/span 12}.plugins-list-container-title{margin-left:.5rem;margin-right:.5rem;font-weight:700}:is(.dark .plugins-list-container-title){color:#ffffffe6}.plugins-list-items-container{position:relative;grid-column:span 12/span 12;max-height:20rem;min-height:55vh;overflow:auto;padding:.5rem}.plugins-list-items-wrap{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:.75rem}.plugins-list-items{position:relative;grid-column:span 12/span 12;display:flex;min-height:3rem;align-items:center;justify-content:space-between;border-radius:.25rem;padding:.75rem .25rem;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.15s}@media (min-width:576px){.plugins-list-items{grid-column:span 6/span 6}}@media (min-width:1320px){.plugins-list-items{grid-column:span 4/span 4}}@media (min-width:1920px){.plugins-list-items{grid-column:span 3/span 3}}.enabled.plugins-list-items{--tw-bg-opacity:1;background-color:rgb(235 239 244/var(--tw-bg-opacity))}.enabled.plugins-list-items:hover{--tw-bg-opacity:1;background-color:rgb(210 214 218/var(--tw-bg-opacity))}:is(.dark .enabled.plugins-list-items){--tw-bg-opacity:1;background-color:rgb(52 71 103/var(--tw-bg-opacity))}:is(.dark .enabled.plugins-list-items:hover){--tw-bg-opacity:1;background-color:rgb(58 65 111/var(--tw-bg-opacity))}.disabled.plugins-list-items{cursor:not-allowed;--tw-bg-opacity:1;background-color:rgb(210 214 218/var(--tw-bg-opacity))}:is(.dark .disabled.plugins-list-items){--tw-bg-opacity:1;background-color:rgb(37 47 64/var(--tw-bg-opacity))}.plugins-list-items-name{margin-left:.75rem;margin-right:.5rem;margin-bottom:0;overflow-wrap:break-word;word-break:break-all;text-align:left;font-size:.875rem;line-height:1.5rem;--tw-text-opacity:1;color:rgb(52 71 103/var(--tw-text-opacity));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.3s;transition-timing-function:ease-in-out}:is(.dark .plugins-list-items-name){--tw-text-opacity:1;color:rgb(233 236 239/var(--tw-text-opacity))}@media (min-width:768px){.plugins-list-items-name{font-size:1rem;line-height:1.5rem}}.disabled.plugins-list-items-name{opacity:.8}:is(.dark .disabled.plugins-list-items-name){opacity:.6}.plugins-list-items-actions{display:flex;align-items:center}.plugins-list-items-delete{z-index:20;margin-left:.5rem;margin-right:.5rem;display:inline-block;cursor:pointer;text-align:left;vertical-align:middle;font-size:.75rem;line-height:1rem;font-weight:700;text-transform:uppercase;letter-spacing:-.025rem;--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity));transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.plugins-list-items-delete:hover{--tw-translate-y:-1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.plugins-list-items-delete:disabled{cursor:not-allowed}.plugins-list-items-delete-svg{height:1.25rem;width:1.25rem;fill:#f53939}:is(.dark .plugins-list-items-delete-svg){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.readonly.plugins-list-items-delete-svg{cursor:not-allowed;opacity:.5}.plugins-list-items-link{margin-left:.25rem;margin-right:.25rem}.plugins-list-items-link:hover{--tw-translate-y:-1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.plugins-list-items-link-svg{height:1.5rem;width:1.5rem;fill:#0ea5e9}.plugins-list-items-link-svg.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(98 117 148/var(--tw-bg-opacity))}:is(.dark .plugins-list-items-link-svg){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.plugins-list-items-pro{margin-left:.25rem;margin-right:.25rem;--tw-translate-y:-0.125rem}.plugins-list-items-pro,.plugins-list-items-pro:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.plugins-list-items-pro:hover{--tw-translate-y:-1px}.plugins-list-items-pro-svg{height:1.5rem;width:1.5rem}:is(.dark .plugins-list-items-pro-svg){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.first-letter\:absolute:first-letter{position:absolute!important}.first-letter\:w-full:first-letter{width:100%!important}.placeholder\:text-gray-500::-moz-placeholder{--tw-text-opacity:1!important;color:rgb(173 181 189/var(--tw-text-opacity))!important}.placeholder\:text-gray-500::placeholder{--tw-text-opacity:1!important;color:rgb(173 181 189/var(--tw-text-opacity))!important}.before\:float-left:before{content:var(--tw-content)!important;float:left!important}.before\:pr-2:before{content:var(--tw-content)!important;padding-right:.5rem!important}.before\:text-white:before{content:var(--tw-content)!important;--tw-text-opacity:1!important;color:rgb(255 255 255/var(--tw-text-opacity))!important}.before\:content-\[\'\/\'\]:before{--tw-content:"/"!important;content:var(--tw-content)!important}.after\:absolute:after{content:var(--tw-content)!important;position:absolute!important}.after\:top-px:after{content:var(--tw-content)!important;top:1px!important}.after\:float-right:after{content:var(--tw-content)!important;float:right!important}.after\:h-4:after{content:var(--tw-content)!important;height:1rem!important}.after\:w-4:after{content:var(--tw-content)!important;width:1rem!important}.after\:translate-x-px:after{content:var(--tw-content)!important;--tw-translate-x:1px!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.after\:rounded-circle:after{content:var(--tw-content)!important;border-radius:50%!important}.after\:bg-white:after{content:var(--tw-content)!important;--tw-bg-opacity:1!important;background-color:rgb(255 255 255/var(--tw-bg-opacity))!important}.after\:pl-2:after{content:var(--tw-content)!important;padding-left:.5rem!important}.after\:text-gray-600:after{content:var(--tw-content)!important;--tw-text-opacity:1!important;color:rgb(108 117 125/var(--tw-text-opacity))!important}.after\:shadow-2xl:after{content:var(--tw-content)!important;--tw-shadow:0 .3125rem .625rem 0 #0000001f!important;--tw-shadow-colored:0 .3125rem .625rem 0 var(--tw-shadow-color)!important;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)!important}.after\:duration-300:after{content:var(--tw-content)!important;transition-duration:.3s!important}.after\:content-\[\'\'\]:after{--tw-content:""!important;content:var(--tw-content)!important}.after\:content-\[\'\/\'\]:after{--tw-content:"/"!important;content:var(--tw-content)!important}.checked\:z-0:checked{z-index:0!important}.checked\:border-primary:checked{--tw-border-opacity:1!important;border-color:rgb(11 85 119/var(--tw-border-opacity))!important}.checked\:bg-primary:checked{--tw-bg-opacity:1!important;background-color:rgb(11 85 119/var(--tw-bg-opacity))!important}.checked\:bg-none:checked{background-image:none!important}.checked\:bg-right:checked{background-position:100%!important}.checked\:after\:translate-x-5:checked:after{--tw-translate-x:1.25rem!important}.checked\:after\:translate-x-5:checked:after,.checked\:after\:translate-x-5\.3:checked:after{content:var(--tw-content)!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.checked\:after\:translate-x-5\.3:checked:after{--tw-translate-x:1.3rem!important}.valid\:\!border-red-500:valid{--tw-border-opacity:1!important;border-color:rgb(245 57 57/var(--tw-border-opacity))!important}.hover\:-translate-y-0:hover{--tw-translate-y:-0px!important}.hover\:-translate-y-0:hover,.hover\:-translate-y-0\.4:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.hover\:-translate-y-0\.4:hover{--tw-translate-y:-0.1rem!important}.hover\:-translate-y-0\.5:hover{--tw-translate-y:-0.125rem!important}.hover\:-translate-y-0\.5:hover,.hover\:-translate-y-px:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.hover\:-translate-y-px:hover{--tw-translate-y:-1px!important}.hover\:scale-102:hover{--tw-scale-x:1.02!important;--tw-scale-y:1.02!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.hover\:rounded-lg:hover{border-radius:.5rem!important}.hover\:bg-emerald-500:hover{--tw-bg-opacity:1!important;background-color:rgb(45 206 137/var(--tw-bg-opacity))!important}.hover\:bg-emerald-500\/80:hover{background-color:#2dce89cc!important}.hover\:bg-gray-100:hover{--tw-bg-opacity:1!important;background-color:rgb(235 239 244/var(--tw-bg-opacity))!important}.hover\:bg-gray-100\/10:hover{background-color:#ebeff41a!important}.hover\:bg-gray-300:hover{--tw-bg-opacity:1!important;background-color:rgb(210 214 218/var(--tw-bg-opacity))!important}.hover\:bg-gray-500:hover{--tw-bg-opacity:1!important;background-color:rgb(173 181 189/var(--tw-bg-opacity))!important}.hover\:bg-gray-500\/80:hover{background-color:#adb5bdcc!important}.hover\:bg-gray-600:hover{--tw-bg-opacity:1!important;background-color:rgb(108 117 125/var(--tw-bg-opacity))!important}.hover\:bg-gray-600\/80:hover{background-color:#6c757dcc!important}.hover\:bg-green-500:hover{--tw-bg-opacity:1!important;background-color:rgb(34 197 94/var(--tw-bg-opacity))!important}.hover\:bg-green-500\/80:hover{background-color:#22c55ecc!important}.hover\:bg-orange-500:hover{--tw-bg-opacity:1!important;background-color:rgb(251 99 64/var(--tw-bg-opacity))!important}.hover\:bg-orange-500\/80:hover{background-color:#fb6340cc!important}.hover\:bg-primary\/30:hover{background-color:#0b55774d!important}.hover\:bg-primary\/5:hover{background-color:#0b55770d!important}.hover\:bg-primary\/80:hover{background-color:#0b5577cc!important}.hover\:bg-red-500:hover{--tw-bg-opacity:1!important;background-color:rgb(245 57 57/var(--tw-bg-opacity))!important}.hover\:bg-red-500\/80:hover{background-color:#f53939cc!important}.hover\:bg-sky-500:hover{--tw-bg-opacity:1!important;background-color:rgb(14 165 233/var(--tw-bg-opacity))!important}.hover\:bg-sky-500\/80:hover{background-color:#0ea5e9cc!important}.hover\:bg-yellow-400:hover{--tw-bg-opacity:1!important;background-color:rgb(251 207 51/var(--tw-bg-opacity))!important}.hover\:bg-yellow-400\/80:hover{background-color:#fbcf33cc!important}.hover\:bg-yellow-500:hover{--tw-bg-opacity:1!important;background-color:rgb(251 177 64/var(--tw-bg-opacity))!important}.hover\:bg-yellow-500\/80:hover{background-color:#fbb140cc!important}.hover\:italic:hover{font-style:italic!important}.hover\:no-underline:hover{text-decoration-line:none!important}.hover\:opacity-80:hover{opacity:.8!important}.hover\:shadow-md:hover{--tw-shadow:0 4px 6px #32325d1a,0 1px 3px #00000014!important;--tw-shadow-colored:0 4px 6px var(--tw-shadow-color),0 1px 3px var(--tw-shadow-color)!important;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)!important}.hover\:brightness-75:hover{--tw-brightness:brightness(.75)!important}.hover\:brightness-75:hover,.hover\:brightness-90:hover{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}.hover\:brightness-90:hover{--tw-brightness:brightness(.9)!important}.hover\:brightness-95:hover{--tw-brightness:brightness(.95)!important;filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}.focus\:\!border-red-500:focus{--tw-border-opacity:1!important;border-color:rgb(245 57 57/var(--tw-border-opacity))!important}.focus\:border-green-500:focus{--tw-border-opacity:1!important;border-color:rgb(34 197 94/var(--tw-border-opacity))!important}.focus\:border-primary:focus{--tw-border-opacity:1!important;border-color:rgb(11 85 119/var(--tw-border-opacity))!important}.focus\:bg-emerald-500:focus{--tw-bg-opacity:1!important;background-color:rgb(45 206 137/var(--tw-bg-opacity))!important}.focus\:bg-emerald-500\/80:focus{background-color:#2dce89cc!important}.focus\:bg-gray-500:focus{--tw-bg-opacity:1!important;background-color:rgb(173 181 189/var(--tw-bg-opacity))!important}.focus\:bg-gray-500\/80:focus{background-color:#adb5bdcc!important}.focus\:bg-gray-600:focus{--tw-bg-opacity:1!important;background-color:rgb(108 117 125/var(--tw-bg-opacity))!important}.focus\:bg-gray-600\/80:focus{background-color:#6c757dcc!important}.focus\:bg-green-500:focus{--tw-bg-opacity:1!important;background-color:rgb(34 197 94/var(--tw-bg-opacity))!important}.focus\:bg-green-500\/80:focus{background-color:#22c55ecc!important}.focus\:bg-orange-500:focus{--tw-bg-opacity:1!important;background-color:rgb(251 99 64/var(--tw-bg-opacity))!important}.focus\:bg-orange-500\/80:focus{background-color:#fb6340cc!important}.focus\:bg-primary\/80:focus{background-color:#0b5577cc!important}.focus\:bg-red-500:focus{--tw-bg-opacity:1!important;background-color:rgb(245 57 57/var(--tw-bg-opacity))!important}.focus\:bg-red-500\/80:focus{background-color:#f53939cc!important}.focus\:bg-sky-500:focus{--tw-bg-opacity:1!important;background-color:rgb(14 165 233/var(--tw-bg-opacity))!important}.focus\:bg-sky-500\/80:focus{background-color:#0ea5e9cc!important}.focus\:bg-yellow-400:focus{--tw-bg-opacity:1!important;background-color:rgb(251 207 51/var(--tw-bg-opacity))!important}.focus\:bg-yellow-400\/80:focus{background-color:#fbcf33cc!important}.focus\:bg-yellow-500:focus{--tw-bg-opacity:1!important;background-color:rgb(251 177 64/var(--tw-bg-opacity))!important}.focus\:bg-yellow-500\/80:focus{background-color:#fbb140cc!important}.focus\:outline:focus{outline-style:solid!important}.focus\:\!ring-red-500:focus{--tw-ring-opacity:1!important;--tw-ring-color:rgb(245 57 57/var(--tw-ring-opacity))!important}.focus\:valid\:\!border-red-500:valid:focus{--tw-border-opacity:1!important;border-color:rgb(245 57 57/var(--tw-border-opacity))!important}.focus\:valid\:border-green-500:valid:focus{--tw-border-opacity:1!important;border-color:rgb(34 197 94/var(--tw-border-opacity))!important}.focus\:valid\:\!ring-red-500:valid:focus{--tw-ring-opacity:1!important;--tw-ring-color:rgb(245 57 57/var(--tw-ring-opacity))!important}.focus\:invalid\:border-red-500:invalid:focus{--tw-border-opacity:1!important;border-color:rgb(245 57 57/var(--tw-border-opacity))!important}.focus\:file\:invalid\:border-red-500:invalid::file-selector-button:focus{--tw-border-opacity:1!important;border-color:rgb(245 57 57/var(--tw-border-opacity))!important}.active\:\!border-red-500:active{--tw-border-opacity:1!important;border-color:rgb(245 57 57/var(--tw-border-opacity))!important}.active\:opacity-85:active{opacity:.85!important}.active\:valid\:\!border-red-500:valid:active{--tw-border-opacity:1!important;border-color:rgb(245 57 57/var(--tw-border-opacity))!important}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed!important}.disabled\:border-gray-400:disabled{--tw-border-opacity:1!important;border-color:rgb(206 212 218/var(--tw-border-opacity))!important}.disabled\:border-gray-400\/0:disabled{border-color:#ced4da00!important}.disabled\:bg-gray-400:disabled{--tw-bg-opacity:1!important;background-color:rgb(206 212 218/var(--tw-bg-opacity))!important}.disabled\:text-gray-700:disabled{--tw-text-opacity:1!important;color:rgb(73 80 87/var(--tw-text-opacity))!important}.disabled\:opacity-75:disabled{opacity:.75!important}.disabled\:hover\:translate-y-0:hover:disabled{--tw-translate-y:0px!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.disabled\:hover\:border-gray-400\/0:hover:disabled{border-color:#ced4da00!important}.disabled\:hover\:bg-gray-400:hover:disabled{--tw-bg-opacity:1!important;background-color:rgb(206 212 218/var(--tw-bg-opacity))!important}.group:hover .group-hover\:z-10{z-index:10!important}.group:hover .group-hover\:opacity-100{opacity:1!important}:is(.dark .dark\:inline){display:inline!important}:is(.dark .dark\:hidden){display:none!important}:is(.dark .dark\:border-gray-200){--tw-border-opacity:1!important;border-color:rgb(233 236 239/var(--tw-border-opacity))!important}:is(.dark .dark\:border-gray-300){--tw-border-opacity:1!important;border-color:rgb(210 214 218/var(--tw-border-opacity))!important}:is(.dark .dark\:border-gray-700){--tw-border-opacity:1!important;border-color:rgb(73 80 87/var(--tw-border-opacity))!important}:is(.dark .dark\:border-slate-600){--tw-border-opacity:1!important;border-color:rgb(98 117 148/var(--tw-border-opacity))!important}:is(.dark .dark\:border-slate-800){--tw-border-opacity:1!important;border-color:rgb(58 65 111/var(--tw-border-opacity))!important}:is(.dark .dark\:bg-gray-400){--tw-bg-opacity:1!important;background-color:rgb(206 212 218/var(--tw-bg-opacity))!important}:is(.dark .dark\:bg-gray-800){--tw-bg-opacity:1!important;background-color:rgb(37 47 64/var(--tw-bg-opacity))!important}:is(.dark .dark\:bg-green-500\/90){background-color:#22c55ee6!important}:is(.dark .dark\:bg-primary){--tw-bg-opacity:1!important;background-color:rgb(11 85 119/var(--tw-bg-opacity))!important}:is(.dark .dark\:bg-primary\/50){background-color:#0b557780!important}:is(.dark .dark\:bg-red-500\/90){background-color:#f53939e6!important}:is(.dark .dark\:bg-slate-700){--tw-bg-opacity:1!important;background-color:rgb(52 71 103/var(--tw-bg-opacity))!important}:is(.dark .dark\:bg-slate-700\/50){background-color:#34476780!important}:is(.dark .dark\:bg-slate-800){--tw-bg-opacity:1!important;background-color:rgb(58 65 111/var(--tw-bg-opacity))!important}:is(.dark .dark\:bg-slate-850){--tw-bg-opacity:1!important;background-color:rgb(17 28 68/var(--tw-bg-opacity))!important}:is(.dark .dark\:bg-slate-900){--tw-bg-opacity:1!important;background-color:rgb(5 17 57/var(--tw-bg-opacity))!important}:is(.dark .dark\:bg-slate-900\/30){background-color:#0511394d!important}:is(.dark .dark\:bg-gradient-to-r){background-image:linear-gradient(to right,var(--tw-gradient-stops))!important}:is(.dark .dark\:from-transparent){--tw-gradient-from:#0000 var(--tw-gradient-from-position)!important;--tw-gradient-to:#0000 var(--tw-gradient-to-position)!important;--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)!important}:is(.dark .dark\:via-white){--tw-gradient-to:#fff0 var(--tw-gradient-to-position)!important;--tw-gradient-stops:var(--tw-gradient-from),#fff var(--tw-gradient-via-position),var(--tw-gradient-to)!important}:is(.dark .dark\:to-transparent){--tw-gradient-to:#0000 var(--tw-gradient-to-position)!important}:is(.dark .dark\:fill-blue-500){fill:#5e72e4!important}:is(.dark .dark\:fill-gray-300){fill:#d2d6da!important}:is(.dark .dark\:fill-gray-500){fill:#adb5bd!important}:is(.dark .dark\:fill-gray-600){fill:#6c757d!important}:is(.dark .dark\:stroke-amber-500){stroke:#f59e0b!important}:is(.dark .dark\:stroke-gray-300){stroke:#d2d6da!important}:is(.dark .dark\:stroke-gray-400){stroke:#ced4da!important}:is(.dark .dark\:stroke-gray-600){stroke:#6c757d!important}:is(.dark .dark\:stroke-red-500){stroke:#f53939!important}:is(.dark .dark\:stroke-white\/90){stroke:#ffffffe6!important}:is(.dark .dark\:text-gray-100){--tw-text-opacity:1!important;color:rgb(235 239 244/var(--tw-text-opacity))!important}:is(.dark .dark\:text-gray-200){--tw-text-opacity:1!important;color:rgb(233 236 239/var(--tw-text-opacity))!important}:is(.dark .dark\:text-gray-300){--tw-text-opacity:1!important;color:rgb(210 214 218/var(--tw-text-opacity))!important}:is(.dark .dark\:text-gray-400){--tw-text-opacity:1!important;color:rgb(206 212 218/var(--tw-text-opacity))!important}:is(.dark .dark\:text-gray-50){--tw-text-opacity:1!important;color:rgb(248 249 250/var(--tw-text-opacity))!important}:is(.dark .dark\:text-gray-500){--tw-text-opacity:1!important;color:rgb(173 181 189/var(--tw-text-opacity))!important}:is(.dark .dark\:text-white){--tw-text-opacity:1!important;color:rgb(255 255 255/var(--tw-text-opacity))!important}:is(.dark .dark\:text-white\/80){color:#fffc!important}:is(.dark .dark\:text-white\/90){color:#ffffffe6!important}:is(.dark .dark\:opacity-100){opacity:1!important}:is(.dark .dark\:opacity-80){opacity:.8!important}:is(.dark .dark\:opacity-90){opacity:.9!important}:is(.dark .dark\:shadow-dark-xl){--tw-shadow:0 2px 2px 0 #00000024,0 3px 1px -2px #0003,0 1px 5px 0 #0000001f!important;--tw-shadow-colored:0 2px 2px 0 var(--tw-shadow-color),0 3px 1px -2px var(--tw-shadow-color),0 1px 5px 0 var(--tw-shadow-color)!important}:is(.dark .dark\:shadow-dark-xl),:is(.dark .dark\:shadow-none){box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)!important}:is(.dark .dark\:shadow-none){--tw-shadow:0 0 #0000!important;--tw-shadow-colored:0 0 #0000!important}:is(.dark .dark\:brightness-110){--tw-brightness:brightness(1.1)!important}:is(.dark .dark\:brightness-110),:is(.dark .dark\:brightness-125){filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}:is(.dark .dark\:brightness-125){--tw-brightness:brightness(1.25)!important}:is(.dark .dark\:brightness-90){--tw-brightness:brightness(.9)!important}:is(.dark .dark\:brightness-90),:is(.dark .dark\:brightness-95){filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}:is(.dark .dark\:brightness-95){--tw-brightness:brightness(.95)!important}:is(.dark .dark\:brightness-\[0\.885\]){--tw-brightness:brightness(0.885)!important;filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}:is(.dark .dark\:placeholder\:text-gray-600)::-moz-placeholder{--tw-text-opacity:1!important;color:rgb(108 117 125/var(--tw-text-opacity))!important}:is(.dark .dark\:placeholder\:text-gray-600)::placeholder{--tw-text-opacity:1!important;color:rgb(108 117 125/var(--tw-text-opacity))!important}:is(.dark .dark\:after\:text-gray-300):after{content:var(--tw-content)!important;--tw-text-opacity:1!important;color:rgb(210 214 218/var(--tw-text-opacity))!important}:is(.dark .dark\:after\:text-gray-500):after{content:var(--tw-content)!important;--tw-text-opacity:1!important;color:rgb(173 181 189/var(--tw-text-opacity))!important}:is(.dark .dark\:checked\:border-primary:checked){--tw-border-opacity:1!important;border-color:rgb(11 85 119/var(--tw-border-opacity))!important}:is(.dark .dark\:checked\:bg-primary:checked){--tw-bg-opacity:1!important;background-color:rgb(11 85 119/var(--tw-bg-opacity))!important}:is(.dark .dark\:hover\:bg-primary\/20:hover){background-color:#0b557733!important}:is(.dark .dark\:hover\:bg-primary\/60:hover){background-color:#0b557799!important}:is(.dark .dark\:hover\:bg-slate-700\/50:hover){background-color:#34476780!important}:is(.dark .dark\:hover\:bg-slate-800:hover){--tw-bg-opacity:1!important;background-color:rgb(58 65 111/var(--tw-bg-opacity))!important}:is(.dark .dark\:hover\:brightness-100:hover){--tw-brightness:brightness(1)!important}:is(.dark .dark\:hover\:brightness-100:hover),:is(.dark .dark\:hover\:brightness-105:hover){filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}:is(.dark .dark\:hover\:brightness-105:hover){--tw-brightness:brightness(1.05)!important}:is(.dark .dark\:hover\:brightness-110:hover){--tw-brightness:brightness(1.1)!important}:is(.dark .dark\:hover\:brightness-110:hover),:is(.dark .dark\:hover\:brightness-90:hover){filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}:is(.dark .dark\:hover\:brightness-90:hover){--tw-brightness:brightness(.9)!important}:is(.dark .dark\:disabled\:border-gray-700\/0:disabled){border-color:#49505700!important}:is(.dark .dark\:disabled\:border-gray-800:disabled){--tw-border-opacity:1!important;border-color:rgb(37 47 64/var(--tw-border-opacity))!important}:is(.dark .dark\:disabled\:bg-gray-700:disabled){--tw-bg-opacity:1!important;background-color:rgb(73 80 87/var(--tw-bg-opacity))!important}:is(.dark .dark\:disabled\:bg-gray-800:disabled){--tw-bg-opacity:1!important;background-color:rgb(37 47 64/var(--tw-bg-opacity))!important}:is(.dark .dark\:disabled\:text-gray-300:disabled){--tw-text-opacity:1!important;color:rgb(210 214 218/var(--tw-text-opacity))!important}:is(.dark .dark\:disabled\:text-gray-400:disabled){--tw-text-opacity:1!important;color:rgb(206 212 218/var(--tw-text-opacity))!important}:is(.dark .dark\:disabled\:hover\:translate-y-0:hover:disabled){--tw-translate-y:0px!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}:is(.dark .dark\:disabled\:hover\:border-gray-700\/0:hover:disabled){border-color:#49505700!important}:is(.dark .dark\:disabled\:hover\:bg-gray-700:hover:disabled){--tw-bg-opacity:1!important;background-color:rgb(73 80 87/var(--tw-bg-opacity))!important}@media (min-width:576px){.sm\:right-24{right:6rem!important}.sm\:right-40{right:10rem!important}.sm\:right-6{right:1.5rem!important}.sm\:top-2{top:.5rem!important}.sm\:top-8{top:2rem!important}.sm\:top-\[4\.5rem\]{top:4.5rem!important}.sm\:col-span-4{grid-column:span 4/span 4!important}.sm\:col-span-6{grid-column:span 6/span 6!important}.sm\:col-start-5{grid-column-start:5!important}.sm\:mx-2{margin-left:.5rem!important;margin-right:.5rem!important}.sm\:mx-6{margin-left:1.5rem!important;margin-right:1.5rem!important}.sm\:my-0{margin-top:0!important;margin-bottom:0!important}.sm\:mb-2{margin-bottom:.5rem!important}.sm\:ml-1{margin-left:.25rem!important}.sm\:ml-4{margin-left:1rem!important}.sm\:mr-16{margin-right:4rem!important}.sm\:block{display:block!important}.sm\:inline{display:inline!important}.sm\:flex{display:flex!important}.sm\:h-10{height:2.5rem!important}.sm\:h-14{height:3.5rem!important}.sm\:h-7{height:1.75rem!important}.sm\:max-h-125{max-height:31.25rem!important}.sm\:w-36{width:9rem!important}.sm\:w-50{width:12.5rem!important}.sm\:w-7{width:1.75rem!important}.sm\:min-w-\[250px\]{min-width:250px!important}.sm\:min-w-\[500px\]{min-width:500px!important}.sm\:max-w-\[350px\]{max-width:350px!important}.sm\:translate-x-0{--tw-translate-x:0px!important}.sm\:scale-100,.sm\:translate-x-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.sm\:scale-100{--tw-scale-x:1!important;--tw-scale-y:1!important}.sm\:flex-row{flex-direction:row!important}.sm\:items-center{align-items:center!important}.sm\:justify-end{justify-content:flex-end!important}.sm\:justify-items-start{justify-items:start!important}.sm\:gap-4{gap:1rem!important}.sm\:p-3{padding:.75rem!important}.sm\:px-12{padding-left:3rem!important;padding-right:3rem!important}.sm\:px-4{padding-left:1rem!important;padding-right:1rem!important}.sm\:px-6{padding-left:1.5rem!important;padding-right:1.5rem!important}.sm\:pt-3{padding-top:.75rem!important}.sm\:text-left{text-align:left!important}.sm\:text-2xl{font-size:1.5rem!important;line-height:2rem!important}.sm\:text-4xl{font-size:2.25rem!important;line-height:2.5rem!important}.sm\:text-7xl{font-size:4.5rem!important;line-height:1!important}.sm\:text-base{font-size:1rem!important}.sm\:text-base,.sm\:text-sm{line-height:1.5rem!important}.sm\:text-sm{font-size:.875rem!important}}@media (min-width:768px){.md\:absolute{position:absolute!important}.md\:right-8{right:2rem!important}.md\:right-\[3\.75rem\]{right:3.75rem!important}.md\:top-\[40\%\]{top:40%!important}.md\:top-\[53\%\]{top:53%!important}.md\:col-span-4{grid-column:span 4/span 4!important}.md\:col-span-6{grid-column:span 6/span 6!important}.md\:col-span-7{grid-column:span 7/span 7!important}.md\:col-span-8{grid-column:span 8/span 8!important}.md\:mx-3{margin-left:.75rem!important;margin-right:.75rem!important}.md\:mx-4{margin-left:1rem!important;margin-right:1rem!important}.md\:my-3{margin-top:.75rem!important;margin-bottom:.75rem!important}.md\:mb-0{margin-bottom:0!important}.md\:mb-3{margin-bottom:.75rem!important}.md\:mr-3{margin-right:.75rem!important}.md\:mt-0{margin-top:0!important}.md\:mt-6{margin-top:1.5rem!important}.md\:hidden{display:none!important}.md\:h-16{height:4rem!important}.md\:max-h-\[90vh\]{max-height:90vh!important}.md\:min-h-50-screen{min-height:50vh!important}.md\:w-1\/2{width:50%!important}.md\:w-60{width:15rem!important}.md\:max-w-\[700px\]{max-width:700px!important}.md\:-translate-y-20{--tw-translate-y:-5rem!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.md\:flex-row{flex-direction:row!important}.md\:items-end{align-items:flex-end!important}.md\:gap-x-4{-moz-column-gap:1rem!important;column-gap:1rem!important}.md\:gap-x-6{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.md\:px-1{padding-left:.25rem!important;padding-right:.25rem!important}.md\:px-3{padding-left:.75rem!important;padding-right:.75rem!important}.md\:px-4{padding-left:1rem!important;padding-right:1rem!important}.md\:px-6{padding-left:1.5rem!important;padding-right:1.5rem!important}.md\:py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.md\:text-base{font-size:1rem!important;line-height:1.5rem!important}}@media (min-width:992px){.lg\:relative{position:relative!important}.lg\:bottom-2{bottom:.5rem!important}.lg\:left-48{left:12rem!important}.lg\:top-24{top:6rem!important}.lg\:order-1{order:1!important}.lg\:order-2{order:2!important}.lg\:col-span-1{grid-column:span 1/span 1!important}.lg\:col-span-4{grid-column:span 4/span 4!important}.lg\:col-span-6{grid-column:span 6/span 6!important}.lg\:col-span-8{grid-column:span 8/span 8!important}.lg\:mx-0{margin-left:0!important;margin-right:0!important}.lg\:mx-4{margin-left:1rem!important;margin-right:1rem!important}.lg\:mx-8{margin-left:2rem!important;margin-right:2rem!important}.lg\:my-3{margin-top:.75rem!important;margin-bottom:.75rem!important}.lg\:mt-0{margin-top:0!important}.lg\:mt-4{margin-top:1rem!important}.lg\:mt-8{margin-top:2rem!important}.lg\:block{display:block!important}.lg\:inline{display:inline!important}.lg\:flex{display:flex!important}.lg\:hidden{display:none!important}.lg\:h-24{height:6rem!important}.lg\:h-36{height:9rem!important}.lg\:h-9{height:2.25rem!important}.lg\:max-h-\[550px\]{max-height:550px!important}.lg\:w-36{width:9rem!important}.lg\:w-80{width:20rem!important}.lg\:w-9{width:2.25rem!important}.lg\:w-\[400px\]{width:400px!important}.lg\:max-w-\[700px\]{max-width:700px!important}.lg\:translate-x-0{--tw-translate-x:0px!important}.lg\:translate-x-0,.lg\:translate-y-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.lg\:translate-y-0{--tw-translate-y:0px!important}.lg\:flex-row{flex-direction:row!important}.lg\:flex-nowrap{flex-wrap:nowrap!important}.lg\:justify-start{justify-content:flex-start!important}.lg\:justify-end{justify-content:flex-end!important}.lg\:justify-between{justify-content:space-between!important}.lg\:gap-6{gap:1.5rem!important}.lg\:bg-gray-50{--tw-bg-opacity:1!important;background-color:rgb(248 249 250/var(--tw-bg-opacity))!important}.lg\:px-6{padding-left:1.5rem!important;padding-right:1.5rem!important}.lg\:pb-1{padding-bottom:.25rem!important}.lg\:pb-28{padding-bottom:7rem!important}.lg\:pt-6{padding-top:1.5rem!important}.lg\:text-left{text-align:left!important}.lg\:text-base{font-size:1rem!important}.lg\:text-base,.lg\:text-sm{line-height:1.5rem!important}.lg\:text-sm{font-size:.875rem!important}}@media (min-width:1200px){.xl\:left-0{left:0!important}.xl\:right-24{right:6rem!important}.xl\:right-6{right:1.5rem!important}.xl\:ml-6{margin-left:1.5rem!important}.xl\:ml-68{margin-left:17rem!important}.xl\:hidden{display:none!important}.xl\:h-44{height:11rem!important}.xl\:max-h-\[550px\]{max-height:550px!important}.xl\:w-1\/3{width:33.333333%!important}.xl\:w-44{width:11rem!important}.xl\:w-\[500px\]{width:500px!important}.xl\:max-w-\[1200px\]{max-width:1200px!important}.xl\:translate-x-0{--tw-translate-x:0px!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.xl\:p-1{padding:.25rem!important}.xl\:p-1\.5{padding:.375rem!important}.xl\:pl-75{padding-left:18.75rem!important}.xl\:text-base{font-size:1rem!important;line-height:1.5rem!important}}@media (min-width:1320px){.\32xl\:col-span-4{grid-column:span 4/span 4!important}.\32xl\:col-span-6{grid-column:span 6/span 6!important}.\32xl\:my-3{margin-top:.75rem!important;margin-bottom:.75rem!important}.\32xl\:max-w-\[1500px\]{max-width:1500px!important}.\32xl\:text-3xl{font-size:1.875rem!important;line-height:2.25rem!important}.\32xl\:text-5xl{font-size:3rem!important;line-height:1!important}.\32xl\:text-8xl{font-size:5rem!important;line-height:1!important}.\32xl\:text-lg{font-size:1.125rem!important;line-height:1.75rem!important}}@media (min-width:1920px){.\33xl\:col-span-3{grid-column:span 3/span 3!important}.\33xl\:col-span-4{grid-column:span 4/span 4!important}.\33xl\:col-span-5{grid-column:span 5/span 5!important}.\33xl\:inline{display:inline!important}.\33xl\:max-w-none{max-width:none!important}.\33xl\:translate-x-60{--tw-translate-x:15rem!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.\33xl\:text-6xl{font-size:3.75rem!important;line-height:1!important}.\33xl\:text-9xl{font-size:6rem!important;line-height:1!important}.\33xl\:text-xl{font-size:1.25rem!important;line-height:1.75rem!important}}@media (min-width:340px){.xs\:flex-row{flex-direction:row!important}.xs\:items-center{align-items:center!important}.xs\:justify-start{justify-content:flex-start!important}.xs\:pl-2{padding-left:.5rem!important}.xs\:text-base{font-size:1rem!important}.xs\:text-base,.xs\:text-sm{line-height:1.5rem!important}.xs\:text-sm{font-size:.875rem!important}}.\[\&\>\*\]\:bg-primary>*{--tw-bg-opacity:1!important;background-color:rgb(11 85 119/var(--tw-bg-opacity))!important} \ No newline at end of file +/*! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e9ecef}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Open Sans;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#ced4da}input::placeholder,textarea::placeholder{opacity:1;color:#ced4da}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#5e72e480;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.\!container{width:100%!important;margin-right:auto!important;margin-left:auto!important;padding-right:1.5rem!important;padding-left:1.5rem!important}.container{width:100%;margin-right:auto;margin-left:auto;padding-right:1.5rem;padding-left:1.5rem}@media (min-width:340px){.\!container{max-width:340px!important}.container{max-width:340px}}@media (min-width:576px){.\!container{max-width:576px!important}.container{max-width:576px}}@media (min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media (min-width:992px){.\!container{max-width:992px!important}.container{max-width:992px}}@media (min-width:1200px){.\!container{max-width:1200px!important}.container{max-width:1200px}}@media (min-width:1320px){.\!container{max-width:1320px!important}.container{max-width:1320px}}@media (min-width:1920px){.\!container{max-width:1920px!important}.container{max-width:1920px}}a{letter-spacing:-.025rem}hr{margin:1rem 0;border:0;opacity:.25}img{max-width:none}label{display:inline-block}p{line-height:1.625;font-weight:400;margin-bottom:1rem}small{font-size:.875em}svg{display:inline}table{border-collapse:inherit}h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;color:#344767}h1,h2,h3,h4{letter-spacing:-.05rem}h1,h2,h3{font-weight:700}h4,h5,h6{font-weight:600}h1{font-size:3rem;line-height:1.25}h2{font-size:2.25rem;line-height:1.3}h3{font-size:1.875rem}h3,h4{line-height:1.375}h4{font-size:1.5rem}h5{font-size:1.25rem;line-height:1.375}h6{font-size:1rem;line-height:1.625}.sr-only{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border-width:0!important}.pointer-events-none{pointer-events:none!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}.collapse{visibility:collapse!important}.static{position:static!important}.fixed{position:fixed!important}.absolute{position:absolute!important}.relative{position:relative!important}.inset-y-0{top:0!important;bottom:0!important}.-left-full{left:-100%!important}.-right-0{right:0!important}.-right-1{right:-.25rem!important}.bottom-0{bottom:0!important}.bottom-1{bottom:.25rem!important}.bottom-1\.5{bottom:.375rem!important}.bottom-2{bottom:.5rem!important}.bottom-24{bottom:6rem!important}.bottom-3{bottom:.75rem!important}.bottom-4{bottom:1rem!important}.bottom-6{bottom:1.5rem!important}.bottom-7{bottom:1.75rem!important}.bottom-8{bottom:2rem!important}.left-0{left:0!important}.left-1{left:.25rem!important}.left-16{left:4rem!important}.left-20{left:5rem!important}.left-24{left:6rem!important}.left-32{left:8rem!important}.left-4{left:1rem!important}.left-40{left:10rem!important}.left-48{left:12rem!important}.left-8{left:2rem!important}.left-auto{left:auto!important}.left-full{left:100%!important}.right-0{right:0!important}.right-12{right:3rem!important}.right-2{right:.5rem!important}.right-20{right:5rem!important}.right-4{right:1rem!important}.right-5{right:1.25rem!important}.right-6{right:1.5rem!important}.right-7{right:1.75rem!important}.right-8{right:2rem!important}.right-\[3\.25rem\]{right:3.25rem!important}.top-0{top:0!important}.top-1{top:.25rem!important}.top-1\.5{top:.375rem!important}.top-1\/2{top:50%!important}.top-10{top:2.5rem!important}.top-12{top:3rem!important}.top-14{top:3.5rem!important}.top-16{top:4rem!important}.top-2{top:.5rem!important}.top-24{top:6rem!important}.top-3{top:.75rem!important}.top-36{top:9rem!important}.top-4{top:1rem!important}.top-6{top:1.5rem!important}.top-7{top:1.75rem!important}.top-8{top:2rem!important}.top-\[38\%\]{top:38%!important}.top-\[4\.5rem\]{top:4.5rem!important}.top-\[52\%\]{top:52%!important}.top-\[55\%\]{top:55%!important}.top-\[8\.2rem\]{top:8.2rem!important}.-z-10{z-index:-10!important}.z-0{z-index:0!important}.z-10{z-index:10!important}.z-100{z-index:100!important}.z-110{z-index:110!important}.z-20{z-index:20!important}.z-990{z-index:990!important}.z-\[10000\]{z-index:10000!important}.z-\[1000\]{z-index:1000!important}.z-\[1001\]{z-index:1001!important}.z-\[20\]{z-index:20!important}.z-sticky{z-index:1020!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.col-span-1{grid-column:span 1/span 1!important}.col-span-10{grid-column:span 10/span 10!important}.col-span-12{grid-column:span 12/span 12!important}.col-span-2{grid-column:span 2/span 2!important}.col-span-3{grid-column:span 3/span 3!important}.col-span-4{grid-column:span 4/span 4!important}.col-span-5{grid-column:span 5/span 5!important}.col-span-6{grid-column:span 6/span 6!important}.col-span-9{grid-column:span 9/span 9!important}.float-right{float:right!important}.float-left{float:left!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.mx-0{margin-left:0!important;margin-right:0!important}.mx-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-1\.5{margin-left:.375rem!important;margin-right:.375rem!important}.mx-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-2\.5{margin-left:.625rem!important;margin-right:.625rem!important}.mx-4{margin-left:1rem!important;margin-right:1rem!important}.mx-auto{margin-left:auto!important;margin-right:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:.75rem!important;margin-bottom:.75rem!important}.my-4{margin-top:1rem!important;margin-bottom:1rem!important}.mb-0{margin-bottom:0!important}.mb-0\.5{margin-bottom:.125rem!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-2\.5{margin-bottom:.625rem!important}.mb-3{margin-bottom:.75rem!important}.mb-4{margin-bottom:1rem!important}.mb-6{margin-bottom:1.5rem!important}.mb-7{margin-bottom:1.75rem!important}.mb-8{margin-bottom:2rem!important}.ml-0{margin-left:0!important}.ml-1{margin-left:.25rem!important}.ml-12{margin-left:3rem!important}.ml-2{margin-left:.5rem!important}.ml-3{margin-left:.75rem!important}.ml-4{margin-left:1rem!important}.mr-1{margin-right:.25rem!important}.mr-12{margin-right:3rem!important}.mr-2{margin-right:.5rem!important}.mr-3{margin-right:.75rem!important}.mr-4{margin-right:1rem!important}.mt-0{margin-top:0!important}.mt-0\.5{margin-top:.125rem!important}.mt-1{margin-top:.25rem!important}.mt-10{margin-top:2.5rem!important}.mt-12{margin-top:3rem!important}.mt-16{margin-top:4rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:.75rem!important}.mt-4{margin-top:1rem!important}.mt-5{margin-top:1.25rem!important}.mt-6{margin-top:1.5rem!important}.mt-8{margin-top:2rem!important}.mt-\[15vh\]{margin-top:15vh!important}.mt-\[4\.5rem\]{margin-top:4.5rem!important}.block{display:block!important}.inline-block{display:inline-block!important}.inline{display:inline!important}.flex{display:flex!important}.table{display:table!important}.grid{display:grid!important}.list-item{display:list-item!important}.\!hidden,.hidden{display:none!important}.h-1{height:.25rem!important}.h-12{height:3rem!important}.h-14{height:3.5rem!important}.h-3{height:.75rem!important}.h-32{height:8rem!important}.h-4{height:1rem!important}.h-4\.5{height:1.125rem!important}.h-40{height:10rem!important}.h-5{height:1.25rem!important}.h-5\.5{height:1.375rem!important}.h-6{height:1.5rem!important}.h-7{height:1.75rem!important}.h-8{height:2rem!important}.h-96{height:24rem!important}.h-\[2\.5rem\]{height:2.5rem!important}.h-\[250px\]{height:250px!important}.h-\[3\.5rem\]{height:3.5rem!important}.h-\[4rem\]{height:4rem!important}.h-\[90vh\]{height:90vh!important}.h-fit{height:-moz-fit-content!important;height:fit-content!important}.h-full{height:100%!important}.h-px{height:1px!important}.h-screen{height:100vh!important}.max-h-100{max-height:25rem!important}.max-h-30{max-height:7.5rem!important}.max-h-\[200px\]{max-height:200px!important}.max-h-\[250px\]{max-height:250px!important}.max-h-\[350px\]{max-height:350px!important}.max-h-\[400px\]{max-height:400px!important}.max-h-\[70vh\]{max-height:70vh!important}.max-h-\[90vh\]{max-height:90vh!important}.max-h-\[95vh\]{max-height:95vh!important}.max-h-screen{max-height:100vh!important}.min-h-20{min-height:5rem!important}.min-h-52{min-height:13rem!important}.min-h-6{min-height:1.5rem!important}.min-h-\[100px\]{min-height:100px!important}.min-h-\[200px\]{min-height:200px!important}.min-h-\[350px\]{min-height:350px!important}.min-h-\[400px\]{min-height:400px!important}.min-h-\[75px\]{min-height:75px!important}.min-h-\[85vh\]{min-height:85vh!important}.min-h-screen{min-height:100vh!important}.w-1{width:.25rem!important}.w-10{width:2.5rem!important}.w-11\/12{width:91.666667%!important}.w-28{width:7rem!important}.w-3{width:.75rem!important}.w-32{width:8rem!important}.w-4{width:1rem!important}.w-4\.5{width:1.125rem!important}.w-40{width:10rem!important}.w-48{width:12rem!important}.w-5{width:1.25rem!important}.w-5\.5{width:1.375rem!important}.w-50{width:12.5rem!important}.w-6{width:1.5rem!important}.w-7{width:1.75rem!important}.w-8{width:2rem!important}.w-80{width:20rem!important}.w-90{width:22.5rem!important}.w-\[2\.5rem\]{width:2.5rem!important}.w-\[50vw\]{width:50vw!important}.w-auto{width:auto!important}.w-fit{width:-moz-fit-content!important;width:fit-content!important}.w-full{width:100%!important}.w-screen{width:100vw!important}.min-w-0{min-width:0!important}.min-w-4{min-width:1rem!important}.min-w-\[1150px\]{min-width:1150px!important}.min-w-\[1300px\]{min-width:1300px!important}.min-w-\[200px\]{min-width:200px!important}.min-w-\[300px\]{min-width:300px!important}.min-w-\[600px\]{min-width:600px!important}.min-w-\[800px\]{min-width:800px!important}.min-w-\[900px\]{min-width:900px!important}.min-w-fit{min-width:-moz-fit-content!important;min-width:fit-content!important}.max-w-40{max-width:10rem!important}.max-w-60{max-width:15rem!important}.max-w-64{max-width:16rem!important}.max-w-\[1000px\]{max-width:1000px!important}.max-w-\[150px\]{max-width:150px!important}.max-w-\[1920px\]{max-width:1920px!important}.max-w-\[300px\]{max-width:300px!important}.max-w-\[400px\]{max-width:400px!important}.max-w-\[450px\]{max-width:450px!important}.max-w-\[550px\]{max-width:550px!important}.max-w-\[650px\]{max-width:650px!important}.max-w-\[700px\]{max-width:700px!important}.max-w-full{max-width:100%!important}.max-w-none{max-width:none!important}.max-w-screen-lg{max-width:992px!important}.flex-auto{flex:1 1 auto!important}.grow{flex-grow:1!important}.basis-full{flex-basis:100%!important}.border-collapse{border-collapse:collapse!important}.-translate-x-1{--tw-translate-x:-0.25rem!important}.-translate-x-1,.-translate-x-1\.5{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.-translate-x-1\.5{--tw-translate-x:-0.375rem!important}.-translate-x-36{--tw-translate-x:-9rem!important}.-translate-x-36,.-translate-x-40{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.-translate-x-40{--tw-translate-x:-10rem!important}.-translate-x-48{--tw-translate-x:-12rem!important}.-translate-x-48,.-translate-x-52{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.-translate-x-52{--tw-translate-x:-13rem!important}.-translate-x-56{--tw-translate-x:-14rem!important}.-translate-x-56,.-translate-x-60{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.-translate-x-60{--tw-translate-x:-15rem!important}.-translate-x-64{--tw-translate-x:-16rem!important}.-translate-x-64,.-translate-x-72{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.-translate-x-72{--tw-translate-x:-18rem!important}.-translate-x-full{--tw-translate-x:-100%!important}.-translate-x-full,.-translate-y-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.-translate-y-0{--tw-translate-y:-0px!important}.-translate-y-0\.4{--tw-translate-y:-0.1rem!important}.-translate-y-0\.4,.-translate-y-0\.5{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.-translate-y-0\.5{--tw-translate-y:-0.125rem!important}.-translate-y-1{--tw-translate-y:-0.25rem!important}.-translate-y-1,.-translate-y-12{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.-translate-y-12{--tw-translate-y:-3rem!important}.-translate-y-16{--tw-translate-y:-4rem!important}.-translate-y-16,.-translate-y-2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.-translate-y-2{--tw-translate-y:-0.5rem!important}.-translate-y-20{--tw-translate-y:-5rem!important}.-translate-y-20,.-translate-y-24{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.-translate-y-24{--tw-translate-y:-6rem!important}.-translate-y-28{--tw-translate-y:-7rem!important}.-translate-y-28,.-translate-y-36{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.-translate-y-36{--tw-translate-y:-9rem!important}.-translate-y-4{--tw-translate-y:-1rem!important}.-translate-y-4,.-translate-y-6{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.-translate-y-6{--tw-translate-y:-1.5rem!important}.-translate-y-8{--tw-translate-y:-2rem!important}.-translate-y-8,.translate-x-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.translate-x-0{--tw-translate-x:0px!important}.translate-x-0\.5{--tw-translate-x:0.125rem!important}.translate-x-0\.5,.translate-x-1{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.translate-x-1{--tw-translate-x:0.25rem!important}.translate-x-16{--tw-translate-x:4rem!important}.translate-x-16,.translate-x-2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.translate-x-2{--tw-translate-x:0.5rem!important}.translate-x-24{--tw-translate-x:6rem!important}.translate-x-24,.translate-x-3{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.translate-x-3{--tw-translate-x:0.75rem!important}.translate-x-32{--tw-translate-x:8rem!important}.translate-x-32,.translate-x-4{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.translate-x-4{--tw-translate-x:1rem!important}.translate-x-40{--tw-translate-x:10rem!important}.translate-x-40,.translate-x-48{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.translate-x-48{--tw-translate-x:12rem!important}.translate-x-52{--tw-translate-x:13rem!important}.translate-x-52,.translate-x-56{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.translate-x-56{--tw-translate-x:14rem!important}.translate-x-60{--tw-translate-x:15rem!important}.translate-x-60,.translate-x-90{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.translate-x-90{--tw-translate-x:22.5rem!important}.translate-x-\[3rem\]{--tw-translate-x:3rem!important}.translate-x-\[3rem\],.translate-y-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.translate-y-0{--tw-translate-y:0px!important}.translate-y-0\.5{--tw-translate-y:0.125rem!important}.translate-y-0\.5,.translate-y-1{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.translate-y-1{--tw-translate-y:0.25rem!important}.translate-y-16{--tw-translate-y:4rem!important}.translate-y-16,.translate-y-2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.translate-y-2{--tw-translate-y:0.5rem!important}.-rotate-12{--tw-rotate:-12deg!important}.-rotate-12,.rotate-12{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.rotate-12{--tw-rotate:12deg!important}.rotate-180{--tw-rotate:180deg!important}.rotate-180,.rotate-90{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.rotate-90{--tw-rotate:90deg!important}.scale-105{--tw-scale-x:1.05!important;--tw-scale-y:1.05!important}.scale-105,.scale-110{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.scale-110{--tw-scale-x:1.1!important;--tw-scale-y:1.1!important}.scale-50{--tw-scale-x:.5!important;--tw-scale-y:.5!important}.scale-50,.scale-75{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.scale-75{--tw-scale-x:.75!important;--tw-scale-y:.75!important}.scale-90{--tw-scale-x:.9!important;--tw-scale-y:.9!important}.scale-90,.scale-\[0\.6\]{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.scale-\[0\.6\]{--tw-scale-x:0.6!important;--tw-scale-y:0.6!important}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.cursor-not-allowed{cursor:not-allowed!important}.cursor-pointer{cursor:pointer!important}.cursor-text{cursor:text!important}.select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.resize{resize:both!important}.list-none{list-style-type:none!important}.appearance-none{-webkit-appearance:none!important;-moz-appearance:none!important;appearance:none!important}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))!important}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))!important}.flex-row{flex-direction:row!important}.flex-col{flex-direction:column!important}.flex-wrap{flex-wrap:wrap!important}.items-start{align-items:flex-start!important}.items-end{align-items:flex-end!important}.items-center{align-items:center!important}.justify-start{justify-content:flex-start!important}.justify-end{justify-content:flex-end!important}.justify-center{justify-content:center!important}.justify-between{justify-content:space-between!important}.justify-items-center{justify-items:center!important}.gap-2{gap:.5rem!important}.gap-3{gap:.75rem!important}.gap-4{gap:1rem!important}.gap-8{gap:2rem!important}.gap-x-4{-moz-column-gap:1rem!important;column-gap:1rem!important}.gap-y-2{row-gap:.5rem!important}.gap-y-3{row-gap:.75rem!important}.gap-y-4{row-gap:1rem!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-x-auto{overflow-x:auto!important}.overflow-y-auto{overflow-y:auto!important}.overflow-x-hidden{overflow-x:hidden!important}.truncate{overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}.whitespace-normal{white-space:normal!important}.whitespace-nowrap{white-space:nowrap!important}.break-words{overflow-wrap:break-word!important}.break-all{word-break:break-all!important}.rounded,.rounded-1{border-radius:.25rem!important}.rounded-1\.4{border-radius:.35rem!important}.rounded-10{border-radius:2.5rem!important}.rounded-2xl{border-radius:1rem!important}.rounded-circle{border-radius:50%!important}.rounded-full{border-radius:9999px!important}.rounded-lg{border-radius:.5rem!important}.rounded-none{border-radius:0!important}.rounded-xl{border-radius:.75rem!important}.rounded-b{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-b-lg{border-bottom-left-radius:.5rem!important}.rounded-b-lg,.rounded-r-lg{border-bottom-right-radius:.5rem!important}.rounded-r-lg{border-top-right-radius:.5rem!important}.rounded-t{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-t-2xl{border-top-left-radius:1rem!important;border-top-right-radius:1rem!important}.rounded-t-lg{border-top-left-radius:.5rem!important;border-top-right-radius:.5rem!important}.border{border-width:1px!important}.border-0{border-width:0!important}.border-2{border-width:2px!important}.border-b{border-bottom-width:1px!important}.border-b-0{border-bottom-width:0!important}.border-l{border-left-width:1px!important}.border-r{border-right-width:1px!important}.border-t{border-top-width:1px!important}.border-solid{border-style:solid!important}.border-dashed{border-style:dashed!important}.border-gray-100{--tw-border-opacity:1!important;border-color:rgb(235 239 244/var(--tw-border-opacity))!important}.border-gray-100\/50{border-color:#ebeff480!important}.border-gray-200{--tw-border-opacity:1!important;border-color:rgb(233 236 239/var(--tw-border-opacity))!important}.border-gray-300{--tw-border-opacity:1!important;border-color:rgb(210 214 218/var(--tw-border-opacity))!important}.border-gray-400{--tw-border-opacity:1!important;border-color:rgb(206 212 218/var(--tw-border-opacity))!important}.border-gray-700{--tw-border-opacity:1!important;border-color:rgb(73 80 87/var(--tw-border-opacity))!important}.border-primary{--tw-border-opacity:1!important;border-color:rgb(11 85 119/var(--tw-border-opacity))!important}.bg-blue-500{--tw-bg-opacity:1!important;background-color:rgb(94 114 228/var(--tw-bg-opacity))!important}.bg-emerald-500{--tw-bg-opacity:1!important;background-color:rgb(45 206 137/var(--tw-bg-opacity))!important}.bg-emerald-500\/80{background-color:#2dce89cc!important}.bg-gray-100{background-color:rgb(235 239 244/var(--tw-bg-opacity))!important}.bg-gray-100,.bg-gray-200{--tw-bg-opacity:1!important}.bg-gray-200{background-color:rgb(233 236 239/var(--tw-bg-opacity))!important}.bg-gray-300{background-color:rgb(210 214 218/var(--tw-bg-opacity))!important}.bg-gray-300,.bg-gray-50{--tw-bg-opacity:1!important}.bg-gray-50{background-color:rgb(248 249 250/var(--tw-bg-opacity))!important}.bg-gray-50\/10{background-color:#f8f9fa1a!important}.bg-gray-500{--tw-bg-opacity:1!important;background-color:rgb(173 181 189/var(--tw-bg-opacity))!important}.bg-gray-500\/80{background-color:#adb5bdcc!important}.bg-gray-600{--tw-bg-opacity:1!important;background-color:rgb(108 117 125/var(--tw-bg-opacity))!important}.bg-gray-600\/50{background-color:#6c757d80!important}.bg-gray-600\/80{background-color:#6c757dcc!important}.bg-green-500{--tw-bg-opacity:1!important;background-color:rgb(34 197 94/var(--tw-bg-opacity))!important}.bg-green-500\/80{background-color:#22c55ecc!important}.bg-orange-500{--tw-bg-opacity:1!important;background-color:rgb(251 99 64/var(--tw-bg-opacity))!important}.bg-orange-500\/80{background-color:#fb6340cc!important}.bg-primary{--tw-bg-opacity:1!important;background-color:rgb(11 85 119/var(--tw-bg-opacity))!important}.bg-primary\/10{background-color:#0b55771a!important}.bg-red-500{--tw-bg-opacity:1!important;background-color:rgb(245 57 57/var(--tw-bg-opacity))!important}.bg-red-500\/80{background-color:#f53939cc!important}.bg-secondary{background-color:rgb(46 172 104/var(--tw-bg-opacity))!important}.bg-secondary,.bg-sky-500{--tw-bg-opacity:1!important}.bg-sky-500{background-color:rgb(14 165 233/var(--tw-bg-opacity))!important}.bg-sky-500\/80{background-color:#0ea5e9cc!important}.bg-slate-800\/10{background-color:#3a416f1a!important}.bg-transparent{background-color:initial!important}.bg-white{background-color:rgb(255 255 255/var(--tw-bg-opacity))!important}.bg-white,.bg-yellow-400{--tw-bg-opacity:1!important}.bg-yellow-400{background-color:rgb(251 207 51/var(--tw-bg-opacity))!important}.bg-yellow-400\/80{background-color:#fbcf33cc!important}.bg-yellow-500{--tw-bg-opacity:1!important;background-color:rgb(251 177 64/var(--tw-bg-opacity))!important}.bg-yellow-500\/80{background-color:#fbb140cc!important}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))!important}.bg-gradient-to-tl{background-image:linear-gradient(to top left,var(--tw-gradient-stops))!important}.bg-none{background-image:none!important}.from-\[\#075577\]{--tw-gradient-from:#075577 var(--tw-gradient-from-position)!important;--tw-gradient-to:#07557700 var(--tw-gradient-to-position)!important;--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)!important}.from-\[\#0b5577\]{--tw-gradient-from:#0b5577 var(--tw-gradient-from-position)!important;--tw-gradient-to:#0b557700 var(--tw-gradient-to-position)!important;--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)!important}.from-transparent{--tw-gradient-from:#0000 var(--tw-gradient-from-position)!important;--tw-gradient-to:#0000 var(--tw-gradient-to-position)!important;--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)!important}.via-black\/40{--tw-gradient-to:#0000 var(--tw-gradient-to-position)!important;--tw-gradient-stops:var(--tw-gradient-from),#0006 var(--tw-gradient-via-position),var(--tw-gradient-to)!important}.to-\[\#116D70\]{--tw-gradient-to:#116d70 var(--tw-gradient-to-position)!important}.to-transparent{--tw-gradient-to:#0000 var(--tw-gradient-to-position)!important}.bg-150{background-size:150%!important}.bg-contain{background-size:contain!important}.bg-clip-border{background-clip:initial!important}.bg-clip-padding{background-clip:padding-box!important}.bg-center{background-position:50%!important}.bg-left{background-position:0!important}.bg-x-25{background-position:25% 0!important}.bg-no-repeat{background-repeat:no-repeat!important}.fill-blue-500{fill:#5e72e4!important}.fill-gray-500{fill:#adb5bd!important}.fill-gray-600{fill:#6c757d!important}.fill-gray-700{fill:#495057!important}.fill-green-500{fill:#22c55e!important}.fill-primary{fill:#0b5577!important}.fill-red-500{fill:#f53939!important}.fill-sky-500{fill:#0ea5e9!important}.fill-slate-800{fill:#3a416f!important}.fill-white{fill:#fff!important}.fill-yellow-500{fill:#fbb140!important}.stroke-amber-500{stroke:#f59e0b!important}.stroke-blue-400{stroke:#60a5fa!important}.stroke-blue-500{stroke:#5e72e4!important}.stroke-emerald-600{stroke:#059669!important}.stroke-gray-100{stroke:#ebeff4!important}.stroke-gray-100\/50{stroke:#ebeff480!important}.stroke-gray-600{stroke:#6c757d!important}.stroke-gray-700{stroke:#495057!important}.stroke-gray-800{stroke:#252f40!important}.stroke-green-700{stroke:#15803d!important}.stroke-orange-500{stroke:#fb6340!important}.stroke-pink-600{stroke:#db2777!important}.stroke-red-500{stroke:#f53939!important}.stroke-sky-500{stroke:#0ea5e9!important}.stroke-stone-500{stroke:#78716c!important}.stroke-white{stroke:#fff!important}.stroke-yellow-400{stroke:#fbcf33!important}.stroke-yellow-500{stroke:#fbb140!important}.stroke-0{stroke-width:0!important}.object-cover{-o-object-fit:cover!important;object-fit:cover!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:.75rem!important}.p-4{padding:1rem!important}.px-0{padding-left:0!important;padding-right:0!important}.px-0\.5{padding-left:.125rem!important;padding-right:.125rem!important}.px-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-1\.5{padding-left:.375rem!important;padding-right:.375rem!important}.px-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-28{padding-left:7rem!important;padding-right:7rem!important}.px-3{padding-left:.75rem!important;padding-right:.75rem!important}.px-4{padding-left:1rem!important;padding-right:1rem!important}.px-6{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-8{padding-left:2rem!important;padding-right:2rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-0\.5{padding-top:.125rem!important;padding-bottom:.125rem!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-1\.5{padding-top:.375rem!important;padding-bottom:.375rem!important}.py-10{padding-top:2.5rem!important;padding-bottom:2.5rem!important}.py-12{padding-top:3rem!important;padding-bottom:3rem!important}.py-16{padding-top:4rem!important;padding-bottom:4rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-2\.5{padding-top:.625rem!important;padding-bottom:.625rem!important}.py-3{padding-top:.75rem!important;padding-bottom:.75rem!important}.py-4{padding-top:1rem!important;padding-bottom:1rem!important}.py-8{padding-top:2rem!important;padding-bottom:2rem!important}.pb-0{padding-bottom:0!important}.pb-10{padding-bottom:2.5rem!important}.pb-16{padding-bottom:4rem!important}.pb-2{padding-bottom:.5rem!important}.pb-24{padding-bottom:6rem!important}.pb-28{padding-bottom:7rem!important}.pb-4{padding-bottom:1rem!important}.pb-6{padding-bottom:1.5rem!important}.pb-8{padding-bottom:2rem!important}.pl-0{padding-left:0!important}.pl-2{padding-left:.5rem!important}.pl-3{padding-left:.75rem!important}.pl-6{padding-left:1.5rem!important}.pt-1{padding-top:.25rem!important}.pt-10{padding-top:2.5rem!important}.pt-2{padding-top:.5rem!important}.pt-20{padding-top:5rem!important}.pt-3{padding-top:.75rem!important}.pt-4{padding-top:1rem!important}.pt-6{padding-top:1.5rem!important}.pt-8{padding-top:2rem!important}.pt-9{padding-top:2.25rem!important}.text-left{text-align:left!important}.text-center{text-align:center!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.font-sans{font-family:Open Sans!important}.text-2xl{font-size:1.5rem!important;line-height:2rem!important}.text-3xl{font-size:1.875rem!important;line-height:2.25rem!important}.text-4xl{font-size:2.25rem!important;line-height:2.5rem!important}.text-5xl{font-size:3rem!important}.text-5xl,.text-6xl{line-height:1!important}.text-6xl{font-size:3.75rem!important}.text-7xl{font-size:4.5rem!important}.text-7xl,.text-9xl{line-height:1!important}.text-9xl{font-size:6rem!important}.text-\[0\.7rem\]{font-size:.7rem!important}.text-\[1\.1rem\]{font-size:1.1rem!important}.text-base{font-size:1rem!important;line-height:1.5rem!important}.text-lg{font-size:1.125rem!important;line-height:1.75rem!important}.text-sm{font-size:.875rem!important;line-height:1.5rem!important}.text-xl{font-size:1.25rem!important;line-height:1.75rem!important}.text-xs{font-size:.75rem!important;line-height:1rem!important}.font-bold{font-weight:700!important}.font-medium{font-weight:500!important}.font-normal{font-weight:400!important}.font-semibold{font-weight:600!important}.uppercase{text-transform:uppercase!important}.capitalize{text-transform:capitalize!important}.italic{font-style:italic!important}.ordinal{--tw-ordinal:ordinal!important;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)!important}.leading-5{line-height:1.25rem!important}.leading-5\.6{line-height:1.4rem!important}.leading-6{line-height:1.5rem!important}.leading-default{line-height:1.6!important}.leading-none{line-height:1!important}.leading-normal{line-height:1.5!important}.leading-tight{line-height:1.25!important}.tracking-\[0\.20rem\]{letter-spacing:.2rem!important}.tracking-normal{letter-spacing:0!important}.tracking-tight-rem{letter-spacing:-.025rem!important}.tracking-wide{letter-spacing:.025em!important}.tracking-wider{letter-spacing:.05em!important}.tracking-widest{letter-spacing:.1em!important}.text-blue-500{--tw-text-opacity:1!important;color:rgb(94 114 228/var(--tw-text-opacity))!important}.text-gray-100{--tw-text-opacity:1!important;color:rgb(235 239 244/var(--tw-text-opacity))!important}.text-gray-100\/50{color:#ebeff480!important}.text-gray-300{--tw-text-opacity:1!important;color:rgb(210 214 218/var(--tw-text-opacity))!important}.text-gray-50{--tw-text-opacity:1!important;color:rgb(248 249 250/var(--tw-text-opacity))!important}.text-gray-500{--tw-text-opacity:1!important;color:rgb(173 181 189/var(--tw-text-opacity))!important}.text-gray-600{--tw-text-opacity:1!important;color:rgb(108 117 125/var(--tw-text-opacity))!important}.text-gray-700{--tw-text-opacity:1!important;color:rgb(73 80 87/var(--tw-text-opacity))!important}.text-gray-700\/80{color:#495057cc!important}.text-gray-800{--tw-text-opacity:1!important;color:rgb(37 47 64/var(--tw-text-opacity))!important}.text-green-500{--tw-text-opacity:1!important;color:rgb(34 197 94/var(--tw-text-opacity))!important}.text-primary{color:rgb(11 85 119/var(--tw-text-opacity))!important}.text-primary,.text-red-500{--tw-text-opacity:1!important}.text-red-500{color:rgb(245 57 57/var(--tw-text-opacity))!important}.text-secondary{--tw-text-opacity:1!important;color:rgb(46 172 104/var(--tw-text-opacity))!important}.text-sky-500{--tw-text-opacity:1!important;color:rgb(14 165 233/var(--tw-text-opacity))!important}.text-slate-500{--tw-text-opacity:1!important;color:rgb(103 116 142/var(--tw-text-opacity))!important}.text-slate-700{color:rgb(52 71 103/var(--tw-text-opacity))!important}.text-slate-700,.text-white{--tw-text-opacity:1!important}.text-white{color:rgb(255 255 255/var(--tw-text-opacity))!important}.text-yellow-400{--tw-text-opacity:1!important;color:rgb(251 207 51/var(--tw-text-opacity))!important}.text-yellow-500{--tw-text-opacity:1!important;color:rgb(251 177 64/var(--tw-text-opacity))!important}.underline{text-decoration-line:underline!important}.antialiased{-webkit-font-smoothing:antialiased!important;-moz-osx-font-smoothing:grayscale!important}.opacity-0{opacity:0!important}.opacity-100{opacity:1!important}.opacity-50{opacity:.5!important}.opacity-60{opacity:.6!important}.shadow-3xl{--tw-shadow:0 8px 26px -4px #14141426,0 8px 9px -5px #1414140f!important;--tw-shadow-colored:0 8px 26px -4px var(--tw-shadow-color),0 8px 9px -5px var(--tw-shadow-color)!important}.shadow-3xl,.shadow-\[8px_8px_12px_rgb\(0\2c 0\2c 0\2c 0\.2\)\]{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)!important}.shadow-\[8px_8px_12px_rgb\(0\2c 0\2c 0\2c 0\.2\)\]{--tw-shadow:8px 8px 12px #0003!important;--tw-shadow-colored:8px 8px 12px var(--tw-shadow-color)!important}.shadow-md{--tw-shadow:0 4px 6px #32325d1a,0 1px 3px #00000014!important;--tw-shadow-colored:0 4px 6px var(--tw-shadow-color),0 1px 3px var(--tw-shadow-color)!important}.shadow-md,.shadow-none{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)!important}.shadow-none{--tw-shadow:0 0 #0000!important;--tw-shadow-colored:0 0 #0000!important}.shadow-sm{--tw-shadow:0 .25rem .375rem -.0625rem #1414141f,0 .125rem .25rem -.0625rem #14141412!important;--tw-shadow-colored:0 .25rem .375rem -.0625rem var(--tw-shadow-color),0 .125rem .25rem -.0625rem var(--tw-shadow-color)!important}.shadow-sm,.shadow-xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)!important}.shadow-xl{--tw-shadow:0 0 2rem 0 #8898aa26!important;--tw-shadow-colored:0 0 2rem 0 var(--tw-shadow-color)!important}.shadow-xs{--tw-shadow:0 7px 14px #32325d1a,0 3px 6px #00000014!important;--tw-shadow-colored:0 7px 14px var(--tw-shadow-color),0 3px 6px var(--tw-shadow-color)!important;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)!important}.outline-none{outline:2px solid #0000!important;outline-offset:2px!important}.outline{outline-style:solid!important}.outline-secondary{outline-color:#2eac68!important}.blur{--tw-blur:blur(8px)!important}.blur,.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter!important;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter!important;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter!important;transition-timing-function:ease!important;transition-duration:.15s!important}.transition-all{transition-property:all!important;transition-timing-function:ease!important;transition-duration:.15s!important}.transition-transform{transition-property:transform!important;transition-timing-function:ease!important;transition-duration:.15s!important}.transition-opacity{transition-property:opacity!important;transition-timing-function:ease!important;transition-duration:.15s!important}.delay-200{transition-delay:.2s!important}.duration-200{transition-duration:.2s!important}.duration-250{transition-duration:.25s!important}.duration-300{transition-duration:.3s!important}.duration-700{transition-duration:.7s!important}.ease-in{transition-timing-function:ease-in!important}.ease-in-out{transition-timing-function:ease-in-out!important}.flex-wrap-inherit{flex-wrap:inherit!important}@font-face{font-family:Open Sans;src:url(../webfonts/OpenSans.ttf)}*{font-family:Open Sans,sans-serif}.ace_editor,.ace_editor *{font-family:Monaco,Menlo,Ubuntu Mono,Droid Sans Mono,Consolas,monospace!important;font-weight:400!important;letter-spacing:0!important}.sr-only{display:none}.separator{margin:.75rem 0 .5rem;height:1px;background-color:initial;--tw-gradient-from:#0000 var(--tw-gradient-from-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to);--tw-gradient-stops:var(--tw-gradient-from),#0006 var(--tw-gradient-via-position),var(--tw-gradient-to);--tw-gradient-to:#0000 var(--tw-gradient-to-position)}.separator,:is(.dark .separator){background-image:linear-gradient(to right,var(--tw-gradient-stops))}:is(.dark .separator){--tw-gradient-from:#0000 var(--tw-gradient-from-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to);--tw-gradient-to:#fff0 var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),#fff var(--tw-gradient-via-position),var(--tw-gradient-to);--tw-gradient-to:#0000 var(--tw-gradient-to-position)}.close-btn{display:inline-block;cursor:pointer;border-radius:.5rem;border-width:1px;--tw-border-opacity:1;border-color:rgb(245 57 57/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));padding:.5rem 1rem;text-align:center;vertical-align:middle;font-weight:700;text-transform:uppercase;line-height:1.5;letter-spacing:-.025rem;--tw-text-opacity:1;color:rgb(245 57 57/var(--tw-text-opacity));--tw-shadow:0 7px 14px #32325d1a,0 3px 6px #00000014;--tw-shadow-colored:0 7px 14px var(--tw-shadow-color),0 3px 6px var(--tw-shadow-color);transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.close-btn,.close-btn:hover{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.close-btn:hover{--tw-translate-y:-1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-shadow:0 4px 6px #32325d1a,0 1px 3px #00000014;--tw-shadow-colored:0 4px 6px var(--tw-shadow-color),0 1px 3px var(--tw-shadow-color)}.close-btn:focus,.close-btn:hover{background-color:#fffc}.close-btn:active{opacity:.85}.close-btn:disabled{cursor:not-allowed;--tw-bg-opacity:1;--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity))}.close-btn:disabled,.close-btn:hover:disabled{border-color:#ced4da00;background-color:rgb(206 212 218/var(--tw-bg-opacity))}.close-btn:hover:disabled{--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-bg-opacity:1}:is(.dark .close-btn){--tw-bg-opacity:1;background-color:rgb(233 236 239/var(--tw-bg-opacity));--tw-brightness:brightness(.9)}:is(.dark .close-btn),:is(.dark .close-btn:hover){filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}:is(.dark .close-btn:hover){--tw-brightness:brightness(.75)}:is(.dark .close-btn:disabled){--tw-bg-opacity:1;--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}:is(.dark .close-btn:disabled),:is(.dark .close-btn:hover:disabled){border-color:#49505700;background-color:rgb(73 80 87/var(--tw-bg-opacity))}:is(.dark .close-btn:hover:disabled){--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-bg-opacity:1}@media (min-width:768px){.close-btn{padding:.625rem 1.25rem}}.valid-btn{display:inline-block;cursor:pointer;border-radius:.5rem;--tw-bg-opacity:1;background-color:rgb(34 197 94/var(--tw-bg-opacity));padding:.5rem 1rem;text-align:center;vertical-align:middle;font-weight:700;text-transform:uppercase;line-height:1.5;letter-spacing:.025em;--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity));--tw-shadow:0 7px 14px #32325d1a,0 3px 6px #00000014;--tw-shadow-colored:0 7px 14px var(--tw-shadow-color),0 3px 6px var(--tw-shadow-color);transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.valid-btn,.valid-btn:hover{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.valid-btn:hover{--tw-translate-y:-1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-shadow:0 4px 6px #32325d1a,0 1px 3px #00000014;--tw-shadow-colored:0 4px 6px var(--tw-shadow-color),0 1px 3px var(--tw-shadow-color)}.valid-btn:focus,.valid-btn:hover{background-color:#22c55ecc}.valid-btn:active{opacity:.85}.valid-btn:disabled{cursor:not-allowed;--tw-bg-opacity:1;--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity))}.valid-btn:disabled,.valid-btn:hover:disabled{border-color:#ced4da00;background-color:rgb(206 212 218/var(--tw-bg-opacity))}.valid-btn:hover:disabled{--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-bg-opacity:1}:is(.dark .valid-btn){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}:is(.dark .valid-btn:disabled){--tw-bg-opacity:1;--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}:is(.dark .valid-btn:disabled),:is(.dark .valid-btn:hover:disabled){border-color:#49505700;background-color:rgb(73 80 87/var(--tw-bg-opacity))}:is(.dark .valid-btn:hover:disabled){--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-bg-opacity:1}@media (min-width:768px){.valid-btn{padding:.625rem 1.25rem}}.delete-btn{display:inline-block;cursor:pointer;border-radius:.5rem;--tw-bg-opacity:1;background-color:rgb(245 57 57/var(--tw-bg-opacity));padding:.5rem 1rem;text-align:center;vertical-align:middle;font-weight:700;text-transform:uppercase;line-height:1.5;letter-spacing:.025em;--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity));--tw-shadow:0 7px 14px #32325d1a,0 3px 6px #00000014;--tw-shadow-colored:0 7px 14px var(--tw-shadow-color),0 3px 6px var(--tw-shadow-color);transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.delete-btn,.delete-btn:hover{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.delete-btn:hover{--tw-translate-y:-1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-shadow:0 4px 6px #32325d1a,0 1px 3px #00000014;--tw-shadow-colored:0 4px 6px var(--tw-shadow-color),0 1px 3px var(--tw-shadow-color)}.delete-btn:focus,.delete-btn:hover{background-color:#f53939cc}.delete-btn:active{opacity:.85}.delete-btn:disabled{cursor:not-allowed;--tw-bg-opacity:1;--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity))}.delete-btn:disabled,.delete-btn:hover:disabled{border-color:#ced4da00;background-color:rgb(206 212 218/var(--tw-bg-opacity))}.delete-btn:hover:disabled{--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-bg-opacity:1}:is(.dark .delete-btn){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}:is(.dark .delete-btn:disabled){border-color:#49505700;--tw-bg-opacity:1;background-color:rgb(73 80 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}:is(.dark .delete-btn:hover:disabled){--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-color:#49505700;--tw-bg-opacity:1;background-color:rgb(73 80 87/var(--tw-bg-opacity))}@media (min-width:768px){.delete-btn{padding:.625rem 1.25rem}}.edit-btn{display:inline-block;cursor:pointer;border-radius:.5rem;--tw-bg-opacity:1;background-color:rgb(251 177 64/var(--tw-bg-opacity));padding:.5rem 1rem;text-align:center;vertical-align:middle;font-weight:700;text-transform:uppercase;line-height:1.5;letter-spacing:.025em;--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity));--tw-shadow:0 7px 14px #32325d1a,0 3px 6px #00000014;--tw-shadow-colored:0 7px 14px var(--tw-shadow-color),0 3px 6px var(--tw-shadow-color);transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.edit-btn,.edit-btn:hover{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.edit-btn:hover{--tw-translate-y:-1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-shadow:0 4px 6px #32325d1a,0 1px 3px #00000014;--tw-shadow-colored:0 4px 6px var(--tw-shadow-color),0 1px 3px var(--tw-shadow-color)}.edit-btn:focus,.edit-btn:hover{background-color:#fbb140cc}.edit-btn:active{opacity:.85}.edit-btn:disabled{cursor:not-allowed;--tw-bg-opacity:1;--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity))}.edit-btn:disabled,.edit-btn:hover:disabled{border-color:#ced4da00;background-color:rgb(206 212 218/var(--tw-bg-opacity))}.edit-btn:hover:disabled{--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-bg-opacity:1}:is(.dark .edit-btn){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}:is(.dark .edit-btn:disabled){--tw-bg-opacity:1;--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}:is(.dark .edit-btn:disabled),:is(.dark .edit-btn:hover:disabled){border-color:#49505700;background-color:rgb(73 80 87/var(--tw-bg-opacity))}:is(.dark .edit-btn:hover:disabled){--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-bg-opacity:1}@media (min-width:768px){.edit-btn{padding:.625rem 1.25rem}}.info-btn{display:inline-block;cursor:pointer;border-radius:.5rem;--tw-bg-opacity:1;background-color:rgb(14 165 233/var(--tw-bg-opacity));padding:.5rem 1rem;text-align:center;vertical-align:middle;font-weight:700;text-transform:uppercase;line-height:1.5;letter-spacing:.025em;--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity));--tw-shadow:0 7px 14px #32325d1a,0 3px 6px #00000014;--tw-shadow-colored:0 7px 14px var(--tw-shadow-color),0 3px 6px var(--tw-shadow-color);transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.info-btn,.info-btn:hover{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.info-btn:hover{--tw-translate-y:-1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-shadow:0 4px 6px #32325d1a,0 1px 3px #00000014;--tw-shadow-colored:0 4px 6px var(--tw-shadow-color),0 1px 3px var(--tw-shadow-color)}.info-btn:focus,.info-btn:hover{background-color:#0ea5e9cc}.info-btn:active{opacity:.85}.info-btn:disabled{cursor:not-allowed;--tw-bg-opacity:1;--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity))}.info-btn:disabled,.info-btn:hover:disabled{border-color:#ced4da00;background-color:rgb(206 212 218/var(--tw-bg-opacity))}.info-btn:hover:disabled{--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-bg-opacity:1}:is(.dark .info-btn){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}:is(.dark .info-btn:disabled){--tw-bg-opacity:1;--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}:is(.dark .info-btn:disabled),:is(.dark .info-btn:hover:disabled){border-color:#49505700;background-color:rgb(73 80 87/var(--tw-bg-opacity))}:is(.dark .info-btn:hover:disabled){--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-bg-opacity:1}@media (min-width:768px){.info-btn{padding:.625rem 1.25rem}}.btn-disabled-style:disabled{cursor:not-allowed;--tw-bg-opacity:1;--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity))}.btn-disabled-style:disabled,.btn-disabled-style:hover:disabled{border-color:#ced4da00;background-color:rgb(206 212 218/var(--tw-bg-opacity))}.btn-disabled-style:hover:disabled{--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-bg-opacity:1}:is(.dark .btn-disabled-style:disabled){border-color:#49505700;--tw-bg-opacity:1;background-color:rgb(73 80 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}:is(.dark .btn-disabled-style:hover:disabled){--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-color:#49505700;--tw-bg-opacity:1;background-color:rgb(73 80 87/var(--tw-bg-opacity))}.checkbox{position:relative;z-index:10;float:left;margin-top:.25rem;height:1.25rem;width:1.25rem;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.35rem;border-width:1px;border-color:rgb(210 214 218/var(--tw-border-opacity));background-color:rgb(255 255 255/var(--tw-bg-opacity));background-size:contain;background-position:50%;background-repeat:no-repeat;vertical-align:top;font-size:1rem;line-height:1.5rem;transition-property:none;transition-property:all;transition-timing-function:ease;transition-duration:.25s}.checkbox,.checkbox:disabled{--tw-border-opacity:1;--tw-bg-opacity:1}.checkbox:disabled{cursor:default;cursor:not-allowed;border-color:rgb(206 212 218/var(--tw-border-opacity));background-color:rgb(206 212 218/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity))}.checkbox[data-checked=true]{z-index:0;--tw-border-opacity:1;border-color:rgb(11 85 119/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(11 85 119/var(--tw-bg-opacity))}.checkbox:disabled[data-checked=true]{--tw-border-opacity:1;border-color:rgb(206 212 218/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(206 212 218/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity))}:is(.dark .checkbox){--tw-border-opacity:1;border-color:rgb(98 117 148/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(52 71 103/var(--tw-bg-opacity))}:is(.dark .checkbox:disabled){--tw-border-opacity:1;border-color:rgb(37 47 64/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(37 47 64/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}:is(.dark .checkbox[data-checked=true]){--tw-border-opacity:1;border-color:rgb(11 85 119/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(11 85 119/var(--tw-bg-opacity))}:is(.dark .checkbox:disabled[data-checked=true]){--tw-border-opacity:1;border-color:rgb(37 47 64/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(37 47 64/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}.custom-select-btn{display:flex;min-height:38px;width:100%;align-items:center;justify-content:space-between;border-radius:.5rem;border-width:1px;border-style:solid;--tw-border-opacity:1;border-color:rgb(210 214 218/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));background-clip:padding-box;padding:.25rem .375rem;text-align:left;vertical-align:middle;font-size:.875rem;font-weight:400;line-height:1.4rem;--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity));transition-property:all;transition-timing-function:ease;transition-duration:.3s;transition-timing-function:ease-in-out}.custom-select-btn::-moz-placeholder{--tw-text-opacity:1;color:rgb(173 181 189/var(--tw-text-opacity))}.custom-select-btn::placeholder{--tw-text-opacity:1;color:rgb(173 181 189/var(--tw-text-opacity))}.custom-select-btn:focus{--tw-border-opacity:1;border-color:rgb(11 85 119/var(--tw-border-opacity))}.custom-select-btn:disabled{cursor:not-allowed;--tw-border-opacity:1;border-color:rgb(206 212 218/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(206 212 218/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity));opacity:.75}:is(.dark .custom-select-btn){--tw-border-opacity:1;border-color:rgb(98 117 148/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(52 71 103/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(233 236 239/var(--tw-text-opacity))}:is(.dark .custom-select-btn:disabled){--tw-border-opacity:1;border-color:rgb(37 47 64/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(37 47 64/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}@media (min-width:768px){.custom-select-btn{padding:.5rem .75rem}}.custom-dropdown-btn{position:relative;margin-top:0;margin-bottom:0;min-height:38px;cursor:pointer;border-radius:0;border-bottom-width:1px;border-left-width:1px;border-right-width:1px;--tw-border-opacity:1;border-color:rgb(210 214 218/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));padding:.5rem 1.5rem;text-align:center;vertical-align:middle;font-size:.875rem;line-height:1.5rem;line-height:1.5;letter-spacing:-.025rem;--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity));transition-property:none;transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.custom-dropdown-btn:hover{--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}:is(.dark .custom-dropdown-btn){--tw-border-opacity:1;border-color:rgb(98 117 148/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(52 71 103/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(233 236 239/var(--tw-text-opacity))}.active.custom-dropdown-btn{position:relative;margin-top:0;margin-bottom:0;min-height:38px;cursor:pointer;border-radius:0;border-bottom-width:1px;border-left-width:1px;border-right-width:1px;--tw-border-opacity:1;border-color:rgb(210 214 218/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(11 85 119/var(--tw-bg-opacity));padding:.5rem 1.5rem;text-align:center;vertical-align:middle;font-size:.875rem;line-height:1.5rem;line-height:1.5;letter-spacing:-.025rem;--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity));transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.active.custom-dropdown-btn:hover{--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}:is(.dark .active.custom-dropdown-btn){border-color:rgb(98 117 148/var(--tw-border-opacity));background-color:rgb(11 85 119/var(--tw-bg-opacity));color:rgb(233 236 239/var(--tw-text-opacity))}.regular-input,:is(.dark .active.custom-dropdown-btn){--tw-border-opacity:1;--tw-bg-opacity:1;--tw-text-opacity:1}.regular-input{display:block;width:100%;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.5rem;border-width:1px;border-style:solid;border-color:rgb(210 214 218/var(--tw-border-opacity));background-color:rgb(255 255 255/var(--tw-bg-opacity));background-clip:padding-box;padding:.25rem .375rem;font-size:.875rem;font-weight:400;line-height:1.4rem;color:rgb(73 80 87/var(--tw-text-opacity));outline:2px solid #0000;outline-offset:2px;transition-property:none;transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.regular-input::-moz-placeholder{--tw-text-opacity:1;color:rgb(173 181 189/var(--tw-text-opacity))}.regular-input::placeholder{--tw-text-opacity:1;color:rgb(173 181 189/var(--tw-text-opacity))}.regular-input:focus{border-color:#d2d6da00;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.regular-input:valid:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(34 197 94/var(--tw-ring-opacity))}.regular-input:invalid:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(245 57 57/var(--tw-ring-opacity))}.regular-input:disabled{cursor:not-allowed;--tw-bg-opacity:1;background-color:rgb(206 212 218/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity));opacity:.75}:is(.dark .regular-input){--tw-border-opacity:1;border-color:rgb(98 117 148/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(52 71 103/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(233 236 239/var(--tw-text-opacity))}:is(.dark .regular-input:disabled){--tw-border-opacity:1;border-color:rgb(37 47 64/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(37 47 64/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}@media (min-width:768px){.regular-input{padding:.5rem .75rem}}.invalid.regular-input{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)!important;--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)!important;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)!important;--tw-ring-opacity:1!important;--tw-ring-color:rgb(245 57 57/var(--tw-ring-opacity))!important}.input-title{margin:0;font-size:.875rem;line-height:1.5rem;font-weight:700;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.3s;transition-timing-function:ease-in-out}:is(.dark .input-title){--tw-text-opacity:1;color:rgb(233 236 239/var(--tw-text-opacity))}.popover-settings-container{position:fixed;z-index:1000;height:-moz-fit-content;height:fit-content;max-width:250px;--tw-translate-y:-1.75rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-radius:.375rem;padding:.75rem;transition-property:all;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.5s}:is(.dark .popover-settings-container){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.info.popover-settings-container{--tw-bg-opacity:1;background-color:rgb(94 114 228/var(--tw-bg-opacity))}.multisite.popover-settings-container{--tw-bg-opacity:1;background-color:rgb(251 99 64/var(--tw-bg-opacity))}.popover-tab{position:absolute;left:0;bottom:0;z-index:50;--tw-translate-y:-1.75rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-radius:.375rem;--tw-bg-opacity:1;background-color:rgb(94 114 228/var(--tw-bg-opacity));padding:.75rem;transition-property:all;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.5s}:is(.dark .popover-tab){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.popover-settings-text{margin:0;font-size:.875rem;line-height:1.5rem;font-weight:700;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.3s;transition-timing-function:ease-in-out}.popover-settings-text,:is(.dark .popover-settings-text){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.popover-settings-svg{margin-left:.5rem;height:1.25rem;width:1.25rem;cursor:pointer;fill:#5e72e4}.popover-settings-svg:hover{--tw-brightness:brightness(.75);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.popover-settings-svg-multiple{margin-left:.5rem;height:1.375rem;width:1.375rem;cursor:pointer;fill:#fb6340;stroke:#495057}.popover-settings-svg-multiple:hover{--tw-brightness:brightness(.75);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}:is(.dark .popover-settings-svg-multiple){stroke:#adb5bd}.hidden-multiple{display:none!important}.active.tabs-tab-btn,.active.tabs-tab-btn:hover{--tw-bg-opacity:1;background-color:rgb(233 236 239/var(--tw-bg-opacity))}:is(.dark .active.tabs-tab-btn),:is(.dark .active.tabs-tab-btn:hover){--tw-bg-opacity:1;background-color:rgb(73 80 87/var(--tw-bg-opacity))}.tabs-tab-btn{position:relative;margin-top:.25rem;margin-bottom:.25rem;cursor:pointer;border-radius:0;border-width:1px;--tw-border-opacity:1;border-color:rgb(11 85 119/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));padding:.75rem;text-align:center;vertical-align:middle;font-size:.875rem;line-height:1.5rem;font-weight:700;text-transform:uppercase;line-height:1.5;letter-spacing:-.025rem;--tw-shadow:0 7px 14px #32325d1a,0 3px 6px #00000014;--tw-shadow-colored:0 7px 14px var(--tw-shadow-color),0 3px 6px var(--tw-shadow-color);transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.tabs-tab-btn,.tabs-tab-btn:hover{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.tabs-tab-btn:hover{--tw-bg-opacity:1;background-color:rgb(235 239 244/var(--tw-bg-opacity));--tw-shadow:0 4px 6px #32325d1a,0 1px 3px #00000014;--tw-shadow-colored:0 4px 6px var(--tw-shadow-color),0 1px 3px var(--tw-shadow-color)}:is(.dark .tabs-tab-btn){--tw-border-opacity:1;border-color:rgb(98 117 148/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(52 71 103/var(--tw-bg-opacity))}:is(.dark .tabs-tab-btn:hover){--tw-bg-opacity:1;background-color:rgb(58 65 111/var(--tw-bg-opacity))}.tabs-name{padding-left:.75rem;padding-right:.5rem;--tw-text-opacity:1;color:rgb(11 85 119/var(--tw-text-opacity));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.3s;transition-timing-function:ease-in-out}:is(.dark .tabs-name){--tw-text-opacity:1;color:rgb(235 239 244/var(--tw-text-opacity))}.tabs-popover-container{position:absolute;top:60px;left:0;z-index:50;min-width:150px;border-radius:.375rem;--tw-bg-opacity:1;background-color:rgb(94 114 228/var(--tw-bg-opacity));padding:.75rem;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.15s}:is(.dark .tabs-popover-container){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.tabs-popover-text{margin:0;font-size:.875rem;line-height:1.5rem;font-weight:700;--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.settings-tabs-select-btn{margin-top:.25rem;margin-bottom:.25rem;display:flex;width:100%;cursor:pointer;align-items:center;justify-content:space-between;border-radius:.5rem;border-width:1px;--tw-border-opacity:1;border-color:rgb(11 85 119/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));padding:.5rem 1rem;text-align:center;vertical-align:middle;font-size:.875rem;line-height:1.5rem;font-weight:700;text-transform:uppercase;line-height:1.5;letter-spacing:-.025rem;--tw-shadow:0 7px 14px #32325d1a,0 3px 6px #00000014;--tw-shadow-colored:0 7px 14px var(--tw-shadow-color),0 3px 6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.settings-tabs-select-btn:hover{--tw-translate-y:-1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-bg-opacity:1;background-color:rgb(248 249 250/var(--tw-bg-opacity));--tw-shadow:0 4px 6px #32325d1a,0 1px 3px #00000014;--tw-shadow-colored:0 4px 6px var(--tw-shadow-color),0 1px 3px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}:is(.dark .settings-tabs-select-btn){--tw-border-opacity:1;border-color:rgb(98 117 148/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(52 71 103/var(--tw-bg-opacity))}:is(.dark .settings-tabs-select-btn:hover){--tw-brightness:brightness(.95);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}@media (min-width:768px){.settings-tabs-select-btn{padding:.75rem 1.5rem}}.settings-tabs-select-btn-text{word-break:break-all;--tw-text-opacity:1;color:rgb(11 85 119/var(--tw-text-opacity));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.3s;transition-timing-function:ease-in-out}:is(.dark .settings-tabs-select-btn-text){--tw-text-opacity:1;color:rgb(206 212 218/var(--tw-text-opacity))}.settings-tabs-select-btn-svg{margin-left:1rem;height:1rem;width:1rem;min-width:1rem;fill:#0b5577;transition-property:transform;transition-timing-function:ease;transition-duration:.15s}:is(.dark .settings-tabs-select-btn-svg){fill:#d2d6da}.active.settings-tabs-select-dropdown-btn{position:relative;z-index:1000;margin-top:0;margin-bottom:0;cursor:pointer;border-radius:0;--tw-border-opacity:1;border-color:rgb(210 214 218/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(11 85 119/var(--tw-bg-opacity));padding:.5rem 1.5rem;text-align:center;vertical-align:middle;font-size:.875rem;line-height:1.5rem;line-height:1.5;letter-spacing:-.025rem;--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity));transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.active.settings-tabs-select-dropdown-btn:hover{--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}:is(.dark .active.settings-tabs-select-dropdown-btn){--tw-border-opacity:1;border-color:rgb(98 117 148/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(11 85 119/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(233 236 239/var(--tw-text-opacity))}:is(.dark .active.settings-tabs-select-dropdown-btn:hover){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.first.settings-tabs-select-dropdown-btn{border-top-left-radius:.25rem;border-top-right-radius:.25rem;border-width:1px}.last.settings-tabs-select-dropdown-btn{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.settings-tabs-select-dropdown-btn{position:relative;margin-top:0;margin-bottom:0;display:flex;cursor:pointer;justify-content:space-between;word-break:break-all;border-radius:0;border-bottom-width:1px;border-left-width:1px;border-right-width:1px;--tw-border-opacity:1;border-color:rgb(210 214 218/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));padding:.5rem 1.5rem;text-align:left;vertical-align:middle;font-size:.875rem;line-height:1.5rem;line-height:1.5;letter-spacing:-.025rem;--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity));transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.settings-tabs-select-dropdown-btn:hover{--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}:is(.dark .settings-tabs-select-dropdown-btn){--tw-border-opacity:1;border-color:rgb(98 117 148/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(52 71 103/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(233 236 239/var(--tw-text-opacity))}:is(.dark .settings-tabs-select-dropdown-btn:hover){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.home-card{grid-column:span 12/span 12;display:flex;width:100%;justify-content:space-between;overflow-wrap:break-word;word-break:break-all;border-radius:1rem;--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));background-clip:initial;padding:1rem;--tw-shadow:0 4px 6px #32325d1a,0 1px 3px #00000014;--tw-shadow-colored:0 4px 6px var(--tw-shadow-color),0 1px 3px var(--tw-shadow-color);transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.15s}.home-card,:is(.dark .home-card){box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}:is(.dark .home-card){--tw-bg-opacity:1;background-color:rgb(17 28 68/var(--tw-bg-opacity));--tw-shadow:0 2px 2px 0 #00000024,0 3px 1px -2px #0003,0 1px 5px 0 #0000001f;--tw-shadow-colored:0 2px 2px 0 var(--tw-shadow-color),0 3px 1px -2px var(--tw-shadow-color),0 1px 5px 0 var(--tw-shadow-color);--tw-brightness:brightness(1.1);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}@media (min-width:576px){.home-card{max-height:7rem}}@media (min-width:768px){.home-card{grid-column:span 6/span 6}}@media (min-width:1320px){.home-card{grid-column:span 4/span 4}}.home-card-name{margin-bottom:0;font-family:Open Sans;font-size:.875rem;line-height:1.5rem;font-weight:600;text-transform:uppercase;line-height:1.5}:is(.dark .home-card-name){--tw-text-opacity:1;color:rgb(206 212 218/var(--tw-text-opacity))}.home-card-title{margin-bottom:.25rem;font-weight:700}:is(.dark .home-card-title){color:#ffffffe6}.home-card-subtitle{margin-left:.125rem;margin-right:.125rem;margin-bottom:0;font-size:.875rem;line-height:1.5rem;font-weight:700;line-height:1.5}.info.home-card-subtitle{--tw-text-opacity:1;color:rgb(14 165 233/var(--tw-text-opacity))}.error.home-card-subtitle{--tw-text-opacity:1;color:rgb(245 57 57/var(--tw-text-opacity))}.success.home-card-subtitle{--tw-text-opacity:1;color:rgb(34 197 94/var(--tw-text-opacity))}.warning.home-card-subtitle{--tw-text-opacity:1;color:rgb(251 177 64/var(--tw-text-opacity))}.home-card-svg-container{display:inline-block;height:3rem;width:3rem;min-width:3rem;border-radius:50%;text-align:center}:is(.dark .home-card-svg-container){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.version.home-card-svg-container{--tw-bg-opacity:1;background-color:rgb(251 177 64/var(--tw-bg-opacity))}.version-number.home-card-svg-container{--tw-bg-opacity:1;background-color:rgb(45 206 137/var(--tw-bg-opacity))}.instances.home-card-svg-container{--tw-bg-opacity:1;background-color:rgb(108 117 125/var(--tw-bg-opacity))}.services.home-card-svg-container{--tw-bg-opacity:1;background-color:rgb(251 99 64/var(--tw-bg-opacity))}.plugins.home-card-svg-container{--tw-bg-opacity:1;background-color:rgb(251 207 51/var(--tw-bg-opacity))}.card-detail-container{margin-top:1rem;margin-bottom:1.5rem;margin-left:.25rem;display:grid;grid-template-columns:repeat(1,minmax(0,1fr));gap:.5rem}.card-detail-item{grid-column:span 1/span 1;display:flex;align-items:center;padding-top:.25rem;padding-bottom:.25rem}@media (min-width:576px){.card-detail-item{padding-top:0;padding-bottom:0}}.card-detail-item-title{margin-bottom:0;min-width:-moz-fit-content;min-width:fit-content;font-family:Open Sans;font-size:.875rem;line-height:1.5rem;font-weight:700;text-transform:uppercase;line-height:1.5;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.3s;transition-timing-function:ease-in-out}:is(.dark .card-detail-item-title){--tw-text-opacity:1;color:rgb(173 181 189/var(--tw-text-opacity))}.card-detail-item-subtitle{grid-column:span 1/span 1;margin-bottom:0;min-width:2rem;word-break:break-all;padding-left:.5rem;font-family:Open Sans;font-size:.875rem;line-height:1.5rem;font-weight:600;text-transform:uppercase;line-height:1.5;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.3s;transition-timing-function:ease-in-out}:is(.dark .card-detail-item-subtitle){--tw-text-opacity:1;color:rgb(235 239 244/var(--tw-text-opacity))}.core-layout{grid-column:span 12/span 12;display:grid;grid-template-columns:repeat(12,minmax(0,1fr))}.core-card{position:relative;grid-column:span 12/span 12;margin:.5rem;height:-moz-fit-content;height:fit-content;min-width:0;overflow-wrap:break-word;border-radius:1rem;--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));background-clip:initial;padding:1rem;--tw-shadow:0 0 2rem 0 #8898aa26;--tw-shadow-colored:0 0 2rem 0 var(--tw-shadow-color);transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.15s}.core-card,:is(.dark .core-card){box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}:is(.dark .core-card){--tw-bg-opacity:1;background-color:rgb(17 28 68/var(--tw-bg-opacity));--tw-shadow:0 2px 2px 0 #00000024,0 3px 1px -2px #0003,0 1px 5px 0 #0000001f;--tw-shadow-colored:0 2px 2px 0 var(--tw-shadow-color),0 3px 1px -2px var(--tw-shadow-color),0 1px 5px 0 var(--tw-shadow-color);--tw-brightness:brightness(1.1);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}@media (min-width:768px){.core-card{grid-column:span 6/span 6}}@media (min-width:1320px){.core-card{grid-column:span 4/span 4}}@media (min-width:1920px){.core-card{grid-column:span 3/span 3}}.core-card-list-large{position:relative;grid-column:span 12/span 12;margin:.5rem;height:-moz-fit-content;height:fit-content;min-width:0;overflow-x:auto;overflow-y:hidden;overflow-wrap:break-word;border-radius:1rem;--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));background-clip:initial;padding:1rem;--tw-shadow:0 0 2rem 0 #8898aa26;--tw-shadow-colored:0 0 2rem 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.15s}:is(.dark .core-card-list-large){--tw-bg-opacity:1;background-color:rgb(17 28 68/var(--tw-bg-opacity));--tw-shadow:0 2px 2px 0 #00000024,0 3px 1px -2px #0003,0 1px 5px 0 #0000001f;--tw-shadow-colored:0 2px 2px 0 var(--tw-shadow-color),0 3px 1px -2px var(--tw-shadow-color),0 1px 5px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);--tw-brightness:brightness(1.1);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}@media (min-width:992px){.core-card-list-large{grid-column:span 6/span 6;margin-bottom:0;height:100%}}.core-card-filter{position:relative;grid-column:span 12/span 12;display:flex;min-width:0;flex-direction:column;overflow-wrap:break-word;border-radius:1rem;--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));background-clip:initial;padding:1rem;--tw-shadow:0 0 2rem 0 #8898aa26;--tw-shadow-colored:0 0 2rem 0 var(--tw-shadow-color)}.core-card-filter,:is(.dark .core-card-filter){box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}:is(.dark .core-card-filter){--tw-bg-opacity:1;background-color:rgb(17 28 68/var(--tw-bg-opacity));--tw-shadow:0 2px 2px 0 #00000024,0 3px 1px -2px #0003,0 1px 5px 0 #0000001f;--tw-shadow-colored:0 2px 2px 0 var(--tw-shadow-color),0 3px 1px -2px var(--tw-shadow-color),0 1px 5px 0 var(--tw-shadow-color)}@media (min-width:768px){.core-card-filter{grid-column:span 6/span 6}}@media (min-width:1320px){.core-card-filter{grid-column:span 4/span 4}}.core-card-lg{position:relative;grid-column:span 12/span 12;margin:.5rem;height:-moz-fit-content;height:fit-content;min-width:0;overflow-wrap:break-word;border-radius:1rem;--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));background-clip:initial;padding:1rem;--tw-shadow:0 0 2rem 0 #8898aa26;--tw-shadow-colored:0 0 2rem 0 var(--tw-shadow-color);transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.15s}.core-card-lg,:is(.dark .core-card-lg){box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}:is(.dark .core-card-lg){--tw-bg-opacity:1;background-color:rgb(17 28 68/var(--tw-bg-opacity));--tw-shadow:0 2px 2px 0 #00000024,0 3px 1px -2px #0003,0 1px 5px 0 #0000001f;--tw-shadow-colored:0 2px 2px 0 var(--tw-shadow-color),0 3px 1px -2px var(--tw-shadow-color),0 1px 5px 0 var(--tw-shadow-color);--tw-brightness:brightness(1.1);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}@media (min-width:768px){.core-card-lg{grid-column:span 6/span 6}}.core-card-wrap{display:flex;justify-content:space-between}.core-card-wrap-logo{display:flex;align-items:center;justify-content:flex-start}.core-card-text{margin-bottom:0;font-family:Open Sans;font-size:.875rem;line-height:1.5rem;line-height:1.5;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.3s;transition-timing-function:ease-in-out}:is(.dark .core-card-text){--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}.core-card-text-doc{margin-top:1rem;margin-bottom:.5rem;padding-left:.25rem;padding-right:.25rem;font-family:Open Sans;font-size:.875rem;line-height:1.5rem;line-height:1.5;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.3s;transition-timing-function:ease-in-out}:is(.dark .core-card-text-doc){--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}.core-card-text-doc-link{margin-top:.5rem;cursor:pointer;--tw-text-opacity:1;color:rgb(14 165 233/var(--tw-text-opacity));text-decoration-line:underline}.core-card-text-doc-link:hover{--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.center.core-card-text{text-align:center}.core-card-title{margin-bottom:.5rem;font-weight:700}:is(.dark .core-card-title){color:#ffffffe6}.core-card-svg-container{display:inline-block;height:3rem;width:3rem;border-radius:50%;text-align:center}:is(.dark .core-card-svg-container){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.core-card-deactivated-title{font-weight:700}:is(.dark .core-card-deactivated-title){color:#ffffffe6}.core-card-deactivated-svg{position:relative;--tw-translate-y:-8px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));fill:#fbb140;stroke:#fff;font-size:1.125rem;line-height:1.75rem;line-height:1}.core-card-text-container{margin:.75rem .25rem;display:flex;align-items:center;justify-content:flex-start}.core-card-status{position:relative;grid-column:span 12/span 12;margin:.5rem;height:-moz-fit-content;height:fit-content;min-width:0;overflow-wrap:break-word;border-radius:1rem;--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));background-clip:initial;padding:1rem;--tw-shadow:0 0 2rem 0 #8898aa26;--tw-shadow-colored:0 0 2rem 0 var(--tw-shadow-color);transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.15s}.core-card-status,:is(.dark .core-card-status){box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}:is(.dark .core-card-status){--tw-bg-opacity:1;background-color:rgb(17 28 68/var(--tw-bg-opacity));--tw-shadow:0 2px 2px 0 #00000024,0 3px 1px -2px #0003,0 1px 5px 0 #0000001f;--tw-shadow-colored:0 2px 2px 0 var(--tw-shadow-color),0 3px 1px -2px var(--tw-shadow-color),0 1px 5px 0 var(--tw-shadow-color);--tw-brightness:brightness(1.1);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}@media (min-width:768px){.core-card-status{grid-column:span 6/span 6}}@media (min-width:1320px){.core-card-status{grid-column:span 3/span 3}}@media (min-width:1920px){.core-card-status{grid-column:span 2/span 2}}.core-card-status-container{display:flex;align-items:center;justify-content:space-between}.core-card-status-title{margin-bottom:0;margin-right:1rem;font-weight:700}:is(.dark .core-card-status-title){color:#ffffffe6}.core-card-status-svg{height:1.5rem;width:1.5rem}.info.core-card-status-svg{fill:#0ea5e9}.error.core-card-status-svg{fill:#f53939}.success.core-card-status-svg{fill:#22c55e}.core-layout-separator{grid-column:span 12/span 12}.core-card-list{position:relative;grid-column:span 12/span 12;margin:.5rem;display:grid;max-height:25rem;grid-template-columns:repeat(12,minmax(0,1fr));align-content:flex-start;overflow-y:auto;overflow-x:hidden;overflow-wrap:break-word;border-radius:1rem;--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));background-clip:initial;padding:1rem;--tw-shadow:0 0 2rem 0 #8898aa26;--tw-shadow-colored:0 0 2rem 0 var(--tw-shadow-color)}.core-card-list,:is(.dark .core-card-list){box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}:is(.dark .core-card-list){--tw-bg-opacity:1;background-color:rgb(17 28 68/var(--tw-bg-opacity));--tw-shadow:0 2px 2px 0 #00000024,0 3px 1px -2px #0003,0 1px 5px 0 #0000001f;--tw-shadow-colored:0 2px 2px 0 var(--tw-shadow-color),0 3px 1px -2px var(--tw-shadow-color),0 1px 5px 0 var(--tw-shadow-color);--tw-brightness:brightness(1.1);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}@media (min-width:576px){.core-card-list{max-height:31.25rem}}@media (min-width:768px){.core-card-list{grid-column:span 6/span 6}}.core-card-list.no-data{place-content:stretch}.core-card-list-no-data{margin-bottom:0;padding-bottom:2rem;text-align:center;font-size:1.5rem;line-height:2rem}@media (min-width:768px){.w-small.core-card-list{max-width:300px}.w-medium.core-card-list{max-width:400px}.w-large.core-card-list{max-width:550px}}.core-card-list-title-container{grid-column:span 12/span 12;display:flex}.core-card-list-title{margin-bottom:1rem;font-weight:700}:is(.dark .core-card-list-title){color:#ffffffe6}.core-card-list-container{grid-column:span 12/span 12;overflow-x:auto;overflow-y:auto}.core-card-list-header{margin:0;height:2rem;border-bottom-width:1px;--tw-border-opacity:1;border-color:rgb(206 212 218/var(--tw-border-opacity));padding-bottom:.5rem;font-size:.875rem;line-height:1.5rem;font-weight:700}:is(.dark .core-card-list-header){--tw-text-opacity:1;color:rgb(233 236 239/var(--tw-text-opacity))}.core-card-list-item{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));align-items:center;border-bottom-width:1px;--tw-border-opacity:1;border-color:rgb(210 214 218/var(--tw-border-opacity));padding-top:.625rem;padding-bottom:.625rem}.core-card-list-item-content{margin:.25rem 0;font-size:.875rem;line-height:1.5rem}:is(.dark .core-card-list-item-content){--tw-text-opacity:1;color:rgb(206 212 218/var(--tw-text-opacity))}.core-card-list-wrap{display:grid;width:100%;grid-template-columns:repeat(12,minmax(0,1fr));border-radius:.25rem;padding:.5rem}.w-small.core-card-list-wrap{min-width:200px}.w-medium.core-card-list-wrap{min-width:300px}.w-large.core-card-list-wrap{min-width:450px}.core-card-metrics{grid-column:span 12/span 12;margin:.5rem;display:flex;height:-moz-fit-content;height:fit-content;justify-content:space-between;overflow-wrap:break-word;border-radius:1rem;--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));background-clip:initial;padding:1rem;--tw-shadow:0 0 2rem 0 #8898aa26;--tw-shadow-colored:0 0 2rem 0 var(--tw-shadow-color);transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.15s}.core-card-metrics,:is(.dark .core-card-metrics){box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}:is(.dark .core-card-metrics){--tw-bg-opacity:1;background-color:rgb(17 28 68/var(--tw-bg-opacity));--tw-shadow:0 2px 2px 0 #00000024,0 3px 1px -2px #0003,0 1px 5px 0 #0000001f;--tw-shadow-colored:0 2px 2px 0 var(--tw-shadow-color),0 3px 1px -2px var(--tw-shadow-color),0 1px 5px 0 var(--tw-shadow-color);--tw-brightness:brightness(1.1);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}@media (min-width:576px){.core-card-metrics{max-height:7rem}}@media (min-width:768px){.core-card-metrics{grid-column:span 6/span 6}}@media (min-width:1320px){.core-card-metrics{grid-column:span 4/span 4}}.core-card-metrics-name{margin-bottom:.5rem;font-family:Open Sans;font-size:.875rem;line-height:1.5rem;font-weight:600;text-transform:uppercase;line-height:1.5}:is(.dark .core-card-metrics-name){--tw-text-opacity:1;color:rgb(206 212 218/var(--tw-text-opacity))}.core-card-metrics-subtitle{margin-bottom:0}:is(.dark .core-card-metrics-subtitle){--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}.core-card-metrics-subtitle-content{margin-left:.125rem;margin-right:.125rem;font-size:.875rem;line-height:1.5rem;font-weight:700;line-height:1.5}.error.core-card-metrics-subtitle-content{--tw-text-opacity:1;color:rgb(245 57 57/var(--tw-text-opacity))}.success.core-card-metrics-subtitle-content{--tw-text-opacity:1;color:rgb(34 197 94/var(--tw-text-opacity))}.warning.core-card-metrics-subtitle-content{--tw-text-opacity:1;color:rgb(251 177 64/var(--tw-text-opacity))}.info.core-card-metrics-subtitle-content{--tw-text-opacity:1;color:rgb(14 165 233/var(--tw-text-opacity))}.core-card-metrics-svg{position:relative;fill:#fff;font-size:1.125rem;line-height:1.75rem;line-height:1}.size-small.core-card-metrics-svg{--tw-scale-x:0.5;--tw-scale-y:0.5}.size-medium.core-card-metrics-svg,.size-small.core-card-metrics-svg{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.size-medium.core-card-metrics-svg{--tw-scale-x:0.6;--tw-scale-y:0.6}.size-base.core-card-metrics-svg{--tw-scale-x:0.75;--tw-scale-y:0.75;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.purple.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(147 51 234/var(--tw-bg-opacity))}.green.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(21 128 61/var(--tw-bg-opacity))}.red.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(185 28 28/var(--tw-bg-opacity))}.orange.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(245 96 54/var(--tw-bg-opacity))}.blue.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(33 82 255/var(--tw-bg-opacity))}.yellow.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(202 138 4/var(--tw-bg-opacity))}.gray.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(108 117 125/var(--tw-bg-opacity))}.dark.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(98 117 148/var(--tw-bg-opacity))}.amber.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(217 119 6/var(--tw-bg-opacity))}.emerald.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(5 150 105/var(--tw-bg-opacity))}.teal.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(13 148 136/var(--tw-bg-opacity))}.indigo.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity))}.cyan.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(8 145 178/var(--tw-bg-opacity))}.sky.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(3 105 161/var(--tw-bg-opacity))}.pink.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(219 39 119/var(--tw-bg-opacity))}.lime.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(101 163 13/var(--tw-bg-opacity))}.core-separator{margin:.75rem 0 .5rem;height:1px;background-color:initial;--tw-gradient-from:#0000 var(--tw-gradient-from-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to);--tw-gradient-stops:var(--tw-gradient-from),#0006 var(--tw-gradient-via-position),var(--tw-gradient-to);--tw-gradient-to:#0000 var(--tw-gradient-to-position)}.core-separator,:is(.dark .core-separator){background-image:linear-gradient(to right,var(--tw-gradient-stops))}:is(.dark .core-separator){--tw-gradient-from:#0000 var(--tw-gradient-from-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to);--tw-gradient-to:#fff0 var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),#fff var(--tw-gradient-via-position),var(--tw-gradient-to);--tw-gradient-to:#0000 var(--tw-gradient-to-position)}.core-card-test-container{margin-top:1rem;display:flex;justify-content:center}.core-card-test-btn{display:inline-block;cursor:pointer;border-radius:.5rem;--tw-bg-opacity:1;background-color:rgb(251 177 64/var(--tw-bg-opacity));padding:.75rem 1.5rem;text-align:center;vertical-align:middle;font-size:.875rem;line-height:1.5rem;font-weight:700;text-transform:uppercase;line-height:1.5;letter-spacing:.025em;--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity));--tw-shadow:0 7px 14px #32325d1a,0 3px 6px #00000014;--tw-shadow-colored:0 7px 14px var(--tw-shadow-color),0 3px 6px var(--tw-shadow-color);transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.core-card-test-btn,.core-card-test-btn:hover{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.core-card-test-btn:hover{--tw-translate-y:-1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));background-color:#fbb140cc;--tw-shadow:0 4px 6px #32325d1a,0 1px 3px #00000014;--tw-shadow-colored:0 4px 6px var(--tw-shadow-color),0 1px 3px var(--tw-shadow-color)}.core-card-test-btn:focus{background-color:#fbb140cc}.core-card-test-btn:active{opacity:.85}.core-card-test-btn:disabled{cursor:not-allowed;--tw-bg-opacity:1;--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity))}.core-card-test-btn:disabled,.core-card-test-btn:hover:disabled{border-color:#ced4da00;background-color:rgb(206 212 218/var(--tw-bg-opacity))}.core-card-test-btn:hover:disabled{--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-bg-opacity:1}:is(.dark .core-card-test-btn){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}:is(.dark .core-card-test-btn:disabled){border-color:#49505700;--tw-bg-opacity:1;background-color:rgb(73 80 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}:is(.dark .core-card-test-btn:hover:disabled){--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-color:#49505700;--tw-bg-opacity:1;background-color:rgb(73 80 87/var(--tw-bg-opacity))}.core-card-upload-btn{display:inline-block;width:-moz-fit-content;width:fit-content;cursor:pointer;border-radius:.5rem;--tw-bg-opacity:1;background-color:rgb(11 85 119/var(--tw-bg-opacity));background-image:linear-gradient(to top left,var(--tw-gradient-stops));background-size:150%;background-position:25% 0;padding:.75rem 1.5rem;text-align:center;vertical-align:middle;font-size:.75rem;line-height:1rem;font-weight:700;text-transform:uppercase;line-height:1.5;letter-spacing:.025em;--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity));--tw-shadow:0 7px 14px #32325d1a,0 3px 6px #00000014;--tw-shadow-colored:0 7px 14px var(--tw-shadow-color),0 3px 6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.core-card-upload-btn:hover{--tw-translate-y:-1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-shadow:0 4px 6px #32325d1a,0 1px 3px #00000014;--tw-shadow-colored:0 4px 6px var(--tw-shadow-color),0 1px 3px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);--tw-brightness:brightness(.75);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.core-card-upload-btn:active{opacity:.85}.core-card-upload-btn:disabled{cursor:not-allowed;--tw-border-opacity:1;border-color:rgb(206 212 218/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(206 212 218/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity));opacity:.75}.core-card-upload-btn:hover:disabled{--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}:is(.dark .core-card-upload-btn){--tw-brightness:brightness(1.25);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}:is(.dark .core-card-upload-btn:disabled){--tw-border-opacity:1;border-color:rgb(37 47 64/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(37 47 64/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}.core-card-test-status-container{margin-left:.25rem;margin-right:.25rem;display:flex;align-items:center;justify-content:center}.core-card-test-status-svg{margin-right:.5rem;height:1.5rem;width:1.5rem}.success.core-card-test-status-svg{fill:#22c55e}.error.core-card-test-status-svg{fill:#f53939}.info.core-card-test-status-svg{fill:#0ea5e9}.core-img-default{margin-right:1rem;height:3rem;width:3rem}.core-img-hor{margin-right:1rem;height:4rem;width:6rem}.core-card-detail{position:relative;grid-column:span 12/span 12;height:-moz-fit-content;height:fit-content;overflow:auto;overflow-wrap:break-word;border-radius:1rem;--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));background-clip:initial;padding:1rem;--tw-shadow:0 0 2rem 0 #8898aa26;--tw-shadow-colored:0 0 2rem 0 var(--tw-shadow-color)}.core-card-detail,:is(.dark .core-card-detail){box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}:is(.dark .core-card-detail){--tw-bg-opacity:1;background-color:rgb(17 28 68/var(--tw-bg-opacity));--tw-shadow:0 2px 2px 0 #00000024,0 3px 1px -2px #0003,0 1px 5px 0 #0000001f;--tw-shadow-colored:0 2px 2px 0 var(--tw-shadow-color),0 3px 1px -2px var(--tw-shadow-color),0 1px 5px 0 var(--tw-shadow-color)}@media (min-width:992px){.core-card-detail{grid-column:span 6/span 6}}@media (min-width:1200px){.core-card-detail{grid-column:span 6/span 6}}@media (min-width:1920px){.core-card-detail{grid-column:span 3/span 3}}.core-card-detail-title{margin-bottom:.5rem;font-weight:700}:is(.dark .core-card-detail-title){color:#ffffffe6}.core-card-detail-items-container{margin-top:1rem;margin-bottom:1.5rem;margin-left:.25rem;display:grid;grid-template-columns:repeat(1,minmax(0,1fr));gap:.5rem}.core-card-detail-item{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));padding-top:.25rem;padding-bottom:.25rem}@media (min-width:576px){.core-card-detail-item{padding-top:0;padding-bottom:0}}.core-card-detail-item-title{grid-column:span 4/span 4;margin-bottom:0;width:100%;min-width:-moz-fit-content;min-width:fit-content;word-break:break-all;font-family:Open Sans;font-size:.875rem;line-height:1.5rem;font-weight:700;text-transform:uppercase;line-height:1.5;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.3s;transition-timing-function:ease-in-out}:is(.dark .core-card-detail-item-title){--tw-text-opacity:1;color:rgb(173 181 189/var(--tw-text-opacity))}.core-card-detail-item-subtitle{grid-column:span 8/span 8;margin-bottom:0;width:100%;min-width:2rem;word-break:break-all;padding-left:.5rem;font-family:Open Sans;font-size:.875rem;line-height:1.5rem;font-weight:600;text-transform:uppercase;line-height:1.5;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.3s;transition-timing-function:ease-in-out}:is(.dark .core-card-detail-item-subtitle){--tw-text-opacity:1;color:rgb(235 239 244/var(--tw-text-opacity))}.file-manager-actions-item-btn{position:relative;margin:.25rem;cursor:pointer;white-space:nowrap;border-radius:.25rem;border-width:1px;--tw-border-opacity:1;border-color:rgb(11 85 119/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));padding:.75rem 1.25rem .625rem 1rem;text-align:center;vertical-align:middle;font-size:.875rem;line-height:1.5rem;font-weight:700;text-transform:uppercase;line-height:1.5;letter-spacing:-.025rem;--tw-text-opacity:1;color:rgb(11 85 119/var(--tw-text-opacity));--tw-shadow:0 7px 14px #32325d1a,0 3px 6px #00000014;--tw-shadow-colored:0 7px 14px var(--tw-shadow-color),0 3px 6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.file-manager-actions-item-btn:hover{--tw-bg-opacity:1;background-color:rgb(235 239 244/var(--tw-bg-opacity));--tw-shadow:0 4px 6px #32325d1a,0 1px 3px #00000014;--tw-shadow-colored:0 4px 6px var(--tw-shadow-color),0 1px 3px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.file-manager-actions-item-btn:disabled{cursor:not-allowed;border-color:#ced4da00;--tw-bg-opacity:1;background-color:rgb(206 212 218/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(73 80 87/var(--tw-text-opacity))}.file-manager-actions-item-btn:hover:disabled{--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-color:#ced4da00;--tw-bg-opacity:1;background-color:rgb(206 212 218/var(--tw-bg-opacity))}:is(.dark .file-manager-actions-item-btn){--tw-border-opacity:1;border-color:rgb(98 117 148/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(52 71 103/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(233 236 239/var(--tw-text-opacity))}:is(.dark .file-manager-actions-item-btn:hover){--tw-bg-opacity:1;background-color:rgb(58 65 111/var(--tw-bg-opacity))}:is(.dark .file-manager-actions-item-btn:disabled){border-color:#49505700;--tw-bg-opacity:1;background-color:rgb(73 80 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(210 214 218/var(--tw-text-opacity))}:is(.dark .file-manager-actions-item-btn:hover:disabled){--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-color:#49505700;--tw-bg-opacity:1;background-color:rgb(73 80 87/var(--tw-bg-opacity))}@media (min-width:768px){.file-manager-actions-item-btn{display:block}}.plugins-list-container{position:relative;grid-column:span 12/span 12;width:100%;overflow:auto;overflow-wrap:break-word;border-radius:1rem;--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));background-clip:initial;padding:1rem;--tw-shadow:0 0 2rem 0 #8898aa26;--tw-shadow-colored:0 0 2rem 0 var(--tw-shadow-color)}.plugins-list-container,:is(.dark .plugins-list-container){box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}:is(.dark .plugins-list-container){--tw-bg-opacity:1;background-color:rgb(17 28 68/var(--tw-bg-opacity));--tw-shadow:0 2px 2px 0 #00000024,0 3px 1px -2px #0003,0 1px 5px 0 #0000001f;--tw-shadow-colored:0 2px 2px 0 var(--tw-shadow-color),0 3px 1px -2px var(--tw-shadow-color),0 1px 5px 0 var(--tw-shadow-color)}.plugins-list-container-title-container{grid-column:span 12/span 12}.plugins-list-container-title{margin-left:.5rem;margin-right:.5rem;font-weight:700}:is(.dark .plugins-list-container-title){color:#ffffffe6}.plugins-list-items-container{position:relative;grid-column:span 12/span 12;max-height:20rem;min-height:55vh;overflow:auto;padding:.5rem}.plugins-list-items-wrap{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:.75rem}.plugins-list-items{position:relative;grid-column:span 12/span 12;display:flex;min-height:3rem;align-items:center;justify-content:space-between;border-radius:.25rem;padding:.75rem .25rem;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.15s}@media (min-width:576px){.plugins-list-items{grid-column:span 6/span 6}}@media (min-width:1320px){.plugins-list-items{grid-column:span 4/span 4}}@media (min-width:1920px){.plugins-list-items{grid-column:span 3/span 3}}.enabled.plugins-list-items{--tw-bg-opacity:1;background-color:rgb(235 239 244/var(--tw-bg-opacity))}.enabled.plugins-list-items:hover{--tw-bg-opacity:1;background-color:rgb(210 214 218/var(--tw-bg-opacity))}:is(.dark .enabled.plugins-list-items){--tw-bg-opacity:1;background-color:rgb(52 71 103/var(--tw-bg-opacity))}:is(.dark .enabled.plugins-list-items:hover){--tw-bg-opacity:1;background-color:rgb(58 65 111/var(--tw-bg-opacity))}.disabled.plugins-list-items{cursor:not-allowed;--tw-bg-opacity:1;background-color:rgb(210 214 218/var(--tw-bg-opacity))}:is(.dark .disabled.plugins-list-items){--tw-bg-opacity:1;background-color:rgb(37 47 64/var(--tw-bg-opacity))}.plugins-list-items-name{margin-left:.75rem;margin-right:.5rem;margin-bottom:0;overflow-wrap:break-word;word-break:break-all;text-align:left;font-size:.875rem;line-height:1.5rem;--tw-text-opacity:1;color:rgb(52 71 103/var(--tw-text-opacity));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:ease;transition-duration:.3s;transition-timing-function:ease-in-out}:is(.dark .plugins-list-items-name){--tw-text-opacity:1;color:rgb(233 236 239/var(--tw-text-opacity))}@media (min-width:768px){.plugins-list-items-name{font-size:1rem;line-height:1.5rem}}.disabled.plugins-list-items-name{opacity:.8}:is(.dark .disabled.plugins-list-items-name){opacity:.6}.plugins-list-items-actions{display:flex;align-items:center}.plugins-list-items-delete{z-index:20;margin-left:.5rem;margin-right:.5rem;display:inline-block;cursor:pointer;text-align:left;vertical-align:middle;font-size:.75rem;line-height:1rem;font-weight:700;text-transform:uppercase;letter-spacing:-.025rem;--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity));transition-property:all;transition-timing-function:ease;transition-duration:.15s;transition-timing-function:ease-in}.plugins-list-items-delete:hover{--tw-translate-y:-1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.plugins-list-items-delete:disabled{cursor:not-allowed}.plugins-list-items-delete-svg{height:1.25rem;width:1.25rem;fill:#f53939}:is(.dark .plugins-list-items-delete-svg){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.readonly.plugins-list-items-delete-svg{cursor:not-allowed;opacity:.5}.plugins-list-items-link{margin-left:.25rem;margin-right:.25rem}.plugins-list-items-link:hover{--tw-translate-y:-1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.plugins-list-items-link-svg{height:1.5rem;width:1.5rem;fill:#0ea5e9}.plugins-list-items-link-svg.core-card-svg-container{--tw-bg-opacity:1;background-color:rgb(98 117 148/var(--tw-bg-opacity))}:is(.dark .plugins-list-items-link-svg){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.plugins-list-items-pro{margin-left:.25rem;margin-right:.25rem;--tw-translate-y:-0.125rem}.plugins-list-items-pro,.plugins-list-items-pro:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.plugins-list-items-pro:hover{--tw-translate-y:-1px}.plugins-list-items-pro-svg{height:1.5rem;width:1.5rem}:is(.dark .plugins-list-items-pro-svg){--tw-brightness:brightness(.9);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.first-letter\:absolute:first-letter{position:absolute!important}.first-letter\:w-full:first-letter{width:100%!important}.placeholder\:text-gray-500::-moz-placeholder{--tw-text-opacity:1!important;color:rgb(173 181 189/var(--tw-text-opacity))!important}.placeholder\:text-gray-500::placeholder{--tw-text-opacity:1!important;color:rgb(173 181 189/var(--tw-text-opacity))!important}.before\:float-left:before{content:var(--tw-content)!important;float:left!important}.before\:pr-2:before{content:var(--tw-content)!important;padding-right:.5rem!important}.before\:text-white:before{content:var(--tw-content)!important;--tw-text-opacity:1!important;color:rgb(255 255 255/var(--tw-text-opacity))!important}.before\:content-\[\'\/\'\]:before{--tw-content:"/"!important;content:var(--tw-content)!important}.after\:absolute:after{content:var(--tw-content)!important;position:absolute!important}.after\:top-px:after{content:var(--tw-content)!important;top:1px!important}.after\:float-right:after{content:var(--tw-content)!important;float:right!important}.after\:h-4:after{content:var(--tw-content)!important;height:1rem!important}.after\:w-4:after{content:var(--tw-content)!important;width:1rem!important}.after\:translate-x-px:after{content:var(--tw-content)!important;--tw-translate-x:1px!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.after\:rounded-circle:after{content:var(--tw-content)!important;border-radius:50%!important}.after\:bg-white:after{content:var(--tw-content)!important;--tw-bg-opacity:1!important;background-color:rgb(255 255 255/var(--tw-bg-opacity))!important}.after\:pl-2:after{content:var(--tw-content)!important;padding-left:.5rem!important}.after\:text-gray-600:after{content:var(--tw-content)!important;--tw-text-opacity:1!important;color:rgb(108 117 125/var(--tw-text-opacity))!important}.after\:shadow-2xl:after{content:var(--tw-content)!important;--tw-shadow:0 .3125rem .625rem 0 #0000001f!important;--tw-shadow-colored:0 .3125rem .625rem 0 var(--tw-shadow-color)!important;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)!important}.after\:duration-300:after{content:var(--tw-content)!important;transition-duration:.3s!important}.after\:content-\[\'\'\]:after{--tw-content:""!important;content:var(--tw-content)!important}.after\:content-\[\'\/\'\]:after{--tw-content:"/"!important;content:var(--tw-content)!important}.checked\:z-0:checked{z-index:0!important}.checked\:border-primary:checked{--tw-border-opacity:1!important;border-color:rgb(11 85 119/var(--tw-border-opacity))!important}.checked\:bg-primary:checked{--tw-bg-opacity:1!important;background-color:rgb(11 85 119/var(--tw-bg-opacity))!important}.checked\:bg-none:checked{background-image:none!important}.checked\:bg-right:checked{background-position:100%!important}.checked\:after\:translate-x-5:checked:after{--tw-translate-x:1.25rem!important}.checked\:after\:translate-x-5:checked:after,.checked\:after\:translate-x-5\.3:checked:after{content:var(--tw-content)!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.checked\:after\:translate-x-5\.3:checked:after{--tw-translate-x:1.3rem!important}.valid\:\!border-red-500:valid{--tw-border-opacity:1!important;border-color:rgb(245 57 57/var(--tw-border-opacity))!important}.hover\:-translate-y-0:hover{--tw-translate-y:-0px!important}.hover\:-translate-y-0:hover,.hover\:-translate-y-0\.4:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.hover\:-translate-y-0\.4:hover{--tw-translate-y:-0.1rem!important}.hover\:-translate-y-0\.5:hover{--tw-translate-y:-0.125rem!important}.hover\:-translate-y-0\.5:hover,.hover\:-translate-y-px:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.hover\:-translate-y-px:hover{--tw-translate-y:-1px!important}.hover\:scale-102:hover{--tw-scale-x:1.02!important;--tw-scale-y:1.02!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.hover\:rounded-lg:hover{border-radius:.5rem!important}.hover\:bg-emerald-500:hover{--tw-bg-opacity:1!important;background-color:rgb(45 206 137/var(--tw-bg-opacity))!important}.hover\:bg-emerald-500\/80:hover{background-color:#2dce89cc!important}.hover\:bg-gray-100:hover{--tw-bg-opacity:1!important;background-color:rgb(235 239 244/var(--tw-bg-opacity))!important}.hover\:bg-gray-100\/10:hover{background-color:#ebeff41a!important}.hover\:bg-gray-300:hover{--tw-bg-opacity:1!important;background-color:rgb(210 214 218/var(--tw-bg-opacity))!important}.hover\:bg-gray-500:hover{--tw-bg-opacity:1!important;background-color:rgb(173 181 189/var(--tw-bg-opacity))!important}.hover\:bg-gray-500\/80:hover{background-color:#adb5bdcc!important}.hover\:bg-gray-600:hover{--tw-bg-opacity:1!important;background-color:rgb(108 117 125/var(--tw-bg-opacity))!important}.hover\:bg-gray-600\/80:hover{background-color:#6c757dcc!important}.hover\:bg-green-500:hover{--tw-bg-opacity:1!important;background-color:rgb(34 197 94/var(--tw-bg-opacity))!important}.hover\:bg-green-500\/80:hover{background-color:#22c55ecc!important}.hover\:bg-orange-500:hover{--tw-bg-opacity:1!important;background-color:rgb(251 99 64/var(--tw-bg-opacity))!important}.hover\:bg-orange-500\/80:hover{background-color:#fb6340cc!important}.hover\:bg-primary\/30:hover{background-color:#0b55774d!important}.hover\:bg-primary\/5:hover{background-color:#0b55770d!important}.hover\:bg-primary\/80:hover{background-color:#0b5577cc!important}.hover\:bg-red-500:hover{--tw-bg-opacity:1!important;background-color:rgb(245 57 57/var(--tw-bg-opacity))!important}.hover\:bg-red-500\/80:hover{background-color:#f53939cc!important}.hover\:bg-sky-500:hover{--tw-bg-opacity:1!important;background-color:rgb(14 165 233/var(--tw-bg-opacity))!important}.hover\:bg-sky-500\/80:hover{background-color:#0ea5e9cc!important}.hover\:bg-yellow-400:hover{--tw-bg-opacity:1!important;background-color:rgb(251 207 51/var(--tw-bg-opacity))!important}.hover\:bg-yellow-400\/80:hover{background-color:#fbcf33cc!important}.hover\:bg-yellow-500:hover{--tw-bg-opacity:1!important;background-color:rgb(251 177 64/var(--tw-bg-opacity))!important}.hover\:bg-yellow-500\/80:hover{background-color:#fbb140cc!important}.hover\:italic:hover{font-style:italic!important}.hover\:no-underline:hover{text-decoration-line:none!important}.hover\:opacity-80:hover{opacity:.8!important}.hover\:shadow-md:hover{--tw-shadow:0 4px 6px #32325d1a,0 1px 3px #00000014!important;--tw-shadow-colored:0 4px 6px var(--tw-shadow-color),0 1px 3px var(--tw-shadow-color)!important;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)!important}.hover\:brightness-75:hover{--tw-brightness:brightness(.75)!important}.hover\:brightness-75:hover,.hover\:brightness-90:hover{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}.hover\:brightness-90:hover{--tw-brightness:brightness(.9)!important}.hover\:brightness-95:hover{--tw-brightness:brightness(.95)!important;filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}.focus\:\!border-red-500:focus{--tw-border-opacity:1!important;border-color:rgb(245 57 57/var(--tw-border-opacity))!important}.focus\:border-green-500:focus{--tw-border-opacity:1!important;border-color:rgb(34 197 94/var(--tw-border-opacity))!important}.focus\:border-primary:focus{--tw-border-opacity:1!important;border-color:rgb(11 85 119/var(--tw-border-opacity))!important}.focus\:bg-emerald-500:focus{--tw-bg-opacity:1!important;background-color:rgb(45 206 137/var(--tw-bg-opacity))!important}.focus\:bg-emerald-500\/80:focus{background-color:#2dce89cc!important}.focus\:bg-gray-500:focus{--tw-bg-opacity:1!important;background-color:rgb(173 181 189/var(--tw-bg-opacity))!important}.focus\:bg-gray-500\/80:focus{background-color:#adb5bdcc!important}.focus\:bg-gray-600:focus{--tw-bg-opacity:1!important;background-color:rgb(108 117 125/var(--tw-bg-opacity))!important}.focus\:bg-gray-600\/80:focus{background-color:#6c757dcc!important}.focus\:bg-green-500:focus{--tw-bg-opacity:1!important;background-color:rgb(34 197 94/var(--tw-bg-opacity))!important}.focus\:bg-green-500\/80:focus{background-color:#22c55ecc!important}.focus\:bg-orange-500:focus{--tw-bg-opacity:1!important;background-color:rgb(251 99 64/var(--tw-bg-opacity))!important}.focus\:bg-orange-500\/80:focus{background-color:#fb6340cc!important}.focus\:bg-primary\/80:focus{background-color:#0b5577cc!important}.focus\:bg-red-500:focus{--tw-bg-opacity:1!important;background-color:rgb(245 57 57/var(--tw-bg-opacity))!important}.focus\:bg-red-500\/80:focus{background-color:#f53939cc!important}.focus\:bg-sky-500:focus{--tw-bg-opacity:1!important;background-color:rgb(14 165 233/var(--tw-bg-opacity))!important}.focus\:bg-sky-500\/80:focus{background-color:#0ea5e9cc!important}.focus\:bg-yellow-400:focus{--tw-bg-opacity:1!important;background-color:rgb(251 207 51/var(--tw-bg-opacity))!important}.focus\:bg-yellow-400\/80:focus{background-color:#fbcf33cc!important}.focus\:bg-yellow-500:focus{--tw-bg-opacity:1!important;background-color:rgb(251 177 64/var(--tw-bg-opacity))!important}.focus\:bg-yellow-500\/80:focus{background-color:#fbb140cc!important}.focus\:outline:focus{outline-style:solid!important}.focus\:\!ring-red-500:focus{--tw-ring-opacity:1!important;--tw-ring-color:rgb(245 57 57/var(--tw-ring-opacity))!important}.focus\:valid\:\!border-red-500:valid:focus{--tw-border-opacity:1!important;border-color:rgb(245 57 57/var(--tw-border-opacity))!important}.focus\:valid\:border-green-500:valid:focus{--tw-border-opacity:1!important;border-color:rgb(34 197 94/var(--tw-border-opacity))!important}.focus\:valid\:\!ring-red-500:valid:focus{--tw-ring-opacity:1!important;--tw-ring-color:rgb(245 57 57/var(--tw-ring-opacity))!important}.focus\:invalid\:border-red-500:invalid:focus{--tw-border-opacity:1!important;border-color:rgb(245 57 57/var(--tw-border-opacity))!important}.focus\:file\:invalid\:border-red-500:invalid::file-selector-button:focus{--tw-border-opacity:1!important;border-color:rgb(245 57 57/var(--tw-border-opacity))!important}.active\:\!border-red-500:active{--tw-border-opacity:1!important;border-color:rgb(245 57 57/var(--tw-border-opacity))!important}.active\:opacity-85:active{opacity:.85!important}.active\:valid\:\!border-red-500:valid:active{--tw-border-opacity:1!important;border-color:rgb(245 57 57/var(--tw-border-opacity))!important}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed!important}.disabled\:border-gray-400:disabled{--tw-border-opacity:1!important;border-color:rgb(206 212 218/var(--tw-border-opacity))!important}.disabled\:border-gray-400\/0:disabled{border-color:#ced4da00!important}.disabled\:bg-gray-400:disabled{--tw-bg-opacity:1!important;background-color:rgb(206 212 218/var(--tw-bg-opacity))!important}.disabled\:text-gray-700:disabled{--tw-text-opacity:1!important;color:rgb(73 80 87/var(--tw-text-opacity))!important}.disabled\:opacity-75:disabled{opacity:.75!important}.disabled\:hover\:translate-y-0:hover:disabled{--tw-translate-y:0px!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.disabled\:hover\:border-gray-400\/0:hover:disabled{border-color:#ced4da00!important}.disabled\:hover\:bg-gray-400:hover:disabled{--tw-bg-opacity:1!important;background-color:rgb(206 212 218/var(--tw-bg-opacity))!important}.group:hover .group-hover\:z-10{z-index:10!important}.group:hover .group-hover\:opacity-100{opacity:1!important}:is(.dark .dark\:inline){display:inline!important}:is(.dark .dark\:hidden){display:none!important}:is(.dark .dark\:border-gray-200){--tw-border-opacity:1!important;border-color:rgb(233 236 239/var(--tw-border-opacity))!important}:is(.dark .dark\:border-gray-300){--tw-border-opacity:1!important;border-color:rgb(210 214 218/var(--tw-border-opacity))!important}:is(.dark .dark\:border-gray-700){--tw-border-opacity:1!important;border-color:rgb(73 80 87/var(--tw-border-opacity))!important}:is(.dark .dark\:border-slate-600){--tw-border-opacity:1!important;border-color:rgb(98 117 148/var(--tw-border-opacity))!important}:is(.dark .dark\:border-slate-800){--tw-border-opacity:1!important;border-color:rgb(58 65 111/var(--tw-border-opacity))!important}:is(.dark .dark\:bg-gray-400){--tw-bg-opacity:1!important;background-color:rgb(206 212 218/var(--tw-bg-opacity))!important}:is(.dark .dark\:bg-gray-800){--tw-bg-opacity:1!important;background-color:rgb(37 47 64/var(--tw-bg-opacity))!important}:is(.dark .dark\:bg-green-500\/90){background-color:#22c55ee6!important}:is(.dark .dark\:bg-primary){--tw-bg-opacity:1!important;background-color:rgb(11 85 119/var(--tw-bg-opacity))!important}:is(.dark .dark\:bg-primary\/50){background-color:#0b557780!important}:is(.dark .dark\:bg-red-500\/90){background-color:#f53939e6!important}:is(.dark .dark\:bg-slate-700){--tw-bg-opacity:1!important;background-color:rgb(52 71 103/var(--tw-bg-opacity))!important}:is(.dark .dark\:bg-slate-700\/50){background-color:#34476780!important}:is(.dark .dark\:bg-slate-800){--tw-bg-opacity:1!important;background-color:rgb(58 65 111/var(--tw-bg-opacity))!important}:is(.dark .dark\:bg-slate-850){--tw-bg-opacity:1!important;background-color:rgb(17 28 68/var(--tw-bg-opacity))!important}:is(.dark .dark\:bg-slate-900){--tw-bg-opacity:1!important;background-color:rgb(5 17 57/var(--tw-bg-opacity))!important}:is(.dark .dark\:bg-slate-900\/30){background-color:#0511394d!important}:is(.dark .dark\:bg-gradient-to-r){background-image:linear-gradient(to right,var(--tw-gradient-stops))!important}:is(.dark .dark\:from-transparent){--tw-gradient-from:#0000 var(--tw-gradient-from-position)!important;--tw-gradient-to:#0000 var(--tw-gradient-to-position)!important;--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)!important}:is(.dark .dark\:via-white){--tw-gradient-to:#fff0 var(--tw-gradient-to-position)!important;--tw-gradient-stops:var(--tw-gradient-from),#fff var(--tw-gradient-via-position),var(--tw-gradient-to)!important}:is(.dark .dark\:to-transparent){--tw-gradient-to:#0000 var(--tw-gradient-to-position)!important}:is(.dark .dark\:fill-blue-500){fill:#5e72e4!important}:is(.dark .dark\:fill-gray-300){fill:#d2d6da!important}:is(.dark .dark\:fill-gray-500){fill:#adb5bd!important}:is(.dark .dark\:fill-gray-600){fill:#6c757d!important}:is(.dark .dark\:stroke-amber-500){stroke:#f59e0b!important}:is(.dark .dark\:stroke-gray-300){stroke:#d2d6da!important}:is(.dark .dark\:stroke-gray-400){stroke:#ced4da!important}:is(.dark .dark\:stroke-gray-600){stroke:#6c757d!important}:is(.dark .dark\:stroke-red-500){stroke:#f53939!important}:is(.dark .dark\:stroke-white\/90){stroke:#ffffffe6!important}:is(.dark .dark\:text-gray-100){--tw-text-opacity:1!important;color:rgb(235 239 244/var(--tw-text-opacity))!important}:is(.dark .dark\:text-gray-200){--tw-text-opacity:1!important;color:rgb(233 236 239/var(--tw-text-opacity))!important}:is(.dark .dark\:text-gray-300){--tw-text-opacity:1!important;color:rgb(210 214 218/var(--tw-text-opacity))!important}:is(.dark .dark\:text-gray-400){--tw-text-opacity:1!important;color:rgb(206 212 218/var(--tw-text-opacity))!important}:is(.dark .dark\:text-gray-50){--tw-text-opacity:1!important;color:rgb(248 249 250/var(--tw-text-opacity))!important}:is(.dark .dark\:text-gray-500){--tw-text-opacity:1!important;color:rgb(173 181 189/var(--tw-text-opacity))!important}:is(.dark .dark\:text-white){--tw-text-opacity:1!important;color:rgb(255 255 255/var(--tw-text-opacity))!important}:is(.dark .dark\:text-white\/80){color:#fffc!important}:is(.dark .dark\:text-white\/90){color:#ffffffe6!important}:is(.dark .dark\:opacity-100){opacity:1!important}:is(.dark .dark\:opacity-80){opacity:.8!important}:is(.dark .dark\:opacity-90){opacity:.9!important}:is(.dark .dark\:shadow-dark-xl){--tw-shadow:0 2px 2px 0 #00000024,0 3px 1px -2px #0003,0 1px 5px 0 #0000001f!important;--tw-shadow-colored:0 2px 2px 0 var(--tw-shadow-color),0 3px 1px -2px var(--tw-shadow-color),0 1px 5px 0 var(--tw-shadow-color)!important}:is(.dark .dark\:shadow-dark-xl),:is(.dark .dark\:shadow-none){box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)!important}:is(.dark .dark\:shadow-none){--tw-shadow:0 0 #0000!important;--tw-shadow-colored:0 0 #0000!important}:is(.dark .dark\:brightness-110){--tw-brightness:brightness(1.1)!important}:is(.dark .dark\:brightness-110),:is(.dark .dark\:brightness-125){filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}:is(.dark .dark\:brightness-125){--tw-brightness:brightness(1.25)!important}:is(.dark .dark\:brightness-90){--tw-brightness:brightness(.9)!important}:is(.dark .dark\:brightness-90),:is(.dark .dark\:brightness-95){filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}:is(.dark .dark\:brightness-95){--tw-brightness:brightness(.95)!important}:is(.dark .dark\:brightness-\[0\.885\]){--tw-brightness:brightness(0.885)!important;filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}:is(.dark .dark\:placeholder\:text-gray-600)::-moz-placeholder{--tw-text-opacity:1!important;color:rgb(108 117 125/var(--tw-text-opacity))!important}:is(.dark .dark\:placeholder\:text-gray-600)::placeholder{--tw-text-opacity:1!important;color:rgb(108 117 125/var(--tw-text-opacity))!important}:is(.dark .dark\:after\:text-gray-300):after{content:var(--tw-content)!important;--tw-text-opacity:1!important;color:rgb(210 214 218/var(--tw-text-opacity))!important}:is(.dark .dark\:after\:text-gray-500):after{content:var(--tw-content)!important;--tw-text-opacity:1!important;color:rgb(173 181 189/var(--tw-text-opacity))!important}:is(.dark .dark\:checked\:border-primary:checked){--tw-border-opacity:1!important;border-color:rgb(11 85 119/var(--tw-border-opacity))!important}:is(.dark .dark\:checked\:bg-primary:checked){--tw-bg-opacity:1!important;background-color:rgb(11 85 119/var(--tw-bg-opacity))!important}:is(.dark .dark\:hover\:bg-primary\/20:hover){background-color:#0b557733!important}:is(.dark .dark\:hover\:bg-primary\/60:hover){background-color:#0b557799!important}:is(.dark .dark\:hover\:bg-slate-700\/50:hover){background-color:#34476780!important}:is(.dark .dark\:hover\:bg-slate-800:hover){--tw-bg-opacity:1!important;background-color:rgb(58 65 111/var(--tw-bg-opacity))!important}:is(.dark .dark\:hover\:brightness-100:hover){--tw-brightness:brightness(1)!important}:is(.dark .dark\:hover\:brightness-100:hover),:is(.dark .dark\:hover\:brightness-105:hover){filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}:is(.dark .dark\:hover\:brightness-105:hover){--tw-brightness:brightness(1.05)!important}:is(.dark .dark\:hover\:brightness-110:hover){--tw-brightness:brightness(1.1)!important}:is(.dark .dark\:hover\:brightness-110:hover),:is(.dark .dark\:hover\:brightness-90:hover){filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}:is(.dark .dark\:hover\:brightness-90:hover){--tw-brightness:brightness(.9)!important}:is(.dark .dark\:disabled\:border-gray-700\/0:disabled){border-color:#49505700!important}:is(.dark .dark\:disabled\:border-gray-800:disabled){--tw-border-opacity:1!important;border-color:rgb(37 47 64/var(--tw-border-opacity))!important}:is(.dark .dark\:disabled\:bg-gray-700:disabled){--tw-bg-opacity:1!important;background-color:rgb(73 80 87/var(--tw-bg-opacity))!important}:is(.dark .dark\:disabled\:bg-gray-800:disabled){--tw-bg-opacity:1!important;background-color:rgb(37 47 64/var(--tw-bg-opacity))!important}:is(.dark .dark\:disabled\:text-gray-300:disabled){--tw-text-opacity:1!important;color:rgb(210 214 218/var(--tw-text-opacity))!important}:is(.dark .dark\:disabled\:text-gray-400:disabled){--tw-text-opacity:1!important;color:rgb(206 212 218/var(--tw-text-opacity))!important}:is(.dark .dark\:disabled\:hover\:translate-y-0:hover:disabled){--tw-translate-y:0px!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}:is(.dark .dark\:disabled\:hover\:border-gray-700\/0:hover:disabled){border-color:#49505700!important}:is(.dark .dark\:disabled\:hover\:bg-gray-700:hover:disabled){--tw-bg-opacity:1!important;background-color:rgb(73 80 87/var(--tw-bg-opacity))!important}@media (min-width:576px){.sm\:right-24{right:6rem!important}.sm\:right-40{right:10rem!important}.sm\:right-6{right:1.5rem!important}.sm\:top-2{top:.5rem!important}.sm\:top-8{top:2rem!important}.sm\:top-\[4\.5rem\]{top:4.5rem!important}.sm\:col-span-4{grid-column:span 4/span 4!important}.sm\:col-span-6{grid-column:span 6/span 6!important}.sm\:col-start-5{grid-column-start:5!important}.sm\:mx-2{margin-left:.5rem!important;margin-right:.5rem!important}.sm\:mx-6{margin-left:1.5rem!important;margin-right:1.5rem!important}.sm\:my-0{margin-top:0!important;margin-bottom:0!important}.sm\:mb-2{margin-bottom:.5rem!important}.sm\:ml-1{margin-left:.25rem!important}.sm\:ml-4{margin-left:1rem!important}.sm\:mr-16{margin-right:4rem!important}.sm\:block{display:block!important}.sm\:inline{display:inline!important}.sm\:flex{display:flex!important}.sm\:h-10{height:2.5rem!important}.sm\:h-14{height:3.5rem!important}.sm\:h-7{height:1.75rem!important}.sm\:max-h-125{max-height:31.25rem!important}.sm\:w-36{width:9rem!important}.sm\:w-50{width:12.5rem!important}.sm\:w-7{width:1.75rem!important}.sm\:min-w-\[250px\]{min-width:250px!important}.sm\:min-w-\[500px\]{min-width:500px!important}.sm\:max-w-\[350px\]{max-width:350px!important}.sm\:translate-x-0{--tw-translate-x:0px!important}.sm\:scale-100,.sm\:translate-x-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.sm\:scale-100{--tw-scale-x:1!important;--tw-scale-y:1!important}.sm\:flex-row{flex-direction:row!important}.sm\:items-center{align-items:center!important}.sm\:justify-end{justify-content:flex-end!important}.sm\:justify-items-start{justify-items:start!important}.sm\:gap-4{gap:1rem!important}.sm\:p-3{padding:.75rem!important}.sm\:px-12{padding-left:3rem!important;padding-right:3rem!important}.sm\:px-4{padding-left:1rem!important;padding-right:1rem!important}.sm\:px-6{padding-left:1.5rem!important;padding-right:1.5rem!important}.sm\:pt-3{padding-top:.75rem!important}.sm\:text-left{text-align:left!important}.sm\:text-2xl{font-size:1.5rem!important;line-height:2rem!important}.sm\:text-4xl{font-size:2.25rem!important;line-height:2.5rem!important}.sm\:text-7xl{font-size:4.5rem!important;line-height:1!important}.sm\:text-base{font-size:1rem!important}.sm\:text-base,.sm\:text-sm{line-height:1.5rem!important}.sm\:text-sm{font-size:.875rem!important}}@media (min-width:768px){.md\:absolute{position:absolute!important}.md\:right-8{right:2rem!important}.md\:right-\[3\.75rem\]{right:3.75rem!important}.md\:top-\[40\%\]{top:40%!important}.md\:top-\[53\%\]{top:53%!important}.md\:col-span-4{grid-column:span 4/span 4!important}.md\:col-span-6{grid-column:span 6/span 6!important}.md\:col-span-7{grid-column:span 7/span 7!important}.md\:col-span-8{grid-column:span 8/span 8!important}.md\:mx-3{margin-left:.75rem!important;margin-right:.75rem!important}.md\:mx-4{margin-left:1rem!important;margin-right:1rem!important}.md\:my-3{margin-top:.75rem!important;margin-bottom:.75rem!important}.md\:mb-0{margin-bottom:0!important}.md\:mb-3{margin-bottom:.75rem!important}.md\:mr-3{margin-right:.75rem!important}.md\:mt-0{margin-top:0!important}.md\:mt-6{margin-top:1.5rem!important}.md\:hidden{display:none!important}.md\:h-16{height:4rem!important}.md\:max-h-\[90vh\]{max-height:90vh!important}.md\:min-h-50-screen{min-height:50vh!important}.md\:w-1\/2{width:50%!important}.md\:w-60{width:15rem!important}.md\:max-w-\[700px\]{max-width:700px!important}.md\:-translate-y-20{--tw-translate-y:-5rem!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.md\:flex-row{flex-direction:row!important}.md\:items-end{align-items:flex-end!important}.md\:gap-x-4{-moz-column-gap:1rem!important;column-gap:1rem!important}.md\:gap-x-6{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.md\:px-1{padding-left:.25rem!important;padding-right:.25rem!important}.md\:px-3{padding-left:.75rem!important;padding-right:.75rem!important}.md\:px-4{padding-left:1rem!important;padding-right:1rem!important}.md\:px-6{padding-left:1.5rem!important;padding-right:1.5rem!important}.md\:py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.md\:text-base{font-size:1rem!important;line-height:1.5rem!important}}@media (min-width:992px){.lg\:relative{position:relative!important}.lg\:bottom-2{bottom:.5rem!important}.lg\:left-48{left:12rem!important}.lg\:top-24{top:6rem!important}.lg\:order-1{order:1!important}.lg\:order-2{order:2!important}.lg\:col-span-1{grid-column:span 1/span 1!important}.lg\:col-span-4{grid-column:span 4/span 4!important}.lg\:col-span-6{grid-column:span 6/span 6!important}.lg\:col-span-8{grid-column:span 8/span 8!important}.lg\:mx-0{margin-left:0!important;margin-right:0!important}.lg\:mx-4{margin-left:1rem!important;margin-right:1rem!important}.lg\:mx-8{margin-left:2rem!important;margin-right:2rem!important}.lg\:my-3{margin-top:.75rem!important;margin-bottom:.75rem!important}.lg\:mt-0{margin-top:0!important}.lg\:mt-4{margin-top:1rem!important}.lg\:mt-8{margin-top:2rem!important}.lg\:block{display:block!important}.lg\:inline{display:inline!important}.lg\:flex{display:flex!important}.lg\:hidden{display:none!important}.lg\:h-24{height:6rem!important}.lg\:h-36{height:9rem!important}.lg\:h-9{height:2.25rem!important}.lg\:max-h-\[550px\]{max-height:550px!important}.lg\:w-36{width:9rem!important}.lg\:w-80{width:20rem!important}.lg\:w-9{width:2.25rem!important}.lg\:w-\[400px\]{width:400px!important}.lg\:max-w-\[700px\]{max-width:700px!important}.lg\:translate-x-0{--tw-translate-x:0px!important}.lg\:translate-x-0,.lg\:translate-y-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.lg\:translate-y-0{--tw-translate-y:0px!important}.lg\:flex-row{flex-direction:row!important}.lg\:flex-nowrap{flex-wrap:nowrap!important}.lg\:justify-start{justify-content:flex-start!important}.lg\:justify-end{justify-content:flex-end!important}.lg\:justify-between{justify-content:space-between!important}.lg\:gap-6{gap:1.5rem!important}.lg\:bg-gray-50{--tw-bg-opacity:1!important;background-color:rgb(248 249 250/var(--tw-bg-opacity))!important}.lg\:px-6{padding-left:1.5rem!important;padding-right:1.5rem!important}.lg\:pb-1{padding-bottom:.25rem!important}.lg\:pb-28{padding-bottom:7rem!important}.lg\:pt-6{padding-top:1.5rem!important}.lg\:text-left{text-align:left!important}.lg\:text-base{font-size:1rem!important}.lg\:text-base,.lg\:text-sm{line-height:1.5rem!important}.lg\:text-sm{font-size:.875rem!important}}@media (min-width:1200px){.xl\:left-0{left:0!important}.xl\:right-24{right:6rem!important}.xl\:right-6{right:1.5rem!important}.xl\:ml-6{margin-left:1.5rem!important}.xl\:ml-68{margin-left:17rem!important}.xl\:hidden{display:none!important}.xl\:h-44{height:11rem!important}.xl\:max-h-\[550px\]{max-height:550px!important}.xl\:w-1\/3{width:33.333333%!important}.xl\:w-44{width:11rem!important}.xl\:w-\[500px\]{width:500px!important}.xl\:max-w-\[1200px\]{max-width:1200px!important}.xl\:translate-x-0{--tw-translate-x:0px!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.xl\:p-1{padding:.25rem!important}.xl\:p-1\.5{padding:.375rem!important}.xl\:pl-75{padding-left:18.75rem!important}.xl\:text-base{font-size:1rem!important;line-height:1.5rem!important}}@media (min-width:1320px){.\32xl\:col-span-4{grid-column:span 4/span 4!important}.\32xl\:col-span-6{grid-column:span 6/span 6!important}.\32xl\:my-3{margin-top:.75rem!important;margin-bottom:.75rem!important}.\32xl\:max-w-\[1500px\]{max-width:1500px!important}.\32xl\:text-3xl{font-size:1.875rem!important;line-height:2.25rem!important}.\32xl\:text-5xl{font-size:3rem!important;line-height:1!important}.\32xl\:text-8xl{font-size:5rem!important;line-height:1!important}.\32xl\:text-lg{font-size:1.125rem!important;line-height:1.75rem!important}}@media (min-width:1920px){.\33xl\:col-span-3{grid-column:span 3/span 3!important}.\33xl\:col-span-4{grid-column:span 4/span 4!important}.\33xl\:col-span-5{grid-column:span 5/span 5!important}.\33xl\:inline{display:inline!important}.\33xl\:max-w-none{max-width:none!important}.\33xl\:translate-x-60{--tw-translate-x:15rem!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.\33xl\:text-6xl{font-size:3.75rem!important;line-height:1!important}.\33xl\:text-9xl{font-size:6rem!important;line-height:1!important}.\33xl\:text-xl{font-size:1.25rem!important;line-height:1.75rem!important}}@media (min-width:340px){.xs\:flex-row{flex-direction:row!important}.xs\:items-center{align-items:center!important}.xs\:justify-start{justify-content:flex-start!important}.xs\:pl-2{padding-left:.5rem!important}.xs\:text-base{font-size:1rem!important}.xs\:text-base,.xs\:text-sm{line-height:1.5rem!important}.xs\:text-sm{font-size:.875rem!important}}.\[\&\>\*\]\:bg-primary>*{--tw-bg-opacity:1!important;background-color:rgb(11 85 119/var(--tw-bg-opacity))!important} \ No newline at end of file diff --git a/src/ui/static/js/global.js b/src/ui/static/js/global.js index 10251881b..1289d016a 100644 --- a/src/ui/static/js/global.js +++ b/src/ui/static/js/global.js @@ -557,43 +557,73 @@ class Clipboard { } catch (e) {} // With Firefox try { - if (this.isCopy) return; - /* write to the clipboard now */ - const copyEl = document.querySelector( - e.target.getAttribute("data-clipboard-target") - ); + if (!this.isCopy) { + /* write to the clipboard now */ + const copyEl = document.querySelector( + e.target.getAttribute("data-clipboard-target") + ); - copyEl.select(); - copyEl.setSelectionRange(0, 99999); // For mobile devices + copyEl.select(); + copyEl.setSelectionRange(0, 99999); // For mobile devices - // Copy the text inside the text field + // Copy the text inside the text field - navigator.clipboard.writeText(copyEl.value); - // Stop selecting - copyEl.blur(); - this.isCopy = true; + navigator.clipboard.writeText(copyEl.value); + // Stop selecting + copyEl.blur(); + this.isCopy = true; + } } catch (e) {} // Default try { - if (this.isCopy) return; - /* write to the clipboard now */ - const copyEl = document.querySelector( - e.target.getAttribute("data-clipboard-target") - ); + if (!this.isCopy) { + /* write to the clipboard now */ + const copyEl = document.querySelector( + e.target.getAttribute("data-clipboard-target") + ); - copyEl.select(); - copyEl.setSelectionRange(0, 99999); // For mobile devices + copyEl.select(); + copyEl.setSelectionRange(0, 99999); // For mobile devices - // Copy the text inside the text field + // Copy the text inside the text field - navigator.clipboard.writeText(copyEl.value); - // Stop selecting - copyEl.blur(); + navigator.clipboard.writeText(copyEl.value); + // Stop selecting - document.execCommand("copy"); + document.execCommand("copy"); + copyEl.blur(); - this.isCopy = true; + this.isCopy = true; + } } catch (e) {} + + // Show feedback + const btn = e.target.closest("[data-clipboard-copy]"); + const feedbackEl = document.createElement("div"); + feedbackEl.classList.add( + "absolute", + "top-0", + "right-0", + "p-1", + "text-white", + "text-xs", + "rounded", + "opacity-0", + "transition-opacity", + "duration-300", + this.isCopy ? "bg-green-500" : "bg-red-500" + ); + feedbackEl.textContent = this.isCopy ? "Copied!" : "Error!"; + btn.appendChild(feedbackEl); + setTimeout(() => { + feedbackEl.classList.remove("opacity-0"); + }, 50); + setTimeout(() => { + feedbackEl.classList.add("opacity-0"); + }, 1200); + setTimeout(() => { + feedbackEl.remove(); + }, 1550); }); } } diff --git a/src/ui/static/js/lottie-web.min.js b/src/ui/static/js/lottie-web.min.js index db3599b9c..1ac0ab655 100644 --- a/src/ui/static/js/lottie-web.min.js +++ b/src/ui/static/js/lottie-web.min.js @@ -1 +1 @@ -"undefined"!=typeof navigator&&function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).lottie=e()}(this,(function(){"use strict";var svgNS="http://www.w3.org/2000/svg",locationHref="",_useWebWorker=!1,initialDefaultFrame=-999999,setWebWorker=function(t){_useWebWorker=!!t},getWebWorker=function(){return _useWebWorker},setLocationHref=function(t){locationHref=t},getLocationHref=function(){return locationHref};function createTag(t){return document.createElement(t)}function extendPrototype(t,e){var i,r,s=t.length;for(i=0;i1?i[1]=1:i[1]<=0&&(i[1]=0),HSVtoRGB(i[0],i[1],i[2])}function addBrightnessToRGB(t,e){var i=RGBtoHSV(255*t[0],255*t[1],255*t[2]);return i[2]+=e,i[2]>1?i[2]=1:i[2]<0&&(i[2]=0),HSVtoRGB(i[0],i[1],i[2])}function addHueToRGB(t,e){var i=RGBtoHSV(255*t[0],255*t[1],255*t[2]);return i[0]+=e/360,i[0]>1?i[0]-=1:i[0]<0&&(i[0]+=1),HSVtoRGB(i[0],i[1],i[2])}var rgbToHex=function(){var t,e,i=[];for(t=0;t<256;t+=1)e=t.toString(16),i[t]=1===e.length?"0"+e:e;return function(t,e,r){return t<0&&(t=0),e<0&&(e=0),r<0&&(r=0),"#"+i[t]+i[e]+i[r]}}(),setSubframeEnabled=function(t){subframeEnabled=!!t},getSubframeEnabled=function(){return subframeEnabled},setExpressionsPlugin=function(t){expressionsPlugin=t},getExpressionsPlugin=function(){return expressionsPlugin},setExpressionInterfaces=function(t){expressionsInterfaces=t},getExpressionInterfaces=function(){return expressionsInterfaces},setDefaultCurveSegments=function(t){defaultCurveSegments=t},getDefaultCurveSegments=function(){return defaultCurveSegments},setIdPrefix=function(t){idPrefix$1=t},getIdPrefix=function(){return idPrefix$1};function createNS(t){return document.createElementNS(svgNS,t)}function _typeof$5(t){return _typeof$5="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},_typeof$5(t)}var dataManager=function(){var t,e,i=1,r=[],s={onmessage:function(){},postMessage:function(e){t({data:e})}},a={postMessage:function(t){s.onmessage({data:t})}};function n(){e||(e=function(e){if(window.Worker&&window.Blob&&getWebWorker()){var i=new Blob(["var _workerSelf = self; self.onmessage = ",e.toString()],{type:"text/javascript"}),r=URL.createObjectURL(i);return new Worker(r)}return t=e,s}((function(t){if(a.dataManager||(a.dataManager=function(){function t(s,a){var n,o,h,l,p,m,c=s.length;for(o=0;o=0;e-=1)if("sh"===t[e].ty)if(t[e].ks.k.i)r(t[e].ks.k);else for(a=t[e].ks.k.length,s=0;si[0]||!(i[0]>t[0])&&(t[1]>i[1]||!(i[1]>t[1])&&(t[2]>i[2]||!(i[2]>t[2])&&null))}var a,n=function(){var t=[4,4,14];function e(t){var e,i,r,s=t.length;for(e=0;e=0;i-=1)if("sh"===t[i].ty)if(t[i].ks.k.i)t[i].ks.k.c=t[i].closed;else for(s=t[i].ks.k.length,r=0;r500)&&(this._imageLoaded(),clearInterval(i)),e+=1}.bind(this),50)}function a(t){var e={assetData:t},i=r(t,this.assetsPath,this.path);return dataManager.loadData(i,function(t){e.img=t,this._footageLoaded()}.bind(this),function(){e.img={},this._footageLoaded()}.bind(this)),e}function n(){this._imageLoaded=e.bind(this),this._footageLoaded=i.bind(this),this.testImageLoaded=s.bind(this),this.createFootageData=a.bind(this),this.assetsPath="",this.path="",this.totalImages=0,this.totalFootages=0,this.loadedAssets=0,this.loadedFootagesCount=0,this.imagesLoadedCb=null,this.images=[]}return n.prototype={loadAssets:function(t,e){var i;this.imagesLoadedCb=e;var r=t.length;for(i=0;ithis.animationData.op&&(this.animationData.op=t.op,this.totalFrames=Math.floor(t.op-this.animationData.ip));var e,i,r=this.animationData.layers,s=r.length,a=t.layers,n=a.length;for(i=0;ithis.timeCompleted&&(this.currentFrame=this.timeCompleted),this.trigger("enterFrame"),this.renderFrame(),this.trigger("drawnFrame")},AnimationItem.prototype.renderFrame=function(){if(!1!==this.isLoaded&&this.renderer)try{this.expressionsPlugin&&this.expressionsPlugin.resetFrame(),this.renderer.renderFrame(this.currentFrame+this.firstFrame)}catch(t){this.triggerRenderFrameError(t)}},AnimationItem.prototype.play=function(t){t&&this.name!==t||!0===this.isPaused&&(this.isPaused=!1,this.trigger("_play"),this.audioController.resume(),this._idle&&(this._idle=!1,this.trigger("_active")))},AnimationItem.prototype.pause=function(t){t&&this.name!==t||!1===this.isPaused&&(this.isPaused=!0,this.trigger("_pause"),this._idle=!0,this.trigger("_idle"),this.audioController.pause())},AnimationItem.prototype.togglePause=function(t){t&&this.name!==t||(!0===this.isPaused?this.play():this.pause())},AnimationItem.prototype.stop=function(t){t&&this.name!==t||(this.pause(),this.playCount=0,this._completedLoop=!1,this.setCurrentRawFrameValue(0))},AnimationItem.prototype.getMarkerData=function(t){for(var e,i=0;i=this.totalFrames-1&&this.frameModifier>0?this.loop&&this.playCount!==this.loop?e>=this.totalFrames?(this.playCount+=1,this.checkSegments(e%this.totalFrames)||(this.setCurrentRawFrameValue(e%this.totalFrames),this._completedLoop=!0,this.trigger("loopComplete"))):this.setCurrentRawFrameValue(e):this.checkSegments(e>this.totalFrames?e%this.totalFrames:0)||(i=!0,e=this.totalFrames-1):e<0?this.checkSegments(e%this.totalFrames)||(!this.loop||this.playCount--<=0&&!0!==this.loop?(i=!0,e=0):(this.setCurrentRawFrameValue(this.totalFrames+e%this.totalFrames),this._completedLoop?this.trigger("loopComplete"):this._completedLoop=!0)):this.setCurrentRawFrameValue(e),i&&(this.setCurrentRawFrameValue(e),this.pause(),this.trigger("complete"))}},AnimationItem.prototype.adjustSegment=function(t,e){this.playCount=0,t[1]0&&(this.playSpeed<0?this.setSpeed(-this.playSpeed):this.setDirection(-1)),this.totalFrames=t[0]-t[1],this.timeCompleted=this.totalFrames,this.firstFrame=t[1],this.setCurrentRawFrameValue(this.totalFrames-.001-e)):t[1]>t[0]&&(this.frameModifier<0&&(this.playSpeed<0?this.setSpeed(-this.playSpeed):this.setDirection(1)),this.totalFrames=t[1]-t[0],this.timeCompleted=this.totalFrames,this.firstFrame=t[0],this.setCurrentRawFrameValue(.001+e)),this.trigger("segmentStart")},AnimationItem.prototype.setSegment=function(t,e){var i=-1;this.isPaused&&(this.currentRawFrame+this.firstFramee&&(i=e-t)),this.firstFrame=t,this.totalFrames=e-t,this.timeCompleted=this.totalFrames,-1!==i&&this.goToAndStop(i,!0)},AnimationItem.prototype.playSegments=function(t,e){if(e&&(this.segments.length=0),"object"===_typeof$4(t[0])){var i,r=t.length;for(i=0;i=0;i-=1)e[i].animation.destroy(t)},t.freeze=function(){n=!0},t.unfreeze=function(){n=!1,d()},t.setVolume=function(t,i){var s;for(s=0;s=.001?function(t,e,i,r){for(var s=0;s<4;++s){var a=h(e,i,r);if(0===a)return e;e-=(o(e,i,r)-t)/a}return e}(t,l,e,r):0===p?l:function(t,e,i,r,s){var a,n,h=0;do{(a=o(n=e+(i-e)/2,r,s)-t)>0?i=n:e=n}while(Math.abs(a)>1e-7&&++h<10);return n}(t,a,a+i,e,r)}},t}(),pooling={double:function(t){return t.concat(createSizedArray(t.length))}},poolFactory=function(t,e,i){var r=0,s=t,a=createSizedArray(s);return{newElement:function(){return r?a[r-=1]:e()},release:function(t){r===s&&(a=pooling.double(a),s*=2),i&&i(t),a[r]=t,r+=1}}},bezierLengthPool=poolFactory(8,(function(){return{addedLength:0,percents:createTypedArray("float32",getDefaultCurveSegments()),lengths:createTypedArray("float32",getDefaultCurveSegments())}})),segmentsLengthPool=poolFactory(8,(function(){return{lengths:[],totalLength:0}}),(function(t){var e,i=t.lengths.length;for(e=0;e-.001&&n<.001}var i=function(t,e,i,r){var s,a,n,o,h,l,p=getDefaultCurveSegments(),f=0,m=[],c=[],d=bezierLengthPool.newElement();for(n=i.length,s=0;sn?-1:1,l=!0;l;)if(r[a]<=n&&r[a+1]>n?(o=(n-r[a])/(r[a+1]-r[a]),l=!1):a+=h,a<0||a>=s-1){if(a===s-1)return i[a];l=!1}return i[a]+(i[a+1]-i[a])*o}var h=createTypedArray("float32",8);return{getSegmentsLength:function(t){var e,r=segmentsLengthPool.newElement(),s=t.c,a=t.v,n=t.o,o=t.i,h=t._length,l=r.lengths,p=0;for(e=0;e1&&(a=1);var p,f=o(a,l),m=o(n=n>1?1:n,l),c=e.length,d=1-f,u=1-m,y=d*d*d,g=f*d*d*3,v=f*f*d*3,b=f*f*f,x=d*d*u,P=f*d*u+d*f*u+d*d*m,E=f*f*u+d*f*m+f*d*m,S=f*f*m,C=d*u*u,_=f*u*u+d*m*u+d*u*m,A=f*m*u+d*m*m+f*u*m,T=f*m*m,M=u*u*u,k=m*u*u+u*m*u+u*u*m,D=m*m*u+u*m*m+m*u*m,F=m*m*m;for(p=0;pc?m>d?m-c-d:d-c-m:d>c?d-c-m:c-m-d)>-1e-4&&f<1e-4}}}var bez=bezFunction(),initFrame=initialDefaultFrame,mathAbs=Math.abs;function interpolateValue(t,e){var i,r=this.offsetTime;"multidimensional"===this.propType&&(i=createTypedArray("float32",this.pv.length));for(var s,a,n,o,h,l,p,f,m,c=e.lastIndex,d=c,u=this.keyframes.length-1,y=!0;y;){if(s=this.keyframes[d],a=this.keyframes[d+1],d===u-1&&t>=a.t-r){s.h&&(s=a),c=0;break}if(a.t-r>t){c=d;break}d=v||t=v?x.points.length-1:0;for(h=x.points[P].point.length,o=0;o=C&&S=v)i[0]=g[0],i[1]=g[1],i[2]=g[2];else if(t<=b)i[0]=s.s[0],i[1]=s.s[1],i[2]=s.s[2];else{quaternionToEuler(i,slerp(createQuaternion(s.s),createQuaternion(g),(t-b)/(v-b)))}else for(d=0;d=v?l=1:t1e-6?(r=Math.acos(s),a=Math.sin(r),n=Math.sin((1-i)*r)/a,o=Math.sin(i*r)/a):(n=1-i,o=i),h[0]=n*l+o*c,h[1]=n*p+o*d,h[2]=n*f+o*u,h[3]=n*m+o*y,h}function quaternionToEuler(t,e){var i=e[0],r=e[1],s=e[2],a=e[3],n=Math.atan2(2*r*a-2*i*s,1-2*r*r-2*s*s),o=Math.asin(2*i*r+2*s*a),h=Math.atan2(2*i*a-2*r*s,1-2*i*i-2*s*s);t[0]=n/degToRads,t[1]=o/degToRads,t[2]=h/degToRads}function createQuaternion(t){var e=t[0]*degToRads,i=t[1]*degToRads,r=t[2]*degToRads,s=Math.cos(e/2),a=Math.cos(i/2),n=Math.cos(r/2),o=Math.sin(e/2),h=Math.sin(i/2),l=Math.sin(r/2);return[o*h*n+s*a*l,o*a*n+s*h*l,s*h*n-o*a*l,s*a*n-o*h*l]}function getValueAtCurrentTime(){var t=this.comp.renderedFrame-this.offsetTime,e=this.keyframes[0].t-this.offsetTime,i=this.keyframes[this.keyframes.length-1].t-this.offsetTime;if(!(t===this._caching.lastFrame||this._caching.lastFrame!==initFrame&&(this._caching.lastFrame>=i&&t>=i||this._caching.lastFrame=t&&(this._caching._lastKeyframeIndex=-1,this._caching.lastIndex=0);var r=this.interpolateValue(t,this._caching);this.pv=r}return this._caching.lastFrame=t,this.pv}function setVValue(t){var e;if("unidimensional"===this.propType)e=t*this.mult,mathAbs(this.v-e)>1e-5&&(this.v=e,this._mdf=!0);else for(var i=0,r=this.v.length;i1e-5&&(this.v[i]=e,this._mdf=!0),i+=1}function processEffectsSequence(){if(this.elem.globalData.frameId!==this.frameId&&this.effectsSequence.length)if(this.lock)this.setVValue(this.pv);else{var t;this.lock=!0,this._mdf=this._isFirstFrame;var e=this.effectsSequence.length,i=this.kf?this.pv:this.data.k;for(t=0;t=this._maxLength&&this.doubleArrayLength(),i){case"v":a=this.v;break;case"i":a=this.i;break;case"o":a=this.o;break;default:a=[]}(!a[r]||a[r]&&!s)&&(a[r]=pointPool.newElement()),a[r][0]=t,a[r][1]=e},ShapePath.prototype.setTripleAt=function(t,e,i,r,s,a,n,o){this.setXYAt(t,e,"v",n,o),this.setXYAt(i,r,"o",n,o),this.setXYAt(s,a,"i",n,o)},ShapePath.prototype.reverse=function(){var t=new ShapePath;t.setPathData(this.c,this._length);var e=this.v,i=this.o,r=this.i,s=0;this.c&&(t.setTripleAt(e[0][0],e[0][1],r[0][0],r[0][1],i[0][0],i[0][1],0,!1),s=1);var a,n=this._length-1,o=this._length;for(a=s;a=c[c.length-1].t-this.offsetTime)r=c[c.length-1].s?c[c.length-1].s[0]:c[c.length-2].e[0],a=!0;else{for(var d,u,y,g=m,v=c.length-1,b=!0;b&&(d=c[g],!((u=c[g+1]).t-this.offsetTime>t));)g=u.t-this.offsetTime)p=1;else if(tr&&e>r)||(this._caching.lastIndex=s0||t>-1e-6&&t<0?r(1e4*t)/1e4:t}function I(){var t=this.props;return"matrix("+w(t[0])+","+w(t[1])+","+w(t[4])+","+w(t[5])+","+w(t[12])+","+w(t[13])+")"}return function(){this.reset=s,this.rotate=a,this.rotateX=n,this.rotateY=o,this.rotateZ=h,this.skew=p,this.skewFromAxis=f,this.shear=l,this.scale=m,this.setTransform=c,this.translate=d,this.transform=u,this.multiply=y,this.applyToPoint=P,this.applyToX=E,this.applyToY=S,this.applyToZ=C,this.applyToPointArray=k,this.applyToTriplePoints=M,this.applyToPointStringified=D,this.toCSS=F,this.to2dCSS=I,this.clone=b,this.cloneFromProps=x,this.equals=v,this.inversePoints=T,this.inversePoint=A,this.getInverseMatrix=_,this._t=this.transform,this.isIdentity=g,this._identity=!0,this._identityCalculated=!1,this.props=createTypedArray("float32",16),this.reset()}}();function _typeof$3(t){return _typeof$3="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},_typeof$3(t)}var lottie={},standalone="__[STANDALONE]__",animationData="__[ANIMATIONDATA]__",renderer="";function setLocation(t){setLocationHref(t)}function searchAnimations(){!0===standalone?animationManager.searchAnimations(animationData,standalone,renderer):animationManager.searchAnimations()}function setSubframeRendering(t){setSubframeEnabled(t)}function setPrefix(t){setIdPrefix(t)}function loadAnimation(t){return!0===standalone&&(t.animationData=JSON.parse(animationData)),animationManager.loadAnimation(t)}function setQuality(t){if("string"==typeof t)switch(t){case"high":setDefaultCurveSegments(200);break;default:case"medium":setDefaultCurveSegments(50);break;case"low":setDefaultCurveSegments(10)}else!isNaN(t)&&t>1&&setDefaultCurveSegments(t);getDefaultCurveSegments()>=50?roundValues(!1):roundValues(!0)}function inBrowser(){return"undefined"!=typeof navigator}function installPlugin(t,e){"expressions"===t&&setExpressionsPlugin(e)}function getFactory(t){switch(t){case"propertyFactory":return PropertyFactory;case"shapePropertyFactory":return ShapePropertyFactory;case"matrix":return Matrix;default:return null}}function checkReady(){"complete"===document.readyState&&(clearInterval(readyStateCheckInterval),searchAnimations())}function getQueryVariable(t){for(var e=queryString.split("&"),i=0;i=1?a.push({s:t-1,e:e-1}):(a.push({s:t,e:1}),a.push({s:0,e:e-1}));var n,o,h=[],l=a.length;for(n=0;nr+i))p=o.s*s<=r?0:(o.s*s-r)/i,f=o.e*s>=r+i?1:(o.e*s-r)/i,h.push([p,f])}return h.length||h.push([0,0]),h},TrimModifier.prototype.releasePathsData=function(t){var e,i=t.length;for(e=0;e1?1+a:this.s.v<0?0+a:this.s.v+a)>(i=this.e.v>1?1+a:this.e.v<0?0+a:this.e.v+a)){var n=e;e=i,i=n}e=1e-4*Math.round(1e4*e),i=1e-4*Math.round(1e4*i),this.sValue=e,this.eValue=i}else e=this.sValue,i=this.eValue;var o,h,l,p,f,m=this.shapes.length,c=0;if(i===e)for(s=0;s=0;s-=1)if((d=this.shapes[s]).shape._mdf){for((u=d.localShapeCollection).releaseShapes(),2===this.m&&m>1?(g=this.calculateShapeEdges(e,i,d.totalShapeLength,x,c),x+=d.totalShapeLength):g=[[v,b]],h=g.length,o=0;o=1?y.push({s:d.totalShapeLength*(v-1),e:d.totalShapeLength*(b-1)}):(y.push({s:d.totalShapeLength*v,e:d.totalShapeLength}),y.push({s:0,e:d.totalShapeLength*(b-1)}));var P=this.addShapes(d,y[0]);if(y[0].s!==y[0].e){if(y.length>1)if(d.shape.paths.shapes[d.shape.paths._length-1].c){var E=P.pop();this.addPaths(P,u),P=this.addShapes(d,y[1],E)}else this.addPaths(P,u),P=this.addShapes(d,y[1]);this.addPaths(P,u)}}d.shape.paths=u}}},TrimModifier.prototype.addPaths=function(t,e){var i,r=t.length;for(i=0;ie.e){i.c=!1;break}e.s<=d&&e.e>=d+n.addedLength?(this.addSegment(m[r].v[s-1],m[r].o[s-1],m[r].i[s],m[r].v[s],i,o,y),y=!1):(l=bez.getNewSegment(m[r].v[s-1],m[r].v[s],m[r].o[s-1],m[r].i[s],(e.s-d)/n.addedLength,(e.e-d)/n.addedLength,h[s-1]),this.addSegmentFromArray(l,i,o,y),y=!1,i.c=!1),d+=n.addedLength,o+=1}if(m[r].c&&h.length){if(n=h[s-1],d<=e.e){var g=h[s-1].addedLength;e.s<=d&&e.e>=d+g?(this.addSegment(m[r].v[s-1],m[r].o[s-1],m[r].i[0],m[r].v[0],i,o,y),y=!1):(l=bez.getNewSegment(m[r].v[s-1],m[r].v[0],m[r].o[s-1],m[r].i[0],(e.s-d)/g,(e.e-d)/g,h[s-1]),this.addSegmentFromArray(l,i,o,y),y=!1,i.c=!1)}else i.c=!1;d+=n.addedLength,o+=1}if(i._length&&(i.setXYAt(i.v[p][0],i.v[p][1],"i",p),i.setXYAt(i.v[i._length-1][0],i.v[i._length-1][1],"o",i._length-1)),d>e.e)break;r=this.p.keyframes[this.p.keyframes.length-1].t?(r=this.p.getValueAtTime(this.p.keyframes[this.p.keyframes.length-1].t/i,0),s=this.p.getValueAtTime((this.p.keyframes[this.p.keyframes.length-1].t-.05)/i,0)):(r=this.p.pv,s=this.p.getValueAtTime((this.p._caching.lastFrame+this.p.offsetTime-.01)/i,this.p.offsetTime));else if(this.px&&this.px.keyframes&&this.py.keyframes&&this.px.getValueAtTime&&this.py.getValueAtTime){r=[],s=[];var a=this.px,n=this.py;a._caching.lastFrame+a.offsetTime<=a.keyframes[0].t?(r[0]=a.getValueAtTime((a.keyframes[0].t+.01)/i,0),r[1]=n.getValueAtTime((n.keyframes[0].t+.01)/i,0),s[0]=a.getValueAtTime(a.keyframes[0].t/i,0),s[1]=n.getValueAtTime(n.keyframes[0].t/i,0)):a._caching.lastFrame+a.offsetTime>=a.keyframes[a.keyframes.length-1].t?(r[0]=a.getValueAtTime(a.keyframes[a.keyframes.length-1].t/i,0),r[1]=n.getValueAtTime(n.keyframes[n.keyframes.length-1].t/i,0),s[0]=a.getValueAtTime((a.keyframes[a.keyframes.length-1].t-.01)/i,0),s[1]=n.getValueAtTime((n.keyframes[n.keyframes.length-1].t-.01)/i,0)):(r=[a.pv,n.pv],s[0]=a.getValueAtTime((a._caching.lastFrame+a.offsetTime-.01)/i,a.offsetTime),s[1]=n.getValueAtTime((n._caching.lastFrame+n.offsetTime-.01)/i,n.offsetTime))}else r=s=t;this.v.rotate(-Math.atan2(r[1]-s[1],r[0]-s[0]))}this.data.p&&this.data.p.s?this.data.p.z?this.v.translate(this.px.v,this.py.v,-this.pz.v):this.v.translate(this.px.v,this.py.v,0):this.v.translate(this.p.v[0],this.p.v[1],-this.p.v[2])}this.frameId=this.elem.globalData.frameId}},precalculateMatrix:function(){if(this.appliedTransformations=0,this.pre.reset(),!this.a.effectsSequence.length&&(this.pre.translate(-this.a.v[0],-this.a.v[1],this.a.v[2]),this.appliedTransformations=1,!this.s.effectsSequence.length)){if(this.pre.scale(this.s.v[0],this.s.v[1],this.s.v[2]),this.appliedTransformations=2,this.sk){if(this.sk.effectsSequence.length||this.sa.effectsSequence.length)return;this.pre.skewFromAxis(-this.sk.v,this.sa.v),this.appliedTransformations=3}this.r?this.r.effectsSequence.length||(this.pre.rotate(-this.r.v),this.appliedTransformations=4):this.rz.effectsSequence.length||this.ry.effectsSequence.length||this.rx.effectsSequence.length||this.or.effectsSequence.length||(this.pre.rotateZ(-this.rz.v).rotateY(this.ry.v).rotateX(this.rx.v).rotateZ(-this.or.v[2]).rotateY(this.or.v[1]).rotateX(this.or.v[0]),this.appliedTransformations=4)}},autoOrient:function(){}},extendPrototype([DynamicPropertyContainer],e),e.prototype.addDynamicProperty=function(t){this._addDynamicProperty(t),this.elem.addDynamicProperty(t),this._isDirty=!0},e.prototype._addDynamicProperty=DynamicPropertyContainer.prototype.addDynamicProperty,{getTransformProperty:function(t,i,r){return new e(t,i,r)}}}();function RepeaterModifier(){}function RoundCornersModifier(){}function floatEqual(t,e){return 1e5*Math.abs(t-e)<=Math.min(Math.abs(t),Math.abs(e))}function floatZero(t){return Math.abs(t)<=1e-5}function lerp(t,e,i){return t*(1-i)+e*i}function lerpPoint(t,e,i){return[lerp(t[0],e[0],i),lerp(t[1],e[1],i)]}function quadRoots(t,e,i){if(0===t)return[];var r=e*e-4*t*i;if(r<0)return[];var s=-e/(2*t);if(0===r)return[s];var a=Math.sqrt(r)/(2*t);return[s-a,s+a]}function polynomialCoefficients(t,e,i,r){return[3*e-t-3*i+r,3*t-6*e+3*i,-3*t+3*e,t]}function singlePoint(t){return new PolynomialBezier(t,t,t,t,!1)}function PolynomialBezier(t,e,i,r,s){s&&pointEqual(t,e)&&(e=lerpPoint(t,r,1/3)),s&&pointEqual(i,r)&&(i=lerpPoint(t,r,2/3));var a=polynomialCoefficients(t[0],e[0],i[0],r[0]),n=polynomialCoefficients(t[1],e[1],i[1],r[1]);this.a=[a[0],n[0]],this.b=[a[1],n[1]],this.c=[a[2],n[2]],this.d=[a[3],n[3]],this.points=[t,e,i,r]}function extrema(t,e){var i=t.points[0][e],r=t.points[t.points.length-1][e];if(i>r){var s=r;r=i,i=s}for(var a=quadRoots(3*t.a[e],2*t.b[e],t.c[e]),n=0;n0&&a[n]<1){var o=t.point(a[n])[e];or&&(r=o)}return{min:i,max:r}}function intersectData(t,e,i){var r=t.boundingBox();return{cx:r.cx,cy:r.cy,width:r.width,height:r.height,bez:t,t:(e+i)/2,t1:e,t2:i}}function splitData(t){var e=t.bez.split(.5);return[intersectData(e[0],t.t1,t.t),intersectData(e[1],t.t,t.t2)]}function boxIntersect(t,e){return 2*Math.abs(t.cx-e.cx)=a||t.width<=r&&t.height<=r&&e.width<=r&&e.height<=r)s.push([t.t,e.t]);else{var n=splitData(t),o=splitData(e);intersectsImpl(n[0],o[0],i+1,r,s,a),intersectsImpl(n[0],o[1],i+1,r,s,a),intersectsImpl(n[1],o[0],i+1,r,s,a),intersectsImpl(n[1],o[1],i+1,r,s,a)}}function crossProduct(t,e){return[t[1]*e[2]-t[2]*e[1],t[2]*e[0]-t[0]*e[2],t[0]*e[1]-t[1]*e[0]]}function lineIntersection(t,e,i,r){var s=[t[0],t[1],1],a=[e[0],e[1],1],n=[i[0],i[1],1],o=[r[0],r[1],1],h=crossProduct(crossProduct(s,a),crossProduct(n,o));return floatZero(h[2])?null:[h[0]/h[2],h[1]/h[2]]}function polarOffset(t,e,i){return[t[0]+Math.cos(e)*i,t[1]-Math.sin(e)*i]}function pointDistance(t,e){return Math.hypot(t[0]-e[0],t[1]-e[1])}function pointEqual(t,e){return floatEqual(t[0],e[0])&&floatEqual(t[1],e[1])}function ZigZagModifier(){}function setPoint(t,e,i,r,s,a,n){var o=i-Math.PI/2,h=i+Math.PI/2,l=e[0]+Math.cos(i)*r*s,p=e[1]-Math.sin(i)*r*s;t.setTripleAt(l,p,l+Math.cos(o)*a,p-Math.sin(o)*a,l+Math.cos(h)*n,p-Math.sin(h)*n,t.length())}function getPerpendicularVector(t,e){var i=[e[0]-t[0],e[1]-t[1]],r=.5*-Math.PI;return[Math.cos(r)*i[0]-Math.sin(r)*i[1],Math.sin(r)*i[0]+Math.cos(r)*i[1]]}function getProjectingAngle(t,e){var i=0===e?t.length()-1:e-1,r=(e+1)%t.length(),s=getPerpendicularVector(t.v[i],t.v[r]);return Math.atan2(0,1)-Math.atan2(s[1],s[0])}function zigZagCorner(t,e,i,r,s,a,n){var o=getProjectingAngle(e,i),h=e.v[i%e._length],l=e.v[0===i?e._length-1:i-1],p=e.v[(i+1)%e._length],f=2===a?Math.sqrt(Math.pow(h[0]-l[0],2)+Math.pow(h[1]-l[1],2)):0,m=2===a?Math.sqrt(Math.pow(h[0]-p[0],2)+Math.pow(h[1]-p[1],2)):0;setPoint(t,e.v[i%e._length],o,n,r,m/(2*(s+1)),f/(2*(s+1)),a)}function zigZagSegment(t,e,i,r,s,a){for(var n=0;n1&&e.length>1&&(s=getIntersection(t[0],e[e.length-1]))?[[t[0].split(s[0])[0]],[e[e.length-1].split(s[1])[1]]]:[i,r]}function pruneIntersections(t){for(var e,i=1;i1&&(e=pruneSegmentIntersection(t[t.length-1],t[0]),t[t.length-1]=e[0],t[0]=e[1]),t}function offsetSegmentSplit(t,e){var i,r,s,a,n=t.inflectionPoints();if(0===n.length)return[offsetSegment(t,e)];if(1===n.length||floatEqual(n[1],1))return i=(s=t.split(n[0]))[0],r=s[1],[offsetSegment(i,e),offsetSegment(r,e)];i=(s=t.split(n[0]))[0];var o=(n[1]-n[0])/(1-n[0]);return a=(s=s[1].split(o))[0],r=s[1],[offsetSegment(i,e),offsetSegment(a,e),offsetSegment(r,e)]}function OffsetPathModifier(){}function getFontProperties(t){for(var e=t.fStyle?t.fStyle.split(" "):[],i="normal",r="normal",s=e.length,a=0;a0;)i-=1,this._elements.unshift(e[i]);this.dynamicProperties.length?this.k=!0:this.getValue(!0)},RepeaterModifier.prototype.resetElements=function(t){var e,i=t.length;for(e=0;e0?Math.floor(m):Math.ceil(m),u=this.pMatrix.props,y=this.rMatrix.props,g=this.sMatrix.props;this.pMatrix.reset(),this.rMatrix.reset(),this.sMatrix.reset(),this.tMatrix.reset(),this.matrix.reset();var v,b,x=0;if(m>0){for(;xd;)this.applyTransforms(this.pMatrix,this.rMatrix,this.sMatrix,this.tr,1,!0),x-=1;c&&(this.applyTransforms(this.pMatrix,this.rMatrix,this.sMatrix,this.tr,-c,!0),x-=c)}for(r=1===this.data.m?0:this._currentCopies-1,s=1===this.data.m?1:-1,a=this._currentCopies;a;){if(b=(i=(e=this.elemsData[r].it)[e.length-1].transform.mProps.v.props).length,e[e.length-1].transform.mProps._mdf=!0,e[e.length-1].transform.op._mdf=!0,e[e.length-1].transform.op.v=1===this._currentCopies?this.so.v:this.so.v+(this.eo.v-this.so.v)*(r/(this._currentCopies-1)),0!==x){for((0!==r&&1===s||r!==this._currentCopies-1&&-1===s)&&this.applyTransforms(this.pMatrix,this.rMatrix,this.sMatrix,this.tr,1,!1),this.matrix.transform(y[0],y[1],y[2],y[3],y[4],y[5],y[6],y[7],y[8],y[9],y[10],y[11],y[12],y[13],y[14],y[15]),this.matrix.transform(g[0],g[1],g[2],g[3],g[4],g[5],g[6],g[7],g[8],g[9],g[10],g[11],g[12],g[13],g[14],g[15]),this.matrix.transform(u[0],u[1],u[2],u[3],u[4],u[5],u[6],u[7],u[8],u[9],u[10],u[11],u[12],u[13],u[14],u[15]),v=0;v0&&r<1?[e]:[]:[e-r,e+r].filter((function(t){return t>0&&t<1}))},PolynomialBezier.prototype.split=function(t){if(t<=0)return[singlePoint(this.points[0]),this];if(t>=1)return[this,singlePoint(this.points[this.points.length-1])];var e=lerpPoint(this.points[0],this.points[1],t),i=lerpPoint(this.points[1],this.points[2],t),r=lerpPoint(this.points[2],this.points[3],t),s=lerpPoint(e,i,t),a=lerpPoint(i,r,t),n=lerpPoint(s,a,t);return[new PolynomialBezier(this.points[0],e,s,n,!0),new PolynomialBezier(n,a,r,this.points[3],!0)]},PolynomialBezier.prototype.bounds=function(){return{x:extrema(this,0),y:extrema(this,1)}},PolynomialBezier.prototype.boundingBox=function(){var t=this.bounds();return{left:t.x.min,right:t.x.max,top:t.y.min,bottom:t.y.max,width:t.x.max-t.x.min,height:t.y.max-t.y.min,cx:(t.x.max+t.x.min)/2,cy:(t.y.max+t.y.min)/2}},PolynomialBezier.prototype.intersections=function(t,e,i){void 0===e&&(e=2),void 0===i&&(i=7);var r=[];return intersectsImpl(intersectData(this,0,1),intersectData(t,0,1),0,e,r,i),r},PolynomialBezier.shapeSegment=function(t,e){var i=(e+1)%t.length();return new PolynomialBezier(t.v[e],t.o[e],t.i[i],t.v[i],!0)},PolynomialBezier.shapeSegmentInverted=function(t,e){var i=(e+1)%t.length();return new PolynomialBezier(t.v[i],t.i[i],t.o[e],t.v[e],!0)},extendPrototype([ShapeModifier],ZigZagModifier),ZigZagModifier.prototype.initModifierProperties=function(t,e){this.getValue=this.processKeys,this.amplitude=PropertyFactory.getProp(t,e.s,0,null,this),this.frequency=PropertyFactory.getProp(t,e.r,0,null,this),this.pointsType=PropertyFactory.getProp(t,e.pt,0,null,this),this._isAnimated=0!==this.amplitude.effectsSequence.length||0!==this.frequency.effectsSequence.length||0!==this.pointsType.effectsSequence.length},ZigZagModifier.prototype.processPath=function(t,e,i,r){var s=t._length,a=shapePool.newElement();if(a.c=t.c,t.c||(s-=1),0===s)return a;var n=-1,o=PolynomialBezier.shapeSegment(t,0);zigZagCorner(a,t,0,e,i,r,n);for(var h=0;h=0;a-=1)o=PolynomialBezier.shapeSegmentInverted(t,a),l.push(offsetSegmentSplit(o,e));l=pruneIntersections(l);var p=null,f=null;for(a=0;a=55296&&i<=56319){var r=t.charCodeAt(1);r>=56320&&r<=57343&&(e=1024*(i-55296)+r-56320+65536)}return e}function o(t){var e=n(t);return e>=127462&&e<=127487}var h=function(){this.fonts=[],this.chars=null,this.typekitLoaded=0,this.isLoaded=!1,this._warned=!1,this.initTime=Date.now(),this.setIsLoadedBinded=this.setIsLoaded.bind(this),this.checkLoadedFontsBinded=this.checkLoadedFonts.bind(this)};h.isModifier=function(t,e){var i=t.toString(16)+e.toString(16);return-1!==r.indexOf(i)},h.isZeroWidthJoiner=function(t){return 8205===t},h.isFlagEmoji=function(t){return o(t.substr(0,2))&&o(t.substr(2,2))},h.isRegionalCode=o,h.isCombinedCharacter=function(t){return-1!==e.indexOf(t)},h.isRegionalFlag=function(t,e){var r=n(t.substr(e,2));if(r!==i)return!1;var s=0;for(e+=2;s<5;){if((r=n(t.substr(e,2)))<917601||r>917626)return!1;s+=1,e+=2}return 917631===n(t.substr(e,2))},h.isVariationSelector=function(t){return 65039===t},h.BLACK_FLAG_CODE_POINT=i;var l={addChars:function(t){if(t){var e;this.chars||(this.chars=[]);var i,r,s=t.length,a=this.chars.length;for(e=0;e0&&(p=!1),p){var f=createTag("style");f.setAttribute("f-forigin",r[i].fOrigin),f.setAttribute("f-origin",r[i].origin),f.setAttribute("f-family",r[i].fFamily),f.type="text/css",f.innerText="@font-face {font-family: "+r[i].fFamily+"; font-style: normal; src: url('"+r[i].fPath+"');}",e.appendChild(f)}}else if("g"===r[i].fOrigin||1===r[i].origin){for(h=document.querySelectorAll('link[f-forigin="g"], link[f-origin="1"]'),l=0;lt?!0!==this.isInRange&&(this.globalData._mdf=!0,this._mdf=!0,this.isInRange=!0,this.show()):!1!==this.isInRange&&(this.globalData._mdf=!0,this.isInRange=!1,this.hide())},renderRenderable:function(){var t,e=this.renderableComponents.length;for(t=0;t.1)&&this.audio.seek(this._currentTime/this.globalData.frameRate):(this.audio.play(),this.audio.seek(this._currentTime/this.globalData.frameRate),this._isPlaying=!0))},AudioElement.prototype.show=function(){},AudioElement.prototype.hide=function(){this.audio.pause(),this._isPlaying=!1},AudioElement.prototype.pause=function(){this.audio.pause(),this._isPlaying=!1,this._canPlay=!1},AudioElement.prototype.resume=function(){this._canPlay=!0},AudioElement.prototype.setRate=function(t){this.audio.rate(t)},AudioElement.prototype.volume=function(t){this._volumeMultiplier=t,this._previousVolume=t*this._volume,this.audio.volume(this._previousVolume)},AudioElement.prototype.getBaseElement=function(){return null},AudioElement.prototype.destroy=function(){},AudioElement.prototype.sourceRectAtTime=function(){},AudioElement.prototype.initExpressions=function(){},BaseRenderer.prototype.checkLayers=function(t){var e,i,r=this.layers.length;for(this.completeLayers=!0,e=r-1;e>=0;e-=1)this.elements[e]||(i=this.layers[e]).ip-i.st<=t-this.layers[e].st&&i.op-i.st>t-this.layers[e].st&&this.buildItem(e),this.completeLayers=!!this.elements[e]&&this.completeLayers;this.checkPendingElements()},BaseRenderer.prototype.createItem=function(t){switch(t.ty){case 2:return this.createImage(t);case 0:return this.createComp(t);case 1:return this.createSolid(t);case 3:default:return this.createNull(t);case 4:return this.createShape(t);case 5:return this.createText(t);case 6:return this.createAudio(t);case 13:return this.createCamera(t);case 15:return this.createFootage(t)}},BaseRenderer.prototype.createCamera=function(){throw new Error("You're using a 3d camera. Try the html renderer.")},BaseRenderer.prototype.createAudio=function(t){return new AudioElement(t,this.globalData,this)},BaseRenderer.prototype.createFootage=function(t){return new FootageElement(t,this.globalData,this)},BaseRenderer.prototype.buildAllItems=function(){var t,e=this.layers.length;for(t=0;t0&&(this.maskElement.setAttribute("id",y),this.element.maskedElement.setAttribute(v,"url("+getLocationHref()+"#"+y+")"),a.appendChild(this.maskElement)),this.viewData.length&&this.element.addRenderableComponent(this)}TransformElement.prototype={initTransform:function(){var t=new Matrix;this.finalTransform={mProp:this.data.ks?TransformPropertyFactory.getTransformProperty(this,this.data.ks,this):{o:0},_matMdf:!1,_localMatMdf:!1,_opMdf:!1,mat:t,localMat:t,localOpacity:1},this.data.ao&&(this.finalTransform.mProp.autoOriented=!0),this.data.ty},renderTransform:function(){if(this.finalTransform._opMdf=this.finalTransform.mProp.o._mdf||this._isFirstFrame,this.finalTransform._matMdf=this.finalTransform.mProp._mdf||this._isFirstFrame,this.hierarchy){var t,e=this.finalTransform.mat,i=0,r=this.hierarchy.length;if(!this.finalTransform._matMdf)for(;i1&&(a+=" C"+e.o[r-1][0]+","+e.o[r-1][1]+" "+e.i[0][0]+","+e.i[0][1]+" "+e.v[0][0]+","+e.v[0][1]),i.lastPath!==a){var n="";i.elem&&(e.c&&(n=t.inv?this.solidPath+a:a),i.elem.setAttribute("d",n)),i.lastPath=a}},MaskElement.prototype.destroy=function(){this.element=null,this.globalData=null,this.maskElement=null,this.data=null,this.masksProperties=null};var filtersFactory=function(){var t={};return t.createFilter=function(t,e){var i=createNS("filter");i.setAttribute("id",t),!0!==e&&(i.setAttribute("filterUnits","objectBoundingBox"),i.setAttribute("x","0%"),i.setAttribute("y","0%"),i.setAttribute("width","100%"),i.setAttribute("height","100%"));return i},t.createAlphaToLuminanceFilter=function(){var t=createNS("feColorMatrix");return t.setAttribute("type","matrix"),t.setAttribute("color-interpolation-filters","sRGB"),t.setAttribute("values","0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 1"),t},t}(),featureSupport=function(){var t={maskType:!0,svgLumaHidden:!0,offscreenCanvas:"undefined"!=typeof OffscreenCanvas};return(/MSIE 10/i.test(navigator.userAgent)||/MSIE 9/i.test(navigator.userAgent)||/rv:11.0/i.test(navigator.userAgent)||/Edge\/\d./i.test(navigator.userAgent))&&(t.maskType=!1),/firefox/i.test(navigator.userAgent)&&(t.svgLumaHidden=!1),t}(),registeredEffects$1={},idPrefix="filter_result_";function SVGEffects(t){var e,i,r="SourceGraphic",s=t.data.ef?t.data.ef.length:0,a=createElementID(),n=filtersFactory.createFilter(a,!0),o=0;for(this.filters=[],e=0;e=0&&!this.shapeModifiers[t].processShapes(this._isFirstFrame);t-=1);}},searchProcessedElement:function(t){for(var e=this.processedElements,i=0,r=e.length;i.01)return!1;i+=1}return!0},GradientProperty.prototype.checkCollapsable=function(){if(this.o.length/2!=this.c.length/4)return!1;if(this.data.k.k[0].s)for(var t=0,e=this.data.k.k.length;t0;)h=r.transformers[d].mProps._mdf||h,c-=1,d-=1;if(h)for(c=y-r.styles[p].lvl,d=r.transformers.length-1;c>0;)m.multiply(r.transformers[d].mProps.v),c-=1,d-=1}else m=t;if(n=(f=r.sh.paths)._length,h){for(o="",a=0;a=1?v=.99:v<=-1&&(v=-.99);var b=o*v,x=Math.cos(g+e.a.v)*b+p[0],P=Math.sin(g+e.a.v)*b+p[1];h.setAttribute("fx",x),h.setAttribute("fy",P),l&&!e.g._collapsable&&(e.of.setAttribute("fx",x),e.of.setAttribute("fy",P))}}function h(t,e,i){var r=e.style,s=e.d;s&&(s._mdf||i)&&s.dashStr&&(r.pElem.setAttribute("stroke-dasharray",s.dashStr),r.pElem.setAttribute("stroke-dashoffset",s.dashoffset[0])),e.c&&(e.c._mdf||i)&&r.pElem.setAttribute("stroke","rgb("+bmFloor(e.c.v[0])+","+bmFloor(e.c.v[1])+","+bmFloor(e.c.v[2])+")"),(e.o._mdf||i)&&r.pElem.setAttribute("stroke-opacity",e.o.v),(e.w._mdf||i)&&(r.pElem.setAttribute("stroke-width",e.w.v),r.msElem&&r.msElem.setAttribute("stroke-width",e.w.v))}return{createRenderFunction:function(t){switch(t.ty){case"fl":return a;case"gf":return o;case"gs":return n;case"st":return h;case"sh":case"el":case"rc":case"sr":return s;case"tr":return i;case"no":return r;default:return null}}}}();function SVGShapeElement(t,e,i){this.shapes=[],this.shapesData=t.shapes,this.stylesList=[],this.shapeModifiers=[],this.itemsData=[],this.processedElements=[],this.animatedContents=[],this.initElement(t,e,i),this.prevViewData=[]}function LetterProps(t,e,i,r,s,a){this.o=t,this.sw=e,this.sc=i,this.fc=r,this.m=s,this.p=a,this._mdf={o:!0,sw:!!e,sc:!!i,fc:!!r,m:!0,p:!0}}function TextProperty(t,e){this._frameId=initialDefaultFrame,this.pv="",this.v="",this.kf=!1,this._isFirstFrame=!0,this._mdf=!1,e.d&&e.d.sid&&(e.d=t.globalData.slotManager.getProp(e.d)),this.data=e,this.elem=t,this.comp=this.elem.comp,this.keysIndex=0,this.canResize=!1,this.minimumFontSize=1,this.effectsSequence=[],this.currentData={ascent:0,boxWidth:this.defaultBoxWidth,f:"",fStyle:"",fWeight:"",fc:"",j:"",justifyOffset:"",l:[],lh:0,lineWidths:[],ls:"",of:"",s:"",sc:"",sw:0,t:0,tr:0,sz:0,ps:null,fillColorAnim:!1,strokeColorAnim:!1,strokeWidthAnim:!1,yOffset:0,finalSize:0,finalText:[],finalLineHeight:0,__complete:!1},this.copyData(this.currentData,this.data.d.k[0].s),this.searchProperty()||this.completeTextData(this.currentData)}extendPrototype([BaseElement,TransformElement,SVGBaseElement,IShapeElement,HierarchyElement,FrameElement,RenderableDOMElement],SVGShapeElement),SVGShapeElement.prototype.initSecondaryElement=function(){},SVGShapeElement.prototype.identityMatrix=new Matrix,SVGShapeElement.prototype.buildExpressionInterface=function(){},SVGShapeElement.prototype.createContent=function(){this.searchShapes(this.shapesData,this.itemsData,this.prevViewData,this.layerElement,0,[],!0),this.filterUniqueShapes()},SVGShapeElement.prototype.filterUniqueShapes=function(){var t,e,i,r,s=this.shapes.length,a=this.stylesList.length,n=[],o=!1;for(i=0;i1&&o&&this.setShapesAsAnimated(n)}},SVGShapeElement.prototype.setShapesAsAnimated=function(t){var e,i=t.length;for(e=0;e=0;o-=1){if((m=this.searchProcessedElement(t[o]))?e[o]=i[m-1]:t[o]._render=n,"fl"===t[o].ty||"st"===t[o].ty||"gf"===t[o].ty||"gs"===t[o].ty||"no"===t[o].ty)m?e[o].style.closed=!1:e[o]=this.createStyleElement(t[o],s),t[o]._render&&e[o].style.pElem.parentNode!==r&&r.appendChild(e[o].style.pElem),u.push(e[o].style);else if("gr"===t[o].ty){if(m)for(l=e[o].it.length,h=0;h1,this.kf&&this.addEffect(this.getKeyframeValue.bind(this)),this.kf},TextProperty.prototype.addEffect=function(t){this.effectsSequence.push(t),this.elem.addDynamicProperty(this)},TextProperty.prototype.getValue=function(t){if(this.elem.globalData.frameId!==this.frameId&&this.effectsSequence.length||t){this.currentData.t=this.data.d.k[this.keysIndex].s.t;var e=this.currentData,i=this.keysIndex;if(this.lock)this.setCurrentData(this.currentData);else{var r;this.lock=!0,this._mdf=!1;var s=this.effectsSequence.length,a=t||this.data.d.k[this.keysIndex].s;for(r=0;re);)i+=1;return this.keysIndex!==i&&(this.keysIndex=i),this.data.d.k[this.keysIndex].s},TextProperty.prototype.buildFinalText=function(t){for(var e,i,r=[],s=0,a=t.length,n=!1,o=!1,h="";s=55296&&e<=56319?FontManager.isRegionalFlag(t,s)?h=t.substr(s,14):(i=t.charCodeAt(s+1))>=56320&&i<=57343&&(FontManager.isModifier(e,i)?(h=t.substr(s,2),n=!0):h=FontManager.isFlagEmoji(t.substr(s,4))?t.substr(s,4):t.substr(s,2)):e>56319?(i=t.charCodeAt(s+1),FontManager.isVariationSelector(e)&&(n=!0)):FontManager.isZeroWidthJoiner(e)&&(n=!0,o=!0),n?(r[r.length-1]+=h,n=!1):r.push(h),s+=h.length;return r},TextProperty.prototype.completeTextData=function(t){t.__complete=!0;var e,i,r,s,a,n,o,h=this.elem.globalData.fontManager,l=this.data,p=[],f=0,m=l.m.g,c=0,d=0,u=0,y=[],g=0,v=0,b=h.getFontByName(t.f),x=0,P=getFontProperties(b);t.fWeight=P.weight,t.fStyle=P.style,t.finalSize=t.s,t.finalText=this.buildFinalText(t.t),i=t.finalText.length,t.finalLineHeight=t.lh;var E,S=t.tr/1e3*t.finalSize;if(t.sz)for(var C,_,A=!0,T=t.sz[0],M=t.sz[1];A;){C=0,g=0,i=(_=this.buildFinalText(t.t)).length,S=t.tr/1e3*t.finalSize;var k=-1;for(e=0;eT&&" "!==_[e]?(-1===k?i+=1:e=k,C+=t.finalLineHeight||1.2*t.finalSize,_.splice(e,k===e?1:0,"\r"),k=-1,g=0):(g+=x,g+=S);C+=b.ascent*t.finalSize/100,this.canResize&&t.finalSize>this.minimumFontSize&&Mv?g:v,g=-2*S,s="",r=!0,u+=1):s=D,h.chars?(o=h.getCharData(D,b.fStyle,h.getFontByName(t.f).fFamily),x=r?0:o.w*t.finalSize/100):x=h.measureText(s,t.f,t.finalSize)," "===D?F+=x+S:(g+=x+S+F,F=0),p.push({l:x,an:x,add:c,n:r,anIndexes:[],val:s,line:u,animatorJustifyOffset:0}),2==m){if(c+=x,""===s||" "===s||e===i-1){for(""!==s&&" "!==s||(c-=x);d<=e;)p[d].an=c,p[d].ind=f,p[d].extra=x,d+=1;f+=1,c=0}}else if(3==m){if(c+=x,""===s||e===i-1){for(""===s&&(c-=x);d<=e;)p[d].an=c,p[d].ind=f,p[d].extra=x,d+=1;c=0,f+=1}}else p[f].ind=f,p[f].extra=0,f+=1;if(t.l=p,v=g>v?g:v,y.push(g),t.sz)t.boxWidth=t.sz[0],t.justifyOffset=0;else switch(t.boxWidth=v,t.j){case 1:t.justifyOffset=-t.boxWidth;break;case 2:t.justifyOffset=-t.boxWidth/2;break;default:t.justifyOffset=0}t.lineWidths=y;var w,I,V,B,R=l.a;n=R.length;var L=[];for(a=0;a0?s=this.ne.v/100:a=-this.ne.v/100,this.xe.v>0?n=1-this.xe.v/100:o=1+this.xe.v/100;var h=BezierFactory.getBezierEasing(s,a,n,o).get,l=0,p=this.finalS,f=this.finalE,m=this.data.sh;if(2===m)l=h(l=f===p?r>=f?1:0:t(0,e(.5/(f-p)+(r-p)/(f-p),1)));else if(3===m)l=h(l=f===p?r>=f?0:1:1-t(0,e(.5/(f-p)+(r-p)/(f-p),1)));else if(4===m)f===p?l=0:(l=t(0,e(.5/(f-p)+(r-p)/(f-p),1)))<.5?l*=2:l=1-2*(l-.5),l=h(l);else if(5===m){if(f===p)l=0;else{var c=f-p,d=-c/2+(r=e(t(0,r+.5-p),f-p)),u=c/2;l=Math.sqrt(1-d*d/(u*u))}l=h(l)}else 6===m?(f===p?l=0:(r=e(t(0,r+.5-p),f-p),l=(1+Math.cos(Math.PI+2*Math.PI*r/(f-p)))/2),l=h(l)):(r>=i(p)&&(l=t(0,e(r-p<0?e(f,1)-(p-r):f-r,1))),l=h(l));if(100!==this.sm.v){var y=.01*this.sm.v;0===y&&(y=1e-8);var g=.5-.5*y;l1&&(l=1)}return l*this.a.v},getValue:function(t){this.iterateDynamicProperties(),this._mdf=t||this._mdf,this._currentTextLength=this.elem.textProperty.currentData.l.length||0,t&&2===this.data.r&&(this.e.v=this._currentTextLength);var e=2===this.data.r?1:100/this.data.totalChars,i=this.o.v/e,r=this.s.v/e+i,s=this.e.v/e+i;if(r>s){var a=r;r=s,s=a}this.finalS=r,this.finalE=s}},extendPrototype([DynamicPropertyContainer],r),{getTextSelectorProp:function(t,e,i){return new r(t,e,i)}}}();function TextAnimatorDataProperty(t,e,i){var r={propType:!1},s=PropertyFactory.getProp,a=e.a;this.a={r:a.r?s(t,a.r,0,degToRads,i):r,rx:a.rx?s(t,a.rx,0,degToRads,i):r,ry:a.ry?s(t,a.ry,0,degToRads,i):r,sk:a.sk?s(t,a.sk,0,degToRads,i):r,sa:a.sa?s(t,a.sa,0,degToRads,i):r,s:a.s?s(t,a.s,1,.01,i):r,a:a.a?s(t,a.a,1,0,i):r,o:a.o?s(t,a.o,0,.01,i):r,p:a.p?s(t,a.p,1,0,i):r,sw:a.sw?s(t,a.sw,0,0,i):r,sc:a.sc?s(t,a.sc,1,0,i):r,fc:a.fc?s(t,a.fc,1,0,i):r,fh:a.fh?s(t,a.fh,0,0,i):r,fs:a.fs?s(t,a.fs,0,.01,i):r,fb:a.fb?s(t,a.fb,0,.01,i):r,t:a.t?s(t,a.t,0,0,i):r},this.s=TextSelectorProp.getTextSelectorProp(t,e.s,i),this.s.t=e.s.t}function TextAnimatorProperty(t,e,i){this._isFirstFrame=!0,this._hasMaskedPath=!1,this._frameId=-1,this._textData=t,this._renderType=e,this._elem=i,this._animatorsData=createSizedArray(this._textData.a.length),this._pathData={},this._moreOptions={alignment:{}},this.renderedLetters=[],this.lettersChangedFlag=!1,this.initDynamicPropertyContainer(i)}function ITextElement(){}TextAnimatorProperty.prototype.searchProperties=function(){var t,e,i=this._textData.a.length,r=PropertyFactory.getProp;for(t=0;t=o+ot||!d?(v=(o+ot-l)/h.partialLength,G=c.point[0]+(h.point[0]-c.point[0])*v,z=c.point[1]+(h.point[1]-c.point[1])*v,C.translate(-P[0]*T[s].an*.005,-P[1]*B*.01),p=!1):d&&(l+=h.partialLength,(f+=1)>=d.length&&(f=0,u[m+=1]?d=u[m].points:x.v.c?(f=0,d=u[m=0].points):(l-=h.partialLength,d=null)),d&&(c=h,y=(h=d[f]).partialLength));L=T[s].an/2-T[s].add,C.translate(-L,0,0)}else L=T[s].an/2-T[s].add,C.translate(-L,0,0),C.translate(-P[0]*T[s].an*.005,-P[1]*B*.01,0);for(F=0;Ft?this.textSpans[t].span:createNS(h?"g":"text"),y<=t){if(n.setAttribute("stroke-linecap","butt"),n.setAttribute("stroke-linejoin","round"),n.setAttribute("stroke-miterlimit","4"),this.textSpans[t].span=n,h){var g=createNS("g");n.appendChild(g),this.textSpans[t].childSpan=g}this.textSpans[t].span=n,this.layerElement.appendChild(n)}n.style.display="inherit"}if(l.reset(),p&&(o[t].n&&(f=-d,m+=i.yOffset,m+=c?1:0,c=!1),this.applyTextPropertiesToMatrix(i,l,o[t].line,f,m),f+=o[t].l||0,f+=d),h){var v;if(1===(u=this.globalData.fontManager.getCharData(i.finalText[t],r.fStyle,this.globalData.fontManager.getFontByName(i.f).fFamily)).t)v=new SVGCompElement(u.data,this.globalData,this);else{var b=emptyShapeData;u.data&&u.data.shapes&&(b=this.buildShapeData(u.data,i.finalSize)),v=new SVGShapeElement(b,this.globalData,this)}if(this.textSpans[t].glyph){var x=this.textSpans[t].glyph;this.textSpans[t].childSpan.removeChild(x.layerElement),x.destroy()}this.textSpans[t].glyph=v,v._debug=!0,v.prepareFrame(0),v.renderFrame(),this.textSpans[t].childSpan.appendChild(v.layerElement),1===u.t&&this.textSpans[t].childSpan.setAttribute("transform","scale("+i.finalSize/100+","+i.finalSize/100+")")}else p&&n.setAttribute("transform","translate("+l.props[12]+","+l.props[13]+")"),n.textContent=o[t].val,n.setAttributeNS("http://www.w3.org/XML/1998/namespace","xml:space","preserve")}p&&n&&n.setAttribute("d","")}else{var P=this.textContainer,E="start";switch(i.j){case 1:E="end";break;case 2:E="middle";break;default:E="start"}P.setAttribute("text-anchor",E),P.setAttribute("letter-spacing",d);var S=this.buildTextContents(i.finalText);for(e=S.length,m=i.ps?i.ps[1]+i.ascent:0,t=0;t=0;e-=1)(this.completeLayers||this.elements[e])&&this.elements[e].prepareFrame(t-this.layers[e].st);if(this.globalData._mdf)for(e=0;e=0;i-=1)(this.completeLayers||this.elements[i])&&(this.elements[i].prepareFrame(this.renderedFrame-this.layers[i].st),this.elements[i]._mdf&&(this._mdf=!0))}},ICompElement.prototype.renderInnerContent=function(){var t,e=this.layers.length;for(t=0;t=0;i-=1)t.finalTransform.multiply(t.transforms[i].transform.mProps.v);t._mdf=s},processSequences:function(t){var e,i=this.sequenceList.length;for(e=0;e=1){this.buffers=[];var t=this.globalData.canvasContext,e=assetLoader.createCanvas(t.canvas.width,t.canvas.height);this.buffers.push(e);var i=assetLoader.createCanvas(t.canvas.width,t.canvas.height);this.buffers.push(i),this.data.tt>=3&&!document._isProxy&&assetLoader.loadLumaCanvas()}this.canvasContext=this.globalData.canvasContext,this.transformCanvas=this.globalData.transformCanvas,this.renderableEffectsManager=new CVEffects(this),this.searchEffectTransforms()},createContent:function(){},setBlendMode:function(){var t=this.globalData;if(t.blendMode!==this.data.bm){t.blendMode=this.data.bm;var e=getBlendMode(this.data.bm);t.canvasContext.globalCompositeOperation=e}},createRenderableComponents:function(){this.maskManager=new CVMaskElement(this.data,this),this.transformEffects=this.renderableEffectsManager.getEffects(effectTypes.TRANSFORM_EFFECT)},hideElement:function(){this.hidden||this.isInRange&&!this.isTransparent||(this.hidden=!0)},showElement:function(){this.isInRange&&!this.isTransparent&&(this.hidden=!1,this._isFirstFrame=!0,this.maskManager._isFirstFrame=!0)},clearCanvas:function(t){t.clearRect(this.transformCanvas.tx,this.transformCanvas.ty,this.transformCanvas.w*this.transformCanvas.sx,this.transformCanvas.h*this.transformCanvas.sy)},prepareLayer:function(){if(this.data.tt>=1){var t=this.buffers[0].getContext("2d");this.clearCanvas(t),t.drawImage(this.canvasContext.canvas,0,0),this.currentTransform=this.canvasContext.getTransform(),this.canvasContext.setTransform(1,0,0,1,0,0),this.clearCanvas(this.canvasContext),this.canvasContext.setTransform(this.currentTransform)}},exitLayer:function(){if(this.data.tt>=1){var t=this.buffers[1],e=t.getContext("2d");if(this.clearCanvas(e),e.drawImage(this.canvasContext.canvas,0,0),this.canvasContext.setTransform(1,0,0,1,0,0),this.clearCanvas(this.canvasContext),this.canvasContext.setTransform(this.currentTransform),this.comp.getElementById("tp"in this.data?this.data.tp:this.data.ind-1).renderFrame(!0),this.canvasContext.setTransform(1,0,0,1,0,0),this.data.tt>=3&&!document._isProxy){var i=assetLoader.getLumaCanvas(this.canvasContext.canvas);i.getContext("2d").drawImage(this.canvasContext.canvas,0,0),this.clearCanvas(this.canvasContext),this.canvasContext.drawImage(i,0,0)}this.canvasContext.globalCompositeOperation=operationsMap[this.data.tt],this.canvasContext.drawImage(t,0,0),this.canvasContext.globalCompositeOperation="destination-over",this.canvasContext.drawImage(this.buffers[0],0,0),this.canvasContext.setTransform(this.currentTransform),this.canvasContext.globalCompositeOperation="source-over"}},renderFrame:function(t){if(!this.hidden&&!this.data.hd&&(1!==this.data.td||t)){this.renderTransform(),this.renderRenderable(),this.renderLocalTransform(),this.setBlendMode();var e=0===this.data.ty;this.prepareLayer(),this.globalData.renderer.save(e),this.globalData.renderer.ctxTransform(this.finalTransform.localMat.props),this.globalData.renderer.ctxOpacity(this.finalTransform.localOpacity),this.renderInnerContent(),this.globalData.renderer.restore(e),this.exitLayer(),this.maskManager.hasMasks&&this.globalData.renderer.restore(!0),this._isFirstFrame&&(this._isFirstFrame=!1)}},destroy:function(){this.canvasContext=null,this.data=null,this.globalData=null,this.maskManager.destroy()},mHelper:new Matrix},CVBaseElement.prototype.hide=CVBaseElement.prototype.hideElement,CVBaseElement.prototype.show=CVBaseElement.prototype.showElement,CVShapeData.prototype.setAsAnimated=SVGShapeData.prototype.setAsAnimated,extendPrototype([BaseElement,TransformElement,CVBaseElement,IShapeElement,HierarchyElement,FrameElement,RenderableElement],CVShapeElement),CVShapeElement.prototype.initElement=RenderableDOMElement.prototype.initElement,CVShapeElement.prototype.transformHelper={opacity:1,_opMdf:!1},CVShapeElement.prototype.dashResetter=[],CVShapeElement.prototype.createContent=function(){this.searchShapes(this.shapesData,this.itemsData,this.prevViewData,!0,[])},CVShapeElement.prototype.createStyleElement=function(t,e){var i={data:t,type:t.ty,preTransforms:this.transformsManager.addTransformSequence(e),transforms:[],elements:[],closed:!0===t.hd},r={};if("fl"===t.ty||"st"===t.ty?(r.c=PropertyFactory.getProp(this,t.c,1,255,this),r.c.k||(i.co="rgb("+bmFloor(r.c.v[0])+","+bmFloor(r.c.v[1])+","+bmFloor(r.c.v[2])+")")):"gf"!==t.ty&&"gs"!==t.ty||(r.s=PropertyFactory.getProp(this,t.s,1,null,this),r.e=PropertyFactory.getProp(this,t.e,1,null,this),r.h=PropertyFactory.getProp(this,t.h||{k:0},0,.01,this),r.a=PropertyFactory.getProp(this,t.a||{k:0},0,degToRads,this),r.g=new GradientProperty(this,t.g,this)),r.o=PropertyFactory.getProp(this,t.o,0,.01,this),"st"===t.ty||"gs"===t.ty){if(i.lc=lineCapEnum[t.lc||2],i.lj=lineJoinEnum[t.lj||2],1==t.lj&&(i.ml=t.ml),r.w=PropertyFactory.getProp(this,t.w,0,null,this),r.w.k||(i.wi=r.w.v),t.d){var s=new DashProperty(this,t.d,"canvas",this);r.d=s,r.d.k||(i.da=r.d.dashArray,i.do=r.d.dashoffset[0])}}else i.r=2===t.r?"evenodd":"nonzero";return this.stylesList.push(i),r.style=i,r},CVShapeElement.prototype.createGroupElement=function(){return{it:[],prevViewData:[]}},CVShapeElement.prototype.createTransformElement=function(t){return{transform:{opacity:1,_opMdf:!1,key:this.transformsManager.getNewKey(),op:PropertyFactory.getProp(this,t.o,0,.01,this),mProps:TransformPropertyFactory.getTransformProperty(this,t,this)}}},CVShapeElement.prototype.createShapeElement=function(t){var e=new CVShapeData(this,t,this.stylesList,this.transformsManager);return this.shapes.push(e),this.addShapeToModifiers(e),e},CVShapeElement.prototype.reloadShapes=function(){var t;this._isFirstFrame=!0;var e=this.itemsData.length;for(t=0;t=0;a-=1){if((h=this.searchProcessedElement(t[a]))?e[a]=i[h-1]:t[a]._shouldRender=r,"fl"===t[a].ty||"st"===t[a].ty||"gf"===t[a].ty||"gs"===t[a].ty)h?e[a].style.closed=!1:e[a]=this.createStyleElement(t[a],d),m.push(e[a].style);else if("gr"===t[a].ty){if(h)for(o=e[a].it.length,n=0;n=0;s-=1)"tr"===e[s].ty?(a=i[s].transform,this.renderShapeTransform(t,a)):"sh"===e[s].ty||"el"===e[s].ty||"rc"===e[s].ty||"sr"===e[s].ty?this.renderPath(e[s],i[s]):"fl"===e[s].ty?this.renderFill(e[s],i[s],a):"st"===e[s].ty?this.renderStroke(e[s],i[s],a):"gf"===e[s].ty||"gs"===e[s].ty?this.renderGradientFill(e[s],i[s],a):"gr"===e[s].ty?this.renderShape(a,e[s].it,i[s].it):e[s].ty;r&&this.drawLayer()},CVShapeElement.prototype.renderStyledShape=function(t,e){if(this._isFirstFrame||e._mdf||t.transforms._mdf){var i,r,s,a=t.trNodes,n=e.paths,o=n._length;a.length=0;var h=t.transforms.finalTransform;for(s=0;s=1?f=.99:f<=-1&&(f=-.99);var m=l*f,c=Math.cos(p+e.a.v)*m+o[0],d=Math.sin(p+e.a.v)*m+o[1];r=n.createRadialGradient(c,d,0,o[0],o[1],l)}var u=t.g.p,y=e.g.c,g=1;for(a=0;ao&&"xMidYMid slice"===h||ns&&"meet"===o||as&&"slice"===o)?(i-this.transformCanvas.w*(r/this.transformCanvas.h))/2*this.renderConfig.dpr:"xMax"===l&&(as&&"slice"===o)?(i-this.transformCanvas.w*(r/this.transformCanvas.h))*this.renderConfig.dpr:0,this.transformCanvas.ty="YMid"===p&&(a>s&&"meet"===o||as&&"meet"===o||a=0;t-=1)this.elements[t]&&this.elements[t].destroy&&this.elements[t].destroy();this.elements.length=0,this.globalData.canvasContext=null,this.animationItem.container=null,this.destroyed=!0},CanvasRendererBase.prototype.renderFrame=function(t,e){if((this.renderedFrame!==t||!0!==this.renderConfig.clearCanvas||e)&&!this.destroyed&&-1!==t){var i;this.renderedFrame=t,this.globalData.frameNum=t-this.animationItem._isFirstFrame,this.globalData.frameId+=1,this.globalData._mdf=!this.renderConfig.clearCanvas||e,this.globalData.projectInterface.currentFrame=t;var r=this.layers.length;for(this.completeLayers||this.checkLayers(t),i=r-1;i>=0;i-=1)(this.completeLayers||this.elements[i])&&this.elements[i].prepareFrame(t-this.layers[i].st);if(this.globalData._mdf){for(!0===this.renderConfig.clearCanvas?this.canvasContext.clearRect(0,0,this.transformCanvas.w,this.transformCanvas.h):this.save(),i=r-1;i>=0;i-=1)(this.completeLayers||this.elements[i])&&this.elements[i].renderFrame();!0!==this.renderConfig.clearCanvas&&this.restore()}}},CanvasRendererBase.prototype.buildItem=function(t){var e=this.elements;if(!e[t]&&99!==this.layers[t].ty){var i=this.createItem(this.layers[t],this,this.globalData);e[t]=i,i.initExpressions()}},CanvasRendererBase.prototype.checkPendingElements=function(){for(;this.pendingElements.length;){this.pendingElements.pop().checkParenting()}},CanvasRendererBase.prototype.hide=function(){this.animationItem.container.style.display="none"},CanvasRendererBase.prototype.show=function(){this.animationItem.container.style.display="block"},CVContextData.prototype.duplicate=function(){var t=2*this._length,e=0;for(e=this._length;e=0;t-=1)(this.completeLayers||this.elements[t])&&this.elements[t].renderFrame()},CVCompElement.prototype.destroy=function(){var t;for(t=this.layers.length-1;t>=0;t-=1)this.elements[t]&&this.elements[t].destroy();this.layers=null,this.elements=null},CVCompElement.prototype.createComp=function(t){return new CVCompElement(t,this.globalData,this)},extendPrototype([CanvasRendererBase],CanvasRenderer),CanvasRenderer.prototype.createComp=function(t){return new CVCompElement(t,this.globalData,this)},HBaseElement.prototype={checkBlendMode:function(){},initRendererElement:function(){this.baseElement=createTag(this.data.tg||"div"),this.data.hasMask?(this.svgElement=createNS("svg"),this.layerElement=createNS("g"),this.maskedElement=this.layerElement,this.svgElement.appendChild(this.layerElement),this.baseElement.appendChild(this.svgElement)):this.layerElement=this.baseElement,styleDiv(this.baseElement)},createContainerElements:function(){this.renderableEffectsManager=new CVEffects(this),this.transformedElement=this.baseElement,this.maskedElement=this.layerElement,this.data.ln&&this.layerElement.setAttribute("id",this.data.ln),this.data.cl&&this.layerElement.setAttribute("class",this.data.cl),0!==this.data.bm&&this.setBlendMode()},renderElement:function(){var t=this.transformedElement?this.transformedElement.style:{};if(this.finalTransform._matMdf){var e=this.finalTransform.mat.toCSS();t.transform=e,t.webkitTransform=e}this.finalTransform._opMdf&&(t.opacity=this.finalTransform.mProp.o.v)},renderFrame:function(){this.data.hd||this.hidden||(this.renderTransform(),this.renderRenderable(),this.renderElement(),this.renderInnerContent(),this._isFirstFrame&&(this._isFirstFrame=!1))},destroy:function(){this.layerElement=null,this.transformedElement=null,this.matteElement&&(this.matteElement=null),this.maskManager&&(this.maskManager.destroy(),this.maskManager=null)},createRenderableComponents:function(){this.maskManager=new MaskElement(this.data,this,this.globalData)},addEffects:function(){},setMatte:function(){}},HBaseElement.prototype.getBaseElement=SVGBaseElement.prototype.getBaseElement,HBaseElement.prototype.destroyBaseElement=HBaseElement.prototype.destroy,HBaseElement.prototype.buildElementParenting=BaseRenderer.prototype.buildElementParenting,extendPrototype([BaseElement,TransformElement,HBaseElement,HierarchyElement,FrameElement,RenderableDOMElement],HSolidElement),HSolidElement.prototype.createContent=function(){var t;this.data.hasMask?((t=createNS("rect")).setAttribute("width",this.data.sw),t.setAttribute("height",this.data.sh),t.setAttribute("fill",this.data.sc),this.svgElement.setAttribute("width",this.data.sw),this.svgElement.setAttribute("height",this.data.sh)):((t=createTag("div")).style.width=this.data.sw+"px",t.style.height=this.data.sh+"px",t.style.backgroundColor=this.data.sc),this.layerElement.appendChild(t)},extendPrototype([BaseElement,TransformElement,HSolidElement,SVGShapeElement,HBaseElement,HierarchyElement,FrameElement,RenderableElement],HShapeElement),HShapeElement.prototype._renderShapeFrame=HShapeElement.prototype.renderInnerContent,HShapeElement.prototype.createContent=function(){var t;if(this.baseElement.style.fontSize=0,this.data.hasMask)this.layerElement.appendChild(this.shapesContainer),t=this.svgElement;else{t=createNS("svg");var e=this.comp.data?this.comp.data:this.globalData.compSize;t.setAttribute("width",e.w),t.setAttribute("height",e.h),t.appendChild(this.shapesContainer),this.layerElement.appendChild(t)}this.searchShapes(this.shapesData,this.itemsData,this.prevViewData,this.shapesContainer,0,[],!0),this.filterUniqueShapes(),this.shapeCont=t},HShapeElement.prototype.getTransformedPoint=function(t,e){var i,r=t.length;for(i=0;i0&&o<1&&f[m].push(this.calculateF(o,t,e,i,r,m)):(h=a*a-4*n*s)>=0&&((l=(-a+bmSqrt(h))/(2*s))>0&&l<1&&f[m].push(this.calculateF(l,t,e,i,r,m)),(p=(-a-bmSqrt(h))/(2*s))>0&&p<1&&f[m].push(this.calculateF(p,t,e,i,r,m))));this.shapeBoundingBox.left=bmMin.apply(null,f[0]),this.shapeBoundingBox.top=bmMin.apply(null,f[1]),this.shapeBoundingBox.right=bmMax.apply(null,f[0]),this.shapeBoundingBox.bottom=bmMax.apply(null,f[1])},HShapeElement.prototype.calculateF=function(t,e,i,r,s,a){return bmPow(1-t,3)*e[a]+3*bmPow(1-t,2)*t*i[a]+3*(1-t)*bmPow(t,2)*r[a]+bmPow(t,3)*s[a]},HShapeElement.prototype.calculateBoundingBox=function(t,e){var i,r=t.length;for(i=0;ii&&(i=s)}i*=t.mult}else i=t.v*t.mult;e.x-=i,e.xMax+=i,e.y-=i,e.yMax+=i},HShapeElement.prototype.currentBoxContains=function(t){return this.currentBBox.x<=t.x&&this.currentBBox.y<=t.y&&this.currentBBox.width+this.currentBBox.x>=t.x+t.width&&this.currentBBox.height+this.currentBBox.y>=t.y+t.height},HShapeElement.prototype.renderInnerContent=function(){if(this._renderShapeFrame(),!this.hidden&&(this._isFirstFrame||this._mdf)){var t=this.tempBoundingBox,e=999999;if(t.x=e,t.xMax=-e,t.y=e,t.yMax=-e,this.calculateBoundingBox(this.itemsData,t),t.width=t.xMax=0;t-=1){var r=this.hierarchy[t].finalTransform.mProp;this.mat.translate(-r.p.v[0],-r.p.v[1],r.p.v[2]),this.mat.rotateX(-r.or.v[0]).rotateY(-r.or.v[1]).rotateZ(r.or.v[2]),this.mat.rotateX(-r.rx.v).rotateY(-r.ry.v).rotateZ(r.rz.v),this.mat.scale(1/r.s.v[0],1/r.s.v[1],1/r.s.v[2]),this.mat.translate(r.a.v[0],r.a.v[1],r.a.v[2])}if(this.p?this.mat.translate(-this.p.v[0],-this.p.v[1],this.p.v[2]):this.mat.translate(-this.px.v,-this.py.v,this.pz.v),this.a){var s;s=this.p?[this.p.v[0]-this.a.v[0],this.p.v[1]-this.a.v[1],this.p.v[2]-this.a.v[2]]:[this.px.v-this.a.v[0],this.py.v-this.a.v[1],this.pz.v-this.a.v[2]];var a=Math.sqrt(Math.pow(s[0],2)+Math.pow(s[1],2)+Math.pow(s[2],2)),n=[s[0]/a,s[1]/a,s[2]/a],o=Math.sqrt(n[2]*n[2]+n[0]*n[0]),h=Math.atan2(n[1],o),l=Math.atan2(n[0],-n[2]);this.mat.rotateY(l).rotateX(-h)}this.mat.rotateX(-this.rx.v).rotateY(-this.ry.v).rotateZ(this.rz.v),this.mat.rotateX(-this.or.v[0]).rotateY(-this.or.v[1]).rotateZ(this.or.v[2]),this.mat.translate(this.globalData.compSize.w/2,this.globalData.compSize.h/2,0),this.mat.translate(0,0,this.pe.v);var p=!this._prevMat.equals(this.mat);if((p||this.pe._mdf)&&this.comp.threeDElements){var f,m,c;for(e=this.comp.threeDElements.length,t=0;t=t)return this.threeDElements[e].perspectiveElem;e+=1}return null},HybridRendererBase.prototype.createThreeDContainer=function(t,e){var i,r,s=createTag("div");styleDiv(s);var a=createTag("div");if(styleDiv(a),"3d"===e){(i=s.style).width=this.globalData.compSize.w+"px",i.height=this.globalData.compSize.h+"px";var n="50% 50%";i.webkitTransformOrigin=n,i.mozTransformOrigin=n,i.transformOrigin=n;var o="matrix3d(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1)";(r=a.style).transform=o,r.webkitTransform=o}s.appendChild(a);var h={container:a,perspectiveElem:s,startPos:t,endPos:t,type:e};return this.threeDElements.push(h),h},HybridRendererBase.prototype.build3dContainers=function(){var t,e,i=this.layers.length,r="";for(t=0;t=0;t-=1)this.resizerElem.appendChild(this.threeDElements[t].perspectiveElem)},HybridRendererBase.prototype.addTo3dContainer=function(t,e){for(var i=0,r=this.threeDElements.length;in?(t=s/this.globalData.compSize.w,e=s/this.globalData.compSize.w,i=0,r=(a-this.globalData.compSize.h*(s/this.globalData.compSize.w))/2):(t=a/this.globalData.compSize.h,e=a/this.globalData.compSize.h,i=(s-this.globalData.compSize.w*(a/this.globalData.compSize.h))/2,r=0);var o=this.resizerElem.style;o.webkitTransform="matrix3d("+t+",0,0,0,0,"+e+",0,0,0,0,1,0,"+i+","+r+",0,1)",o.transform=o.webkitTransform},HybridRendererBase.prototype.renderFrame=SVGRenderer.prototype.renderFrame,HybridRendererBase.prototype.hide=function(){this.resizerElem.style.display="none"},HybridRendererBase.prototype.show=function(){this.resizerElem.style.display="block"},HybridRendererBase.prototype.initItems=function(){if(this.buildAllItems(),this.camera)this.camera.setup();else{var t,e=this.globalData.compSize.w,i=this.globalData.compSize.h,r=this.threeDElements.length;for(t=0;t=o;)t/=2,e/=2,i>>>=1;return(t+i)/e};return b.int32=function(){return 0|v.g(4)},b.quick=function(){return v.g(4)/4294967296},b.double=b,m(c(v.S),t),(d.pass||u||function(t,i,r,s){return s&&(s.S&&p(s,v),t.state=function(){return p(v,{})}),r?(e.random=t,i):t})(b,g,"global"in d?d.global:this==e,d.state)},m(e.random(),t)}function initialize$2(t){seedRandom([],t)}var propTypes={SHAPE:"shape"};function _typeof$1(t){return _typeof$1="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},_typeof$1(t)}var ExpressionManager=function(){var ob={},Math=BMMath,window=null,document=null,XMLHttpRequest=null,fetch=null,frames=null,_lottieGlobal={};function resetFrame(){_lottieGlobal={}}function $bm_isInstanceOfArray(t){return t.constructor===Array||t.constructor===Float32Array}function isNumerable(t,e){return"number"===t||e instanceof Number||"boolean"===t||"string"===t}function $bm_neg(t){var e=_typeof$1(t);if("number"===e||t instanceof Number||"boolean"===e)return-t;if($bm_isInstanceOfArray(t)){var i,r=t.length,s=[];for(i=0;ii){var r=i;i=e,e=r}return Math.min(Math.max(t,e),i)}function radiansToDegrees(t){return t/degToRads}var radians_to_degrees=radiansToDegrees;function degreesToRadians(t){return t*degToRads}var degrees_to_radians=radiansToDegrees,helperLengthArray=[0,0,0,0,0,0];function length(t,e){if("number"==typeof t||t instanceof Number)return e=e||0,Math.abs(t-e);var i;e||(e=helperLengthArray);var r=Math.min(t.length,e.length),s=0;for(i=0;i.5?l/(2-n-o):l/(n+o),n){case r:e=(s-a)/l+(s1&&(i-=1),i<1/6?t+6*(e-t)*i:i<.5?e:i<2/3?t+(e-t)*(2/3-i)*6:t}function hslToRgb(t){var e,i,r,s=t[0],a=t[1],n=t[2];if(0===a)e=n,r=n,i=n;else{var o=n<.5?n*(1+a):n+a-n*a,h=2*n-o;e=hue2rgb(h,o,s+1/3),i=hue2rgb(h,o,s),r=hue2rgb(h,o,s-1/3)}return[e,i,r,t[3]]}function linear(t,e,i,r,s){if(void 0!==r&&void 0!==s||(r=e,s=i,e=0,i=1),i=i)return s;var n,o=i===e?0:(t-e)/(i-e);if(!r.length)return r+(s-r)*o;var h=r.length,l=createTypedArray("float32",h);for(n=0;n1){for(r=0;r1?e=1:e<0&&(e=0);var n=t(e);if($bm_isInstanceOfArray(s)){var o,h=s.length,l=createTypedArray("float32",h);for(o=0;odata.k[e].t&&tdata.k[e+1].t-t?(i=e+2,r=data.k[e+1].t):(i=e+1,r=data.k[e].t);break}}-1===i&&(i=e+1,r=data.k[e].t)}else i=0,r=0;var a={};return a.index=i,a.time=r/elem.comp.globalData.frameRate,a}function key(t){var e,i,r;if(!data.k.length||"number"==typeof data.k[0])throw new Error("The property has no keyframe at index "+t);t-=1,e={time:data.k[t].t/elem.comp.globalData.frameRate,value:[]};var s=Object.prototype.hasOwnProperty.call(data.k[t],"s")?data.k[t].s:data.k[t-1].e;for(r=s.length,i=0;il.length-1)&&(e=l.length-1),r=p-(s=l[l.length-1-e].t)),"pingpong"===t){if(Math.floor((h-s)/r)%2!=0)return this.getValueAtTime((r-(h-s)%r+s)/this.comp.globalData.frameRate,0)}else{if("offset"===t){var f=this.getValueAtTime(s/this.comp.globalData.frameRate,0),m=this.getValueAtTime(p/this.comp.globalData.frameRate,0),c=this.getValueAtTime(((h-s)%r+s)/this.comp.globalData.frameRate,0),d=Math.floor((h-s)/r);if(this.pv.length){for(n=(o=new Array(f.length)).length,a=0;a=p)return this.pv;if(i?s=p+(r=e?Math.abs(this.elem.comp.globalData.frameRate*e):Math.max(0,this.elem.data.op-p)):((!e||e>l.length-1)&&(e=l.length-1),r=(s=l[e].t)-p),"pingpong"===t){if(Math.floor((p-h)/r)%2==0)return this.getValueAtTime(((p-h)%r+p)/this.comp.globalData.frameRate,0)}else{if("offset"===t){var f=this.getValueAtTime(p/this.comp.globalData.frameRate,0),m=this.getValueAtTime(s/this.comp.globalData.frameRate,0),c=this.getValueAtTime((r-(p-h)%r+p)/this.comp.globalData.frameRate,0),d=Math.floor((p-h)/r)+1;if(this.pv.length){for(n=(o=new Array(f.length)).length,a=0;a1?(s+t-a)/(e-1):1,o=0,h=0;for(i=this.pv.length?createTypedArray("float32",this.pv.length):0;on){var p=o,f=i.c&&o===h-1?0:o+1,m=(n-l)/a[o].addedLength;r=bez.getPointInSegment(i.v[p],i.v[f],i.o[p],i.i[f],m,a[o]);break}l+=a[o].addedLength,o+=1}return r||(r=i.c?[i.v[0][0],i.v[0][1]]:[i.v[i._length-1][0],i.v[i._length-1][1]]),r},vectorOnPath:function(t,e,i){1==t?t=this.v.c:0==t&&(t=.999);var r=this.pointOnPath(t,e),s=this.pointOnPath(t+.001,e),a=s[0]-r[0],n=s[1]-r[1],o=Math.sqrt(Math.pow(a,2)+Math.pow(n,2));return 0===o?[0,0]:"tangent"===i?[a/o,n/o]:[-n/o,a/o]},tangentOnPath:function(t,e){return this.vectorOnPath(t,e,"tangent")},normalOnPath:function(t,e){return this.vectorOnPath(t,e,"normal")},setGroupProperty:expressionHelpers.setGroupProperty,getValueAtTime:expressionHelpers.getStaticValueAtTime},extendPrototype([l],o),extendPrototype([l],h),h.prototype.getValueAtTime=function(t){return this._cachingAtTime||(this._cachingAtTime={shapeValue:shapePool.clone(this.pv),lastIndex:0,lastTime:initialDefaultFrame}),t*=this.elem.globalData.frameRate,(t-=this.offsetTime)!==this._cachingAtTime.lastTime&&(this._cachingAtTime.lastIndex=this._cachingAtTime.lastTime=l?c<0?r:s:r+m*Math.pow((a-t)/c,1/i),p[f]=n,f+=1,o+=256/255;return p.join(" ")},SVGProLevelsFilter.prototype.renderFrame=function(t){if(t||this.filterManager._mdf){var e,i=this.filterManager.effectElements;this.feFuncRComposed&&(t||i[3].p._mdf||i[4].p._mdf||i[5].p._mdf||i[6].p._mdf||i[7].p._mdf)&&(e=this.getTableValue(i[3].p.v,i[4].p.v,i[5].p.v,i[6].p.v,i[7].p.v),this.feFuncRComposed.setAttribute("tableValues",e),this.feFuncGComposed.setAttribute("tableValues",e),this.feFuncBComposed.setAttribute("tableValues",e)),this.feFuncR&&(t||i[10].p._mdf||i[11].p._mdf||i[12].p._mdf||i[13].p._mdf||i[14].p._mdf)&&(e=this.getTableValue(i[10].p.v,i[11].p.v,i[12].p.v,i[13].p.v,i[14].p.v),this.feFuncR.setAttribute("tableValues",e)),this.feFuncG&&(t||i[17].p._mdf||i[18].p._mdf||i[19].p._mdf||i[20].p._mdf||i[21].p._mdf)&&(e=this.getTableValue(i[17].p.v,i[18].p.v,i[19].p.v,i[20].p.v,i[21].p.v),this.feFuncG.setAttribute("tableValues",e)),this.feFuncB&&(t||i[24].p._mdf||i[25].p._mdf||i[26].p._mdf||i[27].p._mdf||i[28].p._mdf)&&(e=this.getTableValue(i[24].p.v,i[25].p.v,i[26].p.v,i[27].p.v,i[28].p.v),this.feFuncB.setAttribute("tableValues",e)),this.feFuncA&&(t||i[31].p._mdf||i[32].p._mdf||i[33].p._mdf||i[34].p._mdf||i[35].p._mdf)&&(e=this.getTableValue(i[31].p.v,i[32].p.v,i[33].p.v,i[34].p.v,i[35].p.v),this.feFuncA.setAttribute("tableValues",e))}},extendPrototype([SVGComposableEffect],SVGDropShadowEffect),SVGDropShadowEffect.prototype.renderFrame=function(t){if(t||this.filterManager._mdf){if((t||this.filterManager.effectElements[4].p._mdf)&&this.feGaussianBlur.setAttribute("stdDeviation",this.filterManager.effectElements[4].p.v/4),t||this.filterManager.effectElements[0].p._mdf){var e=this.filterManager.effectElements[0].p.v;this.feFlood.setAttribute("flood-color",rgbToHex(Math.round(255*e[0]),Math.round(255*e[1]),Math.round(255*e[2])))}if((t||this.filterManager.effectElements[1].p._mdf)&&this.feFlood.setAttribute("flood-opacity",this.filterManager.effectElements[1].p.v/255),t||this.filterManager.effectElements[2].p._mdf||this.filterManager.effectElements[3].p._mdf){var i=this.filterManager.effectElements[3].p.v,r=(this.filterManager.effectElements[2].p.v-90)*degToRads,s=i*Math.cos(r),a=i*Math.sin(r);this.feOffset.setAttribute("dx",s),this.feOffset.setAttribute("dy",a)}}};var _svgMatteSymbols=[];function SVGMatte3Effect(t,e,i){this.initialized=!1,this.filterManager=e,this.filterElem=t,this.elem=i,i.matteElement=createNS("g"),i.matteElement.appendChild(i.layerElement),i.matteElement.appendChild(i.transformedElement),i.baseElement=i.matteElement}function SVGGaussianBlurEffect(t,e,i,r){t.setAttribute("x","-100%"),t.setAttribute("y","-100%"),t.setAttribute("width","300%"),t.setAttribute("height","300%"),this.filterManager=e;var s=createNS("feGaussianBlur");s.setAttribute("result",r),t.appendChild(s),this.feGaussianBlur=s}function TransformEffect(){}function SVGTransformEffect(t,e){this.init(e)}function CVTransformEffect(t){this.init(t)}return SVGMatte3Effect.prototype.findSymbol=function(t){for(var e=0,i=_svgMatteSymbols.length;e1?i[1]=1:i[1]<=0&&(i[1]=0),HSVtoRGB(i[0],i[1],i[2])}function addBrightnessToRGB(t,e){var i=RGBtoHSV(255*t[0],255*t[1],255*t[2]);return i[2]+=e,i[2]>1?i[2]=1:i[2]<0&&(i[2]=0),HSVtoRGB(i[0],i[1],i[2])}function addHueToRGB(t,e){var i=RGBtoHSV(255*t[0],255*t[1],255*t[2]);return i[0]+=e/360,i[0]>1?i[0]-=1:i[0]<0&&(i[0]+=1),HSVtoRGB(i[0],i[1],i[2])}var rgbToHex=function(){var t,e,i=[];for(t=0;t<256;t+=1)e=t.toString(16),i[t]=1===e.length?"0"+e:e;return function(t,e,r){return t<0&&(t=0),e<0&&(e=0),r<0&&(r=0),"#"+i[t]+i[e]+i[r]}}(),setSubframeEnabled=function(t){subframeEnabled=!!t},getSubframeEnabled=function(){return subframeEnabled},setExpressionsPlugin=function(t){expressionsPlugin=t},getExpressionsPlugin=function(){return expressionsPlugin},setExpressionInterfaces=function(t){expressionsInterfaces=t},getExpressionInterfaces=function(){return expressionsInterfaces},setDefaultCurveSegments=function(t){defaultCurveSegments=t},getDefaultCurveSegments=function(){return defaultCurveSegments},setIdPrefix=function(t){idPrefix$1=t},getIdPrefix=function(){return idPrefix$1};function createNS(t){return document.createElementNS(svgNS,t)}function _typeof$5(t){return _typeof$5="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},_typeof$5(t)}var dataManager=function(){var t,e,i=1,r=[],s={onmessage:function(){},postMessage:function(e){t({data:e})}},a={postMessage:function(t){s.onmessage({data:t})}};function n(){e||(e=function(e){if(window.Worker&&window.Blob&&getWebWorker()){var i=new Blob(["var _workerSelf = self; self.onmessage = ",e.toString()],{type:"text/javascript"}),r=URL.createObjectURL(i);return new Worker(r)}return t=e,s}((function(t){if(a.dataManager||(a.dataManager=function(){function t(s,a){var n,o,h,l,p,m,c=s.length;for(o=0;o=0;e-=1)if("sh"===t[e].ty)if(t[e].ks.k.i)r(t[e].ks.k);else for(a=t[e].ks.k.length,s=0;si[0]||!(i[0]>t[0])&&(t[1]>i[1]||!(i[1]>t[1])&&(t[2]>i[2]||!(i[2]>t[2])&&null))}var a,n=function(){var t=[4,4,14];function e(t){var e,i,r,s=t.length;for(e=0;e=0;i-=1)if("sh"===t[i].ty)if(t[i].ks.k.i)t[i].ks.k.c=t[i].closed;else for(s=t[i].ks.k.length,r=0;r500)&&(this._imageLoaded(),clearInterval(i)),e+=1}.bind(this),50)}function a(t){var e={assetData:t},i=r(t,this.assetsPath,this.path);return dataManager.loadData(i,function(t){e.img=t,this._footageLoaded()}.bind(this),function(){e.img={},this._footageLoaded()}.bind(this)),e}function n(){this._imageLoaded=e.bind(this),this._footageLoaded=i.bind(this),this.testImageLoaded=s.bind(this),this.createFootageData=a.bind(this),this.assetsPath="",this.path="",this.totalImages=0,this.totalFootages=0,this.loadedAssets=0,this.loadedFootagesCount=0,this.imagesLoadedCb=null,this.images=[]}return n.prototype={loadAssets:function(t,e){var i;this.imagesLoadedCb=e;var r=t.length;for(i=0;ithis.animationData.op&&(this.animationData.op=t.op,this.totalFrames=Math.floor(t.op-this.animationData.ip));var e,i,r=this.animationData.layers,s=r.length,a=t.layers,n=a.length;for(i=0;ithis.timeCompleted&&(this.currentFrame=this.timeCompleted),this.trigger("enterFrame"),this.renderFrame(),this.trigger("drawnFrame")},AnimationItem.prototype.renderFrame=function(){if(!1!==this.isLoaded&&this.renderer)try{this.expressionsPlugin&&this.expressionsPlugin.resetFrame(),this.renderer.renderFrame(this.currentFrame+this.firstFrame)}catch(t){this.triggerRenderFrameError(t)}},AnimationItem.prototype.play=function(t){t&&this.name!==t||!0===this.isPaused&&(this.isPaused=!1,this.trigger("_play"),this.audioController.resume(),this._idle&&(this._idle=!1,this.trigger("_active")))},AnimationItem.prototype.pause=function(t){t&&this.name!==t||!1===this.isPaused&&(this.isPaused=!0,this.trigger("_pause"),this._idle=!0,this.trigger("_idle"),this.audioController.pause())},AnimationItem.prototype.togglePause=function(t){t&&this.name!==t||(!0===this.isPaused?this.play():this.pause())},AnimationItem.prototype.stop=function(t){t&&this.name!==t||(this.pause(),this.playCount=0,this._completedLoop=!1,this.setCurrentRawFrameValue(0))},AnimationItem.prototype.getMarkerData=function(t){for(var e,i=0;i=this.totalFrames-1&&this.frameModifier>0?this.loop&&this.playCount!==this.loop?e>=this.totalFrames?(this.playCount+=1,this.checkSegments(e%this.totalFrames)||(this.setCurrentRawFrameValue(e%this.totalFrames),this._completedLoop=!0,this.trigger("loopComplete"))):this.setCurrentRawFrameValue(e):this.checkSegments(e>this.totalFrames?e%this.totalFrames:0)||(i=!0,e=this.totalFrames-1):e<0?this.checkSegments(e%this.totalFrames)||(!this.loop||this.playCount--<=0&&!0!==this.loop?(i=!0,e=0):(this.setCurrentRawFrameValue(this.totalFrames+e%this.totalFrames),this._completedLoop?this.trigger("loopComplete"):this._completedLoop=!0)):this.setCurrentRawFrameValue(e),i&&(this.setCurrentRawFrameValue(e),this.pause(),this.trigger("complete"))}},AnimationItem.prototype.adjustSegment=function(t,e){this.playCount=0,t[1]0&&(this.playSpeed<0?this.setSpeed(-this.playSpeed):this.setDirection(-1)),this.totalFrames=t[0]-t[1],this.timeCompleted=this.totalFrames,this.firstFrame=t[1],this.setCurrentRawFrameValue(this.totalFrames-.001-e)):t[1]>t[0]&&(this.frameModifier<0&&(this.playSpeed<0?this.setSpeed(-this.playSpeed):this.setDirection(1)),this.totalFrames=t[1]-t[0],this.timeCompleted=this.totalFrames,this.firstFrame=t[0],this.setCurrentRawFrameValue(.001+e)),this.trigger("segmentStart")},AnimationItem.prototype.setSegment=function(t,e){var i=-1;this.isPaused&&(this.currentRawFrame+this.firstFramee&&(i=e-t)),this.firstFrame=t,this.totalFrames=e-t,this.timeCompleted=this.totalFrames,-1!==i&&this.goToAndStop(i,!0)},AnimationItem.prototype.playSegments=function(t,e){if(e&&(this.segments.length=0),"object"===_typeof$4(t[0])){var i,r=t.length;for(i=0;i=0;i-=1)e[i].animation.destroy(t)},t.freeze=function(){n=!0},t.unfreeze=function(){n=!1,d()},t.setVolume=function(t,i){var s;for(s=0;s=.001?function(t,e,i,r){for(var s=0;s<4;++s){var a=h(e,i,r);if(0===a)return e;e-=(o(e,i,r)-t)/a}return e}(t,l,e,r):0===p?l:function(t,e,i,r,s){var a,n,h=0;do{(a=o(n=e+(i-e)/2,r,s)-t)>0?i=n:e=n}while(Math.abs(a)>1e-7&&++h<10);return n}(t,a,a+i,e,r)}},t}(),pooling={double:function(t){return t.concat(createSizedArray(t.length))}},poolFactory=function(t,e,i){var r=0,s=t,a=createSizedArray(s);return{newElement:function(){return r?a[r-=1]:e()},release:function(t){r===s&&(a=pooling.double(a),s*=2),i&&i(t),a[r]=t,r+=1}}},bezierLengthPool=poolFactory(8,(function(){return{addedLength:0,percents:createTypedArray("float32",getDefaultCurveSegments()),lengths:createTypedArray("float32",getDefaultCurveSegments())}})),segmentsLengthPool=poolFactory(8,(function(){return{lengths:[],totalLength:0}}),(function(t){var e,i=t.lengths.length;for(e=0;e-.001&&n<.001}var i=function(t,e,i,r){var s,a,n,o,h,l,p=getDefaultCurveSegments(),f=0,m=[],c=[],d=bezierLengthPool.newElement();for(n=i.length,s=0;sn?-1:1,l=!0;l;)if(r[a]<=n&&r[a+1]>n?(o=(n-r[a])/(r[a+1]-r[a]),l=!1):a+=h,a<0||a>=s-1){if(a===s-1)return i[a];l=!1}return i[a]+(i[a+1]-i[a])*o}var h=createTypedArray("float32",8);return{getSegmentsLength:function(t){var e,r=segmentsLengthPool.newElement(),s=t.c,a=t.v,n=t.o,o=t.i,h=t._length,l=r.lengths,p=0;for(e=0;e1&&(a=1);var p,f=o(a,l),m=o(n=n>1?1:n,l),c=e.length,d=1-f,u=1-m,y=d*d*d,g=f*d*d*3,v=f*f*d*3,b=f*f*f,x=d*d*u,P=f*d*u+d*f*u+d*d*m,E=f*f*u+d*f*m+f*d*m,S=f*f*m,C=d*u*u,_=f*u*u+d*m*u+d*u*m,A=f*m*u+d*m*m+f*u*m,T=f*m*m,M=u*u*u,k=m*u*u+u*m*u+u*u*m,D=m*m*u+u*m*m+m*u*m,F=m*m*m;for(p=0;pc?m>d?m-c-d:d-c-m:d>c?d-c-m:c-m-d)>-1e-4&&f<1e-4}}}var bez=bezFunction(),initFrame=initialDefaultFrame,mathAbs=Math.abs;function interpolateValue(t,e){var i,r=this.offsetTime;"multidimensional"===this.propType&&(i=createTypedArray("float32",this.pv.length));for(var s,a,n,o,h,l,p,f,m,c=e.lastIndex,d=c,u=this.keyframes.length-1,y=!0;y;){if(s=this.keyframes[d],a=this.keyframes[d+1],d===u-1&&t>=a.t-r){s.h&&(s=a),c=0;break}if(a.t-r>t){c=d;break}d=v||t=v?x.points.length-1:0;for(h=x.points[P].point.length,o=0;o=C&&S=v)i[0]=g[0],i[1]=g[1],i[2]=g[2];else if(t<=b)i[0]=s.s[0],i[1]=s.s[1],i[2]=s.s[2];else{quaternionToEuler(i,slerp(createQuaternion(s.s),createQuaternion(g),(t-b)/(v-b)))}else for(d=0;d=v?l=1:t1e-6?(r=Math.acos(s),a=Math.sin(r),n=Math.sin((1-i)*r)/a,o=Math.sin(i*r)/a):(n=1-i,o=i),h[0]=n*l+o*c,h[1]=n*p+o*d,h[2]=n*f+o*u,h[3]=n*m+o*y,h}function quaternionToEuler(t,e){var i=e[0],r=e[1],s=e[2],a=e[3],n=Math.atan2(2*r*a-2*i*s,1-2*r*r-2*s*s),o=Math.asin(2*i*r+2*s*a),h=Math.atan2(2*i*a-2*r*s,1-2*i*i-2*s*s);t[0]=n/degToRads,t[1]=o/degToRads,t[2]=h/degToRads}function createQuaternion(t){var e=t[0]*degToRads,i=t[1]*degToRads,r=t[2]*degToRads,s=Math.cos(e/2),a=Math.cos(i/2),n=Math.cos(r/2),o=Math.sin(e/2),h=Math.sin(i/2),l=Math.sin(r/2);return[o*h*n+s*a*l,o*a*n+s*h*l,s*h*n-o*a*l,s*a*n-o*h*l]}function getValueAtCurrentTime(){var t=this.comp.renderedFrame-this.offsetTime,e=this.keyframes[0].t-this.offsetTime,i=this.keyframes[this.keyframes.length-1].t-this.offsetTime;if(!(t===this._caching.lastFrame||this._caching.lastFrame!==initFrame&&(this._caching.lastFrame>=i&&t>=i||this._caching.lastFrame=t&&(this._caching._lastKeyframeIndex=-1,this._caching.lastIndex=0);var r=this.interpolateValue(t,this._caching);this.pv=r}return this._caching.lastFrame=t,this.pv}function setVValue(t){var e;if("unidimensional"===this.propType)e=t*this.mult,mathAbs(this.v-e)>1e-5&&(this.v=e,this._mdf=!0);else for(var i=0,r=this.v.length;i1e-5&&(this.v[i]=e,this._mdf=!0),i+=1}function processEffectsSequence(){if(this.elem.globalData.frameId!==this.frameId&&this.effectsSequence.length)if(this.lock)this.setVValue(this.pv);else{var t;this.lock=!0,this._mdf=this._isFirstFrame;var e=this.effectsSequence.length,i=this.kf?this.pv:this.data.k;for(t=0;t=this._maxLength&&this.doubleArrayLength(),i){case"v":a=this.v;break;case"i":a=this.i;break;case"o":a=this.o;break;default:a=[]}(!a[r]||a[r]&&!s)&&(a[r]=pointPool.newElement()),a[r][0]=t,a[r][1]=e},ShapePath.prototype.setTripleAt=function(t,e,i,r,s,a,n,o){this.setXYAt(t,e,"v",n,o),this.setXYAt(i,r,"o",n,o),this.setXYAt(s,a,"i",n,o)},ShapePath.prototype.reverse=function(){var t=new ShapePath;t.setPathData(this.c,this._length);var e=this.v,i=this.o,r=this.i,s=0;this.c&&(t.setTripleAt(e[0][0],e[0][1],r[0][0],r[0][1],i[0][0],i[0][1],0,!1),s=1);var a,n=this._length-1,o=this._length;for(a=s;a=c[c.length-1].t-this.offsetTime)r=c[c.length-1].s?c[c.length-1].s[0]:c[c.length-2].e[0],a=!0;else{for(var d,u,y,g=m,v=c.length-1,b=!0;b&&(d=c[g],!((u=c[g+1]).t-this.offsetTime>t));)g=u.t-this.offsetTime)p=1;else if(tr&&e>r)||(this._caching.lastIndex=s0||t>-1e-6&&t<0?r(1e4*t)/1e4:t}function I(){var t=this.props;return"matrix("+w(t[0])+","+w(t[1])+","+w(t[4])+","+w(t[5])+","+w(t[12])+","+w(t[13])+")"}return function(){this.reset=s,this.rotate=a,this.rotateX=n,this.rotateY=o,this.rotateZ=h,this.skew=p,this.skewFromAxis=f,this.shear=l,this.scale=m,this.setTransform=c,this.translate=d,this.transform=u,this.multiply=y,this.applyToPoint=P,this.applyToX=E,this.applyToY=S,this.applyToZ=C,this.applyToPointArray=k,this.applyToTriplePoints=M,this.applyToPointStringified=D,this.toCSS=F,this.to2dCSS=I,this.clone=b,this.cloneFromProps=x,this.equals=v,this.inversePoints=T,this.inversePoint=A,this.getInverseMatrix=_,this._t=this.transform,this.isIdentity=g,this._identity=!0,this._identityCalculated=!1,this.props=createTypedArray("float32",16),this.reset()}}();function _typeof$3(t){return _typeof$3="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},_typeof$3(t)}var lottie={},standalone="__[STANDALONE]__",animationData="__[ANIMATIONDATA]__",renderer="";function setLocation(t){setLocationHref(t)}function searchAnimations(){!0===standalone?animationManager.searchAnimations(animationData,standalone,renderer):animationManager.searchAnimations()}function setSubframeRendering(t){setSubframeEnabled(t)}function setPrefix(t){setIdPrefix(t)}function loadAnimation(t){return!0===standalone&&(t.animationData=JSON.parse(animationData)),animationManager.loadAnimation(t)}function setQuality(t){if("string"==typeof t)switch(t){case"high":setDefaultCurveSegments(200);break;default:case"medium":setDefaultCurveSegments(50);break;case"low":setDefaultCurveSegments(10)}else!isNaN(t)&&t>1&&setDefaultCurveSegments(t);getDefaultCurveSegments()>=50?roundValues(!1):roundValues(!0)}function inBrowser(){return"undefined"!=typeof navigator}function installPlugin(t,e){"expressions"===t&&setExpressionsPlugin(e)}function getFactory(t){switch(t){case"propertyFactory":return PropertyFactory;case"shapePropertyFactory":return ShapePropertyFactory;case"matrix":return Matrix;default:return null}}function checkReady(){"complete"===document.readyState&&(clearInterval(readyStateCheckInterval),searchAnimations())}function getQueryVariable(t){for(var e=queryString.split("&"),i=0;i=1?a.push({s:t-1,e:e-1}):(a.push({s:t,e:1}),a.push({s:0,e:e-1}));var n,o,h=[],l=a.length;for(n=0;nr+i))p=o.s*s<=r?0:(o.s*s-r)/i,f=o.e*s>=r+i?1:(o.e*s-r)/i,h.push([p,f])}return h.length||h.push([0,0]),h},TrimModifier.prototype.releasePathsData=function(t){var e,i=t.length;for(e=0;e1?1+a:this.s.v<0?0+a:this.s.v+a)>(i=this.e.v>1?1+a:this.e.v<0?0+a:this.e.v+a)){var n=e;e=i,i=n}e=1e-4*Math.round(1e4*e),i=1e-4*Math.round(1e4*i),this.sValue=e,this.eValue=i}else e=this.sValue,i=this.eValue;var o,h,l,p,f,m=this.shapes.length,c=0;if(i===e)for(s=0;s=0;s-=1)if((d=this.shapes[s]).shape._mdf){for((u=d.localShapeCollection).releaseShapes(),2===this.m&&m>1?(g=this.calculateShapeEdges(e,i,d.totalShapeLength,x,c),x+=d.totalShapeLength):g=[[v,b]],h=g.length,o=0;o=1?y.push({s:d.totalShapeLength*(v-1),e:d.totalShapeLength*(b-1)}):(y.push({s:d.totalShapeLength*v,e:d.totalShapeLength}),y.push({s:0,e:d.totalShapeLength*(b-1)}));var P=this.addShapes(d,y[0]);if(y[0].s!==y[0].e){if(y.length>1)if(d.shape.paths.shapes[d.shape.paths._length-1].c){var E=P.pop();this.addPaths(P,u),P=this.addShapes(d,y[1],E)}else this.addPaths(P,u),P=this.addShapes(d,y[1]);this.addPaths(P,u)}}d.shape.paths=u}}},TrimModifier.prototype.addPaths=function(t,e){var i,r=t.length;for(i=0;ie.e){i.c=!1;break}e.s<=d&&e.e>=d+n.addedLength?(this.addSegment(m[r].v[s-1],m[r].o[s-1],m[r].i[s],m[r].v[s],i,o,y),y=!1):(l=bez.getNewSegment(m[r].v[s-1],m[r].v[s],m[r].o[s-1],m[r].i[s],(e.s-d)/n.addedLength,(e.e-d)/n.addedLength,h[s-1]),this.addSegmentFromArray(l,i,o,y),y=!1,i.c=!1),d+=n.addedLength,o+=1}if(m[r].c&&h.length){if(n=h[s-1],d<=e.e){var g=h[s-1].addedLength;e.s<=d&&e.e>=d+g?(this.addSegment(m[r].v[s-1],m[r].o[s-1],m[r].i[0],m[r].v[0],i,o,y),y=!1):(l=bez.getNewSegment(m[r].v[s-1],m[r].v[0],m[r].o[s-1],m[r].i[0],(e.s-d)/g,(e.e-d)/g,h[s-1]),this.addSegmentFromArray(l,i,o,y),y=!1,i.c=!1)}else i.c=!1;d+=n.addedLength,o+=1}if(i._length&&(i.setXYAt(i.v[p][0],i.v[p][1],"i",p),i.setXYAt(i.v[i._length-1][0],i.v[i._length-1][1],"o",i._length-1)),d>e.e)break;r=this.p.keyframes[this.p.keyframes.length-1].t?(r=this.p.getValueAtTime(this.p.keyframes[this.p.keyframes.length-1].t/i,0),s=this.p.getValueAtTime((this.p.keyframes[this.p.keyframes.length-1].t-.05)/i,0)):(r=this.p.pv,s=this.p.getValueAtTime((this.p._caching.lastFrame+this.p.offsetTime-.01)/i,this.p.offsetTime));else if(this.px&&this.px.keyframes&&this.py.keyframes&&this.px.getValueAtTime&&this.py.getValueAtTime){r=[],s=[];var a=this.px,n=this.py;a._caching.lastFrame+a.offsetTime<=a.keyframes[0].t?(r[0]=a.getValueAtTime((a.keyframes[0].t+.01)/i,0),r[1]=n.getValueAtTime((n.keyframes[0].t+.01)/i,0),s[0]=a.getValueAtTime(a.keyframes[0].t/i,0),s[1]=n.getValueAtTime(n.keyframes[0].t/i,0)):a._caching.lastFrame+a.offsetTime>=a.keyframes[a.keyframes.length-1].t?(r[0]=a.getValueAtTime(a.keyframes[a.keyframes.length-1].t/i,0),r[1]=n.getValueAtTime(n.keyframes[n.keyframes.length-1].t/i,0),s[0]=a.getValueAtTime((a.keyframes[a.keyframes.length-1].t-.01)/i,0),s[1]=n.getValueAtTime((n.keyframes[n.keyframes.length-1].t-.01)/i,0)):(r=[a.pv,n.pv],s[0]=a.getValueAtTime((a._caching.lastFrame+a.offsetTime-.01)/i,a.offsetTime),s[1]=n.getValueAtTime((n._caching.lastFrame+n.offsetTime-.01)/i,n.offsetTime))}else r=s=t;this.v.rotate(-Math.atan2(r[1]-s[1],r[0]-s[0]))}this.data.p&&this.data.p.s?this.data.p.z?this.v.translate(this.px.v,this.py.v,-this.pz.v):this.v.translate(this.px.v,this.py.v,0):this.v.translate(this.p.v[0],this.p.v[1],-this.p.v[2])}this.frameId=this.elem.globalData.frameId}},precalculateMatrix:function(){if(this.appliedTransformations=0,this.pre.reset(),!this.a.effectsSequence.length&&(this.pre.translate(-this.a.v[0],-this.a.v[1],this.a.v[2]),this.appliedTransformations=1,!this.s.effectsSequence.length)){if(this.pre.scale(this.s.v[0],this.s.v[1],this.s.v[2]),this.appliedTransformations=2,this.sk){if(this.sk.effectsSequence.length||this.sa.effectsSequence.length)return;this.pre.skewFromAxis(-this.sk.v,this.sa.v),this.appliedTransformations=3}this.r?this.r.effectsSequence.length||(this.pre.rotate(-this.r.v),this.appliedTransformations=4):this.rz.effectsSequence.length||this.ry.effectsSequence.length||this.rx.effectsSequence.length||this.or.effectsSequence.length||(this.pre.rotateZ(-this.rz.v).rotateY(this.ry.v).rotateX(this.rx.v).rotateZ(-this.or.v[2]).rotateY(this.or.v[1]).rotateX(this.or.v[0]),this.appliedTransformations=4)}},autoOrient:function(){}},extendPrototype([DynamicPropertyContainer],e),e.prototype.addDynamicProperty=function(t){this._addDynamicProperty(t),this.elem.addDynamicProperty(t),this._isDirty=!0},e.prototype._addDynamicProperty=DynamicPropertyContainer.prototype.addDynamicProperty,{getTransformProperty:function(t,i,r){return new e(t,i,r)}}}();function RepeaterModifier(){}function RoundCornersModifier(){}function floatEqual(t,e){return 1e5*Math.abs(t-e)<=Math.min(Math.abs(t),Math.abs(e))}function floatZero(t){return Math.abs(t)<=1e-5}function lerp(t,e,i){return t*(1-i)+e*i}function lerpPoint(t,e,i){return[lerp(t[0],e[0],i),lerp(t[1],e[1],i)]}function quadRoots(t,e,i){if(0===t)return[];var r=e*e-4*t*i;if(r<0)return[];var s=-e/(2*t);if(0===r)return[s];var a=Math.sqrt(r)/(2*t);return[s-a,s+a]}function polynomialCoefficients(t,e,i,r){return[3*e-t-3*i+r,3*t-6*e+3*i,-3*t+3*e,t]}function singlePoint(t){return new PolynomialBezier(t,t,t,t,!1)}function PolynomialBezier(t,e,i,r,s){s&&pointEqual(t,e)&&(e=lerpPoint(t,r,1/3)),s&&pointEqual(i,r)&&(i=lerpPoint(t,r,2/3));var a=polynomialCoefficients(t[0],e[0],i[0],r[0]),n=polynomialCoefficients(t[1],e[1],i[1],r[1]);this.a=[a[0],n[0]],this.b=[a[1],n[1]],this.c=[a[2],n[2]],this.d=[a[3],n[3]],this.points=[t,e,i,r]}function extrema(t,e){var i=t.points[0][e],r=t.points[t.points.length-1][e];if(i>r){var s=r;r=i,i=s}for(var a=quadRoots(3*t.a[e],2*t.b[e],t.c[e]),n=0;n0&&a[n]<1){var o=t.point(a[n])[e];or&&(r=o)}return{min:i,max:r}}function intersectData(t,e,i){var r=t.boundingBox();return{cx:r.cx,cy:r.cy,width:r.width,height:r.height,bez:t,t:(e+i)/2,t1:e,t2:i}}function splitData(t){var e=t.bez.split(.5);return[intersectData(e[0],t.t1,t.t),intersectData(e[1],t.t,t.t2)]}function boxIntersect(t,e){return 2*Math.abs(t.cx-e.cx)=a||t.width<=r&&t.height<=r&&e.width<=r&&e.height<=r)s.push([t.t,e.t]);else{var n=splitData(t),o=splitData(e);intersectsImpl(n[0],o[0],i+1,r,s,a),intersectsImpl(n[0],o[1],i+1,r,s,a),intersectsImpl(n[1],o[0],i+1,r,s,a),intersectsImpl(n[1],o[1],i+1,r,s,a)}}function crossProduct(t,e){return[t[1]*e[2]-t[2]*e[1],t[2]*e[0]-t[0]*e[2],t[0]*e[1]-t[1]*e[0]]}function lineIntersection(t,e,i,r){var s=[t[0],t[1],1],a=[e[0],e[1],1],n=[i[0],i[1],1],o=[r[0],r[1],1],h=crossProduct(crossProduct(s,a),crossProduct(n,o));return floatZero(h[2])?null:[h[0]/h[2],h[1]/h[2]]}function polarOffset(t,e,i){return[t[0]+Math.cos(e)*i,t[1]-Math.sin(e)*i]}function pointDistance(t,e){return Math.hypot(t[0]-e[0],t[1]-e[1])}function pointEqual(t,e){return floatEqual(t[0],e[0])&&floatEqual(t[1],e[1])}function ZigZagModifier(){}function setPoint(t,e,i,r,s,a,n){var o=i-Math.PI/2,h=i+Math.PI/2,l=e[0]+Math.cos(i)*r*s,p=e[1]-Math.sin(i)*r*s;t.setTripleAt(l,p,l+Math.cos(o)*a,p-Math.sin(o)*a,l+Math.cos(h)*n,p-Math.sin(h)*n,t.length())}function getPerpendicularVector(t,e){var i=[e[0]-t[0],e[1]-t[1]],r=.5*-Math.PI;return[Math.cos(r)*i[0]-Math.sin(r)*i[1],Math.sin(r)*i[0]+Math.cos(r)*i[1]]}function getProjectingAngle(t,e){var i=0===e?t.length()-1:e-1,r=(e+1)%t.length(),s=getPerpendicularVector(t.v[i],t.v[r]);return Math.atan2(0,1)-Math.atan2(s[1],s[0])}function zigZagCorner(t,e,i,r,s,a,n){var o=getProjectingAngle(e,i),h=e.v[i%e._length],l=e.v[0===i?e._length-1:i-1],p=e.v[(i+1)%e._length],f=2===a?Math.sqrt(Math.pow(h[0]-l[0],2)+Math.pow(h[1]-l[1],2)):0,m=2===a?Math.sqrt(Math.pow(h[0]-p[0],2)+Math.pow(h[1]-p[1],2)):0;setPoint(t,e.v[i%e._length],o,n,r,m/(2*(s+1)),f/(2*(s+1)),a)}function zigZagSegment(t,e,i,r,s,a){for(var n=0;n1&&e.length>1&&(s=getIntersection(t[0],e[e.length-1]))?[[t[0].split(s[0])[0]],[e[e.length-1].split(s[1])[1]]]:[i,r]}function pruneIntersections(t){for(var e,i=1;i1&&(e=pruneSegmentIntersection(t[t.length-1],t[0]),t[t.length-1]=e[0],t[0]=e[1]),t}function offsetSegmentSplit(t,e){var i,r,s,a,n=t.inflectionPoints();if(0===n.length)return[offsetSegment(t,e)];if(1===n.length||floatEqual(n[1],1))return i=(s=t.split(n[0]))[0],r=s[1],[offsetSegment(i,e),offsetSegment(r,e)];i=(s=t.split(n[0]))[0];var o=(n[1]-n[0])/(1-n[0]);return a=(s=s[1].split(o))[0],r=s[1],[offsetSegment(i,e),offsetSegment(a,e),offsetSegment(r,e)]}function OffsetPathModifier(){}function getFontProperties(t){for(var e=t.fStyle?t.fStyle.split(" "):[],i="normal",r="normal",s=e.length,a=0;a0;)i-=1,this._elements.unshift(e[i]);this.dynamicProperties.length?this.k=!0:this.getValue(!0)},RepeaterModifier.prototype.resetElements=function(t){var e,i=t.length;for(e=0;e0?Math.floor(m):Math.ceil(m),u=this.pMatrix.props,y=this.rMatrix.props,g=this.sMatrix.props;this.pMatrix.reset(),this.rMatrix.reset(),this.sMatrix.reset(),this.tMatrix.reset(),this.matrix.reset();var v,b,x=0;if(m>0){for(;xd;)this.applyTransforms(this.pMatrix,this.rMatrix,this.sMatrix,this.tr,1,!0),x-=1;c&&(this.applyTransforms(this.pMatrix,this.rMatrix,this.sMatrix,this.tr,-c,!0),x-=c)}for(r=1===this.data.m?0:this._currentCopies-1,s=1===this.data.m?1:-1,a=this._currentCopies;a;){if(b=(i=(e=this.elemsData[r].it)[e.length-1].transform.mProps.v.props).length,e[e.length-1].transform.mProps._mdf=!0,e[e.length-1].transform.op._mdf=!0,e[e.length-1].transform.op.v=1===this._currentCopies?this.so.v:this.so.v+(this.eo.v-this.so.v)*(r/(this._currentCopies-1)),0!==x){for((0!==r&&1===s||r!==this._currentCopies-1&&-1===s)&&this.applyTransforms(this.pMatrix,this.rMatrix,this.sMatrix,this.tr,1,!1),this.matrix.transform(y[0],y[1],y[2],y[3],y[4],y[5],y[6],y[7],y[8],y[9],y[10],y[11],y[12],y[13],y[14],y[15]),this.matrix.transform(g[0],g[1],g[2],g[3],g[4],g[5],g[6],g[7],g[8],g[9],g[10],g[11],g[12],g[13],g[14],g[15]),this.matrix.transform(u[0],u[1],u[2],u[3],u[4],u[5],u[6],u[7],u[8],u[9],u[10],u[11],u[12],u[13],u[14],u[15]),v=0;v0&&r<1?[e]:[]:[e-r,e+r].filter((function(t){return t>0&&t<1}))},PolynomialBezier.prototype.split=function(t){if(t<=0)return[singlePoint(this.points[0]),this];if(t>=1)return[this,singlePoint(this.points[this.points.length-1])];var e=lerpPoint(this.points[0],this.points[1],t),i=lerpPoint(this.points[1],this.points[2],t),r=lerpPoint(this.points[2],this.points[3],t),s=lerpPoint(e,i,t),a=lerpPoint(i,r,t),n=lerpPoint(s,a,t);return[new PolynomialBezier(this.points[0],e,s,n,!0),new PolynomialBezier(n,a,r,this.points[3],!0)]},PolynomialBezier.prototype.bounds=function(){return{x:extrema(this,0),y:extrema(this,1)}},PolynomialBezier.prototype.boundingBox=function(){var t=this.bounds();return{left:t.x.min,right:t.x.max,top:t.y.min,bottom:t.y.max,width:t.x.max-t.x.min,height:t.y.max-t.y.min,cx:(t.x.max+t.x.min)/2,cy:(t.y.max+t.y.min)/2}},PolynomialBezier.prototype.intersections=function(t,e,i){void 0===e&&(e=2),void 0===i&&(i=7);var r=[];return intersectsImpl(intersectData(this,0,1),intersectData(t,0,1),0,e,r,i),r},PolynomialBezier.shapeSegment=function(t,e){var i=(e+1)%t.length();return new PolynomialBezier(t.v[e],t.o[e],t.i[i],t.v[i],!0)},PolynomialBezier.shapeSegmentInverted=function(t,e){var i=(e+1)%t.length();return new PolynomialBezier(t.v[i],t.i[i],t.o[e],t.v[e],!0)},extendPrototype([ShapeModifier],ZigZagModifier),ZigZagModifier.prototype.initModifierProperties=function(t,e){this.getValue=this.processKeys,this.amplitude=PropertyFactory.getProp(t,e.s,0,null,this),this.frequency=PropertyFactory.getProp(t,e.r,0,null,this),this.pointsType=PropertyFactory.getProp(t,e.pt,0,null,this),this._isAnimated=0!==this.amplitude.effectsSequence.length||0!==this.frequency.effectsSequence.length||0!==this.pointsType.effectsSequence.length},ZigZagModifier.prototype.processPath=function(t,e,i,r){var s=t._length,a=shapePool.newElement();if(a.c=t.c,t.c||(s-=1),0===s)return a;var n=-1,o=PolynomialBezier.shapeSegment(t,0);zigZagCorner(a,t,0,e,i,r,n);for(var h=0;h=0;a-=1)o=PolynomialBezier.shapeSegmentInverted(t,a),l.push(offsetSegmentSplit(o,e));l=pruneIntersections(l);var p=null,f=null;for(a=0;a=55296&&i<=56319){var r=t.charCodeAt(1);r>=56320&&r<=57343&&(e=1024*(i-55296)+r-56320+65536)}return e}function o(t){var e=n(t);return e>=127462&&e<=127487}var h=function(){this.fonts=[],this.chars=null,this.typekitLoaded=0,this.isLoaded=!1,this._warned=!1,this.initTime=Date.now(),this.setIsLoadedBinded=this.setIsLoaded.bind(this),this.checkLoadedFontsBinded=this.checkLoadedFonts.bind(this)};h.isModifier=function(t,e){var i=t.toString(16)+e.toString(16);return-1!==r.indexOf(i)},h.isZeroWidthJoiner=function(t){return 8205===t},h.isFlagEmoji=function(t){return o(t.substr(0,2))&&o(t.substr(2,2))},h.isRegionalCode=o,h.isCombinedCharacter=function(t){return-1!==e.indexOf(t)},h.isRegionalFlag=function(t,e){var r=n(t.substr(e,2));if(r!==i)return!1;var s=0;for(e+=2;s<5;){if((r=n(t.substr(e,2)))<917601||r>917626)return!1;s+=1,e+=2}return 917631===n(t.substr(e,2))},h.isVariationSelector=function(t){return 65039===t},h.BLACK_FLAG_CODE_POINT=i;var l={addChars:function(t){if(t){var e;this.chars||(this.chars=[]);var i,r,s=t.length,a=this.chars.length;for(e=0;e0&&(p=!1),p){var f=createTag("style");f.setAttribute("f-forigin",r[i].fOrigin),f.setAttribute("f-origin",r[i].origin),f.setAttribute("f-family",r[i].fFamily),f.type="text/css",f.innerText="@font-face {font-family: "+r[i].fFamily+"; font-style: normal; src: url('"+r[i].fPath+"');}",e.appendChild(f)}}else if("g"===r[i].fOrigin||1===r[i].origin){for(h=document.querySelectorAll('link[f-forigin="g"], link[f-origin="1"]'),l=0;lt?!0!==this.isInRange&&(this.globalData._mdf=!0,this._mdf=!0,this.isInRange=!0,this.show()):!1!==this.isInRange&&(this.globalData._mdf=!0,this.isInRange=!1,this.hide())},renderRenderable:function(){var t,e=this.renderableComponents.length;for(t=0;t.1)&&this.audio.seek(this._currentTime/this.globalData.frameRate):(this.audio.play(),this.audio.seek(this._currentTime/this.globalData.frameRate),this._isPlaying=!0))},AudioElement.prototype.show=function(){},AudioElement.prototype.hide=function(){this.audio.pause(),this._isPlaying=!1},AudioElement.prototype.pause=function(){this.audio.pause(),this._isPlaying=!1,this._canPlay=!1},AudioElement.prototype.resume=function(){this._canPlay=!0},AudioElement.prototype.setRate=function(t){this.audio.rate(t)},AudioElement.prototype.volume=function(t){this._volumeMultiplier=t,this._previousVolume=t*this._volume,this.audio.volume(this._previousVolume)},AudioElement.prototype.getBaseElement=function(){return null},AudioElement.prototype.destroy=function(){},AudioElement.prototype.sourceRectAtTime=function(){},AudioElement.prototype.initExpressions=function(){},BaseRenderer.prototype.checkLayers=function(t){var e,i,r=this.layers.length;for(this.completeLayers=!0,e=r-1;e>=0;e-=1)this.elements[e]||(i=this.layers[e]).ip-i.st<=t-this.layers[e].st&&i.op-i.st>t-this.layers[e].st&&this.buildItem(e),this.completeLayers=!!this.elements[e]&&this.completeLayers;this.checkPendingElements()},BaseRenderer.prototype.createItem=function(t){switch(t.ty){case 2:return this.createImage(t);case 0:return this.createComp(t);case 1:return this.createSolid(t);case 3:default:return this.createNull(t);case 4:return this.createShape(t);case 5:return this.createText(t);case 6:return this.createAudio(t);case 13:return this.createCamera(t);case 15:return this.createFootage(t)}},BaseRenderer.prototype.createCamera=function(){throw new Error("You're using a 3d camera. Try the html renderer.")},BaseRenderer.prototype.createAudio=function(t){return new AudioElement(t,this.globalData,this)},BaseRenderer.prototype.createFootage=function(t){return new FootageElement(t,this.globalData,this)},BaseRenderer.prototype.buildAllItems=function(){var t,e=this.layers.length;for(t=0;t0&&(this.maskElement.setAttribute("id",y),this.element.maskedElement.setAttribute(v,"url("+getLocationHref()+"#"+y+")"),a.appendChild(this.maskElement)),this.viewData.length&&this.element.addRenderableComponent(this)}TransformElement.prototype={initTransform:function(){var t=new Matrix;this.finalTransform={mProp:this.data.ks?TransformPropertyFactory.getTransformProperty(this,this.data.ks,this):{o:0},_matMdf:!1,_localMatMdf:!1,_opMdf:!1,mat:t,localMat:t,localOpacity:1},this.data.ao&&(this.finalTransform.mProp.autoOriented=!0),this.data.ty},renderTransform:function(){if(this.finalTransform._opMdf=this.finalTransform.mProp.o._mdf||this._isFirstFrame,this.finalTransform._matMdf=this.finalTransform.mProp._mdf||this._isFirstFrame,this.hierarchy){var t,e=this.finalTransform.mat,i=0,r=this.hierarchy.length;if(!this.finalTransform._matMdf)for(;i1&&(a+=" C"+e.o[r-1][0]+","+e.o[r-1][1]+" "+e.i[0][0]+","+e.i[0][1]+" "+e.v[0][0]+","+e.v[0][1]),i.lastPath!==a){var n="";i.elem&&(e.c&&(n=t.inv?this.solidPath+a:a),i.elem.setAttribute("d",n)),i.lastPath=a}},MaskElement.prototype.destroy=function(){this.element=null,this.globalData=null,this.maskElement=null,this.data=null,this.masksProperties=null};var filtersFactory=function(){var t={};return t.createFilter=function(t,e){var i=createNS("filter");i.setAttribute("id",t),!0!==e&&(i.setAttribute("filterUnits","objectBoundingBox"),i.setAttribute("x","0%"),i.setAttribute("y","0%"),i.setAttribute("width","100%"),i.setAttribute("height","100%"));return i},t.createAlphaToLuminanceFilter=function(){var t=createNS("feColorMatrix");return t.setAttribute("type","matrix"),t.setAttribute("color-interpolation-filters","sRGB"),t.setAttribute("values","0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 1"),t},t}(),featureSupport=function(){var t={maskType:!0,svgLumaHidden:!0,offscreenCanvas:"undefined"!=typeof OffscreenCanvas};return(/MSIE 10/i.test(navigator.userAgent)||/MSIE 9/i.test(navigator.userAgent)||/rv:11.0/i.test(navigator.userAgent)||/Edge\/\d./i.test(navigator.userAgent))&&(t.maskType=!1),/firefox/i.test(navigator.userAgent)&&(t.svgLumaHidden=!1),t}(),registeredEffects$1={},idPrefix="filter_result_";function SVGEffects(t){var e,i,r="SourceGraphic",s=t.data.ef?t.data.ef.length:0,a=createElementID(),n=filtersFactory.createFilter(a,!0),o=0;for(this.filters=[],e=0;e=0&&!this.shapeModifiers[t].processShapes(this._isFirstFrame);t-=1);}},searchProcessedElement:function(t){for(var e=this.processedElements,i=0,r=e.length;i.01)return!1;i+=1}return!0},GradientProperty.prototype.checkCollapsable=function(){if(this.o.length/2!=this.c.length/4)return!1;if(this.data.k.k[0].s)for(var t=0,e=this.data.k.k.length;t0;)h=r.transformers[d].mProps._mdf||h,c-=1,d-=1;if(h)for(c=y-r.styles[p].lvl,d=r.transformers.length-1;c>0;)m.multiply(r.transformers[d].mProps.v),c-=1,d-=1}else m=t;if(n=(f=r.sh.paths)._length,h){for(o="",a=0;a=1?v=.99:v<=-1&&(v=-.99);var b=o*v,x=Math.cos(g+e.a.v)*b+p[0],P=Math.sin(g+e.a.v)*b+p[1];h.setAttribute("fx",x),h.setAttribute("fy",P),l&&!e.g._collapsable&&(e.of.setAttribute("fx",x),e.of.setAttribute("fy",P))}}function h(t,e,i){var r=e.style,s=e.d;s&&(s._mdf||i)&&s.dashStr&&(r.pElem.setAttribute("stroke-dasharray",s.dashStr),r.pElem.setAttribute("stroke-dashoffset",s.dashoffset[0])),e.c&&(e.c._mdf||i)&&r.pElem.setAttribute("stroke","rgb("+bmFloor(e.c.v[0])+","+bmFloor(e.c.v[1])+","+bmFloor(e.c.v[2])+")"),(e.o._mdf||i)&&r.pElem.setAttribute("stroke-opacity",e.o.v),(e.w._mdf||i)&&(r.pElem.setAttribute("stroke-width",e.w.v),r.msElem&&r.msElem.setAttribute("stroke-width",e.w.v))}return{createRenderFunction:function(t){switch(t.ty){case"fl":return a;case"gf":return o;case"gs":return n;case"st":return h;case"sh":case"el":case"rc":case"sr":return s;case"tr":return i;case"no":return r;default:return null}}}}();function SVGShapeElement(t,e,i){this.shapes=[],this.shapesData=t.shapes,this.stylesList=[],this.shapeModifiers=[],this.itemsData=[],this.processedElements=[],this.animatedContents=[],this.initElement(t,e,i),this.prevViewData=[]}function LetterProps(t,e,i,r,s,a){this.o=t,this.sw=e,this.sc=i,this.fc=r,this.m=s,this.p=a,this._mdf={o:!0,sw:!!e,sc:!!i,fc:!!r,m:!0,p:!0}}function TextProperty(t,e){this._frameId=initialDefaultFrame,this.pv="",this.v="",this.kf=!1,this._isFirstFrame=!0,this._mdf=!1,e.d&&e.d.sid&&(e.d=t.globalData.slotManager.getProp(e.d)),this.data=e,this.elem=t,this.comp=this.elem.comp,this.keysIndex=0,this.canResize=!1,this.minimumFontSize=1,this.effectsSequence=[],this.currentData={ascent:0,boxWidth:this.defaultBoxWidth,f:"",fStyle:"",fWeight:"",fc:"",j:"",justifyOffset:"",l:[],lh:0,lineWidths:[],ls:"",of:"",s:"",sc:"",sw:0,t:0,tr:0,sz:0,ps:null,fillColorAnim:!1,strokeColorAnim:!1,strokeWidthAnim:!1,yOffset:0,finalSize:0,finalText:[],finalLineHeight:0,__complete:!1},this.copyData(this.currentData,this.data.d.k[0].s),this.searchProperty()||this.completeTextData(this.currentData)}extendPrototype([BaseElement,TransformElement,SVGBaseElement,IShapeElement,HierarchyElement,FrameElement,RenderableDOMElement],SVGShapeElement),SVGShapeElement.prototype.initSecondaryElement=function(){},SVGShapeElement.prototype.identityMatrix=new Matrix,SVGShapeElement.prototype.buildExpressionInterface=function(){},SVGShapeElement.prototype.createContent=function(){this.searchShapes(this.shapesData,this.itemsData,this.prevViewData,this.layerElement,0,[],!0),this.filterUniqueShapes()},SVGShapeElement.prototype.filterUniqueShapes=function(){var t,e,i,r,s=this.shapes.length,a=this.stylesList.length,n=[],o=!1;for(i=0;i1&&o&&this.setShapesAsAnimated(n)}},SVGShapeElement.prototype.setShapesAsAnimated=function(t){var e,i=t.length;for(e=0;e=0;o-=1){if((m=this.searchProcessedElement(t[o]))?e[o]=i[m-1]:t[o]._render=n,"fl"===t[o].ty||"st"===t[o].ty||"gf"===t[o].ty||"gs"===t[o].ty||"no"===t[o].ty)m?e[o].style.closed=!1:e[o]=this.createStyleElement(t[o],s),t[o]._render&&e[o].style.pElem.parentNode!==r&&r.appendChild(e[o].style.pElem),u.push(e[o].style);else if("gr"===t[o].ty){if(m)for(l=e[o].it.length,h=0;h1,this.kf&&this.addEffect(this.getKeyframeValue.bind(this)),this.kf},TextProperty.prototype.addEffect=function(t){this.effectsSequence.push(t),this.elem.addDynamicProperty(this)},TextProperty.prototype.getValue=function(t){if(this.elem.globalData.frameId!==this.frameId&&this.effectsSequence.length||t){this.currentData.t=this.data.d.k[this.keysIndex].s.t;var e=this.currentData,i=this.keysIndex;if(this.lock)this.setCurrentData(this.currentData);else{var r;this.lock=!0,this._mdf=!1;var s=this.effectsSequence.length,a=t||this.data.d.k[this.keysIndex].s;for(r=0;re);)i+=1;return this.keysIndex!==i&&(this.keysIndex=i),this.data.d.k[this.keysIndex].s},TextProperty.prototype.buildFinalText=function(t){for(var e,i,r=[],s=0,a=t.length,n=!1,o=!1,h="";s=55296&&e<=56319?FontManager.isRegionalFlag(t,s)?h=t.substr(s,14):(i=t.charCodeAt(s+1))>=56320&&i<=57343&&(FontManager.isModifier(e,i)?(h=t.substr(s,2),n=!0):h=FontManager.isFlagEmoji(t.substr(s,4))?t.substr(s,4):t.substr(s,2)):e>56319?(i=t.charCodeAt(s+1),FontManager.isVariationSelector(e)&&(n=!0)):FontManager.isZeroWidthJoiner(e)&&(n=!0,o=!0),n?(r[r.length-1]+=h,n=!1):r.push(h),s+=h.length;return r},TextProperty.prototype.completeTextData=function(t){t.__complete=!0;var e,i,r,s,a,n,o,h=this.elem.globalData.fontManager,l=this.data,p=[],f=0,m=l.m.g,c=0,d=0,u=0,y=[],g=0,v=0,b=h.getFontByName(t.f),x=0,P=getFontProperties(b);t.fWeight=P.weight,t.fStyle=P.style,t.finalSize=t.s,t.finalText=this.buildFinalText(t.t),i=t.finalText.length,t.finalLineHeight=t.lh;var E,S=t.tr/1e3*t.finalSize;if(t.sz)for(var C,_,A=!0,T=t.sz[0],M=t.sz[1];A;){C=0,g=0,i=(_=this.buildFinalText(t.t)).length,S=t.tr/1e3*t.finalSize;var k=-1;for(e=0;eT&&" "!==_[e]?(-1===k?i+=1:e=k,C+=t.finalLineHeight||1.2*t.finalSize,_.splice(e,k===e?1:0,"\r"),k=-1,g=0):(g+=x,g+=S);C+=b.ascent*t.finalSize/100,this.canResize&&t.finalSize>this.minimumFontSize&&Mv?g:v,g=-2*S,s="",r=!0,u+=1):s=D,h.chars?(o=h.getCharData(D,b.fStyle,h.getFontByName(t.f).fFamily),x=r?0:o.w*t.finalSize/100):x=h.measureText(s,t.f,t.finalSize)," "===D?F+=x+S:(g+=x+S+F,F=0),p.push({l:x,an:x,add:c,n:r,anIndexes:[],val:s,line:u,animatorJustifyOffset:0}),2==m){if(c+=x,""===s||" "===s||e===i-1){for(""!==s&&" "!==s||(c-=x);d<=e;)p[d].an=c,p[d].ind=f,p[d].extra=x,d+=1;f+=1,c=0}}else if(3==m){if(c+=x,""===s||e===i-1){for(""===s&&(c-=x);d<=e;)p[d].an=c,p[d].ind=f,p[d].extra=x,d+=1;c=0,f+=1}}else p[f].ind=f,p[f].extra=0,f+=1;if(t.l=p,v=g>v?g:v,y.push(g),t.sz)t.boxWidth=t.sz[0],t.justifyOffset=0;else switch(t.boxWidth=v,t.j){case 1:t.justifyOffset=-t.boxWidth;break;case 2:t.justifyOffset=-t.boxWidth/2;break;default:t.justifyOffset=0}t.lineWidths=y;var w,I,V,B,R=l.a;n=R.length;var L=[];for(a=0;a0?s=this.ne.v/100:a=-this.ne.v/100,this.xe.v>0?n=1-this.xe.v/100:o=1+this.xe.v/100;var h=BezierFactory.getBezierEasing(s,a,n,o).get,l=0,p=this.finalS,f=this.finalE,m=this.data.sh;if(2===m)l=h(l=f===p?r>=f?1:0:t(0,e(.5/(f-p)+(r-p)/(f-p),1)));else if(3===m)l=h(l=f===p?r>=f?0:1:1-t(0,e(.5/(f-p)+(r-p)/(f-p),1)));else if(4===m)f===p?l=0:(l=t(0,e(.5/(f-p)+(r-p)/(f-p),1)))<.5?l*=2:l=1-2*(l-.5),l=h(l);else if(5===m){if(f===p)l=0;else{var c=f-p,d=-c/2+(r=e(t(0,r+.5-p),f-p)),u=c/2;l=Math.sqrt(1-d*d/(u*u))}l=h(l)}else 6===m?(f===p?l=0:(r=e(t(0,r+.5-p),f-p),l=(1+Math.cos(Math.PI+2*Math.PI*r/(f-p)))/2),l=h(l)):(r>=i(p)&&(l=t(0,e(r-p<0?e(f,1)-(p-r):f-r,1))),l=h(l));if(100!==this.sm.v){var y=.01*this.sm.v;0===y&&(y=1e-8);var g=.5-.5*y;l1&&(l=1)}return l*this.a.v},getValue:function(t){this.iterateDynamicProperties(),this._mdf=t||this._mdf,this._currentTextLength=this.elem.textProperty.currentData.l.length||0,t&&2===this.data.r&&(this.e.v=this._currentTextLength);var e=2===this.data.r?1:100/this.data.totalChars,i=this.o.v/e,r=this.s.v/e+i,s=this.e.v/e+i;if(r>s){var a=r;r=s,s=a}this.finalS=r,this.finalE=s}},extendPrototype([DynamicPropertyContainer],r),{getTextSelectorProp:function(t,e,i){return new r(t,e,i)}}}();function TextAnimatorDataProperty(t,e,i){var r={propType:!1},s=PropertyFactory.getProp,a=e.a;this.a={r:a.r?s(t,a.r,0,degToRads,i):r,rx:a.rx?s(t,a.rx,0,degToRads,i):r,ry:a.ry?s(t,a.ry,0,degToRads,i):r,sk:a.sk?s(t,a.sk,0,degToRads,i):r,sa:a.sa?s(t,a.sa,0,degToRads,i):r,s:a.s?s(t,a.s,1,.01,i):r,a:a.a?s(t,a.a,1,0,i):r,o:a.o?s(t,a.o,0,.01,i):r,p:a.p?s(t,a.p,1,0,i):r,sw:a.sw?s(t,a.sw,0,0,i):r,sc:a.sc?s(t,a.sc,1,0,i):r,fc:a.fc?s(t,a.fc,1,0,i):r,fh:a.fh?s(t,a.fh,0,0,i):r,fs:a.fs?s(t,a.fs,0,.01,i):r,fb:a.fb?s(t,a.fb,0,.01,i):r,t:a.t?s(t,a.t,0,0,i):r},this.s=TextSelectorProp.getTextSelectorProp(t,e.s,i),this.s.t=e.s.t}function TextAnimatorProperty(t,e,i){this._isFirstFrame=!0,this._hasMaskedPath=!1,this._frameId=-1,this._textData=t,this._renderType=e,this._elem=i,this._animatorsData=createSizedArray(this._textData.a.length),this._pathData={},this._moreOptions={alignment:{}},this.renderedLetters=[],this.lettersChangedFlag=!1,this.initDynamicPropertyContainer(i)}function ITextElement(){}TextAnimatorProperty.prototype.searchProperties=function(){var t,e,i=this._textData.a.length,r=PropertyFactory.getProp;for(t=0;t=o+ot||!d?(v=(o+ot-l)/h.partialLength,G=c.point[0]+(h.point[0]-c.point[0])*v,z=c.point[1]+(h.point[1]-c.point[1])*v,C.translate(-P[0]*T[s].an*.005,-P[1]*B*.01),p=!1):d&&(l+=h.partialLength,(f+=1)>=d.length&&(f=0,u[m+=1]?d=u[m].points:x.v.c?(f=0,d=u[m=0].points):(l-=h.partialLength,d=null)),d&&(c=h,y=(h=d[f]).partialLength));L=T[s].an/2-T[s].add,C.translate(-L,0,0)}else L=T[s].an/2-T[s].add,C.translate(-L,0,0),C.translate(-P[0]*T[s].an*.005,-P[1]*B*.01,0);for(F=0;Ft?this.textSpans[t].span:createNS(h?"g":"text"),y<=t){if(n.setAttribute("stroke-linecap","butt"),n.setAttribute("stroke-linejoin","round"),n.setAttribute("stroke-miterlimit","4"),this.textSpans[t].span=n,h){var g=createNS("g");n.appendChild(g),this.textSpans[t].childSpan=g}this.textSpans[t].span=n,this.layerElement.appendChild(n)}n.style.display="inherit"}if(l.reset(),p&&(o[t].n&&(f=-d,m+=i.yOffset,m+=c?1:0,c=!1),this.applyTextPropertiesToMatrix(i,l,o[t].line,f,m),f+=o[t].l||0,f+=d),h){var v;if(1===(u=this.globalData.fontManager.getCharData(i.finalText[t],r.fStyle,this.globalData.fontManager.getFontByName(i.f).fFamily)).t)v=new SVGCompElement(u.data,this.globalData,this);else{var b=emptyShapeData;u.data&&u.data.shapes&&(b=this.buildShapeData(u.data,i.finalSize)),v=new SVGShapeElement(b,this.globalData,this)}if(this.textSpans[t].glyph){var x=this.textSpans[t].glyph;this.textSpans[t].childSpan.removeChild(x.layerElement),x.destroy()}this.textSpans[t].glyph=v,v._debug=!0,v.prepareFrame(0),v.renderFrame(),this.textSpans[t].childSpan.appendChild(v.layerElement),1===u.t&&this.textSpans[t].childSpan.setAttribute("transform","scale("+i.finalSize/100+","+i.finalSize/100+")")}else p&&n.setAttribute("transform","translate("+l.props[12]+","+l.props[13]+")"),n.textContent=o[t].val,n.setAttributeNS("http://www.w3.org/XML/1998/namespace","xml:space","preserve")}p&&n&&n.setAttribute("d","")}else{var P=this.textContainer,E="start";switch(i.j){case 1:E="end";break;case 2:E="middle";break;default:E="start"}P.setAttribute("text-anchor",E),P.setAttribute("letter-spacing",d);var S=this.buildTextContents(i.finalText);for(e=S.length,m=i.ps?i.ps[1]+i.ascent:0,t=0;t=0;e-=1)(this.completeLayers||this.elements[e])&&this.elements[e].prepareFrame(t-this.layers[e].st);if(this.globalData._mdf)for(e=0;e=0;i-=1)(this.completeLayers||this.elements[i])&&(this.elements[i].prepareFrame(this.renderedFrame-this.layers[i].st),this.elements[i]._mdf&&(this._mdf=!0))}},ICompElement.prototype.renderInnerContent=function(){var t,e=this.layers.length;for(t=0;t=0;i-=1)t.finalTransform.multiply(t.transforms[i].transform.mProps.v);t._mdf=s},processSequences:function(t){var e,i=this.sequenceList.length;for(e=0;e=1){this.buffers=[];var t=this.globalData.canvasContext,e=assetLoader.createCanvas(t.canvas.width,t.canvas.height);this.buffers.push(e);var i=assetLoader.createCanvas(t.canvas.width,t.canvas.height);this.buffers.push(i),this.data.tt>=3&&!document._isProxy&&assetLoader.loadLumaCanvas()}this.canvasContext=this.globalData.canvasContext,this.transformCanvas=this.globalData.transformCanvas,this.renderableEffectsManager=new CVEffects(this),this.searchEffectTransforms()},createContent:function(){},setBlendMode:function(){var t=this.globalData;if(t.blendMode!==this.data.bm){t.blendMode=this.data.bm;var e=getBlendMode(this.data.bm);t.canvasContext.globalCompositeOperation=e}},createRenderableComponents:function(){this.maskManager=new CVMaskElement(this.data,this),this.transformEffects=this.renderableEffectsManager.getEffects(effectTypes.TRANSFORM_EFFECT)},hideElement:function(){this.hidden||this.isInRange&&!this.isTransparent||(this.hidden=!0)},showElement:function(){this.isInRange&&!this.isTransparent&&(this.hidden=!1,this._isFirstFrame=!0,this.maskManager._isFirstFrame=!0)},clearCanvas:function(t){t.clearRect(this.transformCanvas.tx,this.transformCanvas.ty,this.transformCanvas.w*this.transformCanvas.sx,this.transformCanvas.h*this.transformCanvas.sy)},prepareLayer:function(){if(this.data.tt>=1){var t=this.buffers[0].getContext("2d");this.clearCanvas(t),t.drawImage(this.canvasContext.canvas,0,0),this.currentTransform=this.canvasContext.getTransform(),this.canvasContext.setTransform(1,0,0,1,0,0),this.clearCanvas(this.canvasContext),this.canvasContext.setTransform(this.currentTransform)}},exitLayer:function(){if(this.data.tt>=1){var t=this.buffers[1],e=t.getContext("2d");if(this.clearCanvas(e),e.drawImage(this.canvasContext.canvas,0,0),this.canvasContext.setTransform(1,0,0,1,0,0),this.clearCanvas(this.canvasContext),this.canvasContext.setTransform(this.currentTransform),this.comp.getElementById("tp"in this.data?this.data.tp:this.data.ind-1).renderFrame(!0),this.canvasContext.setTransform(1,0,0,1,0,0),this.data.tt>=3&&!document._isProxy){var i=assetLoader.getLumaCanvas(this.canvasContext.canvas);i.getContext("2d").drawImage(this.canvasContext.canvas,0,0),this.clearCanvas(this.canvasContext),this.canvasContext.drawImage(i,0,0)}this.canvasContext.globalCompositeOperation=operationsMap[this.data.tt],this.canvasContext.drawImage(t,0,0),this.canvasContext.globalCompositeOperation="destination-over",this.canvasContext.drawImage(this.buffers[0],0,0),this.canvasContext.setTransform(this.currentTransform),this.canvasContext.globalCompositeOperation="source-over"}},renderFrame:function(t){if(!this.hidden&&!this.data.hd&&(1!==this.data.td||t)){this.renderTransform(),this.renderRenderable(),this.renderLocalTransform(),this.setBlendMode();var e=0===this.data.ty;this.prepareLayer(),this.globalData.renderer.save(e),this.globalData.renderer.ctxTransform(this.finalTransform.localMat.props),this.globalData.renderer.ctxOpacity(this.finalTransform.localOpacity),this.renderInnerContent(),this.globalData.renderer.restore(e),this.exitLayer(),this.maskManager.hasMasks&&this.globalData.renderer.restore(!0),this._isFirstFrame&&(this._isFirstFrame=!1)}},destroy:function(){this.canvasContext=null,this.data=null,this.globalData=null,this.maskManager.destroy()},mHelper:new Matrix},CVBaseElement.prototype.hide=CVBaseElement.prototype.hideElement,CVBaseElement.prototype.show=CVBaseElement.prototype.showElement,CVShapeData.prototype.setAsAnimated=SVGShapeData.prototype.setAsAnimated,extendPrototype([BaseElement,TransformElement,CVBaseElement,IShapeElement,HierarchyElement,FrameElement,RenderableElement],CVShapeElement),CVShapeElement.prototype.initElement=RenderableDOMElement.prototype.initElement,CVShapeElement.prototype.transformHelper={opacity:1,_opMdf:!1},CVShapeElement.prototype.dashResetter=[],CVShapeElement.prototype.createContent=function(){this.searchShapes(this.shapesData,this.itemsData,this.prevViewData,!0,[])},CVShapeElement.prototype.createStyleElement=function(t,e){var i={data:t,type:t.ty,preTransforms:this.transformsManager.addTransformSequence(e),transforms:[],elements:[],closed:!0===t.hd},r={};if("fl"===t.ty||"st"===t.ty?(r.c=PropertyFactory.getProp(this,t.c,1,255,this),r.c.k||(i.co="rgb("+bmFloor(r.c.v[0])+","+bmFloor(r.c.v[1])+","+bmFloor(r.c.v[2])+")")):"gf"!==t.ty&&"gs"!==t.ty||(r.s=PropertyFactory.getProp(this,t.s,1,null,this),r.e=PropertyFactory.getProp(this,t.e,1,null,this),r.h=PropertyFactory.getProp(this,t.h||{k:0},0,.01,this),r.a=PropertyFactory.getProp(this,t.a||{k:0},0,degToRads,this),r.g=new GradientProperty(this,t.g,this)),r.o=PropertyFactory.getProp(this,t.o,0,.01,this),"st"===t.ty||"gs"===t.ty){if(i.lc=lineCapEnum[t.lc||2],i.lj=lineJoinEnum[t.lj||2],1==t.lj&&(i.ml=t.ml),r.w=PropertyFactory.getProp(this,t.w,0,null,this),r.w.k||(i.wi=r.w.v),t.d){var s=new DashProperty(this,t.d,"canvas",this);r.d=s,r.d.k||(i.da=r.d.dashArray,i.do=r.d.dashoffset[0])}}else i.r=2===t.r?"evenodd":"nonzero";return this.stylesList.push(i),r.style=i,r},CVShapeElement.prototype.createGroupElement=function(){return{it:[],prevViewData:[]}},CVShapeElement.prototype.createTransformElement=function(t){return{transform:{opacity:1,_opMdf:!1,key:this.transformsManager.getNewKey(),op:PropertyFactory.getProp(this,t.o,0,.01,this),mProps:TransformPropertyFactory.getTransformProperty(this,t,this)}}},CVShapeElement.prototype.createShapeElement=function(t){var e=new CVShapeData(this,t,this.stylesList,this.transformsManager);return this.shapes.push(e),this.addShapeToModifiers(e),e},CVShapeElement.prototype.reloadShapes=function(){var t;this._isFirstFrame=!0;var e=this.itemsData.length;for(t=0;t=0;a-=1){if((h=this.searchProcessedElement(t[a]))?e[a]=i[h-1]:t[a]._shouldRender=r,"fl"===t[a].ty||"st"===t[a].ty||"gf"===t[a].ty||"gs"===t[a].ty)h?e[a].style.closed=!1:e[a]=this.createStyleElement(t[a],d),m.push(e[a].style);else if("gr"===t[a].ty){if(h)for(o=e[a].it.length,n=0;n=0;s-=1)"tr"===e[s].ty?(a=i[s].transform,this.renderShapeTransform(t,a)):"sh"===e[s].ty||"el"===e[s].ty||"rc"===e[s].ty||"sr"===e[s].ty?this.renderPath(e[s],i[s]):"fl"===e[s].ty?this.renderFill(e[s],i[s],a):"st"===e[s].ty?this.renderStroke(e[s],i[s],a):"gf"===e[s].ty||"gs"===e[s].ty?this.renderGradientFill(e[s],i[s],a):"gr"===e[s].ty?this.renderShape(a,e[s].it,i[s].it):e[s].ty;r&&this.drawLayer()},CVShapeElement.prototype.renderStyledShape=function(t,e){if(this._isFirstFrame||e._mdf||t.transforms._mdf){var i,r,s,a=t.trNodes,n=e.paths,o=n._length;a.length=0;var h=t.transforms.finalTransform;for(s=0;s=1?f=.99:f<=-1&&(f=-.99);var m=l*f,c=Math.cos(p+e.a.v)*m+o[0],d=Math.sin(p+e.a.v)*m+o[1];r=n.createRadialGradient(c,d,0,o[0],o[1],l)}var u=t.g.p,y=e.g.c,g=1;for(a=0;ao&&"xMidYMid slice"===h||ns&&"meet"===o||as&&"slice"===o)?(i-this.transformCanvas.w*(r/this.transformCanvas.h))/2*this.renderConfig.dpr:"xMax"===l&&(as&&"slice"===o)?(i-this.transformCanvas.w*(r/this.transformCanvas.h))*this.renderConfig.dpr:0,this.transformCanvas.ty="YMid"===p&&(a>s&&"meet"===o||as&&"meet"===o||a=0;t-=1)this.elements[t]&&this.elements[t].destroy&&this.elements[t].destroy();this.elements.length=0,this.globalData.canvasContext=null,this.animationItem.container=null,this.destroyed=!0},CanvasRendererBase.prototype.renderFrame=function(t,e){if((this.renderedFrame!==t||!0!==this.renderConfig.clearCanvas||e)&&!this.destroyed&&-1!==t){var i;this.renderedFrame=t,this.globalData.frameNum=t-this.animationItem._isFirstFrame,this.globalData.frameId+=1,this.globalData._mdf=!this.renderConfig.clearCanvas||e,this.globalData.projectInterface.currentFrame=t;var r=this.layers.length;for(this.completeLayers||this.checkLayers(t),i=r-1;i>=0;i-=1)(this.completeLayers||this.elements[i])&&this.elements[i].prepareFrame(t-this.layers[i].st);if(this.globalData._mdf){for(!0===this.renderConfig.clearCanvas?this.canvasContext.clearRect(0,0,this.transformCanvas.w,this.transformCanvas.h):this.save(),i=r-1;i>=0;i-=1)(this.completeLayers||this.elements[i])&&this.elements[i].renderFrame();!0!==this.renderConfig.clearCanvas&&this.restore()}}},CanvasRendererBase.prototype.buildItem=function(t){var e=this.elements;if(!e[t]&&99!==this.layers[t].ty){var i=this.createItem(this.layers[t],this,this.globalData);e[t]=i,i.initExpressions()}},CanvasRendererBase.prototype.checkPendingElements=function(){for(;this.pendingElements.length;){this.pendingElements.pop().checkParenting()}},CanvasRendererBase.prototype.hide=function(){this.animationItem.container.style.display="none"},CanvasRendererBase.prototype.show=function(){this.animationItem.container.style.display="block"},CVContextData.prototype.duplicate=function(){var t=2*this._length,e=0;for(e=this._length;e=0;t-=1)(this.completeLayers||this.elements[t])&&this.elements[t].renderFrame()},CVCompElement.prototype.destroy=function(){var t;for(t=this.layers.length-1;t>=0;t-=1)this.elements[t]&&this.elements[t].destroy();this.layers=null,this.elements=null},CVCompElement.prototype.createComp=function(t){return new CVCompElement(t,this.globalData,this)},extendPrototype([CanvasRendererBase],CanvasRenderer),CanvasRenderer.prototype.createComp=function(t){return new CVCompElement(t,this.globalData,this)},HBaseElement.prototype={checkBlendMode:function(){},initRendererElement:function(){this.baseElement=createTag(this.data.tg||"div"),this.data.hasMask?(this.svgElement=createNS("svg"),this.layerElement=createNS("g"),this.maskedElement=this.layerElement,this.svgElement.appendChild(this.layerElement),this.baseElement.appendChild(this.svgElement)):this.layerElement=this.baseElement,styleDiv(this.baseElement)},createContainerElements:function(){this.renderableEffectsManager=new CVEffects(this),this.transformedElement=this.baseElement,this.maskedElement=this.layerElement,this.data.ln&&this.layerElement.setAttribute("id",this.data.ln),this.data.cl&&this.layerElement.setAttribute("class",this.data.cl),0!==this.data.bm&&this.setBlendMode()},renderElement:function(){var t=this.transformedElement?this.transformedElement.style:{};if(this.finalTransform._matMdf){var e=this.finalTransform.mat.toCSS();t.transform=e,t.webkitTransform=e}this.finalTransform._opMdf&&(t.opacity=this.finalTransform.mProp.o.v)},renderFrame:function(){this.data.hd||this.hidden||(this.renderTransform(),this.renderRenderable(),this.renderElement(),this.renderInnerContent(),this._isFirstFrame&&(this._isFirstFrame=!1))},destroy:function(){this.layerElement=null,this.transformedElement=null,this.matteElement&&(this.matteElement=null),this.maskManager&&(this.maskManager.destroy(),this.maskManager=null)},createRenderableComponents:function(){this.maskManager=new MaskElement(this.data,this,this.globalData)},addEffects:function(){},setMatte:function(){}},HBaseElement.prototype.getBaseElement=SVGBaseElement.prototype.getBaseElement,HBaseElement.prototype.destroyBaseElement=HBaseElement.prototype.destroy,HBaseElement.prototype.buildElementParenting=BaseRenderer.prototype.buildElementParenting,extendPrototype([BaseElement,TransformElement,HBaseElement,HierarchyElement,FrameElement,RenderableDOMElement],HSolidElement),HSolidElement.prototype.createContent=function(){var t;this.data.hasMask?((t=createNS("rect")).setAttribute("width",this.data.sw),t.setAttribute("height",this.data.sh),t.setAttribute("fill",this.data.sc),this.svgElement.setAttribute("width",this.data.sw),this.svgElement.setAttribute("height",this.data.sh)):((t=createTag("div")).style.width=this.data.sw+"px",t.style.height=this.data.sh+"px",t.style.backgroundColor=this.data.sc),this.layerElement.appendChild(t)},extendPrototype([BaseElement,TransformElement,HSolidElement,SVGShapeElement,HBaseElement,HierarchyElement,FrameElement,RenderableElement],HShapeElement),HShapeElement.prototype._renderShapeFrame=HShapeElement.prototype.renderInnerContent,HShapeElement.prototype.createContent=function(){var t;if(this.baseElement.style.fontSize=0,this.data.hasMask)this.layerElement.appendChild(this.shapesContainer),t=this.svgElement;else{t=createNS("svg");var e=this.comp.data?this.comp.data:this.globalData.compSize;t.setAttribute("width",e.w),t.setAttribute("height",e.h),t.appendChild(this.shapesContainer),this.layerElement.appendChild(t)}this.searchShapes(this.shapesData,this.itemsData,this.prevViewData,this.shapesContainer,0,[],!0),this.filterUniqueShapes(),this.shapeCont=t},HShapeElement.prototype.getTransformedPoint=function(t,e){var i,r=t.length;for(i=0;i0&&o<1&&f[m].push(this.calculateF(o,t,e,i,r,m)):(h=a*a-4*n*s)>=0&&((l=(-a+bmSqrt(h))/(2*s))>0&&l<1&&f[m].push(this.calculateF(l,t,e,i,r,m)),(p=(-a-bmSqrt(h))/(2*s))>0&&p<1&&f[m].push(this.calculateF(p,t,e,i,r,m))));this.shapeBoundingBox.left=bmMin.apply(null,f[0]),this.shapeBoundingBox.top=bmMin.apply(null,f[1]),this.shapeBoundingBox.right=bmMax.apply(null,f[0]),this.shapeBoundingBox.bottom=bmMax.apply(null,f[1])},HShapeElement.prototype.calculateF=function(t,e,i,r,s,a){return bmPow(1-t,3)*e[a]+3*bmPow(1-t,2)*t*i[a]+3*(1-t)*bmPow(t,2)*r[a]+bmPow(t,3)*s[a]},HShapeElement.prototype.calculateBoundingBox=function(t,e){var i,r=t.length;for(i=0;ii&&(i=s)}i*=t.mult}else i=t.v*t.mult;e.x-=i,e.xMax+=i,e.y-=i,e.yMax+=i},HShapeElement.prototype.currentBoxContains=function(t){return this.currentBBox.x<=t.x&&this.currentBBox.y<=t.y&&this.currentBBox.width+this.currentBBox.x>=t.x+t.width&&this.currentBBox.height+this.currentBBox.y>=t.y+t.height},HShapeElement.prototype.renderInnerContent=function(){if(this._renderShapeFrame(),!this.hidden&&(this._isFirstFrame||this._mdf)){var t=this.tempBoundingBox,e=999999;if(t.x=e,t.xMax=-e,t.y=e,t.yMax=-e,this.calculateBoundingBox(this.itemsData,t),t.width=t.xMax=0;t-=1){var r=this.hierarchy[t].finalTransform.mProp;this.mat.translate(-r.p.v[0],-r.p.v[1],r.p.v[2]),this.mat.rotateX(-r.or.v[0]).rotateY(-r.or.v[1]).rotateZ(r.or.v[2]),this.mat.rotateX(-r.rx.v).rotateY(-r.ry.v).rotateZ(r.rz.v),this.mat.scale(1/r.s.v[0],1/r.s.v[1],1/r.s.v[2]),this.mat.translate(r.a.v[0],r.a.v[1],r.a.v[2])}if(this.p?this.mat.translate(-this.p.v[0],-this.p.v[1],this.p.v[2]):this.mat.translate(-this.px.v,-this.py.v,this.pz.v),this.a){var s;s=this.p?[this.p.v[0]-this.a.v[0],this.p.v[1]-this.a.v[1],this.p.v[2]-this.a.v[2]]:[this.px.v-this.a.v[0],this.py.v-this.a.v[1],this.pz.v-this.a.v[2]];var a=Math.sqrt(Math.pow(s[0],2)+Math.pow(s[1],2)+Math.pow(s[2],2)),n=[s[0]/a,s[1]/a,s[2]/a],o=Math.sqrt(n[2]*n[2]+n[0]*n[0]),h=Math.atan2(n[1],o),l=Math.atan2(n[0],-n[2]);this.mat.rotateY(l).rotateX(-h)}this.mat.rotateX(-this.rx.v).rotateY(-this.ry.v).rotateZ(this.rz.v),this.mat.rotateX(-this.or.v[0]).rotateY(-this.or.v[1]).rotateZ(this.or.v[2]),this.mat.translate(this.globalData.compSize.w/2,this.globalData.compSize.h/2,0),this.mat.translate(0,0,this.pe.v);var p=!this._prevMat.equals(this.mat);if((p||this.pe._mdf)&&this.comp.threeDElements){var f,m,c;for(e=this.comp.threeDElements.length,t=0;t=t)return this.threeDElements[e].perspectiveElem;e+=1}return null},HybridRendererBase.prototype.createThreeDContainer=function(t,e){var i,r,s=createTag("div");styleDiv(s);var a=createTag("div");if(styleDiv(a),"3d"===e){(i=s.style).width=this.globalData.compSize.w+"px",i.height=this.globalData.compSize.h+"px";var n="50% 50%";i.webkitTransformOrigin=n,i.mozTransformOrigin=n,i.transformOrigin=n;var o="matrix3d(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1)";(r=a.style).transform=o,r.webkitTransform=o}s.appendChild(a);var h={container:a,perspectiveElem:s,startPos:t,endPos:t,type:e};return this.threeDElements.push(h),h},HybridRendererBase.prototype.build3dContainers=function(){var t,e,i=this.layers.length,r="";for(t=0;t=0;t-=1)this.resizerElem.appendChild(this.threeDElements[t].perspectiveElem)},HybridRendererBase.prototype.addTo3dContainer=function(t,e){for(var i=0,r=this.threeDElements.length;in?(t=s/this.globalData.compSize.w,e=s/this.globalData.compSize.w,i=0,r=(a-this.globalData.compSize.h*(s/this.globalData.compSize.w))/2):(t=a/this.globalData.compSize.h,e=a/this.globalData.compSize.h,i=(s-this.globalData.compSize.w*(a/this.globalData.compSize.h))/2,r=0);var o=this.resizerElem.style;o.webkitTransform="matrix3d("+t+",0,0,0,0,"+e+",0,0,0,0,1,0,"+i+","+r+",0,1)",o.transform=o.webkitTransform},HybridRendererBase.prototype.renderFrame=SVGRenderer.prototype.renderFrame,HybridRendererBase.prototype.hide=function(){this.resizerElem.style.display="none"},HybridRendererBase.prototype.show=function(){this.resizerElem.style.display="block"},HybridRendererBase.prototype.initItems=function(){if(this.buildAllItems(),this.camera)this.camera.setup();else{var t,e=this.globalData.compSize.w,i=this.globalData.compSize.h,r=this.threeDElements.length;for(t=0;t=o;)t/=2,e/=2,i>>>=1;return(t+i)/e};return b.int32=function(){return 0|v.g(4)},b.quick=function(){return v.g(4)/4294967296},b.double=b,m(c(v.S),t),(d.pass||u||function(t,i,r,s){return s&&(s.S&&p(s,v),t.state=function(){return p(v,{})}),r?(e.random=t,i):t})(b,g,"global"in d?d.global:this==e,d.state)},m(e.random(),t)}function initialize$2(t){seedRandom([],t)}var propTypes={SHAPE:"shape"};function _typeof$1(t){return _typeof$1="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},_typeof$1(t)}var ExpressionManager=function(){var ob={},Math=BMMath,window=null,document=null,XMLHttpRequest=null,fetch=null,frames=null,_lottieGlobal={};function resetFrame(){_lottieGlobal={}}function $bm_isInstanceOfArray(t){return t.constructor===Array||t.constructor===Float32Array}function isNumerable(t,e){return"number"===t||e instanceof Number||"boolean"===t||"string"===t}function $bm_neg(t){var e=_typeof$1(t);if("number"===e||t instanceof Number||"boolean"===e)return-t;if($bm_isInstanceOfArray(t)){var i,r=t.length,s=[];for(i=0;ii){var r=i;i=e,e=r}return Math.min(Math.max(t,e),i)}function radiansToDegrees(t){return t/degToRads}var radians_to_degrees=radiansToDegrees;function degreesToRadians(t){return t*degToRads}var degrees_to_radians=radiansToDegrees,helperLengthArray=[0,0,0,0,0,0];function length(t,e){if("number"==typeof t||t instanceof Number)return e=e||0,Math.abs(t-e);var i;e||(e=helperLengthArray);var r=Math.min(t.length,e.length),s=0;for(i=0;i.5?l/(2-n-o):l/(n+o),n){case r:e=(s-a)/l+(s1&&(i-=1),i<1/6?t+6*(e-t)*i:i<.5?e:i<2/3?t+(e-t)*(2/3-i)*6:t}function hslToRgb(t){var e,i,r,s=t[0],a=t[1],n=t[2];if(0===a)e=n,r=n,i=n;else{var o=n<.5?n*(1+a):n+a-n*a,h=2*n-o;e=hue2rgb(h,o,s+1/3),i=hue2rgb(h,o,s),r=hue2rgb(h,o,s-1/3)}return[e,i,r,t[3]]}function linear(t,e,i,r,s){if(void 0!==r&&void 0!==s||(r=e,s=i,e=0,i=1),i=i)return s;var n,o=i===e?0:(t-e)/(i-e);if(!r.length)return r+(s-r)*o;var h=r.length,l=createTypedArray("float32",h);for(n=0;n1){for(r=0;r1?e=1:e<0&&(e=0);var n=t(e);if($bm_isInstanceOfArray(s)){var o,h=s.length,l=createTypedArray("float32",h);for(o=0;odata.k[e].t&&tdata.k[e+1].t-t?(i=e+2,r=data.k[e+1].t):(i=e+1,r=data.k[e].t);break}}-1===i&&(i=e+1,r=data.k[e].t)}else i=0,r=0;var a={};return a.index=i,a.time=r/elem.comp.globalData.frameRate,a}function key(t){var e,i,r;if(!data.k.length||"number"==typeof data.k[0])throw new Error("The property has no keyframe at index "+t);t-=1,e={time:data.k[t].t/elem.comp.globalData.frameRate,value:[]};var s=Object.prototype.hasOwnProperty.call(data.k[t],"s")?data.k[t].s:data.k[t-1].e;for(r=s.length,i=0;il.length-1)&&(e=l.length-1),r=p-(s=l[l.length-1-e].t)),"pingpong"===t){if(Math.floor((h-s)/r)%2!=0)return this.getValueAtTime((r-(h-s)%r+s)/this.comp.globalData.frameRate,0)}else{if("offset"===t){var f=this.getValueAtTime(s/this.comp.globalData.frameRate,0),m=this.getValueAtTime(p/this.comp.globalData.frameRate,0),c=this.getValueAtTime(((h-s)%r+s)/this.comp.globalData.frameRate,0),d=Math.floor((h-s)/r);if(this.pv.length){for(n=(o=new Array(f.length)).length,a=0;a=p)return this.pv;if(i?s=p+(r=e?Math.abs(this.elem.comp.globalData.frameRate*e):Math.max(0,this.elem.data.op-p)):((!e||e>l.length-1)&&(e=l.length-1),r=(s=l[e].t)-p),"pingpong"===t){if(Math.floor((p-h)/r)%2==0)return this.getValueAtTime(((p-h)%r+p)/this.comp.globalData.frameRate,0)}else{if("offset"===t){var f=this.getValueAtTime(p/this.comp.globalData.frameRate,0),m=this.getValueAtTime(s/this.comp.globalData.frameRate,0),c=this.getValueAtTime((r-(p-h)%r+p)/this.comp.globalData.frameRate,0),d=Math.floor((p-h)/r)+1;if(this.pv.length){for(n=(o=new Array(f.length)).length,a=0;a1?(s+t-a)/(e-1):1,o=0,h=0;for(i=this.pv.length?createTypedArray("float32",this.pv.length):0;on){var p=o,f=i.c&&o===h-1?0:o+1,m=(n-l)/a[o].addedLength;r=bez.getPointInSegment(i.v[p],i.v[f],i.o[p],i.i[f],m,a[o]);break}l+=a[o].addedLength,o+=1}return r||(r=i.c?[i.v[0][0],i.v[0][1]]:[i.v[i._length-1][0],i.v[i._length-1][1]]),r},vectorOnPath:function(t,e,i){1==t?t=this.v.c:0==t&&(t=.999);var r=this.pointOnPath(t,e),s=this.pointOnPath(t+.001,e),a=s[0]-r[0],n=s[1]-r[1],o=Math.sqrt(Math.pow(a,2)+Math.pow(n,2));return 0===o?[0,0]:"tangent"===i?[a/o,n/o]:[-n/o,a/o]},tangentOnPath:function(t,e){return this.vectorOnPath(t,e,"tangent")},normalOnPath:function(t,e){return this.vectorOnPath(t,e,"normal")},setGroupProperty:expressionHelpers.setGroupProperty,getValueAtTime:expressionHelpers.getStaticValueAtTime},extendPrototype([l],o),extendPrototype([l],h),h.prototype.getValueAtTime=function(t){return this._cachingAtTime||(this._cachingAtTime={shapeValue:shapePool.clone(this.pv),lastIndex:0,lastTime:initialDefaultFrame}),t*=this.elem.globalData.frameRate,(t-=this.offsetTime)!==this._cachingAtTime.lastTime&&(this._cachingAtTime.lastIndex=this._cachingAtTime.lastTime=l?c<0?r:s:r+m*Math.pow((a-t)/c,1/i),p[f]=n,f+=1,o+=256/255;return p.join(" ")},SVGProLevelsFilter.prototype.renderFrame=function(t){if(t||this.filterManager._mdf){var e,i=this.filterManager.effectElements;this.feFuncRComposed&&(t||i[3].p._mdf||i[4].p._mdf||i[5].p._mdf||i[6].p._mdf||i[7].p._mdf)&&(e=this.getTableValue(i[3].p.v,i[4].p.v,i[5].p.v,i[6].p.v,i[7].p.v),this.feFuncRComposed.setAttribute("tableValues",e),this.feFuncGComposed.setAttribute("tableValues",e),this.feFuncBComposed.setAttribute("tableValues",e)),this.feFuncR&&(t||i[10].p._mdf||i[11].p._mdf||i[12].p._mdf||i[13].p._mdf||i[14].p._mdf)&&(e=this.getTableValue(i[10].p.v,i[11].p.v,i[12].p.v,i[13].p.v,i[14].p.v),this.feFuncR.setAttribute("tableValues",e)),this.feFuncG&&(t||i[17].p._mdf||i[18].p._mdf||i[19].p._mdf||i[20].p._mdf||i[21].p._mdf)&&(e=this.getTableValue(i[17].p.v,i[18].p.v,i[19].p.v,i[20].p.v,i[21].p.v),this.feFuncG.setAttribute("tableValues",e)),this.feFuncB&&(t||i[24].p._mdf||i[25].p._mdf||i[26].p._mdf||i[27].p._mdf||i[28].p._mdf)&&(e=this.getTableValue(i[24].p.v,i[25].p.v,i[26].p.v,i[27].p.v,i[28].p.v),this.feFuncB.setAttribute("tableValues",e)),this.feFuncA&&(t||i[31].p._mdf||i[32].p._mdf||i[33].p._mdf||i[34].p._mdf||i[35].p._mdf)&&(e=this.getTableValue(i[31].p.v,i[32].p.v,i[33].p.v,i[34].p.v,i[35].p.v),this.feFuncA.setAttribute("tableValues",e))}},extendPrototype([SVGComposableEffect],SVGDropShadowEffect),SVGDropShadowEffect.prototype.renderFrame=function(t){if(t||this.filterManager._mdf){if((t||this.filterManager.effectElements[4].p._mdf)&&this.feGaussianBlur.setAttribute("stdDeviation",this.filterManager.effectElements[4].p.v/4),t||this.filterManager.effectElements[0].p._mdf){var e=this.filterManager.effectElements[0].p.v;this.feFlood.setAttribute("flood-color",rgbToHex(Math.round(255*e[0]),Math.round(255*e[1]),Math.round(255*e[2])))}if((t||this.filterManager.effectElements[1].p._mdf)&&this.feFlood.setAttribute("flood-opacity",this.filterManager.effectElements[1].p.v/255),t||this.filterManager.effectElements[2].p._mdf||this.filterManager.effectElements[3].p._mdf){var i=this.filterManager.effectElements[3].p.v,r=(this.filterManager.effectElements[2].p.v-90)*degToRads,s=i*Math.cos(r),a=i*Math.sin(r);this.feOffset.setAttribute("dx",s),this.feOffset.setAttribute("dy",a)}}};var _svgMatteSymbols=[];function SVGMatte3Effect(t,e,i){this.initialized=!1,this.filterManager=e,this.filterElem=t,this.elem=i,i.matteElement=createNS("g"),i.matteElement.appendChild(i.layerElement),i.matteElement.appendChild(i.transformedElement),i.baseElement=i.matteElement}function SVGGaussianBlurEffect(t,e,i,r){t.setAttribute("x","-100%"),t.setAttribute("y","-100%"),t.setAttribute("width","300%"),t.setAttribute("height","300%"),this.filterManager=e;var s=createNS("feGaussianBlur");s.setAttribute("result",r),t.appendChild(s),this.feGaussianBlur=s}function TransformEffect(){}function SVGTransformEffect(t,e){this.init(e)}function CVTransformEffect(t){this.init(t)}return SVGMatte3Effect.prototype.findSymbol=function(t){for(var e=0,i=_svgMatteSymbols.length;e { if ( @@ -216,7 +216,7 @@ class FolderDropdown { this.prefix = prefix; this.container = document.querySelector(`[data-${this.prefix}-container]`); this.dropEls = document.querySelectorAll( - `[data-${this.prefix}-action-dropdown]`, + `[data-${this.prefix}-action-dropdown]` ); this.init(); } @@ -262,7 +262,7 @@ class FolderDropdown { .closest("button") .getAttribute(`data-${this.prefix}-action-dropdown-btn`); const dropEl = document.querySelector( - `[data-${this.prefix}-action-dropdown="${att}"]`, + `[data-${this.prefix}-action-dropdown="${att}"]` ); this.hideDrop(dropEl); } @@ -346,33 +346,33 @@ class FolderModal { : false; //add service/file elements this.breadContainer = document.querySelector( - `[data-${this.prefix}-breadcrumb]`, + `[data-${this.prefix}-breadcrumb]` ); this.addConfContainer = document.querySelector( - `[data-${this.prefix}-add-container]`, + `[data-${this.prefix}-add-container]` ); //modal DOM elements this.form = document.querySelector(`[data-${this.prefix}-modal-form]`); this.modalEl = document.querySelector(`[data-${this.prefix}-modal]`); this.modalTitle = this.modalEl.querySelector( - `[data-${this.prefix}-modal-title]`, + `[data-${this.prefix}-modal-title]` ); this.modalPath = this.modalEl.querySelector( - `[data-${this.prefix}-modal-path]`, + `[data-${this.prefix}-modal-path]` ); this.modalEditor = this.modalEl.querySelector( - `[data-${this.prefix}-modal-editor]`, + `[data-${this.prefix}-modal-editor]` ); this.modalPathPrev = this.modalPath.querySelector( - `p[data-${this.prefix}-modal-path-prefix]`, + `p[data-${this.prefix}-modal-path-prefix]` ); this.modalPathName = this.modalPath.querySelector("input"); this.modalPathSuffix = this.modalPath.querySelector( - `p[data-${this.prefix}-modal-path-suffix]`, + `p[data-${this.prefix}-modal-path-suffix]` ); this.modalSubmit = this.modalEl.querySelector( - `[data-${this.prefix}-modal-submit]`, + `[data-${this.prefix}-modal-submit]` ); //hidden input for backend this.modalInpPath = this.modalEl.querySelector("#path"); @@ -380,6 +380,9 @@ class FolderModal { this.modalInpType = this.modalEl.querySelector("#_type"); this.modalInpOldName = this.modalEl.querySelector("#old_name"); this.modalTxtarea = this.modalEl.querySelector("#content"); + this.modalDelMsg = this.modalEl.querySelector( + `[data-${this.prefix}-modal-delete]` + ); //HANDLERS //modal and values logic after clicking add file/folder button this.initAddConfig(); @@ -408,7 +411,7 @@ class FolderModal { "file", "", "", - this.getLevelFromBread(), + this.getLevelFromBread() ); } } catch (err) {} @@ -478,7 +481,7 @@ class FolderModal { var element = document.createElement("a"); element.setAttribute( "href", - "data:text/plain;charset=utf-8," + encodeURIComponent(text), + "data:text/plain;charset=utf-8," + encodeURIComponent(text) ); element.setAttribute("download", filename); @@ -533,7 +536,7 @@ class FolderModal { //title this.modalTitle.textContent = `${action} ${type}`; this.setInpt(action, path, type, name); - this.setEditor(type, content); + this.setEditor(action, type, content); this.setSubmitTxt(action); this.setPath(action, path, type, name, level); this.setDisabled(action); @@ -559,8 +562,8 @@ class FolderModal { setPath(action, path, type) { let [prevPath, name] = this.separatePath(path); //remove conf if file type - this.modalPathSuffix.textContent = - type === "file" && this.prefix === "configs" ? ".conf" : ""; + const suffix = type === "file" && this.prefix === "configs" ? ".conf" : ""; + this.modalPathSuffix.textContent = suffix; name = type === "file" && this.prefix === "configs" ? name.replace(".conf", "") @@ -575,6 +578,21 @@ class FolderModal { this.modalPathPrev.textContent = `${prevPath}`; this.modalPathName.value = `${name}`; } + + if (action === "delete") { + this.modalDelMsg.textContent = `Are you sure to delete ${prevPath}${name}${suffix} ?`; + this.modalDelMsg.classList.remove("hidden"); + this.modalPathPrev.classList.add("hidden"); + this.modalPathName.classList.add("hidden"); + this.modalPathSuffix.classList.add("hidden"); + } + + if (action !== "delete") { + this.modalPathName.classList.remove("hidden"); + this.modalPathPrev.classList.remove("hidden"); + this.modalPathSuffix.classList.remove("hidden"); + this.modalDelMsg.classList.add("hidden"); + } } //separate name and previous of path for DOM elements @@ -634,13 +652,15 @@ class FolderModal { "delete-btn", "valid-btn", "edit-btn", - "info-btn", + "info-btn" ); } //show only if type file and display text - setEditor(type, content) { + setEditor(action, type, content) { //SHOW LOGIC + if (action === "delete") return this.modalEditor.classList.add("hidden"); + if (type === "folder") this.modalEditor.classList.add("hidden"); if (type === "file") this.modalEditor.classList.remove("hidden"); diff --git a/src/ui/static/js/utils/purify/purify.min.js b/src/ui/static/js/utils/purify/purify.min.js index 9e0d09d38..6a6f22d40 100644 --- a/src/ui/static/js/utils/purify/purify.min.js +++ b/src/ui/static/js/utils/purify/purify.min.js @@ -1,2 +1,2 @@ -/*! @license DOMPurify 3.1.2 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.1.2/LICENSE */ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).DOMPurify=t()}(this,(function(){"use strict";const{entries:e,setPrototypeOf:t,isFrozen:n,getPrototypeOf:o,getOwnPropertyDescriptor:r}=Object;let{freeze:i,seal:a,create:l}=Object,{apply:c,construct:s}="undefined"!=typeof Reflect&&Reflect;i||(i=function(e){return e}),a||(a=function(e){return e}),c||(c=function(e,t,n){return e.apply(t,n)}),s||(s=function(e,t){return new e(...t)});const u=b(Array.prototype.forEach),m=b(Array.prototype.pop),p=b(Array.prototype.push),f=b(String.prototype.toLowerCase),d=b(String.prototype.toString),h=b(String.prototype.match),g=b(String.prototype.replace),T=b(String.prototype.indexOf),_=b(String.prototype.trim),y=b(Object.prototype.hasOwnProperty),E=b(RegExp.prototype.test),A=(N=TypeError,function(){for(var e=arguments.length,t=new Array(e),n=0;n1?n-1:0),r=1;r2&&void 0!==arguments[2]?arguments[2]:f;t&&t(e,null);let i=o.length;for(;i--;){let t=o[i];if("string"==typeof t){const e=r(t);e!==t&&(n(o)||(o[i]=e),t=e)}e[t]=!0}return e}function R(e){for(let t=0;t/gm),B=a(/\${[\w\W]*}/gm),W=a(/^data-[\-\w.\u00B7-\uFFFF]/),G=a(/^aria-[\-\w]+$/),Y=a(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),j=a(/^(?:\w+script|data):/i),X=a(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),q=a(/^html$/i),$=a(/^[a-z][.\w]*(-[.\w]+)+$/i);var K=Object.freeze({__proto__:null,MUSTACHE_EXPR:H,ERB_EXPR:z,TMPLIT_EXPR:B,DATA_ATTR:W,ARIA_ATTR:G,IS_ALLOWED_URI:Y,IS_SCRIPT_OR_DATA:j,ATTR_WHITESPACE:X,DOCTYPE_NAME:q,CUSTOM_ELEMENT:$});const V=function(){return"undefined"==typeof window?null:window},Z=function(e,t){if("object"!=typeof e||"function"!=typeof e.createPolicy)return null;let n=null;const o="data-tt-policy-suffix";t&&t.hasAttribute(o)&&(n=t.getAttribute(o));const r="dompurify"+(n?"#"+n:"");try{return e.createPolicy(r,{createHTML:e=>e,createScriptURL:e=>e})}catch(e){return console.warn("TrustedTypes policy "+r+" could not be created."),null}};var J=function t(){let n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:V();const o=e=>t(e);if(o.version="3.1.2",o.removed=[],!n||!n.document||9!==n.document.nodeType)return o.isSupported=!1,o;let{document:r}=n;const a=r,c=a.currentScript,{DocumentFragment:s,HTMLTemplateElement:N,Node:b,Element:R,NodeFilter:H,NamedNodeMap:z=n.NamedNodeMap||n.MozNamedAttrMap,HTMLFormElement:B,DOMParser:W,trustedTypes:G}=n,j=R.prototype,X=C(j,"cloneNode"),$=C(j,"nextSibling"),J=C(j,"childNodes"),Q=C(j,"parentNode");if("function"==typeof N){const e=r.createElement("template");e.content&&e.content.ownerDocument&&(r=e.content.ownerDocument)}let ee,te="";const{implementation:ne,createNodeIterator:oe,createDocumentFragment:re,getElementsByTagName:ie}=r,{importNode:ae}=a;let le={};o.isSupported="function"==typeof e&&"function"==typeof Q&&ne&&void 0!==ne.createHTMLDocument;const{MUSTACHE_EXPR:ce,ERB_EXPR:se,TMPLIT_EXPR:ue,DATA_ATTR:me,ARIA_ATTR:pe,IS_SCRIPT_OR_DATA:fe,ATTR_WHITESPACE:de,CUSTOM_ELEMENT:he}=K;let{IS_ALLOWED_URI:ge}=K,Te=null;const _e=S({},[...v,...L,...D,...x,...M]);let ye=null;const Ee=S({},[...I,...U,...P,...F]);let Ae=Object.seal(l(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),Ne=null,be=null,Se=!0,Re=!0,we=!1,Ce=!0,ve=!1,Le=!0,De=!1,Oe=!1,xe=!1,ke=!1,Me=!1,Ie=!1,Ue=!0,Pe=!1;const Fe="user-content-";let He=!0,ze=!1,Be={},We=null;const Ge=S({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]);let Ye=null;const je=S({},["audio","video","img","source","image","track"]);let Xe=null;const qe=S({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),$e="http://www.w3.org/1998/Math/MathML",Ke="http://www.w3.org/2000/svg",Ve="http://www.w3.org/1999/xhtml";let Ze=Ve,Je=!1,Qe=null;const et=S({},[$e,Ke,Ve],d);let tt=null;const nt=["application/xhtml+xml","text/html"],ot="text/html";let rt=null,it=null;const at=255,lt=r.createElement("form"),ct=function(e){return e instanceof RegExp||e instanceof Function},st=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(!it||it!==e){if(e&&"object"==typeof e||(e={}),e=w(e),tt=-1===nt.indexOf(e.PARSER_MEDIA_TYPE)?ot:e.PARSER_MEDIA_TYPE,rt="application/xhtml+xml"===tt?d:f,Te=y(e,"ALLOWED_TAGS")?S({},e.ALLOWED_TAGS,rt):_e,ye=y(e,"ALLOWED_ATTR")?S({},e.ALLOWED_ATTR,rt):Ee,Qe=y(e,"ALLOWED_NAMESPACES")?S({},e.ALLOWED_NAMESPACES,d):et,Xe=y(e,"ADD_URI_SAFE_ATTR")?S(w(qe),e.ADD_URI_SAFE_ATTR,rt):qe,Ye=y(e,"ADD_DATA_URI_TAGS")?S(w(je),e.ADD_DATA_URI_TAGS,rt):je,We=y(e,"FORBID_CONTENTS")?S({},e.FORBID_CONTENTS,rt):Ge,Ne=y(e,"FORBID_TAGS")?S({},e.FORBID_TAGS,rt):{},be=y(e,"FORBID_ATTR")?S({},e.FORBID_ATTR,rt):{},Be=!!y(e,"USE_PROFILES")&&e.USE_PROFILES,Se=!1!==e.ALLOW_ARIA_ATTR,Re=!1!==e.ALLOW_DATA_ATTR,we=e.ALLOW_UNKNOWN_PROTOCOLS||!1,Ce=!1!==e.ALLOW_SELF_CLOSE_IN_ATTR,ve=e.SAFE_FOR_TEMPLATES||!1,Le=!1!==e.SAFE_FOR_XML,De=e.WHOLE_DOCUMENT||!1,ke=e.RETURN_DOM||!1,Me=e.RETURN_DOM_FRAGMENT||!1,Ie=e.RETURN_TRUSTED_TYPE||!1,xe=e.FORCE_BODY||!1,Ue=!1!==e.SANITIZE_DOM,Pe=e.SANITIZE_NAMED_PROPS||!1,He=!1!==e.KEEP_CONTENT,ze=e.IN_PLACE||!1,ge=e.ALLOWED_URI_REGEXP||Y,Ze=e.NAMESPACE||Ve,Ae=e.CUSTOM_ELEMENT_HANDLING||{},e.CUSTOM_ELEMENT_HANDLING&&ct(e.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(Ae.tagNameCheck=e.CUSTOM_ELEMENT_HANDLING.tagNameCheck),e.CUSTOM_ELEMENT_HANDLING&&ct(e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(Ae.attributeNameCheck=e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),e.CUSTOM_ELEMENT_HANDLING&&"boolean"==typeof e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements&&(Ae.allowCustomizedBuiltInElements=e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),ve&&(Re=!1),Me&&(ke=!0),Be&&(Te=S({},M),ye=[],!0===Be.html&&(S(Te,v),S(ye,I)),!0===Be.svg&&(S(Te,L),S(ye,U),S(ye,F)),!0===Be.svgFilters&&(S(Te,D),S(ye,U),S(ye,F)),!0===Be.mathMl&&(S(Te,x),S(ye,P),S(ye,F))),e.ADD_TAGS&&(Te===_e&&(Te=w(Te)),S(Te,e.ADD_TAGS,rt)),e.ADD_ATTR&&(ye===Ee&&(ye=w(ye)),S(ye,e.ADD_ATTR,rt)),e.ADD_URI_SAFE_ATTR&&S(Xe,e.ADD_URI_SAFE_ATTR,rt),e.FORBID_CONTENTS&&(We===Ge&&(We=w(We)),S(We,e.FORBID_CONTENTS,rt)),He&&(Te["#text"]=!0),De&&S(Te,["html","head","body"]),Te.table&&(S(Te,["tbody"]),delete Ne.tbody),e.TRUSTED_TYPES_POLICY){if("function"!=typeof e.TRUSTED_TYPES_POLICY.createHTML)throw A('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if("function"!=typeof e.TRUSTED_TYPES_POLICY.createScriptURL)throw A('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');ee=e.TRUSTED_TYPES_POLICY,te=ee.createHTML("")}else void 0===ee&&(ee=Z(G,c)),null!==ee&&"string"==typeof te&&(te=ee.createHTML(""));i&&i(e),it=e}},ut=S({},["mi","mo","mn","ms","mtext"]),mt=S({},["foreignobject","annotation-xml"]),pt=S({},["title","style","font","a","script"]),ft=S({},[...L,...D,...O]),dt=S({},[...x,...k]),ht=function(e){let t=Q(e);t&&t.tagName||(t={namespaceURI:Ze,tagName:"template"});const n=f(e.tagName),o=f(t.tagName);return!!Qe[e.namespaceURI]&&(e.namespaceURI===Ke?t.namespaceURI===Ve?"svg"===n:t.namespaceURI===$e?"svg"===n&&("annotation-xml"===o||ut[o]):Boolean(ft[n]):e.namespaceURI===$e?t.namespaceURI===Ve?"math"===n:t.namespaceURI===Ke?"math"===n&&mt[o]:Boolean(dt[n]):e.namespaceURI===Ve?!(t.namespaceURI===Ke&&!mt[o])&&(!(t.namespaceURI===$e&&!ut[o])&&(!dt[n]&&(pt[n]||!ft[n]))):!("application/xhtml+xml"!==tt||!Qe[e.namespaceURI]))},gt=function(e){p(o.removed,{element:e});try{e.parentNode.removeChild(e)}catch(t){e.remove()}},Tt=function(e,t){try{p(o.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){p(o.removed,{attribute:null,from:t})}if(t.removeAttribute(e),"is"===e&&!ye[e])if(ke||Me)try{gt(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},_t=function(e){let t=null,n=null;if(xe)e=""+e;else{const t=h(e,/^[\r\n\t ]+/);n=t&&t[0]}"application/xhtml+xml"===tt&&Ze===Ve&&(e=''+e+"");const o=ee?ee.createHTML(e):e;if(Ze===Ve)try{t=(new W).parseFromString(o,tt)}catch(e){}if(!t||!t.documentElement){t=ne.createDocument(Ze,"template",null);try{t.documentElement.innerHTML=Je?te:o}catch(e){}}const i=t.body||t.documentElement;return e&&n&&i.insertBefore(r.createTextNode(n),i.childNodes[0]||null),Ze===Ve?ie.call(t,De?"html":"body")[0]:De?t.documentElement:i},yt=function(e){return oe.call(e.ownerDocument||e,e,H.SHOW_ELEMENT|H.SHOW_COMMENT|H.SHOW_TEXT|H.SHOW_PROCESSING_INSTRUCTION|H.SHOW_CDATA_SECTION,null)},Et=function(e){return e instanceof B&&(void 0!==e.__depth&&"number"!=typeof e.__depth||void 0!==e.__removalCount&&"number"!=typeof e.__removalCount||"string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||!(e.attributes instanceof z)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore||"function"!=typeof e.hasChildNodes)},At=function(e){return"function"==typeof b&&e instanceof b},Nt=function(e,t,n){le[e]&&u(le[e],(e=>{e.call(o,t,n,it)}))},bt=function(e){let t=null;if(Nt("beforeSanitizeElements",e,null),Et(e))return gt(e),!0;const n=rt(e.nodeName);if(Nt("uponSanitizeElement",e,{tagName:n,allowedTags:Te}),e.hasChildNodes()&&!At(e.firstElementChild)&&E(/<[/\w]/g,e.innerHTML)&&E(/<[/\w]/g,e.textContent))return gt(e),!0;if(7===e.nodeType)return gt(e),!0;if(Le&&8===e.nodeType&&E(/<[/\w]/g,e.data))return gt(e),!0;if(!Te[n]||Ne[n]){if(!Ne[n]&&Rt(n)){if(Ae.tagNameCheck instanceof RegExp&&E(Ae.tagNameCheck,n))return!1;if(Ae.tagNameCheck instanceof Function&&Ae.tagNameCheck(n))return!1}if(He&&!We[n]){const t=Q(e)||e.parentNode,n=J(e)||e.childNodes;if(n&&t){for(let o=n.length-1;o>=0;--o){const r=X(n[o],!0);r.__removalCount=(e.__removalCount||0)+1,t.insertBefore(r,$(e))}}}return gt(e),!0}return e instanceof R&&!ht(e)?(gt(e),!0):"noscript"!==n&&"noembed"!==n&&"noframes"!==n||!E(/<\/no(script|embed|frames)/i,e.innerHTML)?(ve&&3===e.nodeType&&(t=e.textContent,u([ce,se,ue],(e=>{t=g(t,e," ")})),e.textContent!==t&&(p(o.removed,{element:e.cloneNode()}),e.textContent=t)),Nt("afterSanitizeElements",e,null),!1):(gt(e),!0)},St=function(e,t,n){if(Ue&&("id"===t||"name"===t)&&(n in r||n in lt))return!1;if(Re&&!be[t]&&E(me,t));else if(Se&&E(pe,t));else if(!ye[t]||be[t]){if(!(Rt(e)&&(Ae.tagNameCheck instanceof RegExp&&E(Ae.tagNameCheck,e)||Ae.tagNameCheck instanceof Function&&Ae.tagNameCheck(e))&&(Ae.attributeNameCheck instanceof RegExp&&E(Ae.attributeNameCheck,t)||Ae.attributeNameCheck instanceof Function&&Ae.attributeNameCheck(t))||"is"===t&&Ae.allowCustomizedBuiltInElements&&(Ae.tagNameCheck instanceof RegExp&&E(Ae.tagNameCheck,n)||Ae.tagNameCheck instanceof Function&&Ae.tagNameCheck(n))))return!1}else if(Xe[t]);else if(E(ge,g(n,de,"")));else if("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==T(n,"data:")||!Ye[e]){if(we&&!E(fe,g(n,de,"")));else if(n)return!1}else;return!0},Rt=function(e){return"annotation-xml"!==e&&h(e,he)},wt=function(e){Nt("beforeSanitizeAttributes",e,null);const{attributes:t}=e;if(!t)return;const n={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:ye};let r=t.length;for(;r--;){const i=t[r],{name:a,namespaceURI:l,value:c}=i,s=rt(a);let p="value"===a?c:_(c);if(n.attrName=s,n.attrValue=p,n.keepAttr=!0,n.forceKeepAttr=void 0,Nt("uponSanitizeAttribute",e,n),p=n.attrValue,n.forceKeepAttr)continue;if(Tt(a,e),!n.keepAttr)continue;if(!Ce&&E(/\/>/i,p)){Tt(a,e);continue}ve&&u([ce,se,ue],(e=>{p=g(p,e," ")}));const f=rt(e.nodeName);if(St(f,s,p)){if(!Pe||"id"!==s&&"name"!==s||(Tt(a,e),p=Fe+p),ee&&"object"==typeof G&&"function"==typeof G.getAttributeType)if(l);else switch(G.getAttributeType(f,s)){case"TrustedHTML":p=ee.createHTML(p);break;case"TrustedScriptURL":p=ee.createScriptURL(p)}try{l?e.setAttributeNS(l,a,p):e.setAttribute(a,p),m(o.removed)}catch(e){}}}Nt("afterSanitizeAttributes",e,null)},Ct=function e(t){let n=null;const o=yt(t);for(Nt("beforeSanitizeShadowDOM",t,null);n=o.nextNode();){if(Nt("uponSanitizeShadowNode",n,null),bt(n))continue;const t=Q(n);1===n.nodeType&&(t&&t.__depth?n.__depth=(n.__removalCount||0)+t.__depth+1:n.__depth=1),n.__depth>=at&>(n),n.content instanceof s&&(n.content.__depth=n.__depth,e(n.content)),wt(n)}Nt("afterSanitizeShadowDOM",t,null)};return o.sanitize=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=null,r=null,i=null,l=null;if(Je=!e,Je&&(e="\x3c!--\x3e"),"string"!=typeof e&&!At(e)){if("function"!=typeof e.toString)throw A("toString is not a function");if("string"!=typeof(e=e.toString()))throw A("dirty is not a string, aborting")}if(!o.isSupported)return e;if(Oe||st(t),o.removed=[],"string"==typeof e&&(ze=!1),ze){if(e.nodeName){const t=rt(e.nodeName);if(!Te[t]||Ne[t])throw A("root node is forbidden and cannot be sanitized in-place")}}else if(e instanceof b)n=_t("\x3c!----\x3e"),r=n.ownerDocument.importNode(e,!0),1===r.nodeType&&"BODY"===r.nodeName||"HTML"===r.nodeName?n=r:n.appendChild(r);else{if(!ke&&!ve&&!De&&-1===e.indexOf("<"))return ee&&Ie?ee.createHTML(e):e;if(n=_t(e),!n)return ke?null:Ie?te:""}n&&xe&>(n.firstChild);const c=yt(ze?e:n);for(;i=c.nextNode();){if(bt(i))continue;const e=Q(i);1===i.nodeType&&(e&&e.__depth?i.__depth=(i.__removalCount||0)+e.__depth+1:i.__depth=1),i.__depth>=at&>(i),i.content instanceof s&&(i.content.__depth=i.__depth,Ct(i.content)),wt(i)}if(ze)return e;if(ke){if(Me)for(l=re.call(n.ownerDocument);n.firstChild;)l.appendChild(n.firstChild);else l=n;return(ye.shadowroot||ye.shadowrootmode)&&(l=ae.call(a,l,!0)),l}let m=De?n.outerHTML:n.innerHTML;return De&&Te["!doctype"]&&n.ownerDocument&&n.ownerDocument.doctype&&n.ownerDocument.doctype.name&&E(q,n.ownerDocument.doctype.name)&&(m="\n"+m),ve&&u([ce,se,ue],(e=>{m=g(m,e," ")})),ee&&Ie?ee.createHTML(m):m},o.setConfig=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};st(e),Oe=!0},o.clearConfig=function(){it=null,Oe=!1},o.isValidAttribute=function(e,t,n){it||st({});const o=rt(e),r=rt(t);return St(o,r,n)},o.addHook=function(e,t){"function"==typeof t&&(le[e]=le[e]||[],p(le[e],t))},o.removeHook=function(e){if(le[e])return m(le[e])},o.removeHooks=function(e){le[e]&&(le[e]=[])},o.removeAllHooks=function(){le={}},o}();return J})); +/*! @license DOMPurify 3.1.4 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.1.4/LICENSE */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).DOMPurify=t()}(this,(function(){"use strict";const{entries:e,setPrototypeOf:t,isFrozen:n,getPrototypeOf:o,getOwnPropertyDescriptor:r}=Object;let{freeze:i,seal:a,create:l}=Object,{apply:c,construct:s}="undefined"!=typeof Reflect&&Reflect;i||(i=function(e){return e}),a||(a=function(e){return e}),c||(c=function(e,t,n){return e.apply(t,n)}),s||(s=function(e,t){return new e(...t)});const u=S(Array.prototype.forEach),m=S(Array.prototype.pop),p=S(Array.prototype.push),f=S(String.prototype.toLowerCase),d=S(String.prototype.toString),h=S(String.prototype.match),g=S(String.prototype.replace),_=S(String.prototype.indexOf),T=S(String.prototype.trim),y=S(Object.prototype.hasOwnProperty),E=S(RegExp.prototype.test),A=(N=TypeError,function(){for(var e=arguments.length,t=new Array(e),n=0;n1?n-1:0),r=1;r2&&void 0!==arguments[2]?arguments[2]:f;t&&t(e,null);let i=o.length;for(;i--;){let t=o[i];if("string"==typeof t){const e=r(t);e!==t&&(n(o)||(o[i]=e),t=e)}e[t]=!0}return e}function v(e){for(let t=0;t/gm),W=a(/\${[\w\W]*}/gm),G=a(/^data-[\-\w.\u00B7-\uFFFF]/),Y=a(/^aria-[\-\w]+$/),j=a(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),X=a(/^(?:\w+script|data):/i),q=a(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),$=a(/^html$/i),K=a(/^[a-z][.\w]*(-[.\w]+)+$/i);var V=Object.freeze({__proto__:null,MUSTACHE_EXPR:z,ERB_EXPR:B,TMPLIT_EXPR:W,DATA_ATTR:G,ARIA_ATTR:Y,IS_ALLOWED_URI:j,IS_SCRIPT_OR_DATA:X,ATTR_WHITESPACE:q,DOCTYPE_NAME:$,CUSTOM_ELEMENT:K});const Z=1,J=3,Q=7,ee=8,te=9,ne=function(){return"undefined"==typeof window?null:window},oe=function(e,t){if("object"!=typeof e||"function"!=typeof e.createPolicy)return null;let n=null;const o="data-tt-policy-suffix";t&&t.hasAttribute(o)&&(n=t.getAttribute(o));const r="dompurify"+(n?"#"+n:"");try{return e.createPolicy(r,{createHTML:e=>e,createScriptURL:e=>e})}catch(e){return console.warn("TrustedTypes policy "+r+" could not be created."),null}};var re=function t(){let n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:ne();const o=e=>t(e);if(o.version="3.1.4",o.removed=[],!n||!n.document||n.document.nodeType!==te)return o.isSupported=!1,o;let{document:r}=n;const a=r,c=a.currentScript,{DocumentFragment:s,HTMLTemplateElement:N,Node:S,Element:v,NodeFilter:z,NamedNodeMap:B=n.NamedNodeMap||n.MozNamedAttrMap,HTMLFormElement:W,DOMParser:G,trustedTypes:Y}=n,X=v.prototype,q=C(X,"cloneNode"),K=C(X,"nextSibling"),re=C(X,"childNodes"),ie=C(X,"parentNode");if("function"==typeof N){const e=r.createElement("template");e.content&&e.content.ownerDocument&&(r=e.content.ownerDocument)}let ae,le="";const{implementation:ce,createNodeIterator:se,createDocumentFragment:ue,getElementsByTagName:me}=r,{importNode:pe}=a;let fe={};o.isSupported="function"==typeof e&&"function"==typeof ie&&ce&&void 0!==ce.createHTMLDocument;const{MUSTACHE_EXPR:de,ERB_EXPR:he,TMPLIT_EXPR:ge,DATA_ATTR:_e,ARIA_ATTR:Te,IS_SCRIPT_OR_DATA:ye,ATTR_WHITESPACE:Ee,CUSTOM_ELEMENT:Ae}=V;let{IS_ALLOWED_URI:Ne}=V,be=null;const Se=R({},[...L,...D,...O,...k,...I]);let Re=null;const ve=R({},[...U,...P,...F,...H]);let we=Object.seal(l(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),Ce=null,Le=null,De=!0,Oe=!0,xe=!1,ke=!0,Me=!1,Ie=!0,Ue=!1,Pe=!1,Fe=!1,He=!1,ze=!1,Be=!1,We=!0,Ge=!1;const Ye="user-content-";let je=!0,Xe=!1,qe={},$e=null;const Ke=R({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]);let Ve=null;const Ze=R({},["audio","video","img","source","image","track"]);let Je=null;const Qe=R({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),et="http://www.w3.org/1998/Math/MathML",tt="http://www.w3.org/2000/svg",nt="http://www.w3.org/1999/xhtml";let ot=nt,rt=!1,it=null;const at=R({},[et,tt,nt],d);let lt=null;const ct=["application/xhtml+xml","text/html"],st="text/html";let ut=null,mt=null;const pt=255,ft=r.createElement("form"),dt=function(e){return e instanceof RegExp||e instanceof Function},ht=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(!mt||mt!==e){if(e&&"object"==typeof e||(e={}),e=w(e),lt=-1===ct.indexOf(e.PARSER_MEDIA_TYPE)?st:e.PARSER_MEDIA_TYPE,ut="application/xhtml+xml"===lt?d:f,be=y(e,"ALLOWED_TAGS")?R({},e.ALLOWED_TAGS,ut):Se,Re=y(e,"ALLOWED_ATTR")?R({},e.ALLOWED_ATTR,ut):ve,it=y(e,"ALLOWED_NAMESPACES")?R({},e.ALLOWED_NAMESPACES,d):at,Je=y(e,"ADD_URI_SAFE_ATTR")?R(w(Qe),e.ADD_URI_SAFE_ATTR,ut):Qe,Ve=y(e,"ADD_DATA_URI_TAGS")?R(w(Ze),e.ADD_DATA_URI_TAGS,ut):Ze,$e=y(e,"FORBID_CONTENTS")?R({},e.FORBID_CONTENTS,ut):Ke,Ce=y(e,"FORBID_TAGS")?R({},e.FORBID_TAGS,ut):{},Le=y(e,"FORBID_ATTR")?R({},e.FORBID_ATTR,ut):{},qe=!!y(e,"USE_PROFILES")&&e.USE_PROFILES,De=!1!==e.ALLOW_ARIA_ATTR,Oe=!1!==e.ALLOW_DATA_ATTR,xe=e.ALLOW_UNKNOWN_PROTOCOLS||!1,ke=!1!==e.ALLOW_SELF_CLOSE_IN_ATTR,Me=e.SAFE_FOR_TEMPLATES||!1,Ie=!1!==e.SAFE_FOR_XML,Ue=e.WHOLE_DOCUMENT||!1,He=e.RETURN_DOM||!1,ze=e.RETURN_DOM_FRAGMENT||!1,Be=e.RETURN_TRUSTED_TYPE||!1,Fe=e.FORCE_BODY||!1,We=!1!==e.SANITIZE_DOM,Ge=e.SANITIZE_NAMED_PROPS||!1,je=!1!==e.KEEP_CONTENT,Xe=e.IN_PLACE||!1,Ne=e.ALLOWED_URI_REGEXP||j,ot=e.NAMESPACE||nt,we=e.CUSTOM_ELEMENT_HANDLING||{},e.CUSTOM_ELEMENT_HANDLING&&dt(e.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(we.tagNameCheck=e.CUSTOM_ELEMENT_HANDLING.tagNameCheck),e.CUSTOM_ELEMENT_HANDLING&&dt(e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(we.attributeNameCheck=e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),e.CUSTOM_ELEMENT_HANDLING&&"boolean"==typeof e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements&&(we.allowCustomizedBuiltInElements=e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),Me&&(Oe=!1),ze&&(He=!0),qe&&(be=R({},I),Re=[],!0===qe.html&&(R(be,L),R(Re,U)),!0===qe.svg&&(R(be,D),R(Re,P),R(Re,H)),!0===qe.svgFilters&&(R(be,O),R(Re,P),R(Re,H)),!0===qe.mathMl&&(R(be,k),R(Re,F),R(Re,H))),e.ADD_TAGS&&(be===Se&&(be=w(be)),R(be,e.ADD_TAGS,ut)),e.ADD_ATTR&&(Re===ve&&(Re=w(Re)),R(Re,e.ADD_ATTR,ut)),e.ADD_URI_SAFE_ATTR&&R(Je,e.ADD_URI_SAFE_ATTR,ut),e.FORBID_CONTENTS&&($e===Ke&&($e=w($e)),R($e,e.FORBID_CONTENTS,ut)),je&&(be["#text"]=!0),Ue&&R(be,["html","head","body"]),be.table&&(R(be,["tbody"]),delete Ce.tbody),e.TRUSTED_TYPES_POLICY){if("function"!=typeof e.TRUSTED_TYPES_POLICY.createHTML)throw A('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if("function"!=typeof e.TRUSTED_TYPES_POLICY.createScriptURL)throw A('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');ae=e.TRUSTED_TYPES_POLICY,le=ae.createHTML("")}else void 0===ae&&(ae=oe(Y,c)),null!==ae&&"string"==typeof le&&(le=ae.createHTML(""));i&&i(e),mt=e}},gt=R({},["mi","mo","mn","ms","mtext"]),_t=R({},["foreignobject","annotation-xml"]),Tt=R({},["title","style","font","a","script"]),yt=R({},[...D,...O,...x]),Et=R({},[...k,...M]),At=function(e){let t=ie(e);t&&t.tagName||(t={namespaceURI:ot,tagName:"template"});const n=f(e.tagName),o=f(t.tagName);return!!it[e.namespaceURI]&&(e.namespaceURI===tt?t.namespaceURI===nt?"svg"===n:t.namespaceURI===et?"svg"===n&&("annotation-xml"===o||gt[o]):Boolean(yt[n]):e.namespaceURI===et?t.namespaceURI===nt?"math"===n:t.namespaceURI===tt?"math"===n&&_t[o]:Boolean(Et[n]):e.namespaceURI===nt?!(t.namespaceURI===tt&&!_t[o])&&(!(t.namespaceURI===et&&!gt[o])&&(!Et[n]&&(Tt[n]||!yt[n]))):!("application/xhtml+xml"!==lt||!it[e.namespaceURI]))},Nt=function(e){p(o.removed,{element:e});try{e.parentNode.removeChild(e)}catch(t){e.remove()}},bt=function(e,t){try{p(o.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){p(o.removed,{attribute:null,from:t})}if(t.removeAttribute(e),"is"===e&&!Re[e])if(He||ze)try{Nt(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},St=function(e){let t=null,n=null;if(Fe)e=""+e;else{const t=h(e,/^[\r\n\t ]+/);n=t&&t[0]}"application/xhtml+xml"===lt&&ot===nt&&(e=''+e+"");const o=ae?ae.createHTML(e):e;if(ot===nt)try{t=(new G).parseFromString(o,lt)}catch(e){}if(!t||!t.documentElement){t=ce.createDocument(ot,"template",null);try{t.documentElement.innerHTML=rt?le:o}catch(e){}}const i=t.body||t.documentElement;return e&&n&&i.insertBefore(r.createTextNode(n),i.childNodes[0]||null),ot===nt?me.call(t,Ue?"html":"body")[0]:Ue?t.documentElement:i},Rt=function(e){return se.call(e.ownerDocument||e,e,z.SHOW_ELEMENT|z.SHOW_COMMENT|z.SHOW_TEXT|z.SHOW_PROCESSING_INSTRUCTION|z.SHOW_CDATA_SECTION,null)},vt=function(e){return e instanceof W&&(void 0!==e.__depth&&"number"!=typeof e.__depth||void 0!==e.__removalCount&&"number"!=typeof e.__removalCount||"string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||!(e.attributes instanceof B)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore||"function"!=typeof e.hasChildNodes)},wt=function(e){return"function"==typeof S&&e instanceof S},Ct=function(e,t,n){fe[e]&&u(fe[e],(e=>{e.call(o,t,n,mt)}))},Lt=function(e){let t=null;if(Ct("beforeSanitizeElements",e,null),vt(e))return Nt(e),!0;const n=ut(e.nodeName);if(Ct("uponSanitizeElement",e,{tagName:n,allowedTags:be}),e.hasChildNodes()&&!wt(e.firstElementChild)&&E(/<[/\w]/g,e.innerHTML)&&E(/<[/\w]/g,e.textContent))return Nt(e),!0;if(e.nodeType===Q)return Nt(e),!0;if(Ie&&e.nodeType===ee&&E(/<[/\w]/g,e.data))return Nt(e),!0;if(!be[n]||Ce[n]){if(!Ce[n]&&Ot(n)){if(we.tagNameCheck instanceof RegExp&&E(we.tagNameCheck,n))return!1;if(we.tagNameCheck instanceof Function&&we.tagNameCheck(n))return!1}if(je&&!$e[n]){const t=ie(e)||e.parentNode,n=re(e)||e.childNodes;if(n&&t){for(let o=n.length-1;o>=0;--o){const r=q(n[o],!0);r.__removalCount=(e.__removalCount||0)+1,t.insertBefore(r,K(e))}}}return Nt(e),!0}return e instanceof v&&!At(e)?(Nt(e),!0):"noscript"!==n&&"noembed"!==n&&"noframes"!==n||!E(/<\/no(script|embed|frames)/i,e.innerHTML)?(Me&&e.nodeType===J&&(t=e.textContent,u([de,he,ge],(e=>{t=g(t,e," ")})),e.textContent!==t&&(p(o.removed,{element:e.cloneNode()}),e.textContent=t)),Ct("afterSanitizeElements",e,null),!1):(Nt(e),!0)},Dt=function(e,t,n){if(We&&("id"===t||"name"===t)&&(n in r||n in ft||"__depth"===n||"__removalCount"===n))return!1;if(Oe&&!Le[t]&&E(_e,t));else if(De&&E(Te,t));else if(!Re[t]||Le[t]){if(!(Ot(e)&&(we.tagNameCheck instanceof RegExp&&E(we.tagNameCheck,e)||we.tagNameCheck instanceof Function&&we.tagNameCheck(e))&&(we.attributeNameCheck instanceof RegExp&&E(we.attributeNameCheck,t)||we.attributeNameCheck instanceof Function&&we.attributeNameCheck(t))||"is"===t&&we.allowCustomizedBuiltInElements&&(we.tagNameCheck instanceof RegExp&&E(we.tagNameCheck,n)||we.tagNameCheck instanceof Function&&we.tagNameCheck(n))))return!1}else if(Je[t]);else if(E(Ne,g(n,Ee,"")));else if("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==_(n,"data:")||!Ve[e]){if(xe&&!E(ye,g(n,Ee,"")));else if(n)return!1}else;return!0},Ot=function(e){return"annotation-xml"!==e&&h(e,Ae)},xt=function(e){Ct("beforeSanitizeAttributes",e,null);const{attributes:t}=e;if(!t)return;const n={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:Re};let r=t.length;for(;r--;){const i=t[r],{name:a,namespaceURI:l,value:c}=i,s=ut(a);let p="value"===a?c:T(c);if(n.attrName=s,n.attrValue=p,n.keepAttr=!0,n.forceKeepAttr=void 0,Ct("uponSanitizeAttribute",e,n),p=n.attrValue,n.forceKeepAttr)continue;if(bt(a,e),!n.keepAttr)continue;if(!ke&&E(/\/>/i,p)){bt(a,e);continue}if(Ie&&E(/((--!?|])>)|<\/(style|title)/i,p)){bt(a,e);continue}Me&&u([de,he,ge],(e=>{p=g(p,e," ")}));const f=ut(e.nodeName);if(Dt(f,s,p)){if(!Ge||"id"!==s&&"name"!==s||(bt(a,e),p=Ye+p),ae&&"object"==typeof Y&&"function"==typeof Y.getAttributeType)if(l);else switch(Y.getAttributeType(f,s)){case"TrustedHTML":p=ae.createHTML(p);break;case"TrustedScriptURL":p=ae.createScriptURL(p)}try{l?e.setAttributeNS(l,a,p):e.setAttribute(a,p),vt(e)?Nt(e):m(o.removed)}catch(e){}}}Ct("afterSanitizeAttributes",e,null)},kt=function e(t){let n=null;const o=Rt(t);for(Ct("beforeSanitizeShadowDOM",t,null);n=o.nextNode();){if(Ct("uponSanitizeShadowNode",n,null),Lt(n))continue;const t=ie(n);n.nodeType===Z&&(t&&t.__depth?n.__depth=(n.__removalCount||0)+t.__depth+1:n.__depth=1),(n.__depth>=pt||n.__depth<0||b(n.__depth))&&Nt(n),n.content instanceof s&&(n.content.__depth=n.__depth,e(n.content)),xt(n)}Ct("afterSanitizeShadowDOM",t,null)};return o.sanitize=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=null,r=null,i=null,l=null;if(rt=!e,rt&&(e="\x3c!--\x3e"),"string"!=typeof e&&!wt(e)){if("function"!=typeof e.toString)throw A("toString is not a function");if("string"!=typeof(e=e.toString()))throw A("dirty is not a string, aborting")}if(!o.isSupported)return e;if(Pe||ht(t),o.removed=[],"string"==typeof e&&(Xe=!1),Xe){if(e.nodeName){const t=ut(e.nodeName);if(!be[t]||Ce[t])throw A("root node is forbidden and cannot be sanitized in-place")}}else if(e instanceof S)n=St("\x3c!----\x3e"),r=n.ownerDocument.importNode(e,!0),r.nodeType===Z&&"BODY"===r.nodeName||"HTML"===r.nodeName?n=r:n.appendChild(r);else{if(!He&&!Me&&!Ue&&-1===e.indexOf("<"))return ae&&Be?ae.createHTML(e):e;if(n=St(e),!n)return He?null:Be?le:""}n&&Fe&&Nt(n.firstChild);const c=Rt(Xe?e:n);for(;i=c.nextNode();){if(Lt(i))continue;const e=ie(i);i.nodeType===Z&&(e&&e.__depth?i.__depth=(i.__removalCount||0)+e.__depth+1:i.__depth=1),(i.__depth>=pt||i.__depth<0||b(i.__depth))&&Nt(i),i.content instanceof s&&(i.content.__depth=i.__depth,kt(i.content)),xt(i)}if(Xe)return e;if(He){if(ze)for(l=ue.call(n.ownerDocument);n.firstChild;)l.appendChild(n.firstChild);else l=n;return(Re.shadowroot||Re.shadowrootmode)&&(l=pe.call(a,l,!0)),l}let m=Ue?n.outerHTML:n.innerHTML;return Ue&&be["!doctype"]&&n.ownerDocument&&n.ownerDocument.doctype&&n.ownerDocument.doctype.name&&E($,n.ownerDocument.doctype.name)&&(m="\n"+m),Me&&u([de,he,ge],(e=>{m=g(m,e," ")})),ae&&Be?ae.createHTML(m):m},o.setConfig=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};ht(e),Pe=!0},o.clearConfig=function(){mt=null,Pe=!1},o.isValidAttribute=function(e,t,n){mt||ht({});const o=ut(e),r=ut(t);return Dt(o,r,n)},o.addHook=function(e,t){"function"==typeof t&&(fe[e]=fe[e]||[],p(fe[e],t))},o.removeHook=function(e){if(fe[e])return m(fe[e])},o.removeHooks=function(e){fe[e]&&(fe[e]=[])},o.removeAllHooks=function(){fe={}},o}();return re})); diff --git a/src/ui/static/js/utils/purify/src/attrs.js b/src/ui/static/js/utils/purify/src/attrs.js index 9a60601dd..ddc488be6 100644 --- a/src/ui/static/js/utils/purify/src/attrs.js +++ b/src/ui/static/js/utils/purify/src/attrs.js @@ -74,6 +74,9 @@ export const html = freeze([ 'pattern', 'placeholder', 'playsinline', + 'popover', + 'popovertarget', + 'popovertargetaction', 'poster', 'preload', 'pubdate', diff --git a/src/ui/static/js/utils/purify/src/purify.js b/src/ui/static/js/utils/purify/src/purify.js index b1a0ef2f7..dcedbe97c 100644 --- a/src/ui/static/js/utils/purify/src/purify.js +++ b/src/ui/static/js/utils/purify/src/purify.js @@ -15,6 +15,7 @@ import { stringToString, stringIndexOf, stringTrim, + numberIsNaN, regExpTest, typeErrorCreate, lookupGetter, @@ -22,6 +23,22 @@ import { objectHasOwnProperty, } from './utils.js'; +// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType +const NODE_TYPE = { + element: 1, + attribute: 2, + text: 3, + cdataSection: 4, + entityReference: 5, // Deprecated + entityNode: 6, // Deprecated + progressingInstruction: 7, + comment: 8, + document: 9, + documentType: 10, + documentFragment: 11, + notation: 12, // Deprecated +}; + const getGlobal = function () { return typeof window === 'undefined' ? null : window; }; @@ -88,7 +105,11 @@ function createDOMPurify(window = getGlobal()) { */ DOMPurify.removed = []; - if (!window || !window.document || window.document.nodeType !== 9) { + if ( + !window || + !window.document || + window.document.nodeType !== NODE_TYPE.document + ) { // Not running in a browser, provide a factory function // so that you can pass your own Window DOMPurify.isSupported = false; @@ -1024,7 +1045,7 @@ function createDOMPurify(window = getGlobal()) { } /* Remove any ocurrence of processing instructions */ - if (currentNode.nodeType === 7) { + if (currentNode.nodeType === NODE_TYPE.progressingInstruction) { _forceRemove(currentNode); return true; } @@ -1032,7 +1053,7 @@ function createDOMPurify(window = getGlobal()) { /* Remove any kind of possibly harmful comments */ if ( SAFE_FOR_XML && - currentNode.nodeType === 8 && + currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\w]/g, currentNode.data) ) { _forceRemove(currentNode); @@ -1096,7 +1117,7 @@ function createDOMPurify(window = getGlobal()) { } /* Sanitize element content to be template-safe */ - if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) { + if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) { /* Get the element's text content */ content = currentNode.textContent; @@ -1130,7 +1151,10 @@ function createDOMPurify(window = getGlobal()) { if ( SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && - (value in document || value in formElement) + (value in document || + value in formElement || + value === '__depth' || + value === '__removalCount') ) { return false; } @@ -1288,6 +1312,12 @@ function createDOMPurify(window = getGlobal()) { continue; } + /* Work around a security issue with comments inside attributes */ + if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|title)/i, value)) { + _removeAttribute(name, currentNode); + continue; + } + /* Sanitize attribute content to be template-safe */ if (SAFE_FOR_TEMPLATES) { arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], (expr) => { @@ -1348,7 +1378,11 @@ function createDOMPurify(window = getGlobal()) { currentNode.setAttribute(name, value); } - arrayPop(DOMPurify.removed); + if (_isClobbered(currentNode)) { + _forceRemove(currentNode); + } else { + arrayPop(DOMPurify.removed); + } } catch (_) {} } @@ -1380,7 +1414,7 @@ function createDOMPurify(window = getGlobal()) { const parentNode = getParentNode(shadowNode); /* Set the nesting depth of an element */ - if (shadowNode.nodeType === 1) { + if (shadowNode.nodeType === NODE_TYPE.element) { if (parentNode && parentNode.__depth) { /* We want the depth of the node in the original tree, which can @@ -1393,8 +1427,15 @@ function createDOMPurify(window = getGlobal()) { } } - /* Remove an element if nested too deeply to avoid mXSS */ - if (shadowNode.__depth >= MAX_NESTING_DEPTH) { + /* + * Remove an element if nested too deeply to avoid mXSS + * or if the __depth might have been tampered with + */ + if ( + shadowNode.__depth >= MAX_NESTING_DEPTH || + shadowNode.__depth < 0 || + numberIsNaN(shadowNode.__depth) + ) { _forceRemove(shadowNode); } @@ -1478,7 +1519,10 @@ function createDOMPurify(window = getGlobal()) { elements being stripped by the parser */ body = _initDocument(''); importedNode = body.ownerDocument.importNode(dirty, true); - if (importedNode.nodeType === 1 && importedNode.nodeName === 'BODY') { + if ( + importedNode.nodeType === NODE_TYPE.element && + importedNode.nodeName === 'BODY' + ) { /* Node is already a body, use as is */ body = importedNode; } else if (importedNode.nodeName === 'HTML') { @@ -1528,7 +1572,7 @@ function createDOMPurify(window = getGlobal()) { const parentNode = getParentNode(currentNode); /* Set the nesting depth of an element */ - if (currentNode.nodeType === 1) { + if (currentNode.nodeType === NODE_TYPE.element) { if (parentNode && parentNode.__depth) { /* We want the depth of the node in the original tree, which can @@ -1541,8 +1585,15 @@ function createDOMPurify(window = getGlobal()) { } } - /* Remove an element if nested too deeply to avoid mXSS */ - if (currentNode.__depth >= MAX_NESTING_DEPTH) { + /* + * Remove an element if nested too deeply to avoid mXSS + * or if the __depth might have been tampered with + */ + if ( + currentNode.__depth >= MAX_NESTING_DEPTH || + currentNode.__depth < 0 || + numberIsNaN(currentNode.__depth) + ) { _forceRemove(currentNode); } diff --git a/src/ui/static/js/utils/purify/src/utils.js b/src/ui/static/js/utils/purify/src/utils.js index f601d47b7..b0726a82f 100644 --- a/src/ui/static/js/utils/purify/src/utils.js +++ b/src/ui/static/js/utils/purify/src/utils.js @@ -52,6 +52,11 @@ const regExpTest = unapply(RegExp.prototype.test); const typeErrorCreate = unconstruct(TypeError); +export function numberIsNaN(x) { + // eslint-disable-next-line unicorn/prefer-number-properties + return typeof x === 'number' && isNaN(x); +} + /** * Creates a new function that calls the given function with a specified thisArg and arguments. * diff --git a/src/ui/templates/file_manager.html b/src/ui/templates/file_manager.html index 0d79e8dc6..0dd3bc82c 100644 --- a/src/ui/templates/file_manager.html +++ b/src/ui/templates/file_manager.html @@ -177,7 +177,7 @@ {% else %}