1
<?php
2
/**
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the LGPL. For more information please see
17
 * <http://phing.info>.
18
 */
19

20
/**
21
 * ManifestTask
22
 *
23
 * Generates a simple Manifest file with optional checksums.
24
 *
25
 *
26
 * Manifest schema:
27
 * ...
28
 * path/to/file     CHECKSUM    [CHECKSUM2]     [CHECKSUM3]
29
 * path/to/secondfile       CHECKSUM    [CHECKSUM2]     [CHECKSUM3]
30
 * ...
31
 *
32
 * Example usage:
33
 * <manifest checksum="crc32" file="${dir_build}/Manifest">
34
 *      <fileset refid="files_build" />
35
 * </manifest>
36
 *
37
 * <manifest checksum="md5,adler32,sha256" file="${dir_build}/Manifest">
38
 *      <fileset refid="files_build" />
39
 * </manifest>
40
 *
41
 * @author David Persson <davidpersson at qeweurope dot org>
42
 *
43
 * @package phing.tasks.ext
44
 *
45
 * @since 2.3.1
46
 */
47
class ManifestTask extends Task
48
{
49
    use FileSetAware;
50

51
    public $taskname = 'manifest';
52

53
    /**
54
     * Action
55
     *
56
     * "w" for reading in files from fileSet
57
     * and writing manifest
58
     *
59
     * or
60
     *
61
     * "r" for reading in files from fileSet
62
     * and checking against manifest
63
     *
64
     * @var string "r" or "w"
65
     */
66
    private $action = 'w';
67

68
    /**
69
     * Enable/Disable checksuming or/and select algorithm
70
     * true defaults to md5
71
     * false disables checksuming
72
     * string "md5,sha256,..." enables generation of multiple checksums
73
     * string "sha256" generates sha256 checksum only
74
     *
75
     * @var bool|string
76
     */
77
    private $checksum = false;
78

79
    /**
80
     * A string used in hashing method
81
     *
82
     * @var string
83
     */
84
    private $salt = '';
85

86
    /**
87
     * Holds some data collected during runtime
88
     *
89
     * @var array
90
     */
91
    private $meta = ['totalFileCount' => 0, 'totalFileSize' => 0];
92

93
    /**
94
     * @var PhingFile The target file passed in the buildfile.
95
     */
96
    private $file;
97

98
    /**
99
     * The setter for the attribute "file".
100
     * This is where the manifest will be written to/read from
101
     *
102
     * @param PhingFile $file Path to readable file
103
     *
104
     * @return void
105
     */
106 1
    public function setFile(PhingFile $file)
107
    {
108 1
        $this->file = $file;
109
    }
110

111
    /**
112
     * The setter for the attribute "checksum"
113
     *
114
     * @param mixed $mixed
115
     *
116
     * @return void
117
     */
118 1
    public function setChecksum($mixed)
119
    {
120 1
        if (is_string($mixed)) {
121 1
            $data = [strtolower($mixed)];
122

123 1
            if (strpos($data[0], ',')) {
124 0
                $data = explode(',', $mixed);
125
            }
126

127 1
            $this->checksum = $data;
128 0
        } elseif ($mixed === true) {
129 0
            $this->checksum = ['md5'];
130
        }
131
    }
132

133
    /**
134
     * The setter for the optional attribute "salt"
135
     *
136
     * @param string $string
137
     *
138
     * @return void
139
     */
140 1
    public function setSalt($string)
141
    {
142 1
        $this->salt = $string;
143
    }
144

145
    /**
146
     * The init method: Do init steps.
147
     *
148
     * {@inheritdoc}
149
     *
150
     * @internal nothing to do here
151
     */
152 1
    public function init()
153
    {
154
    }
155

156
    /**
157
     * Delegate the work.
158
     *
159
     * {@inheritdoc}
160
     */
161 1
    public function main()
162
    {
163 1
        $this->validateAttributes();
164

165 1
        if ($this->action == 'w') {
166 1
            $this->write();
167 0
        } elseif ($this->action == 'r') {
168 0
            $this->read();
169
        }
170
    }
171

172
    /**
173
     * Creates Manifest file
174
     * Writes to $this->file
175
     *
176
     * @throws BuildException
177
     */
178 1
    private function write()
179
    {
180 1
        $project = $this->getProject();
181

182 1
        if (!touch($this->file->getPath())) {
183 0
            throw new BuildException("Unable to write to " . $this->file->getPath() . ".");
184
        }
185

186 1
        $this->log("Writing to " . $this->file->__toString(), Project::MSG_INFO);
187

188 1
        if (is_array($this->checksum)) {
189 1
            $this->log("Using " . implode(', ', $this->checksum) . " for checksuming.", Project::MSG_INFO);
190
        }
191

192 1
        foreach ($this->filesets as $fs) {
193 1
            $dir = $fs->getDir($this->project)->getPath();
194

195 1
            $ds = $fs->getDirectoryScanner($project);
196 1
            $fromDir = $fs->getDir($project);
197 1
            $srcFiles = $ds->getIncludedFiles();
198 1
            $srcDirs = $ds->getIncludedDirectories();
199

200 1
            foreach ($ds->getIncludedFiles() as $file_path) {
201 1
                $line = $file_path;
202 1
                if ($this->checksum) {
203 1
                    foreach ($this->checksum as $algo) {
204 1
                        if (!$hash = $this->hashFile($dir . '/' . $file_path, $algo)) {
205 0
                            throw new BuildException("Hashing $dir/$file_path with $algo failed!");
206
                        }
207

208 1
                        $line .= "\t" . $hash;
209
                    }
210
                }
211 1
                $line .= "\n";
212 1
                $manifest[] = $line;
213 1
                $this->log("Adding file " . $file_path, Project::MSG_VERBOSE);
214 1
                $this->meta['totalFileCount']++;
215 1
                $this->meta['totalFileSize'] += filesize($dir . '/' . $file_path);
216
            }
217
        }
218

219 1
        file_put_contents($this->file, $manifest);
220

221 1
        $this->log(
222 1
            "Done. Total files: " . $this->meta['totalFileCount'] . ". Total file size: " . $this->meta['totalFileSize'] . " bytes.",
223 1
            Project::MSG_INFO
224
        );
225
    }
226

227
    /**
228
     * @todo implement
229
     */
230 0
    private function read()
231
    {
232 0
        throw new BuildException("Checking against manifest not yet supported.");
233
    }
234

235
    /**
236
     * Wrapper method for hash generation
237
     * Automatically selects extension
238
     * Falls back to built-in functions
239
     *
240
     * @link http://www.php.net/mhash
241
     * @link http://www.php.net/hash
242
     *
243
     * @param string $msg The string that should be hashed
244
     * @param string $algo Algorithm
245
     *
246
     * @return mixed  String on success, false if $algo is not available
247
     */
248 1
    private function hash($msg, $algo)
249
    {
250 1
        if (extension_loaded('hash')) {
251 1
            $algo = strtolower($algo);
252

253 1
            if (in_array($algo, hash_algos())) {
254 1
                return hash($algo, $this->salt . $msg);
255
            }
256
        }
257

258 0
        if (extension_loaded('mhash')) {
259 0
            $algo = strtoupper($algo);
260

261 0
            if (defined('MHASH_' . $algo)) {
262 0
                return mhash('MHASH_' . $algo, $this->salt . $msg);
263
            }
264
        }
265

266 0
        switch (strtolower($algo)) {
267 0
            case 'md5':
268 0
                return md5($this->salt . $msg);
269 0
            case 'crc32':
270 0
                return abs(crc32($this->salt . $msg));
271
        }
272

273 0
        return false;
274
    }
275

276
    /**
277
     * Hash a file's contents
278
     *
279
     * @param string $file
280
     * @param string $algo
281
     *
282
     * @return mixed  String on success, false if $algo is not available
283
     */
284 1
    private function hashFile($file, $algo)
285
    {
286 1
        if (!file_exists($file)) {
287 0
            return false;
288
        }
289

290 1
        $msg = file_get_contents($file);
291

292 1
        return $this->hash($msg, $algo);
293
    }
294

295
    /**
296
     * Validates attributes coming in from XML
297
     *
298
     * @return void
299
     *
300
     * @throws BuildException
301
     */
302 1
    protected function validateAttributes()
303
    {
304 1
        if ($this->action != 'r' && $this->action != 'w') {
305 0
            throw new BuildException("'action' attribute has non valid value. Use 'r' or 'w'");
306
        }
307

308 1
        if (empty($this->salt)) {
309 0
            $this->log("No salt provided. Specify one with the 'salt' attribute.", Project::MSG_WARN);
310
        }
311

312 1
        if (null === $this->file && count($this->filesets) === 0) {
313 0
            throw new BuildException("Specify at least sources and destination - a file or a fileset.");
314
        }
315

316 1
        if (null !== $this->file && $this->file->exists() && $this->file->isDirectory()) {
317 0
            throw new BuildException("Destination file cannot be a directory.");
318
        }
319
    }
320
}

Read our documentation on viewing source code .

Loading