Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
Chunk.php
1<?php
2
4
11
12class Chunk
13{
14 protected int $size = 0;
15 protected ?int $fileSize = null;
16 protected ?int $startRange = null;
17 protected ?int $endRange = null;
18 protected string $type = '';
19 protected string $name = '';
20 protected int $width = 0;
21 protected int $height = 0;
22
23 protected IO\File $file;
24
25 protected function __construct(IO\File $file)
26 {
27 $this->setFile($file);
28 }
29
30 public static function createFromRequest(HttpRequest $request): Result
31 {
32 $result = new Result();
33
34 $fileMimeType = (string)$request->getHeader('Content-Type');
35 if (!preg_match('~\w+/[-+.\w]+~', $fileMimeType))
36 {
37 return $result->addError(new UploaderError(UploaderError::INVALID_CONTENT_TYPE));
38 }
39
40 $contentLength = $request->getHeader('Content-Length');
41 if ($contentLength === null)
42 {
43 return $result->addError(new UploaderError(UploaderError::INVALID_CONTENT_LENGTH));
44 }
45
46 $contentLength = (int)$contentLength;
47 $filename = static::normalizeFilename((string)$request->getHeader('X-Upload-Content-Name'));
48 if (empty($filename))
49 {
50 return $result->addError(new UploaderError(UploaderError::INVALID_CONTENT_NAME));
51 }
52
53 if (!static::isValidFilename($filename))
54 {
55 return $result->addError(new UploaderError(UploaderError::INVALID_FILENAME));
56 }
57
58 $contentRangeResult = static::getContentRange($request);
59 if (!$contentRangeResult->isSuccess())
60 {
61 return $result->addErrors($contentRangeResult->getErrors());
62 }
63
64 $file = static::getFileFromHttpInput();
65 $contentRange = $contentRangeResult->getData();
66 $rangeChunkSize = empty($contentRange) ? 0 : ($contentRange['endRange'] - $contentRange['startRange'] + 1);
67
68 if ($rangeChunkSize && $contentLength !== $rangeChunkSize)
69 {
70 return $result->addError(new UploaderError(
72 [
73 'rangeChunkSize' => $rangeChunkSize,
74 'contentLength' => $contentLength,
75 ]
76 ));
77 }
78
79 $chunk = new Chunk($file);
80 if ($chunk->getSize() !== $contentLength)
81 {
82 return $result->addError(new UploaderError(
84 [
85 'chunkSize' => $chunk->getSize(),
86 'contentLength' => $contentLength,
87 ]
88 ));
89 }
90
91 $chunk->setName($filename);
92 $chunk->setType($fileMimeType);
93
94 if (!empty($contentRange))
95 {
96 $chunk->setStartRange($contentRange['startRange']);
97 $chunk->setEndRange($contentRange['endRange']);
98 $chunk->setFileSize($contentRange['fileSize']);
99 }
100
101 $result->setData(['chunk' => $chunk]);
102
103 return $result;
104 }
105
106 private static function getFileFromHttpInput(): IO\File
107 {
108 // This file will be automatically removed on shutdown
109 $tmpFilePath = TempFile::generateLocalTempFile();
110 $file = new IO\File($tmpFilePath);
111 $file->putContents(HttpRequest::getInput());
112
113 return $file;
114 }
115
116 private static function getContentRange(HttpRequest $request): Result
117 {
118 $contentRange = $request->getHeader('Content-Range');
119 if ($contentRange === null)
120 {
121 return new Result();
122 }
123
124 $result = new Result();
125 if (!preg_match('/(\d+)-(\d+)\/(\d+)$/', $contentRange, $match))
126 {
127 return $result->addError(new UploaderError(UploaderError::INVALID_CONTENT_RANGE));
128 }
129
130 [$startRange, $endRange, $fileSize] = [(int)$match[1], (int)$match[2], (int)$match[3]];
131
133 {
134 return $result->addError(new UploaderError(UploaderError::INVALID_CONTENT_RANGE));
135 }
136
137 if ($fileSize <= $endRange)
138 {
139 return $result->addError(new UploaderError(UploaderError::INVALID_CONTENT_RANGE));
140 }
141
142 $result->setData([
143 'startRange' => $startRange,
144 'endRange' => $endRange,
145 'fileSize' => $fileSize,
146 ]);
147
148 return $result;
149 }
150
151 private static function normalizeFilename(string $filename): string
152 {
153 $filename = urldecode($filename);
154 $filename = Encoding::convertEncodingToCurrent($filename);
155
156 return \getFileName($filename);
157 }
158
159 private static function isValidFilename(string $filename): bool
160 {
161 if (mb_strlen($filename) > 255)
162 {
163 return false;
164 }
165
166 if (mb_strpos($filename, '\0') !== false)
167 {
168 return false;
169 }
170
171 return true;
172 }
173
174 public function getFile(): IO\File
175 {
176 return $this->file;
177 }
178
182 public function setFile(IO\File $file): void
183 {
184 $this->file = $file;
185 $this->size = $file->getSize();
186 }
187
188 public function getSize(): int
189 {
190 return $this->size;
191 }
192
193 public function getFileSize(): int
194 {
195 return $this->fileSize ?? $this->size;
196 }
197
198 protected function setFileSize(int $fileSize): void
199 {
200 $this->fileSize = $fileSize;
201 }
202
203 public function getType(): string
204 {
205 return $this->type;
206 }
207
208 public function setType(string $type): void
209 {
210 $this->type = $type;
211 }
212
213 public function getName(): string
214 {
215 return $this->name;
216 }
217
218 public function setName(string $name): void
219 {
220 $this->name = $name;
221 }
222
223 public function getWidth(): int
224 {
225 return $this->width;
226 }
227
228 public function setWidth(int $width): void
229 {
230 $this->width = $width;
231 }
232
233 public function getHeight(): int
234 {
235 return $this->height;
236 }
237
238 public function setHeight(int $height): void
239 {
240 $this->height = $height;
241 }
242
243 public function getStartRange(): ?int
244 {
245 return $this->startRange;
246 }
247
248 protected function setStartRange(int $startRange): void
249 {
250 $this->startRange = $startRange;
251 }
252
253 public function getEndRange(): ?int
254 {
255 return $this->endRange;
256 }
257
258 protected function setEndRange(int $endRange): void
259 {
260 $this->endRange = $endRange;
261 }
262
263 public function isFirst(): bool
264 {
265 return $this->startRange === null || $this->startRange === 0;
266 }
267
268 public function isLast(): bool
269 {
270 return $this->endRange === null || ($this->endRange + 1) === $this->fileSize;
271 }
272
273 public function isOnlyOne(): bool
274 {
275 return (
276 $this->startRange === null
277 || ($this->startRange === 0 && ($this->endRange - $this->startRange + 1) === $this->fileSize)
278 );
279 }
280
281 public function validate(Configuration $config): Result
282 {
283 $result = new Result();
284
285 if (in_array(mb_strtolower($this->getName()), $config->getIgnoredFileNames()))
286 {
287 return $result->addError(new UploaderError(UploaderError::FILE_NAME_NOT_ALLOWED));
288 }
289
290 if ($config->getMaxFileSize() !== null && $this->getFileSize() > $config->getMaxFileSize())
291 {
292 return $result->addError(
293 new UploaderError(
295 [
296 'maxFileSize' => \CFile::formatSize($config->getMaxFileSize()),
297 'maxFileSizeInBytes' => $config->getMaxFileSize(),
298 ]
299 )
300 );
301 }
302
303 if ($this->getFileSize() < $config->getMinFileSize())
304 {
305 return $result->addError(
306 new UploaderError(
308 [
309 'minFileSize' => \CFile::formatSize($config->getMinFileSize()),
310 'minFileSizeInBytes' => $config->getMinFileSize(),
311 ]
312 )
313 );
314 }
315
316 if (!$this->validateFileType($config->getAcceptedFileTypes()))
317 {
318 return $result->addError(new UploaderError(UploaderError::FILE_TYPE_NOT_ALLOWED));
319 }
320
321 $width = 0;
322 $height = 0;
323 if (\CFile::isImage($this->getName(), $this->getType()))
324 {
325 $image = new File\Image($this->getFile()->getPhysicalPath());
326 $imageInfo = $image->getInfo(false);
327 if (!$imageInfo)
328 {
329 if ($config->getIgnoreUnknownImageTypes())
330 {
331 $result->setData(['width' => $width, 'height' => $height]);
332
333 return $result;
334 }
335 else
336 {
337 return $result->addError(new UploaderError(UploaderError::IMAGE_TYPE_NOT_SUPPORTED));
338 }
339 }
340
341 $width = $imageInfo->getWidth();
342 $height = $imageInfo->getHeight();
343 if ($imageInfo->getFormat() === File\Image::FORMAT_JPEG)
344 {
345 $exifData = $image->getExifData();
346 if (isset($exifData['Orientation']) && $exifData['Orientation'] >= 5 && $exifData['Orientation'] <= 8)
347 {
349 }
350 }
351
352 if (!$config->shouldTreatOversizeImageAsFile())
353 {
354 $imageData = new FileData($this->getName(), $this->getType(), $this->getSize());
355 $imageData->setWidth($width);
356 $imageData->setHeight($height);
357
358 $validationResult = $config->validateImage($imageData);
359 if (!$validationResult->isSuccess())
360 {
361 return $result->addErrors($validationResult->getErrors());
362 }
363 }
364 }
365
366 $result->setData(['width' => $width, 'height' => $height]);
367
368 return $result;
369 }
370
371 private function validateFileType(array $fileTypes): bool
372 {
373 if (count($fileTypes) === 0)
374 {
375 return true;
376 }
377
378 $mimeType = $this->getType();
379 $baseMimeType = preg_replace('/\/.*$/', '', $mimeType);
380
381 foreach ($fileTypes as $type)
382 {
383 if (!is_string($type) || mb_strlen($type) === 0)
384 {
385 continue;
386 }
387
388 $type = mb_strtolower(trim($type));
389 if ($type[0] === '.') // extension case
390 {
391 $filename = mb_strtolower($this->getName());
392 $offset = mb_strlen($filename) - mb_strlen($type);
393 if (mb_strpos($filename, $type, $offset) !== false)
394 {
395 return true;
396 }
397 }
398 elseif (preg_match('/\/\*$/', $type)) // image/* mime type case
399 {
400 if ($baseMimeType === preg_replace('/\/.*$/', '', $type))
401 {
402 return true;
403 }
404 }
405 elseif ($mimeType === $type)
406 {
407 return true;
408 }
409 }
410
411 return false;
412 }
413}
static createFromRequest(HttpRequest $request)
Definition Chunk.php:30
__construct(IO\File $file)
Definition Chunk.php:25
setStartRange(int $startRange)
Definition Chunk.php:248
validate(Configuration $config)
Definition Chunk.php:281
setFileSize(int $fileSize)
Definition Chunk.php:198
setFile(IO\File $file)
Definition Chunk.php:182
setEndRange(int $endRange)
Definition Chunk.php:258