mirror of
https://github.com/samsonjs/agate.git
synced 2026-03-25 09:05:50 +00:00
Compare commits
No commits in common. "master" and "v3.3.18" have entirely different histories.
8 changed files with 333 additions and 583 deletions
2
.github/workflows/cargo-audit.yml
vendored
2
.github/workflows/cargo-audit.yml
vendored
|
|
@ -10,7 +10,7 @@ jobs:
|
|||
cargo-audit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions-rs/audit-check@v1
|
||||
# Don't run on dependabot PRs or forks
|
||||
# https://github.com/actions-rs/clippy-check/issues/2#issuecomment-807852653
|
||||
|
|
|
|||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
|
|
@ -18,7 +18,7 @@ jobs:
|
|||
runs-on: ubuntu-22.04
|
||||
needs: create_release
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: build
|
||||
run: bash .github/workflows/release.sh
|
||||
- name: upload release assets linux
|
||||
|
|
@ -32,7 +32,7 @@ jobs:
|
|||
runs-on: windows-latest
|
||||
needs: create_release
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build
|
||||
run: cargo build --verbose --release
|
||||
- name: strip names
|
||||
|
|
@ -50,7 +50,7 @@ jobs:
|
|||
runs-on: macos-latest
|
||||
needs: create_release
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: install toolchain
|
||||
run: rustup target add aarch64-apple-darwin
|
||||
- name: Build x86_64
|
||||
|
|
@ -79,7 +79,7 @@ jobs:
|
|||
packages: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Log into GHCR
|
||||
|
|
|
|||
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
|
|
@ -12,7 +12,7 @@ jobs:
|
|||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Run clippy action to produce annotations
|
||||
# Don't run on dependabot PRs
|
||||
# https://github.com/actions-rs/clippy-check/issues/2#issuecomment-807852653
|
||||
|
|
@ -30,7 +30,7 @@ jobs:
|
|||
formatting:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
|
|
@ -39,7 +39,7 @@ jobs:
|
|||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
|
|
|
|||
93
CHANGELOG.md
93
CHANGELOG.md
|
|
@ -5,66 +5,18 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
Updates to dependencies are not considered notable changes for the purpose of this changelog.
|
||||
This may lead to no listed changes for a version.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [3.3.19] - 2025-09-18
|
||||
|
||||
### Fixed
|
||||
* Update dependencies.
|
||||
* Document commands for converting PEM to DER.
|
||||
|
||||
## [3.3.18] - 2025-08-05
|
||||
## [3.3.17] - 2025-06-27
|
||||
## [3.3.16] - 2025-05-06
|
||||
|
||||
### Changed
|
||||
* Build release artifacts with Ubuntu 22.04, because Ubuntu 20.04 is no longer supported
|
||||
* pre-built binaries may no longer run on Linux distributions older than Ubuntu 22.04 (glibc 2.35)
|
||||
* users with older glibc versions will need to build from source
|
||||
|
||||
## [3.3.15] - 2025-05-06
|
||||
## [3.3.14] - 2025-03-24
|
||||
## [3.3.13] - 2025-02-24
|
||||
Thank you to @luineth for contributing to this release.
|
||||
|
||||
### Added
|
||||
* aarch64 support for Docker image (#376)
|
||||
|
||||
## [3.3.12] - 2025-02-18
|
||||
## [3.3.11] - 2024-11-29
|
||||
Thank you to @geraldwuhoo and @jphastings for contributing to this release.
|
||||
|
||||
### Added
|
||||
* Automatically publish docker images to GHCR (#366)
|
||||
|
||||
### Fixed
|
||||
* Refactor Dockerfile for multi-stage build (#144)
|
||||
|
||||
## [3.3.10] - 2024-11-04
|
||||
## [3.3.9] - 2024-09-10
|
||||
## [3.3.8] - 2024-07-24
|
||||
## [3.3.7] - 2024-04-01
|
||||
## [3.3.6] - 2024-03-22
|
||||
## [3.3.5] - 2024-03-15
|
||||
|
||||
### Fixed
|
||||
* updated and simplified dependencies
|
||||
* fix syntax of license field in Cargo manifest
|
||||
|
||||
## [3.3.4] - 2024-01-16
|
||||
|
||||
### Fixed
|
||||
* cleaned up documentation
|
||||
|
||||
## [3.3.3] - 2023-12-27
|
||||
|
||||
### Fixed
|
||||
* fixed release automation
|
||||
|
||||
## [3.3.2] - 2023-12-27
|
||||
|
||||
### Fixed
|
||||
* updated dependencies
|
||||
|
||||
## [3.3.1] - 2023-08-05
|
||||
Thank you to Jan Stępień and @michaelnordmeyer for contributing to this release.
|
||||
|
||||
|
|
@ -79,6 +31,7 @@ Thank you to @equalsraf, @michaelnordmeyer and @wanderer1988 for contributing to
|
|||
* listening on unix sockets (#244)
|
||||
|
||||
### Fixed
|
||||
* updated dependencies
|
||||
* misstyped email address in section on how to report security vulnerabilities (#239)
|
||||
* wrong language code in README (#189)
|
||||
|
||||
|
|
@ -88,12 +41,14 @@ Thank you to @06kellyjac, @albertlarsan68 and @kahays for contributing to this r
|
|||
### Fixed
|
||||
* removed port collisions in tests, for the last time (#143)
|
||||
* fixed Dockerfile startup command (#169)
|
||||
* upated dependencies
|
||||
|
||||
## [3.2.3] - 2022-02-04
|
||||
Thank you to T. Spivey for contributing to this release.
|
||||
|
||||
### Fixed
|
||||
* improper IRIs are handled instead of crashing (bug reported via email)
|
||||
* updated dependencies
|
||||
|
||||
## [3.2.2] - 2022-01-25
|
||||
Thank you to @Suzie97 for contributing to this release.
|
||||
|
|
@ -101,12 +56,16 @@ Thank you to @Suzie97 for contributing to this release.
|
|||
### Added
|
||||
* CI build for `aarch64-apple-darwin` target (#137)
|
||||
|
||||
### Fixed
|
||||
* updated dependencies
|
||||
|
||||
## [3.2.1] - 2021-12-02
|
||||
Thank you to @MatthiasPortzel for contributing to this release.
|
||||
|
||||
### Fixed
|
||||
* host name comparisons are now case insensitive (#115)
|
||||
* made automatic certificate configuration more prominent in the README
|
||||
* updated dependencies
|
||||
|
||||
## [3.2.0] - 2021-11-15
|
||||
Thank you to @balazsbtond and @joseph-marques for contributing to this release.
|
||||
|
|
@ -115,6 +74,7 @@ Thank you to @balazsbtond and @joseph-marques for contributing to this release.
|
|||
* you can add header text to a directory listing. See the updated readme for details. (#98)
|
||||
|
||||
### Fixed
|
||||
* updated dependencies
|
||||
* error pages also send close_notify (#100)
|
||||
|
||||
## [3.1.3] - 2021-10-25
|
||||
|
|
@ -143,6 +103,7 @@ Thank you to @jgarte and @alvaro-cuesta for contributing to this release.
|
|||
### Fixed
|
||||
* actually bind to multiple IP addresses. Despite the documentation saying so,
|
||||
Agate would only bind to the first address that did not result in an error. (#63)
|
||||
* updated dependencies
|
||||
|
||||
## [3.1.0] - 2021-06-08
|
||||
Thank you to Matthew Ingwersen and Oliver Simmons (@GoodClover) for contributing to this release.
|
||||
|
|
@ -172,6 +133,7 @@ Thank you to @06kellyjac, @cpnfeeny, @lifelike, @skittlesvampir and @steko for c
|
|||
The previous handling could be exploited as a DoS attack vector. (#59)
|
||||
* Two tests were running on the same port, causing them to fail nondeterministically. (#51)
|
||||
* Rephrased the changelog for 3.0.0 on continuing to use older certificates. (#55)
|
||||
* Updated dependencies.
|
||||
|
||||
## [3.0.2] - 2021-04-08
|
||||
Thank you to @kvibber, @lifelike and @pasdechance for contributing to this release.
|
||||
|
|
@ -234,6 +196,7 @@ Thank you to @littleli and @06kellyjac for contributing to this release.
|
|||
* The GitHub workflow has been fixed so Windows binaries are compressed correctly (#36).
|
||||
* Split out install steps to allow for more options in the future.
|
||||
* Add install notes for nix/NixOS to the README (#38).
|
||||
* Updated dependencies.
|
||||
|
||||
## [2.5.2] - 2021-02-12
|
||||
|
||||
|
|
@ -330,6 +293,9 @@ Thank you to @Johann150 and @KilianKemps for contributing to this release.
|
|||
### Added
|
||||
* Optional directory listings (#8, #9).
|
||||
|
||||
### Fixed
|
||||
* Updated dependencies.
|
||||
|
||||
## [2.0.0] - 2020-12-23
|
||||
Thank you to @bortzmeyer, @KillianKemps, and @Ylhp for contributing to this release.
|
||||
|
||||
|
|
@ -360,6 +326,7 @@ Thank you @Johann150, @jonhiggs and @tronje for contributing to this release!
|
|||
* verify hostname and port in request URL (#4).
|
||||
* improved logging (#2, #3).
|
||||
* Don't redirect to "/" when the path is empty (#5).
|
||||
* Update dependencies.
|
||||
|
||||
## [1.2.2] - 2020-09-21
|
||||
Thank you to @m040601 for contributing to this release.
|
||||
|
|
@ -369,11 +336,13 @@ Thank you to @m040601 for contributing to this release.
|
|||
* Built both x86_64 and ARM binaries. These binaries are built for Linux operating systems with glibc 2.28 or later, such as Debian 10 ("buster") or newer, Ubuntu 18.10 or newer, and Raspberry Pi OS 2019-06-20 or newer (#1).
|
||||
|
||||
### Fixed
|
||||
* Update dependencies.
|
||||
* Minor internal code cleanup.
|
||||
|
||||
## [1.2.1] - 2020-06-20
|
||||
### Fixed
|
||||
* Reduce memory usage when serving large files.
|
||||
* Update dependencies.
|
||||
|
||||
## [1.2.0] - 2020-06-10
|
||||
### Changed
|
||||
|
|
@ -382,6 +351,7 @@ Thank you to @m040601 for contributing to this release.
|
|||
### Fixed
|
||||
* Handling for requests that exceed 1KB.
|
||||
* Reduce memory allocations and speed up request parsing.
|
||||
* Update dependencies.
|
||||
|
||||
## [1.1.0] - 2020-05-22
|
||||
### Added
|
||||
|
|
@ -394,24 +364,7 @@ Thank you to @m040601 for contributing to this release.
|
|||
|
||||
## [1.0.0] - 2020-05-21
|
||||
|
||||
[Unreleased]: https://github.com/mbrubeck/agate/compare/v3.3.18...HEAD
|
||||
[3.3.18]: https://github.com/mbrubeck/agate/compare/v3.3.17...v3.3.18
|
||||
[3.3.17]: https://github.com/mbrubeck/agate/compare/v3.3.16...v3.3.17
|
||||
[3.3.16]: https://github.com/mbrubeck/agate/compare/v3.3.15...v3.3.16
|
||||
[3.3.15]: https://github.com/mbrubeck/agate/compare/v3.3.14...v3.3.15
|
||||
[3.3.14]: https://github.com/mbrubeck/agate/compare/v3.3.13...v3.3.14
|
||||
[3.3.13]: https://github.com/mbrubeck/agate/compare/v3.3.12...v3.3.13
|
||||
[3.3.12]: https://github.com/mbrubeck/agate/compare/v3.3.11...v3.3.12
|
||||
[3.3.11]: https://github.com/mbrubeck/agate/compare/v3.3.10...v3.3.11
|
||||
[3.3.10]: https://github.com/mbrubeck/agate/compare/v3.3.9...v3.3.10
|
||||
[3.3.9]: https://github.com/mbrubeck/agate/compare/v3.3.8...v3.3.9
|
||||
[3.3.8]: https://github.com/mbrubeck/agate/compare/v3.3.7...v3.3.8
|
||||
[3.3.7]: https://github.com/mbrubeck/agate/compare/v3.3.6...v3.3.7
|
||||
[3.3.6]: https://github.com/mbrubeck/agate/compare/v3.3.5...v3.3.6
|
||||
[3.3.5]: https://github.com/mbrubeck/agate/compare/v3.3.4...v3.3.5
|
||||
[3.3.4]: https://github.com/mbrubeck/agate/compare/v3.3.3...v3.3.4
|
||||
[3.3.3]: https://github.com/mbrubeck/agate/compare/v3.3.2...v3.3.3
|
||||
[3.3.2]: https://github.com/mbrubeck/agate/compare/v3.3.1...v3.3.2
|
||||
[Unreleased]: https://github.com/mbrubeck/agate/compare/v3.3.1...HEAD
|
||||
[3.3.1]: https://github.com/mbrubeck/agate/compare/v3.3.0...v3.3.1
|
||||
[3.3.0]: https://github.com/mbrubeck/agate/compare/v3.2.4...v3.3.0
|
||||
[3.2.4]: https://github.com/mbrubeck/agate/compare/v3.2.3...v3.2.4
|
||||
|
|
|
|||
658
Cargo.lock
generated
658
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
12
Cargo.toml
12
Cargo.toml
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "agate"
|
||||
version = "3.3.20"
|
||||
version = "3.3.18"
|
||||
authors = ["Matt Brubeck <mbrubeck@limpet.net>", "Johann150 <johann+agate@qwertqwefsday.eu>"]
|
||||
description = "Very simple server for the Gemini hypertext protocol"
|
||||
keywords = ["server", "gemini", "hypertext", "internet", "protocol"]
|
||||
|
|
@ -15,15 +15,15 @@ exclude = ["/tools", "/.github", "/Cross.toml", "/content", "/CODE_OF_CONDUCT.md
|
|||
configparser = "3.0"
|
||||
env_logger = { version = "0.11", default-features = false, features = ["auto-color", "humantime"] }
|
||||
futures-util = "0.3"
|
||||
getopts = { version = "0.2.24", default-features = false }
|
||||
getopts = "0.2.23"
|
||||
glob = "0.3"
|
||||
log = "0.4"
|
||||
mime_guess = "2.0"
|
||||
percent-encoding = "2.3"
|
||||
rcgen = { version = "0.14.7", default-features = false, features = ["ring"] }
|
||||
tokio-rustls = { version = "0.26.4", default-features = false, features = ["logging", "ring", "tls12"] }
|
||||
tokio = { version = "1.49", features = ["fs", "io-util", "net", "rt-multi-thread", "sync"] }
|
||||
url = "2.5.8"
|
||||
rcgen = { version = "0.14.3", default-features = false, features = ["ring"] }
|
||||
tokio-rustls = { version = "0.26.2", default-features = false, features = ["logging", "ring", "tls12"] }
|
||||
tokio = { version = "1.47", features = ["fs", "io-util", "net", "rt-multi-thread", "sync"] }
|
||||
url = "2.5.4"
|
||||
|
||||
[dev-dependencies]
|
||||
trotter = "1.0"
|
||||
|
|
|
|||
|
|
@ -195,13 +195,6 @@ Using a directory named just `.` causes undefined behaviour as this would have t
|
|||
|
||||
The files for a certificate/key pair have to be named `cert.der` and `key.der` respectively. The certificate has to be a X.509 certificate in a DER format file and has to include a subject alt name of the domain name. The private key has to be in DER format and must be either an RSA, ECDSA or Ed25519 key.
|
||||
|
||||
If you have an existing certificate/key pair in PEM format, you can use these commands to convert them to the DER format:
|
||||
|
||||
```shell
|
||||
openssl x509 -inform pem -in cert.pem -outform der -out cert.der
|
||||
openssl rsa -inform pem -in privkey.pem -outform der -out key.der
|
||||
```
|
||||
|
||||
## Logging
|
||||
|
||||
All requests via TCP sockets will be logged using this format:
|
||||
|
|
|
|||
130
src/main.rs
130
src/main.rs
|
|
@ -290,53 +290,55 @@ fn args() -> Result<Args> {
|
|||
let hostname = Host::parse(&s)?;
|
||||
|
||||
// check if we have a certificate for that domain
|
||||
if let Host::Domain(ref domain) = hostname
|
||||
&& !matches!(certs, Some(ref certs) if certs.has_domain(domain))
|
||||
{
|
||||
log::info!("No certificate or key found for {s:?}, generating them.");
|
||||
if let Host::Domain(ref domain) = hostname {
|
||||
if !matches!(certs, Some(ref certs) if certs.has_domain(domain)) {
|
||||
log::info!("No certificate or key found for {s:?}, generating them.");
|
||||
|
||||
let mut cert_params = CertificateParams::new(vec![domain.clone()])?;
|
||||
cert_params
|
||||
.distinguished_name
|
||||
.push(DnType::CommonName, domain);
|
||||
let mut cert_params = CertificateParams::new(vec![domain.clone()])?;
|
||||
cert_params
|
||||
.distinguished_name
|
||||
.push(DnType::CommonName, domain);
|
||||
|
||||
// <CertificateParams as Default>::default() already implements a
|
||||
// date in the far future from the time of writing: 4096-01-01
|
||||
// <CertificateParams as Default>::default() already implements a
|
||||
// date in the far future from the time of writing: 4096-01-01
|
||||
|
||||
let key_pair = if matches.opt_present("e") {
|
||||
KeyPair::generate_for(&rcgen::PKCS_ED25519)
|
||||
} else {
|
||||
KeyPair::generate()
|
||||
}?;
|
||||
let key_pair = if matches.opt_present("e") {
|
||||
KeyPair::generate_for(&rcgen::PKCS_ED25519)
|
||||
} else {
|
||||
KeyPair::generate()
|
||||
}?;
|
||||
|
||||
// generate the certificate with the configuration
|
||||
let cert = cert_params.self_signed(&key_pair)?;
|
||||
// generate the certificate with the configuration
|
||||
let cert = cert_params.self_signed(&key_pair)?;
|
||||
|
||||
// make sure the certificate directory exists
|
||||
let cert_dir = certs_path.join(domain);
|
||||
fs::create_dir(&cert_dir)?;
|
||||
|
||||
// write certificate data to disk
|
||||
let mut cert_file = File::create(cert_dir.join(certificates::CERT_FILE_NAME))?;
|
||||
cert_file.write_all(cert.der())?;
|
||||
|
||||
// write key data to disk
|
||||
let key_file_path = cert_dir.join(certificates::KEY_FILE_NAME);
|
||||
let mut key_file = File::create(&key_file_path)?;
|
||||
#[cfg(unix)]
|
||||
{
|
||||
// set permissions so only owner can read
|
||||
match key_file.set_permissions(std::fs::Permissions::from_mode(0o400)) {
|
||||
Ok(_) => (),
|
||||
Err(_) => log::warn!(
|
||||
"could not set permissions for new key file {}",
|
||||
key_file_path.display()
|
||||
),
|
||||
// make sure the certificate directory exists
|
||||
fs::create_dir(certs_path.join(domain))?;
|
||||
// write certificate data to disk
|
||||
let mut cert_file = File::create(certs_path.join(format!(
|
||||
"{}/{}",
|
||||
domain,
|
||||
certificates::CERT_FILE_NAME
|
||||
)))?;
|
||||
cert_file.write_all(cert.der())?;
|
||||
// write key data to disk
|
||||
let key_file_path =
|
||||
certs_path.join(format!("{}/{}", domain, certificates::KEY_FILE_NAME));
|
||||
let mut key_file = File::create(&key_file_path)?;
|
||||
#[cfg(unix)]
|
||||
{
|
||||
// set permissions so only owner can read
|
||||
match key_file.set_permissions(std::fs::Permissions::from_mode(0o400)) {
|
||||
Ok(_) => (),
|
||||
Err(_) => log::warn!(
|
||||
"could not set permissions for new key file {}",
|
||||
key_file_path.display()
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
key_file.write_all(key_pair.serialized_der())?;
|
||||
key_file.write_all(key_pair.serialized_der())?;
|
||||
|
||||
reload_certs = true;
|
||||
reload_certs = true;
|
||||
}
|
||||
}
|
||||
|
||||
hostnames.push(hostname);
|
||||
|
|
@ -592,13 +594,13 @@ where
|
|||
}
|
||||
|
||||
// correct port
|
||||
if let Some(expected_port) = self.local_port_check
|
||||
&& let Some(port) = url.port()
|
||||
{
|
||||
// Validate that the port in the URL is the same as for the stream this request
|
||||
// came in on.
|
||||
if port != expected_port {
|
||||
return Err((PROXY_REQUEST_REFUSED, "Proxy request refused"));
|
||||
if let Some(expected_port) = self.local_port_check {
|
||||
if let Some(port) = url.port() {
|
||||
// Validate that the port in the URL is the same as for the stream this request
|
||||
// came in on.
|
||||
if port != expected_port {
|
||||
return Err((PROXY_REQUEST_REFUSED, "Proxy request refused"));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(url)
|
||||
|
|
@ -657,24 +659,24 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
if let Ok(metadata) = tokio::fs::metadata(&path).await
|
||||
&& metadata.is_dir()
|
||||
{
|
||||
if url.path().ends_with('/') || url.path().is_empty() {
|
||||
// if the path ends with a slash or the path is empty, the links will work the same
|
||||
// without a redirect
|
||||
// use `push` instead of `join` because the changed path is used later
|
||||
path.push("index.gmi");
|
||||
if !path.exists() {
|
||||
path.pop();
|
||||
// try listing directory
|
||||
return self.list_directory(&path).await;
|
||||
if let Ok(metadata) = tokio::fs::metadata(&path).await {
|
||||
if metadata.is_dir() {
|
||||
if url.path().ends_with('/') || url.path().is_empty() {
|
||||
// if the path ends with a slash or the path is empty, the links will work the same
|
||||
// without a redirect
|
||||
// use `push` instead of `join` because the changed path is used later
|
||||
path.push("index.gmi");
|
||||
if !path.exists() {
|
||||
path.pop();
|
||||
// try listing directory
|
||||
return self.list_directory(&path).await;
|
||||
}
|
||||
} else {
|
||||
// if client is not redirected, links may not work as expected without trailing slash
|
||||
let mut url = url;
|
||||
url.set_path(&format!("{}/", url.path()));
|
||||
return self.send_header(REDIRECT_PERMANENT, url.as_str()).await;
|
||||
}
|
||||
} else {
|
||||
// if client is not redirected, links may not work as expected without trailing slash
|
||||
let mut url = url;
|
||||
url.set_path(&format!("{}/", url.path()));
|
||||
return self.send_header(REDIRECT_PERMANENT, url.as_str()).await;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue