5

I want to compress (on the fly) a directory in tar.gz format using PHP. I know it can be done with exec or using this code (using ZIP format) but my host doesn't support exec and hasn't installed the zip extension of PHP.

After searching the internet I came across this PHP code:

<?php
// Computes the unsigned Checksum of a file’s header
// to try to ensure valid file
// PRIVATE ACCESS FUNCTION
function __computeUnsignedChecksum($bytestring) {
  for($i=0; $i<512; $i++)
    $unsigned_chksum += ord($bytestring[$i]);
  for($i=0; $i<8; $i++)
    $unsigned_chksum -= ord($bytestring[148 + $i]);
  $unsigned_chksum += ord(" ") * 8;

  return $unsigned_chksum;
}

// Generates a TAR file from the processed data
// PRIVATE ACCESS FUNCTION
function tarSection($Name, $Data, $information=NULL) {
  // Generate the TAR header for this file

  $header .= str_pad($Name,100,chr(0));
  $header .= str_pad("777",7,"0",STR_PAD_LEFT) . chr(0);
  $header .= str_pad(decoct($information["user_id"]),7,"0",STR_PAD_LEFT) . chr(0);
  $header .= str_pad(decoct($information["group_id"]),7,"0",STR_PAD_LEFT) . chr(0);
  $header .= str_pad(decoct(strlen($Data)),11,"0",STR_PAD_LEFT) . chr(0);
  $header .= str_pad(decoct(time(0)),11,"0",STR_PAD_LEFT) . chr(0);
  $header .= str_repeat(" ",8);
  $header .= "0";
  $header .= str_repeat(chr(0),100);
  $header .= str_pad("ustar",6,chr(32));
  $header .= chr(32) . chr(0);
  $header .= str_pad($information["user_name"],32,chr(0));
  $header .= str_pad($information["group_name"],32,chr(0));
  $header .= str_repeat(chr(0),8);
  $header .= str_repeat(chr(0),8);
  $header .= str_repeat(chr(0),155);
  $header .= str_repeat(chr(0),12);

  // Compute header checksum
  $checksum = str_pad(decoct(__computeUnsignedChecksum($header)),6,"0",STR_PAD_LEFT);
  for($i=0; $i<6; $i++) {
    $header[(148 + $i)] = substr($checksum,$i,1);
  }
  $header[154] = chr(0);
  $header[155] = chr(32);

  // Pad file contents to byte count divisible by 512
  $file_contents = str_pad($Data,(ceil(strlen($Data) / 512) * 512),chr(0));

  // Add new tar formatted data to tar file contents
  $tar_file = $header . $file_contents;

  return $tar_file;
}

function targz($Name, $Data) {
  return gzencode(tarSection($Name,$Data),9);
}

header("Content-Disposition: attachment; filename=backup.tar.gz");
header("Content-type: application/x-gzip");

$getfile = file_get_contents("test.txt");

echo targz('test.txt', $getfile);
?>

This does work for multiple files using this:

header("Content-Disposition: attachment; filename=backup.tar.gz");
header("Content-type: application/x-gzip");

$getfile = file_get_contents("test.txt");

echo targz('test.txt', $getfile);

$getfile2 = file_get_contents("test2.txt");

echo targz('test2.txt', $getfile2);

In the backup.tar.gz are now 2 files (test.txt and test2.txt).

But how can I use this to download a directory (and the sub directories)?

My directory is something like this:

home/
    file1.html
    file2.html
Another_Dir/
    file8.html
    Sub_Dir/
        file19.html
Community
  • 1
  • 1

1 Answers1

4

You need too list all files and directory inside the directory you want to compress. There's a recursive directory listing function here. I think combining your code with his code with do the work. something like this:

header("Content-Disposition: attachment; filename=backup.tar.gz");
header("Content-type: application/x-gzip");

// Your other tar.gz functions...

function compress( $path = '.', $level = 0 ){ 
    $ignore = array( 'cgi-bin', '.', '..' ); 
    $dh = @opendir( $path );

    while( false !== ( $file = readdir( $dh ) ) ){                                   
        if( !in_array( $file, $ignore ) ){
            if( is_dir( "$path/$file" ) ){
                // Go to the subdirs to compress files inside the subdirs
                compress( "$path/$file", ($level+1) );                                           
            } else {
                $getfile = file_get_contents($path. '/' .$file);
                // get the last dir in the $path
                $dirs = array_filter( explode('/', $path) );
                $dir = array_pop($dirs);

                // if we're not in a sub dir just compress the file in root otherwise add it on the subdir...
                if ($level < 1) {
                    echo targz($file, $getfile);
                } else {
                    // add all top level subdirs when $level is > 2
                    for ($i = 0; $i < $level - 1; $i++) {
                        $temp = array_pop($dirs);
                        $dir = $temp.'/'.$dir;
                    }
                    echo targz($dir. '/' .$file, $getfile);
                }
            }                                        
        }                                    
    } 

    closedir( $dh ); 
}

$dir    = 'dir/to/compress';
if (is_file($dir)) {
    // If it's a file path just compress it & push it to output
    $getfile = file_get_contents($dir);
    echo targz(basename($dir), $getfile);
} elseif(is_dir($dir)) {
    return compress($dir);
}

It works fine for me. It's my first contribution, hope it helps..

  • I was using a php framework to test, so that `return` in the last line maybe unnecessary.. – Hamed Nemati May 12 '13 at 18:53
  • Thanks! But when there is a subdirectory (`home/Another_Dir/Sub_Dir/`) he moves the folder to (`home/Sub_Dir/`) in the `backup.tar.gz` file and he doesn't maintain the normal directory format (`home/Another_Dir/Sub_Dir/`). Can that be fixed? –  May 12 '13 at 19:48
  • 1
    @GerritHoekstra I edited the code so that should be fixed now. – Hamed Nemati May 12 '13 at 22:09
  • Thanks, it's working perfectly now! +1 But when I try to do a file `home/file2.html` and not a dir I get these erros: http://pastebin.com/scuNLbSv Is it possible to do a change in the script when detected a file handle it as a file? –  May 13 '13 at 17:42
  • 1
    @GerritHoekstra Just edited the last part of the code to handle that – Hamed Nemati May 14 '13 at 14:06
  • Thanks again! Great first contribution on Stack Overflow :) –  May 14 '13 at 17:00
  • One more thing, how can I use this to put it in a `tar.gz` file on the server (so not on-the-fly)? It's working for 1 file: `file_put_contents('home/backup.tar.gz', targz(basename($dir), $getfile));` but when I try this: `file_put_contents('home/backup.tar.gz', compress($dir));` it isn't working. I hope you can help me with this. –  May 19 '13 at 19:07
  • tar command on ubuntu says: Archive contains `png00007' where numeric mode_t value expected :( – e-info128 Feb 21 '14 at 14:09