Passed
Push — master ( b5ca42...8fe651 )
by Siad
05:05
created

ManifestTask::write()   B

Complexity

Conditions 8
Paths 11

Size

Total Lines 46
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 8.021

Importance

Changes 0
Metric Value
cc 8
eloc 28
nc 11
nop 0
dl 0
loc 46
ccs 27
cts 29
cp 0.931
crap 8.021
rs 8.4444
c 0
b 0
f 0
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
25
/**
26
 * ManifestTask
27
 *
28
 * Generates a simple Manifest file with optional checksums.
29
 *
30
 *
31
 * Manifest schema:
32
 * ...
33
 * path/to/file     CHECKSUM    [CHECKSUM2]     [CHECKSUM3]
34
 * path/to/secondfile       CHECKSUM    [CHECKSUM2]     [CHECKSUM3]
35
 * ...
36
 *
37
 * Example usage:
38
 * <manifest checksum="crc32" file="${dir_build}/Manifest">
39
 *      <fileset refid="files_build" />
40
 * </manifest>
41
 *
42
 * <manifest checksum="md5,adler32,sha256" file="${dir_build}/Manifest">
43
 *      <fileset refid="files_build" />
44
 * </manifest>
45
 *
46
 * @author David Persson <davidpersson at qeweurope dot org>
47
 *
48
 * @package phing.tasks.ext
49
 *
50
 * @since 2.3.1
51
 */
