¿Cómo manejar con gracia archivos que exceden el `post_max_size` de PHP?

86

Estoy trabajando en un formulario PHP que adjunta un archivo a un correo electrónico y trato de manejar con elegancia los casos en los que el archivo cargado es demasiado grande.

Aprendí que hay dos configuraciones php.inique afectan el tamaño máximo de la carga de un archivo: upload_max_filesizey post_max_size.

Si el tamaño de un archivo excede upload_max_filesize, PHP devuelve el tamaño del archivo como 0. Eso está bien; Puedo comprobar eso.

Pero si excede post_max_size, mi script falla silenciosamente y vuelve al formulario en blanco.

¿Hay alguna forma de detectar este error?

Nathan Long
fuente
1
¿Tiene acceso a php.ini? post_max_size debe establecerse más grande que upload_max_filesize. También debe usar <input type = "hidden" name = "MAX_FILE_SIZE" value = "30000" /> en el formulario como se describe en ca2.php.net/manual/en/features.file-upload.post-method.php
Matt McCormick
@Matt McCormick - la entrada MAX_FILE_SIZE funciona muy bien - si el tamaño del archivo excede eso, el tamaño del archivo ahora se muestra como 0, que es un caso que ya he manejado. Aunque esto puede ser evitado por un usuario malintencionado, sirve para mis propósitos aquí, porque solo estoy tratando de fallar con gracia para los usuarios regulares.
Nathan Long

Respuestas:

55

De la documentación :

Si el tamaño de los datos de la publicación es mayor que post_max_size, las superglobales $ _POST y $ _FILES están vacías . Esto se puede rastrear de varias maneras, por ejemplo, pasando la variable $ _GET al script que procesa los datos, es decir, <form action = "edit.php? Processing = 1">, y luego verificando si $ _GET ['procesado'] está conjunto.

Entonces, desafortunadamente, no parece que PHP envíe un error. Y dado que envía una matriz $ _POST vacía, es por eso que su script está volviendo al formulario en blanco, no cree que sea una POST. (En mi humilde opinión, una decisión de diseño bastante pobre)

Este comentarista también tiene una idea interesante.

Parece que una forma más elegante es la comparación entre post_max_size y $ _SERVER ['CONTENT_LENGTH']. Tenga en cuenta que este último incluye no solo el tamaño del archivo cargado más los datos de publicación, sino también las secuencias de varias partes.

Matt McCormick
fuente
3
Por favor lea la parte en negrita. Es decir, si el tamaño del archivo excede upload_max_filesize, el usuario pregunta qué sucede cuando el formulario excede post_max_size. post_max_size debe establecerse más alto que upload_max_filesize para tratar de evitar este problema, pero el OP puede tener razones para mantenerlo igual.
Matt McCormick
1
Lo siento, confundí post_max_size y upload_max_filesize. +1
Pekka
@Matt - post_max_size IS establecido más alto que upload_max_filesize, pero todavía obtengo el error si la carga excede ambos. Si se encuentra entre los dos, veo que el tamaño del archivo muestra 0.
Nathan Long
1
Lo ha resuelto, pero sí, post_max_size está disponible. Simplemente haz ini_get ('post_max_size'). ini_get () también se puede usar para verificar otras configuraciones INI.
Matt McCormick
1
@SavasVedova: gracias por señalarlo, pero ya no puedo solucionar problemas. Han pasado algunos años y ya no trabajo en esa empresa ni en PHP. :)
Nathan Long
45

hay una forma de capturar / manejar archivos que exceden el tamaño máximo de publicación, esta es mi preferencia, ya que le dice al usuario final lo que sucedió y quién tiene la culpa;)

if (empty($_FILES) && empty($_POST) &&
        isset($_SERVER['REQUEST_METHOD']) &&
        strtolower($_SERVER['REQUEST_METHOD']) == 'post') {
    //catch file overload error...
    $postMax = ini_get('post_max_size'); //grab the size limits...
    echo "<p style=\"color: #F00;\">\nPlease note files larger than {$postMax} will result in this error!<br>Please be advised this is not a limitation in the CMS, This is a limitation of the hosting server.<br>For various reasons they limit the max size of uploaded files, if you have access to the php ini file you can fix this by changing the post_max_size setting.<br> If you can't then please ask your host to increase the size limits, or use the FTP uploaded form</p>"; // echo out error and solutions...
    addForm(); //bounce back to the just filled out form.
}
else {
    // continue on with processing of the page...
}
AbdullahAJM
fuente
2
Esto funciona cuando track_errors = Off, posiblemente también display_errors = Off y display_startup_errors = Off en php.ini. De lo contrario, PHP ni siquiera llegará tan lejos y enviará la advertencia, como en el título de la pregunta. Pero en un sistema de producción, esta debería ser la configuración php.ini, por lo que funciona muy bien.
raoulsson
2
Esta ingeniosa idea funciona bien en mis pruebas (PHP / 5.5.8). También se puede mejorar teniendo $_SERVER['CONTENT_LENGTH']y upload_max_filesizeen cuenta.
Álvaro González
A partir del comentario de raoulsson , ¿hay alguna forma de suprimir la advertencia si no se encuentra en un entorno con errores suprimidos?
brendo
6

