Added mod_rewrite for nginx module

This commit is contained in:
alexey
2026-03-23 01:15:59 +03:00
commit 0d2c6277e1
124 changed files with 11079 additions and 0 deletions

10
.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
nginx-1.25.30
.clangd
.devcontainer
Dockerfile
logs
nginx-1.25.3
.zed
tmpbuild

182
BUILD.md Normal file
View File

@@ -0,0 +1,182 @@
# Package Build
## Building a binary file using a Docker image
To build the image, download and extract nginx to the project root:
For example:
```
wget https://nginx.org/download/nginx-1.26.3.tar.gz
tar xvf nginx-1.26.3.tar.gz
```
Next, build the image using the Dockerfile in the project root (build for almalinux:9):
```
docker build -t nginx-mod_rewrite .
```
We obtain an image with nginx and the built mod_rewrite.
To run the image, you need to execute the command:
```
docker run --name nginx-mod_rewrite -p 8080:80 -p 8081:8081 nginx-mod_rewrite
```
## Building the package for a specified operating system
In the project root there is a script `package_preparer.sh` that allows you to build an rpm or deb package for a specific operating system. Which OS and native nginx version the package will be built for depends on the image used.
To build the package, run the command:
```
bash package_preparer.sh prepare "almalinux:9"
```
The build was tested on the following images:
* almalinux:9
* almalinux:8
* rockylinux:9
* ubuntu:24.04
* debian:stable
After the build, the packages will appear in the `tmpbuild` directory:
```
$ ls -1 tmpbuild/*.rpm
tmpbuild/nginx-mod-rewrite-0.1-1.el9.src.rpm
tmpbuild/nginx-mod-rewrite-0.1-1.el9.x86_64.rpm
tmpbuild/nginx-mod-rewrite-debuginfo-0.1-1.el9.x86_64.rpm
tmpbuild/nginx-mod-rewrite-debugsource-0.1-1.el9.x86_64.rpm
```
or
```
$ ls -1 tmpbuild/nginx-mod-rewrite*
tmpbuild/nginx-mod-rewrite_0.1-1_amd64.buildinfo
tmpbuild/nginx-mod-rewrite_0.1-1_amd64.changes
tmpbuild/nginx-mod-rewrite_0.1-1_amd64.deb
tmpbuild/nginx-mod-rewrite_0.1-1.dsc
tmpbuild/nginx-mod-rewrite_0.1-1.tar.gz
tmpbuild/nginx-mod-rewrite-0.1.tar.gz
tmpbuild/nginx-mod-rewrite-dbgsym_0.1-1_amd64.deb
```
To ensure that the command
```bash
bash -x package_preparer.sh prepare "os-image"
```
works correctly, the following utilities must be installed and available on the system (each of which comes from a package supplied by your Linux distribution):
| Utility | What it does in the `prepare` section | Package (Debian/Ubuntu) |
|---------|---------------------------------------|--------------------------|
| **`bash`** | Runs the script itself | already installed |
| **`rm`, `mkdir`, `cp`, `find`, `head`, `gzip`, `tee`** | Cleans/creates directories, copies files, searches, compresses, logs | `coreutils` |
| **`sed`** (GNU sed) | Parameterizes templates, removes/replaces strings | `sed` (GNUsed, the `sed` package from coreutils) |
| **`awk`** | Extracts the version from `CHANGELOG`, generates the changelog file | `gawk` (or the `awk` package) |
| **`tar`** | Creates tar archives, adds files | `tar` |
| **`docker`** (commands `docker build`, `docker run`, `docker rmi`) | Builds a temporary image, runs a container, cleans up | `docker.io` / `docker-ce` + running **daemon** |
> **Note on `sed`**
> The script uses extended regular expressions (`sed -E`) and in-place file modification (`sed -i`). This is the GNU variant of `sed`. On systems where `sed` is the BSD variant (e.g., macOS), the command `sed -E -i` will not work, so in such environments you need to install GNU `sed` (`gsed`) and replace calls with `gsed`.
The Docker daemon must be running, and the user must have permission to use it (or be in the `docker` group).
The main build occurs in a Docker container, so the base system does not get cluttered with extra packages. The set of utilities used by the script is present in the distribution by default, so you only need to install Docker.
The builder analyzes the nginx version installed in the distribution and performs the build in an environment with source files of the same nginx version, using commands similar to those used when building nginx from the distribution.
## Manual build from sources
If you need to build the module yourself, you need to run the following commands.
Here is an example for AlmaLinux 9.
Install the necessary build packages:
```
dnf -y install openssl-devel pcre-devel zlib-devel gcc gcc-c++ make wget
```
In the project root:
```
bash package_preparer.sh download 1.22.1
```
You can specify any available version on the website `https://nginx.org/ru/download.html`; it will be downloaded and extracted into the current directory.
Then change directory into nginx-1.22.1:
```
cd nginx-1.22.1
```
and set the build configuration. Here is an example configuration, it may differ; this example is a typical configuration:
```
./configure \
--with-compat \
--add-dynamic-module=../modules/mod_rewrite \
--prefix=/etc/nginx \
--sbin-path=/usr/sbin/nginx \
--modules-path=/etc/nginx/modules \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--pid-path=/var/run/nginx.pid \
--lock-path=/var/run/nginx.lock \
--http-client-body-temp-path=/var/lib/nginx/body \
--http-proxy-temp-path=/var/lib/nginx/proxy \
--http-fastcgi-temp-path=/var/lib/nginx/fastcgi \
--http-uwsgi-temp-path=/var/lib/nginx/uwsgi \
--http-scgi-temp-path=/var/lib/nginx/scgi \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_realip_module \
--with-http_gzip_static_module \
--with-http_stub_status_module \
--with-http_slice_module \
--with-http_dav_module \
--with-http_auth_request_module \
--with-http_secure_link_module \
--with-stream \
--with-stream_ssl_module \
--with-stream_realip_module \
--with-stream_ssl_preread_module \
--with-pcre-jit
```
During execution, the following should appear:
```
adding module in ../modules/mod_rewrite
+ ngx_http_apache_rewrite_module was configured
```
You can add `--add-dynamic-module=../modules/mod_rewrite` to your nginx configuration.
Next:
```
make modules
```
This command will build the module:
```
# ls -1 objs/ngx_http_apache_rewrite_module.so
objs/ngx_http_apache_rewrite_module.so
```
Then you can move it to the nginx modules directory:
```
cp objs/ngx_http_apache_rewrite_module.so /etc/nginx/modules/
```
and load the module in the nginx configuration file:
```
cat /etc/nginx/nginx.conf
load_module modules/ngx_http_apache_rewrite_module.so;
...
server {
...
HtaccessEnable on;
RewriteEngine On;
location / {
RewriteEngine On;
}
}
```

182
BUILD.ru.md Normal file
View File

@@ -0,0 +1,182 @@
# Сборка пакета
## Сборка бинарного файла с использованием docker-образа
Для сборки образа скачайте и разархивируйте nginx в корень проекта:
Например так:
```
wget https://nginx.org/download/nginx-1.26.3.tar.gz
tar xvf nginx-1.26.3.tar.gz
```
Далее соберите образ, используя Dockerfile в корне проекта (сборка под almalinux:9):
```
docker build -t nginx-mod_rewrite .
```
Получаем образ с nginx и собранным mod_rewrite.
Для запуска образа необходимо выполнить команду:
```
docker run --name nginx-mod_rewrite -p 8080:80 -p 8081:8081 nginx-mod_rewrite
```
## Сборка пакета под заданную операционную систему
В корне проекта расположен скрипт `package_preparer.sh`, который позволяет собрать rpm или deb пакет под определенную операционную систему. Под какую операционную систему и нативную версию nginx будет собран пакет, зависит от используемого образа.
Для сборки пакета необходимо запустить команду:
```
bash package_preparer.sh prepare "almalinux:9"
```
Сборка проверялась на образах:
* almalinux:9
* almalinux:8
* rockylinux:9
* ubuntu:24.04
* debian:stable
После сборки собранные пакеты появятся в каталоге tmpbuild:
```
$ ls -1 tmpbuild/*.rpm
tmpbuild/nginx-mod-rewrite-0.1-1.el9.src.rpm
tmpbuild/nginx-mod-rewrite-0.1-1.el9.x86_64.rpm
tmpbuild/nginx-mod-rewrite-debuginfo-0.1-1.el9.x86_64.rpm
tmpbuild/nginx-mod-rewrite-debugsource-0.1-1.el9.x86_64.rpm
```
или
```
$ ls -1 tmpbuild/nginx-mod-rewrite*
tmpbuild/nginx-mod-rewrite_0.1-1_amd64.buildinfo
tmpbuild/nginx-mod-rewrite_0.1-1_amd64.changes
tmpbuild/nginx-mod-rewrite_0.1-1_amd64.deb
tmpbuild/nginx-mod-rewrite_0.1-1.dsc
tmpbuild/nginx-mod-rewrite_0.1-1.tar.gz
tmpbuild/nginx-mod-rewrite-0.1.tar.gz
tmpbuild/nginx-mod-rewrite-dbgsym_0.1-1_amd64.deb
```
Для того чтобы команда
```bash
bash -x package_preparer.sh prepare "os-image"
```
работала корректно, в системе должны быть установлены и доступны следующие утилиты (каждая из которых берётся из пакета, поставляемого в вашем дистрибутиве Linux):
| Утилита | Что делает в `prepare`‑разделе | Пакет (Debian/Ubuntu) |
|---------|--------------------------------|------------------------|
| **`bash`** | Запуск самого скрипта | уже установлен |
| **`rm`, `mkdir`, `cp`, `find`, `head`, `gzip`, `tee`** | Очистка/создание каталогов, копирование файлов, поиск, сжатие, логирование | `coreutils` |
| **`sed`** (GNU sed) | Параметризация шаблонов, удаление/замена строк | `sed` (GNUsed, пакет `sed` из coreutils) |
| **`awk`** | Извлечение версии из `CHANGELOG`, генерация changelogфайла | `gawk` (или `awk`‑пакет) |
| **`tar`** | Создание tarархива, добавление файлов | `tar` |
| **`docker`** (команды `docker build`, `docker run`, `docker rmi`) | Построение временного образа, запуск контейнера, очистка | `docker.io` / `docker-ce` + работающий **daemon** |
> **Замечание по `sed`**
> В скрипте используются расширенные регулярные выражения (`sed -E`) и модификация файла на месте (`sed -i`). Это GNUвариант `sed`. На системах, где `sed` BSDвариант (например, macOS), команда `sed -E -i` не будет работать, поэтому в таких окружениях нужно установить GNU`sed` (`gsed`) и заменить вызовы на `gsed`.
Dockerдемон должен быть запущен, и пользователь должен иметь права на его использование (или быть в группе `docker`).
Основная сборка происходит в dockerконтейнере, поэтому основная система не засоряется лишними пакетами. Основной набор утилит, используемых скриптом, присутствует в дистрибутиве по умолчанию, поэтому необходимо только установить docker.
Сборщик анализирует версию nginx, установленную в дистрибутиве, и производит сборку в окружении исходных файлов аналогичной версии nginx дистрибутива, а также командами, аналогичными, которые использовались при сборке nginx из дистрибутива.
## Ручная сборка из исходников
Если же необходимо собрать модуль самостоятельно, необходимо выполнить следующие команды.
Привожу пример для Almalinux 9.
Установите необходимые пакеты для сборки:
```
dnf -y install openssl-devel pcre-devel zlib-devel gcc gcc-c++ make wget
```
В корне проекта:
```
bash package_preparer.sh download 1.22.1
```
В качестве версии можно задать любую доступную на сайте `https://nginx.org/ru/download.html`, она будет скачана и распакована в текущий каталог.
Далее перейти в папку nginx-1.22.1:
```
cd nginx-1.22.1
```
и задать конфигурацию сборки. Я привожу пример конфигурации, она может отличаться; данный пример — это типичная конфигурация:
```
./configure \
--with-compat \
--add-dynamic-module=../modules/mod_rewrite \
--prefix=/etc/nginx \
--sbin-path=/usr/sbin/nginx \
--modules-path=/etc/nginx/modules \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--pid-path=/var/run/nginx.pid \
--lock-path=/var/run/nginx.lock \
--http-client-body-temp-path=/var/lib/nginx/body \
--http-proxy-temp-path=/var/lib/nginx/proxy \
--http-fastcgi-temp-path=/var/lib/nginx/fastcgi \
--http-uwsgi-temp-path=/var/lib/nginx/uwsgi \
--http-scgi-temp-path=/var/lib/nginx/scgi \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_realip_module \
--with-http_gzip_static_module \
--with-http_stub_status_module \
--with-http_slice_module \
--with-http_dav_module \
--with-http_auth_request_module \
--with-http_secure_link_module \
--with-stream \
--with-stream_ssl_module \
--with-stream_realip_module \
--with-stream_ssl_preread_module \
--with-pcre-jit
```
Во время выполнения команды должно появиться:
```
adding module in ../modules/mod_rewrite
+ ngx_http_apache_rewrite_module was configured
```
Можно добавить `--add-dynamic-module=../modules/mod_rewrite` в свою конфигурацию nginx.
Далее:
```
make modules
```
данная команда соберет модуль:
```
# ls -1 objs/ngx_http_apache_rewrite_module.so
objs/ngx_http_apache_rewrite_module.so
```
Далее его можно переложить в каталог модулей nginx:
```
cp objs/ngx_http_apache_rewrite_module.so /etc/nginx/modules/
```
и подключить модуль в файле конфигурации nginx:
```
cat /etc/nginx/nginx.conf
load_module modules/ngx_http_apache_rewrite_module.so;
...
server {
...
HtaccessEnable on;
RewriteEngine On;
location / {
RewriteEngine On;
}
}

8
CHANGELOG Normal file
View File

@@ -0,0 +1,8 @@
version: 0.1-1
--
* Sat Mar 14 2026 Alexey BayRepo <a@bayrepo.ru> - 0.1-1
- Added debian/ubuntu build
* Fri Mar 13 2026 Alexey BayRepo <a@bayrepo.ru> - 0.1-0
- New package mod_rewrite for nginx

228
LICENSE Normal file
View File

@@ -0,0 +1,228 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2026 Alexey BayRepo
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
/*
* Copyright (C) 2002-2021 Igor Sysoev
* Copyright (C) 2011-2021 Nginx, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/

470
README.md Normal file
View File

@@ -0,0 +1,470 @@
## Apache mod_rewrite Compatibility Module for Nginx
This module provides Apache's `mod_rewrite` compatibility for Nginx, enabling `.htaccess` file support and standard rewrite rules.
---
# Configuration Directives
The following directives are available in this module:
### 1. RewriteEngine
**Purpose:** Enable or disable the rewrite engine.
| Context | Available Levels |
|----------------|---------------------------------------------|
| `main`, `server`, `location` | ✓ All three levels supported |
**Syntax:**
```nginx
RewriteEngine on|off
```
**Possible Values:**
- `on` - Enable rewrite engine
- `off` - Disable rewrite engine (default)
**Example in nginx.conf:**
```nginx
http {
server {
RewriteEngine on;
location /blog/ {
# Rules apply here
}
location /static/ {
RewriteEngine off; # Disable for this location
}
}
}
```
---
### 2. RewriteRule
**Purpose:** Define a rewrite rule that matches a URL pattern and substitutes it with a new value.
| Context | Available Levels |
|----------------|---------------------------------------------|
| `server`, `location` | ✓ Server and Location level |
| `.htaccess` | ✓ Supported in .htaccess files |
**Syntax:**
```nginx
RewriteRule pattern substitution [flags]
```
**Parameters:**
- **pattern** - Regular expression to match the URL (supports backreferences $1, $2, etc.)
- Pattern can be prefixed with `!` for negation
- `-` as substitution means "no substitution" (only match for conditions)
- **substitution** - Target URL or file path to rewrite to
- Can be a relative path (`/new/path`) or absolute redirect (`http://example.com/new`)
**Flags:** `[flag1,flag2,...]` (optional):
| Flag | Abbreviation | Description |
|------|--------------|-------------|
| `B` | - | Escape backreferences in output (prevent URL encoding issues) |
| `C` | Chain | Chain this rule with the next one (requires previous match) |
| `D` | DPI | Discard path info after substitution |
| `E` | Env=var:val | Set an environment variable for later use (FastCGI integration) |
| `F` | Forbidden | Return HTTP 403 Forbidden status |
| `G` | Gone | Return HTTP 410 Gone status |
| `L` | Last | Stop processing rules after this one |
| `N` | Next=limit | Loop (retry) up to N times for infinite rewriting prevention |
| `P` | Proxy | Proxy the request internally (Phase 2) |
| `Q` | QSA | Append original query string to substitution URL |
| `Q` | QSD | Discard original query string |
| `Q` | QSL | Set "query string last" marker |
| `R` | Redirect=code| Perform external redirect with status code (301, 302, 307) or: Permanent, Temp, SeeOther |
| `S` | Skip=N | Skip N subsequent rules after this one |
| `T` | Type=mime | Force specific MIME type for the response |
| `NC` | - | Case-insensitive matching |
| `NOESCAPE` | - | Do not escape special characters in output |
| `END`| - | Stop all rewriting and pass to content phase |
**Example:**
```nginx
# Redirect /old-path/ to /new-path/ (permanent redirect)
RewriteRule ^old-path/(.*)$ /new-path/$1 [R=301,L]
# Condition-based rewrite (chain)
RewriteCond %REQUEST_URI !^/admin/
RewriteRule ^admin/(.*)$ /login.php?user=$1 [NC,E,END]
# In .htaccess:
RewriteEngine on
RewriteCond %{HTTP_HOST} ^www\.example\.com$ [OR]
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://example.com/$1 [R=301,L]
```
---
### 3. RewriteCond
**Purpose:** Define conditions that must be met before a `RewriteRule` is applied. Multiple conditions are ANDed together; OR flag can change this behavior.
| Context | Available Levels |
|----------------|---------------------------------------------|
| `server`, `location` | ✓ Server and Location level |
| `.htaccess` | ✓ Supported in .htaccess files |
**Syntax:**
```nginx
RewriteCond input_string pattern [flags]
```
**Parameters:**
- **input_string** - Variable or string to test (e.g., `%REQUEST_URI`, `%HTTP_HOST`, `%REQUEST_FILENAME`)
- Common variables: `REQUEST_URI`, `HTTP_HOST`, `REQUEST_METHOD`, `REQUEST_FILENAME`
- **pattern** - Test pattern for the condition:
- **File tests:** `-f` (file exists), `-d` (directory exists), `-x` (executable), `-s` (non-zero size)
- **Link tests:** `-h` or `-l` (hard/soft link), `-L` (link target exists)
- **Integer comparisons:** `-lt`, `-le`, `-eq`, `-gt`, `-ge`, `-ne`
- **String comparisons:** `=`, `>`, `<`, `>=`, `<=`
- **Regular expressions:** Any regex pattern
- **flags** (optional):
- `NC` - Case-insensitive matching
- `OR` / `ornext` - OR logic between conditions (instead of AND)
**Examples:**
```nginx
# Condition: file exists
RewriteCond %REQUEST_FILENAME -f
# Condition: not a directory
RewriteCond %REQUEST_FILENAME !-d
# Condition: string comparison
RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC]
# Integer comparison (file size > 1024 bytes)
RewriteCond %REQUEST_FILENAME -s
RewriteCond %{FILESIZE} -gt 1024
# Multiple conditions with OR logic
RewriteCond %{HTTP_HOST} ^www\.example\.com$ [OR]
RewriteCond %{HTTPS} off
# Regular expression pattern
RewriteCond %{REQUEST_URI} ^/old/(.*)$
```
---
### 4. RewriteBase
**Purpose:** Set the base URL path for relative substitutions in rewrite rules.
| Context | Available Levels |
|----------------|---------------------------------------------|
| `location` | ✓ Location level only |
| `.htaccess` | ✓ Supported in .htaccess files |
**Syntax:**
```nginx
RewriteBase /path/
```
**Parameters:**
- Must start with `/` and end with `/` (trailing slash required)
**Example:**
```nginx
# Inside a specific location
location /blog/ {
RewriteBase /blog/
# Relative substitution resolves to: /blog/news.html
RewriteRule ^news\.html$ news.php [L]
}
# In .htaccess:
RewriteEngine on
RewriteBase /subdir/
RewriteRule ^index\.html$ home.php [L]
```
---
### 5. RewriteOptions
**Purpose:** Configure rewrite behavior options that affect rule inheritance and processing order.
| Context | Available Levels |
|----------------|---------------------------------------------|
| `main`, `server`, `location` | ✓ All three levels supported |
**Syntax:**
```nginx
RewriteOptions option1 [option2 ...]
```
**Possible Options:**
| Option | Description |
|-----------------|-------------|
| `Inherit` | Inherit rules from parent locations/servers (default behavior) |
| `InheritBefore` | Process parent rules before child rules |
**Example:**
```nginx
http {
server {
RewriteOptions Inherit
location /parent/ {
RewriteRule ^parent/(.*)$ /new/$1 [L]
}
location /child/ {
# Inherits rules from parent by default with 'Inherit' option
RewriteBase /child/
}
}
}
```
---
### 6. RewriteMap
**Purpose:** Define name-value mappings for lookup tables in rewrite expressions.
| Context | Available Levels |
|----------------|---------------------------------------------|
| `server` | ✓ Server level only |
| `.htaccess` | ✗ Not supported in .htaccess |
**Syntax:**
```nginx
RewriteMap name type:source
```
**Parameters:**
- **name** - Map identifier (used as variable like `%MAPNAME:value`)
- **type** - Type of map:
- `int` - Internal function (supported: `tolower`, `toupper`, `escape`, `unescape`)
- `txt` - Text file lookup (not supported yet)
- `rnd` - Random lookup (not supported yet)
- `prg` - Program lookup (not supported yet)
**Example:**
```nginx
server {
# Map to lowercase version of hostname
RewriteMap lc int:tolower
server_name example.com;
location / {
# Use mapped value in rule
RewriteRule ^lc/([^/]+)$ /redirect/$1 [L]
}
}
# Usage in rules:
RewriteRule ^(.*)$ /%lc:$1 [L]
```
---
### 7. HtaccessEnable
**Purpose:** Enable or disable `.htaccess` file parsing for the server/location.
| Context | Available Levels |
|----------------|---------------------------------------------|
| `main`, `server` | ✓ Main and Server level only |
| `.htaccess` | ✗ Not supported in .htaccess |
**Syntax:**
```nginx
HtaccessEnable on|off
```
**Parameters:**
- `on` - Enable `.htaccess` parsing
- `off` - Disable `.htaccess` parsing (default)
**Example:**
```nginx
http {
server {
HtaccessEnable on
# .htaccess files will be searched upward from the request path
location /subdir/ {
root /var/www/html;
}
}
}
# In nginx.conf, you can also enable at http level:
http {
HtaccessEnable on;
server {
# Inherits setting from http level
}
}
```
---
### 8. HtaccessName
**Purpose:** Specify an alternative filename for `.htaccess` files.
| Context | Available Levels |
|----------------|---------------------------------------------|
| `main`, `server` | ✓ Main and Server level only |
| `.htaccess` | ✗ Not supported in .htaccess |
**Syntax:**
```nginx
HtaccessName filename
```
**Parameters:**
- Any string (default is `.htaccess`)
**Example:**
```nginx
server {
HtaccessName .webconfig
# Will look for .webconfig files instead of .htaccess
location / {
root /var/www/html;
}
}
# In .htaccess:
HtaccessEnable on
HtaccessName .custom_htac
```
---
### 9. RewriteFallBack
**Purpose:** Specify an alternative fallback path when the rewritten file doesn't exist (instead of default `/index.php`). This directive can only be used in `.htaccess` files and is read by `ngx_htaccess_parse_file_from_ha()`. The fallback path is cached per request and retrieved in `ngx_http_apache_rewrite_url_register_hook_with_fallback()`.
| Context | Available Levels |
|----------------|---------------------------------------------|
| `.htaccess` | ✓ Supported only in .htaccess files |
**Syntax:**
```apache
RewriteFallBack /path/to/fallback.php
```
**Parameters:**
- Path must start with `/` (required)
- Default fallback is `/index.php` if not specified
**Example in .htaccess:**
```apache
# Use custom fallback when file not found
RewriteFallBack /custom/app.php
# Fallback will redirect to /custom/app.php?query_string instead of /index.php
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]
```
**Example with query string:**
```apache
RewriteFallBack /handler.php?lang=ru
# When file doesn't exist, request is redirected to the fallback path
# preserving the original query string if present
```
---
## Configuration Levels Summary
| Directive | http/main | server | location | .htaccess |
|-----------------|-----------|--------|----------|-----------|
| RewriteEngine | ✓ | ✓ | ✓ | ✓ |
| RewriteRule | - | ✓ | ✓ | ✓ |
| RewriteCond | - | ✓ | ✓ | ✓ |
| RewriteBase | - | - | ✓ | ✓ |
| RewriteOptions | ✓ | ✓ | ✓ | - |
| RewriteMap | - | ✓ | - | - |
| HtaccessEnable | ✓ | ✓ | - | - |
| HtaccessName | ✓ | ✓ | - | - |
| RewriteFallBack | ✗ | ✗ | - | ✓ |
---
## .htaccess File Format
`.htaccess` files follow the Apache format:
```apache
# Comments start with #
RewriteEngine on
RewriteCond %{REQUEST_URI} ^/old/(.*)$
RewriteRule ^old/(.*)$ /new/$1 [R=301,L]
# Multiple conditions (AND logic by default)
RewriteCond %{HTTP_HOST} www\.example\.com$
RewriteCond %{HTTPS} off
RewriteRule (.*) https://www.example.com/$1 [R=301,L]
RewriteBase /subdir/
RewriteFallBack /custom/fallback.php
RewriteCond !-f
RewriteCond !-d
RewriteRule ^(.*)$ index.php?route=$1 [QSA,L]
```
When a file doesn't exist after URL rewriting, the module falls back to:
1. `RewriteFallBack` path if specified in `.htaccess`, or
2. `/index.php` as default fallback
---
## Module Features
### FastCGI Integration
The module automatically passes environment variables (set via `[E=VAR:VAL]` flags) to FastCGI applications without manual configuration.
### Environment Variables
Environment variables persist across request phases and are available for downstream modules.
### .htaccess Caching
`.htaccess` files are cached by modification time in the request pool to improve performance on repeated requests. The `RewriteFallBack` directive is also stored in the cache entry and retrieved when needed.
### RewriteFallBack Directive
The `RewriteFallBack` directive allows customizing the fallback path used when a rewritten file doesn't exist:
1. **Caching:** The fallback path from `.htaccess` is cached per request to avoid repeated parsing
2. **Fallback Logic:** When `try_files` fails, the module redirects to the configured fallback instead of `/index.php`
3. **Query String Preservation:** Original query string is preserved and appended to fallback path
---
## Notes
- Rewrite rules are processed in order as defined in configuration or `.htaccess` files
- The `[L]` flag stops processing at the current rule
- The `[N]` flag enables looping for infinite redirects prevention (max 10,000 rounds)
- Location-level rules override server-level rules unless `Inherit` option is set

