false, 'error' => 'No file uploaded']); exit; } $file = $_FILES['image']; if (!is_array($file) || !isset($file['error'])) { http_response_code(400); echo json_encode(['ok' => false, 'error' => 'Invalid upload']); exit; } if ($file['error'] !== UPLOAD_ERR_OK) { http_response_code(400); echo json_encode(['ok' => false, 'error' => 'Upload error: ' . $file['error']]); exit; } if (!isset($file['size']) || $file['size'] <= 0 || $file['size'] > $maxBytes) { http_response_code(400); echo json_encode(['ok' => false, 'error' => 'File too large (max 10MB)']); exit; } // Validate image content $tmp = $file['tmp_name']; $info = @getimagesize($tmp); if ($info === false) { http_response_code(400); echo json_encode(['ok' => false, 'error' => 'Not a valid image']); exit; } $mime = isset($info['mime']) ? strtolower($info['mime']) : ''; $allowedMimeToExt = [ 'image/jpeg' => 'jpg', 'image/png' => 'png', 'image/gif' => 'gif', 'image/webp' => 'webp', ]; if (!isset($allowedMimeToExt[$mime])) { http_response_code(400); echo json_encode(['ok' => false, 'error' => 'Unsupported image type']); exit; } $imgDir = realpath(__DIR__ . '/../img'); if ($imgDir === false || !is_dir($imgDir)) { http_response_code(500); echo json_encode(['ok' => false, 'error' => 'img directory not found']); exit; } // Generate safe unique filename $ext = $allowedMimeToExt[$mime]; $base = 'upload_' . date('Ymd_His') . '_' . bin2hex(random_bytes(4)); $filename = $base . '.' . $ext; $dest = $imgDir . DIRECTORY_SEPARATOR . $filename; // Ensure we don't overwrite (extremely unlikely) $tries = 0; while (file_exists($dest) && $tries < 5) { $filename = $base . '_' . bin2hex(random_bytes(2)) . '.' . $ext; $dest = $imgDir . DIRECTORY_SEPARATOR . $filename; $tries++; } if (!move_uploaded_file($tmp, $dest)) { http_response_code(500); echo json_encode(['ok' => false, 'error' => 'Failed to save file (check permissions)']); exit; } // Conservative permissions @chmod($dest, 0644); echo json_encode(['ok' => true, 'filename' => $filename], JSON_UNESCAPED_UNICODE);