Verzeichnisstruktur phpBB-3.2.0
- Veröffentlicht
- 06.01.2017
So funktioniert es
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
functions_download.php
001 <?php
002 /**
003 *
004 * This file is part of the phpBB Forum Software package.
005 *
006 * @copyright (c) phpBB Limited <https://www.phpbb.com>
007 * @license GNU General Public License, version 2 (GPL-2.0)
008 *
009 * For full copyright and license information, please see
010 * the docs/CREDITS.txt file.
011 *
012 */
013
014 /**
015 * @ignore
016 */
017 if (!defined('IN_PHPBB'))
018 {
019 exit;
020 }
021
022 /**
023 * A simplified function to deliver avatars
024 * The argument needs to be checked before calling this function.
025 */
026 function send_avatar_to_browser($file, $browser)
027 {
028 global $config, $phpbb_root_path;
029
030 $prefix = $config['avatar_salt'] . '_';
031 $image_dir = $config['avatar_path'];
032
033 // Adjust image_dir path (no trailing slash)
034 if (substr($image_dir, -1, 1) == '/' || substr($image_dir, -1, 1) == '\\')
035 {
036 $image_dir = substr($image_dir, 0, -1) . '/';
037 }
038 $image_dir = str_replace(array('../', '..\\', './', '.\\'), '', $image_dir);
039
040 if ($image_dir && ($image_dir[0] == '/' || $image_dir[0] == '\\'))
041 {
042 $image_dir = '';
043 }
044 $file_path = $phpbb_root_path . $image_dir . '/' . $prefix . $file;
045
046 if ((@file_exists($file_path) && @is_readable($file_path)) && !headers_sent())
047 {
048 header('Cache-Control: public');
049
050 $image_data = @getimagesize($file_path);
051 header('Content-Type: ' . image_type_to_mime_type($image_data[2]));
052
053 if ((strpos(strtolower($browser), 'msie') !== false) && !phpbb_is_greater_ie_version($browser, 7))
054 {
055 header('Content-Disposition: attachment; ' . header_filename($file));
056
057 if (strpos(strtolower($browser), 'msie 6.0') !== false)
058 {
059 header('Expires: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT');
060 }
061 else
062 {
063 header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 31536000) . ' GMT');
064 }
065 }
066 else
067 {
068 header('Content-Disposition: inline; ' . header_filename($file));
069 header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 31536000) . ' GMT');
070 }
071
072 $size = @filesize($file_path);
073 if ($size)
074 {
075 header("Content-Length: $size");
076 }
077
078 if (@readfile($file_path) == false)
079 {
080 $fp = @fopen($file_path, 'rb');
081
082 if ($fp !== false)
083 {
084 while (!feof($fp))
085 {
086 echo fread($fp, 8192);
087 }
088 fclose($fp);
089 }
090 }
091
092 flush();
093 }
094 else
095 {
096 header('HTTP/1.0 404 Not Found');
097 }
098 }
099
100 /**
101 * Wraps an url into a simple html page. Used to display attachments in IE.
102 * this is a workaround for now; might be moved to template system later
103 * direct any complaints to 1 Microsoft Way, Redmond
104 */
105 function wrap_img_in_html($src, $title)
106 {
107 echo '<!DOCTYPE html>';
108 echo '<html>';
109 echo '<head>';
110 echo '<meta charset="utf-8">';
111 echo '<meta http-equiv="X-UA-Compatible" content="IE=edge">';
112 echo '<title>' . $title . '</title>';
113 echo '</head>';
114 echo '<body>';
115 echo '<div>';
116 echo '<img src="' . $src . '" alt="' . $title . '" />';
117 echo '</div>';
118 echo '</body>';
119 echo '</html>';
120 }
121
122 /**
123 * Send file to browser
124 */
125 function send_file_to_browser($attachment, $upload_dir, $category)
126 {
127 global $user, $db, $phpbb_dispatcher, $phpbb_root_path, $request;
128
129 $filename = $phpbb_root_path . $upload_dir . '/' . $attachment['physical_filename'];
130
131 if (!@file_exists($filename))
132 {
133 send_status_line(404, 'Not Found');
134 trigger_error('ERROR_NO_ATTACHMENT');
135 }
136
137 // Correct the mime type - we force application/octetstream for all files, except images
138 // Please do not change this, it is a security precaution
139 if ($category != ATTACHMENT_CATEGORY_IMAGE || strpos($attachment['mimetype'], 'image') !== 0)
140 {
141 $attachment['mimetype'] = (strpos(strtolower($user->browser), 'msie') !== false || strpos(strtolower($user->browser), 'opera') !== false) ? 'application/octetstream' : 'application/octet-stream';
142 }
143
144 if (@ob_get_length())
145 {
146 @ob_end_clean();
147 }
148
149 // Now send the File Contents to the Browser
150 $size = @filesize($filename);
151
152 /**
153 * Event to alter attachment before it is sent to browser.
154 *
155 * @event core.send_file_to_browser_before
156 * @var array attachment Attachment data
157 * @var string upload_dir Relative path of upload directory
158 * @var int category Attachment category
159 * @var string filename Path to file, including filename
160 * @var int size File size
161 * @since 3.1.11-RC1
162 */
163 $vars = array(
164 'attachment',
165 'upload_dir',
166 'category',
167 'filename',
168 'size',
169 );
170 extract($phpbb_dispatcher->trigger_event('core.send_file_to_browser_before', compact($vars)));
171
172 // To correctly display further errors we need to make sure we are using the correct headers for both (unsetting content-length may not work)
173
174 // Check if headers already sent or not able to get the file contents.
175 if (headers_sent() || !@file_exists($filename) || !@is_readable($filename))
176 {
177 // PHP track_errors setting On?
178 if (!empty($php_errormsg))
179 {
180 send_status_line(500, 'Internal Server Error');
181 trigger_error($user->lang['UNABLE_TO_DELIVER_FILE'] . '<br />' . sprintf($user->lang['TRACKED_PHP_ERROR'], $php_errormsg));
182 }
183
184 send_status_line(500, 'Internal Server Error');
185 trigger_error('UNABLE_TO_DELIVER_FILE');
186 }
187
188 // Make sure the database record for the filesize is correct
189 if ($size > 0 && $size != $attachment['filesize'] && strpos($attachment['physical_filename'], 'thumb_') === false)
190 {
191 // Update database record
192 $sql = 'UPDATE ' . ATTACHMENTS_TABLE . '
193 SET filesize = ' . (int) $size . '
194 WHERE attach_id = ' . (int) $attachment['attach_id'];
195 $db->sql_query($sql);
196 }
197
198 // Now the tricky part... let's dance
199 header('Cache-Control: public');
200
201 // Send out the Headers. Do not set Content-Disposition to inline please, it is a security measure for users using the Internet Explorer.
202 header('Content-Type: ' . $attachment['mimetype']);
203
204 if (phpbb_is_greater_ie_version($user->browser, 7))
205 {
206 header('X-Content-Type-Options: nosniff');
207 }
208
209 if ($category == ATTACHMENT_CATEGORY_FLASH && $request->variable('view', 0) === 1)
210 {
211 // We use content-disposition: inline for flash files and view=1 to let it correctly play with flash player 10 - any other disposition will fail to play inline
212 header('Content-Disposition: inline');
213 }
214 else
215 {
216 if (empty($user->browser) || ((strpos(strtolower($user->browser), 'msie') !== false) && !phpbb_is_greater_ie_version($user->browser, 7)))
217 {
218 header('Content-Disposition: attachment; ' . header_filename(htmlspecialchars_decode($attachment['real_filename'])));
219 if (empty($user->browser) || (strpos(strtolower($user->browser), 'msie 6.0') !== false))
220 {
221 header('Expires: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT');
222 }
223 }
224 else
225 {
226 header('Content-Disposition: ' . ((strpos($attachment['mimetype'], 'image') === 0) ? 'inline' : 'attachment') . '; ' . header_filename(htmlspecialchars_decode($attachment['real_filename'])));
227 if (phpbb_is_greater_ie_version($user->browser, 7) && (strpos($attachment['mimetype'], 'image') !== 0))
228 {
229 header('X-Download-Options: noopen');
230 }
231 }
232 }
233
234 // Close the db connection before sending the file etc.
235 file_gc(false);
236
237 if (!set_modified_headers($attachment['filetime'], $user->browser))
238 {
239 // We make sure those have to be enabled manually by defining a constant
240 // because of the potential disclosure of full attachment path
241 // in case support for features is absent in the webserver software.
242 if (defined('PHPBB_ENABLE_X_ACCEL_REDIRECT') && PHPBB_ENABLE_X_ACCEL_REDIRECT)
243 {
244 // X-Accel-Redirect - http://wiki.nginx.org/XSendfile
245 header('X-Accel-Redirect: ' . $user->page['root_script_path'] . $upload_dir . '/' . $attachment['physical_filename']);
246 exit;
247 }
248 else if (defined('PHPBB_ENABLE_X_SENDFILE') && PHPBB_ENABLE_X_SENDFILE && !phpbb_http_byte_range($size))
249 {
250 // X-Sendfile - http://blog.lighttpd.net/articles/2006/07/02/x-sendfile
251 // Lighttpd's X-Sendfile does not support range requests as of 1.4.26
252 // and always requires an absolute path.
253 header('X-Sendfile: ' . dirname(__FILE__) . "/../$upload_dir/{$attachment['physical_filename']}");
254 exit;
255 }
256
257 if ($size)
258 {
259 header("Content-Length: $size");
260 }
261
262 // Try to deliver in chunks
263 @set_time_limit(0);
264
265 $fp = @fopen($filename, 'rb');
266
267 if ($fp !== false)
268 {
269 // Deliver file partially if requested
270 if ($range = phpbb_http_byte_range($size))
271 {
272 fseek($fp, $range['byte_pos_start']);
273
274 send_status_line(206, 'Partial Content');
275 header('Content-Range: bytes ' . $range['byte_pos_start'] . '-' . $range['byte_pos_end'] . '/' . $range['bytes_total']);
276 header('Content-Length: ' . $range['bytes_requested']);
277
278 // First read chunks
279 while (!feof($fp) && ftell($fp) < $range['byte_pos_end'] - 8192)
280 {
281 echo fread($fp, 8192);
282 }
283 // Then, read the remainder
284 echo fread($fp, $range['bytes_requested'] % 8192);
285 }
286 else
287 {
288 while (!feof($fp))
289 {
290 echo fread($fp, 8192);
291 }
292 }
293 fclose($fp);
294 }
295 else
296 {
297 @readfile($filename);
298 }
299
300 flush();
301 }
302
303 exit;
304 }
305
306 /**
307 * Get a browser friendly UTF-8 encoded filename
308 */
309 function header_filename($file)
310 {
311 global $request;
312
313 $user_agent = $request->header('User-Agent');
314
315 // There be dragons here.
316 // Not many follows the RFC...
317 if (strpos($user_agent, 'MSIE') !== false || strpos($user_agent, 'Konqueror') !== false)
318 {
319 return "filename=" . rawurlencode($file);
320 }
321
322 // follow the RFC for extended filename for the rest
323 return "filename*=UTF-8''" . rawurlencode($file);
324 }
325
326 /**
327 * Check if downloading item is allowed
328 */
329 function download_allowed()
330 {
331 global $config, $user, $db, $request;
332
333 if (!$config['secure_downloads'])
334 {
335 return true;
336 }
337
338 $url = htmlspecialchars_decode($request->header('Referer'));
339
340 if (!$url)
341 {
342 return ($config['secure_allow_empty_referer']) ? true : false;
343 }
344
345 // Split URL into domain and script part
346 $url = @parse_url($url);
347
348 if ($url === false)
349 {
350 return ($config['secure_allow_empty_referer']) ? true : false;
351 }
352
353 $hostname = $url['host'];
354 unset($url);
355
356 $allowed = ($config['secure_allow_deny']) ? false : true;
357 $iplist = array();
358
359 if (($ip_ary = @gethostbynamel($hostname)) !== false)
360 {
361 foreach ($ip_ary as $ip)
362 {
363 if ($ip)
364 {
365 $iplist[] = $ip;
366 }
367 }
368 }
369
370 // Check for own server...
371 $server_name = $user->host;
372
373 // Forcing server vars is the only way to specify/override the protocol
374 if ($config['force_server_vars'] || !$server_name)
375 {
376 $server_name = $config['server_name'];
377 }
378
379 if (preg_match('#^.*?' . preg_quote($server_name, '#') . '.*?$#i', $hostname))
380 {
381 $allowed = true;
382 }
383
384 // Get IP's and Hostnames
385 if (!$allowed)
386 {
387 $sql = 'SELECT site_ip, site_hostname, ip_exclude
388 FROM ' . SITELIST_TABLE;
389 $result = $db->sql_query($sql);
390
391 while ($row = $db->sql_fetchrow($result))
392 {
393 $site_ip = trim($row['site_ip']);
394 $site_hostname = trim($row['site_hostname']);
395
396 if ($site_ip)
397 {
398 foreach ($iplist as $ip)
399 {
400 if (preg_match('#^' . str_replace('\*', '.*?', preg_quote($site_ip, '#')) . '$#i', $ip))
401 {
402 if ($row['ip_exclude'])
403 {
404 $allowed = ($config['secure_allow_deny']) ? false : true;
405 break 2;
406 }
407 else
408 {
409 $allowed = ($config['secure_allow_deny']) ? true : false;
410 }
411 }
412 }
413 }
414
415 if ($site_hostname)
416 {
417 if (preg_match('#^' . str_replace('\*', '.*?', preg_quote($site_hostname, '#')) . '$#i', $hostname))
418 {
419 if ($row['ip_exclude'])
420 {
421 $allowed = ($config['secure_allow_deny']) ? false : true;
422 break;
423 }
424 else
425 {
426 $allowed = ($config['secure_allow_deny']) ? true : false;
427 }
428 }
429 }
430 }
431 $db->sql_freeresult($result);
432 }
433
434 return $allowed;
435 }
436
437 /**
438 * Check if the browser has the file already and set the appropriate headers-
439 * @returns false if a resend is in order.
440 */
441 function set_modified_headers($stamp, $browser)
442 {
443 global $request;
444
445 // let's see if we have to send the file at all
446 $last_load = $request->header('If-Modified-Since') ? strtotime(trim($request->header('If-Modified-Since'))) : false;
447
448 if (strpos(strtolower($browser), 'msie 6.0') === false && !phpbb_is_greater_ie_version($browser, 7))
449 {
450 if ($last_load !== false && $last_load >= $stamp)
451 {
452 send_status_line(304, 'Not Modified');
453 // seems that we need those too ... browsers
454 header('Cache-Control: public');
455 header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 31536000) . ' GMT');
456 return true;
457 }
458 else
459 {
460 header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $stamp) . ' GMT');
461 }
462 }
463 return false;
464 }
465
466 /**
467 * Garbage Collection
468 *
469 * @param bool $exit Whether to die or not.
470 *
471 * @return null
472 */
473 function file_gc($exit = true)
474 {
475 global $cache, $db;
476
477 if (!empty($cache))
478 {
479 $cache->unload();
480 }
481
482 $db->sql_close();
483
484 if ($exit)
485 {
486 exit;
487 }
488 }
489
490 /**
491 * HTTP range support (RFC 2616 Section 14.35)
492 *
493 * Allows browsers to request partial file content
494 * in case a download has been interrupted.
495 *
496 * @param int $filesize the size of the file in bytes we are about to deliver
497 *
498 * @return mixed false if the whole file has to be delivered
499 * associative array on success
500 */
501 function phpbb_http_byte_range($filesize)
502 {
503 // Only call find_range_request() once.
504 static $request_array;
505
506 if (!$filesize)
507 {
508 return false;
509 }
510
511 if (!isset($request_array))
512 {
513 $request_array = phpbb_find_range_request();
514 }
515
516 return (empty($request_array)) ? false : phpbb_parse_range_request($request_array, $filesize);
517 }
518
519 /**
520 * Searches for HTTP range request in request headers.
521 *
522 * @return mixed false if no request found
523 * array of strings containing the requested ranges otherwise
524 * e.g. array(0 => '0-0', 1 => '123-125')
525 */
526 function phpbb_find_range_request()
527 {
528 global $request;
529
530 $value = $request->header('Range');
531
532 // Make sure range request starts with "bytes="
533 if (strpos($value, 'bytes=') === 0)
534 {
535 // Strip leading 'bytes='
536 // Multiple ranges can be separated by a comma
537 return explode(',', substr($value, 6));
538 }
539
540 return false;
541 }
542
543 /**
544 * Analyses a range request array.
545 *
546 * A range request can contain multiple ranges,
547 * we however only handle the first request and
548 * only support requests from a given byte to the end of the file.
549 *
550 * @param array $request_array array of strings containing the requested ranges
551 * @param int $filesize the full size of the file in bytes that has been requested
552 *
553 * @return mixed false if the whole file has to be delivered
554 * associative array on success
555 * byte_pos_start the first byte position, can be passed to fseek()
556 * byte_pos_end the last byte position
557 * bytes_requested the number of bytes requested
558 * bytes_total the full size of the file
559 */
560 function phpbb_parse_range_request($request_array, $filesize)
561 {
562 $first_byte_pos = -1;
563 $last_byte_pos = -1;
564
565 // Go through all ranges
566 foreach ($request_array as $range_string)
567 {
568 $range = explode('-', trim($range_string));
569
570 // "-" is invalid, "0-0" however is valid and means the very first byte.
571 if (sizeof($range) != 2 || $range[0] === '' && $range[1] === '')
572 {
573 continue;
574 }
575
576 // Substitute defaults
577 if ($range[0] === '')
578 {
579 $range[0] = 0;
580 }
581
582 if ($range[1] === '')
583 {
584 $range[1] = $filesize - 1;
585 }
586
587 if ($last_byte_pos >= 0 && $last_byte_pos + 1 != $range[0])
588 {
589 // We only support contiguous ranges, no multipart stuff :(
590 return false;
591 }
592
593 if ($range[1] && $range[1] < $range[0])
594 {
595 // The requested range contains 0 bytes.
596 continue;
597 }
598
599 // Return bytes from $range[0] to $range[1]
600 if ($first_byte_pos < 0)
601 {
602 $first_byte_pos = (int) $range[0];
603 }
604
605 $last_byte_pos = (int) $range[1];
606
607 if ($first_byte_pos >= $filesize)
608 {
609 // Requested range not satisfiable
610 return false;
611 }
612
613 // Adjust last-byte-pos if it is absent or greater than the content.
614 if ($range[1] === '' || $last_byte_pos >= $filesize)
615 {
616 $last_byte_pos = $filesize - 1;
617 }
618 }
619
620 if ($first_byte_pos < 0 || $last_byte_pos < 0)
621 {
622 return false;
623 }
624
625 return array(
626 'byte_pos_start' => $first_byte_pos,
627 'byte_pos_end' => $last_byte_pos,
628 'bytes_requested' => $last_byte_pos - $first_byte_pos + 1,
629 'bytes_total' => $filesize,
630 );
631 }
632
633 /**
634 * Increments the download count of all provided attachments
635 *
636 * @param \phpbb\db\driver\driver_interface $db The database object
637 * @param array|int $ids The attach_id of each attachment
638 *
639 * @return null
640 */
641 function phpbb_increment_downloads($db, $ids)
642 {
643 if (!is_array($ids))
644 {
645 $ids = array($ids);
646 }
647
648 $sql = 'UPDATE ' . ATTACHMENTS_TABLE . '
649 SET download_count = download_count + 1
650 WHERE ' . $db->sql_in_set('attach_id', $ids);
651 $db->sql_query($sql);
652 }
653
654 /**
655 * Handles authentication when downloading attachments from a post or topic
656 *
657 * @param \phpbb\db\driver\driver_interface $db The database object
658 * @param \phpbb\auth\auth $auth The authentication object
659 * @param int $topic_id The id of the topic that we are downloading from
660 *
661 * @return null
662 */
663 function phpbb_download_handle_forum_auth($db, $auth, $topic_id)
664 {
665 $sql_array = array(
666 'SELECT' => 't.topic_visibility, t.forum_id, f.forum_name, f.forum_password, f.parent_id',
667 'FROM' => array(
668 TOPICS_TABLE => 't',
669 FORUMS_TABLE => 'f',
670 ),
671 'WHERE' => 't.topic_id = ' . (int) $topic_id . '
672 AND t.forum_id = f.forum_id',
673 );
674
675 $sql = $db->sql_build_query('SELECT', $sql_array);
676 $result = $db->sql_query($sql);
677 $row = $db->sql_fetchrow($result);
678 $db->sql_freeresult($result);
679
680 if ($row && $row['topic_visibility'] != ITEM_APPROVED && !$auth->acl_get('m_approve', $row['forum_id']))
681 {
682 send_status_line(404, 'Not Found');
683 trigger_error('ERROR_NO_ATTACHMENT');
684 }
685 else if ($row && $auth->acl_get('u_download') && $auth->acl_get('f_download', $row['forum_id']))
686 {
687 if ($row['forum_password'])
688 {
689 // Do something else ... ?
690 login_forum_box($row);
691 }
692 }
693 else
694 {
695 send_status_line(403, 'Forbidden');
696 trigger_error('SORRY_AUTH_VIEW_ATTACH');
697 }
698 }
699
700 /**
701 * Handles authentication when downloading attachments from PMs
702 *
703 * @param \phpbb\db\driver\driver_interface $db The database object
704 * @param \phpbb\auth\auth $auth The authentication object
705 * @param int $user_id The user id
706 * @param int $msg_id The id of the PM that we are downloading from
707 *
708 * @return null
709 */
710 function phpbb_download_handle_pm_auth($db, $auth, $user_id, $msg_id)
711 {
712 global $phpbb_dispatcher;
713
714 if (!$auth->acl_get('u_pm_download'))
715 {
716 send_status_line(403, 'Forbidden');
717 trigger_error('SORRY_AUTH_VIEW_ATTACH');
718 }
719
720 $allowed = phpbb_download_check_pm_auth($db, $user_id, $msg_id);
721
722 /**
723 * Event to modify PM attachments download auth
724 *
725 * @event core.modify_pm_attach_download_auth
726 * @var bool allowed Whether the user is allowed to download from that PM or not
727 * @var int msg_id The id of the PM to download from
728 * @var int user_id The user id for auth check
729 * @since 3.1.11-RC1
730 */
731 $vars = array('allowed', 'msg_id', 'user_id');
732 extract($phpbb_dispatcher->trigger_event('core.modify_pm_attach_download_auth', compact($vars)));
733
734 if (!$allowed)
735 {
736 send_status_line(403, 'Forbidden');
737 trigger_error('ERROR_NO_ATTACHMENT');
738 }
739 }
740
741 /**
742 * Checks whether a user can download from a particular PM
743 *
744 * @param \phpbb\db\driver\driver_interface $db The database object
745 * @param int $user_id The user id
746 * @param int $msg_id The id of the PM that we are downloading from
747 *
748 * @return bool Whether the user is allowed to download from that PM or not
749 */
750 function phpbb_download_check_pm_auth($db, $user_id, $msg_id)
751 {
752 // Check if the attachment is within the users scope...
753 $sql = 'SELECT msg_id
754 FROM ' . PRIVMSGS_TO_TABLE . '
755 WHERE msg_id = ' . (int) $msg_id . '
756 AND (
757 user_id = ' . (int) $user_id . '
758 OR author_id = ' . (int) $user_id . '
759 )';
760 $result = $db->sql_query_limit($sql, 1);
761 $allowed = (bool) $db->sql_fetchfield('msg_id');
762 $db->sql_freeresult($result);
763
764 return $allowed;
765 }
766
767 /**
768 * Check if the browser is internet explorer version 7+
769 *
770 * @param string $user_agent User agent HTTP header
771 * @param int $version IE version to check against
772 *
773 * @return bool true if internet explorer version is greater than $version
774 */
775 function phpbb_is_greater_ie_version($user_agent, $version)
776 {
777 if (preg_match('/msie (\d+)/', strtolower($user_agent), $matches))
778 {
779 $ie_version = (int) $matches[1];
780 return ($ie_version > $version);
781 }
782 else
783 {
784 return false;
785 }
786 }
787