Internet Info, s.r.o. Lupa Měšec Podnikatel Root Zdroják DigiZone Slunečnice Vitalia TopDrive KupDnes Navrcholu NovýTarif Dobrý web Weblogy Woko Jagg Computer.cz SK: MojeLinky

Hlavní navigace

PHP okénko: Získání souboru ze ZIP archivu

Dnešní PHP okénko ukazuje na příkladu získání souboru ze ZIP archivu dostupného protokolem HTTP nízkoúrovňovou práci s tímto protokolem a způsob zacházení s binárními daty.

Tweetni to Twitter Jaggni to! Jagg Del.icio.us Delicious

Už se mi několikrát stalo, že jsem musel stahovat mnohamegový ZIP archiv jenom proto, že jsem z něj následně potřeboval získat jeden soubor. Vzhledem k pevné struktuře ZIPu by ale získání jednoho souboru mělo být možné i bez stahování celého archivu. Pokud by PHP rozšíření ZIP umělo pracovat se vzdálenými soubory, možná by se problém dal vyřešit snadno – nicméně předpokládám, že by se archiv stejně vždycky nejprve celý stáhl a pak by se s ním teprve pracovalo. Řešení tedy bude muset jít poměrně hluboko:

  1. popisu formátu ZIP zjistíme, jak jsou v archivu uloženy jednotlivé soubory.
  2. Pomocí HTTP hlavičky Range budeme z archivu získávat jednotlivé kousky.
  3. Požadované soubory si zkopírujeme do vlastního archivu, který půjde následně rozbalit standardními prostředky.

Postup bude samozřejmě fungovat jen u HTTP serverů s podporou stahování částí souborů. Pokud bude v archivu uložena spousta malých souborů, nepřinese postup žádnou časovou úsporu – vyplatí se tedy jen u archivů, kde stažení průměrně velkého souboru zabere víc času než položení nového HTTP požadavku.

Situaci máme trochu zkomplikovanou tím, že PHP zatím uznává jako úspěšný návratový kód pouze 200 OK, takže 206 Partial Content považuje za chybu. Proto nemůžeme použít kontexty a k serveru musíme přistupovat nízkoúrovňově funkcí fsockopen:

<?php
/** Získání části souboru protokolem HTTP
@param string $url adresa souboru
@param int $from začátek části počítaný od 0
@param int $length délka části
@return odpovídající část souboru nebo "", pokud server nepodporuje stahování částí
*/
function http_get_part($url, $from, $length)
{
    $url = parse_url($url);
    $fp = fsockopen(($url["scheme"] == "https" ? "ssl://" : "") . $url["host"], ($url["scheme"] == "https" ? 443 : 80));
    fwrite($fp, "GET $url[path]" . (isset($url["query"]) ? "?$url[query]" : "") . " HTTP/1.1\r\n");
    fwrite($fp, "Host: $url[host]\r\n");
    fwrite($fp, "Range: bytes=$from-" . ($from + $length - 1) . "\r\n");
    fwrite($fp, "\r\n");
    $status = fgets($fp);
    $return = "";
    if (preg_match('~^HTTP/[^ ]+ 206~', $status)) {
        while ("\r\n" != fgets($fp)) {
            // přeskočení hlaviček
        }
        while (strlen($return) < $length && ($s = fread($fp, $length))) {
            $return .= $s;
        }
    }
    fclose($fp);
    return $return;
}
?> 

Funkce fsockopen nám dovoluje se serverem komunikovat přímo na úrovni protokolu HTTP – serveru pošleme dotaz (např. GET / HTTP/1.1), hlavičky (název: hodnota), prázdný řádek a případné tělo (u metody POST) a on nám vrátí stav (např. HTTP/1.1 206 Partial Content), hlavičky, prázdný řádek a tělo. Server může data poslat v kódování chunked, pro jednoduchost ale předpokládejme, že to neudělá – obvyklé to je u souborů, u kterých není dopředu známá velikost vracených dat (např. výstup z PHP skriptu).

Dalším krokem je postupné procházení archivu po jednotlivých souborech. Z popisu formátu jsme se dozvěděli, že hlavička každého uloženého souboru musí začínat pevným řetězcem, délka názvu souboru je uložena na pozici 26–27, délka zkomprimovaných dat na 18–21 a délka dodatečných hlaviček na 28–29. Na základě těchto informací můžeme načítat názvy souborů a přeskakovat nezajímavé soubory.

