victorgeek commited on
Commit
9552aa0
·
verified ·
1 Parent(s): 108f7d7

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .dockerignore +21 -0
  2. .gitattributes +2 -0
  3. .github/dependabot.yml +11 -0
  4. .github/workflows/release.yml +237 -0
  5. .github/workflows/rust.yml +37 -0
  6. .gitignore +21 -0
  7. .rusty-hook.toml +5 -0
  8. CODE_OF_CONDUCT.md +5 -0
  9. CONTRIBUTING.md +3 -0
  10. Cargo.lock +0 -0
  11. Cargo.toml +18 -0
  12. Dockerfile +40 -0
  13. LICENSE +21 -0
  14. README.md +64 -10
  15. SECURITY.md +40 -0
  16. ferron-docker.yaml +4 -0
  17. ferron-passwd/Cargo.toml +20 -0
  18. ferron-passwd/assets/icon.ico +3 -0
  19. ferron-passwd/build.rs +13 -0
  20. ferron-passwd/src/main.rs +65 -0
  21. ferron-release.yaml +2 -0
  22. ferron.yaml +11 -0
  23. ferron/Cargo.toml +80 -0
  24. ferron/assets/icon.ico +3 -0
  25. ferron/build.rs +13 -0
  26. ferron/src/common/log.rs +32 -0
  27. ferron/src/common/mod.rs +593 -0
  28. ferron/src/common/with_runtime.rs +56 -0
  29. ferron/src/main.rs +478 -0
  30. ferron/src/modules/blocklist.rs +135 -0
  31. ferron/src/modules/default_handler_checks.rs +134 -0
  32. ferron/src/modules/non_standard_codes.rs +537 -0
  33. ferron/src/modules/redirect_trailing_slashes.rs +235 -0
  34. ferron/src/modules/redirects.rs +245 -0
  35. ferron/src/modules/static_file_serving.rs +928 -0
  36. ferron/src/modules/url_rewrite.rs +350 -0
  37. ferron/src/modules/x_forwarded_for.rs +144 -0
  38. ferron/src/optional_modules/asgi.rs +1476 -0
  39. ferron/src/optional_modules/cache.rs +525 -0
  40. ferron/src/optional_modules/cgi.rs +859 -0
  41. ferron/src/optional_modules/example.rs +143 -0
  42. ferron/src/optional_modules/fauth.rs +572 -0
  43. ferron/src/optional_modules/fcgi.rs +964 -0
  44. ferron/src/optional_modules/fproxy.rs +301 -0
  45. ferron/src/optional_modules/rproxy.rs +803 -0
  46. ferron/src/optional_modules/scgi.rs +673 -0
  47. ferron/src/optional_modules/wsgi.rs +742 -0
  48. ferron/src/optional_modules/wsgid.rs +1035 -0
  49. ferron/src/request_handler.rs +2211 -0
  50. ferron/src/res/server_software.rs +1 -0
.dockerignore ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Compiled binaries and generated documentation
2
+ /target/
3
+
4
+ # Profiling data
5
+ /perf.data
6
+ /perf.data.old
7
+ /flamegraph.svg
8
+
9
+ # Logs generated by the web server
10
+ /access.log
11
+ /error.log
12
+
13
+ # OS-specific files
14
+ .DS_Store
15
+ Thumbs.db
16
+ .Spotlight-V100
17
+ .Trashes
18
+
19
+ # Temporary files used by the editor
20
+ *.swp
21
+ *.swo
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ ferron/assets/icon.ico filter=lfs diff=lfs merge=lfs -text
37
+ ferron-passwd/assets/icon.ico filter=lfs diff=lfs merge=lfs -text
.github/dependabot.yml ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # To get started with Dependabot version updates, you'll need to specify which
2
+ # package ecosystems to update and where the package manifests are located.
3
+ # Please see the documentation for all configuration options:
4
+ # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5
+
6
+ version: 2
7
+ updates:
8
+ - package-ecosystem: "cargo" # See documentation for possible values
9
+ directory: "/" # Location of package manifests
10
+ schedule:
11
+ interval: "weekly"
.github/workflows/release.yml ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ branches: [ "main" ]
6
+
7
+ jobs:
8
+ release:
9
+ # Set the job to run on the platform specified by the matrix below
10
+ runs-on: ${{ matrix.runner }}
11
+
12
+ # Define the build matrix for cross-compilation
13
+ strategy:
14
+ matrix:
15
+ include:
16
+ - name: x86_64-unknown-linux-gnu
17
+ runner: ubuntu-latest
18
+ target: x86_64-unknown-linux-gnu
19
+ command: cross
20
+ - name: x86_64-unknown-linux-musl
21
+ runner: ubuntu-latest
22
+ target: x86_64-unknown-linux-musl
23
+ command: cross
24
+ - name: i686-unknown-linux-gnu
25
+ runner: ubuntu-latest
26
+ target: i686-unknown-linux-gnu
27
+ command: cross
28
+ - name: aarch64-unknown-linux-gnu
29
+ runner: ubuntu-latest
30
+ target: aarch64-unknown-linux-gnu
31
+ command: cross
32
+ - name: armv7-unknown-linux-gnueabihf
33
+ runner: ubuntu-latest
34
+ target: armv7-unknown-linux-gnueabihf
35
+ command: cross
36
+ - name: riscv64gc-unknown-linux-gnu
37
+ runner: ubuntu-latest
38
+ target: riscv64gc-unknown-linux-gnu
39
+ command: cross
40
+ - name: x86_64-unknown-freebsd
41
+ runner: ubuntu-latest
42
+ target: x86_64-unknown-freebsd
43
+ command: cross
44
+ - name: x86_64-pc-windows-msvc
45
+ runner: windows-latest
46
+ target: x86_64-pc-windows-msvc
47
+ command: cargo
48
+ - name: i686-pc-windows-msvc
49
+ runner: windows-latest
50
+ target: i686-pc-windows-msvc
51
+ command: cargo
52
+ - name: aarch64-pc-windows-msvc
53
+ runner: windows-latest
54
+ target: aarch64-pc-windows-msvc
55
+ command: cargo
56
+ - name: x86_64-apple-darwin
57
+ runner: macos-latest
58
+ target: x86_64-apple-darwin
59
+ command: cargo
60
+ - name: aarch64-apple-darwin
61
+ runner: macos-latest
62
+ target: aarch64-apple-darwin
63
+ command: cargo
64
+
65
+ # The steps to run for each matrix item
66
+ steps:
67
+ - name: Checkout
68
+ uses: actions/checkout@v3
69
+ with:
70
+ ref: main
71
+ fetch-depth: 0
72
+
73
+ - name: Install Rust
74
+ uses: dtolnay/rust-toolchain@stable
75
+ with:
76
+ targets: "${{ matrix.target }}"
77
+
78
+ - name: Setup cache
79
+ uses: Swatinem/rust-cache@v2
80
+
81
+ - name: Determine Ferron version
82
+ shell: bash
83
+ run: |
84
+ FERRON_VERSION_CARGO="$(cat ferron/Cargo.toml | grep -E '^version' | sed -E 's|.*"([0-9a-zA-Z.+-]+)"$|\1|g')"
85
+ FERRON_VERSION_GIT="$(git tag --sort=-committerdate | head -n 1 | sed s/[^0-9a-zA-Z.+-]//g)"
86
+ if [ "$FERRON_VERSION_CARGO" != "" ]; then
87
+ echo "Version determined from Cargo.toml file"
88
+ echo "FERRON_VERSION=$FERRON_VERSION_CARGO" >> $GITHUB_ENV
89
+ elif [ "$FERRON_VERSION_GIT" != "" ]; then
90
+ echo "Version determined from the Git tag"
91
+ echo "FERRON_VERSION=$FERRON_VERSION_GIT" >> $GITHUB_ENV
92
+ else
93
+ echo "Can't determine the server version!" 2>&1
94
+ exit 1
95
+ fi
96
+
97
+ - name: Install Cross
98
+ if: matrix.command == 'cross'
99
+ shell: bash
100
+ run: |
101
+ curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
102
+ cargo binstall --no-confirm cross
103
+
104
+ - name: Build binaries
105
+ run: "${{ matrix.command }} build --verbose --locked --release --target ${{ matrix.target }}"
106
+
107
+ - name: Prepare for packaging
108
+ shell: bash
109
+ run: |
110
+ mkdir release
111
+ find target/${{ matrix.target }}/release -mindepth 1 -maxdepth 1 -type f ! -name "*.*" -o -name "*.exe" -o -name "*.dll" -o -name "*.dylib" -o -name "*.so" | sed -E "s|(.*)|cp -a \1 release|" | bash
112
+ cp -a ferron-release.yaml release/ferron.yaml
113
+ cp -a wwwroot release
114
+
115
+ - name: Create a release ZIP archive
116
+ uses: thedoctor0/zip-release@0.7.5
117
+ with:
118
+ type: "zip"
119
+ filename: "../ferron.zip"
120
+ directory: "release"
121
+
122
+ - name: Set up SSH
123
+ uses: LuisEnMarroquin/setup-ssh-action@v2.0.5
124
+ with:
125
+ ORIGIN: ${{ secrets.SSH_HOSTNAME }}
126
+ SSHKEY: ${{ secrets.SSH_KEY }}
127
+ NAME: ferron-servers
128
+ PORT: ${{ secrets.SSH_PORT }}
129
+ USER: ${{ secrets.SSH_USERNAME }}
130
+
131
+ - name: Release Ferron on Ferron's servers
132
+ shell: bash
133
+ run: |
134
+ ssh ferron-servers "mkdir -p ferron/${{ env.FERRON_VERSION }} || true"
135
+ scp ferron.zip ferron-servers:ferron/${{ env.FERRON_VERSION }}/ferron-${{ env.FERRON_VERSION }}-${{ matrix.target }}.zip
136
+
137
+ # The "move-ferron-archive" is a custom command that moves the ZIP archive to be served by the download server
138
+ ssh ferron-servers "sudo move-ferron-archive ${{ env.FERRON_VERSION }} ${{ matrix.target }}"
139
+
140
+ docs:
141
+ runs-on: ubuntu-latest
142
+
143
+ steps:
144
+ - name: Checkout
145
+ uses: actions/checkout@v3
146
+ with:
147
+ ref: main
148
+
149
+ - name: Install Rust
150
+ uses: dtolnay/rust-toolchain@stable
151
+
152
+ - name: Setup cache
153
+ uses: Swatinem/rust-cache@v2
154
+
155
+ - name: Generate the Rust crate documentation
156
+ run: "cargo doc --verbose --locked --release"
157
+
158
+ - name: Create the documentation ZIP archive
159
+ uses: thedoctor0/zip-release@0.7.5
160
+ with:
161
+ type: "zip"
162
+ filename: "../../ferron-rustdocs.zip"
163
+ directory: "target/doc"
164
+
165
+ - name: Set up SSH
166
+ uses: LuisEnMarroquin/setup-ssh-action@v2.0.5
167
+ with:
168
+ ORIGIN: ${{ secrets.SSH_HOSTNAME }}
169
+ SSHKEY: ${{ secrets.SSH_KEY }}
170
+ NAME: ferron-servers
171
+ PORT: ${{ secrets.SSH_PORT }}
172
+ USER: ${{ secrets.SSH_USERNAME }}
173
+
174
+ - name: Deploy the documentation
175
+ shell: bash
176
+ run: |
177
+ scp ferron-rustdocs.zip ferron-servers:.
178
+
179
+ # The "deploy-ferron-rustdocs" is a custom command that deploys the Ferron's Rust crate documentation
180
+ ssh ferron-servers "sudo deploy-ferron-rustdocs ferron-rustdocs.zip && rm ferron-rustdocs.zip"
181
+
182
+ docker:
183
+ runs-on: ubuntu-latest
184
+
185
+ permissions:
186
+ packages: write
187
+ contents: read
188
+ attestations: write
189
+ id-token: write
190
+
191
+ steps:
192
+ - name: Checkout
193
+ uses: actions/checkout@v3
194
+ with:
195
+ ref: main
196
+ fetch-depth: 0
197
+
198
+ - name: Log in to Docker Hub
199
+ uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a
200
+ with:
201
+ username: ${{ secrets.DOCKER_USERNAME }}
202
+ password: ${{ secrets.DOCKER_PASSWORD }}
203
+
204
+ - name: Determine Ferron version
205
+ shell: bash
206
+ run: |
207
+ FERRON_VERSION_CARGO="$(cat ferron/Cargo.toml | grep -E '^version' | sed -E 's|.*"([0-9a-zA-Z.+-]+)"$|\1|g')"
208
+ FERRON_VERSION_GIT="$(git tag --sort=-committerdate | head -n 1 | sed s/[^0-9a-zA-Z.+-]//g)"
209
+ if [ "$FERRON_VERSION_CARGO" != "" ]; then
210
+ echo "Version determined from Cargo.toml file"
211
+ echo "FERRON_VERSION=$FERRON_VERSION_CARGO" >> $GITHUB_ENV
212
+ elif [ "$FERRON_VERSION_GIT" != "" ]; then
213
+ echo "Version determined from the Git tag"
214
+ echo "FERRON_VERSION=$FERRON_VERSION_GIT" >> $GITHUB_ENV
215
+ else
216
+ echo "Can't determine the server version!" 2>&1
217
+ exit 1
218
+ fi
219
+
220
+ - name: Set up Docker Buildx
221
+ uses: docker/setup-buildx-action@v3
222
+
223
+ - name: Build and push Docker image
224
+ id: push
225
+ uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671
226
+ with:
227
+ context: .
228
+ file: ./Dockerfile
229
+ push: true
230
+ tags: "ferronserver/ferron:${{ env.FERRON_VERSION }},ferronserver/ferron:latest"
231
+
232
+ - name: Generate artifact attestation
233
+ uses: actions/attest-build-provenance@v2
234
+ with:
235
+ subject-name: index.docker.io/ferronserver/ferron
236
+ subject-digest: ${{ steps.push.outputs.digest }}
237
+ push-to-registry: true
.github/workflows/rust.yml ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Rust
2
+
3
+ on:
4
+ push:
5
+ branches: [ "develop" ]
6
+ pull_request:
7
+ branches: [ "develop" ]
8
+
9
+ env:
10
+ CARGO_TERM_COLOR: always
11
+
12
+ jobs:
13
+ build:
14
+
15
+ runs-on: ubuntu-latest
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - uses: Swatinem/rust-cache@v2
20
+ - name: Build
21
+ run: cargo build --verbose
22
+ - name: Run tests
23
+ run: cargo test --workspace --verbose
24
+
25
+ check:
26
+ runs-on: ubuntu-latest
27
+ steps:
28
+ - uses: actions/checkout@v4
29
+ - uses: dtolnay/rust-toolchain@stable
30
+ with:
31
+ components: clippy, rustfmt
32
+ - uses: Swatinem/rust-cache@v2
33
+ - name: rustfmt
34
+ run: cargo fmt --all -- --check
35
+ - name: clippy
36
+ run: cargo clippy --workspace -- -D warnings
37
+
.gitignore ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Compiled binaries and generated documentation
2
+ /target/
3
+
4
+ # Profiling data
5
+ /perf.data
6
+ /perf.data.old
7
+ /flamegraph.svg
8
+
9
+ # Logs generated by the web server
10
+ /access.log
11
+ /error.log
12
+
13
+ # OS-specific files
14
+ .DS_Store
15
+ Thumbs.db
16
+ .Spotlight-V100
17
+ .Trashes
18
+
19
+ # Temporary files used by the editor
20
+ *.swp
21
+ *.swo
.rusty-hook.toml ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ [hooks]
2
+ pre-commit = "cargo clippy --fix --all-targets --allow-staged && cargo fmt && git add -A"
3
+
4
+ [logging]
5
+ verbose = true
CODE_OF_CONDUCT.md ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # Contributor Code of Conduct
2
+
3
+ This project adheres to No Code of Conduct. We are all adults. We accept anyone's contributions. Nothing else matters.
4
+
5
+ For more information please visit the [No Code of Conduct](https://nocodeofconduct.com) homepage.
CONTRIBUTING.md ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # Contribution guidelines
2
+
3
+ _See [contribution page on Ferron's website](https://www.ferronweb.org/contribute)_
Cargo.lock ADDED
The diff for this file is too large to render. See raw diff
 
Cargo.toml ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [workspace]
2
+ members = [
3
+ "ferron",
4
+ "ferron-passwd",
5
+ ]
6
+ resolver = "2"
7
+
8
+ [workspace.dependencies]
9
+ yaml-rust2 = "0.10.0"
10
+ password-auth = { version = "1.0.0", features = ["argon2", "pbkdf2", "scrypt"] }
11
+ rusty-hook = "0.11.2"
12
+ mimalloc = "0.1.45"
13
+
14
+ [profile.release]
15
+ strip = true
16
+ lto = true
17
+ codegen-units = 1
18
+ panic = "abort"
Dockerfile ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use the official Rust image as a build stage
2
+ FROM rust as builder
3
+
4
+ # Set the working directory
5
+ WORKDIR /usr/src/ferron
6
+
7
+ # Copy the source code
8
+ COPY . .
9
+
10
+ # Build the actual application
11
+ RUN cargo build --release
12
+
13
+ # Use a Devuan base image for the final image
14
+ FROM devuan/devuan
15
+
16
+ # Copy the compiled binaries from the builder stage
17
+ COPY --from=builder /usr/src/ferron/target/release/ferron /usr/sbin/ferron
18
+ COPY --from=builder /usr/src/ferron/target/release/ferron-passwd /usr/sbin/ferron-passwd
19
+
20
+ # Copy the web server configuration
21
+ COPY ferron-docker.yaml /etc/ferron.yaml
22
+
23
+ # Copy the web root contents
24
+ RUN mkdir -p /var/www/ferron
25
+ COPY wwwroot/* /var/www/ferron
26
+
27
+ # Create a directory where Ferron logs are stored
28
+ RUN mkdir -p /var/log/ferron
29
+
30
+ # Create a "ferron" user and grant the permissions for the log directory and the webroot to that user
31
+ RUN useradd -d /nonexistent -s /usr/sbin/nologin -r ferron && chown -hR ferron:ferron /var/www/ferron && chown -hR ferron:ferron /var/log/ferron
32
+
33
+ # Expose the port 80 (used for HTTP)
34
+ EXPOSE 80
35
+
36
+ # Switch to "ferron" user
37
+ USER ferron
38
+
39
+ # Set the command to run the binary
40
+ CMD ["/usr/sbin/ferron", "-c", "/etc/ferron.yaml"]
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ferron
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -1,10 +1,64 @@
1
- ---
2
- title: Web
3
- emoji: ⚡
4
- colorFrom: yellow
5
- colorTo: pink
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <p align="center">
2
+ <a href="https://www.ferronweb.org" target="_blank">
3
+ <picture>
4
+ <source media="(prefers-color-scheme: dark)" srcset="logo-dark.png">
5
+ <img alt="Ferron logo" src="logo.png" width="256">
6
+ </picture>
7
+ </a>
8
+ </p>
9
+ <p align="center">
10
+ <b>Ferron</b> - a fast, memory-safe web server written in Rust
11
+ </p>
12
+ <p align="center">
13
+ <a href="https://www.ferronweb.org/docs" target="_blank"><img alt="Static Badge" src="https://img.shields.io/badge/Documentation-orange"></a>
14
+ <a href="https://www.ferronweb.org" target="_blank"><img alt="Website" src="https://img.shields.io/website?url=https%3A%2F%2Fwww.ferronweb.org"></a>
15
+ <a href="https://x.com/ferron_web" target="_blank"><img alt="X (formerly Twitter) Follow" src="https://img.shields.io/twitter/follow/ferron_web"></a>
16
+ <a href="https://hub.docker.com/r/ferronserver/ferron" target="_blank"><img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/ferronserver/ferron"></a>
17
+ <a href="https://github.com/ferronweb/ferron" target="_blank"><img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/ferronweb/ferron"></a>
18
+ </p>
19
+
20
+ * * *
21
+
22
+ ## Features
23
+
24
+ - **High performance** - built with Rust’s async capabilities for optimal speed.
25
+ - **Memory-safe** - built with Rust, which is a programming language offering memory safety.
26
+ - **Extensibility** - modular architecture for easy customization.
27
+ - **Secure** - focus on robust security practices and safe concurrency.
28
+
29
+ ## Components
30
+
31
+ Ferron consists of multiple components:
32
+
33
+ - **`ferron`**: The main web server.
34
+ - **`ferron-passwd`**: A tool for generating user entries with hashed passwords, which can be copied into the web server's configuration file.
35
+
36
+ ## Building Ferron from source
37
+
38
+ You can clone the repository and explore the existing code:
39
+
40
+ ```sh
41
+ git clone https://github.com/ferronweb/ferron.git
42
+ cd ferron
43
+ ```
44
+
45
+ You can then build and run the web server using Cargo:
46
+
47
+ ```sh
48
+ cargo build -r
49
+ cargo run -r --bin ferron
50
+ ```
51
+
52
+ You can also use [Ferron Forge](https://github.com/ferronweb/ferron-forge) to build the web server. Ferron Forge outputs a ZIP archive that can be used by the Ferron installer.
53
+
54
+ ## Server configuration
55
+
56
+ You can check the [Ferron documentation](https://www.ferronweb.org/docs/configuration) to see configuration properties used by Ferron.
57
+
58
+ ## Contributing
59
+
60
+ See [Ferron contribution page](https://www.ferronweb.org/contribute) for details.
61
+
62
+ ## License
63
+
64
+ Ferron is licensed under the MIT License. See `LICENSE` for details.
SECURITY.md ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Ferron Security Policy
2
+
3
+ ## Overview
4
+ Ferron is a fast, memory-safe web server written in Rust, designed for performance and security. This document outlines the security policies and procedures to ensure Ferron remains a secure and reliable software project.
5
+
6
+ ## Supported versions
7
+ Ferron actively supports the latest stable release and provides security updates for the most recent minor versions. Users are encouraged to upgrade promptly to receive security patches.
8
+
9
+ ## Reporting security issues
10
+ Security is a top priority for Ferron. If you discover a vulnerability, please report it responsibly by sending an email message to [security@ferronweb.org](mailto:security@ferronweb.org).
11
+
12
+ We strongly discourage public disclosure of vulnerabilities before a fix is released.
13
+
14
+ ## Security best practices
15
+ To maintain security, we follow these principles:
16
+
17
+ - **Memory safety** - Ferron leverages Rust’s ownership model and borrow checker to eliminate memory-related vulnerabilities.
18
+ - **Minimal attack surface** - features are enabled only as needed, reducing exposure to potential threats.
19
+ - **Regular audits** - code is reviewed regularly, and dependencies are monitored for security vulnerabilities.
20
+ - **Safe defaults** - Ferron has some insecure configuration disabled by default, like exposing the server version or directory listings.
21
+
22
+ ## Secure development process
23
+ Ferron follows industry best practices to maintain a secure development lifecycle:
24
+
25
+ 1. **Code review** - all changes undergo peer review with security checks.
26
+ 2. **Dependency management** - regularly check and update dependencies to patch known vulnerabilities.
27
+ 3. **Responsible disclosure** - work with the security community to resolve issues before public disclosure.
28
+
29
+ ## Handling security incidents
30
+ In the event of a security breach or vulnerability:
31
+
32
+ 1. **Triage** - assess and prioritize the issue based on severity.
33
+ 2. **Mitigation** - develop and test a fix.
34
+ 3. **Advisory** - issue a security advisory with mitigation steps and fixed versions.
35
+ 4. **Update users** - notify users via release notes and security mailing lists.
36
+
37
+ ## Contact information
38
+ For any security concerns, contact us at [security@ferronweb.org](mailto:security@ferronweb.org). Stay updated on security patches via [our website](https://www.ferronweb.org).
39
+
40
+ By following this policy, we ensure Ferron remains a secure and trustworthy web server for all users.
ferron-docker.yaml ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ global:
2
+ wwwroot: /var/www/ferron
3
+ logFilePath: /var/log/ferron/access.log
4
+ errorLogFilePath: /var/log/ferron/error.log
ferron-passwd/Cargo.toml ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [package]
2
+ name = "ferron-passwd"
3
+ version = "1.0.0"
4
+ edition = "2021"
5
+
6
+ [package.metadata.winresource]
7
+ ProductName = "Ferron password utility"
8
+
9
+ [dependencies]
10
+ clap = { version = "4.5.28", features = ["derive"] }
11
+ password-auth.workspace = true
12
+ rpassword = "7.4.0"
13
+ yaml-rust2.workspace = true
14
+ mimalloc = { workspace = true }
15
+
16
+ [dev-dependencies]
17
+ rusty-hook = { workspace = true }
18
+
19
+ [build-dependencies]
20
+ winresource = "0.1.19"
ferron-passwd/assets/icon.ico ADDED

Git LFS Details

  • SHA256: ea3ac3c25f884b8c4891412ff035851d2eff097e518c8652fc99fe114205951b
  • Pointer size: 131 Bytes
  • Size of remote file: 119 kB
ferron-passwd/build.rs ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use {
2
+ std::{env, io},
3
+ winresource::WindowsResource,
4
+ };
5
+
6
+ fn main() -> io::Result<()> {
7
+ if env::var_os("CARGO_CFG_WINDOWS").is_some() {
8
+ WindowsResource::new()
9
+ .set_icon("assets/icon.ico")
10
+ .compile()?;
11
+ }
12
+ Ok(())
13
+ }
ferron-passwd/src/main.rs ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use clap::Parser;
2
+ use mimalloc::MiMalloc;
3
+ use password_auth::generate_hash;
4
+ use rpassword::prompt_password;
5
+ use std::process;
6
+ use yaml_rust2::{yaml, Yaml, YamlEmitter};
7
+
8
+ #[global_allocator]
9
+ static GLOBAL: MiMalloc = MiMalloc;
10
+
11
+ /// A password tool for Ferron
12
+ #[derive(Parser, Debug)]
13
+ #[command(version, about, long_about = None)]
14
+ struct Args {
15
+ /// The username, for which you want to generate an user entry
16
+ #[arg()]
17
+ username: String,
18
+ }
19
+
20
+ fn main() {
21
+ let args = Args::parse();
22
+
23
+ let password = match prompt_password("Password: ") {
24
+ Ok(pass) => pass,
25
+ Err(e) => {
26
+ eprintln!("Error reading password: {}", e);
27
+ process::exit(1);
28
+ }
29
+ };
30
+ let password2 = match prompt_password("Confirm password: ") {
31
+ Ok(pass) => pass,
32
+ Err(e) => {
33
+ eprintln!("Error reading password confirmation: {}", e);
34
+ process::exit(1);
35
+ }
36
+ };
37
+
38
+ if password != password2 {
39
+ eprintln!("Passwords don't match!");
40
+ process::exit(1);
41
+ }
42
+
43
+ let password_hash = generate_hash(password);
44
+
45
+ let mut yaml_user_hashmap = yaml::Hash::new();
46
+ yaml_user_hashmap.insert(
47
+ Yaml::String("name".to_string()),
48
+ Yaml::String(args.username),
49
+ );
50
+ yaml_user_hashmap.insert(
51
+ Yaml::String("pass".to_string()),
52
+ Yaml::String(password_hash),
53
+ );
54
+
55
+ let yaml_data = Yaml::Array(vec![Yaml::Hash(yaml_user_hashmap)]);
56
+
57
+ let mut output = String::new();
58
+ if let Err(e) = YamlEmitter::new(&mut output).dump(&yaml_data) {
59
+ eprintln!("Error generating YAML output: {}", e);
60
+ process::exit(1);
61
+ }
62
+
63
+ println!("Copy the user object below into \"users\" property of either global configuration or a virtual host in the \"ferron.yaml\" file. Remember about the indentation in the server configuration.");
64
+ println!("{}", output);
65
+ }
ferron-release.yaml ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ global:
2
+ wwwroot: wwwroot
ferron.yaml ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Global server configuration
2
+ global:
3
+ port: 8080
4
+ logFilePath: access.log
5
+ errorLogFilePath: error.log
6
+ loadModules:
7
+ - cgi
8
+ wwwroot: wwwroot # Replace "wwwroot" with desired webroot
9
+ enableDirectoryListing: true
10
+ cgiScriptExtensions:
11
+ - .php # Necessary to be able to execute PHP scripts via PHP-CGI
ferron/Cargo.toml ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [package]
2
+ name = "ferron"
3
+ version = "1.0.0"
4
+ edition = "2021"
5
+
6
+ [package.metadata.winresource]
7
+ ProductName = "Ferron"
8
+
9
+ [dependencies]
10
+ hyper = { version = "1.6.0", features = ["full"] }
11
+ tokio = { version = "1.44.2", features = ["full"] }
12
+ http-body-util = "0.1.0"
13
+ hyper-util = { version = "0.1", features = ["full"] }
14
+ tokio-util = { version = "0.7.13", features = ["io"] }
15
+ rustls = { version = "0.23.24", default-features = false, features = ["tls12", "std", "ring"] }
16
+ rustls-acme = { version = "0.13.0", default-features = false, features = ["tls12", "ring"] }
17
+ tokio-rustls = { version = "0.26.1", default-features = false, features = ["tls12", "ring"] }
18
+ rustls-pki-types = "1.11.0"
19
+ rustls-pemfile = "2.2.0"
20
+ yaml-rust2 = { workspace = true }
21
+ anyhow = "1.0.98"
22
+ futures-util = "0.3.31"
23
+ chrono = "0.4.39"
24
+ async-trait = "0.1.86"
25
+ rustls-native-certs = "0.8.1"
26
+ ocsp-stapler = { version = "0.4.4", default-features = false }
27
+ clap = { version = "4.5.28", features = ["derive"] }
28
+ fancy-regex = "0.14.0"
29
+ password-auth = { workspace = true }
30
+ base64 = "0.22.1"
31
+ sha2 = "0.10.8"
32
+ new_mime_guess = "4.0.4"
33
+ async-compression = { version = "0.4.18", features = ["tokio", "gzip", "brotli", "deflate", "zstd"] }
34
+ urlencoding = "2.1.3"
35
+ async-channel = "2.3.1"
36
+ mimalloc = { workspace = true }
37
+ cache_control = { git = "https://github.com/DorianNiemiecSVRJS/rust-cache-control.git", optional = true } # Temporarily replaced with a fork
38
+ itertools = { version = "0.14.0", optional = true }
39
+ rand = "0.9.0"
40
+ memmem = { version = "0.1.1", optional = true }
41
+ httparse = { version = "1.10.0", optional = true }
42
+ pin-project-lite = "0.2.16"
43
+ hashlink = "0.10.0"
44
+ glob = "0.3.2"
45
+ hyper-tungstenite = "0.17.0"
46
+ tokio-tungstenite = { version = "0.26.2", features = ["rustls-tls-native-roots"] }
47
+ http = "1.2.0"
48
+ pyo3 = { version = "0.24.1", optional = true, features = ["anyhow", "auto-initialize"] }
49
+ futures-lite = "2.6.0"
50
+ nix = { version = "0.30.0", optional = true, features = ["process", "signal"] }
51
+ interprocess = { version = "2.2.3", features = ["tokio"], optional = true }
52
+ serde = { version = "1.0.219", optional = true, features = ["derive"] }
53
+ serde_bytes = { version = "0.11.17", optional = true }
54
+ postcard = { version = "1.1.1", optional = true, default-features = false, features = ["use-std"] }
55
+ bytes = { version = "1.10.1", optional = true }
56
+ pyo3-async-runtimes = { version = "0.24.0", optional = true, features = ["tokio", "tokio-runtime"] }
57
+ h3 = "0.0.7"
58
+ h3-quinn = "0.0.9"
59
+ quinn = "0.11.7"
60
+
61
+ [dev-dependencies]
62
+ tokio-test = "0.4.4"
63
+ rusty-hook = { workspace = true }
64
+
65
+ [features]
66
+ default = ["cache", "cgi", "fauth", "fcgi", "fproxy", "rproxy", "scgi"]
67
+ asgi = ["pyo3", "pyo3-async-runtimes"]
68
+ cache = ["cache_control", "itertools"]
69
+ cgi = ["httparse", "memmem"]
70
+ example = []
71
+ fauth = []
72
+ fcgi = ["httparse", "memmem"]
73
+ fproxy = []
74
+ rproxy = []
75
+ scgi = ["httparse", "memmem"]
76
+ wsgi = ["pyo3"]
77
+ wsgid = ["pyo3", "nix", "interprocess", "itertools", "serde", "serde_bytes", "postcard", "hashlink/serde", "hashlink/serde_impl", "bytes"]
78
+
79
+ [build-dependencies]
80
+ winresource = "0.1.19"
ferron/assets/icon.ico ADDED

Git LFS Details

  • SHA256: d013886361c7a885c9ab6d82455b316e68515030d3fe8dee7d970eb0950e4a0a
  • Pointer size: 131 Bytes
  • Size of remote file: 113 kB
ferron/build.rs ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use {
2
+ std::{env, io},
3
+ winresource::WindowsResource,
4
+ };
5
+
6
+ fn main() -> io::Result<()> {
7
+ if env::var_os("CARGO_CFG_WINDOWS").is_some() {
8
+ WindowsResource::new()
9
+ .set_icon("assets/icon.ico")
10
+ .compile()?;
11
+ }
12
+ Ok(())
13
+ }
ferron/src/common/log.rs ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /// Represents a log message with its content and error status.
2
+ pub struct LogMessage {
3
+ is_error: bool,
4
+ message: String,
5
+ }
6
+
7
+ impl LogMessage {
8
+ /// Creates a new `LogMessage` instance.
9
+ ///
10
+ /// # Parameters
11
+ ///
12
+ /// - `message`: The content of the log message.
13
+ /// - `is_error`: A boolean indicating whether the message is an error (`true`) or not (`false`).
14
+ ///
15
+ /// # Returns
16
+ ///
17
+ /// A `LogMessage` object containing the specified message and error status.
18
+ pub fn new(message: String, is_error: bool) -> Self {
19
+ Self { is_error, message }
20
+ }
21
+
22
+ /// Consumes the `LogMessage` and returns its components.
23
+ ///
24
+ /// # Returns
25
+ ///
26
+ /// A tuple containing:
27
+ /// - `String`: The content of the log message.
28
+ /// - `bool`: A boolean indicating whether the message is an error.
29
+ pub fn get_message(self) -> (String, bool) {
30
+ (self.message, self.is_error)
31
+ }
32
+ }
ferron/src/common/mod.rs ADDED
@@ -0,0 +1,593 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #![allow(dead_code)]
2
+
3
+ use std::{error::Error, future::Future, net::SocketAddr, pin::Pin};
4
+
5
+ use async_channel::Sender;
6
+ use async_trait::async_trait;
7
+ use http_body_util::combinators::BoxBody;
8
+ use hyper::{body::Bytes, upgrade::Upgraded, HeaderMap, Request, Response, StatusCode, Uri};
9
+ use hyper_tungstenite::HyperWebsocket;
10
+ use tokio::runtime::Handle;
11
+ use yaml_rust2::Yaml;
12
+
13
+ #[path = "log.rs"]
14
+ mod log;
15
+ #[path = "with_runtime.rs"]
16
+ mod with_runtime;
17
+
18
+ /// Contains information about a network socket, including remote and local addresses,
19
+ /// and whether the connection is encrypted.
20
+ pub struct SocketData {
21
+ /// The remote address of the socket.
22
+ pub remote_addr: SocketAddr,
23
+ /// The local address of the socket.
24
+ pub local_addr: SocketAddr,
25
+ /// Indicates if the connection is encrypted.
26
+ pub encrypted: bool,
27
+ }
28
+
29
+ impl SocketData {
30
+ /// Creates a new `SocketData` instance.
31
+ ///
32
+ /// # Parameters
33
+ ///
34
+ /// - `remote_addr`: The remote address of the socket.
35
+ /// - `local_addr`: The local address of the socket.
36
+ /// - `encrypted`: A boolean indicating if the connection is encrypted.
37
+ ///
38
+ /// # Returns
39
+ ///
40
+ /// A new `SocketData` instance with the provided parameters.
41
+ pub fn new(remote_addr: SocketAddr, local_addr: SocketAddr, encrypted: bool) -> Self {
42
+ Self {
43
+ remote_addr,
44
+ local_addr,
45
+ encrypted,
46
+ }
47
+ }
48
+ }
49
+
50
+ /// Represents a log message. This is a type alias for `crate::log::LogMessage`.
51
+ pub type LogMessage = log::LogMessage;
52
+
53
+ /// Represents the server configuration object. This is a type alias for `Yaml` from the `yaml_rust2` crate.
54
+ pub type ServerConfig = Yaml;
55
+
56
+ /// Represents the HTTP request from Hyper.
57
+ pub type HyperRequest = Request<BoxBody<Bytes, std::io::Error>>;
58
+
59
+ /// Represents the HTTP response from Hyper.
60
+ pub type HyperResponse = Response<BoxBody<Bytes, std::io::Error>>;
61
+
62
+ /// Represents the upgraded HTTP connection from Hyper.
63
+ pub type HyperUpgraded = Upgraded;
64
+
65
+ /// A wrapper that ensures a function is executed within a specific runtime context.
66
+ /// This is a type alias for `crate::with_runtime::WithRuntime<F>`.
67
+ pub type WithRuntime<F> = with_runtime::WithRuntime<F>;
68
+
69
+ /// Contains data related to an HTTP request, including the original Hyper request
70
+ /// and optional authentication user information.
71
+ pub struct RequestData {
72
+ hyper_request: HyperRequest,
73
+ auth_user: Option<String>,
74
+ original_url: Option<Uri>,
75
+ }
76
+
77
+ impl RequestData {
78
+ /// Creates a new `RequestData` instance.
79
+ ///
80
+ /// # Parameters
81
+ ///
82
+ /// - `hyper_request`: The original Hyper `Request` object.
83
+ /// - `auth_user`: An optional string representing the authenticated user.
84
+ ///
85
+ /// # Returns
86
+ ///
87
+ /// A new `RequestData` instance with the provided parameters.
88
+ pub fn new(
89
+ hyper_request: HyperRequest,
90
+ auth_user: Option<String>,
91
+ original_url: Option<Uri>,
92
+ ) -> Self {
93
+ Self {
94
+ hyper_request,
95
+ auth_user,
96
+ original_url,
97
+ }
98
+ }
99
+
100
+ /// Sets the authenticated user for the request.
101
+ ///
102
+ /// # Parameters
103
+ ///
104
+ /// - `auth_user`: A string representing the authenticated user.
105
+ pub fn set_auth_user(&mut self, auth_user: String) {
106
+ self.auth_user = Some(auth_user);
107
+ }
108
+
109
+ /// Retrieves the authenticated user associated with the request, if any.
110
+ ///
111
+ /// # Returns
112
+ ///
113
+ /// An `Option` containing a reference to the authenticated user's string, or `None` if not set.
114
+ pub fn get_auth_user(&self) -> Option<&str> {
115
+ match &self.auth_user {
116
+ Some(auth_user) => Some(auth_user),
117
+ None => None,
118
+ }
119
+ }
120
+
121
+ /// Sets the original URL (before URL rewriting) for the request.
122
+ ///
123
+ /// # Parameters
124
+ ///
125
+ /// - `original_url`: An `Uri` object representing the original request URL before rewriting.
126
+ pub fn set_original_url(&mut self, original_url: Uri) {
127
+ self.original_url = Some(original_url);
128
+ }
129
+
130
+ /// Retrieves the original URL (before URL rewriting) associated with the request, if any.
131
+ ///
132
+ /// # Returns
133
+ ///
134
+ /// An `Option` containing a reference to the `Uri` object representing the original request URL before rewriting, or `None` if not set.
135
+ pub fn get_original_url(&self) -> Option<&Uri> {
136
+ match &self.original_url {
137
+ Some(original_url) => Some(original_url),
138
+ None => None,
139
+ }
140
+ }
141
+
142
+ /// Provides a reference to the underlying Hyper `Request` object.
143
+ ///
144
+ /// # Returns
145
+ ///
146
+ /// A reference to the `HyperRequest` object.
147
+ pub fn get_hyper_request(&self) -> &HyperRequest {
148
+ &self.hyper_request
149
+ }
150
+
151
+ /// Provides a mutable reference to the underlying Hyper `Request` object.
152
+ ///
153
+ /// # Returns
154
+ ///
155
+ /// A mutable reference to the `HyperRequest` object.
156
+ pub fn get_mut_hyper_request(&mut self) -> &mut HyperRequest {
157
+ &mut self.hyper_request
158
+ }
159
+
160
+ /// Consumes the `RequestData` instance and returns its components.
161
+ ///
162
+ /// # Returns
163
+ ///
164
+ /// A tuple containing the `HyperRequest` object, an optional authenticated user string, and an optional `Uri` object representing the original request URL before rewriting.
165
+ pub fn into_parts(self) -> (HyperRequest, Option<String>, Option<Uri>) {
166
+ (self.hyper_request, self.auth_user, self.original_url)
167
+ }
168
+ }
169
+
170
+ /// Facilitates logging of error messages through a provided logger sender.
171
+ pub struct ErrorLogger {
172
+ logger: Option<Sender<LogMessage>>,
173
+ }
174
+
175
+ impl ErrorLogger {
176
+ /// Creates a new `ErrorLogger` instance.
177
+ ///
178
+ /// # Parameters
179
+ ///
180
+ /// - `logger`: A `Sender<LogMessage>` used for sending log messages.
181
+ ///
182
+ /// # Returns
183
+ ///
184
+ /// A new `ErrorLogger` instance associated with the provided logger.
185
+ pub fn new(logger: Sender<LogMessage>) -> Self {
186
+ Self {
187
+ logger: Some(logger),
188
+ }
189
+ }
190
+
191
+ /// Creates a new `ErrorLogger` instance without any underlying logger.
192
+ ///
193
+ /// # Returns
194
+ ///
195
+ /// A new `ErrorLogger` instance not associated with any logger.
196
+ pub fn without_logger() -> Self {
197
+ Self { logger: None }
198
+ }
199
+
200
+ /// Logs an error message asynchronously.
201
+ ///
202
+ /// # Parameters
203
+ ///
204
+ /// - `message`: A string slice containing the error message to be logged.
205
+ ///
206
+ /// # Examples
207
+ ///
208
+ /// ```
209
+ /// # use crate::ferron_common::ErrorLogger;
210
+ /// # #[tokio::main]
211
+ /// # async fn main() {
212
+ /// let (tx, mut rx) = async_channel::bounded(100);
213
+ /// let logger = ErrorLogger::new(tx);
214
+ /// logger.log("An error occurred").await;
215
+ /// # }
216
+ /// ```
217
+ pub async fn log(&self, message: &str) {
218
+ if let Some(logger) = &self.logger {
219
+ logger
220
+ .send(LogMessage::new(String::from(message), true))
221
+ .await
222
+ .unwrap_or_default();
223
+ }
224
+ }
225
+ }
226
+
227
+ impl Clone for ErrorLogger {
228
+ /// Clone a `ErrorLogger`.
229
+ ///
230
+ /// # Returns
231
+ ///
232
+ /// A cloned `ErrorLogger` instance
233
+ fn clone(&self) -> Self {
234
+ Self {
235
+ logger: self.logger.clone(),
236
+ }
237
+ }
238
+ }
239
+
240
+ /// Holds data related to an HTTP response, including the original request,
241
+ /// optional authentication user information, and the response details.
242
+ pub struct ResponseData {
243
+ request: Option<HyperRequest>,
244
+ auth_user: Option<String>,
245
+ original_url: Option<Uri>,
246
+ response: Option<Response<BoxBody<Bytes, std::io::Error>>>,
247
+ response_status: Option<StatusCode>,
248
+ response_headers: Option<HeaderMap>,
249
+ new_remote_address: Option<SocketAddr>,
250
+ parallel_fn: Option<Pin<Box<dyn Future<Output = ()> + Send>>>,
251
+ }
252
+
253
+ impl ResponseData {
254
+ /// Initiates the building process for a `ResponseData` instance using a `RequestData` object.
255
+ ///
256
+ /// # Parameters
257
+ ///
258
+ /// - `request`: A `RequestData` instance containing the original request and authentication information.
259
+ ///
260
+ /// # Returns
261
+ ///
262
+ /// A `ResponseDataBuilder` initialized with the provided request data.
263
+ pub fn builder(request: RequestData) -> ResponseDataBuilder {
264
+ let (request, auth_user, original_url) = request.into_parts();
265
+
266
+ ResponseDataBuilder {
267
+ request: Some(request),
268
+ auth_user,
269
+ original_url,
270
+ response: None,
271
+ response_status: None,
272
+ response_headers: None,
273
+ new_remote_address: None,
274
+ parallel_fn: None,
275
+ }
276
+ }
277
+
278
+ /// Initiates the building process for a `ResponseData` instance without a `RequestData` object.
279
+ ///
280
+ /// # Returns
281
+ ///
282
+ /// A `ResponseDataBuilder` initialized without any request data.
283
+ pub fn builder_without_request() -> ResponseDataBuilder {
284
+ ResponseDataBuilder {
285
+ request: None,
286
+ auth_user: None,
287
+ original_url: None,
288
+ response: None,
289
+ response_status: None,
290
+ response_headers: None,
291
+ new_remote_address: None,
292
+ parallel_fn: None,
293
+ }
294
+ }
295
+
296
+ /// Consumes the `ResponseData` instance and returns its components.
297
+ ///
298
+ /// # Returns
299
+ ///
300
+ /// A tuple containing:
301
+ /// - The optional original `HyperRequest` object.
302
+ /// - An optional authenticated user string.
303
+ /// - An optional `Uri` object representing the original request URL (before rewriting)
304
+ /// - An optional `Response` object encapsulated in a `BoxBody` with `Bytes` and `std::io::Error`.
305
+ /// - An optional HTTP `StatusCode`.
306
+ /// - An optional `HeaderMap` containing the HTTP headers.
307
+ /// - An optional `SocketAddr` containing the client's new IP address and port.
308
+ /// - An optional `Future` with `()` output that would be executed in parallel.
309
+ #[allow(clippy::type_complexity)]
310
+ pub fn into_parts(
311
+ self,
312
+ ) -> (
313
+ Option<HyperRequest>,
314
+ Option<String>,
315
+ Option<Uri>,
316
+ Option<Response<BoxBody<Bytes, std::io::Error>>>,
317
+ Option<StatusCode>,
318
+ Option<HeaderMap>,
319
+ Option<SocketAddr>,
320
+ Option<Pin<Box<dyn Future<Output = ()> + Send>>>,
321
+ ) {
322
+ (
323
+ self.request,
324
+ self.auth_user,
325
+ self.original_url,
326
+ self.response,
327
+ self.response_status,
328
+ self.response_headers,
329
+ self.new_remote_address,
330
+ self.parallel_fn,
331
+ )
332
+ }
333
+ }
334
+
335
+ pub struct ResponseDataBuilder {
336
+ request: Option<HyperRequest>,
337
+ auth_user: Option<String>,
338
+ original_url: Option<Uri>,
339
+ response: Option<Response<BoxBody<Bytes, std::io::Error>>>,
340
+ response_status: Option<StatusCode>,
341
+ response_headers: Option<HeaderMap>,
342
+ new_remote_address: Option<SocketAddr>,
343
+ parallel_fn: Option<Pin<Box<dyn Future<Output = ()> + Send>>>,
344
+ }
345
+
346
+ impl ResponseDataBuilder {
347
+ /// Sets the response for the `ResponseData`.
348
+ ///
349
+ /// # Parameters
350
+ ///
351
+ /// - `response`: A `Response` object encapsulated in a `BoxBody` with `Bytes` and `std::io::Error`.
352
+ ///
353
+ /// # Returns
354
+ ///
355
+ /// The updated `ResponseDataBuilder` instance with the specified response.
356
+ pub fn response(mut self, response: Response<BoxBody<Bytes, std::io::Error>>) -> Self {
357
+ self.response = Some(response);
358
+ self
359
+ }
360
+
361
+ /// Sets the status code for the `ResponseData`.
362
+ ///
363
+ /// # Parameters
364
+ ///
365
+ /// - `status`: A `StatusCode` representing the HTTP status code.
366
+ ///
367
+ /// # Returns
368
+ ///
369
+ /// The updated `ResponseDataBuilder` instance with the specified status code.
370
+ pub fn status(mut self, status: StatusCode) -> Self {
371
+ self.response_status = Some(status);
372
+ self
373
+ }
374
+
375
+ /// Sets the headers for the `ResponseData`.
376
+ ///
377
+ /// # Parameters
378
+ ///
379
+ /// - `headers`: A `HeaderMap` containing the HTTP headers.
380
+ ///
381
+ /// # Returns
382
+ ///
383
+ /// The updated `ResponseDataBuilder` instance with the specified headers.
384
+ pub fn headers(mut self, headers: HeaderMap) -> Self {
385
+ self.response_headers = Some(headers);
386
+ self
387
+ }
388
+
389
+ /// Sets the new client address for the `ResponseData`.
390
+ ///
391
+ /// # Parameters
392
+ ///
393
+ /// - `new_remote_address`: A `SocketAddr` containing the new client's IP address and port.
394
+ ///
395
+ /// # Returns
396
+ ///
397
+ /// The updated `ResponseDataBuilder` instance with the specified headers.
398
+ pub fn new_remote_address(mut self, new_remote_address: SocketAddr) -> Self {
399
+ self.new_remote_address = Some(new_remote_address);
400
+ self
401
+ }
402
+
403
+ /// Sets the function to be executed in parallel.
404
+ ///
405
+ /// # Parameters
406
+ ///
407
+ /// - `parallel_fn`: A `Future` with `()` output.
408
+ ///
409
+ /// # Returns
410
+ ///
411
+ /// The updated `ResponseDataBuilder` instance with the specified function to be executed in parallel.
412
+ pub fn parallel_fn(mut self, parallel_fn: impl Future<Output = ()> + Send + 'static) -> Self {
413
+ self.parallel_fn = Some(Box::pin(parallel_fn));
414
+ self
415
+ }
416
+
417
+ /// Builds the `ResponseData` instance.
418
+ ///
419
+ /// # Returns
420
+ ///
421
+ /// A `ResponseData` object containing the accumulated data from the builder.
422
+ pub fn build(self) -> ResponseData {
423
+ ResponseData {
424
+ request: self.request,
425
+ auth_user: self.auth_user,
426
+ original_url: self.original_url,
427
+ response: self.response,
428
+ response_status: self.response_status,
429
+ response_headers: self.response_headers,
430
+ new_remote_address: self.new_remote_address,
431
+ parallel_fn: self.parallel_fn,
432
+ }
433
+ }
434
+ }
435
+
436
+ /// Defines the interface for server module handlers, specifying how requests should be processed.
437
+ #[async_trait]
438
+ pub trait ServerModuleHandlers {
439
+ /// Handles an incoming request.
440
+ ///
441
+ /// # Parameters
442
+ ///
443
+ /// - `request`: A `RequestData` object containing the incoming request and associated data.
444
+ /// - `config`: A reference to the combined server configuration (`ServerConfig`). The combined configuration has properties in its root.
445
+ /// - `socket_data`: A reference to the `SocketData` containing socket-related information.
446
+ /// - `error_logger`: A reference to an `ErrorLogger` for logging errors.
447
+ ///
448
+ /// # Returns
449
+ ///
450
+ /// A `Result` containing a `ResponseData` object upon success, or a boxed `dyn Error` if an error occurs.
451
+ async fn request_handler(
452
+ &mut self,
453
+ request: RequestData,
454
+ config: &ServerConfig,
455
+ socket_data: &SocketData,
456
+ error_logger: &ErrorLogger,
457
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>>;
458
+
459
+ /// Handles an incoming forward proxy request (not using CONNECT method).
460
+ ///
461
+ /// # Parameters
462
+ ///
463
+ /// - `request`: A `RequestData` object containing the incoming request and associated data.
464
+ /// - `config`: A reference to the combined server configuration (`ServerConfig`). The combined configuration has properties in its root.
465
+ /// - `socket_data`: A reference to the `SocketData` containing socket-related information.
466
+ /// - `error_logger`: A reference to an `ErrorLogger` for logging errors.
467
+ ///
468
+ /// # Returns
469
+ ///
470
+ /// A `Result` containing a `ResponseData` object upon success, or a boxed `dyn Error` if an error occurs.
471
+ async fn proxy_request_handler(
472
+ &mut self,
473
+ request: RequestData,
474
+ config: &ServerConfig,
475
+ socket_data: &SocketData,
476
+ error_logger: &ErrorLogger,
477
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>>;
478
+
479
+ /// Modifies an outgoing response before it is sent to the client.
480
+ ///
481
+ /// This function allows for inspection and modification of the response generated by the server
482
+ /// or other handlers. Implementers can use this to add, remove, or alter headers, change the
483
+ /// status code, or modify the body of the response as needed.
484
+ ///
485
+ /// # Parameters
486
+ ///
487
+ /// - `response`: A `HyperResponse` object representing the outgoing HTTP response.
488
+ ///
489
+ /// # Returns
490
+ ///
491
+ /// A `Result` containing the potentially modified `HyperResponse` object upon success, or a boxed
492
+ /// `dyn Error` if an error occurs during processing.
493
+ async fn response_modifying_handler(
494
+ &mut self,
495
+ response: HyperResponse,
496
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>>;
497
+
498
+ /// Modifies an outgoing response for forward proxy requests (not using CONNECT method) before it is sent to the client.
499
+ ///
500
+ /// This function allows for inspection and modification of the response generated by the server
501
+ /// or other handlers. Implementers can use this to add, remove, or alter headers, change the
502
+ /// status code, or modify the body of the response as needed.
503
+ ///
504
+ /// # Parameters
505
+ ///
506
+ /// - `response`: A `HyperResponse` object representing the outgoing HTTP response.
507
+ ///
508
+ /// # Returns
509
+ ///
510
+ /// A `Result` containing the potentially modified `HyperResponse` object upon success, or a boxed
511
+ /// `dyn Error` if an error occurs during processing.
512
+ async fn proxy_response_modifying_handler(
513
+ &mut self,
514
+ response: HyperResponse,
515
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>>;
516
+
517
+ /// Handles an incoming forward proxy request (using CONNECT method).
518
+ ///
519
+ /// # Parameters
520
+ ///
521
+ /// - `upgraded_request`: A `HyperUpgraded` object containing the upgraded HTTP connection.
522
+ /// - `connect_address`: A reference to a string containing the address and port number of the destination server (for example "example.com:443").
523
+ /// - `config`: A reference to the combined server configuration (`ServerConfig`). The combined configuration has properties in its root.
524
+ /// - `socket_data`: A reference to the `SocketData` containing socket-related information.
525
+ /// - `error_logger`: A reference to an `ErrorLogger` for logging errors.
526
+ ///
527
+ /// # Returns
528
+ ///
529
+ /// A `Result` containing an empty value upon success, or a boxed `dyn Error` if an error occurs.
530
+ async fn connect_proxy_request_handler(
531
+ &mut self,
532
+ upgraded_request: HyperUpgraded,
533
+ connect_address: &str,
534
+ config: &ServerConfig,
535
+ socket_data: &SocketData,
536
+ error_logger: &ErrorLogger,
537
+ ) -> Result<(), Box<dyn Error + Send + Sync>>;
538
+
539
+ /// Checks if the module is a forward proxy module utilizing CONNECT method.
540
+ ///
541
+ /// # Returns
542
+ ///
543
+ /// `true` if the module is a forward proxy module utlilzing CONNECT method, or `false` otherwise.
544
+ fn does_connect_proxy_requests(&mut self) -> bool;
545
+
546
+ /// Handles an incoming WebSocket request.
547
+ ///
548
+ /// # Parameters
549
+ ///
550
+ /// - `websocket`: A `HyperWebsocket` object containing a future that resolves to a WebSocket stream.
551
+ /// - `uri`: A `hyper::Uri` object containig the HTTP request URI.
552
+ /// - `config`: A reference to the combined server configuration (`ServerConfig`). The combined configuration has properties in its root.
553
+ /// - `socket_data`: A reference to the `SocketData` containing socket-related information.
554
+ /// - `error_logger`: A reference to an `ErrorLogger` for logging errors.
555
+ ///
556
+ /// # Returns
557
+ ///
558
+ /// A `Result` containing an empty value upon success, or a boxed `dyn Error` if an error occurs.
559
+ async fn websocket_request_handler(
560
+ &mut self,
561
+ websocket: HyperWebsocket,
562
+ uri: &hyper::Uri,
563
+ config: &ServerConfig,
564
+ socket_data: &SocketData,
565
+ error_logger: &ErrorLogger,
566
+ ) -> Result<(), Box<dyn Error + Send + Sync>>;
567
+
568
+ /// Checks if the module is a module supporting WebSocket requests.
569
+ ///
570
+ /// # Parameters
571
+ ///
572
+ /// - `config`: A reference to the combined server configuration (`ServerConfig`). The combined configuration has properties in its root.
573
+ /// - `socket_data`: A reference to the `SocketData` containing socket-related information.
574
+ ///
575
+ /// # Returns
576
+ ///
577
+ /// `true` if the module is a module supporting WebSocket requests, or `false` otherwise.
578
+ fn does_websocket_requests(&mut self, config: &ServerConfig, socket_data: &SocketData) -> bool;
579
+ }
580
+
581
+ /// Represents a server module that can provide handlers for processing requests.
582
+ pub trait ServerModule {
583
+ /// Retrieves the handlers associated with the server module.
584
+ ///
585
+ /// # Parameters
586
+ ///
587
+ /// - `handle`: A `Handle` to the Tokio runtime.
588
+ ///
589
+ /// # Returns
590
+ ///
591
+ /// A boxed object implementing `ServerModuleHandlers` that can be sent across threads.
592
+ fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send>;
593
+ }
ferron/src/common/with_runtime.rs ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use pin_project_lite::pin_project;
2
+ use std::{
3
+ future::Future,
4
+ pin::Pin,
5
+ task::{Context, Poll},
6
+ };
7
+ use tokio::runtime::Handle;
8
+
9
+ pin_project! {
10
+ /// A future that executes within a specific Tokio runtime.
11
+ ///
12
+ /// This struct ensures that the wrapped future (`fut`) is polled within the context of the provided Tokio runtime handle (`runtime`).
13
+ pub struct WithRuntime<F> {
14
+ runtime: Handle,
15
+ #[pin]
16
+ fut: F,
17
+ }
18
+ }
19
+
20
+ impl<F> WithRuntime<F> {
21
+ /// Creates a new `WithRuntime` instance.
22
+ ///
23
+ /// # Parameters
24
+ ///
25
+ /// - `runtime`: A `Handle` to the Tokio runtime in which the future should be executed.
26
+ /// - `fut`: The future to be executed within the specified runtime.
27
+ ///
28
+ /// # Returns
29
+ ///
30
+ /// A `WithRuntime` object encapsulating the provided runtime handle and future.
31
+ pub fn new(runtime: Handle, fut: F) -> Self {
32
+ Self { runtime, fut }
33
+ }
34
+ }
35
+
36
+ impl<F> Future for WithRuntime<F>
37
+ where
38
+ F: Future,
39
+ {
40
+ type Output = F::Output;
41
+
42
+ /// Polls the wrapped future within the context of the specified Tokio runtime.
43
+ ///
44
+ /// # Parameters
45
+ ///
46
+ /// - `ctx`: The current task context.
47
+ ///
48
+ /// # Returns
49
+ ///
50
+ /// A `Poll` indicating the state of the wrapped future (`Pending` or `Ready`).
51
+ fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> {
52
+ let this = self.project();
53
+ let _guard = this.runtime.enter();
54
+ this.fut.poll(ctx)
55
+ }
56
+ }
ferron/src/main.rs ADDED
@@ -0,0 +1,478 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Import server module from "server.rs"
2
+ #[path = "server.rs"]
3
+ mod ferron_server;
4
+
5
+ // Import request handler module from "request_handler.rs"
6
+ #[path = "request_handler.rs"]
7
+ mod ferron_request_handler;
8
+
9
+ // Import resources from "res" directory
10
+ #[path = "res"]
11
+ mod ferron_res {
12
+ pub mod server_software;
13
+ }
14
+
15
+ // Import common modules from "common" directory
16
+ #[path = "common/mod.rs"]
17
+ mod ferron_common;
18
+
19
+ // Import utility modules from "util" directory
20
+ #[path = "util"]
21
+ mod ferron_util {
22
+ pub mod anti_xss;
23
+ #[cfg(feature = "asgi")]
24
+ pub mod asgi_messages;
25
+ #[cfg(feature = "asgi")]
26
+ pub mod asgi_structs;
27
+ #[cfg(any(feature = "cgi", feature = "scgi", feature = "fcgi"))]
28
+ pub mod cgi_response;
29
+ pub mod combine_config;
30
+ #[cfg(any(feature = "cgi", feature = "scgi", feature = "fcgi"))]
31
+ pub mod copy_move;
32
+ pub mod error_pages;
33
+ #[cfg(feature = "fcgi")]
34
+ pub mod fcgi_decoder;
35
+ #[cfg(feature = "fcgi")]
36
+ pub mod fcgi_encoder;
37
+ #[cfg(feature = "fcgi")]
38
+ pub mod fcgi_name_value_pair;
39
+ #[cfg(feature = "fcgi")]
40
+ pub mod fcgi_record;
41
+ pub mod generate_directory_listing;
42
+ pub mod ip_blocklist;
43
+ pub mod ip_match;
44
+ pub mod load_config;
45
+ pub mod load_tls;
46
+ pub mod match_hostname;
47
+ pub mod match_location;
48
+ #[cfg(any(feature = "rproxy", feature = "fauth"))]
49
+ pub mod no_server_verifier;
50
+ pub mod non_standard_code_structs;
51
+ #[cfg(all(unix, feature = "wsgid"))]
52
+ pub mod preforked_process_pool;
53
+ #[cfg(feature = "fcgi")]
54
+ pub mod read_to_end_move;
55
+ pub mod sizify;
56
+ pub mod sni;
57
+ #[cfg(feature = "fcgi")]
58
+ pub mod split_stream_by_map;
59
+ pub mod ttl_cache;
60
+ pub mod url_rewrite_structs;
61
+ pub mod url_sanitizer;
62
+ pub mod validate_config;
63
+ #[cfg(feature = "wsgi")]
64
+ pub mod wsgi_error_stream;
65
+ #[cfg(feature = "wsgi")]
66
+ pub mod wsgi_input_stream;
67
+ #[cfg(any(feature = "wsgi", feature = "wsgid"))]
68
+ pub mod wsgi_load_application;
69
+ #[cfg(feature = "wsgi")]
70
+ pub mod wsgi_structs;
71
+ #[cfg(feature = "wsgid")]
72
+ pub mod wsgid_body_reader;
73
+ #[cfg(feature = "wsgid")]
74
+ pub mod wsgid_error_stream;
75
+ #[cfg(feature = "wsgid")]
76
+ pub mod wsgid_input_stream;
77
+ #[cfg(feature = "wsgid")]
78
+ pub mod wsgid_message_structs;
79
+ #[cfg(feature = "wsgid")]
80
+ pub mod wsgid_structs;
81
+ }
82
+
83
+ // Import project modules from "modules" directory
84
+ #[path = "modules"]
85
+ mod ferron_modules {
86
+ pub mod blocklist;
87
+ pub mod default_handler_checks;
88
+ pub mod non_standard_codes;
89
+ pub mod redirect_trailing_slashes;
90
+ pub mod redirects;
91
+ pub mod static_file_serving;
92
+ pub mod url_rewrite;
93
+ pub mod x_forwarded_for;
94
+ }
95
+
96
+ // Import optional project modules from "modules" directory
97
+ #[path = "optional_modules"]
98
+ mod ferron_optional_modules {
99
+ #[cfg(feature = "asgi")]
100
+ pub mod asgi;
101
+ #[cfg(feature = "cache")]
102
+ pub mod cache;
103
+ #[cfg(feature = "cgi")]
104
+ pub mod cgi;
105
+ #[cfg(feature = "example")]
106
+ pub mod example;
107
+ #[cfg(feature = "fauth")]
108
+ pub mod fauth;
109
+ #[cfg(feature = "fcgi")]
110
+ pub mod fcgi;
111
+ #[cfg(feature = "fproxy")]
112
+ pub mod fproxy;
113
+ #[cfg(feature = "rproxy")]
114
+ pub mod rproxy;
115
+ #[cfg(feature = "scgi")]
116
+ pub mod scgi;
117
+ #[cfg(feature = "wsgi")]
118
+ pub mod wsgi;
119
+ #[cfg(feature = "wsgid")]
120
+ pub mod wsgid;
121
+ }
122
+
123
+ // Standard library imports
124
+ use std::sync::Arc;
125
+ use std::{error::Error, path::PathBuf};
126
+
127
+ // External crate imports
128
+ use clap::Parser;
129
+ use ferron_server::start_server;
130
+ use ferron_util::load_config::load_config;
131
+ use mimalloc::MiMalloc;
132
+
133
+ // Set the global allocator to use mimalloc for performance optimization
134
+ #[global_allocator]
135
+ static GLOBAL: MiMalloc = MiMalloc;
136
+
137
+ // Struct for command-line arguments
138
+ /// A fast, memory-safe web server written in Rust
139
+ #[derive(Parser, Debug)]
140
+ #[command(name = "Ferron")]
141
+ #[command(version, about, long_about = None)]
142
+ struct Args {
143
+ /// The path to the server configuration file
144
+ #[arg(short, long, default_value_t = String::from("./ferron.yaml"))]
145
+ config: String,
146
+ }
147
+
148
+ // Function to execute before starting the server
149
+ #[allow(clippy::type_complexity)]
150
+ fn before_starting_server(
151
+ args: &Args,
152
+ first_start: bool,
153
+ ) -> Result<bool, Box<dyn Error + Send + Sync>> {
154
+ // Load the configuration
155
+ let yaml_config = load_config(PathBuf::from(args.config.clone()))?;
156
+
157
+ let mut module_error = None;
158
+ let mut module_libs = Vec::new();
159
+
160
+ // Load external modules defined in the configuration file
161
+ if let Some(modules) = yaml_config["global"]["loadModules"].as_vec() {
162
+ for module_name_yaml in modules.iter() {
163
+ if let Some(module_name) = module_name_yaml.as_str() {
164
+ module_libs.push(String::from(module_name));
165
+ }
166
+ }
167
+ }
168
+
169
+ let mut external_modules = Vec::new();
170
+ #[allow(unused_mut)]
171
+ let mut modules_optional_builtin = Vec::new();
172
+ // Iterate over loaded module libraries and initialize them
173
+ for module_name in module_libs.iter() {
174
+ match module_name as &str {
175
+ #[cfg(feature = "rproxy")]
176
+ "rproxy" => {
177
+ external_modules.push(
178
+ match ferron_optional_modules::rproxy::server_module_init(&yaml_config) {
179
+ Ok(module) => module,
180
+ Err(err) => {
181
+ module_error = Some(anyhow::anyhow!(
182
+ "Cannot initialize optional built-in module \"{}\": {}",
183
+ module_name,
184
+ err
185
+ ));
186
+ break;
187
+ }
188
+ },
189
+ );
190
+
191
+ modules_optional_builtin.push(module_name.clone());
192
+ }
193
+ #[cfg(feature = "fproxy")]
194
+ "fproxy" => {
195
+ external_modules.push(
196
+ match ferron_optional_modules::fproxy::server_module_init(&yaml_config) {
197
+ Ok(module) => module,
198
+ Err(err) => {
199
+ module_error = Some(anyhow::anyhow!(
200
+ "Cannot initialize optional built-in module \"{}\": {}",
201
+ module_name,
202
+ err
203
+ ));
204
+ break;
205
+ }
206
+ },
207
+ );
208
+
209
+ modules_optional_builtin.push(module_name.clone());
210
+ }
211
+ #[cfg(feature = "cache")]
212
+ "cache" => {
213
+ external_modules.push(
214
+ match ferron_optional_modules::cache::server_module_init(&yaml_config) {
215
+ Ok(module) => module,
216
+ Err(err) => {
217
+ module_error = Some(anyhow::anyhow!(
218
+ "Cannot initialize optional built-in module \"{}\": {}",
219
+ module_name,
220
+ err
221
+ ));
222
+ break;
223
+ }
224
+ },
225
+ );
226
+
227
+ modules_optional_builtin.push(module_name.clone());
228
+ }
229
+ #[cfg(feature = "cgi")]
230
+ "cgi" => {
231
+ external_modules.push(
232
+ match ferron_optional_modules::cgi::server_module_init(&yaml_config) {
233
+ Ok(module) => module,
234
+ Err(err) => {
235
+ module_error = Some(anyhow::anyhow!(
236
+ "Cannot initialize optional built-in module \"{}\": {}",
237
+ module_name,
238
+ err
239
+ ));
240
+ break;
241
+ }
242
+ },
243
+ );
244
+
245
+ modules_optional_builtin.push(module_name.clone());
246
+ }
247
+ #[cfg(feature = "scgi")]
248
+ "scgi" => {
249
+ external_modules.push(
250
+ match ferron_optional_modules::scgi::server_module_init(&yaml_config) {
251
+ Ok(module) => module,
252
+ Err(err) => {
253
+ module_error = Some(anyhow::anyhow!(
254
+ "Cannot initialize optional built-in module \"{}\": {}",
255
+ module_name,
256
+ err
257
+ ));
258
+ break;
259
+ }
260
+ },
261
+ );
262
+
263
+ modules_optional_builtin.push(module_name.clone());
264
+ }
265
+ #[cfg(feature = "fcgi")]
266
+ "fcgi" => {
267
+ external_modules.push(
268
+ match ferron_optional_modules::fcgi::server_module_init(&yaml_config) {
269
+ Ok(module) => module,
270
+ Err(err) => {
271
+ module_error = Some(anyhow::anyhow!(
272
+ "Cannot initialize optional built-in module \"{}\": {}",
273
+ module_name,
274
+ err
275
+ ));
276
+ break;
277
+ }
278
+ },
279
+ );
280
+
281
+ modules_optional_builtin.push(module_name.clone());
282
+ }
283
+ #[cfg(feature = "fauth")]
284
+ "fauth" => {
285
+ external_modules.push(
286
+ match ferron_optional_modules::fauth::server_module_init(&yaml_config) {
287
+ Ok(module) => module,
288
+ Err(err) => {
289
+ module_error = Some(anyhow::anyhow!(
290
+ "Cannot initialize optional built-in module \"{}\": {}",
291
+ module_name,
292
+ err
293
+ ));
294
+ break;
295
+ }
296
+ },
297
+ );
298
+
299
+ modules_optional_builtin.push(module_name.clone());
300
+ }
301
+ #[cfg(feature = "example")]
302
+ "example" => {
303
+ external_modules.push(
304
+ match ferron_optional_modules::example::server_module_init(&yaml_config) {
305
+ Ok(module) => module,
306
+ Err(err) => {
307
+ module_error = Some(anyhow::anyhow!(
308
+ "Cannot initialize optional built-in module \"{}\": {}",
309
+ module_name,
310
+ err
311
+ ));
312
+ break;
313
+ }
314
+ },
315
+ );
316
+
317
+ modules_optional_builtin.push(module_name.clone());
318
+ }
319
+ #[cfg(feature = "wsgi")]
320
+ "wsgi" => {
321
+ external_modules.push(
322
+ match ferron_optional_modules::wsgi::server_module_init(&yaml_config) {
323
+ Ok(module) => module,
324
+ Err(err) => {
325
+ module_error = Some(anyhow::anyhow!(
326
+ "Cannot initialize optional built-in module \"{}\": {}",
327
+ module_name,
328
+ err
329
+ ));
330
+ break;
331
+ }
332
+ },
333
+ );
334
+
335
+ modules_optional_builtin.push(module_name.clone());
336
+ }
337
+ #[cfg(feature = "wsgid")]
338
+ "wsgid" => {
339
+ external_modules.push(
340
+ match ferron_optional_modules::wsgid::server_module_init(&yaml_config) {
341
+ Ok(module) => module,
342
+ Err(err) => {
343
+ module_error = Some(anyhow::anyhow!(
344
+ "Cannot initialize optional built-in module \"{}\": {}",
345
+ module_name,
346
+ err
347
+ ));
348
+ break;
349
+ }
350
+ },
351
+ );
352
+
353
+ modules_optional_builtin.push(module_name.clone());
354
+ }
355
+ #[cfg(feature = "asgi")]
356
+ "asgi" => {
357
+ external_modules.push(
358
+ match ferron_optional_modules::asgi::server_module_init(&yaml_config) {
359
+ Ok(module) => module,
360
+ Err(err) => {
361
+ module_error = Some(anyhow::anyhow!(
362
+ "Cannot initialize optional built-in module \"{}\": {}",
363
+ module_name,
364
+ err
365
+ ));
366
+ break;
367
+ }
368
+ },
369
+ );
370
+
371
+ modules_optional_builtin.push(module_name.clone());
372
+ }
373
+ _ => {
374
+ module_error = Some(anyhow::anyhow!(
375
+ "The optional built-in module \"{}\" doesn't exist",
376
+ module_name
377
+ ));
378
+ break;
379
+ }
380
+ }
381
+ }
382
+
383
+ // Add modules (both built-in and loaded)
384
+ let mut modules = Vec::new();
385
+ match ferron_modules::x_forwarded_for::server_module_init() {
386
+ Ok(module) => modules.push(module),
387
+ Err(err) => {
388
+ if module_error.is_none() {
389
+ module_error = Some(anyhow::anyhow!("Cannot load a built-in module: {}", err));
390
+ }
391
+ }
392
+ };
393
+ match ferron_modules::redirects::server_module_init() {
394
+ Ok(module) => modules.push(module),
395
+ Err(err) => {
396
+ if module_error.is_none() {
397
+ module_error = Some(anyhow::anyhow!("Cannot load a built-in module: {}", err));
398
+ }
399
+ }
400
+ };
401
+ match ferron_modules::blocklist::server_module_init(&yaml_config) {
402
+ Ok(module) => modules.push(module),
403
+ Err(err) => {
404
+ if module_error.is_none() {
405
+ module_error = Some(anyhow::anyhow!("Cannot load a built-in module: {}", err));
406
+ }
407
+ }
408
+ };
409
+ match ferron_modules::url_rewrite::server_module_init(&yaml_config) {
410
+ Ok(module) => modules.push(module),
411
+ Err(err) => {
412
+ if module_error.is_none() {
413
+ module_error = Some(anyhow::anyhow!("Cannot load a built-in module: {}", err));
414
+ }
415
+ }
416
+ };
417
+ match ferron_modules::non_standard_codes::server_module_init(&yaml_config) {
418
+ Ok(module) => modules.push(module),
419
+ Err(err) => {
420
+ if module_error.is_none() {
421
+ module_error = Some(anyhow::anyhow!("Cannot load a built-in module: {}", err));
422
+ }
423
+ }
424
+ };
425
+ match ferron_modules::redirect_trailing_slashes::server_module_init() {
426
+ Ok(module) => modules.push(module),
427
+ Err(err) => {
428
+ if module_error.is_none() {
429
+ module_error = Some(anyhow::anyhow!("Cannot load a built-in module: {}", err));
430
+ }
431
+ }
432
+ };
433
+ modules.append(&mut external_modules);
434
+ match ferron_modules::default_handler_checks::server_module_init() {
435
+ Ok(module) => modules.push(module),
436
+ Err(err) => {
437
+ if module_error.is_none() {
438
+ module_error = Some(anyhow::anyhow!("Cannot load a built-in module: {}", err));
439
+ }
440
+ }
441
+ };
442
+ match ferron_modules::static_file_serving::server_module_init() {
443
+ Ok(module) => modules.push(module),
444
+ Err(err) => {
445
+ if module_error.is_none() {
446
+ module_error = Some(anyhow::anyhow!("Cannot load a built-in module: {}", err));
447
+ }
448
+ }
449
+ };
450
+
451
+ // Start the server with configuration and loaded modules
452
+ start_server(
453
+ Arc::new(yaml_config),
454
+ modules,
455
+ module_error,
456
+ modules_optional_builtin,
457
+ first_start,
458
+ )
459
+ }
460
+
461
+ // Entry point of the application
462
+ fn main() {
463
+ let args = &Args::parse(); // Parse command-line arguments
464
+ let mut first_start = true;
465
+ loop {
466
+ match before_starting_server(args, first_start) {
467
+ Ok(false) => break,
468
+ Ok(true) => {
469
+ first_start = false;
470
+ println!("Reloading the server configuration...");
471
+ }
472
+ Err(err) => {
473
+ eprintln!("FATAL ERROR: {}", err);
474
+ std::process::exit(1);
475
+ }
476
+ }
477
+ }
478
+ }
ferron/src/modules/blocklist.rs ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use std::error::Error;
2
+ use std::sync::Arc;
3
+
4
+ use crate::ferron_common::{
5
+ ErrorLogger, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
6
+ ServerModuleHandlers, SocketData,
7
+ };
8
+ use crate::ferron_common::{HyperUpgraded, WithRuntime};
9
+ use async_trait::async_trait;
10
+ use hyper::StatusCode;
11
+ use hyper_tungstenite::HyperWebsocket;
12
+ use tokio::runtime::Handle;
13
+
14
+ use crate::ferron_util::ip_blocklist::IpBlockList;
15
+
16
+ struct BlockListModule {
17
+ blocklist: Arc<IpBlockList>,
18
+ }
19
+
20
+ pub fn server_module_init(
21
+ config: &ServerConfig,
22
+ ) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
23
+ let blocklist_vec = match config["global"]["blocklist"].as_vec() {
24
+ Some(blocklist_vec) => blocklist_vec,
25
+ None => &Vec::new(),
26
+ };
27
+
28
+ let mut blocklist_str_vec = Vec::new();
29
+ for blocked_yaml in blocklist_vec.iter() {
30
+ if let Some(blocked) = blocked_yaml.as_str() {
31
+ blocklist_str_vec.push(blocked);
32
+ }
33
+ }
34
+
35
+ let mut blocklist = IpBlockList::new();
36
+ blocklist.load_from_vec(blocklist_str_vec);
37
+
38
+ Ok(Box::new(BlockListModule::new(Arc::new(blocklist))))
39
+ }
40
+
41
+ impl BlockListModule {
42
+ fn new(blocklist: Arc<IpBlockList>) -> Self {
43
+ Self { blocklist }
44
+ }
45
+ }
46
+
47
+ impl ServerModule for BlockListModule {
48
+ fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
49
+ Box::new(BlockListModuleHandlers {
50
+ blocklist: self.blocklist.clone(),
51
+ handle,
52
+ })
53
+ }
54
+ }
55
+ struct BlockListModuleHandlers {
56
+ blocklist: Arc<IpBlockList>,
57
+ handle: Handle,
58
+ }
59
+
60
+ #[async_trait]
61
+ impl ServerModuleHandlers for BlockListModuleHandlers {
62
+ async fn request_handler(
63
+ &mut self,
64
+ request: RequestData,
65
+ _config: &ServerConfig,
66
+ socket_data: &SocketData,
67
+ _error_logger: &ErrorLogger,
68
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
69
+ WithRuntime::new(self.handle.clone(), async move {
70
+ if self.blocklist.is_blocked(socket_data.remote_addr.ip()) {
71
+ return Ok(
72
+ ResponseData::builder(request)
73
+ .status(StatusCode::FORBIDDEN)
74
+ .build(),
75
+ );
76
+ }
77
+ Ok(ResponseData::builder(request).build())
78
+ })
79
+ .await
80
+ }
81
+
82
+ async fn proxy_request_handler(
83
+ &mut self,
84
+ request: RequestData,
85
+ _config: &ServerConfig,
86
+ _socket_data: &SocketData,
87
+ _error_logger: &ErrorLogger,
88
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
89
+ Ok(ResponseData::builder(request).build())
90
+ }
91
+
92
+ async fn response_modifying_handler(
93
+ &mut self,
94
+ response: HyperResponse,
95
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
96
+ Ok(response)
97
+ }
98
+
99
+ async fn proxy_response_modifying_handler(
100
+ &mut self,
101
+ response: HyperResponse,
102
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
103
+ Ok(response)
104
+ }
105
+
106
+ async fn connect_proxy_request_handler(
107
+ &mut self,
108
+ _upgraded_request: HyperUpgraded,
109
+ _connect_address: &str,
110
+ _config: &ServerConfig,
111
+ _socket_data: &SocketData,
112
+ _error_logger: &ErrorLogger,
113
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
114
+ Ok(())
115
+ }
116
+
117
+ fn does_connect_proxy_requests(&mut self) -> bool {
118
+ false
119
+ }
120
+
121
+ async fn websocket_request_handler(
122
+ &mut self,
123
+ _websocket: HyperWebsocket,
124
+ _uri: &hyper::Uri,
125
+ _config: &ServerConfig,
126
+ _socket_data: &SocketData,
127
+ _error_logger: &ErrorLogger,
128
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
129
+ Ok(())
130
+ }
131
+
132
+ fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
133
+ false
134
+ }
135
+ }
ferron/src/modules/default_handler_checks.rs ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use std::error::Error;
2
+
3
+ use crate::ferron_common::{
4
+ ErrorLogger, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
5
+ ServerModuleHandlers, SocketData,
6
+ };
7
+ use crate::ferron_common::{HyperUpgraded, WithRuntime};
8
+ use async_trait::async_trait;
9
+ use http_body_util::{BodyExt, Empty};
10
+ use hyper::header::HeaderValue;
11
+ use hyper::{header, HeaderMap, Method, Response, StatusCode};
12
+ use hyper_tungstenite::HyperWebsocket;
13
+ use tokio::runtime::Handle;
14
+
15
+ struct DefaultHandlerChecksModule;
16
+
17
+ pub fn server_module_init(
18
+ ) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
19
+ Ok(Box::new(DefaultHandlerChecksModule::new()))
20
+ }
21
+
22
+ impl DefaultHandlerChecksModule {
23
+ fn new() -> Self {
24
+ Self
25
+ }
26
+ }
27
+
28
+ impl ServerModule for DefaultHandlerChecksModule {
29
+ fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
30
+ Box::new(DefaultHandlerChecksModuleHandlers { handle })
31
+ }
32
+ }
33
+ struct DefaultHandlerChecksModuleHandlers {
34
+ handle: Handle,
35
+ }
36
+
37
+ #[async_trait]
38
+ impl ServerModuleHandlers for DefaultHandlerChecksModuleHandlers {
39
+ async fn request_handler(
40
+ &mut self,
41
+ request: RequestData,
42
+ _config: &ServerConfig,
43
+ _socket_data: &SocketData,
44
+ _error_logger: &ErrorLogger,
45
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
46
+ WithRuntime::new(self.handle.clone(), async move {
47
+ match request.get_hyper_request().method() {
48
+ &Method::OPTIONS => Ok(
49
+ ResponseData::builder(request)
50
+ .response(
51
+ Response::builder()
52
+ .status(StatusCode::NO_CONTENT)
53
+ .header(header::ALLOW, "GET, POST, HEAD, OPTIONS")
54
+ .body(Empty::new().map_err(|e| match e {}).boxed())
55
+ .unwrap_or_default(),
56
+ )
57
+ .build(),
58
+ ),
59
+ &Method::GET | &Method::POST | &Method::HEAD => Ok(ResponseData::builder(request).build()),
60
+ _ => {
61
+ let mut header_map = HeaderMap::new();
62
+ if let Ok(header_value) = HeaderValue::from_str("GET, POST, HEAD, OPTIONS") {
63
+ header_map.insert(header::ALLOW, header_value);
64
+ };
65
+ Ok(
66
+ ResponseData::builder(request)
67
+ .status(StatusCode::METHOD_NOT_ALLOWED)
68
+ .headers(header_map)
69
+ .build(),
70
+ )
71
+ }
72
+ }
73
+ })
74
+ .await
75
+ }
76
+
77
+ async fn proxy_request_handler(
78
+ &mut self,
79
+ request: RequestData,
80
+ _config: &ServerConfig,
81
+ _socket_data: &SocketData,
82
+ _error_logger: &ErrorLogger,
83
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
84
+ Ok(
85
+ ResponseData::builder(request)
86
+ .status(StatusCode::NOT_IMPLEMENTED)
87
+ .build(),
88
+ )
89
+ }
90
+
91
+ async fn response_modifying_handler(
92
+ &mut self,
93
+ response: HyperResponse,
94
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
95
+ Ok(response)
96
+ }
97
+
98
+ async fn proxy_response_modifying_handler(
99
+ &mut self,
100
+ response: HyperResponse,
101
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
102
+ Ok(response)
103
+ }
104
+
105
+ async fn connect_proxy_request_handler(
106
+ &mut self,
107
+ _upgraded_request: HyperUpgraded,
108
+ _connect_address: &str,
109
+ _config: &ServerConfig,
110
+ _socket_data: &SocketData,
111
+ _error_logger: &ErrorLogger,
112
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
113
+ Ok(())
114
+ }
115
+
116
+ fn does_connect_proxy_requests(&mut self) -> bool {
117
+ false
118
+ }
119
+
120
+ async fn websocket_request_handler(
121
+ &mut self,
122
+ _websocket: HyperWebsocket,
123
+ _uri: &hyper::Uri,
124
+ _config: &ServerConfig,
125
+ _socket_data: &SocketData,
126
+ _error_logger: &ErrorLogger,
127
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
128
+ Ok(())
129
+ }
130
+
131
+ fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
132
+ false
133
+ }
134
+ }
ferron/src/modules/non_standard_codes.rs ADDED
@@ -0,0 +1,537 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use std::error::Error;
2
+ use std::sync::Arc;
3
+ use std::time::Duration;
4
+
5
+ use crate::ferron_util::ip_blocklist::IpBlockList;
6
+ use crate::ferron_util::ip_match::ip_match;
7
+ use crate::ferron_util::match_hostname::match_hostname;
8
+ use crate::ferron_util::match_location::match_location;
9
+ use crate::ferron_util::non_standard_code_structs::{
10
+ NonStandardCode, NonStandardCodesLocationWrap, NonStandardCodesWrap,
11
+ };
12
+ use crate::ferron_util::ttl_cache::TtlCache;
13
+
14
+ use crate::ferron_common::{
15
+ ErrorLogger, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
16
+ ServerModuleHandlers, SocketData,
17
+ };
18
+ use crate::ferron_common::{HyperUpgraded, WithRuntime};
19
+ use async_trait::async_trait;
20
+ use base64::{engine::general_purpose, Engine};
21
+ use fancy_regex::RegexBuilder;
22
+ use http_body_util::{BodyExt, Empty};
23
+ use hyper::header::HeaderValue;
24
+ use hyper::{header, HeaderMap, Response, StatusCode};
25
+ use hyper_tungstenite::HyperWebsocket;
26
+ use password_auth::verify_password;
27
+ use tokio::runtime::Handle;
28
+ use tokio::sync::RwLock;
29
+ use yaml_rust2::Yaml;
30
+
31
+ fn non_standard_codes_config_init(
32
+ non_standard_codes_list: &[Yaml],
33
+ ) -> Result<Vec<NonStandardCode>, anyhow::Error> {
34
+ let non_standard_codes_list_iter = non_standard_codes_list.iter();
35
+ let mut non_standard_codes_list_vec = Vec::new();
36
+ for non_standard_codes_list_entry in non_standard_codes_list_iter {
37
+ let status_code: u16 = match non_standard_codes_list_entry["scode"].as_i64() {
38
+ Some(scode) => scode.try_into()?,
39
+ None => {
40
+ return Err(anyhow::anyhow!(
41
+ "Non-standard codes must include a status code"
42
+ ));
43
+ }
44
+ };
45
+ let regex = match non_standard_codes_list_entry["regex"].as_str() {
46
+ Some(regex_str) => match RegexBuilder::new(regex_str)
47
+ .case_insensitive(cfg!(windows))
48
+ .build()
49
+ {
50
+ Ok(regex) => Some(regex),
51
+ Err(err) => {
52
+ return Err(anyhow::anyhow!(
53
+ "Invalid non-standard code regular expression: {}",
54
+ err.to_string()
55
+ ));
56
+ }
57
+ },
58
+ None => None,
59
+ };
60
+ let url = non_standard_codes_list_entry["url"]
61
+ .as_str()
62
+ .map(|s| s.to_string());
63
+
64
+ if regex.is_none() && url.is_none() {
65
+ return Err(anyhow::anyhow!(
66
+ "Non-standard codes must either include URL or a matching regular expression"
67
+ ));
68
+ }
69
+
70
+ let location = non_standard_codes_list_entry["location"]
71
+ .as_str()
72
+ .map(|s| s.to_string());
73
+ let realm = non_standard_codes_list_entry["realm"]
74
+ .as_str()
75
+ .map(|s| s.to_string());
76
+ let disable_brute_force_protection = non_standard_codes_list_entry["disableBruteProtection"]
77
+ .as_bool()
78
+ .unwrap_or(false);
79
+ let user_list = match non_standard_codes_list_entry["userList"].as_vec() {
80
+ Some(userlist) => {
81
+ let mut new_userlist = Vec::new();
82
+ for user_yaml in userlist.iter() {
83
+ if let Some(user) = user_yaml.as_str() {
84
+ new_userlist.push(user.to_string());
85
+ }
86
+ }
87
+ Some(new_userlist)
88
+ }
89
+ None => None,
90
+ };
91
+ let users = match non_standard_codes_list_entry["users"].as_vec() {
92
+ Some(users_vec) => {
93
+ let mut users_str_vec = Vec::new();
94
+ for user_yaml in users_vec.iter() {
95
+ if let Some(user) = user_yaml.as_str() {
96
+ users_str_vec.push(user);
97
+ }
98
+ }
99
+
100
+ let mut users_init = IpBlockList::new();
101
+ users_init.load_from_vec(users_str_vec);
102
+ Some(users_init)
103
+ }
104
+ None => None,
105
+ };
106
+ non_standard_codes_list_vec.push(NonStandardCode::new(
107
+ status_code,
108
+ url,
109
+ regex,
110
+ location,
111
+ realm,
112
+ disable_brute_force_protection,
113
+ user_list,
114
+ users,
115
+ ));
116
+ }
117
+
118
+ Ok(non_standard_codes_list_vec)
119
+ }
120
+
121
+ pub fn server_module_init(
122
+ config: &ServerConfig,
123
+ ) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
124
+ let mut global_non_standard_codes_list = Vec::new();
125
+ let mut host_non_standard_codes_lists = Vec::new();
126
+ if let Some(non_standard_codes_list_yaml) = config["global"]["nonStandardCodes"].as_vec() {
127
+ global_non_standard_codes_list = non_standard_codes_config_init(non_standard_codes_list_yaml)?;
128
+ }
129
+
130
+ if let Some(hosts) = config["hosts"].as_vec() {
131
+ for host_yaml in hosts.iter() {
132
+ let domain = host_yaml["domain"].as_str().map(String::from);
133
+ let ip = host_yaml["ip"].as_str().map(String::from);
134
+ let mut locations = Vec::new();
135
+ if let Some(locations_yaml) = host_yaml["locations"].as_vec() {
136
+ for location_yaml in locations_yaml.iter() {
137
+ if let Some(path_str) = location_yaml["path"].as_str() {
138
+ let path = String::from(path_str);
139
+ if let Some(non_standard_codes_list_yaml) = location_yaml["nonStandardCodes"].as_vec() {
140
+ locations.push(NonStandardCodesLocationWrap::new(
141
+ path,
142
+ non_standard_codes_config_init(non_standard_codes_list_yaml)?,
143
+ ));
144
+ }
145
+ }
146
+ }
147
+ }
148
+ if let Some(non_standard_codes_list_yaml) = host_yaml["nonStandardCodes"].as_vec() {
149
+ host_non_standard_codes_lists.push(NonStandardCodesWrap::new(
150
+ domain,
151
+ ip,
152
+ non_standard_codes_config_init(non_standard_codes_list_yaml)?,
153
+ locations,
154
+ ));
155
+ } else if !locations.is_empty() {
156
+ host_non_standard_codes_lists.push(NonStandardCodesWrap::new(
157
+ domain,
158
+ ip,
159
+ Vec::new(),
160
+ locations,
161
+ ));
162
+ }
163
+ }
164
+ }
165
+
166
+ Ok(Box::new(NonStandardCodesModule::new(
167
+ Arc::new(global_non_standard_codes_list),
168
+ Arc::new(host_non_standard_codes_lists),
169
+ Arc::new(RwLock::new(TtlCache::new(Duration::new(300, 0)))),
170
+ )))
171
+ }
172
+
173
+ struct NonStandardCodesModule {
174
+ global_non_standard_codes_list: Arc<Vec<NonStandardCode>>,
175
+ host_non_standard_codes_lists: Arc<Vec<NonStandardCodesWrap>>,
176
+ brute_force_db: Arc<RwLock<TtlCache<String, u8>>>,
177
+ }
178
+
179
+ impl NonStandardCodesModule {
180
+ fn new(
181
+ global_non_standard_codes_list: Arc<Vec<NonStandardCode>>,
182
+ host_non_standard_codes_lists: Arc<Vec<NonStandardCodesWrap>>,
183
+ brute_force_db: Arc<RwLock<TtlCache<String, u8>>>,
184
+ ) -> Self {
185
+ Self {
186
+ global_non_standard_codes_list,
187
+ host_non_standard_codes_lists,
188
+ brute_force_db,
189
+ }
190
+ }
191
+ }
192
+
193
+ impl ServerModule for NonStandardCodesModule {
194
+ fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
195
+ Box::new(NonStandardCodesModuleHandlers {
196
+ global_non_standard_codes_list: self.global_non_standard_codes_list.clone(),
197
+ host_non_standard_codes_lists: self.host_non_standard_codes_lists.clone(),
198
+ brute_force_db: self.brute_force_db.clone(),
199
+ handle,
200
+ })
201
+ }
202
+ }
203
+
204
+ fn parse_basic_auth(auth_str: &str) -> Option<(String, String)> {
205
+ if let Some(base64_credentials) = auth_str.strip_prefix("Basic ") {
206
+ if let Ok(decoded) = general_purpose::STANDARD.decode(base64_credentials) {
207
+ if let Ok(decoded_str) = std::str::from_utf8(&decoded) {
208
+ let parts: Vec<&str> = decoded_str.splitn(2, ':').collect();
209
+ if parts.len() == 2 {
210
+ return Some((parts[0].to_string(), parts[1].to_string()));
211
+ }
212
+ }
213
+ }
214
+ }
215
+ None
216
+ }
217
+
218
+ struct NonStandardCodesModuleHandlers {
219
+ global_non_standard_codes_list: Arc<Vec<NonStandardCode>>,
220
+ host_non_standard_codes_lists: Arc<Vec<NonStandardCodesWrap>>,
221
+ brute_force_db: Arc<RwLock<TtlCache<String, u8>>>,
222
+ handle: Handle,
223
+ }
224
+
225
+ #[async_trait]
226
+ impl ServerModuleHandlers for NonStandardCodesModuleHandlers {
227
+ async fn request_handler(
228
+ &mut self,
229
+ request: RequestData,
230
+ config: &ServerConfig,
231
+ socket_data: &SocketData,
232
+ error_logger: &ErrorLogger,
233
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
234
+ WithRuntime::new(self.handle.clone(), async move {
235
+ let hyper_request = request.get_hyper_request();
236
+ let global_non_standard_codes_list = self.global_non_standard_codes_list.iter();
237
+ let empty_vector = Vec::new();
238
+ let another_empty_vector = Vec::new();
239
+ let mut host_non_standard_codes_list = empty_vector.iter();
240
+ let mut location_non_standard_codes_list = another_empty_vector.iter();
241
+
242
+ // Should have used a HashMap instead of iterating over an array for better performance...
243
+ for host_non_standard_codes_list_wrap in self.host_non_standard_codes_lists.iter() {
244
+ if match_hostname(
245
+ match &host_non_standard_codes_list_wrap.domain {
246
+ Some(value) => Some(value as &str),
247
+ None => None,
248
+ },
249
+ match hyper_request.headers().get(header::HOST) {
250
+ Some(value) => value.to_str().ok(),
251
+ None => None,
252
+ },
253
+ ) && match &host_non_standard_codes_list_wrap.ip {
254
+ Some(value) => ip_match(value as &str, socket_data.remote_addr.ip()),
255
+ None => true,
256
+ } {
257
+ host_non_standard_codes_list =
258
+ host_non_standard_codes_list_wrap.non_standard_codes.iter();
259
+ if let Ok(path_decoded) = urlencoding::decode(
260
+ request
261
+ .get_original_url()
262
+ .unwrap_or(request.get_hyper_request().uri())
263
+ .path(),
264
+ ) {
265
+ for location_wrap in host_non_standard_codes_list_wrap.locations.iter() {
266
+ if match_location(&location_wrap.path, &path_decoded) {
267
+ location_non_standard_codes_list = location_wrap.non_standard_codes.iter();
268
+ break;
269
+ }
270
+ }
271
+ }
272
+ break;
273
+ }
274
+ }
275
+
276
+ let combined_non_standard_codes_list = global_non_standard_codes_list
277
+ .chain(host_non_standard_codes_list)
278
+ .chain(location_non_standard_codes_list);
279
+
280
+ let request_url = format!(
281
+ "{}{}",
282
+ hyper_request.uri().path(),
283
+ match hyper_request.uri().query() {
284
+ Some(query) => format!("?{}", query),
285
+ None => String::from(""),
286
+ }
287
+ );
288
+
289
+ let mut auth_user = None;
290
+
291
+ for non_standard_code in combined_non_standard_codes_list {
292
+ let mut redirect_url = None;
293
+ let mut url_matched = false;
294
+
295
+ if let Some(users) = &non_standard_code.users {
296
+ if !users.is_blocked(socket_data.remote_addr.ip()) {
297
+ // Don't process this non-standard code
298
+ continue;
299
+ }
300
+ }
301
+
302
+ if let Some(regex) = &non_standard_code.regex {
303
+ let regex_match_option = regex.find(&request_url)?;
304
+ if let Some(regex_match) = regex_match_option {
305
+ url_matched = true;
306
+ if non_standard_code.status_code == 301
307
+ || non_standard_code.status_code == 302
308
+ || non_standard_code.status_code == 307
309
+ || non_standard_code.status_code == 308
310
+ {
311
+ let matched_text = regex_match.as_str();
312
+ if let Some(location) = &non_standard_code.location {
313
+ redirect_url = Some(regex.replace(matched_text, location).to_string());
314
+ }
315
+ }
316
+ }
317
+ }
318
+
319
+ if !url_matched {
320
+ if let Some(url) = &non_standard_code.url {
321
+ if url == hyper_request.uri().path() {
322
+ url_matched = true;
323
+ if non_standard_code.status_code == 301
324
+ || non_standard_code.status_code == 302
325
+ || non_standard_code.status_code == 307
326
+ || non_standard_code.status_code == 308
327
+ {
328
+ if let Some(location) = &non_standard_code.location {
329
+ redirect_url = Some(format!(
330
+ "{}{}",
331
+ location,
332
+ match hyper_request.uri().query() {
333
+ Some(query) => format!("?{}", query),
334
+ None => String::from(""),
335
+ }
336
+ ));
337
+ }
338
+ }
339
+ }
340
+ }
341
+ }
342
+
343
+ if url_matched {
344
+ match non_standard_code.status_code {
345
+ 301 | 302 | 307 | 308 => {
346
+ return Ok(
347
+ ResponseData::builder(request)
348
+ .response(
349
+ Response::builder()
350
+ .status(StatusCode::from_u16(non_standard_code.status_code)?)
351
+ .header(header::LOCATION, redirect_url.unwrap_or(request_url))
352
+ .body(Empty::new().map_err(|e| match e {}).boxed())?,
353
+ )
354
+ .build(),
355
+ );
356
+ }
357
+ 401 => {
358
+ let brute_force_db_key = socket_data.remote_addr.ip().to_string();
359
+ if !non_standard_code.disable_brute_force_protection {
360
+ let rwlock_read = self.brute_force_db.read().await;
361
+ let current_attempts = rwlock_read.get(&brute_force_db_key).unwrap_or(0);
362
+ if current_attempts >= 10 {
363
+ error_logger
364
+ .log(&format!(
365
+ "Too many failed authorization attempts for client \"{}\"",
366
+ socket_data.remote_addr.ip()
367
+ ))
368
+ .await;
369
+
370
+ return Ok(
371
+ ResponseData::builder(request)
372
+ .status(StatusCode::TOO_MANY_REQUESTS)
373
+ .build(),
374
+ );
375
+ }
376
+ }
377
+ let mut header_map = HeaderMap::new();
378
+ header_map.insert(
379
+ header::WWW_AUTHENTICATE,
380
+ HeaderValue::from_str(&format!(
381
+ "Basic realm=\"{}\", charset=\"UTF-8\"",
382
+ non_standard_code
383
+ .realm
384
+ .clone()
385
+ .unwrap_or("Ferron HTTP Basic Authorization".to_string())
386
+ .replace("\\", "\\\\")
387
+ .replace("\"", "\\\"")
388
+ ))?,
389
+ );
390
+
391
+ if let Some(authorization_header_value) =
392
+ hyper_request.headers().get(header::AUTHORIZATION)
393
+ {
394
+ let authorization_str = match authorization_header_value.to_str() {
395
+ Ok(str) => str,
396
+ Err(_) => {
397
+ return Ok(
398
+ ResponseData::builder(request)
399
+ .status(StatusCode::BAD_REQUEST)
400
+ .build(),
401
+ );
402
+ }
403
+ };
404
+
405
+ if let Some((username, password)) = parse_basic_auth(authorization_str) {
406
+ if let Some(users_vec_yaml) = config["users"].as_vec() {
407
+ let mut authorized_user = None;
408
+ for user_yaml in users_vec_yaml {
409
+ if let Some(username_db) = user_yaml["name"].as_str() {
410
+ if username_db != username {
411
+ continue;
412
+ }
413
+ if let Some(user_list) = &non_standard_code.user_list {
414
+ if !user_list.contains(&username) {
415
+ continue;
416
+ }
417
+ }
418
+ if let Some(password_hash_db) = user_yaml["pass"].as_str() {
419
+ let password_cloned = password.clone();
420
+ let password_hash_db_cloned = password_hash_db.to_string();
421
+ // Offload verifying the hash into a separate blocking thread.
422
+ let password_valid = tokio::task::spawn_blocking(move || {
423
+ verify_password(password_cloned, &password_hash_db_cloned).is_ok()
424
+ })
425
+ .await?;
426
+ if password_valid {
427
+ authorized_user = Some(&username);
428
+ break;
429
+ }
430
+ }
431
+ }
432
+ }
433
+ if let Some(authorized_user) = authorized_user {
434
+ auth_user = Some(authorized_user.to_owned());
435
+ continue;
436
+ }
437
+ }
438
+
439
+ if !non_standard_code.disable_brute_force_protection {
440
+ let mut rwlock_write = self.brute_force_db.write().await;
441
+ rwlock_write.cleanup();
442
+ let current_attempts = rwlock_write.get(&brute_force_db_key).unwrap_or(0);
443
+ rwlock_write.insert(brute_force_db_key, current_attempts + 1);
444
+ }
445
+
446
+ error_logger
447
+ .log(&format!(
448
+ "Authorization failed for user \"{}\" and client \"{}\"",
449
+ username,
450
+ socket_data.remote_addr.ip()
451
+ ))
452
+ .await;
453
+ }
454
+ }
455
+
456
+ return Ok(
457
+ ResponseData::builder(request)
458
+ .status(StatusCode::UNAUTHORIZED)
459
+ .headers(header_map)
460
+ .build(),
461
+ );
462
+ }
463
+ _ => {
464
+ return Ok(
465
+ ResponseData::builder(request)
466
+ .status(StatusCode::from_u16(non_standard_code.status_code)?)
467
+ .build(),
468
+ )
469
+ }
470
+ }
471
+ }
472
+ }
473
+
474
+ if auth_user.is_some() {
475
+ let (hyper_request, _, original_url) = request.into_parts();
476
+ Ok(ResponseData::builder(RequestData::new(hyper_request, auth_user, original_url)).build())
477
+ } else {
478
+ Ok(ResponseData::builder(request).build())
479
+ }
480
+ })
481
+ .await
482
+ }
483
+
484
+ async fn proxy_request_handler(
485
+ &mut self,
486
+ request: RequestData,
487
+ _config: &ServerConfig,
488
+ _socket_data: &SocketData,
489
+ _error_logger: &ErrorLogger,
490
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
491
+ Ok(ResponseData::builder(request).build())
492
+ }
493
+
494
+ async fn response_modifying_handler(
495
+ &mut self,
496
+ response: HyperResponse,
497
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
498
+ Ok(response)
499
+ }
500
+
501
+ async fn proxy_response_modifying_handler(
502
+ &mut self,
503
+ response: HyperResponse,
504
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
505
+ Ok(response)
506
+ }
507
+
508
+ async fn connect_proxy_request_handler(
509
+ &mut self,
510
+ _upgraded_request: HyperUpgraded,
511
+ _connect_address: &str,
512
+ _config: &ServerConfig,
513
+ _socket_data: &SocketData,
514
+ _error_logger: &ErrorLogger,
515
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
516
+ Ok(())
517
+ }
518
+
519
+ fn does_connect_proxy_requests(&mut self) -> bool {
520
+ false
521
+ }
522
+
523
+ async fn websocket_request_handler(
524
+ &mut self,
525
+ _websocket: HyperWebsocket,
526
+ _uri: &hyper::Uri,
527
+ _config: &ServerConfig,
528
+ _socket_data: &SocketData,
529
+ _error_logger: &ErrorLogger,
530
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
531
+ Ok(())
532
+ }
533
+
534
+ fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
535
+ false
536
+ }
537
+ }
ferron/src/modules/redirect_trailing_slashes.rs ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use std::error::Error;
2
+ use std::path::Path;
3
+ use std::sync::Arc;
4
+ use std::time::Duration;
5
+
6
+ use crate::ferron_common::{
7
+ ErrorLogger, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
8
+ ServerModuleHandlers, SocketData,
9
+ };
10
+ use crate::ferron_common::{HyperUpgraded, WithRuntime};
11
+ use async_trait::async_trait;
12
+ use http_body_util::{BodyExt, Empty};
13
+ use hyper::{header, Response, StatusCode};
14
+ use hyper_tungstenite::HyperWebsocket;
15
+ use tokio::fs;
16
+ use tokio::runtime::Handle;
17
+ use tokio::sync::RwLock;
18
+
19
+ use crate::ferron_util::ttl_cache::TtlCache;
20
+
21
+ pub fn server_module_init(
22
+ ) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
23
+ let cache = Arc::new(RwLock::new(TtlCache::new(Duration::from_millis(100))));
24
+ Ok(Box::new(RedirectTrailingSlashesModule::new(cache)))
25
+ }
26
+
27
+ struct RedirectTrailingSlashesModule {
28
+ cache: Arc<RwLock<TtlCache<String, bool>>>,
29
+ }
30
+
31
+ impl RedirectTrailingSlashesModule {
32
+ fn new(cache: Arc<RwLock<TtlCache<String, bool>>>) -> Self {
33
+ Self { cache }
34
+ }
35
+ }
36
+
37
+ impl ServerModule for RedirectTrailingSlashesModule {
38
+ fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
39
+ Box::new(RedirectTrailingSlashesModuleHandlers {
40
+ cache: self.cache.clone(),
41
+ handle,
42
+ })
43
+ }
44
+ }
45
+
46
+ struct RedirectTrailingSlashesModuleHandlers {
47
+ cache: Arc<RwLock<TtlCache<String, bool>>>,
48
+ handle: Handle,
49
+ }
50
+
51
+ #[async_trait]
52
+ impl ServerModuleHandlers for RedirectTrailingSlashesModuleHandlers {
53
+ async fn request_handler(
54
+ &mut self,
55
+ request: RequestData,
56
+ config: &ServerConfig,
57
+ _socket_data: &SocketData,
58
+ _error_logger: &ErrorLogger,
59
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
60
+ WithRuntime::new(self.handle.clone(), async move {
61
+ if config["disableTrailingSlashRedirects"].as_bool() != Some(true) {
62
+ if let Some(wwwroot) = config["wwwroot"].as_str() {
63
+ let hyper_request = request.get_hyper_request();
64
+
65
+ let request_path = hyper_request.uri().path();
66
+ let request_query = hyper_request.uri().query();
67
+ let mut request_path_bytes = request_path.bytes();
68
+ if request_path_bytes.len() < 1 || request_path_bytes.nth(0) != Some(b'/') {
69
+ return Ok(
70
+ ResponseData::builder(request)
71
+ .status(StatusCode::BAD_REQUEST)
72
+ .build(),
73
+ );
74
+ }
75
+
76
+ match request_path_bytes.last() {
77
+ Some(b'/') | None => {
78
+ return Ok(ResponseData::builder(request).build());
79
+ }
80
+ _ => {
81
+ let cache_key = format!(
82
+ "{}{}{}",
83
+ match config["ip"].as_str() {
84
+ Some(ip) => format!("{}-", ip),
85
+ None => String::from(""),
86
+ },
87
+ match config["domain"].as_str() {
88
+ Some(domain) => format!("{}-", domain),
89
+ None => String::from(""),
90
+ },
91
+ request_path
92
+ );
93
+
94
+ let read_rwlock = self.cache.read().await;
95
+ if let Some(is_directory) = read_rwlock.get(&cache_key) {
96
+ drop(read_rwlock);
97
+ if is_directory {
98
+ let new_request_uri = format!(
99
+ "{}/{}",
100
+ request_path,
101
+ match request_query {
102
+ Some(query) => format!("?{}", query),
103
+ None => String::from(""),
104
+ }
105
+ );
106
+ return Ok(
107
+ ResponseData::builder(request)
108
+ .response(
109
+ Response::builder()
110
+ .status(StatusCode::MOVED_PERMANENTLY)
111
+ .header(header::LOCATION, new_request_uri)
112
+ .body(Empty::new().map_err(|e| match e {}).boxed())?,
113
+ )
114
+ .build(),
115
+ );
116
+ }
117
+ } else {
118
+ drop(read_rwlock);
119
+
120
+ let path = Path::new(wwwroot);
121
+ let mut relative_path = &request_path[1..];
122
+ while relative_path.as_bytes().first().copied() == Some(b'/') {
123
+ relative_path = &relative_path[1..];
124
+ }
125
+
126
+ let decoded_relative_path = match urlencoding::decode(relative_path) {
127
+ Ok(path) => path.to_string(),
128
+ Err(_) => {
129
+ return Ok(
130
+ ResponseData::builder(request)
131
+ .status(StatusCode::BAD_REQUEST)
132
+ .build(),
133
+ );
134
+ }
135
+ };
136
+
137
+ let joined_pathbuf = path.join(decoded_relative_path);
138
+
139
+ match fs::metadata(joined_pathbuf).await {
140
+ Ok(metadata) => {
141
+ let is_directory = metadata.is_dir();
142
+ let mut write_rwlock = self.cache.write().await;
143
+ write_rwlock.cleanup();
144
+ write_rwlock.insert(cache_key, is_directory);
145
+ if is_directory {
146
+ let new_request_uri = format!(
147
+ "{}/{}",
148
+ request_path,
149
+ match request_query {
150
+ Some(query) => format!("?{}", query),
151
+ None => String::from(""),
152
+ }
153
+ );
154
+ return Ok(
155
+ ResponseData::builder(request)
156
+ .response(
157
+ Response::builder()
158
+ .status(StatusCode::MOVED_PERMANENTLY)
159
+ .header(header::LOCATION, new_request_uri)
160
+ .body(Empty::new().map_err(|e| match e {}).boxed())?,
161
+ )
162
+ .build(),
163
+ );
164
+ }
165
+ }
166
+ Err(_) => {
167
+ let mut write_rwlock = self.cache.write().await;
168
+ write_rwlock.cleanup();
169
+ write_rwlock.insert(cache_key, false);
170
+ }
171
+ }
172
+ }
173
+ }
174
+ };
175
+ }
176
+ }
177
+ Ok(ResponseData::builder(request).build())
178
+ })
179
+ .await
180
+ }
181
+
182
+ async fn proxy_request_handler(
183
+ &mut self,
184
+ request: RequestData,
185
+ _config: &ServerConfig,
186
+ _socket_data: &SocketData,
187
+ _error_logger: &ErrorLogger,
188
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
189
+ Ok(ResponseData::builder(request).build())
190
+ }
191
+
192
+ async fn response_modifying_handler(
193
+ &mut self,
194
+ response: HyperResponse,
195
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
196
+ Ok(response)
197
+ }
198
+
199
+ async fn proxy_response_modifying_handler(
200
+ &mut self,
201
+ response: HyperResponse,
202
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
203
+ Ok(response)
204
+ }
205
+
206
+ async fn connect_proxy_request_handler(
207
+ &mut self,
208
+ _upgraded_request: HyperUpgraded,
209
+ _connect_address: &str,
210
+ _config: &ServerConfig,
211
+ _socket_data: &SocketData,
212
+ _error_logger: &ErrorLogger,
213
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
214
+ Ok(())
215
+ }
216
+
217
+ fn does_connect_proxy_requests(&mut self) -> bool {
218
+ false
219
+ }
220
+
221
+ async fn websocket_request_handler(
222
+ &mut self,
223
+ _websocket: HyperWebsocket,
224
+ _uri: &hyper::Uri,
225
+ _config: &ServerConfig,
226
+ _socket_data: &SocketData,
227
+ _error_logger: &ErrorLogger,
228
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
229
+ Ok(())
230
+ }
231
+
232
+ fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
233
+ false
234
+ }
235
+ }
ferron/src/modules/redirects.rs ADDED
@@ -0,0 +1,245 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use std::error::Error;
2
+
3
+ use crate::ferron_common::{
4
+ ErrorLogger, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
5
+ ServerModuleHandlers, SocketData,
6
+ };
7
+ use crate::ferron_common::{HyperUpgraded, WithRuntime};
8
+ use async_trait::async_trait;
9
+ use http_body_util::{BodyExt, Empty};
10
+ use hyper::{header, Response, StatusCode, Uri};
11
+ use hyper_tungstenite::HyperWebsocket;
12
+ use tokio::runtime::Handle;
13
+
14
+ struct RedirectsModule;
15
+
16
+ pub fn server_module_init(
17
+ ) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
18
+ Ok(Box::new(RedirectsModule::new()))
19
+ }
20
+
21
+ impl RedirectsModule {
22
+ fn new() -> Self {
23
+ Self
24
+ }
25
+ }
26
+
27
+ impl ServerModule for RedirectsModule {
28
+ fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
29
+ Box::new(RedirectsModuleHandlers { handle })
30
+ }
31
+ }
32
+ struct RedirectsModuleHandlers {
33
+ handle: Handle,
34
+ }
35
+
36
+ #[async_trait]
37
+ impl ServerModuleHandlers for RedirectsModuleHandlers {
38
+ async fn request_handler(
39
+ &mut self,
40
+ request: RequestData,
41
+ config: &ServerConfig,
42
+ socket_data: &SocketData,
43
+ _error_logger: &ErrorLogger,
44
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
45
+ WithRuntime::new(self.handle.clone(), async move {
46
+ let hyper_request = request.get_hyper_request();
47
+
48
+ if config["secure"].as_bool() == Some(true)
49
+ && !socket_data.encrypted
50
+ && config["disableNonEncryptedServer"].as_bool() != Some(true)
51
+ && config["disableToHTTPSRedirect"].as_bool() != Some(true)
52
+ {
53
+ let host_header_option = hyper_request.headers().get(header::HOST);
54
+ let host_header = match host_header_option {
55
+ Some(header_data) => header_data.to_str()?,
56
+ None => {
57
+ return Ok(
58
+ ResponseData::builder(request)
59
+ .status(StatusCode::BAD_REQUEST)
60
+ .build(),
61
+ )
62
+ }
63
+ };
64
+
65
+ let path_and_query_option = hyper_request.uri().path_and_query();
66
+ let path_and_query = match path_and_query_option {
67
+ Some(path_and_query) => path_and_query.to_string(),
68
+ None => {
69
+ return Ok(
70
+ ResponseData::builder(request)
71
+ .status(StatusCode::BAD_REQUEST)
72
+ .build(),
73
+ )
74
+ }
75
+ };
76
+
77
+ let mut parts: Vec<&str> = host_header.split(':').collect();
78
+
79
+ if parts.len() > 1
80
+ && !(parts[0].starts_with('[')
81
+ && parts
82
+ .last()
83
+ .map(|part| part.ends_with(']'))
84
+ .unwrap_or(false))
85
+ {
86
+ parts.pop();
87
+ }
88
+
89
+ let host_name = parts.join(":");
90
+
91
+ let new_uri = Uri::builder()
92
+ .scheme("https")
93
+ .authority(match config["sport"].as_i64() {
94
+ None | Some(443) => host_name,
95
+ Some(port) => format!("{}:{}", host_name, port),
96
+ })
97
+ .path_and_query(path_and_query)
98
+ .build()?;
99
+
100
+ return Ok(
101
+ ResponseData::builder(request)
102
+ .response(
103
+ Response::builder()
104
+ .status(StatusCode::MOVED_PERMANENTLY)
105
+ .header(header::LOCATION, new_uri.to_string())
106
+ .body(Empty::new().map_err(|e| match e {}).boxed())?,
107
+ )
108
+ .build(),
109
+ );
110
+ }
111
+
112
+ let domain_yaml = &config["domain"];
113
+ let domain = domain_yaml.as_str();
114
+
115
+ if let Some(domain) = domain {
116
+ if config["wwwredirect"].as_bool() == Some(true) {
117
+ // Even more code rewritten from SVR.JS...
118
+ if let Some(host_header_value) = hyper_request.headers().get(header::HOST) {
119
+ let host_header = host_header_value.to_str()?;
120
+
121
+ let path_and_query_option = hyper_request.uri().path_and_query();
122
+ let path_and_query = match path_and_query_option {
123
+ Some(path_and_query) => path_and_query.to_string(),
124
+ None => {
125
+ return Ok(
126
+ ResponseData::builder(request)
127
+ .status(StatusCode::BAD_REQUEST)
128
+ .build(),
129
+ )
130
+ }
131
+ };
132
+
133
+ let mut parts: Vec<&str> = host_header.split(':').collect();
134
+ let mut host_port: Option<&str> = None;
135
+
136
+ if parts.len() > 1
137
+ && !(parts[0].starts_with('[')
138
+ && parts
139
+ .last()
140
+ .map(|part| part.ends_with(']'))
141
+ .unwrap_or(false))
142
+ {
143
+ host_port = parts.pop();
144
+ }
145
+
146
+ let host_name = parts.join(":");
147
+
148
+ if host_name == domain && !host_name.starts_with("www.") {
149
+ let new_uri = Uri::builder()
150
+ .scheme(match socket_data.encrypted {
151
+ true => "https",
152
+ false => "http",
153
+ })
154
+ .authority(match host_port {
155
+ Some(port) => format!("www.{}:{}", host_name, port),
156
+ None => host_name,
157
+ })
158
+ .path_and_query(path_and_query)
159
+ .build()?;
160
+
161
+ return Ok(
162
+ ResponseData::builder(request)
163
+ .response(
164
+ Response::builder()
165
+ .status(StatusCode::MOVED_PERMANENTLY)
166
+ .header(header::LOCATION, new_uri.to_string())
167
+ .body(Empty::new().map_err(|e| match e {}).boxed())?,
168
+ )
169
+ .build(),
170
+ );
171
+ }
172
+ }
173
+ }
174
+ }
175
+
176
+ Ok(ResponseData::builder(request).build())
177
+ })
178
+ .await
179
+ }
180
+
181
+ async fn proxy_request_handler(
182
+ &mut self,
183
+ request: RequestData,
184
+ config: &ServerConfig,
185
+ socket_data: &SocketData,
186
+ _error_logger: &ErrorLogger,
187
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
188
+ if config["secure"].as_bool() == Some(true)
189
+ && !socket_data.encrypted
190
+ && config["disableNonEncryptedServer"].as_bool() != Some(true)
191
+ && config["disableToHTTPSRedirect"].as_bool() != Some(true)
192
+ {
193
+ return Ok(
194
+ ResponseData::builder(request)
195
+ .status(StatusCode::NOT_IMPLEMENTED)
196
+ .build(),
197
+ );
198
+ }
199
+ Ok(ResponseData::builder(request).build())
200
+ }
201
+
202
+ async fn response_modifying_handler(
203
+ &mut self,
204
+ response: HyperResponse,
205
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
206
+ Ok(response)
207
+ }
208
+
209
+ async fn proxy_response_modifying_handler(
210
+ &mut self,
211
+ response: HyperResponse,
212
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
213
+ Ok(response)
214
+ }
215
+
216
+ async fn connect_proxy_request_handler(
217
+ &mut self,
218
+ _upgraded_request: HyperUpgraded,
219
+ _connect_address: &str,
220
+ _config: &ServerConfig,
221
+ _socket_data: &SocketData,
222
+ _error_logger: &ErrorLogger,
223
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
224
+ Ok(())
225
+ }
226
+
227
+ fn does_connect_proxy_requests(&mut self) -> bool {
228
+ false
229
+ }
230
+
231
+ async fn websocket_request_handler(
232
+ &mut self,
233
+ _websocket: HyperWebsocket,
234
+ _uri: &hyper::Uri,
235
+ _config: &ServerConfig,
236
+ _socket_data: &SocketData,
237
+ _error_logger: &ErrorLogger,
238
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
239
+ Ok(())
240
+ }
241
+
242
+ fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
243
+ false
244
+ }
245
+ }
ferron/src/modules/static_file_serving.rs ADDED
@@ -0,0 +1,928 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use std::error::Error;
2
+ use std::fmt::Write;
3
+ use std::io::SeekFrom;
4
+ use std::path::{Path, PathBuf};
5
+ use std::str::FromStr;
6
+ use std::sync::Arc;
7
+ use std::time::Duration;
8
+
9
+ use crate::ferron_common::{
10
+ ErrorLogger, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
11
+ ServerModuleHandlers, SocketData,
12
+ };
13
+ use crate::ferron_common::{HyperUpgraded, WithRuntime};
14
+ use async_compression::tokio::bufread::{BrotliEncoder, DeflateEncoder, GzipEncoder, ZstdEncoder};
15
+ use async_compression::zstd::CParameter;
16
+ use async_compression::Level;
17
+ use async_trait::async_trait;
18
+ use chrono::offset::Local;
19
+ use chrono::DateTime;
20
+ use futures_util::TryStreamExt;
21
+ use hashlink::LruCache;
22
+ use http::HeaderValue;
23
+ use http_body_util::{BodyExt, Empty, Full, StreamBody};
24
+ use hyper::body::Bytes;
25
+ use hyper::{body::Frame, Response, StatusCode};
26
+ use hyper::{header, HeaderMap, Method};
27
+ use hyper_tungstenite::HyperWebsocket;
28
+ use sha2::{Digest, Sha256};
29
+ use tokio::fs;
30
+ use tokio::io::{AsyncReadExt, AsyncSeekExt, BufReader};
31
+ use tokio::runtime::Handle;
32
+ use tokio::sync::RwLock;
33
+ use tokio_util::io::ReaderStream;
34
+
35
+ use crate::ferron_util::generate_directory_listing::generate_directory_listing;
36
+ use crate::ferron_util::ttl_cache::TtlCache;
37
+
38
+ pub fn server_module_init(
39
+ ) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
40
+ let pathbuf_cache = Arc::new(RwLock::new(TtlCache::new(Duration::from_millis(100))));
41
+ let etag_cache = Arc::new(RwLock::new(LruCache::new(1000)));
42
+ Ok(Box::new(StaticFileServingModule::new(
43
+ pathbuf_cache,
44
+ etag_cache,
45
+ )))
46
+ }
47
+
48
+ struct StaticFileServingModule {
49
+ pathbuf_cache: Arc<RwLock<TtlCache<String, PathBuf>>>,
50
+ etag_cache: Arc<RwLock<LruCache<String, String>>>,
51
+ }
52
+
53
+ impl StaticFileServingModule {
54
+ fn new(
55
+ pathbuf_cache: Arc<RwLock<TtlCache<String, PathBuf>>>,
56
+ etag_cache: Arc<RwLock<LruCache<String, String>>>,
57
+ ) -> Self {
58
+ Self {
59
+ pathbuf_cache,
60
+ etag_cache,
61
+ }
62
+ }
63
+ }
64
+
65
+ impl ServerModule for StaticFileServingModule {
66
+ fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
67
+ Box::new(StaticFileServingModuleHandlers {
68
+ pathbuf_cache: self.pathbuf_cache.clone(),
69
+ etag_cache: self.etag_cache.clone(),
70
+ handle,
71
+ })
72
+ }
73
+ }
74
+ struct StaticFileServingModuleHandlers {
75
+ pathbuf_cache: Arc<RwLock<TtlCache<String, PathBuf>>>,
76
+ etag_cache: Arc<RwLock<LruCache<String, String>>>,
77
+ handle: Handle,
78
+ }
79
+
80
+ fn parse_range_header(range_str: &str, default_end: u64) -> Option<(u64, u64)> {
81
+ if let Some(range_part) = range_str.strip_prefix("bytes=") {
82
+ let parts: Vec<&str> = range_part.split('-').collect();
83
+ if parts.len() == 2 {
84
+ if parts[0].is_empty() {
85
+ if let Ok(end) = u64::from_str(parts[1]) {
86
+ return Some((default_end - end + 1, default_end));
87
+ }
88
+ } else if parts[1].is_empty() {
89
+ if let Ok(start) = u64::from_str(parts[0]) {
90
+ return Some((start, default_end));
91
+ }
92
+ } else if !parts[0].is_empty() && !parts[1].is_empty() {
93
+ if let (Ok(start), Ok(end)) = (u64::from_str(parts[0]), u64::from_str(parts[1])) {
94
+ return Some((start, end));
95
+ }
96
+ }
97
+ }
98
+ }
99
+ None
100
+ }
101
+
102
+ fn extract_etag_inner(input: &str) -> Option<String> {
103
+ // Remove the surrounding double quotes
104
+ let trimmed = input.trim_matches('"');
105
+
106
+ // Split the string at the hyphen and take the first part
107
+ let parts: Vec<&str> = trimmed.split('-').collect();
108
+ if parts.is_empty() {
109
+ None
110
+ } else {
111
+ Some(parts[0].to_string())
112
+ }
113
+ }
114
+
115
+ #[async_trait]
116
+ impl ServerModuleHandlers for StaticFileServingModuleHandlers {
117
+ async fn request_handler(
118
+ &mut self,
119
+ request: RequestData,
120
+ config: &ServerConfig,
121
+ _socket_data: &SocketData,
122
+ _error_logger: &ErrorLogger,
123
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
124
+ WithRuntime::new(self.handle.clone(), async move {
125
+ if let Some(wwwroot) = config["wwwroot"].as_str() {
126
+ let hyper_request = request.get_hyper_request();
127
+ let request_path = hyper_request.uri().path();
128
+ let mut request_path_bytes = request_path.bytes();
129
+ if request_path_bytes.len() < 1 || request_path_bytes.nth(0) != Some(b'/') {
130
+ return Ok(
131
+ ResponseData::builder(request)
132
+ .status(StatusCode::BAD_REQUEST)
133
+ .build(),
134
+ );
135
+ }
136
+
137
+ let original_request_path = request
138
+ .get_original_url()
139
+ .map_or(request_path, |u| u.path());
140
+
141
+ let cache_key = format!(
142
+ "{}{}{}",
143
+ match config["ip"].as_str() {
144
+ Some(ip) => format!("{}-", ip),
145
+ None => String::from(""),
146
+ },
147
+ match config["domain"].as_str() {
148
+ Some(domain) => format!("{}-", domain),
149
+ None => String::from(""),
150
+ },
151
+ request_path
152
+ );
153
+
154
+ let rwlock_read = self.pathbuf_cache.read().await;
155
+ let joined_pathbuf_option = rwlock_read.get(&cache_key);
156
+ drop(rwlock_read);
157
+
158
+ let joined_pathbuf_cached = joined_pathbuf_option.is_some();
159
+ let mut joined_pathbuf = match joined_pathbuf_option {
160
+ Some(joined_pathbuf) => joined_pathbuf,
161
+ None => {
162
+ let path = Path::new(wwwroot);
163
+ let mut relative_path = &request_path[1..];
164
+ while relative_path.as_bytes().first().copied() == Some(b'/') {
165
+ relative_path = &relative_path[1..];
166
+ }
167
+
168
+ let decoded_relative_path = match urlencoding::decode(relative_path) {
169
+ Ok(path) => path.to_string(),
170
+ Err(_) => {
171
+ return Ok(
172
+ ResponseData::builder(request)
173
+ .status(StatusCode::BAD_REQUEST)
174
+ .build(),
175
+ );
176
+ }
177
+ };
178
+
179
+ path.join(decoded_relative_path)
180
+ }
181
+ };
182
+
183
+ match fs::metadata(&joined_pathbuf).await {
184
+ Ok(mut metadata) => {
185
+ if !joined_pathbuf_cached {
186
+ if metadata.is_dir() {
187
+ let indexes = vec!["index.html", "index.htm", "index.xhtml"];
188
+ for index in indexes {
189
+ let temp_joined_pathbuf = joined_pathbuf.join(index);
190
+ match fs::metadata(&temp_joined_pathbuf).await {
191
+ Ok(temp_metadata) => {
192
+ if temp_metadata.is_file() {
193
+ metadata = temp_metadata;
194
+ joined_pathbuf = temp_joined_pathbuf;
195
+ break;
196
+ }
197
+ }
198
+ Err(err) => match err.kind() {
199
+ tokio::io::ErrorKind::NotFound | tokio::io::ErrorKind::NotADirectory => {
200
+ continue;
201
+ }
202
+ tokio::io::ErrorKind::PermissionDenied => {
203
+ return Ok(
204
+ ResponseData::builder(request)
205
+ .status(StatusCode::FORBIDDEN)
206
+ .build(),
207
+ );
208
+ }
209
+ _ => Err(err)?,
210
+ },
211
+ };
212
+ }
213
+ }
214
+ let mut rwlock_write = self.pathbuf_cache.write().await;
215
+ rwlock_write.cleanup();
216
+ rwlock_write.insert(cache_key, joined_pathbuf.clone());
217
+ drop(rwlock_write);
218
+ }
219
+
220
+ if metadata.is_file() {
221
+ // Check if compression is possible at all
222
+ let mut compression_possible = false;
223
+
224
+ if config["enableCompression"].as_bool() != Some(false) {
225
+ // A hard-coded list of non-compressible file extension
226
+ let non_compressible_file_extensions = vec![
227
+ "7z",
228
+ "air",
229
+ "amlx",
230
+ "apk",
231
+ "apng",
232
+ "appinstaller",
233
+ "appx",
234
+ "appxbundle",
235
+ "arj",
236
+ "au",
237
+ "avif",
238
+ "bdoc",
239
+ "boz",
240
+ "br",
241
+ "bz",
242
+ "bz2",
243
+ "caf",
244
+ "class",
245
+ "doc",
246
+ "docx",
247
+ "dot",
248
+ "dvi",
249
+ "ear",
250
+ "epub",
251
+ "flv",
252
+ "gdoc",
253
+ "gif",
254
+ "gsheet",
255
+ "gslides",
256
+ "gz",
257
+ "iges",
258
+ "igs",
259
+ "jar",
260
+ "jnlp",
261
+ "jp2",
262
+ "jpe",
263
+ "jpeg",
264
+ "jpf",
265
+ "jpg",
266
+ "jpg2",
267
+ "jpgm",
268
+ "jpm",
269
+ "jpx",
270
+ "kmz",
271
+ "latex",
272
+ "m1v",
273
+ "m2a",
274
+ "m2v",
275
+ "m3a",
276
+ "m4a",
277
+ "mesh",
278
+ "mk3d",
279
+ "mks",
280
+ "mkv",
281
+ "mov",
282
+ "mp2",
283
+ "mp2a",
284
+ "mp3",
285
+ "mp4",
286
+ "mp4a",
287
+ "mp4v",
288
+ "mpe",
289
+ "mpeg",
290
+ "mpg",
291
+ "mpg4",
292
+ "mpga",
293
+ "msg",
294
+ "msh",
295
+ "msix",
296
+ "msixbundle",
297
+ "odg",
298
+ "odp",
299
+ "ods",
300
+ "odt",
301
+ "oga",
302
+ "ogg",
303
+ "ogv",
304
+ "ogx",
305
+ "opus",
306
+ "p12",
307
+ "pdf",
308
+ "pfx",
309
+ "pgp",
310
+ "pkpass",
311
+ "png",
312
+ "pot",
313
+ "pps",
314
+ "ppt",
315
+ "pptx",
316
+ "qt",
317
+ "ser",
318
+ "silo",
319
+ "sit",
320
+ "snd",
321
+ "spx",
322
+ "stpxz",
323
+ "stpz",
324
+ "swf",
325
+ "tif",
326
+ "tiff",
327
+ "ubj",
328
+ "usdz",
329
+ "vbox-extpack",
330
+ "vrml",
331
+ "war",
332
+ "wav",
333
+ "weba",
334
+ "webm",
335
+ "wmv",
336
+ "wrl",
337
+ "x3dbz",
338
+ "x3dvz",
339
+ "xla",
340
+ "xlc",
341
+ "xlm",
342
+ "xls",
343
+ "xlsx",
344
+ "xlt",
345
+ "xlw",
346
+ "xpi",
347
+ "xps",
348
+ "zip",
349
+ "zst",
350
+ ];
351
+ let file_extension = joined_pathbuf
352
+ .extension()
353
+ .map_or_else(|| "".to_string(), |ext| ext.to_string_lossy().to_string());
354
+ let file_extension_compressible =
355
+ !non_compressible_file_extensions.contains(&(&file_extension as &str));
356
+
357
+ if metadata.len() > 256 && file_extension_compressible {
358
+ compression_possible = true;
359
+ }
360
+ }
361
+
362
+ let vary;
363
+
364
+ // Handle ETags
365
+ let mut etag_option = None;
366
+ if config["enableETag"].as_bool() != Some(false) {
367
+ let etag_cache_key = format!(
368
+ "{}-{}-{}",
369
+ joined_pathbuf.to_string_lossy(),
370
+ metadata.len(),
371
+ match metadata.modified() {
372
+ Ok(mtime) => {
373
+ let datetime: DateTime<Local> = mtime.into();
374
+ datetime.format("%Y-%m-%d %H:%M:%S").to_string()
375
+ }
376
+ Err(_) => String::from(""),
377
+ }
378
+ );
379
+ let rwlock_read = self.etag_cache.read().await;
380
+ // Had to use "peek", since "get" would mutate the LRU cache
381
+ let etag_locked_option = rwlock_read.peek(&etag_cache_key).cloned();
382
+ drop(rwlock_read);
383
+ let etag = match etag_locked_option {
384
+ Some(etag) => etag,
385
+ None => {
386
+ let etag_cache_key_clone = etag_cache_key.clone();
387
+ let etag = tokio::task::spawn_blocking(move || {
388
+ let mut hasher = Sha256::new();
389
+ hasher.update(etag_cache_key_clone);
390
+ hasher
391
+ .finalize()
392
+ .iter()
393
+ .fold(String::new(), |mut output, b| {
394
+ let _ = write!(output, "{b:02x}");
395
+ output
396
+ })
397
+ })
398
+ .await?;
399
+
400
+ let mut rwlock_write = self.etag_cache.write().await;
401
+ rwlock_write.insert(etag_cache_key, etag.clone());
402
+ drop(rwlock_write);
403
+
404
+ etag
405
+ }
406
+ };
407
+
408
+ vary = if compression_possible {
409
+ "Accept-Encoding, If-Match, If-None-Match, Range"
410
+ } else {
411
+ "If-Match, If-None-Match, Range"
412
+ };
413
+
414
+ if let Some(if_none_match_value) =
415
+ hyper_request.headers().get(header::IF_NONE_MATCH)
416
+ {
417
+ match if_none_match_value.to_str() {
418
+ Ok(if_none_match) => {
419
+ if let Some(etag_extracted) = extract_etag_inner(if_none_match) {
420
+ if etag_extracted == etag {
421
+ let etag_original = if_none_match.to_string();
422
+ return Ok(
423
+ ResponseData::builder(request)
424
+ .response(
425
+ Response::builder()
426
+ .status(StatusCode::NOT_MODIFIED)
427
+ .header(header::ETAG, etag_original)
428
+ .header(header::VARY, vary)
429
+ .body(Empty::new().map_err(|e| match e {}).boxed())?,
430
+ )
431
+ .build(),
432
+ );
433
+ }
434
+ }
435
+ }
436
+ Err(_) => {
437
+ let mut header_map = HeaderMap::new();
438
+ if let Ok(vary) = HeaderValue::from_str(vary) {
439
+ header_map.insert(header::VARY, vary);
440
+ }
441
+ return Ok(
442
+ ResponseData::builder(request)
443
+ .status(StatusCode::BAD_REQUEST)
444
+ .headers(header_map)
445
+ .build(),
446
+ );
447
+ }
448
+ }
449
+ }
450
+
451
+ if let Some(if_match_value) = hyper_request.headers().get(header::IF_MATCH) {
452
+ match if_match_value.to_str() {
453
+ Ok(if_match) => {
454
+ if if_match != "*" {
455
+ if let Some(etag_extracted) = extract_etag_inner(if_match) {
456
+ if etag_extracted != etag {
457
+ let mut header_map = HeaderMap::new();
458
+ header_map.insert(header::ETAG, if_match_value.clone());
459
+ if let Ok(vary) = HeaderValue::from_str(vary) {
460
+ header_map.insert(header::VARY, vary);
461
+ }
462
+ return Ok(
463
+ ResponseData::builder(request)
464
+ .status(StatusCode::PRECONDITION_FAILED)
465
+ .headers(header_map)
466
+ .build(),
467
+ );
468
+ }
469
+ }
470
+ }
471
+ }
472
+ Err(_) => {
473
+ let mut header_map = HeaderMap::new();
474
+ if let Ok(vary) = HeaderValue::from_str(vary) {
475
+ header_map.insert(header::VARY, vary);
476
+ }
477
+ return Ok(
478
+ ResponseData::builder(request)
479
+ .status(StatusCode::BAD_REQUEST)
480
+ .headers(header_map)
481
+ .build(),
482
+ );
483
+ }
484
+ }
485
+ }
486
+ etag_option = Some(etag);
487
+ } else {
488
+ vary = if compression_possible {
489
+ "Accept-Encoding, Range"
490
+ } else {
491
+ "Range"
492
+ };
493
+ }
494
+
495
+ let content_type_option = new_mime_guess::from_path(&joined_pathbuf)
496
+ .first()
497
+ .map(|mime_type| mime_type.to_string());
498
+
499
+ let range_header = match hyper_request.headers().get(header::RANGE) {
500
+ Some(value) => match value.to_str() {
501
+ Ok(value) => Some(value),
502
+ Err(_) => {
503
+ let mut header_map = HeaderMap::new();
504
+ if let Ok(vary) = HeaderValue::from_str(vary) {
505
+ header_map.insert(header::VARY, vary);
506
+ }
507
+ return Ok(
508
+ ResponseData::builder(request)
509
+ .status(StatusCode::BAD_REQUEST)
510
+ .headers(header_map)
511
+ .build(),
512
+ );
513
+ }
514
+ },
515
+ None => None,
516
+ };
517
+
518
+ if let Some(range_header) = range_header {
519
+ let file_length = metadata.len();
520
+ if file_length == 0 {
521
+ let mut header_map = HeaderMap::new();
522
+ if let Ok(vary) = HeaderValue::from_str(vary) {
523
+ header_map.insert(header::VARY, vary);
524
+ }
525
+ return Ok(
526
+ ResponseData::builder(request)
527
+ .status(StatusCode::RANGE_NOT_SATISFIABLE)
528
+ .headers(header_map)
529
+ .build(),
530
+ );
531
+ }
532
+ if let Some((range_begin, range_end)) =
533
+ parse_range_header(range_header, file_length - 1)
534
+ {
535
+ if range_end > file_length - 1
536
+ || range_begin > file_length - 1
537
+ || range_begin > range_end
538
+ {
539
+ let mut header_map = HeaderMap::new();
540
+ if let Ok(vary) = HeaderValue::from_str(vary) {
541
+ header_map.insert(header::VARY, vary);
542
+ }
543
+ return Ok(
544
+ ResponseData::builder(request)
545
+ .status(StatusCode::RANGE_NOT_SATISFIABLE)
546
+ .headers(header_map)
547
+ .build(),
548
+ );
549
+ }
550
+
551
+ let request_method = hyper_request.method();
552
+ let content_length = range_end - range_begin + 1;
553
+
554
+ // Build response
555
+ let mut response_builder = Response::builder()
556
+ .status(StatusCode::PARTIAL_CONTENT)
557
+ .header(header::CONTENT_LENGTH, content_length)
558
+ .header(
559
+ header::CONTENT_RANGE,
560
+ format!("bytes {}-{}/{}", range_begin, range_end, file_length),
561
+ );
562
+
563
+ if let Some(etag) = etag_option {
564
+ response_builder = response_builder.header(header::ETAG, etag);
565
+ }
566
+
567
+ if let Some(content_type) = content_type_option {
568
+ response_builder = response_builder.header(header::CONTENT_TYPE, content_type);
569
+ }
570
+
571
+ response_builder = response_builder.header(header::VARY, vary);
572
+
573
+ let response = match request_method {
574
+ &Method::HEAD => {
575
+ response_builder.body(Empty::new().map_err(|e| match e {}).boxed())?
576
+ }
577
+ _ => {
578
+ // Open file for reading
579
+ let mut file = match fs::File::open(joined_pathbuf).await {
580
+ Ok(file) => file,
581
+ Err(err) => match err.kind() {
582
+ tokio::io::ErrorKind::NotFound | tokio::io::ErrorKind::NotADirectory => {
583
+ return Ok(
584
+ ResponseData::builder(request)
585
+ .status(StatusCode::NOT_FOUND)
586
+ .build(),
587
+ );
588
+ }
589
+ tokio::io::ErrorKind::PermissionDenied => {
590
+ return Ok(
591
+ ResponseData::builder(request)
592
+ .status(StatusCode::FORBIDDEN)
593
+ .build(),
594
+ );
595
+ }
596
+ _ => Err(err)?,
597
+ },
598
+ };
599
+
600
+ // Seek and limit the file reader
601
+ file.seek(SeekFrom::Start(range_begin)).await?;
602
+ let file_limited = file.take(content_length);
603
+
604
+ // Use BufReader for better performance.
605
+ let file_bufreader = BufReader::with_capacity(12800, file_limited);
606
+
607
+ // Construct a boxed body
608
+ let reader_stream = ReaderStream::new(file_bufreader);
609
+ let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
610
+ let boxed_body = stream_body.boxed();
611
+
612
+ response_builder.body(boxed_body)?
613
+ }
614
+ };
615
+
616
+ return Ok(ResponseData::builder(request).response(response).build());
617
+ } else {
618
+ let mut header_map = HeaderMap::new();
619
+ if let Ok(vary) = HeaderValue::from_str(vary) {
620
+ header_map.insert(header::VARY, vary);
621
+ }
622
+
623
+ return Ok(
624
+ ResponseData::builder(request)
625
+ .status(StatusCode::RANGE_NOT_SATISFIABLE)
626
+ .headers(header_map)
627
+ .build(),
628
+ );
629
+ }
630
+ } else {
631
+ let mut use_gzip = false;
632
+ let mut use_deflate = false;
633
+ let mut use_brotli = false;
634
+ let mut use_zstd = false;
635
+
636
+ if compression_possible {
637
+ let user_agent = match hyper_request.headers().get(header::USER_AGENT) {
638
+ Some(user_agent_value) => user_agent_value.to_str().unwrap_or_default(),
639
+ None => "",
640
+ };
641
+
642
+ // Some web browsers have broken HTTP compression handling
643
+ let is_netscape_4_broken_html_compression = user_agent.starts_with("Mozilla/4.");
644
+ let is_netscape_4_broken_compression = match user_agent.strip_prefix("Mozilla/4.")
645
+ {
646
+ Some(stripped_user_agent) => matches!(
647
+ stripped_user_agent.chars().nth(0),
648
+ Some('6') | Some('7') | Some('8')
649
+ ),
650
+ None => false,
651
+ };
652
+ let is_w3m_broken_html_compression = user_agent.starts_with("w3m/");
653
+ if !(content_type_option == Some("text/html".to_string())
654
+ && (is_netscape_4_broken_html_compression || is_w3m_broken_html_compression))
655
+ && !is_netscape_4_broken_compression
656
+ {
657
+ let accept_encoding = match hyper_request.headers().get(header::ACCEPT_ENCODING)
658
+ {
659
+ Some(header_value) => header_value.to_str().unwrap_or_default(),
660
+ None => "",
661
+ };
662
+
663
+ // Checking the Accept-Encoding header naively...
664
+ if accept_encoding.contains("br") {
665
+ use_brotli = true;
666
+ } else if accept_encoding.contains("zstd") {
667
+ use_zstd = true;
668
+ } else if accept_encoding.contains("deflate") {
669
+ use_deflate = true;
670
+ } else if accept_encoding.contains("gzip") {
671
+ use_gzip = true;
672
+ }
673
+ }
674
+ }
675
+
676
+ let request_method = hyper_request.method();
677
+ let content_length = metadata.len();
678
+
679
+ // Build response
680
+ let mut response_builder = Response::builder()
681
+ .status(StatusCode::OK)
682
+ .header(header::ACCEPT_RANGES, "bytes");
683
+
684
+ if let Some(etag) = etag_option {
685
+ if use_brotli {
686
+ response_builder =
687
+ response_builder.header(header::ETAG, format!("\"{}-br\"", etag));
688
+ } else if use_zstd {
689
+ response_builder =
690
+ response_builder.header(header::ETAG, format!("\"{}-zstd\"", etag));
691
+ } else if use_deflate {
692
+ response_builder =
693
+ response_builder.header(header::ETAG, format!("\"{}-deflate\"", etag));
694
+ } else if use_gzip {
695
+ response_builder =
696
+ response_builder.header(header::ETAG, format!("\"{}-gzip\"", etag));
697
+ } else {
698
+ response_builder =
699
+ response_builder.header(header::ETAG, format!("\"{}\"", etag));
700
+ }
701
+ }
702
+
703
+ response_builder = response_builder.header(header::VARY, vary);
704
+
705
+ if let Some(content_type) = content_type_option {
706
+ response_builder = response_builder.header(header::CONTENT_TYPE, content_type);
707
+ }
708
+
709
+ if use_brotli {
710
+ response_builder = response_builder.header(header::CONTENT_ENCODING, "br");
711
+ } else if use_zstd {
712
+ response_builder = response_builder.header(header::CONTENT_ENCODING, "zstd");
713
+ } else if use_deflate {
714
+ response_builder = response_builder.header(header::CONTENT_ENCODING, "deflate");
715
+ } else if use_gzip {
716
+ response_builder = response_builder.header(header::CONTENT_ENCODING, "gzip");
717
+ } else {
718
+ // Content-Length header + HTTP compression = broken HTTP responses!
719
+ response_builder =
720
+ response_builder.header(header::CONTENT_LENGTH, content_length);
721
+ }
722
+
723
+ let response = match request_method {
724
+ &Method::HEAD => {
725
+ response_builder.body(Empty::new().map_err(|e| match e {}).boxed())?
726
+ }
727
+ _ => {
728
+ // Open file for reading
729
+ let file = match fs::File::open(joined_pathbuf).await {
730
+ Ok(file) => file,
731
+ Err(err) => match err.kind() {
732
+ tokio::io::ErrorKind::NotFound | tokio::io::ErrorKind::NotADirectory => {
733
+ return Ok(
734
+ ResponseData::builder(request)
735
+ .status(StatusCode::NOT_FOUND)
736
+ .build(),
737
+ );
738
+ }
739
+ tokio::io::ErrorKind::PermissionDenied => {
740
+ return Ok(
741
+ ResponseData::builder(request)
742
+ .status(StatusCode::FORBIDDEN)
743
+ .build(),
744
+ );
745
+ }
746
+ _ => Err(err)?,
747
+ },
748
+ };
749
+
750
+ // Use BufReader for better performance.
751
+ let file_bufreader = BufReader::with_capacity(12800, file);
752
+
753
+ // Construct a boxed body
754
+ let boxed_body = if use_brotli {
755
+ // Brotli compression quality of 4
756
+ let reader_stream = ReaderStream::new(BrotliEncoder::with_quality(
757
+ file_bufreader,
758
+ Level::Precise(4),
759
+ ));
760
+ let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
761
+ stream_body.boxed()
762
+ } else if use_zstd {
763
+ // Limit the Zstandard window size to 128K (2^17 bytes) to support many HTTP clients
764
+ let reader_stream = ReaderStream::new(ZstdEncoder::with_quality_and_params(
765
+ file_bufreader,
766
+ Level::Default,
767
+ &[CParameter::window_log(17)],
768
+ ));
769
+ let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
770
+ stream_body.boxed()
771
+ } else if use_deflate {
772
+ let reader_stream = ReaderStream::new(DeflateEncoder::new(file_bufreader));
773
+ let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
774
+ stream_body.boxed()
775
+ } else if use_gzip {
776
+ let reader_stream = ReaderStream::new(GzipEncoder::new(file_bufreader));
777
+ let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
778
+ stream_body.boxed()
779
+ } else {
780
+ let reader_stream = ReaderStream::new(file_bufreader);
781
+ let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
782
+ stream_body.boxed()
783
+ };
784
+
785
+ response_builder.body(boxed_body)?
786
+ }
787
+ };
788
+
789
+ return Ok(ResponseData::builder(request).response(response).build());
790
+ }
791
+ } else if metadata.is_dir() {
792
+ if config["enableDirectoryListing"].as_bool() == Some(true) {
793
+ let joined_maindesc_pathbuf = joined_pathbuf.join(".maindesc");
794
+ let directory = match fs::read_dir(joined_pathbuf).await {
795
+ Ok(directory) => directory,
796
+ Err(err) => match err.kind() {
797
+ tokio::io::ErrorKind::NotFound => {
798
+ return Ok(
799
+ ResponseData::builder(request)
800
+ .status(StatusCode::NOT_FOUND)
801
+ .build(),
802
+ );
803
+ }
804
+ tokio::io::ErrorKind::PermissionDenied => {
805
+ return Ok(
806
+ ResponseData::builder(request)
807
+ .status(StatusCode::FORBIDDEN)
808
+ .build(),
809
+ );
810
+ }
811
+ _ => Err(err)?,
812
+ },
813
+ };
814
+
815
+ let description = (fs::read_to_string(joined_maindesc_pathbuf).await).ok();
816
+
817
+ let directory_listing_html =
818
+ generate_directory_listing(directory, original_request_path, description).await?;
819
+ let content_length: Option<u64> = directory_listing_html.len().try_into().ok();
820
+
821
+ let mut response_builder = Response::builder().status(StatusCode::OK);
822
+
823
+ if let Some(content_length) = content_length {
824
+ response_builder = response_builder.header(header::CONTENT_LENGTH, content_length)
825
+ }
826
+ response_builder = response_builder.header(header::CONTENT_TYPE, "text/html");
827
+
828
+ let response = response_builder.body(
829
+ Full::new(Bytes::from(directory_listing_html))
830
+ .map_err(|e| match e {})
831
+ .boxed(),
832
+ )?;
833
+
834
+ return Ok(ResponseData::builder(request).response(response).build());
835
+ } else {
836
+ return Ok(
837
+ ResponseData::builder(request)
838
+ .status(StatusCode::FORBIDDEN)
839
+ .build(),
840
+ );
841
+ }
842
+ } else {
843
+ return Ok(
844
+ ResponseData::builder(request)
845
+ .status(StatusCode::NOT_IMPLEMENTED)
846
+ .build(),
847
+ );
848
+ }
849
+ }
850
+ Err(err) => match err.kind() {
851
+ tokio::io::ErrorKind::NotFound | tokio::io::ErrorKind::NotADirectory => {
852
+ return Ok(
853
+ ResponseData::builder(request)
854
+ .status(StatusCode::NOT_FOUND)
855
+ .build(),
856
+ );
857
+ }
858
+ tokio::io::ErrorKind::PermissionDenied => {
859
+ return Ok(
860
+ ResponseData::builder(request)
861
+ .status(StatusCode::FORBIDDEN)
862
+ .build(),
863
+ );
864
+ }
865
+ _ => Err(err)?,
866
+ },
867
+ }
868
+ }
869
+
870
+ Ok(ResponseData::builder(request).build())
871
+ })
872
+ .await
873
+ }
874
+
875
+ async fn proxy_request_handler(
876
+ &mut self,
877
+ request: RequestData,
878
+ _config: &ServerConfig,
879
+ _socket_data: &SocketData,
880
+ _error_logger: &ErrorLogger,
881
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
882
+ Ok(ResponseData::builder(request).build())
883
+ }
884
+
885
+ async fn response_modifying_handler(
886
+ &mut self,
887
+ response: HyperResponse,
888
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
889
+ Ok(response)
890
+ }
891
+
892
+ async fn proxy_response_modifying_handler(
893
+ &mut self,
894
+ response: HyperResponse,
895
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
896
+ Ok(response)
897
+ }
898
+
899
+ async fn connect_proxy_request_handler(
900
+ &mut self,
901
+ _upgraded_request: HyperUpgraded,
902
+ _connect_address: &str,
903
+ _config: &ServerConfig,
904
+ _socket_data: &SocketData,
905
+ _error_logger: &ErrorLogger,
906
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
907
+ Ok(())
908
+ }
909
+
910
+ fn does_connect_proxy_requests(&mut self) -> bool {
911
+ false
912
+ }
913
+
914
+ async fn websocket_request_handler(
915
+ &mut self,
916
+ _websocket: HyperWebsocket,
917
+ _uri: &hyper::Uri,
918
+ _config: &ServerConfig,
919
+ _socket_data: &SocketData,
920
+ _error_logger: &ErrorLogger,
921
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
922
+ Ok(())
923
+ }
924
+
925
+ fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
926
+ false
927
+ }
928
+ }
ferron/src/modules/url_rewrite.rs ADDED
@@ -0,0 +1,350 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use std::error::Error;
2
+ use std::path::Path;
3
+ use std::sync::Arc;
4
+
5
+ use crate::ferron_util::ip_match::ip_match;
6
+ use crate::ferron_util::match_hostname::match_hostname;
7
+ use crate::ferron_util::match_location::match_location;
8
+ use crate::ferron_util::url_rewrite_structs::{
9
+ UrlRewriteMapEntry, UrlRewriteMapLocationWrap, UrlRewriteMapWrap,
10
+ };
11
+
12
+ use crate::ferron_common::{
13
+ ErrorLogger, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
14
+ ServerModuleHandlers, SocketData,
15
+ };
16
+ use crate::ferron_common::{HyperUpgraded, WithRuntime};
17
+ use async_trait::async_trait;
18
+ use fancy_regex::RegexBuilder;
19
+ use hyper::{header, Request, StatusCode};
20
+ use hyper_tungstenite::HyperWebsocket;
21
+ use tokio::fs;
22
+ use tokio::runtime::Handle;
23
+ use yaml_rust2::Yaml;
24
+
25
+ fn url_rewrite_config_init(rewrite_map: &[Yaml]) -> Result<Vec<UrlRewriteMapEntry>, anyhow::Error> {
26
+ let rewrite_map_iter = rewrite_map.iter();
27
+ let mut rewrite_map_vec = Vec::new();
28
+ for rewrite_map_entry in rewrite_map_iter {
29
+ let regex_str = match rewrite_map_entry["regex"].as_str() {
30
+ Some(regex_str) => regex_str,
31
+ None => return Err(anyhow::anyhow!("Invalid URL rewrite regular expression")),
32
+ };
33
+ let regex = match RegexBuilder::new(regex_str)
34
+ .case_insensitive(cfg!(windows))
35
+ .build()
36
+ {
37
+ Ok(regex) => regex,
38
+ Err(err) => {
39
+ return Err(anyhow::anyhow!(
40
+ "Invalid URL rewrite regular expression: {}",
41
+ err.to_string()
42
+ ))
43
+ }
44
+ };
45
+ let replacement = match rewrite_map_entry["replacement"].as_str() {
46
+ Some(replacement) => String::from(replacement),
47
+ None => return Err(anyhow::anyhow!("URL rewrite rules must have replacements")),
48
+ };
49
+ let is_not_file = rewrite_map_entry["isNotFile"].as_bool().unwrap_or(false);
50
+ let is_not_directory = rewrite_map_entry["isNotDirectory"]
51
+ .as_bool()
52
+ .unwrap_or(false);
53
+ let last = rewrite_map_entry["last"].as_bool().unwrap_or_default();
54
+ let allow_double_slashes = rewrite_map_entry["allowDoubleSlashes"]
55
+ .as_bool()
56
+ .unwrap_or(false);
57
+ rewrite_map_vec.push(UrlRewriteMapEntry::new(
58
+ regex,
59
+ replacement,
60
+ is_not_directory,
61
+ is_not_file,
62
+ last,
63
+ allow_double_slashes,
64
+ ));
65
+ }
66
+
67
+ Ok(rewrite_map_vec)
68
+ }
69
+
70
+ pub fn server_module_init(
71
+ config: &ServerConfig,
72
+ ) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
73
+ let mut global_url_rewrite_map = Vec::new();
74
+ let mut host_url_rewrite_maps = Vec::new();
75
+ if let Some(rewrite_map_yaml) = config["global"]["rewriteMap"].as_vec() {
76
+ global_url_rewrite_map = url_rewrite_config_init(rewrite_map_yaml)?;
77
+ }
78
+
79
+ if let Some(hosts) = config["hosts"].as_vec() {
80
+ for host_yaml in hosts.iter() {
81
+ let domain = host_yaml["domain"].as_str().map(String::from);
82
+ let ip = host_yaml["ip"].as_str().map(String::from);
83
+ let mut locations = Vec::new();
84
+ if let Some(locations_yaml) = host_yaml["locations"].as_vec() {
85
+ for location_yaml in locations_yaml.iter() {
86
+ if let Some(path_str) = location_yaml["path"].as_str() {
87
+ let path = String::from(path_str);
88
+ if let Some(rewrite_map_yaml) = location_yaml["rewriteMap"].as_vec() {
89
+ locations.push(UrlRewriteMapLocationWrap::new(
90
+ path,
91
+ url_rewrite_config_init(rewrite_map_yaml)?,
92
+ ));
93
+ }
94
+ }
95
+ }
96
+ }
97
+ if let Some(rewrite_map_yaml) = host_yaml["rewriteMap"].as_vec() {
98
+ host_url_rewrite_maps.push(UrlRewriteMapWrap::new(
99
+ domain,
100
+ ip,
101
+ url_rewrite_config_init(rewrite_map_yaml)?,
102
+ locations,
103
+ ));
104
+ } else if !locations.is_empty() {
105
+ host_url_rewrite_maps.push(UrlRewriteMapWrap::new(domain, ip, Vec::new(), locations));
106
+ }
107
+ }
108
+ }
109
+
110
+ Ok(Box::new(UrlRewriteModule::new(
111
+ Arc::new(global_url_rewrite_map),
112
+ Arc::new(host_url_rewrite_maps),
113
+ )))
114
+ }
115
+
116
+ struct UrlRewriteModule {
117
+ global_url_rewrite_map: Arc<Vec<UrlRewriteMapEntry>>,
118
+ host_url_rewrite_maps: Arc<Vec<UrlRewriteMapWrap>>,
119
+ }
120
+
121
+ impl UrlRewriteModule {
122
+ fn new(
123
+ global_url_rewrite_map: Arc<Vec<UrlRewriteMapEntry>>,
124
+ host_url_rewrite_maps: Arc<Vec<UrlRewriteMapWrap>>,
125
+ ) -> Self {
126
+ Self {
127
+ global_url_rewrite_map,
128
+ host_url_rewrite_maps,
129
+ }
130
+ }
131
+ }
132
+
133
+ impl ServerModule for UrlRewriteModule {
134
+ fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
135
+ Box::new(UrlRewriteModuleHandlers {
136
+ global_url_rewrite_map: self.global_url_rewrite_map.clone(),
137
+ host_url_rewrite_maps: self.host_url_rewrite_maps.clone(),
138
+ handle,
139
+ })
140
+ }
141
+ }
142
+ struct UrlRewriteModuleHandlers {
143
+ global_url_rewrite_map: Arc<Vec<UrlRewriteMapEntry>>,
144
+ host_url_rewrite_maps: Arc<Vec<UrlRewriteMapWrap>>,
145
+ handle: Handle,
146
+ }
147
+
148
+ #[async_trait]
149
+ impl ServerModuleHandlers for UrlRewriteModuleHandlers {
150
+ async fn request_handler(
151
+ &mut self,
152
+ request: RequestData,
153
+ config: &ServerConfig,
154
+ socket_data: &SocketData,
155
+ error_logger: &ErrorLogger,
156
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
157
+ WithRuntime::new(self.handle.clone(), async move {
158
+ let hyper_request = request.get_hyper_request();
159
+ let global_url_rewrite_map = self.global_url_rewrite_map.iter();
160
+ let empty_vector = Vec::new();
161
+ let another_empty_vector = Vec::new();
162
+ let mut host_url_rewrite_map = empty_vector.iter();
163
+ let mut location_url_rewrite_map = another_empty_vector.iter();
164
+
165
+ // Should have used a HashMap instead of iterating over an array for better performance...
166
+ for host_url_rewrite_map_wrap in self.host_url_rewrite_maps.iter() {
167
+ if match_hostname(
168
+ match &host_url_rewrite_map_wrap.domain {
169
+ Some(value) => Some(value as &str),
170
+ None => None,
171
+ },
172
+ match hyper_request.headers().get(header::HOST) {
173
+ Some(value) => value.to_str().ok(),
174
+ None => None,
175
+ },
176
+ ) && match &host_url_rewrite_map_wrap.ip {
177
+ Some(value) => ip_match(value as &str, socket_data.remote_addr.ip()),
178
+ None => true,
179
+ } {
180
+ host_url_rewrite_map = host_url_rewrite_map_wrap.rewrite_map.iter();
181
+ if let Ok(path_decoded) = urlencoding::decode(
182
+ request
183
+ .get_original_url()
184
+ .unwrap_or(request.get_hyper_request().uri())
185
+ .path(),
186
+ ) {
187
+ for location_wrap in host_url_rewrite_map_wrap.locations.iter() {
188
+ if match_location(&location_wrap.path, &path_decoded) {
189
+ location_url_rewrite_map = location_wrap.rewrite_map.iter();
190
+ break;
191
+ }
192
+ }
193
+ }
194
+ break;
195
+ }
196
+ }
197
+
198
+ let combined_url_rewrite_map = global_url_rewrite_map
199
+ .chain(host_url_rewrite_map)
200
+ .chain(location_url_rewrite_map);
201
+
202
+ let original_url = format!(
203
+ "{}{}",
204
+ hyper_request.uri().path(),
205
+ match hyper_request.uri().query() {
206
+ Some(query) => format!("?{}", query),
207
+ None => String::from(""),
208
+ }
209
+ );
210
+ let mut rewritten_url = original_url.clone();
211
+
212
+ let mut rewritten_url_bytes = rewritten_url.bytes();
213
+ if rewritten_url_bytes.len() < 1 || rewritten_url_bytes.nth(0) != Some(b'/') {
214
+ return Ok(
215
+ ResponseData::builder(request)
216
+ .status(StatusCode::BAD_REQUEST)
217
+ .build(),
218
+ );
219
+ }
220
+
221
+ for url_rewrite_map_entry in combined_url_rewrite_map {
222
+ // Check if it's a file or a directory according to the rewrite map configuration
223
+ if url_rewrite_map_entry.is_not_directory || url_rewrite_map_entry.is_not_file {
224
+ if let Some(wwwroot) = config["wwwroot"].as_str() {
225
+ let path = Path::new(wwwroot);
226
+ let mut relative_path = &rewritten_url[1..];
227
+ while relative_path.as_bytes().first().copied() == Some(b'/') {
228
+ relative_path = &relative_path[1..];
229
+ }
230
+ let relative_path_split: Vec<&str> = relative_path.split("?").collect();
231
+ if !relative_path_split.is_empty() {
232
+ relative_path = relative_path_split[0];
233
+ }
234
+ let joined_pathbuf = path.join(relative_path);
235
+ if let Ok(metadata) = fs::metadata(joined_pathbuf).await {
236
+ if (url_rewrite_map_entry.is_not_file && metadata.is_file())
237
+ || (url_rewrite_map_entry.is_not_directory && metadata.is_dir())
238
+ {
239
+ continue;
240
+ }
241
+ }
242
+ }
243
+ }
244
+
245
+ if !url_rewrite_map_entry.allow_double_slashes {
246
+ while rewritten_url.contains("//") {
247
+ rewritten_url = rewritten_url.replace("//", "/");
248
+ }
249
+ }
250
+
251
+ // Actual URL rewriting
252
+ let old_rewritten_url = rewritten_url;
253
+ rewritten_url = url_rewrite_map_entry
254
+ .regex
255
+ .replace(&old_rewritten_url, &url_rewrite_map_entry.replacement)
256
+ .to_string();
257
+
258
+ let mut rewritten_url_bytes = rewritten_url.bytes();
259
+ if rewritten_url_bytes.len() < 1 || rewritten_url_bytes.nth(0) != Some(b'/') {
260
+ return Ok(
261
+ ResponseData::builder(request)
262
+ .status(StatusCode::BAD_REQUEST)
263
+ .build(),
264
+ );
265
+ }
266
+
267
+ if url_rewrite_map_entry.last && old_rewritten_url != rewritten_url {
268
+ break;
269
+ }
270
+ }
271
+
272
+ if rewritten_url == original_url {
273
+ Ok(ResponseData::builder(request).build())
274
+ } else {
275
+ if config["enableRewriteLogging"].as_bool() == Some(true) {
276
+ error_logger
277
+ .log(&format!(
278
+ "URL rewritten from \"{}\" to \"{}\"",
279
+ original_url, rewritten_url
280
+ ))
281
+ .await;
282
+ }
283
+ let (hyper_request, auth_user, _) = request.into_parts();
284
+ let (mut parts, body) = hyper_request.into_parts();
285
+ let original_url = parts.uri.clone();
286
+ let mut url_parts = parts.uri.into_parts();
287
+ url_parts.path_and_query = Some(rewritten_url.parse()?);
288
+ parts.uri = hyper::Uri::from_parts(url_parts)?;
289
+ let hyper_request = Request::from_parts(parts, body);
290
+ let request = RequestData::new(hyper_request, auth_user, Some(original_url));
291
+ Ok(ResponseData::builder(request).build())
292
+ }
293
+ })
294
+ .await
295
+ }
296
+
297
+ async fn proxy_request_handler(
298
+ &mut self,
299
+ request: RequestData,
300
+ _config: &ServerConfig,
301
+ _socket_data: &SocketData,
302
+ _error_logger: &ErrorLogger,
303
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
304
+ Ok(ResponseData::builder(request).build())
305
+ }
306
+
307
+ async fn response_modifying_handler(
308
+ &mut self,
309
+ response: HyperResponse,
310
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
311
+ Ok(response)
312
+ }
313
+
314
+ async fn proxy_response_modifying_handler(
315
+ &mut self,
316
+ response: HyperResponse,
317
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
318
+ Ok(response)
319
+ }
320
+
321
+ async fn connect_proxy_request_handler(
322
+ &mut self,
323
+ _upgraded_request: HyperUpgraded,
324
+ _connect_address: &str,
325
+ _config: &ServerConfig,
326
+ _socket_data: &SocketData,
327
+ _error_logger: &ErrorLogger,
328
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
329
+ Ok(())
330
+ }
331
+
332
+ fn does_connect_proxy_requests(&mut self) -> bool {
333
+ false
334
+ }
335
+
336
+ async fn websocket_request_handler(
337
+ &mut self,
338
+ _websocket: HyperWebsocket,
339
+ _uri: &hyper::Uri,
340
+ _config: &ServerConfig,
341
+ _socket_data: &SocketData,
342
+ _error_logger: &ErrorLogger,
343
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
344
+ Ok(())
345
+ }
346
+
347
+ fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
348
+ false
349
+ }
350
+ }
ferron/src/modules/x_forwarded_for.rs ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use std::error::Error;
2
+ use std::net::{IpAddr, SocketAddr};
3
+
4
+ use crate::ferron_common::{
5
+ ErrorLogger, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
6
+ ServerModuleHandlers, SocketData,
7
+ };
8
+ use crate::ferron_common::{HyperUpgraded, WithRuntime};
9
+ use async_trait::async_trait;
10
+ use hyper::StatusCode;
11
+ use hyper_tungstenite::HyperWebsocket;
12
+ use tokio::runtime::Handle;
13
+
14
+ struct XForwardedForModule;
15
+
16
+ pub fn server_module_init(
17
+ ) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
18
+ Ok(Box::new(XForwardedForModule::new()))
19
+ }
20
+
21
+ impl XForwardedForModule {
22
+ fn new() -> Self {
23
+ Self
24
+ }
25
+ }
26
+
27
+ impl ServerModule for XForwardedForModule {
28
+ fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
29
+ Box::new(XForwardedForModuleHandlers { handle })
30
+ }
31
+ }
32
+ struct XForwardedForModuleHandlers {
33
+ handle: Handle,
34
+ }
35
+
36
+ #[async_trait]
37
+ impl ServerModuleHandlers for XForwardedForModuleHandlers {
38
+ async fn request_handler(
39
+ &mut self,
40
+ request: RequestData,
41
+ config: &ServerConfig,
42
+ socket_data: &SocketData,
43
+ _error_logger: &ErrorLogger,
44
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
45
+ WithRuntime::new(self.handle.clone(), async move {
46
+ if config["enableIPSpoofing"].as_bool() == Some(true) {
47
+ let hyper_request = request.get_hyper_request();
48
+
49
+ if let Some(x_forwarded_for_value) = hyper_request.headers().get("x-forwarded-for") {
50
+ let x_forwarded_for = x_forwarded_for_value.to_str()?;
51
+
52
+ let prepared_remote_ip_str = match x_forwarded_for.split(",").nth(0) {
53
+ Some(ip_address_str) => ip_address_str.replace(" ", ""),
54
+ None => {
55
+ return Ok(
56
+ ResponseData::builder(request)
57
+ .status(StatusCode::BAD_REQUEST)
58
+ .build(),
59
+ );
60
+ }
61
+ };
62
+
63
+ let prepared_remote_ip: IpAddr = match prepared_remote_ip_str.parse() {
64
+ Ok(ip_address) => ip_address,
65
+ Err(_) => {
66
+ return Ok(
67
+ ResponseData::builder(request)
68
+ .status(StatusCode::BAD_REQUEST)
69
+ .build(),
70
+ );
71
+ }
72
+ };
73
+
74
+ let new_socket_addr = SocketAddr::new(prepared_remote_ip, socket_data.remote_addr.port());
75
+
76
+ return Ok(
77
+ ResponseData::builder(request)
78
+ .new_remote_address(new_socket_addr)
79
+ .build(),
80
+ );
81
+ }
82
+
83
+ return Ok(ResponseData::builder(request).build());
84
+ }
85
+
86
+ Ok(ResponseData::builder(request).build())
87
+ })
88
+ .await
89
+ }
90
+
91
+ async fn proxy_request_handler(
92
+ &mut self,
93
+ request: RequestData,
94
+ _config: &ServerConfig,
95
+ _socket_data: &SocketData,
96
+ _error_logger: &ErrorLogger,
97
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
98
+ Ok(ResponseData::builder(request).build())
99
+ }
100
+
101
+ async fn response_modifying_handler(
102
+ &mut self,
103
+ response: HyperResponse,
104
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
105
+ Ok(response)
106
+ }
107
+
108
+ async fn proxy_response_modifying_handler(
109
+ &mut self,
110
+ response: HyperResponse,
111
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
112
+ Ok(response)
113
+ }
114
+
115
+ async fn connect_proxy_request_handler(
116
+ &mut self,
117
+ _upgraded_request: HyperUpgraded,
118
+ _connect_address: &str,
119
+ _config: &ServerConfig,
120
+ _socket_data: &SocketData,
121
+ _error_logger: &ErrorLogger,
122
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
123
+ Ok(())
124
+ }
125
+
126
+ fn does_connect_proxy_requests(&mut self) -> bool {
127
+ false
128
+ }
129
+
130
+ async fn websocket_request_handler(
131
+ &mut self,
132
+ _websocket: HyperWebsocket,
133
+ _uri: &hyper::Uri,
134
+ _config: &ServerConfig,
135
+ _socket_data: &SocketData,
136
+ _error_logger: &ErrorLogger,
137
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
138
+ Ok(())
139
+ }
140
+
141
+ fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
142
+ false
143
+ }
144
+ }
ferron/src/optional_modules/asgi.rs ADDED
@@ -0,0 +1,1476 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // WARNING: We have measured this module on our computers, and found it to be slower than Uvicorn (with 1 worker),
2
+ // with FastAPI application, vanilla ASGI application is found out to be faster than Uvicorn (with 1 worker).
3
+ // It might be more performant to just use Ferron as a reverse proxy for Uvicorn (or any other ASGI server).
4
+
5
+ use std::error::Error;
6
+ use std::ffi::CString;
7
+ use std::path::{Path, PathBuf};
8
+ use std::str::FromStr;
9
+ use std::sync::atomic::{AtomicBool, Ordering};
10
+ use std::sync::Arc;
11
+ use std::thread;
12
+
13
+ use crate::ferron_common::{
14
+ ErrorLogger, HyperUpgraded, RequestData, ResponseData, ServerConfig, ServerModule,
15
+ ServerModuleHandlers, SocketData,
16
+ };
17
+ use crate::ferron_common::{HyperResponse, WithRuntime};
18
+ use crate::ferron_util::asgi_messages::{
19
+ asgi_event_to_outgoing_struct, incoming_struct_to_asgi_event, AsgiHttpBody, AsgiHttpInitData,
20
+ AsgiInitData, AsgiWebsocketClose, AsgiWebsocketInitData, AsgiWebsocketMessage,
21
+ IncomingAsgiMessage, IncomingAsgiMessageInner, OutgoingAsgiMessage, OutgoingAsgiMessageInner,
22
+ };
23
+ use crate::ferron_util::asgi_structs::{AsgiApplicationLocationWrap, AsgiApplicationWrap};
24
+ use crate::ferron_util::ip_match::ip_match;
25
+ use crate::ferron_util::match_hostname::match_hostname;
26
+ use crate::ferron_util::match_location::match_location;
27
+ use async_channel::{Receiver, Sender};
28
+ use async_trait::async_trait;
29
+ use futures_util::{SinkExt, StreamExt};
30
+ use http::{HeaderMap, HeaderName, HeaderValue, Response, Version};
31
+ use http_body_util::{BodyExt, StreamBody};
32
+ use hyper::body::{Bytes, Frame};
33
+ use hyper::{header, StatusCode};
34
+ use hyper_tungstenite::HyperWebsocket;
35
+ use pyo3::exceptions::{PyIOError, PyOSError, PyRuntimeError, PyTypeError};
36
+ use pyo3::prelude::*;
37
+ use pyo3::types::{PyCFunction, PyDict, PyList, PyTuple, PyType};
38
+ use tokio::fs;
39
+ use tokio::runtime::{Handle, Runtime};
40
+ use tokio::sync::Mutex;
41
+ use tokio_tungstenite::tungstenite::protocol::CloseFrame;
42
+ use tokio_tungstenite::tungstenite::Message;
43
+ use tokio_util::sync::CancellationToken;
44
+
45
+ type AsgiChannelResult =
46
+ Result<(Sender<IncomingAsgiMessage>, Receiver<OutgoingAsgiMessage>), anyhow::Error>;
47
+ type AsgiEventLoopCommunication = Vec<(Sender<()>, Receiver<AsgiChannelResult>)>;
48
+
49
+ async fn asgi_application_fn(
50
+ asgi_application: Arc<Py<PyAny>>,
51
+ tx: Sender<OutgoingAsgiMessage>,
52
+ rx: Receiver<IncomingAsgiMessage>,
53
+ ) {
54
+ let init_message = match rx.recv().await {
55
+ Ok(IncomingAsgiMessage::Init(message)) => message,
56
+ Err(err) => {
57
+ tx.send(OutgoingAsgiMessage::Error(PyErr::new::<PyIOError, _>(
58
+ err.to_string(),
59
+ )))
60
+ .await
61
+ .unwrap_or_default();
62
+ return;
63
+ }
64
+ _ => {
65
+ tx.send(OutgoingAsgiMessage::Error(PyErr::new::<PyIOError, _>(
66
+ "Unexpected message received",
67
+ )))
68
+ .await
69
+ .unwrap_or_default();
70
+ return;
71
+ }
72
+ };
73
+ let tx_clone = tx.clone();
74
+ let rx_clone = rx.clone();
75
+ match Python::with_gil(move |py| -> PyResult<_> {
76
+ let tx_clone = tx_clone.clone();
77
+ let rx_clone = rx_clone.clone();
78
+
79
+ let scope = PyDict::new(py);
80
+ let scope_asgi = PyDict::new(py);
81
+
82
+ match init_message {
83
+ AsgiInitData::Lifespan => {
84
+ scope.set_item("type", "lifespan")?;
85
+ scope_asgi.set_item("version", "3.0")?;
86
+ }
87
+ AsgiInitData::Http(http_init_data) => {
88
+ let path = http_init_data.hyper_request_parts.uri.path().to_owned();
89
+ let query_string = http_init_data
90
+ .hyper_request_parts
91
+ .uri
92
+ .query()
93
+ .unwrap_or("")
94
+ .to_owned();
95
+ let original_request_uri = http_init_data
96
+ .original_request_uri
97
+ .unwrap_or(http_init_data.hyper_request_parts.uri);
98
+ scope.set_item("type", "http")?;
99
+ scope_asgi.set_item("version", "2.5")?;
100
+ scope.set_item(
101
+ "http_version",
102
+ match http_init_data.hyper_request_parts.version {
103
+ Version::HTTP_09 => "1.0", // ASGI doesn't support HTTP/0.9
104
+ Version::HTTP_10 => "1.0",
105
+ Version::HTTP_11 => "1.1",
106
+ Version::HTTP_2 => "2",
107
+ Version::HTTP_3 => "2", // ASGI doesn't support HTTP/3
108
+ _ => "1.1", // Some other HTTP versions, of course...
109
+ },
110
+ )?;
111
+ scope.set_item(
112
+ "method",
113
+ http_init_data.hyper_request_parts.method.to_string(),
114
+ )?;
115
+ scope.set_item(
116
+ "scheme",
117
+ if http_init_data.socket_data.encrypted {
118
+ "https"
119
+ } else {
120
+ "http"
121
+ },
122
+ )?;
123
+ scope.set_item("path", urlencoding::decode(&path)?)?;
124
+ scope.set_item("raw_path", original_request_uri.to_string().as_bytes())?;
125
+ scope.set_item("query_string", query_string.as_bytes())?;
126
+ if let Ok(script_path) = http_init_data
127
+ .execute_pathbuf
128
+ .as_path()
129
+ .strip_prefix(http_init_data.wwwroot)
130
+ {
131
+ scope.set_item(
132
+ "root_path",
133
+ format!(
134
+ "/{}",
135
+ match cfg!(windows) {
136
+ true => script_path.to_string_lossy().to_string().replace("\\", "/"),
137
+ false => script_path.to_string_lossy().to_string(),
138
+ }
139
+ ),
140
+ )?;
141
+ }
142
+ let headers = PyList::empty(py);
143
+ for (header_name, header_value) in http_init_data.hyper_request_parts.headers.iter() {
144
+ let header_name = header_name.as_str().as_bytes();
145
+ let header_value = header_value.as_bytes();
146
+ if !header_name.is_empty() && header_name[0] != b':' {
147
+ headers.append(PyTuple::new(py, [header_name, header_value].into_iter())?)?;
148
+ }
149
+ }
150
+ scope.set_item("headers", headers)?;
151
+ scope.set_item(
152
+ "client",
153
+ (
154
+ http_init_data
155
+ .socket_data
156
+ .remote_addr
157
+ .ip()
158
+ .to_canonical()
159
+ .to_string(),
160
+ http_init_data.socket_data.remote_addr.port(),
161
+ ),
162
+ )?;
163
+ scope.set_item(
164
+ "server",
165
+ (
166
+ http_init_data
167
+ .socket_data
168
+ .local_addr
169
+ .ip()
170
+ .to_canonical()
171
+ .to_string(),
172
+ http_init_data.socket_data.local_addr.port(),
173
+ ),
174
+ )?;
175
+ }
176
+ AsgiInitData::Websocket(websocket_init_data) => {
177
+ let path = websocket_init_data.uri.path().to_owned();
178
+ let query_string = websocket_init_data.uri.query().unwrap_or("").to_owned();
179
+ let original_request_uri = websocket_init_data.uri;
180
+ scope.set_item("type", "websocket")?;
181
+ scope_asgi.set_item("version", "2.5")?;
182
+ scope.set_item(
183
+ "http_version",
184
+ "1.1", // WebSocket is supported only on HTTP/1.1 in Ferron
185
+ )?;
186
+ scope.set_item(
187
+ "scheme",
188
+ if websocket_init_data.socket_data.encrypted {
189
+ "wss"
190
+ } else {
191
+ "ws"
192
+ },
193
+ )?;
194
+ scope.set_item("path", urlencoding::decode(&path)?)?;
195
+ scope.set_item("raw_path", original_request_uri.to_string().as_bytes())?;
196
+ scope.set_item("query_string", query_string.as_bytes())?;
197
+ if let Ok(script_path) = websocket_init_data
198
+ .execute_pathbuf
199
+ .as_path()
200
+ .strip_prefix(websocket_init_data.wwwroot)
201
+ {
202
+ scope.set_item(
203
+ "root_path",
204
+ format!(
205
+ "/{}",
206
+ match cfg!(windows) {
207
+ true => script_path.to_string_lossy().to_string().replace("\\", "/"),
208
+ false => script_path.to_string_lossy().to_string(),
209
+ }
210
+ ),
211
+ )?;
212
+ }
213
+ // Ferron doesn't send original request headers (before WebSocket upgrade) to WebSocket request handlers
214
+ scope.set_item("headers", PyList::empty(py))?;
215
+ scope.set_item(
216
+ "client",
217
+ (
218
+ websocket_init_data
219
+ .socket_data
220
+ .remote_addr
221
+ .ip()
222
+ .to_canonical()
223
+ .to_string(),
224
+ websocket_init_data.socket_data.remote_addr.port(),
225
+ ),
226
+ )?;
227
+ scope.set_item(
228
+ "server",
229
+ (
230
+ websocket_init_data
231
+ .socket_data
232
+ .local_addr
233
+ .ip()
234
+ .to_canonical()
235
+ .to_string(),
236
+ websocket_init_data.socket_data.local_addr.port(),
237
+ ),
238
+ )?;
239
+ scope.set_item("subprotocols", PyList::empty(py))?;
240
+ }
241
+ };
242
+
243
+ scope_asgi.set_item("spec_version", "1.0")?;
244
+ scope.set_item("asgi", scope_asgi)?;
245
+ let scope_extensions = PyDict::new(py);
246
+ scope_extensions.set_item("http.response.trailers", PyDict::new(py))?;
247
+ scope.set_item("extensions", scope_extensions)?;
248
+
249
+ let client_disconnected = Arc::new(AtomicBool::new(false));
250
+ let client_disconnected_clone = client_disconnected.clone();
251
+
252
+ let receive = PyCFunction::new_closure(
253
+ py,
254
+ None,
255
+ None,
256
+ move |args: &Bound<'_, PyTuple>, _: Option<&Bound<'_, PyDict>>| -> PyResult<_> {
257
+ let rx = rx_clone.clone();
258
+ let client_disconnected = client_disconnected.clone();
259
+ Ok(
260
+ pyo3_async_runtimes::tokio::future_into_py(args.py(), async move {
261
+ if client_disconnected.load(Ordering::Relaxed) {
262
+ Err(PyErr::new::<PyOSError, _>("Client disconnected"))
263
+ } else {
264
+ let message = rx
265
+ .recv()
266
+ .await
267
+ .map_err(|e| PyErr::new::<PyOSError, _>(e.to_string()))?;
268
+ match message {
269
+ IncomingAsgiMessage::Init(_) => Err(PyErr::new::<PyOSError, _>(
270
+ "Unexpected ASGI initialization message",
271
+ )),
272
+ IncomingAsgiMessage::Message(message) => {
273
+ if let IncomingAsgiMessageInner::HttpDisconnect = &message {
274
+ client_disconnected.store(true, Ordering::Relaxed);
275
+ }
276
+ incoming_struct_to_asgi_event(message)
277
+ }
278
+ }
279
+ }
280
+ })?
281
+ .unbind(),
282
+ )
283
+ },
284
+ )?;
285
+ let send = PyCFunction::new_closure(
286
+ py,
287
+ None,
288
+ None,
289
+ move |args: &Bound<'_, PyTuple>, _: Option<&Bound<'_, PyDict>>| -> PyResult<_> {
290
+ let event = args.get_item(0)?.downcast::<PyDict>()?.clone();
291
+ let message = asgi_event_to_outgoing_struct(event)?;
292
+ let tx = tx_clone.clone();
293
+ let client_disconnected = client_disconnected_clone.clone();
294
+ Ok(
295
+ pyo3_async_runtimes::tokio::future_into_py(args.py(), async move {
296
+ if client_disconnected.load(Ordering::Relaxed) {
297
+ Err(PyErr::new::<PyOSError, _>("Client disconnected"))
298
+ } else {
299
+ tx.send(OutgoingAsgiMessage::Message(message))
300
+ .await
301
+ .map_err(|e| PyErr::new::<PyOSError, _>(e.to_string()))?;
302
+ Ok(())
303
+ }
304
+ })?
305
+ .unbind(),
306
+ )
307
+ },
308
+ )?;
309
+
310
+ let asgi_coroutine =
311
+ match asgi_application.call(py, (scope.clone(), receive.clone(), send.clone()), None) {
312
+ Ok(coroutine) => coroutine,
313
+ Err(err) => {
314
+ if !err.get_type(py).is(&PyType::new::<PyTypeError>(py)) {
315
+ return Err(err);
316
+ } else {
317
+ asgi_application
318
+ .call(py, (scope,), None)?
319
+ .call(py, (receive, send), None)?
320
+ }
321
+ }
322
+ };
323
+
324
+ pyo3_async_runtimes::tokio::into_future(asgi_coroutine.into_bound(py))
325
+ }) {
326
+ Err(err) => tx
327
+ .send(OutgoingAsgiMessage::Error(PyErr::new::<PyRuntimeError, _>(
328
+ err.to_string(),
329
+ )))
330
+ .await
331
+ .unwrap_or_default(),
332
+ Ok(asgi_future) => match asgi_future.await {
333
+ Err(err) => tx
334
+ .send(OutgoingAsgiMessage::Error(err))
335
+ .await
336
+ .unwrap_or_default(),
337
+ Ok(_) => tx
338
+ .send(OutgoingAsgiMessage::Finished)
339
+ .await
340
+ .unwrap_or_default(),
341
+ },
342
+ }
343
+ }
344
+
345
+ async fn asgi_lifetime_init_fn(asgi_applications: Vec<Arc<Py<PyAny>>>) -> Vec<AsgiChannelResult> {
346
+ let mut results = Vec::new();
347
+ for asgi_application in asgi_applications {
348
+ results.push(
349
+ async {
350
+ let (tx, rx_task) = async_channel::unbounded::<IncomingAsgiMessage>();
351
+ let (tx_task, rx) = async_channel::unbounded::<OutgoingAsgiMessage>();
352
+ if let Ok(locals) = Python::with_gil(pyo3_async_runtimes::tokio::get_current_locals) {
353
+ tokio::spawn(pyo3_async_runtimes::tokio::scope(
354
+ locals,
355
+ asgi_application_fn(asgi_application, tx_task, rx_task),
356
+ ));
357
+ tx.send(IncomingAsgiMessage::Init(AsgiInitData::Lifespan))
358
+ .await
359
+ .map_err(|e| anyhow::anyhow!(e.to_string()))?;
360
+ Ok((tx, rx))
361
+ } else {
362
+ Err(anyhow::anyhow!("Cannot obtain task locals"))
363
+ }
364
+ }
365
+ .await,
366
+ );
367
+ }
368
+ results
369
+ }
370
+
371
+ async fn asgi_event_loop_fn(
372
+ asgi_application: Arc<Py<PyAny>>,
373
+ tx: Sender<AsgiChannelResult>,
374
+ rx: Receiver<()>,
375
+ ) {
376
+ loop {
377
+ if rx.recv().await.is_err() {
378
+ continue;
379
+ }
380
+
381
+ let (tx_send, rx_task) = async_channel::unbounded::<IncomingAsgiMessage>();
382
+ let (tx_task, rx_send) = async_channel::unbounded::<OutgoingAsgiMessage>();
383
+ let asgi_application_cloned = asgi_application.clone();
384
+ if let Ok(locals) = Python::with_gil(pyo3_async_runtimes::tokio::get_current_locals) {
385
+ tokio::spawn(pyo3_async_runtimes::tokio::scope(
386
+ locals,
387
+ asgi_application_fn(asgi_application_cloned, tx_task, rx_task),
388
+ ));
389
+ tx.send(Ok((tx_send, rx_send))).await.unwrap_or_default();
390
+ }
391
+ }
392
+ }
393
+
394
+ async fn asgi_init_event_loop_fn(
395
+ cancel_token: CancellationToken,
396
+ asgi_applications: Vec<Arc<Py<PyAny>>>,
397
+ mut channels: Vec<(Sender<AsgiChannelResult>, Receiver<()>)>,
398
+ ) {
399
+ Python::with_gil(|py| {
400
+ // Try installing `uvloop`, when it fails, use `asyncio` fallback instead.
401
+ if let Ok(uvloop) = py.import("uvloop") {
402
+ let _ = uvloop.call_method0("install");
403
+ }
404
+
405
+ pyo3_async_runtimes::tokio::run::<_, ()>(py, async move {
406
+ let asgi_lifetime_channels = asgi_lifetime_init_fn(asgi_applications.clone()).await;
407
+ for asgi_lifetime_channel_result in &asgi_lifetime_channels {
408
+ if let Ok((tx, rx)) = asgi_lifetime_channel_result.as_ref() {
409
+ tx.send(IncomingAsgiMessage::Message(
410
+ IncomingAsgiMessageInner::LifespanStartup,
411
+ ))
412
+ .await
413
+ .unwrap_or_default();
414
+ loop {
415
+ match rx.recv().await {
416
+ Ok(OutgoingAsgiMessage::Message(
417
+ OutgoingAsgiMessageInner::LifespanStartupComplete,
418
+ ))
419
+ | Ok(OutgoingAsgiMessage::Message(
420
+ OutgoingAsgiMessageInner::LifespanStartupFailed(_),
421
+ ))
422
+ | Ok(OutgoingAsgiMessage::Finished)
423
+ | Ok(OutgoingAsgiMessage::Error(_))
424
+ | Err(_) => break,
425
+ _ => (),
426
+ }
427
+ }
428
+ }
429
+ }
430
+ let init_closure = async move {
431
+ let mut channels_len = channels.len();
432
+ if let Some((tx_last, rx_last)) = channels.pop() {
433
+ channels_len -= 1;
434
+ let last_channel_id = channels_len;
435
+ for (tx, rx) in channels {
436
+ channels_len -= 1;
437
+ if let Ok(locals) = Python::with_gil(pyo3_async_runtimes::tokio::get_current_locals) {
438
+ tokio::spawn(pyo3_async_runtimes::tokio::scope(
439
+ locals,
440
+ asgi_event_loop_fn(asgi_applications[channels_len].clone(), tx, rx),
441
+ ));
442
+ }
443
+ }
444
+
445
+ if let Ok(locals) = Python::with_gil(pyo3_async_runtimes::tokio::get_current_locals) {
446
+ tokio::spawn(pyo3_async_runtimes::tokio::scope(
447
+ locals,
448
+ asgi_event_loop_fn(asgi_applications[last_channel_id].clone(), tx_last, rx_last),
449
+ ))
450
+ .await
451
+ .unwrap_or_default();
452
+ }
453
+ }
454
+ };
455
+ tokio::select! {
456
+ _ = cancel_token.cancelled() => {}
457
+ _ = init_closure => {}
458
+ }
459
+ for asgi_lifetime_channel_result in &asgi_lifetime_channels {
460
+ if let Ok((tx, rx)) = asgi_lifetime_channel_result.as_ref() {
461
+ tx.send(IncomingAsgiMessage::Message(
462
+ IncomingAsgiMessageInner::LifespanShutdown,
463
+ ))
464
+ .await
465
+ .unwrap_or_default();
466
+ loop {
467
+ match rx.recv().await {
468
+ Ok(OutgoingAsgiMessage::Message(
469
+ OutgoingAsgiMessageInner::LifespanShutdownComplete,
470
+ ))
471
+ | Ok(OutgoingAsgiMessage::Message(
472
+ OutgoingAsgiMessageInner::LifespanShutdownFailed(_),
473
+ ))
474
+ | Ok(OutgoingAsgiMessage::Finished)
475
+ | Ok(OutgoingAsgiMessage::Error(_))
476
+ | Err(_) => break,
477
+ _ => (),
478
+ }
479
+ }
480
+ }
481
+ }
482
+ Ok(())
483
+ })
484
+ })
485
+ .unwrap_or_default();
486
+ }
487
+
488
+ pub fn load_asgi_application(
489
+ file_path: &Path,
490
+ clear_sys_path: bool,
491
+ ) -> Result<Py<PyAny>, Box<dyn Error + Send + Sync>> {
492
+ let script_dirname = file_path
493
+ .parent()
494
+ .map(|path| path.to_string_lossy().to_string());
495
+ let script_name = file_path.to_string_lossy().to_string();
496
+ let script_name_cstring = CString::from_str(&script_name)?;
497
+ let module_name = script_name
498
+ .strip_suffix(".py")
499
+ .unwrap_or(&script_name)
500
+ .to_lowercase()
501
+ .chars()
502
+ .map(|c| if c.is_lowercase() { '_' } else { c })
503
+ .collect::<String>();
504
+ let module_name_cstring = CString::from_str(&module_name)?;
505
+ let script_data = std::fs::read_to_string(file_path)?;
506
+ let script_data_cstring = CString::from_str(&script_data)?;
507
+ let asgi_application = Python::with_gil(move |py| -> PyResult<Py<PyAny>> {
508
+ let mut sys_path_old = None;
509
+ if let Some(script_dirname) = script_dirname {
510
+ if let Ok(sys_module) = PyModule::import(py, "sys") {
511
+ if let Ok(sys_path_any) = sys_module.getattr("path") {
512
+ if let Ok(sys_path) = sys_path_any.downcast::<PyList>() {
513
+ let sys_path = sys_path.clone();
514
+ sys_path_old = sys_path.extract::<Vec<String>>().ok();
515
+ sys_path.insert(0, script_dirname).unwrap_or_default();
516
+ }
517
+ }
518
+ }
519
+ }
520
+ let asgi_application = PyModule::from_code(
521
+ py,
522
+ &script_data_cstring,
523
+ &script_name_cstring,
524
+ &module_name_cstring,
525
+ )?
526
+ .getattr("application")?
527
+ .unbind();
528
+ if clear_sys_path {
529
+ if let Some(sys_path) = sys_path_old {
530
+ if let Ok(sys_module) = PyModule::import(py, "sys") {
531
+ sys_module.setattr("path", sys_path).unwrap_or_default();
532
+ }
533
+ }
534
+ }
535
+ Ok(asgi_application)
536
+ })?;
537
+ Ok(asgi_application)
538
+ }
539
+
540
+ pub fn server_module_init(
541
+ config: &ServerConfig,
542
+ ) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
543
+ let mut asgi_applications = Vec::new();
544
+ let mut global_asgi_application_id = None;
545
+ let mut host_asgi_application_ids = Vec::new();
546
+ let clear_sys_path = config["global"]["asgiClearModuleImportPath"]
547
+ .as_bool()
548
+ .unwrap_or(false);
549
+ if let Some(asgi_application_path) = config["global"]["asgiApplicationPath"].as_str() {
550
+ let asgi_application_id = asgi_applications.len();
551
+ asgi_applications.push(Arc::new(load_asgi_application(
552
+ PathBuf::from_str(asgi_application_path)?.as_path(),
553
+ clear_sys_path,
554
+ )?));
555
+ global_asgi_application_id = Some(asgi_application_id);
556
+ }
557
+ let global_asgi_path = config["global"]["asgiPath"].as_str().map(|s| s.to_string());
558
+
559
+ if let Some(hosts) = config["hosts"].as_vec() {
560
+ for host_yaml in hosts.iter() {
561
+ let domain = host_yaml["domain"].as_str().map(String::from);
562
+ let ip = host_yaml["ip"].as_str().map(String::from);
563
+ let mut locations = Vec::new();
564
+ if let Some(locations_yaml) = host_yaml["locations"].as_vec() {
565
+ for location_yaml in locations_yaml.iter() {
566
+ if let Some(path_str) = location_yaml["path"].as_str() {
567
+ let path = String::from(path_str);
568
+ if let Some(asgi_application_path) = location_yaml["asgiApplicationPath"].as_str() {
569
+ let asgi_application_id = asgi_applications.len();
570
+ asgi_applications.push(Arc::new(load_asgi_application(
571
+ PathBuf::from_str(asgi_application_path)?.as_path(),
572
+ clear_sys_path,
573
+ )?));
574
+
575
+ locations.push(AsgiApplicationLocationWrap::new(
576
+ path,
577
+ asgi_application_id,
578
+ asgi_application_path.to_string(),
579
+ location_yaml["asgiPath"].as_str().map(|s| s.to_string()),
580
+ ));
581
+ }
582
+ }
583
+ }
584
+ }
585
+ if let Some(asgi_application_path) = host_yaml["asgiApplicationPath"].as_str() {
586
+ let asgi_application_id = asgi_applications.len();
587
+ asgi_applications.push(Arc::new(load_asgi_application(
588
+ PathBuf::from_str(asgi_application_path)?.as_path(),
589
+ clear_sys_path,
590
+ )?));
591
+ host_asgi_application_ids.push(AsgiApplicationWrap::new(
592
+ domain,
593
+ ip,
594
+ Some(asgi_application_id),
595
+ Some(asgi_application_path.to_string()),
596
+ host_yaml["asgiPath"].as_str().map(|s| s.to_string()),
597
+ locations,
598
+ ));
599
+ } else if !locations.is_empty() {
600
+ host_asgi_application_ids.push(AsgiApplicationWrap::new(
601
+ domain,
602
+ ip,
603
+ None,
604
+ None,
605
+ host_yaml["asgiPath"].as_str().map(|s| s.to_string()),
606
+ locations,
607
+ ));
608
+ }
609
+ }
610
+ }
611
+
612
+ let cancel_token: CancellationToken = CancellationToken::new();
613
+ let cancel_token_thread = cancel_token.clone();
614
+ let mut asgi_event_loop_communication = Vec::new();
615
+ let mut asgi_event_loop_communication_thread = Vec::new();
616
+
617
+ for _ in 0..asgi_applications.len() {
618
+ let (tx, rx_thread) = async_channel::unbounded::<()>();
619
+ let (tx_thread, rx) = async_channel::unbounded::<AsgiChannelResult>();
620
+ asgi_event_loop_communication.push((tx, rx));
621
+ asgi_event_loop_communication_thread.push((tx_thread, rx_thread));
622
+ }
623
+
624
+ let available_parallelism = thread::available_parallelism()?.get();
625
+
626
+ // Initialize a single-threaded (due to Python's GIL) Tokio runtime to be used as an intermediary event loop for asynchronous Python
627
+ let mut runtime_builder = tokio::runtime::Builder::new_multi_thread();
628
+ runtime_builder
629
+ .worker_threads(1)
630
+ .enable_all()
631
+ .thread_name("python-async-pool");
632
+ pyo3_async_runtimes::tokio::init(runtime_builder);
633
+
634
+ // Create and spawn a task in the Tokio runtime for ASGI
635
+ let runtime = tokio::runtime::Builder::new_multi_thread()
636
+ .worker_threads(match available_parallelism / 2 {
637
+ 0 => 1,
638
+ non_zero => non_zero,
639
+ })
640
+ .enable_all()
641
+ .thread_name("asgi-pool")
642
+ .build()?;
643
+
644
+ runtime.spawn(asgi_init_event_loop_fn(
645
+ cancel_token_thread,
646
+ asgi_applications,
647
+ asgi_event_loop_communication_thread,
648
+ ));
649
+
650
+ Ok(Box::new(AsgiModule::new(
651
+ global_asgi_application_id,
652
+ global_asgi_path,
653
+ Arc::new(host_asgi_application_ids),
654
+ cancel_token,
655
+ asgi_event_loop_communication,
656
+ runtime,
657
+ )))
658
+ }
659
+
660
+ struct AsgiModule {
661
+ global_asgi_application_id: Option<usize>,
662
+ global_asgi_path: Option<String>,
663
+ host_asgi_application_ids: Arc<Vec<AsgiApplicationWrap>>,
664
+ cancel_token: CancellationToken,
665
+ asgi_event_loop_communication: AsgiEventLoopCommunication,
666
+ #[allow(dead_code)]
667
+ runtime: Runtime,
668
+ }
669
+
670
+ impl AsgiModule {
671
+ fn new(
672
+ global_asgi_application_id: Option<usize>,
673
+ global_asgi_path: Option<String>,
674
+ host_asgi_application_ids: Arc<Vec<AsgiApplicationWrap>>,
675
+ cancel_token: CancellationToken,
676
+ asgi_event_loop_communication: AsgiEventLoopCommunication,
677
+ runtime: Runtime,
678
+ ) -> Self {
679
+ AsgiModule {
680
+ global_asgi_application_id,
681
+ global_asgi_path,
682
+ host_asgi_application_ids,
683
+ cancel_token,
684
+ asgi_event_loop_communication,
685
+ runtime,
686
+ }
687
+ }
688
+ }
689
+
690
+ impl ServerModule for AsgiModule {
691
+ fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
692
+ Box::new(AsgiModuleHandlers {
693
+ global_asgi_application_id: self.global_asgi_application_id,
694
+ global_asgi_path: self.global_asgi_path.clone(),
695
+ host_asgi_application_ids: self.host_asgi_application_ids.clone(),
696
+ asgi_event_loop_communication: self.asgi_event_loop_communication.clone(),
697
+ handle,
698
+ })
699
+ }
700
+ }
701
+
702
+ impl Drop for AsgiModule {
703
+ fn drop(&mut self) {
704
+ self.cancel_token.cancel();
705
+ }
706
+ }
707
+
708
+ struct AsgiModuleHandlers {
709
+ global_asgi_application_id: Option<usize>,
710
+ global_asgi_path: Option<String>,
711
+ host_asgi_application_ids: Arc<Vec<AsgiApplicationWrap>>,
712
+ asgi_event_loop_communication: AsgiEventLoopCommunication,
713
+ handle: Handle,
714
+ }
715
+
716
+ #[async_trait]
717
+ impl ServerModuleHandlers for AsgiModuleHandlers {
718
+ async fn request_handler(
719
+ &mut self,
720
+ request: RequestData,
721
+ config: &ServerConfig,
722
+ socket_data: &SocketData,
723
+ error_logger: &ErrorLogger,
724
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
725
+ WithRuntime::new(self.handle.clone(), async move {
726
+ let hyper_request = request.get_hyper_request();
727
+
728
+ // Use .take() instead of .clone(), since the values in Options will only be used once.
729
+ let mut asgi_application_id = self.global_asgi_application_id.take();
730
+ let mut asgi_path = self.global_asgi_path.take();
731
+
732
+ // Should have used a HashMap instead of iterating over an array for better performance...
733
+ for host_asgi_application_wrap in self.host_asgi_application_ids.iter() {
734
+ if match_hostname(
735
+ match &host_asgi_application_wrap.domain {
736
+ Some(value) => Some(value as &str),
737
+ None => None,
738
+ },
739
+ match hyper_request.headers().get(header::HOST) {
740
+ Some(value) => value.to_str().ok(),
741
+ None => None,
742
+ },
743
+ ) && match &host_asgi_application_wrap.ip {
744
+ Some(value) => ip_match(value as &str, socket_data.remote_addr.ip()),
745
+ None => true,
746
+ } {
747
+ asgi_application_id = host_asgi_application_wrap.asgi_application_id;
748
+ asgi_path = host_asgi_application_wrap.asgi_path.clone();
749
+ if let Ok(path_decoded) = urlencoding::decode(
750
+ request
751
+ .get_original_url()
752
+ .unwrap_or(request.get_hyper_request().uri())
753
+ .path(),
754
+ ) {
755
+ for location_wrap in host_asgi_application_wrap.locations.iter() {
756
+ if match_location(&location_wrap.path, &path_decoded) {
757
+ asgi_application_id = Some(location_wrap.asgi_application_id);
758
+ asgi_path = location_wrap.asgi_path.clone();
759
+ break;
760
+ }
761
+ }
762
+ }
763
+ break;
764
+ }
765
+ }
766
+
767
+ let request_path = hyper_request.uri().path();
768
+ let mut request_path_bytes = request_path.bytes();
769
+ if request_path_bytes.len() < 1 || request_path_bytes.nth(0) != Some(b'/') {
770
+ return Ok(
771
+ ResponseData::builder(request)
772
+ .status(StatusCode::BAD_REQUEST)
773
+ .build(),
774
+ );
775
+ }
776
+
777
+ if let Some(asgi_application_id) = asgi_application_id {
778
+ let asgi_path = asgi_path.unwrap_or("/".to_string());
779
+ let mut canonical_asgi_path: &str = &asgi_path;
780
+ if canonical_asgi_path.bytes().last() == Some(b'/') {
781
+ canonical_asgi_path = &canonical_asgi_path[..(canonical_asgi_path.len() - 1)];
782
+ }
783
+
784
+ let request_path_with_slashes = match request_path == canonical_asgi_path {
785
+ true => format!("{}/", request_path),
786
+ false => request_path.to_string(),
787
+ };
788
+ if let Some(stripped_request_path) =
789
+ request_path_with_slashes.strip_prefix(canonical_asgi_path)
790
+ {
791
+ let wwwroot_yaml = &config["wwwroot"];
792
+ let wwwroot = wwwroot_yaml.as_str().unwrap_or("/nonexistent");
793
+
794
+ let wwwroot_unknown = PathBuf::from(wwwroot);
795
+ let wwwroot_pathbuf = match wwwroot_unknown.as_path().is_absolute() {
796
+ true => wwwroot_unknown,
797
+ false => match fs::canonicalize(&wwwroot_unknown).await {
798
+ Ok(pathbuf) => pathbuf,
799
+ Err(_) => wwwroot_unknown,
800
+ },
801
+ };
802
+ let wwwroot = wwwroot_pathbuf.as_path();
803
+
804
+ let mut relative_path = &request_path[1..];
805
+ while relative_path.as_bytes().first().copied() == Some(b'/') {
806
+ relative_path = &relative_path[1..];
807
+ }
808
+
809
+ let decoded_relative_path = match urlencoding::decode(relative_path) {
810
+ Ok(path) => path.to_string(),
811
+ Err(_) => {
812
+ return Ok(
813
+ ResponseData::builder(request)
814
+ .status(StatusCode::BAD_REQUEST)
815
+ .build(),
816
+ );
817
+ }
818
+ };
819
+
820
+ let joined_pathbuf = wwwroot.join(decoded_relative_path);
821
+ let execute_pathbuf = joined_pathbuf;
822
+ let execute_path_info = stripped_request_path
823
+ .strip_prefix("/")
824
+ .map(|s| s.to_string());
825
+
826
+ let (tx, rx) = {
827
+ let (tx, rx) = &self.asgi_event_loop_communication[asgi_application_id];
828
+ tx.send(()).await?;
829
+ rx.recv().await??
830
+ };
831
+
832
+ return execute_asgi(
833
+ request,
834
+ socket_data,
835
+ error_logger,
836
+ wwwroot,
837
+ execute_pathbuf,
838
+ execute_path_info,
839
+ config["serverAdministratorEmail"].as_str(),
840
+ tx,
841
+ rx,
842
+ )
843
+ .await;
844
+ }
845
+ }
846
+ Ok(ResponseData::builder(request).build())
847
+ })
848
+ .await
849
+ }
850
+
851
+ async fn proxy_request_handler(
852
+ &mut self,
853
+ request: RequestData,
854
+ _config: &ServerConfig,
855
+ _socket_data: &SocketData,
856
+ _error_logger: &ErrorLogger,
857
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
858
+ Ok(ResponseData::builder(request).build())
859
+ }
860
+
861
+ async fn response_modifying_handler(
862
+ &mut self,
863
+ response: HyperResponse,
864
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
865
+ Ok(response)
866
+ }
867
+
868
+ async fn proxy_response_modifying_handler(
869
+ &mut self,
870
+ response: HyperResponse,
871
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
872
+ Ok(response)
873
+ }
874
+
875
+ async fn connect_proxy_request_handler(
876
+ &mut self,
877
+ _upgraded_request: HyperUpgraded,
878
+ _connect_address: &str,
879
+ _config: &ServerConfig,
880
+ _socket_data: &SocketData,
881
+ _error_logger: &ErrorLogger,
882
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
883
+ Ok(())
884
+ }
885
+
886
+ fn does_connect_proxy_requests(&mut self) -> bool {
887
+ false
888
+ }
889
+
890
+ async fn websocket_request_handler(
891
+ &mut self,
892
+ websocket: HyperWebsocket,
893
+ uri: &hyper::Uri,
894
+ config: &ServerConfig,
895
+ socket_data: &SocketData,
896
+ error_logger: &ErrorLogger,
897
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
898
+ WithRuntime::new(self.handle.clone(), async move {
899
+ // Use .take() instead of .clone(), since the values in Options will only be used once.
900
+ let mut asgi_application_id = self.global_asgi_application_id.take();
901
+ let mut asgi_path = self.global_asgi_path.take();
902
+
903
+ // Should have used a HashMap instead of iterating over an array for better performance...
904
+ for host_asgi_application_wrap in self.host_asgi_application_ids.iter() {
905
+ // Workaround for Ferron not providing the domain name for WebSocket connections
906
+ let config_test_domain = host_asgi_application_wrap
907
+ .domain
908
+ .as_ref()
909
+ .map(|value| value as &str);
910
+ let obtained_domain = config["domain"].as_str();
911
+ if config_test_domain == obtained_domain
912
+ && config["asgiApplicationPath"].as_str()
913
+ == host_asgi_application_wrap.asgi_application_path.as_deref()
914
+ && match &host_asgi_application_wrap.ip {
915
+ Some(value) => ip_match(value as &str, socket_data.remote_addr.ip()),
916
+ None => true,
917
+ }
918
+ {
919
+ asgi_application_id = host_asgi_application_wrap.asgi_application_id;
920
+ asgi_path = host_asgi_application_wrap.asgi_path.clone();
921
+ if let Ok(path_decoded) = urlencoding::decode(uri.path()) {
922
+ for location_wrap in host_asgi_application_wrap.locations.iter() {
923
+ if match_location(&location_wrap.path, &path_decoded) {
924
+ asgi_application_id = Some(location_wrap.asgi_application_id);
925
+ asgi_path = location_wrap.asgi_path.clone();
926
+ break;
927
+ }
928
+ }
929
+ }
930
+ break;
931
+ }
932
+ }
933
+
934
+ let request_path = uri.path();
935
+ let mut request_path_bytes = request_path.bytes();
936
+ if request_path_bytes.len() < 1 || request_path_bytes.nth(0) != Some(b'/') {
937
+ return Ok(());
938
+ }
939
+
940
+ if let Some(asgi_application_id) = asgi_application_id {
941
+ let asgi_path = asgi_path.unwrap_or("/".to_string());
942
+ let mut canonical_asgi_path: &str = &asgi_path;
943
+ if canonical_asgi_path.bytes().last() == Some(b'/') {
944
+ canonical_asgi_path = &canonical_asgi_path[..(canonical_asgi_path.len() - 1)];
945
+ }
946
+
947
+ let request_path_with_slashes = match request_path == canonical_asgi_path {
948
+ true => format!("{}/", request_path),
949
+ false => request_path.to_string(),
950
+ };
951
+ if let Some(stripped_request_path) =
952
+ request_path_with_slashes.strip_prefix(canonical_asgi_path)
953
+ {
954
+ let wwwroot_yaml = &config["wwwroot"];
955
+ let wwwroot = wwwroot_yaml.as_str().unwrap_or("/nonexistent");
956
+
957
+ let wwwroot_unknown = PathBuf::from(wwwroot);
958
+ let wwwroot_pathbuf = match wwwroot_unknown.as_path().is_absolute() {
959
+ true => wwwroot_unknown,
960
+ false => match fs::canonicalize(&wwwroot_unknown).await {
961
+ Ok(pathbuf) => pathbuf,
962
+ Err(_) => wwwroot_unknown,
963
+ },
964
+ };
965
+ let wwwroot = wwwroot_pathbuf.as_path();
966
+
967
+ let mut relative_path = &request_path[1..];
968
+ while relative_path.as_bytes().first().copied() == Some(b'/') {
969
+ relative_path = &relative_path[1..];
970
+ }
971
+
972
+ let decoded_relative_path = match urlencoding::decode(relative_path) {
973
+ Ok(path) => path.to_string(),
974
+ Err(_) => {
975
+ return Ok(());
976
+ }
977
+ };
978
+
979
+ let joined_pathbuf = wwwroot.join(decoded_relative_path);
980
+ let execute_pathbuf = joined_pathbuf;
981
+ let execute_path_info = stripped_request_path
982
+ .strip_prefix("/")
983
+ .map(|s| s.to_string());
984
+
985
+ let (tx, rx) = {
986
+ let (tx, rx) = &self.asgi_event_loop_communication[asgi_application_id];
987
+ tx.send(()).await?;
988
+ rx.recv().await??
989
+ };
990
+
991
+ return execute_asgi_websocket(
992
+ websocket,
993
+ uri,
994
+ socket_data,
995
+ error_logger,
996
+ wwwroot,
997
+ execute_pathbuf,
998
+ execute_path_info,
999
+ config["serverAdministratorEmail"].as_str(),
1000
+ tx,
1001
+ rx,
1002
+ )
1003
+ .await;
1004
+ }
1005
+ }
1006
+ Ok(())
1007
+ })
1008
+ .await
1009
+ }
1010
+
1011
+ fn does_websocket_requests(&mut self, config: &ServerConfig, _socket_data: &SocketData) -> bool {
1012
+ config["asgiApplicationPath"].as_str().is_some()
1013
+ }
1014
+ }
1015
+
1016
+ #[allow(clippy::too_many_arguments)]
1017
+ async fn execute_asgi(
1018
+ request: RequestData,
1019
+ socket_data: &SocketData,
1020
+ error_logger: &ErrorLogger,
1021
+ wwwroot: &Path,
1022
+ execute_pathbuf: PathBuf,
1023
+ _path_info: Option<String>,
1024
+ _server_administrator_email: Option<&str>,
1025
+ asgi_tx: Sender<IncomingAsgiMessage>,
1026
+ asgi_rx: Receiver<OutgoingAsgiMessage>,
1027
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
1028
+ let (hyper_request, _, original_request_uri) = request.into_parts();
1029
+ let (hyper_request_parts, request_body) = hyper_request.into_parts();
1030
+ asgi_tx
1031
+ .send(IncomingAsgiMessage::Init(AsgiInitData::Http(
1032
+ AsgiHttpInitData {
1033
+ hyper_request_parts,
1034
+ original_request_uri,
1035
+ socket_data: SocketData {
1036
+ remote_addr: socket_data.remote_addr,
1037
+ local_addr: socket_data.local_addr,
1038
+ encrypted: socket_data.encrypted,
1039
+ },
1040
+ error_logger: error_logger.clone(),
1041
+ wwwroot: wwwroot.to_path_buf(),
1042
+ execute_pathbuf,
1043
+ },
1044
+ )))
1045
+ .await?;
1046
+
1047
+ let mut request_body_stream = request_body.into_data_stream();
1048
+ let asgi_tx_clone = asgi_tx.clone();
1049
+
1050
+ tokio::spawn(async move {
1051
+ loop {
1052
+ match request_body_stream.next().await {
1053
+ Some(Ok(data)) => asgi_tx_clone
1054
+ .send(IncomingAsgiMessage::Message(
1055
+ IncomingAsgiMessageInner::HttpRequest(AsgiHttpBody {
1056
+ body: data.to_vec(),
1057
+ more_body: true,
1058
+ }),
1059
+ ))
1060
+ .await
1061
+ .unwrap_or_default(),
1062
+ Some(Err(_)) => {
1063
+ asgi_tx_clone
1064
+ .send(IncomingAsgiMessage::Message(
1065
+ IncomingAsgiMessageInner::HttpDisconnect,
1066
+ ))
1067
+ .await
1068
+ .unwrap_or_default();
1069
+ }
1070
+ None => {
1071
+ asgi_tx_clone
1072
+ .send(IncomingAsgiMessage::Message(
1073
+ IncomingAsgiMessageInner::HttpRequest(AsgiHttpBody {
1074
+ body: b"".to_vec(),
1075
+ more_body: false,
1076
+ }),
1077
+ ))
1078
+ .await
1079
+ .unwrap_or_default();
1080
+ break;
1081
+ }
1082
+ }
1083
+ }
1084
+ });
1085
+
1086
+ let asgi_http_response_start;
1087
+
1088
+ loop {
1089
+ match asgi_rx.recv().await? {
1090
+ OutgoingAsgiMessage::Finished => Err(anyhow::anyhow!(
1091
+ "ASGI application returned before sending the HTTP response start event"
1092
+ ))?,
1093
+ OutgoingAsgiMessage::Error(err) => Err(err)?,
1094
+ OutgoingAsgiMessage::Message(OutgoingAsgiMessageInner::HttpResponseStart(
1095
+ http_response_start,
1096
+ )) => {
1097
+ asgi_http_response_start = http_response_start;
1098
+ break;
1099
+ }
1100
+ _ => (),
1101
+ }
1102
+ }
1103
+
1104
+ let response_body_stream = futures_util::stream::unfold(
1105
+ (asgi_tx, asgi_rx, false),
1106
+ move |(asgi_tx, asgi_rx, request_end)| {
1107
+ let has_trailers = asgi_http_response_start.trailers;
1108
+ async move {
1109
+ if request_end {
1110
+ asgi_tx
1111
+ .send(IncomingAsgiMessage::Message(
1112
+ IncomingAsgiMessageInner::HttpDisconnect,
1113
+ ))
1114
+ .await
1115
+ .unwrap_or_default();
1116
+ return None;
1117
+ }
1118
+ loop {
1119
+ match asgi_rx.recv().await {
1120
+ Err(err) => {
1121
+ return Some((
1122
+ Err(std::io::Error::other(err.to_string())),
1123
+ (asgi_tx, asgi_rx, false),
1124
+ ))
1125
+ }
1126
+ Ok(OutgoingAsgiMessage::Finished) => return None,
1127
+ Ok(OutgoingAsgiMessage::Error(err)) => {
1128
+ return Some((
1129
+ Err(std::io::Error::other(err.to_string())),
1130
+ (asgi_tx, asgi_rx, false),
1131
+ ))
1132
+ }
1133
+ Ok(OutgoingAsgiMessage::Message(OutgoingAsgiMessageInner::HttpResponseBody(
1134
+ http_response_body,
1135
+ ))) => {
1136
+ if !http_response_body.more_body {
1137
+ if http_response_body.body.is_empty() {
1138
+ if !has_trailers {
1139
+ asgi_tx
1140
+ .send(IncomingAsgiMessage::Message(
1141
+ IncomingAsgiMessageInner::HttpDisconnect,
1142
+ ))
1143
+ .await
1144
+ .unwrap_or_default();
1145
+ return None;
1146
+ }
1147
+ } else {
1148
+ return Some((
1149
+ Ok(Frame::data(Bytes::from(http_response_body.body))),
1150
+ (asgi_tx, asgi_rx, !has_trailers),
1151
+ ));
1152
+ }
1153
+ } else if !http_response_body.body.is_empty() {
1154
+ return Some((
1155
+ Ok(Frame::data(Bytes::from(http_response_body.body))),
1156
+ (asgi_tx, asgi_rx, false),
1157
+ ));
1158
+ }
1159
+ }
1160
+ Ok(OutgoingAsgiMessage::Message(OutgoingAsgiMessageInner::HttpResponseTrailers(
1161
+ http_response_trailers,
1162
+ ))) => {
1163
+ if !http_response_trailers.more_trailers {
1164
+ if http_response_trailers.headers.is_empty() {
1165
+ asgi_tx
1166
+ .send(IncomingAsgiMessage::Message(
1167
+ IncomingAsgiMessageInner::HttpDisconnect,
1168
+ ))
1169
+ .await
1170
+ .unwrap_or_default();
1171
+ return None;
1172
+ } else {
1173
+ match async {
1174
+ let mut headers = HeaderMap::new();
1175
+ for (header_name, header_value) in http_response_trailers.headers {
1176
+ if !header_name.is_empty() && header_name[0] != b':' {
1177
+ headers.append(
1178
+ HeaderName::from_bytes(&header_name)?,
1179
+ HeaderValue::from_bytes(&header_value)?,
1180
+ );
1181
+ }
1182
+ }
1183
+ Ok::<_, Box<dyn Error + Send + Sync>>(headers)
1184
+ }
1185
+ .await
1186
+ {
1187
+ Ok(headers) => {
1188
+ return Some((Ok(Frame::trailers(headers)), (asgi_tx, asgi_rx, true)))
1189
+ }
1190
+ Err(err) => {
1191
+ return Some((
1192
+ Err(std::io::Error::other(err.to_string())),
1193
+ (asgi_tx, asgi_rx, false),
1194
+ ))
1195
+ }
1196
+ }
1197
+ }
1198
+ } else if !http_response_trailers.headers.is_empty() {
1199
+ match async {
1200
+ let mut headers = HeaderMap::new();
1201
+ for (header_name, header_value) in http_response_trailers.headers {
1202
+ if !header_name.is_empty() && header_name[0] != b':' {
1203
+ headers.append(
1204
+ HeaderName::from_bytes(&header_name)?,
1205
+ HeaderValue::from_bytes(&header_value)?,
1206
+ );
1207
+ }
1208
+ }
1209
+ Ok::<_, Box<dyn Error + Send + Sync>>(headers)
1210
+ }
1211
+ .await
1212
+ {
1213
+ Ok(headers) => {
1214
+ return Some((Ok(Frame::trailers(headers)), (asgi_tx, asgi_rx, true)))
1215
+ }
1216
+ Err(err) => {
1217
+ return Some((
1218
+ Err(std::io::Error::other(err.to_string())),
1219
+ (asgi_tx, asgi_rx, false),
1220
+ ))
1221
+ }
1222
+ }
1223
+ }
1224
+ }
1225
+ _ => (),
1226
+ }
1227
+ }
1228
+ }
1229
+ },
1230
+ );
1231
+ let response_body = BodyExt::boxed(StreamBody::new(response_body_stream));
1232
+
1233
+ let mut hyper_response = Response::new(response_body);
1234
+ *hyper_response.status_mut() = StatusCode::from_u16(asgi_http_response_start.status)?;
1235
+ let headers = hyper_response.headers_mut();
1236
+ for (header_name, header_value) in asgi_http_response_start.headers {
1237
+ if !header_name.is_empty() && header_name[0] != b':' {
1238
+ headers.append(
1239
+ HeaderName::from_bytes(&header_name)?,
1240
+ HeaderValue::from_bytes(&header_value)?,
1241
+ );
1242
+ }
1243
+ }
1244
+
1245
+ Ok(
1246
+ ResponseData::builder_without_request()
1247
+ .response(hyper_response)
1248
+ .build(),
1249
+ )
1250
+ }
1251
+
1252
+ #[allow(clippy::too_many_arguments)]
1253
+ async fn execute_asgi_websocket(
1254
+ websocket: HyperWebsocket,
1255
+ uri: &hyper::Uri,
1256
+ socket_data: &SocketData,
1257
+ error_logger: &ErrorLogger,
1258
+ wwwroot: &Path,
1259
+ execute_pathbuf: PathBuf,
1260
+ _path_info: Option<String>,
1261
+ _server_administrator_email: Option<&str>,
1262
+ asgi_tx: Sender<IncomingAsgiMessage>,
1263
+ asgi_rx: Receiver<OutgoingAsgiMessage>,
1264
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
1265
+ asgi_tx
1266
+ .send(IncomingAsgiMessage::Init(AsgiInitData::Websocket(
1267
+ AsgiWebsocketInitData {
1268
+ uri: uri.to_owned(),
1269
+ socket_data: SocketData {
1270
+ remote_addr: socket_data.remote_addr,
1271
+ local_addr: socket_data.local_addr,
1272
+ encrypted: socket_data.encrypted,
1273
+ },
1274
+ error_logger: error_logger.clone(),
1275
+ wwwroot: wwwroot.to_path_buf(),
1276
+ execute_pathbuf,
1277
+ },
1278
+ )))
1279
+ .await?;
1280
+
1281
+ asgi_tx
1282
+ .send(IncomingAsgiMessage::Message(
1283
+ IncomingAsgiMessageInner::WebsocketConnect,
1284
+ ))
1285
+ .await?;
1286
+
1287
+ let client_bi_stream;
1288
+ loop {
1289
+ match asgi_rx.recv().await? {
1290
+ OutgoingAsgiMessage::Finished => Err(anyhow::anyhow!(
1291
+ "ASGI application returned before sending the WebSocket accept event"
1292
+ ))?,
1293
+ OutgoingAsgiMessage::Error(err) => Err(err)?,
1294
+ OutgoingAsgiMessage::Message(OutgoingAsgiMessageInner::WebsocketAccept(_)) => {
1295
+ client_bi_stream = websocket.await?;
1296
+ break;
1297
+ }
1298
+ OutgoingAsgiMessage::Message(OutgoingAsgiMessageInner::WebsocketClose(_)) => {
1299
+ asgi_tx
1300
+ .send(IncomingAsgiMessage::Message(
1301
+ IncomingAsgiMessageInner::WebsocketDisconnect(AsgiWebsocketClose {
1302
+ code: 1005,
1303
+ reason: "ASGI application closed the WebSocket connection before accepting it"
1304
+ .to_string(),
1305
+ }),
1306
+ ))
1307
+ .await
1308
+ .unwrap_or_default();
1309
+ }
1310
+ _ => (),
1311
+ }
1312
+ }
1313
+
1314
+ let (client_sink, mut client_stream) = client_bi_stream.split();
1315
+
1316
+ let client_disconnected_mutex = Arc::new(Mutex::new(AtomicBool::new(false)));
1317
+ let client_disconnected_mutex_clone = client_disconnected_mutex.clone();
1318
+
1319
+ let asgi_tx_clone = asgi_tx.clone();
1320
+ let (ping, pong) = async_channel::unbounded();
1321
+
1322
+ tokio::spawn(async move {
1323
+ while let Some(websocket_frame) = client_stream.next().await {
1324
+ match websocket_frame {
1325
+ Err(_) => {
1326
+ let client_disconnected = client_disconnected_mutex_clone.lock().await;
1327
+ if !client_disconnected.load(Ordering::Relaxed) {
1328
+ client_disconnected.store(true, Ordering::Relaxed);
1329
+ asgi_tx_clone
1330
+ .send(IncomingAsgiMessage::Message(
1331
+ IncomingAsgiMessageInner::WebsocketDisconnect(AsgiWebsocketClose {
1332
+ code: 1005,
1333
+ reason: "Error while receiving WebSocket data".to_string(),
1334
+ }),
1335
+ ))
1336
+ .await
1337
+ .unwrap_or_default();
1338
+ }
1339
+ }
1340
+ Ok(Message::Ping(message)) => {
1341
+ ping.send(message).await.unwrap_or_default();
1342
+ }
1343
+ Ok(Message::Binary(message)) => {
1344
+ asgi_tx_clone
1345
+ .send(IncomingAsgiMessage::Message(
1346
+ IncomingAsgiMessageInner::WebsocketReceive(AsgiWebsocketMessage {
1347
+ bytes: Some(message.to_vec()),
1348
+ text: None,
1349
+ }),
1350
+ ))
1351
+ .await
1352
+ .unwrap_or_default();
1353
+ }
1354
+ Ok(Message::Text(message)) => {
1355
+ asgi_tx_clone
1356
+ .send(IncomingAsgiMessage::Message(
1357
+ IncomingAsgiMessageInner::WebsocketReceive(AsgiWebsocketMessage {
1358
+ bytes: None,
1359
+ text: Some(message.to_string()),
1360
+ }),
1361
+ ))
1362
+ .await
1363
+ .unwrap_or_default();
1364
+ }
1365
+ Ok(Message::Close(close_frame)) => {
1366
+ let client_disconnected = client_disconnected_mutex_clone.lock().await;
1367
+ if !client_disconnected.load(Ordering::Relaxed) {
1368
+ client_disconnected.store(true, Ordering::Relaxed);
1369
+ client_disconnected_mutex_clone
1370
+ .lock()
1371
+ .await
1372
+ .store(true, Ordering::Relaxed);
1373
+ let (status_code, message) = if let Some(close_frame) = close_frame {
1374
+ (close_frame.code.into(), close_frame.reason.to_string())
1375
+ } else {
1376
+ (
1377
+ 1005,
1378
+ "Websocket connection closed for unknown reason".to_string(),
1379
+ )
1380
+ };
1381
+ asgi_tx_clone
1382
+ .send(IncomingAsgiMessage::Message(
1383
+ IncomingAsgiMessageInner::WebsocketDisconnect(AsgiWebsocketClose {
1384
+ code: status_code,
1385
+ reason: message,
1386
+ }),
1387
+ ))
1388
+ .await
1389
+ .unwrap_or_default();
1390
+ }
1391
+ }
1392
+ _ => (),
1393
+ }
1394
+ }
1395
+ });
1396
+
1397
+ let client_sink_mutex = Arc::new(Mutex::new(client_sink));
1398
+ let client_sink_mutex_cloned = client_sink_mutex.clone();
1399
+
1400
+ tokio::spawn(async move {
1401
+ while let Ok(message) = pong.recv().await {
1402
+ if client_sink_mutex_cloned
1403
+ .lock()
1404
+ .await
1405
+ .send(Message::Pong(message))
1406
+ .await
1407
+ .is_err()
1408
+ {
1409
+ break;
1410
+ }
1411
+ }
1412
+ });
1413
+
1414
+ loop {
1415
+ match asgi_rx.recv().await? {
1416
+ OutgoingAsgiMessage::Finished => Err(anyhow::anyhow!(
1417
+ "ASGI application returned before sending the WebSocket accept event"
1418
+ ))?,
1419
+ OutgoingAsgiMessage::Error(err) => Err(err)?,
1420
+ OutgoingAsgiMessage::Message(OutgoingAsgiMessageInner::WebsocketSend(websocket_message)) => {
1421
+ let frame_option = if let Some(bytes) = websocket_message.bytes {
1422
+ Some(Message::binary(bytes))
1423
+ } else {
1424
+ websocket_message.text.map(Message::text)
1425
+ };
1426
+ if let Some(frame) = frame_option {
1427
+ let mut client_sink = client_sink_mutex.lock().await;
1428
+ if let Err(err) = client_sink.send(frame).await {
1429
+ drop(client_sink);
1430
+ let client_disconnected = client_disconnected_mutex.lock().await;
1431
+ if !client_disconnected.load(Ordering::Relaxed) {
1432
+ client_disconnected.store(true, Ordering::Relaxed);
1433
+ asgi_tx
1434
+ .send(IncomingAsgiMessage::Message(
1435
+ IncomingAsgiMessageInner::WebsocketDisconnect(AsgiWebsocketClose {
1436
+ code: 1005,
1437
+ reason: "Error while sending WebSocket data".to_string(),
1438
+ }),
1439
+ ))
1440
+ .await
1441
+ .unwrap_or_default();
1442
+ }
1443
+ Err(err)?;
1444
+ }
1445
+ }
1446
+ }
1447
+ OutgoingAsgiMessage::Message(OutgoingAsgiMessageInner::WebsocketClose(websocket_close)) => {
1448
+ let client_disconnected = client_disconnected_mutex.lock().await;
1449
+ if !client_disconnected.load(Ordering::Relaxed) {
1450
+ client_disconnected.store(true, Ordering::Relaxed);
1451
+ asgi_tx
1452
+ .send(IncomingAsgiMessage::Message(
1453
+ IncomingAsgiMessageInner::WebsocketDisconnect(AsgiWebsocketClose {
1454
+ code: websocket_close.code,
1455
+ reason: websocket_close.reason.clone(),
1456
+ }),
1457
+ ))
1458
+ .await
1459
+ .unwrap_or_default();
1460
+ }
1461
+ let mut client_sink = client_sink_mutex.lock().await;
1462
+ client_sink
1463
+ .send(Message::Close(Some(CloseFrame {
1464
+ code: websocket_close.code.into(),
1465
+ reason: websocket_close.reason.into(),
1466
+ })))
1467
+ .await?;
1468
+ client_sink.close().await.unwrap_or_default();
1469
+ break;
1470
+ }
1471
+ _ => (),
1472
+ }
1473
+ }
1474
+
1475
+ Ok(())
1476
+ }
ferron/src/optional_modules/cache.rs ADDED
@@ -0,0 +1,525 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use std::collections::HashMap;
2
+ use std::error::Error;
3
+ use std::hash::RandomState;
4
+ use std::sync::Arc;
5
+ use std::time::{Duration, Instant};
6
+
7
+ use crate::ferron_common::{
8
+ ErrorLogger, HyperUpgraded, RequestData, ResponseData, ServerConfig, ServerModule,
9
+ ServerModuleHandlers, SocketData,
10
+ };
11
+ use crate::ferron_common::{HyperResponse, WithRuntime};
12
+ use async_trait::async_trait;
13
+ use cache_control::{Cachability, CacheControl};
14
+ use futures_util::{StreamExt, TryStreamExt};
15
+ use hashlink::LinkedHashMap;
16
+ use http_body_util::{BodyExt, Full, StreamBody};
17
+ use hyper::body::{Bytes, Frame};
18
+ use hyper::header::HeaderValue;
19
+ use hyper::{header, HeaderMap, Method, Response, StatusCode};
20
+ use hyper_tungstenite::HyperWebsocket;
21
+ use itertools::Itertools;
22
+ use tokio::runtime::Handle;
23
+ use tokio::sync::RwLock;
24
+
25
+ const CACHE_HEADER_NAME: &str = "X-Ferron-Cache";
26
+ const DEFAULT_MAX_AGE: u64 = 300;
27
+
28
+ pub fn server_module_init(
29
+ config: &ServerConfig,
30
+ ) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
31
+ let maximum_cache_entries = config["global"]["maximumCacheEntries"]
32
+ .as_i64()
33
+ .map(|v| v as usize);
34
+
35
+ Ok(Box::new(CacheModule::new(
36
+ Arc::new(RwLock::new(LinkedHashMap::with_hasher(RandomState::new()))),
37
+ Arc::new(RwLock::new(HashMap::new())),
38
+ maximum_cache_entries,
39
+ )))
40
+ }
41
+
42
+ #[allow(clippy::type_complexity)]
43
+ struct CacheModule {
44
+ cache: Arc<
45
+ RwLock<
46
+ LinkedHashMap<
47
+ String,
48
+ (
49
+ StatusCode,
50
+ HeaderMap,
51
+ Vec<u8>,
52
+ Instant,
53
+ Option<CacheControl>,
54
+ ),
55
+ RandomState,
56
+ >,
57
+ >,
58
+ >,
59
+ vary_cache: Arc<RwLock<HashMap<String, Vec<String>>>>,
60
+ maximum_cache_entries: Option<usize>,
61
+ }
62
+
63
+ impl CacheModule {
64
+ #[allow(clippy::type_complexity)]
65
+ fn new(
66
+ cache: Arc<
67
+ RwLock<
68
+ LinkedHashMap<
69
+ String,
70
+ (
71
+ StatusCode,
72
+ HeaderMap,
73
+ Vec<u8>,
74
+ Instant,
75
+ Option<CacheControl>,
76
+ ),
77
+ RandomState,
78
+ >,
79
+ >,
80
+ >,
81
+ vary_cache: Arc<RwLock<HashMap<String, Vec<String>>>>,
82
+ maximum_cache_entries: Option<usize>,
83
+ ) -> Self {
84
+ Self {
85
+ cache,
86
+ vary_cache,
87
+ maximum_cache_entries,
88
+ }
89
+ }
90
+ }
91
+
92
+ impl ServerModule for CacheModule {
93
+ fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
94
+ Box::new(CacheModuleHandlers {
95
+ cache: self.cache.clone(),
96
+ vary_cache: self.vary_cache.clone(),
97
+ maximum_cache_entries: self.maximum_cache_entries,
98
+ cache_vary_headers_configured: Vec::new(),
99
+ cache_ignore_headers_configured: Vec::new(),
100
+ maximum_cached_response_size: None,
101
+ cache_key: None,
102
+ request_headers: HeaderMap::new(),
103
+ has_authorization: false,
104
+ cached: false,
105
+ no_store: false,
106
+ handle,
107
+ })
108
+ }
109
+ }
110
+
111
+ #[allow(clippy::type_complexity)]
112
+ struct CacheModuleHandlers {
113
+ handle: Handle,
114
+ cache: Arc<
115
+ RwLock<
116
+ LinkedHashMap<
117
+ String,
118
+ (
119
+ StatusCode,
120
+ HeaderMap,
121
+ Vec<u8>,
122
+ Instant,
123
+ Option<CacheControl>,
124
+ ),
125
+ RandomState,
126
+ >,
127
+ >,
128
+ >,
129
+ vary_cache: Arc<RwLock<HashMap<String, Vec<String>>>>,
130
+ maximum_cache_entries: Option<usize>,
131
+ cache_vary_headers_configured: Vec<String>,
132
+ cache_ignore_headers_configured: Vec<String>,
133
+ maximum_cached_response_size: Option<u64>,
134
+ cache_key: Option<String>,
135
+ request_headers: HeaderMap<HeaderValue>,
136
+ has_authorization: bool,
137
+ cached: bool,
138
+ no_store: bool,
139
+ }
140
+
141
+ #[async_trait]
142
+ impl ServerModuleHandlers for CacheModuleHandlers {
143
+ async fn request_handler(
144
+ &mut self,
145
+ request: RequestData,
146
+ config: &ServerConfig,
147
+ socket_data: &SocketData,
148
+ _error_logger: &ErrorLogger,
149
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
150
+ WithRuntime::new(self.handle.clone(), async move {
151
+ self.cache_vary_headers_configured = match config["cacheVaryHeaders"].as_vec() {
152
+ Some(vector) => {
153
+ let mut new_vector = Vec::new();
154
+ for yaml_value in vector.iter() {
155
+ if let Some(str_value) = yaml_value.as_str() {
156
+ new_vector.push(str_value.to_string());
157
+ }
158
+ }
159
+ new_vector
160
+ }
161
+ None => Vec::new(),
162
+ };
163
+ self.cache_ignore_headers_configured = match config["cacheIgnoreHeaders"].as_vec() {
164
+ Some(vector) => {
165
+ let mut new_vector = Vec::new();
166
+ for yaml_value in vector.iter() {
167
+ if let Some(str_value) = yaml_value.as_str() {
168
+ new_vector.push(str_value.to_string());
169
+ }
170
+ }
171
+ new_vector
172
+ }
173
+ None => Vec::new(),
174
+ };
175
+ self.maximum_cached_response_size = config["maximumCachedResponseSize"]
176
+ .as_i64()
177
+ .map(|f| f as u64);
178
+
179
+ let hyper_request = request.get_hyper_request();
180
+ let cache_key = format!(
181
+ "{} {}{}{}{}",
182
+ hyper_request.method().as_str(),
183
+ match socket_data.encrypted {
184
+ false => "http://",
185
+ true => "https://",
186
+ },
187
+ match hyper_request.headers().get(header::HOST) {
188
+ Some(host) => String::from_utf8_lossy(host.as_bytes()).into_owned(),
189
+ None => "".to_string(),
190
+ },
191
+ hyper_request.uri().path(),
192
+ match hyper_request.uri().query() {
193
+ Some(query) => format!("?{}", query),
194
+ None => "".to_string(),
195
+ }
196
+ );
197
+
198
+ let request_cache_control = match hyper_request.headers().get(header::CACHE_CONTROL) {
199
+ Some(value) => CacheControl::from_value(&String::from_utf8_lossy(value.as_bytes())),
200
+ None => None,
201
+ };
202
+
203
+ let mut no_store = false;
204
+ let mut no_cache = false;
205
+
206
+ if let Some(request_cache_control) = request_cache_control {
207
+ no_store = request_cache_control.no_store;
208
+ if let Some(cachability) = request_cache_control.cachability {
209
+ if cachability == Cachability::NoCache {
210
+ no_cache = true;
211
+ }
212
+ }
213
+ }
214
+
215
+ match hyper_request.method() {
216
+ &Method::GET | &Method::HEAD => (),
217
+ _ => {
218
+ no_store = true;
219
+ }
220
+ };
221
+
222
+ if no_store {
223
+ self.no_store = true;
224
+ return Ok(ResponseData::builder(request).build());
225
+ }
226
+
227
+ if !no_cache {
228
+ let rwlock_read = self.vary_cache.read().await;
229
+ let processed_vary = rwlock_read.get(&cache_key);
230
+ if let Some(processed_vary) = processed_vary {
231
+ let cache_key_with_vary = format!(
232
+ "{}\n{}",
233
+ &cache_key,
234
+ processed_vary
235
+ .iter()
236
+ .map(|header_name| {
237
+ match hyper_request.headers().get(header_name) {
238
+ Some(header_value) => format!(
239
+ "{}: {}",
240
+ header_name,
241
+ String::from_utf8_lossy(header_value.as_bytes()).into_owned()
242
+ ),
243
+ None => "".to_string(),
244
+ }
245
+ })
246
+ .collect::<Vec<String>>()
247
+ .join("\n")
248
+ );
249
+
250
+ drop(rwlock_read);
251
+
252
+ let rwlock_read = self.cache.read().await;
253
+ let cached_entry_option = rwlock_read.get(&cache_key_with_vary);
254
+
255
+ if let Some((status_code, headers, body, timestamp, response_cache_control)) =
256
+ cached_entry_option
257
+ {
258
+ let max_age = match response_cache_control {
259
+ Some(response_cache_control) => match response_cache_control.s_max_age {
260
+ Some(s_max_age) => Some(s_max_age),
261
+ None => response_cache_control.max_age,
262
+ },
263
+ None => None,
264
+ };
265
+
266
+ let mut cached = true;
267
+
268
+ if timestamp.elapsed() > max_age.unwrap_or(Duration::from_secs(DEFAULT_MAX_AGE)) {
269
+ cached = false;
270
+ }
271
+
272
+ if cached {
273
+ self.cached = true;
274
+ let mut hyper_response_builder = Response::builder().status(status_code);
275
+ for (header_name, header_value) in headers.iter() {
276
+ hyper_response_builder = hyper_response_builder.header(header_name, header_value);
277
+ }
278
+ let hyper_response = hyper_response_builder.body(
279
+ Full::new(Bytes::from(body.clone()))
280
+ .map_err(|e| match e {})
281
+ .boxed(),
282
+ )?;
283
+ return Ok(
284
+ ResponseData::builder(request)
285
+ .response(hyper_response)
286
+ .build(),
287
+ );
288
+ } else {
289
+ drop(rwlock_read);
290
+ }
291
+ }
292
+ } else {
293
+ drop(rwlock_read);
294
+ }
295
+ }
296
+
297
+ self.request_headers = hyper_request.headers().clone();
298
+ self.cache_key = Some(cache_key);
299
+ self.has_authorization = hyper_request.headers().contains_key(header::AUTHORIZATION);
300
+
301
+ Ok(ResponseData::builder(request).build())
302
+ })
303
+ .await
304
+ }
305
+
306
+ async fn proxy_request_handler(
307
+ &mut self,
308
+ request: RequestData,
309
+ _config: &ServerConfig,
310
+ _socket_data: &SocketData,
311
+ _error_logger: &ErrorLogger,
312
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
313
+ Ok(ResponseData::builder(request).build())
314
+ }
315
+
316
+ async fn response_modifying_handler(
317
+ &mut self,
318
+ mut response: HyperResponse,
319
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
320
+ WithRuntime::new(self.handle.clone(), async move {
321
+ if self.no_store {
322
+ response
323
+ .headers_mut()
324
+ .insert(CACHE_HEADER_NAME, HeaderValue::from_str("BYPASS")?);
325
+ Ok(response)
326
+ } else if self.cached {
327
+ response
328
+ .headers_mut()
329
+ .insert(CACHE_HEADER_NAME, HeaderValue::from_str("HIT")?);
330
+ Ok(response)
331
+ } else if let Some(cache_key) = &self.cache_key {
332
+ let (mut response_parts, mut response_body) = response.into_parts();
333
+ let response_cache_control = match response_parts.headers.get(header::CACHE_CONTROL) {
334
+ Some(value) => CacheControl::from_value(&String::from_utf8_lossy(value.as_bytes())),
335
+ None => None,
336
+ };
337
+
338
+ let should_cache_response = match &response_cache_control {
339
+ Some(response_cache_control) => {
340
+ let is_private = response_cache_control.cachability == Some(Cachability::Private);
341
+ let is_public = response_cache_control.cachability == Some(Cachability::Public);
342
+
343
+ !response_cache_control.no_store
344
+ && !is_private
345
+ && (is_public
346
+ || (!self.has_authorization
347
+ && (response_cache_control.max_age.is_some()
348
+ || response_cache_control.s_max_age.is_some())))
349
+ }
350
+ None => false,
351
+ };
352
+
353
+ if should_cache_response {
354
+ let mut response_body_buffer = Vec::new();
355
+ let mut maximum_cached_response_size_exceeded = false;
356
+
357
+ while let Some(frame) = response_body.frame().await {
358
+ let frame_unwrapped = frame?;
359
+ if frame_unwrapped.is_data() {
360
+ if let Some(bytes) = frame_unwrapped.data_ref() {
361
+ response_body_buffer.extend_from_slice(bytes);
362
+ if let Some(maximum_cached_response_size) = self.maximum_cached_response_size {
363
+ if response_body_buffer.len() as u64 > maximum_cached_response_size {
364
+ maximum_cached_response_size_exceeded = true;
365
+ break;
366
+ }
367
+ }
368
+ }
369
+ }
370
+ }
371
+
372
+ if maximum_cached_response_size_exceeded {
373
+ let cached_stream =
374
+ futures_util::stream::once(async move { Ok(Bytes::from(response_body_buffer)) });
375
+ let response_stream = response_body.into_data_stream();
376
+ let chained_stream = cached_stream.chain(response_stream);
377
+ let stream_body = StreamBody::new(chained_stream.map_ok(Frame::data));
378
+ let response_body = BodyExt::boxed(stream_body);
379
+ response_parts
380
+ .headers
381
+ .insert(CACHE_HEADER_NAME, HeaderValue::from_str("MISS")?);
382
+ let response = Response::from_parts(response_parts, response_body);
383
+ Ok(response)
384
+ } else {
385
+ let mut response_vary = match response_parts.headers.get(header::VARY) {
386
+ Some(value) => String::from_utf8_lossy(value.as_bytes())
387
+ .split(",")
388
+ .map(|s| s.trim().to_owned())
389
+ .collect(),
390
+ None => Vec::new(),
391
+ };
392
+
393
+ let mut processed_vary_orig = self.cache_vary_headers_configured.clone();
394
+ processed_vary_orig.append(&mut response_vary);
395
+
396
+ let processed_vary = processed_vary_orig
397
+ .iter()
398
+ .unique()
399
+ .map(|s| s.to_owned())
400
+ .collect::<Vec<String>>();
401
+
402
+ if !processed_vary.contains(&"*".to_string()) {
403
+ let cache_key_with_vary = format!(
404
+ "{}\n{}",
405
+ &cache_key,
406
+ processed_vary
407
+ .iter()
408
+ .map(|header_name| {
409
+ match self.request_headers.get(header_name) {
410
+ Some(header_value) => format!(
411
+ "{}: {}",
412
+ header_name,
413
+ String::from_utf8_lossy(header_value.as_bytes()).into_owned()
414
+ ),
415
+ None => "".to_string(),
416
+ }
417
+ })
418
+ .collect::<Vec<String>>()
419
+ .join("\n")
420
+ );
421
+
422
+ let mut rwlock_write = self.vary_cache.write().await;
423
+ rwlock_write.insert(cache_key.clone(), processed_vary);
424
+ drop(rwlock_write);
425
+
426
+ let mut written_headers = response_parts.headers.clone();
427
+ for header in self.cache_ignore_headers_configured.iter() {
428
+ while written_headers.remove(header).is_some() {}
429
+ }
430
+
431
+ let mut rwlock_write = self.cache.write().await;
432
+ rwlock_write.retain(|_, (_, _, _, timestamp, response_cache_control)| {
433
+ let max_age = match response_cache_control {
434
+ Some(response_cache_control) => match response_cache_control.s_max_age {
435
+ Some(s_max_age) => Some(s_max_age),
436
+ None => response_cache_control.max_age,
437
+ },
438
+ None => None,
439
+ };
440
+
441
+ timestamp.elapsed() <= max_age.unwrap_or(Duration::from_secs(DEFAULT_MAX_AGE))
442
+ });
443
+
444
+ if let Some(maximum_cache_entries) = self.maximum_cache_entries {
445
+ // Remove a value at the front of the list
446
+ while !rwlock_write.is_empty() && rwlock_write.len() >= maximum_cache_entries {
447
+ rwlock_write.pop_front();
448
+ }
449
+ }
450
+
451
+ // This inserts a value at the back of the list
452
+ rwlock_write.insert(
453
+ cache_key_with_vary,
454
+ (
455
+ response_parts.status,
456
+ written_headers,
457
+ response_body_buffer.clone(),
458
+ Instant::now(),
459
+ response_cache_control,
460
+ ),
461
+ );
462
+ drop(rwlock_write);
463
+ }
464
+
465
+ let cached_stream =
466
+ futures_util::stream::once(async move { Ok(Bytes::from(response_body_buffer)) });
467
+ let stream_body = StreamBody::new(cached_stream.map_ok(Frame::data));
468
+ let response_body = BodyExt::boxed(stream_body);
469
+ response_parts
470
+ .headers
471
+ .insert(CACHE_HEADER_NAME, HeaderValue::from_str("MISS")?);
472
+ let response = Response::from_parts(response_parts, response_body);
473
+ Ok(response)
474
+ }
475
+ } else {
476
+ response_parts
477
+ .headers
478
+ .insert(CACHE_HEADER_NAME, HeaderValue::from_str("MISS")?);
479
+ let response = Response::from_parts(response_parts, response_body);
480
+ Ok(response)
481
+ }
482
+ } else {
483
+ Ok(response)
484
+ }
485
+ })
486
+ .await
487
+ }
488
+
489
+ async fn proxy_response_modifying_handler(
490
+ &mut self,
491
+ response: HyperResponse,
492
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
493
+ Ok(response)
494
+ }
495
+
496
+ async fn connect_proxy_request_handler(
497
+ &mut self,
498
+ _upgraded_request: HyperUpgraded,
499
+ _connect_address: &str,
500
+ _config: &ServerConfig,
501
+ _socket_data: &SocketData,
502
+ _error_logger: &ErrorLogger,
503
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
504
+ Ok(())
505
+ }
506
+
507
+ fn does_connect_proxy_requests(&mut self) -> bool {
508
+ false
509
+ }
510
+
511
+ async fn websocket_request_handler(
512
+ &mut self,
513
+ _websocket: HyperWebsocket,
514
+ _uri: &hyper::Uri,
515
+ _config: &ServerConfig,
516
+ _socket_data: &SocketData,
517
+ _error_logger: &ErrorLogger,
518
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
519
+ Ok(())
520
+ }
521
+
522
+ fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
523
+ false
524
+ }
525
+ }
ferron/src/optional_modules/cgi.rs ADDED
@@ -0,0 +1,859 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // CGI handler code inspired by SVR.JS's RedBrick mod, translated from JavaScript to Rust.
2
+ use std::collections::HashMap;
3
+ use std::error::Error;
4
+ use std::path::{Path, PathBuf};
5
+ use std::process::Stdio;
6
+ use std::sync::Arc;
7
+ use std::time::Duration;
8
+
9
+ use crate::ferron_common::{
10
+ ErrorLogger, HyperRequest, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
11
+ ServerModuleHandlers, SocketData,
12
+ };
13
+ use crate::ferron_common::{HyperUpgraded, WithRuntime};
14
+ use async_trait::async_trait;
15
+ use futures_util::TryStreamExt;
16
+ use hashlink::LinkedHashMap;
17
+ use http_body_util::{BodyExt, StreamBody};
18
+ use httparse::EMPTY_HEADER;
19
+ use hyper::body::Frame;
20
+ use hyper::{header, Response, StatusCode};
21
+ use hyper_tungstenite::HyperWebsocket;
22
+ use tokio::fs;
23
+ use tokio::io::AsyncReadExt;
24
+ use tokio::process::Command;
25
+ use tokio::runtime::Handle;
26
+ use tokio::sync::RwLock;
27
+ use tokio_util::io::{ReaderStream, StreamReader};
28
+
29
+ use crate::ferron_res::server_software::SERVER_SOFTWARE;
30
+ use crate::ferron_util::cgi_response::CgiResponse;
31
+ use crate::ferron_util::copy_move::Copier;
32
+ use crate::ferron_util::ttl_cache::TtlCache;
33
+
34
+ pub fn server_module_init(
35
+ _config: &ServerConfig,
36
+ ) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
37
+ let cache = Arc::new(RwLock::new(TtlCache::new(Duration::from_millis(100))));
38
+ Ok(Box::new(CgiModule::new(cache)))
39
+ }
40
+
41
+ #[allow(clippy::type_complexity)]
42
+ struct CgiModule {
43
+ path_cache: Arc<RwLock<TtlCache<String, (Option<PathBuf>, Option<String>)>>>,
44
+ }
45
+
46
+ impl CgiModule {
47
+ #[allow(clippy::type_complexity)]
48
+ fn new(path_cache: Arc<RwLock<TtlCache<String, (Option<PathBuf>, Option<String>)>>>) -> Self {
49
+ Self { path_cache }
50
+ }
51
+ }
52
+
53
+ impl ServerModule for CgiModule {
54
+ fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
55
+ Box::new(CgiModuleHandlers {
56
+ path_cache: self.path_cache.clone(),
57
+ handle,
58
+ })
59
+ }
60
+ }
61
+
62
+ #[allow(clippy::type_complexity)]
63
+ struct CgiModuleHandlers {
64
+ handle: Handle,
65
+ path_cache: Arc<RwLock<TtlCache<String, (Option<PathBuf>, Option<String>)>>>,
66
+ }
67
+
68
+ #[async_trait]
69
+ impl ServerModuleHandlers for CgiModuleHandlers {
70
+ async fn request_handler(
71
+ &mut self,
72
+ request: RequestData,
73
+ config: &ServerConfig,
74
+ socket_data: &SocketData,
75
+ error_logger: &ErrorLogger,
76
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
77
+ WithRuntime::new(self.handle.clone(), async move {
78
+ let mut cgi_script_exts = Vec::new();
79
+
80
+ let cgi_script_exts_yaml = &config["cgiScriptExtensions"];
81
+ if let Some(cgi_script_exts_obtained) = cgi_script_exts_yaml.as_vec() {
82
+ for cgi_script_ext_yaml in cgi_script_exts_obtained.iter() {
83
+ if let Some(cgi_script_ext) = cgi_script_ext_yaml.as_str() {
84
+ cgi_script_exts.push(cgi_script_ext);
85
+ }
86
+ }
87
+ }
88
+
89
+ if let Some(wwwroot) = config["wwwroot"].as_str() {
90
+ let hyper_request = request.get_hyper_request();
91
+
92
+ let request_path = hyper_request.uri().path();
93
+ let mut request_path_bytes = request_path.bytes();
94
+ if request_path_bytes.len() < 1 || request_path_bytes.nth(0) != Some(b'/') {
95
+ return Ok(
96
+ ResponseData::builder(request)
97
+ .status(StatusCode::BAD_REQUEST)
98
+ .build(),
99
+ );
100
+ }
101
+
102
+ let cache_key = format!(
103
+ "{}{}{}",
104
+ match config["ip"].as_str() {
105
+ Some(ip) => format!("{}-", ip),
106
+ None => String::from(""),
107
+ },
108
+ match config["domain"].as_str() {
109
+ Some(domain) => format!("{}-", domain),
110
+ None => String::from(""),
111
+ },
112
+ request_path
113
+ );
114
+
115
+ let wwwroot_unknown = PathBuf::from(wwwroot);
116
+ let wwwroot_pathbuf = match wwwroot_unknown.as_path().is_absolute() {
117
+ true => wwwroot_unknown,
118
+ false => match fs::canonicalize(&wwwroot_unknown).await {
119
+ Ok(pathbuf) => pathbuf,
120
+ Err(_) => wwwroot_unknown,
121
+ },
122
+ };
123
+ let wwwroot = wwwroot_pathbuf.as_path();
124
+
125
+ let read_rwlock = self.path_cache.read().await;
126
+ let (execute_pathbuf, execute_path_info) = match read_rwlock.get(&cache_key) {
127
+ Some(data) => {
128
+ drop(read_rwlock);
129
+ data
130
+ }
131
+ None => {
132
+ drop(read_rwlock);
133
+ let mut relative_path = &request_path[1..];
134
+ while relative_path.as_bytes().first().copied() == Some(b'/') {
135
+ relative_path = &relative_path[1..];
136
+ }
137
+
138
+ let decoded_relative_path = match urlencoding::decode(relative_path) {
139
+ Ok(path) => path.to_string(),
140
+ Err(_) => {
141
+ return Ok(
142
+ ResponseData::builder(request)
143
+ .status(StatusCode::BAD_REQUEST)
144
+ .build(),
145
+ );
146
+ }
147
+ };
148
+
149
+ let joined_pathbuf = wwwroot.join(decoded_relative_path);
150
+ let mut execute_pathbuf: Option<PathBuf> = None;
151
+ let mut execute_path_info: Option<String> = None;
152
+
153
+ match fs::metadata(&joined_pathbuf).await {
154
+ Ok(metadata) => {
155
+ if metadata.is_file() {
156
+ let mut request_path_normalized = match cfg!(windows) {
157
+ true => request_path.to_lowercase(),
158
+ false => request_path.to_string(),
159
+ };
160
+ while request_path_normalized.contains("//") {
161
+ request_path_normalized = request_path_normalized.replace("//", "/");
162
+ }
163
+ if request_path_normalized == "/cgi-bin"
164
+ || request_path_normalized.starts_with("/cgi-bin/")
165
+ {
166
+ execute_pathbuf = Some(joined_pathbuf);
167
+ } else {
168
+ let contained_extension = joined_pathbuf
169
+ .extension()
170
+ .map(|a| format!(".{}", a.to_string_lossy()));
171
+ if let Some(contained_extension) = contained_extension {
172
+ if cgi_script_exts.contains(&(&contained_extension as &str)) {
173
+ execute_pathbuf = Some(joined_pathbuf);
174
+ }
175
+ }
176
+ }
177
+ } else if metadata.is_dir() {
178
+ let indexes = vec!["index.php", "index.cgi"];
179
+ for index in indexes {
180
+ let temp_joined_pathbuf = joined_pathbuf.join(index);
181
+ match fs::metadata(&temp_joined_pathbuf).await {
182
+ Ok(temp_metadata) => {
183
+ if temp_metadata.is_file() {
184
+ let request_path_normalized = match cfg!(windows) {
185
+ true => request_path.to_lowercase(),
186
+ false => request_path.to_string(),
187
+ };
188
+ if request_path_normalized == "/cgi-bin"
189
+ || request_path_normalized.starts_with("/cgi-bin/")
190
+ {
191
+ execute_pathbuf = Some(temp_joined_pathbuf);
192
+ break;
193
+ } else {
194
+ let contained_extension = temp_joined_pathbuf
195
+ .extension()
196
+ .map(|a| format!(".{}", a.to_string_lossy()));
197
+ if let Some(contained_extension) = contained_extension {
198
+ if cgi_script_exts.contains(&(&contained_extension as &str)) {
199
+ execute_pathbuf = Some(temp_joined_pathbuf);
200
+ break;
201
+ }
202
+ }
203
+ }
204
+ }
205
+ }
206
+ Err(_) => continue,
207
+ };
208
+ }
209
+ }
210
+ }
211
+ Err(err) => {
212
+ if err.kind() == tokio::io::ErrorKind::NotADirectory {
213
+ // TODO: find a file
214
+ let mut temp_pathbuf = joined_pathbuf.clone();
215
+ loop {
216
+ if !temp_pathbuf.pop() {
217
+ break;
218
+ }
219
+ match fs::metadata(&temp_pathbuf).await {
220
+ Ok(metadata) => {
221
+ if metadata.is_file() {
222
+ let temp_path = temp_pathbuf.as_path();
223
+ if !temp_path.starts_with(wwwroot) {
224
+ // Traversed above the webroot, so ignore that.
225
+ break;
226
+ }
227
+ let path_info = match joined_pathbuf.as_path().strip_prefix(temp_path) {
228
+ Ok(path) => {
229
+ let path = path.to_string_lossy().to_string();
230
+ Some(match cfg!(windows) {
231
+ true => path.replace("\\", "/"),
232
+ false => path,
233
+ })
234
+ }
235
+ Err(_) => None,
236
+ };
237
+ let mut request_path_normalized = match cfg!(windows) {
238
+ true => request_path.to_lowercase(),
239
+ false => request_path.to_string(),
240
+ };
241
+ while request_path_normalized.contains("//") {
242
+ request_path_normalized = request_path_normalized.replace("//", "/");
243
+ }
244
+ if request_path_normalized == "/cgi-bin"
245
+ || request_path_normalized.starts_with("/cgi-bin/")
246
+ {
247
+ execute_pathbuf = Some(temp_pathbuf);
248
+ execute_path_info = path_info;
249
+ break;
250
+ } else {
251
+ let contained_extension = temp_pathbuf
252
+ .extension()
253
+ .map(|a| format!(".{}", a.to_string_lossy()));
254
+ if let Some(contained_extension) = contained_extension {
255
+ if cgi_script_exts.contains(&(&contained_extension as &str)) {
256
+ execute_pathbuf = Some(temp_pathbuf);
257
+ execute_path_info = path_info;
258
+ break;
259
+ }
260
+ }
261
+ }
262
+ } else {
263
+ break;
264
+ }
265
+ }
266
+ Err(err) => match err.kind() {
267
+ tokio::io::ErrorKind::NotADirectory => (),
268
+ _ => break,
269
+ },
270
+ };
271
+ }
272
+ }
273
+ }
274
+ };
275
+ let data = (execute_pathbuf, execute_path_info);
276
+
277
+ let mut write_rwlock = self.path_cache.write().await;
278
+ write_rwlock.cleanup();
279
+ write_rwlock.insert(cache_key, data.clone());
280
+ drop(write_rwlock);
281
+ data
282
+ }
283
+ };
284
+
285
+ if let Some(execute_pathbuf) = execute_pathbuf {
286
+ let mut cgi_interpreters = HashMap::new();
287
+ cgi_interpreters.insert(".pl".to_string(), vec!["perl".to_string()]);
288
+ cgi_interpreters.insert(".py".to_string(), vec!["python".to_string()]);
289
+ cgi_interpreters.insert(".sh".to_string(), vec!["bash".to_string()]);
290
+ cgi_interpreters.insert(".ksh".to_string(), vec!["ksh".to_string()]);
291
+ cgi_interpreters.insert(".csh".to_string(), vec!["csh".to_string()]);
292
+ cgi_interpreters.insert(".rb".to_string(), vec!["ruby".to_string()]);
293
+ cgi_interpreters.insert(".php".to_string(), vec!["php-cgi".to_string()]);
294
+ if cfg!(windows) {
295
+ cgi_interpreters.insert(".exe".to_string(), vec![]);
296
+ cgi_interpreters.insert(
297
+ ".bat".to_string(),
298
+ vec!["cmd".to_string(), "/c".to_string()],
299
+ );
300
+ cgi_interpreters.insert(".vbs".to_string(), vec!["cscript".to_string()]);
301
+ }
302
+
303
+ let cgi_interpreters_yaml = &config["cgiScriptInterpreters"];
304
+ if let Some(cgi_interpreters_hashmap) = cgi_interpreters_yaml.as_hash() {
305
+ for (key_yaml, value_yaml) in cgi_interpreters_hashmap.iter() {
306
+ if let Some(key) = key_yaml.as_str() {
307
+ if value_yaml.is_null() {
308
+ cgi_interpreters.remove(key);
309
+ } else if let Some(value) = value_yaml.as_vec() {
310
+ let mut params = Vec::new();
311
+ for param_yaml in value.iter() {
312
+ if let Some(param) = param_yaml.as_str() {
313
+ params.push(param.to_string());
314
+ }
315
+ }
316
+ cgi_interpreters.insert(key.to_string(), params);
317
+ }
318
+ }
319
+ }
320
+ }
321
+
322
+ return execute_cgi_with_environment_variables(
323
+ request,
324
+ socket_data,
325
+ error_logger,
326
+ wwwroot,
327
+ execute_pathbuf,
328
+ execute_path_info,
329
+ config["serverAdministratorEmail"].as_str(),
330
+ cgi_interpreters,
331
+ )
332
+ .await;
333
+ }
334
+ }
335
+
336
+ Ok(ResponseData::builder(request).build())
337
+ })
338
+ .await
339
+ }
340
+
341
+ async fn proxy_request_handler(
342
+ &mut self,
343
+ request: RequestData,
344
+ _config: &ServerConfig,
345
+ _socket_data: &SocketData,
346
+ _error_logger: &ErrorLogger,
347
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
348
+ Ok(ResponseData::builder(request).build())
349
+ }
350
+
351
+ async fn response_modifying_handler(
352
+ &mut self,
353
+ response: HyperResponse,
354
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
355
+ Ok(response)
356
+ }
357
+
358
+ async fn proxy_response_modifying_handler(
359
+ &mut self,
360
+ response: HyperResponse,
361
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
362
+ Ok(response)
363
+ }
364
+
365
+ async fn connect_proxy_request_handler(
366
+ &mut self,
367
+ _upgraded_request: HyperUpgraded,
368
+ _connect_address: &str,
369
+ _config: &ServerConfig,
370
+ _socket_data: &SocketData,
371
+ _error_logger: &ErrorLogger,
372
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
373
+ Ok(())
374
+ }
375
+
376
+ fn does_connect_proxy_requests(&mut self) -> bool {
377
+ false
378
+ }
379
+
380
+ async fn websocket_request_handler(
381
+ &mut self,
382
+ _websocket: HyperWebsocket,
383
+ _uri: &hyper::Uri,
384
+ _config: &ServerConfig,
385
+ _socket_data: &SocketData,
386
+ _error_logger: &ErrorLogger,
387
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
388
+ Ok(())
389
+ }
390
+
391
+ fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
392
+ false
393
+ }
394
+ }
395
+
396
+ #[allow(clippy::too_many_arguments)]
397
+ async fn execute_cgi_with_environment_variables(
398
+ request: RequestData,
399
+ socket_data: &SocketData,
400
+ error_logger: &ErrorLogger,
401
+ wwwroot: &Path,
402
+ execute_pathbuf: PathBuf,
403
+ path_info: Option<String>,
404
+ server_administrator_email: Option<&str>,
405
+ cgi_interpreters: HashMap<String, Vec<String>>,
406
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
407
+ let mut environment_variables: LinkedHashMap<String, String> = LinkedHashMap::new();
408
+
409
+ let hyper_request = request.get_hyper_request();
410
+ let original_request_uri = request.get_original_url().unwrap_or(hyper_request.uri());
411
+
412
+ if let Some(auth_user) = request.get_auth_user() {
413
+ if let Some(authorization) = hyper_request.headers().get(header::AUTHORIZATION) {
414
+ let authorization_value = String::from_utf8_lossy(authorization.as_bytes()).to_string();
415
+ let mut authorization_value_split = authorization_value.split(" ");
416
+ if let Some(authorization_type) = authorization_value_split.next() {
417
+ environment_variables.insert("AUTH_TYPE".to_string(), authorization_type.to_string());
418
+ }
419
+ }
420
+ environment_variables.insert("REMOTE_USER".to_string(), auth_user.to_string());
421
+ }
422
+
423
+ environment_variables.insert(
424
+ "QUERY_STRING".to_string(),
425
+ match hyper_request.uri().query() {
426
+ Some(query) => query.to_string(),
427
+ None => "".to_string(),
428
+ },
429
+ );
430
+
431
+ environment_variables.insert("SERVER_SOFTWARE".to_string(), SERVER_SOFTWARE.to_string());
432
+ environment_variables.insert(
433
+ "SERVER_PROTOCOL".to_string(),
434
+ match hyper_request.version() {
435
+ hyper::Version::HTTP_09 => "HTTP/0.9".to_string(),
436
+ hyper::Version::HTTP_10 => "HTTP/1.0".to_string(),
437
+ hyper::Version::HTTP_11 => "HTTP/1.1".to_string(),
438
+ hyper::Version::HTTP_2 => "HTTP/2.0".to_string(),
439
+ hyper::Version::HTTP_3 => "HTTP/3.0".to_string(),
440
+ _ => "HTTP/Unknown".to_string(),
441
+ },
442
+ );
443
+ environment_variables.insert(
444
+ "SERVER_PORT".to_string(),
445
+ socket_data.local_addr.port().to_string(),
446
+ );
447
+ environment_variables.insert(
448
+ "SERVER_ADDR".to_string(),
449
+ socket_data.local_addr.ip().to_canonical().to_string(),
450
+ );
451
+ if let Some(server_administrator_email) = server_administrator_email {
452
+ environment_variables.insert(
453
+ "SERVER_ADMIN".to_string(),
454
+ server_administrator_email.to_string(),
455
+ );
456
+ }
457
+ if let Some(host) = hyper_request.headers().get(header::HOST) {
458
+ environment_variables.insert(
459
+ "SERVER_NAME".to_string(),
460
+ String::from_utf8_lossy(host.as_bytes()).to_string(),
461
+ );
462
+ }
463
+
464
+ environment_variables.insert(
465
+ "DOCUMENT_ROOT".to_string(),
466
+ wwwroot.to_string_lossy().to_string(),
467
+ );
468
+ environment_variables.insert(
469
+ "PATH_INFO".to_string(),
470
+ match &path_info {
471
+ Some(path_info) => format!("/{}", path_info),
472
+ None => "".to_string(),
473
+ },
474
+ );
475
+ environment_variables.insert(
476
+ "PATH_TRANSLATED".to_string(),
477
+ match &path_info {
478
+ Some(path_info) => {
479
+ let mut path_translated = execute_pathbuf.clone();
480
+ path_translated.push(path_info);
481
+ path_translated.to_string_lossy().to_string()
482
+ }
483
+ None => "".to_string(),
484
+ },
485
+ );
486
+ environment_variables.insert(
487
+ "REQUEST_METHOD".to_string(),
488
+ hyper_request.method().to_string(),
489
+ );
490
+ environment_variables.insert("GATEWAY_INTERFACE".to_string(), "CGI/1.1".to_string());
491
+ environment_variables.insert(
492
+ "REQUEST_URI".to_string(),
493
+ format!(
494
+ "{}{}",
495
+ original_request_uri.path(),
496
+ match original_request_uri.query() {
497
+ Some(query) => format!("?{}", query),
498
+ None => String::from(""),
499
+ }
500
+ ),
501
+ );
502
+
503
+ environment_variables.insert(
504
+ "REMOTE_PORT".to_string(),
505
+ socket_data.remote_addr.port().to_string(),
506
+ );
507
+ environment_variables.insert(
508
+ "REMOTE_ADDR".to_string(),
509
+ socket_data.remote_addr.ip().to_canonical().to_string(),
510
+ );
511
+
512
+ environment_variables.insert(
513
+ "SCRIPT_FILENAME".to_string(),
514
+ execute_pathbuf.to_string_lossy().to_string(),
515
+ );
516
+ if let Ok(script_path) = execute_pathbuf.as_path().strip_prefix(wwwroot) {
517
+ environment_variables.insert(
518
+ "SCRIPT_NAME".to_string(),
519
+ format!(
520
+ "/{}",
521
+ match cfg!(windows) {
522
+ true => script_path.to_string_lossy().to_string().replace("\\", "/"),
523
+ false => script_path.to_string_lossy().to_string(),
524
+ }
525
+ ),
526
+ );
527
+ }
528
+
529
+ if socket_data.encrypted {
530
+ environment_variables.insert("HTTPS".to_string(), "ON".to_string());
531
+ }
532
+
533
+ for (header_name, header_value) in hyper_request.headers().iter() {
534
+ let env_header_name = match *header_name {
535
+ header::CONTENT_LENGTH => "CONTENT_LENGTH".to_string(),
536
+ header::CONTENT_TYPE => "CONTENT_TYPE".to_string(),
537
+ _ => {
538
+ let mut result = String::new();
539
+
540
+ result.push_str("HTTP_");
541
+
542
+ for c in header_name.as_str().to_uppercase().chars() {
543
+ if c.is_alphanumeric() {
544
+ result.push(c);
545
+ } else {
546
+ result.push('_');
547
+ }
548
+ }
549
+
550
+ result
551
+ }
552
+ };
553
+ if environment_variables.contains_key(&env_header_name) {
554
+ let value = environment_variables.get_mut(&env_header_name);
555
+ if let Some(value) = value {
556
+ if env_header_name == "HTTP_COOKIE" {
557
+ value.push_str("; ");
558
+ } else {
559
+ // See https://stackoverflow.com/a/1801191
560
+ value.push_str(", ");
561
+ }
562
+ value.push_str(String::from_utf8_lossy(header_value.as_bytes()).as_ref());
563
+ } else {
564
+ environment_variables.insert(
565
+ env_header_name,
566
+ String::from_utf8_lossy(header_value.as_bytes()).to_string(),
567
+ );
568
+ }
569
+ } else {
570
+ environment_variables.insert(
571
+ env_header_name,
572
+ String::from_utf8_lossy(header_value.as_bytes()).to_string(),
573
+ );
574
+ }
575
+ }
576
+
577
+ let (hyper_request, _, _) = request.into_parts();
578
+
579
+ execute_cgi(
580
+ hyper_request,
581
+ error_logger,
582
+ execute_pathbuf,
583
+ cgi_interpreters,
584
+ environment_variables,
585
+ )
586
+ .await
587
+ }
588
+
589
+ async fn execute_cgi(
590
+ hyper_request: HyperRequest,
591
+ error_logger: &ErrorLogger,
592
+ execute_pathbuf: PathBuf,
593
+ cgi_interpreters: HashMap<String, Vec<String>>,
594
+ environment_variables: LinkedHashMap<String, String>,
595
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
596
+ let (_, body) = hyper_request.into_parts();
597
+
598
+ let executable_params = match get_executable(&execute_pathbuf).await {
599
+ Ok(params) => params,
600
+ Err(err) => {
601
+ let contained_extension = execute_pathbuf
602
+ .extension()
603
+ .map(|a| format!(".{}", a.to_string_lossy()));
604
+ if let Some(contained_extension) = contained_extension {
605
+ if let Some(params_init) = cgi_interpreters.get(&contained_extension) {
606
+ let mut params: Vec<String> = params_init.iter().map(|s| s.to_owned()).collect();
607
+ params.push(execute_pathbuf.to_string_lossy().to_string());
608
+ params
609
+ } else {
610
+ Err(err)?
611
+ }
612
+ } else {
613
+ Err(err)?
614
+ }
615
+ }
616
+ };
617
+
618
+ let mut executable_params_iter = executable_params.iter();
619
+
620
+ let mut command = Command::new(match executable_params_iter.next() {
621
+ Some(executable_name) => executable_name,
622
+ None => Err(anyhow::anyhow!("Cannot determine the executable"))?,
623
+ });
624
+
625
+ // Set standard I/O to be piped
626
+ command.stdin(Stdio::piped());
627
+ command.stdout(Stdio::piped());
628
+ command.stderr(Stdio::piped());
629
+
630
+ for param in executable_params_iter {
631
+ command.arg(param);
632
+ }
633
+
634
+ command.envs(environment_variables);
635
+
636
+ let mut execute_dir_pathbuf = execute_pathbuf.clone();
637
+ execute_dir_pathbuf.pop();
638
+ command.current_dir(execute_dir_pathbuf);
639
+
640
+ let mut child = command.spawn()?;
641
+
642
+ let cgi_stdin_reader = StreamReader::new(body.into_data_stream().map_err(std::io::Error::other));
643
+
644
+ let stdin = match child.stdin.take() {
645
+ Some(stdin) => stdin,
646
+ None => Err(anyhow::anyhow!(
647
+ "The CGI process doesn't have standard input"
648
+ ))?,
649
+ };
650
+ let stdout = match child.stdout.take() {
651
+ Some(stdout) => stdout,
652
+ None => Err(anyhow::anyhow!(
653
+ "The CGI process doesn't have standard output"
654
+ ))?,
655
+ };
656
+ let stderr = child.stderr.take();
657
+
658
+ let mut cgi_response = CgiResponse::new(stdout);
659
+
660
+ let stdin_copy_future = Copier::new(cgi_stdin_reader, stdin).copy();
661
+ let mut stdin_copy_future_pinned = Box::pin(stdin_copy_future);
662
+
663
+ let mut headers = [EMPTY_HEADER; 128];
664
+
665
+ let mut early_stdin_copied = false;
666
+
667
+ // Needed to wrap this in another scope to prevent errors with multiple mutable borrows.
668
+ {
669
+ let mut head_obtained = false;
670
+ let stdout_parse_future = cgi_response.get_head();
671
+ tokio::pin!(stdout_parse_future);
672
+
673
+ // Cannot use a loop with tokio::select, since stdin_copy_future_pinned being constantly ready will make the web server stop responding to HTTP requests
674
+ tokio::select! {
675
+ biased;
676
+
677
+ obtained_head = &mut stdout_parse_future => {
678
+ let obtained_head = obtained_head?;
679
+ if !obtained_head.is_empty() {
680
+ httparse::parse_headers(obtained_head, &mut headers)?;
681
+ }
682
+ head_obtained = true;
683
+ },
684
+ result = &mut stdin_copy_future_pinned => {
685
+ early_stdin_copied = true;
686
+ result?;
687
+ }
688
+ }
689
+
690
+ if !head_obtained {
691
+ // Kept it same as in the tokio::select macro
692
+ let obtained_head = stdout_parse_future.await?;
693
+ if !obtained_head.is_empty() {
694
+ httparse::parse_headers(obtained_head, &mut headers)?;
695
+ }
696
+ }
697
+ }
698
+
699
+ let mut response_builder = Response::builder();
700
+ let mut status_code = 200;
701
+ for header in headers {
702
+ if header == EMPTY_HEADER {
703
+ break;
704
+ }
705
+ let mut is_status_header = false;
706
+ match &header.name.to_lowercase() as &str {
707
+ "location" => {
708
+ if !(300..=399).contains(&status_code) {
709
+ status_code = 302;
710
+ }
711
+ }
712
+ "status" => {
713
+ is_status_header = true;
714
+ let header_value_cow = String::from_utf8_lossy(header.value);
715
+ let mut split_status = header_value_cow.split(" ");
716
+ let first_part = split_status.next();
717
+ if let Some(first_part) = first_part {
718
+ if first_part.starts_with("HTTP/") {
719
+ let second_part = split_status.next();
720
+ if let Some(second_part) = second_part {
721
+ if let Ok(parsed_status_code) = second_part.parse::<u16>() {
722
+ status_code = parsed_status_code;
723
+ }
724
+ }
725
+ } else if let Ok(parsed_status_code) = first_part.parse::<u16>() {
726
+ status_code = parsed_status_code;
727
+ }
728
+ }
729
+ }
730
+ _ => (),
731
+ }
732
+ if !is_status_header {
733
+ response_builder = response_builder.header(header.name, header.value);
734
+ }
735
+ }
736
+
737
+ response_builder = response_builder.status(status_code);
738
+
739
+ let reader_stream = ReaderStream::new(cgi_response);
740
+ let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
741
+ let boxed_body = stream_body.boxed();
742
+
743
+ let response = response_builder.body(boxed_body)?;
744
+
745
+ if let Some(exit_code) = child.try_wait()? {
746
+ if !exit_code.success() {
747
+ if let Some(mut stderr) = stderr {
748
+ let mut stderr_string = String::new();
749
+ stderr
750
+ .read_to_string(&mut stderr_string)
751
+ .await
752
+ .unwrap_or_default();
753
+ let stderr_string_trimmed = stderr_string.trim();
754
+ if !stderr_string_trimmed.is_empty() {
755
+ error_logger
756
+ .log(&format!("There were CGI errors: {}", stderr_string_trimmed))
757
+ .await;
758
+ }
759
+ }
760
+ return Ok(
761
+ ResponseData::builder_without_request()
762
+ .status(StatusCode::INTERNAL_SERVER_ERROR)
763
+ .build(),
764
+ );
765
+ }
766
+ }
767
+
768
+ let error_logger = error_logger.clone();
769
+
770
+ Ok(
771
+ ResponseData::builder_without_request()
772
+ .response(response)
773
+ .parallel_fn(async move {
774
+ if !early_stdin_copied {
775
+ stdin_copy_future_pinned.await.unwrap_or_default();
776
+ }
777
+
778
+ if let Some(mut stderr) = stderr {
779
+ let mut stderr_string = String::new();
780
+ stderr
781
+ .read_to_string(&mut stderr_string)
782
+ .await
783
+ .unwrap_or_default();
784
+ let stderr_string_trimmed = stderr_string.trim();
785
+ if !stderr_string_trimmed.is_empty() {
786
+ error_logger
787
+ .log(&format!("There were CGI errors: {}", stderr_string_trimmed))
788
+ .await;
789
+ }
790
+ }
791
+ })
792
+ .build(),
793
+ )
794
+ }
795
+
796
+ #[allow(dead_code)]
797
+ #[cfg(unix)]
798
+ async fn get_executable(
799
+ execute_pathbuf: &PathBuf,
800
+ ) -> Result<Vec<String>, Box<dyn Error + Send + Sync>> {
801
+ use std::os::unix::fs::PermissionsExt;
802
+
803
+ let metadata = fs::metadata(&execute_pathbuf).await?;
804
+ let permissions = metadata.permissions();
805
+ let is_executable = permissions.mode() & 0o111 != 0;
806
+
807
+ if !is_executable {
808
+ Err(anyhow::anyhow!("The CGI program is not executable"))?
809
+ }
810
+
811
+ let executable_params_vector = vec![execute_pathbuf.to_string_lossy().to_string()];
812
+ Ok(executable_params_vector)
813
+ }
814
+
815
+ #[allow(dead_code)]
816
+ #[cfg(not(unix))]
817
+ async fn get_executable(
818
+ execute_pathbuf: &PathBuf,
819
+ ) -> Result<Vec<String>, Box<dyn Error + Send + Sync>> {
820
+ use tokio::io::{AsyncBufReadExt, AsyncSeekExt, BufReader};
821
+
822
+ let mut magic_signature_buffer = [0u8; 2];
823
+ let mut open_file = fs::File::open(&execute_pathbuf).await?;
824
+ if open_file
825
+ .read_exact(&mut magic_signature_buffer)
826
+ .await
827
+ .is_err()
828
+ {
829
+ Err(anyhow::anyhow!("Failed to read the CGI program signature"))?
830
+ }
831
+
832
+ match &magic_signature_buffer {
833
+ b"PE" => {
834
+ // Windows executables
835
+ let executable_params_vector = vec![execute_pathbuf.to_string_lossy().to_string()];
836
+ Ok(executable_params_vector)
837
+ }
838
+ b"#!" => {
839
+ // Scripts with a shebang line
840
+ open_file.rewind().await?;
841
+ let mut buffered_file = BufReader::new(open_file);
842
+ let mut shebang_line = String::new();
843
+ buffered_file.read_line(&mut shebang_line).await?;
844
+
845
+ let mut command_begin: Vec<String> = (&shebang_line[2..])
846
+ .replace("\r", "")
847
+ .replace("\n", "")
848
+ .split(" ")
849
+ .map(|s| s.to_owned())
850
+ .collect();
851
+ command_begin.push(execute_pathbuf.to_string_lossy().to_string());
852
+ Ok(command_begin)
853
+ }
854
+ _ => {
855
+ // It's not executable
856
+ Err(anyhow::anyhow!("The CGI program is not executable"))?
857
+ }
858
+ }
859
+ }
ferron/src/optional_modules/example.rs ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use std::error::Error;
2
+
3
+ use crate::ferron_common::{
4
+ ErrorLogger, HyperUpgraded, RequestData, ResponseData, ServerConfig, ServerModule,
5
+ ServerModuleHandlers, SocketData,
6
+ };
7
+ use crate::ferron_common::{HyperResponse, WithRuntime};
8
+ use async_trait::async_trait;
9
+ use http_body_util::{BodyExt, Full};
10
+ use hyper::Response;
11
+ use hyper_tungstenite::HyperWebsocket;
12
+ use tokio::runtime::Handle;
13
+
14
+ // Define a struct for the module implementation
15
+ struct ExampleModule;
16
+
17
+ /// Initializes the server module and returns an instance of `ExampleModule`.
18
+ pub fn server_module_init(
19
+ _config: &ServerConfig, // This is YAML configuration parsed as-is.
20
+ ) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
21
+ Ok(Box::new(ExampleModule::new()))
22
+ }
23
+
24
+ impl ExampleModule {
25
+ /// Creates a new instance of `ExampleModule`.
26
+ fn new() -> Self {
27
+ ExampleModule
28
+ }
29
+ }
30
+
31
+ /// Implements the `ServerModule` trait for `ExampleModule`.
32
+ impl ServerModule for ExampleModule {
33
+ /// Returns an instance of `ExampleModuleHandlers` to handle HTTP requests.
34
+ fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
35
+ Box::new(ExampleModuleHandlers { handle })
36
+ }
37
+ }
38
+
39
+ // Define a struct to handle HTTP requests
40
+ struct ExampleModuleHandlers {
41
+ handle: Handle,
42
+ }
43
+
44
+ /// Implements the `ServerModuleHandlers` trait for `ExampleModuleHandlers`.
45
+ #[async_trait]
46
+ impl ServerModuleHandlers for ExampleModuleHandlers {
47
+ /// Handles incoming HTTP requests.
48
+ /// If the request path is `/hello`, it responds with "Hello World!".
49
+ async fn request_handler(
50
+ &mut self,
51
+ request: RequestData,
52
+ _config: &ServerConfig,
53
+ _socket_data: &SocketData,
54
+ _error_logger: &ErrorLogger,
55
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
56
+ WithRuntime::new(self.handle.clone(), async move {
57
+ if request.get_hyper_request().uri().path() == "/hello" {
58
+ Ok(
59
+ ResponseData::builder(request)
60
+ .response(
61
+ Response::builder().body(
62
+ Full::new("Hello World!".into())
63
+ .map_err(|e| match e {})
64
+ .boxed(),
65
+ )?,
66
+ )
67
+ .build(),
68
+ )
69
+ } else {
70
+ Ok(ResponseData::builder(request).build())
71
+ }
72
+ })
73
+ .await
74
+ }
75
+
76
+ /// Handles non-CONNECT proxy requests (not used in this module).
77
+ async fn proxy_request_handler(
78
+ &mut self,
79
+ request: RequestData,
80
+ _config: &ServerConfig,
81
+ _socket_data: &SocketData,
82
+ _error_logger: &ErrorLogger,
83
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
84
+ // No proxy request handling needed.
85
+ Ok(ResponseData::builder(request).build())
86
+ }
87
+
88
+ /// Modifies outgoing responses (not used in this module).
89
+ async fn response_modifying_handler(
90
+ &mut self,
91
+ response: HyperResponse,
92
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
93
+ // No response modification needed.
94
+ Ok(response)
95
+ }
96
+
97
+ /// Modifies outgoing proxy responses (not used in this module).
98
+ async fn proxy_response_modifying_handler(
99
+ &mut self,
100
+ response: HyperResponse,
101
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
102
+ // No proxy response modification needed.
103
+ Ok(response)
104
+ }
105
+
106
+ /// Handles CONNECT proxy requests (not used in this module).
107
+ async fn connect_proxy_request_handler(
108
+ &mut self,
109
+ _upgraded_request: HyperUpgraded,
110
+ _connect_address: &str,
111
+ _config: &ServerConfig,
112
+ _socket_data: &SocketData,
113
+ _error_logger: &ErrorLogger,
114
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
115
+ // No proxy request handling needed.
116
+ Ok(())
117
+ }
118
+
119
+ /// Checks if the module is a forward proxy module utilizing CONNECT method.
120
+ fn does_connect_proxy_requests(&mut self) -> bool {
121
+ // This is not a forward proxy module utilizing CONNECT method
122
+ false
123
+ }
124
+
125
+ /// Handles WebSocket requests (not used in this module).
126
+ async fn websocket_request_handler(
127
+ &mut self,
128
+ _websocket: HyperWebsocket,
129
+ _uri: &hyper::Uri,
130
+ _config: &ServerConfig,
131
+ _socket_data: &SocketData,
132
+ _error_logger: &ErrorLogger,
133
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
134
+ // No proxy request handling needed.
135
+ Ok(())
136
+ }
137
+
138
+ /// Checks if the module supports WebSocket connections.
139
+ fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
140
+ // This module doesn't support WebSocket connections.
141
+ false
142
+ }
143
+ }
ferron/src/optional_modules/fauth.rs ADDED
@@ -0,0 +1,572 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // The "fauth" module is derived from the "rproxy" module, and inspired by Traefik's ForwardAuth middleware.
2
+
3
+ use std::collections::HashMap;
4
+ use std::error::Error;
5
+ use std::str::FromStr;
6
+ use std::sync::Arc;
7
+
8
+ use crate::ferron_common::{
9
+ ErrorLogger, HyperUpgraded, RequestData, ResponseData, ServerConfig, ServerModule,
10
+ ServerModuleHandlers, SocketData,
11
+ };
12
+ use crate::ferron_common::{HyperResponse, WithRuntime};
13
+ use async_trait::async_trait;
14
+ use http_body_util::combinators::BoxBody;
15
+ use http_body_util::{BodyExt, Empty};
16
+ use hyper::body::Bytes;
17
+ use hyper::client::conn::http1::SendRequest;
18
+ use hyper::header::HeaderName;
19
+ use hyper::{header, Method, Request, StatusCode, Uri};
20
+ use hyper_tungstenite::HyperWebsocket;
21
+ use hyper_util::rt::TokioIo;
22
+ use rustls::pki_types::ServerName;
23
+ use rustls::RootCertStore;
24
+ use rustls_native_certs::load_native_certs;
25
+ use tokio::io::{AsyncRead, AsyncWrite};
26
+ use tokio::net::TcpStream;
27
+ use tokio::runtime::Handle;
28
+ use tokio::sync::RwLock;
29
+ use tokio_rustls::TlsConnector;
30
+
31
+ const DEFAULT_CONCURRENT_CONNECTIONS_PER_HOST: u32 = 32;
32
+
33
+ pub fn server_module_init(
34
+ _config: &ServerConfig,
35
+ ) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
36
+ let mut roots: RootCertStore = RootCertStore::empty();
37
+ let certs_result = load_native_certs();
38
+ if !certs_result.errors.is_empty() {
39
+ Err(anyhow::anyhow!(format!(
40
+ "Couldn't load the native certificate store: {}",
41
+ certs_result.errors[0]
42
+ )))?
43
+ }
44
+ let certs = certs_result.certs;
45
+
46
+ for cert in certs {
47
+ match roots.add(cert) {
48
+ Ok(_) => (),
49
+ Err(err) => Err(anyhow::anyhow!(format!(
50
+ "Couldn't add a certificate to the certificate store: {}",
51
+ err
52
+ )))?,
53
+ }
54
+ }
55
+
56
+ let mut connections_vec = Vec::new();
57
+ for _ in 0..DEFAULT_CONCURRENT_CONNECTIONS_PER_HOST {
58
+ connections_vec.push(RwLock::new(HashMap::new()));
59
+ }
60
+ Ok(Box::new(ForwardedAuthenticationModule::new(
61
+ Arc::new(roots),
62
+ Arc::new(connections_vec),
63
+ )))
64
+ }
65
+
66
+ #[allow(clippy::type_complexity)]
67
+ struct ForwardedAuthenticationModule {
68
+ roots: Arc<RootCertStore>,
69
+ connections: Arc<Vec<RwLock<HashMap<String, SendRequest<BoxBody<Bytes, hyper::Error>>>>>>,
70
+ }
71
+
72
+ impl ForwardedAuthenticationModule {
73
+ #[allow(clippy::type_complexity)]
74
+ fn new(
75
+ roots: Arc<RootCertStore>,
76
+ connections: Arc<Vec<RwLock<HashMap<String, SendRequest<BoxBody<Bytes, hyper::Error>>>>>>,
77
+ ) -> Self {
78
+ Self { roots, connections }
79
+ }
80
+ }
81
+
82
+ impl ServerModule for ForwardedAuthenticationModule {
83
+ fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
84
+ Box::new(ForwardedAuthenticationModuleHandlers {
85
+ roots: self.roots.clone(),
86
+ connections: self.connections.clone(),
87
+ handle,
88
+ })
89
+ }
90
+ }
91
+
92
+ #[allow(clippy::type_complexity)]
93
+ struct ForwardedAuthenticationModuleHandlers {
94
+ handle: Handle,
95
+ roots: Arc<RootCertStore>,
96
+ connections: Arc<Vec<RwLock<HashMap<String, SendRequest<BoxBody<Bytes, hyper::Error>>>>>>,
97
+ }
98
+
99
+ #[async_trait]
100
+ impl ServerModuleHandlers for ForwardedAuthenticationModuleHandlers {
101
+ async fn request_handler(
102
+ &mut self,
103
+ request: RequestData,
104
+ config: &ServerConfig,
105
+ socket_data: &SocketData,
106
+ error_logger: &ErrorLogger,
107
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
108
+ WithRuntime::new(self.handle.clone(), async move {
109
+ let mut auth_to = None;
110
+
111
+ if let Some(auth_to_str) = config["authTo"].as_str() {
112
+ auth_to = Some(auth_to_str.to_string());
113
+ }
114
+
115
+ let forwarded_auth_copy_headers = match config["forwardedAuthCopyHeaders"].as_vec() {
116
+ Some(vector) => {
117
+ let mut new_vector = Vec::new();
118
+ for yaml_value in vector.iter() {
119
+ if let Some(str_value) = yaml_value.as_str() {
120
+ new_vector.push(str_value.to_string());
121
+ }
122
+ }
123
+ new_vector
124
+ }
125
+ None => Vec::new(),
126
+ };
127
+
128
+ if let Some(auth_to) = auth_to {
129
+ let (hyper_request, auth_user, original_url) = request.into_parts();
130
+ let (hyper_request_parts, request_body) = hyper_request.into_parts();
131
+
132
+ let auth_request_url = auth_to.parse::<hyper::Uri>()?;
133
+ let scheme_str = auth_request_url.scheme_str();
134
+ let mut encrypted = false;
135
+
136
+ match scheme_str {
137
+ Some("http") => {
138
+ encrypted = false;
139
+ }
140
+ Some("https") => {
141
+ encrypted = true;
142
+ }
143
+ _ => Err(anyhow::anyhow!(
144
+ "Only HTTP and HTTPS reverse proxy URLs are supported."
145
+ ))?,
146
+ };
147
+
148
+ let host = match auth_request_url.host() {
149
+ Some(host) => host,
150
+ None => Err(anyhow::anyhow!(
151
+ "The reverse proxy URL doesn't include the host"
152
+ ))?,
153
+ };
154
+
155
+ let port = auth_request_url.port_u16().unwrap_or(match scheme_str {
156
+ Some("http") => 80,
157
+ Some("https") => 443,
158
+ _ => 80,
159
+ });
160
+
161
+ let addr = format!("{}:{}", host, port);
162
+ let authority = auth_request_url.authority().cloned();
163
+
164
+ let hyper_request_path = hyper_request_parts.uri.path();
165
+
166
+ let path_and_query = format!(
167
+ "{}{}",
168
+ hyper_request_path,
169
+ match hyper_request_parts.uri.query() {
170
+ Some(query) => format!("?{}", query),
171
+ None => "".to_string(),
172
+ }
173
+ );
174
+
175
+ let mut auth_hyper_request_parts = hyper_request_parts.clone();
176
+
177
+ auth_hyper_request_parts.uri = Uri::from_str(&format!(
178
+ "{}{}",
179
+ auth_request_url.path(),
180
+ match auth_request_url.query() {
181
+ Some(query) => format!("?{}", query),
182
+ None => "".to_string(),
183
+ }
184
+ ))?;
185
+
186
+ let original_host = hyper_request_parts.headers.get(header::HOST).cloned();
187
+
188
+ // Host header for host identification
189
+ match authority {
190
+ Some(authority) => {
191
+ auth_hyper_request_parts
192
+ .headers
193
+ .insert(header::HOST, authority.to_string().parse()?);
194
+ }
195
+ None => {
196
+ auth_hyper_request_parts.headers.remove(header::HOST);
197
+ }
198
+ }
199
+
200
+ // Connection header to enable HTTP/1.1 keep-alive
201
+ auth_hyper_request_parts
202
+ .headers
203
+ .insert(header::CONNECTION, "keep-alive".parse()?);
204
+
205
+ // X-Forwarded-* headers to send the client's data to a forwarded authentication server
206
+ auth_hyper_request_parts.headers.insert(
207
+ "x-forwarded-for",
208
+ socket_data
209
+ .remote_addr
210
+ .ip()
211
+ .to_canonical()
212
+ .to_string()
213
+ .parse()?,
214
+ );
215
+
216
+ if socket_data.encrypted {
217
+ auth_hyper_request_parts
218
+ .headers
219
+ .insert("x-forwarded-proto", "https".parse()?);
220
+ } else {
221
+ auth_hyper_request_parts
222
+ .headers
223
+ .insert("x-forwarded-proto", "http".parse()?);
224
+ }
225
+
226
+ if let Some(original_host) = original_host {
227
+ auth_hyper_request_parts
228
+ .headers
229
+ .insert("x-forwarded-host", original_host);
230
+ }
231
+
232
+ auth_hyper_request_parts
233
+ .headers
234
+ .insert("x-forwarded-uri", path_and_query.parse()?);
235
+
236
+ auth_hyper_request_parts.headers.insert(
237
+ "x-forwarded-method",
238
+ hyper_request_parts.method.as_str().parse()?,
239
+ );
240
+
241
+ auth_hyper_request_parts.method = Method::GET;
242
+
243
+ let auth_request = Request::from_parts(
244
+ auth_hyper_request_parts,
245
+ Empty::new().map_err(|e| match e {}).boxed(),
246
+ );
247
+ let original_hyper_request = Request::from_parts(hyper_request_parts, request_body);
248
+ let original_request = RequestData::new(original_hyper_request, auth_user, original_url);
249
+
250
+ let connections = &self.connections[rand::random_range(..self.connections.len())];
251
+
252
+ let rwlock_read = connections.read().await;
253
+ let sender_read_option = rwlock_read.get(&addr);
254
+
255
+ if let Some(sender_read) = sender_read_option {
256
+ if !sender_read.is_closed() {
257
+ drop(rwlock_read);
258
+ let mut rwlock_write = connections.write().await;
259
+ let sender_option = rwlock_write.get_mut(&addr);
260
+
261
+ if let Some(sender) = sender_option {
262
+ if !sender.is_closed() {
263
+ let result = http_forwarded_auth_kept_alive(
264
+ sender,
265
+ auth_request,
266
+ error_logger,
267
+ original_request,
268
+ forwarded_auth_copy_headers,
269
+ )
270
+ .await;
271
+ drop(rwlock_write);
272
+ return result;
273
+ } else {
274
+ drop(rwlock_write);
275
+ }
276
+ } else {
277
+ drop(rwlock_write);
278
+ }
279
+ } else {
280
+ drop(rwlock_read);
281
+ }
282
+ } else {
283
+ drop(rwlock_read);
284
+ }
285
+
286
+ let stream = match TcpStream::connect(&addr).await {
287
+ Ok(stream) => stream,
288
+ Err(err) => {
289
+ match err.kind() {
290
+ tokio::io::ErrorKind::ConnectionRefused
291
+ | tokio::io::ErrorKind::NotFound
292
+ | tokio::io::ErrorKind::HostUnreachable => {
293
+ error_logger
294
+ .log(&format!("Service unavailable: {}", err))
295
+ .await;
296
+ return Ok(
297
+ ResponseData::builder_without_request()
298
+ .status(StatusCode::SERVICE_UNAVAILABLE)
299
+ .build(),
300
+ );
301
+ }
302
+ tokio::io::ErrorKind::TimedOut => {
303
+ error_logger.log(&format!("Gateway timeout: {}", err)).await;
304
+ return Ok(
305
+ ResponseData::builder_without_request()
306
+ .status(StatusCode::GATEWAY_TIMEOUT)
307
+ .build(),
308
+ );
309
+ }
310
+ _ => {
311
+ error_logger.log(&format!("Bad gateway: {}", err)).await;
312
+ return Ok(
313
+ ResponseData::builder_without_request()
314
+ .status(StatusCode::BAD_GATEWAY)
315
+ .build(),
316
+ );
317
+ }
318
+ };
319
+ }
320
+ };
321
+
322
+ match stream.set_nodelay(true) {
323
+ Ok(_) => (),
324
+ Err(err) => {
325
+ error_logger.log(&format!("Bad gateway: {}", err)).await;
326
+ return Ok(
327
+ ResponseData::builder_without_request()
328
+ .status(StatusCode::BAD_GATEWAY)
329
+ .build(),
330
+ );
331
+ }
332
+ };
333
+
334
+ if !encrypted {
335
+ http_forwarded_auth(
336
+ connections,
337
+ addr,
338
+ stream,
339
+ auth_request,
340
+ error_logger,
341
+ original_request,
342
+ forwarded_auth_copy_headers,
343
+ )
344
+ .await
345
+ } else {
346
+ let tls_client_config = rustls::ClientConfig::builder()
347
+ .with_root_certificates(self.roots.clone())
348
+ .with_no_client_auth();
349
+ let connector = TlsConnector::from(Arc::new(tls_client_config));
350
+ let domain = ServerName::try_from(host)?.to_owned();
351
+
352
+ let tls_stream = match connector.connect(domain, stream).await {
353
+ Ok(stream) => stream,
354
+ Err(err) => {
355
+ error_logger.log(&format!("Bad gateway: {}", err)).await;
356
+ return Ok(
357
+ ResponseData::builder_without_request()
358
+ .status(StatusCode::BAD_GATEWAY)
359
+ .build(),
360
+ );
361
+ }
362
+ };
363
+
364
+ http_forwarded_auth(
365
+ connections,
366
+ addr,
367
+ tls_stream,
368
+ auth_request,
369
+ error_logger,
370
+ original_request,
371
+ forwarded_auth_copy_headers,
372
+ )
373
+ .await
374
+ }
375
+ } else {
376
+ Ok(ResponseData::builder(request).build())
377
+ }
378
+ })
379
+ .await
380
+ }
381
+
382
+ async fn proxy_request_handler(
383
+ &mut self,
384
+ request: RequestData,
385
+ _config: &ServerConfig,
386
+ _socket_data: &SocketData,
387
+ _error_logger: &ErrorLogger,
388
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
389
+ Ok(ResponseData::builder(request).build())
390
+ }
391
+
392
+ async fn response_modifying_handler(
393
+ &mut self,
394
+ response: HyperResponse,
395
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
396
+ Ok(response)
397
+ }
398
+
399
+ async fn proxy_response_modifying_handler(
400
+ &mut self,
401
+ response: HyperResponse,
402
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
403
+ Ok(response)
404
+ }
405
+
406
+ async fn connect_proxy_request_handler(
407
+ &mut self,
408
+ _upgraded_request: HyperUpgraded,
409
+ _connect_address: &str,
410
+ _config: &ServerConfig,
411
+ _socket_data: &SocketData,
412
+ _error_logger: &ErrorLogger,
413
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
414
+ Ok(())
415
+ }
416
+
417
+ fn does_connect_proxy_requests(&mut self) -> bool {
418
+ false
419
+ }
420
+
421
+ async fn websocket_request_handler(
422
+ &mut self,
423
+ _websocket: HyperWebsocket,
424
+ _uri: &hyper::Uri,
425
+ _config: &ServerConfig,
426
+ _socket_data: &SocketData,
427
+ _error_logger: &ErrorLogger,
428
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
429
+ Ok(())
430
+ }
431
+
432
+ fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
433
+ false
434
+ }
435
+ }
436
+
437
+ async fn http_forwarded_auth(
438
+ connections: &RwLock<HashMap<String, SendRequest<BoxBody<Bytes, hyper::Error>>>>,
439
+ connect_addr: String,
440
+ stream: impl AsyncRead + AsyncWrite + Send + Unpin + 'static,
441
+ proxy_request: Request<BoxBody<Bytes, hyper::Error>>,
442
+ error_logger: &ErrorLogger,
443
+ mut original_request: RequestData,
444
+ forwarded_auth_copy_headers: Vec<String>,
445
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
446
+ let io = TokioIo::new(stream);
447
+
448
+ let (mut sender, conn) = match hyper::client::conn::http1::handshake(io).await {
449
+ Ok(data) => data,
450
+ Err(err) => {
451
+ error_logger.log(&format!("Bad gateway: {}", err)).await;
452
+ return Ok(
453
+ ResponseData::builder_without_request()
454
+ .status(StatusCode::BAD_GATEWAY)
455
+ .build(),
456
+ );
457
+ }
458
+ };
459
+
460
+ let send_request = sender.send_request(proxy_request);
461
+
462
+ let mut pinned_conn = Box::pin(conn);
463
+ tokio::pin!(send_request);
464
+
465
+ let response;
466
+
467
+ loop {
468
+ tokio::select! {
469
+ biased;
470
+
471
+ proxy_response = &mut send_request => {
472
+ let proxy_response = match proxy_response {
473
+ Ok(response) => response,
474
+ Err(err) => {
475
+ error_logger.log(&format!("Bad gateway: {}", err)).await;
476
+ return Ok(ResponseData::builder_without_request().status(StatusCode::BAD_GATEWAY).build());
477
+ }
478
+ };
479
+
480
+ if proxy_response.status().is_success() {
481
+ if !forwarded_auth_copy_headers.is_empty() {
482
+ let response_headers = proxy_response.headers();
483
+ let request_headers = original_request.get_mut_hyper_request().headers_mut();
484
+ for forwarded_auth_copy_header_string in forwarded_auth_copy_headers.iter() {
485
+ let forwarded_auth_copy_header= HeaderName::from_str(forwarded_auth_copy_header_string)?;
486
+ if response_headers.contains_key(&forwarded_auth_copy_header) {
487
+ while request_headers.remove(&forwarded_auth_copy_header).is_some() {}
488
+ for header_value in response_headers.get_all(&forwarded_auth_copy_header).iter() {
489
+ request_headers.append(&forwarded_auth_copy_header, header_value.clone());
490
+ }
491
+ }
492
+ }
493
+ }
494
+ response = ResponseData::builder(original_request).build();
495
+ } else {
496
+ response = ResponseData::builder_without_request()
497
+ .response(proxy_response.map(|b| {
498
+ b.map_err(|e| std::io::Error::other(e.to_string()))
499
+ .boxed()
500
+ }))
501
+ .parallel_fn(async move {
502
+ pinned_conn.await.unwrap_or_default();
503
+ })
504
+ .build();
505
+
506
+ }
507
+
508
+ break;
509
+ },
510
+ state = &mut pinned_conn => {
511
+ if state.is_err() {
512
+ error_logger.log("Bad gateway: incomplete response").await;
513
+ return Ok(ResponseData::builder_without_request().status(StatusCode::BAD_GATEWAY).build());
514
+ }
515
+ },
516
+ };
517
+ }
518
+
519
+ if !sender.is_closed() {
520
+ let mut rwlock_write = connections.write().await;
521
+ rwlock_write.insert(connect_addr, sender);
522
+ drop(rwlock_write);
523
+ }
524
+
525
+ Ok(response)
526
+ }
527
+
528
+ async fn http_forwarded_auth_kept_alive(
529
+ sender: &mut SendRequest<BoxBody<Bytes, hyper::Error>>,
530
+ proxy_request: Request<BoxBody<Bytes, hyper::Error>>,
531
+ error_logger: &ErrorLogger,
532
+ mut original_request: RequestData,
533
+ forwarded_auth_copy_headers: Vec<String>,
534
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
535
+ let proxy_response = match sender.send_request(proxy_request).await {
536
+ Ok(response) => response,
537
+ Err(err) => {
538
+ error_logger.log(&format!("Bad gateway: {}", err)).await;
539
+ return Ok(
540
+ ResponseData::builder_without_request()
541
+ .status(StatusCode::BAD_GATEWAY)
542
+ .build(),
543
+ );
544
+ }
545
+ };
546
+
547
+ let response = if proxy_response.status().is_success() {
548
+ if !forwarded_auth_copy_headers.is_empty() {
549
+ let response_headers = proxy_response.headers();
550
+ let request_headers = original_request.get_mut_hyper_request().headers_mut();
551
+ for forwarded_auth_copy_header_string in forwarded_auth_copy_headers.iter() {
552
+ let forwarded_auth_copy_header = HeaderName::from_str(forwarded_auth_copy_header_string)?;
553
+ if response_headers.contains_key(&forwarded_auth_copy_header) {
554
+ while request_headers
555
+ .remove(&forwarded_auth_copy_header)
556
+ .is_some()
557
+ {}
558
+ for header_value in response_headers.get_all(&forwarded_auth_copy_header).iter() {
559
+ request_headers.append(&forwarded_auth_copy_header, header_value.clone());
560
+ }
561
+ }
562
+ }
563
+ }
564
+ ResponseData::builder(original_request).build()
565
+ } else {
566
+ ResponseData::builder_without_request()
567
+ .response(proxy_response.map(|b| b.map_err(|e| std::io::Error::other(e.to_string())).boxed()))
568
+ .build()
569
+ };
570
+
571
+ Ok(response)
572
+ }
ferron/src/optional_modules/fcgi.rs ADDED
@@ -0,0 +1,964 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // FastCGI handler code inspired by SVR.JS's GreenRhombus mod, translated from JavaScript to Rust.
2
+ // Based on the "cgi" and "scgi" module
3
+ use std::env;
4
+ use std::error::Error;
5
+ use std::path::{Path, PathBuf};
6
+ use std::sync::Arc;
7
+ use std::time::Duration;
8
+
9
+ use crate::ferron_common::{
10
+ ErrorLogger, HyperRequest, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
11
+ ServerModuleHandlers, SocketData,
12
+ };
13
+ use crate::ferron_common::{HyperUpgraded, WithRuntime};
14
+ use async_trait::async_trait;
15
+ use futures_util::future::Either;
16
+ use futures_util::TryStreamExt;
17
+ use hashlink::LinkedHashMap;
18
+ use http_body_util::{BodyExt, StreamBody};
19
+ use httparse::EMPTY_HEADER;
20
+ use hyper::body::{Bytes, Frame};
21
+ use hyper::{header, Response, StatusCode};
22
+ use hyper_tungstenite::HyperWebsocket;
23
+ use tokio::fs;
24
+ use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
25
+ use tokio::net::TcpStream;
26
+ use tokio::runtime::Handle;
27
+ use tokio::sync::RwLock;
28
+ use tokio_util::codec::{FramedRead, FramedWrite};
29
+ use tokio_util::io::{ReaderStream, SinkWriter, StreamReader};
30
+
31
+ use crate::ferron_res::server_software::SERVER_SOFTWARE;
32
+ use crate::ferron_util::cgi_response::CgiResponse;
33
+ use crate::ferron_util::copy_move::Copier;
34
+ use crate::ferron_util::fcgi_decoder::{FcgiDecodedData, FcgiDecoder};
35
+ use crate::ferron_util::fcgi_encoder::FcgiEncoder;
36
+ use crate::ferron_util::fcgi_name_value_pair::construct_fastcgi_name_value_pair;
37
+ use crate::ferron_util::fcgi_record::construct_fastcgi_record;
38
+ use crate::ferron_util::read_to_end_move::ReadToEndFuture;
39
+ use crate::ferron_util::split_stream_by_map::SplitStreamByMapExt;
40
+ use crate::ferron_util::ttl_cache::TtlCache;
41
+
42
+ pub fn server_module_init(
43
+ _config: &ServerConfig,
44
+ ) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
45
+ let cache = Arc::new(RwLock::new(TtlCache::new(Duration::from_millis(100))));
46
+ Ok(Box::new(FcgiModule::new(cache)))
47
+ }
48
+
49
+ #[allow(clippy::type_complexity)]
50
+ struct FcgiModule {
51
+ path_cache: Arc<RwLock<TtlCache<String, (Option<PathBuf>, Option<String>)>>>,
52
+ }
53
+
54
+ impl FcgiModule {
55
+ #[allow(clippy::type_complexity)]
56
+ fn new(path_cache: Arc<RwLock<TtlCache<String, (Option<PathBuf>, Option<String>)>>>) -> Self {
57
+ Self { path_cache }
58
+ }
59
+ }
60
+
61
+ impl ServerModule for FcgiModule {
62
+ fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
63
+ Box::new(FcgiModuleHandlers {
64
+ path_cache: self.path_cache.clone(),
65
+ handle,
66
+ })
67
+ }
68
+ }
69
+
70
+ #[allow(clippy::type_complexity)]
71
+ struct FcgiModuleHandlers {
72
+ handle: Handle,
73
+ path_cache: Arc<RwLock<TtlCache<String, (Option<PathBuf>, Option<String>)>>>,
74
+ }
75
+
76
+ #[async_trait]
77
+ impl ServerModuleHandlers for FcgiModuleHandlers {
78
+ async fn request_handler(
79
+ &mut self,
80
+ request: RequestData,
81
+ config: &ServerConfig,
82
+ socket_data: &SocketData,
83
+ error_logger: &ErrorLogger,
84
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
85
+ WithRuntime::new(self.handle.clone(), async move {
86
+ let mut fastcgi_script_exts = Vec::new();
87
+
88
+ let fastcgi_script_exts_yaml = &config["fcgiScriptExtensions"];
89
+ if let Some(fastcgi_script_exts_obtained) = fastcgi_script_exts_yaml.as_vec() {
90
+ for fastcgi_script_ext_yaml in fastcgi_script_exts_obtained.iter() {
91
+ if let Some(fastcgi_script_ext) = fastcgi_script_ext_yaml.as_str() {
92
+ fastcgi_script_exts.push(fastcgi_script_ext);
93
+ }
94
+ }
95
+ }
96
+
97
+ let mut fastcgi_to = "tcp://localhost:4000/";
98
+ let fastcgi_to_yaml = &config["fcgiTo"];
99
+ if let Some(fastcgi_to_obtained) = fastcgi_to_yaml.as_str() {
100
+ fastcgi_to = fastcgi_to_obtained;
101
+ }
102
+
103
+ let mut fastcgi_path = None;
104
+ if let Some(fastcgi_path_obtained) = config["fcgiPath"].as_str() {
105
+ fastcgi_path = Some(fastcgi_path_obtained.to_string());
106
+ }
107
+
108
+ let hyper_request = request.get_hyper_request();
109
+
110
+ let request_path = hyper_request.uri().path();
111
+ let mut request_path_bytes = request_path.bytes();
112
+ if request_path_bytes.len() < 1 || request_path_bytes.nth(0) != Some(b'/') {
113
+ return Ok(
114
+ ResponseData::builder(request)
115
+ .status(StatusCode::BAD_REQUEST)
116
+ .build(),
117
+ );
118
+ }
119
+
120
+ let mut execute_pathbuf = None;
121
+ let mut execute_path_info = None;
122
+ let mut wwwroot_detected = None;
123
+
124
+ if let Some(fastcgi_path) = fastcgi_path {
125
+ let mut canonical_fastcgi_path: &str = &fastcgi_path;
126
+ if canonical_fastcgi_path.bytes().last() == Some(b'/') {
127
+ canonical_fastcgi_path = &canonical_fastcgi_path[..(canonical_fastcgi_path.len() - 1)];
128
+ }
129
+
130
+ let request_path_with_slashes = match request_path == canonical_fastcgi_path {
131
+ true => format!("{}/", request_path),
132
+ false => request_path.to_string(),
133
+ };
134
+ if let Some(stripped_request_path) =
135
+ request_path_with_slashes.strip_prefix(canonical_fastcgi_path)
136
+ {
137
+ let wwwroot_yaml = &config["wwwroot"];
138
+ let wwwroot = wwwroot_yaml.as_str().unwrap_or("/nonexistent");
139
+
140
+ let wwwroot_unknown = PathBuf::from(wwwroot);
141
+ let wwwroot_pathbuf = match wwwroot_unknown.as_path().is_absolute() {
142
+ true => wwwroot_unknown,
143
+ false => match fs::canonicalize(&wwwroot_unknown).await {
144
+ Ok(pathbuf) => pathbuf,
145
+ Err(_) => wwwroot_unknown,
146
+ },
147
+ };
148
+ wwwroot_detected = Some(wwwroot_pathbuf.clone());
149
+ let wwwroot = wwwroot_pathbuf.as_path();
150
+
151
+ let mut relative_path = &request_path[1..];
152
+ while relative_path.as_bytes().first().copied() == Some(b'/') {
153
+ relative_path = &relative_path[1..];
154
+ }
155
+
156
+ let decoded_relative_path = match urlencoding::decode(relative_path) {
157
+ Ok(path) => path.to_string(),
158
+ Err(_) => {
159
+ return Ok(
160
+ ResponseData::builder(request)
161
+ .status(StatusCode::BAD_REQUEST)
162
+ .build(),
163
+ );
164
+ }
165
+ };
166
+
167
+ let joined_pathbuf = wwwroot.join(decoded_relative_path);
168
+ execute_pathbuf = Some(joined_pathbuf);
169
+ execute_path_info = stripped_request_path
170
+ .strip_prefix("/")
171
+ .map(|s| s.to_string());
172
+ }
173
+ }
174
+
175
+ if execute_pathbuf.is_none() {
176
+ if let Some(wwwroot) = config["wwwroot"].as_str() {
177
+ let cache_key = format!(
178
+ "{}{}{}",
179
+ match config["ip"].as_str() {
180
+ Some(ip) => format!("{}-", ip),
181
+ None => String::from(""),
182
+ },
183
+ match config["domain"].as_str() {
184
+ Some(domain) => format!("{}-", domain),
185
+ None => String::from(""),
186
+ },
187
+ request_path
188
+ );
189
+
190
+ let wwwroot_unknown = PathBuf::from(wwwroot);
191
+ let wwwroot_pathbuf = match wwwroot_unknown.as_path().is_absolute() {
192
+ true => wwwroot_unknown,
193
+ false => match fs::canonicalize(&wwwroot_unknown).await {
194
+ Ok(pathbuf) => pathbuf,
195
+ Err(_) => wwwroot_unknown,
196
+ },
197
+ };
198
+ wwwroot_detected = Some(wwwroot_pathbuf.clone());
199
+ let wwwroot = wwwroot_pathbuf.as_path();
200
+
201
+ let read_rwlock = self.path_cache.read().await;
202
+ let (execute_pathbuf_got, execute_path_info_got) = match read_rwlock.get(&cache_key) {
203
+ Some(data) => {
204
+ drop(read_rwlock);
205
+ data
206
+ }
207
+ None => {
208
+ drop(read_rwlock);
209
+ let mut relative_path = &request_path[1..];
210
+ while relative_path.as_bytes().first().copied() == Some(b'/') {
211
+ relative_path = &relative_path[1..];
212
+ }
213
+
214
+ let decoded_relative_path = match urlencoding::decode(relative_path) {
215
+ Ok(path) => path.to_string(),
216
+ Err(_) => {
217
+ return Ok(
218
+ ResponseData::builder(request)
219
+ .status(StatusCode::BAD_REQUEST)
220
+ .build(),
221
+ );
222
+ }
223
+ };
224
+
225
+ let joined_pathbuf = wwwroot.join(decoded_relative_path);
226
+ let mut execute_pathbuf: Option<PathBuf> = None;
227
+ let mut execute_path_info: Option<String> = None;
228
+
229
+ match fs::metadata(&joined_pathbuf).await {
230
+ Ok(metadata) => {
231
+ if metadata.is_file() {
232
+ let contained_extension = joined_pathbuf
233
+ .extension()
234
+ .map(|a| format!(".{}", a.to_string_lossy()));
235
+ if let Some(contained_extension) = contained_extension {
236
+ if fastcgi_script_exts.contains(&(&contained_extension as &str)) {
237
+ execute_pathbuf = Some(joined_pathbuf);
238
+ }
239
+ }
240
+ } else if metadata.is_dir() {
241
+ let indexes = vec!["index.php", "index.cgi"];
242
+ for index in indexes {
243
+ let temp_joined_pathbuf = joined_pathbuf.join(index);
244
+ match fs::metadata(&temp_joined_pathbuf).await {
245
+ Ok(temp_metadata) => {
246
+ if temp_metadata.is_file() {
247
+ let contained_extension = temp_joined_pathbuf
248
+ .extension()
249
+ .map(|a| format!(".{}", a.to_string_lossy()));
250
+ if let Some(contained_extension) = contained_extension {
251
+ if fastcgi_script_exts.contains(&(&contained_extension as &str)) {
252
+ execute_pathbuf = Some(temp_joined_pathbuf);
253
+ break;
254
+ }
255
+ }
256
+ }
257
+ }
258
+ Err(_) => continue,
259
+ };
260
+ }
261
+ }
262
+ }
263
+ Err(err) => {
264
+ if err.kind() == tokio::io::ErrorKind::NotADirectory {
265
+ // TODO: find a file
266
+ let mut temp_pathbuf = joined_pathbuf.clone();
267
+ loop {
268
+ if !temp_pathbuf.pop() {
269
+ break;
270
+ }
271
+ match fs::metadata(&temp_pathbuf).await {
272
+ Ok(metadata) => {
273
+ if metadata.is_file() {
274
+ let temp_path = temp_pathbuf.as_path();
275
+ if !temp_path.starts_with(wwwroot) {
276
+ // Traversed above the webroot, so ignore that.
277
+ break;
278
+ }
279
+ let path_info = match joined_pathbuf.as_path().strip_prefix(temp_path) {
280
+ Ok(path) => {
281
+ let path = path.to_string_lossy().to_string();
282
+ Some(match cfg!(windows) {
283
+ true => path.replace("\\", "/"),
284
+ false => path,
285
+ })
286
+ }
287
+ Err(_) => None,
288
+ };
289
+ let mut request_path_normalized = match cfg!(windows) {
290
+ true => request_path.to_lowercase(),
291
+ false => request_path.to_string(),
292
+ };
293
+ while request_path_normalized.contains("//") {
294
+ request_path_normalized = request_path_normalized.replace("//", "/");
295
+ }
296
+ if request_path_normalized == "/cgi-bin"
297
+ || request_path_normalized.starts_with("/cgi-bin/")
298
+ {
299
+ execute_pathbuf = Some(temp_pathbuf);
300
+ execute_path_info = path_info;
301
+ break;
302
+ } else {
303
+ let contained_extension = temp_pathbuf
304
+ .extension()
305
+ .map(|a| format!(".{}", a.to_string_lossy()));
306
+ if let Some(contained_extension) = contained_extension {
307
+ if fastcgi_script_exts.contains(&(&contained_extension as &str)) {
308
+ execute_pathbuf = Some(temp_pathbuf);
309
+ execute_path_info = path_info;
310
+ break;
311
+ }
312
+ }
313
+ }
314
+ } else {
315
+ break;
316
+ }
317
+ }
318
+ Err(err) => match err.kind() {
319
+ tokio::io::ErrorKind::NotADirectory => (),
320
+ _ => break,
321
+ },
322
+ };
323
+ }
324
+ }
325
+ }
326
+ };
327
+ let data = (execute_pathbuf, execute_path_info);
328
+
329
+ let mut write_rwlock = self.path_cache.write().await;
330
+ write_rwlock.cleanup();
331
+ write_rwlock.insert(cache_key, data.clone());
332
+ drop(write_rwlock);
333
+ data
334
+ }
335
+ };
336
+
337
+ execute_pathbuf = execute_pathbuf_got;
338
+ execute_path_info = execute_path_info_got;
339
+ }
340
+ }
341
+
342
+ if let Some(execute_pathbuf) = execute_pathbuf {
343
+ if let Some(wwwroot_detected) = wwwroot_detected {
344
+ return execute_fastcgi_with_environment_variables(
345
+ request,
346
+ socket_data,
347
+ error_logger,
348
+ wwwroot_detected.as_path(),
349
+ execute_pathbuf,
350
+ execute_path_info,
351
+ config["serverAdministratorEmail"].as_str(),
352
+ fastcgi_to,
353
+ )
354
+ .await;
355
+ }
356
+ }
357
+
358
+ Ok(ResponseData::builder(request).build())
359
+ })
360
+ .await
361
+ }
362
+
363
+ async fn proxy_request_handler(
364
+ &mut self,
365
+ request: RequestData,
366
+ _config: &ServerConfig,
367
+ _socket_data: &SocketData,
368
+ _error_logger: &ErrorLogger,
369
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
370
+ Ok(ResponseData::builder(request).build())
371
+ }
372
+
373
+ async fn response_modifying_handler(
374
+ &mut self,
375
+ response: HyperResponse,
376
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
377
+ Ok(response)
378
+ }
379
+
380
+ async fn proxy_response_modifying_handler(
381
+ &mut self,
382
+ response: HyperResponse,
383
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
384
+ Ok(response)
385
+ }
386
+
387
+ async fn connect_proxy_request_handler(
388
+ &mut self,
389
+ _upgraded_request: HyperUpgraded,
390
+ _connect_address: &str,
391
+ _config: &ServerConfig,
392
+ _socket_data: &SocketData,
393
+ _error_logger: &ErrorLogger,
394
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
395
+ Ok(())
396
+ }
397
+
398
+ fn does_connect_proxy_requests(&mut self) -> bool {
399
+ false
400
+ }
401
+
402
+ async fn websocket_request_handler(
403
+ &mut self,
404
+ _websocket: HyperWebsocket,
405
+ _uri: &hyper::Uri,
406
+ _config: &ServerConfig,
407
+ _socket_data: &SocketData,
408
+ _error_logger: &ErrorLogger,
409
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
410
+ Ok(())
411
+ }
412
+
413
+ fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
414
+ false
415
+ }
416
+ }
417
+
418
+ #[allow(clippy::too_many_arguments)]
419
+ async fn execute_fastcgi_with_environment_variables(
420
+ request: RequestData,
421
+ socket_data: &SocketData,
422
+ error_logger: &ErrorLogger,
423
+ wwwroot: &Path,
424
+ execute_pathbuf: PathBuf,
425
+ path_info: Option<String>,
426
+ server_administrator_email: Option<&str>,
427
+ fastcgi_to: &str,
428
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
429
+ let mut environment_variables: LinkedHashMap<String, String> = LinkedHashMap::new();
430
+
431
+ let hyper_request = request.get_hyper_request();
432
+ let original_request_uri = request.get_original_url().unwrap_or(hyper_request.uri());
433
+
434
+ if let Some(auth_user) = request.get_auth_user() {
435
+ if let Some(authorization) = hyper_request.headers().get(header::AUTHORIZATION) {
436
+ let authorization_value = String::from_utf8_lossy(authorization.as_bytes()).to_string();
437
+ let mut authorization_value_split = authorization_value.split(" ");
438
+ if let Some(authorization_type) = authorization_value_split.next() {
439
+ environment_variables.insert("AUTH_TYPE".to_string(), authorization_type.to_string());
440
+ }
441
+ }
442
+ environment_variables.insert("REMOTE_USER".to_string(), auth_user.to_string());
443
+ }
444
+
445
+ environment_variables.insert(
446
+ "QUERY_STRING".to_string(),
447
+ match hyper_request.uri().query() {
448
+ Some(query) => query.to_string(),
449
+ None => "".to_string(),
450
+ },
451
+ );
452
+
453
+ environment_variables.insert("SERVER_SOFTWARE".to_string(), SERVER_SOFTWARE.to_string());
454
+ environment_variables.insert(
455
+ "SERVER_PROTOCOL".to_string(),
456
+ match hyper_request.version() {
457
+ hyper::Version::HTTP_09 => "HTTP/0.9".to_string(),
458
+ hyper::Version::HTTP_10 => "HTTP/1.0".to_string(),
459
+ hyper::Version::HTTP_11 => "HTTP/1.1".to_string(),
460
+ hyper::Version::HTTP_2 => "HTTP/2.0".to_string(),
461
+ hyper::Version::HTTP_3 => "HTTP/3.0".to_string(),
462
+ _ => "HTTP/Unknown".to_string(),
463
+ },
464
+ );
465
+ environment_variables.insert(
466
+ "SERVER_PORT".to_string(),
467
+ socket_data.local_addr.port().to_string(),
468
+ );
469
+ environment_variables.insert(
470
+ "SERVER_ADDR".to_string(),
471
+ socket_data.local_addr.ip().to_canonical().to_string(),
472
+ );
473
+ if let Some(server_administrator_email) = server_administrator_email {
474
+ environment_variables.insert(
475
+ "SERVER_ADMIN".to_string(),
476
+ server_administrator_email.to_string(),
477
+ );
478
+ }
479
+ if let Some(host) = hyper_request.headers().get(header::HOST) {
480
+ environment_variables.insert(
481
+ "SERVER_NAME".to_string(),
482
+ String::from_utf8_lossy(host.as_bytes()).to_string(),
483
+ );
484
+ }
485
+
486
+ environment_variables.insert(
487
+ "DOCUMENT_ROOT".to_string(),
488
+ wwwroot.to_string_lossy().to_string(),
489
+ );
490
+ environment_variables.insert(
491
+ "PATH_INFO".to_string(),
492
+ match &path_info {
493
+ Some(path_info) => format!("/{}", path_info),
494
+ None => "".to_string(),
495
+ },
496
+ );
497
+ environment_variables.insert(
498
+ "PATH_TRANSLATED".to_string(),
499
+ match &path_info {
500
+ Some(path_info) => {
501
+ let mut path_translated = execute_pathbuf.clone();
502
+ path_translated.push(path_info);
503
+ path_translated.to_string_lossy().to_string()
504
+ }
505
+ None => "".to_string(),
506
+ },
507
+ );
508
+ environment_variables.insert(
509
+ "REQUEST_METHOD".to_string(),
510
+ hyper_request.method().to_string(),
511
+ );
512
+ environment_variables.insert("GATEWAY_INTERFACE".to_string(), "CGI/1.1".to_string());
513
+ environment_variables.insert(
514
+ "REQUEST_URI".to_string(),
515
+ format!(
516
+ "{}{}",
517
+ original_request_uri.path(),
518
+ match original_request_uri.query() {
519
+ Some(query) => format!("?{}", query),
520
+ None => String::from(""),
521
+ }
522
+ ),
523
+ );
524
+
525
+ environment_variables.insert(
526
+ "REMOTE_PORT".to_string(),
527
+ socket_data.remote_addr.port().to_string(),
528
+ );
529
+ environment_variables.insert(
530
+ "REMOTE_ADDR".to_string(),
531
+ socket_data.remote_addr.ip().to_canonical().to_string(),
532
+ );
533
+
534
+ environment_variables.insert(
535
+ "SCRIPT_FILENAME".to_string(),
536
+ execute_pathbuf.to_string_lossy().to_string(),
537
+ );
538
+ if let Ok(script_path) = execute_pathbuf.as_path().strip_prefix(wwwroot) {
539
+ environment_variables.insert(
540
+ "SCRIPT_NAME".to_string(),
541
+ format!(
542
+ "/{}",
543
+ match cfg!(windows) {
544
+ true => script_path.to_string_lossy().to_string().replace("\\", "/"),
545
+ false => script_path.to_string_lossy().to_string(),
546
+ }
547
+ ),
548
+ );
549
+ }
550
+
551
+ if socket_data.encrypted {
552
+ environment_variables.insert("HTTPS".to_string(), "ON".to_string());
553
+ }
554
+
555
+ let mut content_length_set = false;
556
+ for (header_name, header_value) in hyper_request.headers().iter() {
557
+ let env_header_name = match *header_name {
558
+ header::CONTENT_LENGTH => {
559
+ content_length_set = true;
560
+ "CONTENT_LENGTH".to_string()
561
+ }
562
+ header::CONTENT_TYPE => "CONTENT_TYPE".to_string(),
563
+ _ => {
564
+ let mut result = String::new();
565
+
566
+ result.push_str("HTTP_");
567
+
568
+ for c in header_name.as_str().to_uppercase().chars() {
569
+ if c.is_alphanumeric() {
570
+ result.push(c);
571
+ } else {
572
+ result.push('_');
573
+ }
574
+ }
575
+
576
+ result
577
+ }
578
+ };
579
+ if environment_variables.contains_key(&env_header_name) {
580
+ let value = environment_variables.get_mut(&env_header_name);
581
+ if let Some(value) = value {
582
+ if env_header_name == "HTTP_COOKIE" {
583
+ value.push_str("; ");
584
+ } else {
585
+ // See https://stackoverflow.com/a/1801191
586
+ value.push_str(", ");
587
+ }
588
+ value.push_str(String::from_utf8_lossy(header_value.as_bytes()).as_ref());
589
+ } else {
590
+ environment_variables.insert(
591
+ env_header_name,
592
+ String::from_utf8_lossy(header_value.as_bytes()).to_string(),
593
+ );
594
+ }
595
+ } else {
596
+ environment_variables.insert(
597
+ env_header_name,
598
+ String::from_utf8_lossy(header_value.as_bytes()).to_string(),
599
+ );
600
+ }
601
+ }
602
+
603
+ if !content_length_set {
604
+ environment_variables.insert("CONTENT_LENGTH".to_string(), "0".to_string());
605
+ }
606
+
607
+ let (hyper_request, _, _) = request.into_parts();
608
+
609
+ execute_fastcgi(
610
+ hyper_request,
611
+ error_logger,
612
+ fastcgi_to,
613
+ environment_variables,
614
+ )
615
+ .await
616
+ }
617
+
618
+ async fn execute_fastcgi(
619
+ hyper_request: HyperRequest,
620
+ error_logger: &ErrorLogger,
621
+ fastcgi_to: &str,
622
+ mut environment_variables: LinkedHashMap<String, String>,
623
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
624
+ let (_, body) = hyper_request.into_parts();
625
+
626
+ // Insert other environment variables
627
+ for (key, value) in env::vars_os() {
628
+ let key_string = key.to_string_lossy().to_string();
629
+ let value_string = value.to_string_lossy().to_string();
630
+ environment_variables
631
+ .entry(key_string)
632
+ .or_insert(value_string);
633
+ }
634
+
635
+ let fastcgi_to_fixed = if let Some(stripped) = fastcgi_to.strip_prefix("unix:///") {
636
+ // hyper::Uri fails to parse a string if there is an empty authority, so add an "ignore" authority to Unix socket URLs
637
+ &format!("unix://ignore/{}", stripped)
638
+ } else {
639
+ fastcgi_to
640
+ };
641
+
642
+ let fastcgi_to_url = fastcgi_to_fixed.parse::<hyper::Uri>()?;
643
+ let scheme_str = fastcgi_to_url.scheme_str();
644
+
645
+ let (socket_reader, mut socket_writer) = match scheme_str {
646
+ Some("tcp") => {
647
+ let host = match fastcgi_to_url.host() {
648
+ Some(host) => host,
649
+ None => Err(anyhow::anyhow!("The FastCGI URL doesn't include the host"))?,
650
+ };
651
+
652
+ let port = match fastcgi_to_url.port_u16() {
653
+ Some(port) => port,
654
+ None => Err(anyhow::anyhow!("The FastCGI URL doesn't include the port"))?,
655
+ };
656
+
657
+ let addr = format!("{}:{}", host, port);
658
+
659
+ match connect_tcp(&addr).await {
660
+ Ok(data) => data,
661
+ Err(err) => match err.kind() {
662
+ tokio::io::ErrorKind::ConnectionRefused
663
+ | tokio::io::ErrorKind::NotFound
664
+ | tokio::io::ErrorKind::HostUnreachable => {
665
+ error_logger
666
+ .log(&format!("Service unavailable: {}", err))
667
+ .await;
668
+ return Ok(
669
+ ResponseData::builder_without_request()
670
+ .status(StatusCode::SERVICE_UNAVAILABLE)
671
+ .build(),
672
+ );
673
+ }
674
+ _ => Err(err)?,
675
+ },
676
+ }
677
+ }
678
+ Some("unix") => {
679
+ let path = fastcgi_to_url.path();
680
+ match connect_unix(path).await {
681
+ Ok(data) => data,
682
+ Err(err) => match err.kind() {
683
+ tokio::io::ErrorKind::ConnectionRefused
684
+ | tokio::io::ErrorKind::NotFound
685
+ | tokio::io::ErrorKind::HostUnreachable => {
686
+ error_logger
687
+ .log(&format!("Service unavailable: {}", err))
688
+ .await;
689
+ return Ok(
690
+ ResponseData::builder_without_request()
691
+ .status(StatusCode::SERVICE_UNAVAILABLE)
692
+ .build(),
693
+ );
694
+ }
695
+ _ => Err(err)?,
696
+ },
697
+ }
698
+ }
699
+ _ => Err(anyhow::anyhow!(
700
+ "Only HTTP and HTTPS reverse proxy URLs are supported."
701
+ ))?,
702
+ };
703
+
704
+ // Construct and send BEGIN_REQUEST record
705
+ // Use the responder role and don't use keep-alive
706
+ let begin_request_packet = construct_fastcgi_record(1, 1, &[0, 1, 0, 0, 0, 0, 0, 0]);
707
+ socket_writer.write_all(&begin_request_packet).await?;
708
+
709
+ // Construct and send PARAMS records
710
+ let mut environment_variables_to_wrap = Vec::new();
711
+ for (key, value) in environment_variables.iter() {
712
+ let mut environment_variable =
713
+ construct_fastcgi_name_value_pair(key.as_bytes(), value.as_bytes());
714
+ environment_variables_to_wrap.append(&mut environment_variable);
715
+ }
716
+ if !environment_variables_to_wrap.is_empty() {
717
+ let mut offset = 0;
718
+ while offset < environment_variables_to_wrap.len() {
719
+ let chunk_size = std::cmp::min(65536, environment_variables_to_wrap.len() - offset);
720
+ let chunk = &environment_variables_to_wrap[offset..offset + chunk_size];
721
+
722
+ // Record type 4 means PARAMS
723
+ let params_packet = construct_fastcgi_record(4, 1, chunk);
724
+ socket_writer.write_all(&params_packet).await?;
725
+
726
+ offset += chunk_size;
727
+ }
728
+ }
729
+
730
+ let params_packet_terminating = construct_fastcgi_record(4, 1, &[]);
731
+ socket_writer.write_all(&params_packet_terminating).await?;
732
+
733
+ let cgi_stdin_reader = StreamReader::new(body.into_data_stream().map_err(std::io::Error::other));
734
+
735
+ // Emulated standard input, standard output, and standard error
736
+ type EitherStream = Either<Result<Bytes, std::io::Error>, Result<Bytes, std::io::Error>>;
737
+ let stdin = SinkWriter::new(FramedWrite::new(socket_writer, FcgiEncoder::new()));
738
+ let stdout_and_stderr = FramedRead::new(socket_reader, FcgiDecoder::new());
739
+ let (stdout_stream, stderr_stream) = stdout_and_stderr.split_by_map(|item| match item {
740
+ Ok(FcgiDecodedData::Stdout(bytes)) => EitherStream::Left(Ok(bytes)),
741
+ Ok(FcgiDecodedData::Stderr(bytes)) => EitherStream::Right(Ok(bytes)),
742
+ Err(err) => EitherStream::Left(Err(err)),
743
+ });
744
+ let stdout = StreamReader::new(stdout_stream);
745
+ let stderr = StreamReader::new(stderr_stream);
746
+
747
+ let mut cgi_response = CgiResponse::new(stdout);
748
+
749
+ let stdin_copy_future = Copier::with_zero_packet_writing(cgi_stdin_reader, stdin).copy();
750
+ let mut stdin_copy_future_pinned = Box::pin(stdin_copy_future);
751
+
752
+ let stderr_read_future = ReadToEndFuture::new(stderr);
753
+ let mut stderr_read_future_pinned = Box::pin(stderr_read_future);
754
+
755
+ let mut headers = [EMPTY_HEADER; 128];
756
+
757
+ let mut early_stdin_copied = false;
758
+
759
+ // Needed to wrap this in another scope to prevent errors with multiple mutable borrows.
760
+ {
761
+ let mut head_obtained = false;
762
+ let stdout_parse_future = cgi_response.get_head();
763
+ tokio::pin!(stdout_parse_future);
764
+
765
+ // Cannot use a loop with tokio::select, since stdin_copy_future_pinned being constantly ready will make the web server stop responding to HTTP requests
766
+ tokio::select! {
767
+ biased;
768
+
769
+ result = &mut stdin_copy_future_pinned => {
770
+ early_stdin_copied = true;
771
+ result?;
772
+ },
773
+ obtained_head = &mut stdout_parse_future => {
774
+ let obtained_head = obtained_head?;
775
+ if !obtained_head.is_empty() {
776
+ httparse::parse_headers(obtained_head, &mut headers)?;
777
+ }
778
+ head_obtained = true;
779
+ },
780
+ result = &mut stderr_read_future_pinned => {
781
+ let stderr_vec = result?;
782
+ let stderr_string = String::from_utf8_lossy(stderr_vec.as_slice()).to_string();
783
+ if !stderr_string.is_empty() {
784
+ error_logger
785
+ .log(&format!("There were CGI errors: {}", stderr_string))
786
+ .await;
787
+ }
788
+ return Ok(
789
+ ResponseData::builder_without_request()
790
+ .status(StatusCode::INTERNAL_SERVER_ERROR)
791
+ .build(),
792
+ );
793
+ },
794
+ }
795
+
796
+ if !head_obtained {
797
+ // Kept it same as in the tokio::select macro
798
+ tokio::select! {
799
+ biased;
800
+
801
+ result = &mut stderr_read_future_pinned => {
802
+ let stderr_vec = result?;
803
+ let stderr_string = String::from_utf8_lossy(stderr_vec.as_slice()).to_string();
804
+ if !stderr_string.is_empty() {
805
+ error_logger
806
+ .log(&format!("There were FastCGI errors: {}", stderr_string))
807
+ .await;
808
+ }
809
+ return Ok(
810
+ ResponseData::builder_without_request()
811
+ .status(StatusCode::INTERNAL_SERVER_ERROR)
812
+ .build(),
813
+ );
814
+ },
815
+ obtained_head = &mut stdout_parse_future => {
816
+ let obtained_head = obtained_head?;
817
+ if !obtained_head.is_empty() {
818
+ httparse::parse_headers(obtained_head, &mut headers)?;
819
+ }
820
+ }
821
+ }
822
+ }
823
+ }
824
+
825
+ let mut response_builder = Response::builder();
826
+ let mut status_code = 200;
827
+ for header in headers {
828
+ if header == EMPTY_HEADER {
829
+ break;
830
+ }
831
+ let mut is_status_header = false;
832
+ match &header.name.to_lowercase() as &str {
833
+ "location" => {
834
+ if !(300..=399).contains(&status_code) {
835
+ status_code = 302;
836
+ }
837
+ }
838
+ "status" => {
839
+ is_status_header = true;
840
+ let header_value_cow = String::from_utf8_lossy(header.value);
841
+ let mut split_status = header_value_cow.split(" ");
842
+ let first_part = split_status.next();
843
+ if let Some(first_part) = first_part {
844
+ if first_part.starts_with("HTTP/") {
845
+ let second_part = split_status.next();
846
+ if let Some(second_part) = second_part {
847
+ if let Ok(parsed_status_code) = second_part.parse::<u16>() {
848
+ status_code = parsed_status_code;
849
+ }
850
+ }
851
+ } else if let Ok(parsed_status_code) = first_part.parse::<u16>() {
852
+ status_code = parsed_status_code;
853
+ }
854
+ }
855
+ }
856
+ _ => (),
857
+ }
858
+ if !is_status_header {
859
+ response_builder = response_builder.header(header.name, header.value);
860
+ }
861
+ }
862
+
863
+ response_builder = response_builder.status(status_code);
864
+
865
+ let reader_stream = ReaderStream::new(cgi_response);
866
+ let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
867
+ let boxed_body = stream_body.boxed();
868
+
869
+ let response = response_builder.body(boxed_body)?;
870
+
871
+ let error_logger = error_logger.clone();
872
+
873
+ Ok(
874
+ ResponseData::builder_without_request()
875
+ .response(response)
876
+ .parallel_fn(async move {
877
+ let mut stdin_copied = early_stdin_copied;
878
+
879
+ if !stdin_copied {
880
+ tokio::select! {
881
+ biased;
882
+
883
+ _ = &mut stdin_copy_future_pinned => {
884
+ stdin_copied = true;
885
+ },
886
+ result = &mut stderr_read_future_pinned => {
887
+ let stderr_vec = result.unwrap_or(vec![]);
888
+ let stderr_string = String::from_utf8_lossy(stderr_vec.as_slice()).to_string();
889
+ if !stderr_string.is_empty() {
890
+ error_logger
891
+ .log(&format!("There were FastCGI errors: {}", stderr_string))
892
+ .await;
893
+ }
894
+ },
895
+ }
896
+ }
897
+
898
+ if stdin_copied {
899
+ let stderr_vec = stderr_read_future_pinned.await.unwrap_or(vec![]);
900
+ let stderr_string = String::from_utf8_lossy(stderr_vec.as_slice()).to_string();
901
+ if !stderr_string.is_empty() {
902
+ error_logger
903
+ .log(&format!("There were FastCGI errors: {}", stderr_string))
904
+ .await;
905
+ }
906
+ } else {
907
+ stdin_copy_future_pinned.await.unwrap_or_default();
908
+ }
909
+ })
910
+ .build(),
911
+ )
912
+ }
913
+
914
+ async fn connect_tcp(
915
+ addr: &str,
916
+ ) -> Result<
917
+ (
918
+ Box<dyn AsyncRead + Send + Sync + Unpin>,
919
+ Box<dyn AsyncWrite + Send + Sync + Unpin>,
920
+ ),
921
+ tokio::io::Error,
922
+ > {
923
+ let socket = TcpStream::connect(addr).await?;
924
+ socket.set_nodelay(true)?;
925
+
926
+ let (socket_reader_set, socket_writer_set) = tokio::io::split(socket);
927
+ Ok((Box::new(socket_reader_set), Box::new(socket_writer_set)))
928
+ }
929
+
930
+ #[allow(dead_code)]
931
+ #[cfg(unix)]
932
+ async fn connect_unix(
933
+ path: &str,
934
+ ) -> Result<
935
+ (
936
+ Box<dyn AsyncRead + Send + Sync + Unpin>,
937
+ Box<dyn AsyncWrite + Send + Sync + Unpin>,
938
+ ),
939
+ tokio::io::Error,
940
+ > {
941
+ use tokio::net::UnixStream;
942
+
943
+ let socket = UnixStream::connect(path).await?;
944
+
945
+ let (socket_reader_set, socket_writer_set) = tokio::io::split(socket);
946
+ Ok((Box::new(socket_reader_set), Box::new(socket_writer_set)))
947
+ }
948
+
949
+ #[allow(dead_code)]
950
+ #[cfg(not(unix))]
951
+ async fn connect_unix(
952
+ _path: &str,
953
+ ) -> Result<
954
+ (
955
+ Box<dyn AsyncRead + Send + Sync + Unpin>,
956
+ Box<dyn AsyncWrite + Send + Sync + Unpin>,
957
+ ),
958
+ tokio::io::Error,
959
+ > {
960
+ Err(tokio::io::Error::new(
961
+ tokio::io::ErrorKind::Unsupported,
962
+ "Unix sockets are not supports on non-Unix platforms.",
963
+ ))
964
+ }
ferron/src/optional_modules/fproxy.rs ADDED
@@ -0,0 +1,301 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use std::error::Error;
2
+ use std::str::FromStr;
3
+
4
+ use crate::ferron_common::{
5
+ ErrorLogger, HyperUpgraded, RequestData, ResponseData, ServerConfig, ServerModule,
6
+ ServerModuleHandlers, SocketData,
7
+ };
8
+ use crate::ferron_common::{HyperResponse, WithRuntime};
9
+ use async_trait::async_trait;
10
+ use http_body_util::combinators::BoxBody;
11
+ use http_body_util::BodyExt;
12
+ use hyper::body::Bytes;
13
+ use hyper::{header, Request, StatusCode, Uri};
14
+ use hyper_tungstenite::HyperWebsocket;
15
+ use hyper_util::rt::TokioIo;
16
+ use tokio::io::{AsyncRead, AsyncWrite};
17
+ use tokio::net::TcpStream;
18
+ use tokio::runtime::Handle;
19
+
20
+ pub fn server_module_init(
21
+ _config: &ServerConfig,
22
+ ) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
23
+ Ok(Box::new(ForwardProxyModule::new()))
24
+ }
25
+
26
+ struct ForwardProxyModule;
27
+
28
+ impl ForwardProxyModule {
29
+ fn new() -> Self {
30
+ Self
31
+ }
32
+ }
33
+
34
+ impl ServerModule for ForwardProxyModule {
35
+ fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
36
+ Box::new(ForwardProxyModuleHandlers { handle })
37
+ }
38
+ }
39
+
40
+ struct ForwardProxyModuleHandlers {
41
+ handle: Handle,
42
+ }
43
+
44
+ #[async_trait]
45
+ impl ServerModuleHandlers for ForwardProxyModuleHandlers {
46
+ async fn request_handler(
47
+ &mut self,
48
+ request: RequestData,
49
+ _config: &ServerConfig,
50
+ _socket_data: &SocketData,
51
+ _error_logger: &ErrorLogger,
52
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
53
+ Ok(ResponseData::builder(request).build())
54
+ }
55
+
56
+ async fn proxy_request_handler(
57
+ &mut self,
58
+ request: RequestData,
59
+ _config: &ServerConfig,
60
+ _socket_data: &SocketData,
61
+ error_logger: &ErrorLogger,
62
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
63
+ WithRuntime::new(self.handle.clone(), async move {
64
+ // Code taken from reverse proxy module
65
+ let (hyper_request, _auth_user, _original_url) = request.into_parts();
66
+ let (mut hyper_request_parts, request_body) = hyper_request.into_parts();
67
+
68
+ match hyper_request_parts.uri.scheme_str() {
69
+ Some("http") | None => (),
70
+ _ => {
71
+ return Ok(
72
+ ResponseData::builder_without_request()
73
+ .status(StatusCode::BAD_REQUEST)
74
+ .build(),
75
+ );
76
+ }
77
+ };
78
+
79
+ let host = match hyper_request_parts.uri.host() {
80
+ Some(host) => host,
81
+ None => {
82
+ return Ok(
83
+ ResponseData::builder_without_request()
84
+ .status(StatusCode::BAD_REQUEST)
85
+ .build(),
86
+ );
87
+ }
88
+ };
89
+
90
+ let port = hyper_request_parts.uri.port_u16().unwrap_or(80);
91
+
92
+ let addr = format!("{}:{}", host, port);
93
+ let stream = match TcpStream::connect(addr).await {
94
+ Ok(stream) => stream,
95
+ Err(err) => {
96
+ match err.kind() {
97
+ tokio::io::ErrorKind::ConnectionRefused
98
+ | tokio::io::ErrorKind::NotFound
99
+ | tokio::io::ErrorKind::HostUnreachable => {
100
+ error_logger
101
+ .log(&format!("Service unavailable: {}", err))
102
+ .await;
103
+ return Ok(
104
+ ResponseData::builder_without_request()
105
+ .status(StatusCode::SERVICE_UNAVAILABLE)
106
+ .build(),
107
+ );
108
+ }
109
+ tokio::io::ErrorKind::TimedOut => {
110
+ error_logger.log(&format!("Gateway timeout: {}", err)).await;
111
+ return Ok(
112
+ ResponseData::builder_without_request()
113
+ .status(StatusCode::GATEWAY_TIMEOUT)
114
+ .build(),
115
+ );
116
+ }
117
+ _ => {
118
+ error_logger.log(&format!("Bad gateway: {}", err)).await;
119
+ return Ok(
120
+ ResponseData::builder_without_request()
121
+ .status(StatusCode::BAD_GATEWAY)
122
+ .build(),
123
+ );
124
+ }
125
+ };
126
+ }
127
+ };
128
+
129
+ match stream.set_nodelay(true) {
130
+ Ok(_) => (),
131
+ Err(err) => {
132
+ error_logger.log(&format!("Bad gateway: {}", err)).await;
133
+ return Ok(
134
+ ResponseData::builder_without_request()
135
+ .status(StatusCode::BAD_GATEWAY)
136
+ .build(),
137
+ );
138
+ }
139
+ };
140
+
141
+ let hyper_request_path = hyper_request_parts.uri.path();
142
+
143
+ hyper_request_parts.uri = Uri::from_str(&format!(
144
+ "{}{}",
145
+ hyper_request_path,
146
+ match hyper_request_parts.uri.query() {
147
+ Some(query) => format!("?{}", query),
148
+ None => "".to_string(),
149
+ }
150
+ ))?;
151
+
152
+ // Connection header to disable HTTP/1.1 keep-alive
153
+ hyper_request_parts
154
+ .headers
155
+ .insert(header::CONNECTION, "close".parse()?);
156
+
157
+ let proxy_request = Request::from_parts(hyper_request_parts, request_body);
158
+
159
+ http_proxy(stream, proxy_request, error_logger).await
160
+ })
161
+ .await
162
+ }
163
+
164
+ async fn response_modifying_handler(
165
+ &mut self,
166
+ response: HyperResponse,
167
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
168
+ Ok(response)
169
+ }
170
+
171
+ async fn proxy_response_modifying_handler(
172
+ &mut self,
173
+ response: HyperResponse,
174
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
175
+ Ok(response)
176
+ }
177
+
178
+ async fn connect_proxy_request_handler(
179
+ &mut self,
180
+ upgraded_request: HyperUpgraded,
181
+ connect_address: &str,
182
+ _config: &ServerConfig,
183
+ _socket_data: &SocketData,
184
+ error_logger: &ErrorLogger,
185
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
186
+ WithRuntime::new(self.handle.clone(), async move {
187
+ let mut stream = match TcpStream::connect(connect_address).await {
188
+ Ok(stream) => stream,
189
+ Err(err) => {
190
+ error_logger
191
+ .log(&format!("Cannot connect to the remote server: {}", err))
192
+ .await;
193
+ return Ok(());
194
+ }
195
+ };
196
+ match stream.set_nodelay(true) {
197
+ Ok(_) => (),
198
+ Err(err) => {
199
+ error_logger
200
+ .log(&format!(
201
+ "Cannot disable Nagle algorithm when connecting to the remote server: {}",
202
+ err
203
+ ))
204
+ .await;
205
+ return Ok(());
206
+ }
207
+ };
208
+
209
+ let mut upgraded = TokioIo::new(upgraded_request);
210
+
211
+ tokio::io::copy_bidirectional(&mut upgraded, &mut stream)
212
+ .await
213
+ .unwrap_or_default();
214
+
215
+ Ok(())
216
+ })
217
+ .await
218
+ }
219
+
220
+ fn does_connect_proxy_requests(&mut self) -> bool {
221
+ true
222
+ }
223
+
224
+ async fn websocket_request_handler(
225
+ &mut self,
226
+ _websocket: HyperWebsocket,
227
+ _uri: &hyper::Uri,
228
+ _config: &ServerConfig,
229
+ _socket_data: &SocketData,
230
+ _error_logger: &ErrorLogger,
231
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
232
+ Ok(())
233
+ }
234
+
235
+ fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
236
+ false
237
+ }
238
+ }
239
+
240
+ async fn http_proxy(
241
+ stream: impl AsyncRead + AsyncWrite + Send + Unpin + 'static,
242
+ proxy_request: Request<BoxBody<Bytes, std::io::Error>>,
243
+ error_logger: &ErrorLogger,
244
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
245
+ let io = TokioIo::new(stream);
246
+
247
+ let (mut sender, conn) = match hyper::client::conn::http1::handshake(io).await {
248
+ Ok(data) => data,
249
+ Err(err) => {
250
+ error_logger.log(&format!("Bad gateway: {}", err)).await;
251
+ return Ok(
252
+ ResponseData::builder_without_request()
253
+ .status(StatusCode::BAD_GATEWAY)
254
+ .build(),
255
+ );
256
+ }
257
+ };
258
+
259
+ let send_request = sender.send_request(proxy_request);
260
+
261
+ let mut pinned_conn = Box::pin(conn);
262
+ tokio::pin!(send_request);
263
+
264
+ let response;
265
+
266
+ loop {
267
+ tokio::select! {
268
+ biased;
269
+
270
+ proxy_response = &mut send_request => {
271
+ let proxy_response = match proxy_response {
272
+ Ok(response) => response,
273
+ Err(err) => {
274
+ error_logger.log(&format!("Bad gateway: {}", err)).await;
275
+ return Ok(ResponseData::builder_without_request().status(StatusCode::BAD_GATEWAY).build());
276
+ }
277
+ };
278
+
279
+ response = ResponseData::builder_without_request()
280
+ .response(proxy_response.map(|b| {
281
+ b.map_err(|e| std::io::Error::other(e.to_string()))
282
+ .boxed()
283
+ }))
284
+ .parallel_fn(async move {
285
+ pinned_conn.await.unwrap_or_default();
286
+ })
287
+ .build();
288
+
289
+ break;
290
+ },
291
+ state = &mut pinned_conn => {
292
+ if state.is_err() {
293
+ error_logger.log("Bad gateway: incomplete response").await;
294
+ return Ok(ResponseData::builder_without_request().status(StatusCode::BAD_GATEWAY).build());
295
+ }
296
+ },
297
+ };
298
+ }
299
+
300
+ Ok(response)
301
+ }
ferron/src/optional_modules/rproxy.rs ADDED
@@ -0,0 +1,803 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use std::collections::HashMap;
2
+ use std::error::Error;
3
+ use std::str::FromStr;
4
+ use std::sync::Arc;
5
+ use std::time::Duration;
6
+
7
+ use crate::ferron_common::{
8
+ ErrorLogger, HyperUpgraded, RequestData, ResponseData, ServerConfig, ServerModule,
9
+ ServerModuleHandlers, SocketData,
10
+ };
11
+ use crate::ferron_common::{HyperResponse, WithRuntime};
12
+ use async_trait::async_trait;
13
+ use futures_util::{SinkExt, StreamExt};
14
+ use http::uri::{PathAndQuery, Scheme};
15
+ use http_body_util::combinators::BoxBody;
16
+ use http_body_util::BodyExt;
17
+ use hyper::body::Bytes;
18
+ use hyper::client::conn::http1::SendRequest;
19
+ use hyper::{header, Request, StatusCode, Uri};
20
+ use hyper_tungstenite::HyperWebsocket;
21
+ use hyper_util::rt::TokioIo;
22
+ use rustls::pki_types::ServerName;
23
+ use rustls::RootCertStore;
24
+ use rustls_native_certs::load_native_certs;
25
+ use tokio::io::{AsyncRead, AsyncWrite};
26
+ use tokio::net::TcpStream;
27
+ use tokio::runtime::Handle;
28
+ use tokio::sync::RwLock;
29
+ use tokio_rustls::TlsConnector;
30
+ use tokio_tungstenite::Connector;
31
+
32
+ use crate::ferron_util::no_server_verifier::NoServerVerifier;
33
+ use crate::ferron_util::ttl_cache::TtlCache;
34
+
35
+ const DEFAULT_CONCURRENT_CONNECTIONS_PER_HOST: u32 = 32;
36
+
37
+ pub fn server_module_init(
38
+ config: &ServerConfig,
39
+ ) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
40
+ let mut roots: RootCertStore = RootCertStore::empty();
41
+ let certs_result = load_native_certs();
42
+ if !certs_result.errors.is_empty() {
43
+ Err(anyhow::anyhow!(format!(
44
+ "Couldn't load the native certificate store: {}",
45
+ certs_result.errors[0]
46
+ )))?
47
+ }
48
+ let certs = certs_result.certs;
49
+
50
+ for cert in certs {
51
+ match roots.add(cert) {
52
+ Ok(_) => (),
53
+ Err(err) => Err(anyhow::anyhow!(format!(
54
+ "Couldn't add a certificate to the certificate store: {}",
55
+ err
56
+ )))?,
57
+ }
58
+ }
59
+
60
+ let mut connections_vec = Vec::new();
61
+ for _ in 0..DEFAULT_CONCURRENT_CONNECTIONS_PER_HOST {
62
+ connections_vec.push(RwLock::new(HashMap::new()));
63
+ }
64
+ Ok(Box::new(ReverseProxyModule::new(
65
+ Arc::new(roots),
66
+ Arc::new(connections_vec),
67
+ Arc::new(RwLock::new(TtlCache::new(Duration::from_millis(
68
+ config["global"]["loadBalancerHealthCheckWindow"]
69
+ .as_i64()
70
+ .unwrap_or(5000) as u64,
71
+ )))),
72
+ )))
73
+ }
74
+
75
+ #[allow(clippy::type_complexity)]
76
+ struct ReverseProxyModule {
77
+ roots: Arc<RootCertStore>,
78
+ connections: Arc<Vec<RwLock<HashMap<String, SendRequest<BoxBody<Bytes, std::io::Error>>>>>>,
79
+ failed_backends: Arc<RwLock<TtlCache<String, u64>>>,
80
+ }
81
+
82
+ impl ReverseProxyModule {
83
+ #[allow(clippy::type_complexity)]
84
+ fn new(
85
+ roots: Arc<RootCertStore>,
86
+ connections: Arc<Vec<RwLock<HashMap<String, SendRequest<BoxBody<Bytes, std::io::Error>>>>>>,
87
+ failed_backends: Arc<RwLock<TtlCache<String, u64>>>,
88
+ ) -> Self {
89
+ Self {
90
+ roots,
91
+ connections,
92
+ failed_backends,
93
+ }
94
+ }
95
+ }
96
+
97
+ impl ServerModule for ReverseProxyModule {
98
+ fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
99
+ Box::new(ReverseProxyModuleHandlers {
100
+ roots: self.roots.clone(),
101
+ connections: self.connections.clone(),
102
+ failed_backends: self.failed_backends.clone(),
103
+ handle,
104
+ })
105
+ }
106
+ }
107
+
108
+ #[allow(clippy::type_complexity)]
109
+ struct ReverseProxyModuleHandlers {
110
+ handle: Handle,
111
+ roots: Arc<RootCertStore>,
112
+ connections: Arc<Vec<RwLock<HashMap<String, SendRequest<BoxBody<Bytes, std::io::Error>>>>>>,
113
+ failed_backends: Arc<RwLock<TtlCache<String, u64>>>,
114
+ }
115
+
116
+ #[async_trait]
117
+ impl ServerModuleHandlers for ReverseProxyModuleHandlers {
118
+ async fn request_handler(
119
+ &mut self,
120
+ request: RequestData,
121
+ config: &ServerConfig,
122
+ socket_data: &SocketData,
123
+ error_logger: &ErrorLogger,
124
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
125
+ WithRuntime::new(self.handle.clone(), async move {
126
+ let enable_health_check = config["enableLoadBalancerHealthCheck"]
127
+ .as_bool()
128
+ .unwrap_or(false);
129
+ let health_check_max_fails = config["loadBalancerHealthCheckMaximumFails"]
130
+ .as_i64()
131
+ .unwrap_or(3) as u64;
132
+ let disable_certificate_verification = config["disableProxyCertificateVerification"]
133
+ .as_bool()
134
+ .unwrap_or(false);
135
+ if let Some(proxy_to) = determine_proxy_to(
136
+ config,
137
+ socket_data.encrypted,
138
+ &self.failed_backends,
139
+ enable_health_check,
140
+ health_check_max_fails,
141
+ )
142
+ .await
143
+ {
144
+ let (hyper_request, _auth_user, _original_url) = request.into_parts();
145
+ let (mut hyper_request_parts, request_body) = hyper_request.into_parts();
146
+
147
+ let proxy_request_url = proxy_to.parse::<hyper::Uri>()?;
148
+ let scheme_str = proxy_request_url.scheme_str();
149
+ let mut encrypted = false;
150
+
151
+ match scheme_str {
152
+ Some("http") => {
153
+ encrypted = false;
154
+ }
155
+ Some("https") => {
156
+ encrypted = true;
157
+ }
158
+ _ => Err(anyhow::anyhow!(
159
+ "Only HTTP and HTTPS reverse proxy URLs are supported."
160
+ ))?,
161
+ };
162
+
163
+ let host = match proxy_request_url.host() {
164
+ Some(host) => host,
165
+ None => Err(anyhow::anyhow!(
166
+ "The reverse proxy URL doesn't include the host"
167
+ ))?,
168
+ };
169
+
170
+ let port = proxy_request_url.port_u16().unwrap_or(match scheme_str {
171
+ Some("http") => 80,
172
+ Some("https") => 443,
173
+ _ => 80,
174
+ });
175
+
176
+ let addr = format!("{}:{}", host, port);
177
+ let authority = proxy_request_url.authority().cloned();
178
+
179
+ let hyper_request_path = hyper_request_parts.uri.path();
180
+
181
+ let path = match hyper_request_path.as_bytes().first() {
182
+ Some(b'/') => {
183
+ let mut proxy_request_path = proxy_request_url.path();
184
+ while proxy_request_path.as_bytes().last().copied() == Some(b'/') {
185
+ proxy_request_path = &proxy_request_path[..(proxy_request_path.len() - 1)];
186
+ }
187
+ format!("{}{}", proxy_request_path, hyper_request_path)
188
+ }
189
+ _ => hyper_request_path.to_string(),
190
+ };
191
+
192
+ hyper_request_parts.uri = Uri::from_str(&format!(
193
+ "{}{}",
194
+ path,
195
+ match hyper_request_parts.uri.query() {
196
+ Some(query) => format!("?{}", query),
197
+ None => "".to_string(),
198
+ }
199
+ ))?;
200
+
201
+ let original_host = hyper_request_parts.headers.get(header::HOST).cloned();
202
+
203
+ // Host header for host identification
204
+ match authority {
205
+ Some(authority) => {
206
+ hyper_request_parts
207
+ .headers
208
+ .insert(header::HOST, authority.to_string().parse()?);
209
+ }
210
+ None => {
211
+ hyper_request_parts.headers.remove(header::HOST);
212
+ }
213
+ }
214
+
215
+ // Connection header to enable HTTP/1.1 keep-alive
216
+ hyper_request_parts
217
+ .headers
218
+ .insert(header::CONNECTION, "keep-alive".parse()?);
219
+
220
+ // X-Forwarded-* headers to send the client's data to a server that's behind the reverse proxy
221
+ hyper_request_parts.headers.insert(
222
+ "x-forwarded-for",
223
+ socket_data
224
+ .remote_addr
225
+ .ip()
226
+ .to_canonical()
227
+ .to_string()
228
+ .parse()?,
229
+ );
230
+
231
+ if socket_data.encrypted {
232
+ hyper_request_parts
233
+ .headers
234
+ .insert("x-forwarded-proto", "https".parse()?);
235
+ } else {
236
+ hyper_request_parts
237
+ .headers
238
+ .insert("x-forwarded-proto", "http".parse()?);
239
+ }
240
+
241
+ if let Some(original_host) = original_host {
242
+ hyper_request_parts
243
+ .headers
244
+ .insert("x-forwarded-host", original_host);
245
+ }
246
+
247
+ let proxy_request = Request::from_parts(hyper_request_parts, request_body);
248
+
249
+ let connections = &self.connections[rand::random_range(..self.connections.len())];
250
+
251
+ let rwlock_read = connections.read().await;
252
+ let sender_read_option = rwlock_read.get(&addr);
253
+
254
+ if let Some(sender_read) = sender_read_option {
255
+ if !sender_read.is_closed() {
256
+ drop(rwlock_read);
257
+ let mut rwlock_write = connections.write().await;
258
+ let sender_option = rwlock_write.get_mut(&addr);
259
+
260
+ if let Some(sender) = sender_option {
261
+ if !sender.is_closed() {
262
+ let result = http_proxy_kept_alive(sender, proxy_request, error_logger).await;
263
+ drop(rwlock_write);
264
+ return result;
265
+ } else {
266
+ drop(rwlock_write);
267
+ }
268
+ } else {
269
+ drop(rwlock_write);
270
+ }
271
+ } else {
272
+ drop(rwlock_read);
273
+ }
274
+ } else {
275
+ drop(rwlock_read);
276
+ }
277
+
278
+ let stream = match TcpStream::connect(&addr).await {
279
+ Ok(stream) => stream,
280
+ Err(err) => {
281
+ if enable_health_check {
282
+ let mut failed_backends_write = self.failed_backends.write().await;
283
+ let proxy_to = proxy_to.clone();
284
+ let failed_attempts = failed_backends_write.get(&proxy_to);
285
+ failed_backends_write.insert(proxy_to, failed_attempts.map_or(1, |x| x + 1));
286
+ }
287
+ match err.kind() {
288
+ tokio::io::ErrorKind::ConnectionRefused
289
+ | tokio::io::ErrorKind::NotFound
290
+ | tokio::io::ErrorKind::HostUnreachable => {
291
+ error_logger
292
+ .log(&format!("Service unavailable: {}", err))
293
+ .await;
294
+ return Ok(
295
+ ResponseData::builder_without_request()
296
+ .status(StatusCode::SERVICE_UNAVAILABLE)
297
+ .build(),
298
+ );
299
+ }
300
+ tokio::io::ErrorKind::TimedOut => {
301
+ error_logger.log(&format!("Gateway timeout: {}", err)).await;
302
+ return Ok(
303
+ ResponseData::builder_without_request()
304
+ .status(StatusCode::GATEWAY_TIMEOUT)
305
+ .build(),
306
+ );
307
+ }
308
+ _ => {
309
+ error_logger.log(&format!("Bad gateway: {}", err)).await;
310
+ return Ok(
311
+ ResponseData::builder_without_request()
312
+ .status(StatusCode::BAD_GATEWAY)
313
+ .build(),
314
+ );
315
+ }
316
+ };
317
+ }
318
+ };
319
+
320
+ match stream.set_nodelay(true) {
321
+ Ok(_) => (),
322
+ Err(err) => {
323
+ if enable_health_check {
324
+ let mut failed_backends_write = self.failed_backends.write().await;
325
+ let proxy_to = proxy_to.clone();
326
+ let failed_attempts = failed_backends_write.get(&proxy_to);
327
+ failed_backends_write.insert(proxy_to, failed_attempts.map_or(1, |x| x + 1));
328
+ }
329
+ error_logger.log(&format!("Bad gateway: {}", err)).await;
330
+ return Ok(
331
+ ResponseData::builder_without_request()
332
+ .status(StatusCode::BAD_GATEWAY)
333
+ .build(),
334
+ );
335
+ }
336
+ };
337
+
338
+ let failed_backends_option_borrowed = if enable_health_check {
339
+ Some(&*self.failed_backends)
340
+ } else {
341
+ None
342
+ };
343
+
344
+ if !encrypted {
345
+ http_proxy(
346
+ connections,
347
+ addr,
348
+ stream,
349
+ proxy_request,
350
+ error_logger,
351
+ proxy_to,
352
+ failed_backends_option_borrowed,
353
+ )
354
+ .await
355
+ } else {
356
+ let tls_client_config = (if disable_certificate_verification {
357
+ rustls::ClientConfig::builder()
358
+ .dangerous()
359
+ .with_custom_certificate_verifier(Arc::new(NoServerVerifier::new()))
360
+ } else {
361
+ rustls::ClientConfig::builder().with_root_certificates(self.roots.clone())
362
+ })
363
+ .with_no_client_auth();
364
+ let connector = TlsConnector::from(Arc::new(tls_client_config));
365
+ let domain = ServerName::try_from(host)?.to_owned();
366
+
367
+ let tls_stream = match connector.connect(domain, stream).await {
368
+ Ok(stream) => stream,
369
+ Err(err) => {
370
+ if enable_health_check {
371
+ let mut failed_backends_write = self.failed_backends.write().await;
372
+ let proxy_to = proxy_to.clone();
373
+ let failed_attempts = failed_backends_write.get(&proxy_to);
374
+ failed_backends_write.insert(proxy_to, failed_attempts.map_or(1, |x| x + 1));
375
+ }
376
+ error_logger.log(&format!("Bad gateway: {}", err)).await;
377
+ return Ok(
378
+ ResponseData::builder_without_request()
379
+ .status(StatusCode::BAD_GATEWAY)
380
+ .build(),
381
+ );
382
+ }
383
+ };
384
+
385
+ http_proxy(
386
+ connections,
387
+ addr,
388
+ tls_stream,
389
+ proxy_request,
390
+ error_logger,
391
+ proxy_to,
392
+ failed_backends_option_borrowed,
393
+ )
394
+ .await
395
+ }
396
+ } else {
397
+ Ok(ResponseData::builder(request).build())
398
+ }
399
+ })
400
+ .await
401
+ }
402
+
403
+ async fn proxy_request_handler(
404
+ &mut self,
405
+ request: RequestData,
406
+ _config: &ServerConfig,
407
+ _socket_data: &SocketData,
408
+ _error_logger: &ErrorLogger,
409
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
410
+ Ok(ResponseData::builder(request).build())
411
+ }
412
+
413
+ async fn response_modifying_handler(
414
+ &mut self,
415
+ response: HyperResponse,
416
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
417
+ Ok(response)
418
+ }
419
+
420
+ async fn proxy_response_modifying_handler(
421
+ &mut self,
422
+ response: HyperResponse,
423
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
424
+ Ok(response)
425
+ }
426
+
427
+ async fn connect_proxy_request_handler(
428
+ &mut self,
429
+ _upgraded_request: HyperUpgraded,
430
+ _connect_address: &str,
431
+ _config: &ServerConfig,
432
+ _socket_data: &SocketData,
433
+ _error_logger: &ErrorLogger,
434
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
435
+ Ok(())
436
+ }
437
+
438
+ fn does_connect_proxy_requests(&mut self) -> bool {
439
+ false
440
+ }
441
+
442
+ async fn websocket_request_handler(
443
+ &mut self,
444
+ websocket: HyperWebsocket,
445
+ uri: &hyper::Uri,
446
+ config: &ServerConfig,
447
+ socket_data: &SocketData,
448
+ error_logger: &ErrorLogger,
449
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
450
+ WithRuntime::new(self.handle.clone(), async move {
451
+ let enable_health_check = config["enableLoadBalancerHealthCheck"]
452
+ .as_bool()
453
+ .unwrap_or(false);
454
+ let health_check_max_fails = config["loadBalancerHealthCheckMaximumFails"]
455
+ .as_i64()
456
+ .unwrap_or(3) as u64;
457
+
458
+ let disable_certificate_verification = config["disableProxyCertificateVerification"]
459
+ .as_bool()
460
+ .unwrap_or(false);
461
+ if let Some(proxy_to) = determine_proxy_to(
462
+ config,
463
+ socket_data.encrypted,
464
+ &self.failed_backends,
465
+ enable_health_check,
466
+ health_check_max_fails,
467
+ )
468
+ .await
469
+ {
470
+ let proxy_request_url = proxy_to.parse::<hyper::Uri>()?;
471
+ let scheme_str = proxy_request_url.scheme_str();
472
+ let mut encrypted = false;
473
+
474
+ match scheme_str {
475
+ Some("http") => {
476
+ encrypted = false;
477
+ }
478
+ Some("https") => {
479
+ encrypted = true;
480
+ }
481
+ _ => Err(anyhow::anyhow!(
482
+ "Only HTTP and HTTPS reverse proxy URLs are supported."
483
+ ))?,
484
+ };
485
+
486
+ let request_path = uri.path();
487
+
488
+ let path = match request_path.as_bytes().first() {
489
+ Some(b'/') => {
490
+ let mut proxy_request_path = proxy_request_url.path();
491
+ while proxy_request_path.as_bytes().last().copied() == Some(b'/') {
492
+ proxy_request_path = &proxy_request_path[..(proxy_request_path.len() - 1)];
493
+ }
494
+ format!("{}{}", proxy_request_path, request_path)
495
+ }
496
+ _ => request_path.to_string(),
497
+ };
498
+
499
+ let mut proxy_request_url_parts = proxy_request_url.into_parts();
500
+ proxy_request_url_parts.scheme = if encrypted {
501
+ Some(Scheme::from_str("wss")?)
502
+ } else {
503
+ Some(Scheme::from_str("ws")?)
504
+ };
505
+ match proxy_request_url_parts.path_and_query {
506
+ Some(path_and_query) => {
507
+ let path_and_query_string = match path_and_query.query() {
508
+ Some(query) => {
509
+ format!("{}?{}", path, query)
510
+ }
511
+ None => path,
512
+ };
513
+ proxy_request_url_parts.path_and_query =
514
+ Some(PathAndQuery::from_str(&path_and_query_string)?);
515
+ }
516
+ None => {
517
+ proxy_request_url_parts.path_and_query = Some(PathAndQuery::from_str(&path)?);
518
+ }
519
+ };
520
+
521
+ let proxy_request_url = hyper::Uri::from_parts(proxy_request_url_parts)?;
522
+
523
+ let connector = if !encrypted {
524
+ Connector::Plain
525
+ } else {
526
+ Connector::Rustls(Arc::new(
527
+ (if disable_certificate_verification {
528
+ rustls::ClientConfig::builder()
529
+ .dangerous()
530
+ .with_custom_certificate_verifier(Arc::new(NoServerVerifier::new()))
531
+ } else {
532
+ rustls::ClientConfig::builder().with_root_certificates(self.roots.clone())
533
+ })
534
+ .with_no_client_auth(),
535
+ ))
536
+ };
537
+
538
+ let client_bi_stream = websocket.await?;
539
+
540
+ let (proxy_bi_stream, _) = match tokio_tungstenite::connect_async_tls_with_config(
541
+ proxy_request_url,
542
+ None,
543
+ true,
544
+ Some(connector),
545
+ )
546
+ .await
547
+ {
548
+ Ok(data) => data,
549
+ Err(err) => {
550
+ error_logger
551
+ .log(&format!("Cannot connect to WebSocket server: {}", err))
552
+ .await;
553
+ return Ok(());
554
+ }
555
+ };
556
+
557
+ let (mut client_sink, mut client_stream) = client_bi_stream.split();
558
+ let (mut proxy_sink, mut proxy_stream) = proxy_bi_stream.split();
559
+
560
+ let client_to_proxy = async {
561
+ while let Some(Ok(value)) = client_stream.next().await {
562
+ if proxy_sink.send(value).await.is_err() {
563
+ break;
564
+ }
565
+ }
566
+ };
567
+
568
+ let proxy_to_client = async {
569
+ while let Some(Ok(value)) = proxy_stream.next().await {
570
+ if client_sink.send(value).await.is_err() {
571
+ break;
572
+ }
573
+ }
574
+ };
575
+
576
+ tokio::pin!(client_to_proxy);
577
+ tokio::pin!(proxy_to_client);
578
+
579
+ let client_to_proxy_first;
580
+ tokio::select! {
581
+ _ = &mut client_to_proxy => {
582
+ client_to_proxy_first = true;
583
+ }
584
+ _ = &mut proxy_to_client => {
585
+ client_to_proxy_first = false;
586
+ }
587
+ }
588
+
589
+ if client_to_proxy_first {
590
+ proxy_to_client.await;
591
+ } else {
592
+ client_to_proxy.await;
593
+ }
594
+ }
595
+
596
+ Ok(())
597
+ })
598
+ .await
599
+ }
600
+
601
+ fn does_websocket_requests(&mut self, config: &ServerConfig, socket_data: &SocketData) -> bool {
602
+ if socket_data.encrypted {
603
+ let secure_proxy_to = &config["secureProxyTo"];
604
+ if secure_proxy_to.as_vec().is_some() || secure_proxy_to.as_str().is_some() {
605
+ return true;
606
+ }
607
+ }
608
+
609
+ let proxy_to = &config["proxyTo"];
610
+ proxy_to.as_vec().is_some() || proxy_to.as_str().is_some()
611
+ }
612
+ }
613
+
614
+ async fn determine_proxy_to(
615
+ config: &ServerConfig,
616
+ encrypted: bool,
617
+ failed_backends: &RwLock<TtlCache<String, u64>>,
618
+ enable_health_check: bool,
619
+ health_check_max_fails: u64,
620
+ ) -> Option<String> {
621
+ let mut proxy_to = None;
622
+ // When the array is supplied with non-string values, the reverse proxy may have undesirable behavior
623
+ // The "proxyTo" and "secureProxyTo" are validated though.
624
+
625
+ if encrypted {
626
+ let secure_proxy_to_yaml = &config["secureProxyTo"];
627
+ if let Some(secure_proxy_to_vector) = secure_proxy_to_yaml.as_vec() {
628
+ if enable_health_check {
629
+ let mut secure_proxy_to_vector = secure_proxy_to_vector.clone();
630
+ loop {
631
+ if !secure_proxy_to_vector.is_empty() {
632
+ let index = rand::random_range(..secure_proxy_to_vector.len());
633
+ if let Some(secure_proxy_to) = secure_proxy_to_vector[index].as_str() {
634
+ proxy_to = Some(secure_proxy_to.to_string());
635
+ let failed_backends_read = failed_backends.read().await;
636
+ let failed_backend_fails =
637
+ match failed_backends_read.get(&secure_proxy_to.to_string()) {
638
+ Some(fails) => fails,
639
+ None => break,
640
+ };
641
+ if failed_backend_fails > health_check_max_fails {
642
+ secure_proxy_to_vector.remove(index);
643
+ } else {
644
+ break;
645
+ }
646
+ }
647
+ } else {
648
+ break;
649
+ }
650
+ }
651
+ } else if !secure_proxy_to_vector.is_empty() {
652
+ if let Some(secure_proxy_to) =
653
+ secure_proxy_to_vector[rand::random_range(..secure_proxy_to_vector.len())].as_str()
654
+ {
655
+ proxy_to = Some(secure_proxy_to.to_string());
656
+ }
657
+ }
658
+ } else if let Some(secure_proxy_to) = secure_proxy_to_yaml.as_str() {
659
+ proxy_to = Some(secure_proxy_to.to_string());
660
+ }
661
+ }
662
+
663
+ if proxy_to.is_none() {
664
+ let proxy_to_yaml = &config["proxyTo"];
665
+ if let Some(proxy_to_vector) = proxy_to_yaml.as_vec() {
666
+ if enable_health_check {
667
+ let mut proxy_to_vector = proxy_to_vector.clone();
668
+ loop {
669
+ if !proxy_to_vector.is_empty() {
670
+ let index = rand::random_range(..proxy_to_vector.len());
671
+ if let Some(proxy_to_str) = proxy_to_vector[index].as_str() {
672
+ proxy_to = Some(proxy_to_str.to_string());
673
+ let failed_backends_read = failed_backends.read().await;
674
+ let failed_backend_fails = match failed_backends_read.get(&proxy_to_str.to_string()) {
675
+ Some(fails) => fails,
676
+ None => break,
677
+ };
678
+ if failed_backend_fails > health_check_max_fails {
679
+ proxy_to_vector.remove(index);
680
+ } else {
681
+ break;
682
+ }
683
+ }
684
+ } else {
685
+ break;
686
+ }
687
+ }
688
+ } else if !proxy_to_vector.is_empty() {
689
+ if let Some(proxy_to_str) =
690
+ proxy_to_vector[rand::random_range(..proxy_to_vector.len())].as_str()
691
+ {
692
+ proxy_to = Some(proxy_to_str.to_string());
693
+ }
694
+ }
695
+ } else if let Some(proxy_to_str) = proxy_to_yaml.as_str() {
696
+ proxy_to = Some(proxy_to_str.to_string());
697
+ }
698
+ }
699
+
700
+ proxy_to
701
+ }
702
+
703
+ async fn http_proxy(
704
+ connections: &RwLock<HashMap<String, SendRequest<BoxBody<Bytes, std::io::Error>>>>,
705
+ connect_addr: String,
706
+ stream: impl AsyncRead + AsyncWrite + Send + Unpin + 'static,
707
+ proxy_request: Request<BoxBody<Bytes, std::io::Error>>,
708
+ error_logger: &ErrorLogger,
709
+ proxy_to: String,
710
+ failed_backends: Option<&tokio::sync::RwLock<TtlCache<std::string::String, u64>>>,
711
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
712
+ let io = TokioIo::new(stream);
713
+
714
+ let (mut sender, conn) = match hyper::client::conn::http1::handshake(io).await {
715
+ Ok(data) => data,
716
+ Err(err) => {
717
+ if let Some(failed_backends) = failed_backends {
718
+ let mut failed_backends_write = failed_backends.write().await;
719
+ let failed_attempts = failed_backends_write.get(&proxy_to);
720
+ failed_backends_write.insert(proxy_to, failed_attempts.map_or(1, |x| x + 1));
721
+ }
722
+ error_logger.log(&format!("Bad gateway: {}", err)).await;
723
+ return Ok(
724
+ ResponseData::builder_without_request()
725
+ .status(StatusCode::BAD_GATEWAY)
726
+ .build(),
727
+ );
728
+ }
729
+ };
730
+
731
+ let send_request = sender.send_request(proxy_request);
732
+
733
+ let mut pinned_conn = Box::pin(conn);
734
+ tokio::pin!(send_request);
735
+
736
+ let response;
737
+
738
+ loop {
739
+ tokio::select! {
740
+ biased;
741
+
742
+ proxy_response = &mut send_request => {
743
+ let proxy_response = match proxy_response {
744
+ Ok(response) => response,
745
+ Err(err) => {
746
+ error_logger.log(&format!("Bad gateway: {}", err)).await;
747
+ return Ok(ResponseData::builder_without_request().status(StatusCode::BAD_GATEWAY).build());
748
+ }
749
+ };
750
+
751
+ response = ResponseData::builder_without_request()
752
+ .response(proxy_response.map(|b| {
753
+ b.map_err(|e| std::io::Error::other(e.to_string()))
754
+ .boxed()
755
+ }))
756
+ .parallel_fn(async move {
757
+ pinned_conn.await.unwrap_or_default();
758
+ })
759
+ .build();
760
+
761
+ break;
762
+ },
763
+ state = &mut pinned_conn => {
764
+ if state.is_err() {
765
+ error_logger.log("Bad gateway: incomplete response").await;
766
+ return Ok(ResponseData::builder_without_request().status(StatusCode::BAD_GATEWAY).build());
767
+ }
768
+ },
769
+ };
770
+ }
771
+
772
+ if !sender.is_closed() {
773
+ let mut rwlock_write = connections.write().await;
774
+ rwlock_write.insert(connect_addr, sender);
775
+ drop(rwlock_write);
776
+ }
777
+
778
+ Ok(response)
779
+ }
780
+
781
+ async fn http_proxy_kept_alive(
782
+ sender: &mut SendRequest<BoxBody<Bytes, std::io::Error>>,
783
+ proxy_request: Request<BoxBody<Bytes, std::io::Error>>,
784
+ error_logger: &ErrorLogger,
785
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
786
+ let proxy_response = match sender.send_request(proxy_request).await {
787
+ Ok(response) => response,
788
+ Err(err) => {
789
+ error_logger.log(&format!("Bad gateway: {}", err)).await;
790
+ return Ok(
791
+ ResponseData::builder_without_request()
792
+ .status(StatusCode::BAD_GATEWAY)
793
+ .build(),
794
+ );
795
+ }
796
+ };
797
+
798
+ let response = ResponseData::builder_without_request()
799
+ .response(proxy_response.map(|b| b.map_err(|e| std::io::Error::other(e.to_string())).boxed()))
800
+ .build();
801
+
802
+ Ok(response)
803
+ }
ferron/src/optional_modules/scgi.rs ADDED
@@ -0,0 +1,673 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // SCGI handler code inspired by SVR.JS's OrangeCircle mod, translated from JavaScript to Rust.
2
+ // Based on the "cgi" module
3
+ use std::env;
4
+ use std::error::Error;
5
+ use std::path::{Path, PathBuf};
6
+
7
+ use crate::ferron_common::{
8
+ ErrorLogger, HyperRequest, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
9
+ ServerModuleHandlers, SocketData,
10
+ };
11
+ use crate::ferron_common::{HyperUpgraded, WithRuntime};
12
+ use async_trait::async_trait;
13
+ use futures_util::TryStreamExt;
14
+ use hashlink::LinkedHashMap;
15
+ use http_body_util::{BodyExt, StreamBody};
16
+ use httparse::EMPTY_HEADER;
17
+ use hyper::body::Frame;
18
+ use hyper::{header, Response, StatusCode};
19
+ use hyper_tungstenite::HyperWebsocket;
20
+ use tokio::fs;
21
+ use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
22
+ use tokio::net::TcpStream;
23
+ use tokio::runtime::Handle;
24
+ use tokio_util::io::{ReaderStream, StreamReader};
25
+
26
+ use crate::ferron_res::server_software::SERVER_SOFTWARE;
27
+ use crate::ferron_util::cgi_response::CgiResponse;
28
+ use crate::ferron_util::copy_move::Copier;
29
+
30
+ pub fn server_module_init(
31
+ _config: &ServerConfig,
32
+ ) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
33
+ Ok(Box::new(ScgiModule::new()))
34
+ }
35
+
36
+ struct ScgiModule;
37
+
38
+ impl ScgiModule {
39
+ fn new() -> Self {
40
+ Self
41
+ }
42
+ }
43
+
44
+ impl ServerModule for ScgiModule {
45
+ fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
46
+ Box::new(ScgiModuleHandlers { handle })
47
+ }
48
+ }
49
+ struct ScgiModuleHandlers {
50
+ handle: Handle,
51
+ }
52
+
53
+ #[async_trait]
54
+ impl ServerModuleHandlers for ScgiModuleHandlers {
55
+ async fn request_handler(
56
+ &mut self,
57
+ request: RequestData,
58
+ config: &ServerConfig,
59
+ socket_data: &SocketData,
60
+ error_logger: &ErrorLogger,
61
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
62
+ WithRuntime::new(self.handle.clone(), async move {
63
+ let mut scgi_to = "tcp://localhost:4000/";
64
+ let scgi_to_yaml = &config["scgiTo"];
65
+ if let Some(scgi_to_obtained) = scgi_to_yaml.as_str() {
66
+ scgi_to = scgi_to_obtained;
67
+ }
68
+
69
+ let mut scgi_path = None;
70
+ if let Some(scgi_path_obtained) = config["scgiPath"].as_str() {
71
+ scgi_path = Some(scgi_path_obtained.to_string());
72
+ }
73
+
74
+ let hyper_request = request.get_hyper_request();
75
+
76
+ let request_path = hyper_request.uri().path();
77
+ let mut request_path_bytes = request_path.bytes();
78
+ if request_path_bytes.len() < 1 || request_path_bytes.nth(0) != Some(b'/') {
79
+ return Ok(
80
+ ResponseData::builder(request)
81
+ .status(StatusCode::BAD_REQUEST)
82
+ .build(),
83
+ );
84
+ }
85
+
86
+ if let Some(scgi_path) = scgi_path {
87
+ let mut canonical_scgi_path: &str = &scgi_path;
88
+ if canonical_scgi_path.bytes().last() == Some(b'/') {
89
+ canonical_scgi_path = &canonical_scgi_path[..(canonical_scgi_path.len() - 1)];
90
+ }
91
+
92
+ let request_path_with_slashes = match request_path == canonical_scgi_path {
93
+ true => format!("{}/", request_path),
94
+ false => request_path.to_string(),
95
+ };
96
+ if let Some(stripped_request_path) =
97
+ request_path_with_slashes.strip_prefix(canonical_scgi_path)
98
+ {
99
+ let wwwroot_yaml = &config["wwwroot"];
100
+ let wwwroot = wwwroot_yaml.as_str().unwrap_or("/nonexistent");
101
+
102
+ let wwwroot_unknown = PathBuf::from(wwwroot);
103
+ let wwwroot_pathbuf = match wwwroot_unknown.as_path().is_absolute() {
104
+ true => wwwroot_unknown,
105
+ false => match fs::canonicalize(&wwwroot_unknown).await {
106
+ Ok(pathbuf) => pathbuf,
107
+ Err(_) => wwwroot_unknown,
108
+ },
109
+ };
110
+ let wwwroot = wwwroot_pathbuf.as_path();
111
+
112
+ let mut relative_path = &request_path[1..];
113
+ while relative_path.as_bytes().first().copied() == Some(b'/') {
114
+ relative_path = &relative_path[1..];
115
+ }
116
+
117
+ let decoded_relative_path = match urlencoding::decode(relative_path) {
118
+ Ok(path) => path.to_string(),
119
+ Err(_) => {
120
+ return Ok(
121
+ ResponseData::builder(request)
122
+ .status(StatusCode::BAD_REQUEST)
123
+ .build(),
124
+ );
125
+ }
126
+ };
127
+
128
+ let joined_pathbuf = wwwroot.join(decoded_relative_path);
129
+ let execute_pathbuf = joined_pathbuf;
130
+ let execute_path_info = stripped_request_path
131
+ .strip_prefix("/")
132
+ .map(|s| s.to_string());
133
+
134
+ return execute_scgi_with_environment_variables(
135
+ request,
136
+ socket_data,
137
+ error_logger,
138
+ wwwroot,
139
+ execute_pathbuf,
140
+ execute_path_info,
141
+ config["serverAdministratorEmail"].as_str(),
142
+ scgi_to,
143
+ )
144
+ .await;
145
+ }
146
+ }
147
+ Ok(ResponseData::builder(request).build())
148
+ })
149
+ .await
150
+ }
151
+
152
+ async fn proxy_request_handler(
153
+ &mut self,
154
+ request: RequestData,
155
+ _config: &ServerConfig,
156
+ _socket_data: &SocketData,
157
+ _error_logger: &ErrorLogger,
158
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
159
+ Ok(ResponseData::builder(request).build())
160
+ }
161
+
162
+ async fn response_modifying_handler(
163
+ &mut self,
164
+ response: HyperResponse,
165
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
166
+ Ok(response)
167
+ }
168
+
169
+ async fn proxy_response_modifying_handler(
170
+ &mut self,
171
+ response: HyperResponse,
172
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
173
+ Ok(response)
174
+ }
175
+
176
+ async fn connect_proxy_request_handler(
177
+ &mut self,
178
+ _upgraded_request: HyperUpgraded,
179
+ _connect_address: &str,
180
+ _config: &ServerConfig,
181
+ _socket_data: &SocketData,
182
+ _error_logger: &ErrorLogger,
183
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
184
+ Ok(())
185
+ }
186
+
187
+ fn does_connect_proxy_requests(&mut self) -> bool {
188
+ false
189
+ }
190
+
191
+ async fn websocket_request_handler(
192
+ &mut self,
193
+ _websocket: HyperWebsocket,
194
+ _uri: &hyper::Uri,
195
+ _config: &ServerConfig,
196
+ _socket_data: &SocketData,
197
+ _error_logger: &ErrorLogger,
198
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
199
+ Ok(())
200
+ }
201
+
202
+ fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
203
+ false
204
+ }
205
+ }
206
+
207
+ #[allow(clippy::too_many_arguments)]
208
+ async fn execute_scgi_with_environment_variables(
209
+ request: RequestData,
210
+ socket_data: &SocketData,
211
+ error_logger: &ErrorLogger,
212
+ wwwroot: &Path,
213
+ execute_pathbuf: PathBuf,
214
+ path_info: Option<String>,
215
+ server_administrator_email: Option<&str>,
216
+ scgi_to: &str,
217
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
218
+ let mut environment_variables: LinkedHashMap<String, String> = LinkedHashMap::new();
219
+
220
+ let hyper_request = request.get_hyper_request();
221
+ let original_request_uri = request.get_original_url().unwrap_or(hyper_request.uri());
222
+
223
+ if let Some(auth_user) = request.get_auth_user() {
224
+ if let Some(authorization) = hyper_request.headers().get(header::AUTHORIZATION) {
225
+ let authorization_value = String::from_utf8_lossy(authorization.as_bytes()).to_string();
226
+ let mut authorization_value_split = authorization_value.split(" ");
227
+ if let Some(authorization_type) = authorization_value_split.next() {
228
+ environment_variables.insert("AUTH_TYPE".to_string(), authorization_type.to_string());
229
+ }
230
+ }
231
+ environment_variables.insert("REMOTE_USER".to_string(), auth_user.to_string());
232
+ }
233
+
234
+ environment_variables.insert(
235
+ "QUERY_STRING".to_string(),
236
+ match hyper_request.uri().query() {
237
+ Some(query) => query.to_string(),
238
+ None => "".to_string(),
239
+ },
240
+ );
241
+
242
+ environment_variables.insert("SERVER_SOFTWARE".to_string(), SERVER_SOFTWARE.to_string());
243
+ environment_variables.insert(
244
+ "SERVER_PROTOCOL".to_string(),
245
+ match hyper_request.version() {
246
+ hyper::Version::HTTP_09 => "HTTP/0.9".to_string(),
247
+ hyper::Version::HTTP_10 => "HTTP/1.0".to_string(),
248
+ hyper::Version::HTTP_11 => "HTTP/1.1".to_string(),
249
+ hyper::Version::HTTP_2 => "HTTP/2.0".to_string(),
250
+ hyper::Version::HTTP_3 => "HTTP/3.0".to_string(),
251
+ _ => "HTTP/Unknown".to_string(),
252
+ },
253
+ );
254
+ environment_variables.insert(
255
+ "SERVER_PORT".to_string(),
256
+ socket_data.local_addr.port().to_string(),
257
+ );
258
+ environment_variables.insert(
259
+ "SERVER_ADDR".to_string(),
260
+ socket_data.local_addr.ip().to_canonical().to_string(),
261
+ );
262
+ if let Some(server_administrator_email) = server_administrator_email {
263
+ environment_variables.insert(
264
+ "SERVER_ADMIN".to_string(),
265
+ server_administrator_email.to_string(),
266
+ );
267
+ }
268
+ if let Some(host) = hyper_request.headers().get(header::HOST) {
269
+ environment_variables.insert(
270
+ "SERVER_NAME".to_string(),
271
+ String::from_utf8_lossy(host.as_bytes()).to_string(),
272
+ );
273
+ }
274
+
275
+ environment_variables.insert(
276
+ "DOCUMENT_ROOT".to_string(),
277
+ wwwroot.to_string_lossy().to_string(),
278
+ );
279
+ environment_variables.insert(
280
+ "PATH_INFO".to_string(),
281
+ match &path_info {
282
+ Some(path_info) => format!("/{}", path_info),
283
+ None => "".to_string(),
284
+ },
285
+ );
286
+ environment_variables.insert(
287
+ "PATH_TRANSLATED".to_string(),
288
+ match &path_info {
289
+ Some(path_info) => {
290
+ let mut path_translated = execute_pathbuf.clone();
291
+ path_translated.push(path_info);
292
+ path_translated.to_string_lossy().to_string()
293
+ }
294
+ None => "".to_string(),
295
+ },
296
+ );
297
+ environment_variables.insert(
298
+ "REQUEST_METHOD".to_string(),
299
+ hyper_request.method().to_string(),
300
+ );
301
+ environment_variables.insert("GATEWAY_INTERFACE".to_string(), "CGI/1.1".to_string());
302
+ environment_variables.insert("SCGI".to_string(), "1".to_string());
303
+ environment_variables.insert(
304
+ "REQUEST_URI".to_string(),
305
+ format!(
306
+ "{}{}",
307
+ original_request_uri.path(),
308
+ match original_request_uri.query() {
309
+ Some(query) => format!("?{}", query),
310
+ None => String::from(""),
311
+ }
312
+ ),
313
+ );
314
+
315
+ environment_variables.insert(
316
+ "REMOTE_PORT".to_string(),
317
+ socket_data.remote_addr.port().to_string(),
318
+ );
319
+ environment_variables.insert(
320
+ "REMOTE_ADDR".to_string(),
321
+ socket_data.remote_addr.ip().to_canonical().to_string(),
322
+ );
323
+
324
+ environment_variables.insert(
325
+ "SCRIPT_FILENAME".to_string(),
326
+ execute_pathbuf.to_string_lossy().to_string(),
327
+ );
328
+ if let Ok(script_path) = execute_pathbuf.as_path().strip_prefix(wwwroot) {
329
+ environment_variables.insert(
330
+ "SCRIPT_NAME".to_string(),
331
+ format!(
332
+ "/{}",
333
+ match cfg!(windows) {
334
+ true => script_path.to_string_lossy().to_string().replace("\\", "/"),
335
+ false => script_path.to_string_lossy().to_string(),
336
+ }
337
+ ),
338
+ );
339
+ }
340
+
341
+ if socket_data.encrypted {
342
+ environment_variables.insert("HTTPS".to_string(), "ON".to_string());
343
+ }
344
+
345
+ let mut content_length_set = false;
346
+ for (header_name, header_value) in hyper_request.headers().iter() {
347
+ let env_header_name = match *header_name {
348
+ header::CONTENT_LENGTH => {
349
+ content_length_set = true;
350
+ "CONTENT_LENGTH".to_string()
351
+ }
352
+ header::CONTENT_TYPE => "CONTENT_TYPE".to_string(),
353
+ _ => {
354
+ let mut result = String::new();
355
+
356
+ result.push_str("HTTP_");
357
+
358
+ for c in header_name.as_str().to_uppercase().chars() {
359
+ if c.is_alphanumeric() {
360
+ result.push(c);
361
+ } else {
362
+ result.push('_');
363
+ }
364
+ }
365
+
366
+ result
367
+ }
368
+ };
369
+ if environment_variables.contains_key(&env_header_name) {
370
+ let value = environment_variables.get_mut(&env_header_name);
371
+ if let Some(value) = value {
372
+ if env_header_name == "HTTP_COOKIE" {
373
+ value.push_str("; ");
374
+ } else {
375
+ // See https://stackoverflow.com/a/1801191
376
+ value.push_str(", ");
377
+ }
378
+ value.push_str(String::from_utf8_lossy(header_value.as_bytes()).as_ref());
379
+ } else {
380
+ environment_variables.insert(
381
+ env_header_name,
382
+ String::from_utf8_lossy(header_value.as_bytes()).to_string(),
383
+ );
384
+ }
385
+ } else {
386
+ environment_variables.insert(
387
+ env_header_name,
388
+ String::from_utf8_lossy(header_value.as_bytes()).to_string(),
389
+ );
390
+ }
391
+ }
392
+
393
+ if !content_length_set {
394
+ environment_variables.insert("CONTENT_LENGTH".to_string(), "0".to_string());
395
+ }
396
+
397
+ let (hyper_request, _, _) = request.into_parts();
398
+
399
+ execute_scgi(hyper_request, error_logger, scgi_to, environment_variables).await
400
+ }
401
+
402
+ async fn execute_scgi(
403
+ hyper_request: HyperRequest,
404
+ error_logger: &ErrorLogger,
405
+ scgi_to: &str,
406
+ mut environment_variables: LinkedHashMap<String, String>,
407
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
408
+ let (_, body) = hyper_request.into_parts();
409
+
410
+ // Insert other environment variables
411
+ for (key, value) in env::vars_os() {
412
+ let key_string = key.to_string_lossy().to_string();
413
+ let value_string = value.to_string_lossy().to_string();
414
+ environment_variables
415
+ .entry(key_string)
416
+ .or_insert(value_string);
417
+ }
418
+
419
+ let scgi_to_fixed = if let Some(stripped) = scgi_to.strip_prefix("unix:///") {
420
+ // hyper::Uri fails to parse a string if there is an empty authority, so add an "ignore" authority to Unix socket URLs
421
+ &format!("unix://ignore/{}", stripped)
422
+ } else {
423
+ scgi_to
424
+ };
425
+
426
+ let scgi_to_url = scgi_to_fixed.parse::<hyper::Uri>()?;
427
+ let scheme_str = scgi_to_url.scheme_str();
428
+
429
+ let (socket_reader, mut socket_writer) = match scheme_str {
430
+ Some("tcp") => {
431
+ let host = match scgi_to_url.host() {
432
+ Some(host) => host,
433
+ None => Err(anyhow::anyhow!("The SCGI URL doesn't include the host"))?,
434
+ };
435
+
436
+ let port = match scgi_to_url.port_u16() {
437
+ Some(port) => port,
438
+ None => Err(anyhow::anyhow!("The SCGI URL doesn't include the port"))?,
439
+ };
440
+
441
+ let addr = format!("{}:{}", host, port);
442
+
443
+ match connect_tcp(&addr).await {
444
+ Ok(data) => data,
445
+ Err(err) => match err.kind() {
446
+ tokio::io::ErrorKind::ConnectionRefused
447
+ | tokio::io::ErrorKind::NotFound
448
+ | tokio::io::ErrorKind::HostUnreachable => {
449
+ error_logger
450
+ .log(&format!("Service unavailable: {}", err))
451
+ .await;
452
+ return Ok(
453
+ ResponseData::builder_without_request()
454
+ .status(StatusCode::SERVICE_UNAVAILABLE)
455
+ .build(),
456
+ );
457
+ }
458
+ _ => Err(err)?,
459
+ },
460
+ }
461
+ }
462
+ Some("unix") => {
463
+ let path = scgi_to_url.path();
464
+ match connect_unix(path).await {
465
+ Ok(data) => data,
466
+ Err(err) => match err.kind() {
467
+ tokio::io::ErrorKind::ConnectionRefused
468
+ | tokio::io::ErrorKind::NotFound
469
+ | tokio::io::ErrorKind::HostUnreachable => {
470
+ error_logger
471
+ .log(&format!("Service unavailable: {}", err))
472
+ .await;
473
+ return Ok(
474
+ ResponseData::builder_without_request()
475
+ .status(StatusCode::SERVICE_UNAVAILABLE)
476
+ .build(),
477
+ );
478
+ }
479
+ _ => Err(err)?,
480
+ },
481
+ }
482
+ }
483
+ _ => Err(anyhow::anyhow!(
484
+ "Only HTTP and HTTPS reverse proxy URLs are supported."
485
+ ))?,
486
+ };
487
+
488
+ // Create environment variable netstring
489
+ let mut environment_variables_to_wrap = Vec::new();
490
+ for (key, value) in environment_variables.iter() {
491
+ let mut environment_variable = Vec::new();
492
+ environment_variable.extend_from_slice(key.as_bytes());
493
+ environment_variable.push(b'\0');
494
+ environment_variable.extend_from_slice(value.as_bytes());
495
+ environment_variable.push(b'\0');
496
+ if key == "CONTENT_LENGTH" {
497
+ environment_variable.append(&mut environment_variables_to_wrap);
498
+ environment_variables_to_wrap = environment_variable;
499
+ } else {
500
+ environment_variables_to_wrap.append(&mut environment_variable);
501
+ }
502
+ }
503
+
504
+ let environment_variables_to_wrap_length = environment_variables_to_wrap.len();
505
+ let mut environment_variables_netstring = Vec::new();
506
+ environment_variables_netstring
507
+ .extend_from_slice(environment_variables_to_wrap_length.to_string().as_bytes());
508
+ environment_variables_netstring.push(b':');
509
+ environment_variables_netstring.append(&mut environment_variables_to_wrap);
510
+ environment_variables_netstring.push(b',');
511
+
512
+ // Write environment variable netstring
513
+ socket_writer
514
+ .write_all(&environment_variables_netstring)
515
+ .await?;
516
+
517
+ let cgi_stdin_reader = StreamReader::new(body.into_data_stream().map_err(std::io::Error::other));
518
+
519
+ // Emulated standard input and standard output
520
+ // SCGI doesn't support standard error
521
+ let stdin = socket_writer;
522
+ let stdout = socket_reader;
523
+
524
+ let mut cgi_response = CgiResponse::new(stdout);
525
+
526
+ let stdin_copy_future = Copier::new(cgi_stdin_reader, stdin).copy();
527
+ let mut stdin_copy_future_pinned = Box::pin(stdin_copy_future);
528
+
529
+ let mut headers = [EMPTY_HEADER; 128];
530
+
531
+ let mut early_stdin_copied = false;
532
+
533
+ // Needed to wrap this in another scope to prevent errors with multiple mutable borrows.
534
+ {
535
+ let mut head_obtained = false;
536
+ let stdout_parse_future = cgi_response.get_head();
537
+ tokio::pin!(stdout_parse_future);
538
+
539
+ // Cannot use a loop with tokio::select, since stdin_copy_future_pinned being constantly ready will make the web server stop responding to HTTP requests
540
+ tokio::select! {
541
+ biased;
542
+
543
+ obtained_head = &mut stdout_parse_future => {
544
+ let obtained_head = obtained_head?;
545
+ if !obtained_head.is_empty() {
546
+ httparse::parse_headers(obtained_head, &mut headers)?;
547
+ }
548
+ head_obtained = true;
549
+ },
550
+ result = &mut stdin_copy_future_pinned => {
551
+ early_stdin_copied = true;
552
+ result?;
553
+ }
554
+ }
555
+
556
+ if !head_obtained {
557
+ // Kept it same as in the tokio::select macro
558
+ let obtained_head = stdout_parse_future.await?;
559
+ if !obtained_head.is_empty() {
560
+ httparse::parse_headers(obtained_head, &mut headers)?;
561
+ }
562
+ }
563
+ }
564
+
565
+ let mut response_builder = Response::builder();
566
+ let mut status_code = 200;
567
+ for header in headers {
568
+ if header == EMPTY_HEADER {
569
+ break;
570
+ }
571
+ let mut is_status_header = false;
572
+ match &header.name.to_lowercase() as &str {
573
+ "location" => {
574
+ if !(300..=399).contains(&status_code) {
575
+ status_code = 302;
576
+ }
577
+ }
578
+ "status" => {
579
+ is_status_header = true;
580
+ let header_value_cow = String::from_utf8_lossy(header.value);
581
+ let mut split_status = header_value_cow.split(" ");
582
+ let first_part = split_status.next();
583
+ if let Some(first_part) = first_part {
584
+ if first_part.starts_with("HTTP/") {
585
+ let second_part = split_status.next();
586
+ if let Some(second_part) = second_part {
587
+ if let Ok(parsed_status_code) = second_part.parse::<u16>() {
588
+ status_code = parsed_status_code;
589
+ }
590
+ }
591
+ } else if let Ok(parsed_status_code) = first_part.parse::<u16>() {
592
+ status_code = parsed_status_code;
593
+ }
594
+ }
595
+ }
596
+ _ => (),
597
+ }
598
+ if !is_status_header {
599
+ response_builder = response_builder.header(header.name, header.value);
600
+ }
601
+ }
602
+
603
+ response_builder = response_builder.status(status_code);
604
+
605
+ let reader_stream = ReaderStream::new(cgi_response);
606
+ let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
607
+ let boxed_body = stream_body.boxed();
608
+
609
+ let response = response_builder.body(boxed_body)?;
610
+
611
+ Ok(
612
+ ResponseData::builder_without_request()
613
+ .response(response)
614
+ .parallel_fn(async move {
615
+ if !early_stdin_copied {
616
+ stdin_copy_future_pinned.await.unwrap_or_default();
617
+ }
618
+ })
619
+ .build(),
620
+ )
621
+ }
622
+
623
+ async fn connect_tcp(
624
+ addr: &str,
625
+ ) -> Result<
626
+ (
627
+ Box<dyn AsyncRead + Send + Sync + Unpin>,
628
+ Box<dyn AsyncWrite + Send + Sync + Unpin>,
629
+ ),
630
+ tokio::io::Error,
631
+ > {
632
+ let socket = TcpStream::connect(addr).await?;
633
+ socket.set_nodelay(true)?;
634
+
635
+ let (socket_reader_set, socket_writer_set) = tokio::io::split(socket);
636
+ Ok((Box::new(socket_reader_set), Box::new(socket_writer_set)))
637
+ }
638
+
639
+ #[allow(dead_code)]
640
+ #[cfg(unix)]
641
+ async fn connect_unix(
642
+ path: &str,
643
+ ) -> Result<
644
+ (
645
+ Box<dyn AsyncRead + Send + Sync + Unpin>,
646
+ Box<dyn AsyncWrite + Send + Sync + Unpin>,
647
+ ),
648
+ tokio::io::Error,
649
+ > {
650
+ use tokio::net::UnixStream;
651
+
652
+ let socket = UnixStream::connect(path).await?;
653
+
654
+ let (socket_reader_set, socket_writer_set) = tokio::io::split(socket);
655
+ Ok((Box::new(socket_reader_set), Box::new(socket_writer_set)))
656
+ }
657
+
658
+ #[allow(dead_code)]
659
+ #[cfg(not(unix))]
660
+ async fn connect_unix(
661
+ _path: &str,
662
+ ) -> Result<
663
+ (
664
+ Box<dyn AsyncRead + Send + Sync + Unpin>,
665
+ Box<dyn AsyncWrite + Send + Sync + Unpin>,
666
+ ),
667
+ tokio::io::Error,
668
+ > {
669
+ Err(tokio::io::Error::new(
670
+ tokio::io::ErrorKind::Unsupported,
671
+ "Unix sockets are not supports on non-Unix platforms.",
672
+ ))
673
+ }
ferron/src/optional_modules/wsgi.rs ADDED
@@ -0,0 +1,742 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // This module would provide higher WSGI application performance,
2
+ // if it used a process pool dedicated for WSGI applications (aka pre-fork model)
3
+ // instead of spawning blocking threads in a Tokio runtime,
4
+ // because of Python's GIL, which causes the WSGI application in current setup
5
+ // to effectively run as single-threaded single-process.
6
+ // Pre-forking a process pool isn't supported on Windows.
7
+
8
+ use std::collections::HashMap;
9
+ use std::error::Error;
10
+ use std::path::{Path, PathBuf};
11
+ use std::str::FromStr;
12
+ use std::sync::Arc;
13
+
14
+ use crate::ferron_common::{
15
+ ErrorLogger, HyperRequest, HyperUpgraded, RequestData, ResponseData, ServerConfig, ServerModule,
16
+ ServerModuleHandlers, SocketData,
17
+ };
18
+ use crate::ferron_common::{HyperResponse, WithRuntime};
19
+ use crate::ferron_res::server_software::SERVER_SOFTWARE;
20
+ use crate::ferron_util::ip_match::ip_match;
21
+ use crate::ferron_util::match_hostname::match_hostname;
22
+ use crate::ferron_util::match_location::match_location;
23
+ use crate::ferron_util::wsgi_error_stream::WsgiErrorStream;
24
+ use crate::ferron_util::wsgi_input_stream::WsgiInputStream;
25
+ use crate::ferron_util::wsgi_load_application::load_wsgi_application;
26
+ use crate::ferron_util::wsgi_structs::{WsgiApplicationLocationWrap, WsgiApplicationWrap};
27
+ use async_trait::async_trait;
28
+ use futures_util::{StreamExt, TryStreamExt};
29
+ use hashlink::LinkedHashMap;
30
+ use http::{HeaderMap, HeaderName, HeaderValue, StatusCode};
31
+ use http_body_util::{BodyExt, Empty, StreamBody};
32
+ use hyper::body::{Bytes, Frame};
33
+ use hyper::header;
34
+ use hyper::Response;
35
+ use hyper_tungstenite::HyperWebsocket;
36
+ use pyo3::exceptions::{PyAssertionError, PyException};
37
+ use pyo3::prelude::*;
38
+ use pyo3::types::{PyAny, PyBool, PyCFunction, PyDict, PyIterator, PyString, PyTuple};
39
+ use tokio::fs;
40
+ use tokio::io::AsyncReadExt;
41
+ use tokio::runtime::Handle;
42
+ use tokio::sync::Mutex;
43
+ use tokio_util::io::StreamReader;
44
+
45
+ pub fn server_module_init(
46
+ config: &ServerConfig,
47
+ ) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
48
+ let mut global_wsgi_application = None;
49
+ let mut host_wsgi_applications = Vec::new();
50
+ let clear_sys_path = config["global"]["wsgiClearModuleImportPath"]
51
+ .as_bool()
52
+ .unwrap_or(false);
53
+ if let Some(wsgi_application_path) = config["global"]["wsgiApplicationPath"].as_str() {
54
+ global_wsgi_application = Some(Arc::new(load_wsgi_application(
55
+ PathBuf::from_str(wsgi_application_path)?.as_path(),
56
+ clear_sys_path,
57
+ )?));
58
+ }
59
+ let global_wsgi_path = config["global"]["wsgiPath"].as_str().map(|s| s.to_string());
60
+
61
+ if let Some(hosts) = config["hosts"].as_vec() {
62
+ for host_yaml in hosts.iter() {
63
+ let domain = host_yaml["domain"].as_str().map(String::from);
64
+ let ip = host_yaml["ip"].as_str().map(String::from);
65
+ let mut locations = Vec::new();
66
+ if let Some(locations_yaml) = host_yaml["locations"].as_vec() {
67
+ for location_yaml in locations_yaml.iter() {
68
+ if let Some(path_str) = location_yaml["path"].as_str() {
69
+ let path = String::from(path_str);
70
+ if let Some(wsgi_application_path) = location_yaml["wsgiApplicationPath"].as_str() {
71
+ locations.push(WsgiApplicationLocationWrap::new(
72
+ path,
73
+ Arc::new(load_wsgi_application(
74
+ PathBuf::from_str(wsgi_application_path)?.as_path(),
75
+ clear_sys_path,
76
+ )?),
77
+ location_yaml["wsgiPath"].as_str().map(|s| s.to_string()),
78
+ ));
79
+ }
80
+ }
81
+ }
82
+ }
83
+ if let Some(wsgi_application_path) = host_yaml["wsgiApplicationPath"].as_str() {
84
+ host_wsgi_applications.push(WsgiApplicationWrap::new(
85
+ domain,
86
+ ip,
87
+ Some(Arc::new(load_wsgi_application(
88
+ PathBuf::from_str(wsgi_application_path)?.as_path(),
89
+ clear_sys_path,
90
+ )?)),
91
+ host_yaml["wsgiPath"].as_str().map(|s| s.to_string()),
92
+ locations,
93
+ ));
94
+ } else if !locations.is_empty() {
95
+ host_wsgi_applications.push(WsgiApplicationWrap::new(
96
+ domain,
97
+ ip,
98
+ None,
99
+ host_yaml["wsgiPath"].as_str().map(|s| s.to_string()),
100
+ locations,
101
+ ));
102
+ }
103
+ }
104
+ }
105
+
106
+ Ok(Box::new(WsgiModule::new(
107
+ global_wsgi_application,
108
+ global_wsgi_path,
109
+ Arc::new(host_wsgi_applications),
110
+ )))
111
+ }
112
+
113
+ struct WsgiModule {
114
+ global_wsgi_application: Option<Arc<Py<PyAny>>>,
115
+ global_wsgi_path: Option<String>,
116
+ host_wsgi_applications: Arc<Vec<WsgiApplicationWrap>>,
117
+ }
118
+
119
+ impl WsgiModule {
120
+ fn new(
121
+ global_wsgi_application: Option<Arc<Py<PyAny>>>,
122
+ global_wsgi_path: Option<String>,
123
+ host_wsgi_applications: Arc<Vec<WsgiApplicationWrap>>,
124
+ ) -> Self {
125
+ Self {
126
+ global_wsgi_application,
127
+ global_wsgi_path,
128
+ host_wsgi_applications,
129
+ }
130
+ }
131
+ }
132
+
133
+ impl ServerModule for WsgiModule {
134
+ fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
135
+ Box::new(WsgiModuleHandlers {
136
+ handle,
137
+ global_wsgi_application: self.global_wsgi_application.clone(),
138
+ global_wsgi_path: self.global_wsgi_path.clone(),
139
+ host_wsgi_applications: self.host_wsgi_applications.clone(),
140
+ })
141
+ }
142
+ }
143
+
144
+ struct WsgiModuleHandlers {
145
+ handle: Handle,
146
+ global_wsgi_application: Option<Arc<Py<PyAny>>>,
147
+ global_wsgi_path: Option<String>,
148
+ host_wsgi_applications: Arc<Vec<WsgiApplicationWrap>>,
149
+ }
150
+
151
+ #[async_trait]
152
+ impl ServerModuleHandlers for WsgiModuleHandlers {
153
+ async fn request_handler(
154
+ &mut self,
155
+ request: RequestData,
156
+ config: &ServerConfig,
157
+ socket_data: &SocketData,
158
+ error_logger: &ErrorLogger,
159
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
160
+ WithRuntime::new(self.handle.clone(), async move {
161
+ let hyper_request = request.get_hyper_request();
162
+
163
+ // Use .take() instead of .clone(), since the values in Options will only be used once.
164
+ let mut wsgi_application = self.global_wsgi_application.take();
165
+ let mut wsgi_path = self.global_wsgi_path.take();
166
+
167
+ // Should have used a HashMap instead of iterating over an array for better performance...
168
+ for host_wsgi_application_wrap in self.host_wsgi_applications.iter() {
169
+ if match_hostname(
170
+ match &host_wsgi_application_wrap.domain {
171
+ Some(value) => Some(value as &str),
172
+ None => None,
173
+ },
174
+ match hyper_request.headers().get(header::HOST) {
175
+ Some(value) => value.to_str().ok(),
176
+ None => None,
177
+ },
178
+ ) && match &host_wsgi_application_wrap.ip {
179
+ Some(value) => ip_match(value as &str, socket_data.remote_addr.ip()),
180
+ None => true,
181
+ } {
182
+ wsgi_application = host_wsgi_application_wrap.wsgi_application.clone();
183
+ wsgi_path = host_wsgi_application_wrap.wsgi_path.clone();
184
+ if let Ok(path_decoded) = urlencoding::decode(
185
+ request
186
+ .get_original_url()
187
+ .unwrap_or(request.get_hyper_request().uri())
188
+ .path(),
189
+ ) {
190
+ for location_wrap in host_wsgi_application_wrap.locations.iter() {
191
+ if match_location(&location_wrap.path, &path_decoded) {
192
+ wsgi_application = Some(location_wrap.wsgi_application.clone());
193
+ wsgi_path = location_wrap.wsgi_path.clone();
194
+ break;
195
+ }
196
+ }
197
+ }
198
+ break;
199
+ }
200
+ }
201
+
202
+ let request_path = hyper_request.uri().path();
203
+ let mut request_path_bytes = request_path.bytes();
204
+ if request_path_bytes.len() < 1 || request_path_bytes.nth(0) != Some(b'/') {
205
+ return Ok(
206
+ ResponseData::builder(request)
207
+ .status(StatusCode::BAD_REQUEST)
208
+ .build(),
209
+ );
210
+ }
211
+
212
+ if let Some(wsgi_application) = wsgi_application {
213
+ let wsgi_path = wsgi_path.unwrap_or("/".to_string());
214
+ let mut canonical_wsgi_path: &str = &wsgi_path;
215
+ if canonical_wsgi_path.bytes().last() == Some(b'/') {
216
+ canonical_wsgi_path = &canonical_wsgi_path[..(canonical_wsgi_path.len() - 1)];
217
+ }
218
+
219
+ let request_path_with_slashes = match request_path == canonical_wsgi_path {
220
+ true => format!("{}/", request_path),
221
+ false => request_path.to_string(),
222
+ };
223
+ if let Some(stripped_request_path) =
224
+ request_path_with_slashes.strip_prefix(canonical_wsgi_path)
225
+ {
226
+ let wwwroot_yaml = &config["wwwroot"];
227
+ let wwwroot = wwwroot_yaml.as_str().unwrap_or("/nonexistent");
228
+
229
+ let wwwroot_unknown = PathBuf::from(wwwroot);
230
+ let wwwroot_pathbuf = match wwwroot_unknown.as_path().is_absolute() {
231
+ true => wwwroot_unknown,
232
+ false => match fs::canonicalize(&wwwroot_unknown).await {
233
+ Ok(pathbuf) => pathbuf,
234
+ Err(_) => wwwroot_unknown,
235
+ },
236
+ };
237
+ let wwwroot = wwwroot_pathbuf.as_path();
238
+
239
+ let mut relative_path = &request_path[1..];
240
+ while relative_path.as_bytes().first().copied() == Some(b'/') {
241
+ relative_path = &relative_path[1..];
242
+ }
243
+
244
+ let decoded_relative_path = match urlencoding::decode(relative_path) {
245
+ Ok(path) => path.to_string(),
246
+ Err(_) => {
247
+ return Ok(
248
+ ResponseData::builder(request)
249
+ .status(StatusCode::BAD_REQUEST)
250
+ .build(),
251
+ );
252
+ }
253
+ };
254
+
255
+ let joined_pathbuf = wwwroot.join(decoded_relative_path);
256
+ let execute_pathbuf = joined_pathbuf;
257
+ let execute_path_info = stripped_request_path
258
+ .strip_prefix("/")
259
+ .map(|s| s.to_string());
260
+
261
+ return execute_wsgi_with_environment_variables(
262
+ request,
263
+ socket_data,
264
+ error_logger,
265
+ wwwroot,
266
+ execute_pathbuf,
267
+ execute_path_info,
268
+ config["serverAdministratorEmail"].as_str(),
269
+ wsgi_application,
270
+ )
271
+ .await;
272
+ }
273
+ }
274
+ Ok(ResponseData::builder(request).build())
275
+ })
276
+ .await
277
+ }
278
+
279
+ async fn proxy_request_handler(
280
+ &mut self,
281
+ request: RequestData,
282
+ _config: &ServerConfig,
283
+ _socket_data: &SocketData,
284
+ _error_logger: &ErrorLogger,
285
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
286
+ Ok(ResponseData::builder(request).build())
287
+ }
288
+
289
+ async fn response_modifying_handler(
290
+ &mut self,
291
+ response: HyperResponse,
292
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
293
+ Ok(response)
294
+ }
295
+
296
+ async fn proxy_response_modifying_handler(
297
+ &mut self,
298
+ response: HyperResponse,
299
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
300
+ Ok(response)
301
+ }
302
+
303
+ async fn connect_proxy_request_handler(
304
+ &mut self,
305
+ _upgraded_request: HyperUpgraded,
306
+ _connect_address: &str,
307
+ _config: &ServerConfig,
308
+ _socket_data: &SocketData,
309
+ _error_logger: &ErrorLogger,
310
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
311
+ Ok(())
312
+ }
313
+
314
+ fn does_connect_proxy_requests(&mut self) -> bool {
315
+ false
316
+ }
317
+
318
+ async fn websocket_request_handler(
319
+ &mut self,
320
+ _websocket: HyperWebsocket,
321
+ _uri: &hyper::Uri,
322
+ _config: &ServerConfig,
323
+ _socket_data: &SocketData,
324
+ _error_logger: &ErrorLogger,
325
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
326
+ Ok(())
327
+ }
328
+
329
+ fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
330
+ false
331
+ }
332
+ }
333
+
334
+ struct ResponseHead {
335
+ status: StatusCode,
336
+ headers: Option<HeaderMap>,
337
+ is_set: bool,
338
+ is_sent: bool,
339
+ }
340
+
341
+ impl ResponseHead {
342
+ fn new() -> Self {
343
+ Self {
344
+ status: StatusCode::OK,
345
+ headers: None,
346
+ is_set: false,
347
+ is_sent: false,
348
+ }
349
+ }
350
+ }
351
+
352
+ #[allow(clippy::too_many_arguments)]
353
+ async fn execute_wsgi_with_environment_variables(
354
+ request: RequestData,
355
+ socket_data: &SocketData,
356
+ error_logger: &ErrorLogger,
357
+ wwwroot: &Path,
358
+ execute_pathbuf: PathBuf,
359
+ path_info: Option<String>,
360
+ server_administrator_email: Option<&str>,
361
+ wsgi_application: Arc<Py<PyAny>>,
362
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
363
+ let mut environment_variables: LinkedHashMap<String, String> = LinkedHashMap::new();
364
+
365
+ let hyper_request = request.get_hyper_request();
366
+ let original_request_uri = request.get_original_url().unwrap_or(hyper_request.uri());
367
+
368
+ if let Some(auth_user) = request.get_auth_user() {
369
+ if let Some(authorization) = hyper_request.headers().get(header::AUTHORIZATION) {
370
+ let authorization_value = String::from_utf8_lossy(authorization.as_bytes()).to_string();
371
+ let mut authorization_value_split = authorization_value.split(" ");
372
+ if let Some(authorization_type) = authorization_value_split.next() {
373
+ environment_variables.insert("AUTH_TYPE".to_string(), authorization_type.to_string());
374
+ }
375
+ }
376
+ environment_variables.insert("REMOTE_USER".to_string(), auth_user.to_string());
377
+ }
378
+
379
+ environment_variables.insert(
380
+ "QUERY_STRING".to_string(),
381
+ match hyper_request.uri().query() {
382
+ Some(query) => query.to_string(),
383
+ None => "".to_string(),
384
+ },
385
+ );
386
+
387
+ environment_variables.insert("SERVER_SOFTWARE".to_string(), SERVER_SOFTWARE.to_string());
388
+ environment_variables.insert(
389
+ "SERVER_PROTOCOL".to_string(),
390
+ match hyper_request.version() {
391
+ hyper::Version::HTTP_09 => "HTTP/0.9".to_string(),
392
+ hyper::Version::HTTP_10 => "HTTP/1.0".to_string(),
393
+ hyper::Version::HTTP_11 => "HTTP/1.1".to_string(),
394
+ hyper::Version::HTTP_2 => "HTTP/2.0".to_string(),
395
+ hyper::Version::HTTP_3 => "HTTP/3.0".to_string(),
396
+ _ => "HTTP/Unknown".to_string(),
397
+ },
398
+ );
399
+ environment_variables.insert(
400
+ "SERVER_PORT".to_string(),
401
+ socket_data.local_addr.port().to_string(),
402
+ );
403
+ environment_variables.insert(
404
+ "SERVER_ADDR".to_string(),
405
+ socket_data.local_addr.ip().to_canonical().to_string(),
406
+ );
407
+ if let Some(server_administrator_email) = server_administrator_email {
408
+ environment_variables.insert(
409
+ "SERVER_ADMIN".to_string(),
410
+ server_administrator_email.to_string(),
411
+ );
412
+ }
413
+ if let Some(host) = hyper_request.headers().get(header::HOST) {
414
+ environment_variables.insert(
415
+ "SERVER_NAME".to_string(),
416
+ String::from_utf8_lossy(host.as_bytes()).to_string(),
417
+ );
418
+ }
419
+
420
+ environment_variables.insert(
421
+ "DOCUMENT_ROOT".to_string(),
422
+ wwwroot.to_string_lossy().to_string(),
423
+ );
424
+ environment_variables.insert(
425
+ "PATH_INFO".to_string(),
426
+ match &path_info {
427
+ Some(path_info) => format!("/{}", path_info),
428
+ None => "".to_string(),
429
+ },
430
+ );
431
+ environment_variables.insert(
432
+ "PATH_TRANSLATED".to_string(),
433
+ match &path_info {
434
+ Some(path_info) => {
435
+ let mut path_translated = execute_pathbuf.clone();
436
+ path_translated.push(path_info);
437
+ path_translated.to_string_lossy().to_string()
438
+ }
439
+ None => "".to_string(),
440
+ },
441
+ );
442
+ environment_variables.insert(
443
+ "REQUEST_METHOD".to_string(),
444
+ hyper_request.method().to_string(),
445
+ );
446
+ environment_variables.insert("GATEWAY_INTERFACE".to_string(), "CGI/1.1".to_string());
447
+ environment_variables.insert(
448
+ "REQUEST_URI".to_string(),
449
+ format!(
450
+ "{}{}",
451
+ original_request_uri.path(),
452
+ match original_request_uri.query() {
453
+ Some(query) => format!("?{}", query),
454
+ None => String::from(""),
455
+ }
456
+ ),
457
+ );
458
+
459
+ environment_variables.insert(
460
+ "REMOTE_PORT".to_string(),
461
+ socket_data.remote_addr.port().to_string(),
462
+ );
463
+ environment_variables.insert(
464
+ "REMOTE_ADDR".to_string(),
465
+ socket_data.remote_addr.ip().to_canonical().to_string(),
466
+ );
467
+
468
+ environment_variables.insert(
469
+ "SCRIPT_FILENAME".to_string(),
470
+ execute_pathbuf.to_string_lossy().to_string(),
471
+ );
472
+ if let Ok(script_path) = execute_pathbuf.as_path().strip_prefix(wwwroot) {
473
+ environment_variables.insert(
474
+ "SCRIPT_NAME".to_string(),
475
+ format!(
476
+ "/{}",
477
+ match cfg!(windows) {
478
+ true => script_path.to_string_lossy().to_string().replace("\\", "/"),
479
+ false => script_path.to_string_lossy().to_string(),
480
+ }
481
+ ),
482
+ );
483
+ }
484
+
485
+ if socket_data.encrypted {
486
+ environment_variables.insert("HTTPS".to_string(), "ON".to_string());
487
+ }
488
+
489
+ let mut content_length_set = false;
490
+ for (header_name, header_value) in hyper_request.headers().iter() {
491
+ let env_header_name = match *header_name {
492
+ header::CONTENT_LENGTH => {
493
+ content_length_set = true;
494
+ "CONTENT_LENGTH".to_string()
495
+ }
496
+ header::CONTENT_TYPE => "CONTENT_TYPE".to_string(),
497
+ _ => {
498
+ let mut result = String::new();
499
+
500
+ result.push_str("HTTP_");
501
+
502
+ for c in header_name.as_str().to_uppercase().chars() {
503
+ if c.is_alphanumeric() {
504
+ result.push(c);
505
+ } else {
506
+ result.push('_');
507
+ }
508
+ }
509
+
510
+ result
511
+ }
512
+ };
513
+ if environment_variables.contains_key(&env_header_name) {
514
+ let value = environment_variables.get_mut(&env_header_name);
515
+ if let Some(value) = value {
516
+ if env_header_name == "HTTP_COOKIE" {
517
+ value.push_str("; ");
518
+ } else {
519
+ // See https://stackoverflow.com/a/1801191
520
+ value.push_str(", ");
521
+ }
522
+ value.push_str(String::from_utf8_lossy(header_value.as_bytes()).as_ref());
523
+ } else {
524
+ environment_variables.insert(
525
+ env_header_name,
526
+ String::from_utf8_lossy(header_value.as_bytes()).to_string(),
527
+ );
528
+ }
529
+ } else {
530
+ environment_variables.insert(
531
+ env_header_name,
532
+ String::from_utf8_lossy(header_value.as_bytes()).to_string(),
533
+ );
534
+ }
535
+ }
536
+
537
+ if !content_length_set {
538
+ environment_variables.insert("CONTENT_LENGTH".to_string(), "0".to_string());
539
+ }
540
+
541
+ let (hyper_request, _, _) = request.into_parts();
542
+
543
+ execute_wsgi(
544
+ hyper_request,
545
+ error_logger,
546
+ wsgi_application,
547
+ environment_variables,
548
+ )
549
+ .await
550
+ }
551
+
552
+ async fn execute_wsgi(
553
+ hyper_request: HyperRequest,
554
+ error_logger: &ErrorLogger,
555
+ wsgi_application: Arc<Py<PyAny>>,
556
+ environment_variables: LinkedHashMap<String, String>,
557
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
558
+ let (_, body) = hyper_request.into_parts();
559
+ let body_reader = StreamReader::new(body.into_data_stream().map_err(std::io::Error::other));
560
+ let wsgi_head = Arc::new(Mutex::new(ResponseHead::new()));
561
+ let wsgi_head_clone = wsgi_head.clone();
562
+ let error_logger_owned = error_logger.to_owned();
563
+ let body_iterator = tokio::task::spawn_blocking(move || {
564
+ Python::with_gil(move |py| -> PyResult<Py<PyIterator>> {
565
+ let start_response = PyCFunction::new_closure(
566
+ py,
567
+ None,
568
+ None,
569
+ move |args: &Bound<'_, PyTuple>, kwargs: Option<&Bound<'_, PyDict>>| -> PyResult<_> {
570
+ let args_native = args.extract::<(String, Vec<(String, String)>)>()?;
571
+ let exc_info = kwargs.map_or(Ok(None), |kwargs| {
572
+ let exc_info = kwargs.get_item("exc_info");
573
+ if let Ok(Some(exc_info)) = exc_info {
574
+ if exc_info.is_none() {
575
+ Ok(None)
576
+ } else {
577
+ Ok(Some(exc_info))
578
+ }
579
+ } else {
580
+ exc_info
581
+ }
582
+ })?;
583
+ let mut wsgi_head_locked = wsgi_head_clone.blocking_lock();
584
+ if let Some(exc_info) = exc_info {
585
+ if wsgi_head_locked.is_sent {
586
+ let exc_info_tuple = exc_info.downcast::<PyTuple>()?;
587
+ let exc_info_exception = exc_info_tuple
588
+ .get_item(1)?
589
+ .getattr("with_traceback")?
590
+ .call((exc_info_tuple.get_item(2)?,), None)?
591
+ .downcast::<PyException>()?
592
+ .clone();
593
+ Err(exc_info_exception)?
594
+ }
595
+ } else if wsgi_head_locked.is_set {
596
+ Err(PyAssertionError::new_err("Headers already set"))?
597
+ }
598
+ let status_code_string_option = args_native.0.split(" ").next();
599
+ if let Some(status_code_string) = status_code_string_option {
600
+ wsgi_head_locked.status =
601
+ StatusCode::from_u16(status_code_string.parse()?).map_err(|e| anyhow::anyhow!(e))?;
602
+ } else {
603
+ Err(anyhow::anyhow!("Can't extract status code"))?;
604
+ }
605
+ let mut header_map = HeaderMap::new();
606
+ for header in args_native.1 {
607
+ header_map.append(
608
+ HeaderName::from_str(&header.0).map_err(|e| anyhow::anyhow!(e))?,
609
+ HeaderValue::from_str(&header.1).map_err(|e| anyhow::anyhow!(e))?,
610
+ );
611
+ }
612
+ wsgi_head_locked.headers = Some(header_map);
613
+ wsgi_head_locked.is_set = true;
614
+ Ok(())
615
+ },
616
+ )?;
617
+ let mut environment: HashMap<String, Bound<'_, PyAny>> = HashMap::new();
618
+ let is_https = environment_variables.contains_key("HTTPS");
619
+ let content_length = if let Some(content_length) = environment_variables.get("CONTENT_LENGTH")
620
+ {
621
+ content_length.parse::<u64>().ok()
622
+ } else {
623
+ None
624
+ };
625
+ for (environment_variable, environment_variable_value) in environment_variables {
626
+ environment.insert(
627
+ environment_variable,
628
+ PyString::new(py, &environment_variable_value).into_any(),
629
+ );
630
+ }
631
+ environment.insert(
632
+ "wsgi.version".to_string(),
633
+ PyTuple::new(py, [1, 0])?.into_any(),
634
+ );
635
+ environment.insert(
636
+ "wsgi.url_scheme".to_string(),
637
+ PyString::new(py, if is_https { "https" } else { "http" }).into_any(),
638
+ );
639
+ environment.insert(
640
+ "wsgi.input".to_string(),
641
+ (if let Some(content_length) = content_length {
642
+ WsgiInputStream::new(body_reader.take(content_length))
643
+ } else {
644
+ WsgiInputStream::new(body_reader)
645
+ })
646
+ .into_pyobject(py)?
647
+ .into_any(),
648
+ );
649
+ environment.insert(
650
+ "wsgi.errors".to_string(),
651
+ WsgiErrorStream::new(error_logger_owned)
652
+ .into_pyobject(py)?
653
+ .into_any(),
654
+ );
655
+ environment.insert(
656
+ "wsgi.multithread".to_string(),
657
+ PyBool::new(py, true).as_any().clone(),
658
+ );
659
+ environment.insert(
660
+ "wsgi.multiprocess".to_string(),
661
+ PyBool::new(py, false).as_any().clone(),
662
+ );
663
+ environment.insert(
664
+ "wsgi.run_once".to_string(),
665
+ PyBool::new(py, false).as_any().clone(),
666
+ );
667
+ let body_unknown = wsgi_application.call(py, (environment, start_response), None)?;
668
+ let body_iterator = body_unknown
669
+ .downcast_bound::<PyIterator>(py)?
670
+ .clone()
671
+ .unbind();
672
+ Ok(body_iterator)
673
+ })
674
+ })
675
+ .await??;
676
+
677
+ let wsgi_head_clone = wsgi_head.clone();
678
+ let mut response_stream =
679
+ futures_util::stream::unfold(Arc::new(body_iterator), move |body_iterator_arc| {
680
+ let wsgi_head_clone = wsgi_head_clone.clone();
681
+ Box::pin(async move {
682
+ let body_iterator_arc_clone = body_iterator_arc.clone();
683
+ let blocking_thread_result = tokio::task::spawn_blocking(move || {
684
+ Python::with_gil(|py| -> PyResult<Option<Bytes>> {
685
+ let mut body_iterator_bound = body_iterator_arc_clone.bind(py).clone();
686
+ if let Some(body_chunk) = body_iterator_bound.next() {
687
+ Ok(Some(Bytes::from(body_chunk?.extract::<Vec<u8>>()?)))
688
+ } else {
689
+ Ok(None)
690
+ }
691
+ })
692
+ })
693
+ .await;
694
+
695
+ match blocking_thread_result {
696
+ Err(error) => Some((Err(std::io::Error::other(error)), body_iterator_arc)),
697
+ Ok(Err(error)) => Some((Err(std::io::Error::other(error)), body_iterator_arc)),
698
+ Ok(Ok(None)) => None,
699
+ Ok(Ok(Some(chunk))) => {
700
+ let wsgi_head_locked = wsgi_head_clone.lock().await;
701
+ if !wsgi_head_locked.is_set {
702
+ Some((
703
+ Err(std::io::Error::other(
704
+ "The \"start_response\" function hasn't been called.",
705
+ )),
706
+ body_iterator_arc,
707
+ ))
708
+ } else {
709
+ Some((Ok(chunk), body_iterator_arc))
710
+ }
711
+ }
712
+ }
713
+ })
714
+ });
715
+
716
+ let first_chunk = response_stream.next().await;
717
+ let response_body = if let Some(Err(first_chunk_error)) = first_chunk {
718
+ Err(first_chunk_error)?
719
+ } else if let Some(Ok(first_chunk)) = first_chunk {
720
+ let response_stream_first_item = futures_util::stream::once(async move { Ok(first_chunk) });
721
+ let response_stream_combined = response_stream_first_item.chain(response_stream);
722
+ let stream_body = StreamBody::new(response_stream_combined.map_ok(Frame::data));
723
+
724
+ BodyExt::boxed(stream_body)
725
+ } else {
726
+ BodyExt::boxed(Empty::new().map_err(|e| match e {}))
727
+ };
728
+
729
+ let mut wsgi_head_locked = wsgi_head.lock().await;
730
+ let mut hyper_response = Response::new(response_body);
731
+ *hyper_response.status_mut() = wsgi_head_locked.status;
732
+ if let Some(headers) = wsgi_head_locked.headers.take() {
733
+ *hyper_response.headers_mut() = headers;
734
+ }
735
+ wsgi_head_locked.is_sent = true;
736
+
737
+ Ok(
738
+ ResponseData::builder_without_request()
739
+ .response(hyper_response)
740
+ .build(),
741
+ )
742
+ }
ferron/src/optional_modules/wsgid.rs ADDED
@@ -0,0 +1,1035 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #[cfg(not(unix))]
2
+ compile_error!("This module is supported only on Unix and Unix-like systems.");
3
+
4
+ use std::collections::HashMap;
5
+ use std::error::Error;
6
+ use std::io::{BufReader, Read};
7
+ use std::path::{Path, PathBuf};
8
+ use std::str::FromStr;
9
+ use std::sync::Arc;
10
+ use std::thread;
11
+
12
+ use crate::ferron_common::{
13
+ ErrorLogger, HyperRequest, HyperUpgraded, RequestData, ResponseData, ServerConfig, ServerModule,
14
+ ServerModuleHandlers, SocketData,
15
+ };
16
+ use crate::ferron_common::{HyperResponse, WithRuntime};
17
+ use crate::ferron_res::server_software::SERVER_SOFTWARE;
18
+ use crate::ferron_util::ip_match::ip_match;
19
+ use crate::ferron_util::match_hostname::match_hostname;
20
+ use crate::ferron_util::match_location::match_location;
21
+ use crate::ferron_util::preforked_process_pool::{
22
+ read_ipc_message, read_ipc_message_async, write_ipc_message, write_ipc_message_async,
23
+ PreforkedProcessPool,
24
+ };
25
+ use crate::ferron_util::wsgi_load_application::load_wsgi_application;
26
+ use crate::ferron_util::wsgid_body_reader::WsgidBodyReader;
27
+ use crate::ferron_util::wsgid_error_stream::WsgidErrorStream;
28
+ use crate::ferron_util::wsgid_input_stream::WsgidInputStream;
29
+ use crate::ferron_util::wsgid_message_structs::{
30
+ ProcessPoolToServerMessage, ServerToProcessPoolMessage,
31
+ };
32
+ use crate::ferron_util::wsgid_structs::{WsgidApplicationLocationWrap, WsgidApplicationWrap};
33
+ use async_trait::async_trait;
34
+ use futures_util::{StreamExt, TryStreamExt};
35
+ use hashlink::LinkedHashMap;
36
+ use http::{HeaderMap, HeaderName, HeaderValue, StatusCode};
37
+ use http_body_util::{BodyExt, Empty, StreamBody};
38
+ use hyper::body::{Bytes, Frame};
39
+ use hyper::header;
40
+ use hyper::Response;
41
+ use hyper_tungstenite::HyperWebsocket;
42
+ use interprocess::unnamed_pipe::{Recver, Sender};
43
+ use pyo3::exceptions::{PyAssertionError, PyException};
44
+ use pyo3::prelude::*;
45
+ use pyo3::types::{PyBool, PyCFunction, PyDict, PyIterator, PyString, PyTuple};
46
+ //use postcard::{DeOptions, SerOptions};
47
+ use tokio::fs;
48
+ use tokio::runtime::Handle;
49
+ use tokio::sync::Mutex;
50
+
51
+ struct ResponseHead {
52
+ status: u16,
53
+ headers: Option<LinkedHashMap<String, Vec<String>>>,
54
+ is_set: bool,
55
+ is_sent: bool,
56
+ }
57
+
58
+ impl ResponseHead {
59
+ fn new() -> Self {
60
+ Self {
61
+ status: 200,
62
+ headers: None,
63
+ is_set: false,
64
+ is_sent: false,
65
+ }
66
+ }
67
+ }
68
+
69
+ fn wsgi_pool_fn(tx: Sender, rx: Recver, wsgi_script_path: PathBuf) {
70
+ let wsgi_application_result: Result<Py<PyAny>, Box<dyn Error + Send + Sync>> =
71
+ load_wsgi_application(wsgi_script_path.as_path(), false);
72
+ let mut body_iterators = HashMap::new();
73
+ let mut application_id = 0;
74
+ let mut wsgi_head = Arc::new(Mutex::new(ResponseHead::new()));
75
+ let rx_mutex = Arc::new(Mutex::new(rx));
76
+ let tx_mutex = Arc::new(Mutex::new(tx));
77
+
78
+ loop {
79
+ let received_raw_message = match read_ipc_message(&mut rx_mutex.blocking_lock()) {
80
+ Ok(message) => message,
81
+ Err(_) => break,
82
+ };
83
+
84
+ let received_message =
85
+ match postcard::from_bytes::<ServerToProcessPoolMessage>(&received_raw_message) {
86
+ Ok(message) => message,
87
+ Err(_) => continue,
88
+ };
89
+
90
+ if let Some(error) = (|| -> Result<(), Box<dyn Error + Send + Sync>> {
91
+ let wsgi_application = wsgi_application_result
92
+ .as_ref()
93
+ .map_err(|x| anyhow::anyhow!(x.to_string()))?;
94
+ if let Some(environment_variables) = received_message.environment_variables {
95
+ wsgi_head = Arc::new(Mutex::new(ResponseHead::new()));
96
+ let wsgi_head_clone = wsgi_head.clone();
97
+ let tx_mutex_clone = tx_mutex.clone();
98
+ let rx_mutex_clone = rx_mutex.clone();
99
+ let body_iterator = Python::with_gil(move |py| -> PyResult<Py<PyIterator>> {
100
+ let start_response = PyCFunction::new_closure(
101
+ py,
102
+ None,
103
+ None,
104
+ move |args: &Bound<'_, PyTuple>, kwargs: Option<&Bound<'_, PyDict>>| -> PyResult<_> {
105
+ let args_native = args.extract::<(String, Vec<(String, String)>)>()?;
106
+ let exc_info = kwargs.map_or(Ok(None), |kwargs| {
107
+ let exc_info = kwargs.get_item("exc_info");
108
+ if let Ok(Some(exc_info)) = exc_info {
109
+ if exc_info.is_none() {
110
+ Ok(None)
111
+ } else {
112
+ Ok(Some(exc_info))
113
+ }
114
+ } else {
115
+ exc_info
116
+ }
117
+ })?;
118
+ let mut wsgi_head_locked = wsgi_head_clone.blocking_lock();
119
+ if let Some(exc_info) = exc_info {
120
+ if wsgi_head_locked.is_sent {
121
+ let exc_info_tuple = exc_info.downcast::<PyTuple>()?;
122
+ let exc_info_exception = exc_info_tuple
123
+ .get_item(1)?
124
+ .getattr("with_traceback")?
125
+ .call((exc_info_tuple.get_item(2)?,), None)?
126
+ .downcast::<PyException>()?
127
+ .clone();
128
+ Err(exc_info_exception)?
129
+ }
130
+ } else if wsgi_head_locked.is_set {
131
+ Err(PyAssertionError::new_err("Headers already set"))?
132
+ }
133
+ let status_code_string_option = args_native.0.split(" ").next();
134
+ if let Some(status_code_string) = status_code_string_option {
135
+ wsgi_head_locked.status = status_code_string
136
+ .parse()
137
+ .map_err(|e: std::num::ParseIntError| anyhow::anyhow!(e))?;
138
+ } else {
139
+ Err(anyhow::anyhow!("Can't extract status code"))?;
140
+ }
141
+ let mut header_map: LinkedHashMap<String, Vec<String>> = LinkedHashMap::new();
142
+ for header in args_native.1 {
143
+ let header_name = header.0.to_lowercase();
144
+ let header_value = header.1;
145
+ if let Some(header_values) = header_map.get_mut(&header_name) {
146
+ header_values.push(header_value);
147
+ } else {
148
+ header_map.insert(header_name, vec![header_value]);
149
+ }
150
+ }
151
+ wsgi_head_locked.headers = Some(header_map);
152
+ wsgi_head_locked.is_set = true;
153
+ Ok(())
154
+ },
155
+ )?;
156
+ let mut environment: HashMap<String, Bound<'_, PyAny>> = HashMap::new();
157
+ let is_https = environment_variables.contains_key("HTTPS");
158
+ let content_length =
159
+ if let Some(content_length) = environment_variables.get("CONTENT_LENGTH") {
160
+ content_length.parse::<u64>().ok()
161
+ } else {
162
+ None
163
+ };
164
+ for (environment_variable, environment_variable_value) in environment_variables {
165
+ environment.insert(
166
+ environment_variable,
167
+ PyString::new(py, &environment_variable_value).into_any(),
168
+ );
169
+ }
170
+ environment.insert(
171
+ "wsgi.version".to_string(),
172
+ PyTuple::new(py, [1, 0])?.into_any(),
173
+ );
174
+ environment.insert(
175
+ "wsgi.url_scheme".to_string(),
176
+ PyString::new(py, if is_https { "https" } else { "http" }).into_any(),
177
+ );
178
+ environment.insert(
179
+ "wsgi.input".to_string(),
180
+ (if let Some(content_length) = content_length {
181
+ WsgidInputStream::new(
182
+ BufReader::new(WsgidBodyReader::new(
183
+ tx_mutex_clone.clone(),
184
+ rx_mutex_clone.clone(),
185
+ ))
186
+ .take(content_length),
187
+ )
188
+ } else {
189
+ WsgidInputStream::new(BufReader::new(WsgidBodyReader::new(
190
+ tx_mutex_clone.clone(),
191
+ rx_mutex_clone.clone(),
192
+ )))
193
+ })
194
+ .into_pyobject(py)?
195
+ .into_any(),
196
+ );
197
+ environment.insert(
198
+ "wsgi.errors".to_string(),
199
+ WsgidErrorStream::new(tx_mutex_clone.clone())
200
+ .into_pyobject(py)?
201
+ .into_any(),
202
+ );
203
+ environment.insert(
204
+ "wsgi.multithread".to_string(),
205
+ PyBool::new(py, false).as_any().clone(),
206
+ );
207
+ environment.insert(
208
+ "wsgi.multiprocess".to_string(),
209
+ PyBool::new(py, true).as_any().clone(),
210
+ );
211
+ environment.insert(
212
+ "wsgi.run_once".to_string(),
213
+ PyBool::new(py, false).as_any().clone(),
214
+ );
215
+ let body_unknown = wsgi_application.call(py, (environment, start_response), None)?;
216
+ let body_iterator = body_unknown
217
+ .downcast_bound::<PyIterator>(py)?
218
+ .clone()
219
+ .unbind();
220
+ Ok(body_iterator)
221
+ })?;
222
+ let current_application_id = application_id;
223
+ body_iterators.insert(current_application_id, Arc::new(body_iterator));
224
+ application_id += 1;
225
+ write_ipc_message(
226
+ &mut tx_mutex.blocking_lock(),
227
+ &postcard::to_allocvec::<ProcessPoolToServerMessage>(&ProcessPoolToServerMessage {
228
+ application_id: Some(current_application_id),
229
+ status_code: None,
230
+ headers: None,
231
+ body_chunk: None,
232
+ error_log_line: None,
233
+ error_message: None,
234
+ requests_body_chunk: false,
235
+ })?,
236
+ )?
237
+ } else if received_message.requests_body_chunk {
238
+ if let Some(application_id) = received_message.application_id {
239
+ if let Some(body_iterator_arc) = body_iterators.get(&application_id) {
240
+ let wsgi_head_clone = wsgi_head.clone();
241
+ let body_iterator_arc_clone = body_iterator_arc.clone();
242
+ let body_chunk_result = Python::with_gil(|py| -> PyResult<Option<Vec<u8>>> {
243
+ let mut body_iterator_bound = body_iterator_arc_clone.bind(py).clone();
244
+ if let Some(body_chunk) = body_iterator_bound.next() {
245
+ Ok(Some(body_chunk?.extract::<Vec<u8>>()?))
246
+ } else {
247
+ Ok(None)
248
+ }
249
+ });
250
+
251
+ let body_chunk = (match body_chunk_result {
252
+ Err(error) => Err(std::io::Error::other(error)),
253
+ Ok(None) => Ok(None),
254
+ Ok(Some(chunk)) => {
255
+ let wsgi_head_locked = wsgi_head_clone.blocking_lock();
256
+ if !wsgi_head_locked.is_set {
257
+ Err(std::io::Error::other(
258
+ "The \"start_response\" function hasn't been called.",
259
+ ))
260
+ } else {
261
+ Ok(Some(chunk))
262
+ }
263
+ }
264
+ })?;
265
+
266
+ let status_code;
267
+ let headers;
268
+
269
+ let mut wsgi_head_locked = wsgi_head_clone.blocking_lock();
270
+ if wsgi_head_locked.is_sent {
271
+ status_code = None;
272
+ headers = None;
273
+ } else {
274
+ status_code = Some(wsgi_head_locked.status);
275
+ headers = wsgi_head_locked.headers.take();
276
+ wsgi_head_locked.is_sent = true;
277
+ }
278
+ drop(wsgi_head_locked);
279
+
280
+ if body_chunk.is_none() {
281
+ body_iterators.remove(&application_id);
282
+ }
283
+
284
+ write_ipc_message(
285
+ &mut tx_mutex.blocking_lock(),
286
+ &postcard::to_allocvec::<ProcessPoolToServerMessage>(&ProcessPoolToServerMessage {
287
+ application_id: None,
288
+ status_code,
289
+ headers,
290
+ body_chunk,
291
+ error_log_line: None,
292
+ error_message: None,
293
+ requests_body_chunk: false,
294
+ })?,
295
+ )?
296
+ } else {
297
+ Err(anyhow::anyhow!("The WSGI request wasn't initialized"))?
298
+ }
299
+ } else {
300
+ Err(anyhow::anyhow!("The WSGI request wasn't initialized"))?
301
+ }
302
+ }
303
+
304
+ Ok(())
305
+ })()
306
+ .err()
307
+ {
308
+ if write_ipc_message(
309
+ &mut tx_mutex.blocking_lock(),
310
+ &postcard::to_allocvec::<ProcessPoolToServerMessage>(&ProcessPoolToServerMessage {
311
+ application_id: None,
312
+ status_code: None,
313
+ headers: None,
314
+ body_chunk: None,
315
+ error_log_line: None,
316
+ error_message: Some(error.to_string()),
317
+ requests_body_chunk: false,
318
+ })
319
+ .unwrap_or_default(),
320
+ )
321
+ .is_err()
322
+ {
323
+ break;
324
+ }
325
+ }
326
+ }
327
+ }
328
+
329
+ fn init_wsgi_process_pool(
330
+ wsgi_script_path: PathBuf,
331
+ ) -> Result<PreforkedProcessPool, Box<dyn Error + Send + Sync>> {
332
+ let available_parallelism = thread::available_parallelism()?.get();
333
+ // Safety: The function depends on `nix::unistd::fork`, which is executed before any threads are spawned.
334
+ // The forking function is safe to call for single-threaded applications.
335
+ unsafe {
336
+ PreforkedProcessPool::new(available_parallelism, move |tx, rx| {
337
+ let wsgi_script_path_clone = wsgi_script_path.clone();
338
+ wsgi_pool_fn(tx, rx, wsgi_script_path_clone)
339
+ })
340
+ }
341
+ }
342
+
343
+ pub fn server_module_init(
344
+ config: &ServerConfig,
345
+ ) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
346
+ let mut global_wsgi_process_pool = None;
347
+ let mut host_wsgi_process_pools = Vec::new();
348
+ if let Some(wsgi_process_pool_path) = config["global"]["wsgidApplicationPath"].as_str() {
349
+ global_wsgi_process_pool = Some(Arc::new(init_wsgi_process_pool(PathBuf::from_str(
350
+ wsgi_process_pool_path,
351
+ )?)?));
352
+ }
353
+ let global_wsgi_path = config["global"]["wsgidPath"]
354
+ .as_str()
355
+ .map(|s| s.to_string());
356
+
357
+ if let Some(hosts) = config["hosts"].as_vec() {
358
+ for host_yaml in hosts.iter() {
359
+ let domain = host_yaml["domain"].as_str().map(String::from);
360
+ let ip = host_yaml["ip"].as_str().map(String::from);
361
+ let mut locations = Vec::new();
362
+ if let Some(locations_yaml) = host_yaml["locations"].as_vec() {
363
+ for location_yaml in locations_yaml.iter() {
364
+ if let Some(path_str) = location_yaml["path"].as_str() {
365
+ let path = String::from(path_str);
366
+ if let Some(wsgi_process_pool_path) = location_yaml["wsgidApplicationPath"].as_str() {
367
+ locations.push(WsgidApplicationLocationWrap::new(
368
+ path,
369
+ Arc::new(init_wsgi_process_pool(PathBuf::from_str(
370
+ wsgi_process_pool_path,
371
+ )?)?),
372
+ location_yaml["wsgidPath"].as_str().map(|s| s.to_string()),
373
+ ));
374
+ }
375
+ }
376
+ }
377
+ }
378
+ if let Some(wsgi_process_pool_path) = host_yaml["wsgidApplicationPath"].as_str() {
379
+ host_wsgi_process_pools.push(WsgidApplicationWrap::new(
380
+ domain,
381
+ ip,
382
+ Some(Arc::new(init_wsgi_process_pool(PathBuf::from_str(
383
+ wsgi_process_pool_path,
384
+ )?)?)),
385
+ host_yaml["wsgiPath"].as_str().map(|s| s.to_string()),
386
+ locations,
387
+ ));
388
+ } else if !locations.is_empty() {
389
+ host_wsgi_process_pools.push(WsgidApplicationWrap::new(
390
+ domain,
391
+ ip,
392
+ None,
393
+ host_yaml["wsgiPath"].as_str().map(|s| s.to_string()),
394
+ locations,
395
+ ));
396
+ }
397
+ }
398
+ }
399
+
400
+ Ok(Box::new(WsgidModule::new(
401
+ global_wsgi_process_pool,
402
+ global_wsgi_path,
403
+ Arc::new(host_wsgi_process_pools),
404
+ )))
405
+ }
406
+
407
+ struct WsgidModule {
408
+ global_wsgi_process_pool: Option<Arc<PreforkedProcessPool>>,
409
+ global_wsgi_path: Option<String>,
410
+ host_wsgi_process_pools: Arc<Vec<WsgidApplicationWrap>>,
411
+ }
412
+
413
+ impl WsgidModule {
414
+ fn new(
415
+ global_wsgi_process_pool: Option<Arc<PreforkedProcessPool>>,
416
+ global_wsgi_path: Option<String>,
417
+ host_wsgi_process_pools: Arc<Vec<WsgidApplicationWrap>>,
418
+ ) -> Self {
419
+ Self {
420
+ global_wsgi_process_pool,
421
+ global_wsgi_path,
422
+ host_wsgi_process_pools,
423
+ }
424
+ }
425
+ }
426
+
427
+ impl ServerModule for WsgidModule {
428
+ fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
429
+ Box::new(WsgidModuleHandlers {
430
+ handle,
431
+ global_wsgi_process_pool: self.global_wsgi_process_pool.clone(),
432
+ global_wsgi_path: self.global_wsgi_path.clone(),
433
+ host_wsgi_process_pools: self.host_wsgi_process_pools.clone(),
434
+ })
435
+ }
436
+ }
437
+
438
+ struct WsgidModuleHandlers {
439
+ handle: Handle,
440
+ global_wsgi_process_pool: Option<Arc<PreforkedProcessPool>>,
441
+ global_wsgi_path: Option<String>,
442
+ host_wsgi_process_pools: Arc<Vec<WsgidApplicationWrap>>,
443
+ }
444
+
445
+ #[async_trait]
446
+ impl ServerModuleHandlers for WsgidModuleHandlers {
447
+ async fn request_handler(
448
+ &mut self,
449
+ request: RequestData,
450
+ config: &ServerConfig,
451
+ socket_data: &SocketData,
452
+ error_logger: &ErrorLogger,
453
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
454
+ WithRuntime::new(self.handle.clone(), async move {
455
+ let hyper_request = request.get_hyper_request();
456
+
457
+ // Use .take() instead of .clone(), since the values in Options will only be used once.
458
+ let mut wsgi_process_pool = self.global_wsgi_process_pool.take();
459
+ let mut wsgi_path = self.global_wsgi_path.take();
460
+
461
+ // Should have used a HashMap instead of iterating over an array for better performance...
462
+ for host_wsgi_process_pool_wrap in self.host_wsgi_process_pools.iter() {
463
+ if match_hostname(
464
+ match &host_wsgi_process_pool_wrap.domain {
465
+ Some(value) => Some(value as &str),
466
+ None => None,
467
+ },
468
+ match hyper_request.headers().get(header::HOST) {
469
+ Some(value) => value.to_str().ok(),
470
+ None => None,
471
+ },
472
+ ) && match &host_wsgi_process_pool_wrap.ip {
473
+ Some(value) => ip_match(value as &str, socket_data.remote_addr.ip()),
474
+ None => true,
475
+ } {
476
+ wsgi_process_pool = host_wsgi_process_pool_wrap.wsgi_process_pool.clone();
477
+ wsgi_path = host_wsgi_process_pool_wrap.wsgi_path.clone();
478
+ if let Ok(path_decoded) = urlencoding::decode(
479
+ request
480
+ .get_original_url()
481
+ .unwrap_or(request.get_hyper_request().uri())
482
+ .path(),
483
+ ) {
484
+ for location_wrap in host_wsgi_process_pool_wrap.locations.iter() {
485
+ if match_location(&location_wrap.path, &path_decoded) {
486
+ wsgi_process_pool = Some(location_wrap.wsgi_process_pool.clone());
487
+ wsgi_path = location_wrap.wsgi_path.clone();
488
+ break;
489
+ }
490
+ }
491
+ }
492
+ break;
493
+ }
494
+ }
495
+
496
+ let request_path = hyper_request.uri().path();
497
+ let mut request_path_bytes = request_path.bytes();
498
+ if request_path_bytes.len() < 1 || request_path_bytes.nth(0) != Some(b'/') {
499
+ return Ok(
500
+ ResponseData::builder(request)
501
+ .status(StatusCode::BAD_REQUEST)
502
+ .build(),
503
+ );
504
+ }
505
+
506
+ if let Some(wsgi_process_pool) = wsgi_process_pool {
507
+ let wsgi_path = wsgi_path.unwrap_or("/".to_string());
508
+ let mut canonical_wsgi_path: &str = &wsgi_path;
509
+ if canonical_wsgi_path.bytes().last() == Some(b'/') {
510
+ canonical_wsgi_path = &canonical_wsgi_path[..(canonical_wsgi_path.len() - 1)];
511
+ }
512
+
513
+ let request_path_with_slashes = match request_path == canonical_wsgi_path {
514
+ true => format!("{}/", request_path),
515
+ false => request_path.to_string(),
516
+ };
517
+ if let Some(stripped_request_path) =
518
+ request_path_with_slashes.strip_prefix(canonical_wsgi_path)
519
+ {
520
+ let wwwroot_yaml = &config["wwwroot"];
521
+ let wwwroot = wwwroot_yaml.as_str().unwrap_or("/nonexistent");
522
+
523
+ let wwwroot_unknown = PathBuf::from(wwwroot);
524
+ let wwwroot_pathbuf = match wwwroot_unknown.as_path().is_absolute() {
525
+ true => wwwroot_unknown,
526
+ false => match fs::canonicalize(&wwwroot_unknown).await {
527
+ Ok(pathbuf) => pathbuf,
528
+ Err(_) => wwwroot_unknown,
529
+ },
530
+ };
531
+ let wwwroot = wwwroot_pathbuf.as_path();
532
+
533
+ let mut relative_path = &request_path[1..];
534
+ while relative_path.as_bytes().first().copied() == Some(b'/') {
535
+ relative_path = &relative_path[1..];
536
+ }
537
+
538
+ let decoded_relative_path = match urlencoding::decode(relative_path) {
539
+ Ok(path) => path.to_string(),
540
+ Err(_) => {
541
+ return Ok(
542
+ ResponseData::builder(request)
543
+ .status(StatusCode::BAD_REQUEST)
544
+ .build(),
545
+ );
546
+ }
547
+ };
548
+
549
+ let joined_pathbuf = wwwroot.join(decoded_relative_path);
550
+ let execute_pathbuf = joined_pathbuf;
551
+ let execute_path_info = stripped_request_path
552
+ .strip_prefix("/")
553
+ .map(|s| s.to_string());
554
+
555
+ return execute_wsgi_with_environment_variables(
556
+ request,
557
+ socket_data,
558
+ error_logger,
559
+ wwwroot,
560
+ execute_pathbuf,
561
+ execute_path_info,
562
+ config["serverAdministratorEmail"].as_str(),
563
+ wsgi_process_pool,
564
+ )
565
+ .await;
566
+ }
567
+ }
568
+ Ok(ResponseData::builder(request).build())
569
+ })
570
+ .await
571
+ }
572
+
573
+ async fn proxy_request_handler(
574
+ &mut self,
575
+ request: RequestData,
576
+ _config: &ServerConfig,
577
+ _socket_data: &SocketData,
578
+ _error_logger: &ErrorLogger,
579
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
580
+ Ok(ResponseData::builder(request).build())
581
+ }
582
+
583
+ async fn response_modifying_handler(
584
+ &mut self,
585
+ response: HyperResponse,
586
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
587
+ Ok(response)
588
+ }
589
+
590
+ async fn proxy_response_modifying_handler(
591
+ &mut self,
592
+ response: HyperResponse,
593
+ ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
594
+ Ok(response)
595
+ }
596
+
597
+ async fn connect_proxy_request_handler(
598
+ &mut self,
599
+ _upgraded_request: HyperUpgraded,
600
+ _connect_address: &str,
601
+ _config: &ServerConfig,
602
+ _socket_data: &SocketData,
603
+ _error_logger: &ErrorLogger,
604
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
605
+ Ok(())
606
+ }
607
+
608
+ fn does_connect_proxy_requests(&mut self) -> bool {
609
+ false
610
+ }
611
+
612
+ async fn websocket_request_handler(
613
+ &mut self,
614
+ _websocket: HyperWebsocket,
615
+ _uri: &hyper::Uri,
616
+ _config: &ServerConfig,
617
+ _socket_data: &SocketData,
618
+ _error_logger: &ErrorLogger,
619
+ ) -> Result<(), Box<dyn Error + Send + Sync>> {
620
+ Ok(())
621
+ }
622
+
623
+ fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
624
+ false
625
+ }
626
+ }
627
+
628
+ struct ResponseHeadHyper {
629
+ status: StatusCode,
630
+ headers: Option<HeaderMap>,
631
+ }
632
+
633
+ impl ResponseHeadHyper {
634
+ fn new() -> Self {
635
+ Self {
636
+ status: StatusCode::OK,
637
+ headers: None,
638
+ }
639
+ }
640
+ }
641
+
642
+ #[allow(clippy::too_many_arguments)]
643
+ async fn execute_wsgi_with_environment_variables(
644
+ request: RequestData,
645
+ socket_data: &SocketData,
646
+ error_logger: &ErrorLogger,
647
+ wwwroot: &Path,
648
+ execute_pathbuf: PathBuf,
649
+ path_info: Option<String>,
650
+ server_administrator_email: Option<&str>,
651
+ wsgi_process_pool: Arc<PreforkedProcessPool>,
652
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
653
+ let mut environment_variables: LinkedHashMap<String, String> = LinkedHashMap::new();
654
+
655
+ let hyper_request = request.get_hyper_request();
656
+ let original_request_uri = request.get_original_url().unwrap_or(hyper_request.uri());
657
+
658
+ if let Some(auth_user) = request.get_auth_user() {
659
+ if let Some(authorization) = hyper_request.headers().get(header::AUTHORIZATION) {
660
+ let authorization_value = String::from_utf8_lossy(authorization.as_bytes()).to_string();
661
+ let mut authorization_value_split = authorization_value.split(" ");
662
+ if let Some(authorization_type) = authorization_value_split.next() {
663
+ environment_variables.insert("AUTH_TYPE".to_string(), authorization_type.to_string());
664
+ }
665
+ }
666
+ environment_variables.insert("REMOTE_USER".to_string(), auth_user.to_string());
667
+ }
668
+
669
+ environment_variables.insert(
670
+ "QUERY_STRING".to_string(),
671
+ match hyper_request.uri().query() {
672
+ Some(query) => query.to_string(),
673
+ None => "".to_string(),
674
+ },
675
+ );
676
+
677
+ environment_variables.insert("SERVER_SOFTWARE".to_string(), SERVER_SOFTWARE.to_string());
678
+ environment_variables.insert(
679
+ "SERVER_PROTOCOL".to_string(),
680
+ match hyper_request.version() {
681
+ hyper::Version::HTTP_09 => "HTTP/0.9".to_string(),
682
+ hyper::Version::HTTP_10 => "HTTP/1.0".to_string(),
683
+ hyper::Version::HTTP_11 => "HTTP/1.1".to_string(),
684
+ hyper::Version::HTTP_2 => "HTTP/2.0".to_string(),
685
+ hyper::Version::HTTP_3 => "HTTP/3.0".to_string(),
686
+ _ => "HTTP/Unknown".to_string(),
687
+ },
688
+ );
689
+ environment_variables.insert(
690
+ "SERVER_PORT".to_string(),
691
+ socket_data.local_addr.port().to_string(),
692
+ );
693
+ environment_variables.insert(
694
+ "SERVER_ADDR".to_string(),
695
+ socket_data.local_addr.ip().to_canonical().to_string(),
696
+ );
697
+ if let Some(server_administrator_email) = server_administrator_email {
698
+ environment_variables.insert(
699
+ "SERVER_ADMIN".to_string(),
700
+ server_administrator_email.to_string(),
701
+ );
702
+ }
703
+ if let Some(host) = hyper_request.headers().get(header::HOST) {
704
+ environment_variables.insert(
705
+ "SERVER_NAME".to_string(),
706
+ String::from_utf8_lossy(host.as_bytes()).to_string(),
707
+ );
708
+ }
709
+
710
+ environment_variables.insert(
711
+ "DOCUMENT_ROOT".to_string(),
712
+ wwwroot.to_string_lossy().to_string(),
713
+ );
714
+ environment_variables.insert(
715
+ "PATH_INFO".to_string(),
716
+ match &path_info {
717
+ Some(path_info) => format!("/{}", path_info),
718
+ None => "".to_string(),
719
+ },
720
+ );
721
+ environment_variables.insert(
722
+ "PATH_TRANSLATED".to_string(),
723
+ match &path_info {
724
+ Some(path_info) => {
725
+ let mut path_translated = execute_pathbuf.clone();
726
+ path_translated.push(path_info);
727
+ path_translated.to_string_lossy().to_string()
728
+ }
729
+ None => "".to_string(),
730
+ },
731
+ );
732
+ environment_variables.insert(
733
+ "REQUEST_METHOD".to_string(),
734
+ hyper_request.method().to_string(),
735
+ );
736
+ environment_variables.insert("GATEWAY_INTERFACE".to_string(), "CGI/1.1".to_string());
737
+ environment_variables.insert(
738
+ "REQUEST_URI".to_string(),
739
+ format!(
740
+ "{}{}",
741
+ original_request_uri.path(),
742
+ match original_request_uri.query() {
743
+ Some(query) => format!("?{}", query),
744
+ None => String::from(""),
745
+ }
746
+ ),
747
+ );
748
+
749
+ environment_variables.insert(
750
+ "REMOTE_PORT".to_string(),
751
+ socket_data.remote_addr.port().to_string(),
752
+ );
753
+ environment_variables.insert(
754
+ "REMOTE_ADDR".to_string(),
755
+ socket_data.remote_addr.ip().to_canonical().to_string(),
756
+ );
757
+
758
+ environment_variables.insert(
759
+ "SCRIPT_FILENAME".to_string(),
760
+ execute_pathbuf.to_string_lossy().to_string(),
761
+ );
762
+ if let Ok(script_path) = execute_pathbuf.as_path().strip_prefix(wwwroot) {
763
+ environment_variables.insert(
764
+ "SCRIPT_NAME".to_string(),
765
+ format!(
766
+ "/{}",
767
+ match cfg!(windows) {
768
+ true => script_path.to_string_lossy().to_string().replace("\\", "/"),
769
+ false => script_path.to_string_lossy().to_string(),
770
+ }
771
+ ),
772
+ );
773
+ }
774
+
775
+ if socket_data.encrypted {
776
+ environment_variables.insert("HTTPS".to_string(), "ON".to_string());
777
+ }
778
+
779
+ let mut content_length_set = false;
780
+ for (header_name, header_value) in hyper_request.headers().iter() {
781
+ let env_header_name = match *header_name {
782
+ header::CONTENT_LENGTH => {
783
+ content_length_set = true;
784
+ "CONTENT_LENGTH".to_string()
785
+ }
786
+ header::CONTENT_TYPE => "CONTENT_TYPE".to_string(),
787
+ _ => {
788
+ let mut result = String::new();
789
+
790
+ result.push_str("HTTP_");
791
+
792
+ for c in header_name.as_str().to_uppercase().chars() {
793
+ if c.is_alphanumeric() {
794
+ result.push(c);
795
+ } else {
796
+ result.push('_');
797
+ }
798
+ }
799
+
800
+ result
801
+ }
802
+ };
803
+ if environment_variables.contains_key(&env_header_name) {
804
+ let value = environment_variables.get_mut(&env_header_name);
805
+ if let Some(value) = value {
806
+ if env_header_name == "HTTP_COOKIE" {
807
+ value.push_str("; ");
808
+ } else {
809
+ // See https://stackoverflow.com/a/1801191
810
+ value.push_str(", ");
811
+ }
812
+ value.push_str(String::from_utf8_lossy(header_value.as_bytes()).as_ref());
813
+ } else {
814
+ environment_variables.insert(
815
+ env_header_name,
816
+ String::from_utf8_lossy(header_value.as_bytes()).to_string(),
817
+ );
818
+ }
819
+ } else {
820
+ environment_variables.insert(
821
+ env_header_name,
822
+ String::from_utf8_lossy(header_value.as_bytes()).to_string(),
823
+ );
824
+ }
825
+ }
826
+
827
+ if !content_length_set {
828
+ environment_variables.insert("CONTENT_LENGTH".to_string(), "0".to_string());
829
+ }
830
+
831
+ let (hyper_request, _, _) = request.into_parts();
832
+
833
+ execute_wsgi(
834
+ hyper_request,
835
+ error_logger,
836
+ wsgi_process_pool,
837
+ environment_variables,
838
+ )
839
+ .await
840
+ }
841
+
842
+ async fn execute_wsgi(
843
+ hyper_request: HyperRequest,
844
+ error_logger: &ErrorLogger,
845
+ wsgi_process_pool: Arc<PreforkedProcessPool>,
846
+ environment_variables: LinkedHashMap<String, String>,
847
+ ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
848
+ let ipc_mutex = wsgi_process_pool
849
+ .obtain_process_with_init_async_ipc()
850
+ .await?;
851
+ let (_, body) = hyper_request.into_parts();
852
+ let mut body_stream = body.into_data_stream().map_err(std::io::Error::other);
853
+ let application_id = {
854
+ let (tx, rx) = &mut *ipc_mutex.lock().await;
855
+ write_ipc_message_async(
856
+ tx,
857
+ &postcard::to_allocvec(&ServerToProcessPoolMessage {
858
+ application_id: None,
859
+ environment_variables: Some(environment_variables),
860
+ body_chunk: None,
861
+ body_error_message: None,
862
+ requests_body_chunk: false,
863
+ })?,
864
+ )
865
+ .await?;
866
+
867
+ let application_id;
868
+ loop {
869
+ let received_message =
870
+ postcard::from_bytes::<ProcessPoolToServerMessage>(&read_ipc_message_async(rx).await?)?;
871
+
872
+ if let Some(error_message) = received_message.error_message {
873
+ Err(anyhow::anyhow!(error_message))?
874
+ }
875
+
876
+ if let Some(application_id_obtained) = received_message.application_id {
877
+ application_id = application_id_obtained;
878
+ break;
879
+ }
880
+
881
+ if let Some(error_log_line) = received_message.error_log_line {
882
+ error_logger.log(&error_log_line).await;
883
+ } else if received_message.requests_body_chunk {
884
+ let body_chunk;
885
+ let body_error_message;
886
+ match body_stream.next().await {
887
+ None => {
888
+ body_chunk = None;
889
+ body_error_message = None;
890
+ }
891
+ Some(Err(err)) => {
892
+ body_chunk = None;
893
+ body_error_message = Some(err.to_string());
894
+ }
895
+ Some(Ok(chunk)) => {
896
+ body_chunk = Some(chunk.to_vec());
897
+ body_error_message = None;
898
+ }
899
+ };
900
+ write_ipc_message_async(
901
+ tx,
902
+ &postcard::to_allocvec(&ServerToProcessPoolMessage {
903
+ application_id: None,
904
+ environment_variables: None,
905
+ body_chunk,
906
+ body_error_message,
907
+ requests_body_chunk: false,
908
+ })?,
909
+ )
910
+ .await?;
911
+ }
912
+ }
913
+
914
+ application_id
915
+ };
916
+
917
+ let wsgi_head = Arc::new(Mutex::new(ResponseHeadHyper::new()));
918
+ let wsgi_head_clone = wsgi_head.clone();
919
+ let error_logger_arc = Arc::new(error_logger.clone());
920
+ let body_stream_mutex = Arc::new(Mutex::new(body_stream));
921
+ let mut response_stream = futures_util::stream::unfold(ipc_mutex, move |ipc_mutex| {
922
+ let wsgi_head_clone = wsgi_head_clone.clone();
923
+ let error_logger_arc_clone = error_logger_arc.clone();
924
+ let body_stream_mutex_clone = body_stream_mutex.clone();
925
+ Box::pin(async move {
926
+ let ipc_mutex_borrowed = &ipc_mutex;
927
+ let chunk_result: Result<Option<Bytes>, Box<dyn Error + Send + Sync>> = async {
928
+ let (tx, rx) = &mut *ipc_mutex_borrowed.lock().await;
929
+ write_ipc_message_async(
930
+ tx,
931
+ &postcard::to_allocvec(&ServerToProcessPoolMessage {
932
+ application_id: Some(application_id),
933
+ environment_variables: None,
934
+ body_chunk: None,
935
+ body_error_message: None,
936
+ requests_body_chunk: true,
937
+ })?,
938
+ )
939
+ .await?;
940
+
941
+ loop {
942
+ let received_message =
943
+ postcard::from_bytes::<ProcessPoolToServerMessage>(&read_ipc_message_async(rx).await?)?;
944
+
945
+ if let Some(error_message) = received_message.error_message {
946
+ Err(anyhow::anyhow!(error_message))?
947
+ } else if let Some(body_chunk) = received_message.body_chunk {
948
+ if let Some(status_code) = received_message.status_code {
949
+ let mut wsgi_head_locked = wsgi_head_clone.lock().await;
950
+ wsgi_head_locked.status = StatusCode::from_u16(status_code)?;
951
+ if let Some(headers) = received_message.headers {
952
+ let mut header_map = HeaderMap::new();
953
+ for (key, value) in headers {
954
+ for value in value {
955
+ header_map.append(
956
+ HeaderName::from_str(&key)?,
957
+ HeaderValue::from_bytes(value.as_bytes())?,
958
+ );
959
+ }
960
+ }
961
+ wsgi_head_locked.headers = Some(header_map);
962
+ }
963
+ }
964
+ return Ok(Some(Bytes::from(body_chunk)));
965
+ } else if let Some(error_log_line) = received_message.error_log_line {
966
+ error_logger_arc_clone.log(&error_log_line).await;
967
+ } else if received_message.requests_body_chunk {
968
+ let body_chunk;
969
+ let body_error_message;
970
+ match body_stream_mutex_clone.lock().await.next().await {
971
+ None => {
972
+ body_chunk = None;
973
+ body_error_message = None;
974
+ }
975
+ Some(Err(err)) => {
976
+ body_chunk = None;
977
+ body_error_message = Some(err.to_string());
978
+ }
979
+ Some(Ok(chunk)) => {
980
+ body_chunk = Some(chunk.to_vec());
981
+ body_error_message = None;
982
+ }
983
+ };
984
+ write_ipc_message_async(
985
+ tx,
986
+ &postcard::to_allocvec(&ServerToProcessPoolMessage {
987
+ application_id: None,
988
+ environment_variables: None,
989
+ body_chunk,
990
+ body_error_message,
991
+ requests_body_chunk: false,
992
+ })?,
993
+ )
994
+ .await?;
995
+ } else {
996
+ return Ok(None);
997
+ }
998
+ }
999
+ }
1000
+ .await;
1001
+
1002
+ match chunk_result {
1003
+ Err(error) => Some((Err(std::io::Error::other(error.to_string())), ipc_mutex)),
1004
+ Ok(None) => None,
1005
+ Ok(Some(chunk)) => Some((Ok(chunk), ipc_mutex)),
1006
+ }
1007
+ })
1008
+ });
1009
+
1010
+ let first_chunk = response_stream.next().await;
1011
+ let response_body = if let Some(Err(first_chunk_error)) = first_chunk {
1012
+ Err(first_chunk_error)?
1013
+ } else if let Some(Ok(first_chunk)) = first_chunk {
1014
+ let response_stream_first_item = futures_util::stream::once(async move { Ok(first_chunk) });
1015
+ let response_stream_combined = response_stream_first_item.chain(response_stream);
1016
+ let stream_body = StreamBody::new(response_stream_combined.map_ok(Frame::data));
1017
+
1018
+ BodyExt::boxed(stream_body)
1019
+ } else {
1020
+ BodyExt::boxed(Empty::new().map_err(|e| match e {}))
1021
+ };
1022
+
1023
+ let mut wsgi_head_locked = wsgi_head.lock().await;
1024
+ let mut hyper_response = Response::new(response_body);
1025
+ *hyper_response.status_mut() = wsgi_head_locked.status;
1026
+ if let Some(headers) = wsgi_head_locked.headers.take() {
1027
+ *hyper_response.headers_mut() = headers;
1028
+ }
1029
+
1030
+ Ok(
1031
+ ResponseData::builder_without_request()
1032
+ .response(hyper_response)
1033
+ .build(),
1034
+ )
1035
+ }
ferron/src/request_handler.rs ADDED
@@ -0,0 +1,2211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use std::convert::Infallible;
2
+ use std::net::{IpAddr, SocketAddr};
3
+ use std::str::FromStr;
4
+ use std::sync::Arc;
5
+ use std::time::Duration;
6
+
7
+ use crate::ferron_res::server_software::SERVER_SOFTWARE;
8
+ use crate::ferron_util::combine_config::combine_config;
9
+ use crate::ferron_util::error_pages::generate_default_error_page;
10
+ use crate::ferron_util::url_sanitizer::sanitize_url;
11
+
12
+ use crate::ferron_common::{
13
+ ErrorLogger, LogMessage, RequestData, ServerModuleHandlers, SocketData,
14
+ };
15
+ use async_channel::Sender;
16
+ use chrono::prelude::*;
17
+ use futures_util::TryStreamExt;
18
+ use http::header::CONTENT_TYPE;
19
+ use http_body_util::combinators::BoxBody;
20
+ use http_body_util::{BodyExt, Empty, Full, StreamBody};
21
+ use hyper::body::{Body, Bytes, Frame};
22
+ use hyper::header::{self, HeaderName, HeaderValue};
23
+ use hyper::{HeaderMap, Method, Request, Response, StatusCode};
24
+ use hyper_tungstenite::is_upgrade_request;
25
+ use rustls_acme::ResolvesServerCertAcme;
26
+ use tokio::fs;
27
+ use tokio::io::BufReader;
28
+ use tokio::time::timeout;
29
+ use tokio_util::io::ReaderStream;
30
+ use yaml_rust2::Yaml;
31
+
32
+ async fn generate_error_response(
33
+ status_code: StatusCode,
34
+ config: &Yaml,
35
+ headers: &Option<HeaderMap>,
36
+ ) -> Response<BoxBody<Bytes, std::io::Error>> {
37
+ let bare_body =
38
+ generate_default_error_page(status_code, config["serverAdministratorEmail"].as_str());
39
+ let mut content_length: Option<u64> = bare_body.len().try_into().ok();
40
+ let mut response_body = Full::new(Bytes::from(bare_body))
41
+ .map_err(|e| match e {})
42
+ .boxed();
43
+
44
+ if let Some(error_pages) = config["errorPages"].as_vec() {
45
+ for error_page_yaml in error_pages {
46
+ if let Some(page_status_code) = error_page_yaml["scode"].as_i64() {
47
+ let page_status_code = match StatusCode::from_u16(match page_status_code.try_into() {
48
+ Ok(status_code) => status_code,
49
+ Err(_) => continue,
50
+ }) {
51
+ Ok(status_code) => status_code,
52
+ Err(_) => continue,
53
+ };
54
+ if status_code != page_status_code {
55
+ continue;
56
+ }
57
+ if let Some(page_path) = error_page_yaml["path"].as_str() {
58
+ let file = fs::File::open(page_path).await;
59
+
60
+ let file = match file {
61
+ Ok(file) => file,
62
+ Err(_) => continue,
63
+ };
64
+
65
+ content_length = match file.metadata().await {
66
+ Ok(metadata) => Some(metadata.len()),
67
+ Err(_) => None,
68
+ };
69
+
70
+ // Use BufReader for better performance.
71
+ let reader_stream = ReaderStream::new(BufReader::with_capacity(12800, file));
72
+
73
+ let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
74
+ let boxed_body = stream_body.boxed();
75
+
76
+ response_body = boxed_body;
77
+
78
+ break;
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ let mut response_builder = Response::builder().status(status_code);
85
+
86
+ if let Some(headers) = headers {
87
+ let headers_iter = headers.iter();
88
+ for (name, value) in headers_iter {
89
+ if name != header::CONTENT_TYPE && name != header::CONTENT_LENGTH {
90
+ response_builder = response_builder.header(name, value);
91
+ }
92
+ }
93
+ }
94
+
95
+ if let Some(content_length) = content_length {
96
+ response_builder = response_builder.header(header::CONTENT_LENGTH, content_length);
97
+ }
98
+ response_builder = response_builder.header(header::CONTENT_TYPE, "text/html");
99
+
100
+ response_builder.body(response_body).unwrap_or_default()
101
+ }
102
+
103
+ #[allow(clippy::too_many_arguments)]
104
+ async fn log_combined(
105
+ logger: &Sender<LogMessage>,
106
+ client_ip: IpAddr,
107
+ auth_user: Option<String>,
108
+ method: String,
109
+ request_path: String,
110
+ protocol: String,
111
+ status_code: u16,
112
+ content_length: Option<u64>,
113
+ referrer: Option<String>,
114
+ user_agent: Option<String>,
115
+ ) {
116
+ let now: DateTime<Local> = Local::now();
117
+ let formatted_time = now.format("%d/%b/%Y:%H:%M:%S %z").to_string();
118
+ logger
119
+ .send(LogMessage::new(
120
+ format!(
121
+ "{} - {} [{}] \"{} {} {}\" {} {} {} {}",
122
+ client_ip,
123
+ match auth_user {
124
+ Some(auth_user) => auth_user,
125
+ None => String::from("-"),
126
+ },
127
+ formatted_time,
128
+ method,
129
+ request_path,
130
+ protocol,
131
+ status_code,
132
+ match content_length {
133
+ Some(content_length) => format!("{}", content_length),
134
+ None => String::from("-"),
135
+ },
136
+ match referrer {
137
+ Some(referrer) => format!(
138
+ "\"{}\"",
139
+ referrer.replace("\\", "\\\\").replace("\"", "\\\"")
140
+ ),
141
+ None => String::from("-"),
142
+ },
143
+ match user_agent {
144
+ Some(user_agent) => format!(
145
+ "\"{}\"",
146
+ user_agent.replace("\\", "\\\\").replace("\"", "\\\"")
147
+ ),
148
+ None => String::from("-"),
149
+ },
150
+ ),
151
+ false,
152
+ ))
153
+ .await
154
+ .unwrap_or_default();
155
+ }
156
+
157
+ #[allow(clippy::too_many_arguments)]
158
+ async fn request_handler_wrapped(
159
+ mut request: Request<BoxBody<Bytes, std::io::Error>>,
160
+ remote_address: SocketAddr,
161
+ local_address: SocketAddr,
162
+ encrypted: bool,
163
+ config: Arc<Yaml>,
164
+ logger: Sender<LogMessage>,
165
+ handlers_vec: Vec<Box<dyn ServerModuleHandlers + Send>>,
166
+ acme_http01_resolver_option: Option<Arc<ResolvesServerCertAcme>>,
167
+ http3_alt_port: Option<u16>,
168
+ ) -> Result<Response<BoxBody<Bytes, std::io::Error>>, Infallible> {
169
+ let is_proxy_request = match request.version() {
170
+ hyper::Version::HTTP_2 | hyper::Version::HTTP_3 => {
171
+ request.method() == hyper::Method::CONNECT && request.uri().host().is_some()
172
+ }
173
+ _ => request.uri().host().is_some(),
174
+ };
175
+ let is_connect_proxy_request = request.method() == hyper::Method::CONNECT;
176
+
177
+ // Collect request data for logging
178
+ let log_method = String::from(request.method().as_str());
179
+ let log_request_path = match is_proxy_request {
180
+ true => request.uri().to_string(),
181
+ false => format!(
182
+ "{}{}",
183
+ request.uri().path(),
184
+ match request.uri().query() {
185
+ Some(query) => format!("?{}", query),
186
+ None => String::from(""),
187
+ }
188
+ ),
189
+ };
190
+ let log_protocol = String::from(match request.version() {
191
+ hyper::Version::HTTP_09 => "HTTP/0.9",
192
+ hyper::Version::HTTP_10 => "HTTP/1.0",
193
+ hyper::Version::HTTP_11 => "HTTP/1.1",
194
+ hyper::Version::HTTP_2 => "HTTP/2.0",
195
+ hyper::Version::HTTP_3 => "HTTP/3.0",
196
+ _ => "HTTP/Unknown",
197
+ });
198
+ let log_referrer = match request.headers().get(header::REFERER) {
199
+ Some(header_value) => match header_value.to_str() {
200
+ Ok(header_value) => Some(String::from(header_value)),
201
+ Err(_) => None,
202
+ },
203
+ None => None,
204
+ };
205
+ let log_user_agent = match request.headers().get(header::USER_AGENT) {
206
+ Some(header_value) => match header_value.to_str() {
207
+ Ok(header_value) => Some(String::from(header_value)),
208
+ Err(_) => None,
209
+ },
210
+ None => None,
211
+ };
212
+ let log_enabled = config["global"]["logFilePath"].as_str().is_some();
213
+ let error_log_enabled = config["global"]["errorLogFilePath"].as_str().is_some();
214
+
215
+ // Construct SocketData
216
+ let mut socket_data = SocketData::new(remote_address, local_address, encrypted);
217
+
218
+ match request.version() {
219
+ hyper::Version::HTTP_2 | hyper::Version::HTTP_3 => {
220
+ // Set "Host" request header for HTTP/2 and HTTP/3 connections
221
+ if let Some(authority) = request.uri().authority() {
222
+ let authority = authority.to_owned();
223
+ let headers = request.headers_mut();
224
+ if !headers.contains_key(header::HOST) {
225
+ if let Ok(authority_value) = HeaderValue::from_bytes(authority.as_str().as_bytes()) {
226
+ headers.append(header::HOST, authority_value);
227
+ }
228
+ }
229
+ }
230
+
231
+ // Normalize the Cookie header for HTTP/2 and HTTP/3
232
+ let mut cookie_normalized = String::new();
233
+ let mut cookie_set = false;
234
+ let headers = request.headers_mut();
235
+ for cookie in headers.get_all(header::COOKIE) {
236
+ if let Ok(cookie) = cookie.to_str() {
237
+ if cookie_set {
238
+ cookie_normalized.push_str("; ");
239
+ }
240
+ cookie_set = true;
241
+ cookie_normalized.push_str(cookie);
242
+ }
243
+ }
244
+ if cookie_set {
245
+ if let Ok(cookie_value) = HeaderValue::from_bytes(cookie_normalized.as_bytes()) {
246
+ headers.insert(header::COOKIE, cookie_value);
247
+ }
248
+ }
249
+ }
250
+ _ => (),
251
+ }
252
+
253
+ let host_header_option = request.headers().get(header::HOST);
254
+ if let Some(header_data) = host_header_option {
255
+ match header_data.to_str() {
256
+ Ok(host_header) => {
257
+ let host_header_lower_case = host_header.to_lowercase();
258
+ if host_header_lower_case != *host_header {
259
+ let host_header_value = match HeaderValue::from_str(&host_header_lower_case) {
260
+ Ok(host_header_value) => host_header_value,
261
+ Err(err) => {
262
+ if error_log_enabled {
263
+ logger
264
+ .send(LogMessage::new(
265
+ format!("Host header sanitation error: {}", err),
266
+ true,
267
+ ))
268
+ .await
269
+ .unwrap_or_default();
270
+ }
271
+ let response = Response::builder()
272
+ .status(StatusCode::BAD_REQUEST)
273
+ .header(header::CONTENT_TYPE, "text/html")
274
+ .body(
275
+ Full::new(Bytes::from(generate_default_error_page(
276
+ StatusCode::BAD_REQUEST,
277
+ None,
278
+ )))
279
+ .map_err(|e| match e {})
280
+ .boxed(),
281
+ )
282
+ .unwrap_or_default();
283
+
284
+ if log_enabled {
285
+ log_combined(
286
+ &logger,
287
+ socket_data.remote_addr.ip(),
288
+ None,
289
+ log_method,
290
+ log_request_path,
291
+ log_protocol,
292
+ response.status().as_u16(),
293
+ match response.headers().get(header::CONTENT_LENGTH) {
294
+ Some(header_value) => match header_value.to_str() {
295
+ Ok(header_value) => match header_value.parse::<u64>() {
296
+ Ok(content_length) => Some(content_length),
297
+ Err(_) => response.body().size_hint().exact(),
298
+ },
299
+ Err(_) => response.body().size_hint().exact(),
300
+ },
301
+ None => response.body().size_hint().exact(),
302
+ },
303
+ log_referrer,
304
+ log_user_agent,
305
+ )
306
+ .await;
307
+ }
308
+ let (mut response_parts, response_body) = response.into_parts();
309
+ if let Some(http3_alt_port) = http3_alt_port {
310
+ if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
311
+ Some(value) => HeaderValue::from_bytes(
312
+ format!(
313
+ "{}, h3=\":{}\", h3-29=\":{}\"",
314
+ String::from_utf8_lossy(value.as_bytes()),
315
+ http3_alt_port,
316
+ http3_alt_port
317
+ )
318
+ .as_bytes(),
319
+ ),
320
+ None => HeaderValue::from_bytes(
321
+ format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
322
+ ),
323
+ } {
324
+ response_parts.headers.insert(header::ALT_SVC, header_value);
325
+ }
326
+ }
327
+ response_parts
328
+ .headers
329
+ .insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
330
+
331
+ return Ok(Response::from_parts(response_parts, response_body));
332
+ }
333
+ };
334
+
335
+ request
336
+ .headers_mut()
337
+ .insert(header::HOST, host_header_value);
338
+ }
339
+ }
340
+ Err(err) => {
341
+ if error_log_enabled {
342
+ logger
343
+ .send(LogMessage::new(
344
+ format!("Host header sanitation error: {}", err),
345
+ true,
346
+ ))
347
+ .await
348
+ .unwrap_or_default();
349
+ }
350
+ let response = Response::builder()
351
+ .status(StatusCode::BAD_REQUEST)
352
+ .header(header::CONTENT_TYPE, "text/html")
353
+ .body(
354
+ Full::new(Bytes::from(generate_default_error_page(
355
+ StatusCode::BAD_REQUEST,
356
+ None,
357
+ )))
358
+ .map_err(|e| match e {})
359
+ .boxed(),
360
+ )
361
+ .unwrap_or_default();
362
+ if log_enabled {
363
+ log_combined(
364
+ &logger,
365
+ socket_data.remote_addr.ip(),
366
+ None,
367
+ log_method,
368
+ log_request_path,
369
+ log_protocol,
370
+ response.status().as_u16(),
371
+ match response.headers().get(header::CONTENT_LENGTH) {
372
+ Some(header_value) => match header_value.to_str() {
373
+ Ok(header_value) => match header_value.parse::<u64>() {
374
+ Ok(content_length) => Some(content_length),
375
+ Err(_) => response.body().size_hint().exact(),
376
+ },
377
+ Err(_) => response.body().size_hint().exact(),
378
+ },
379
+ None => response.body().size_hint().exact(),
380
+ },
381
+ log_referrer,
382
+ log_user_agent,
383
+ )
384
+ .await;
385
+ }
386
+ let (mut response_parts, response_body) = response.into_parts();
387
+ if let Some(http3_alt_port) = http3_alt_port {
388
+ if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
389
+ Some(value) => HeaderValue::from_bytes(
390
+ format!(
391
+ "{}, h3=\":{}\", h3-29=\":{}\"",
392
+ String::from_utf8_lossy(value.as_bytes()),
393
+ http3_alt_port,
394
+ http3_alt_port
395
+ )
396
+ .as_bytes(),
397
+ ),
398
+ None => HeaderValue::from_bytes(
399
+ format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
400
+ ),
401
+ } {
402
+ response_parts.headers.insert(header::ALT_SVC, header_value);
403
+ }
404
+ }
405
+ response_parts
406
+ .headers
407
+ .insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
408
+
409
+ return Ok(Response::from_parts(response_parts, response_body));
410
+ }
411
+ }
412
+ };
413
+
414
+ // Combine the server configuration
415
+ let combined_config = match combine_config(
416
+ config,
417
+ match is_proxy_request || is_connect_proxy_request {
418
+ false => match request.headers().get(header::HOST) {
419
+ Some(value) => value.to_str().ok(),
420
+ None => None,
421
+ },
422
+ true => None,
423
+ },
424
+ local_address.ip(),
425
+ request.uri().path(),
426
+ ) {
427
+ Some(config) => config,
428
+ None => {
429
+ if error_log_enabled {
430
+ logger
431
+ .send(LogMessage::new(
432
+ String::from("Cannot determine server configuration"),
433
+ true,
434
+ ))
435
+ .await
436
+ .unwrap_or_default();
437
+ }
438
+ let response = Response::builder()
439
+ .status(StatusCode::INTERNAL_SERVER_ERROR)
440
+ .header(header::CONTENT_TYPE, "text/html")
441
+ .body(
442
+ Full::new(Bytes::from(generate_default_error_page(
443
+ StatusCode::INTERNAL_SERVER_ERROR,
444
+ None,
445
+ )))
446
+ .map_err(|e| match e {})
447
+ .boxed(),
448
+ )
449
+ .unwrap_or_default();
450
+ if log_enabled {
451
+ log_combined(
452
+ &logger,
453
+ socket_data.remote_addr.ip(),
454
+ None,
455
+ log_method,
456
+ log_request_path,
457
+ log_protocol,
458
+ response.status().as_u16(),
459
+ match response.headers().get(header::CONTENT_LENGTH) {
460
+ Some(header_value) => match header_value.to_str() {
461
+ Ok(header_value) => match header_value.parse::<u64>() {
462
+ Ok(content_length) => Some(content_length),
463
+ Err(_) => response.body().size_hint().exact(),
464
+ },
465
+ Err(_) => response.body().size_hint().exact(),
466
+ },
467
+ None => response.body().size_hint().exact(),
468
+ },
469
+ log_referrer,
470
+ log_user_agent,
471
+ )
472
+ .await;
473
+ }
474
+ let (mut response_parts, response_body) = response.into_parts();
475
+ if let Some(http3_alt_port) = http3_alt_port {
476
+ if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
477
+ Some(value) => HeaderValue::from_bytes(
478
+ format!(
479
+ "{}, h3=\":{}\", h3-29=\":{}\"",
480
+ String::from_utf8_lossy(value.as_bytes()),
481
+ http3_alt_port,
482
+ http3_alt_port
483
+ )
484
+ .as_bytes(),
485
+ ),
486
+ None => HeaderValue::from_bytes(
487
+ format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
488
+ ),
489
+ } {
490
+ response_parts.headers.insert(header::ALT_SVC, header_value);
491
+ }
492
+ }
493
+ response_parts
494
+ .headers
495
+ .insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
496
+
497
+ return Ok(Response::from_parts(response_parts, response_body));
498
+ }
499
+ };
500
+
501
+ let url_pathname = request.uri().path();
502
+ let sanitized_url_pathname = match sanitize_url(
503
+ url_pathname,
504
+ combined_config["allowDoubleSlashes"]
505
+ .as_bool()
506
+ .unwrap_or_default(),
507
+ ) {
508
+ Ok(sanitized_url) => sanitized_url,
509
+ Err(err) => {
510
+ if error_log_enabled {
511
+ logger
512
+ .send(LogMessage::new(
513
+ format!("URL sanitation error: {}", err),
514
+ true,
515
+ ))
516
+ .await
517
+ .unwrap_or_default();
518
+ }
519
+ let response =
520
+ generate_error_response(StatusCode::BAD_REQUEST, &combined_config, &None).await;
521
+ if log_enabled {
522
+ log_combined(
523
+ &logger,
524
+ socket_data.remote_addr.ip(),
525
+ None,
526
+ log_method,
527
+ log_request_path,
528
+ log_protocol,
529
+ response.status().as_u16(),
530
+ match response.headers().get(header::CONTENT_LENGTH) {
531
+ Some(header_value) => match header_value.to_str() {
532
+ Ok(header_value) => match header_value.parse::<u64>() {
533
+ Ok(content_length) => Some(content_length),
534
+ Err(_) => response.body().size_hint().exact(),
535
+ },
536
+ Err(_) => response.body().size_hint().exact(),
537
+ },
538
+ None => response.body().size_hint().exact(),
539
+ },
540
+ log_referrer,
541
+ log_user_agent,
542
+ )
543
+ .await;
544
+ }
545
+ let (mut response_parts, response_body) = response.into_parts();
546
+ if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
547
+ let custom_headers_hash_iter = custom_headers_hash.iter();
548
+ for (header_name, header_value) in custom_headers_hash_iter {
549
+ if let Some(header_name) = header_name.as_str() {
550
+ if let Some(header_value) = header_value.as_str() {
551
+ if !response_parts.headers.contains_key(header_name) {
552
+ if let Ok(header_value) =
553
+ HeaderValue::from_str(&header_value.replace("{path}", url_pathname))
554
+ {
555
+ if let Ok(header_name) = HeaderName::from_str(header_name) {
556
+ response_parts.headers.insert(header_name, header_value);
557
+ }
558
+ }
559
+ }
560
+ }
561
+ }
562
+ }
563
+ }
564
+ if let Some(http3_alt_port) = http3_alt_port {
565
+ if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
566
+ Some(value) => HeaderValue::from_bytes(
567
+ format!(
568
+ "{}, h3=\":{}\", h3-29=\":{}\"",
569
+ String::from_utf8_lossy(value.as_bytes()),
570
+ http3_alt_port,
571
+ http3_alt_port
572
+ )
573
+ .as_bytes(),
574
+ ),
575
+ None => HeaderValue::from_bytes(
576
+ format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
577
+ ),
578
+ } {
579
+ response_parts.headers.insert(header::ALT_SVC, header_value);
580
+ }
581
+ }
582
+ response_parts
583
+ .headers
584
+ .insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
585
+
586
+ return Ok(Response::from_parts(response_parts, response_body));
587
+ }
588
+ };
589
+
590
+ if sanitized_url_pathname != url_pathname {
591
+ let (mut parts, body) = request.into_parts();
592
+ let mut url_parts = parts.uri.into_parts();
593
+ url_parts.path_and_query = Some(
594
+ match format!(
595
+ "{}{}",
596
+ sanitized_url_pathname,
597
+ match url_parts.path_and_query {
598
+ Some(path_and_query) => {
599
+ match path_and_query.query() {
600
+ Some(query) => format!("?{}", query),
601
+ None => String::from(""),
602
+ }
603
+ }
604
+ None => String::from(""),
605
+ }
606
+ )
607
+ .parse()
608
+ {
609
+ Ok(path_and_query) => path_and_query,
610
+ Err(err) => {
611
+ if error_log_enabled {
612
+ logger
613
+ .send(LogMessage::new(
614
+ format!("URL sanitation error: {}", err),
615
+ true,
616
+ ))
617
+ .await
618
+ .unwrap_or_default();
619
+ }
620
+ let response =
621
+ generate_error_response(StatusCode::BAD_REQUEST, &combined_config, &None).await;
622
+ if log_enabled {
623
+ log_combined(
624
+ &logger,
625
+ socket_data.remote_addr.ip(),
626
+ None,
627
+ log_method,
628
+ log_request_path,
629
+ log_protocol,
630
+ response.status().as_u16(),
631
+ match response.headers().get(header::CONTENT_LENGTH) {
632
+ Some(header_value) => match header_value.to_str() {
633
+ Ok(header_value) => match header_value.parse::<u64>() {
634
+ Ok(content_length) => Some(content_length),
635
+ Err(_) => response.body().size_hint().exact(),
636
+ },
637
+ Err(_) => response.body().size_hint().exact(),
638
+ },
639
+ None => response.body().size_hint().exact(),
640
+ },
641
+ log_referrer,
642
+ log_user_agent,
643
+ )
644
+ .await;
645
+ }
646
+ let (mut response_parts, response_body) = response.into_parts();
647
+ if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
648
+ let custom_headers_hash_iter = custom_headers_hash.iter();
649
+ for (header_name, header_value) in custom_headers_hash_iter {
650
+ if let Some(header_name) = header_name.as_str() {
651
+ if let Some(header_value) = header_value.as_str() {
652
+ if !response_parts.headers.contains_key(header_name) {
653
+ if let Ok(header_value) = HeaderValue::from_str(
654
+ &header_value.replace("{path}", &sanitized_url_pathname),
655
+ ) {
656
+ if let Ok(header_name) = HeaderName::from_str(header_name) {
657
+ response_parts.headers.insert(header_name, header_value);
658
+ }
659
+ }
660
+ }
661
+ }
662
+ }
663
+ }
664
+ }
665
+ if let Some(http3_alt_port) = http3_alt_port {
666
+ if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
667
+ Some(value) => HeaderValue::from_bytes(
668
+ format!(
669
+ "{}, h3=\":{}\", h3-29=\":{}\"",
670
+ String::from_utf8_lossy(value.as_bytes()),
671
+ http3_alt_port,
672
+ http3_alt_port
673
+ )
674
+ .as_bytes(),
675
+ ),
676
+ None => HeaderValue::from_bytes(
677
+ format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
678
+ ),
679
+ } {
680
+ response_parts.headers.insert(header::ALT_SVC, header_value);
681
+ }
682
+ }
683
+ response_parts
684
+ .headers
685
+ .insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
686
+
687
+ return Ok(Response::from_parts(response_parts, response_body));
688
+ }
689
+ },
690
+ );
691
+ parts.uri = match hyper::Uri::from_parts(url_parts) {
692
+ Ok(uri) => uri,
693
+ Err(err) => {
694
+ if error_log_enabled {
695
+ logger
696
+ .send(LogMessage::new(
697
+ format!("URL sanitation error: {}", err),
698
+ true,
699
+ ))
700
+ .await
701
+ .unwrap_or_default();
702
+ }
703
+ let response =
704
+ generate_error_response(StatusCode::BAD_REQUEST, &combined_config, &None).await;
705
+ if log_enabled {
706
+ log_combined(
707
+ &logger,
708
+ socket_data.remote_addr.ip(),
709
+ None,
710
+ log_method,
711
+ log_request_path,
712
+ log_protocol,
713
+ response.status().as_u16(),
714
+ match response.headers().get(header::CONTENT_LENGTH) {
715
+ Some(header_value) => match header_value.to_str() {
716
+ Ok(header_value) => match header_value.parse::<u64>() {
717
+ Ok(content_length) => Some(content_length),
718
+ Err(_) => response.body().size_hint().exact(),
719
+ },
720
+ Err(_) => response.body().size_hint().exact(),
721
+ },
722
+ None => response.body().size_hint().exact(),
723
+ },
724
+ log_referrer,
725
+ log_user_agent,
726
+ )
727
+ .await;
728
+ }
729
+ let (mut response_parts, response_body) = response.into_parts();
730
+ if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
731
+ let custom_headers_hash_iter = custom_headers_hash.iter();
732
+ for (header_name, header_value) in custom_headers_hash_iter {
733
+ if let Some(header_name) = header_name.as_str() {
734
+ if let Some(header_value) = header_value.as_str() {
735
+ if !response_parts.headers.contains_key(header_name) {
736
+ if let Ok(header_value) =
737
+ HeaderValue::from_str(&header_value.replace("{path}", &sanitized_url_pathname))
738
+ {
739
+ if let Ok(header_name) = HeaderName::from_str(header_name) {
740
+ response_parts.headers.insert(header_name, header_value);
741
+ }
742
+ }
743
+ }
744
+ }
745
+ }
746
+ }
747
+ }
748
+ if let Some(http3_alt_port) = http3_alt_port {
749
+ if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
750
+ Some(value) => HeaderValue::from_bytes(
751
+ format!(
752
+ "{}, h3=\":{}\", h3-29=\":{}\"",
753
+ String::from_utf8_lossy(value.as_bytes()),
754
+ http3_alt_port,
755
+ http3_alt_port
756
+ )
757
+ .as_bytes(),
758
+ ),
759
+ None => HeaderValue::from_bytes(
760
+ format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
761
+ ),
762
+ } {
763
+ response_parts.headers.insert(header::ALT_SVC, header_value);
764
+ }
765
+ }
766
+ response_parts
767
+ .headers
768
+ .insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
769
+
770
+ return Ok(Response::from_parts(response_parts, response_body));
771
+ }
772
+ };
773
+ request = Request::from_parts(parts, body);
774
+ }
775
+
776
+ if request.uri().path() == "*" {
777
+ let response = match request.method() {
778
+ &Method::OPTIONS => Response::builder()
779
+ .status(StatusCode::NO_CONTENT)
780
+ .header(header::ALLOW, "GET, POST, HEAD, OPTIONS")
781
+ .body(Empty::new().map_err(|e| match e {}).boxed())
782
+ .unwrap_or_default(),
783
+ _ => {
784
+ let mut header_map = HeaderMap::new();
785
+ if let Ok(header_value) = HeaderValue::from_str("GET, POST, HEAD, OPTIONS") {
786
+ header_map.insert(header::ALLOW, header_value);
787
+ };
788
+ generate_error_response(StatusCode::BAD_REQUEST, &combined_config, &Some(header_map)).await
789
+ }
790
+ };
791
+ if log_enabled {
792
+ log_combined(
793
+ &logger,
794
+ socket_data.remote_addr.ip(),
795
+ None,
796
+ log_method,
797
+ log_request_path,
798
+ log_protocol,
799
+ response.status().as_u16(),
800
+ match response.headers().get(header::CONTENT_LENGTH) {
801
+ Some(header_value) => match header_value.to_str() {
802
+ Ok(header_value) => match header_value.parse::<u64>() {
803
+ Ok(content_length) => Some(content_length),
804
+ Err(_) => response.body().size_hint().exact(),
805
+ },
806
+ Err(_) => response.body().size_hint().exact(),
807
+ },
808
+ None => response.body().size_hint().exact(),
809
+ },
810
+ log_referrer,
811
+ log_user_agent,
812
+ )
813
+ .await;
814
+ }
815
+ let (mut response_parts, response_body) = response.into_parts();
816
+ if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
817
+ let custom_headers_hash_iter = custom_headers_hash.iter();
818
+ for (header_name, header_value) in custom_headers_hash_iter {
819
+ if let Some(header_name) = header_name.as_str() {
820
+ if let Some(header_value) = header_value.as_str() {
821
+ if !response_parts.headers.contains_key(header_name) {
822
+ if let Ok(header_value) =
823
+ HeaderValue::from_str(&header_value.replace("{path}", &sanitized_url_pathname))
824
+ {
825
+ if let Ok(header_name) = HeaderName::from_str(header_name) {
826
+ response_parts.headers.insert(header_name, header_value);
827
+ }
828
+ }
829
+ }
830
+ }
831
+ }
832
+ }
833
+ }
834
+ if let Some(http3_alt_port) = http3_alt_port {
835
+ if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
836
+ Some(value) => HeaderValue::from_bytes(
837
+ format!(
838
+ "{}, h3=\":{}\", h3-29=\":{}\"",
839
+ String::from_utf8_lossy(value.as_bytes()),
840
+ http3_alt_port,
841
+ http3_alt_port
842
+ )
843
+ .as_bytes(),
844
+ ),
845
+ None => HeaderValue::from_bytes(
846
+ format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
847
+ ),
848
+ } {
849
+ response_parts.headers.insert(header::ALT_SVC, header_value);
850
+ }
851
+ }
852
+ response_parts
853
+ .headers
854
+ .insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
855
+
856
+ return Ok(Response::from_parts(response_parts, response_body));
857
+ }
858
+
859
+ // HTTP-01 ACME challenge for automatic TLS
860
+ if let Some(acme_http01_resolver) = acme_http01_resolver_option {
861
+ if let Some(challenge_token) = request
862
+ .uri()
863
+ .path()
864
+ .strip_prefix("/.well-known/acme-challenge/")
865
+ {
866
+ if let Some(acme_response) = acme_http01_resolver.get_http_01_key_auth(challenge_token) {
867
+ let response = Response::builder()
868
+ .status(StatusCode::OK)
869
+ .header(
870
+ CONTENT_TYPE,
871
+ HeaderValue::from_static("application/octet-stream"),
872
+ )
873
+ .body(
874
+ Full::new(Bytes::from(acme_response))
875
+ .map_err(|e| match e {})
876
+ .boxed(),
877
+ )
878
+ .unwrap_or_default();
879
+
880
+ let (mut response_parts, response_body) = response.into_parts();
881
+ if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
882
+ let custom_headers_hash_iter = custom_headers_hash.iter();
883
+ for (header_name, header_value) in custom_headers_hash_iter {
884
+ if let Some(header_name) = header_name.as_str() {
885
+ if let Some(header_value) = header_value.as_str() {
886
+ if !response_parts.headers.contains_key(header_name) {
887
+ if let Ok(header_value) =
888
+ HeaderValue::from_str(&header_value.replace("{path}", &sanitized_url_pathname))
889
+ {
890
+ if let Ok(header_name) = HeaderName::from_str(header_name) {
891
+ response_parts.headers.insert(header_name, header_value);
892
+ }
893
+ }
894
+ }
895
+ }
896
+ }
897
+ }
898
+ }
899
+ if let Some(http3_alt_port) = http3_alt_port {
900
+ if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
901
+ Some(value) => HeaderValue::from_bytes(
902
+ format!(
903
+ "{}, h3=\":{}\", h3-29=\":{}\"",
904
+ String::from_utf8_lossy(value.as_bytes()),
905
+ http3_alt_port,
906
+ http3_alt_port
907
+ )
908
+ .as_bytes(),
909
+ ),
910
+ None => HeaderValue::from_bytes(
911
+ format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
912
+ ),
913
+ } {
914
+ response_parts.headers.insert(header::ALT_SVC, header_value);
915
+ }
916
+ }
917
+ response_parts
918
+ .headers
919
+ .insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
920
+
921
+ let response = Response::from_parts(response_parts, response_body);
922
+
923
+ if log_enabled {
924
+ log_combined(
925
+ &logger,
926
+ socket_data.remote_addr.ip(),
927
+ None,
928
+ log_method,
929
+ log_request_path,
930
+ log_protocol,
931
+ response.status().as_u16(),
932
+ match response.headers().get(header::CONTENT_LENGTH) {
933
+ Some(header_value) => match header_value.to_str() {
934
+ Ok(header_value) => match header_value.parse::<u64>() {
935
+ Ok(content_length) => Some(content_length),
936
+ Err(_) => response.body().size_hint().exact(),
937
+ },
938
+ Err(_) => response.body().size_hint().exact(),
939
+ },
940
+ None => response.body().size_hint().exact(),
941
+ },
942
+ log_referrer,
943
+ log_user_agent,
944
+ )
945
+ .await;
946
+ }
947
+ return Ok(response);
948
+ }
949
+ }
950
+ };
951
+
952
+ let cloned_logger = logger.clone();
953
+ let error_logger = match error_log_enabled {
954
+ true => ErrorLogger::new(cloned_logger),
955
+ false => ErrorLogger::without_logger(),
956
+ };
957
+
958
+ if is_connect_proxy_request {
959
+ let mut connect_proxy_handlers = None;
960
+ for mut handlers in handlers_vec {
961
+ if handlers.does_connect_proxy_requests() {
962
+ connect_proxy_handlers = Some(handlers);
963
+ break;
964
+ }
965
+ }
966
+
967
+ if let Some(mut connect_proxy_handlers) = connect_proxy_handlers {
968
+ if let Some(connect_address) = request.uri().authority().map(|auth| auth.to_string()) {
969
+ // Variables moved to before "tokio::spawn" to avoid issues with moved values
970
+ let client_ip = socket_data.remote_addr.ip();
971
+ let custom_headers_yaml = combined_config["customHeaders"].clone();
972
+
973
+ tokio::spawn(async move {
974
+ match hyper::upgrade::on(request).await {
975
+ Ok(upgraded_request) => {
976
+ let result = connect_proxy_handlers
977
+ .connect_proxy_request_handler(
978
+ upgraded_request,
979
+ &connect_address,
980
+ &combined_config,
981
+ &socket_data,
982
+ &error_logger,
983
+ )
984
+ .await;
985
+ match result {
986
+ Ok(_) => (),
987
+ Err(err) => {
988
+ error_logger
989
+ .log(&format!("Unexpected error for CONNECT request: {}", err))
990
+ .await;
991
+ }
992
+ }
993
+ }
994
+ Err(err) => {
995
+ error_logger
996
+ .log(&format!(
997
+ "Error while upgrading HTTP CONNECT request: {}",
998
+ err
999
+ ))
1000
+ .await
1001
+ }
1002
+ }
1003
+ });
1004
+
1005
+ let response = Response::builder()
1006
+ .body(Empty::new().map_err(|e| match e {}).boxed())
1007
+ .unwrap_or_default();
1008
+
1009
+ if log_enabled {
1010
+ log_combined(
1011
+ &logger,
1012
+ client_ip,
1013
+ None,
1014
+ log_method,
1015
+ log_request_path,
1016
+ log_protocol,
1017
+ response.status().as_u16(),
1018
+ match response.headers().get(header::CONTENT_LENGTH) {
1019
+ Some(header_value) => match header_value.to_str() {
1020
+ Ok(header_value) => match header_value.parse::<u64>() {
1021
+ Ok(content_length) => Some(content_length),
1022
+ Err(_) => response.body().size_hint().exact(),
1023
+ },
1024
+ Err(_) => response.body().size_hint().exact(),
1025
+ },
1026
+ None => response.body().size_hint().exact(),
1027
+ },
1028
+ log_referrer,
1029
+ log_user_agent,
1030
+ )
1031
+ .await;
1032
+ }
1033
+
1034
+ let (mut response_parts, response_body) = response.into_parts();
1035
+ if let Some(custom_headers_hash) = custom_headers_yaml.as_hash() {
1036
+ let custom_headers_hash_iter = custom_headers_hash.iter();
1037
+ for (header_name, header_value) in custom_headers_hash_iter {
1038
+ if let Some(header_name) = header_name.as_str() {
1039
+ if let Some(header_value) = header_value.as_str() {
1040
+ if !response_parts.headers.contains_key(header_name) {
1041
+ if let Ok(header_value) =
1042
+ HeaderValue::from_str(&header_value.replace("{path}", &sanitized_url_pathname))
1043
+ {
1044
+ if let Ok(header_name) = HeaderName::from_str(header_name) {
1045
+ response_parts.headers.insert(header_name, header_value);
1046
+ }
1047
+ }
1048
+ }
1049
+ }
1050
+ }
1051
+ }
1052
+ }
1053
+ if let Some(http3_alt_port) = http3_alt_port {
1054
+ if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
1055
+ Some(value) => HeaderValue::from_bytes(
1056
+ format!(
1057
+ "{}, h3=\":{}\", h3-29=\":{}\"",
1058
+ String::from_utf8_lossy(value.as_bytes()),
1059
+ http3_alt_port,
1060
+ http3_alt_port
1061
+ )
1062
+ .as_bytes(),
1063
+ ),
1064
+ None => HeaderValue::from_bytes(
1065
+ format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
1066
+ ),
1067
+ } {
1068
+ response_parts.headers.insert(header::ALT_SVC, header_value);
1069
+ }
1070
+ }
1071
+ response_parts
1072
+ .headers
1073
+ .insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
1074
+
1075
+ Ok(Response::from_parts(response_parts, response_body))
1076
+ } else {
1077
+ let response = Response::builder()
1078
+ .status(StatusCode::BAD_REQUEST)
1079
+ .body(Empty::new().map_err(|e| match e {}).boxed())
1080
+ .unwrap_or_default();
1081
+
1082
+ if log_enabled {
1083
+ log_combined(
1084
+ &logger,
1085
+ socket_data.remote_addr.ip(),
1086
+ None,
1087
+ log_method,
1088
+ log_request_path,
1089
+ log_protocol,
1090
+ response.status().as_u16(),
1091
+ match response.headers().get(header::CONTENT_LENGTH) {
1092
+ Some(header_value) => match header_value.to_str() {
1093
+ Ok(header_value) => match header_value.parse::<u64>() {
1094
+ Ok(content_length) => Some(content_length),
1095
+ Err(_) => response.body().size_hint().exact(),
1096
+ },
1097
+ Err(_) => response.body().size_hint().exact(),
1098
+ },
1099
+ None => response.body().size_hint().exact(),
1100
+ },
1101
+ log_referrer,
1102
+ log_user_agent,
1103
+ )
1104
+ .await;
1105
+ }
1106
+ let (mut response_parts, response_body) = response.into_parts();
1107
+ if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
1108
+ let custom_headers_hash_iter = custom_headers_hash.iter();
1109
+ for (header_name, header_value) in custom_headers_hash_iter {
1110
+ if let Some(header_name) = header_name.as_str() {
1111
+ if let Some(header_value) = header_value.as_str() {
1112
+ if !response_parts.headers.contains_key(header_name) {
1113
+ if let Ok(header_value) =
1114
+ HeaderValue::from_str(&header_value.replace("{path}", &sanitized_url_pathname))
1115
+ {
1116
+ if let Ok(header_name) = HeaderName::from_str(header_name) {
1117
+ response_parts.headers.insert(header_name, header_value);
1118
+ }
1119
+ }
1120
+ }
1121
+ }
1122
+ }
1123
+ }
1124
+ }
1125
+ if let Some(http3_alt_port) = http3_alt_port {
1126
+ if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
1127
+ Some(value) => HeaderValue::from_bytes(
1128
+ format!(
1129
+ "{}, h3=\":{}\", h3-29=\":{}\"",
1130
+ String::from_utf8_lossy(value.as_bytes()),
1131
+ http3_alt_port,
1132
+ http3_alt_port
1133
+ )
1134
+ .as_bytes(),
1135
+ ),
1136
+ None => HeaderValue::from_bytes(
1137
+ format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
1138
+ ),
1139
+ } {
1140
+ response_parts.headers.insert(header::ALT_SVC, header_value);
1141
+ }
1142
+ }
1143
+ response_parts
1144
+ .headers
1145
+ .insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
1146
+
1147
+ Ok(Response::from_parts(response_parts, response_body))
1148
+ }
1149
+ } else {
1150
+ let response = Response::builder()
1151
+ .status(StatusCode::NOT_IMPLEMENTED)
1152
+ .body(Empty::new().map_err(|e| match e {}).boxed())
1153
+ .unwrap_or_default();
1154
+
1155
+ if log_enabled {
1156
+ log_combined(
1157
+ &logger,
1158
+ socket_data.remote_addr.ip(),
1159
+ None,
1160
+ log_method,
1161
+ log_request_path,
1162
+ log_protocol,
1163
+ response.status().as_u16(),
1164
+ match response.headers().get(header::CONTENT_LENGTH) {
1165
+ Some(header_value) => match header_value.to_str() {
1166
+ Ok(header_value) => match header_value.parse::<u64>() {
1167
+ Ok(content_length) => Some(content_length),
1168
+ Err(_) => response.body().size_hint().exact(),
1169
+ },
1170
+ Err(_) => response.body().size_hint().exact(),
1171
+ },
1172
+ None => response.body().size_hint().exact(),
1173
+ },
1174
+ log_referrer,
1175
+ log_user_agent,
1176
+ )
1177
+ .await;
1178
+ }
1179
+ let (mut response_parts, response_body) = response.into_parts();
1180
+ if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
1181
+ let custom_headers_hash_iter = custom_headers_hash.iter();
1182
+ for (header_name, header_value) in custom_headers_hash_iter {
1183
+ if let Some(header_name) = header_name.as_str() {
1184
+ if let Some(header_value) = header_value.as_str() {
1185
+ if !response_parts.headers.contains_key(header_name) {
1186
+ if let Ok(header_value) =
1187
+ HeaderValue::from_str(&header_value.replace("{path}", &sanitized_url_pathname))
1188
+ {
1189
+ if let Ok(header_name) = HeaderName::from_str(header_name) {
1190
+ response_parts.headers.insert(header_name, header_value);
1191
+ }
1192
+ }
1193
+ }
1194
+ }
1195
+ }
1196
+ }
1197
+ }
1198
+ if let Some(http3_alt_port) = http3_alt_port {
1199
+ if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
1200
+ Some(value) => HeaderValue::from_bytes(
1201
+ format!(
1202
+ "{}, h3=\":{}\", h3-29=\":{}\"",
1203
+ String::from_utf8_lossy(value.as_bytes()),
1204
+ http3_alt_port,
1205
+ http3_alt_port
1206
+ )
1207
+ .as_bytes(),
1208
+ ),
1209
+ None => HeaderValue::from_bytes(
1210
+ format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
1211
+ ),
1212
+ } {
1213
+ response_parts.headers.insert(header::ALT_SVC, header_value);
1214
+ }
1215
+ }
1216
+ response_parts
1217
+ .headers
1218
+ .insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
1219
+
1220
+ Ok(Response::from_parts(response_parts, response_body))
1221
+ }
1222
+ } else {
1223
+ let is_websocket_request = is_upgrade_request(&request);
1224
+ let mut request_data = RequestData::new(request, None, None);
1225
+ let mut latest_auth_data = None;
1226
+ let mut executed_handlers = Vec::new();
1227
+ for mut handlers in handlers_vec {
1228
+ if is_websocket_request && handlers.does_websocket_requests(&combined_config, &socket_data) {
1229
+ let (request, _, _) = request_data.into_parts();
1230
+
1231
+ // Variables moved to before "tokio::spawn" to avoid issues with moved values
1232
+ let client_ip = socket_data.remote_addr.ip();
1233
+ let custom_headers_yaml = combined_config["customHeaders"].clone();
1234
+ let request_uri = request.uri().to_owned();
1235
+
1236
+ let (original_response, websocket) = match hyper_tungstenite::upgrade(request, None) {
1237
+ Ok(data) => data,
1238
+ Err(err) => {
1239
+ error_logger
1240
+ .log(&format!("Error while upgrading WebSocket request: {}", err))
1241
+ .await;
1242
+ let response = Response::builder()
1243
+ .status(StatusCode::INTERNAL_SERVER_ERROR)
1244
+ .body(
1245
+ Full::new(Bytes::from(generate_default_error_page(
1246
+ StatusCode::INTERNAL_SERVER_ERROR,
1247
+ None,
1248
+ )))
1249
+ .map_err(|e| match e {})
1250
+ .boxed(),
1251
+ )
1252
+ .unwrap_or_default();
1253
+
1254
+ if log_enabled {
1255
+ log_combined(
1256
+ &logger,
1257
+ socket_data.remote_addr.ip(),
1258
+ None,
1259
+ log_method,
1260
+ log_request_path,
1261
+ log_protocol,
1262
+ response.status().as_u16(),
1263
+ match response.headers().get(header::CONTENT_LENGTH) {
1264
+ Some(header_value) => match header_value.to_str() {
1265
+ Ok(header_value) => match header_value.parse::<u64>() {
1266
+ Ok(content_length) => Some(content_length),
1267
+ Err(_) => response.body().size_hint().exact(),
1268
+ },
1269
+ Err(_) => response.body().size_hint().exact(),
1270
+ },
1271
+ None => response.body().size_hint().exact(),
1272
+ },
1273
+ log_referrer,
1274
+ log_user_agent,
1275
+ )
1276
+ .await;
1277
+ }
1278
+ let (mut response_parts, response_body) = response.into_parts();
1279
+ if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
1280
+ let custom_headers_hash_iter = custom_headers_hash.iter();
1281
+ for (header_name, header_value) in custom_headers_hash_iter {
1282
+ if let Some(header_name) = header_name.as_str() {
1283
+ if let Some(header_value) = header_value.as_str() {
1284
+ if !response_parts.headers.contains_key(header_name) {
1285
+ if let Ok(header_value) = HeaderValue::from_str(
1286
+ &header_value.replace("{path}", &sanitized_url_pathname),
1287
+ ) {
1288
+ if let Ok(header_name) = HeaderName::from_str(header_name) {
1289
+ response_parts.headers.insert(header_name, header_value);
1290
+ }
1291
+ }
1292
+ }
1293
+ }
1294
+ }
1295
+ }
1296
+ }
1297
+ if let Some(http3_alt_port) = http3_alt_port {
1298
+ if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
1299
+ Some(value) => HeaderValue::from_bytes(
1300
+ format!(
1301
+ "{}, h3=\":{}\", h3-29=\":{}\"",
1302
+ String::from_utf8_lossy(value.as_bytes()),
1303
+ http3_alt_port,
1304
+ http3_alt_port
1305
+ )
1306
+ .as_bytes(),
1307
+ ),
1308
+ None => HeaderValue::from_bytes(
1309
+ format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
1310
+ ),
1311
+ } {
1312
+ response_parts.headers.insert(header::ALT_SVC, header_value);
1313
+ }
1314
+ }
1315
+ response_parts
1316
+ .headers
1317
+ .insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
1318
+
1319
+ return Ok(Response::from_parts(response_parts, response_body));
1320
+ }
1321
+ };
1322
+
1323
+ tokio::spawn(async move {
1324
+ let result = handlers
1325
+ .websocket_request_handler(
1326
+ websocket,
1327
+ &request_uri,
1328
+ &combined_config,
1329
+ &socket_data,
1330
+ &error_logger,
1331
+ )
1332
+ .await;
1333
+ match result {
1334
+ Ok(_) => (),
1335
+ Err(err) => {
1336
+ error_logger
1337
+ .log(&format!("Unexpected error for WebSocket request: {}", err))
1338
+ .await;
1339
+ }
1340
+ }
1341
+ });
1342
+
1343
+ let response = original_response.map(|body| body.map_err(|err| match err {}).boxed());
1344
+
1345
+ if log_enabled {
1346
+ log_combined(
1347
+ &logger,
1348
+ client_ip,
1349
+ None,
1350
+ log_method,
1351
+ log_request_path,
1352
+ log_protocol,
1353
+ response.status().as_u16(),
1354
+ match response.headers().get(header::CONTENT_LENGTH) {
1355
+ Some(header_value) => match header_value.to_str() {
1356
+ Ok(header_value) => match header_value.parse::<u64>() {
1357
+ Ok(content_length) => Some(content_length),
1358
+ Err(_) => response.body().size_hint().exact(),
1359
+ },
1360
+ Err(_) => response.body().size_hint().exact(),
1361
+ },
1362
+ None => response.body().size_hint().exact(),
1363
+ },
1364
+ log_referrer,
1365
+ log_user_agent,
1366
+ )
1367
+ .await;
1368
+ }
1369
+
1370
+ let (mut response_parts, response_body) = response.into_parts();
1371
+ if let Some(custom_headers_hash) = custom_headers_yaml.as_hash() {
1372
+ let custom_headers_hash_iter = custom_headers_hash.iter();
1373
+ for (header_name, header_value) in custom_headers_hash_iter {
1374
+ if let Some(header_name) = header_name.as_str() {
1375
+ if let Some(header_value) = header_value.as_str() {
1376
+ if !response_parts.headers.contains_key(header_name) {
1377
+ if let Ok(header_value) =
1378
+ HeaderValue::from_str(&header_value.replace("{path}", &sanitized_url_pathname))
1379
+ {
1380
+ if let Ok(header_name) = HeaderName::from_str(header_name) {
1381
+ response_parts.headers.insert(header_name, header_value);
1382
+ }
1383
+ }
1384
+ }
1385
+ }
1386
+ }
1387
+ }
1388
+ }
1389
+ if let Some(http3_alt_port) = http3_alt_port {
1390
+ if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
1391
+ Some(value) => HeaderValue::from_bytes(
1392
+ format!(
1393
+ "{}, h3=\":{}\", h3-29=\":{}\"",
1394
+ String::from_utf8_lossy(value.as_bytes()),
1395
+ http3_alt_port,
1396
+ http3_alt_port
1397
+ )
1398
+ .as_bytes(),
1399
+ ),
1400
+ None => HeaderValue::from_bytes(
1401
+ format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
1402
+ ),
1403
+ } {
1404
+ response_parts.headers.insert(header::ALT_SVC, header_value);
1405
+ }
1406
+ }
1407
+ response_parts
1408
+ .headers
1409
+ .insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
1410
+
1411
+ return Ok(Response::from_parts(response_parts, response_body));
1412
+ }
1413
+
1414
+ let response_result = match is_proxy_request {
1415
+ true => {
1416
+ handlers
1417
+ .proxy_request_handler(request_data, &combined_config, &socket_data, &error_logger)
1418
+ .await
1419
+ }
1420
+ false => {
1421
+ handlers
1422
+ .request_handler(request_data, &combined_config, &socket_data, &error_logger)
1423
+ .await
1424
+ }
1425
+ };
1426
+
1427
+ executed_handlers.push(handlers);
1428
+ match response_result {
1429
+ Ok(response) => {
1430
+ let (
1431
+ request_option,
1432
+ auth_data,
1433
+ original_url,
1434
+ response,
1435
+ status,
1436
+ headers,
1437
+ new_remote_address,
1438
+ parallel_fn,
1439
+ ) = response.into_parts();
1440
+ latest_auth_data = auth_data.clone();
1441
+ if let Some(new_remote_address) = new_remote_address {
1442
+ socket_data.remote_addr = new_remote_address;
1443
+ };
1444
+ if let Some(parallel_fn) = parallel_fn {
1445
+ // Spawn the function in the web server's Tokio runtime.
1446
+ // We have implemented parallel_fn parameter in the ResponseData
1447
+ // because tokio::spawn doesn't work on dynamic libraries,
1448
+ // see https://github.com/tokio-rs/tokio/issues/6927
1449
+ tokio::spawn(parallel_fn);
1450
+ }
1451
+ match response {
1452
+ Some(response) => {
1453
+ let (mut response_parts, response_body) = response.into_parts();
1454
+ if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
1455
+ let custom_headers_hash_iter = custom_headers_hash.iter();
1456
+ for (header_name, header_value) in custom_headers_hash_iter {
1457
+ if let Some(header_name) = header_name.as_str() {
1458
+ if let Some(header_value) = header_value.as_str() {
1459
+ if !response_parts.headers.contains_key(header_name) {
1460
+ if let Ok(header_value) = HeaderValue::from_str(
1461
+ &header_value.replace("{path}", &sanitized_url_pathname),
1462
+ ) {
1463
+ if let Ok(header_name) = HeaderName::from_str(header_name) {
1464
+ response_parts.headers.insert(header_name, header_value);
1465
+ }
1466
+ }
1467
+ }
1468
+ }
1469
+ }
1470
+ }
1471
+ }
1472
+ if let Some(http3_alt_port) = http3_alt_port {
1473
+ if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
1474
+ Some(value) => HeaderValue::from_bytes(
1475
+ format!(
1476
+ "{}, h3=\":{}\", h3-29=\":{}\"",
1477
+ String::from_utf8_lossy(value.as_bytes()),
1478
+ http3_alt_port,
1479
+ http3_alt_port
1480
+ )
1481
+ .as_bytes(),
1482
+ ),
1483
+ None => HeaderValue::from_bytes(
1484
+ format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
1485
+ ),
1486
+ } {
1487
+ response_parts.headers.insert(header::ALT_SVC, header_value);
1488
+ }
1489
+ }
1490
+ response_parts
1491
+ .headers
1492
+ .insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
1493
+
1494
+ let mut response = Response::from_parts(response_parts, response_body);
1495
+
1496
+ while let Some(mut executed_handler) = executed_handlers.pop() {
1497
+ let response_status = match is_proxy_request {
1498
+ true => {
1499
+ executed_handler
1500
+ .proxy_response_modifying_handler(response)
1501
+ .await
1502
+ }
1503
+ false => executed_handler.response_modifying_handler(response).await,
1504
+ };
1505
+ response = match response_status {
1506
+ Ok(response) => response,
1507
+ Err(err) => {
1508
+ if error_log_enabled {
1509
+ logger
1510
+ .send(LogMessage::new(
1511
+ format!("Unexpected error while serving a request: {}", err),
1512
+ true,
1513
+ ))
1514
+ .await
1515
+ .unwrap_or_default();
1516
+ }
1517
+
1518
+ let response = generate_error_response(
1519
+ StatusCode::INTERNAL_SERVER_ERROR,
1520
+ &combined_config,
1521
+ &headers,
1522
+ )
1523
+ .await;
1524
+ if log_enabled {
1525
+ log_combined(
1526
+ &logger,
1527
+ socket_data.remote_addr.ip(),
1528
+ auth_data,
1529
+ log_method,
1530
+ log_request_path,
1531
+ log_protocol,
1532
+ response.status().as_u16(),
1533
+ match response.headers().get(header::CONTENT_LENGTH) {
1534
+ Some(header_value) => match header_value.to_str() {
1535
+ Ok(header_value) => match header_value.parse::<u64>() {
1536
+ Ok(content_length) => Some(content_length),
1537
+ Err(_) => response.body().size_hint().exact(),
1538
+ },
1539
+ Err(_) => response.body().size_hint().exact(),
1540
+ },
1541
+ None => response.body().size_hint().exact(),
1542
+ },
1543
+ log_referrer,
1544
+ log_user_agent,
1545
+ )
1546
+ .await;
1547
+ }
1548
+ let (mut response_parts, response_body) = response.into_parts();
1549
+ if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
1550
+ let custom_headers_hash_iter = custom_headers_hash.iter();
1551
+ for (header_name, header_value) in custom_headers_hash_iter {
1552
+ if let Some(header_name) = header_name.as_str() {
1553
+ if let Some(header_value) = header_value.as_str() {
1554
+ if !response_parts.headers.contains_key(header_name) {
1555
+ if let Ok(header_value) = HeaderValue::from_str(
1556
+ &header_value.replace("{path}", &sanitized_url_pathname),
1557
+ ) {
1558
+ if let Ok(header_name) = HeaderName::from_str(header_name) {
1559
+ response_parts.headers.insert(header_name, header_value);
1560
+ }
1561
+ }
1562
+ }
1563
+ }
1564
+ }
1565
+ }
1566
+ }
1567
+ if let Some(http3_alt_port) = http3_alt_port {
1568
+ if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
1569
+ Some(value) => HeaderValue::from_bytes(
1570
+ format!(
1571
+ "{}, h3=\":{}\", h3-29=\":{}\"",
1572
+ String::from_utf8_lossy(value.as_bytes()),
1573
+ http3_alt_port,
1574
+ http3_alt_port
1575
+ )
1576
+ .as_bytes(),
1577
+ ),
1578
+ None => HeaderValue::from_bytes(
1579
+ format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port)
1580
+ .as_bytes(),
1581
+ ),
1582
+ } {
1583
+ response_parts.headers.insert(header::ALT_SVC, header_value);
1584
+ }
1585
+ }
1586
+ response_parts
1587
+ .headers
1588
+ .insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
1589
+
1590
+ return Ok(Response::from_parts(response_parts, response_body));
1591
+ }
1592
+ };
1593
+ }
1594
+
1595
+ if log_enabled {
1596
+ log_combined(
1597
+ &logger,
1598
+ socket_data.remote_addr.ip(),
1599
+ auth_data,
1600
+ log_method,
1601
+ log_request_path,
1602
+ log_protocol,
1603
+ response.status().as_u16(),
1604
+ match response.headers().get(header::CONTENT_LENGTH) {
1605
+ Some(header_value) => match header_value.to_str() {
1606
+ Ok(header_value) => match header_value.parse::<u64>() {
1607
+ Ok(content_length) => Some(content_length),
1608
+ Err(_) => response.body().size_hint().exact(),
1609
+ },
1610
+ Err(_) => response.body().size_hint().exact(),
1611
+ },
1612
+ None => response.body().size_hint().exact(),
1613
+ },
1614
+ log_referrer,
1615
+ log_user_agent,
1616
+ )
1617
+ .await;
1618
+ }
1619
+
1620
+ return Ok(response);
1621
+ }
1622
+ None => match status {
1623
+ Some(status) => {
1624
+ let response = generate_error_response(status, &combined_config, &headers).await;
1625
+ let (mut response_parts, response_body) = response.into_parts();
1626
+ if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
1627
+ let custom_headers_hash_iter = custom_headers_hash.iter();
1628
+ for (header_name, header_value) in custom_headers_hash_iter {
1629
+ if let Some(header_name) = header_name.as_str() {
1630
+ if let Some(header_value) = header_value.as_str() {
1631
+ if !response_parts.headers.contains_key(header_name) {
1632
+ if let Ok(header_value) = HeaderValue::from_str(
1633
+ &header_value.replace("{path}", &sanitized_url_pathname),
1634
+ ) {
1635
+ if let Ok(header_name) = HeaderName::from_str(header_name) {
1636
+ response_parts.headers.insert(header_name, header_value);
1637
+ }
1638
+ }
1639
+ }
1640
+ }
1641
+ }
1642
+ }
1643
+ }
1644
+ if let Some(http3_alt_port) = http3_alt_port {
1645
+ if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
1646
+ Some(value) => HeaderValue::from_bytes(
1647
+ format!(
1648
+ "{}, h3=\":{}\", h3-29=\":{}\"",
1649
+ String::from_utf8_lossy(value.as_bytes()),
1650
+ http3_alt_port,
1651
+ http3_alt_port
1652
+ )
1653
+ .as_bytes(),
1654
+ ),
1655
+ None => HeaderValue::from_bytes(
1656
+ format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port)
1657
+ .as_bytes(),
1658
+ ),
1659
+ } {
1660
+ response_parts.headers.insert(header::ALT_SVC, header_value);
1661
+ }
1662
+ }
1663
+ response_parts
1664
+ .headers
1665
+ .insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
1666
+
1667
+ let mut response = Response::from_parts(response_parts, response_body);
1668
+
1669
+ while let Some(mut executed_handler) = executed_handlers.pop() {
1670
+ let response_status = match is_proxy_request {
1671
+ true => {
1672
+ executed_handler
1673
+ .proxy_response_modifying_handler(response)
1674
+ .await
1675
+ }
1676
+ false => executed_handler.response_modifying_handler(response).await,
1677
+ };
1678
+ response = match response_status {
1679
+ Ok(response) => response,
1680
+ Err(err) => {
1681
+ if error_log_enabled {
1682
+ logger
1683
+ .send(LogMessage::new(
1684
+ format!("Unexpected error while serving a request: {}", err),
1685
+ true,
1686
+ ))
1687
+ .await
1688
+ .unwrap_or_default();
1689
+ }
1690
+
1691
+ let response = generate_error_response(
1692
+ StatusCode::INTERNAL_SERVER_ERROR,
1693
+ &combined_config,
1694
+ &headers,
1695
+ )
1696
+ .await;
1697
+ if log_enabled {
1698
+ log_combined(
1699
+ &logger,
1700
+ socket_data.remote_addr.ip(),
1701
+ auth_data,
1702
+ log_method,
1703
+ log_request_path,
1704
+ log_protocol,
1705
+ response.status().as_u16(),
1706
+ match response.headers().get(header::CONTENT_LENGTH) {
1707
+ Some(header_value) => match header_value.to_str() {
1708
+ Ok(header_value) => match header_value.parse::<u64>() {
1709
+ Ok(content_length) => Some(content_length),
1710
+ Err(_) => response.body().size_hint().exact(),
1711
+ },
1712
+ Err(_) => response.body().size_hint().exact(),
1713
+ },
1714
+ None => response.body().size_hint().exact(),
1715
+ },
1716
+ log_referrer,
1717
+ log_user_agent,
1718
+ )
1719
+ .await;
1720
+ }
1721
+ let (mut response_parts, response_body) = response.into_parts();
1722
+ if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash()
1723
+ {
1724
+ let custom_headers_hash_iter = custom_headers_hash.iter();
1725
+ for (header_name, header_value) in custom_headers_hash_iter {
1726
+ if let Some(header_name) = header_name.as_str() {
1727
+ if let Some(header_value) = header_value.as_str() {
1728
+ if !response_parts.headers.contains_key(header_name) {
1729
+ if let Ok(header_value) = HeaderValue::from_str(
1730
+ &header_value.replace("{path}", &sanitized_url_pathname),
1731
+ ) {
1732
+ if let Ok(header_name) = HeaderName::from_str(header_name) {
1733
+ response_parts.headers.insert(header_name, header_value);
1734
+ }
1735
+ }
1736
+ }
1737
+ }
1738
+ }
1739
+ }
1740
+ }
1741
+ if let Some(http3_alt_port) = http3_alt_port {
1742
+ if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC)
1743
+ {
1744
+ Some(value) => HeaderValue::from_bytes(
1745
+ format!(
1746
+ "{}, h3=\":{}\", h3-29=\":{}\"",
1747
+ String::from_utf8_lossy(value.as_bytes()),
1748
+ http3_alt_port,
1749
+ http3_alt_port
1750
+ )
1751
+ .as_bytes(),
1752
+ ),
1753
+ None => HeaderValue::from_bytes(
1754
+ format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port)
1755
+ .as_bytes(),
1756
+ ),
1757
+ } {
1758
+ response_parts.headers.insert(header::ALT_SVC, header_value);
1759
+ }
1760
+ }
1761
+ response_parts
1762
+ .headers
1763
+ .insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
1764
+
1765
+ return Ok(Response::from_parts(response_parts, response_body));
1766
+ }
1767
+ };
1768
+ }
1769
+
1770
+ if log_enabled {
1771
+ log_combined(
1772
+ &logger,
1773
+ socket_data.remote_addr.ip(),
1774
+ auth_data,
1775
+ log_method,
1776
+ log_request_path,
1777
+ log_protocol,
1778
+ response.status().as_u16(),
1779
+ match response.headers().get(header::CONTENT_LENGTH) {
1780
+ Some(header_value) => match header_value.to_str() {
1781
+ Ok(header_value) => match header_value.parse::<u64>() {
1782
+ Ok(content_length) => Some(content_length),
1783
+ Err(_) => response.body().size_hint().exact(),
1784
+ },
1785
+ Err(_) => response.body().size_hint().exact(),
1786
+ },
1787
+ None => response.body().size_hint().exact(),
1788
+ },
1789
+ log_referrer,
1790
+ log_user_agent,
1791
+ )
1792
+ .await;
1793
+ }
1794
+ return Ok(response);
1795
+ }
1796
+ None => match request_option {
1797
+ Some(request) => {
1798
+ request_data = RequestData::new(request, auth_data, original_url);
1799
+ continue;
1800
+ }
1801
+ None => {
1802
+ break;
1803
+ }
1804
+ },
1805
+ },
1806
+ }
1807
+ }
1808
+ Err(err) => {
1809
+ let response =
1810
+ generate_error_response(StatusCode::INTERNAL_SERVER_ERROR, &combined_config, &None)
1811
+ .await;
1812
+
1813
+ let (mut response_parts, response_body) = response.into_parts();
1814
+ if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
1815
+ let custom_headers_hash_iter = custom_headers_hash.iter();
1816
+ for (header_name, header_value) in custom_headers_hash_iter {
1817
+ if let Some(header_name) = header_name.as_str() {
1818
+ if let Some(header_value) = header_value.as_str() {
1819
+ if !response_parts.headers.contains_key(header_name) {
1820
+ if let Ok(header_value) = HeaderValue::from_str(
1821
+ &header_value.replace("{path}", &sanitized_url_pathname),
1822
+ ) {
1823
+ if let Ok(header_name) = HeaderName::from_str(header_name) {
1824
+ response_parts.headers.insert(header_name, header_value);
1825
+ }
1826
+ }
1827
+ }
1828
+ }
1829
+ }
1830
+ }
1831
+ }
1832
+ if let Some(http3_alt_port) = http3_alt_port {
1833
+ if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
1834
+ Some(value) => HeaderValue::from_bytes(
1835
+ format!(
1836
+ "{}, h3=\":{}\", h3-29=\":{}\"",
1837
+ String::from_utf8_lossy(value.as_bytes()),
1838
+ http3_alt_port,
1839
+ http3_alt_port
1840
+ )
1841
+ .as_bytes(),
1842
+ ),
1843
+ None => HeaderValue::from_bytes(
1844
+ format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
1845
+ ),
1846
+ } {
1847
+ response_parts.headers.insert(header::ALT_SVC, header_value);
1848
+ }
1849
+ }
1850
+ response_parts
1851
+ .headers
1852
+ .insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
1853
+
1854
+ let mut response = Response::from_parts(response_parts, response_body);
1855
+
1856
+ while let Some(mut executed_handler) = executed_handlers.pop() {
1857
+ let response_status = match is_proxy_request {
1858
+ true => {
1859
+ executed_handler
1860
+ .proxy_response_modifying_handler(response)
1861
+ .await
1862
+ }
1863
+ false => executed_handler.response_modifying_handler(response).await,
1864
+ };
1865
+ response = match response_status {
1866
+ Ok(response) => response,
1867
+ Err(err) => {
1868
+ if error_log_enabled {
1869
+ logger
1870
+ .send(LogMessage::new(
1871
+ format!("Unexpected error while serving a request: {}", err),
1872
+ true,
1873
+ ))
1874
+ .await
1875
+ .unwrap_or_default();
1876
+ }
1877
+
1878
+ let response = generate_error_response(
1879
+ StatusCode::INTERNAL_SERVER_ERROR,
1880
+ &combined_config,
1881
+ &None,
1882
+ )
1883
+ .await;
1884
+ if log_enabled {
1885
+ log_combined(
1886
+ &logger,
1887
+ socket_data.remote_addr.ip(),
1888
+ latest_auth_data,
1889
+ log_method,
1890
+ log_request_path,
1891
+ log_protocol,
1892
+ response.status().as_u16(),
1893
+ match response.headers().get(header::CONTENT_LENGTH) {
1894
+ Some(header_value) => match header_value.to_str() {
1895
+ Ok(header_value) => match header_value.parse::<u64>() {
1896
+ Ok(content_length) => Some(content_length),
1897
+ Err(_) => response.body().size_hint().exact(),
1898
+ },
1899
+ Err(_) => response.body().size_hint().exact(),
1900
+ },
1901
+ None => response.body().size_hint().exact(),
1902
+ },
1903
+ log_referrer,
1904
+ log_user_agent,
1905
+ )
1906
+ .await;
1907
+ }
1908
+ let (mut response_parts, response_body) = response.into_parts();
1909
+ if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
1910
+ let custom_headers_hash_iter = custom_headers_hash.iter();
1911
+ for (header_name, header_value) in custom_headers_hash_iter {
1912
+ if let Some(header_name) = header_name.as_str() {
1913
+ if let Some(header_value) = header_value.as_str() {
1914
+ if !response_parts.headers.contains_key(header_name) {
1915
+ if let Ok(header_value) = HeaderValue::from_str(
1916
+ &header_value.replace("{path}", &sanitized_url_pathname),
1917
+ ) {
1918
+ if let Ok(header_name) = HeaderName::from_str(header_name) {
1919
+ response_parts.headers.insert(header_name, header_value);
1920
+ }
1921
+ }
1922
+ }
1923
+ }
1924
+ }
1925
+ }
1926
+ }
1927
+ if let Some(http3_alt_port) = http3_alt_port {
1928
+ if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
1929
+ Some(value) => HeaderValue::from_bytes(
1930
+ format!(
1931
+ "{}, h3=\":{}\", h3-29=\":{}\"",
1932
+ String::from_utf8_lossy(value.as_bytes()),
1933
+ http3_alt_port,
1934
+ http3_alt_port
1935
+ )
1936
+ .as_bytes(),
1937
+ ),
1938
+ None => HeaderValue::from_bytes(
1939
+ format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port)
1940
+ .as_bytes(),
1941
+ ),
1942
+ } {
1943
+ response_parts.headers.insert(header::ALT_SVC, header_value);
1944
+ }
1945
+ }
1946
+ response_parts
1947
+ .headers
1948
+ .insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
1949
+
1950
+ return Ok(Response::from_parts(response_parts, response_body));
1951
+ }
1952
+ };
1953
+ }
1954
+
1955
+ if error_log_enabled {
1956
+ logger
1957
+ .send(LogMessage::new(
1958
+ format!("Unexpected error while serving a request: {}", err),
1959
+ true,
1960
+ ))
1961
+ .await
1962
+ .unwrap_or_default();
1963
+ }
1964
+
1965
+ if log_enabled {
1966
+ log_combined(
1967
+ &logger,
1968
+ socket_data.remote_addr.ip(),
1969
+ latest_auth_data,
1970
+ log_method,
1971
+ log_request_path,
1972
+ log_protocol,
1973
+ response.status().as_u16(),
1974
+ match response.headers().get(header::CONTENT_LENGTH) {
1975
+ Some(header_value) => match header_value.to_str() {
1976
+ Ok(header_value) => match header_value.parse::<u64>() {
1977
+ Ok(content_length) => Some(content_length),
1978
+ Err(_) => response.body().size_hint().exact(),
1979
+ },
1980
+ Err(_) => response.body().size_hint().exact(),
1981
+ },
1982
+ None => response.body().size_hint().exact(),
1983
+ },
1984
+ log_referrer,
1985
+ log_user_agent,
1986
+ )
1987
+ .await;
1988
+ }
1989
+ return Ok(response);
1990
+ }
1991
+ }
1992
+ }
1993
+
1994
+ let response = generate_error_response(StatusCode::NOT_FOUND, &combined_config, &None).await;
1995
+
1996
+ let (mut response_parts, response_body) = response.into_parts();
1997
+ if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
1998
+ let custom_headers_hash_iter = custom_headers_hash.iter();
1999
+ for (header_name, header_value) in custom_headers_hash_iter {
2000
+ if let Some(header_name) = header_name.as_str() {
2001
+ if let Some(header_value) = header_value.as_str() {
2002
+ if !response_parts.headers.contains_key(header_name) {
2003
+ if let Ok(header_value) =
2004
+ HeaderValue::from_str(&header_value.replace("{path}", &sanitized_url_pathname))
2005
+ {
2006
+ if let Ok(header_name) = HeaderName::from_str(header_name) {
2007
+ response_parts.headers.insert(header_name, header_value);
2008
+ }
2009
+ }
2010
+ }
2011
+ }
2012
+ }
2013
+ }
2014
+ }
2015
+ if let Some(http3_alt_port) = http3_alt_port {
2016
+ if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
2017
+ Some(value) => HeaderValue::from_bytes(
2018
+ format!(
2019
+ "{}, h3=\":{}\", h3-29=\":{}\"",
2020
+ String::from_utf8_lossy(value.as_bytes()),
2021
+ http3_alt_port,
2022
+ http3_alt_port
2023
+ )
2024
+ .as_bytes(),
2025
+ ),
2026
+ None => HeaderValue::from_bytes(
2027
+ format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
2028
+ ),
2029
+ } {
2030
+ response_parts.headers.insert(header::ALT_SVC, header_value);
2031
+ }
2032
+ }
2033
+ response_parts
2034
+ .headers
2035
+ .insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
2036
+
2037
+ let mut response = Response::from_parts(response_parts, response_body);
2038
+
2039
+ while let Some(mut executed_handler) = executed_handlers.pop() {
2040
+ let response_status = match is_proxy_request {
2041
+ true => {
2042
+ executed_handler
2043
+ .proxy_response_modifying_handler(response)
2044
+ .await
2045
+ }
2046
+ false => executed_handler.response_modifying_handler(response).await,
2047
+ };
2048
+ response = match response_status {
2049
+ Ok(response) => response,
2050
+ Err(err) => {
2051
+ if error_log_enabled {
2052
+ logger
2053
+ .send(LogMessage::new(
2054
+ format!("Unexpected error while serving a request: {}", err),
2055
+ true,
2056
+ ))
2057
+ .await
2058
+ .unwrap_or_default();
2059
+ }
2060
+
2061
+ let response =
2062
+ generate_error_response(StatusCode::INTERNAL_SERVER_ERROR, &combined_config, &None)
2063
+ .await;
2064
+ if log_enabled {
2065
+ log_combined(
2066
+ &logger,
2067
+ socket_data.remote_addr.ip(),
2068
+ latest_auth_data,
2069
+ log_method,
2070
+ log_request_path,
2071
+ log_protocol,
2072
+ response.status().as_u16(),
2073
+ match response.headers().get(header::CONTENT_LENGTH) {
2074
+ Some(header_value) => match header_value.to_str() {
2075
+ Ok(header_value) => match header_value.parse::<u64>() {
2076
+ Ok(content_length) => Some(content_length),
2077
+ Err(_) => response.body().size_hint().exact(),
2078
+ },
2079
+ Err(_) => response.body().size_hint().exact(),
2080
+ },
2081
+ None => response.body().size_hint().exact(),
2082
+ },
2083
+ log_referrer,
2084
+ log_user_agent,
2085
+ )
2086
+ .await;
2087
+ }
2088
+ let (mut response_parts, response_body) = response.into_parts();
2089
+ if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
2090
+ let custom_headers_hash_iter = custom_headers_hash.iter();
2091
+ for (header_name, header_value) in custom_headers_hash_iter {
2092
+ if let Some(header_name) = header_name.as_str() {
2093
+ if let Some(header_value) = header_value.as_str() {
2094
+ if !response_parts.headers.contains_key(header_name) {
2095
+ if let Ok(header_value) = HeaderValue::from_str(
2096
+ &header_value.replace("{path}", &sanitized_url_pathname),
2097
+ ) {
2098
+ if let Ok(header_name) = HeaderName::from_str(header_name) {
2099
+ response_parts.headers.insert(header_name, header_value);
2100
+ }
2101
+ }
2102
+ }
2103
+ }
2104
+ }
2105
+ }
2106
+ }
2107
+ if let Some(http3_alt_port) = http3_alt_port {
2108
+ if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
2109
+ Some(value) => HeaderValue::from_bytes(
2110
+ format!(
2111
+ "{}, h3=\":{}\", h3-29=\":{}\"",
2112
+ String::from_utf8_lossy(value.as_bytes()),
2113
+ http3_alt_port,
2114
+ http3_alt_port
2115
+ )
2116
+ .as_bytes(),
2117
+ ),
2118
+ None => HeaderValue::from_bytes(
2119
+ format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
2120
+ ),
2121
+ } {
2122
+ response_parts.headers.insert(header::ALT_SVC, header_value);
2123
+ }
2124
+ }
2125
+ response_parts
2126
+ .headers
2127
+ .insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
2128
+
2129
+ return Ok(Response::from_parts(response_parts, response_body));
2130
+ }
2131
+ };
2132
+ }
2133
+
2134
+ if log_enabled {
2135
+ log_combined(
2136
+ &logger,
2137
+ socket_data.remote_addr.ip(),
2138
+ latest_auth_data,
2139
+ log_method,
2140
+ log_request_path,
2141
+ log_protocol,
2142
+ response.status().as_u16(),
2143
+ match response.headers().get(header::CONTENT_LENGTH) {
2144
+ Some(header_value) => match header_value.to_str() {
2145
+ Ok(header_value) => match header_value.parse::<u64>() {
2146
+ Ok(content_length) => Some(content_length),
2147
+ Err(_) => response.body().size_hint().exact(),
2148
+ },
2149
+ Err(_) => response.body().size_hint().exact(),
2150
+ },
2151
+ None => response.body().size_hint().exact(),
2152
+ },
2153
+ log_referrer,
2154
+ log_user_agent,
2155
+ )
2156
+ .await;
2157
+ }
2158
+ Ok(response)
2159
+ }
2160
+ }
2161
+
2162
+ #[allow(clippy::too_many_arguments)]
2163
+ pub async fn request_handler(
2164
+ request: Request<BoxBody<Bytes, std::io::Error>>,
2165
+ remote_address: SocketAddr,
2166
+ local_address: SocketAddr,
2167
+ encrypted: bool,
2168
+ config: Arc<Yaml>,
2169
+ logger: Sender<LogMessage>,
2170
+ handlers_vec: Vec<Box<dyn ServerModuleHandlers + Send>>,
2171
+ acme_http01_resolver_option: Option<Arc<ResolvesServerCertAcme>>,
2172
+ http3_alt_port: Option<u16>,
2173
+ ) -> Result<Response<BoxBody<Bytes, std::io::Error>>, anyhow::Error> {
2174
+ let timeout_yaml = &config["global"]["timeout"];
2175
+ if timeout_yaml.is_null() {
2176
+ request_handler_wrapped(
2177
+ request,
2178
+ remote_address,
2179
+ local_address,
2180
+ encrypted,
2181
+ config,
2182
+ logger,
2183
+ handlers_vec,
2184
+ acme_http01_resolver_option,
2185
+ http3_alt_port,
2186
+ )
2187
+ .await
2188
+ .map_err(|e| anyhow::anyhow!(e))
2189
+ } else {
2190
+ let timeout_millis = timeout_yaml.as_i64().unwrap_or(300000) as u64;
2191
+ match timeout(
2192
+ Duration::from_millis(timeout_millis),
2193
+ request_handler_wrapped(
2194
+ request,
2195
+ remote_address,
2196
+ local_address,
2197
+ encrypted,
2198
+ config,
2199
+ logger,
2200
+ handlers_vec,
2201
+ acme_http01_resolver_option,
2202
+ http3_alt_port,
2203
+ ),
2204
+ )
2205
+ .await
2206
+ {
2207
+ Ok(response) => response.map_err(|e| anyhow::anyhow!(e)),
2208
+ Err(_) => Err(anyhow::anyhow!("The client or server has timed out")),
2209
+ }
2210
+ }
2211
+ }
ferron/src/res/server_software.rs ADDED
@@ -0,0 +1 @@
 
 
1
+ pub const SERVER_SOFTWARE: &str = "Ferron";