Passed
Push — master ( cbf6d3...d62b89 )
by Michiel
08:56
created

ManifestTask   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 273
Duplicated Lines 0 %

Test Coverage

Coverage 72.09%

Importance

Changes 0
Metric Value
wmc 37
eloc 78
dl 0
loc 273
ccs 62
cts 86
cp 0.7209
rs 9.44
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 2 1
A setSalt() 0 3 1
A setChecksum() 0 12 4
A main() 0 8 3
A setFile() 0 3 1
B hash() 0 26 7
B validateAttributes() 0 16 9
B write() 0 48 8
A read() 0 3 1
A hashFile() 0 9 2
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
use Phing\Exception\BuildException;
21
use Phing\Io\File;
22
use Phing\Project;
23
use Phing\Task;
24
use Phing\Type\Element\FileSetAware;
25
26
/**
27
 * ManifestTask
28
 *
29
 * Generates a simple Manifest file with optional checksums.
30
 *
31
 *
32
 * Manifest schema:
33
 * ...
34
 * path/to/file     CHECKSUM    [CHECKSUM2]     [CHECKSUM3]
35
 * path/to/secondfile       CHECKSUM    [CHECKSUM2]     [CHECKSUM3]
36
 * ...
37
 *
38
 * Example usage:
39
 * <manifest checksum="crc32" file="${dir_build}/Manifest">
40
 *      <fileset refid="files_build" />
41
 * </manifest>
42
 *
43
 * <manifest checksum="md5,adler32,sha256" file="${dir_build}/Manifest">
44
 *      <fileset refid="files_build" />
45
 * </manifest>
46
 *
47
 * @author David Persson <davidpersson at qeweurope dot org>
48
 *
49
 * @package phing.tasks.ext
50
 *
51
 * @since 2.3.1
52
 */
