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;
}
?>