@@ -266,6 +266,91 @@ public function testIsImage(): void
266266 $ this ->assertTrue ($ this ->validation ->run ([]));
267267 }
268268
269+ public function testIsImageFailsForMismatchedClientExtension (): void
270+ {
271+ $ payload = $ this ->createGifPayload ();
272+
273+ try {
274+ $ this ->setUploadedAvatar ($ payload , 'shell.php ' );
275+
276+ $ this ->validation ->setRules (['avatar ' => 'is_image[avatar] ' ]);
277+ $ this ->assertFalse ($ this ->validation ->run ([]));
278+ } finally {
279+ unlink ($ payload );
280+ }
281+ }
282+
283+ public function testIsImageFailsForNonImageClientExtension (): void
284+ {
285+ $ payload = $ this ->createGifPayload ();
286+
287+ try {
288+ $ this ->setUploadedAvatar ($ payload , 'document.pdf ' );
289+
290+ $ this ->validation ->setRules (['avatar ' => 'is_image[avatar] ' ]);
291+ $ this ->assertFalse ($ this ->validation ->run ([]));
292+ } finally {
293+ unlink ($ payload );
294+ }
295+ }
296+
297+ public function testIsImageAllowsImageClientExtensionThatDoesNotMatchContent (): void
298+ {
299+ $ payload = $ this ->createGifPayload ();
300+
301+ try {
302+ $ this ->setUploadedAvatar ($ payload , 'my-avatar.jpg ' );
303+
304+ $ this ->validation ->setRules (['avatar ' => 'is_image[avatar] ' ]);
305+ $ this ->assertTrue ($ this ->validation ->run ([]));
306+ } finally {
307+ unlink ($ payload );
308+ }
309+ }
310+
311+ public function testIsImageAllowsExtensionlessClientFilename (): void
312+ {
313+ $ payload = $ this ->createGifPayload ();
314+
315+ try {
316+ $ this ->setUploadedAvatar ($ payload , 'blob ' );
317+
318+ $ this ->validation ->setRules (['avatar ' => 'is_image[avatar] ' ]);
319+ $ this ->assertTrue ($ this ->validation ->run ([]));
320+ } finally {
321+ unlink ($ payload );
322+ }
323+ }
324+
325+ public function testIsImageAllowsSvg (): void
326+ {
327+ $ payload = $ this ->createSvgPayload ();
328+
329+ try {
330+ $ this ->setUploadedAvatar ($ payload , 'my-avatar.svg ' , 'image/svg+xml ' );
331+
332+ $ this ->validation ->setRules (['avatar ' => 'is_image[avatar] ' ]);
333+ $ this ->assertTrue ($ this ->validation ->run ([]));
334+ } finally {
335+ unlink ($ payload );
336+ }
337+ }
338+
339+ public function testIsImageFailsForNonImageContent (): void
340+ {
341+ $ payload = $ this ->createPhpPayload ();
342+
343+ try {
344+ // An image extension is not enough; the file content must be an image too.
345+ $ this ->setUploadedAvatar ($ payload , 'fake.gif ' );
346+
347+ $ this ->validation ->setRules (['avatar ' => 'is_image[avatar] ' ]);
348+ $ this ->assertFalse ($ this ->validation ->run ([]));
349+ } finally {
350+ unlink ($ payload );
351+ }
352+ }
353+
269354 public function testIsntImage (): void
270355 {
271356 $ _FILES ['stuff ' ] = [
@@ -294,6 +379,62 @@ public function testMimeTypeOk(): void
294379 $ this ->assertTrue ($ this ->validation ->run ([]));
295380 }
296381
382+ public function testMimeTypeFailsForMismatchedClientExtension (): void
383+ {
384+ $ payload = $ this ->createGifPayload ();
385+
386+ try {
387+ $ this ->setUploadedAvatar ($ payload , 'shell.php ' );
388+
389+ $ this ->validation ->setRules (['avatar ' => 'mime_in[avatar,image/gif] ' ]);
390+ $ this ->assertFalse ($ this ->validation ->run ([]));
391+ } finally {
392+ unlink ($ payload );
393+ }
394+ }
395+
396+ public function testMimeTypeFailsForIncompatibleClientExtension (): void
397+ {
398+ $ payload = $ this ->createGifPayload ();
399+
400+ try {
401+ $ this ->setUploadedAvatar ($ payload , 'document.pdf ' );
402+
403+ $ this ->validation ->setRules (['avatar ' => 'mime_in[avatar,image/gif] ' ]);
404+ $ this ->assertFalse ($ this ->validation ->run ([]));
405+ } finally {
406+ unlink ($ payload );
407+ }
408+ }
409+
410+ public function testMimeTypeFailsForAllowedClientExtensionThatDoesNotMatchContent (): void
411+ {
412+ $ payload = $ this ->createGifPayload ();
413+
414+ try {
415+ $ this ->setUploadedAvatar ($ payload , 'my-avatar.jpg ' );
416+
417+ $ this ->validation ->setRules (['avatar ' => 'mime_in[avatar,image/gif,image/jpeg] ' ]);
418+ $ this ->assertFalse ($ this ->validation ->run ([]));
419+ } finally {
420+ unlink ($ payload );
421+ }
422+ }
423+
424+ public function testMimeTypeAllowsExtensionlessClientFilename (): void
425+ {
426+ $ payload = $ this ->createGifPayload ();
427+
428+ try {
429+ $ this ->setUploadedAvatar ($ payload , 'blob ' );
430+
431+ $ this ->validation ->setRules (['avatar ' => 'mime_in[avatar,image/gif] ' ]);
432+ $ this ->assertTrue ($ this ->validation ->run ([]));
433+ } finally {
434+ unlink ($ payload );
435+ }
436+ }
437+
297438 public function testMimeTypeNotOk (): void
298439 {
299440 $ this ->validation ->setRules ([
@@ -397,14 +538,34 @@ private function createGifPayload(): string
397538 return $ payload ;
398539 }
399540
400- private function setUploadedAvatar (string $ payload , string $ name ): void
541+ private function createPhpPayload (): string
542+ {
543+ $ payload = tempnam (sys_get_temp_dir (), 'ci4-upload-poc- ' );
544+ $ this ->assertIsString ($ payload );
545+
546+ file_put_contents ($ payload , "<?php echo 'pwned'; ?> \n" );
547+
548+ return $ payload ;
549+ }
550+
551+ private function createSvgPayload (): string
552+ {
553+ $ payload = tempnam (sys_get_temp_dir (), 'ci4-upload-poc- ' );
554+ $ this ->assertIsString ($ payload );
555+
556+ file_put_contents ($ payload , '<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"></svg> ' );
557+
558+ return $ payload ;
559+ }
560+
561+ private function setUploadedAvatar (string $ payload , string $ name , string $ clientMimeType = 'image/gif ' ): void
401562 {
402563 service ('superglobals ' )->setFilesArray ([
403564 'avatar ' => [
404565 'tmp_name ' => $ payload ,
405566 'name ' => $ name ,
406567 'size ' => filesize ($ payload ),
407- 'type ' => ' image/gif ' ,
568+ 'type ' => $ clientMimeType ,
408569 'error ' => UPLOAD_ERR_OK ,
409570 ],
410571 ]);
0 commit comments