Pagina 1 di 2 1 2 ultimoultimo
Visualizzazione dei risultati da 1 a 10 su 14
  1. #1

    [pillola] Le basi dell'upload di file

    Capita frequentemente su questo forum di ricevere sollecitazioni in merito ai processi di upload di file tramite PHP. Molto spesso questi problemi sono dovuti ad un'insufficiente conoscenza di questa materia. Ho pensato quindi di scrivere questa breve pillola per chiarire i concetti principali e terminerò postandovi la mia classe Upload, in grado di gestire oltre al caricamento dei file anche diverse funzioni relative alla sicurezza ed alla gestione degli errori.

    1. Configurazione di PHP
    Ma innanzitutto vediamo alcune direttive importanti del file php.ini che potrebbero condizionare l'esito del nostro upload:

    post_max_size: di default é settato a 8M
    upload_max_filesize: di default é settato a 2M

    Se la configurazione di php non é stata modificata potremo quindi caricare dei file aventi una dimensione massima di 2M. Per file di dimensioni maggiori dovremo considerare l'utilizzo di un sistema ftp.


    2. Il form
    Iniziamo da un esempio di form.
    Codice PHP:
    <form action="upload.php" method="POST" enctype="multipart/form-data"
        <
    input type="hidden" name="MAX_FILE_SIZE" value="700000" /> 
        <
    input type="file" name="upfile" value="" /> 
        <
    input type="hidden" name="control" value="upload" /> 
        <
    input type="submit" value="upload" /> 
    </
    form
    Iniziamo dalla dichiarazione del form. Il metodo dovrà essere ovviamente POST ed é fondamentale che l'attributo enctype sia multipart/form-data.

    Il primo input é facoltativo e serve a definire una dimensione massima del file che verrà caricato espressa in byte; chiaramente questo valore avrà senso solo se inferiore al valore di upload_max_filesize del php.ini. La dichiarazione MAX_FILE_SIZE deve avvenire prima dell'input relativo al file.

    Il secondo input é quello relativo al file appunto. L'attributo name ci permetterà in seguito di manipolare l'array S_FILES, che come vedremo é ricco di informazioni.

    Il terzo input é facoltativo. Semplicemente invio un campo nascosto con un nome e un valore che mi permetteranno in seguito di verificare che il file arrivi davvero dal mio form. E' un po' una ridondanza in quanto, come vedremo, la funzione is_uploaded_file() serve già a verificare che il file provenga da una richiesta HTTP POST e quindi può essere considerato legittimo. Ma con i balordi che ci sono in giro, non si é mai tropo prudenti.


    3. L'array $_FILES
    Anche se abbiamo utilizzato il metodo POST, ritroveremo le informazioni relative al nostro upload nell'array $_FILES.
    Come attributo name abbiamo utilizzato upfile per il nostro file, dunque avremo le seguenti informazioni:

    $_FILES['upfile']['name'] : E' il nome originale del file.
    $_FILES['upfile']['tmp_name] : Contiene il nome del file temporaneo. Inizialmente il nostro file viene caricato nella cartella temporanea, che in assenza di altre direttive corrisponde alla cartella temporanea di sistema.
    $_FILES['upfile']['size'] : La dimensione del file espressa in byte.
    $_FILES['upfile']['error'] : Restituisce un codice numerico di errore. Per ogni numero, PHP dispone di costanti predefinite che contengono questo valore; L'utilizzo di queste costanti renderà più leggibile il codice. Vediamo questi errori:

    0 = UPLOAD_ERR_OK. Il file é stato caricato correttamente.
    1 = UPLOAD_ERR_INI_SIZE. La dimensione del file supera il valore impostato nella specifica direttiva di php.ini
    2 = UPLOAD_ERR_FORM_SIZE. La dimensione del file supera il valore impostato nel form tramite MAX_FILE_SIZE.
    3 = UPLOAD_ERR_PARTIAL. Il file é stato inviato solo parzialmente.
    4 = UPLOAD_ERR_NO_FILE. Il file non é stato inviato.
    6 = UPLOAD_ERR_NO_TMP_DIR. Mancanza di una directory temporanea.
    7 = UPLOAD_ERR_CANT_WRITE. Impossibile scrivere su disco.


    4. Controlli e finalizzazione dell'upload
    Il primo controllo che possiamo fare é verificare la presenza del campo di controllo:

    Codice PHP:
    if($_POST['control'] == “upload”
    In seguito controlleremo se il file caricato nella directory temporanea é leggittimamente inviato dal protocollo HTTP

    Codice PHP:
    if(is_uploaded_file($_FILES['upfile']['tmp_name'])) 
    Inoltre verifichiamo l'assenza di errori:

    Codice PHP:
    if($_FILES['upfile']['error'] == UPLOAD_ERR_OK
    Se queste minime condizioni vengono soddisfatte, si potrà trasferire il file temporaneo nella directory di destinazione.

    Codice PHP:
    move_uploaded_file($_FILES['upfile']['tmp_name'], ./destinazione/” $_FILES['upfile']['name']); 
    Personalmente proporrei ancora un controllo come minimo:
    Se il nome del file esiste già nella cartella di destinazione, il file presente verrà sovrascritto. Se non desideriamo questo comportamento, prima di spostare il file dovremo verificare la cartella di destinazione.

    Codice PHP:
    $files scandir(./destinazione”); 

                    if(
    in_array($_FILES['upfile']['name'], $files)) 
                    { 
                        
    //non spostare il file
                


    5. La mia classe Upload
    Vi posto la mia classe Upload seguita da un commento

    Codice PHP:
    class Upload 

        public      
    $SavePath
        public      
    $ErrorReport
        protected   
    $file = array(); 
        protected   
    $AllowedExtensions = array(); 
        protected   
    $SecurityPostName
        protected   
    $SecurityPostKey
        protected   
    $HiddenSecurity
        protected   
    $OverWrite

            public function 
    __construct($path$name$overwrite TRUE$extensions ""$PostName =""$PostKey ""
            { 
                
    $this->SavePath $path
                
    $this->file $_FILES[$name]; 
                
    $this->OverWrite $overwrite

                if (
    $extensions
                { 
                    
    $this->AllowedExtensions $this->ParseExtensions($extensions); 
                } 
                else 
                { 
                    
    $this->AllowedExtensions FALSE
                } 

                if(
    $PostName AND $PostKey
                { 
                    
    $this->SecurityPostKey $PostKey
                    
    $this->SecurityPostName $PostName
                    
    $this->HiddenSecurity TRUE
                } 
                else 
                { 
                    
    $this->HiddenSecurity FALSE
                } 

                
    $this->ErrorManagement(); 

            } 
     
            private function 
    ParseExtensions($string
            { 
                
    $ExtensionsArray explode(","$string); 
                return 
    $ExtensionsArray
            } 

            private function 
    FileExtention() 
            { 
                
    $exp explode("."$this->file['name']); 
                
    $exp array_reverse($exp); 
                return 
    $exp[0]; 
            } 


            protected function 
    ErrorManagement() 
            { 
                if (
    $this->HiddenSecurity
                { 
                    if (
    $_POST[$this->SecurityPostName] != $this->SecurityPostKey
                    { 
                        
    $this->ErrorReport "Tentativo non permesso di upload - possibile tentativo di forzatura"
                        return 
    FALSE
                    } 
                } 

                if (
    $this->AllowedExtensions
                { 
                    if (!
    in_array($this->FileExtention(), $this->AllowedExtensions)) 
                    { 
                        
    $this->ErrorReport "Estensione file non accettata"
                        return 
    FALSE
                    } 
                } 

                if (!
    $this->OverWrite
                { 
                    
    $files scandir($this->SavePath); 

                    if(
    in_array($this->file['name'], $files)) 
                    { 
                        
    $this->ErrorReport "Il nome del file caricato esiste già nella cartella di destinazione"

                        return 
    FALSE
                    } 
                } 

                switch(
    $this->file['error']) 
                { 
                    case 
    UPLOAD_ERR_OK
                    
    $this->ErrorReport "File caricato correttamente"
                    
    $this->SaveUploadFile(); 
                    break; 

                    case 
    UPLOAD_ERR_INI_SIZE
                    
    $this->ErrorReport "Il file supera la dimensione massima impostata nel file php.ini (direttiva upload_max_filesize)"
                    break; 

                    case 
    UPLOAD_ERR_FORM_SIZE
                    
    $this->ErrorReport "Il file supera la dimensione massima impostata nel form"
                    break; 

                    case 
    UPLOAD_ERR_PARTIAL
                    
    $this->ErrorReport "Il file é stato caricato solo parzialmente"
                    break; 

                    case 
    UPLOAD_ERR_NO_FILE
                    
    $this->ErrorReport "Nessun file é stato caricato"
                    break; 

                    case 
    UPLOAD_ERR_NO_TMP_DIR
                    
    $this->ErrorReport "Nessuna cartella temporanea impostata"
                    break; 

                    case 
    UPLOAD_ERR_CANT_WRITE
                    
    $this->ErrorReport "Impossibile scrivere sul disco"
                    break; 
                }  
            } 

            protected function 
    SaveUploadFile() 
            { 
                if(
    is_uploaded_file($this->file['tmp_name'])) 
                { 
                    
    move_uploaded_file($this->file['tmp_name'], $this->SavePath $this->file['name']); 
                    return 
    TRUE;
                }
                else
                {
                     
    $this->ErrorReport "Tentativo non permesso di upload - possibile tentativo di forzatura"
                     return 
    FALSE
                 }
            } 

    Iniziamo dagli argomenti passati dal costruttore:

    $pach = Il percorso dove verrà salvato il file, deve terminare con /
    $name = L'attributo name del file nel form (nel nostro caso "upfile").
    $overwrite (facoltativo) = TRUE di default, significa che se il nome file esiste già nella destinazione, il file verrà sovrascritto. Impostando su FALSE verrà interrotto il processo e restituito un errore tramite la proprietà ErrorReport.
    $extensions (facoltativo) = Di default non contiene nulla, dunque vengono accettate tutte
    le estensioni. Se si volessero accettare solo certe estensioni, basta inserirle separandole con la virgola ("jpg,png,gif").
    $PostName (facoltativo) = Il name dell'input di controllo (nel nostro caso "control").
    $PostKey (facoltativo) = Il valore dell'input di controllo (nel nostro caso "upload").

    Il costruttore, alla fine, chiama il metodo ErrorManagement. Questo metodo chiamerà poi, solo in assenza di errori, il metodo SaveUploadFile(). Diversamente valorizzerà la proprietà ErrorReport e teminerà il codice senza fare altro.

    Dunque il file upload.php nel quale verremo indirizzati dal form avrà questo aspetto

    Codice PHP:
    include 'upload.class.php';

    $upload = new Upload(./uploads/“upfile”TRUE“jpg,gif,png”“control”“upload”);

    // Eventualmente

    echo $upload->ErrorReport
    PHP LEARN - Guide, tutorial e articoli sempre aggiornati
    NUOVO: standardLib; il potente framework PHP é ora disponibile
    *******************************************
    Scarica oggi la tua copia di MtxEventManager

  2. #2
    Sei sempre un grande!
    Non ho provato il codice ma a prima vista mi sembra davvero molto ben fatto!

    <ALCIO />
    Per cortesia: no PVT Tecnici
    ******* LINKS *******
    SRL
    MetalWave

  3. #3
    METALLO Alcio, era un po' che non ti si vedeva
    PHP LEARN - Guide, tutorial e articoli sempre aggiornati
    NUOVO: standardLib; il potente framework PHP é ora disponibile
    *******************************************
    Scarica oggi la tua copia di MtxEventManager

  4. #4
    Io bazzico in orari stranissimi!
    <ALCIO />
    Per cortesia: no PVT Tecnici
    ******* LINKS *******
    SRL
    MetalWave

  5. #5
    non mi piace questo:
    codice:
    <input type="hidden" name="MAX_FILE_SIZE" value="700000" />
    rimuovibile via url con questo:
    codice:
    javascript:$("input[name=MAX_FILE_SIZE]").remove();
    o più comunemente con:
    codice:
    javascript:(function(input){for(var i=0;i<input.length;i++){if(input[i].name=="MAX_FILE_SIZE")input[i].parentNode.removeChild(input[i]);}})(document.getElementsByTagName("input"));
    controlli di questo tipo SEMPRE sul server, il client aiuta ma è raramente affidabile.

    bella pillola, per il resto :ciauz:
    Formaldehyde a new Ajax PHP Zero Config Error Debugger

    WebReflection @WebReflection

  6. #6
    bella pillola

    detto questo, però, dico la mia

    non conviene, per nessun motivo, mantenere il nome del file utilizzato sul server!

    Personalmente utilizzo un paio di righe di codice che mi generano un nome casualmente su un loop

    codice:
    do
    {
        $name = $path . '/' . sha1(uniqid(microtime(), true)) . '.' . $ext;
    }
    while(file_exists($filename) === true);
    Con queste poche righe di codice viene generato un nome di file causale, con relativo percorso e estensione (rappresentati da path e ext) cosi da evitare la possibilità di richiamare direttamente il file se non si vuole/non si può (vedi ad es se si sta su hosting windows) bloccare l'accesso alla directory tramite htaccess

    In aggiunta, un'altra cosa che faccio per abitudine, è quella di utilizzare le sotto cartelle basate sulla prima lettera e poi sulle prime due lettere

    quindi il codice diventerebbe

    codice:
    do
    {
        $random = sha1(uniqid(microtime(), true));
        $name = $path . '/' . substr($random, 0, 1) . '/' . substr($random, 0, 2) . '/' . $random . '.' . $ext;
        while(file_exists($filename) === true);
    }
    Questo serve per evitare un lavoro inutile quando il sistema deve accedere alla directory con i file da scaricare, cosa che si fa sentire SOPRATTUTTO quando si sta su file system di rete perché non può essere utilizzato nessun sistema di caching o simili (e se uno c'ha 50 mila file o 100 mila file ha un rallentamento maggiore rispetto ad avere sparpagliati questi file per le directory)

    Altre techniche comprendono quello di utilizzare un file indice in modo da poter mantenere bilanciato il numero di file presenti nella struttura ad albero ma non è convieniente con php per due motivi:
    - un eventuale accesso simultaneo in scrittura distruggerebbe il file di indice e l'uso di un database farebbe perdere il vantaggio prestazionale
    - una soluzione del genere mostra i muscoli solo su ENORMI quantità di file con filesystem lenti e/o di rete

  7. #7
    scusate l'ignoranza, faccio solo una domanda breve e poi scompaio :

    la pillola funziona anche con register_globals = On ?
    1,2,3,4,5,10,100 passi!

  8. #8
    Si, tanto lo script usa $_POST e $_FILES per recuperare le variabili passate dalla form, quindi che tu abbia register_global a On o Off è indifferente.... per lo script!
    Però mi viene da pensare: PHP5 imposta in automatico il parametro su Off, a differenza di PHP4.
    Se te lo hai forzato a ON è un discorso, ma se hai una versione precedente di PHP (4.x) allora la classe non ti funzionerà: construct è presente solo nelle ultime release del linguaggio.

    <ALCIO />
    Per cortesia: no PVT Tecnici
    ******* LINKS *******
    SRL
    MetalWave

  9. #9
    Originariamente inviato da i_am_antipop
    scusate l'ignoranza, faccio solo una domanda breve e poi scompaio :

    la pillola funziona anche con register_globals = On ?
    Spiacente, sono il fondatore di un'associazione che sta facendo pressione affinchè venga introdotta la pena di morte per chi lavora con register_globals su ON.
    PHP LEARN - Guide, tutorial e articoli sempre aggiornati
    NUOVO: standardLib; il potente framework PHP é ora disponibile
    *******************************************
    Scarica oggi la tua copia di MtxEventManager

  10. #10
    Adesso che ho un po' di tempo rispondo anche a andr3a e a daniele.
    Trovo le vostre considerazioni validissime e se ne potrebbero fare mille altre. Quello che volevo fare era semplicemente fornire le basi per l'upload dei file.

    Quando ho detto una cosa più di cento volte, di solito scrivo una pillola così devo solo postare il link. In questo caso, quando qualcuno ha problemi con l'upload si tratta spesso di questioni che riguardano proprio i fondamenti, tipo:
    - hanno impostato enctype su text
    - cercano il file in POST
    - non sanno dove viene scaricato il file e che bisogna spostarlo
    - ecc...

    Quello di cui parlate invece riguarda già un livello più avanzato

    -------------------------------------------------------------------------------

    adesso volevo fare una piccola aggiunta a questa pillola.

    A volte arrivano sollecitazioni in merito all'upload di file e successivo inserimento dei file nel database. Non voglio scatenare una guerra di religione visto che per alcuni è una cosa da aborrire mentre per altri è il meglio che si può fare.

    Comunque, se dopo l'upload vogliamo inserire il file nel database anzichè salvarlo in una cartella dobbiamo procedere come segue:

    Creare una struttura del database che contenga:
    - il nome del file (VARCHAR)
    -il contenuto binario del file (LONGTEXT o MEDIUMTEXT)
    - il type (VARCHAR) potrebbe servire se ad esempio si tratta di immagini. Qualora volessimo visualizzarle dovremo indicare il tipo di file nell'header.
    - il size (INT) potrebbe servire sempre nella definizione degli header

    In seguito:

    1. Leggere il contenuto del file con una funzione binary safe ad esempio file_get_contents().
    2. Passare il contenuto per addslashes() per evitare sorprese
    3. Eliminare gli eventuali spazi nel nome del file (potrebbero causare problemi).
    4. Salvare il tutto nel database.

    Già che c'ero ho scritto un'estensione della classe upload

    Codice PHP:
    class UploadToDb extends Upload
    {
        protected 
    $conn;
        
            protected function 
    DbConnectAnSelect()
            {
                
    // includere file di configurazione db
                // o impostare i parametri manualmente
                
                
    $host "localhost";
                
    $db "uploads";
                
    $user "root";
                
    $password "password";
                
                
    $this->conn mysql_connect($host$user$password) OR die("connessione non riuscita");
                
    mysql_select_db($db$this->conn) OR die("impossibile selezionare il database");
            }
            
            protected function 
    SaveUploadFile()
            {
                
    $this->DbConnectAnSelect();
                
    $filename str_replace(" ""_"$this->file['name']);
                
    $content addslashes(file_get_contents($this->file['tmp_name']));
                
    $type $this->file['type'];
                
    $size $this->file['size'];
                
                
    $sql "INSERT INTO upload_file (name,size,type,content) VALUES ('$filename','$size','$type','$content')";
                
    mysql_query($sql$this->conn);
            }

    Come vedete riscrivo il metodo SaveUploadFile() e aggiungo un semplice metodo per la connessione al database.

    Unica cosa, prestate attenzione quando istanziate la classe agli argomenti.

    il primo non ha più senso ma dobbima comunque mettere qualcosa

    $upload = new UploadToDb ("", "upfile");
    PHP LEARN - Guide, tutorial e articoli sempre aggiornati
    NUOVO: standardLib; il potente framework PHP é ora disponibile
    *******************************************
    Scarica oggi la tua copia di MtxEventManager

Permessi di invio

  • Non puoi inserire discussioni
  • Non puoi inserire repliche
  • Non puoi inserire allegati
  • Non puoi modificare i tuoi messaggi
  •  
Powered by vBulletin® Version 4.2.1
Copyright © 2025 vBulletin Solutions, Inc. All rights reserved.