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 performance

View on GitHub (pinned to 65b080eef4)

Solutions

  1. Use one of the allowed schemes, lowercase: http, https, file, ftp, s3.
  2. For phar:// or php:// streams, copy the bytes to a temp file first and setPath() the temp path.
  3. 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

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


AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17). Data as JSON: /api/errors/e98d8aa05be480d6. Report an issue: GitHub.