The 4th Annual China PHP Conference

Subida con el método POST

Esta característica permite la subida de ficheros de texto y binarios. Con la autenticación de PHP y las funciones de manipulación de ficheros se tiene control completo sobre quién está autorizado a realizar una subida y qué hay que hacer con el fichero una vez subido.

PHP es capaz de recibir subidas de ficheros de cualquier navegador compatible con el RFC-1867.

Nota: Configuraciones relacionadas

Véanse también las directivas file_uploads, upload_max_filesize, upload_tmp_dir, post_max_size y max_input_time de php.ini

PHP también admite el método PUT para subir ficheros tal y como lo utilizan los clientes Netscape Composer y Amaya del W3C. Véase el Soporte para el método PUT para más detalles.

Ejemplo #1 Formulario para la subida de ficheros

Se puede construir una página de subida de ficheros creando un formulario especial parecido a este:

<!-- El tipo de codificación de datos, enctype, DEBE especificarse como sigue -->
<form enctype="multipart/form-data" action="__URL__" method="POST">
    <!-- MAX_FILE_SIZE debe preceder al campo de entrada del fichero -->
    <input type="hidden" name="MAX_FILE_SIZE" value="30000" />
    <!-- El nombre del elemento de entrada determina el nombre en el array $_FILES -->
    Enviar este fichero: <input name="fichero_usuario" type="file" />
    <input type="submit" value="Enviar fichero" />
</form>

El __URL__ del ejemplo anterior se debe sustituir y debe apuntar a un fichero de PHP.

El campo oculto MAX_FILE_SIZE (medido en bytes) debe preceder al campo de entrada del fichero, siendo su valor el tamaño de fichero máximo aceptado por PHP. Se debe utilizar siempre este elemento del formulario, ya que evita a los usuarios la molestia de esperar a que un fichero grande sea transferido sólo para descubrir que falló la transferencia porque era demasiado grande. Hay que tener en cuenta que engañar a esta configuración en el lado del navegador es muy fácil; nunca dependa de que los ficheros que tengan un tamaño mayor sean bloqueados por esta característica. Es simplemente una característica conventiene para los usuarios en el lado cliente de la aplicación. No obstante, la configuración de PHP (en el lado del servidor) para un tamaño máximo no puede ser engañada.

Nota:

Asegúrese de que el formulario de subida de ficheros tiene el atributo enctype="multipart/form-data" o de lo contrario la subida de ficheros no funcionará.

El array global $_FILES contendrá toda la información de los los ficheros subidos. Su contenido en el formulario del ejemplo es el siguiente. Observe que se asume el empleo del nombre fichero_usuario para el fichero subido, tal como se utiliza en el script de ejemplo anterior. Este puede ser cualquier nombre.

$_FILES['fichero_usuario']['name']

El nombre original del fichero en la máquina del cliente.

$_FILES['fichero_usuario']['type']

El tipo MIME del fichero, si el navegador proporcionó esta información. Un ejemplo sería "image/gif". Este tipo MIME, sin embargo, no se comprueba en el lado de PHP y por lo tanto no se garantiza su valor.

$_FILES['fichero_usuario']['size']

El tamaño, en bytes, del fichero subido.

$_FILES['fichero_usuario']['tmp_name']

El nombre temporal del fichero en el cual se almacena el fichero subido en el servidor.

$_FILES['fichero_usuario']['error']

El código de error asociado a esta subida.

Por defecto, los ficheros se almacenan en el directorio temporal predeterminado del servidor, a menos que se haya indicado otra ubicaicón con la directiva upload_tmp_dir en php.ini. Se puede cambiar el directorio predeterminado del servidor estableciendo la variable de entorno TMPDIR en el entorno en que se ejecuta PHP. Configurarlo usando putenv() desde un script de PHP no funcionará. Esta variable de entorno también se puede utilizar para asegurarse de que las demás operaciones están trabajando sobre los ficheros subidos.

Ejemplo #2 Validación de la subida de ficheros

Véanse también las entradas de las funciones is_uploaded_file() y move_uploaded_file() para más información. El siguiente ejemplo procesará la subida de fichero desde un formulario.

<?php
// En versiones de PHP anteriores a la 4.1.0, debería utilizarse $HTTP_POST_FILES en lugar
// de $_FILES.

$dir_subida '/var/www/uploads/';
$fichero_subido $dir_subida basename($_FILES['fichero_usuario']['name']);

