--- id: KB-PLUGIN-017 title: "Bug do detector de wrapper directory no instalador Mindplace mutilava nomes de arquivos" domain: plugin-dev tags: - mindplace - marketplace - zip - extractor - install-bug status: active severity: high created_at: 2026-05-21 updated_at: 2026-05-21 applies_to: - 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//`. 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): ```php $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. ```php // 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//` — 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 .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 - [KB-PLUGIN-011](KB-PLUGIN-011-mindplace-release-zip-procedure.md) — Procedimento de release do Mindplace - [KB-PLUGIN-013](KB-PLUGIN-013-mindplace-plugin-publication-runbook.md) — Runbook de publicação de plugin no Mindplace - [KB-PLUGIN-018](KB-PLUGIN-018-plugin-release-zip-wrapper-convention.md) — Convenção de wrapper directory para ZIPs de release