Making a temporary dir for unpacking a zipfile int

2019-02-05 22:01发布

问题:

I have a script that checks a zipfile containing a number of matching PDF+textfiles. I want to unpack, or somehow read the textfiles from the zipfile, and just pick out some information from the textfile to see that the file version is correct.

I was looking at the tempnam() function to find an equivalent to make a tempdir, but maybe someone has a better solution for the problem.

The indexfile looks something like this. (-> is for TAB char). I have made the function to extract the version from the textfile and to check if its correct already, its only the unpacking, tmpdir or some other solution im looking for.

1000->filename->file version->program version->customer no->company no->distribution
2000->pagenumber->more info->more info->...

回答1:

quite easy (I took partly it from the PHP manual):

<?php

function tempdir() {
    $tempfile=tempnam(sys_get_temp_dir(),'');
    // you might want to reconsider this line when using this snippet.
    // it "could" clash with an existing directory and this line will
    // try to delete the existing one. Handle with caution.
    if (file_exists($tempfile)) { unlink($tempfile); }
    mkdir($tempfile);
    if (is_dir($tempfile)) { return $tempfile; }
}

/*example*/

echo tempdir();
// returns: /tmp/8e9MLi

See: http://de.php.net/manual/en/function.tempnam.php

Please look at Will's solution below.

=> My answer should not be the accepted answer anymore.



回答2:

So I first found a post by Ron Korving on PHP.net, which I then modified to make a bit safer (from endless loops, invalid characters, and unwritable parent dirs) and use a bit more entropy.

<?php
/**
 * Creates a random unique temporary directory, with specified parameters,
 * that does not already exist (like tempnam(), but for dirs).
 *
 * Created dir will begin with the specified prefix, followed by random
 * numbers.
 *
 * @link https://php.net/manual/en/function.tempnam.php
 *
 * @param string|null $dir Base directory under which to create temp dir.
 *     If null, the default system temp dir (sys_get_temp_dir()) will be
 *     used.
 * @param string $prefix String with which to prefix created dirs.
 * @param int $mode Octal file permission mask for the newly-created dir.
 *     Should begin with a 0.
 * @param int $maxAttempts Maximum attempts before giving up (to prevent
 *     endless loops).
 * @return string|bool Full path to newly-created dir, or false on failure.
 */
function tempdir($dir = null, $prefix = 'tmp_', $mode = 0700, $maxAttempts = 1000)
{
    /* Use the system temp dir by default. */
    if (is_null($dir))
    {
        $dir = sys_get_temp_dir();
    }

    /* Trim trailing slashes from $dir. */
    $dir = rtrim($dir, DIRECTORY_SEPARATOR);

    /* If we don't have permission to create a directory, fail, otherwise we will
     * be stuck in an endless loop.
     */
    if (!is_dir($dir) || !is_writable($dir))
    {
        return false;
    }

    /* Make sure characters in prefix are safe. */
    if (strpbrk($prefix, '\\/:*?"<>|') !== false)
    {
        return false;
    }

    /* Attempt to create a random directory until it works. Abort if we reach
     * $maxAttempts. Something screwy could be happening with the filesystem
     * and our loop could otherwise become endless.
     */
    $attempts = 0;
    do
    {
        $path = sprintf('%s%s%s%s', $dir, DIRECTORY_SEPARATOR, $prefix, mt_rand(100000, mt_getrandmax()));
    } while (
        !mkdir($path, $mode) &&
        $attempts++ < $maxAttempts
    );

    return $path;
}
?>

So, let's try it out:

<?php
echo "\n";
$dir1 = tempdir();
echo $dir1, "\n";
var_dump(is_dir($dir1), is_writable($dir1));
var_dump(rmdir($dir1));

echo "\n";
$dir2 = tempdir('/tmp', 'stack_');
echo $dir2, "\n";
var_dump(is_dir($dir2), is_writable($dir2));
var_dump(rmdir($dir2));

echo "\n";
$dir3 = tempdir(null, 'stack_');
echo $dir3, "\n";
var_dump(is_dir($dir3), is_writable($dir3));
var_dump(rmdir($dir3));
?>

Result:

/var/folders/v4/647wm24x2ysdjwx6z_f07_kw0000gp/T/tmp_900342820
bool(true)
bool(true)
bool(true)

/tmp/stack_1102047767
bool(true)
bool(true)
bool(true)

/var/folders/v4/647wm24x2ysdjwx6z_f07_kw0000gp/T/stack_638989419
bool(true)
bool(true)
bool(true)


回答3:

Another option if running on linux with mktemp and access to the exec function is the following:

<?php

