Programação Por Equipe DevBras

Capa do artigo: Criar pasta em PHP com mkdir sem deixar o usuário escolher o caminho

Criar pasta em PHP com mkdir sem deixar o usuário escolher o caminho

Valide o nome, monte o caminho a partir de uma raiz fixa, crie com permissão mínima e confira com realpath. Assim "../../" nunca vira uma pasta fora do projeto.

O botão "nova pasta do cliente" funcionou no teste: você digitou "padaria-central" e a pasta apareceu. Em produção, alguém digita ../../tmp/teste e o PHP cria um diretório fora da área do sistema, onde quer que o usuário do servidor web tenha permissão de escrita. A resposta curta: aceite um nome, nunca um caminho; valide esse nome com uma regra estreita, junte-o a uma pasta raiz fixa no código, crie com mkdir e permissão mínima, e confira com realpath que o resultado ficou dentro da raiz.

O exemplo deste guia é um sistema de contabilidade que guarda documentos por cliente em /var/www/dados/clientes, uma pasta fora da raiz pública do site.

Por que mkdir com o que veio do formulário é perigoso

O mkdir faz exatamente o que você pede. Ele não sabe que .. significa "subir um nível" na sua intenção; para o sistema operacional, /var/www/dados/clientes/../../tmp/teste é um caminho válido que aponta para /var/www/tmp/teste. Se o nome vier com barra, vira subpasta. Se vier com ponto duplo, sobe. Se vier com caminho absoluto e você concatenar sem pensar, pode até ignorar a raiz.

O risco não para na pasta criada. Na sequência, o mesmo sistema costuma gravar arquivos ali. Uma pasta criada dentro da raiz pública, com um arquivo .php enviado depois, é um caminho conhecido para execução de código no servidor. Por isso, o caminho é decisão do código, e o usuário contribui no máximo com um rótulo.

Como criar a pasta do jeito certo

  1. Defina a raiz numa constante, fora da pasta pública do site.
  2. Prefira um nome gerado por você, como cliente- seguido do id do banco. Se o nome precisar vir do usuário, valide com uma expressão regular que só aceita letras minúsculas, números e hífen.
  3. Monte o caminho juntando a raiz real com o nome validado.
  4. Crie com mkdir e permissão 0750, tratando o caso de a pasta já existir.
  5. Resolva o caminho com realpath e confira que ele começa pela raiz seguida de barra.
<?php
const RAIZ_CLIENTES = '/var/www/dados/clientes';

function nomeDePastaValido(string $nome): bool
{
    return preg_match('/^[a-z0-9][a-z0-9-]{0,49}$/', $nome) === 1;
}

function garantirPastaDoCliente(string $nome): string
{
    if (!nomeDePastaValido($nome)) {
        throw new InvalidArgumentException('Nome de pasta inválido.');
    }

    $raiz = realpath(RAIZ_CLIENTES);
    if ($raiz === false) {
        throw new RuntimeException('A pasta raiz não existe.');
    }

    $caminho = $raiz . '/' . $nome;

    if (!is_dir($caminho) && !mkdir($caminho, 0750) && !is_dir($caminho)) {
        throw new RuntimeException('Não foi possível criar a pasta.');
    }

    $real = realpath($caminho);
    if ($real === false || !str_starts_with($real, $raiz . '/')) {
        throw new RuntimeException('A pasta ficou fora da raiz permitida.');
    }

    return $real;
}

$pasta = garantirPastaDoCliente('cliente-' . (int) $clienteId);

A expressão regular é o filtro principal: sem barra e sem ponto, não há como subir de nível nem criar subpasta. O primeiro caractere precisa ser letra ou número, o que bloqueia nomes como -rf ou .oculta. O limite de 50 caracteres evita nomes absurdos. A função str_starts_with existe a partir do PHP 8.0.

Por que conferir de novo com realpath

Se o nome já passou na regex, a conferência final parece redundante. Ela existe para o que a regex não vê: um link simbólico. Se alguém com acesso ao servidor (ou uma rotina mal escrita) criou /var/www/dados/clientes/cliente-42 como link para /etc, o is_dir diz que a pasta existe e o sistema passa a gravar no lugar errado. O realpath resolve o link e devolve o destino verdadeiro, e a comparação com a raiz pega o desvio. Note o '/' no fim da comparação: sem ele, uma pasta /var/www/dados/clientes-antigos passaria por "começar com a raiz".

