Merge pull request #12340 from eileenmcnaughton/merge_cleanup
[civicrm-core.git] / CRM / Utils / Zip.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2018 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2018
32 */
33
34 /**
35 * Utilities for working with zip files
36 */
37 class CRM_Utils_Zip {
38
39 /**
40 * Given a zip file which contains a single root directory, determine the root's name.
41 *
42 * @param ZipArchive $zip
43 *
44 * @return mixed
45 * FALSE if #root level items !=1; otherwise, the name of base dir
46 */
47 static public function findBaseDirName(ZipArchive $zip) {
48 $cnt = $zip->numFiles;
49
50 $base = FALSE;
51 $baselen = FALSE;
52
53 for ($i = 0; $i < $cnt; $i++) {
54 $filename = $zip->getNameIndex($i);
55 if ($base === FALSE) {
56 if (preg_match('/^[^\/]+\/$/', $filename) && $filename != './' && $filename != '../') {
57 $base = $filename;
58 $baselen = strlen($filename);
59 }
60 else {
61 return FALSE;
62 }
63 }
64 elseif (0 != substr_compare($base, $filename, 0, $baselen)) {
65 return FALSE;
66 }
67 }
68
69 return $base;
70 }
71
72 /**
73 * Given a zip file, find all directory names in the root
74 *
75 * @param ZipArchive $zip
76 *
77 * @return array(string)
78 * no trailing /
79 */
80 static public function findBaseDirs(ZipArchive $zip) {
81 $cnt = $zip->numFiles;
82 $basedirs = array();
83
84 for ($i = 0; $i < $cnt; $i++) {
85 $filename = $zip->getNameIndex($i);
86 // hypothetically, ./ or ../ would not be legit here
87 if (preg_match('/^[^\/]+\/$/', $filename) && $filename != './' && $filename != '../') {
88 $basedirs[] = rtrim($filename, '/');
89 }
90 }
91
92 return $basedirs;
93 }
94
95 /**
96 * Determine the name of the folder within a zip.
97 *
98 * @param ZipArchive $zip
99 * @param $expected
100 *
101 * @return string|bool
102 * Return string or FALSE
103 */
104 static public function guessBasedir(ZipArchive $zip, $expected) {
105 $candidate = FALSE;
106 $basedirs = CRM_Utils_Zip::findBaseDirs($zip);
107 if (in_array($expected, $basedirs)) {
108 $candidate = $expected;
109 }
110 elseif (count($basedirs) == 1) {
111 $candidate = array_shift($basedirs);
112 }
113 if ($candidate !== FALSE && preg_match('/^[a-zA-Z0-9]/', $candidate)) {
114 return $candidate;
115 }
116 else {
117 return FALSE;
118 }
119 }
120
121
122 /**
123 * An inefficient helper for creating a ZIP file from data in memory.
124 * This is only intended for building temp files for unit-testing.
125 *
126 * @param string $zipName
127 * file name.
128 * @param array $dirs
129 * Array, list of directory paths.
130 * @param array $files
131 * Array, keys are file names and values are file contents.
132 * @return bool
133 */
134 static public function createTestZip($zipName, $dirs, $files) {
135 $zip = new ZipArchive();
136 $res = $zip->open($zipName, ZipArchive::CREATE);
137 if ($res === TRUE) {
138 foreach ($dirs as $dir) {
139 if (!$zip->addEmptyDir($dir)) {
140 return FALSE;
141 }
142 }
143 foreach ($files as $fileName => $fileData) {
144 if (!$zip->addFromString($fileName, $fileData)) {
145 return FALSE;
146 }
147 }
148 $zip->close();
149 }
150 else {
151 return FALSE;
152 }
153 return TRUE;
154 }
155
156 }