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.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

functions_download.php

Zuletzt modifiziert: 09.10.2024, 12:51 - Dateigröße: 18.24 KiB


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