thephpleague/flysystem · error · InvalidListResponseReceived

Metadata can't be parsed from item '$item' , not enough part

Error message

Metadata can't be parsed from item '$item' , not enough parts.

What it means

When listing an FTP server detected as Windows-style (the LIST line starts with a date-like pattern such as MM-DD-YY or YYYY-MM-DD), FtpAdapter::normalizeWindowsObject() collapses whitespace and splits the line into exactly 4 parts: date, time, size-or-<DIR>, and name. Fewer than 4 parts means the line is not a parseable Windows directory entry, and InvalidListResponseReceived is thrown with the offending item embedded in the message.

Source

Thrown at src/Ftp/FtpAdapter.php:408

        return $this->normalizeWindowsObject($item, $base);
    }

    private function detectSystemType(string $item): string
    {
        return preg_match(
            '/^[0-9]{2,4}-[0-9]{2}-[0-9]{2}/',
            $item
        ) ? self::SYSTEM_TYPE_WINDOWS : self::SYSTEM_TYPE_UNIX;
    }

    private function normalizeWindowsObject(string $item, string $base): StorageAttributes
    {
        $item = preg_replace('#\s+#', ' ', trim($item), 3);
        $parts = explode(' ', $item, 4);

        if (count($parts) !== 4) {
            throw new InvalidListResponseReceived("Metadata can't be parsed from item '$item' , not enough parts.");
        }

        [$date, $time, $size, $name] = $parts;
        $path = $base === '' ? $name : rtrim($base, '/') . '/' . $name;

        if ($size === '<DIR>') {
            return new DirectoryAttributes($path);
        }

        // Check for the correct date/time format
        $format = strlen($date) === 8 ? 'm-d-yH:iA' : 'Y-m-dH:i';
        $dt = DateTime::createFromFormat($format, $date . $time);
        $lastModified = $dt ? $dt->getTimestamp() : (int) strtotime("$date $time");

        return new FileAttributes($path, (int) $size, null, $lastModified);
    }

    private function normalizeUnixObject(string $item, string $base): StorageAttributes

View on GitHub (pinned to b277b5dc3d)

Solutions

  1. Pin the listing format explicitly: FtpConnectionOptions::lazy('host', ..., systemType: 'unix') or 'windows' — removing auto-detection when you know the server type.
  2. Enable/disable 'recurseManually' or set 'useRawListContents' via a decorator to inspect raw LIST output and identify the offending line.
  3. If the server offers MLSD (machine listings) prefer an MLSD-capable server config, or fix the server's LIST format / chroot so it emits standard lines.
  4. Catch InvalidListResponseReceived around listContents() and log $e->getMessage() to capture the exact malformed item for the server admin.

Example fix

// before (auto-detection misfires on odd first line)
$options = FtpConnectionOptions::lazy('ftp.example.com');
$adapter = new FtpAdapter($options);
$adapter->listContents('/'); // throws "Metadata can't be parsed from item 'Total files: 42' , not enough parts."

// after (explicit system type)
$options = FtpConnectionOptions::lazy('ftp.example.com', '/', FTP_NATIVE, 'windows'); // 5th arg = systemType
$adapter = new FtpAdapter($options);
Defensive patterns

Strategy: validation

Validate before calling

// Pin the server type instead of relying on per-connection auto-detection
use League\Flysystem\Ftp\FtpConnectionOptions;

$options = FtpConnectionOptions::lazy('ftp.example.com', '/', FTP_NATIVE, 'windows'); // explicit systemType
$adapter = new FtpAdapter($options);

// Optional: probe the raw listing before calling listContents
$raw = ftp_rawlist($connection, '/');
foreach ($raw as $line) {
    if (substr_count(preg_replace('#\s+#', ' ', trim($line)), ' ') < 3) {
        // non-entry line detected: filter or abort before Flysystem parses it
    }
}

Try / catch

use League\Flysystem\InvalidListResponseReceived;

try {
    $contents = $filesystem->listContents('/')->toArray();
} catch (InvalidListResponseReceived $e) {
    // message contains the exact malformed line — log it for the server admin
    $this->logger->error('Unparseable FTP listing line', ['exception' => $e]);
    $contents = []; // or fall back to non-recursive per-file has() checks
}

Prevention

When it happens

Trigger: listContents() on an FTP server whose raw LIST output contains non-entry lines (messages like 'Total files: 42', banners, blank-ish lines) that got classified as Windows format; a server whose date format accidentally matches the Windows detection regex; localized or non-standard LIST formats.

Common situations: Connecting to Windows/IIS FTP servers with custom directory-listing formats; mainframe or appliance FTP gateways that prepend status lines; servers behind FTP proxies injecting text into listings; systemType left null so detection happens per-connection based on the first line seen.

Related errors


AI-assisted analysis of thephpleague/flysystem@b277b5dc3d (2026-08-17). Data as JSON: /api/errors/188b41d73fe799da. Report an issue: GitHub.