root / drupal7 / misc / typo3 / phar-stream-wrapper / src / Phar / Reader.php @ 6b24a280
1 |
<?php
|
---|---|
2 |
namespace TYPO3\PharStreamWrapper\Phar; |
3 |
|
4 |
/*
|
5 |
* This file is part of the TYPO3 project.
|
6 |
*
|
7 |
* It is free software; you can redistribute it and/or modify it under the terms
|
8 |
* of the MIT License (MIT). For the full copyright and license information,
|
9 |
* please read the LICENSE file that was distributed with this source code.
|
10 |
*
|
11 |
* The TYPO3 project - inspiring people to share!
|
12 |
*/
|
13 |
|
14 |
class Reader |
15 |
{ |
16 |
/**
|
17 |
* @var string
|
18 |
*/
|
19 |
private $fileName; |
20 |
|
21 |
/**
|
22 |
* Mime-type in order to use zlib, bzip2 or no compression.
|
23 |
* In case ext-fileinfo is not present only the relevant types
|
24 |
* 'application/x-gzip' and 'application/x-bzip2' are assigned
|
25 |
* to this class property.
|
26 |
*
|
27 |
* @var string
|
28 |
*/
|
29 |
private $fileType; |
30 |
|
31 |
/**
|
32 |
* @param string $fileName
|
33 |
*/
|
34 |
public function __construct($fileName) |
35 |
{ |
36 |
if (strpos($fileName, '://') !== false) { |
37 |
throw new ReaderException( |
38 |
'File name must not contain stream prefix',
|
39 |
1539623708
|
40 |
); |
41 |
} |
42 |
|
43 |
$this->fileName = $fileName; |
44 |
$this->fileType = $this->determineFileType(); |
45 |
} |
46 |
|
47 |
/**
|
48 |
* @return Container
|
49 |
*/
|
50 |
public function resolveContainer() |
51 |
{ |
52 |
$data = $this->extractData($this->resolveStream() . $this->fileName); |
53 |
|
54 |
if ($data['stubContent'] === null) { |
55 |
throw new ReaderException( |
56 |
'Cannot resolve stub',
|
57 |
1547807881
|
58 |
); |
59 |
} |
60 |
if ($data['manifestContent'] === null || $data['manifestLength'] === null) { |
61 |
throw new ReaderException( |
62 |
'Cannot resolve manifest',
|
63 |
1547807882
|
64 |
); |
65 |
} |
66 |
if (strlen($data['manifestContent']) < $data['manifestLength']) { |
67 |
throw new ReaderException( |
68 |
sprintf(
|
69 |
'Exected manifest length %d, got %d',
|
70 |
strlen($data['manifestContent']), |
71 |
$data['manifestLength'] |
72 |
), |
73 |
1547807883
|
74 |
); |
75 |
} |
76 |
|
77 |
return new Container( |
78 |
Stub::fromContent($data['stubContent']), |
79 |
Manifest::fromContent($data['manifestContent']) |
80 |
); |
81 |
} |
82 |
|
83 |
/**
|
84 |
* @param string $fileName e.g. '/path/file.phar' or 'compress.zlib:///path/file.phar'
|
85 |
* @return array
|
86 |
*/
|
87 |
private function extractData($fileName) |
88 |
{ |
89 |
$stubContent = null; |
90 |
$manifestContent = null; |
91 |
$manifestLength = null; |
92 |
|
93 |
$resource = fopen($fileName, 'r'); |
94 |
if (!is_resource($resource)) { |
95 |
throw new ReaderException( |
96 |
sprintf('Resource %s could not be opened', $fileName), |
97 |
1547902055
|
98 |
); |
99 |
} |
100 |
|
101 |
while (!feof($resource)) { |
102 |
$line = fgets($resource); |
103 |
// stop reading file when manifest can be extracted
|
104 |
if ($manifestLength !== null && $manifestContent !== null && strlen($manifestContent) >= $manifestLength) { |
105 |
break;
|
106 |
} |
107 |
|
108 |
$manifestPosition = strpos($line, '__HALT_COMPILER();'); |
109 |
|
110 |
// first line contains start of manifest
|
111 |
if ($stubContent === null && $manifestContent === null && $manifestPosition !== false) { |
112 |
$stubContent = substr($line, 0, $manifestPosition - 1); |
113 |
$manifestContent = preg_replace('#^.*__HALT_COMPILER\(\);(?>[ \n]\?>(?>\r\n|\n)?)?#', '', $line); |
114 |
$manifestLength = $this->resolveManifestLength($manifestContent); |
115 |
// line contains start of stub
|
116 |
} elseif ($stubContent === null) { |
117 |
$stubContent = $line; |
118 |
// line contains start of manifest
|
119 |
} elseif ($manifestContent === null && $manifestPosition !== false) { |
120 |
$manifestContent = preg_replace('#^.*__HALT_COMPILER\(\);(?>[ \n]\?>(?>\r\n|\n)?)?#', '', $line); |
121 |
$manifestLength = $this->resolveManifestLength($manifestContent); |
122 |
// manifest has been started (thus is cannot be stub anymore), add content
|
123 |
} elseif ($manifestContent !== null) { |
124 |
$manifestContent .= $line; |
125 |
$manifestLength = $this->resolveManifestLength($manifestContent); |
126 |
// stub has been started (thus cannot be manifest here, yet), add content
|
127 |
} elseif ($stubContent !== null) { |
128 |
$stubContent .= $line; |
129 |
} |
130 |
} |
131 |
fclose($resource); |
132 |
|
133 |
return array( |
134 |
'stubContent' => $stubContent, |
135 |
'manifestContent' => $manifestContent, |
136 |
'manifestLength' => $manifestLength, |
137 |
); |
138 |
} |
139 |
|
140 |
/**
|
141 |
* Resolves stream in order to handle compressed Phar archives.
|
142 |
*
|
143 |
* @return string
|
144 |
*/
|
145 |
private function resolveStream() |
146 |
{ |
147 |
if ($this->fileType === 'application/x-gzip' || $this->fileType === 'application/gzip') { |
148 |
return 'compress.zlib://'; |
149 |
} elseif ($this->fileType === 'application/x-bzip2') { |
150 |
return 'compress.bzip2://'; |
151 |
} |
152 |
return ''; |
153 |
} |
154 |
|
155 |
/**
|
156 |
* @return string
|
157 |
*/
|
158 |
private function determineFileType() |
159 |
{ |
160 |
if (class_exists('\\finfo')) { |
161 |
$fileInfo = new \finfo(); |
162 |
return $fileInfo->file($this->fileName, FILEINFO_MIME_TYPE); |
163 |
} |
164 |
return $this->determineFileTypeByHeader(); |
165 |
} |
166 |
|
167 |
/**
|
168 |
* In case ext-fileinfo is not present only the relevant types
|
169 |
* 'application/x-gzip' and 'application/x-bzip2' are resolved.
|
170 |
*
|
171 |
* @return string
|
172 |
*/
|
173 |
private function determineFileTypeByHeader() |
174 |
{ |
175 |
$resource = fopen($this->fileName, 'r'); |
176 |
if (!is_resource($resource)) { |
177 |
throw new ReaderException( |
178 |
sprintf('Resource %s could not be opened', $this->fileName), |
179 |
1557753055
|
180 |
); |
181 |
} |
182 |
$header = fgets($resource, 4); |
183 |
fclose($resource); |
184 |
$mimeType = ''; |
185 |
if (strpos($header, "\x42\x5a\x68") === 0) { |
186 |
$mimeType = 'application/x-bzip2'; |
187 |
} elseif (strpos($header, "\x1f\x8b") === 0) { |
188 |
$mimeType = 'application/x-gzip'; |
189 |
} |
190 |
return $mimeType; |
191 |
} |
192 |
|
193 |
/**
|
194 |
* @param string $content
|
195 |
* @return int|null
|
196 |
*/
|
197 |
private function resolveManifestLength($content) |
198 |
{ |
199 |
if (strlen($content) < 4) { |
200 |
return null; |
201 |
} |
202 |
return static::resolveFourByteLittleEndian($content, 0); |
203 |
} |
204 |
|
205 |
/**
|
206 |
* @param string $content
|
207 |
* @param int $start
|
208 |
* @return int
|
209 |
*/
|
210 |
public static function resolveFourByteLittleEndian($content, $start) |
211 |
{ |
212 |
$payload = substr($content, $start, 4); |
213 |
if (!is_string($payload)) { |
214 |
throw new ReaderException( |
215 |
sprintf('Cannot resolve value at offset %d', $start), |
216 |
1539614260
|
217 |
); |
218 |
} |
219 |
|
220 |
$value = unpack('V', $payload); |
221 |
if (!isset($value[1])) { |
222 |
throw new ReaderException( |
223 |
sprintf('Cannot resolve value at offset %d', $start), |
224 |
1539614261
|
225 |
); |
226 |
} |
227 |
return $value[1]; |
228 |
} |
229 |
|
230 |
/**
|
231 |
* @param string $content
|
232 |
* @param int $start
|
233 |
* @return int
|
234 |
*/
|
235 |
public static function resolveTwoByteBigEndian($content, $start) |
236 |
{ |
237 |
$payload = substr($content, $start, 2); |
238 |
if (!is_string($payload)) { |
239 |
throw new ReaderException( |
240 |
sprintf('Cannot resolve value at offset %d', $start), |
241 |
1539614263
|
242 |
); |
243 |
} |
244 |
|
245 |
$value = unpack('n', $payload); |
246 |
if (!isset($value[1])) { |
247 |
throw new ReaderException( |
248 |
sprintf('Cannot resolve value at offset %d', $start), |
249 |
1539614264
|
250 |
); |
251 |
} |
252 |
return $value[1]; |
253 |
} |
254 |
} |