Single File Uploads

Receiving a Single Uploaded File

Files travel only in a POST form with enctype="multipart/form-data". For <input type="file" name="image">, PHP streams the file to a temporary path and fills $_FILES['image'] with name, full_path (PHP 8.1) and type as the client sent them, plus tmp_name, error (an UPLOAD_ERR_* code) and size. Unmoved temporary files are deleted.

upload.php: accept a product image by its bytes, not its labelPHP
<?php
declare(strict_types=1);
const TYPES = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp'];
header('Content-Type: text/plain');
$f = $_FILES['image'] ?? null;
if (!is_int($f['error'] ?? null)) exit("expected one file in field 'image'\n");  // not image[]
if ($f['error'] !== UPLOAD_ERR_OK) exit("upload failed with code {$f['error']}\n");
$type = (new finfo(FILEINFO_MIME_TYPE))->file($f['tmp_name']);    // read the bytes
if (!isset(TYPES[$type])) exit("rejected: client said {$f['type']}, bytes say $type\n");
$name = bin2hex(random_bytes(8)) . '.' . TYPES[$type];            // never the client's name
if (!move_uploaded_file($f['tmp_name'], __DIR__ . "/../uploads/$name")) exit("move failed\n");
[$w, $h] = getimagesize(__DIR__ . "/../uploads/$name");
echo "stored uploads/$name ($type, {$w}x{$h}, {$f['size']} bytes)\n";
Output
$ curl -s -F image=@cover.png 127.0.0.1:8215/upload.php
stored uploads/a92113dc20159750.png (image/png, 400x600, 2062 bytes)
$ curl -s -F 'image=@shell.png;type=image/png' 127.0.0.1:8215/upload.php
rejected: client said image/png, bytes say text/x-php
$ curl -s -F image=@big.png 127.0.0.1:8215/upload.php
upload failed with code 1

shell.png holds <?php system($_GET["c"]);; its name and type claim PNG because the client writes both. finfo, built on libmagic like the file command, reads the bytes and finds PHP. The random name and an uploads/ directory outside the document root mean nothing uploaded can be requested and run, and move_uploaded_file() refuses any path that was not uploaded in this request. Code 1 is the subject of Multiple Uploads; re-encoding and safe serving are in 4.19 and 4.17.13.