Função de Upload Segura em PHP para Ambientes de Produção

Formulário de Upload em HTML


<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <title>Envio de Arquivo</title>
</head>
<body>
    <h1>Escolha um arquivo para enviar</h1>
    <form action="processar_envio.php" method="post" enctype="multipart/form-data">
        <label for="arquivo">Arquivo:</label>
        <input type="file" name="arquivo" id="arquivo">
        <input type="submit" value="Enviar">
    </form>
</body>
</html>

Função PHP para Upload com Validações

A função aprimorada abaixo inclui verificações de segurança essenicais para uso em produção, como limitação de tamanho, validação de extensões e tipos MIME, geração de nomes de arquivo seguros e tratamenot básico de imagens.

<?php
function processarUpload($campoArquivo, $diretorioDestino, $extensoesPermitidas = [], $tamanhoMaximo = 0, $tiposMimePermitidos = []) {
    // Verifica se a requisição é do tipo POST e se o arquivo foi enviado
    if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_FILES[$campoArquivo])) {
        return ['sucesso' => false, 'mensagem' => 'Requisição inválida.'];
    }

    $arquivoEnviado = $_FILES[$campoArquivo];

    // Trata erros comuns no upload
    if ($arquivoEnviado['error'] !== UPLOAD_ERR_OK) {
        $mensagensErro = [
            UPLOAD_ERR_INI_SIZE => 'O arquivo excede o limite definido no servidor.',
            UPLOAD_ERR_FORM_SIZE => 'O arquivo excede o limite permitido pelo formulário.',
            UPLOAD_ERR_NO_FILE => 'Nenhum arquivo foi enviado.',
        ];
        $mensagemErro = $mensagensErro[$arquivoEnviado['error']] ?? 'Erro desconhecido durante o upload.';
        return ['sucesso' => false, 'mensagem' => $mensagemErro];
    }

    // Valida o tamanho máximo do arquivo
    if ($tamanhoMaximo > 0 && $arquivoEnviado['size'] > $tamanhoMaximo) {
        return ['sucesso' => false, 'mensagem' => 'O arquivo ultrapassa o tamanho máximo permitido.'];
    }

    // Obtém e valida a extensão do arquivo
    $nomeOriginal = $arquivoEnviado['name'];
    $extensaoArquivo = strtolower(pathinfo($nomeOriginal, PATHINFO_EXTENSION));
    if (!empty($extensoesPermitidas) && !in_array($extensaoArquivo, $extensoesPermitidas)) {
        return ['sucesso' => false, 'mensagem' => 'Extensão de arquivo não permitida.'];
    }

    // Verifica o tipo MIME real do arquivo
    if (!empty($tiposMimePermitidos)) {
        $finfo = new finfo(FILEINFO_MIME_TYPE);
        $tipoMime = $finfo->file($arquivoEnviado['tmp_name']);
        if (!in_array($tipoMime, $tiposMimePermitidos)) {
            return ['sucesso' => false, 'mensagem' => 'Tipo de arquivo inválido.'];
        }
    }

    // Gera um nome de arquivo único e seguro para evitar ataques
    $nomeSeguro = bin2hex(random_bytes(12)) . '.' . $extensaoArquivo;

    // Verifica se o diretório de destino existe e é gravável
    $caminhoDestino = rtrim($diretorioDestino, '/') . '/';
    if (!is_dir($caminhoDestino) || !is_writable($caminhoDestino)) {
        return ['sucesso' => false, 'mensagem' => 'O diretório de upload não está disponível ou não tem permissão de escrita.'];
    }

    // Para imagens, realiza validação adicional usando a biblioteca GD
    if (in_array($tipoMime, ['image/jpeg', 'image/png', 'image/gif'])) {
        $infoImagem = getimagesize($arquivoEnviado['tmp_name']);
        if (!$infoImagem) {
            return ['sucesso' => false, 'mensagem' => 'O arquivo de imagem está corrompido ou não é válido.'];
        }
        // Descomente para validação mais rigorosa, como criar um recurso temporário
        // $imagemTemp = imagecreatefromstring(file_get_contents($arquivoEnviado['tmp_name']));
        // if (!$imagemTemp) {
        //     return ['sucesso' => false, 'mensagem' => 'Falha ao processar a imagem.'];
        // }
        // imagedestroy($imagemTemp);
    }

    // Move o arquivo para o diretório final
    $caminhoArquivo = $caminhoDestino . $nomeSeguro;
    if (!move_uploaded_file($arquivoEnviado['tmp_name'], $caminhoArquivo)) {
        return ['sucesso' => false, 'mensagem' => 'Falha ao mover o arquivo para o destino.'];
    }

    return [
        'sucesso' => true,
        'mensagem' => 'Upload realizado com sucesso.',
        'nome_arquivo' => $nomeSeguro,
        'caminho_completo' => $caminhoArquivo,
    ];
}

Exemplo de Utilização

<?php
// Configurações para o upload
$diretorio = './uploads';
$extensoes = ['jpg', 'jpeg', 'png'];
$tamanhoLimite = 5 * 1024 * 1024; // 5MB em bytes
$mimeTypes = ['image/jpeg', 'image/png'];

// Processa o upload se a requisição for POST
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['arquivo'])) {
    $resultado = processarUpload('arquivo', $diretorio, $extensoes, $tamanhoLimite, $mimeTypes);
    if ($resultado['sucesso']) {
        echo 'Arquivo salvo em: ' . htmlspecialchars($resultado['caminho_completo']);
    } else {
        echo 'Erro: ' . htmlspecialchars($resultado['mensagem']);
    }
}

Tags: PHP upload-de-arquivos segurança-web validacao-entrada biblioteca-GD

Publicado em 6-15 02:59 por Thomas