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.
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: 19.60 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 '<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