53
class ManifestTask extends Task
54
{
55
    use FileSetAware;
56
57
    public $taskname = 'manifest';
58
59
    /**
60
     * Action
61
     *
62
     * "w" for reading in files from fileSet
63
     * and writing manifest
64
     *
65
     * or
66
     *
67
     * "r" for reading in files from fileSet
68
     * and checking against manifest
69
     *
70
     * @var string "r" or "w"
71
     */
72
    private $action = 'w';
73
74
    /**
75
     * Enable/Disable checksuming or/and select algorithm
76
     * true defaults to md5
77
     * false disables checksuming
78
     * string "md5,sha256,..." enables generation of multiple checksums
79
     * string "sha256" generates sha256 checksum only
80
     *
81
     * @var bool|string
82
     */
83
    private $checksum = false;
84
85
    /**
86
     * A string used in hashing method
87
     *
88
     * @var string
89
     */
90
    private $salt = '';
91
92
    /**
93
     * Holds some data collected during runtime
94
     *
95
     * @var array
96
     */
97
    private $meta = ['totalFileCount' => 0, 'totalFileSize' => 0];
98
99
    /**
100
     * @var File The target file passed in the buildfile.
101
     */
102
    private $file;
103
104
    /**
105
     * The setter for the attribute "file".
106
     * This is where the manifest will be written to/read from
107
     *
108
     * @param File $file Path to readable file
109
     *
110
     * @return void
111
     */
112 1
    public function setFile(File $file)
113
    {
114 1
        $this->file = $file;
115 1
    }
116
117
    /**
118
     * The setter for the attribute "checksum"
119
     *
120
     * @param mixed $mixed
121
     *
122
     * @return void
123
     */
124 1
    public function setChecksum($mixed)
125
    {
126 1
        if (is_string($mixed)) {
127 1
            $data = [strtolower($mixed)];
128
129 1
            if (strpos($data[0], ',')) {
130
                $data = explode(',', $mixed);
131
            }
132
133 1
            $this->checksum = $data;
0 ignored issues
show
Documentation Bug introduced by
It seems like $data of type array<integer,string> or string[] is incompatible with the declared type boolean|string of property $checksum.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
134
        } elseif ($mixed === true) {
135
            $this->checksum = ['md5'];
136
        }
137 1
    }
138
139
    /**
140
     * The setter for the optional attribute "salt"
141
     *
142
     * @param string $string
143
     *
144
     * @return void
145
     */
146 1
    public function setSalt($string)
147
    {
148 1
        $this->salt = $string;
149 1
    }
150
151
    /**
152
     * The init method: Do init steps.
153
     *
154
     * {@inheritdoc}
155
     *
156
     * @internal nothing to do here
157
     */
158 1
    public function init()
159
    {
160 1
    }
161
162
    /**
163
     * Delegate the work.
164
     *
165
     * {@inheritdoc}
166
     */
167 1
    public function main()
168
    {
169 1
        $this->validateAttributes();
170
171 1
        if ($this->action == 'w') {
172 1
            $this->write();
173
        } elseif ($this->action == 'r') {
174
            $this->read();
175
        }
176 1
    }
177
178
    /**
179
     * Creates Manifest file
180
     * Writes to $this->file
181
     *
182
     * @throws BuildException
183
     */
184 1
    private function write()
185
    {
186 1
        $project = $this->getProject();
187
188 1
        if (!touch($this->file->getPath())) {
189
            throw new BuildException("Unable to write to " . $this->file->getPath() . ".");
190
        }
191
192 1
        $this->log("Writing to " . $this->file->__toString(), Project::MSG_INFO);
193
194 1
        if (is_array($this->checksum)) {
0 ignored issues
show
introduced by
The condition is_array($this->checksum) is always false.
Loading history...
195 1
            $this->log("Using " . implode(', ', $this->checksum) . " for checksuming.", Project::MSG_INFO);
196
        }
197
198 1
        $manifest = [];
199
200 1
        foreach ($this->filesets as $fs) {
201 1
            $dir = $fs->getDir($this->project)->getPath();
202
203 1
            $ds = $fs->getDirectoryScanner($project);
204 1
            $fromDir = $fs->getDir($project);
0 ignored issues
show
Unused Code introduced by
The assignment to $fromDir is dead and can be removed.
Loading history...
205 1
            $srcFiles = $ds->getIncludedFiles();
0 ignored issues
show
Unused Code introduced by
The assignment to $srcFiles is dead and can be removed.
Loading history...
206 1
            $srcDirs = $ds->getIncludedDirectories();
0 ignored issues
show
Unused Code introduced by
The assignment to $srcDirs is dead and can be removed.
Loading history...
207
208 1
            foreach ($ds->getIncludedFiles() as $file_path) {
209 1
                $line = $file_path;
210 1
                if ($this->checksum) {
211 1
                    foreach ($this->checksum as $algo) {
0 ignored issues
show
Bug introduced by
The expression $this->checksum of type string|true is not traversable.
Loading history...
212 1
                        if (!$hash = $this->hashFile($dir . '/' . $file_path, $algo)) {
213
                            throw new BuildException("Hashing $dir/$file_path with $algo failed!");
214
                        }
215
216 1
                        $line .= "\t" . $hash;
217
                    }
218
                }
219 1
                $line .= "\n";
220 1
                $manifest[] = $line;
221 1
                $this->log("Adding file " . $file_path, Project::MSG_VERBOSE);
222 1
                $this->meta['totalFileCount']++;
223 1
                $this->meta['totalFileSize'] += filesize($dir . '/' . $file_path);
224
            }
225
        }
226
227 1
        file_put_contents($this->file, $manifest);
228
229 1
        $this->log(
230 1
            "Done. Total files: " . $this->meta['totalFileCount'] . ". Total file size: " . $this->meta['totalFileSize'] . " bytes.",
231 1
            Project::MSG_INFO
232
        );
233 1
    }
234
235
    /**
236
     * @todo implement
237
     */
238
    private function read()
239
    {
240
        throw new BuildException("Checking against manifest not yet supported.");
241
    }
242
243
    /**
244
     * Wrapper method for hash generation
245
     * Automatically selects extension
246
     * Falls back to built-in functions
247
     *
248
     * @link http://www.php.net/mhash
249
     * @link http://www.php.net/hash
250
     *
251
     * @param string $msg The string that should be hashed
252
     * @param string $algo Algorithm
253
     *
254
     * @return mixed  String on success, false if $algo is not available
255
     */
256 1
    private function hash($msg, $algo)
257
    {
258 1
        if (extension_loaded('hash')) {
259 1
            $algo = strtolower($algo);
260
261 1
            if (in_array($algo, hash_algos())) {
262 1
                return hash($algo, $this->salt . $msg);
263
            }
264
        }
265
266
        if (extension_loaded('mhash')) {
267
            $algo = strtoupper($algo);
268
269
            if (defined('MHASH_' . $algo)) {
270
                return mhash('MHASH_' . $algo, $this->salt . $msg);
0 ignored issues
show
Bug introduced by
'MHASH_' . $algo of type string is incompatible with the type integer expected by parameter $algo of mhash(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

270
                return mhash(/** @scrutinizer ignore-type */ 'MHASH_' . $algo, $this->salt . $msg);
Loading history...
271
            }
272
        }
273
274
        switch (strtolower($algo)) {
275
            case 'md5':
276
                return md5($this->salt . $msg);
277
            case 'crc32':
278
                return abs(crc32($this->salt . $msg));
279
        }
280
281
        return false;
282
    }
283
284
    /**
285
     * Hash a file's contents
286
     *
287
     * @param string $file
288
     * @param string $algo
289
     *
290
     * @return mixed  String on success, false if $algo is not available
291
     */
292 1
    private function hashFile($file, $algo)
293
    {
294 1
        if (!file_exists($file)) {
295
            return false;
296
        }
297
298 1
        $msg = file_get_contents($file);
299
300 1
        return $this->hash($msg, $algo);
301
    }
302
303
    /**
304
     * Validates attributes coming in from XML
305
     *
306
     * @return void
307
     *
308
     * @throws BuildException
309
     */
310 1
    protected function validateAttributes()
311
    {
312 1
        if ($this->action != 'r' && $this->action != 'w') {
313
            throw new BuildException("'action' attribute has non valid value. Use 'r' or 'w'");
314
        }
315
316 1
        if (empty($this->salt)) {
317
            $this->log("No salt provided. Specify one with the 'salt' attribute.", Project::MSG_WARN);
318
        }
319
320 1
        if (null === $this->file && count($this->filesets) === 0) {
321
            throw new BuildException("Specify at least sources and destination - a file or a fileset.");
322
        }
323
324 1
        if (null !== $this->file && $this->file->exists() && $this->file->isDirectory()) {
325
            throw new BuildException("Destination file cannot be a directory.");
326
        }
327 1
    }
328
}
329