88use AceOfAces \LaravelImageTransformUrl \Enums \AllowedOptions ;
99use AceOfAces \LaravelImageTransformUrl \Traits \ManagesImageCache ;
1010use AceOfAces \LaravelImageTransformUrl \Traits \ResolvesOptions ;
11+ use AceOfAces \LaravelImageTransformUrl \ValueObjects \ImageSource ;
12+ use Illuminate \Filesystem \FilesystemAdapter ;
1113use Illuminate \Http \Request ;
1214use Illuminate \Http \Response ;
1315use Illuminate \Support \Arr ;
1416use Illuminate \Support \Facades \App ;
1517use Illuminate \Support \Facades \Cache ;
1618use Illuminate \Support \Facades \File ;
1719use Illuminate \Support \Facades \RateLimiter ;
20+ use Illuminate \Support \Facades \Storage ;
1821use Illuminate \Support \Str ;
1922use Intervention \Image \Drivers \Gd \Encoders \WebpEncoder ;
2023use Intervention \Image \Encoders \AutoEncoder ;
@@ -48,7 +51,7 @@ public function transformDefault(Request $request, string $options, string $path
4851 */
4952 protected function handleTransform (Request $ request , ?string $ pathPrefix , string $ options , ?string $ path = null ): Response
5053 {
51- $ realPath = $ this ->handlePath ($ pathPrefix , $ path );
54+ $ source = $ this ->handlePath ($ pathPrefix , $ path );
5255
5356 $ options = $ this ->parseOptions ($ options );
5457
@@ -75,7 +78,10 @@ protected function handleTransform(Request $request, ?string $pathPrefix, string
7578 $ this ->rateLimit ($ request , $ path );
7679 }
7780
78- $ image = Image::read ($ realPath );
81+ $ image = match ($ source ->type ) {
82+ 'disk ' => Image::read (Storage::disk ($ source ->disk )->get ($ source ->path )),
83+ default => Image::read ($ source ->path ),
84+ };
7985
8086 if (Arr::hasAny ($ options , ['width ' , 'height ' ])) {
8187 $ image ->scale (
@@ -116,7 +122,7 @@ protected function handleTransform(Request $request, ?string $pathPrefix, string
116122
117123 }
118124
119- $ originalMimetype = File:: mimeType ( $ realPath ) ;
125+ $ originalMimetype = $ source -> mime ;
120126
121127 $ format = $ this ->getStringOptionValue ($ options , 'format ' , $ originalMimetype );
122128 $ quality = $ this ->getPositiveIntOptionValue ($ options , 'quality ' , 100 , 100 );
@@ -150,7 +156,7 @@ protected function handleTransform(Request $request, ?string $pathPrefix, string
150156 *
151157 * @param-out string $pathPrefix
152158 */
153- protected function handlePath (?string &$ pathPrefix , ?string &$ path ): string
159+ protected function handlePath (?string &$ pathPrefix , ?string &$ path ): ImageSource
154160 {
155161 if ($ path === null ) {
156162 $ path = $ pathPrefix ;
@@ -164,8 +170,38 @@ protected function handlePath(?string &$pathPrefix, ?string &$path): string
164170
165171 abort_unless (array_key_exists ($ pathPrefix , $ allowedSourceDirectories ), 404 );
166172
167- $ basePath = $ allowedSourceDirectories [$ pathPrefix ];
168- $ requestedPath = $ basePath .'/ ' .$ path ;
173+ $ base = $ allowedSourceDirectories [$ pathPrefix ];
174+
175+ // Handle disk-based source directories
176+ if (is_array ($ base ) && array_key_exists ('disk ' , $ base )) {
177+
178+ $ disk = (string ) $ base ['disk ' ];
179+ $ prefix = isset ($ base ['prefix ' ]) ? trim ((string ) $ base ['prefix ' ], '/ ' ) : '' ;
180+
181+ $ normalized = $ this ->normalizeRelativePath ($ path );
182+ abort_unless (! is_null ($ normalized ), 404 );
183+
184+ $ diskPath = trim ($ prefix !== '' ? $ prefix .'/ ' .$ normalized : $ normalized , '/ ' );
185+
186+ abort_unless (Storage::disk ($ disk )->exists ($ diskPath ), 404 );
187+
188+ /** @var FilesystemAdapter $diskAdapter */
189+ $ diskAdapter = Storage::disk ($ disk );
190+ $ mime = $ diskAdapter ->mimeType ($ diskPath );
191+
192+ abort_unless (in_array ($ mime , AllowedMimeTypes::all (), true ), 404 );
193+
194+ return new ImageSource (
195+ type: 'disk ' ,
196+ path: $ diskPath ,
197+ mime: $ mime ,
198+ disk: $ disk ,
199+ );
200+ }
201+
202+ // Handle local filesystem paths
203+ $ basePath = (string ) $ base ;
204+ $ requestedPath = rtrim ($ basePath , '/ ' ).'/ ' .$ path ;
169205 $ realPath = realpath ($ requestedPath );
170206
171207 abort_unless ($ realPath , 404 );
@@ -177,7 +213,39 @@ protected function handlePath(?string &$pathPrefix, ?string &$path): string
177213
178214 abort_unless (in_array (File::mimeType ($ realPath ), AllowedMimeTypes::all (), true ), 404 );
179215
180- return $ realPath ;
216+ return new ImageSource (
217+ type: 'local ' ,
218+ path: $ realPath ,
219+ mime: (string ) File::mimeType ($ realPath ),
220+ );
221+ }
222+
223+ /**
224+ * Normalize a relative path by resolving `.` and `..` segments.
225+ * Returns null if the path escapes above the root.
226+ */
227+ protected function normalizeRelativePath (string $ path ): ?string
228+ {
229+ $ path = str_replace ('\\' , '/ ' , $ path );
230+ $ segments = array_filter (explode ('/ ' , $ path ), fn ($ s ) => $ s !== '' );
231+ $ stack = [];
232+
233+ foreach ($ segments as $ segment ) {
234+ if ($ segment === '. ' ) {
235+ continue ;
236+ }
237+ if ($ segment === '.. ' ) {
238+ if (empty ($ stack )) {
239+ return null ;
240+ }
241+ array_pop ($ stack );
242+
243+ continue ;
244+ }
245+ $ stack [] = $ segment ;
246+ }
247+
248+ return implode ('/ ' , $ stack );
181249 }
182250
183251 /**
0 commit comments