Passed
Push — master ( 88d9d6...ec4c8d )
by Tom
04:42
created

Timestamps::determineSignatureBegin()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 51
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 5

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 5
eloc 26
c 2
b 1
f 0
nc 5
nop 0
dl 0
loc 51
ccs 25
cts 25
cp 1
crap 5
rs 9.1928

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * this file is part of pipelines
5
 *
6
 * Copyright (c) 2017-2019 Tom Klingenberg <[email protected]>
7
 *
8
 * terms specific to this file (the "this software and associated
9
 * documentation files"):
10
 *
11
 * Copyright (c) 2015 Jordi Boggiano
12
 *
13
 * Permission is hereby granted, free of charge, to any person obtaining a copy
14
 * of this software and associated documentation files (the "Software"), to deal
15
 * in the Software without restriction, including without limitation the rights
16
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
 * copies of the Software, and to permit persons to whom the Software is furnished
18
 * to do so, subject to the following conditions:
19
 *
20
 * The above copyright notice and this permission notice shall be included in all
21
 * copies or substantial portions of the Software.
22
 *
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29
 * THE SOFTWARE.
30
 */
31
namespace Ktomk\Pipelines\PharBuild;
32
33
class Timestamps
34
{
35
    private $contents;
36
37
    /**
38
     * @param string $file path to the phar file to use
39
     */
40 1
    public function __construct($file)
41
    {
42 1
        $this->contents = file_get_contents($file);
43 1
    }
44
45
    /**
46
     * Updates each file's unix timestamps in the PHAR
47
     *
48
     * The PHAR signature can then be produced in a reproducible manner.
49
     *
50
     * @param int|\DateTime|string|bool $timestamp Date string or DateTime or unix timestamp to use
51
     *
52
     * @throws \LogicException
53
     * @throws \RuntimeException
54
     *
55
     * @return void
56
     */
57 1
    public function updateTimestamps($timestamp = null)
58
    {
59 1
        if ($timestamp instanceof \DateTime) {
60 1
            $timestamp = $timestamp->getTimestamp();
61 1
        } elseif (is_string($timestamp)) {
62 1
            $timestamp = strtotime($timestamp);
63 1
        } elseif (!is_int($timestamp)) {
64 1
            $timestamp = strtotime('1984-12-24T00:00:00Z');
65
        }
66
67
        // detect manifest offset / end of stub
68 1
        if (!preg_match('{__HALT_COMPILER\(\);(?: +\?>)?\r?\n}', $this->contents, $match, PREG_OFFSET_CAPTURE)) {
69
            throw new \RuntimeException('Could not detect the stub\'s end in the phar'); // @codeCoverageIgnore
70
        }
71
72
        // set starting position and skip past manifest length
73 1
        $pos = $match[0][1] + strlen($match[0][0]);
74 1
        $stubEnd = $pos + $this->readUint($pos, 4);
75 1
        $pos += 4;
76
77 1
        $numFiles = $this->readUint($pos, 4);
78 1
        $pos += 4;
79
80
        // skip API version (YOLO)
81 1
        $pos += 2;
82
83
        // skip PHAR flags
84 1
        $pos += 4;
85
86 1
        $aliasLength = $this->readUint($pos, 4);
87 1
        $pos += 4 + $aliasLength;
88
89 1
        $metadataLength = $this->readUint($pos, 4);
90 1
        $pos += 4 + $metadataLength;
91
92 1
        while ($pos < $stubEnd) {
93 1
            $filenameLength = $this->readUint($pos, 4);
94 1
            $pos += 4 + $filenameLength;
95
96
            // skip filesize
97 1
            $pos += 4;
98
99
            // update timestamp to a fixed value
100 1
            $this->contents = substr_replace($this->contents, pack('L', $timestamp), $pos, 4);
101
102
            // skip timestamp, compressed file size and crc32 checksum
103 1
            $pos += 3*4;
104
105
            // update or skip file flags - see Bug #77022, use 0644 over 0666
106
            //                           - see Bug #79082, use 0644 over 0664
107 1
            $fileFlags = $this->readUint($pos, 4);
108 1
            $permission = $fileFlags & 0x000001FF;
109 1
            if ($permission !== 0644) {
110
                // @codeCoverageIgnoreStart
111
                $permission = 0644;
112
                $compression = $fileFlags & 0xFFFFF000;
113
                $this->contents = substr_replace($this->contents, pack('L', $permission | $compression), $pos, 4);
114
                // @codeCoverageIgnoreEnd
115
            }
116 1
            $pos += 4;
117
118 1
            $metadataLength = $this->readUint($pos, 4);
119 1
            $pos += 4 + $metadataLength;
120
121 1
            $numFiles--;
122
        }
123
124 1
        if ($numFiles !== 0) {
125
            throw new \LogicException('All files were not processed, something must have gone wrong'); // @codeCoverageIgnore
126
        }
127 1
    }
128
129
    /**
130
     * Saves the updated phar file, optionally with an updated signature.
131
     *
132
     * @param  string $path
133
     * @param  int $signatureAlgo One of Phar::MD5, Phar::SHA1, Phar::SHA256 or Phar::SHA512
134
     * @return bool
135
     * @throws \UnexpectedValueException
136
     */
137 1
    public function save($path, $signatureAlgo)
138
    {
139 1
        $pos = $this->determineSignatureBegin();
140
141
        $algos = array(
142 1
            \Phar::MD5 => 'md5',
143
            \Phar::SHA1 => 'sha1',
144
            \Phar::SHA256 => 'sha256',
145
            \Phar::SHA512 => 'sha512',
146
        );
147
148 1
        if (!isset($algos[$signatureAlgo])) {
149
            throw new \UnexpectedValueException('Invalid hash algorithm given: '.$signatureAlgo.' expected one of Phar::MD5, Phar::SHA1, Phar::SHA256 or Phar::SHA512'); // @codeCoverageIgnore
150
        }
151 1
        $algo = $algos[$signatureAlgo];
152
153
        // re-sign phar
154
        //           signature
155 1
        $signature = hash($algo, substr($this->contents, 0, $pos), true)
156
            // sig type
157 1
            . pack('L', $signatureAlgo)
158
            // ohai Greg & Marcus
159 1
            . 'GBMB';
160
161 1
        $this->contents = substr($this->contents, 0, $pos) . $signature;
162
163 1
        return (bool) file_put_contents($path, $this->contents);
164
    }
165
166
    /**
167
     * @param $pos
168
     * @param int $bytes
169
     *
170
     * @return mixed
171
     */
172 1
    private function readUint($pos, $bytes)
173
    {
174 1
        $res = /** @scrutinizer ignore-call */ unpack('V', substr($this->contents, $pos, $bytes));
175
176 1
        return $res[1];
177
    }
178
179
    /**
180
     * Determine the beginning of the signature.
181
     *
182
     * @return int
183
     * @throws \LogicException
184
     * @throws \RuntimeException
185
     */
186 1
    private function determineSignatureBegin()
187
    {
188
        // detect signature position
189 1
        if (!preg_match('{__HALT_COMPILER\(\);(?: +\?>)?\r?\n}', $this->contents, $match, PREG_OFFSET_CAPTURE)) {
190
            throw new \RuntimeException('Could not detect the stub\'s end in the phar'); // @codeCoverageIgnore
191
        }
192
193
        // set starting position and skip past manifest length
194 1
        $pos = $match[0][1] + strlen($match[0][0]);
195 1
        $manifestEnd = $pos + 4 + $this->readUint($pos, 4);
196
197 1
        $pos += 4;
198 1
        $numFiles = $this->readUint($pos, 4);
199
200 1
        $pos += 4;
201
202
        // skip API version (YOLO)
203 1
        $pos += 2;
204
205
        // skip PHAR flags
206 1
        $pos += 4;
207
208 1
        $aliasLength = $this->readUint($pos, 4);
209 1
        $pos += 4 + $aliasLength;
210
211 1
        $metadataLength = $this->readUint($pos, 4);
212 1
        $pos += 4 + $metadataLength;
213
214 1
        $compressedSizes = 0;
215 1
        while (($numFiles > 0) && ($pos < $manifestEnd - 24)) {
216 1
            $filenameLength = $this->readUint($pos, 4);
217 1
            $pos += 4 + $filenameLength;
218
219
            // skip filesize and timestamp
220 1
            $pos += 2*4;
221
222 1
            $compressedSizes += $this->readUint($pos, 4);
223
            // skip compressed file size, crc32 checksum and file flags
224 1
            $pos += 3*4;
225
226 1
            $metadataLength = $this->readUint($pos, 4);
227 1
            $pos += 4 + $metadataLength;
228
229 1
            $numFiles--;
230
        }
231
232 1
        if ($numFiles !== 0) {
233
            throw new \LogicException('All files were not processed, something must have gone wrong'); // @codeCoverageIgnore
234
        }
235
236 1
        return $manifestEnd + $compressedSizes;
237
    }
238
}
239