Programação Por Equipe DevBras

Capa do artigo: Uploadify e Flash morreram: como trocar o plugin por um upload que funciona hoje

Uploadify e Flash morreram: como trocar o plugin por um upload que funciona hoje

Encontre os restos do Uploadify no projeto, troque por input de arquivo com envio via FormData, remova o truque de sessão por POST e apague a pasta antiga que ainda aceita upload.

O cliente liga dizendo que "o botão de enviar foto sumiu" do painel da imobiliária. O código não mudou há anos. O que mudou foi o mundo: o botão era um SWF do Uploadify, e nenhum navegador atual executa Flash. Não existe configuração que traga isso de volta, e não deveria existir.

A resposta curta: troque o plugin por um <input type="file" multiple> comum, envie com FormData quando quiser fazer isso sem recarregar a página, valide tudo no PHP e apague a pasta do Uploadify do servidor. O script de upload antigo, esquecido no servidor, é o pedaço mais perigoso dessa história.

Por que o tutorial parou de funcionar

O Uploadify clássico existia porque, por volta de 2010, o HTML não tinha seleção de vários arquivos nem progresso de upload em todos os navegadores. O Flash Player tinha. O plugin desenhava um botão em Flash por cima da página, e era o Flash que lia os arquivos e fazia o POST.

A Adobe encerrou o suporte ao Flash Player no fim de 2020 e os navegadores removeram o plugin. Desde então, o SWF simplesmente não carrega. Não aparece erro do PHP porque o PHP nunca é chamado: o botão some e nenhuma requisição sai. Quem segue o tutorial hoje acha que errou a configuração do servidor, quando o problema é que a peça central não existe mais.

O input de arquivo resolve hoje, nativamente, o que o Flash resolvia: atributo multiple, filtro com accept, arrastar e soltar, e progresso via XMLHttpRequest. Não há motivo para procurar "a versão HTML5 do plugin" se o objetivo é só subir fotos.

O que o Uploadify deixava para trás

Antes de trocar o botão, entenda o que mais o plugin trazia, porque parte disso é problema de segurança que continua ativo mesmo com o Flash morto.

O script de upload sem validação

Os exemplos da época vinham com um uploadify.php que movia o arquivo recebido para a pasta de destino, muitas vezes sem conferir tipo nem extensão. Esse script continua respondendo a qualquer POST, com ou sem Flash. Pastas do Uploadify esquecidas em servidores são alvo comum de varredura automática, justamente porque às vezes aceitam um arquivo .php e o deixam numa pasta pública.

A sessão passada pelo formulário

O Flash não enviava os cookies do navegador junto com o upload. Para o PHP saber quem estava logado, os tutoriais passavam o ID da sessão como parâmetro e faziam algo como session_id($_POST['PHPSESSID']) antes do session_start(). Isso permite que qualquer um escolha a sessão que o servidor vai usar, o que abre caminho para fixação de sessão. Com o envio pelo próprio navegador, o cookie vai sozinho e esse truque precisa sair.