Quando a pasta já existe

A linha com dois is_dir parece estranha, mas resolve uma corrida real. Duas requisições chegam ao mesmo tempo para o mesmo cliente; as duas veem que a pasta não existe, as duas chamam mkdir, e a segunda falha porque a primeira acabou de criar. A segunda verificação transforma essa falha em sucesso, porque o objetivo era "garantir que a pasta esteja lá". O mkdir que falhou ainda emite um aviso, que em produção deve ir para o log, não para a tela.

Se a regra do negócio for outra, por exemplo um usuário criando uma pasta com nome que não pode repetir, inverta a lógica: pasta existente é erro, e a mensagem para a pessoa é sua ("já existe uma pasta com esse nome"), não o aviso cru do PHP.

Permissão: 0750, não 0777

O segundo argumento do mkdir é a permissão em octal, e o zero na frente é obrigatório. 0750 significa: o dono lê, escreve e entra; o grupo lê e entra; o resto do servidor não faz nada. O valor final ainda passa pela umask do processo, que só pode retirar permissões, nunca acrescentar.

O 0777 "para garantir" dá escrita a todos os usuários da máquina. Em hospedagem compartilhada, isso inclui outros sites. Se o PHP não consegue criar a pasta com 0750, o problema é o dono da raiz, e se resolve dando a ela o dono certo:

sudo mkdir -p /var/www/dados/clientes
sudo chown www-data:www-data /var/www/dados/clientes
sudo chmod 750 /var/www/dados/clientes
ls -ld /var/www/dados/clientes
drwxr-x--- 2 www-data www-data 4096 out 17 21:50 /var/www/dados/clientes

O usuário www-data é o padrão do Apache e do PHP-FPM no Debian e no Ubuntu. Em outras distribuições, confira qual usuário roda o processo antes de copiar o comando.

Pastas aninhadas por data

Se você organiza por ano e mês, como cliente-42/2026/10, passe true no terceiro argumento: mkdir($caminho, 0750, true) cria toda a sequência que faltar. Ano e mês vêm do date(), não do formulário, então continuam sob seu controle.

Erros comuns

  • Concatenar $_POST['pasta'] direto no caminho. Um ../ no nome cria diretório fora da área prevista, onde o servidor tiver permissão.
  • Criar a pasta dentro da raiz pública. Qualquer arquivo gravado ali fica acessível por URL, e um .php enviado depois pode ser executado.
  • Usar 0777. Todo usuário da máquina passa a poder escrever e apagar os documentos dos seus clientes.
  • Esquecer o zero da permissão. mkdir($p, 750) é decimal, vira outra combinação de bits e cria uma pasta com permissões sem sentido.
  • Silenciar com @mkdir e não checar o retorno. O sistema segue achando que a pasta existe e falha mais adiante, num lugar difícil de rastrear.
  • Tratar o nome do usuário como identificador. Dois clientes chamados "Padaria Central" disputam a mesma pasta; o id do banco não repete.

Checklist

  • A raiz está numa constante e fora da pasta pública do site.
  • O nome da pasta é gerado pelo código ou validado por uma regex sem barra e sem ponto.
  • O caminho final, resolvido com realpath, começa pela raiz seguida de barra.
  • A permissão é 0750 ou mais restrita, com o zero na frente.
  • Pasta já existente tem comportamento definido: sucesso silencioso ou erro com mensagem sua.
  • Falha de mkdir gera exceção e entrada no log.
  • Um teste automatizado tenta criar ../fora e confirma que nada foi criado.

Depois de criar a pasta, o próximo passo costuma ser gravar arquivos nela; os cuidados com o que entra estão em upload de arquivo em PHP, e os de leitura em listar arquivos por data sem abrir a pasta do sistema. Para fechar, rode no seu servidor um ls -la na pasta onde o PHP cria diretórios: existe alguma com permissão drwxrwxrwx, e quem decidiu que ela precisava ser assim?

Avalie este artigo

Referência: http://www.blogwebdesignmicrocamp.com.br/programacao/como-criar-pastas-com-php/