PluginProbe
User Access Manager / 2.1.8
User Access Manager v2.1.8
2.3.20 2.3.19 2.3.18 2.3.17 2.3.16 2.3.15 2.3.14 2.3.13 trunk 0.6 0.6.1 0.6.2 0.7 0.7 Beta 0.7.0.1 0.8 0.8.0.1 0.8.0.2 0.9 0.9.1 0.9.1.1 0.9.1.2 0.9.1.3 0.9.1.4 1.0 All 136 releases
user-access-manager / src / File / FileHandler.php

FileHandler.php in User Access Manager 2.1.8, at src/File/FileHandler.php

390 lines 10.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * FileHandler.php
4 *
5 * The FileHandler class file.
6 *
7 * PHP versions 5
8 *
9 * @author Alexander Schneider <alexanderschneider85@gmail.com>
10 * @copyright 2008-2017 Alexander Schneider
11 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2
12 * @version SVN: $id$
13 * @link http://wordpress.org/extend/plugins/user-access-manager/
14 */
15 namespace UserAccessManager\File;
16
17 use UserAccessManager\Config\MainConfig;
18 use UserAccessManager\Config\WordpressConfig;
19 use UserAccessManager\Wrapper\Php;
20 use UserAccessManager\Wrapper\Wordpress;
21
22 /**
23 * Class FileHandler
24 *
25 * @package UserAccessManager\FileHandler
26 */
27 class FileHandler
28 {
29 const X_SEND_FILE_TEST_FILE = 'xSendFileTestFile';
30
31 /**
32 * @var Php
33 */
34 private $php;
35
36 /**
37 * @var Wordpress
38 */
39 private $wordpress;
40
41 /**
42 * @var WordpressConfig
43 */
44 private $wordpressConfig;
45
46 /**
47 * @var MainConfig
48 */
49 private $mainConfig;
50
51 /**
52 * @var FileProtectionFactory
53 */
54 private $fileProtectionFactory;
55
56 /**
57 * FileHandler constructor.
58 *
59 * @param Php $php
60 * @param Wordpress $wordpress
61 * @param WordpressConfig $wordpressConfig
62 * @param MainConfig $mainConfig
63 * @param FileProtectionFactory $fileProtectionFactory
64 */
65 public function __construct(
66 Php $php,
67 Wordpress $wordpress,
68 WordpressConfig $wordpressConfig,
69 MainConfig $mainConfig,
70 FileProtectionFactory $fileProtectionFactory
71 ) {
72 $this->php = $php;
73 $this->wordpress = $wordpress;
74 $this->wordpressConfig = $wordpressConfig;
75 $this->mainConfig = $mainConfig;
76 $this->fileProtectionFactory = $fileProtectionFactory;
77 }
78
79 /**
80 * Clears the buffer.
81 */
82 private function clearBuffer()
83 {
84 //prevent '\n' / '0A'
85 if (is_numeric(ob_get_length()) === true) {
86 ob_clean();
87 }
88
89 flush();
90 }
91
92 /**
93 * Returns the file mine type.
94 *
95 * @param string $file
96 *
97 * @return string
98 */
99 private function getFileMineType($file)
100 {
101 $fileName = basename($file);
102
103 /*
104 * This only for compatibility
105 * mime_content_type has been deprecated as the PECL extension file info
106 * provides the same functionality (and more) in a much cleaner way.
107 */
108 $explodedFileName = explode('.', $fileName);
109 $lastElement = array_pop($explodedFileName);
110 $fileExt = strtolower($lastElement);
111
112 $mimeTypes = $this->wordpressConfig->getMimeTypes();
113
114 if ($this->php->functionExists('finfo_open') === true) {
115 $fileInfo = finfo_open(FILEINFO_MIME);
116 $fileMimeType = finfo_file($fileInfo, $file);
117 finfo_close($fileInfo);
118 } elseif ($this->php->functionExists('mime_content_type')) {
119 $fileMimeType = mime_content_type($file);
120 } elseif (isset($mimeTypes[$fileExt]) === true) {
121 $fileMimeType = $mimeTypes[$fileExt];
122 } else {
123 $fileMimeType = 'application/octet-stream';
124 }
125
126 return (string)$fileMimeType;
127 }
128
129 /**
130 * Adds the default header.
131 *
132 * @param string $file
133 * @param bool $isInline
134 */
135 private function addDefaultHeader($file, $isInline)
136 {
137 $fileMimeType = $this->getFileMineType($file);
138 $contentDisposition = ($isInline === true) ? 'inline' : 'attachment';
139 $baseName = str_replace(' ', '_', basename($file));
140
141 header('Content-Description: File Transfer');
142 header('Content-Type: '.$fileMimeType);
143 header("Content-Disposition: {$contentDisposition}; filename=\"{$baseName}\"");
144 }
145
146 /**
147 * Delivers the file via fopen.
148 *
149 * @param string $file
150 */
151 private function deliverFileViaFopen($file)
152 {
153 $handler = fopen($file, 'r');
154
155 while (feof($handler) === false) {
156 if ($this->php->iniGet('safe_mode') !== '') {
157 $this->php->setTimeLimit(30);
158 }
159
160 echo $this->php->fread($handler, 1024);
161 }
162 }
163
164 /**
165 * Delivers the file.
166 *
167 * @param string $file
168 * @param bool $isInline
169 */
170 private function deliverFile($file, $isInline)
171 {
172 $downloadType = $this->mainConfig->getDownloadType();
173
174 if ($downloadType === 'xsendfile') {
175 header("X-Sendfile: {$file}");
176 }
177
178 $this->addDefaultHeader($file, $isInline);
179
180 if ($downloadType !== 'xsendfile') {
181 header('Content-Transfer-Encoding: binary');
182 header('Content-Length: '.filesize($file));
183 $this->clearBuffer();
184
185 if ($downloadType === 'fopen') {
186 $this->deliverFileViaFopen($file);
187 } else {
188 readfile($file);
189 }
190 }
191 }
192
193 /**
194 * Sets the seek start and end.
195 *
196 * @param string $rangeOrigin
197 * @param int $fileSize
198 * @param int $seekStart
199 * @param int $seekEnd
200 */
201 private function getSeekStartEnd($rangeOrigin, $fileSize, &$seekStart, &$seekEnd)
202 {
203 //just serve the first range
204 $range = explode(',', $rangeOrigin)[0];
205 //figure out download piece from range (if set)
206 $seek = explode('-', $range);
207 $seekStart = abs((int)$seek[0]);
208 $seekEnd = isset($seek[1]) === true ? abs((int)$seek[1]) : 0;
209 //start and end based on range (if set), else set defaults also check for invalid ranges.
210 $seekEnd = ($seekEnd === 0) ? ($fileSize - 1) : min($seekEnd, ($fileSize - 1));
211 $seekStart = ($seekEnd < $seekStart) ? 0 : max($seekStart, 0);
212 }
213
214 /**
215 * Delivers the file partial.
216 *
217 * @param string $file
218 * @param bool $isInline
219 */
220 private function deliverFilePartial($file, $isInline)
221 {
222 $httpRange = explode('=', $_SERVER['HTTP_RANGE']);
223 $sizeUnit = $httpRange[0];
224 $rangeOrigin = isset($httpRange[1]) === true ? $httpRange[1] : '';
225
226 if ($sizeUnit === 'bytes') {
227 $fileSize = filesize($file);
228 $this->getSeekStartEnd($rangeOrigin, $fileSize, $seekStart, $seekEnd);
229
230 $this->addDefaultHeader($file, $isInline);
231 header('HTTP/1.1 206 Partial Content');
232 header('Content-Transfer-Encoding: binary');
233 header('Accept-Ranges: bytes');
234 header('Content-Range: bytes '.$seekStart.'-'.$seekEnd.'/'.$fileSize);
235 header('Content-Length: '.($seekEnd - $seekStart + 1));
236
237 $handler = fopen($file, 'r');
238 fseek($handler, $seekStart);
239
240 while (feof($handler) === false) {
241 echo $this->php->fread($handler, 1024 * 8);
242 $this->clearBuffer();
243
244 if ($this->php->connectionStatus() !== 0) {
245 $this->php->fClose($handler);
246 break;
247 }
248 }
249 } else {
250 header('HTTP/1.1 416 Requested Range Not Satisfiable');
251 }
252 }
253
254 /**
255 * Checks if the file is an inline file
256 *
257 * @param string $file
258 *
259 * @return bool
260 */
261 private function isInlineFile($file)
262 {
263 $inlineFiles = array_map('trim', explode(',', $this->mainConfig->getInlineFiles()));
264 $map = array_flip($inlineFiles);
265 $extension = pathinfo($file, PATHINFO_EXTENSION);
266
267 return isset($map[$extension]);
268 }
269
270 /**
271 * Delivers the content of the requested file.
272 *
273 * @param string $file
274 * @param bool $isImage
275 */
276 public function getFile($file, $isImage)
277 {
278 //Deliver content
279 if (file_exists($file) === true) {
280 $isInline = $isImage === true || $this->isInlineFile($file) === true;
281
282 if (isset($_SERVER['HTTP_RANGE']) === true
283 && isset($_SERVER['REQUEST_METHOD']) === true
284 && $_SERVER['REQUEST_METHOD'] === 'GET'
285 ) {
286 $this->deliverFilePartial($file, $isInline);
287 } else {
288 $this->deliverFile($file, $isInline);
289 }
290
291 $this->php->callExit();
292 } else {
293 $this->wordpress->wpDie(
294 TXT_UAM_FILE_NOT_FOUND_ERROR_MESSAGE,
295 TXT_UAM_FILE_NOT_FOUND_ERROR_TITLE,
296 ['response' => 404]
297 );
298 }
299 }
300
301 /**
302 * Returns the current file protection handler.
303 *
304 * @return FileProtectionInterface
305 */
306 private function getCurrentFileProtectionHandler()
307 {
308 if ($this->wordpress->isNginx() === true) {
309 return $this->fileProtectionFactory->createNginxFileProtection();
310 }
311
312 return $this->fileProtectionFactory->createApacheFileProtection();
313 }
314
315 /**
316 * Returns the file protection file.
317 *
318 * @return string
319 */
320 public function getFileProtectionFileName()
321 {
322 return $this->getCurrentFileProtectionHandler()->getFileNameWithPath(
323 $this->wordpressConfig->getUploadDirectory()
324 );
325 }
326
327 /**
328 * Creates a protection file.
329 *
330 * @param string $dir The destination directory.
331 * @param string $objectType The object type.
332 *
333 * @return false
334 */
335 public function createFileProtection($dir = null, $objectType = null)
336 {
337 $dir = ($dir === null) ? $this->wordpressConfig->getUploadDirectory() : $dir;
338
339 if ($dir !== null) {
340 return $this->getCurrentFileProtectionHandler()->create($dir, $objectType);
341 }
342
343 return false;
344 }
345
346 /**
347 * Deletes the protection files.
348 *
349 * @param string $dir The destination directory.
350 *
351 * @return false
352 */
353 public function deleteFileProtection($dir = null)
354 {
355 $dir = ($dir === null) ? $this->wordpressConfig->getUploadDirectory() : $dir;
356
357 if ($dir !== null) {
358 return $this->getCurrentFileProtectionHandler()->delete($dir);
359 }
360
361 return false;
362 }
363
364 /**
365 * Delivers a xsendfile test file.
366 */
367 public function deliverXSendFileTestFile()
368 {
369 $file = $this->wordpressConfig->getUploadDirectory().DIRECTORY_SEPARATOR.self::X_SEND_FILE_TEST_FILE;
370 file_put_contents($file, 'success');
371
372 header("X-Sendfile: {$file}");
373 header('Content-Type: application/octet-stream');
374 header('Content-Disposition: attachment; filename="'.basename($file).'"');
375 $this->php->callExit();
376 }
377
378 /**
379 * Removes the xsendfile test file if exists.
380 */
381 public function removeXSendFileTestFile()
382 {
383 $file = $this->wordpressConfig->getUploadDirectory().DIRECTORY_SEPARATOR.self::X_SEND_FILE_TEST_FILE;
384
385 if (file_exists($file) === true) {
386 unlink($file);
387 }
388 }
389 }
390