PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Exception
Invalid protocol for linked drawing
Error message
Invalid protocol for linked drawing
What it means
Drawing::setPath() classifies its argument as zip entry, URL, or local file. If the value looks like a URL (passes FILTER_VALIDATE_URL, starts with phar://, or has a scheme-like prefix) the scheme must be one of http, https, file, ftp or s3 — anything else throws 'Invalid protocol for linked drawing'. Properly formed data:image/...;base64 URIs are accepted earlier in the method, so this fires for other schemes only.
Source
Thrown at src/PhpSpreadsheet/Worksheet/Drawing.php:126
$this->path = '';
if ($zip instanceof ZipArchive) {
$zipPath = explode('#', $path)[1];
$locate = @$zip->locateName($zipPath);
if ($locate !== false) {
if ($this->isImage($path)) {
$this->path = $path;
$this->setSizesAndType($path);
}
}
// Check if a URL has been passed. https://stackoverflow.com/a/2058596/1252979
} elseif (
filter_var($path, FILTER_VALIDATE_URL)
|| Preg::isMatch('~^phar://~i', $path)
|| (Preg::isMatch('/^([\w.\s\x00-\x1f]+):/', $path) && !Preg::isMatch('/^([\w.]+):/', $path))
) {
if (!Preg::isMatch('/^(http|https|file|ftp|s3):/', $path)) {
throw new PhpSpreadsheetException('Invalid protocol for linked drawing');
}
if (!$allowExternal) {
return $this;
}
if ($isWhitelisted !== null && !$isWhitelisted($path)) {
return $this;
}
// Implicit that it is a URL, rather store info than running check above on value in other places.
$this->isUrl = true;
$ctx = null;
// https://github.com/php/php-src/issues/16023
// https://github.com/php/php-src/issues/17121
if (str_starts_with($path, 'https:') || str_starts_with($path, 'http:')) {
$ctxArray = [
'http' => [
'user_agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
'header' => [
//'Connection: keep-alive', // unacceptable performanceView on GitHub (pinned to 65b080eef4)
Solutions
- Use one of the allowed schemes, lowercase: http, https, file, ftp, s3.
- For phar:// or php:// streams, copy the bytes to a temp file first and setPath() the temp path.
- For non-simple data URIs (e.g. svg+xml), decode the payload and use MemoryDrawing::fromString() instead.
Example fix
// before
$drawing->setPath('phar://app.phar/assets/logo.png'); // Invalid protocol for linked drawing
// after
$tmp = tempnam(sys_get_temp_dir(), 'img');
file_put_contents($tmp, file_get_contents('phar://app.phar/assets/logo.png'));
$drawing->setPath($tmp); Defensive patterns
Strategy: validation
Validate before calling
$scheme = strtolower((string) parse_url($path, PHP_URL_SCHEME));
if ($scheme !== '' && !in_array($scheme, ['http', 'https', 'file', 'ftp', 's3'], true)) {
$tmp = tempnam(sys_get_temp_dir(), 'img');
file_put_contents($tmp, file_get_contents($path)); // phar://, php:// etc.
$path = $tmp;
}
$drawing->setPath($path); Type guard
function isAllowedDrawingScheme(string $path): bool
{
$scheme = strtolower((string) parse_url($path, PHP_URL_SCHEME));
return $scheme === '' || in_array($scheme, ['http', 'https', 'file', 'ftp', 's3'], true);
} Try / catch
try {
$drawing->setPath($path);
} catch (PhpSpreadsheetException $e) {
if (str_contains($e->getMessage(), 'Invalid protocol')) {
$drawing = MemoryDrawing::fromString(file_get_contents($path));
}
} Prevention
- Only http, https, file, ftp and s3 schemes (lowercase) are accepted for linked drawings
- Materialize phar:// and php:// streams into temp files before setPath()
- Simple data:image/...;base64 URIs are accepted, but svg+xml and other variants are not — decode those to MemoryDrawing
When it happens
Trigger: $drawing->setPath('phar://app.phar/assets/logo.png') (matched by the phar check, rejected by the whitelist); 'php://temp'; 'gopher://' or custom stream wrappers; 'data:image/svg+xml;base64,...' (the '+' fails the early data-image regex); uppercase schemes like 'HTTP://...'.
Common situations: Code packaged or run inside phar archives (boxed CLI apps, PHPUnit fixtures); passing PHP stream wrappers; HTML-editor payloads with svg+xml data URIs; note the $allowExternal flag and optional isWhitelisted callback only control fetching, not scheme validity.
Related errors
- Unsupported binary comparison operator
- Cloning the calculation engine is not allowed!
- Unsupported numeric binary operation
- Locale file not found
- #VALUE!
AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17).
Data as JSON: /api/errors/e98d8aa05be480d6.
Report an issue: GitHub.