Skip to content

Commit 37b0780

Browse files
authored
ImageStorage: Use strict policy.
1 parent cc3dbbd commit 37b0780

File tree

1 file changed

+38
-3
lines changed

1 file changed

+38
-3
lines changed

src/ImageStorage.php

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
final class ImageStorage
1414
{
15+
public const IMAGE_MIME_TYPES = ['image/gif', 'image/png', 'image/jpeg', 'image/webp'];
16+
1517
private string $storagePath;
1618

1719
private string $relativeStoragePath;
@@ -33,36 +35,65 @@ public function __construct(?string $storagePath = null, string $relativeStorage
3335
}
3436

3537

38+
/**
39+
* The method downloads the image from the physical URL and saves it to the internal storage.
40+
* The download is checked to ensure that it is a valid data type for the image.
41+
*/
3642
public function save(string $url): void
3743
{
3844
if (Validators::isUrl($url) === false) {
3945
throw new \InvalidArgumentException('Given input is not valid absolute URL, because "' . $url . '" given.');
4046
}
4147
$storagePath = $this->getInternalPath($url);
42-
if (\is_file($storagePath) === false) {
43-
FileSystem::copy($url, $storagePath);
48+
if (is_file($storagePath) === false) { // image does not exist in local storage
49+
$content = FileSystem::read($url); // download image
50+
$contentType = strtolower(finfo_file(finfo_open(FILEINFO_MIME_TYPE), $content));
51+
if (in_array($contentType, self::IMAGE_MIME_TYPES, true) === false) {
52+
throw new \RuntimeException(
53+
'Security issue: Downloaded file "' . $url . '" is not valid image, '
54+
. 'because content type "' . $contentType . '" has been detected.',
55+
);
56+
}
57+
FileSystem::write($storagePath, $content);
4458
}
4559
}
4660

4761

62+
/**
63+
* Returns the absolute path for the internal data store.
64+
* Specifies the absolute physical disk path where the image will be written to (or read from) based on the URL.
65+
* The disk path is calculated by a deterministic algorithm for future content readability.
66+
*/
4867
public function getInternalPath(string $url): string
4968
{
5069
return $this->storagePath . '/' . $this->getRelativeInternalUrl($url);
5170
}
5271

5372

73+
/**
74+
* Returns the physical URL to download the image directly to local disk storage.
75+
*/
5476
public function getAbsoluteInternalUrl(string $url): string
5577
{
5678
try {
5779
$baseUrl = Url::get()->getBaseUrl();
5880
} catch (\Throwable) {
81+
if (PHP_SAPI === 'cli') {
82+
throw new \LogicException(
83+
__METHOD__ . ': Absolute URL is not available in CLI. '
84+
. 'Did you set context URL to "' . Url::class . '" service?',
85+
);
86+
}
5987
$baseUrl = '';
6088
}
6189

6290
return $baseUrl . '/' . $this->relativeStoragePath . '/' . $this->getRelativeInternalUrl($url);
6391
}
6492

6593

94+
/**
95+
* Returns the relative URL to retrieve the image.
96+
*/
6697
public function getRelativeInternalUrl(string $url): string
6798
{
6899
$originalFileName = (string) preg_replace_callback(
@@ -76,10 +107,14 @@ public function getRelativeInternalUrl(string $url): string
76107
}
77108

78109

110+
/**
111+
* Generate an automatic unique hash based on the image URL
112+
* so that file names from many different sources cannot collide.
113+
*/
79114
private function resolvePrefixDir(string $url): string
80115
{
81116
if ($url === '') {
82-
throw new \InvalidArgumentException('URL can not be empty string.');
117+
throw new \LogicException('URL can not be empty string.');
83118
}
84119
if (preg_match('/wp-content.+(\d{4})\/(\d{2})/', $url, $urlParser)) {
85120
return $urlParser[1] . '-' . $urlParser[2];

0 commit comments

Comments
 (0)