PHP Untar-gz without exec()?
Asked Answered
L

3

29

How would I untar-gz a file in php without the use of exec('tar') or any other commands, using pure PHP?

My problem is as follows; I have a 26mb tar.gz file that needs to be uploaded onto my server and extracted. I have tried using net2ftp to extract it, but it doesn't support tar.gz uncompressing after upload.

I'm using a free web host, so they don't allow any exec() commands, and they don't allow access to a prompt. So how would I go about untaring this?

Does PHP have a built in command?

Loudhailer answered 23/2, 2012 at 15:55 Comment(0)
F
56

Since PHP 5.3.0 you do not need to use Archive_Tar.

There is new class to work on tar archive: The PharData class.

To extract an archive (using PharData::extractTo() which work like the ZipArchive::extractTo()):

try {
    $phar = new PharData('myphar.tar');
    $phar->extractTo('/full/path'); // extract all files
} catch (Exception $e) {
    // handle errors
}

And if you have a tar.gz archive, just decompress it before extract (using PharData::decompress()):

// decompress from gz
$p = new PharData('/path/to/my.tar.gz');
$p->decompress(); // creates /path/to/my.tar

// unarchive from the tar
$phar = new PharData('/path/to/my.tar');
$phar->extractTo('/full/path');
Fetch answered 7/9, 2012 at 9:17 Comment(6)
For some reason PharData::extractTo doesn't work in PHP 5.4.0 on Windows. Can't figure out whyBooby
I saw your question but I do not have a Windows with 5.4.0 to test it, sorry.Fetch
It also, contrary to documentation, does not seem to take directories as the second argument.Meisel
Also PharData fails on certain types of tar contents #37190187Clumsy
to anyone still encountering this error . convert it to zip and then extract it. saw the solution here .#38844438Mariehamn
Very cool. I used this on a custom Moodle backup / restore script. They switched from .zip to .tar but disguise their backups as an .mbz. A few lines of code changes and it was working perfectly. Thanks!Fink
F
3

PEAR provides the Archive_Tar class, which supports both Gzip and BZ2 compressions, provided you have the zlib and bz2 extensions loaded, respectively.

Fourhanded answered 23/2, 2012 at 16:4 Comment(0)
U
3

I wrote a PHP function which can be used without Phar. It also works with long filenames and was tested with big TAR archives. Compatible with PHP 5 to 8.

Usage:

<?php

$ret = tar_extract ("filename.tar", "./path-to-extract/");

if (empty ($ret["errText"])) {
    print ($ret["fileCnt"]." files extracted.");
    print ('<br>'.$ret["log"]);
}
else {
    print ("Error: ".$ret["errText"]);
    print ('<br>'.$ret["log"]);
}

?>

TAR archive functions:

<?php

function tar_truncate_name (&$str) {
    for ($i=0;$i<strlen ($str);$i++) {
        if (0==ord (substr ($str, $i, 1)))
            break;
    }
    $str = substr ($str, 0, $i);
}

function tar_checksum (&$str, $size) {
    $checksum = 0;
    for ($i=0;$i<$size;$i++) {
        $checksum += ord (substr ($str, $i, 1));
    }
    return $checksum;
}

function tar_read_header ($fh) 
{
    $header = array (
    "name"    => 100,
    "mode"    => 8,
    "uid"     => 8,
    "gid"     => 8,
    "size"    => 12,
    "mtime"   => 12,
    "chksum"  => 8,
    "linkflag"=> 1,
    "linkname"=> 100,
    "magic"   => 8,
    "uname"   => 32,
    "gname"   => 32,
    "devmajor"=> 8,
    "devminor"=> 8,
    "rest"    => 167
    );

    $dataPos = ftell ($fh)+512;
    $stat = fstat($fh);
    $filesize = $stat['size'];
    if ($dataPos>$filesize) {
        return -1;
    }

    $checksum = 0;
    foreach ($header as $key => $len) {    
        $header[$key] = fread ($fh, $len);
        $read = strlen ($header[$key]);
        if ($read<$len) 
            return -1;
        if ($key=="chksum")
            $checksum+=256;
        else
            $checksum+=tar_checksum ($header[$key], $len);
    }
    
    tar_truncate_name ($header["chksum"]);
    if (empty ($header["chksum"])) {
        return false; // Last TAR header
    }
    
    // Checksum 
    if ($header["chksum"]!=sprintf ("%06o", $checksum)) {
        return -1;
    }

    // Parse form octal to decimal
    if (sscanf ($header["size"], "%o", $header["size"])!=1)
        return false;

    if (sscanf ($header["mtime"], "%o", $header["mtime"])!=1)
        return false;

    // Truncate names
    tar_truncate_name ($header["name"]);
    tar_truncate_name ($header["linkname"]);
    tar_truncate_name ($header["uname"]);
    tar_truncate_name ($header["gname"]);

    fseek ($fh, $dataPos);

    $fPaxHeader = ($header["linkflag"]=="x" && $header["name"]=="././@PaxHeader");
    $fLongLink = ($header["linkflag"]=="L" && $header["name"]=="././@LongLink");
    
    if ($fLongLink || $fPaxHeader) {
        // Read long filename       
        $longFilename = fread ($fh, $header["size"]);
        if (strlen ($longFilename)!=$header["size"])
            return -1;

        // Skip 512 Byte block
        $size=(512-($header["size"] % 512));
        fseek ($fh, ftell ($fh)+$size);

        // Now comes the real block
        $header = tar_read_header ($fh);
        if (!is_array ($header)) {
            return -1;
        }
        if ($fPaxHeader) {
            $len = (int) $longFilename;
            if ($len) {
                $longFilename = substr ($longFilename, 0, $len);
                if (($p=strpos ($longFilename, "path="))!==false) {
                    $longFilename = trim (substr ($longFilename, $p+5));
                }
                else {
                    $longFilename = "";
                }
            }
            else
                return -1;                          
        }
        if (!empty ($longFilename))
            $header["name"]=trim ($longFilename);
    }
    
    // Remove relative paths for security
    $header["name"] = str_replace (["../", "..\\"], ["./", "./"], $header["name"]);
    return $header;
}

