Upload souboru

Úvodem je třeba zdůraznit, že protokol http 1.1 (neplést s html x.y) upload umožňuje (je to jedna ze základních funkcí). Prohlížeče podporují upload souboru ve formulářích do té míry, že přidání prvku formuláře

<input name="soubor" type="file">

vygeneruje samo položku pro vyplnění jména souboru včetně tlačítka "procházet" na výběr myší z adresáře. Je jen rozumné u definice formuláře uvést (kromě jména php scriptu pro akci "submit" a metody "post") také parametr "enctype" a způsob předávání souboru:

<form action="upload.php" method="post" enctype="multipart/form-data">

Zbývá jen si soubor na serveru převzít.

Použil jsem Google a hned první dva články mne zaujaly. Takže uvedu jen odkazy:

Jaromír Skřivan na serveru Interval

Tomáš Vinduška na serveru programujeme.com

Při porovnání obou skriptů zjistíme, jak diametrálně odlišné může řešení v php být. Například - používají jiný formát podmínky (v druhém je použita alternativní syntaxe). Druhý nelogicky generuje všechny řádky v php (asi to není vůbec html soubor - pak ale prohlížeč bude postrádat hlavičky - nedoporučuji). Jinak správné řešení pro kopírování souboru je ale použito právě ve druhém textu - zcela korektní řešení je opravdu právě volání funkce move_uploaded_file.

Bezpečnost. V textech není patřičně zdůrazněn princip uploadu - pokud se soubor okamžitě (před ukončením skriptu) neuloží, je ze serveru definitivně smazán! Někde také jistě naleznete důležité varování, v žádném případě neumožnit uživateli upload souboru do adresáře, kde by mohl být s příponou .php spuštěn - jedná se o vážné bezpečnostní riziko. Není-li jiná možnost, je třeba kontrolovat příponu. Má-li se přes webový soubor předávat z nějakých důvodů cokoli, trvejte na zazipování a příponě .zip. Další možností je ukládání souborů do podadresáře, jehož jméno (např. náhodně vygenerované) uživatelé nevědí, resp. lze přímo ukládat jména souborů do databáze a na disku místo jména použít ID příslušné řádky (to je výhodné u proto, že lze dvakrát uložit soubor se stejným jménem). Je však třeba mít na paměti, že o ochranu se jedná pouze tehdy, když uživatel k souboru nebude mít nikdy přístup přes http. Pokud soubor potřebuje, může se mu poslat například e-mailem. Bohužel není řešením vytvořit ve zvoleném adresáři konfigurační soubor Apache ".htaccess", protože .php soubory se spustí, i když mají oprávnění jen "read", a to jim zachovat musíte.

My si budeme ukládat obrázky, a to ještě po provedení funkce "resize". Ty pak budou bezpečné vždy (pokud nám někdo podstrčí soubor s příponou ".jpg", který bude obsahovat cokoli ošklivého, tak se to uloží "přechroustané", resp. pokud se nebude ani formálně jednat o obrázek, uloží se černý obdélník bez jakýchkoli dat).

Z druhého odkazu bych si dovolil kousek převzít, ale po úpravě na úsek html kódu (nejsem přítelem stránek, které nemají hlavu ani patu - ty si prosím doplňte). V obou výše citovaných případech je používán tentýž php soubor pro zadávání jako pro zpracování (výhodou je, že můžeme posílat jeden soubor za druhým, dokud nás to baví), rozdíl mezi odkazovanými stránkami je například v detekci, že se nejedná o první volání - v prvním případě se testuje, zda nám formulář vrátil hodnotu proměnné $akce, což je tlačítko typu Submit; v druhém případě se php skript ptá, zda vrácená proměnná $soubor je typu file, což je zřejmě formálně správnější.

<?  
if (is_uploaded_file($soubor)): //pokud jiz byl proveden upload 
  $cesta="data/"; //nami definovana cesta  
  if (move_uploaded_file ($soubor, $cesta.$soubor_name)):
    //presunuti souboru do naseho adresare, // oznameni o uspesnosti
    echo "Soubor $soubor_name o velikosti $soubor_size bajtů byl úspěšně nahrán na server"; 
  else:  
    echo "Při nahrávání souboru došlo k chybě!"; //oznameni chyby  
  endif;  
endif;  
// formular pro upload
?> 
<form action="upload.php" method="post" enctype="multipart/form-data">
<input name="soubor" type="file">  
<br>  
<input value="Uložit" type="submit"></form>  

Pozor! Register globals!

Uvedený skript byl odladěn pro server webzdarma.cz, kde bylo register globals = on. Na některých serverech to může vyžadovat doplnit do souboru .htaccess (v adresáři, kde máme php skripty) následující text:

