文件
2026-08-16 17:03:10 +08:00

200 行
6.8 KiB
PHP

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
<?php
if (!defined('IN_APP')) {
http_response_code(403);
exit('Forbidden');
}
function validateUploadedImage($file, array $opts = [])
{
$opts = array_merge([
'allowed' => ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/x-icon', 'image/vnd.microsoft.icon'],
'maxBytes' => 2 * 1024 * 1024,
'minDim' => 0,
], $opts);
if (!isset($file['error']) || $file['error'] !== UPLOAD_ERR_OK) {
return ['ok' => false, 'error' => '上传错误,请重试'];
}
$tmp = $file['tmp_name'];
if (!is_uploaded_file($tmp)) {
return ['ok' => false, 'error' => '非法上传来源'];
}
$info = @getimagesize($tmp);
if (!$info || empty($info['mime'])) {
return ['ok' => false, 'error' => '不是有效的图片文件'];
}
$mime = $info['mime'];
$exifType = false;
if (function_exists('exif_imagetype')) {
$exifType = @exif_imagetype($tmp);
}
$extMap = [
IMAGETYPE_JPEG => 'jpg',
IMAGETYPE_PNG => 'png',
IMAGETYPE_GIF => 'gif',
IMAGETYPE_WEBP => 'webp',
IMAGETYPE_ICO => 'ico',
];
$ext = $extMap[$exifType ?: ($info[2] ?? 0)] ?? '';
if ($ext === '' || !in_array($mime, $opts['allowed'], true)) {
return ['ok' => false, 'error' => '不支持的图片格式(' . h($mime) . ''];
}
if ($opts['minDim'] > 0 && ($info[0] < $opts['minDim'] || $info[1] < $opts['minDim'])) {
return ['ok' => false, 'error' => '图片尺寸不能小于 ' . $opts['minDim'] . '×' . $opts['minDim'] . ' 像素'];
}
if ((int) $file['size'] > (int) $opts['maxBytes']) {
return ['ok' => false, 'error' => '文件过大(上限 ' . round($opts['maxBytes'] / 1024 / 1024, 1) . 'MB'];
}
$head = file_get_contents($tmp, false, null, 0, 8192);
if ($head !== false && preg_match('/<\\?php|<\\?=|\\?>\s*
return ['ok' => false, 'error' => '文件内容检测到不安全代码,已拒绝'];
}
return ['ok' => true, 'mime' => $mime, 'ext' => $ext, 'type' => $exifType ?: ($info[2] ?? 0), 'error' => ''];
}
function reencodeImage($tmp, $ext, $dest)
{
$dir = dirname($dest);
if (!is_dir($dir)) {
if (!@mkdir($dir, 0755, true) && !is_dir($dir)) {
return false;
}
}
if (!is_writable($dir)) {
return false;
}
if ($ext === 'ico') {
return @copy($tmp, $dest) && is_file($dest);
}
if (!extension_loaded('gd')) {
return @copy($tmp, $dest) && is_file($dest);
}
$src = null;
switch ($ext) {
case 'jpg': $src = @imagecreatefromjpeg($tmp); break;
case 'png': $src = @imagecreatefrompng($tmp); break;
case 'gif': $src = @imagecreatefromgif($tmp); break;
case 'webp': $src = @imagecreatefromwebp($tmp); break;
}
if (!$src) {
return @copy($tmp, $dest) && is_file($dest);
}
$ok = false;
if ($ext === 'png') $ok = @imagepng($src, $dest, 9);
elseif ($ext === 'gif') $ok = @imagegif($src, $dest);
elseif ($ext === 'webp') $ok = @imagewebp($src, $dest, 90);
else $ok = @imagejpeg($src, $dest, 90);
@imagedestroy($src);
return $ok && is_file($dest);
}
function cleanInput($str)
{
$str = (string) $str;
$str = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $str);
$str = preg_replace('
$str = preg_replace('
$str = preg_replace('
return trim($str);
}
function isBannedUsername($name)
{
$name = strtolower(trim((string) $name));
if ($name === '') {
return true;
}
$banned = [
'null', 'nil', 'undefined', 'nan', 'admin', 'root', 'administrator',
'superuser', 'system', 'guest', 'support', 'webmaster', 'postmaster',
'nobody', 'www', 'ftp', 'mail', 'root', 'sudo', 'owner', 'moderator',
];
if (in_array($name, $banned, true)) {
return true;
}
if (preg_match('/^\d+$/', $name) || mb_strlen($name, 'UTF-8') < 2) {
return true;
}
return false;
}
function mailRateLimit($email)
{
$email = strtolower(trim((string) $email));
if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
return ['allowed' => true, 'wait' => 0];
}
$file = __DIR__ . '/../storage/cache/mail_throttle.json';
$now = time();
$data = is_file($file) ? (json_decode(@file_get_contents($file), true) ?: []) : [];
foreach ($data as $k => $t) {
if ($now - (int) $t >= 300) {
unset($data[$k]);
}
}
if (isset($data[$email]) && $now - (int) $data[$email] < 300) {
return ['allowed' => false, 'wait' => 300 - ($now - (int) $data[$email])];
}
$data[$email] = $now;
@file_put_contents($file, json_encode($data));
return ['allowed' => true, 'wait' => 0];
}
function escLike($v)
{
return str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], (string) $v);
}
function sqlInjectionGuard()
{
$patterns = [
'/\bUNION\b\s+\bSELECT\b/i',
'/\bOR\b\s+\d+\s*=\s*\d+/i',
'/\bAND\b\s+\d+\s*=\s*\d+/i',
'/--\s*$|
'/\bSLEEP\s*\(\s*\d+\s*\)/i',
'/\bBENCHMARK\s*\(/i',
'/\bINFORMATION_SCHEMA\b/i',
'/\bxp_cmdshell\b/i',
'/\bDROP\s+TABLE\b|\bTRUNCATE\b/i',
];
foreach (['_GET', '_COOKIE'] as $k) {
if (!isset($$k) || !is_array($$k)) {
continue;
}
foreach ($$k as $key => $val) {
if (!is_string($val)) {
continue;
}
foreach ($patterns as $p) {
if (preg_match($p, $val)) {
if (function_exists('Logger')) {
Logger::warn('疑似 SQL 注入请求被拦截', [
'ip' => getClientIp(),
'key' => $key,
'val' => mb_substr($val, 0, 200, 'UTF-8'),
]);
}
http_response_code(403);
exit('Access Denied');
}
}
}
}
}
function antiCrawlerGuard()
{
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
$l = strtolower($ua);
if (preg_match('/(googlebot|bingbot|baiduspider|yandexbot|duckduckbot|slurp|sogou|applebot|bytespider|twitterbot|facebookexternalhit)/i', $ua)) {
return;
}
$blockUa = [
'scrapy', 'python-requests', 'curl/', 'wget', 'libwww', 'nikto', 'sqlmap',
'masscan', 'nmap', 'zgrab', 'go-http-client', 'java/', 'httpclient',
'okhttp', 'phantomjs', 'headlesschrome', 'semrushbot', 'ahrefsbot', 'mj12bot',
];
foreach ($blockUa as $b) {
if (strpos($l, $b) !== false) {
http_response_code(403);
exit('Blocked by anti-crawler policy');
}
}
$script = $_SERVER['SCRIPT_NAME'] ?? '';
if (strpos($script, '/admin/') !== false || basename($script) === 'api.php') {
header('X-Robots-Tag: noindex, nofollow');
}
}