Verzeichnisstruktur phpBB-3.1.0
- Veröffentlicht
- 27.10.2014
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 '<title>' . $title . '</title>';
112 echo '</head>';
113 echo '<body>';
114 echo '<div>';
115 echo '<img src="' . $src . '" alt="' . $title . '" />';
116 echo '</div>';
117 echo '</body>';
118 echo '</html>';
119 }
120
121 /**
122 * Send file to browser
123 */
124 function send_file_to_browser($attachment, $upload_dir, $category)
125 {
126 global $user, $db, $config, $phpbb_root_path;
127
128 $filename = $phpbb_root_path . $upload_dir . '/' . $attachment['physical_filename'];
129
130 if (!@file_exists($filename))
131 {
132 send_status_line(404, 'Not Found');
133 trigger_error('ERROR_NO_ATTACHMENT');
134 }
135
136 // Correct the mime type - we force application/octetstream for all files, except images
137 // Please do not change this, it is a security precaution
138 if ($category != ATTACHMENT_CATEGORY_IMAGE || strpos($attachment['mimetype'], 'image') !== 0)
139 {
140 $attachment['mimetype'] = (strpos(strtolower($user->browser), 'msie') !== false || strpos(strtolower($user->browser), 'opera') !== false) ? 'application/octetstream' : 'application/octet-stream';
141 }
142
143 if (@ob_get_length())
144 {
145 @ob_end_clean();
146 }
147
148 // Now send the File Contents to the Browser
149 $size = @filesize($filename);
150
151 // To correctly display further errors we need to make sure we are using the correct headers for both (unsetting content-length may not work)
152
153 // Check if headers already sent or not able to get the file contents.
154 if (headers_sent() || !@file_exists($filename) || !@is_readable($filename))
155 {
156 // PHP track_errors setting On?
157 if (!empty($php_errormsg))
158 {
159 send_status_line(500, 'Internal Server Error');
160 trigger_error($user->lang['UNABLE_TO_DELIVER_FILE'] . '<br />' . sprintf($user->lang['TRACKED_PHP_ERROR'], $php_errormsg));
161 }
162
163 send_status_line(500, 'Internal Server Error');
164 trigger_error('UNABLE_TO_DELIVER_FILE');
165 }
166
167 // Make sure the database record for the filesize is correct
168 if ($size > 0 && $size != $attachment['filesize'])
169 {
170 // Update database record
171 $sql = 'UPDATE ' . ATTACHMENTS_TABLE . '
172 SET filesize = ' . (int) $size . '
173 WHERE attach_id = ' . (int) $attachment['attach_id'];
174 $db->sql_query($sql);
175 }
176
177 // Now the tricky part... let's dance
178 header('Cache-Control: public');
179
180 // Send out the Headers. Do not set Content-Disposition to inline please, it is a security measure for users using the Internet Explorer.
181 header('Content-Type: ' . $attachment['mimetype']);
182
183 if (phpbb_is_greater_ie_version($user->browser, 7))
184 {
185 header('X-Content-Type-Options: nosniff');
186 }
187
188 if ($category == ATTACHMENT_CATEGORY_FLASH && request_var('view', 0) === 1)
189 {
190 // 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
191 header('Content-Disposition: inline');
192 }
193 else
194 {
195 if (empty($user->browser) || ((strpos(strtolower($user->browser), 'msie') !== false) && !phpbb_is_greater_ie_version($user->browser, 7)))
196 {
197 header('Content-Disposition: attachment; ' . header_filename(htmlspecialchars_decode($attachment['real_filename'])));
198 if (empty($user->browser) || (strpos(strtolower($user->browser), 'msie 6.0') !== false))
199 {
200 header('Expires: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT');
201 }
202 }
203 else
204 {
205 header('Content-Disposition: ' . ((strpos($attachment['mimetype'], 'image') === 0) ? 'inline' : 'attachment') . '; ' . header_filename(htmlspecialchars_decode($attachment['real_filename'])));
206 if (phpbb_is_greater_ie_version($user->browser, 7) && (strpos($attachment['mimetype'], 'image') !== 0))
207 {
208 header('X-Download-Options: noopen');
209 }
210 }
211 }
212
213 if ($size)
214 {
215 header("Content-Length: $size");
216 }
217
218 // Close the db connection before sending the file etc.
219 file_gc(false);
220
221 if (!set_modified_headers($attachment['filetime'], $user->browser))
222 {
223 // We make sure those have to be enabled manually by defining a constant
224 // because of the potential disclosure of full attachment path
225 // in case support for features is absent in the webserver software.
226 if (defined('PHPBB_ENABLE_X_ACCEL_REDIRECT') && PHPBB_ENABLE_X_ACCEL_REDIRECT)
227 {
228 // X-Accel-Redirect - http://wiki.nginx.org/XSendfile
229 header('X-Accel-Redirect: ' . $user->page['root_script_path'] . $upload_dir . '/' . $attachment['physical_filename']);
230 exit;
231 }
232 else if (defined('PHPBB_ENABLE_X_SENDFILE') && PHPBB_ENABLE_X_SENDFILE && !phpbb_http_byte_range($size))
233 {
234 // X-Sendfile - http://blog.lighttpd.net/articles/2006/07/02/x-sendfile
235 // Lighttpd's X-Sendfile does not support range requests as of 1.4.26
236 // and always requires an absolute path.
237 header('X-Sendfile: ' . dirname(__FILE__) . "/../$upload_dir/{$attachment['physical_filename']}");
238 exit;
239 }
240
241 // Try to deliver in chunks
242 @set_time_limit(0);
243
244 $fp = @fopen($filename, 'rb');
245
246 if ($fp !== false)
247 {
248 // Deliver file partially if requested
249 if ($range = phpbb_http_byte_range($size))
250 {
251 fseek($fp, $range['byte_pos_start']);
252
253 send_status_line(206, 'Partial Content');
254 header('Content-Range: bytes ' . $range['byte_pos_start'] . '-' . $range['byte_pos_end'] . '/' . $range['bytes_total']);
255 header('Content-Length: ' . $range['bytes_requested']);
256 }
257
258 while (!feof($fp))
259 {
260 echo fread($fp, 8192);
261 }
262 fclose($fp);
263 }
264 else
265 {
266 @readfile($filename);
267 }
268
269 flush();
270 }
271
272 exit;
273 }
274
275 /**
276 * Get a browser friendly UTF-8 encoded filename
277 */
278 function header_filename($file)
279 {
280 global $request;
281
282 $user_agent = $request->header('User-Agent');
283
284 // There be dragons here.
285 // Not many follows the RFC...
286 if (strpos($user_agent, 'MSIE') !== false || strpos($user_agent, 'Safari') !== false || strpos($user_agent, 'Konqueror') !== false)
287 {
288 return "filename=" . rawurlencode($file);
289 }
290
291 // follow the RFC for extended filename for the rest
292 return "filename*=UTF-8''" . rawurlencode($file);
293 }
294
295 /**
296 * Check if downloading item is allowed
297 */
298 function download_allowed()
299 {
300 global $config, $user, $db, $request;
301
302 if (!$config['secure_downloads'])
303 {
304 return true;
305 }
306
307 $url = htmlspecialchars_decode($request->header('Referer'));
308
309 if (!$url)
310 {
311 return ($config['secure_allow_empty_referer']) ? true : false;
312 }
313
314 // Split URL into domain and script part
315 $url = @parse_url($url);
316
317 if ($url === false)
318 {
319 return ($config['secure_allow_empty_referer']) ? true : false;
320 }
321
322 $hostname = $url['host'];
323 unset($url);
324
325 $allowed = ($config['secure_allow_deny']) ? false : true;
326 $iplist = array();
327
328 if (($ip_ary = @gethostbynamel($hostname)) !== false)
329 {
330 foreach ($ip_ary as $ip)
331 {
332 if ($ip)
333 {
334 $iplist[] = $ip;
335 }
336 }
337 }
338
339 // Check for own server...
340 $server_name = $user->host;
341
342 // Forcing server vars is the only way to specify/override the protocol
343 if ($config['force_server_vars'] || !$server_name)
344 {
345 $server_name = $config['server_name'];
346 }
347
348 if (preg_match('#^.*?' . preg_quote($server_name, '#') . '.*?$#i', $hostname))
349 {
350 $allowed = true;
351 }
352
353 // Get IP's and Hostnames
354 if (!$allowed)
355 {
356 $sql = 'SELECT site_ip, site_hostname, ip_exclude
357 FROM ' . SITELIST_TABLE;
358 $result = $db->sql_query($sql);
359
360 while ($row = $db->sql_fetchrow($result))
361 {
362 $site_ip = trim($row['site_ip']);
363 $site_hostname = trim($row['site_hostname']);
364
365 if ($site_ip)
366 {
367 foreach ($iplist as $ip)
368 {
369 if (preg_match('#^' . str_replace('\*', '.*?', preg_quote($site_ip, '#')) . '$#i', $ip))
370 {
371 if ($row['ip_exclude'])
372 {
373 $allowed = ($config['secure_allow_deny']) ? false : true;
374 break 2;
375 }
376 else
377 {
378 $allowed = ($config['secure_allow_deny']) ? true : false;
379 }
380 }
381 }
382 }
383
384 if ($site_hostname)
385 {
386 if (preg_match('#^' . str_replace('\*', '.*?', preg_quote($site_hostname, '#')) . '$#i', $hostname))
387 {
388 if ($row['ip_exclude'])
389 {
390 $allowed = ($config['secure_allow_deny']) ? false : true;
391 break;
392 }
393 else
394 {
395 $allowed = ($config['secure_allow_deny']) ? true : false;
396 }
397 }
398 }
399 }
400 $db->sql_freeresult($result);
401 }
402
403 return $allowed;
404 }
405
406 /**
407 * Check if the browser has the file already and set the appropriate headers-
408 * @returns false if a resend is in order.
409 */
410 function set_modified_headers($stamp, $browser)
411 {
412 global $request;
413
414 // let's see if we have to send the file at all
415 $last_load = $request->header('Modified-Since') ? strtotime(trim($request->header('Modified-Since'))) : false;
416
417 if (strpos(strtolower($browser), 'msie 6.0') === false && !phpbb_is_greater_ie_version($browser, 7))
418 {
419 if ($last_load !== false && $last_load >= $stamp)
420 {
421 send_status_line(304, 'Not Modified');
422 // seems that we need those too ... browsers
423 header('Cache-Control: public');
424 header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 31536000) . ' GMT');
425 return true;
426 }
427 else
428 {
429 header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $stamp) . ' GMT');
430 }
431 }
432 return false;
433 }
434
435 /**
436 * Garbage Collection
437 *
438 * @param bool $exit Whether to die or not.
439 *
440 * @return null
441 */
442 function file_gc($exit = true)
443 {
444 global $cache, $db;
445
446 if (!empty($cache))
447 {
448 $cache->unload();
449 }
450
451 $db->sql_close();
452
453 if ($exit)
454 {
455 exit;
456 }
457 }
458
459 /**
460 * HTTP range support (RFC 2616 Section 14.35)
461 *
462 * Allows browsers to request partial file content
463 * in case a download has been interrupted.
464 *
465 * @param int $filesize the size of the file in bytes we are about to deliver
466 *
467 * @return mixed false if the whole file has to be delivered
468 * associative array on success
469 */
470 function phpbb_http_byte_range($filesize)
471 {
472 // Only call find_range_request() once.
473 static $request_array;
474
475 if (!$filesize)
476 {
477 return false;
478 }
479
480 if (!isset($request_array))
481 {
482 $request_array = phpbb_find_range_request();
483 }
484
485 return (empty($request_array)) ? false : phpbb_parse_range_request($request_array, $filesize);
486 }
487
488 /**
489 * Searches for HTTP range request in request headers.
490 *
491 * @return mixed false if no request found
492 * array of strings containing the requested ranges otherwise
493 * e.g. array(0 => '0-0', 1 => '123-125')
494 */
495 function phpbb_find_range_request()
496 {
497 global $request;
498
499 $value = $request->header('Range');
500
501 // Make sure range request starts with "bytes="
502 if (strpos($value, 'bytes=') === 0)
503 {
504 // Strip leading 'bytes='
505 // Multiple ranges can be separated by a comma
506 return explode(',', substr($value, 6));
507 }
508
509 return false;
510 }
511
512 /**
513 * Analyses a range request array.
514 *
515 * A range request can contain multiple ranges,
516 * we however only handle the first request and
517 * only support requests from a given byte to the end of the file.
518 *
519 * @param array $request_array array of strings containing the requested ranges
520 * @param int $filesize the full size of the file in bytes that has been requested
521 *
522 * @return mixed false if the whole file has to be delivered
523 * associative array on success
524 * byte_pos_start the first byte position, can be passed to fseek()
525 * byte_pos_end the last byte position
526 * bytes_requested the number of bytes requested
527 * bytes_total the full size of the file
528 */
529 function phpbb_parse_range_request($request_array, $filesize)
530 {
531 // Go through all ranges
532 foreach ($request_array as $range_string)
533 {
534 $range = explode('-', trim($range_string));
535
536 // "-" is invalid, "0-0" however is valid and means the very first byte.
537 if (sizeof($range) != 2 || $range[0] === '' && $range[1] === '')
538 {
539 continue;
540 }
541
542 if ($range[0] === '')
543 {
544 // Return last $range[1] bytes.
545
546 if (!$range[1])
547 {
548 continue;
549 }
550
551 if ($range[1] >= $filesize)
552 {
553 return false;
554 }
555
556 $first_byte_pos = $filesize - (int) $range[1];
557 $last_byte_pos = $filesize - 1;
558 }
559 else
560 {
561 // Return bytes from $range[0] to $range[1]
562
563 $first_byte_pos = (int) $range[0];
564 $last_byte_pos = (int) $range[1];
565
566 if ($last_byte_pos && $last_byte_pos < $first_byte_pos)
567 {
568 // The requested range contains 0 bytes.
569 continue;
570 }
571
572 if ($first_byte_pos >= $filesize)
573 {
574 // Requested range not satisfiable
575 return false;
576 }
577
578 // Adjust last-byte-pos if it is absent or greater than the content.
579 if ($range[1] === '' || $last_byte_pos >= $filesize)
580 {
581 $last_byte_pos = $filesize - 1;
582 }
583 }
584
585 // We currently do not support range requests that end before the end of the file
586 if ($last_byte_pos != $filesize - 1)
587 {
588 continue;
589 }
590
591 return array(
592 'byte_pos_start' => $first_byte_pos,
593 'byte_pos_end' => $last_byte_pos,
594 'bytes_requested' => $last_byte_pos - $first_byte_pos + 1,
595 'bytes_total' => $filesize,
596 );
597 }
598 }
599
600 /**
601 * Increments the download count of all provided attachments
602 *
603 * @param \phpbb\db\driver\driver_interface $db The database object
604 * @param array|int $ids The attach_id of each attachment
605 *
606 * @return null
607 */
608 function phpbb_increment_downloads($db, $ids)
609 {
610 if (!is_array($ids))
611 {
612 $ids = array($ids);
613 }
614
615 $sql = 'UPDATE ' . ATTACHMENTS_TABLE . '
616 SET download_count = download_count + 1
617 WHERE ' . $db->sql_in_set('attach_id', $ids);
618 $db->sql_query($sql);
619 }
620
621 /**
622 * Handles authentication when downloading attachments from a post or topic
623 *
624 * @param \phpbb\db\driver\driver_interface $db The database object
625 * @param \phpbb\auth\auth $auth The authentication object
626 * @param int $topic_id The id of the topic that we are downloading from
627 *
628 * @return null
629 */
630 function phpbb_download_handle_forum_auth($db, $auth, $topic_id)
631 {
632 $sql_array = array(
633 'SELECT' => 't.topic_visibility, t.forum_id, f.forum_name, f.forum_password, f.parent_id',
634 'FROM' => array(
635 TOPICS_TABLE => 't',
636 FORUMS_TABLE => 'f',
637 ),
638 'WHERE' => 't.topic_id = ' . (int) $topic_id . '
639 AND t.forum_id = f.forum_id',
640 );
641
642 $sql = $db->sql_build_query('SELECT', $sql_array);
643 $result = $db->sql_query($sql);
644 $row = $db->sql_fetchrow($result);
645 $db->sql_freeresult($result);
646
647 if ($row && $row['topic_visibility'] != ITEM_APPROVED && !$auth->acl_get('m_approve', $row['forum_id']))
648 {
649 send_status_line(404, 'Not Found');
650 trigger_error('ERROR_NO_ATTACHMENT');
651 }
652 else if ($row && $auth->acl_get('u_download') && $auth->acl_get('f_download', $row['forum_id']))
653 {
654 if ($row['forum_password'])
655 {
656 // Do something else ... ?
657 login_forum_box($row);
658 }
659 }
660 else
661 {
662 send_status_line(403, 'Forbidden');
663 trigger_error('SORRY_AUTH_VIEW_ATTACH');
664 }
665 }
666
667 /**
668 * Handles authentication when downloading attachments from PMs
669 *
670 * @param \phpbb\db\driver\driver_interface $db The database object
671 * @param \phpbb\auth\auth $auth The authentication object
672 * @param int $user_id The user id
673 * @param int $msg_id The id of the PM that we are downloading from
674 *
675 * @return null
676 */
677 function phpbb_download_handle_pm_auth($db, $auth, $user_id, $msg_id)
678 {
679 if (!$auth->acl_get('u_pm_download'))
680 {
681 send_status_line(403, 'Forbidden');
682 trigger_error('SORRY_AUTH_VIEW_ATTACH');
683 }
684
685 $allowed = phpbb_download_check_pm_auth($db, $user_id, $msg_id);
686
687 if (!$allowed)
688 {
689 send_status_line(403, 'Forbidden');
690 trigger_error('ERROR_NO_ATTACHMENT');
691 }
692 }
693
694 /**
695 * Checks whether a user can download from a particular PM
696 *
697 * @param \phpbb\db\driver\driver_interface $db The database object
698 * @param int $user_id The user id
699 * @param int $msg_id The id of the PM that we are downloading from
700 *
701 * @return bool Whether the user is allowed to download from that PM or not
702 */
703 function phpbb_download_check_pm_auth($db, $user_id, $msg_id)
704 {
705 // Check if the attachment is within the users scope...
706 $sql = 'SELECT msg_id
707 FROM ' . PRIVMSGS_TO_TABLE . '
708 WHERE msg_id = ' . (int) $msg_id . '
709 AND (
710 user_id = ' . (int) $user_id . '
711 OR author_id = ' . (int) $user_id . '
712 )';
713 $result = $db->sql_query_limit($sql, 1);
714 $allowed = (bool) $db->sql_fetchfield('msg_id');
715 $db->sql_freeresult($result);
716
717 return $allowed;
718 }
719
720 /**
721 * Check if the browser is internet explorer version 7+
722 *
723 * @param string $user_agent User agent HTTP header
724 * @param int $version IE version to check against
725 *
726 * @return bool true if internet explorer version is greater than $version
727 */
728 function phpbb_is_greater_ie_version($user_agent, $version)
729 {
730 if (preg_match('/msie (\d+)/', strtolower($user_agent), $matches))
731 {
732 $ie_version = (int) $matches[1];
733 return ($ie_version > $version);
734 }
735 else
736 {
737 return false;
738 }
739 }
740