SCRIPT_NAME 传成了带路径的整串 URI,主题拿它推算「当前域名」,算出来是个不存在的地址,于是判定未授权,把内页全部降级成首页。问题不在 Typecho,不在 PHP,也不在主题授权。
环境:Handsome 9.x版本(八百年过去了,作者的10.1版本还是开发测试版)
一、现象
点开自己博客里的任意一篇文章,地址栏变了,页面不动,还停在首页。刷新一下首页正常,后台能登进去,但是文章打不开。
我拿 curl 把几个地址跑了一遍:
| 请求 | 状态码 | 返回大小 | 实际内容 |
|---|---|---|---|
/ | 200 | 57450 | 首页 ✓ |
/index.php/archives/26/ | 200 | 57450 | 首页 ✗ |
/index.php/feed/ | 200 | 57450 | 首页,不是 RSS ✗ |
/index.php/zzzznotexist/ | 200 | 57450 | 首页,本该 404 ✗ |
/index.php/action/login | 302 | — | 正常跳转 ✓ |
四个完全不同的地址,返回同一份 57450 字节。文章页、RSS、甚至连不存在的路径,统统是首页。
现象:/action/login 是唯一正常的。它不渲染模板,说明 Typecho 核心、数据库、路由都没事,坏的只是走主题渲染的那一层。
二、原因
后来翻主题代码才看明白,它是用 dirname($_SERVER['SCRIPT_NAME']) 推算当前域名的:
- 正常:
SCRIPT_NAME=/index.php,dirname得/,域名 =https://你的域名✓ - 故障:
SCRIPT_NAME=/index.php/archives/26/,dirname得/index.php/archives,域名 =https://你的域名/index.php/archives✗
一个不存在的域名,授权自然对不上,主题就静默返回首页。
(大肥鱼说的,正常来说授权对不上应该返回主题作者的授权页面,为什么静默返回主页我不太清楚,我也不想去破解作者的主题找原因)
那 SCRIPT_NAME 为什么会带上一串路径?问题出在宝塔的 PHP 转发配置里,include 放错了位置,nginx/conf/php/82.conf:
location ~ \.php(.*)$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
set $real_script_name $fastcgi_script_name;
if ($fastcgi_script_name ~ "^(.+?\.php)(/.+)$") {
set $real_script_name $1;
set $path_info $2;
}
fastcgi_param SCRIPT_FILENAME $document_root$real_script_name;
fastcgi_param SCRIPT_NAME $real_script_name;
fastcgi_param PATH_INFO $path_info;
include fastcgi_params; # ← 就是这一行
}原本的思路是:$1 取脚本路径,$2 取 pathinfo,各自赋给变量再传下去。但 fastcgi_params 这个文件里自带一句:
fastcgi_param SCRIPT_NAME $fastcgi_script_name;nginx 的 fastcgi_param 是后写的覆盖先写的,不是先到先得。include 在最下面,等于用原始的整串 URI 把前面辛苦算出来的值又盖了回去。
这里看起来完全正确。但set 那一句改一百遍都不会有效果,问题只是 include 的位置。
另外那行 try_files $uri =404; 也有问题:/index.php/archives/26/ 在磁盘上找不到对应文件,pathinfo 修好之后它会立刻把所有内页打成 404,一起改掉。
三、改法
最后换成了 nginx 的官方指令,把那段手写的 if 拆解整个删掉:
location ~ \.php(.*)$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
try_files $fastcgi_script_name =404;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param REQUEST_URI $request_uri;
}这么写有三个好处:
include的顺序不再致命。fastcgi_split_path_info会把$fastcgi_script_name就地修正成脚本路径,于是fastcgi_params里那句SCRIPT_NAME自动就是对的,哪怕有人把 include 又挪回最后也不会翻车。try_files可以放心用。此时它校验的是磁盘上真实存在的脚本,不会误杀 pathinfo。- 少一个
if。nginx官方wiki有一页就叫If Is Evil,这是作者自己承认的历史包袱。
四、验证
任意点开一个博客,正常显示内容,问题解决。