Tenemos el problema de las solicitudes SOAP en las que una verificación de vacío de $ _POST y $ _FILES no funciona, porque también están vacíos en solicitudes válidas.

Por lo tanto, implementamos una verificación, comparando CONTENT_LENGTH y post_max_size. La excepción lanzada se transforma más tarde en un XML-SOAP-FAULT por nuestro controlador de excepciones registrado.

private function checkPostSizeExceeded() {
    $maxPostSize = $this->iniGetBytes('post_max_size');

    if ($_SERVER['CONTENT_LENGTH'] > $maxPostSize) {
        throw new Exception(
            sprintf('Max post size exceeded! Got %s bytes, but limit is %s bytes.',
                $_SERVER['CONTENT_LENGTH'],
                $maxPostSize
            )
        );
    }
}

private function iniGetBytes($val)
{
    $val = trim(ini_get($val));
    if ($val != '') {
        $last = strtolower(
            $val{strlen($val) - 1}
        );
    } else {
        $last = '';
    }
    switch ($last) {
        // The 'G' modifier is available since PHP 5.1.0
        case 'g':
            $val *= 1024;
            // fall through
        case 'm':
            $val *= 1024;
            // fall through
        case 'k':
            $val *= 1024;
            // fall through
    }

    return $val;
}
staabm
fuente
@ manuel-azar: los stements de "rotura" añadidos no son correctos. esos no pertenecen allí .. ver 3v4l.org/ABfGs
staabm
No es necesario verificar el cheque si el tamaño excede. No habría content_length si no hubiera ningún archivo. Por lo tanto, solo tiene que verificar si content_length está configurado y las variables de publicación y archivo están vacías. ¿No?
ADJenks
4

Sobre la base de las respuestas de @Matt McCormick y @ AbdullahAJM, aquí hay un caso de prueba de PHP que verifica que las variables utilizadas en la prueba estén configuradas y luego verifica si $ _SERVER ['CONTENT_LENGTH'] excede la configuración de php_max_filesize:

            if (
                isset( $_SERVER['REQUEST_METHOD'] )      &&
                ($_SERVER['REQUEST_METHOD'] === 'POST' ) &&
                isset( $_SERVER['CONTENT_LENGTH'] )      &&
                ( empty( $_POST ) )
            ) {
                $max_post_size = ini_get('post_max_size');
                $content_length = $_SERVER['CONTENT_LENGTH'] / 1024 / 1024;
                if ($content_length > $max_post_size ) {
                    print "<div class='updated fade'>" .
                        sprintf(
                            __('It appears you tried to upload %d MiB of data but the PHP post_max_size is %d MiB.', 'csa-slplus'),
                            $content_length,
                            $max_post_size
                        ) .
                        '<br/>' .
                        __( 'Try increasing the post_max_size setting in your php.ini file.' , 'csa-slplus' ) .
                        '</div>';
                }
            }
Lance Cleveland
fuente
Esta es la forma más lógica de hacerlo. Solo verifique si _post está vacío pero la longitud del contenido no lo está. No es necesario comparar tamaños.
ADJenks
1

Esa es una forma sencilla de solucionar este problema:

Simplemente llame a "checkPostSizeExceeded" al comienzo de su código

function checkPostSizeExceeded() {
        if (isset($_SERVER['REQUEST_METHOD']) and $_SERVER['REQUEST_METHOD'] == 'POST' and
            isset($_SERVER['CONTENT_LENGTH']) and empty($_POST)//if is a post request and $_POST variable is empty(a symptom of "post max size error")
        ) {
            $max = get_ini_bytes('post_max_size');//get the limit of post size 
            $send = $_SERVER['CONTENT_LENGTH'];//get the sent post size

            if($max < $_SERVER['CONTENT_LENGTH'])//compare
                throw new Exception(
                    'Max size exceeded! Were sent ' . 
                        number_format($send/(1024*1024), 2) . 'MB, but ' . number_format($max/(1024*1024), 2) . 'MB is the application limit.'
                    );
        }
    }

Recuerde copiar esta función auxiliar:

function get_ini_bytes($attr){
    $attr_value = trim(ini_get($attr));

    if ($attr_value != '') {
        $type_byte = strtolower(
            $attr_value{strlen($attr_value) - 1}
        );
    } else
        return $attr_value;

    switch ($type_byte) {
        case 'g': $attr_value *= 1024*1024*1024; break;
        case 'm': $attr_value *= 1024*1024; break;
        case 'k': $attr_value *= 1024; break;
    }

    return $attr_value;
}
Doglas
fuente