70 lines
2.7 KiB
PHP
70 lines
2.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace app\common\service;
|
|
|
|
use think\file\UploadedFile;
|
|
|
|
/** Request-owned JPEG only. No URL, original filename or caller-supplied path is accepted. */
|
|
final class TongueScreenshotImage
|
|
{
|
|
public const MAX_BYTES = 10 * 1024 * 1024;
|
|
private string $path;
|
|
private string $sha256;
|
|
|
|
private function __construct(string $path, string $sha256)
|
|
{
|
|
$this->path = $path;
|
|
$this->sha256 = $sha256;
|
|
}
|
|
|
|
public static function fromUpload($file): self
|
|
{
|
|
if (!$file instanceof UploadedFile || !$file->isValid()) {
|
|
throw new \DomainException('请上传有效的 JPEG 视频截图');
|
|
}
|
|
$path = $file->getRealPath();
|
|
if (!is_string($path) || is_link($file->getPathname()) || !is_file($path) || !is_readable($path)) {
|
|
throw new \DomainException('截图无法读取,请重新截图');
|
|
}
|
|
$bytes = @filesize($path);
|
|
if ($bytes === false || $bytes < 4 || $bytes > self::MAX_BYTES) {
|
|
throw new \DomainException('截图不能为空且不能超过 10 MiB');
|
|
}
|
|
$tail = @file_get_contents($path, false, null, $bytes - 2, 2);
|
|
if ($tail !== "\xFF\xD9") throw new \DomainException('截图内容损坏,请重新截图');
|
|
$dimensions = @getimagesize($path);
|
|
if (!is_array($dimensions) || ($dimensions[2] ?? null) !== IMAGETYPE_JPEG
|
|
|| @(new \finfo(FILEINFO_MIME_TYPE))->file($path) !== 'image/jpeg') {
|
|
throw new \DomainException('仅支持真实的 JPEG 视频截图');
|
|
}
|
|
[$width, $height] = $dimensions;
|
|
// Check before decoding, bounding memory even for a malicious JPEG header.
|
|
if ($width < 64 || $height < 64 || $width > 8192 || $height > 8192 || $width * $height > 16777216) {
|
|
throw new \DomainException('截图尺寸无效,请重新截图');
|
|
}
|
|
if (!function_exists('imagecreatefromjpeg')) {
|
|
throw new \DomainException('截图校验服务暂不可用');
|
|
}
|
|
$decoded = @imagecreatefromjpeg($path);
|
|
if ($decoded === false) throw new \DomainException('截图内容损坏,请重新截图');
|
|
imagedestroy($decoded);
|
|
$sha256 = @hash_file('sha256', $path);
|
|
if (!is_string($sha256)) throw new \DomainException('截图无法读取,请重新截图');
|
|
return new self($path, $sha256);
|
|
}
|
|
|
|
public function path(): string { return $this->path; }
|
|
public function sha256(): string { return $this->sha256; }
|
|
|
|
/** PHP also removes its upload on request shutdown, including pre-validation failures. */
|
|
public function release(): void
|
|
{
|
|
if ($this->path !== '') {
|
|
@unlink($this->path);
|
|
$this->path = '';
|
|
}
|
|
}
|
|
}
|