前言

疫情期间闲的没事学了PHP,显然对于总是折腾网站的我很是管用,很多时候我甚至用它写了些很奇葩的东西,什么彩票概率计算啊,给老弟发作业啊,这些都是后话,这次写个正常点的,大部分博客可能会有自己的后端或是自己站点的资源服务器文章里一些资源链接又怕被盗用(wordpress这些一站式往后稍稍),毕竟写着玩肯定比不过那些一站式的下载验证,只需PHP环境就行,不需要数据库下面直接上思路和下载地址,不说直接上链接和思路

项目链接https://github.com/JHPatchouli/PHPDownloadCheck

涉及以下知识

php:超文本预处理器(懂得处理json是关键)

json:一种轻量级的数据交换格式(用它代替了数据库)

思路 1

为了方便理解我将思路整理了一下,配合代码阅读更好

可能会有大量解释出现在代码块注释里

首先我们先要做一个很简单的验证,就是判断HTTP_REFERER是否存在,因为直接在浏览器地址栏上输入访问是不会带HTTP_REFERER的,那么什么情况下会有HTTP_REFERER存在,比较常用的就是a标签跳转(其他跳转方式不讲)

判断HTTP_REFERER前需要了解的 1.1

E_XO~G_NC6KP2`L@A4PB_D1.png

那么这个有什么弊端呢?我翻阅过网上不少资料,至少我没有看到有讲域名对HTTP_REFERER的影响的,那么这里我就讲一下顺便基于这张图再添加些东西方便理解

SOFAAAS_VFN2~Y@Y79__CLA.png

如果你并不打算将你的资源服务器放在你的跳转源同域或子域的时候HTTP_REFERER验证便没法实现

验证过程 1.2

所有代码都是一个文件,这里统一叫做 check.php方便后期做假设场景

我们先来看下面这段代码

1
2
3
4
5
6
7
8
9
10
11
12
$HTTP_REFERER = $_SERVER['HTTP_REFERER'];   //获取请求信息
if (isset($HTTP_REFERER)) { //第一次判断是否存在HTTP_REFERER
if (strstr($HTTP_REFERER,'mrhao')) { //第二次判断HTTP_REFERER是否存在关键字符‘mrhao’
echo 'ok'; //关键词就是你跳转前的链接关键词
}else{
echo 'no';
exit();
}
}else {
echo ('NO');
exit();
}

那么最开始也最简单的HTTP_REFERER判断就搞定了

拟定链接失效思路 1.3

下面内容可能会很多

首先让文件失效的办法我想到是改文件名字,那么就获取一些必要的参数,并还不急文件改名

1
2
3
4
5
6
7
8
9
10
11
12
13
   $time = time(); //获取时间戳
//很简单的获取时间戳函数
@$getfile = $_GET[file]; //获取参数file
//接收来着GET的请求里的file参数(这个参数其实就是文件全名的base64密文)
@$base64de = base64_decode($getfile); //解密file参数

@$dname = md5("$base64de.$getdlfile.$time"); //拼接解密的文件名+参数file+现在时间戳,并MD5加密留用
//这里我们将解出来的文件名(原名)+提交过来的密文+时间戳,然后MD5加密后得到一个32位的密文
$data = array('time' => "$time",'yname' => "$base64de",'dname' => "$dname"); //更新数组数据留用
//为了到时候方便储存我们整个数组出来,数组的参数有time(写入时间),yname(源名),dname(更新的名)
//这个dname其实就是加密后的MD5密文
$datajson = json_encode($data); //格式化数组数据为json
//将数组json格式化下次方便读取

拿到数据后可以开始处理了,现在我们看下方这个场景介绍

有一个叫www.zip的文件刚刚放进目录

然后我提交一个请求 http://xxx.xxx/check.php?file=d3d3LnppcA==

这里我们提交的数据是 file=d3d3LnppcA==

好,那么接下来我们拿到一个密文d3d3LnppcA==解析出来是www.zip那么我们获取新文件名,我们的文件名计算公式是MD5加密源文件名+收到的base64密文+时间戳

我们要将处理好的json数据储存到一个文件里,看下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
   $filecz = file_exists("./$base64de");   //file_exists函数判断请求文件是否存在
if ($filecz==true) { //首次上传文件修改
$fileput = file_put_contents ("./ZmlsZA/$base64de".'.php', "$datajson"); //写入数组到文件
//这个写入函数的需要文件名和写入内容两个参数,这里我需要讲一下文件名参数
//文件名后缀我用`.php`因为这样就无法被下载,但是要是有人直接打开看呢
//还是有办法的首先你需要新建一个文件夹这个文件夹是乱码最好
//这里我的路径是`./ZmlsZA/`这里只存放php后缀的json数据文件
$oldname = "/volume1/web/download/$base64de"; //旧名字(首次上传名)
$newname = "/volume1/web/download/$dname"; //新名字(自动根据时间戳生成)
//这里是文件路径,你可以选择直接新建一个文件夹或者直接在主目录
rename($oldname,$newname); //修改名字
//然后修改名字完成新文件第一次改名
}

这里需要对rename函数再多讲几句

Windows下使用rename函数可能没有太大问题相对路径也是可用的

而linux下就有些差别,相对路径我是无法使用的(群晖NAS),以外还有linux的文件权限问题,http用户组权限要有写入权限(写入json数据用)还有删除权限(改名大概算是删除上一个文件),网站目录的权限最好0770

读取数据 1.3.1

废话不多说直接上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$fileget = file_get_contents ("./ZmlsZA/$base64de".'.php');  //读取现存数据
//读取数据--每个文件都有自己的独立数据不会混淆
$datajsonde = json_decode($fileget); //解json现存数据
//解析json为数组
$filetime = $datajsonde->{'time'}; //提取现存数据时间戳

$filename = $datajsonde->{'dname'}; //提取现存文件识标

$timeadd = $filetime + 20; //设置过期时间
//过期时间就是文件时间+设置的过期时效得到过期时间戳
if ($filetime == null) { //如果获取不到现存数据直接停止
echo '验证下载失败,请联系管理员';
exit();
}
//这里是为了防止写入错误等一系列突发情况写的
if ($timeadd < $time) {
//这里是下载处理下面讲
}

文件过期处理改名处理已经搞定,接下来是文件下载处理

文件下载处理 1.4

直接上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if ($timeadd < $time) {     //如果过期就将上方的数组写入
//对比过期时间戳和现在时间
echo '获取链接中';
$oldname = "/volume1/web/owncloud/$filename"; //旧名字(现存数据读取)
$newname = "/volume1/web/owncloud/$dname.zip"; //新名字(自动根据时间戳生成)
rename($oldname,$newname); //重新命名文件以更新链接
$fileput = file_put_contents ("./ZmlsZA/$base64de".'.php', "$datajson"); //写入数据以便下次读取
//过期直接更新链接并保存数据进文件
//自动刷新以不动声色下载
//本来采用header("Refresh:0");
//但是由于有时候不知道为什么没反应,所以直接换成js的跳转,插入js代码
echo '...';
echo '<script>';
echo "window.location.href='check.php?file=$getfile'";
//js重新发送请求刷新界面,这并不会丢失HTTP_REFERER,刷新后就是在文件时限内了,就直接跳到下面的代码了
echo '</script>';
}else{
echo '可下载';
echo "<script>";
echo "window.location.href='$filename'";
//同样跳转到读取数据里保存的文件名
echo "</script>";
//header("Location: $dname.zip"); //若在时限内则可直接访问下载
}

那么这就是所有内容了


后言

其实这是个很简单的写法,而且验证的方式也很简单,并没有其他一站式应用这么完整的验证机制(如session和cookie这些判断包括一些专门的交付相应代币来下载)