52
class ManifestTask extends Task
53
{
54
    use FileSetAware;
55
56
    public $taskname = 'manifest';
57
58
    /**
59
     * Action
60
     *
61
     * "w" for reading in files from fileSet
62
     * and writing manifest
63
     *
64
     * or
65
     *
66
     * "r" for reading in files from fileSet
67
     * and checking against manifest
68
     *
69
     * @var string "r" or "w"
70
     */
71
    private $action = 'w';
72
73
    /**
74
     * Enable/Disable checksuming or/and select algorithm
75
     * true defaults to md5
76
     * false disables checksuming
77
     * string "md5,sha256,..." enables generation of multiple checksums
78
     * string "sha256" generates sha256 checksum only
79
     *
80
     * @var bool|string
81
     */
82
    private $checksum = false;
83
84
    /**
85
     * A string used in hashing method
86
     *
87
     * @var string
88
     */
89
    private $salt = '';
90
91
    /**
92
     * Holds some data collected during runtime
93
     *
94
     * @var array
95
     */
96
    private $meta = ['totalFileCount' => 0, 'totalFileSize' => 0];
97
98
    /**
99
     * @var File The target file passed in the buildfile.
100
     */
101
    private $file;
102
103
    /**
104
     * The setter for the attribute "file".
105
     * This is where the manifest will be written to/read from
106
     *
107
     * @param File $file Path to readable file
108
     *
109
     * @return void
110
     */
111 1
    public function setFile(File $file)
112
    {
113 1
        $this->file = $file;
114 1
    }
115
116
    /**
117
     * The setter for the attribute "checksum"
118
     *
119
     * @param mixed $mixed
120
     *
121
     * @return void
122
     */
123 1
    public function setChecksum($mixed)
124
    {
125 1
        if (is_string($mixed)) {
126 1
            $data = [strtolower($mixed)];
127
128 1
            if (strpos($data[0], ',')) {
129
                $data = explode(',', $mixed);
130
            }
131
132 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...
133
        } elseif ($mixed === true) {
134
            $this->checksum = ['md5'];
135
        }
136 1
    }
137
138
    /**
139
     * The setter for the optional attribute "salt"
140
     *
141
     * @param string $string
142
     *
143
     * @return void
144
     */
145 1
    public function setSalt($string)
146
    {
147 1
        $this->salt = $string;
148 1
    }
149
150
    /**
151
     * The init method: Do init steps.
152
     *
153
     * {@inheritdoc}
154
     *
155
     * @internal nothing to do here
156
     */
157 1
    public function init()
158
    {
159 1
    }
160
161
    /**
162
     * Delegate the work.
163
     *
164
     * {@inheritdoc}
165
     */
166 1
    public function main()
167
    {
168 1
        $this->validateAttributes();
169
170 1
        if ($this->action == 'w') {
171 1
            $this->write();
172
        } elseif ($this->action == 'r') {
173
            $this->read();
174
        }
175 1
    }
176
177
    /**
178
     * Creates Manifest file
179
     * Writes to $this->file
180
     *
181
     * @throws BuildException
182
     */
183 1
    private function write()
184
    {
185 1
        $project = $this->getProject();
186
187 1
        if (!touch($this->file->getPath())) {
188
            throw new BuildException("Unable to write to " . $this->file->getPath() . ".");
189
        }
190
191 1
        $this->log("Writing to " . $this->file->__toString(), Project::MSG_INFO);
192
193 1
        if (is_array($this->checksum)) {
0 ignored issues
show
introduced by
The condition is_array($this->checksum) is always false.
Loading history...
194 1
            $this->log("Using " . implode(', ', $this->checksum) . " for checksuming.", Project::MSG_INFO);
195
        }
196
197 1
        foreach ($this->filesets as $fs) {
198 1
            $dir = $fs->getDir($this->project)->getPath();
199
200 1
            $ds = $fs->getDirectoryScanner($project);
201 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...
202 1
            $srcFiles = $ds->getIncludedFiles();
0 ignored issues
show
Unused Code introduced by
The assignment to $srcFiles is dead and can be removed.
Loading history...
203 1
            $srcDirs = $ds->getIncludedDirectories();
0 ignored issues
show
Unused Code introduced by
The assignment to $srcDirs is dead and can be removed.
Loading history...
204
205 1
            foreach ($ds->getIncludedFiles() as $file_path) {
206 1
                $line = $file_path;
207 1
                if ($this->checksum) {
208 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...
209 1
                        if (!$hash = $this->hashFile($dir . '/' . $file_path, $algo)) {
210
                            throw new BuildException("Hashing $dir/$file_path with $algo failed!");
211
                        }
212
213 1
                        $line .= "\t" . $hash;
214
                    }
215
                }
216 1
                $line .= "\n";
217 1
                $manifest[] = $line;
218 1
                $this->log("Adding file " . $file_path, Project::MSG_VERBOSE);
219 1
                $this->meta['totalFileCount']++;
220 1
                $this->meta['totalFileSize'] += filesize($dir . '/' . $file_path);
221
            }
222
        }
223
224 1
        file_put_contents($this->file, $manifest);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $manifest does not seem to be defined for all execution paths leading up to this point.
Loading history...
225
226 1
        $this->log(
227 1
            "Done. Total files: " . $this->meta['totalFileCount'] . ". Total file size: " . $this->meta['totalFileSize'] . " bytes.",
228 1
            Project::MSG_INFO
229
        );
230 1
    }
231
232
    /**
233
     * @todo implement
234
     */
235
    private function read()
236
    {
237
        throw new BuildException("Checking against manifest not yet supported.");
238
    }
239
240
    /**
241
     * Wrapper method for hash generation
242
     * Automatically selects extension
243
     * Falls back to built-in functions
244
     *
245
     * @link http://www.php.net/mhash
246
     * @link http://www.php.net/hash
247
     *
248
     * @param string $msg The string that should be hashed
249
     * @param string $algo Algorithm
250
     *
251
     * @return mixed  String on success, false if $algo is not available
252
     */
253 1
    private function hash($msg, $algo)
254
    {
255 1
        if (extension_loaded('hash')) {
256 1
            $algo = strtolower($algo);
257
258 1
            if (in_array($algo, hash_algos())) {
259 1
                return hash($algo, $this->salt . $msg);
260
            }
261
        }
262
263
        if (extension_loaded('mhash')) {
264
            $algo = strtoupper($algo);
265
266
            if (defined('MHASH_' . $algo)) {
267
                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

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