CVE-2014-9767: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: Directory traversal vulnerability in the ZipArchive::extractTo function in ext/zip/php_zip.c in PHP before 5.4.45, 5.5.x before 5.5.29, and 5.6.x before 5.6.13 and ext/zip/ext_zip.cpp in HHVM before 3.12.1 allows remote attackers to create arbitrary empty directories via a crafted ZIP archive.
CWE description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
CVSS v3 base score: 4.3
Published: 2016-05-22T01:59Z
Repository: https://github.com/facebook/hhvm
Language: PHP
Files changed: hphp/runtime/ext/zip/ext_zip.cpp, hphp/test/slow/ext_zlib/ziparchive_extractto_directory.php, hphp/test/slow/ext_zlib/ziparchive_extractto_directory.php.expect, hphp/test/slow/ext_zlib/ziparchive_extractto_file.php, hphp/test/slow/ext_zlib/ziparchive_extractto_file.php.expect
Commit date: 2016-02-29 16:00:53 -0800
Commit message: ZipArchive::extractTo bug 70350

Summary:Don't allow upward directory traversal when extracting zip archive files.

Files in zip files with `..` or starting at main root `/` should be normalized
to something where the file being extracted winds up within the directory or
a subdirectory where the actual extraction is taking place.

http://git.php.net/?p=php-src.git;a=commit;h=f9c2bf73adb2ede0a486b0db466c264f2b27e0bb

Reviewed By: FBNeal

Differential Revision: D2798452

fb-gh-sync-id: 844549c93e011d1e991bb322bf85822246b04e30
shipit-source-id: 844549c93e011d1e991bb322bf85822246b04e30

Diff: diff --git a/hphp/runtime/ext/zip/ext_zip.cpp b/hphp/runtime/ext/zip/ext_zip.cpp
index 7b060430ecd..8862c7a638a 100644
--- a/hphp/runtime/ext/zip/ext_zip.cpp
+++ b/hphp/runtime/ext/zip/ext_zip.cpp
@@ -15,10 +15,11 @@
    +----------------------------------------------------------------------+
 */
 #include <zip.h>
 
 #include "hphp/runtime/base/array-init.h"
+#include "hphp/runtime/base/file-util.h"
 #include "hphp/runtime/base/preg.h"
 #include "hphp/runtime/base/stream-wrapper-registry.h"
 #include "hphp/runtime/ext/extension.h"
 #include "hphp/runtime/ext/pcre/ext_pcre.h"
 #include "hphp/runtime/ext/std/ext_std_file.h"
@@ -648,29 +649,107 @@ static bool HHVM_METHOD(ZipArchive, deleteName, const String& name) {
 
   zip_error_clear(zipDir->getZip());
   return true;
 }
 
