knowledge-base/records/plugin-dev/KB-PLUGIN-017-mindplace-zip-wrapper-detector-bug.md
2026-06-29 11:29:24 -03:00

5 KiB

id title domain tags status severity created_at updated_at applies_to
KB-PLUGIN-017 Bug do detector de wrapper directory no instalador Mindplace mutilava nomes de arquivos plugin-dev
mindplace
marketplace
zip
extractor
install-bug
active high 2026-05-21 2026-05-21
Plugin Mindplace (ajax/marketplace.php)
Qualquer plugin distribuído via Mindplace

KB-PLUGIN-017 — Bug do detector de wrapper directory no instalador Mindplace

Contexto

O extrator de plugins do Mindplace (/home/glpi/glpi_dev/plugins/mindplace/ajax/marketplace.php) baixa o .zip da release do GitHub, detecta um "wrapper directory" no topo e remove esse prefixo dos demais entries antes de extrair para marketplace/<plugin>/.

Quando o ZIP de release de um plugin é gerado sem um wrapper directory consistente, a heurística antiga aceitava qualquer pasta top-level como wrapper e mutilava os paths de todos os outros entries.

Sintoma

A instalação via Mindplace completa "com sucesso" mas o GLPI marca o plugin como não-carregável silenciosamente. Listando o diretório de instalação aparecem arquivos com nomes truncados:

marketplace/mcprotocol/
├── .php           ← deveria ser hook.php
├── .png           ← deveria ser logo.png
├── p.php          ← deveria ser setup.php
├── est.php        ← deveria ser apirest.php
├── ic/            ← deveria ser public/
│   ├── index.php
│   └── logo.png
├── t/             ← deveria ser front/
└── Boot.php       ← deveria ser src/Boot.php

O conteúdo dos arquivos está íntegro — apenas o nome do arquivo (caminho) foi mutilado.

Causa Raiz

A detecção em ajax/marketplace.php (versão buggy):

$first  = $zip->getNameIndex(0);
$prefix = '';
if ($first !== false && str_ends_with($first, '/') && substr_count(rtrim($first, '/'), '/') === 0) {
    $prefix = $first;  // aceito como wrapper sem validar
}

A heurística aceitava como wrapper qualquer pasta top-level que fosse o primeiro entry do ZIP, mesmo que essa pasta não fosse realmente um wrapper (envelopando todos os demais).

Quando o ZIP do MCProtocol foi gerado pelo script bin/mindplace-release.sh (versão anterior, que fazia cd "$PLUGIN_DIR" && zip -r "$ZIP_PATH" . — sem wrapper directory), o primeiro entry era a pasta inc/ (top-level legítima do plugin). O extrator então removia 4 chars (inc/) do começo de todos os outros entries:

Entry no ZIP substr(_, 4) Resultado em disco
hook.php .php .php
logo.png .png .png
setup.php p.php p.php
apirest.php est.php est.php
public/ ic/ pasta ic/
public/index.php ic/index.php dentro de ic/
front/ t/ pasta t/
src/Boot.php Boot.php top-level
inc/mcp.class.php mcp.class.php top-level

Resolução (fix aplicado em maio/2026)

O detector agora valida que o candidato a wrapper é prefixo de todos os entries antes de aceitá-lo. Se algum entry não começar com o candidato, o prefix fica vazio e a extração preserva a estrutura original do ZIP.

// Detect common top-level wrapper directory.
// The wrapper is only valid if EVERY entry starts with it; otherwise a
// top-level folder like "inc/" would be mistaken for a wrapper and the
// substr() below would mutilate the remaining entries' paths.
$prefix    = '';
$count     = $zip->count();
$candidate = $count > 0 ? $zip->getNameIndex(0) : false;

if (
    $candidate !== false
    && str_ends_with($candidate, '/')
    && substr_count(rtrim($candidate, '/'), '/') === 0
) {
    $allMatch = true;
    for ($i = 1; $i < $count; $i++) {
        $name = $zip->getNameIndex($i);
        if ($name === false || !str_starts_with($name, $candidate)) {
            $allMatch = false;
            break;
        }
    }
    if ($allMatch) {
        $prefix = $candidate;
    }
}

Arquivo: /home/glpi/glpi_dev/plugins/mindplace/ajax/marketplace.php (linhas ~91-114).

Como diagnosticar uma instalação afetada

  1. Listar /home/glpi/glpi_dev/marketplace/<plugin>/ — se aparecerem arquivos com nomes começando por . (.php, .png), arquivos com nomes incompletos (p.php, est.php), ou pastas com nomes esquisitos (ic/, t/), o bug está presente.
  2. Confirmar inspecionando o .zip original: unzip -l <plugin>.zip | head — se o primeiro entry não é um wrapper directory que contém todos os demais, o ZIP é vulnerável a esta versão do bug.

Como evitar (em plugins novos)

Gerar releases com wrapper directory consistente — ver KB-PLUGIN-018 para a workflow recomendada.

Veja também