function tar_extract ($tarFilename, $dstPath=".", $options=array ()) {

    $ret = array (
        "fileCnt" => 0,
        "dirCnt" => 0,
        "size" => 0,
        "log" => "",
        "errText" => ""
    );

    $fh = fopen ($tarFilename, "r");
    if (!$fh) {
        $ret["errText"] = "Cannot open TAR file: ".$tarFilename;
        $ret["log"].=$ret["errText"]."\r\n";
        return $ret;
    }
    while (!feof ($fh)) {
        $header = tar_read_header ($fh);
        if (!is_array ($header)) {
            if ($header==-1) {
                $ret["errText"] = "TAR header corrupt.";
                $ret["log"].=$ret["errText"]."\r\n";
            }
            break;
        }
        else
        if ($header["linkflag"]==0) {
            // Read file
            $ret["log"].=$header["name"].", ".$header["size"]."\r\n";

            // Try to create sub directories
            $pathTokens = explode ("/", $header["name"]);
            $subDir = "";
            if (count ($pathTokens)>1) {
                unset ($pathTokens[count($pathTokens)-1]);
                foreach ($pathTokens as $pathToken) {
                    if (!empty ($subDir))
                        $subDir.="/";
                    $subDir.=$pathToken;
                    if (!is_dir ($dstPath."/".$subDir) && !mkdir ($dstPath."/".$subDir)) {
                        $ret["errText"] = "Cannot create directory: ".$dstPath."/".$subDir;
                        $ret["log"].=$ret["errText"]."\r\n";
                        break;
                    }
                }
            }

            $dstFilename = $dstPath."/".$header["name"];
            $fh2 = fopen ($dstFilename, "w+");
            if (!$fh2) {
                $ret["errText"] = "Cannot create file: ".$dstFilename;
                $ret["log"].=$ret["errText"]."\r\n";
                break;
            }
            else {
                $size = $header["size"];
                $buf = "";
                while ($size>0) {
                    $bufSize = 0xFFFF;
                    if ($size<$bufSize)
                        $bufSize=$size;
                    $buf = fread ($fh, $bufSize);
                    $read = strlen ($buf);
                    if ($read<=0)
                        break;
                    fwrite ($fh2, $buf);
                    $size-=$read;
                }
                fclose ($fh2);

                // Set file time and mode
                touch ($dstFilename, $header["mtime"], $header["mtime"]);
                if (sscanf ($header["mode"], "%o", $header["mode"])==1)
                    chmod ($dstFilename, $header["mode"]);
                if ($size>0) {
                    $ret["errText"] = "Filesize incorrect: ".$dstFilename;
                    $ret["log"].=ret["errText"]."\r\n";
                    break;
                }
            }
            // Skip 512 Byte block
            if (($header["size"] % 512)!=0) {
                $rest = 512-($header["size"] % 512);
                fseek ($fh, ftell ($fh)+$rest);
            }
            $ret["fileCnt"]++;
            $ret["size"]+=$header["size"];
        }
        else 
        if ($header["linkflag"]==5) {
            $dstDir = $dstPath."/".$header["name"];
            if (!is_dir ($dstDir) && !mkdir ($dstDir)) {
                $ret["errText"] = "Cannot create directory: ".$dstDir;
                $ret["log"].=$ret["errText"]."\r\n";
                break;
            }

            // Set directory time and mode
            touch ($dstDir, $header["mtime"], $header["mtime"]);
            // chmod ($dstDir, octdec($header["mode"]));
            $ret["dirCnt"]++;
        }
        else {
            // Unknown linkflag
            // Ignore header but skip size
            if ($header["size"]>0) {
                $size = $header["size"];
                // Skip 512 Byte block
                if ($header["size"]<512)
                    $size=512;
                else
                    $size+=(512-($header["size"] % 512));
                fseek ($fh, ftell ($fh)+$size);
            }
        }
    }
    fclose ($fh);
    return $ret;
}

?>
Ulbricht answered 30/3 at 14:59 Comment(2)
Error for me. File "mediawiki-1.41.1.tar.gz", download at mediawiki.org/wiki/Download, ERROR log: <Error: Cannot create file: ./mediawiki/extensions/VisualEditor/lib/ve/lib/oojs-ui/themes/wikimediaui/images/icons/browser-ltr.svg>.Edmondo
I integrated 'PaxHeader' support. It will work now with no GNU TAR formats.Ulbricht

© 2022 - 2024 — McMap. All rights reserved.