Como migrar, passo a passo

  1. Inventarie os restos. Na raiz do projeto, rode rg -n -i "uploadify|swf|PHPSESSID" e liste cada arquivo encontrado: JavaScript, CSS, SWF, script de upload e qualquer session_id( vindo de requisição.
  2. Escreva o formulário que funciona sem script. Um form com method="post", enctype="multipart/form-data" e o input com multiple. Ele já resolve o problema do cliente.
  3. Reescreva o recebimento no PHP com validação de erro, tamanho e tipo por conteúdo, nome gerado pelo servidor e conferência de login pela sessão normal.
  4. Adicione o envio sem recarregar, se quiser, interceptando o submit e mandando o mesmo FormData.
  5. Apague o plugin do servidor, não só do repositório: a pasta, o SWF, o script antigo. Confirme que a URL antiga responde 404.
  6. Olhe os logs de acesso procurando POSTs para o script antigo e arquivos estranhos na pasta de destino.

O formulário e o envio com FormData

No painel da imobiliária, cada imóvel recebe até 20 fotos. O formulário abaixo funciona sozinho; o script só melhora a experiência.

<form id="form-fotos" action="/imoveis/fotos.php" method="post" enctype="multipart/form-data">
  <input type="hidden" name="imovel_id" value="482">
  <input type="hidden" name="csrf" value="TOKEN_GERADO_NO_SERVIDOR">
  <label for="fotos">Fotos do imóvel (JPG, PNG ou WebP, até 5 MB cada)</label>
  <input id="fotos" type="file" name="fotos[]" multiple accept="image/jpeg,image/png,image/webp">
  <button type="submit">Enviar fotos</button>
  <p id="resultado" aria-live="polite"></p>
</form>

<script>
const form = document.getElementById('form-fotos');
const resultado = document.getElementById('resultado');

form.addEventListener('submit', async function (e) {
  e.preventDefault();
  resultado.textContent = 'Enviando...';
  try {
    const resp = await fetch(form.action, { method: 'POST', body: new FormData(form) });
    const corpo = await resp.json().catch(function () { return null; });
    if (!resp.ok || !corpo) {
      resultado.textContent = 'Falhou: ' + ((corpo && corpo.erro) || 'erro ' + resp.status);
      return;
    }
    resultado.textContent = corpo.salvos + ' foto(s) salva(s). ' +
      (corpo.recusados.length ? 'Recusadas: ' + corpo.recusados.join(', ') : '');
  } catch (err) {
    resultado.textContent = 'Sem conexão com o servidor. Tente de novo.';
  }
});
</script>

O accept só filtra a janela de seleção; não é validação. O fetch com FormData envia o cookie de sessão do próprio domínio sem configuração extra. Se você precisa de barra de progresso, troque o fetch por XMLHttpRequest e siga o que está no post sobre barra de upload que não mente sobre o sucesso.

O recebimento no PHP

Com name="fotos[]", o PHP organiza $_FILES de um jeito pouco intuitivo: um array por propriedade, não um array por arquivo. O laço abaixo percorre pelo índice.

<?php
session_start();
header('Content-Type: application/json; charset=utf-8');

if (!isset($_SESSION['usuario_id'])) {
    http_response_code(401);
    echo json_encode(['erro' => 'Faça login de novo.']);
    exit;
}
if (!hash_equals($_SESSION['csrf'] ?? '', $_POST['csrf'] ?? '')) {
    http_response_code(403);
    echo json_encode(['erro' => 'Formulário expirado. Recarregue a página.']);
    exit;
}

$tipos = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp'];
$finfo = new finfo(FILEINFO_MIME_TYPE);
$salvos = 0;
$recusados = [];
$total = count($_FILES['fotos']['name'] ?? []);

for ($i = 0; $i < $total; $i++) {
    $nomeOriginal = $_FILES['fotos']['name'][$i];
    $tmp = $_FILES['fotos']['tmp_name'][$i];

    if ($_FILES['fotos']['error'][$i] !== UPLOAD_ERR_OK
        || $_FILES['fotos']['size'][$i] > 5 * 1024 * 1024) {
        $recusados[] = $nomeOriginal;
        continue;
    }
    $mime = $finfo->file($tmp);
    if (!isset($tipos[$mime])) {
        $recusados[] = $nomeOriginal;
        continue;
    }
    $destino = '/var/app/fotos/' . bin2hex(random_bytes(16)) . '.' . $tipos[$mime];
    if (move_uploaded_file($tmp, $destino)) {
        $salvos++;
    } else {
        error_log('fotos: falha ao mover ' . $tmp);
        $recusados[] = $nomeOriginal;
    }
}

echo json_encode(['salvos' => $salvos, 'recusados' => $recusados], JSON_UNESCAPED_UNICODE);

A pasta de destino fica fora da raiz pública, então mesmo um arquivo malicioso que passasse não seria executado por URL. A sessão vem do cookie, e não de um parâmetro. Para reprocessar a imagem e gravar a referência no banco, o guia de upload de arquivo em PHP cobre o restante.

Arrastar e soltar sem plugin

Se o cliente sentir falta de arrastar as fotos para a página, isso também é nativo. Uma área que escuta dragover (chamando preventDefault()) e drop recebe os arquivos em e.dataTransfer.files. Você pode atribuir essa lista ao próprio input com input.files = e.dataTransfer.files e reaproveitar o mesmo envio. Não precisa de biblioteca para isso.

Erros comuns

  • Procurar como habilitar Flash: não há caminho suportado, e qualquer "player alternativo" baixado de site desconhecido é um risco maior que o problema.
  • Trocar só o botão e manter o uploadify.php: o script antigo continua aceitando POST de qualquer origem, sem validação.
  • Manter o session_id($_POST[...]): o servidor continua aceitando sessão escolhida por quem envia, o que abre espaço para fixação de sessão.
  • Confiar no accept como filtro: ele só muda a janela do sistema; qualquer arquivo pode ser enviado direto para o endpoint.
  • Apagar do Git e esquecer o servidor: o deploy por cópia não remove arquivos antigos, e a pasta segue publicada.
  • Seguir outro tutorial da mesma época: trocar Uploadify por outro plugin de 2012 só adia a próxima migração; veja por que resposta de fórum antigo não é manual.

Checklist

  • A busca por uploadify, swf e PHPSESSID no projeto não retorna nada.
  • A URL do script de upload antigo responde 404 no servidor de produção.
  • O formulário envia fotos com o JavaScript desligado.
  • O PHP confere login pela sessão normal e valida CSRF.
  • Tipo é validado por conteúdo com finfo, e o nome final é gerado pelo servidor.
  • Os arquivos ficam fora da pasta pública.
  • A resposta diz quantos arquivos foram salvos e quais foram recusados.
  • Os logs foram verificados em busca de POSTs para o script antigo.

Abra agora o servidor de produção e procure uma pasta chamada uploadify ou um arquivo .swf. Se encontrar, o script que vinha junto ainda aceita upload?

Avalie este artigo

Referência: http://www.oficinadanet.com.br/post/9518-upload-de-imagens-php-com-uploadify