# ==========================================================================
# aucms - Apache 重写与安全规则（FPM / mod_php 模式）
#
# 【修复说明】早期版本的这份文件有两个真实漏洞：
#
#   1) 顺序错误导致 deny 规则被短路
#      原文先写 `RewriteRule ^ - [L]`（"真实文件直接返回"），
#      再写 `RewriteRule ^(core|config|...)/ - [F,L]`（"拒绝敏感目录"）。
#      Apache 的 RewriteRule 按**从上到下**顺序匹配，`[L]` 表示"到此为止"，
#      因此对于 `/core/Bootstrap.php` 这种**真实存在的文件**，
#      第一条就命中并终止了 —— deny 规则永远执行不到，源码可下载。
#      ⇒ 必须把 deny 规则**放在最前面**。
#
#   2) `/aucms`（无扩展名）不在任何规则内
#      FilesMatch 只按扩展名匹配，`aucms` 没有扩展名，
#      于是它作为静态文件被直接下载，暴露 CLI 入口。
#      （入口历史上叫 `nova`，改名后必须同步改这条，否则保护形同虚设。）
#      ⇒ 用 RewriteRule 精确拦截无扩展名入口与说明文件。
# ==========================================================================

# --------------------------------------------------------------------------
# 1) 敏感路径：必须放最前面（在任何 [L] 之前）
# --------------------------------------------------------------------------
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /

    # 内核 / 配置 / 数据 / 运行时 / 测试 / 文档 / 语言包
    #
    # 【为什么必须加 -f / -d 条件】
    # 内容节点的 slug 由运营自定义，很容易取到 docs / lang / data 这类名字。
    # 若不带条件地按前缀 deny，就会把**合法的内容页**一起挡成 403
    # （实测：/docs 节点首页 403）。
    # 这里用 REQUEST_FILENAME 判断"是不是真实的文件/目录"：
    #   · 真的存在 docs/ 目录 → 源码目录，403  ✔
    #   · 不存在 → 同名路由，交给 index.php  ✔
    #
    # 【install 不在这个名单里】
    # 安装向导是全新部署的唯一入口，把 install/ 整目录 deny 掉会让
    # 全新站点一打开就 403（本文件历史版本正是如此）。
    # 它改由下面第 1.5 节的规则**整体交给单一入口**，由应用层依据
    # runtime/install.lock 决定是继续安装还是 302 回首页。
    # 1.4) 上传目录必须可被静态访问
    #
    # runtime/ 整体拒绝是对的，但 runtime/upload 是唯一的例外：
    # 前台文章配图与会员头像都存在这里。整目录 403 的后果是
    # "上传接口返回成功、页面上全是裂图"，而排查时会误以为上传失败。
    #
    # 只放行静态扩展名；脚本、点文件与目录穿越仍由下面的规则拒绝，
    # 上传目录内另有 .htaccess（php_flag engine off）兜底。
    RewriteCond %{REQUEST_URI} ^/runtime/upload/.+\.(jpe?g|png|gif|webp|bmp|ico|pdf|zip|docx?|xlsx?|pptx?|txt|mp4|mp3)$ [NC]
    RewriteRule ^runtime/upload/ - [L]

    RewriteCond %{REQUEST_FILENAME} -f [OR]
    RewriteCond %{REQUEST_FILENAME} -d
    RewriteRule ^(core|config|data|runtime|tests|docs|lang)(/|$) - [F,L]

    # 1.5) 安装向导：物理目录与模板都不直出，全部交给入口
    #      （/install 本身是真实目录，不拦的话会 301 到 /install/ 再被
    #        Options -Indexes 变成 403，用户根本进不去向导）
    RewriteRule ^install(/.*)?$ index.php [L,QSA]

    # 插件内部目录
    RewriteCond %{REQUEST_FILENAME} -f [OR]
    RewriteCond %{REQUEST_FILENAME} -d
    RewriteRule ^plugin/[^/]+/(src|view|lang)(/|$) - [F,L]

    # 无扩展名与不需要对外暴露的入口 / 说明文件
    # CLI 入口：`aucms` 为现名，`nova` 是旧名（兼容未改名的部署）
    RewriteRule ^aucms$                - [F,L]
    RewriteRule ^nova$                 - [F,L]
    RewriteRule ^server\.php$          - [F,L]
    RewriteRule ^swoole\.php$          - [F,L]
    RewriteRule ^README\.md$           - [F,L]
    RewriteRule ^nginx\.conf\.example$ - [F,L]
    RewriteRule ^composer\.(json|lock)$ - [F,L]

    # 点文件（.htaccess / .env / .git 等），保留 ACME 的 .well-known
    RewriteRule (^|/)\.(?!well-known/) - [F,L]
</IfModule>

# --------------------------------------------------------------------------
# 2) 路由：静态资源直出，其余进单一入口
#
#    注意 `!-f` / `!-d` 的判断顺序，以及不要给目录加 [L]，
#    否则与物理目录同名的路由（如 /install）会 301 到目录 → 403。
# --------------------------------------------------------------------------
<IfModule mod_rewrite.c>
    RewriteEngine On

    # 真实文件直接返回
    RewriteCond %{REQUEST_FILENAME} -f
    RewriteRule ^ - [L]

    # 其余全部交给单一入口
    RewriteRule ^ index.php [L]
</IfModule>

# --------------------------------------------------------------------------
# 3) 目录与文件权限
# --------------------------------------------------------------------------
Options -Indexes -ExecCGI

# 只允许单一入口执行 PHP（Apache 2.4 语法）
<FilesMatch "\.php$">
    <IfModule mod_authz_core.c>
        Require all denied
    </IfModule>
    <IfModule !mod_authz_core.c>
        Order allow,deny
        Deny from all
    </IfModule>
</FilesMatch>
<Files "index.php">
    <IfModule mod_authz_core.c>
        Require all granted
    </IfModule>
    <IfModule !mod_authz_core.c>
        Order allow,deny
        Allow from all
    </IfModule>
</Files>

# --------------------------------------------------------------------------
# 4) 安全响应头
# --------------------------------------------------------------------------
<IfModule mod_headers.c>
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header unset X-Powered-By
</IfModule>

<IfModule mod_php.c>
    php_flag expose_php off
</IfModule>