echo 
'<pre>';
if (
move_uploaded_file($_FILES['fichero_usuario']['tmp_name'], $fichero_subido)) {
    echo 
"El fichero es válido y se subió con éxito.\n";
} else {
    echo 
"¡Posible ataque de subida de ficheros!\n";
}

echo 
'Más información de depuración:';
print_r($_FILES);

print 
"</pre>";

?>

El script de PHP que recibe el fichero subido debería implementar cualquier lógica necesaria para determinar qué se debe hacer con el fichero subido. Se puede, por ejemplo, utilizar la variable $_FILES['fichero_usuario']['size'] para descartar cualquier fichero que sea demasiado pequeño o demasiado grande. Se podría utilizar la variable $_FILES['fichero_usuario']['type'] para descartar cualquier fichero que no corresponda con un cierto criterio de tipo, aunque esto se debe emplear solo como la primera de una serie de comprobaciones debido a que este valor está completamente bajo el control del cliente y no se comprueba en el lado de PHP. También se puede usar $_FILES['fichero_usuario']['error'] y planear la lógica de acuerdo con los códigos de error. Cualquiera que sea la lógica, se debería borrar el fichero del directorio temporal o moverlo a otra ubicación.

Si no se selecciona ningún fichero en el formulario para realizar la subida, PHP devolverá $_FILES['fichero_usuario']['size'] como 0, y $_FILES['fichero_usuario']['tmp_name'] como ninguno.

El fichero será borrado del directorio temporal al final de la solicitud si este no ha sido movido o renombrado.

Ejemplo #3 Subir un array de ficheros

PHP admite la funcionalidad de array en HTML incluso con ficheros.

<form action="" method="post" enctype="multipart/form-data">
<p>Imágenes:
<input type="file" name="imágenes[]" />
<input type="file" name="imágenes[]" />
<input type="file" name="imágenes[]" />
<input type="submit" value="Enviar" />
</p>
</form>
<?php
foreach ($_FILES["imágenes"]["error"] as $clave => $error) {
    if (
$error == UPLOAD_ERR_OK) {
        
$nombre_tmp $_FILES["imágenes"]["tmp_name"][$clave];
        
$nombre $_FILES["imágenes"]["name"][$clave];
        
move_uploaded_file($nombre_tmp"datos/$nombre");
    }
}
?>

Se puede implementar una barra de progreso de subida de ficheros con el Progreso de subida en sesiones.

add a note add a note

User Contributed Notes 14 notes

up
229
Anonymous
1 year ago
For the love of god, don't do what michael suggests in http://php.net/manual/en/features.file-upload.post-method.php#94973 or you will be instantly pwned by someone uploading a php-shell to your script dir.

When the mods come to delete this note for violating the don't-refer-to-another-note rule, please please /please/ delete michael's note too.
up
7
coreywelch+phpnet at gmail dot com
3 months ago
The documentation doesn't have any details about how the HTML array feature formats the $_FILES array.

Example $_FILES array:

For single file -

Array
(
    [document] => Array
        (
            [name] => sample-file.doc
            [type] => application/msword
            [tmp_name] => /tmp/path/phpVGCDAJ
            [error] => 0
            [size] => 0
        )
)

Multi-files with HTML array feature -

Array
(
    [documents] => Array
        (
            [name] => Array
                (
                    [0] => sample-file.doc
                    [1] => sample-file.doc
                )

            [type] => Array
                (
                    [0] => application/msword
                    [1] => application/msword
                )

            [tmp_name] => Array
                (
                    [0] => /tmp/path/phpVGCDAJ
                    [1] => /tmp/path/phpVGCDAJ
                )

            [error] => Array
                (
                    [0] => 0
                    [1] => 0
                )

            [size] => Array
                (
                    [0] => 0
                    [1] => 0
                )

        )

)

The problem occurs when you have a form that uses both single file and HTML array feature. The array isn't normalized and tends to make coding for it really sloppy. I have included a nice method to normalize the $_FILES array.

<?php

   
function normalize_files_array($files = []) {

       
$normalized_array = [];

        foreach(
$files as $index => $file) {

            if (!
is_array($file['name'])) {
               
$normalized_array[$index][] = $file;
                continue;
            }

            foreach(
$file['name'] as $idx => $name) {
               
$normalized_array[$index][$idx] = [
                   
'name' => $name,
                   
'type' => $file['type'][$idx],
                   
'tmp_name' => $file['tmp_name'][$idx],
                   
'error' => $file['error'][$idx],
                   
'size' => $file['size'][$idx]
                ];
            }

        }

        return
$normalized_array;

    }