<?php
/** Získání vybraných souborů ze ZIP archivu dostupného protokolem HTTP
@param string $url adresa ZIP archivu
@param array $files soubory, které chceme získat - může být i řetězec s jedním souborem
@return string ZIP archiv s požadovanými soubory
*/
function http_get_files_from_zip($url, $files)
{
    static $header_len = 30;
    static $max_name_len = 256;
    if (!is_array($files)) {
        $files = array($files);
    }

    $return = "";
    $from = 0; // aktuální pozice v archivu
    while (substr(($part = http_get_part($url, $from, $header_len + $max_name_len)), 0, 4) == "PK" . chr(3) . chr(4)) {
        $lengths = unpack("Vsize/vname/vextra", substr($part, 18, 4) . substr($part, 26, 4));
        if (in_array(basename(substr($part, $header_len, $lengths["name"])), $files)) {
            $return .= http_get_part($url, $from, $header_len + array_sum($lengths));
        }
        $from += $header_len + array_sum($lengths);
    }
    return $return;
}
?> 

Funkce prochází archivem, načítá názvy souborů, a pokud byl soubor požadován, tak ho načte. Pokud server nepodporuje stahování částí souborů a funkce http_get_part tedy vrátí prázdný řetězec, while cyklus ihned skončí a funkce vrátí prázdný řetězec. Pro zjištění velikostí uložených v hlavičce každého souboru je použita funkce unpack.

Kód je nakonec poměrně krátký, byť vyžadoval rozličné znalosti. Kdyby PHP uznávalo kód 206 za úspěšný, smrskla by se navíc funkce http_get_part do dvou řádek:

<?php
function http_get_part_context($url, $from, $length)
{
    $context = stream_context_create(array('http' => array('header' => "Range: bytes=$from-" . ($from + $length - 1))));
    return file_get_contents($url, false, $context);
}
?> 

Na závěr nezbytná ukázka: získání souboru php.ini-dist z PHP verze 4.3.0:

<?php
$url = "http://museum.php.net/win32/php-4.3.0-Win32.zip";
file_put_contents("php430.ini-dist.zip", http_get_files_from_zip($url, "php.ini-dist"));
?> 

Podobně laděné texty můžete najít i na autorově weblogu PHP triky.

Jakub Vrána

Jakub Vrána

Autor se živí programováním v PHP, podílí se na jeho oficiální dokumentaci, vyučuje ho na MFF UK a vede odborná školení. Poznámky si zapisuje na weblog PHP triky.

Školení: TCP/IP síť na Linuxu II

V tomto školení prohloubíte svoji znalost síťování na Linuxu a vyzkoušíte si pokročilejší techniky. Školení je určené mimojiné i pro správce poskytovatelských sítí.

  • Průřez technologiemi TCP/IP 
  • Stavba testovací sítě
  • Dynamický routing: OSPFv2 a OSPFv3
  • Bridging, proxy ARP, proxy NDP
  • Různé metody překladu adres
  • NAT traversal v praxi
  • Přepis paketů
  • Tunelování a VPN
  • Troubleshooting, situace z praxe

Podrobnější informace a přihláška 

Ohodnoťte jako ve škole:
Průměrná známka 2,99

Přehled názorů

Vyzivny kousek
Vladimir Kotala 16. 5. 2005 10:55
Nový
supr clanek
markon 16. 5. 2005 11:03
Nový
Keby autor nebol amater...
kciii 16. 5. 2005 12:24
Nový
└ 
Re: Keby autor nebol amater...
Jakub Vrána 16. 5. 2005 12:30
Nový
 
└ 
Re: Keby autor nebol amater...
kciii 16. 5. 2005 12:53
Nový
opět trapné pokračování seriálu
martin 16. 5. 2005 14:44
Nový
├ 
Re: opět trapné pokračování seriálu
Zdeněk Merta 17. 5. 2005 08:15
Nový
└ 
Re: opět trapné pokračování seriálu
Jackk 17. 5. 2005 14:03
Nový
 
└ 
Re: opět trapné pokračování seriálu
Martin 'Bilbo' Petricek 17. 5. 2005 19:28
Nový
 
 
├ 
Re: opět trapné pokračování seriálu
martin 18. 5. 2005 10:56
Nový
 
 
└ 
Re: opět trapné pokračování seriálu
Podhy 19. 5. 2005 21:37
Nový
Stačí exec("unzip -d adresář file.zip")
Radek Hulán 20. 5. 2005 09:30
Nový
└ 
Re: Stačí exec("unzip -d adresář file.zip")
Podhy 20. 5. 2005 14:18
Nový
 
└ 
Re: Stačí exec("unzip -d adresář file.zip")
Jakub Vrána 20. 5. 2005 14:22
Nový
 
 
└ 
Re: Stačí exec("unzip -d adresář file.zip")
Radek Hulán 20. 5. 2005 14:28
Nový
       

Tento text je již více než dva měsíce starý. Chcete-li na něj reagovat v diskusi, pravděpodobně vám již nikdo neodpoví. Pro řešení aktuálních problémů doporučujeme využít naše diskusní fórum.

Zasílat nově přidané příspěvky e-mailem