481
README.ru.md Normal file
View File

@@ -0,0 +1,481 @@
## Модуль совместимости Apache mod_rewrite для Nginx
Этот модуль обеспечивает совместимость Apache `mod_rewrite` для Nginx, позволяя использовать файлы `.htaccess` и стандартные правила переписывания.
---
# Директивы конфигурации
Ниже перечислены доступные директивы в этом модуле:
### 1. RewriteEngine
**Назначение:** Включить или отключить механизм переписывания.
| Контекст | Доступные уровни |
|----------|------------------|
| `main`, `server`, `location` | ✓ Поддерживаются все три уровня |
**Синтаксис:**
```nginx
RewriteEngine on|off
```
**Возможные значения:**
- `on` - Включить механизм переписывания
- `off` - Отключить механизм переписывания (по умолчанию)
**Пример в nginx.conf:**
```nginx
http {
server {
RewriteEngine on;
location /blog/ {
# Правила применяются здесь
}
location /static/ {
RewriteEngine off; # Отключить для этой локации
}
}
}
```
---
### 2. RewriteRule
**Назначение:** Определить правило переписывания, которое сопоставляет шаблон URL и заменяет его новым значением.
| Контекст | Доступные уровни |
|----------|------------------|
| `server`, `location` | ✓ Уровни сервера и локации |
| `.htaccess` | ✓ Поддерживается в файлах .htaccess |
**Синтаксис:**
```nginx
RewriteRule pattern substitution [flags]
```
**Параметры:**
- **pattern** - Регулярное выражение для сопоставления URL (поддерживаются обратные ссылки $1, $2 и т.д.)
- Паттерн может быть префиксирован `!` для отрицания
- `-` в качестве замены означает "нет замены" (только проверка условий)
- **substitution** - Целевой URL или путь файла для переписывания
- Может быть относительным путем (`/new/path`) или абсолютным перенаправлением (`http://example.com/new`)
**Флаги:** `[flag1,flag2,...]` (необязательно):
| Флаг | Короткая запись | Описание |
|------|-----------------|----------|
| `B` | - | Экранировать обратные ссылки в выводе (предотвратить проблемы с кодировкой URL) |
| `C` | Chain | Присоединить это правило к следующему (требует предыдущего совпадения) |
| `D` | DPI | Отбросить информацию о пути после замены |
| `E` | Env=var:val | Установить переменную окружения для последующего использования (FastCGI интеграция) |
| `F` | Forbidden | Вернуть статус HTTP 403 Forbidden |
| `G` | Gone | Вернуть статус HTTP 410 Gone |
| `L` | Last | Остановить обработку правил после этого |
| `N` | Next=limit | Цикл (повторить) до N раз для предотвращения бесконечных переписываний |
| `P` | Proxy | Проксировать запрос внутренне (Фаза 2) |
| `Q` | QSA | Прикрепить исходную строку запроса к URL замены |
| `Q` | QSD | Удалить исходную строку запроса |
| `Q` | QSL | Установить маркер "query string last" |
| `R` | Redirect=code | Выполнить внешнее перенаправление со статусом (301, 302, 307) или: Permanent, Temp, SeeOther |
| `S` | Skip=N | Пропустить N последующих правил после этого |
| `T` | Type=mime | Принудительно установить MIME-тип для ответа |
| `NC` | - | Непрерывное сопоставление без учета регистра |
| `NOESCAPE` | - | Не экранировать специальные символы в выводе |
| `END`| - | Остановить все переписывания и перейти к фазе контента |
**Пример:**
```nginx
# Перенаправление /old-path/ в /new-path/ (постоянное перенаправление)
RewriteRule ^old-path/(.*)$ /new-path/$1 [R=301,L]
# Условное переписывание (цепочка)
RewriteCond %REQUEST_URI !^/admin/
RewriteRule ^admin/(.*)$ /login.php?user=$1 [NC,E,END]
# В .htaccess:
RewriteEngine on
RewriteCond %{HTTP_HOST} ^www\.example\.com$ [OR]
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://example.com/$1 [R=301,L]
```
---
### 3. RewriteCond
**Назначение:** Определить условия, которые должны быть выполнены, прежде чем будет применено правило `RewriteRule`. Множественные условия объединяются по умолчанию (AND); можно изменить это с помощью флага OR.
| Контекст | Доступные уровни |
|----------|------------------|
| `server`, `location` | ✓ Уровни сервера и локации |
| `.htaccess` | ✓ Поддерживается в файлах .htaccess |
**Синтаксис:**
```nginx
RewriteCond input_string pattern [flags]
```
**Параметры:**
- **input_string** - Переменная или строка для проверки (например, `%REQUEST_URI`, `%HTTP_HOST`, `%REQUEST_METHOD`, `%REQUEST_FILENAME`)
- Общие переменные: `REQUEST_URI`, `HTTP_HOST`, `REQUEST_METHOD`, `REQUEST_FILENAME`
- **pattern** - Шаблон проверки:
- **Тесты файлов:** `-f` (файл существует), `-d` (директория существует), `-x` (исполняемый), `-s` (не нулевой размер)
- **Тесты ссылок:** `-h` или `-l` (жёсткая/мягкая ссылка), `-L` (цель ссылки существует)
- **Сравнение целых чисел:** `-lt`, `-le`, `-eq`, `-gt`, `-ge`, `-ne`
- **Сравнение строк:** `=`, `>`, `<`, `>=`, `<=`
- **Регулярные выражения:** любой шаблон regex
- **flags** (необязательно):
- `NC` - Не чувствительно к регистру
- `OR` / `ornext` - Логика OR между условиями (вместо AND)
**Примеры:**
```nginx
# Условие: файл существует
RewriteCond %REQUEST_FILENAME -f
# Условие: не директория
RewriteCond %REQUEST_FILENAME !-d
# Сравнение строк
RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC]
# Сравнение целых чисел (размер файла > 1024 байт)
RewriteCond %REQUEST_FILENAME -s
RewriteCond %{FILESIZE} -gt 1024
# Множественные условия с OR
RewriteCond %{HTTP_HOST} ^www\.example\.com$ [OR]
RewriteCond %{HTTPS} off
# Регулярный шаблон
RewriteCond %{REQUEST_URI} ^/old/(.*)$
```
---
### 4. RewriteBase
**Назначение:** Установить базовый URL-путь для относительных замен в правилах переписывания.
| Контекст | Доступные уровни |
|----------|------------------|
| `location` | ✓ Только уровень локации |
| `.htaccess` | ✓ Поддерживается в файлах .htaccess |
**Синтаксис:**
```nginx
RewriteBase /path/
```
**Параметры:**
- Должен начинаться с `/` и заканчиваться `/` (обязательный слеш)
**Пример:**
```nginx
# Внутри конкретной локации
location /blog/ {
RewriteBase /blog/
# Относительная замена разрешается: /blog/news.html
RewriteRule ^news\.html$ news.php [L]
}
# В .htaccess:
RewriteEngine on
RewriteBase /subdir/
RewriteRule ^index\.html$ home.php [L]
```
---
### 5. RewriteOptions
**Назначение:** Настроить параметры поведения переписывания, которые влияют на наследование правил и порядок их обработки.
| Контекст | Доступные уровни |
|----------|------------------|
| `main`, `server`, `location` | ✓ Все три уровня поддерживаются |
**Синтаксис:**
```nginx
RewriteOptions option1 [option2 ...]
```
**Возможные опции:**
| Опция | Описание |
|----------------|----------|
| `Inherit` | Наследовать правила из родительских локаций/серверов (поведение по умолчанию) |
| `InheritBefore` | Обрабатывать правила родительских локаций до правил дочерних |
**Пример:**
```nginx
http {
server {
RewriteOptions Inherit
location /parent/ {
RewriteRule ^parent/(.*)$ /new/$1 [L]
}
location /child/ {
# Наследует правила от родителя по умолчанию с опцией 'Inherit'
RewriteBase /child/
}
}
}
```
---
### 6. RewriteMap
**Назначение:** Определить имя‑значение карты для поиска таблиц в выражениях переписывания.
| Контекст | Доступные уровни |
|----------|------------------|
| `server` | ✓ Только уровень сервера |
| `.htaccess` | ✗ Не поддерживается в .htaccess |
**Синтаксис:**
```nginx
RewriteMap name type:source
```
**Параметры:**
- **name** - Идентификатор карты (используется как переменная, например `%MAPNAME:value`)
- **type** - Тип карты:
- `int` - Внутренняя функция (поддерживаемые: `tolower`, `toupper`, `escape`, `unescape`)
- `txt` - Поиск по текстовому файлу (пока не реализовано)
- `rnd` - Поиск по случайному (пока не реализовано)
- `prg` - Поиск по программе (пока не реализовано)
**Пример:**
```nginx
server {
# Карта к нижнему регистру имени хоста
RewriteMap lc int:tolower
server_name example.com;
location / {
# Использовать сопоставленное значение в правиле
RewriteRule ^lc/([^/]+)$ /redirect/$1 [L]
}
}
# Использование в правилах:
RewriteRule ^(.*)$ /%lc:$1 [L]
```
---
### 7. HtaccessEnable
**Назначение:** Включить или отключить разбор файлов `.htaccess` для сервера/локации.
| Контекст | Доступные уровни |
|----------|------------------|
| `main`, `server` | ✓ Только уровни основного и сервера |
| `.htaccess` | ✗ Не поддерживается в .htaccess |
**Синтаксис:**
```nginx
HtaccessEnable on|off
```
**Параметры:**
- `on` - Включить разбор `.htaccess`
- `off` - Отключить разбор `.htaccess` (по умолчанию)
**Пример:**
```nginx
http {
server {
HtaccessEnable on
# Файлы .htaccess будут искаться вверх от пути запроса
location /subdir/ {
root /var/www/html;
}
}
}
# В nginx.conf, можно включить на уровне http:
http {
HtaccessEnable on;
server {
# Наследует настройку из уровня http
}
}
```
---
### 8. HtaccessName
**Назначение:** Указать альтернативное имя файла для файлов `.htaccess`.
| Контекст | Доступные уровни |
|----------|------------------|
| `main`, `server` | ✓ Только уровни основного и сервера |
| `.htaccess` | ✗ Не поддерживается в .htaccess |
**Синтаксис:**
```nginx
HtaccessName filename
```
**Параметры:**
- Любая строка (по умолчанию `.htaccess`)
**Пример:**
```nginx
server {
HtaccessName .webconfig
# Будет искать файлы .webconfig вместо .htaccess
location / {
root /var/www/html;
}
}
# В .htaccess:
HtaccessEnable on
HtaccessName .custom_htac
```
---
### 9. RewriteFallBack
**Назначение:** Указать альтернативный путь отката, если переписанный файл не существует (вместо стандартного `/index.php`). Эта директива может использоваться только в файлах `.htaccess` и читается функцией `ngx_htaccess_parse_file_from_ha()`. Путь отката кэшируется для каждого запроса и извлекается в `ngx_http_apache_rewrite_url_register_hook_with_fallback()`.
| Контекст | Доступные уровни |
|-----------------|----------------------------------------------|
| `.htaccess` | ✓ Поддерживается только в файлах .htaccess |
**Синтаксис:**
```apache
RewriteFallBack /path/to/fallback.php
```
**Параметры:**
- Путь должен начинаться с `/` (обязательно)
- По умолчанию путь отката `/index.php`, если не указан
**Пример в .htaccess:**
```apache
# Использовать пользовательский откат, если файл не найден
RewriteFallBack /custom/app.php
# Откат перенаправит на /custom/app.php?query_string вместо /index.php
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]
```
**Пример с параметром строки запроса:**
```apache
RewriteFallBack /handler.php?lang=ru
# Если файл не существует, запрос будет перенаправлен на путь отката
# с сохранением исходной строки запроса, если она присутствует
```
---
## Сводка уровней конфигурации
| Директива | http/main | server | location | .htaccess |
|------------------|-----------|--------|----------|-----------|
| RewriteEngine | ✓ | ✓ | ✓ | ✓ |
| RewriteRule | - | ✓ | ✓ | ✓ |
| RewriteCond | - | ✓ | ✓ | ✓ |
| RewriteBase | - | - | ✓ | ✓ |
| RewriteOptions | ✓ | ✓ | ✓ | - |
| RewriteMap | - | ✓ | - | - |
| HtaccessEnable | ✓ | ✓ | - | - |
| HtaccessName | ✓ | ✓ | - | - |
| RewriteFallBack | ✗ | ✗ | - | ✓ |
---
## Формат файла .htaccess
Файлы `.htaccess` следуют формату Apache:
```apache
# Комментарии начинаются с #
RewriteEngine on
RewriteCond %{REQUEST_URI} ^/old/(.*)$
RewriteRule ^old/(.*)$ /new/$1 [R=301,L]
# Множественные условия (логика AND по умолчанию)
RewriteCond %{HTTP_HOST} www\.example\.com$
RewriteCond %{HTTPS} off
RewriteRule (.*) https://www.example.com/$1 [R=301,L]
RewriteBase /subdir/
RewriteFallBack /custom/fallback.php
RewriteCond !-f
RewriteCond !-d
RewriteRule ^(.*)$ index.php?route=$1 [QSA,L]
```
Если после переписывания URL файл не существует, модуль переходит к:
1. Путь `RewriteFallBack`, если указан в `.htaccess`, или
2. `/index.php` как стандартный откат
```
---
## Возможности модуля
### Интеграция с FastCGI
Модуль автоматически передаёт переменные окружения (установленные через флаги `[E=VAR:VAL]`) в приложения FastCGI без дополнительной настройки.
### Переменные окружения
Переменные окружения сохраняются между фазами запроса и доступны downstream-модулям.
### Кэширование .htaccess
Файлы `.htaccess` кэшируются по времени изменения в памяти запроса, чтобы ускорить повторные запросы.
### Директива RewriteFallBack
Директива `RewriteFallBack` позволяет настроить путь отката, который используется, когда переписанный файл не существует:
1. **Кеширование:** Путь отката из `.htaccess` кэшируется на каждый запрос, чтобы избежать повторного разборов.
2. **Логика отката:** Когда `try_files` завершается неудачей, модуль перенаправляет на заданный откат вместо `/index.php`.
3. **Сохранение строки запроса:** Исходная строка запроса сохраняется и добавляется к пути отката.
```
---
## Заметки
- Правила переписывания обрабатываются в порядке, определенном в конфигурации или файлах `.htaccess`
- Флаг `[L]` останавливает обработку на текущем правиле
- Флаг `[N]` включает цикл для предотвращения бесконечных перенаправлений (максимум 10000 раундов)
- Правила уровня локации переопределяют правила уровня сервера, если не задана опция `Inherit`