function tempdir($dir=NULL,$prefix=NULL) {
  $template = "{$prefix}XXXXXX";
  if (($dir) && (is_dir($dir))) { $tmpdir = "--tmpdir=$dir"; }
  else { $tmpdir = '--tmpdir=' . sys_get_temp_dir(); }
  return exec("mktemp -d $tmpdir $template");
}

/*example*/

$dir = tempdir();
echo "$dir\n";
rmdir($dir);

$dir = tempdir('/tmp/foo', 'bar');
echo "$dir\n";
rmdir($dir);

// returns:
//   /tmp/BN4Wcd
//   /tmp/foo/baruLWFsN (if /tmp/foo exists, /tmp/baruLWFsN otherwise)

?>

This avoids the potential (although unlikely) race issue above and has the same behavior as the tempnam function.



回答4:

I wanted to add a refinement to @Mario Mueller's answer, as his is subject to possible race conditions, however I believe the following should not be:

function tempdir(int $mode = 0700): string {
    do { $tmp = sys_get_temp_dir() . '/' . mt_rand(); }
    while (!@mkdir($tmp, $mode));
    return $tmp;
}

This works because mkdir returns false if $tmp already exists, causing the loop to repeat and try another name.

Note also that I've added handling for $mode, with a default that ensures the directory is accessible to the current user only, as mkdir's default is 0777 otherwise.

It is strongly advised that you use a shutdown function to ensure the directory is removed when no longer needed, even if your script exits by unexpected means*. To facilitate this, the full function that I use does this automatically unless the $auto_delete argument is set to false.

// Deletes a non-empty directory
function destroydir(string $dir): bool { 
    if (!is_dir($dir)) { return false; }

    $files = array_diff(scandir($dir), ['.', '..']);
    foreach ($files as $file) {
        if (is_dir("$dir/$file")) { destroydir("$dir/$file"); }
        else { unlink("$dir/$file"); }
    }
    return rmdir($dir); 
}

function tempdir(int $mode = 0700, bool $auto_delete = true): string {
    do { $tmp = sys_get_temp_dir() . '/' . mt_rand(); }
    while (!@mkdir($tmp, $mode));

    if ($auto_delete) {
        register_shutdown_function(function() use ($tmp) { destroydir($tmp); });
    }
    return $tmp;
}

This means that by default any temporary directory created by tempdir() will have permissions of 0700 and will be automatically deleted (along with its contents) when your script ends.

NOTE: *This may not be the case if the script is killed, for this you might need to look into registering a signal handler as well.



回答5:

The "mkdir" function raises a warning if the directory already exists, so you can catch this using "@mkdir" and avoid any race condition:

function tempDir($parent = null)
{
    // Prechecks
    if ($parent === null) {
        $parent = sys_get_temp_dir();
    }
    $parent = rtrim($parent, '/');
    if (!is_dir($parent) || !is_writeable($parent)) {
        throw new Exception(sprintf('Parent directory is not writable: %s', $parent));
    }

    // Create directory
    do  { 
        $directory = $parent . '/' . mt_rand();
        $success = @mkdir($directory);
    }
    while (!$success);

    return $directory; 
}


回答6:

There are a lot of overkill answers to this question. One simple answer would be:

$tempdir = tempnam(sys_get_temp_dir()) . 'dir';
mkdir($tempdir);
  1. Obtain a temporary file name.
  2. Create the directory (append a suffix to temp file, to avoid file name collision.)
  3. Done.


回答7:

Another possibility is to use the temporal file as a kind of semaphore to guarantee the unicity of the directory name. Then, create a directory whose name is based on the file name.

define ('TMP_DIR', '/tmp'); // sys_get_temp_dir() PHP 5 >= 5.2.1
define ('TMP_DIR_PREFIX', 'tmpdir_');
define ('TMP_DIR_SUFFIX', '.d');

/* ************************************************************************** */

function createTmpDir() {
  $tmpFile = tempnam(TMP_DIR, TMP_DIR_PREFIX);
  $tmpDir = $tmpFile.TMP_DIR_SUFFIX;
  mkdir($tmpDir);
  return $tmpDir;
}

function rmTmpDir($tmpDir) {
  $offsetSuffix = -1 * strlen(TMP_DIR_SUFFIX);
  assert(strcmp(substr($tmpDir, $offsetSuffix), TMP_DIR_SUFFIX) === 0);
  $tmpFile = substr($tmpDir, 0, $offsetSuffix);

  // Removes non-empty directory
  $command = "rm -rf $tmpDir/";
  exec($command);
  // rmdir($tmpDir);

  unlink($tmpFile);
}

/* ************************************************************************** */