?>

The following is the output from the above method.

Array
(
    [document] => Array
        (
            [0] => Array
                (
                [name] => sample-file.doc
                    [type] => application/msword
                    [tmp_name] => /tmp/path/phpVGCDAJ
                    [error] => 0
                    [size] => 0
                )

        )

    [documents] => Array
        (
            [0] => Array
                (
                    [name] => sample-file.doc
                    [type] => application/msword
                    [tmp_name] => /tmp/path/phpVGCDAJ
                    [error] => 0
                    [size] => 0
                )

            [1] => Array
                (
                    [name] => sample-file.doc
                    [type] => application/msword
                    [tmp_name] => /tmp/path/phpVGCDAJ
                    [error] => 0
                    [size] => 0
                )

        )

)
up
53
daevid at daevid dot com
6 years ago
I think the way an array of attachments works is kind of cumbersome. Usually the PHP guys are right on the money, but this is just counter-intuitive. It should have been more like:

Array
(
    [0] => Array
        (
            [name] => facepalm.jpg
            [type] => image/jpeg
            [tmp_name] => /tmp/phpn3FmFr
            [error] => 0
            [size] => 15476
        )

    [1] => Array
        (
            [name] =>
            [type] =>
            [tmp_name] =>
            [error] => 4
            [size] =>
        )
)

and not this
Array
(
    [name] => Array
        (
            [0] => facepalm.jpg
            [1] =>
        )

    [type] => Array
        (
            [0] => image/jpeg
            [1] =>
        )

    [tmp_name] => Array
        (
            [0] => /tmp/phpn3FmFr
            [1] =>
        )

    [error] => Array
        (
            [0] => 0
            [1] => 4
        )

    [size] => Array
        (
            [0] => 15476
            [1] => 0
        )
)

Anyways, here is a fuller example than the sparce one in the documentation above:

<?php
foreach ($_FILES["attachment"]["error"] as $key => $error)
{
      
$tmp_name = $_FILES["attachment"]["tmp_name"][$key];
       if (!
$tmp_name) continue;

      
$name = basename($_FILES["attachment"]["name"][$key]);

    if (
$error == UPLOAD_ERR_OK)
    {
        if (
move_uploaded_file($tmp_name, "/tmp/".$name) )
           
$uploaded_array[] .= "Uploaded file '".$name."'.<br/>\n";
        else
           
$errormsg .= "Could not move uploaded file '".$tmp_name."' to '".$name."'<br/>\n";
    }
    else
$errormsg .= "Upload error. [".$error."] on file '".$name."'<br/>\n";
}
?>
up
5
anon
10 months ago
For clarity; the reason you would NOT want to replace the example script with
$uploaddir = './';
is because if you have no coded file constraints a nerd could upload a php script with the same name of one of your scripts in the scripts directory.

Given the right settings and permissions php-cgi is capable of replacing even php files.

Imagine if it replaced the upload post processor file itself. The next "upload" could lead to some easy exploits.

Even when replacements are not possible; uploading an .htaccess file could cause some problems, especially if it is sent after the nerd throws in a devious script to use htaccess to redirect to his upload.

There are probably more ways of exploiting it. Don't let the nerds get you.

More sensible to use a fresh directory for uploads with some form of unique naming algorithm; maybe even a cron job for sanitizing the directory so older files do not linger for too long.
up
8
eslindsey at gmail dot com
7 years ago
Also note that since MAX_FILE_SIZE hidden field is supplied by the browser doing the submitting, it is easily overridden from the clients' side.  You should always perform your own examination and error checking of the file after it reaches you, instead of relying on information submitted by the client.  This includes checks for file size (always check the length of the actual data versus the reported file size) as well as file type (the MIME type submitted by the browser can be inaccurate at best, and intentionally set to an incorrect value at worst).
up
2
Mark
5 years ago
$_FILES will be empty if a user attempts to upload a file greater than post_max_size in your php.ini

post_max_size should be >= upload_max_filesize in your php.ini.
up
-1
Age Bosma
4 years ago
"If no file is selected for upload in your form, PHP will return $_FILES['userfile']['size'] as 0, and $_FILES['userfile']['tmp_name'] as none."

