PageRenderTime 46ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/sources/controllers/Attachment.controller.php

https://github.com/Arantor/Elkarte
PHP | 215 lines | 126 code | 37 blank | 52 comment | 46 complexity | bb67a876dc4f846ea4e037a44a60ce14 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-3.0
  1. <?php
  2. /**
  3. * @name ElkArte Forum
  4. * @copyright ElkArte Forum contributors
  5. * @license BSD http://opensource.org/licenses/BSD-3-Clause
  6. *
  7. * This software is a derived product, based on:
  8. *
  9. * Simple Machines Forum (SMF)
  10. * copyright: 2011 Simple Machines (http://www.simplemachines.org)
  11. * license: BSD, See included LICENSE.TXT for terms and conditions.
  12. *
  13. * @version 1.0 Alpha
  14. *
  15. * Attachment display.
  16. *
  17. */
  18. /**
  19. * The default action is to download an attachment.
  20. * This allows ?action=attachment to be forwarded to action_dlattach()
  21. */
  22. function action_attachment()
  23. {
  24. // default action to execute
  25. action_dlattach();
  26. }
  27. /**
  28. * Downloads an attachment or avatar, and increments the download count.
  29. * It requires the view_attachments permission. (not for avatars!)
  30. * It disables the session parser, and clears any previous output.
  31. * It is accessed via the query string ?action=dlattach.
  32. * Views to attachments and avatars do not increase hits and are not logged in the "Who's Online" log.
  33. */
  34. function action_dlattach()
  35. {
  36. global $txt, $modSettings, $user_info, $scripturl, $context, $topic, $smcFunc;
  37. // Some defaults that we need.
  38. $context['character_set'] = 'UTF-8';
  39. $context['no_last_modified'] = true;
  40. // Make sure some attachment was requested!
  41. if (!isset($_REQUEST['attach']) && !isset($_REQUEST['id']))
  42. fatal_lang_error('no_access', false);
  43. // We need to do some work on attachments and avatars.
  44. require_once(SUBSDIR . '/Attachments.subs.php');
  45. $id_attach = isset($_REQUEST['attach']) ? (int) $_REQUEST['attach'] : (int) $_REQUEST['id'];
  46. if (isset($_REQUEST['type']) && $_REQUEST['type'] == 'avatar')
  47. {
  48. $attachment = getAvatar($id_attach);
  49. $is_avatar = true;
  50. $_REQUEST['image'] = true;
  51. }
  52. // This is just a regular attachment...
  53. else
  54. {
  55. // This checks only the current board for $board/$topic's permissions.
  56. // @todo: We must verify that $topic is the attachment's topic, or else the permission check is broken.
  57. isAllowedTo('view_attachments');
  58. $attachment = getAttachmentFromTopic($id_attach, $topic);
  59. }
  60. if (empty($attachment))
  61. fatal_lang_error('no_access', false);
  62. list ($id_folder, $real_filename, $file_hash, $file_ext, $attachment_type, $mime_type, $is_approved, $id_member) = $attachment;
  63. // If it isn't yet approved, do they have permission to view it?
  64. if (!$is_approved && ($id_member == 0 || $user_info['id'] != $id_member) && ($attachment_type == 0 || $attachment_type == 3))
  65. isAllowedTo('approve_posts');
  66. // Update the download counter (unless it's a thumbnail or an avatar).
  67. if (empty($is_avatar) || $attachment_type != 3)
  68. increaseDownloadCounter($id_attach);
  69. $filename = getAttachmentFilename($real_filename, $id_attach, $id_folder, false, $file_hash);
  70. // This is done to clear any output that was made before now.
  71. ob_end_clean();
  72. if (!empty($modSettings['enableCompressedOutput']) && @filesize($filename) <= 4194304 && in_array($file_ext, array('txt', 'html', 'htm', 'js', 'doc', 'docx', 'rtf', 'css', 'php', 'log', 'xml', 'sql', 'c', 'java')))
  73. @ob_start('ob_gzhandler');
  74. else
  75. {
  76. ob_start();
  77. header('Content-Encoding: none');
  78. }
  79. // No point in a nicer message, because this is supposed to be an attachment anyway...
  80. if (!file_exists($filename))
  81. {
  82. loadLanguage('Errors');
  83. header((preg_match('~HTTP/1\.[01]~i', $_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0') . ' 404 Not Found');
  84. header('Content-Type: text/plain; charset=UTF-8');
  85. // We need to die like this *before* we send any anti-caching headers as below.
  86. die('404 - ' . $txt['attachment_not_found']);
  87. }
  88. // If it hasn't been modified since the last time this attachment was retrieved, there's no need to display it again.
  89. if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']))
  90. {
  91. list($modified_since) = explode(';', $_SERVER['HTTP_IF_MODIFIED_SINCE']);
  92. if (strtotime($modified_since) >= filemtime($filename))
  93. {
  94. ob_end_clean();
  95. // Answer the question - no, it hasn't been modified ;).
  96. header('HTTP/1.1 304 Not Modified');
  97. exit;
  98. }
  99. }
  100. // Check whether the ETag was sent back, and cache based on that...
  101. $eTag = '"' . substr($id_attach . $real_filename . filemtime($filename), 0, 64) . '"';
  102. if (!empty($_SERVER['HTTP_IF_NONE_MATCH']) && strpos($_SERVER['HTTP_IF_NONE_MATCH'], $eTag) !== false)
  103. {
  104. ob_end_clean();
  105. header('HTTP/1.1 304 Not Modified');
  106. exit;
  107. }
  108. // Send the attachment headers.
  109. header('Pragma: ');
  110. if (!isBrowser('gecko'))
  111. header('Content-Transfer-Encoding: binary');
  112. header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 525600 * 60) . ' GMT');
  113. header('Last-Modified: ' . gmdate('D, d M Y H:i:s', filemtime($filename)) . ' GMT');
  114. header('Accept-Ranges: bytes');
  115. header('Connection: close');
  116. header('ETag: ' . $eTag);
  117. // Make sure the mime type warrants an inline display.
  118. if (isset($_REQUEST['image']) && !empty($mime_type) && strpos($mime_type, 'image/') !== 0)
  119. unset($_REQUEST['image']);
  120. // Does this have a mime type?
  121. elseif (!empty($mime_type) && (isset($_REQUEST['image']) || !in_array($file_ext, array('jpg', 'gif', 'jpeg', 'x-ms-bmp', 'png', 'psd', 'tiff', 'iff'))))
  122. header('Content-Type: ' . strtr($mime_type, array('image/bmp' => 'image/x-ms-bmp')));
  123. else
  124. {
  125. header('Content-Type: ' . (isBrowser('ie') || isBrowser('opera') ? 'application/octetstream' : 'application/octet-stream'));
  126. if (isset($_REQUEST['image']))
  127. unset($_REQUEST['image']);
  128. }
  129. $disposition = !isset($_REQUEST['image']) ? 'attachment' : 'inline';
  130. // Different browsers like different standards...
  131. if (isBrowser('firefox'))
  132. header('Content-Disposition: ' . $disposition . '; filename*=UTF-8\'\'' . rawurlencode(preg_replace_callback('~&#(\d{3,8});~', 'fixchar__callback', $real_filename)));
  133. elseif (isBrowser('opera'))
  134. header('Content-Disposition: ' . $disposition . '; filename="' . preg_replace_callback('~&#(\d{3,8});~', 'fixchar__callback', $real_filename) . '"');
  135. elseif (isBrowser('ie'))
  136. header('Content-Disposition: ' . $disposition . '; filename="' . urlencode(preg_replace_callback('~&#(\d{3,8});~', 'fixchar__callback', $real_filename)) . '"');
  137. else
  138. header('Content-Disposition: ' . $disposition . '; filename="' . $real_filename . '"');
  139. // If this has an "image extension" - but isn't actually an image - then ensure it isn't cached cause of silly IE.
  140. if (!isset($_REQUEST['image']) && in_array($file_ext, array('gif', 'jpg', 'bmp', 'png', 'jpeg', 'tiff')))
  141. header('Cache-Control: no-cache');
  142. else
  143. header('Cache-Control: max-age=' . (525600 * 60) . ', private');
  144. header('Content-Length: ' . filesize($filename));
  145. // Try to buy some time...
  146. @set_time_limit(600);
  147. // Recode line endings for text files, if enabled.
  148. if (!empty($modSettings['attachmentRecodeLineEndings']) && !isset($_REQUEST['image']) && in_array($file_ext, array('txt', 'css', 'htm', 'html', 'php', 'xml')))
  149. {
  150. if (strpos($_SERVER['HTTP_USER_AGENT'], 'Windows') !== false)
  151. $callback = create_function('$buffer', 'return preg_replace(\'~[\r]?\n~\', "\r\n", $buffer);');
  152. elseif (strpos($_SERVER['HTTP_USER_AGENT'], 'Mac') !== false)
  153. $callback = create_function('$buffer', 'return preg_replace(\'~[\r]?\n~\', "\r", $buffer);');
  154. else
  155. $callback = create_function('$buffer', 'return preg_replace(\'~[\r]?\n~\', "\n", $buffer);');
  156. }
  157. // Since we don't do output compression for files this large...
  158. if (filesize($filename) > 4194304)
  159. {
  160. // Forcibly end any output buffering going on.
  161. while (@ob_get_level() > 0)
  162. @ob_end_clean();
  163. $fp = fopen($filename, 'rb');
  164. while (!feof($fp))
  165. {
  166. if (isset($callback))
  167. echo $callback(fread($fp, 8192));
  168. else
  169. echo fread($fp, 8192);
  170. flush();
  171. }
  172. fclose($fp);
  173. }
  174. // On some of the less-bright hosts, readfile() is disabled. It's just a faster, more byte safe, version of what's in the if.
  175. elseif (isset($callback) || @readfile($filename) === null)
  176. echo isset($callback) ? $callback(file_get_contents($filename)) : file_get_contents($filename);
  177. obExit(false);
  178. }