1
1
<?php
2
2
3
3
class ImageUploader {
4
- private const ALLOWED_TYPES = [" image/ gif" , " image/ jpeg" , " image/ jpg" , " image/pjpeg " , " image/x- png" , " image/png " ];
4
+ private const ALLOWED_EXTENSIONS = [' gif ' , ' jpeg ' , ' jpg ' , ' png ' ];
5
5
private const MAX_SIZE = 5 * 1024 * 1024 ; // 5MB
6
+ private const MAX_RESOLUTION = 25 * 1024 * 1024 ; // 25MB
6
7
private const DOMAINS = ['img.pub ' , 'pic.ym.today ' ];
7
8
private const UPLOAD_URL = 'https://telegra.ph/upload ' ;
8
9
10
+ // 文件头部的魔术数字
11
+ private const MAGIC_NUMBERS = [
12
+ 'image/jpeg ' => "\xFF\xD8\xFF" ,
13
+ 'image/png ' => "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A" ,
14
+ 'image/gif ' => "GIF "
15
+ ];
16
+
9
17
public function upload (): void {
10
18
try {
11
19
$ file = $ this ->validateFile ($ _FILES ['file ' ] ?? null );
@@ -22,19 +30,34 @@ public function upload(): void {
22
30
}
23
31
24
32
private function validateFile ($ file ): array {
25
- if (!$ file ) {
33
+ if (!$ file || ! isset ( $ file [ ' tmp_name ' ]) || ! is_uploaded_file ( $ file [ ' tmp_name ' ]) ) {
26
34
throw new Exception ("没有上传文件! " );
27
35
}
28
- if (!in_array ($ file ['type ' ], self ::ALLOWED_TYPES )) {
29
- throw new Exception ("只允许上传gif、jpeg、jpg、png格式的图片文件! " );
36
+
37
+ $ finfo = new finfo (FILEINFO_MIME_TYPE );
38
+ $ mime = $ finfo ->file ($ file ['tmp_name ' ]);
39
+ $ fileExtension = pathinfo ($ file ['name ' ], PATHINFO_EXTENSION );
40
+
41
+ if (!in_array (strtolower ($ fileExtension ), self ::ALLOWED_EXTENSIONS ) || !in_array ($ mime , ['image/gif ' , 'image/jpeg ' , 'image/png ' ])) {
42
+ throw new Exception ("只允许上传 gif、jpeg、jpg、png 格式的图片文件! " );
43
+ }
44
+
45
+ if (!$ this ->validateMagicNumber ($ file ['tmp_name ' ], $ mime )) {
46
+ throw new Exception ("文件的魔术数字与宣称的类型不匹配! " );
30
47
}
48
+
31
49
return $ file ;
32
50
}
33
51
34
52
private function checkSizeAndCompress (array $ file ): array {
53
+ // 验证文件大小是否超出限制
54
+ if ($ file ['size ' ] > self ::MAX_RESOLUTION ) {
55
+ throw new Exception ("图片分辨率超过最大限制! " );
56
+ }
57
+
35
58
if ($ file ['size ' ] > self ::MAX_SIZE ) {
36
59
if ($ file ['type ' ] === 'image/gif ' ) {
37
- throw new Exception ("GIF文件超过5MB ,无法上传! " );
60
+ throw new Exception ("GIF 文件超过5MB ,无法上传! " );
38
61
}
39
62
return $ this ->compressImage ($ file );
40
63
}
@@ -57,20 +80,28 @@ private function compressImage(array $image): array {
57
80
throw new Exception ("图片压缩失败或压缩后仍超过最大限制! " );
58
81
}
59
82
60
- return ['name ' => $ image [ ' name ' ] , 'type ' => 'image/jpeg ' , 'tmp_name ' => $ tempFile , 'error ' => 0 , 'size ' => $ compressedSize ];
83
+ return ['name ' => uniqid ( '' , true ) . ' .jpg ' , 'type ' => 'image/jpeg ' , 'tmp_name ' => $ tempFile , 'error ' => 0 , 'size ' => $ compressedSize ];
61
84
}
62
85
63
86
private function uploadToServer (array $ file ): ?string {
87
+ // 生成安全的文件名,使用 UUID
88
+ $ safeFileName = $ file ['name ' ];
89
+
64
90
$ ch = curl_init ();
65
91
curl_setopt ($ ch , CURLOPT_URL , self ::UPLOAD_URL );
66
92
curl_setopt ($ ch , CURLOPT_POST , true );
67
- curl_setopt ($ ch , CURLOPT_POSTFIELDS , ['file ' => new CURLFile ($ file ['tmp_name ' ], $ file ['type ' ], $ file [ ' name ' ] )]);
93
+ curl_setopt ($ ch , CURLOPT_POSTFIELDS , ['file ' => new CURLFile ($ file ['tmp_name ' ], $ file ['type ' ], $ safeFileName )]);
68
94
curl_setopt ($ ch , CURLOPT_RETURNTRANSFER , true );
69
95
$ response = curl_exec ($ ch );
96
+ $ httpStatus = curl_getinfo ($ ch , CURLINFO_HTTP_CODE );
70
97
curl_close ($ ch );
71
98
72
99
unlink ($ file ['tmp_name ' ]); // 删除上传后的临时文件
73
100
101
+ if ($ httpStatus !== 200 ) {
102
+ throw new Exception ("远程服务器返回错误:HTTP $ httpStatus " );
103
+ }
104
+
74
105
$ json = json_decode ($ response , true );
75
106
if ($ json === null || !isset ($ json [0 ]['src ' ])) {
76
107
return null ;
@@ -79,6 +110,20 @@ private function uploadToServer(array $file): ?string {
79
110
return $ json [0 ]['src ' ];
80
111
}
81
112
113
+ private function validateMagicNumber (string $ filePath , string $ fileType ): bool {
114
+ // 获取文件头部的前几个字节
115
+ $ handle = fopen ($ filePath , 'rb ' );
116
+ $ fileSignature = fread ($ handle , 4 );
117
+ fclose ($ handle );
118
+
119
+ // 检查文件的魔术数字是否与宣称的类型匹配
120
+ if (isset (self ::MAGIC_NUMBERS [$ fileType ])) {
121
+ return strpos ($ fileSignature , self ::MAGIC_NUMBERS [$ fileType ]) === 0 ;
122
+ }
123
+
124
+ return false ;
125
+ }
126
+
82
127
private function outputResult (array $ result ): void {
83
128
header ("Content-type: application/json " );
84
129
echo json_encode ($ result );
0 commit comments