Files
zyt/server/app/common/service/TongueScreenshotImage.php
2026-11-03 17:20:35 +08:00

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 = '';
}
}
}