Note that the situation above is the same when a file exceeding the MAX_FILE_SIZE hidden field is being uploaded. In this case $_FILES['userfile']['size'] is also set to 0, and $_FILES['userfile']['tmp_name'] is also empty. The difference would only be the error code.
Simply checking for these two conditions and assuming no file upload has been attempted is incorrect.

Instead, check if $_FILES['userfile']['name'] is set or not. If it is, a file upload has at least been attempted (a failed attempt or not). If it is not set, no attempt has been made.
up
-5
claude dot pache at gmail dot com
7 years ago
Note that the MAX_FILE_SIZE hidden field is only used by the PHP script which receives the request, as an instruction to reject files larger than the given bound. This field has no significance for the browser, it does not provide a client-side check of the file-size, and it has nothing to do with web standards or browser features.
up
-5
milosdj at gmail dot com
1 year ago
MAX_FILE_SIZE is completely useless from browser perspective. Up to this day there is no browser that supports it.

From PHP's point there is one mention to max_file_size in source code.
http://stackoverflow.com/a/6273418/352260

In essence, no need to use MAX_FILE_SIZE until browsers start to support it. Which will probably never happen.
But if html5 kicks in, input maxlength attribute might become applicable to file input type.
up
-9
bimal at sanjaal dot com
1 year ago
Some suggestions here:

1. It is always better to check for your error status. If MAX_FILE_SIZE is active and the uploaded file crossed the limit, it will set the error. So, only when error is zero (0), move the file.

2. If possible, never allow your script to upload in the path where file can be downloaded. Point your upload path to outside of public_html area or prevent direct browsing (using .htaccess restrictions). Think, if someone uploads malicious code, specially php codes, they will be executed on the server.

3. Do not use the file name sent by the client. Regenerate a new name for newly uploaded file. This prevents overwriting your old files.

4. Regularly track the disk space consumed, if you are running out of storage.
up
-19
Anonymous
3 years ago
Normalizing $_FILES structure:

<?php
    $files
= [];
   
$fix = function (&$files, $values, $prop) use (&$fix) {
        foreach (
$values as $key => $value) {
            if (
is_array($value)) {
               
$fix($files[$key], $value, $prop);
            } else {
               
$files[$key][$prop] = $value;
            }
        }
    };
    foreach (
$_FILES as $name => $props) {
        foreach (
$props as $prop => $value) {
            if (
is_array($value)) {
               
$fix($files[$name], $value, $prop);
            } else {
               
$files[$name][$prop] = $value;
            }
        }
    }
?>
up
-18
mail at markuszeller dot com
5 years ago
If you want to increase the upload size, it could make sense to allow it only to a specified directory and not in the php.ini for the whole domain or server. In my case it worked very well placing that into the .htaccess file like this:

php_value    upload_max_filesize    100M
php_value    post_max_size    101M

Remember, post_max_size must be bigger than the upload_max_filesize.
up
-25
zingaburga at hotmail dot com
5 years ago
The documentation is a little unclear about the MAX_FILE_SIZE value sent in the form with multiple file input fields.  The following is what I have found through testing - hopefully it may clarify it for others.

The MAX_FILE_SIZE is applied to each file (not the total size of all files) and to all file inputs which appear after it.  This means that it can be overridden for different file fields.  You can also disable it by sending no number, or sending 0 (probably anything that == '0' if you think about it).

Example:

<form enctype="multipart/form-data" action="." method="POST">
  <!-- no maximum size for userfile0 -->
  <input name="userfile0" type="file" />

  <input type="hidden" name="MAX_FILE_SIZE" value="1000" />
  <!-- maximum size for userfile1 is 1000 bytes -->
  <input name="userfile1" type="file" />
  <!-- maximum size for userfile2 is 1000 bytes -->
  <input name="userfile2" type="file" />

  <input type="hidden" name="MAX_FILE_SIZE" value="2000" />
  <!-- maximum size for userfile3 is 2000 bytes -->
  <input name="userfile3" type="file" />

  <input type="hidden" name="MAX_FILE_SIZE" value="0" />
  <!-- no maximum size for userfile4 -->
  <input name="userfile4" type="file" />
</form>
up
-25
gustavo at roskus dot com
4 years ago
Some hosting does not have permission to use the move_uploaded_file function should replace the copy function
To Top