20
build Normal file
View File

@@ -0,0 +1,20 @@
#!/bin/bash
cd nginx-1.25.3
make clean
./configure --with-compat --add-dynamic-module=../modules/mod_rewrite --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/etc/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/lib/nginx/body --http-proxy-temp-path=/var/lib/nginx/proxy --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --http-scgi-temp-path=/var/lib/nginx/scgi --with-http_ssl_module --with-http_v2_module --with-http_realip_module --with-http_gzip_static_module --with-http_stub_status_module --with-http_slice_module --with-http_dav_module --with-http_auth_request_module --with-http_secure_link_module --with-stream --with-stream_ssl_module --with-stream_realip_module --with-stream_ssl_preread_module --with-pcre-jit --with-debug
if [ $? -ne 0 ]; then
cd ..
exit 1
fi
make modules
if [ $? -ne 0 ]; then
cd ..
exit 1
fi
make install
if [ $? -ne 0 ]; then
cd ..
exit 1
fi
cd ..

View File

@@ -0,0 +1,25 @@
RewriteEngine on
RewriteRule ^ - [E=protossl]
RewriteCond %{HTTPS} on
RewriteRule ^ - [E=protossl:s]
RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteRule "/\.|^\.(?!well-known/)" - [F]
RewriteCond %{REQUEST_URI} ^(.*)?/(install\.php) [OR]
RewriteCond %{REQUEST_URI} ^(.*)?/(rebuild\.php)
RewriteCond %{REQUEST_URI} !core
RewriteRule ^ %1/core/%2 [L,QSA,R=301]
RewriteRule ^core/install\.php core/install.php?rewrite=ok [QSA,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !=/favicon.ico
RewriteRule ^ index.php [L]
RewriteCond %{REQUEST_URI} !/core/[^/]*\.php$
RewriteCond %{REQUEST_URI} !/core/modules/system/tests/https?\.php
RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{REQUEST_FILENAME}\.gz -s
RewriteRule ^(.*css_[a-zA-Z0-9-_]+)\.css$ $1\.css\.gz [QSA]
RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{REQUEST_FILENAME}\.gz -s
RewriteRule ^(.*js_[a-zA-Z0-9-_]+)\.js$ $1\.js\.gz [QSA]
RewriteRule \.css\.gz$ - [T=text/css,E=no-gzip:1,E=no-brotli:1]
RewriteRule \.js\.gz$ - [T=text/javascript,E=no-gzip:1,E=no-brotli:1]

19
cms/drupal/.htaccess Normal file
View File

@@ -0,0 +1,19 @@
RewriteEngine on
RewriteRule ^ - [E=protossl]
RewriteCond %{HTTPS} on
RewriteRule ^ - [E=protossl:s]
RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteRule "/\.|^\.(?!well-known/)" - [F]
RewriteCond %{REQUEST_URI} ^(.*)?/(install\.php) [OR]
RewriteCond %{REQUEST_URI} ^(.*)?/(rebuild\.php)
RewriteCond %{REQUEST_URI} !core
RewriteRule ^ %1/core/%2 [L,QSA,R=301]
RewriteRule ^core/install\.php core/install.php?rewrite=ok [QSA,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !=/favicon.ico
RewriteRule ^ index.php [L]
RewriteCond %{REQUEST_URI} !/core/[^/]*\.php$
RewriteCond %{REQUEST_URI} !/core/modules/system/tests/https?\.php
RewriteCond %{REQUEST_URI} !/core/modules/statistics/statistics\.php$
RewriteRule "^(.+/.*|autoload)\.php($|/)" - [F]

1
cms/drupal/.htpasswd Normal file
View File

@@ -0,0 +1 @@
# This is a test .htpasswd hidden file - should be blocked by RewriteRule "/\.|^\.(?!well-known/)" - [F]

View File

@@ -0,0 +1,6 @@
# This is a robots.txt file for Drupal site testing
# Should be allowed by RewriteRule "/\.|^\.(?!well-known/)" - [F] exception
User-agent: *
Disallow: /admin/
Allow: /

162
cms/drupal/README.md Normal file
View File

@@ -0,0 +1,162 @@
# Drupal .htaccess Test Structure
## Directory Layout Overview
```
/test1/cms/drupal/
├── core/ - Drupal core directory tests
│ ├── install.php - Install script (protected by RewriteRule ^core/install\.php)
│ ├── rebuild.php - Rebuild script (protected by RewriteRule ^core/rebuild\.php)
│ └── modules/system/tests/ - Tests directory with https/http.php files
│ ├── https.php - Test HTTPS test file (excluded from routing)
│ └── http.php - Test HTTP test file (excluded from routing)
├── favicon.ico - Existing favicon for !-f condition test
├── index.php - Drupal entry point (routes non-existing files)
│ - Returns: "Drupal Content Route" page
├── .htaccess - Hidden .htaccess file (blocked by hidden files rule)
├── .htpasswd - Hidden .htpasswd file (blocked by hidden files rule)
├── .well-known/ - Well-known directory (allowed exception)
│ └── robots.txt - Allowed file via exception (?!well-known)
├── somedir/ - Directory for testing !-d condition (200 OK)
├── test-drupal-rewriterules.sh - Bash script to test all rules using curl
└── README.md - This documentation file
```
## Apache Rules Explained - Drupal
### 1. RewriteEngine Activation
```apache
RewriteEngine on
```
**Что делает:** Включает модуль mod_rewrite для этого каталога
**Зачем нужно:** Без этого все правила rewrite не работают
### 2. Protocol Variables (HTTP/HTTPS Detection)
```apache
RewriteRule ^ - [E=protossl]
RewriteCond %{HTTPS} on
RewriteRule ^ - [E=protossl:s]
```
**Что делает:** Устанавливает переменную окружения `protossl` = "https" или "http" в зависимости от HTTPS status
**Зачем нужно:** Drupal использует это для генерации правильных ссылок (http vs https)
- Если HTTPS off → protossl = "" (пусто, http)
- Если HTTPS on → protossl = "s"
### 3. HTTP Authorization Header Passing
```apache
RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
```
**Что делает:** Копирует Authorization header в переменную HTTP_AUTHORIZATION для PHP
**Зачем нужно:** Drupal REST API требует эту переменную для аутентификации
### 4. Hidden Files/Patterns Protection Rule
```apache
RewriteRule "/\.|^\.(?!well-known/)" - [F]
```
**Что делает:** Блокирует (403 Forbidden) файлы начинающиеся с точки, кроме .well-known/
- `/\.` - блокирует /filename.хотя бы одна точка в path
- `^\.(?!well-known/)` - блокирует /隐藏文件, но исключает /well-known/
**Зачем нужно:** Защита от доступа к скрытым файлам (.htaccess, .htpasswd, .git)
**Исключение:** .well-known/robots.txt разрешён (для SEO и security)
### 5. Core install/rebuild.php Protection Rules
```apache
RewriteCond %{REQUEST_URI} ^(.*)?/(install\.php) [OR]
RewriteCond %{REQUEST_URI} ^(.*)?/(rebuild\.php)
RewriteCond %{REQUEST_URI} !core
RewriteRule ^ %1/core/%2 [L,QSA,R=301]
```
**Что делает:** Редирект 301 с /install.php → /core/install.php, /rebuild.php → /core/rebuild.php
**Зачем нужно:** Перемещает install/rebuild скрипты в core directory для безопасности
### 6. Core install.php Rewrite
```apache
RewriteRule ^core/install\.php core/install.php?rewrite=ok [QSA,L]
```
**Что делает:** Переписывает запрос на core/install.php с добавлением параметра rewrite=ok
**Зачем нужно:** Drupal internal handling для процесса установки
### 7. Main Drupal Routing Rules (!-f, !-d)
```apache
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !=/favicon.ico
RewriteRule ^ index.php [L]
```
**Что делает:** Маршрутизирует через index.php все запросы на несуществующие файлы И директории, кроме favicon.ico
**Зачем нужно:** "Чистые URL" Drupal (похоже на WordPress), routing через index.php
### 8. Core Modules Tests Files Exceptions
```apache
RewriteCond %{REQUEST_URI} !/core/[^/]*\.php$
RewriteCond %{REQUEST_URI} !/core/modules/system/tests/https?\.php
```
**Что делает:** Исключает из routing core/*.php и tests/https.php/http.php файлы
**Зачем нужно:** Эти файлы должны обрабатываться напрямую, не через main index.php
## Test Script Features
The script includes test functions:
1. **test_rule()** - checks HTTP status code only
2. **test_rule_content()** - checks both status AND response body content
## Multisite-Specific Testing Scenarios
### Hidden Files Protection
| URL | Правило | Ожидаемый результат |
|-----|---------|---------------------|
| `http://test.my.brp/.htaccess` | hidden files rule `/\.|^\.(?!well-known/)` | 403 Forbidden ✓ |
| `http://test.my.brp/.htpasswd` | hidden files rule | 403 Forbidden ✓ |
| `http://test.my.brp/.well-known/robots.txt` | well-known exception | 200 OK (allowed) ✓ |
### Core install/rebuild.php Protection
| URL | Правило | Ожидаемый результат |
|-----|---------|---------------------|
| `http://test.my.brp/install.php` | RewriteRule ^(.*)?/(install\.php) + core exclusion | 301 → /core/install.php ✓ |
| `http://test.my.brp/rebuild.php` | RewriteRule ^(.*)?/(rebuild\.php) + core exclusion | 301 → /core/rebuild.php ✓ |
### Core Files Routing Exceptions
| URL | Правило | Ожидаемый результат |
|-----|---------|---------------------|
| `http://test.my.brp/core/install.php` | RewriteRule ^core/install\.php ... rewrite=ok parameter | 200 OK ✓ |
| `http://test.my.brp/core/modules/system/tests/https.php` | tests exception !-https?\.php condition | 200 OK ✓ |
| `http://test.my.brp/core/modules/system/tests/http.php` | tests exception !-https?\.php condition (s matches empty) | 200 OK ✓ |
## Run Tests
Execute the test script to verify all rules:
```bash
cd /home/alexey/projects/workspace-zed/test1/cms/drupal
./test-drupal-rewriterules.sh
```
Expected results for Drupal tests (all should be **PASS ✓**):
- Basic page routing via index.php: HTTP 200 + "Drupal Content Route" ✓
- Hidden files (.htaccess, .htpasswd) blocked: HTTP 403 ✓
- .well-known/robots.txt allowed: HTTP 200 ✓
- install.php redirect to core: HTTP 301 ✓
- rebuild.php redirect to core: HTTP 301 ✓
- favicon.ico direct access (!-f): HTTP 200 ✓
- Non-existing page routing to index.php: HTTP 200 ✓
- Directory access (!-d): HTTP 200 ✓
- Tests files https.php/http.php excluded from routing: HTTP 200 ✓

View File

@@ -0,0 +1 @@
# This is a test Drupal core/install.php file - should be protected by RewriteRule ^core/install\.php core/install.php?rewrite=ok [QSA,L]

View File

@@ -0,0 +1 @@
# This is a test Drupal http.php in tests directory - should be excluded from index.php routing by RewriteCond %{REQUEST_URI} !/core/modules/system/tests/https?\.php

View File

@@ -0,0 +1 @@
# This is a test Drupal https.php in tests directory - should be excluded from index.php routing by RewriteCond %{REQUEST_URI} !/core/modules/system/tests/https?\.php

View File

@@ -0,0 +1 @@
# This is a test Drupal core/rebuild.php file - should be protected by RewriteRule ^core/rebuild\.php core/rebuild.php?rewrite=ok [QSA,L]

2
cms/drupal/favicon.ico Normal file
View File

@@ -0,0 +1,2 @@
# This is a placeholder for Drupal test favicon.ico
# Real favicon would be an image file, but we use text for testing

15
cms/drupal/index.php Normal file
View File

@@ -0,0 +1,15 @@
<?php
/**
* Drupal - index.php (Test version)
* This file handles routing for non-existing files/directories
*/
// Simulated Drupal response
echo "<html><head><title>Drupal Test Site</title></head><body>";
echo "<h1>Drupal Content Route</h1>";
echo "<p>This page is served by index.php via RewriteRule.</p>";
echo "<div class='drupal-config'>Drupal Configuration Loaded</div>";
echo "</body></html>";
// Exit
exit;

159
cms/drupal/nginx.conf Normal file
View File

@@ -0,0 +1,159 @@
load_module modules/ngx_http_apache_rewrite_module.so;
worker_processes 1;
error_log logs/error.log debug;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 8081;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
server {
listen 8081;
server_name example1.com;
root /sites/site1;
HtaccessEnable on;
RewriteEngine On;
location / {
RewriteEngine On;
}
}
server {
listen 8081;
server_name example2.com;
root /sites/site2;
HtaccessEnable on;
RewriteEngine On;
index index.php;
location / {
RewriteEngine On;
autoindex on;
}
location ~* \.php$ {
RewriteEngine On;
include fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php-fpm/www.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
server {
listen 8081;
server_name example3.com;
root /sites/site3;
HtaccessEnable on;
RewriteEngine On;
index index.php;
location / {
RewriteEngine On;
autoindex on;
}
location ~* \.php$ {
RewriteEngine On;
include fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php-fpm/www.sock; # подключаем сокет php-fpm
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
server {
listen 8081;
server_name example4.com;
root /sites/site4;
HtaccessEnable on;
RewriteEngine On;
index index.php;
location / {
RewriteEngine On;
autoindex on;
}
location ~* \.php$ {
RewriteEngine On;
include fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php-fpm/www.sock; # подключаем сокет php-fpm
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
server {
listen 8081;
server_name example5.com;
root /sites/site5;
HtaccessEnable on;
RewriteEngine On;
index index.php;
location / {
RewriteEngine On;
autoindex on;
}
location ~* \.php$ {
RewriteEngine On;
include fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php-fpm/www.sock; # подключаем сокет php-fpm
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
}

20
cms/drupal/site5.conf Normal file
View File

@@ -0,0 +1,20 @@
<VirtualHost *:80>
DocumentRoot "/sites/site5"
ServerName example5.com
ErrorLog logs/site5.log
DirectoryIndex index.php
<Directory /sites/site5>
Options +Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
<FilesMatch "^\.htaccess$">
Require all granted
</FilesMatch>
<FilesMatch \.php$>
SetHandler "proxy:unix:/run/php-fpm/www.sock|fcgi://localhost"
</FilesMatch>
</VirtualHost>

View File

View File

@@ -0,0 +1,3 @@
<?php
echo "Ok";

View File

@@ -0,0 +1,182 @@
#!/bin/bash
# ============================================
# Drupal .htaccess Rules Test Script
# ============================================
# This script tests each rule from cms/drupal/.htaccess
# Assumption: Site root is mapped to /home/alexey/projects/workspace-zed/test1/cms/drupal
# Domain: test.my.brp
# ============================================
BASE_URL="http://test.my.brp"
echo "=============================================="
echo "Drupal .htaccess Rules Test Suite"
echo "=============================================="
echo ""
# Function to test a rule and report result (status only)
test_rule() {
local description="$1"
local url="$2"
local expected_status="$3" # e.g., 403, 404, 200, 301
echo "--- Test: $description ---"
response=$(curl -s -o /dev/null -w "%{http_code}" "$url")
if [ "$response" = "$expected_status" ]; then
echo "✓ PASS (HTTP $response)"
else
echo "✗ FAIL (Expected: HTTP $expected_status, Got: HTTP $response)"
fi
echo ""
}
# Function to test a rule and verify content contains expected string
test_rule_content() {
local description="$1"
local url="$2"
local headers="$3" # Optional: additional curl -H header flags (can be empty)
local expected_status="$4" # e.g., 403, 404, 200, 301
local expected_content="$5" # Expected substring in response body
echo "--- Test: $description ---"
if [ -n "$headers" ]; then
response=$(curl -s -H "$headers" "$url")
http_code=$(curl -s -H "$headers" -o /dev/null -w "%{http_code}" "$url")
else
response=$(curl -s "$url")
http_code=$(curl -s -o /dev/null -w "%{http_code}" "$url")
fi
# Check status code
if [ "$http_code" != "$expected_status" ]; then
echo "✗ FAIL (Status: HTTP $http_code, Expected: HTTP $expected_status)"
return 1
fi
# Check content contains expected substring
if [[ "$response" == *"$expected_content"* ]]; then
echo "✓ PASS (HTTP $http_code, Content matches '$expected_content')"
else
echo "✗ FAIL (Content missing: '$expected_content') - Response:"
echo "$response" | head -5
fi
echo ""
}
echo "=============================================="
echo "1. RewriteEngine Activation"
echo "=============================================="
# Test basic routing through index.php (proves RewriteEngine is active)
test_rule_content "Basic page routing via index.php" \
"$BASE_URL/normal-page/" \
"" \
"200" \
"Drupal Content Route"
echo ""
echo "=============================================="
echo "2. Protocol Variables (protossl)"
echo "============================================}"
# Test HTTPS protocol detection - since we use http://, HTTPS should be off
test_rule_content "HTTP request without HTTPS (protocol detection)" \
"$BASE_URL/normal-page/" \
"" \
"200" \
"Drupal Content Route"
echo ""
echo "=============================================="
echo "3. HTTP Authorization Header Passing"
echo "=============================================="
# Test that Authorization header is properly handled by Drupal REST API
test_rule_content "Drupal handles Authorization header (API request)" \
"$BASE_URL/rest/api/v1" \
"Authorization: Bearer token_abc123" \
"200" \
"Drupal Content Route"
echo ""
echo "=============================================="
echo "4. Hidden Files/Patterns Protection Rule"
echo "=============================================="
# Test hidden files blocked by RewriteRule "/\.|^\.(?!well-known/)" - [F]
test_rule "Block .htaccess hidden file (pattern \.)" \
"$BASE_URL/.htaccess" \
"403"
test_rule "Block .htpasswd hidden file (pattern \.)" \
"$BASE_URL/.htpasswd" \
"403"
test_rule_content "Allow .well-known/robots.txt (exception for well-known)" \
"$BASE_URL/.well-known/robots.txt" \
"" \
"200" \
"User-agent:"
echo ""
echo "=============================================="
echo "5. Core install/rebuild.php Protection Rules"
echo "============================================}"
# Test install.php protection - should route to core/install.php with rewrite=ok parameter
test_rule "Core install.php protected routing" \
"$BASE_URL/install.php" \
"301"
# Test rebuild.php protection - similar redirect pattern
test_rule "Core rebuild.php protected routing" \
"$BASE_URL/rebuild.php" \
"301"
echo ""
echo "=============================================="
echo "6. Drupal Core Files Routing Rules"
echo "============================================}"
# Test existing file access (!-f condition passes) - should return 200 OK without routing to index.php
test_rule_content "Existing favicon.ico access (!-f condition)" \
"$BASE_URL/favicon.ico" \
"" \
"200" \
"This is a placeholder for Drupal test favicon.ico"
echo ""
echo "=============================================="
echo "7. Main Drupal Routing Rules"
echo "============================================}"
# Test non-existing file routing through index.php (main routing) - !-f AND !-d pass
test_rule_content "Non-existing page routing (routes to index.php)" \
"$BASE_URL/nonexistent-page/" \
"" \
"200" \
"Drupal Content Route"
# Test existing directory access (!-d condition passes) - should return 200 OK
test_rule "Existing directory access (somedir/)" \
"$BASE_URL/somedir/" \
"403"
echo ""
echo "=============================================="
echo "8. Core Modules Tests Files Exceptions"
echo "============================================}"
# Test https.php in tests directory - should NOT route to index.php (excluded by RewriteCond)
test_rule_content "Core modules/tests/https.php excluded from routing (!-php condition)" \
"$BASE_URL/core/modules/system/tests/https.php" \
"" \
"200" \
"# This is a test Drupal https.php in tests directory"
# Test http.php in tests directory - same exclusion applies (s for https? regex)
test_rule_content "Core modules/tests/http.php excluded from routing" \
"$BASE_URL/core/modules/system/tests/http.php" \
"" \
"200" \
"# This is a test Drupal http.php in tests directory"
echo ""
echo "=============================================="
echo "Test Suite Complete"
echo "=============================================="

74
cms/grav/.htaccess Normal file
View File

@@ -0,0 +1,74 @@
RewriteEngine On
# ============================================
# 1. Security Rules - Test malicious patterns
# ============================================
# Detect template injection (Mustache, Twig syntax)
RewriteCond %{REQUEST_URI} ({{|}}|{%|%}) [OR]
RewriteCond %{QUERY_STRING} ({{|}}|{%25|%25}) [OR]
# Base64 encoded payloads in query string
RewriteCond %{QUERY_STRING} base64_encode[^(]*\([^)]*\) [OR]
# Script injection patterns (HTML entities decoded)
RewriteCond %{QUERY_STRING} (<|%3C)([^s]*s)+cript.*(>|%3E) [NC,OR]
# GLOBALS exploitation attempt
RewriteCond %{QUERY_STRING} GLOBALS(=|\[|\%[0-9A-Z]{0,2}) [OR]
# _REQUEST manipulation (PHP superglobal abuse)
RewriteCond %{QUERY_STRING} _REQUEST(=|\[|\%[0-9A-Z]{0,2})
# Block malicious requests with 403 Forbidden
RewriteRule .* index.php [F]
# ============================================
# 2. Grav CMS Core Routing Rules
# ============================================
# Prevent rewriting to index.php if already there
RewriteCond %{REQUEST_URI} !^/index\.php
# Allow access to existing files
RewriteCond %{REQUEST_FILENAME} !-f
# Allow access to existing directories
RewriteCond %{REQUEST_FILENAME} !-d
# Route everything else through Grav's index.php
RewriteRule .* index.php [L]
# ============================================
# 3. Sensitive Directory Protection
# ============================================
# Block system, cache, logs, backups folders
RewriteRule ^(\.git|cache|bin|logs|backup|webserver-configs|tests)/(.*) error [F]
# Block system and vendor directories (prevent access to .txt/.xml files)
RewriteRule ^(system|vendor)/(.*)\.(txt|xml|md|html|json|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ error [F]
# Block user directory access to configuration and content files
RewriteRule ^(user)/(.*)\.(txt|md|json|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ error [F]
# ============================================
# 4. File Extension Protection
# ============================================
# Block raw .md file access (content source files)
RewriteRule \.md$ error [F]
# ============================================
# 5. Hidden Files/Directories Protection
# ============================================
# Block hidden files except well-known/robots.txt directories
RewriteRule (^|/)\.(?!well-known) - [F]
# ============================================
# 6. Critical Configuration File Protection
# ============================================
# Block sensitive configuration and documentation files
RewriteRule ^(LICENSE\.txt|composer\.lock|composer\.json|\.htaccess)$ error [F]

1
cms/grav/.htaccess.test Normal file
View File

@@ -0,0 +1 @@
This is a .htaccess file - should be blocked by RewriteRule ^(LICENSE\.txt|composer\.lock|composer\.json|\.htaccess)$ error [F]

1
cms/grav/.htpasswd Normal file
View File

@@ -0,0 +1 @@
This is a hidden .htpasswd file - should be blocked by RewriteRule (^|/)\.(?!well-known) - [F]

View File

@@ -0,0 +1 @@
Robots.txt file - should be ALLOWED because it matches (.well-known) exception in RewriteRule (^|/)\.(?!well-known) - [F]

1
cms/grav/LICENSE.txt Normal file
View File

@@ -0,0 +1 @@
This is a LICENSE.txt file - should be blocked by RewriteRule ^(LICENSE\.txt|composer\.lock|composer\.json|\.htaccess)$ error [F]

119
cms/grav/README.md Normal file
View File

@@ -0,0 +1,119 @@
# Grav CMS Test Structure - Updated
## Directory Layout Overview
```
/test1/cms/grav/
├── .git/ - Git folder tests (blocked)
│ └── secret.txt
├── .well-known/ - Well-known directory (allowed exception)
│ └── robots.txt
├── .htaccess - Main Apache rules configuration
├── .htpasswd - Hidden file (blocked by Rule 5)
├── bin/ - Bin folder tests (blocked)
│ └── helper.php
├── backup/ - Backup folder tests (blocked)
│ └── archive.zip
├── cache/ - Cache folder tests (blocked)
│ └── test.txt
├── composer.json - Config file protection (blocked)
├── composer.lock - Config file protection (blocked)
├── existing.jpg - Existing file for routing test (200 OK)
├── index.php - Grav CMS entry point - routes non-existing files
│ - Returns: "Grav CMS Content Route" page
├── LICENSE.txt - Config file protection (blocked)
├── logs/ - Logs folder tests (blocked)
│ └── app.log
├── normal-page.md - Normal Grav CMS page (routes through index.php)
├── README.md - This documentation file
├── somedir/ - Empty directory for routing test (200 OK)
├── system/ - System folder tests (blocked extensions)
│ └── config.xml
├── vendor/ - Vendor folder tests (blocked extensions)
│ └── module.txt
├── user/ - User folder tests (blocked extensions)
│ ├── test.txt
│ ├── data.json
│ ├── template.twig
│ ├── script.sh
│ ├── module.php
│ ├── config.yml
│ └── settings.yaml
├── webserver-configs/ - Webserver configs folder tests (blocked)
│ └── nginx.conf
├── tests/ - Tests folder tests (blocked)
│ └── unit-test.php
├── test-mustache.php - Security: Mustache template injection pattern
├── twig-test.html - Security: Twig syntax injection pattern
├── test-rewriterules.sh - Bash script to test all rules using curl
└── README.md - Documentation
```
## Updated Test Script Features
### New Content Verification Function
The script now includes a `test_rule_content()` function that:
1. Checks HTTP status code matches expected value
2. Verifies response body contains expected content string
**Example usage:**
```bash
test_rule_content "Normal page routing via index.php" \
"$BASE_URL/normal-page/" \
"200" \
"Grav CMS Content Route"
```
This tests that:
- URL `/home/alexey/projects/workspace-zed/test1/cms/grav/normal-page/` (non-existing file)
- Returns HTTP 200 status (routed through index.php via Rule 2)
- Response body contains "Grav CMS Content Route" text from index.php
## Test Coverage Summary
### 1. Security Rules - Malicious Patterns ✓
- Template injection: `{{ }}`, `{% %}` in URI/Query String → **403**
- Base64 payloads: `base64_encode()` pattern → **403**
- Script injection: `<script>` or `%3C` encoded → **403**
- GLOBALS exploitation: `GLOBALS[` pattern → **403**
- _REQUEST manipulation: `_REQUEST[` pattern → **403**
### 2. Core Routing Rules ✓ (Updated)
- Non-existing file routes to index.php → **200** + content check
- Existing file access → **200**
- Existing directory access → **200**
### 3. Sensitive Directory Protection ✓
- Blocks: `.git`, `cache`, `bin`, `logs`, `backup` folders → **403**
- Blocks system/vendor with sensitive extensions (`.txt`, `.xml`) → **403**
### 4. File Extension Protection ✓
- Blocks raw `.md` files → **403**
### 5. Hidden Files/Directories Protection ✓
- Blocks hidden files except `.well-known`**403** / **200**
### 6. Critical Configuration File Protection ✓
- Blocks: `LICENSE.txt`, `composer.lock`, `composer.json`, `.htaccess`**403**
## Run Tests
Execute the test script to verify all rules:
```bash
cd /home/alexey/projects/workspace-zed/test1/cms/grav
./test-rewriterules.sh
```
Expected results for security tests (all should be **PASS ✓**):
- Template injection: HTTP 403 ✓
- Twig injection: HTTP 403 ✓
- Base64 payload: HTTP 403 ✓
- Script injection: HTTP 403 ✓
- GLOBALS manipulation: HTTP 403 ✓
- _REQUEST manipulation: HTTP 403 ✓
Expected results for routing tests (should be **PASS ✓**):
- Normal page routing: HTTP 200 + content "Grav CMS Content Route" ✓
- Existing file access: HTTP 200 ✓
- Directory access: HTTP 200 ✓

View File

@@ -0,0 +1 @@
Backup archive.zip file - should be blocked by RewriteRule ^(\.git|cache|bin|logs|backup|webserver-configs|tests)/(.*) error [F]

1
cms/grav/bin/helper.php Normal file
View File

@@ -0,0 +1 @@
Bin helper.php file - should be blocked by RewriteRule ^(\.git|cache|bin|logs|backup|webserver-configs|tests)/(.*) error [F]

1
cms/grav/cache/test.txt vendored Normal file
View File

@@ -0,0 +1 @@
Cache test.txt file - should be blocked by RewriteRule ^(\.git|cache|bin|logs|backup|webserver-configs|tests)/(.*) error [F]

1
cms/grav/composer.json Normal file
View File

@@ -0,0 +1 @@
{\"name\": \"test/my-site\", \"description\": \"Test Composer file\"}

1
cms/grav/composer.lock generated Normal file
View File

@@ -0,0 +1 @@
This is a composer.lock file - should be blocked by RewriteRule ^(LICENSE\.txt|composer\.lock|composer\.json|\.htaccess)$ error [F]

0
cms/grav/existing.jpg Normal file
View File

15
cms/grav/index.php Normal file
View File

@@ -0,0 +1,15 @@
<?php
/**
* Grav CMS - index.php (Test version)
* This file handles routing for non-existing files/directories
*/
// Simulated Grav CMS response
echo "<html><head><title>Grav CMS Test Site</title></head><body>";
echo "<h1>Grav CMS Content Route</h1>";
echo "<p>This page is served by index.php via RewriteRule.</p>";
echo "<div class='graviconfig'>Site Configuration Loaded</div>";
echo "</body></html>";
// Exit
exit;

159
cms/grav/nginx.conf Normal file
View File

@@ -0,0 +1,159 @@
load_module modules/ngx_http_apache_rewrite_module.so;
worker_processes 1;
error_log logs/error.log debug;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 8081;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
server {
listen 8081;
server_name example1.com;
root /sites/site1;
HtaccessEnable on;
RewriteEngine On;
location / {
RewriteEngine On;
}
}
server {
listen 8081;
server_name example2.com;
root /sites/site2;
HtaccessEnable on;
RewriteEngine On;
index index.php;
location / {
RewriteEngine On;
autoindex on;
}
location ~* \.php$ {
RewriteEngine On;
include fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php-fpm/www.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
server {
listen 8081;
server_name example3.com;
root /sites/site3;
HtaccessEnable on;
RewriteEngine On;
index index.php;
location / {
RewriteEngine On;
autoindex on;
}
location ~* \.php$ {
RewriteEngine On;
include fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php-fpm/www.sock; # подключаем сокет php-fpm
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
server {
listen 8081;
server_name example4.com;
root /sites/site4;
HtaccessEnable on;
RewriteEngine On;
index index.php;
location / {
RewriteEngine On;
autoindex on;
}
location ~* \.php$ {
RewriteEngine On;
include fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php-fpm/www.sock; # подключаем сокет php-fpm
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
server {
listen 8081;
server_name example5.com;
root /sites/site5;
HtaccessEnable on;
RewriteEngine On;
index index.php;
location / {
RewriteEngine On;
autoindex on;
}
location ~* \.php$ {
RewriteEngine On;
include fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php-fpm/www.sock; # подключаем сокет php-fpm
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
}

1
cms/grav/normal-page.md Normal file
View File

@@ -0,0 +1 @@
Normal Grav CMS page - should be routed through index.php

14
cms/grav/site2.conf Normal file
View File

@@ -0,0 +1,14 @@
<VirtualHost *:80>
DocumentRoot "/sites/site2"
ServerName example2.com
<Directory /sites/site2>
Options +Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
<FilesMatch \.php$>
SetHandler "proxy:unix:/run/php-fpm/www.sock|fcgi://localhost"
</FilesMatch>
</VirtualHost>

View File

View File

@@ -0,0 +1 @@
System config.xml file - should be blocked by RewriteRule ^(system|vendor)/(.*)\.(txt|xml|md|html|json|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ error [F]

View File

@@ -0,0 +1 @@
This is a Mustache template injection test file with {{ variable }} syntax

150
cms/grav/test-rewriterules.sh Executable file
View File

@@ -0,0 +1,150 @@
#!/bin/bash
# ============================================
# Grav CMS .htaccess Rules Test Script
# ============================================
# This script tests each rule from cms/grav/.htaccess
# Domain: test.my.brp
# ============================================
BASE_URL="http://test.my.brp"
echo "=============================================="
echo "Grav CMS .htaccess Rules Test Suite"
echo "=============================================="
echo ""
# Function to test a rule and report result (status only)
test_rule() {
local description="$1"
local url="$2"
local expected_status="$3" # e.g., 403, 404, 200
echo "--- Test: $description ---"
response=$(curl -s -o /dev/null -w "%{http_code}" "$url")
if [ "$response" = "$expected_status" ]; then
echo "✓ PASS (HTTP $response)"
else
echo "✗ FAIL (Expected: HTTP $expected_status, Got: HTTP $response)"
fi
echo ""
}
# Function to test a rule and verify content contains expected string
test_rule_content() {
local description="$1"
local url="$2"
local expected_status="$3" # e.g., 403, 404, 200
local expected_content="$4" # Expected substring in response body
echo "--- Test: $description ---"
response=$(curl -s "$url")
http_code=$(curl -s -o /dev/null -w "%{http_code}" "$url")
# Check status code
if [ "$http_code" != "$expected_status" ]; then
echo "✗ FAIL (Status: HTTP $http_code, Expected: HTTP $expected_status)"
return 1
fi
# Check content contains expected substring
if [[ "$response" == *"$expected_content"* ]]; then
echo "✓ PASS (HTTP $http_code, Content matches '$expected_content')"
else
echo "✗ FAIL (Content missing: '$expected_content') - Response:"
echo "$response" | head -5
fi
echo ""
}
echo "=============================================="
echo "1. Security Rules - Test malicious patterns"
echo "=============================================="
# Template injection via query string with {{ }} Mustache syntax
test_rule "Template injection via query string ({{ }}" \
"$BASE_URL/test-mustache.php?\{\{config.secret\}\}" \
"403"
# Base64 encoded payloads (base64_encode function call)
test_rule "Base64 payload pattern" \
"$BASE_URL/test.php?data=base64_encode(some_secret)" \
"403"
# Script injection (<script> or HTML entities %3C)
test_rule "Script injection (encoded)" \
"$BASE_URL/search.html?q=%3Cscript%25alert(1)%3E" \
"403"
# GLOBALS exploitation attempt
test_rule "GLOBALS manipulation" \
"$BASE_URL/leak.php?GLOBALS\[secret\]=exploit" \
"403"
# _REQUEST manipulation (PHP superglobal abuse)
test_rule "_REQUEST manipulation" \
"$BASE_URL/attack.php?_REQUEST\[user\]=admin" \
"403"
echo ""
echo "=============================================="
echo "2. Grav CMS Core Routing Rules"
echo "=============================================="
# Test normal page routing through index.php (non-existing file -> index.php)
test_rule_content "Normal page routing via index.php" \
"$BASE_URL/normal-page/" \
"200" \
"Grav CMS Content Route"
# Test existing file access - check status + content type/image
test_rule "Existing file access (existing.jpg)" \
"$BASE_URL/existing.jpg" \
"200"
# Test existing directory access
test_rule "Existing directory access (somedir/)" \
"$BASE_URL/somedir/" \
"200"
echo ""
echo "=============================================="
echo "3. Sensitive Directory Protection"
echo "=============================================="
# Block system, cache, logs, backups folders
test_rule "Block .git folder" "$BASE_URL/.git/secret.txt" "403"
test_rule "Block cache folder" "$BASE_URL/cache/test.txt" "403"
test_rule "Block bin folder" "$BASE_URL/bin/helper.php" "403"
test_rule "Block logs folder" "$BASE_URL/logs/app.log" "403"
test_rule "Block backup folder" "$BASE_URL/backup/archive.zip" "403"
# Block system and vendor directories with sensitive extensions
test_rule "Block system config.xml" "$BASE_URL/system/config.xml" "403"
test_rule "Block vendor module.txt" "$BASE_URL/vendor/module.txt" "403"
echo ""
echo "=============================================="
echo "4. File Extension Protection"
echo "=============================================="
# Block raw .md file access (content source files)
test_rule "Block raw .md file" "$BASE_URL/test.md" "403"
echo ""
echo "=============================================="
echo "5. Hidden Files/Directories Protection"
echo "=============================================="
test_rule "Block hidden .htpasswd" "$BASE_URL/.htpasswd" "403"
test_rule "Allow .well-known/robots.txt" "$BASE_URL/.well-known/robots.txt" "200"
echo ""
echo "=============================================="
echo "6. Critical Configuration File Protection"
echo "=============================================="
test_rule "Block LICENSE.txt" "$BASE_URL/LICENSE.txt" "403"
test_rule "Block composer.lock" "$BASE_URL/composer.lock" "403"
test_rule "Block composer.json" "$BASE_URL/composer.json" "403"
test_rule "Block .htaccess" "$BASE_URL/.htaccess" "403"
echo ""
echo "=============================================="
echo "Test Suite Complete"
echo "=============================================="

1
cms/grav/test.md Normal file
View File

@@ -0,0 +1 @@
Test.md

View File

@@ -0,0 +1 @@
Tests unit-test.php file - should be blocked by RewriteRule ^(\.git|cache|bin|logs|backup|webserver-configs|tests)/(.*) error [F]

1
cms/grav/twig-test.html Normal file
View File

@@ -0,0 +1 @@
This is a Twig template syntax test file with {% comment %} syntax

1
cms/grav/user/config.yml Normal file
View File

@@ -0,0 +1 @@
User config.yml file - should be blocked by RewriteRule ^(user)/(.*)\.(txt|md|json|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ error [F]

1
cms/grav/user/data.json Normal file
View File

@@ -0,0 +1 @@
User data.json file - should be blocked by RewriteRule ^(user)/(.*)\.(txt|md|json|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ error [F]

1
cms/grav/user/module.php Normal file
View File

@@ -0,0 +1 @@
User module.php file - should be blocked by RewriteRule ^(user)/(.*)\.(txt|md|json|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ error [F]

1
cms/grav/user/script.sh Normal file
View File

@@ -0,0 +1 @@
User script.sh file - should be blocked by RewriteRule ^(user)/(.*)\.(txt|md|json|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ error [F]

View File

@@ -0,0 +1 @@
User settings.yaml file - should be blocked by RewriteRule ^(user)/(.*)\.(txt|md|json|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ error [F]

View File

@@ -0,0 +1 @@
User template.twig file - should be blocked by RewriteRule ^(user)/(.*)\.(txt|md|json|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ error [F]

1
cms/grav/user/test.txt Normal file
View File

@@ -0,0 +1 @@
User test.txt file - should be blocked by RewriteRule ^(user)/(.*)\.(txt|md|json|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ error [F]

1
cms/grav/vendor/module.txt vendored Normal file
View File

@@ -0,0 +1 @@
Vendor module.txt file - should be blocked by RewriteRule ^(system|vendor)/(.*)\.(txt|xml|md|html|json|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ error [F]

View File

@@ -0,0 +1 @@
Webserver configs nginx.conf file - should be blocked by RewriteRule ^(\.git|cache|bin|logs|backup|webserver-configs|tests)/(.*) error [F]

16
cms/joomla/.htaccess Normal file
View File

@@ -0,0 +1,16 @@
RewriteEngine On
RewriteCond %{QUERY_STRING} base64_encode[^(]*\([^)]*\) [OR]
RewriteCond %{QUERY_STRING} (<|%3C)([^s]*s)+cript.*(>|%3E) [NC,OR]
RewriteCond %{QUERY_STRING} GLOBALS(=|\[|\%[0-9A-Z]{0,2}) [OR]
RewriteCond %{QUERY_STRING} _REQUEST(=|\[|\%[0-9A-Z]{0,2})
RewriteRule .* index.php [F]
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteCond %{REQUEST_URI} ^/api/
RewriteCond %{REQUEST_URI} !^/api/index\.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .* api/index.php [L]
RewriteCond %{REQUEST_URI} !^/index\.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .* index.php [L]

View File

@@ -0,0 +1,6 @@
# This is a well-known robots.txt file for Joomla testing
# Should be accessible via RewriteRule /api/index.php [L] or standard routing
User-agent: *
Disallow: /administrator/
Allow: /

153
cms/joomla/README.md Normal file
View File

@@ -0,0 +1,153 @@
# Joomla .htaccess Test Structure
## Directory Layout Overview
```
/test1/cms/joomla/
├── api/ - API directory tests
│ └── index.php - Joomla API entry point (routes /api/ requests)
│ - Returns: "Joomla API Configuration Loaded"
├── .well-known/ - Well-known directory
│ └── robots.txt - Allowed file via exception
├── base64-test.php - Security test for base64_encode pattern detection
├── globals-test.php - Security test for GLOBALS exploitation pattern
├── request-test.php - Security test for _REQUEST manipulation pattern
├── script-test.php - Security test for script injection pattern
├── index.php - Joomla main entry point (routes non-existing files)
│ - Returns: "Joomla Content Route" page
├── somedir/ - Directory for testing !-d condition (200 OK)
├── test-joomla-rewriterules.sh - Bash script to test all rules using curl
└── README.md - This documentation file
```
## Apache Rules Explained - Joomla
### 1. Base64 Encoded Payload Detection Rule
```apache
RewriteCond %{QUERY_STRING} base64_encode[^(]*\([^)]*\) [OR]
```
**Что делает:** Detects Base64 encoded payloads in query string (function call pattern)
**Зачем нужно:** Защита от Base64-encoded malicious code injection attacks
- Pattern: `base64_encode(...)` - detect function calls that encode data
### 2. Script Injection Pattern Detection Rule
```apache
RewriteCond %{QUERY_STRING} (<|%3C)([^s]*s)+cript.*(>|%3E) [NC,OR]
```
**Что делает:** Detects script injection patterns (HTML entities decoded)
**Зачем нужно:** Защита от XSS attacks через URL parameters
- Pattern: `<script>...` or `%3Cscript%3E` - detect HTML script tags
- `[NC]` - case-insensitive matching
### 3. GLOBALS Exploitation Detection Rule
```apache
RewriteCond %{QUERY_STRING} GLOBALS(=|\[|\%[0-9A-Z]{0,2}) [OR]
```
**Что делает:** Detects GLOBALS exploitation attempt in query string
**Зачем нужно:** Защита от PHP superglobal abuse attacks
- Pattern: `GLOBALS=` or `GLOBALS[` - detect attempts to manipulate global variables
### 4. _REQUEST Manipulation Detection Rule
```apache
RewriteCond %{QUERY_STRING} _REQUEST(=|\[|\%[0-9A-Z]{0,2})
```
**Что делает:** Detects _REQUEST manipulation (PHP superglobal abuse)
**Зачем нужно:** Защита от PHP superglobal exploitation attacks
- Pattern: `_REQUEST=` or `_REQUEST[` - detect attempts to manipulate $_REQUEST array
### 5. Malicious Requests Blocked with [F] Flag
```apache
RewriteRule .* index.php [F]
```
**Что делает:** Blocks malicious requests with 403 Forbidden
**Зачем нужно:** Все запросы, которые прошли security checks выше - блокируются
### 6. HTTP Authorization Header Passing Rule
```apache
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
```
**Что делает:** Копирует Authorization header в переменную HTTP_AUTHORIZATION для PHP
**Зачем нужно:** Joomla REST API требует эту переменную для аутентификации
### 7. Joomla API Routing Rule (/api/)
```apache
RewriteCond %{REQUEST_URI} ^/api/
RewriteCond %{REQUEST_URI} !^/api/index\.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .* api/index.php [L]
```
**Что делает:** Маршрутизирует запросы в `/api/` через `api/index.php` (если не существующий файл/directory)
**Зачем нужно:** Joomla REST API routing - отдельная точка входа для API endpoints
- **Исключение:** Если запрос прямо на /api/index.php - пропускаем (не переписываем)
### 8. Main Joomla Routing Rule (/index.php exclusion)
```apache
RewriteCond %{REQUEST_URI} !^/index\.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .* index.php [L]
```
**Что делает:** Маршрутизирует все запросы через main `index.php` (!-f AND !-d pass)
**Зачем нужно:** Joomla "clean URLs" routing (похоже на WordPress/Drupal)
- **Исключение:** Не переписывает прямой доступ к /index.php
## Test Script Features
The script includes test functions:
1. **test_rule()** - checks HTTP status code only
2. **test_rule_content()** - checks both status AND response body content
## Security Pattern Testing Scenarios
### Query String Pattern Detection
| URL | Правило | Ожидаемый результат |
|-----|---------|---------------------|
| `http://test.my.brp/base64-test.php?data=base64_encode(secret)` | base64_encode pattern detection → 403 ✓ |
| `http://test.my.brp/script-test.php?q=%3Cscript%3Ealert(1)%3E` | script injection pattern → 403 ✓ |
| `http://test.my.brp/globals-test.php?GLOBALS[user]=admin` | GLOBALS exploitation → 403 ✓ |
| `http://test.my.brp/request-test.php?_REQUEST[config]=true` | _REQUEST manipulation → 403 ✓ |
### API vs Main Routing
| URL | Правило | Ожидаемый результат |
|-----|---------|---------------------|
| `http://test.my.brp/api/` | api/index.php routing (!-f, !-d pass) → api/index.php ✓ |
| `http://test.my.brp/api/index.php` | Direct access (excluded from api routing) → 200 OK ✓ |
| `http://test.my.brp/nonexistent-page/` | Main index.php routing (!-f, !-d pass) → main index.php ✓ |
## Run Tests
Execute the test script to verify all rules:
```bash
cd /home/alexey/projects/workspace-zed/test1/cms/joomla
./test-joomla-rewriterules.sh
```
Expected results for Joomla tests (all should be **PASS ✓**):
- Base64 encoded payload blocked: HTTP 403 ✓
- Script injection blocked: HTTP 403 ✓
- GLOBALS exploitation blocked: HTTP 403 ✓
- _REQUEST manipulation blocked: HTTP 403 ✓
- Authorization header handling: HTTP 200 + "Joomla Content Route" ✓
- API index.php routing: HTTP 200 + "Joomla API Configuration Loaded" ✓
- Direct /api/index.php file access: HTTP 200 OK ✓
- Main index.php routing (non-existing page): HTTP 200 + "Joomla Content Route" ✓
- Directory access (!-d): HTTP 200 OK ✓

56
cms/joomla/api/index.php Normal file
View File

@@ -0,0 +1,56 @@
<?php
/**
* WordPress REST API Mock (Test version)
* This file checks if Authorization header is properly passed from mod_rewrite
*/
// Check if Authorization header was received
$auth_header = "";
if (isset($_SERVER["HTTP_AUTHORIZATION"])) {
$auth_header = $_SERVER["HTTP_AUTHORIZATION"];
} else {
// Also check for rewritten env var
$auth_env = getenv("HTTP_AUTHORIZATION");
if ($auth_env !== false && $auth_env !== "") {
$auth_header = $auth_env;
}
}
// Set response headers
header("Content-Type: application/json; charset=UTF-8");
if ($auth_header === "secret_token_123") {
// SUCCESS - Authorization header was properly passed through mod_rewrite
echo json_encode(
[
"status" => "success",
"message" => "Authorization verified",
"token_verified" => true,
"wordpress_config_loaded" => true,
"received_auth_header" => $auth_header,
],
JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES,
);
} else {
// FAIL - Authorization header was not passed through mod_rewrite
http_response_code(401);
echo json_encode(
[
"status" => "error",
"message" => "unauth",
"expected" => "Bearer secret_token_123",
"received" => $auth_header ?: "(not set)",
"test_failed" => true,
"hint" =>
"mod_rewrite [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] is NOT passing header to PHP",
],
JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES,
);
}
exit();

View File

@@ -0,0 +1 @@
# Joomla API test - should be allowed when accessed via /api/index.php (excluded from index.php routing)

View File

@@ -0,0 +1,2 @@
# Joomla security test - base64_encode payload pattern
# This file should be blocked when accessed with query string containing base64_encode[^(]*\([^)]*\)

View File

@@ -0,0 +1,2 @@
# Joomla security test - GLOBALS exploitation pattern
# This file should be blocked when accessed with query string containing GLOBALS(=|\[|\%[0-9A-Z]{0,2})

15
cms/joomla/index.php Normal file
View File

@@ -0,0 +1,15 @@
<?php
/**
* Joomla - index.php (Test version)
* This file handles routing for non-existing files/directories
*/
// Simulated Joomla response
echo "<html><head><title>Joomla Test Site</title></head><body>";
echo "<h1>Joomla Content Route</h1>";
echo "<p>This page is served by index.php via RewriteRule.</p>";
echo "<div class='joomla-config'>Joomla Configuration Loaded</div>";
echo "</body></html>";
// Exit
exit;

159
cms/joomla/nginx.conf Normal file
View File

@@ -0,0 +1,159 @@
load_module modules/ngx_http_apache_rewrite_module.so;
worker_processes 1;
error_log logs/error.log debug;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 8081;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
server {
listen 8081;
server_name example1.com;
root /sites/site1;
HtaccessEnable on;
RewriteEngine On;
location / {
RewriteEngine On;
}
}
server {
listen 8081;
server_name example2.com;
root /sites/site2;
HtaccessEnable on;
RewriteEngine On;
index index.php;
location / {
RewriteEngine On;
autoindex on;
}
location ~* \.php$ {
RewriteEngine On;
include fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php-fpm/www.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
server {
listen 8081;
server_name example3.com;
root /sites/site3;
HtaccessEnable on;
RewriteEngine On;
index index.php;
location / {
RewriteEngine On;
autoindex on;
}
location ~* \.php$ {
RewriteEngine On;
include fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php-fpm/www.sock; # подключаем сокет php-fpm
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
server {
listen 8081;
server_name example4.com;
root /sites/site4;
HtaccessEnable on;
RewriteEngine On;
index index.php;
location / {
RewriteEngine On;
autoindex on;
}
location ~* \.php$ {
RewriteEngine On;
include fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php-fpm/www.sock; # подключаем сокет php-fpm
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
server {
listen 8081;
server_name example5.com;
root /sites/site5;
HtaccessEnable on;
RewriteEngine On;
index index.php;
location / {
RewriteEngine On;
autoindex on;
}
location ~* \.php$ {
RewriteEngine On;
include fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php-fpm/www.sock; # подключаем сокет php-fpm
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
}

View File

@@ -0,0 +1,2 @@
# Joomla security test - _REQUEST manipulation pattern
# This file should be blocked when accessed with query string containing _REQUEST(=|\[|\%[0-9A-Z]{0,2})

View File

@@ -0,0 +1,2 @@
# Joomla security test - script injection pattern
# This file should be blocked when accessed with query string containing (<|%3C)([^s]*s)+cript.*(>|%3E)

16
cms/joomla/site4.conf Normal file
View File

@@ -0,0 +1,16 @@
<VirtualHost *:80>
DocumentRoot "/sites/site4"
ServerName example4.com
DirectoryIndex index.php
<Directory /sites/site4>
Options +Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
<FilesMatch \.php$>
SetHandler "proxy:unix:/run/php-fpm/www.sock|fcgi://localhost"
</FilesMatch>
</VirtualHost>

View File

@@ -0,0 +1,170 @@
#!/bin/bash
# ============================================
# Joomla .htaccess Rules Test Script
# ============================================
# This script tests each rule from cms/joomla/.htaccess
# Assumption: Site root is mapped to /home/alexey/projects/workspace-zed/test1/cms/joomla
# Domain: test.my.brp
# ============================================
BASE_URL="http://test.my.brp"
echo "=============================================="
echo "Joomla .htaccess Rules Test Suite"
echo "=============================================="
echo ""
# Function to test a rule and report result (status only)
test_rule() {
local description="$1"
local url="$2"
local expected_status="$3" # e.g., 403, 404, 200, 301
echo "--- Test: $description ---"
response=$(curl -s -o /dev/null -w "%{http_code}" "$url")
if [ "$response" = "$expected_status" ]; then
echo "✓ PASS (HTTP $response)"
else
echo "✗ FAIL (Expected: HTTP $expected_status, Got: HTTP $response)"
fi
echo ""
}
# Function to test a rule and verify content contains expected string
test_rule_content() {
local description="$1"
local url="$2"
local headers="$3" # Optional: additional curl -H header flags (can be empty)
local expected_status="$4" # e.g., 403, 404, 200, 301
local expected_content="$5" # Expected substring in response body
echo "--- Test: $description ---"
if [ -n "$headers" ]; then
response=$(curl -s -H "$headers" "$url")
http_code=$(curl -s -H "$headers" -o /dev/null -w "%{http_code}" "$url")
else
response=$(curl -s "$url")
http_code=$(curl -s -o /dev/null -w "%{http_code}" "$url")
fi
# Check status code
if [ "$http_code" != "$expected_status" ]; then
echo "✗ FAIL (Status: HTTP $http_code, Expected: HTTP $expected_status)"
return 1
fi
# Check content contains expected substring
if [[ "$response" == *"$expected_content"* ]]; then
echo "✓ PASS (HTTP $http_code, Content matches '$expected_content')"
else
echo "✗ FAIL (Content missing: '$expected_content') - Response:"
echo "$response" | head -5
fi
echo ""
}
echo "=============================================="
echo "1. Base64 Encoded Payload Detection Rule"
echo "=============================================="
# Test base64_encode pattern detection in query string
test_rule "Base64 encoded payload blocked (base64_encode function call)" \
"$BASE_URL/base64-test.php?data=base64_encode(secret_password)" \
"403"
echo ""
echo "=============================================="
echo "2. Script Injection Pattern Detection Rule"
echo "============================================}"
# Test script injection pattern detection in query string (HTML entities)
test_rule "Script injection blocked (script tag encoded)" \
"$BASE_URL/script-test.php?q=%3Cscript%3Ealert(1)%3E" \
"403"
echo ""
echo "=============================================="
echo "3. GLOBALS Exploitation Detection Rule"
echo "============================================}"
# Test GLOBALS exploitation pattern detection in query string
test_rule "GLOBALS exploitation blocked (GLOBALS[secret] pattern)" \
"$BASE_URL/globals-test.php?GLOBALS%5Buser%5D=admin" \
"403"
echo ""
echo "=============================================="
echo "4. _REQUEST Manipulation Detection Rule"
echo "============================================}"
# Test _REQUEST manipulation pattern detection in query string
test_rule "_REQUEST manipulation blocked (_REQUEST[config] pattern)" \
"$BASE_URL/request-test.php?_REQUEST%5Badmin%5D=true" \
"403"
echo ""
echo "=============================================="
echo "5. HTTP Authorization Header Passing Rule"
echo "============================================}"
# Test that Authorization header is properly handled by Joomla REST API
test_rule_content "Joomla handles Authorization header (API request)" \
"$BASE_URL/rest/api/v1" \
"Authorization: Bearer joomla_token_abc" \
"200" \
"Joomla Content Route"
echo ""
echo "=============================================="
echo "6. Joomla API Routing Rule (/api/)"
echo "============================================}"
# Test /api/index.php routing - should route to api/index.php (!-f, !-d pass for non-existing files)
test_rule_content "API index.php routing (routes to /api/index.php)" \
"$BASE_URL/api/" \
"Authorization: secret_token_123" \
"200" \
"\"status\": \"success\""
# Additional test: verify token verification in response
test_rule_content "Direct /api/index.php file access" \
"$BASE_URL/api/index.php" \
"" \
"401" \
"\"message\": \"unauth\""
echo ""
echo "=============================================="
echo "7. Main Joomla Routing Rule (/index.php exclusion)"
echo "============================================}"
# Test that /api/index.php is NOT routed to main index.php (!^/api/index\.php passes)
test_rule_content "/api/index.php excluded from main routing (direct file access)" \
"$BASE_URL/api/index.php" \
"" \
"200" \
"Joomla API Configuration Loaded"
# Test non-existing file routing through main index.php (!-f AND !-d pass)
test_rule_content "Non-existing page routing (routes to /index.php)" \
"$BASE_URL/nonexistent-page/" \
"" \
"200" \
"Joomla Content Route"
# Test existing directory access (!-d condition passes) - should return 200 OK
test_rule "Existing directory access (somedir/)" \
"$BASE_URL/somedir/" \
"200"
echo ""
echo "=============================================="
echo "8. Joomla index.php reditercting"
echo "============================================}"
# Test that /api/index.php is NOT routed to main index.php (!^/api/index\.php passes)
test_rule_content "/index.php/component/config?view=modules accessing" \
"$BASE_URL/index.php/component/config?view=modules" \
"" \
"200" \
"Joomla Content Route"
echo ""
echo "=============================================="
echo "Test Suite Complete"
echo "=============================================="

73
cms/simple/.htaccess Normal file
View File

@@ -0,0 +1,73 @@
# ============================================================
# .htaccess - Тесты Apache mod_rewrite для nginx
# ============================================================
# Этот файл содержит правила mod_rewrite для тестирования функционала
# модуля ngx_http_apache_rewrite_module в nginx, аналогичного Apache mod_rewrite
# ============================================================
# 1. Перенаправление файлов с расширением .xmx на index.html
# ============================================================
# Правиство перенаправляет ВСЕ файлы заканчивающиеся на .xmx
# на страницу index.html (внешнее перенаправление)
RewriteEngine On
RewriteRule \.xmx$ /index.html [R=301,L]
# Тестирование:
# - http://localhost/test/test.xmx → 301 redirect to /index.html
# - http://localhost/test/subdir/file.xmx → 301 redirect to /index.html
# ============================================================
# 2. Блокировка доступа к stop.html (403 Forbidden)
# ============================================================
# Правиство запрещает доступ к файлу stop.html, возвращая ошибку 403
RewriteCond %{REQUEST_FILENAME} stop.html$
RewriteRule ^ - [F,L]
# Тестирование:
# - http://localhost/test/stop.html → 403 Forbidden (доступ запрещен)
# ============================================================
# 3. Внутреннее перенаправление redirect.html на show.html
# ============================================================
# Правиство скрывает redirect.html и показывает вместо него show.html
# Это внутренняя переадресация (без изменения URL в браузере)
RewriteRule ^redirect\.html$ show.html [L]
# Тестирование:
# - http://localhost/test/redirect.html → показывает содержимое show.html
# - URL остается /test/redirect.html, но показывается content из show.html
# ============================================================
# 4. Перенаправление подкаталога subdir на index.html
# ============================================================
# При обращении к подкаталогу redirect его на главную страницу
RewriteRule ^subdir/?$ /index.html [L]
# Тестирование:
# - http://localhost/test/subdir/ → показывает index.html
# ============================================================
# 5. Блокировка доступа ко всем .xmx файлам (кроме test.xmx)
# ============================================================
# Дополнительно блокируем все файлы с расширением xmx кроме test.xmx
# Это демонстрирует условные правила с RewriteCond
RewriteCond %{REQUEST_FILENAME} !test\.xmx$
RewriteRule \.xmx$ - [F,L]
# Примечание: это правило будет работать ПОСЛЕ первого правила перенаправления
# поэтому test.xmx сначала перенаправляется на index.html, а затем блокируется
# ============================================================
# 6. Переопределение статуса ответа (403 Forbidden) для subdir
# ============================================================
# При обращении к подкаталогу возвращаем ошибку 403 вместо 200 OK
#RewriteRule ^subdir/?$ - [F,L]
# Примечание: это правило будет работать ПОСЛЕ правила 4, поэтому subdir
# сначала будет переадресован на index.html, а затем заблокирован

127
cms/simple/README.md Normal file
View File

@@ -0,0 +1,127 @@
# Тесты Apache mod_rewrite для nginx
## Обзор
Этот каталог содержит тестовые файлы и правила `.htaccess` для проверки функционала модуля `ngx_http_apache_rewrite_module` в nginx, который обеспечивает совместимость с Apache mod_rewrite.
## Файлы
| Файл | Назначение |
|------|------------|
| `index.html` | Главная страница - назначение для перенаправления .xmx файлов |
| `stop.html` | Заблокированная страница (должен возвращать 403) |
| `redirect.html` | Внутреннее перенаправление на show.html (URL не меняется) |
| `show.html` | Показывается вместо redirect.html при внутреннем rewrite |
| `test.xmx` | Тестовый файл для проверки правила перенаправления .xmx расширения |
## Правила .htaccess
### 1. Перенаделение файлов с расширением `.xmx` на index.html
```apache
RewriteRule \.xmx$ /index.html [R=301,L]
```
**Ожидаемое поведение:**
- Запрос `http://localhost/test/test.xmx`**301 Redirect** к `/test/index.html`
- URL в браузере изменится на `/test/index.html`
### 2. Блокировка доступа к stop.html (403 Forbidden)
```apache
RewriteCond %{REQUEST_FILENAME} =stop.html
RewriteRule ^ - [F,L]
```
**Ожидаемое поведение:**
- Запрос `http://localhost/test/stop.html`**403 Forbidden** ("Доступ запрещен")
- Страница не будет отображаться
### 3. Внутреннее перенаправление redirect.html на show.html
```apache
RewriteRule ^redirect\.html$ show.html [L]
```
**Ожидаемое поведение:**
- Запрос `http://localhost/test/redirect.html`**показывается содержимое show.html**
- URL в браузере остается `/test/redirect.html` (не происходит перенаправления)
### 4. Перенаделение подкаталога subdir на index.html
```apache
RewriteRule ^subdir/?$ /index.html [L]
```
**Ожидаемое поведение:**
- Запрос `http://localhost/test/subdir/`**показывается index.html**
- URL в браузере остается `/test/subdir/`
### 5. Дополнительные проверки
- Флаг `[L]` (Last) - останавливает обработку правил после выполнения
- Флаг `[F]` (Forbidden) - возвращает ошибку 403 Forbidden
- Флаг `[R=301]` - внешнее перенаправление с кодом статуса 301 Moved Permanently
## Порядок применения правил
В модуле nginx приоритет следующий:
1. **Правила из .htaccess** (высокий приоритет)
2. **Правила из nginx конфигурации** (низкий приоритет)
При наличии флага `[END]` в правилах .htaccess, дальнейшая обработка всех правил останавливается.
## Поддержка флагоv mod_rewrite
| Флаг | Значение | Описание |
|------|----------|----------|
| `L` | Last | Остановить обработку остальных правил |
| `F` | Forbidden | Заблокировать доступ (403) |
| `R=301` | Redirect | Внешнее перенаправление с кодом 301 |
| `END` | End | Полностью остановить обработку rewrite |
## Примеры запросов для тестирования
```bash
# Перенаделение .xmx файла на index.html
curl -I http://localhost/test/test.xmx
# Ожидаемый ответ: HTTP/1.1 301 Moved Permanently
# Location: /test/index.html
# Блокировка stop.html
curl -I http://localhost/test/stop.html
# Ожидаемый ответ: HTTP/1.1 403 Forbidden
# Внутреннее перенаправление redirect.html на show.html
curl -I http://localhost/test/redirect.html
# Ожидаемый ответ: HTTP/1.1 200 OK (content из show.html)
# Перенаделение subdir на index.html
curl -I http://localhost/test/subdir/
# Ожидаемый ответ: HTTP/1.1 200 OK (content из index.html)
```
## Установка и запуск
Для запуска тестов потребуется:
1. Скомпилированный nginx с модулем `ngx_http_apache_rewrite_module`
2. Конфигурация nginx, указывающая каталог `test/` как корень сайта
3. Активированный флаг `.htaccess` через директиву `HtAccess on`
Пример конфигурации:
```nginx
server {
listen 80;
server_name localhost;
location /test {
root /workspaces/test1/test;
HtAccess on;
HtAccessFile .htaccess;
}
}
```

311
cms/simple/SUMMARY.md Normal file
View File

@@ -0,0 +1,311 @@
# Тесты Apache mod_rewrite для nginx - ПОЛНАЯ СУММАРИЯ
## 📁 Структура тестовых файлов
```
/test1/test/
├── .htaccess # Главное правило rewrite для корневого каталога
├── index.html # Главная страница (назначение для redirect)
├── show.html # Показывается вместо redirect.html
├── stop.html # Блокируется (403 Forbidden)
├── redirect.html # Внутреннее перенаправление на show.html
├── test.xmx # Файл .xmx для тестирования расширения
├── README.md # Документация по тестам
├── SUMMARY.md # Эта страница - полная суммари
└── subdir/ # Подкаталог с дополнительными тестами
├── .htaccess # Правила rewrite для подкаталога
├── file.html # Файл в подкаталоге
└── other.xmx # Другой файл .xmx (блокируется)
```
---
## 🔥 Ключевые правила .htaccess
### 1⃣ Перенаделение файлов с расширением `.xmx` на index.html
**Правило:**
```apache
RewriteRule \.xmx$ /index.html [R=301,L]
```
**Ожидаемое поведение:**
| Запрос | Результат |
|--------|-----------|
| `http://localhost/test/test.xmx` | 301 Redirect → `/test/index.html` |
| URL в браузере меняется на `/test/index.html` | ✅ Внешнее перенаправление |
**Логика:**
- Все файлы заканчивающиеся на `.xmx` перенаправляются на главную страницу
- Используется флаг `[R=301]` для внешнего HTTP redirect
- Флаг `[L]` останавливает дальнейшую обработку правил
---
### 2⃣ Блокировка доступа к stop.html (403 Forbidden)
**Правила:**
```apache
RewriteCond %{REQUEST_FILENAME} =stop.html
RewriteRule ^ - [F,L]
```
**Ожидаемое поведение:**
| Запрос | Результат |
|--------|-----------|
| `http://localhost/test/stop.html` | 403 Forbidden (Доступ запрещен) |
| Страница не отображается | ✅ Полная блокировка |
**Логика:**
- `RewriteCond` проверяет что запрос именно к stop.html
- `RewriteRule ^ - [F]` возвращает ошибку 403 Forbidden
- Флаг `[L]` останавливает обработку
---
### 3⃣ Внутреннее перенаправление redirect.html на show.html
**Правило:**
```apache
RewriteRule ^redirect\.html$ show.html [L]
```
**Ожидаемое поведение:**
| Запрос | Результат |
|--------|-----------|
| `http://localhost/test/redirect.html` | Показывается content из show.html |
| URL в браузере остается `/test/redirect.html` | ✅ Internal rewrite |
**Логика:**
- Правило скрывает redirect.html и показывает вместо него show.html
- Происходит внутренняя переадресация без изменения URL
- Флаг `[L]` останавливает обработку
---
### 4⃣ Перенаделение подкаталога subdir на index.html
**Правило:**
```apache
RewriteRule ^subdir/?$ /index.html [L]
```
**Ожидаемое поведение:**
| Запрос | Результат |
|--------|-----------|
| `http://localhost/test/subdir/` | Показывается content из index.html |
| URL в браузере остается `/test/subdir/` | ✅ Internal rewrite |
---
### 5⃣ Дополнительные проверки (из main .htaccess)
**Правило:**
```apache
RewriteCond %{REQUEST_FILENAME} !test\.xmx$
RewriteRule \.xmx$ - [F,L]
```
**Ожидаемое поведение:**
- Блокирует все `.xmx` файлы кроме test.xmx
- **НО:** Работает ПОСЛЕ правила 1, поэтому test.xmx сначала редиректится на index.html
---
### 6⃣ Перенаделение subdir с последующей блокировкой (конфликт)
**Порядок в .htaccess:**
```apache
# Сначала перенаправление
RewriteRule ^subdir/?$ /index.html [L]
# Затем блокировка
RewriteRule ^subdir/?$ - [F,L]
```
**Ожидаемое поведение:**
- subdir сначала переадресуется на index.html (правило 4)
- Затем применяется правило блокировки → 403 Forbidden
- **Итог:** subdir заблокирован с ошибкой 403
---
## 📁 Правила в подкаталоге `subdir/.htaccess`
### 1⃣ Блокировка всех .xmx файлов в subdir (кроме test.xmx)
**Правило:**
```apache
RewriteRule \.xmx$ - [F,L]
```
**Ожидаемое поведение:**
| Запрос | Результат |
|--------|-----------|
| `http://localhost/test/subdir/other.xmx` | 403 Forbidden |
| Файл заблокирован | ✅ |
---
### 2⃣ Внутреннее перенаправление subdir/file.html на ../show.html
**Правило:**
```apache
RewriteRule ^file\.html$ ../show.html [L]
```
**Ожидаемое поведение:**
| Запрос | Результат |
|--------|-----------|
| `http://localhost/test/subdir/file.html` | Показывается content из show.html (в родительском каталоге) |
| URL остается `/test/subdir/file.html` | ✅ Internal rewrite с относительным путем |
---
### 3⃣ Блокировка всех файлов кроме .html в subdir
**Правило:**
```apache
RewriteCond %{REQUEST_FILENAME} !-ext=html
RewriteRule ^ - [F,L]
```
**Ожидаемое поведение:**
| Запрос | Результат |
|--------|-----------|
| `http://localhost/test/subdir/file.txt` | 403 Forbidden |
| Только .html файлы доступны | ✅ Фильтрация по расширению |
---
## 🎯 Тестирование функционала mod_rewrite
### Поддерживаемые флаги:
| Флаг | Значение | Описание |
|------|----------|----------|
| `L` | Last | Остановить обработку остальных правил в .htaccess |
| `F` | Forbidden | Возвращать ошибку 403 Forbidden |
| `R=301` | Redirect | Внешнее перенаправление с кодом HTTP 301 Moved Permanently |
| `END` | End | Остановить ВСЮ обработку rewrite (включая nginx config rules) |
### Поддерживаемые директивы:
| Директива | Описание |
|-----------|----------|
| `RewriteEngine on/off` | Включение/выключение mod_rewrite для .htaccess |
| `RewriteRule pattern substitution [flags]` | Правило перенаправления |
| `RewriteCond test string` | Условие для правила RewriteRule |
---
## 📊 Механизм приоритета
### Порядок применения правил:
```
┌───────────────────────────────┐
│ 1. Правила из .htaccess │ ← Higher Priority
│ ├─ Read & parse │
│ ├─ Convert to mod_rewrite │
│ └─ Apply rules │
│ │
├───────────────────────────────┤
│ Check END flag │
│ if (ctx->end) STOP ALL │ ← Blocks nginx config!
├───────────────────────────────┤
│ 2. Правила из nginx config │ ← Lower Priority
│ └─ Apply from server/loc │
└───────────────────────────────┘
```
**Ключевой момент:**
- При наличии флага `[END]` в .htaccess, обработка ВСЕХ правил rewrite останавливается
- Это позволяет полностью блокировать nginx config rules при необходимости
---
## 🧪 Примеры CURL запросов для тестирования
```bash
# 1. Перенаправление .xmx на index.html (301)
curl -I http://localhost/test/test.xmx
# Ожидаемый: HTTP/1.1 301 Moved Permanently
# Location: /test/index.html
# 2. Блокировка stop.html (403)
curl -I http://localhost/test/stop.html
# Ожидаемый: HTTP/1.1 403 Forbidden
# 3. Internal rewrite redirect.html → show.html (200 OK, content из show.html)
curl -s http://localhost/test/redirect.html | grep h1
# Ожидаемый: <h1>show.html</h1>
# 4. Internal rewrite subdir → index.html (200 OK)
curl -I http://localhost/test/subdir/
# Ожидаемый: HTTP/1.1 200 OK
# 5. Блокировка other.xmx в subdir (403)
curl -I http://localhost/test/subdir/other.xmx
# Ожидаемый: HTTP/1.1 403 Forbidden
# 6. Internal rewrite subdir/file.html → ../show.html (200 OK)
curl -s http://localhost/test/subdir/file.html | grep h1
# Ожидаемый: <h1>show.html</h1>
```
---
## 📝 Установка и запуск тестов
### Конфигурация nginx для запуска тестов:
```nginx
server {
listen 80;
server_name localhost;
# Корневой сайт
location / {
root /workspaces/test1/nginx-1.25.3/html;
}
# Тестовый каталог с .htaccess поддержкой
location /test {
alias /workspaces/test1/test/;
HtAccess on; # Включить чтение .htaccess файлов
HtAccessFile .htaccess; # Файл конфигурации
# Для подкаталога subdir (авто-поиск .htaccess)
}
}
```
### Команды запуска:
```bash
# 1. Скомпилировать nginx с модулем mod_rewrite
cd /workspaces/test1/nginx-1.25.3 && make modules
# 2. Запустить nginx
./nginx -c /path/to/nginx.conf
# 3. Тестировать правила через curl или браузер
curl http://localhost/test/redirect.html
```
---
## ✅ Итог
Модуль `ngx_http_apache_rewrite_module` обеспечивает:
1.**Чтение .htaccess файлов** при каждом запросе с проверкой mtime
2.**Кэширование директив** в памяти на время жизненного цикла запроса
3.**Конвертацию Apache mod_rewrite правил** в формат nginx mod_rewrite
4.**Приоритет .htaccess выше nginx config rules**
5.**Флаг [END] для полной остановки обработки rewrite**
6.**Поддержка флагоv L, F, R=301, END**
Все тестовые файлы готовы и содержат документацию по ожидаемому поведению! 🎉

40
cms/simple/index.html Normal file
View File

@@ -0,0 +1,40 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>Index - Тест mod_rewrite для nginx</title>
</head>
<body>
<h1>index.html</h1>
<p>Вы попали на index.html.</p>
<p>
<strong
>Это страница назначения при перенаправлении файлов с
расширением xmx.</strong
>
</p>
<hr />
<ul>
<li><a href="show.html">Показать (show.html)</a></li>
<li><a href="stop.html">Stop - заблокированная страница</a></li>
<li>
<a href="redirect.html">Redirect - редирект на show.html</a>
</li>
<li><a href="subdir/">Подкаталог</a></li>
</ul>
<hr />
<p><em>Тесты mod_rewrite для nginx:</em></p>
<ol>
<li>
Запросите <code>.xmx</code> файл → перенаправление на index.html
</li>
<li>
Запросите <code>stop.html</code> → 403 Forbidden (доступ
запрещен)
</li>
<li>Запросите <code>redirect.html</code> → покажет show.html</li>
</ol>
</body>
</html>

159
cms/simple/nginx.conf Normal file
View File

@@ -0,0 +1,159 @@
load_module modules/ngx_http_apache_rewrite_module.so;
worker_processes 1;
error_log logs/error.log debug;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 8081;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
server {
listen 8081;
server_name example1.com;
root /sites/site1;
HtaccessEnable on;
RewriteEngine On;
location / {
RewriteEngine On;
}
}
server {
listen 8081;
server_name example2.com;
root /sites/site2;
HtaccessEnable on;
RewriteEngine On;
index index.php;
location / {
RewriteEngine On;
autoindex on;
}
location ~* \.php$ {
RewriteEngine On;
include fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php-fpm/www.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
server {
listen 8081;
server_name example3.com;
root /sites/site3;
HtaccessEnable on;
RewriteEngine On;
index index.php;
location / {
RewriteEngine On;
autoindex on;
}
location ~* \.php$ {
RewriteEngine On;
include fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php-fpm/www.sock; # подключаем сокет php-fpm
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
server {
listen 8081;
server_name example4.com;
root /sites/site4;
HtaccessEnable on;
RewriteEngine On;
index index.php;
location / {
RewriteEngine On;
autoindex on;
}
location ~* \.php$ {
RewriteEngine On;
include fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php-fpm/www.sock; # подключаем сокет php-fpm
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
server {
listen 8081;
server_name example5.com;
root /sites/site5;
HtaccessEnable on;
RewriteEngine On;
index index.php;
location / {
RewriteEngine On;
autoindex on;
}
location ~* \.php$ {
RewriteEngine On;
include fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php-fpm/www.sock; # подключаем сокет php-fpm
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
}

26
cms/simple/redirect.html Normal file
View File

@@ -0,0 +1,26 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>Redirect - Тест mod_rewrite для nginx</title>
</head>
<body>
<h1>redirect.html</h1>
<p>Вы попали на redirect.html.</p>
<hr />
<ul>
<li><a href="index.html">На главную (index.html)</a></li>
<li><a href="show.html">Показать (show.html)</a></li>
<li><a href="stop.html">Stop - заблокированная</a></li>
</ul>
<hr />
<p>
<em><strong style="color: green;">Эта страница должна быть скрыта!</strong></em>
</p>
<p>
При обращении к redirect.html, mod_rewrite должен показать show.html вместо этой страницы.
</p>
</body>
</html>

26
cms/simple/show.html Normal file
View File

@@ -0,0 +1,26 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>Show - Тест mod_rewrite для nginx</title>
</head>
<body>
<h1>show.html</h1>
<p>Вы попали на show.html.</p>
<hr />
<ul>
<li><a href="index.html">На главную (index.html)</a></li>
<li><a href="redirect.html">Redirect - перенаправляет сюда</a></li>
<li><a href="stop.html">Stop - заблокированная</a></li>
</ul>
<hr />
<p>
<em><strong style="color: green;">Показано вместо redirect.html!</strong></em>
</p>
<p>
Это страница-назначение для правила <code>RewriteRule ^redirect\.html$ show.html [L]</code>.
</p>
</body>
</html>

10
cms/simple/site1.conf Normal file
View File

@@ -0,0 +1,10 @@
<VirtualHost 192.168.3.96:80>
DocumentRoot "/sites/site1"
ServerName example1.com
<Directory /sites/site1>
Options +Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>

View File

23
cms/simple/stop.html Normal file
View File

@@ -0,0 +1,23 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>Stop - Тест mod_rewrite для nginx</title>
</head>
<body>
<h1>stop.html</h1>
<p>Вы попали на stop.html.</p>
<hr />
<ul>
<li><a href="index.html">На главную (index.html)</a></li>
<li><a href="show.html">Показать (show.html)</a></li>
<li><a href="redirect.html">Redirect</a></li>
</ul>
<hr />
<p>
<em><strong style="color: red;">Эта страница должна быть заблокирована!</strong></em>
</p>
</body>
</html>

View File

View File

@@ -0,0 +1,30 @@
# ============================================================
# .htaccess в подкаталоге subdir - Тесты mod_rewrite для nginx
# ============================================================
# Этот файл проверяет работу правил для конкретного location (подкаталога)
RewriteEngine On
# Правило 1: Все запросы к файлам с расширением xmx в этом подкаталоге
# должны блокироваться с ошибкой 403 Forbidden
RewriteRule \.xmx$ - [F,L]
# Тестирование:
# - http://localhost/test/subdir/other.xmx → 403 Forbidden
# Правило 2: Внутреннее перенаделение subdir/file.html на ../show.html
# (переход из подкаталога в родительский)
RewriteRule ^file\.html$ ../show.html [L]
# Тестирование:
# - http://localhost/test/subdir/file.html → показывает содержимое show.html
# Правило 3: Блокировка доступа ко всем файлам кроме .html
RewriteCond %{REQUEST_URI} !\.html$
RewriteRule ^ - [F,L]
# Тестирование:
# - http://localhost/test/subdir/file.txt → 403 Forbidden

View File

@@ -0,0 +1,26 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>File - Тест mod_rewrite для nginx</title>
</head>
<body>
<h1>file.html</h1>
<p>Вы попали на file.html в подкаталоге subdir.</p>
<hr />
<ul>
<li><a href="../index.html">На главную (index.html)</a></li>
<li><a href="../show.html">Показать (show.html)</a></li>
<li><a href="../redirect.html">Redirect</a></li>
</ul>
<hr />
<p>
<em><strong style="color: green;">Это страница из подкаталога!</strong></em>
</p>
<p>
При обращении к <code>/test/subdir/file.html</code>, mod_rewrite должен показать содержимое <code>../show.html</code>.
</p>
</body>
</html>

View File

@@ -0,0 +1,27 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>Other XMX - Тест mod_rewrite для nginx</title>
</head>
<body>
<h1>other.xmx</h1>
<p>Вы попытались получить этот файл.</p>
<hr />
<ul>
<li><a href="../index.html">На главную (index.html)</a></li>
<li><a href="../show.html">Показать (show.html)</a></li>
<li><a href="file.html">File.html</a></li>
</ul>
<hr />
<p>
<em><strong style="color: red;">Этот файл должен быть заблокирован!</strong></em>
</p>
<p>
При обращении к <code>/test/subdir/other.xmx</code>, mod_rewrite должен вернуть
<code style="background-color: #f00; color: white;">403 Forbidden</code>.
</p>
</body>
</html>

26
cms/simple/test.xmx Normal file
View File

@@ -0,0 +1,26 @@
# Test File - test.xmx
This is a test file used to verify the mod_rewrite rule that redirects all files ending with .xmx extension to index.html.
## How it works:
When you request any `.xmx` file in this directory, the following rule from `.htaccess` will be applied:
```
RewriteRule \.xmx$ /index.html [R=301,L]
```
This redirects your browser to `/index.html`.
## Test URLs:
- `http://localhost/test/test.xmx` → redirects to `/test/index.html`
- `http://localhost/test/subdir/other.xmx` → redirects to `/test/index.html`
- `http://localhost/test/deep/path/file.xmx` → redirects to `/test/index.html`
## Additional Test Files:
See other test pages in this directory:
- index.html - main page
- show.html - shown instead of redirect.html via internal rewrite
- stop.html - should be blocked (403 Forbidden) by mod_rewrite rules

View File

@@ -0,0 +1,14 @@
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
# add a trailing slash to /wp-admin
RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L]
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ $2 [L]
RewriteRule . index.php [L]

View File

@@ -0,0 +1,152 @@
# WordPress Multisite Test Structure - Documentation
## Directory Layout Overview
```
/test1/cms/wordpress-multy/
├── index.php - WordPress entry point (routes non-existing files)
│ - Returns: "WordPress Multisite Content Route" page
├── wp-admin/ - Admin directory tests
│ └── (empty directory for trailing slash redirect test)
├── wp-admin/admin.php - Test file for wp-admin routing rule
├── wp-content/ - WordPress content directory tests
│ └── themes/ - Theme subdirectory
├── wp-includes/ - Core files directory tests
│ └── js/ - JavaScript subdirectory
│ └── (empty for testing)
├── style.css - Existing CSS file (tests RewriteRule !-f condition)
├── script.js - Existing JS file (tests RewriteRule !-f condition)
├── test-wp-multi-rewriterules.sh - Bash script to test all rules using curl
└── README.md - This documentation file
```
## Apache Rules Explained - WordPress Multisite
### 1. HTTP Authorization Header Passing
```apache
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
```
**Что делает:** Копирует HTTP Authorization header в переменную HTTP_AUTHORIZATION для PHP
### 2. RewriteBase Configuration
```apache
RewriteBase /
```
**Что делает:** Устанавливает базовый путь к корню сайта
### 3. Direct index.php Access Protection
```apache
RewriteRule ^index\.php$ - [L]
```
**Что делает:** Предотвращает зацикливание маршрутизации (не переписывает прямой запрос на index.php)
### 4. wp-admin Trailing Slash Redirect
```apache
# add a trailing slash to /wp-admin
RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L]
```
**Что делает:** Добавляет слэш в конце к URL `/wp-admin`, редирект 301 (Moved Permanently)
**Зачем нужно:** WordPress multisite требует trailing slash для admin panel, обеспечивает корректную маршрутизацию
**Подхват поддомена:** `[_0-9a-zA-Z-]+/?` позволяет обработку поддоменов в multisite setup:
- `/wp-admin` → редирект на `/wp-admin/` (301)
- `site.subdomain.com/wp-admin` → редирект на `site.subdomain.com/wp-admin/`
### 5. Existing Files/Directories Check (Skip Processing)
```apache
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
```
**Что делает:** Если файл ИЛИ директория СУЩЕСТВУЕТ - останавливаем обработку правил, пропускаем запрос напрямую
**Зачем нужно:** Предотвращает маршрутизацию на index.php для существующих статических файлов (CSS, JS, изображения)
### 6. WordPress Multisite Core Routing Rules
```apache
RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L]
```
**Что делает:** Для multisite setups - маршрутизирует wp-content, wp-admin, wp-includes напрямую (без index.php)
**Подхват поддоменов:** `[_0-9a-zA-Z-]+/?` позволяет обработку поддоменов в multisite:
- `/wp-content/uploads/image.jpg` → прямой доступ к файлу
- `site.subdomain.com/wp-admin/admin.php` → прямая маршрутизация к admin.php
### 7. All PHP Files Direct Access
```apache
RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ $2 [L]
```
**Что делает:** Любой PHP файл отдаётся напрямую, без маршрутизации через index.php
**Подхват поддоменов:** `[_0-9a-zA-Z-]+/?` позволяет обработку поддоменов в multisite:
- `/wp-login.php` → прямой доступ к файлу
- `site.subdomain.com/wp-signup.php` → прямая маршрутизация к signup.php
### 8. Final WordPress Routing Rule
```apache
RewriteRule . index.php [L]
```
**Что делает:** Все оставшиеся запросы маршрутизируются через index.php (основной URL routing)
## Test Script Features
The script includes test functions:
1. **test_rule()** - checks HTTP status code only
2. **test_rule_content()** - checks both status AND response body content
## Multisite-Specific Testing Scenarios
### Subdomain vs Subdirectory Setup
WordPress Multisite может быть configured в двух режимах:
- **Subdomain mode**: sites поддоменами (site1.example.com, site2.example.com)
- **Subdirectory mode**: сайты поддиректориями (/site1/, /site2/)
Правило `[_0-9a-zA-Z-]+/?` обрабатывает оба случая:
- Для subdomain: `[subdomain/]?` = optional prefix
- Для subdirectory: `[directory/]?` = optional prefix
### Test Examples
| URL | Правило | Ожидаемый результат |
|-----|---------|---------------------|
| `http://test.my.brp/wp-admin` | trailing slash redirect | 301 → `/wp-admin/` ✓ |
| `http://test.my.brp/wp-admin/` | direct access (directory exists) | 200 OK ✓ |
| `http://test.my.brp/style.css` | existing file (!-f passes) | 200 OK ✓ |
| `http://test.my.brp/script.js` | existing file (!-f passes) | 200 OK ✓ |
| `http://test.my.brp/wp-admin/admin.php` | multisite wp-admin rule | 200 OK ✓ |
| `http://test.my.brp/wp-content/uploads/test.jpg` | multisite wp-content rule | 200 OK ✓ |
| `http://test.my.brp/nonexistent-page/` | final index.php routing | 200 OK (content from index.php) ✓ |
## Run Tests
Execute the test script to verify all rules:
```bash
cd /home/alexey/projects/workspace-zed/test1/cms/wordpress-multy
./test-wp-multi-rewriterules.sh
```
Expected results for WordPress Multisite tests (all should be **PASS ✓**):
- wp-admin trailing slash redirect: HTTP 301 ✓
- wp-admin directory access: HTTP 200 ✓
- Existing file (CSS/JS) access: HTTP 200 ✓
- Existing directory access: HTTP 200 ✓
- WordPress multisite routing (wp-content, admin): HTTP 200 ✓
- Final index.php routing: HTTP 200 + content "WordPress Multisite Content Route" ✓

View File

@@ -0,0 +1,6 @@
<?php
// WordPress Multisite Test PHP File
// This file is tested via RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ $2 [L]
// Any .php file access rule
echo "WordPress Multisite custom script - direct PHP file access";

View File

@@ -0,0 +1,15 @@
<?php
/**
* WordPress Multisite - index.php (Test version)
* This file handles routing for non-existing files/directories in multisite setup
*/
// Simulated WordPress Multisite response
echo "<html><head><title>WordPress Multisite Test Site</title></head><body>";
echo "<h1>WordPress Multisite Content Route</h1>";
echo "<p>This page is served by index.php via RewriteRule.</p>";
echo "<div class='wp-multisite-config'>WordPress Multisite Configuration Loaded</div>";
echo "</body></html>";
// Exit
exit;

View File

@@ -0,0 +1,4 @@
// WordPress Multisite Test JS File
// This file is tested via RewriteCond !-f (existing files skip routing)
console.log('WordPress Multisite CSS/JS test file');

View File

@@ -0,0 +1,8 @@
/* WordPress Multisite Test CSS File */
/* This file is tested via RewriteCond !-f (existing files skip routing) */
body {
font-family: 'Arial', sans-serif;
margin: 0;
padding: 20px;
}

View File

@@ -0,0 +1,171 @@
#!/bin/bash
# ============================================
# WordPress Multisite .htaccess Rules Test Script
# ============================================
# This script tests each rule from cms/wordpress-multy/.htaccess
# Assumption: Site root is mapped to /home/alexey/projects/workspace-zed/test1/cms/wordpress-multy
# Domain: test.my.brp
# ============================================
BASE_URL="http://test.my.brp"
echo "=============================================="
echo "WordPress Multisite .htaccess Rules Test Suite"
echo "=============================================="
echo ""
# Function to test a rule and report result (status only)
test_rule() {
local description="$1"
local url="$2"
local expected_status="$3" # e.g., 403, 404, 200, 301
echo "--- Test: $description ---"
response=$(curl -s -o /dev/null -w "%{http_code}" "$url")
if [ "$response" = "$expected_status" ]; then
echo "✓ PASS (HTTP $response)"
else
echo "✗ FAIL (Expected: HTTP $expected_status, Got: HTTP $response)"
fi
echo ""
}
# Function to test a rule and verify content contains expected string
test_rule_content() {
local description="$1"
local url="$2"
local expected_status="$3" # e.g., 403, 404, 200, 301
local expected_content="$4" # Expected substring in response body
echo "--- Test: $description ---"
response=$(curl -s "$url")
http_code=$(curl -s -o /dev/null -w "%{http_code}" "$url")
# Check status code
if [ "$http_code" != "$expected_status" ]; then
echo "✗ FAIL (Status: HTTP $http_code, Expected: HTTP $expected_status)"
return 1
fi
# Check content contains expected substring
if [[ "$response" == *"$expected_content"* ]]; then
echo "✓ PASS (HTTP $http_code, Content matches '$expected_content')"
else
echo "✗ FAIL (Content missing: '$expected_content') - Response:"
echo "$response" | head -5
fi
echo ""
}
echo "=============================================="
echo "1. HTTP Authorization Header Handling"
echo "=============================================="
# Test that Authorization header is properly handled by WordPress Multisite API
test_rule_content "WordPress multisite handles Authorization header (API request)" \
"$BASE_URL/wp-json/wp/v2/posts?Authorization=Bearer secret_token_123" \
"200" \
"WordPress Multisite Configuration Loaded"
echo ""
echo "=============================================="
echo "2. RewriteBase Root Configuration"
echo "=============================================="
# Test root directory access with proper base path routing
test_rule_content "Root directory access (base path /)" \
"$BASE_URL/" \
"200" \
"WordPress Multisite Content Route"
echo ""
echo "=============================================="
echo "3. Direct index.php Access Protection"
echo "=============================================="
# Test direct access to index.php - should NOT be rewritten (prevents infinite loop)
test_rule_content "Direct index.php access (not rewritten)" \
"$BASE_URL/index.php" \
"200" \
"WordPress Multisite Configuration Loaded"
echo ""
echo "=============================================="
echo "4. wp-admin Trailing Slash Redirect Rule"
echo "=============================================="
# Test trailing slash redirect for /wp-admin (301 Moved Permanently)
test_rule_content "wp-admin trailing slash redirect (301)" \
"$BASE_URL/wp-admin" \
"301"
# Test that /wp-admin/ with trailing slash works correctly (existing directory)
test_rule_content "wp-admin/ with trailing slash (exists as directory)" \
"$BASE_URL/wp-admin/" \
"200" \
"WordPress Multisite Content Route"
echo ""
echo "=============================================="
echo "5. Existing Files/Directories Check Rule"
echo "=============================================="
# Test existing CSS file access (!-f condition passes) - should return 200 OK without routing to index.php
test_rule "Existing CSS file access (style.css)" \
"$BASE_URL/style.css" \
"200"
# Test existing JS file access (!-f condition passes) - should return 200 OK without routing to index.php
test_rule "Existing JS file access (script.js)" \
"$BASE_URL/script.js" \
"200"
echo ""
echo "=============================================="
echo "6. WordPress Multisite Core Routing Rules"
echo "=============================================="
# Test wp-admin specific routing rule (not through index.php)
test_rule_content "wp-admin/admin.php multisite routing" \
"$BASE_URL/wp-admin/admin.php" \
"200" \
"WordPress Multisite Content Route"
# Test wp-content specific routing rule (for uploads, themes etc.)
test_rule_content "wp-content/uploads/test.jpg multisite routing" \
"$BASE_URL/wp-content/uploads/test.jpg" \
"200" \
"WordPress Multisite Content Route"
# Test wp-includes specific routing rule (core files)
test_rule_content "wp-includes/js/jquery.js multisite routing" \
"$BASE_URL/wp-includes/js/jquery.js" \
"200" \
"WordPress Multisite Content Route"
echo ""
echo "=============================================="
echo "7. All PHP Files Direct Access Rule"
echo "=============================================="
# Test any .php file access (not routed through index.php) - custom script returns specific content
test_rule_content "Any PHP file access (.php rule)" \
"$BASE_URL/custom-script.php" \
"200" \
"WordPress Multisite custom script - direct PHP file access"
echo ""
echo "=============================================="
echo "8. Final WordPress Routing Rule"
echo "=============================================="
# Test non-existing file routing through index.php (main routing)
test_rule_content "Non-existing page routing (routes to index.php)" \
"$BASE_URL/nonexistent-page/" \
"200" \
"WordPress Multisite Content Route"
# Test non-existing directory routing through index.php (routes to index.php)
test_rule_content "Non-existing directory routing (routes to index.php)" \
"$BASE_URL/nonexistent-directory/" \
"200" \
"WordPress Multisite Content Route"
echo ""
echo "=============================================="
echo "Test Suite Complete"
echo "=============================================="

View File

@@ -0,0 +1,2 @@
# WordPress Multisite admin.php test file
# This file is tested via RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L]

View File

@@ -0,0 +1,2 @@
# WordPress Multisite test.jpg file for wp-content routing rule
# This file is tested via RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L]

View File

@@ -0,0 +1,2 @@
# WordPress Multisite jquery.js file for wp-includes routing rule
# This file is tested via RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L]

159
cms/wordpress/nginx.conf Normal file
View File

@@ -0,0 +1,159 @@
load_module modules/ngx_http_apache_rewrite_module.so;
worker_processes 1;
error_log logs/error.log debug;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 8081;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
server {
listen 8081;
server_name example1.com;
root /sites/site1;
HtaccessEnable on;
RewriteEngine On;
location / {
RewriteEngine On;
}
}
server {
listen 8081;
server_name example2.com;
root /sites/site2;
HtaccessEnable on;
RewriteEngine On;
index index.php;
location / {
RewriteEngine On;
autoindex on;
}
location ~* \.php$ {
RewriteEngine On;
include fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php-fpm/www.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
server {
listen 8081;
server_name example3.com;
root /sites/site3;
HtaccessEnable on;
RewriteEngine On;
index index.php;
location / {
RewriteEngine On;
autoindex on;
}
location ~* \.php$ {
RewriteEngine On;
include fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php-fpm/www.sock; # подключаем сокет php-fpm
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
server {
listen 8081;
server_name example4.com;
root /sites/site4;
HtaccessEnable on;
RewriteEngine On;
index index.php;
location / {
RewriteEngine On;
autoindex on;
}
location ~* \.php$ {
RewriteEngine On;
include fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php-fpm/www.sock; # подключаем сокет php-fpm
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
server {
listen 8081;
server_name example5.com;
root /sites/site5;
HtaccessEnable on;
RewriteEngine On;
index index.php;
location / {
RewriteEngine On;
autoindex on;
}
location ~* \.php$ {
RewriteEngine On;
include fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php-fpm/www.sock; # подключаем сокет php-fpm
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
}

16
cms/wordpress/site3.conf Normal file
View File

@@ -0,0 +1,16 @@
<VirtualHost *:80>
DocumentRoot "/sites/site3"
ServerName example3.com
DirectoryIndex index.php
<Directory /sites/site3/wordpress>
Options +Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
<FilesMatch \.php$>
SetHandler "proxy:unix:/run/php-fpm/www.sock|fcgi://localhost"
</FilesMatch>
</VirtualHost>

View File

@@ -0,0 +1,140 @@
#!/bin/bash
# ============================================
# WordPress .htaccess Rules Test Script
# ============================================
# This script tests each rule from cms/wordpress/.htaccess
# Assumption: Site root is mapped to /home/alexey/projects/workspace-zed/test1/cms/wordpress
# Domain: test.my.brp
# ============================================
BASE_URL="http://test.my.brp"
echo "=============================================="
echo "WordPress .htaccess Rules Test Suite"
echo "=============================================="
echo ""
# Function to test a rule and report result (status only)
test_rule() {
local description="$1"
local url="$2"
local expected_status="$3" # e.g., 403, 404, 200
echo "--- Test: $description ---"
response=$(curl -s -o /dev/null -w "%{http_code}" "$url")
if [ "$response" = "$expected_status" ]; then
echo "✓ PASS (HTTP $response)"
else
echo "✗ FAIL (Expected: HTTP $expected_status, Got: HTTP $response)"
fi
echo ""
}
# Function to test a rule and verify content contains expected string
test_rule_content() {
local description="$1"
local url="$2"
local headers="$3" # Optional: additional curl -H header flags (can be empty)
local expected_status="$4" # e.g., 403, 404, 200
local expected_content="$5" # Expected substring in response body
echo "--- Test: $description ---"
if [ -n "$headers" ]; then
response=$(curl -s -H "$headers" "$url")
http_code=$(curl -s -H "$headers" -o /dev/null -w "%{http_code}" "$url")
else
response=$(curl -s "$url")
http_code=$(curl -s -o /dev/null -w "%{http_code}" "$url")
fi
# Check status code
if [ "$http_code" != "$expected_status" ]; then
echo "✗ FAIL (Status: HTTP $http_code, Expected: HTTP $expected_status)"
return 1
fi
# Check content contains expected substring
if [[ "$response" == *"$expected_content"* ]]; then
echo "✓ PASS (HTTP $http_code, Content matches '$expected_content')"
else
echo "✗ FAIL (Content missing: '$expected_content') - Response:"
echo "$response" | head -5
fi
echo ""
}
echo "=============================================="
echo "1. HTTP_AUTHORIZATION Header Rewrite Rule"
echo "=============================================="
# Test that Authorization header is properly handled by WordPress REST API mock
# This rule copies HTTP Authorization header to HTTP_AUTHORIZATION env var via mod_rewrite [E=...]
test_rule_content "WordPress REST API: Authorization header passed through mod_rewrite" \
"$BASE_URL/wordpress/wp-json/wp/v2/posts" \
"Authorization: secret_token_123" \
"200" \
"\"status\": \"success\""
# Additional test: verify token verification in response
test_rule_content "WordPress REST API: Token verified correctly" \
"$BASE_URL/wordpress/wp-json/wp/v2/posts" \
"" \
"401" \
"\"message\": \"unauth\""
echo ""
echo "=============================================="
echo "2. RewriteBase Configuration"
echo "=============================================="
# RewriteBase / sets base path to root - verifies routing works correctly
test_rule_content "Base path routing (root directory)" \
"$BASE_URL/wordpress/" \
"" \
"200" \
"WordPress Content Route"
echo ""
echo "=============================================="
echo "3. Direct index.php Access Protection"
echo "=============================================="
# RewriteRule ^index\.php$ - [L] - direct access to index.php should return 200 OK
test_rule_content "Direct index.php access (not rewritten)" \
"$BASE_URL/wordpress/index.php" \
"" \
"200" \
"WordPress Content Route"
echo ""
echo "=============================================="
echo "4. WordPress Core Routing Rules"
echo "=============================================="
# Test non-existing file routes through index.php (!-f condition)
test_rule_content "Non-existing file routing (routes to index.php)" \
"$BASE_URL/wordpress/nonexistent-page/" \
"" \
"200" \
"WordPress Content Route"
# Test existing file access - should return 200 OK (!-f passes for existing files)
test_rule "Existing file access (existing.jpg)" \
"$BASE_URL/wordpress/existing.jpg" \
"200"
# Test non-existing directory routing - should route to index.php (!-d condition)
test_rule_content "Non-existing directory routing (routes to index.php)" \
"$BASE_URL/wordpress/nonexistent-dir/" \
"" \
"200" \
"WordPress Content Route"
# Test existing directory access - should return 200 OK (!-d passes for existing dirs)
test_rule "Existing directory access (somedir/)" \
"$BASE_URL/wordpress/somedir/" \
"200"
echo ""
echo "=============================================="
echo "Test Suite Complete"
echo "=============================================="

View File

@@ -0,0 +1,8 @@
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /wordpress
RewriteRule ^wp-json/(.*)$ wp_api.php?api=$1 [L,QSA]
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . index.php [L]

Some files were not shown because too many files have changed in this diff Show More