Passed
Push — main ( ddd1a4...b98cb8 )
by Michiel
08:47
created

PharPackageTask::setCliStub()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
ccs 0
cts 2
cp 0
cc 1
nc 1
nop 1
crap 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
namespace Phing\Task\System;
21
22
use Exception;
23
use Phar;
24
use Phing\Exception\BuildException;
25
use Phing\Io\File;
26
use Phing\Project;
27
use Phing\Type\FileSet;
28
29
/**
30
 * Package task for {@link http://www.php.net/manual/en/book.phar.php Phar technology}.
31
 *
32
 * @package phing.tasks.ext
33
 * @author  Alexey Shockov <[email protected]>
34
 * @since   2.4.0
35
 */
36
class PharPackageTask extends MatchingTask
37
{
38
    /**
39
     * @var File
40
     */
41
    private $destinationFile;
42
43
    /**
44
     * @var int
45
     */
46
    private $compression = Phar::NONE;
47
48
    /**
49
     * Base directory, from where local package paths will be calculated.
50
     *
51
     * @var File
52
     */
53
    private $baseDirectory;
54
55
    /**
56
     * @var File
57
     */
58
    private $cliStubFile;
59
60
    /**
61
     * @var File
62
     */
63
    private $webStubFile;
64
65
    /**
66
     * @var string
67
     */
68
    private $stubPath;
69
70
    /**
71
     * Private key the Phar will be signed with.
72
     *
73
     * @var File
74
     */
75
    private $key;
76
77
    /**
78
     * Password for the private key.
79
     *
80
     * @var string
81
     */
82
    private $keyPassword = '';
83
84
    /**
85
     * @var int
86
     */
87
    private $signatureAlgorithm = Phar::SHA1;
88
89
    /**
90
     * @var array
91
     */
92
    private $filesets = [];
93
94
    /**
95
     * @var PharMetadata
96
     */
97
    private $metadata = null;
98
99
    /**
100
     * @var string
101
     */
102
    private $alias;
103
104
    /**
105
     * @return PharMetadata
106
     */
107
    public function createMetadata()
108
    {
109
        return ($this->metadata = new PharMetadata());
110
    }
111
112
    /**
113
     * @return FileSet
114
     */
115
    public function createFileSet()
116
    {
117
        $this->fileset = new FileSet();
118
        $this->filesets[] = $this->fileset;
119
120
        return $this->fileset;
121
    }
122
123
    /**
124
     * Signature algorithm (md5, sha1, sha256, sha512, openssl),
125
     * used for this package.
126
     *
127
     * @param string $algorithm
128
     */
129
    public function setSignature($algorithm)
130
    {
131
        /*
132
         * If we don't support passed algprithm, leave old one.
133
         */
134
        switch ($algorithm) {
135
            case 'md5':
136
                $this->signatureAlgorithm = Phar::MD5;
137
                break;
138
            case 'sha1':
139
                $this->signatureAlgorithm = Phar::SHA1;
140
                break;
141
            case 'sha256':
142
                $this->signatureAlgorithm = Phar::SHA256;
143
                break;
144
            case 'sha512':
145
                $this->signatureAlgorithm = Phar::SHA512;
146
                break;
147
            case 'openssl':
148
                $this->signatureAlgorithm = Phar::OPENSSL;
149
                break;
150
            default:
151
                break;
152
        }
153
    }
154
155
    /**
156
     * Compression type (gzip, bzip2, none) to apply to the packed files.
157
     *
158
     * @param string $compression
159
     */
160
    public function setCompression($compression)
161
    {
162
        /**
163
         * If we don't support passed compression, leave old one.
164
         */
165
        switch ($compression) {
166
            case 'gzip':
167
                $this->compression = Phar::GZ;
168
                break;
169
            case 'bzip2':
170
                $this->compression = Phar::BZ2;
171
                break;
172
            default:
173
                break;
174
        }
175
    }
176
177
    /**
178
     * Destination (output) file.
179
     *
180
     * @param File $destinationFile
181
     */
182
    public function setDestFile(File $destinationFile)
183
    {
184
        $this->destinationFile = $destinationFile;
185
    }
186
187
    /**
188
     * Base directory, which will be deleted from each included file (from path).
189
     * Paths with deleted basedir part are local paths in package.
190
     *
191
     * @param File $baseDirectory
192
     */
193
    public function setBaseDir(File $baseDirectory)
194
    {
195
        $this->baseDirectory = $baseDirectory;
196
    }
197
198
    /**
199
     * Relative path within the phar package to run,
200
     * if accessed on the command line.
201
     *
202
     * @param File $stubFile
203
     */
204
    public function setCliStub(File $stubFile)
205
    {
206
        $this->cliStubFile = $stubFile;
207
    }
208
209
    /**
210
     * Relative path within the phar package to run,
211
     * if accessed through a web browser.
212
     *
213
     * @param File $stubFile
214
     */
215
    public function setWebStub(File $stubFile)
216
    {
217
        $this->webStubFile = $stubFile;
218
    }
219
220
    /**
221
     * A path to a php file that contains a custom stub.
222
     *
223
     * @param string $stubPath
224
     */
225
    public function setStub($stubPath)
226
    {
227
        $this->stubPath = $stubPath;
228
    }
229
230
    /**
231
     * An alias to assign to the phar package.
232
     *
233
     * @param string $alias
234
     */
235
    public function setAlias($alias)
236
    {
237
        $this->alias = $alias;
238
    }
239
240
    /**
241
     * Sets the private key to use to sign the Phar with.
242
     *
243
     * @param File $key Private key to sign the Phar with.
244
     */
245
    public function setKey(File $key)
246
    {
247
        $this->key = $key;
248
    }
249
250
    /**
251
     * Password for the private key.
252
     *
253
     * @param string $keyPassword
254
     */
255
    public function setKeyPassword($keyPassword)
256
    {
257
        $this->keyPassword = $keyPassword;
258
    }
259
260
    /**
261
     * @throws BuildException
262
     */
263
    public function main()
264
    {
265
        $this->checkPreconditions();
266
267
        try {
268
            $this->log(
269
                'Building package: ' . $this->destinationFile->__toString(),
270
                Project::MSG_INFO
271
            );
272
273
            /**
274
             * Delete old package, if exists.
275
             */
276
            if ($this->destinationFile->exists()) {
277
                /**
278
                 * TODO Check operation for errors...
279
                 */
280
                $this->destinationFile->delete();
281
            }
282
283
            $phar = $this->buildPhar();
284
            $phar->startBuffering();
285
286
            $baseDirectory = realpath($this->baseDirectory->getPath());
287
288
            foreach ($this->filesets as $fileset) {
289
                $this->log(
290
                    'Adding specified files in ' . $fileset->getDir($this->project) . ' to package',
291
                    Project::MSG_VERBOSE
292
                );
293
294
                $phar->buildFromIterator($fileset, $baseDirectory);
295
            }
296
297
            $phar->stopBuffering();
298
299
            /**
300
             * File compression, if needed.
301
             */
302
            if (Phar::NONE != $this->compression) {
303
                $phar->compressFiles($this->compression);
304
            }
305
306
            if ($this->signatureAlgorithm == Phar::OPENSSL) {
307
                // Load up the contents of the key
308
                $keyContents = file_get_contents($this->key);
309
310
                // Attempt to load the given key as a PKCS#12 Cert Store first.
311
                if (openssl_pkcs12_read($keyContents, $certs, $this->keyPassword)) {
312
                    $private = openssl_pkey_get_private($certs['pkey']);
313
                } else {
314
                    // Fall back to a regular PEM-encoded private key.
315
                    // Setup an OpenSSL resource using the private key
316
                    // and tell the Phar to sign it using that key.
317
                    $private = openssl_pkey_get_private($keyContents, $this->keyPassword);
318
                }
319
320
                openssl_pkey_export($private, $pkey);
321
                $phar->setSignatureAlgorithm(Phar::OPENSSL, $pkey);
322
323
                // Get the details so we can get the public key and write that out
324
                // alongside the phar.
325
                $details = openssl_pkey_get_details($private);
326
                file_put_contents($this->destinationFile . '.pubkey', $details['key']);
327
            } else {
328
                $phar->setSignatureAlgorithm($this->signatureAlgorithm);
329
            }
330
        } catch (Exception $e) {
331
            throw new BuildException(
332
                'Problem creating package: ' . $e->getMessage(),
333
                $e,
334
                $this->getLocation()
335
            );
336
        }
337
    }
338
339
    /**
340
     * @throws BuildException
341
     */
342
    private function checkPreconditions()
343
    {
344
        if (ini_get('phar.readonly') == "1") {
345
            throw new BuildException(
346
                "PharPackageTask require phar.readonly php.ini setting to be disabled"
347
            );
348
        }
349
350
        if (!extension_loaded('phar')) {
351
            throw new BuildException(
352
                "PharPackageTask require either PHP 5.3 or better or the PECL's Phar extension"
353
            );
354
        }
355
356
        if (null === $this->destinationFile) {
357
            throw new BuildException("destfile attribute must be set!", $this->getLocation());
358
        }
359
360
        if ($this->destinationFile->exists() && $this->destinationFile->isDirectory()) {
361
            throw new BuildException("destfile is a directory!", $this->getLocation());
362
        }
363
364
        if (!$this->destinationFile->canWrite()) {
365
            throw new BuildException("Can not write to the specified destfile!", $this->getLocation());
366
        }
367
        if (null !== $this->baseDirectory) {
368
            if (!$this->baseDirectory->exists()) {
369
                throw new BuildException(
370
                    "basedir '" . (string) $this->baseDirectory . "' does not exist!",
371
                    $this->getLocation()
372
                );
373
            }
374
        }
375
        if ($this->signatureAlgorithm == Phar::OPENSSL) {
376
            if (!extension_loaded('openssl')) {
377
                throw new BuildException(
378
                    "PHP OpenSSL extension is required for OpenSSL signing of Phars!",
379
                    $this->getLocation()
380
                );
381
            }
382
383
            if (null === $this->key) {
384
                throw new BuildException("key attribute must be set for OpenSSL signing!", $this->getLocation());
385
            }
386
387
            if (!$this->key->exists()) {
388
                throw new BuildException("key '" . (string) $this->key . "' does not exist!", $this->getLocation());
389
            }
390
391
            if (!$this->key->canRead()) {
392
                throw new BuildException("key '" . (string) $this->key . "' cannot be read!", $this->getLocation());
393
            }
394
        }
395
    }
396
397
    /**
398
     * Build and configure Phar object.
399
     *
400
     * @return Phar
401
     */
402
    private function buildPhar()
403
    {
404
        $phar = new Phar($this->destinationFile);
405
406
        if (!empty($this->stubPath)) {
407
            $phar->setStub(file_get_contents($this->stubPath));
408
        } else {
409
            if (!empty($this->cliStubFile)) {
410
                $cliStubFile = str_replace('\\', '/', $this->cliStubFile->getPathWithoutBase($this->baseDirectory));
411
            } else {
412
                $cliStubFile = null;
413
            }
414
415
            if (!empty($this->webStubFile)) {
416
                $webStubFile = str_replace('\\', '/', $this->webStubFile->getPathWithoutBase($this->baseDirectory));
417
            } else {
418
                $webStubFile = null;
419
            }
420
421
            $phar->setDefaultStub($cliStubFile, $webStubFile);
422
        }
423
424
        if ($this->metadata === null) {
425
            $this->createMetaData();
426
        }
427
428
        if ($metadata = $this->metadata->toArray()) {
429
            $phar->setMetadata($metadata);
430
        }
431
432
        if (!empty($this->alias)) {
433
            $phar->setAlias($this->alias);
434
        }
435
436
        return $phar;
437
    }
438
}
439