v0.29.5
This commit is contained in:
1 parent
dd8c195600
commit
3e33982cd3
20 files changed
+287
-52
No files matched your search
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Read README.md, ROADMAP.md and docs/TEST_REPORT.md before making changes.
|
Read README.md, ROADMAP.md and docs/TEST_REPORT.md before making changes.
|
||||||
The project name is RustProxy; the Cargo package/binary remains `rust-proxy` for
|
The project name is RustProxy; the Cargo package/binary remains `rust-proxy` for
|
||||||
compatibility. Current source version: 0.29.4.
|
compatibility. Current source version: 0.29.5.
|
||||||
|
|
||||||
## Delivery
|
## Delivery
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,14 @@
|
|||||||
> test-only changes and packaging implementation notes were moved to
|
> test-only changes and packaging implementation notes were moved to
|
||||||
> [`docs/CHANGELOG_TECHNICAL_ARCHIVE.md`](docs/CHANGELOG_TECHNICAL_ARCHIVE.md).
|
> [`docs/CHANGELOG_TECHNICAL_ARCHIVE.md`](docs/CHANGELOG_TECHNICAL_ARCHIVE.md).
|
||||||
|
|
||||||
|
## 0.29.5
|
||||||
|
|
||||||
|
- Fix Roundcube 1.7 static asset delivery: a valid `static.php/...` PHP `PATH_INFO` target executes directly, before the Web Service's `try_files` fallback can mistakenly route it to `index.php` and return HTML instead of CSS/JavaScript.
|
||||||
|
- Apply Web Service hardening and `.htaccess` restrictions to the resolved PHP script, not just the full virtual URL.
|
||||||
|
- Test the actual FastCGI params for Roundcube static assets and verify opt-in PATH_INFO behavior with the front-controller fallback preserved when disabled.
|
||||||
|
- Retain the 0.29.4 shared PHP-FPM profile manager, Web Server cache/transform fixes, SQLite schema 9 and existing saved configuration.
|
||||||
|
- Rust compilation and live Roundcube verification remain pending in this environment.
|
||||||
|
|
||||||
## 0.29.4
|
## 0.29.4
|
||||||
|
|
||||||
- Expose the existing shared PHP-FPM profile manager directly beside **New Web Service** in the Web Server toolbar, without removing the shortcut in the service editor.
|
- Expose the existing shared PHP-FPM profile manager directly beside **New Web Service** in the Web Server toolbar, without removing the shortcut in the service editor.
|
||||||
|
|||||||
Generated
+1
-1
@@ -1688,7 +1688,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-proxy"
|
name = "rust-proxy"
|
||||||
version = "0.29.4"
|
version = "0.29.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rust-proxy"
|
name = "rust-proxy"
|
||||||
version = "0.29.4"
|
version = "0.29.5"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.88"
|
rust-version = "1.88"
|
||||||
description = "RustProxy - independent reverse proxy with local administration"
|
description = "RustProxy - independent reverse proxy with local administration"
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# RustProxy 0.29.4
|
# RustProxy 0.29.5
|
||||||
|
|
||||||
Independent Rust reverse proxy and static Web Server with a dashboard, SQLite configuration,
|
Independent Rust reverse proxy and static Web Server with a dashboard, SQLite configuration,
|
||||||
streaming HTTP/TLS/WebSocket forwarding, raw TCP/UDP stream hosts, IP-bound vhosts and backend pools.
|
streaming HTTP/TLS/WebSocket forwarding, raw TCP/UDP stream hosts, IP-bound vhosts and backend pools.
|
||||||
|
|
||||||
**0.29.4 release source.**
|
**0.29.5 release source.**
|
||||||
|
|
||||||
## Layer-4 streams
|
## Layer-4 streams
|
||||||
|
|
||||||
@@ -11,7 +11,14 @@ Raw TCP/UDP forwarding has its own **Streams** page in the primary navigation, a
|
|||||||
|
|
||||||
Named HTTP/HTTPS listener bind addresses use the same chip-style editor as vhost hostnames, including multi-address listeners.
|
Named HTTP/HTTPS listener bind addresses use the same chip-style editor as vhost hostnames, including multi-address listeners.
|
||||||
|
|
||||||
## What changed in 0.29.4
|
## What changed in 0.29.5
|
||||||
|
|
||||||
|
- Fix PHP-FPM `PATH_INFO` dispatch for existing scripts such as Roundcube 1.7 `/static.php/skins/...`: when PATH_INFO is enabled, invoke the script before `try_files` can fall back to `/index.php`.
|
||||||
|
- Keep FastCGI `SCRIPT_FILENAME`, `SCRIPT_NAME`, `PATH_INFO`, `REQUEST_URI` and the query string accurate, and check access rules on the resolved PHP script path.
|
||||||
|
- Add mock FastCGI regression tests proving that Roundcube static assets reach `static.php`; opt-out (`path_info: false`) retains the previous fallback behavior.
|
||||||
|
- No database migration or saved Web Service configuration changes. Rust compiler/runtime verification is still required before production deployment.
|
||||||
|
|
||||||
|
## Previously in 0.29.4
|
||||||
|
|
||||||
- Reuse named PHP-FPM profiles across Web Services, or keep per-service PHP-FPM settings; edit profiles in the expandable Web Server modal.
|
- Reuse named PHP-FPM profiles across Web Services, or keep per-service PHP-FPM settings; edit profiles in the expandable Web Server modal.
|
||||||
- Open the same PHP-FPM profile manager from the **PHP-FPM profiles** button beside **New Web Service** on the Web Server page.
|
- Open the same PHP-FPM profile manager from the **PHP-FPM profiles** button beside **New Web Service** on the Web Server page.
|
||||||
@@ -33,7 +40,7 @@ Named HTTP/HTTPS listener bind addresses use the same chip-style editor as vhost
|
|||||||
- Add streaming **multipart byte-range** responses for requests such as `Range: bytes=0-99,200-299`, with a maximum of 16 ranges and identity representation for multipart responses.
|
- Add streaming **multipart byte-range** responses for requests such as `Range: bytes=0-99,200-299`, with a maximum of 16 ranges and identity representation for multipart responses.
|
||||||
- Add opt-in PHP-FPM **PATH_INFO** splitting. With `path_info: true`, `/index.php/foo/bar` securely resolves the existing `index.php` script, keeps `SCRIPT_FILENAME` on that file and sends `/foo/bar` as `PATH_INFO`.
|
- Add opt-in PHP-FPM **PATH_INFO** splitting. With `path_info: true`, `/index.php/foo/bar` securely resolves the existing `index.php` script, keeps `SCRIPT_FILENAME` on that file and sends `/foo/bar` as `PATH_INFO`.
|
||||||
|
|
||||||
0.29.4 keeps SQLite schema 9 and Cold Cache format 3. Existing 0.29.0 configuration remains compatible; new fields use safe/default-off serde defaults. Historical details are in [CHANGELOG.md](CHANGELOG.md).
|
0.29.5 keeps SQLite schema 9 and Cold Cache format 3. Existing 0.29.0 configuration remains compatible; new fields use safe/default-off serde defaults. Historical details are in [CHANGELOG.md](CHANGELOG.md).
|
||||||
|
|
||||||
### Historical 0.26.7 release notes
|
### Historical 0.26.7 release notes
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# RustProxy roadmap
|
# RustProxy roadmap
|
||||||
|
|
||||||
|
## 0.29.5 - Roundcube PHP PATH_INFO routing
|
||||||
|
|
||||||
|
- Resolve PHP `PATH_INFO` on an existing script before `try_files` falls back to the front controller, while preserving normal URL and script security checks.
|
||||||
|
- Verify FastCGI parameters from an actual mock PHP-FPM exchange in Rust integration tests when Cargo is available.
|
||||||
|
- Retain schema 9, Cold Cache format 3 and all previous PHP-FPM profile UI and vhost-cache behavior.
|
||||||
|
|
||||||
## 0.29.4 - Shared PHP-FPM profiles and local Web Server processing
|
## 0.29.4 - Shared PHP-FPM profiles and local Web Server processing
|
||||||
|
|
||||||
Current direction: 0.29.3 -> 0.29.4
|
Current direction: 0.29.3 -> 0.29.4
|
||||||
|
|||||||
+19
-18
@@ -1,13 +1,13 @@
|
|||||||
61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore
|
61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore
|
||||||
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
|
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
|
||||||
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
|
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
|
||||||
2b708f425f80f11e4a3e92b16b396eb6da685bcd79730839e23ddc1002871cac AGENTS.md
|
03caed3d51469aba1a5ef646c9b986b79648a14f9fc58631c3e26a836eb675b9 AGENTS.md
|
||||||
4cb02e7c01b2920ab4b62919094b891a775a0af233c22d448d0879b2ddda0ca3 CHANGELOG.md
|
5fc20f00dcbce7659a1366a79c9adc65644db4790997904f66a40f611dc3e6b0 CHANGELOG.md
|
||||||
6d43d2521eee7d8a0a7317c585b7fae474ecdf9c092d02d045d3891cf3ae5764 Cargo.lock
|
733220d3b7f4b2c5b79c0d152f6e046cdeb34a29ce0a4e51565078e7f1562773 Cargo.lock
|
||||||
db8c2ffcea5348298a0af085e589730b42efa7e1bc57fa3d748bf6b36aec1040 Cargo.toml
|
88b2e5d4b64288177b9ecd4319616a89984b8cc4654cc92e615c685f3c86685f Cargo.toml
|
||||||
75b008f237b2226c62a7721b7901018ce83bfc2726f047a83a17c66fcb799f3b Dockerfile
|
75b008f237b2226c62a7721b7901018ce83bfc2726f047a83a17c66fcb799f3b Dockerfile
|
||||||
6598d33c7a56362c71011c71ac1c05d34241996f9cff97e4d43593e7dda735c2 README.md
|
2c7a5c76f1cab0ec0c3a4ac24d97c3e94505b88d6fb177e7a81c9f63be071de3 README.md
|
||||||
1369ae49b2bfc7c205323df713be9272c6971523e7f615ef6e86c1cfe071884c ROADMAP.md
|
b91d99f9b1415a12d124d1223b806591f528b3ea629cb5b97e2e9498f9963c4d ROADMAP.md
|
||||||
da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml
|
da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml
|
||||||
f645f218dd062bf6cb54a8ee634290562cad364a4cccf09f16e3c8303582af8c build.rs
|
f645f218dd062bf6cb54a8ee634290562cad364a4cccf09f16e3c8303582af8c build.rs
|
||||||
ea8d091843f86f4c66a5c76b20268b2feb397070851b6e05c5bc7d7a8d9157eb deploy/README.md
|
ea8d091843f86f4c66a5c76b20268b2feb397070851b6e05c5bc7d7a8d9157eb deploy/README.md
|
||||||
@@ -48,7 +48,7 @@ fea1ff4055d0930edeffb0c8501a6ba1ac55ec7a7064dcd0e300a43915e8432e docs/MIGRATION
|
|||||||
1f85fac0e06e432ef6f744f0b5f9c1b7a27a1e9e1ad30dc1a218eab96b1caffe docs/MOCK_SERVER.md
|
1f85fac0e06e432ef6f744f0b5f9c1b7a27a1e9e1ad30dc1a218eab96b1caffe docs/MOCK_SERVER.md
|
||||||
e7516646dfb107d6240370f4e7460b1189f2eb21f398740381d5d102847bbfe6 docs/NOT_FOUND_HOSTS.md
|
e7516646dfb107d6240370f4e7460b1189f2eb21f398740381d5d102847bbfe6 docs/NOT_FOUND_HOSTS.md
|
||||||
89a1af6c9fe10c572b211f9632ce256166e67990b5dacacf2ae07265d99a5172 docs/NPM_MIGRATION.md
|
89a1af6c9fe10c572b211f9632ce256166e67990b5dacacf2ae07265d99a5172 docs/NPM_MIGRATION.md
|
||||||
d223dff88cc734993d93b67d33545c8d700b37ac1981de111a4d6899a6f7a469 docs/PHP_FPM.md
|
7d19e5617b18c95bedd307b311517460f7c6ce0a19a67fc614fa9a1f48dfcb1c docs/PHP_FPM.md
|
||||||
572c70fbd2f61fecd80a8ff9fa5de4a94c4f158de5e90da8e920f99b051241fa docs/PROMETHEUS.md
|
572c70fbd2f61fecd80a8ff9fa5de4a94c4f158de5e90da8e920f99b051241fa docs/PROMETHEUS.md
|
||||||
7046079140db7c152dc9caa536259d3c175c8893bda3a23d6ca4e4acb8d0b4c0 docs/README.md
|
7046079140db7c152dc9caa536259d3c175c8893bda3a23d6ca4e4acb8d0b4c0 docs/README.md
|
||||||
6e46a137b9c88dac8cb9e3fdca877430092392de9913d746278dbff8652460c2 docs/REDIRECTS.md
|
6e46a137b9c88dac8cb9e3fdca877430092392de9913d746278dbff8652460c2 docs/REDIRECTS.md
|
||||||
@@ -60,7 +60,7 @@ a2bb837cdd3559d58158880c1f832badf64035376a3623050bb4e40c2bca104c docs/ROADMAP_H
|
|||||||
620b69e566d27af04f0604f4ca3f88cf95cdfea7fbc6ae9dd92446d2b2b5d9be docs/SECURITY_AUDIT.md
|
620b69e566d27af04f0604f4ca3f88cf95cdfea7fbc6ae9dd92446d2b2b5d9be docs/SECURITY_AUDIT.md
|
||||||
d5c5c59f6e63b8d83dad5d159657c4d4039dfeb4cc1d8af210169b576adbf7e2 docs/SETTINGS.md
|
d5c5c59f6e63b8d83dad5d159657c4d4039dfeb4cc1d8af210169b576adbf7e2 docs/SETTINGS.md
|
||||||
3611defd206cde107e75cac006159856d12cd8f15df7ae2eb9ec3592ed2b4abd docs/STREAMS.md
|
3611defd206cde107e75cac006159856d12cd8f15df7ae2eb9ec3592ed2b4abd docs/STREAMS.md
|
||||||
f03b4c365612cee787393bdd195ad9723251febe533568f59b0cb2da031e2c17 docs/TEST_REPORT.md
|
b29d1e50e75a6f9d440ffa6e4e6a3fb572a8971cad0751fa2f7b2026b641c8c3 docs/TEST_REPORT.md
|
||||||
cfb452d8496839e832b5fbacdd4e52525b0ecc3112fbb62340c1e580bd5c1717 docs/TODO.md
|
cfb452d8496839e832b5fbacdd4e52525b0ecc3112fbb62340c1e580bd5c1717 docs/TODO.md
|
||||||
751c9b13b47d0959a8575d6e82bdecba988b950a374ba0b084fbba08b4b65ebe docs/TRANSFORM.md
|
751c9b13b47d0959a8575d6e82bdecba988b950a374ba0b084fbba08b4b65ebe docs/TRANSFORM.md
|
||||||
660687fc961f71aae2bdf71595aa47a336fa782f4e78ceee8c1d1e63e46393ee docs/TRANSPORT.md
|
660687fc961f71aae2bdf71595aa47a336fa782f4e78ceee8c1d1e63e46393ee docs/TRANSPORT.md
|
||||||
@@ -182,7 +182,7 @@ e9f72e65c9ceb255248f460cd7a003fff33e4b258160e248353c3e1fb4e952fb src/admin/file
|
|||||||
1596561bb57976268b02e36e8ace426091045ee9d3f8832ce775eccdbc30bc81 src/admin/file_logs_io.rs
|
1596561bb57976268b02e36e8ace426091045ee9d3f8832ce775eccdbc30bc81 src/admin/file_logs_io.rs
|
||||||
b3e2c11dbc11bb8930117afdac64779111acc2ca7b2857f6e359a5108fc9ced8 src/admin/logging_api.rs
|
b3e2c11dbc11bb8930117afdac64779111acc2ca7b2857f6e359a5108fc9ced8 src/admin/logging_api.rs
|
||||||
58802528128f53e493f7339063433a747b605007a8842b5efc670132d897d6f7 src/admin/mod.rs
|
58802528128f53e493f7339063433a747b605007a8842b5efc670132d897d6f7 src/admin/mod.rs
|
||||||
8f2a19e8cb4791fb525e36907099ec010b3034dc7e65fb1e086b6804b410b7e6 src/admin/openapi.json
|
1396d24136707a81b9427290a855d9b5ec20fc1b222acd9e26b5319a52426328 src/admin/openapi.json
|
||||||
6c2b9a0ecb2f4172f110a363c22a6be32895dbcff9605fe7aa94efe91e0a536a src/admin/operations.rs
|
6c2b9a0ecb2f4172f110a363c22a6be32895dbcff9605fe7aa94efe91e0a536a src/admin/operations.rs
|
||||||
5287fff6b3ad436ac695e9b66feb1b0b5705b744a9f00cec7ea6a3e951e5eebf src/admin/preferences.rs
|
5287fff6b3ad436ac695e9b66feb1b0b5705b744a9f00cec7ea6a3e951e5eebf src/admin/preferences.rs
|
||||||
31bd90ee5f5ec2984920225ff6b57e07db3e0758092fb55d7c230e3590c1b015 src/admin/public.rs
|
31bd90ee5f5ec2984920225ff6b57e07db3e0758092fb55d7c230e3590c1b015 src/admin/public.rs
|
||||||
@@ -469,7 +469,7 @@ b61a40bb0ead18b3e348f03a258bab6a68d5c8f46968430f692458aa0cfd5bce src/vhost/vali
|
|||||||
7853f6ec127a91163a4418ca45b93a92f424d5c618f27b29f9d526598a26f7ef src/web_server/config/model.rs
|
7853f6ec127a91163a4418ca45b93a92f424d5c618f27b29f9d526598a26f7ef src/web_server/config/model.rs
|
||||||
42a613234af7015a0b14f125bb88be1dcc30a47e9064b12e3fbb5b71ce6f7caf src/web_server/config/validation.rs
|
42a613234af7015a0b14f125bb88be1dcc30a47e9064b12e3fbb5b71ce6f7caf src/web_server/config/validation.rs
|
||||||
86c744216d7132d8294a6e90ba9be5fd871646006313d1c62506d40c81db9b12 src/web_server/fastcgi.rs
|
86c744216d7132d8294a6e90ba9be5fd871646006313d1c62506d40c81db9b12 src/web_server/fastcgi.rs
|
||||||
b4b8e86a0ae415310c740eb2575cb16fca07cf7f74b5dc780e846707a01b9f16 src/web_server/fastcgi/protocol.rs
|
1c0759a3b2fda727b2af7180484bb72febdb079356dbba3169844d74281acb6f src/web_server/fastcgi/protocol.rs
|
||||||
6a2fff148bf5a6485c2017a8b0cb3d4214cfc247197dd3c77619bf0d19331a19 src/web_server/fastcgi/response.rs
|
6a2fff148bf5a6485c2017a8b0cb3d4214cfc247197dd3c77619bf0d19331a19 src/web_server/fastcgi/response.rs
|
||||||
8186363315bd998919c6c783e6f7501528f257a5c0da77c5d60f423e8670db5e src/web_server/filesystem.rs
|
8186363315bd998919c6c783e6f7501528f257a5c0da77c5d60f423e8670db5e src/web_server/filesystem.rs
|
||||||
0aafd7f37c4fe784b047646e4ce5ddcb5fe821231a41ed0a9f519529385288a8 src/web_server/htaccess.rs
|
0aafd7f37c4fe784b047646e4ce5ddcb5fe821231a41ed0a9f519529385288a8 src/web_server/htaccess.rs
|
||||||
@@ -484,11 +484,12 @@ c5cb561b0f276e8082f904b9446fa5885f6b3c0608f25ea2ef425061e9f7f6b4 src/web_server
|
|||||||
20509f9302b79d3a7be76dda6edaf5711826d3ddf47d19ebe07cb289427ca328 src/web_server/response/headers.rs
|
20509f9302b79d3a7be76dda6edaf5711826d3ddf47d19ebe07cb289427ca328 src/web_server/response/headers.rs
|
||||||
9b58d6bb3ff1520060fc5aa86a3594f99192b1a8f8183255be1fb3ff35b4bb35 src/web_server/routing.rs
|
9b58d6bb3ff1520060fc5aa86a3594f99192b1a8f8183255be1fb3ff35b4bb35 src/web_server/routing.rs
|
||||||
400e1c8aa2fe857d96036a64499fbc3de44c1e3a53811c5b25101fb843359792 src/web_server/security.rs
|
400e1c8aa2fe857d96036a64499fbc3de44c1e3a53811c5b25101fb843359792 src/web_server/security.rs
|
||||||
84868853bfe2d895ec4051c7c8ce9ccdc7c429fdd9718be460a81e78fb7391f1 src/web_server/serve.rs
|
d61ebdab046b6d2f8e34bb3058c5eb3f2d0766be573810387f48dd3116dc1a6d src/web_server/serve.rs
|
||||||
cf86c4692080723bbdf8f4105e1abfae33d7fcf020d2ad9e13ebae3943bd0cbe src/web_server/tests.rs
|
63d71a336187779e9cbc1cc6afa065c5fa9472835a8c2cc15dbf4527279d0c90 src/web_server/tests.rs
|
||||||
f5254272e3588e89e8417932244c136cedd769a5286342332be57800579d8390 src/web_server/tests/part_01.rs
|
77edb44b58d08d9818c42baaa34acdcbb19aea5ef8fb9014e6d6809d133deb07 src/web_server/tests/part_01.rs
|
||||||
874c228e03d11b33b5db7e58279569b35820d813ad6a9f45ee73b630f7bdad03 src/web_server/tests/part_02.rs
|
874c228e03d11b33b5db7e58279569b35820d813ad6a9f45ee73b630f7bdad03 src/web_server/tests/part_02.rs
|
||||||
8f9a0e2b0a69a1f2b8cf7505a654606150da4a53bc8c09d1d6c4b8751185b134 src/web_server/tests/part_03.rs
|
8f9a0e2b0a69a1f2b8cf7505a654606150da4a53bc8c09d1d6c4b8751185b134 src/web_server/tests/part_03.rs
|
||||||
|
b25b6e0082b5153272119c7faba9d6f2ccdd288ce5c066526431c56954c66c4c src/web_server/tests/part_04.rs
|
||||||
f00743af2cf4a5952caef7a1dcf68e7817b5599a252a9973e1c22045517c0b0e tests/fixtures/argon2id.json
|
f00743af2cf4a5952caef7a1dcf68e7817b5599a252a9973e1c22045517c0b0e tests/fixtures/argon2id.json
|
||||||
1725c10331745dc04a07675784d385260da7b14567b8dba3cbae5e891eb5dadf tests/http2.rs
|
1725c10331745dc04a07675784d385260da7b14567b8dba3cbae5e891eb5dadf tests/http2.rs
|
||||||
347a4ff9e028a1e999ad129590c355b5734cf928dd81e5099aea0c8aca8d5908 tests/http3.rs
|
347a4ff9e028a1e999ad129590c355b5734cf928dd81e5099aea0c8aca8d5908 tests/http3.rs
|
||||||
@@ -496,12 +497,12 @@ eecf680f001f2110dde4eceaccf2f922859a16c946a644f8854e8de4d3771f7c tests/policies
|
|||||||
728999110a6d8cacb62bc0399bc333fa23a66526ee23f563a3c93bf00e5fad19 tests/transport_config.rs
|
728999110a6d8cacb62bc0399bc333fa23a66526ee23f563a3c93bf00e5fad19 tests/transport_config.rs
|
||||||
b04362ad082a32c9ccc4cf38c46f44d71347adecf7aba3224b3af3ea4872e3d0 tools/README.md
|
b04362ad082a32c9ccc4cf38c46f44d71347adecf7aba3224b3af3ea4872e3d0 tools/README.md
|
||||||
358c1e94dd2b4a756430e5335747f0b437345b1713670a9972c5bcf78b837ccb tools/audit/admin_audit.py
|
358c1e94dd2b4a756430e5335747f0b437345b1713670a9972c5bcf78b837ccb tools/audit/admin_audit.py
|
||||||
ff891e5fc5ed591d2dd7483492ea0fad292d587c60e790a434b66390c3466a5a tools/checks/check_source.py
|
74b6ab569703d0e558b9708fddc3ed0cae4cd7bc4740be536205beba5d0a20f1 tools/checks/check_source.py
|
||||||
291a73c5c51862d4dcdba5af94bf146c2f1ed273eac015f0b8b7f634fc2cfa30 tools/checks/docs_check.py
|
0db0b6b6083df7e0e977bb4fd963826c761b83b0d68ea8f001347f02c7b0e728 tools/checks/docs_check.py
|
||||||
4f7821e7e25b09ce251d25a0b3fb4d8ce8396c36a5ed971c2637aeb325425909 tools/checks/langs_check.py
|
4f7821e7e25b09ce251d25a0b3fb4d8ce8396c36a5ed971c2637aeb325425909 tools/checks/langs_check.py
|
||||||
cf762780c4085791c72c3c5f93054d7f2a1107403bee0616a1f4113bbcbb99f9 tools/checks/memory_contracts.py
|
cf762780c4085791c72c3c5f93054d7f2a1107403bee0616a1f4113bbcbb99f9 tools/checks/memory_contracts.py
|
||||||
b476cd1ae0047fb110bb6b3bf20a39f1926b45f8572b7edc6b2a313e59e779a9 tools/checks/preflight.py
|
655eedbe607fa8b65e3184f2d47cf5cabe0afbf19506fdf777b2a5f1a745529a tools/checks/preflight.py
|
||||||
b09fd21ca90f92a31cf00565fbe81e71bef01e46aeec932796b55c6cc63b74f4 tools/checks/release_smoke.py
|
0ad29b742f77aa8347f71e0030ab8b6d4504e0fa2380b7ecb8cf88bf1da024ff tools/checks/release_smoke.py
|
||||||
f69845bc26ec33160fc756953991cf32aa8ad6a7fb4a131caa2093db91e783ef tools/checks/rust_module_contracts.py
|
f69845bc26ec33160fc756953991cf32aa8ad6a7fb4a131caa2093db91e783ef tools/checks/rust_module_contracts.py
|
||||||
ecfe0bb21a7549225a19b0846bb6de8c99cbfbbf5a706d33cf84db2c7146abed tools/checks/rust_source.py
|
ecfe0bb21a7549225a19b0846bb6de8c99cbfbbf5a706d33cf84db2c7146abed tools/checks/rust_source.py
|
||||||
adf84a983e0dbba228e47d71ec2d5c9964f7ca9989d5139de58f96ee961b035a tools/checks/security_contracts.py
|
adf84a983e0dbba228e47d71ec2d5c9964f7ca9989d5139de58f96ee961b035a tools/checks/security_contracts.py
|
||||||
@@ -510,7 +511,7 @@ adf84a983e0dbba228e47d71ec2d5c9964f7ca9989d5139de58f96ee961b035a tools/checks/s
|
|||||||
987b4c593b51a7c340d8f01f9a2eea1387043cdd997eeb932c8341bbb8c0bd64 tools/checks/version_contracts.py
|
987b4c593b51a7c340d8f01f9a2eea1387043cdd997eeb932c8341bbb8c0bd64 tools/checks/version_contracts.py
|
||||||
0c071c26fc316934bdfd2bfd19f5fed9521790d8c887760eaabda81a9be96703 tools/checks/vhost_www_ui_test.mjs
|
0c071c26fc316934bdfd2bfd19f5fed9521790d8c887760eaabda81a9be96703 tools/checks/vhost_www_ui_test.mjs
|
||||||
0aaa80453e25aae7ea953fadb91b3ae3eaff627977c81c9ab5a2779f9a93582f tools/checks/web_assets.py
|
0aaa80453e25aae7ea953fadb91b3ae3eaff627977c81c9ab5a2779f9a93582f tools/checks/web_assets.py
|
||||||
14a4f93dfc0704163334598332fe4549d4067937fc7f6f8bd86c2790b6857df6 tools/checks/web_server_contracts.py
|
0398892be30fe416a00578915c588f360fd93244e3af77ece834e560e547d1c8 tools/checks/web_server_contracts.py
|
||||||
d5e786084178a9eac9c69b818c2d48945e797b7064a68214e918a5b842b8e40e tools/checks/web_service_ui_test.mjs
|
d5e786084178a9eac9c69b818c2d48945e797b7064a68214e918a5b842b8e40e tools/checks/web_service_ui_test.mjs
|
||||||
adaa1ed9e5958739df40190055c780c80d0a8e270ec8dc105ed3fa2dbb8fb74f tools/checks/www_redirect_ui_test.mjs
|
adaa1ed9e5958739df40190055c780c80d0a8e270ec8dc105ed3fa2dbb8fb74f tools/checks/www_redirect_ui_test.mjs
|
||||||
d1418f8e574a7390fd45a2aa879bdf5720b773162d65e295ab0350506aa5220a tools/dev/mock_auth.py
|
d1418f8e574a7390fd45a2aa879bdf5720b773162d65e295ab0350506aa5220a tools/dev/mock_auth.py
|
||||||
|
|||||||
+30
-1
@@ -123,7 +123,7 @@ post_max_size = 64M
|
|||||||
max_execution_time = 30
|
max_execution_time = 30
|
||||||
```
|
```
|
||||||
|
|
||||||
RustProxy validates the lines and combines them with newline separators into the FastCGI `PHP_VALUE` parameter. PHP-FPM/PHP still decides whether a directive is changeable at this scope. `PHP_ADMIN_VALUE` is intentionally not exposed. Control characters/newlines inside names or values are rejected.
|
RustProxy validates the lines and combines them with newline separators into the FastCGI `PHP_VALUE` parameter. PHP-FPM/PHP still decides whether a directive is changeable at this scope. `PHP_ADMIN_VALUE` is available separately through the validated administrator INI overrides for trusted PHP-FPM endpoints. Control characters/newlines inside names or values are rejected.
|
||||||
|
|
||||||
These custom parameters are stored as normal Web Service configuration. Do not put secrets there unless normal configuration storage is an acceptable location for them.
|
These custom parameters are stored as normal Web Service configuration. Do not put secrets there unless normal configuration storage is an acceptable location for them.
|
||||||
|
|
||||||
@@ -139,6 +139,35 @@ $uri/
|
|||||||
|
|
||||||
Existing static files/directories are used first. The final path becomes the fallback and can therefore reach `index.php`. This resolution remains inside the already-selected Web Service and does not introduce a second vhost router.
|
Existing static files/directories are used first. The final path becomes the fallback and can therefore reach `index.php`. This resolution remains inside the already-selected Web Service and does not introduce a second vhost router.
|
||||||
|
|
||||||
|
## Roundcube 1.7 static.php / PATH_INFO (0.29.5)
|
||||||
|
|
||||||
|
Roundcube serves CSS, JavaScript and images through URLs such as
|
||||||
|
`/static.php/skins/elastic/styles/styles.min.css?s=123`. These paths are
|
||||||
|
**PHP-FPM requests to the existing** `public_html/static.php` **script**, not
|
||||||
|
static filesystem lookups. Configure the selected Web Service (or attached
|
||||||
|
shared PHP-FPM profile) with `php.enabled: true`, `php.path_info: true`, and
|
||||||
|
`php.extensions: ["php"]`. Point its root to the Roundcube `public_html` tree.
|
||||||
|
|
||||||
|
You can keep the normal Web Service `try_files` (`$uri`, `$uri/`,
|
||||||
|
`/index.php?$query_string`). RustProxy 0.29.5 resolves a valid PHP script plus
|
||||||
|
PATH_INFO after security checks and rewrites, **before** falling back through
|
||||||
|
`try_files`. No regex rewrite or extra Web Service is required for these URLs
|
||||||
|
when the main vhost selects this Web Service.
|
||||||
|
|
||||||
|
For `/static.php/skins/elastic/images/favicon.ico?s=123`, FastCGI receives:
|
||||||
|
|
||||||
|
```text
|
||||||
|
SCRIPT_NAME=/static.php
|
||||||
|
SCRIPT_FILENAME=<document_root>/static.php
|
||||||
|
PATH_INFO=/skins/elastic/images/favicon.ico
|
||||||
|
REQUEST_URI=/static.php/skins/elastic/images/favicon.ico?s=123
|
||||||
|
QUERY_STRING=s=123
|
||||||
|
```
|
||||||
|
|
||||||
|
PHP-FPM still needs filesystem access to `static.php` and the Roundcube
|
||||||
|
installation outside `public_html` that script reads. The PHP `PATH_INFO`
|
||||||
|
option is explicit and remains off by default for unrelated services.
|
||||||
|
|
||||||
## Bounded execution
|
## Bounded execution
|
||||||
|
|
||||||
Each compiled Web Service owns a bounded PHP concurrency semaphore. `max_concurrent_requests` limits simultaneous FastCGI requests. RustProxy does not create an unbounded wait queue: if no execution slot is immediately available it returns `503 Service Unavailable` with `Retry-After`.
|
Each compiled Web Service owns a bounded PHP concurrency semaphore. `max_concurrent_requests` limits simultaneous FastCGI requests. RustProxy does not create an unbounded wait queue: if no execution slot is immediately available it returns `503 Service Unavailable` with `Retry-After`.
|
||||||
|
|||||||
+11
-3
@@ -1,4 +1,12 @@
|
|||||||
# Verification report - RustProxy 0.29.4
|
# Verification report - RustProxy 0.29.5
|
||||||
|
|
||||||
|
## 0.29.5 Roundcube PHP PATH_INFO dispatch (2026-10-09)
|
||||||
|
|
||||||
|
- Cause confirmed by source: `serve_inner` originally ran `try_files` before recognizing `/static.php/skins/...` as a PHP script plus `PATH_INFO`. For an existing `static.php`, the last `try_files` candidate `/index.php` intercepted the asset request and could return HTML instead of the asset.
|
||||||
|
- Fixed dispatch order following rewrites and path ACLs; execute existing PHP PATH_INFO targets before `try_files`. Access restrictions also apply to the resolved PHP script path; the other fallback path remains protected.
|
||||||
|
- Added `roundcube_static_php_path_info_runs_before_front_controller_try_files` and `without_php_path_info_try_files_keeps_legacy_front_controller_fallback` using a local test FastCGI server and decoded FastCGI params. These are source tests until run with Cargo, **not** validated against live PHP-FPM or Roundcube.
|
||||||
|
- Operator action: ensure PHP-FPM PATH_INFO splitting enabled on the chosen Web Service / shared profile; keep `try_files` for the main service. No extra special-case URL rewrite is needed for Roundcube `static.php` URLs.
|
||||||
|
- Static checks passed: `version_contracts.py`, `web_server_contracts.py`, `rust_module_contracts.py`, `docs_check.py`, `langs_check.py`, `web_service_ui_test.mjs`, and `release_smoke.py`. `./dev.sh test` exceeded the available execution limit while running broader static `check_source.py`. `cargo`/`rustc` are unavailable; the new Rust FastCGI tests were not executed and neither was a live Roundcube integration.
|
||||||
|
|
||||||
## 0.29.4 PHP-FPM profile toolbar shortcut (2026-10-09)
|
## 0.29.4 PHP-FPM profile toolbar shortcut (2026-10-09)
|
||||||
|
|
||||||
@@ -94,9 +102,9 @@ This source package is not certified for production deployment.
|
|||||||
|
|
||||||
Installer/update now installs /etc/logrotate.d/rustproxy using the recorded service user/group. Updates repair /etc/profile.d/rustproxy-path.sh rather than warning. logrotate uses copytruncate because RustProxy holds open log descriptors; some log records may be lost during truncate. Rust build and live Debian/systemd acceptance remain unverified in this environment.
|
Installer/update now installs /etc/logrotate.d/rustproxy using the recorded service user/group. Updates repair /etc/profile.d/rustproxy-path.sh rather than warning. logrotate uses copytruncate because RustProxy holds open log descriptors; some log records may be lost during truncate. Rust build and live Debian/systemd acceptance remain unverified in this environment.
|
||||||
|
|
||||||
## Version identity (0.29.4 source release)
|
## Version identity (0.29.5 source release)
|
||||||
|
|
||||||
`Cargo.toml` package version is the canonical release identity (now 0.29.4). Rust uses `CARGO_PKG_VERSION` at build time; `build.rs` substitutes the admin HTML placeholder. Packaging, upgrade and dev scripts read the manifest with `scripts/version.py`. Run `python3 tools/checks/version_contracts.py` to check that executable paths do not hardcode a version. Historical documentation and external dashboards may retain manually managed versions. Rust compilation/runtime verification is still pending.
|
`Cargo.toml` package version is the canonical release identity (now 0.29.5). Rust uses `CARGO_PKG_VERSION` at build time; `build.rs` substitutes the admin HTML placeholder. Packaging, upgrade and dev scripts read the manifest with `scripts/version.py`. Run `python3 tools/checks/version_contracts.py` to check that executable paths do not hardcode a version. Historical documentation and external dashboards may retain manually managed versions. Rust compilation/runtime verification is still pending.
|
||||||
|
|
||||||
## Shared PHP-FPM profiles and Web Server cache/transform/Host patch (0.29.4)
|
## Shared PHP-FPM profiles and Web Server cache/transform/Host patch (0.29.4)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"openapi": "3.0.3",
|
"openapi": "3.0.3",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "RustProxy Administration API",
|
"title": "RustProxy Administration API",
|
||||||
"version": "0.29.4",
|
"version": "0.29.5",
|
||||||
"description": "Administration API for the active panel origin. Sign in through the panel; the same-origin HttpOnly cookie authenticates all private operations. Mutations also require the exact Origin, X-RustProxy-Request: 1 and a session-bound X-CSRF-Token. Loopback HTTP is the default; Settings can enable HTTPS before using a non-loopback administration bind. This document contains no live configuration or credentials. Object schemas marked extensible are summaries: full configuration validation is authoritative in Rust."
|
"description": "Administration API for the active panel origin. Sign in through the panel; the same-origin HttpOnly cookie authenticates all private operations. Mutations also require the exact Origin, X-RustProxy-Request: 1 and a session-bound X-CSRF-Token. Loopback HTTP is the default; Settings can enable HTTPS before using a non-loopback administration bind. This document contains no live configuration or credentials. Object schemas marked extensible are summaries: full configuration validation is authoritative in Rust."
|
||||||
},
|
},
|
||||||
"servers": [
|
"servers": [
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ use tokio::{
|
|||||||
const FCGI_VERSION_1: u8 = 1;
|
const FCGI_VERSION_1: u8 = 1;
|
||||||
const FCGI_BEGIN_REQUEST: u8 = 1;
|
const FCGI_BEGIN_REQUEST: u8 = 1;
|
||||||
pub(super) const FCGI_END_REQUEST: u8 = 3;
|
pub(super) const FCGI_END_REQUEST: u8 = 3;
|
||||||
const FCGI_PARAMS: u8 = 4;
|
pub(super) const FCGI_PARAMS: u8 = 4;
|
||||||
pub(super) const FCGI_STDIN: u8 = 5;
|
pub(super) const FCGI_STDIN: u8 = 5;
|
||||||
pub(super) const FCGI_STDOUT: u8 = 6;
|
pub(super) const FCGI_STDOUT: u8 = 6;
|
||||||
const FCGI_STDERR: u8 = 7;
|
const FCGI_STDERR: u8 = 7;
|
||||||
|
|||||||
@@ -106,6 +106,38 @@ async fn serve_inner(
|
|||||||
{
|
{
|
||||||
return builtin_error_response(StatusCode::FORBIDDEN, &method);
|
return builtin_error_response(StatusCode::FORBIDDEN, &method);
|
||||||
}
|
}
|
||||||
|
// An existing PHP script followed by PATH_INFO is a valid virtual PHP
|
||||||
|
// request, even though the full URL is not a filesystem path. Dispatch it
|
||||||
|
// before try_files can rewrite /static.php/skins/... to /index.php (as in
|
||||||
|
// Roundcube 1.7), while still applying the original path's rewrites and
|
||||||
|
// access controls above.
|
||||||
|
if let Ok(components) = path_components(&rewritten_path) {
|
||||||
|
if let Some((script_path, path_info)) = resolve_php_path_info(service, &components).await {
|
||||||
|
let script_components = path_components(&script_path).unwrap_or_default();
|
||||||
|
if hardening_denied_path(service, &script_path)
|
||||||
|
|| script_components.iter().any(|component| htaccess::protected_component(service.htaccess.as_ref(), component))
|
||||||
|
|| htaccess::denied(service.htaccess.as_ref(), &script_path)
|
||||||
|
{
|
||||||
|
return builtin_error_response(StatusCode::FORBIDDEN, &method);
|
||||||
|
}
|
||||||
|
if let Some(php) = php_for_path(service, &PathBuf::from(&script_path)) {
|
||||||
|
return php_response(
|
||||||
|
service,
|
||||||
|
php,
|
||||||
|
method,
|
||||||
|
headers,
|
||||||
|
uri,
|
||||||
|
script_path,
|
||||||
|
Some(path_info),
|
||||||
|
rewritten_query,
|
||||||
|
body,
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let (effective_path, effective_query) = match resolve_try_files(
|
let (effective_path, effective_query) = match resolve_try_files(
|
||||||
service,
|
service,
|
||||||
&rewritten_path,
|
&rewritten_path,
|
||||||
@@ -140,6 +172,13 @@ async fn serve_inner(
|
|||||||
if matches!(error.kind(), io::ErrorKind::NotFound | io::ErrorKind::NotADirectory) =>
|
if matches!(error.kind(), io::ErrorKind::NotFound | io::ErrorKind::NotADirectory) =>
|
||||||
{
|
{
|
||||||
if let Some((script_path, path_info)) = resolve_php_path_info(service, &components).await {
|
if let Some((script_path, path_info)) = resolve_php_path_info(service, &components).await {
|
||||||
|
let script_components = path_components(&script_path).unwrap_or_default();
|
||||||
|
if hardening_denied_path(service, &script_path)
|
||||||
|
|| script_components.iter().any(|component| htaccess::protected_component(service.htaccess.as_ref(), component))
|
||||||
|
|| htaccess::denied(service.htaccess.as_ref(), &script_path)
|
||||||
|
{
|
||||||
|
return builtin_error_response(StatusCode::FORBIDDEN, &method);
|
||||||
|
}
|
||||||
let public_path = PathBuf::from(&script_path);
|
let public_path = PathBuf::from(&script_path);
|
||||||
if let Some(php) = php_for_path(service, &public_path) {
|
if let Some(php) = php_for_path(service, &public_path) {
|
||||||
return php_response(
|
return php_response(
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
include!("tests/part_01.rs");
|
include!("tests/part_01.rs");
|
||||||
include!("tests/part_02.rs");
|
include!("tests/part_02.rs");
|
||||||
include!("tests/part_03.rs");
|
include!("tests/part_03.rs");
|
||||||
|
include!("tests/part_04.rs");
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use super::fastcgi::{
|
use super::fastcgi::{
|
||||||
fastcgi_params, parse_cgi_headers, read_fcgi_record, write_fcgi_record, FCGI_END_REQUEST,
|
fastcgi_params, parse_cgi_headers, read_fcgi_record, write_fcgi_record, FCGI_END_REQUEST,
|
||||||
FCGI_STDIN, FCGI_STDOUT,
|
FCGI_PARAMS, FCGI_STDIN, FCGI_STDOUT,
|
||||||
};
|
};
|
||||||
use axum::{body::Body, response::Response};
|
use axum::{body::Body, response::Response};
|
||||||
use http::{
|
use http::{
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
// Decode the actual FastCGI PARAMS records received by a mock PHP-FPM peer.
|
||||||
|
// This tests the Web Service dispatch and parameter generation together.
|
||||||
|
fn decode_fastcgi_params(mut bytes: &[u8]) -> BTreeMap<String, String> {
|
||||||
|
fn read_len(bytes: &mut &[u8]) -> usize {
|
||||||
|
let prefix = bytes[0];
|
||||||
|
if prefix & 0x80 == 0 {
|
||||||
|
*bytes = &bytes[1..];
|
||||||
|
prefix as usize
|
||||||
|
} else {
|
||||||
|
let value = u32::from_be_bytes(bytes[..4].try_into().unwrap()) & 0x7fff_ffff;
|
||||||
|
*bytes = &bytes[4..];
|
||||||
|
value as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut result = BTreeMap::new();
|
||||||
|
while !bytes.is_empty() {
|
||||||
|
let name_len = read_len(&mut bytes);
|
||||||
|
let value_len = read_len(&mut bytes);
|
||||||
|
let name = std::str::from_utf8(&bytes[..name_len]).unwrap().to_owned();
|
||||||
|
let value = std::str::from_utf8(&bytes[name_len..name_len + value_len]).unwrap().to_owned();
|
||||||
|
bytes = &bytes[name_len + value_len..];
|
||||||
|
result.insert(name, value);
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fastcgi_fixture_record_params() -> (
|
||||||
|
std::net::SocketAddr,
|
||||||
|
tokio::sync::oneshot::Receiver<BTreeMap<String, String>>,
|
||||||
|
) {
|
||||||
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||||
|
let address = listener.local_addr().unwrap();
|
||||||
|
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let (mut stream, _) = listener.accept().await.unwrap();
|
||||||
|
let deadline = Instant::now() + Duration::from_secs(5);
|
||||||
|
let mut params = Vec::new();
|
||||||
|
loop {
|
||||||
|
let (kind, content) = read_fcgi_record(&mut stream, deadline).await.unwrap();
|
||||||
|
if kind == FCGI_PARAMS {
|
||||||
|
params.extend_from_slice(&content);
|
||||||
|
}
|
||||||
|
if kind == FCGI_STDIN && content.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _ = tx.send(decode_fastcgi_params(¶ms));
|
||||||
|
write_fcgi_record(&mut stream, FCGI_STDOUT,
|
||||||
|
b"Status: 200 OK\r\nContent-Type: text/css\r\n\r\nPHP-STATIC-OK").await.unwrap();
|
||||||
|
write_fcgi_record(&mut stream, FCGI_STDOUT, &[]).await.unwrap();
|
||||||
|
write_fcgi_record(&mut stream, FCGI_END_REQUEST, &[0u8; 8]).await.unwrap();
|
||||||
|
stream.flush().await.unwrap();
|
||||||
|
});
|
||||||
|
(address, rx)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn roundcube_static_php_path_info_runs_before_front_controller_try_files() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
std::fs::write(tmp.path().join("static.php"), b"<?php // static asset handler").unwrap();
|
||||||
|
std::fs::write(tmp.path().join("index.php"), b"<?php // front controller").unwrap();
|
||||||
|
// This is the Roundcube v1.7 URL layout; /static.php/skins/... is
|
||||||
|
// virtual PATH_INFO, not a readable directory on disk.
|
||||||
|
let uri = "/static.php/skins/elastic/images/favicon.ico?s=1783249197";
|
||||||
|
let (address, received) = fastcgi_fixture_record_params().await;
|
||||||
|
let mut configured = Service {
|
||||||
|
id: "roundcube".into(),
|
||||||
|
name: "Roundcube".into(),
|
||||||
|
document_root: tmp.path().to_path_buf(),
|
||||||
|
index: vec!["index.php".into()],
|
||||||
|
try_files: vec!["$uri".into(), "$uri/".into(), "/index.php?$query_string".into()],
|
||||||
|
php: Php {
|
||||||
|
enabled: true,
|
||||||
|
path_info: true,
|
||||||
|
endpoint: format!("tcp:{address}"),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
configured.validate().unwrap();
|
||||||
|
let service = configured.compile().unwrap();
|
||||||
|
let response = serve_request(&service, &request(Method::GET, uri),
|
||||||
|
"/static.php/skins/elastic/images/favicon.ico").await;
|
||||||
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
|
assert_eq!(response.headers()[CONTENT_TYPE], "text/css");
|
||||||
|
assert_eq!(response.into_body().collect().await.unwrap().to_bytes(), "PHP-STATIC-OK");
|
||||||
|
let params = received.await.unwrap();
|
||||||
|
assert_eq!(params["SCRIPT_FILENAME"].as_str(), tmp.path().join("static.php").to_string_lossy().as_ref());
|
||||||
|
assert_eq!(params["SCRIPT_NAME"], "/static.php");
|
||||||
|
assert_eq!(params["PATH_INFO"], "/skins/elastic/images/favicon.ico");
|
||||||
|
assert_eq!(params["QUERY_STRING"], "s=1783249197");
|
||||||
|
assert_eq!(params["REQUEST_URI"], uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn without_php_path_info_try_files_keeps_legacy_front_controller_fallback() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
std::fs::write(tmp.path().join("static.php"), b"<?php // static asset handler").unwrap();
|
||||||
|
std::fs::write(tmp.path().join("index.php"), b"<?php // front controller").unwrap();
|
||||||
|
let (address, received) = fastcgi_fixture_record_params().await;
|
||||||
|
let mut configured = Service {
|
||||||
|
id: "roundcube-no-path-info".into(),
|
||||||
|
name: "Roundcube without PATH_INFO".into(),
|
||||||
|
document_root: tmp.path().to_path_buf(),
|
||||||
|
try_files: vec!["$uri".into(), "/index.php?$query_string".into()],
|
||||||
|
php: Php {
|
||||||
|
enabled: true,
|
||||||
|
path_info: false,
|
||||||
|
endpoint: format!("tcp:{address}"),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
configured.validate().unwrap();
|
||||||
|
let service = configured.compile().unwrap();
|
||||||
|
let uri = "/static.php/plugins/zipdownload/zipdownload.min.js?s=1783249197";
|
||||||
|
let response = serve_request(&service, &request(Method::GET, uri),
|
||||||
|
"/static.php/plugins/zipdownload/zipdownload.min.js").await;
|
||||||
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
|
let _ = response.into_body().collect().await.unwrap().to_bytes();
|
||||||
|
let params = received.await.unwrap();
|
||||||
|
assert_eq!(params["SCRIPT_FILENAME"].as_str(), tmp.path().join("index.php").to_string_lossy().as_ref());
|
||||||
|
assert_eq!(params["SCRIPT_NAME"], "/index.php");
|
||||||
|
assert!(!params.contains_key("PATH_INFO"));
|
||||||
|
}
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ def read_docker_agent_source() -> str:
|
|||||||
|
|
||||||
preflight(root)
|
preflight(root)
|
||||||
manifest = tomllib.loads((root / "Cargo.toml").read_text())
|
manifest = tomllib.loads((root / "Cargo.toml").read_text())
|
||||||
assert manifest["package"]["version"] == "0.29.4"
|
assert manifest["package"]["version"] == "0.29.5"
|
||||||
assert manifest["dependencies"]["quinn"]["version"] == "0.11.12"
|
assert manifest["dependencies"]["quinn"]["version"] == "0.11.12"
|
||||||
h3dep = manifest["dependencies"]["h3"]
|
h3dep = manifest["dependencies"]["h3"]
|
||||||
assert h3dep["package"] == "hpx-h3" and h3dep["version"] == "=2.5.19" and "quinn" in h3dep["features"]
|
assert h3dep["package"] == "hpx-h3" and h3dep["version"] == "=2.5.19" and "quinn" in h3dep["features"]
|
||||||
@@ -32,14 +32,14 @@ assert "h3-quinn" not in manifest["dependencies"]
|
|||||||
lock_path = root / "Cargo.lock"
|
lock_path = root / "Cargo.lock"
|
||||||
if lock_path.exists():
|
if lock_path.exists():
|
||||||
lock = lock_path.read_text()
|
lock = lock_path.read_text()
|
||||||
assert 'name = "rust-proxy"\nversion = "0.29.4"' in lock
|
assert 'name = "rust-proxy"\nversion = "0.29.5"' in lock
|
||||||
assert 'name = "systemstat"' in lock and 'name = "metrics-process"' in lock and 'name = "sysinfo"' in lock, "Cargo.lock must be resolver-generated after adding cross-platform statistics libraries"
|
assert 'name = "systemstat"' in lock and 'name = "metrics-process"' in lock and 'name = "sysinfo"' in lock, "Cargo.lock must be resolver-generated after adding cross-platform statistics libraries"
|
||||||
for release_doc, marker in {
|
for release_doc, marker in {
|
||||||
"README.md": "# RustProxy 0.29.4",
|
"README.md": "# RustProxy 0.29.5",
|
||||||
"CHANGELOG.md": "## 0.29.4",
|
"CHANGELOG.md": "## 0.29.5",
|
||||||
"ROADMAP.md": "## 0.29.4",
|
"ROADMAP.md": "## 0.29.5",
|
||||||
"AGENTS.md": "Current source version: 0.29.4.",
|
"AGENTS.md": "Current source version: 0.29.5.",
|
||||||
"docs/TEST_REPORT.md": "# Verification report - RustProxy 0.29.4",
|
"docs/TEST_REPORT.md": "# Verification report - RustProxy 0.29.5",
|
||||||
}.items():
|
}.items():
|
||||||
assert marker in (root / release_doc).read_text(), f"stale release identity in {release_doc}"
|
assert marker in (root / release_doc).read_text(), f"stale release identity in {release_doc}"
|
||||||
assert tomllib.loads((root / "rust-toolchain.toml").read_text())["toolchain"]["channel"] == "stable"
|
assert tomllib.loads((root / "rust-toolchain.toml").read_text())["toolchain"]["channel"] == "stable"
|
||||||
@@ -285,7 +285,7 @@ assert 'Opt-in health decisions based on ordinary user traffic. Defaults to disa
|
|||||||
assert '"force_ssl"' in openapi and '"forward_client_ip"' in openapi
|
assert '"force_ssl"' in openapi and '"forward_client_ip"' in openapi
|
||||||
assert '"backend_header"' in openapi and '"backend_timing_header"' in openapi
|
assert '"backend_header"' in openapi and '"backend_timing_header"' in openapi
|
||||||
assert '"StreamMetrics"' in openapi and '"streams"' in openapi
|
assert '"StreamMetrics"' in openapi and '"streams"' in openapi
|
||||||
for marker in ['"version": "0.29.4"', '"/api/traffic"', '"capture_user_agent"', '"capture_country"', '"hot_auto_trim_enabled"', '"hot_auto_trim_after_seconds"', '"hot_auto_trim_target_percent"', '"/api/config/{section}"', '"ConfigSectionUpdate"', '"cold_on_idle"', '"cold_on_age"', '"cold_max_hot_seconds"', '"cold_on_lru"', '"cold_on_shutdown"', '"cold_gc_interval_seconds"', '"cold_gc_max_delete"', '"collapse_enabled"', '"collapse_wait_timeout_ms"', '"collapse_timeout_fallback"', '"stale_if_error_timeout"', '"stale_if_error_connection"', '"stale_if_error_dns"', '"stale_if_error_tls"', '"stale_if_error_statuses"', '"proactive_refresh_enabled"', '"proactive_refresh_before_expiry_seconds"', '"proactive_refresh_percent"', '"proactive_refresh_max_inflight"', '"TransformSettings"', '"TransformOverrides"', '"/api/cache/cold/cleanup"', '"/api/cache/invalidate"', '"/api/cache/bans"', '"/api/cache/bans/{id}"', '"CacheInvalidationRequest"', '"CacheInvalidationResponse"', '"ColdCleanupJob"', '"match_type"', '"regex_insensitive"', '"rewrite"', '"response"', '"covered_hosts"', '"default": true']:
|
for marker in ['"version": "0.29.5"', '"/api/traffic"', '"capture_user_agent"', '"capture_country"', '"hot_auto_trim_enabled"', '"hot_auto_trim_after_seconds"', '"hot_auto_trim_target_percent"', '"/api/config/{section}"', '"ConfigSectionUpdate"', '"cold_on_idle"', '"cold_on_age"', '"cold_max_hot_seconds"', '"cold_on_lru"', '"cold_on_shutdown"', '"cold_gc_interval_seconds"', '"cold_gc_max_delete"', '"collapse_enabled"', '"collapse_wait_timeout_ms"', '"collapse_timeout_fallback"', '"stale_if_error_timeout"', '"stale_if_error_connection"', '"stale_if_error_dns"', '"stale_if_error_tls"', '"stale_if_error_statuses"', '"proactive_refresh_enabled"', '"proactive_refresh_before_expiry_seconds"', '"proactive_refresh_percent"', '"proactive_refresh_max_inflight"', '"TransformSettings"', '"TransformOverrides"', '"/api/cache/cold/cleanup"', '"/api/cache/invalidate"', '"/api/cache/bans"', '"/api/cache/bans/{id}"', '"CacheInvalidationRequest"', '"CacheInvalidationResponse"', '"ColdCleanupJob"', '"match_type"', '"regex_insensitive"', '"rewrite"', '"response"', '"covered_hosts"', '"default": true']:
|
||||||
assert marker in openapi, marker
|
assert marker in openapi, marker
|
||||||
for marker in ['"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']:
|
for marker in ['"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']:
|
||||||
assert marker in openapi, marker
|
assert marker in openapi, marker
|
||||||
@@ -1396,7 +1396,7 @@ assert '"error_log" => {' not in admin_0241 and 'request("/api/config/error_log"
|
|||||||
assert '"error_log"' in openapi and 'logging, error_log, forwarding' not in openapi
|
assert '"error_log"' in openapi and 'logging, error_log, forwarding' not in openapi
|
||||||
assert 'logging_section_persists_error_log_settings_atomically' in admin_0241 and 'error_log_is_not_a_standalone_configuration_section' in admin_0241
|
assert 'logging_section_persists_error_log_settings_atomically' in admin_0241 and 'error_log_is_not_a_standalone_configuration_section' in admin_0241
|
||||||
assert 'location.pathname === "/logging"' in settings_js_0241
|
assert 'location.pathname === "/logging"' in settings_js_0241
|
||||||
assert '"RuntimeInformation"' in openapi and '"version": "0.29.4"' in openapi
|
assert '"RuntimeInformation"' in openapi and '"version": "0.29.5"' in openapi
|
||||||
|
|
||||||
# 0.24.1 compiler and hostname-cardinality regressions.
|
# 0.24.1 compiler and hostname-cardinality regressions.
|
||||||
tls_0241 = rust_source(root, "src/tls.rs")
|
tls_0241 = rust_source(root, "src/tls.rs")
|
||||||
@@ -1635,5 +1635,5 @@ for marker_0282 in [
|
|||||||
assert 'Zewnętrzne ścieżki plików' in (root / 'langs/pl_pl.json').read_text()
|
assert 'Zewnętrzne ścieżki plików' in (root / 'langs/pl_pl.json').read_text()
|
||||||
assert 'setfacl -m u:rustproxy:--x /etc/ssl/' in (root / 'docs/V0.28.2_IMPLEMENTATION.md').read_text()
|
assert 'setfacl -m u:rustproxy:--x /etc/ssl/' in (root / 'docs/V0.28.2_IMPLEMENTATION.md').read_text()
|
||||||
|
|
||||||
print("PASS: 0.29.4 manifest/package, shared system-statistics/runtime metadata, Settings logging, retained H2, external-packaging/Grafana/Prometheus/404-host/custom-location/path/cache/dashboard/Nagios UI/source contracts, stopped legacy-renewal removal, session/admin/transport contracts, deterministic package source and embedded SQLite schema-v9 migrations.")
|
print("PASS: 0.29.5 manifest/package, shared system-statistics/runtime metadata, Settings logging, retained H2, external-packaging/Grafana/Prometheus/404-host/custom-location/path/cache/dashboard/Nagios UI/source contracts, stopped legacy-renewal removal, session/admin/transport contracts, deterministic package source and embedded SQLite schema-v9 migrations.")
|
||||||
print("NOT VERIFIED: Rust parsing, types, compilation, linking or runtime behavior. Run ./dev.sh test.")
|
print("NOT VERIFIED: Rust parsing, types, compilation, linking or runtime behavior. Run ./dev.sh test.")
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Rust-independent documentation/Reader consistency checks for the 0.29.4 release."""
|
"""Rust-independent documentation/Reader consistency checks for the 0.29.5 release."""
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
from rust_source import rust_source
|
from rust_source import rust_source
|
||||||
@@ -64,11 +64,11 @@ assert '"/docs/content.json"' in admin
|
|||||||
readme = (ROOT / "README.md").read_text(encoding="utf-8")
|
readme = (ROOT / "README.md").read_text(encoding="utf-8")
|
||||||
roadmap = (ROOT / "ROADMAP.md").read_text(encoding="utf-8")
|
roadmap = (ROOT / "ROADMAP.md").read_text(encoding="utf-8")
|
||||||
changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8")
|
changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8")
|
||||||
assert readme.startswith("# RustProxy 0.29.4")
|
assert readme.startswith("# RustProxy 0.29.5")
|
||||||
assert "docs/WEB_SERVER.md" in readme and "docs/README.md" in readme
|
assert "docs/WEB_SERVER.md" in readme and "docs/README.md" in readme
|
||||||
assert readme.count("## What changed in ") == 1, "README duplicates historical release notes; use CHANGELOG/archive"
|
assert readme.count("## What changed in ") == 1, "README duplicates historical release notes; use CHANGELOG/archive"
|
||||||
assert "## Release history" in readme and "docs/ROADMAP_HISTORY.md" in readme
|
assert "## Release history" in readme and "docs/ROADMAP_HISTORY.md" in readme
|
||||||
assert roadmap.startswith("# RustProxy roadmap") and "## 0.29.4" and "## 0.29.1" in roadmap and "## 0.29.0" in roadmap and "## 0.28.2" in roadmap and "## 0.28.1" in roadmap and "## 0.27.3" in roadmap and "## 0.27.2" in roadmap and "## 0.27.1" in roadmap and "## 0.27.0" in roadmap and "## 0.26.7" in roadmap and "## 0.26.6" in roadmap and "## 0.26.5" in roadmap and "## 0.26.4" in roadmap and "## 0.26.0" in roadmap and "## 0.25.5" in roadmap and "## 0.25.4" in roadmap and "## 0.25.3" in roadmap and "## 0.25.2" in roadmap and "## 0.25.1" in roadmap and "## 0.25.0" in roadmap
|
assert roadmap.startswith("# RustProxy roadmap") and "## 0.29.5" and "## 0.29.1" in roadmap and "## 0.29.0" in roadmap and "## 0.28.2" in roadmap and "## 0.28.1" in roadmap and "## 0.27.3" in roadmap and "## 0.27.2" in roadmap and "## 0.27.1" in roadmap and "## 0.27.0" in roadmap and "## 0.26.7" in roadmap and "## 0.26.6" in roadmap and "## 0.26.5" in roadmap and "## 0.26.4" in roadmap and "## 0.26.0" in roadmap and "## 0.25.5" in roadmap and "## 0.25.4" in roadmap and "## 0.25.3" in roadmap and "## 0.25.2" in roadmap and "## 0.25.1" in roadmap and "## 0.25.0" in roadmap
|
||||||
assert "## 0.24." not in roadmap and "## Web Server follow-up" in roadmap and "docs/ROADMAP_HISTORY.md" in roadmap
|
assert "## 0.24." not in roadmap and "## Web Server follow-up" in roadmap and "docs/ROADMAP_HISTORY.md" in roadmap
|
||||||
assert changelog.startswith("# Changelog\n")
|
assert changelog.startswith("# Changelog\n")
|
||||||
for release in ["0.29.1", "0.29.0", "0.28.2", "0.28.1", "0.28.0", "0.27.3", "0.27.2", "0.27.1", "0.27.0", "0.26.7", "0.26.6", "0.26.5", "0.26.4", "0.26.1", "0.26.0", "0.25.6", "0.25.5", "0.25.4", "0.25.3"]:
|
for release in ["0.29.1", "0.29.0", "0.28.2", "0.28.1", "0.28.0", "0.27.3", "0.27.2", "0.27.1", "0.27.0", "0.26.7", "0.26.6", "0.26.5", "0.26.4", "0.26.1", "0.26.0", "0.25.6", "0.25.5", "0.25.4", "0.25.3"]:
|
||||||
|
|||||||
@@ -75,10 +75,10 @@ def check(root: Path, repair_known: bool = False) -> list[str]:
|
|||||||
raise ValueError(f"Conflicting Rust modules: {flat.relative_to(root)} and {module.relative_to(root)}")
|
raise ValueError(f"Conflicting Rust modules: {flat.relative_to(root)} and {module.relative_to(root)}")
|
||||||
for path in ["src/auth/mod.rs", "src/auth/store.rs", "src/backup/snapshot.rs", "src/upstream.rs", "src/tls.rs", "src/config/internal_routing.rs", "src/proxy/websocket.rs", "src/proxy/error_fallback.rs", "src/cache/mod.rs", "src/cache/model.rs", "src/cache/lifecycle.rs", "src/cache/cold.rs", "src/cache/dashboard.rs", "src/cache/tickets.rs", "src/cache/lookup.rs", "src/cache/policy.rs", "src/cache/fill.rs", "src/cache/tests.rs", "src/settings.rs", "src/compression.rs", "src/diagnostics.rs", "src/traffic.rs", "src/system_stats/mod.rs", "src/system_stats/linux.rs", "src/vhost.rs", "src/vhost/error_fallback.rs", "src/web_server/mod.rs", "src/web_server/config.rs", "src/web_server/filesystem.rs", "src/web_server/security.rs", "src/web_server/response.rs", "src/web_server/request.rs", "src/web_server/routing.rs", "src/web_server/fastcgi.rs", "src/web_server/htaccess.rs", "src/web_server/serve.rs", "src/web_server/tests.rs", "src/balance.rs", "src/security.rs", "src/admin/web/vhosts/core.js", "src/admin/web/vhosts/editor.js", "src/admin/web/vhosts/render.js", "src/admin/web/web-server.js", "src/admin/web/streams.js", "src/admin/web/host404.js", "src/admin/web/app/core.js", "src/admin/web/app/preferences.js", "src/admin/web/app/config-history.js", "src/admin/web/app/runtime.js", "src/admin/web/app/settings.js", "src/admin/web/management.js", "src/admin/web/drafts.js", "src/pages.rs", "tools/dev/mock_server.py", "tools/dev/mock_auth.py", "src/forwarding.rs", "src/tinyauth.rs", "src/http3.rs", "src/stream.rs", "src/admin/web/integrations.js", "src/admin/web/traffic-panel.js", "src/prometheus.rs", "src/admin/web/settings.js", "src/admin/web/docker.js", "src/docker.rs", "docker-agent/main.go", "docker-agent/model.go", "docker-agent/docker.go", "docker-agent/network.go", "docker-agent/certificate.go", "docker-agent/server.go", "docker-agent/main_test.go", "docker-agent/rustproxy-docker-agent.service", "docker-agent/install.sh", "docs/DOCKER_DISCOVERY.md", "docs/WEB_SERVER.md", "docs/PHP_FPM.md", "docs/README.md", "docs/TODO.md", "docs/V0.25.5_AUDIT.md", "docs/V0.25.4_AUDIT.md", "docs/V0.25.3_AUDIT.md", "docs/V0.25.2_AUDIT.md", "docs/CHANGELOG_TECHNICAL_ARCHIVE.md", "docs/ROADMAP_HISTORY.md", "tools/checks/docs_check.py", "tools/checks/web_server_contracts.py", "tools/checks/rust_source.py", "tools/checks/rust_module_contracts.py", "tools/checks/source_module_contracts.py", "scripts/runtime/acme_provider.py", "src/basic_auth.rs", "scripts/deploy/deploy.py", "scripts/deploy/deploy_core.py", "scripts/deploy/deploy_runtime.py", "scripts/migration/migrate_npm.py", "scripts/migration/migrate_npm_core.py", "scripts/migration/migrate_npm_api.py", "docs/NPM_MIGRATION.md", "docs/STREAMS.md", "install.sh", "install-debian13.sh", "update.sh", "build.rs", "scripts/build/swagger_assets.py", "src/admin/documentation.rs", "src/admin/openapi.json", "langs/en_US.json", "langs/pl_pl.json", "Dockerfile", ".dockerignore", "docs/CONTAINER.md", "docs/PROMETHEUS.md", "docs/NOT_FOUND_HOSTS.md", "grafana/rustproxy-overview.json"]:
|
for path in ["src/auth/mod.rs", "src/auth/store.rs", "src/backup/snapshot.rs", "src/upstream.rs", "src/tls.rs", "src/config/internal_routing.rs", "src/proxy/websocket.rs", "src/proxy/error_fallback.rs", "src/cache/mod.rs", "src/cache/model.rs", "src/cache/lifecycle.rs", "src/cache/cold.rs", "src/cache/dashboard.rs", "src/cache/tickets.rs", "src/cache/lookup.rs", "src/cache/policy.rs", "src/cache/fill.rs", "src/cache/tests.rs", "src/settings.rs", "src/compression.rs", "src/diagnostics.rs", "src/traffic.rs", "src/system_stats/mod.rs", "src/system_stats/linux.rs", "src/vhost.rs", "src/vhost/error_fallback.rs", "src/web_server/mod.rs", "src/web_server/config.rs", "src/web_server/filesystem.rs", "src/web_server/security.rs", "src/web_server/response.rs", "src/web_server/request.rs", "src/web_server/routing.rs", "src/web_server/fastcgi.rs", "src/web_server/htaccess.rs", "src/web_server/serve.rs", "src/web_server/tests.rs", "src/balance.rs", "src/security.rs", "src/admin/web/vhosts/core.js", "src/admin/web/vhosts/editor.js", "src/admin/web/vhosts/render.js", "src/admin/web/web-server.js", "src/admin/web/streams.js", "src/admin/web/host404.js", "src/admin/web/app/core.js", "src/admin/web/app/preferences.js", "src/admin/web/app/config-history.js", "src/admin/web/app/runtime.js", "src/admin/web/app/settings.js", "src/admin/web/management.js", "src/admin/web/drafts.js", "src/pages.rs", "tools/dev/mock_server.py", "tools/dev/mock_auth.py", "src/forwarding.rs", "src/tinyauth.rs", "src/http3.rs", "src/stream.rs", "src/admin/web/integrations.js", "src/admin/web/traffic-panel.js", "src/prometheus.rs", "src/admin/web/settings.js", "src/admin/web/docker.js", "src/docker.rs", "docker-agent/main.go", "docker-agent/model.go", "docker-agent/docker.go", "docker-agent/network.go", "docker-agent/certificate.go", "docker-agent/server.go", "docker-agent/main_test.go", "docker-agent/rustproxy-docker-agent.service", "docker-agent/install.sh", "docs/DOCKER_DISCOVERY.md", "docs/WEB_SERVER.md", "docs/PHP_FPM.md", "docs/README.md", "docs/TODO.md", "docs/V0.25.5_AUDIT.md", "docs/V0.25.4_AUDIT.md", "docs/V0.25.3_AUDIT.md", "docs/V0.25.2_AUDIT.md", "docs/CHANGELOG_TECHNICAL_ARCHIVE.md", "docs/ROADMAP_HISTORY.md", "tools/checks/docs_check.py", "tools/checks/web_server_contracts.py", "tools/checks/rust_source.py", "tools/checks/rust_module_contracts.py", "tools/checks/source_module_contracts.py", "scripts/runtime/acme_provider.py", "src/basic_auth.rs", "scripts/deploy/deploy.py", "scripts/deploy/deploy_core.py", "scripts/deploy/deploy_runtime.py", "scripts/migration/migrate_npm.py", "scripts/migration/migrate_npm_core.py", "scripts/migration/migrate_npm_api.py", "docs/NPM_MIGRATION.md", "docs/STREAMS.md", "install.sh", "install-debian13.sh", "update.sh", "build.rs", "scripts/build/swagger_assets.py", "src/admin/documentation.rs", "src/admin/openapi.json", "langs/en_US.json", "langs/pl_pl.json", "Dockerfile", ".dockerignore", "docs/CONTAINER.md", "docs/PROMETHEUS.md", "docs/NOT_FOUND_HOSTS.md", "grafana/rustproxy-overview.json"]:
|
||||||
if not (root / path).is_file():
|
if not (root / path).is_file():
|
||||||
raise ValueError(f"Incomplete RustProxy 0.29.4 source tree: missing {path}")
|
raise ValueError(f"Incomplete RustProxy 0.29.5 source tree: missing {path}")
|
||||||
text = (root / "src/lib.rs").read_text(encoding="utf-8")
|
text = (root / "src/lib.rs").read_text(encoding="utf-8")
|
||||||
if '#[path = "auth/mod.rs"]' not in text:
|
if '#[path = "auth/mod.rs"]' not in text:
|
||||||
raise ValueError("src/lib.rs is not the 0.29.4 entry point; extract the complete package, not individual files.")
|
raise ValueError("src/lib.rs is not the 0.29.5 entry point; extract the complete package, not individual files.")
|
||||||
return messages
|
return messages
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ def upgrade_smoke(tmp: Path) -> None:
|
|||||||
assert backup.is_dir()
|
assert backup.is_dir()
|
||||||
assert (backup / "src/obsolete.rs").is_file()
|
assert (backup / "src/obsolete.rs").is_file()
|
||||||
assert not (target / "src/obsolete.rs").exists()
|
assert not (target / "src/obsolete.rs").exists()
|
||||||
assert 'version = "0.29.4"' in (target / "Cargo.toml").read_text(encoding="utf-8")
|
assert 'version = "0.29.5"' in (target / "Cargo.toml").read_text(encoding="utf-8")
|
||||||
assert (target / "data/keep.txt").read_text(encoding="utf-8") == "preserve\n"
|
assert (target / "data/keep.txt").read_text(encoding="utf-8") == "preserve\n"
|
||||||
assert (target / ".env").read_text(encoding="utf-8") == "RUST_PROXY_DEMO=0\n"
|
assert (target / ".env").read_text(encoding="utf-8") == "RUST_PROXY_DEMO=0\n"
|
||||||
assert (target / "langs/site.json").is_file()
|
assert (target / "langs/site.json").is_file()
|
||||||
@@ -146,7 +146,7 @@ def upgrade_smoke(tmp: Path) -> None:
|
|||||||
assert (target / "docker-agent/certificate.go").is_file()
|
assert (target / "docker-agent/certificate.go").is_file()
|
||||||
assert (target / "docker-agent/server.go").is_file()
|
assert (target / "docker-agent/server.go").is_file()
|
||||||
manifest = json.loads((backup / "upgrade.json").read_text(encoding="utf-8"))
|
manifest = json.loads((backup / "upgrade.json").read_text(encoding="utf-8"))
|
||||||
assert manifest["to_version"] == "0.29.4"
|
assert manifest["to_version"] == "0.29.5"
|
||||||
assert manifest["removed_obsolete_runtime_state"] is True
|
assert manifest["removed_obsolete_runtime_state"] is True
|
||||||
assert not legacy.exists()
|
assert not legacy.exists()
|
||||||
assert not (backup / "legacy-data").exists()
|
assert not (backup / "legacy-data").exists()
|
||||||
@@ -619,7 +619,7 @@ def source_contract_smoke() -> None:
|
|||||||
assert marker in admin, marker
|
assert marker in admin, marker
|
||||||
anonymous_block = admin.split("fn anonymous_allowed", 1)[1].split("const CERTIFICATE_API_KEY_HEADER", 1)[0]
|
anonymous_block = admin.split("fn anonymous_allowed", 1)[1].split("const CERTIFICATE_API_KEY_HEADER", 1)[0]
|
||||||
assert '"/api/status"' not in anonymous_block
|
assert '"/api/status"' not in anonymous_block
|
||||||
assert '"RuntimeInformation"' in openapi_0241 and '"version": "0.29.4"' in openapi_0241
|
assert '"RuntimeInformation"' in openapi_0241 and '"version": "0.29.5"' in openapi_0241
|
||||||
assert 'id="panel-logging"' not in html
|
assert 'id="panel-logging"' not in html
|
||||||
for marker in ['id="settings-tab-logging"', 'id="settings-page-logging"', 'id="logging-form"', 'id="error-log-form"']:
|
for marker in ['id="settings-tab-logging"', 'id="settings-page-logging"', 'id="logging-form"', 'id="error-log-form"']:
|
||||||
assert marker in html, marker
|
assert marker in html, marker
|
||||||
@@ -1109,7 +1109,7 @@ def source_contract_smoke() -> None:
|
|||||||
assert '.managed-update' in main and "flush_hot_to_cold_for_update" in main
|
assert '.managed-update' in main and "flush_hot_to_cold_for_update" in main
|
||||||
openapi = (ROOT / "src/admin/openapi.json").read_text(encoding="utf-8")
|
openapi = (ROOT / "src/admin/openapi.json").read_text(encoding="utf-8")
|
||||||
assert '"/api/config/history/restore"' in openapi
|
assert '"/api/config/history/restore"' in openapi
|
||||||
for marker in ['"version": "0.29.4"', '"memory_preset"', '"cold_preset"', '"memory_budget_bytes"', '"max_inflight"', '"preset"', '"session_days"', '"session_lifetime_seconds"', '"accept_local_listener_risk"', '"backend_header"', '"backend_timing_header"', '"cold_on_age"', '"match_type"', '"regex_insensitive"', '"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']:
|
for marker in ['"version": "0.29.5"', '"memory_preset"', '"cold_preset"', '"memory_budget_bytes"', '"max_inflight"', '"preset"', '"session_days"', '"session_lifetime_seconds"', '"accept_local_listener_risk"', '"backend_header"', '"backend_timing_header"', '"cold_on_age"', '"match_type"', '"regex_insensitive"', '"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']:
|
||||||
assert marker in openapi, marker
|
assert marker in openapi, marker
|
||||||
assert '"software_update"' in openapi and '"update_available"' in openapi
|
assert '"software_update"' in openapi and '"update_available"' in openapi
|
||||||
for marker in ['"/api/certificates/deploy"', '"CertificateDeploy"', '"CertificateDeployResult"', '"/api/certificates/reload"']:
|
for marker in ['"/api/certificates/deploy"', '"CertificateDeploy"', '"CertificateDeployResult"', '"/api/certificates/reload"']:
|
||||||
@@ -1314,7 +1314,7 @@ def main() -> None:
|
|||||||
source_contract_smoke()
|
source_contract_smoke()
|
||||||
container_contract_smoke()
|
container_contract_smoke()
|
||||||
grafana_contract_smoke()
|
grafana_contract_smoke()
|
||||||
print("PASS: 0.29.4 SQLite/schema-9/upgrade, local-response 404 hosts, Prometheus/Grafana diagnostics, external Debian-packaging separation, cache/dashboard/logging/Nagios, retained admin/H3/stream/TLS contracts and reproducible source archives.")
|
print("PASS: 0.29.5 SQLite/schema-9/upgrade, local-response 404 hosts, Prometheus/Grafana diagnostics, external Debian-packaging separation, cache/dashboard/logging/Nagios, retained admin/H3/stream/TLS contracts and reproducible source archives.")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -45,6 +45,16 @@ need(vhost,
|
|||||||
"pub source: Source",
|
"pub source: Source",
|
||||||
"vhost requires at least one backend member for Reverse Proxy sources",
|
"vhost requires at least one backend member for Reverse Proxy sources",
|
||||||
"Web Server location cannot pin a reverse-proxy backend")
|
"Web Server location cannot pin a reverse-proxy backend")
|
||||||
|
# PATH_INFO must be dispatched before `try_files` rewrites a valid
|
||||||
|
# Roundcube /static.php/skins/... request into the index.php controller.
|
||||||
|
serve = (web_dir / "serve.rs").read_text()
|
||||||
|
assert serve.index("if let Ok(components) = path_components(&rewritten_path)") < serve.index("match resolve_try_files(")
|
||||||
|
assert "hardening_denied_path(service, &script_path)" in serve
|
||||||
|
assert "htaccess::denied(service.htaccess.as_ref(), &script_path)" in serve
|
||||||
|
need(web,
|
||||||
|
"roundcube_static_php_path_info_runs_before_front_controller_try_files",
|
||||||
|
"without_php_path_info_try_files_keeps_legacy_front_controller_fallback")
|
||||||
|
|
||||||
need(web,
|
need(web,
|
||||||
"pub struct Service",
|
"pub struct Service",
|
||||||
"pub document_root: PathBuf",
|
"pub document_root: PathBuf",
|
||||||
|
|||||||
Reference in new issue
Block a user