O problema resume-se em enviar uma mensagem amigável de "Este arquivo excede o tamanho permitido" (ou algo parecido) quando se permite upload via POST de determinados arquivos na aplicação/site em PHP porque o PHP não possui um tipo
unknown.
A diretiva do arquivo
php.ini upload_max_filesize define o tamanho máximo de arquivo que um usuário pode enviar, enquanto
post_max_size define a quantidade máxima de dados que podem ser enviados por meio de um POST em um formulário. Por exemplo, você pode definir
upload_max_filesize como 1 megabyte no
php.ini, o que significa que o maior arquivo único que um usuário enviará terá 1 megabyte, porém, esse usuário pode enviar 5 arquivos de 1 megabyte se
post_max_size estiver definido como 5 megabytes.
Lembrando que, para permitir upload de vários arquivos ao mesmo tempo, é preciso adicionar a propriedade "multiple" ao elemento input no HTML:
<input type="file" id="meuinput" multiple>
Aqui não usaremos essa propriedade 'multiple', pois só permitiremos o upload de um arquivo por vez.
As diretivas
file_uploads e
max_file_uploads devem estar configuradas no
php.ini. Os códigos dos arquivos estão na seção "A Solução".
Nível Intermediário.
Primeiro veremos as diretivas do
php.ini:
- - file_uploads = On (Permite uploads de arquivos HTTP);
- - max_file_uploads = 10 (Número máximo de arquivos que podem ser enviados em uma única requisição);
- - post_max_size = 2M (Tamanho máximo dos dados POST que o PHP aceitará, valor 0 desabilita o limite. É ignorada se a leitura dos dados POST está desabilitada por meio de enable_post_data_reading = Off, ou seja, para post_max_size funcionar, enable_post_data_reading deve estar no padrão (comentada) ou não comentada com On;
- - ;enable_post_data_reading = Off
ou
- enable_post_data_reading = On;
- upload_max_filesize = 2M (Tamanho máximo permitido para envio de um arquivo);
Algumas vezes, caso use FCGID com Apache (FPM/FASTCGI), Nginx, etc, tem de se alterar também os arquivos nesses programas para não dar ERRO 500, 413, 400, etc. Por exemplo, no Apache em algumas distribuições
Linux tem de se alterar o arquivo /etc/apache2/mods-available/fcgid.conf (ou num vhost.conf) acrescentando uma linha assim:
"FcgidMaxRequestLen 10485760" (10M) tendo o cuidado de deixar esse valor com o mesmo valor de
upload_max_filesize no
php.ini.
É que o limite para o tamanho do corpo da requisição
HTTP estabelecido pelo módulo FastCGI costuma ser de 128 KBytes (131072 bytes) no Apache e 1M no Nginx, o que afeta o tamanho do arquivo para upload com POST, já que o mesmo é enviado no corpo da requisição.
Apache:
Nginx:
Para fins de informação, na RFC 9110 na seção "18.3. Status Code Registration" (link também nas referências) tem os códigos 1XX, 2XX, 3XX, 4XX e 5XX.
O PHP não capta o tamanho do arquivo da aplicação/site quando as diretivas
post_max_size e
upload_max_filesize estiverem setadas no
php.ini para menos do que o tamanho do arquivo enviado (o que sempre acontecerá, pois é isso mesmo que queremos: limitar o tamanho do upload), porém, o PHP "pega" o tamanho do arquivo no cabeçalho da solicitação
HTTP quando esta chega no servidor. Por exemplo: quero enviar uma mensagem ao usuário de que o arquivo excedeu o tamanho permitido de upload para determinada aplicação/site, sendo que as diretivas
post_max_size e
upload_max_filesize estão setadas em 2M e o usuário tenta enviar um arquivo de 10M;
neste caso, a mensagem de "O arquivo excede o limite..." não será exibida com um código simples porque a variável $_FILES['arquivo']['size'] vem igual a NULL e isso acontece porque o PHP é executado no servidor.
Outro exemplo: caso as diretivas no
php.ini estiverem em 10M e o usuário envia um arquivo de 15M, a mensagem não é exibida porque $_FILES['arquivo']['size'] vem igual a NULL da mesma maneira. Para exibir a mensagem devem-se aumentar as diretivas, porém, isso representa uma incongruência e uma possível falha de segurança, pois estou permitindo no
php.ini uploads maiores do que o desejado e/ou do que o servidor suporta no conjunto das requisições feitas por vários clientes e, muitas vezes, ao mesmo tempo. Além disso, para quanto devo aumentar as diretivas?
Não se sabe o tamanho do arquivo ou quantos arquivos um usuário mal intencionado tentará enviar. Alguns colocam um .htaccess no Apache e em conjunto aumentam as diretivas, porém, não é uma boa prática. Aliás, o próprio Apache e o Nginx desaconselham o uso de tal arquivo. O .htaccess é aconselhado somente onde tenha vários usuários do mesmo servidor web, como, por exemplo, um provedor VPS... e ainda assim com restrições.
Para quem tenha interesse, deixo aqui um conversor htaccess para Nginx:
Quando o arquivo é maior do que o permitido e não foram feitas as configurações necessárias, aparece no navegador aquela mensagem padrão do servidor web como, por exemplo, a mensagem do Nginx: "413 Request Entity Too Large nginx/1.10.3" (A entidade solicitada é muito grande), mensagem esta ininteligível para o usuário.
Vamos a um exemplo no caso que nos interessa. Considerando o seguinte trecho de código abaixo, que está comentado no código da próxima página, mas utilizei para teste:
var_dump($_FILES['arquivo']['size']);
$arquivo = $_FILES['arquivo'];
switch($arquivo) {
case ($arquivo['size'] > (2097152)): //2MB
echo "Este arquivo excede o tamanho de 2MB!";
break;
}
Enviando um arquivo maior do que o limite setado no
php.ini gerou o seguinte aviso:
Warning: Undefined array key "arquivo" in /var/www/html/testup/upload.php on line 62
E o
"var_dump($_FILES['arquivo']]['size']);"
gerou o seguinte:
/var/www/html/testup/upload.php:62:null
E enviando um arquivo dentro do limite, no caso um arquivo com 312,4KB, gerou somente o var_dump, o que é óbvio:
/var/www/html/testup/upload.php:62:int 319925
No caso da diretiva
upload_max_file_size estar setada no
php.ini em, por exemplo, 10M, se o usuário enviar um arquivo de 15M o PHP não exibe a mensagem porque a variável $arquivo['size'] vem como NULL. Com
if else acontece a mesma coisa (entre
if else e
switch procuro sempre utilizar quando possível o
switch, pois a diferença de desempenho em relação ao
if else é muito melhor e bem mais rápido).
E caso você colocar no código:
case ($arquivo['size'] > (10485760)): //10MB
ainda assim o PHP não enviará mensagem e permitirá uploads via POST até o limite permitido no
php.ini, no caso, 10MB. Alguns aumentam os limites para 100MB ou mais para poder enviar mensagem ou para evitar o "Warning" do PHP. Contudo, aumentar os limites para 100MB, por exemplo, permitirá que um usuário mal intencionado burle o HTML e envie arquivos com 100MB podendo saturar e travar o servidor, pois você mesmo configurou o PHP para aceitar uploads de 100MB. Lembrando que a diretiva 'max_file_uploads' (número máximo de arquivos que podem ser enviados em uma única requisição) vem com 20 como padrão, ou seja, 20 arquivos de 100MB são 2GB de upload, isso somente de um cliente.
Setando agora o
upload_max_filesize em 2.5MB e o
post_max_size em 2MB, o
var_dump($bytesup) nos diz que a conversão está sendo feita:
/var/www/html/testup/upload.php:51:float 2621440 //2.5MB
Na seção "A Solução" no código "upload.php" tem a função que converte string em bytes.
A questão é que por estarem setados os parâmetros no
php.ini (e não pode ser diferente), eles vem como NULL quando o arquivo enviado excede o tamanho permitido.
Quando se clica no botão "Enviar" (input type="submit" name="enviar_arquivo" value="Enviar") depois de selecionado um arquivo maior do que 2.5MB, no caso um arquivo de 8.3MB, temos no var_dump($value):
/var/www/html/testup/upload.php:53:
array(size=1)
0 => string '8716100'(length=7)
Que gerará no PHP o seguinte aviso:
warning: POST Content-Length of 8716100 bytes exceeds the limit of 2097152 bytes in Unknown on line 0
Que nos diz que o Content-Length – tamanho do arquivo selecionado – excede o limite em
upload_max_filesize. Sabemos que é em
upload_max_filesize porque a variável $uploadmaximo recebe o valor de ini_get(
upload_max_filesize) para a conversão, além do que, como foi falado antes, o
upload_max_filesize está em 2.5MB e o
post_max_size em 2MB.
A questão agora é que o PHP só identifica o tamanho do arquivo enviado pelo
HTTP quando a solicitação chega (o que é óbvio, pois o PHP é executado no servidor) e também porque o PHP não tem um tipo
unknown, somente um tipo
mixed.
Pode se configurar o HTML para restringir os uploads com um input escondido e um input que restringe para aceitar somente PDF:
<input type="hidden" name="MAX_FILE_SIZE" value="2097152">
<input type="file" name="arquivo" id="arquivo" class="inputfile inputfile-1" accept="application/pdf,.pdf">
Porém, o HTML pode ser burlado mais facilmente do que o PHP, mas é obrigatório fazer as sanitizações tanto no cliente quanto no servidor.
A estilização em CSS, caso for colocar em produção os arquivos, aconselho a colocar num arquivo CSS em separado e não deixar inline como está.
Aliás, sugiro fortemente nunca colocar CSS nem Javascript inline no HTML.
Para resolver o problema de enviar uma mensagem amigável, vamos aos códigos na página 2.