+// Make the path relative to "." by flattening.
+// This function is named the same and similar in implementation to that in
+// php-src:php_zip.c
+// One difference is that we canonicalize here whereas php-src is already
+// assumed passed a canonicalized path.
+static std::string make_relative_path(const std::string& path) {
+  if (path.empty()) {
+    return path;
+  }
+
+  // First get the path to a state where we don't have .. in the middle of it
+  // etc. canonicalize handles Windows paths too.
+  std::string canonical(FileUtil::canonicalize(path));
+
+  // If we have a slash at the beginning, then just remove it and we are
+  // relative. This check will hold because we have canonicalized the
+  // path above to remove .. from the path, so we know we can be sure
+  // we are at a good place for this check.
+  if (FileUtil::isDirSeparator(canonical[0])) {
+    return canonical.substr(1);
+  }
+
+  // If we get here, canonical looks something like:
+  //   a/b/c
+
+  // Search through the path and if we find a place where we have a slash
+  // and a "." just before that slash, then cut the path off right there
+  // and just take everything after the slash.
+  std::string relative(canonical);
+  int idx = canonical.length() - 1;
+  while (1) {
+    while (idx > 0 && !(FileUtil::isDirSeparator(canonical[idx]))) {
+      idx--;
+    }
+    // If we ever get to idx == 0, then there were no other slashes to deal with
+    if (idx == 0) {
+      return canonical;
+    }
+    if (idx >= 1 && (canonical[idx - 1] == '.' || canonical[idx - 1] == ':')) {
+      relative = canonical.substr(idx + 1);
+      break;
+    }
+    idx--;
+  }
+  return relative;
+}
+
 static bool extractFileTo(zip* zip, const std::string &file, std::string& to,
                           char* buf, size_t len) {
-  auto sep = file.rfind('/');
+
+  struct zip_stat zipStat;
+  // Verify the file to be extracted is actually in the zip file
+  if (zip_stat(zip, file.c_str(), 0, &zipStat) != 0) {
+    return false;
+  }
+
+  auto clean_file = file;
+  auto sep = std::string::npos;
+  // Normally would just use std::string::rfind here, but if we want to be
+  // consistent between Windows and Linux, even if techincally Linux won't use
+  // backslash for a separator, we are checking for both types.
+  int idx = file.length() - 1;
+  while (idx >= 0) {
+    if (FileUtil::isDirSeparator(file[idx])) {
+      sep = idx;
+      break;
+    }
+    idx--;
+  }
   if (sep != std::string::npos) {
-    auto path = to + file.substr(0, sep);
+    // make_relative_path so we do not try to put files or dirs in bad
+    // places. This securely "cleans" the file.
+    clean_file = make_relative_path(file);
+    std::string path = to + clean_file;
+    bool is_dir_only = true;
+    if (sep < file.length() - 1) { // not just a directory
+      auto clean_file_dir = HHVM_FN(dirname)(clean_file);
+      path = to + clean_file_dir.toCppString();
+      is_dir_only = false;
+    }
+
+    // Make sure the directory path to extract to exists or can be created
     if (!HHVM_FN(is_dir)(path) && !HHVM_FN(mkdir)(path, 0777, true)) {
       return false;
     }
 
-    if (sep == file.length() - 1) {
+    // If we have a good directory to extract to above, we now check whether
+    // the "file" parameter passed in is a directory or actually a file.
+    if (is_dir_only) { // directory, like /usr/bin/
       return true;
     }
+    // otherwise file is actually a file, so we actually extract.
   }
 
-  to.append(file);
-  struct zip_stat zipStat;
-  if (zip_stat(zip, file.c_str(), 0, &zipStat) != 0) {
-    return false;
-  }
+  // We have ensured that clean_file will be added to a relative path by the
+  // time we get here.
+  to.append(clean_file);
 
   auto zipFile = zip_fopen_index(zip, zipStat.index, 0);
   FAIL_IF_INVALID_PTR(zipFile);
 
   auto outFile = fopen(to.c_str(), "wb");
diff --git a/hphp/test/slow/ext_zlib/ziparchive_extractto_directory.php b/hphp/test/slow/ext_zlib/ziparchive_extractto_directory.php
new file mode 100644
index 00000000000..9367ec21443
--- /dev/null
+++ b/hphp/test/slow/ext_zlib/ziparchive_extractto_directory.php
@@ -0,0 +1,57 @@
+<?php
+$dir = tempnam(sys_get_temp_dir(), __FILE__);
+unlink($dir);
+mkdir($dir);
+$archive = new ZipArchive();
+$archive->open("$dir/a.zip",ZipArchive::CREATE);
+$archive->addEmptyDir("../dir1/");
+$archive->addEmptyDir("/var/www/dir2/");
+$archive->addEmptyDir("a/b/../../../dir3");
+$archive->addEmptyDir("a/b/../dir4/");
+$archive->addEmptyDir("a/b/../c/../../d/dir5/");
+$archive->addEmptyDir("./dir6");
+$archive->addEmptyDir("x/y/dir7/..");
+$archive->addEmptyDir("z/dir8/.");
+$archive->addEmptyDir("simple");
+$archive->close();
+$archive2 = new ZipArchive();
+$archive2->open("$dir/a.zip");
+$archive2->extractTo($dir);
+$archive2->close();
+var_dump(file_exists("$dir/dir1/")); // true
+var_dump(file_exists("../dir1/")); // false
+var_dump(file_exists("$dir/var/www/dir2")); // true
+var_dump(file_exists("/var/www/dir2/")); // false
+var_dump(file_exists("$dir/dir3/")); // true
+var_dump(file_exists("a/b/../../../dir3/")); // false
+var_dump(file_exists("$dir/a/dir4/")); // true
+var_dump(file_exists("a/b/../dir4/")); // false
+var_dump(file_exists("$dir/d/dir5/")); // true
+var_dump(file_exists("a/b/../c/../../d/dir5/")); // false
+var_dump(file_exists("$dir/dir6")); // true
+var_dump(file_exists("./dir6")); // false
+var_dump(file_exists("$dir/x/y/")); // true
+var_dump(file_exists("x/y/dir7/..")); // false
+var_dump(file_exists("$dir/z/dir8")); // true
+var_dump(file_exists("z/dir8/.")); // false
+var_dump(file_exists("$dir/simple")); // true
+var_dump(file_exists("simple")); // false
+
+// Cleanup. Also verifies that everything is where it is supposed to be.
+rmdir("$dir/dir1");
+rmdir("$dir/var/www/dir2");
+rmdir("$dir/var/www");
+rmdir("$dir/var");
+rmdir("$dir/dir3");
+rmdir("$dir/a/dir4");
+rmdir("$dir/a");
+rmdir("$dir/d/dir5");
+rmdir("$dir/d");
+rmdir("$dir/dir6");
+rmdir("$dir/x/y");
+rmdir("$dir/x");
+rmdir("$dir/z/dir8");
+rmdir("$dir/z");
+rmdir("$dir/simple");
+unlink("$dir/a.zip");
+rmdir($dir);
diff --git a/hphp/test/slow/ext_zlib/ziparchive_extractto_directory.php.expect b/hphp/test/slow/ext_zlib/ziparchive_extractto_directory.php.expect
new file mode 100644
index 00000000000..51e54ef72d9
--- /dev/null
+++ b/hphp/test/slow/ext_zlib/ziparchive_extractto_directory.php.expect
@@ -0,0 +1,18 @@
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
diff --git a/hphp/test/slow/ext_zlib/ziparchive_extractto_file.php b/hphp/test/slow/ext_zlib/ziparchive_extractto_file.php
new file mode 100644
index 00000000000..8ec75263ba5
--- /dev/null
+++ b/hphp/test/slow/ext_zlib/ziparchive_extractto_file.php
@@ -0,0 +1,75 @@
+<?php
+$str = 'temp';
+$dir = tempnam(sys_get_temp_dir(), __FILE__);
+unlink($dir);
+mkdir($dir);
+$archive = new ZipArchive();
+$archive->open("$dir/a.zip",ZipArchive::CREATE);
+$archive->addFromString("../dir1/A.txt", $str);
+$archive->addFromString("/var/www/dir2/B", $str);
+$archive->addFromString("a/b/../../../dir3/C.txt.other", $str);
+$archive->addFromString("a/b/../dir4/D.not.a.file/D.a.file", $str);
+$archive->addFromString("a/b/../c/../../d/dir5/E", $str);
+$archive->addFromString("./dir6/F.exe", $str);
+$archive->addFromString("./G.txt", $str);
+$archive->addFromString("H.txt", $str);
+$archive->addFromString("x/y/dir7/../I.txt", $str);
+$archive->addFromString("z/dir8/./J.txt", $str);
+$archive->addFromString("SIMPLE.txt", $str);
+$archive->close();
+$archive2 = new ZipArchive();
+$archive2->open("$dir/a.zip");
+$archive2->extractTo($dir);
+$archive2->close();
+var_dump(file_exists("$dir/dir1/A.txt")); // true
+var_dump(file_exists("../dir1/A.txt")); // false
+var_dump(file_exists("$dir/var/www/dir2/B")); // true
+var_dump(file_exists("/var/www/dir2/B")); // false
+var_dump(file_exists("$dir/dir3/C.txt.other")); // true
+var_dump(file_exists("a/b/../../../dir3/C.txt.other")); // false
+var_dump(file_exists("$dir/a/dir4/D.not.a.file/D.a.file")); // true
+var_dump(file_exists("a/b/../dir4/D.not.a.file/D.a.file")); // false
+var_dump(file_exists("$dir/d/dir5/E")); // true
+var_dump(file_exists("a/b/../c/../../d/dir5/E")); // false
+var_dump(file_exists("$dir/dir6/F.exe")); // true
+var_dump(file_exists("./dir6/F.exe")); // false
+var_dump(file_exists("$dir/G.txt")); // true
+var_dump(file_exists("./G.txt")); // false
+var_dump(file_exists("$dir/H.txt")); // true
+var_dump(file_exists("H.txt")); // false
+var_dump(file_exists("$dir/x/y/I.txt")); // true
+var_dump(file_exists("x/y/dir7/../I.txt")); // false
+var_dump(file_exists("$dir/z/dir8/J.txt")); // true
+var_dump(file_exists("z/dir8/./J.txt")); // false
+var_dump(file_exists("$dir/SIMPLE.txt")); // true
+var_dump(file_exists("SIMPLE.txt")); // false
+
+// Cleanup. Also verifies that everything is where it is supposed to be.
+unlink("$dir/dir1/A.txt");
+rmdir("$dir/dir1");
+unlink("$dir/var/www/dir2/B");
+rmdir("$dir/var/www/dir2");
+rmdir("$dir/var/www");
+rmdir("$dir/var");
+unlink("$dir/dir3/C.txt.other");
+rmdir("$dir/dir3");
+unlink("$dir/a/dir4/D.not.a.file/D.a.file");
+rmdir("$dir/a/dir4/D.not.a.file");
+rmdir("$dir/a/dir4");
+rmdir("$dir/a");
+unlink("$dir/d/dir5/E");
+rmdir("$dir/d/dir5");
+rmdir("$dir/d");
+unlink("$dir/dir6/F.exe");
+rmdir("$dir/dir6");
+unlink("$dir/x/y/I.txt");
+rmdir("$dir/x/y");
+rmdir("$dir/x");
+unlink("$dir/z/dir8/J.txt");
+rmdir("$dir/z/dir8");
+rmdir("$dir/z");
+unlink("$dir/G.txt");
+unlink("$dir/H.txt");
+unlink("$dir/a.zip");
+unlink("$dir/SIMPLE.txt");
+rmdir($dir);
diff --git a/hphp/test/slow/ext_zlib/ziparchive_extractto_file.php.expect b/hphp/test/slow/ext_zlib/ziparchive_extractto_file.php.expect
new file mode 100644
index 00000000000..d0d102aa372
--- /dev/null
+++ b/hphp/test/slow/ext_zlib/ziparchive_extractto_file.php.expect
@@ -0,0 +1,22 @@
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)

Vulnerable code:   auto sep = file.rfind('/');
    auto path = to + file.substr(0, sep);
    if (sep == file.length() - 1) {
  to.append(file);
  struct zip_stat zipStat;
  if (zip_stat(zip, file.c_str(), 0, &zipStat) != 0) {
    return false;
  }
Fixed code: #include "hphp/runtime/base/file-util.h"
// Make the path relative to "." by flattening.
// This function is named the same and similar in implementation to that in
// php-src:php_zip.c
// One difference is that we canonicalize here whereas php-src is already
// assumed passed a canonicalized path.
static std::string make_relative_path(const std::string& path) {
  if (path.empty()) {
    return path;
  }

  // First get the path to a state where we don't have .. in the middle of it
  // etc. canonicalize handles Windows paths too.
  std::string canonical(FileUtil::canonicalize(path));

  // If we have a slash at the beginning, then just remove it and we are
  // relative. This check will hold because we have canonicalized the
  // path above to remove .. from the path, so we know we can be sure
  // we are at a good place for this check.
  if (FileUtil::isDirSeparator(canonical[0])) {
    return canonical.substr(1);
  }

  // If we get here, canonical looks something like:
  //   a/b/c

  // Search through the path and if we find a place where we have a slash
  // and a "." just before that slash, then cut the path off right there
  // and just take everything after the slash.
  std::string relative(canonical);
  int idx = canonical.length() - 1;
  while (1) {
    while (idx > 0 && !(FileUtil::isDirSeparator(canonical[idx]))) {
      idx--;
    }
    // If we ever get to idx == 0, then there were no other slashes to deal with
    if (idx == 0) {
      return canonical;
    }
    if (idx >= 1 && (canonical[idx - 1] == '.' || canonical[idx - 1] == ':')) {
      relative = canonical.substr(idx + 1);
      break;
    }
    idx--;
  }
  return relative;
}


  struct zip_stat zipStat;
  // Verify the file to be extracted is actually in the zip file
  if (zip_stat(zip, file.c_str(), 0, &zipStat) != 0) {
    return false;
  }

  auto clean_file = file;
  auto sep = std::string::npos;
  // Normally would just use std::string::rfind here, but if we want to be
  // consistent between Windows and Linux, even if techincally Linux won't use
  // backslash for a separator, we are checking for both types.
  int idx = file.length() - 1;
  while (idx >= 0) {
    if (FileUtil::isDirSeparator(file[idx])) {
      sep = idx;
      break;
    }
    idx--;
  }
    // make_relative_path so we do not try to put files or dirs in bad
    // places. This securely "cleans" the file.
    clean_file = make_relative_path(file);
    std::string path = to + clean_file;
    bool is_dir_only = true;
    if (sep < file.length() - 1) { // not just a directory
      auto clean_file_dir = HHVM_FN(dirname)(clean_file);
      path = to + clean_file_dir.toCppString();
      is_dir_only = false;
    }

    // Make sure the directory path to extract to exists or can be created
    // If we have a good directory to extract to above, we now check whether
    // the "file" parameter passed in is a directory or actually a file.
    if (is_dir_only) { // directory, like /usr/bin/
    // otherwise file is actually a file, so we actually extract.
  // We have ensured that clean_file will be added to a relative path by the
  // time we get here.
  to.append(clean_file);
<?php
$dir = tempnam(sys_get_temp_dir(), __FILE__);
unlink($dir);
mkdir($dir);
$archive = new ZipArchive();
$archive->open("$dir/a.zip",ZipArchive::CREATE);
$archive->addEmptyDir("../dir1/");
$archive->addEmptyDir("/var/www/dir2/");
$archive->addEmptyDir("a/b/../../../dir3");
$archive->addEmptyDir("a/b/../dir4/");
$archive->addEmptyDir("a/b/../c/../../d/dir5/");
$archive->addEmptyDir("./dir6");
$archive->addEmptyDir("x/y/dir7/..");
$archive->addEmptyDir("z/dir8/.");
$archive->addEmptyDir("simple");
$archive->close();
$archive2 = new ZipArchive();
$archive2->open("$dir/a.zip");
$archive2->extractTo($dir);
$archive2->close();
var_dump(file_exists("$dir/dir1/")); // true
var_dump(file_exists("../dir1/")); // false
var_dump(file_exists("$dir/var/www/dir2")); // true
var_dump(file_exists("/var/www/dir2/")); // false
var_dump(file_exists("$dir/dir3/")); // true
var_dump(file_exists("a/b/../../../dir3/")); // false
var_dump(file_exists("$dir/a/dir4/")); // true
var_dump(file_exists("a/b/../dir4/")); // false
var_dump(file_exists("$dir/d/dir5/")); // true
var_dump(file_exists("a/b/../c/../../d/dir5/")); // false
var_dump(file_exists("$dir/dir6")); // true
var_dump(file_exists("./dir6")); // false
var_dump(file_exists("$dir/x/y/")); // true
var_dump(file_exists("x/y/dir7/..")); // false
var_dump(file_exists("$dir/z/dir8")); // true
var_dump(file_exists("z/dir8/.")); // false
var_dump(file_exists("$dir/simple")); // true
var_dump(file_exists("simple")); // false

// Cleanup. Also verifies that everything is where it is supposed to be.
rmdir("$dir/dir1");
rmdir("$dir/var/www/dir2");
rmdir("$dir/var/www");
rmdir("$dir/var");
rmdir("$dir/dir3");
rmdir("$dir/a/dir4");
rmdir("$dir/a");
rmdir("$dir/d/dir5");
rmdir("$dir/d");
rmdir("$dir/dir6");
rmdir("$dir/x/y");
rmdir("$dir/x");
rmdir("$dir/z/dir8");
rmdir("$dir/z");
rmdir("$dir/simple");
unlink("$dir/a.zip");
rmdir($dir);
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)
<?php
$str = 'temp';
$dir = tempnam(sys_get_temp_dir(), __FILE__);
unlink($dir);
mkdir($dir);
$archive = new ZipArchive();
$archive->open("$dir/a.zip",ZipArchive::CREATE);
$archive->addFromString("../dir1/A.txt", $str);
$archive->addFromString("/var/www/dir2/B", $str);
$archive->addFromString("a/b/../../../dir3/C.txt.other", $str);
$archive->addFromString("a/b/../dir4/D.not.a.file/D.a.file", $str);
$archive->addFromString("a/b/../c/../../d/dir5/E", $str);
$archive->addFromString("./dir6/F.exe", $str);
$archive->addFromString("./G.txt", $str);
$archive->addFromString("H.txt", $str);
$archive->addFromString("x/y/dir7/../I.txt", $str);
$archive->addFromString("z/dir8/./J.txt", $str);
$archive->addFromString("SIMPLE.txt", $str);
$archive->close();
$archive2 = new ZipArchive();
$archive2->open("$dir/a.zip");
$archive2->extractTo($dir);
$archive2->close();
var_dump(file_exists("$dir/dir1/A.txt")); // true
var_dump(file_exists("../dir1/A.txt")); // false
var_dump(file_exists("$dir/var/www/dir2/B")); // true
var_dump(file_exists("/var/www/dir2/B")); // false
var_dump(file_exists("$dir/dir3/C.txt.other")); // true
var_dump(file_exists("a/b/../../../dir3/C.txt.other")); // false
var_dump(file_exists("$dir/a/dir4/D.not.a.file/D.a.file")); // true
var_dump(file_exists("a/b/../dir4/D.not.a.file/D.a.file")); // false
var_dump(file_exists("$dir/d/dir5/E")); // true
var_dump(file_exists("a/b/../c/../../d/dir5/E")); // false
var_dump(file_exists("$dir/dir6/F.exe")); // true
var_dump(file_exists("./dir6/F.exe")); // false
var_dump(file_exists("$dir/G.txt")); // true
var_dump(file_exists("./G.txt")); // false
var_dump(file_exists("$dir/H.txt")); // true
var_dump(file_exists("H.txt")); // false
var_dump(file_exists("$dir/x/y/I.txt")); // true
var_dump(file_exists("x/y/dir7/../I.txt")); // false
var_dump(file_exists("$dir/z/dir8/J.txt")); // true
var_dump(file_exists("z/dir8/./J.txt")); // false
var_dump(file_exists("$dir/SIMPLE.txt")); // true
var_dump(file_exists("SIMPLE.txt")); // false

// Cleanup. Also verifies that everything is where it is supposed to be.
unlink("$dir/dir1/A.txt");
rmdir("$dir/dir1");
unlink("$dir/var/www/dir2/B");
rmdir("$dir/var/www/dir2");
rmdir("$dir/var/www");
rmdir("$dir/var");
unlink("$dir/dir3/C.txt.other");
rmdir("$dir/dir3");
unlink("$dir/a/dir4/D.not.a.file/D.a.file");
rmdir("$dir/a/dir4/D.not.a.file");
rmdir("$dir/a/dir4");
rmdir("$dir/a");
unlink("$dir/d/dir5/E");
rmdir("$dir/d/dir5");
rmdir("$dir/d");
unlink("$dir/dir6/F.exe");
rmdir("$dir/dir6");
unlink("$dir/x/y/I.txt");
rmdir("$dir/x/y");
rmdir("$dir/x");
unlink("$dir/z/dir8/J.txt");
rmdir("$dir/z/dir8");
rmdir("$dir/z");
unlink("$dir/G.txt");
unlink("$dir/H.txt");
unlink("$dir/a.zip");
unlink("$dir/SIMPLE.txt");
rmdir($dir);
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)