php_flag register_globals on
Order allow,deny
Allow from all

php preprocessor byl původně určen pro malé vložené kripty v běžných html stránkách (odtud php = personal home page). V současné době je z něho násilně vytvářen absolutně zabezpečený prostředek pro vytváření velkých systémů, jako jsou webové portály a velké internetové obchody. Tam má jisté oprávnění tuto direktivu vypnout (off). Pokud je vypnutá, je třeba pro přebírání dat z formulářů používat globální pole $_GET a $_POST. Pro předávání souborů je pak vyčleněno dvourozměrné pole $_FILES. Pro jeho použití se prosím podívejte na anglický ekvivalent této stránky - příklady php skriptů jsou jinak identické (největší rozdíl je v tom, že proměnnou $soubor_name jde běžně zapsat do řetězce, ohraničeného uvozovkami, kdežto $_FILES[soubor][name] nikoli).

U předpokládaného ukládání sbírky fotografií na server je další problém: limit přenosu, který je většinou nastaven na 2 MB. Fotografie, převzatá přímo z fotoaparátu, je většinou větší. Limit místa na free-web serveru nám tolik nevadí, obrázky budeme ukládat zmenšené (doporučil bych cca 640x480 a kvalitu jpeg-komprese cca 60%).

Celé řešení je zde:

<html>

<head>
<meta http-equiv="Content-Type"
content="text/html; charset=windows-1250">
<title>Odezva formuláře</title>

</head>

<body bgcolor="#FFFFFF">

<p><font size="4"><strong>Odezva formuláře:</strong></font></p>

<?  
if (is_uploaded_file($soubor)): //pokud již byl proveden upload (a zdařil-li se, samozřejmě)
  $cesta="data/"; //nami definovaný adresář, kam se budou ukládat obrázky 
    if (move_uploaded_file ($soubor, $cesta.$soubor_name)):
    //presunuti souboru do naseho adresare, // oznámení o úspěšnosti, pokud šlo přejmenovat
     echo "Soubor $soubor_name o velikosti $soubor_size bajtů byl úspěšně nahrán na server"; 

     $src = @imagecreatefromjpeg($cesta.$soubor_name);   //prozatím otvírám přesunutý soubor
           //zde by mělo být ošetření, zda se povedlo otevřít soubor jako obrázek
     $dst = imagecreatetruecolor(640,480);               //požadované rozlišení
           //obrázek je "truecolor" a nebo s barevnou paletou, ale s tím se hůře pracuje
     imagecopyresampled($dst, $src, 0, 0, 0, 0, 640, 480, imagesx($src), imagesy($src));
           //cíl, zdroj, levý horní roh cíle, levý h.r. zdroje, šířka a výška cíle a zdroje
           //      použité funkce imagesx($src), imagesy($src) vrací rozměry obrázku (size x)
     imagejpeg($dst, $cesta."1".$soubor_name,60);        //drobná změna jména - předsazení jedničky
           //vytvoří jpeg ze zadaného truecolor obr.,se zadaným jménem souboru, o kvalitě 60%.
     imagedestroy($dst);           //závěrečný úklid - vlastně by po ukončení php mělo zmizet samo
     imagedestroy($src);

  else:  
    echo "Při nahrávání souboru došlo k chybě!"; //oznámení chyby z posledního "if"
  endif;  
endif;  
// a na závěr - toto php je samo sobě formulářem
//formulář pro upload případného dalšího souboru:
//jazyk php již nebude třeba...
?>         

<hr>
<form action="upload1.php" method="post" enctype="multipart/form-data">
<input name="soubor" type="file">  
<br>  
<input value="Uložit" type="submit"></form>  
<p>&nbsp;</p>
<sub>poslední verze 25.3.2010</sub>
</body>
</html>

Sáhl jsem do kódu po překopírování, takže nevím, zda to ještě funguje. Zkuste odkaz na server Fsinet - jen upload souboru, upload a resize obrázku. Takto mně to chodí na serveru webzdarma.cz . Při použití těchto dvou odkazů si můžete kliknout pravým tlačítkem a zobrazit zdrojový kód .php stránky - na serveru Fsinet totiž není nic, co by jej přeložilo.

Poznámky:
- místo <?php je <? . Některým serverům by to mohlo vadit.
- chybí ošetření chyb při generování obrázků do souboru
- chybí smazání uloženého souboru po jeho použití - knihovna GD bohužel neumožňuje vytvořit soubor z jiného obrázku než někde uloženého (nikoli v paměti), nebo jsem to alespoň nenašel (pokud najdete řešení, prosím napište).
- nekontroluji, zda předávaný obrázek je jpeg, a pro jiné to nechodí. Navrhl bych alespoň následující opatření.