albertlast /
SMF2.1
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | |||
| 3 | /** |
||
| 4 | * This file handles avatar and attachment preview requests. The whole point of this file is to reduce the loaded stuff to show an image. |
||
| 5 | * |
||
| 6 | * Simple Machines Forum (SMF) |
||
| 7 | * |
||
| 8 | * @package SMF |
||
| 9 | * @author Simple Machines http://www.simplemachines.org |
||
| 10 | * @copyright 2017 Simple Machines and individual contributors |
||
| 11 | * @license http://www.simplemachines.org/about/smf/license.php BSD |
||
| 12 | * |
||
| 13 | * @version 2.1 Beta 4 |
||
| 14 | */ |
||
| 15 | |||
| 16 | if (!defined('SMF')) |
||
| 17 | die('No direct access...'); |
||
| 18 | |||
| 19 | /** |
||
| 20 | * Shows an avatar based on $_GET['attach'] |
||
| 21 | */ |
||
| 22 | function showAttachment() |
||
| 23 | { |
||
| 24 | global $smcFunc, $modSettings, $maintenance, $context; |
||
| 25 | |||
| 26 | // Some defaults that we need. |
||
| 27 | $context['character_set'] = empty($modSettings['global_character_set']) ? (empty($txt['lang_character_set']) ? 'ISO-8859-1' : $txt['lang_character_set']) : $modSettings['global_character_set']; |
||
| 28 | $context['utf8'] = $context['character_set'] === 'UTF-8'; |
||
| 29 | |||
| 30 | // An early hook to set up global vars, clean cache and other early process. |
||
| 31 | call_integration_hook('integrate_pre_download_request'); |
||
| 32 | |||
| 33 | // This is done to clear any output that was made before now. |
||
| 34 | ob_end_clean(); |
||
| 35 | |||
| 36 | if (!empty($modSettings['enableCompressedOutput']) && !headers_sent() && ob_get_length() == 0) |
||
| 37 | { |
||
| 38 | View Code Duplication | if (@ini_get('zlib.output_compression') == '1' || @ini_get('output_handler') == 'ob_gzhandler') |
|
|
0 ignored issues
–
show
|
|||
| 39 | $modSettings['enableCompressedOutput'] = 0; |
||
| 40 | |||
| 41 | else |
||
| 42 | ob_start('ob_gzhandler'); |
||
| 43 | } |
||
| 44 | |||
| 45 | if (empty($modSettings['enableCompressedOutput'])) |
||
| 46 | { |
||
| 47 | ob_start(); |
||
| 48 | header('Content-Encoding: none'); |
||
| 49 | } |
||
| 50 | |||
| 51 | // Better handling. |
||
| 52 | $attachId = isset($_REQUEST['attach']) ? (int) $_REQUEST['attach'] : (int) (isset($_REQUEST['id']) ? (int) $_REQUEST['id'] : 0); |
||
| 53 | |||
| 54 | // We need a valid ID. |
||
| 55 | if (empty($attachId)) |
||
| 56 | { |
||
| 57 | header('HTTP/1.0 404 File Not Found'); |
||
| 58 | die('404 File Not Found'); |
||
| 59 | } |
||
| 60 | |||
| 61 | // A thumbnail has been requested? madness! madness I say! |
||
| 62 | $preview = isset($_REQUEST['preview']) ? $_REQUEST['preview'] : (isset($_REQUEST['type']) && $_REQUEST['type'] == 'preview' ? $_REQUEST['type'] : 0); |
||
| 63 | $showThumb = isset($_REQUEST['thumb']) || !empty($preview); |
||
| 64 | $attachTopic = isset($_REQUEST['topic']) ? (int) $_REQUEST['topic'] : 0; |
||
| 65 | |||
| 66 | // No access in strict maintenance mode or you don't have permission to see attachments. |
||
| 67 | if ((!empty($maintenance) && $maintenance == 2) || !allowedTo('view_attachments')) |
||
| 68 | { |
||
| 69 | header('HTTP/1.0 404 File Not Found'); |
||
| 70 | die('404 File Not Found'); |
||
| 71 | } |
||
| 72 | |||
| 73 | // Use cache when possible. |
||
| 74 | if (($cache = cache_get_data('attachment_lookup_id-' . $attachId)) != null) |
||
| 75 | list($file, $thumbFile) = $cache; |
||
| 76 | |||
| 77 | // Get the info from the DB. |
||
| 78 | if (empty($file) || empty($thumbFile) && !empty($file['id_thumb'])) |
||
| 79 | { |
||
| 80 | // Do we have a hook wanting to use our attachment system? We use $attachRequest to prevent accidental usage of $request. |
||
| 81 | $attachRequest = null; |
||
| 82 | call_integration_hook('integrate_download_request', array(&$attachRequest)); |
||
| 83 | if (!is_null($attachRequest) && $smcFunc['db_is_resource']($attachRequest)) |
||
| 84 | $request = $attachRequest; |
||
| 85 | |||
| 86 | else |
||
| 87 | { |
||
| 88 | // Make sure this attachment is on this board and load its info while we are at it. |
||
| 89 | $request = $smcFunc['db_query']('', ' |
||
| 90 | SELECT id_folder, filename, file_hash, fileext, id_attach, id_thumb, attachment_type, mime_type, approved, id_msg |
||
| 91 | FROM {db_prefix}attachments |
||
| 92 | WHERE id_attach = {int:attach} |
||
| 93 | LIMIT 1', |
||
| 94 | array( |
||
| 95 | 'attach' => $attachId, |
||
| 96 | ) |
||
| 97 | ); |
||
| 98 | } |
||
| 99 | |||
| 100 | // No attachment has been found. |
||
| 101 | if ($smcFunc['db_num_rows']($request) == 0) |
||
| 102 | { |
||
| 103 | header('HTTP/1.0 404 File Not Found'); |
||
| 104 | die('404 File Not Found'); |
||
| 105 | } |
||
| 106 | |||
| 107 | $file = $smcFunc['db_fetch_assoc']($request); |
||
| 108 | $smcFunc['db_free_result']($request); |
||
| 109 | |||
| 110 | // If theres a message ID stored, we NEED a topic ID. |
||
| 111 | if (!empty($file['id_msg']) && empty($attachTopic) && empty($preview)) |
||
| 112 | { |
||
| 113 | header('HTTP/1.0 404 File Not Found'); |
||
| 114 | die('404 File Not Found'); |
||
| 115 | } |
||
| 116 | |||
| 117 | // Previews doesn't have this info. |
||
| 118 | if (empty($preview)) |
||
| 119 | { |
||
| 120 | $request2 = $smcFunc['db_query']('', ' |
||
| 121 | SELECT a.id_msg |
||
| 122 | FROM {db_prefix}attachments AS a |
||
| 123 | INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg AND m.id_topic = {int:current_topic}) |
||
| 124 | INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board}) |
||
| 125 | WHERE a.id_attach = {int:attach} |
||
| 126 | LIMIT 1', |
||
| 127 | array( |
||
| 128 | 'attach' => $attachId, |
||
| 129 | 'current_topic' => $attachTopic, |
||
| 130 | ) |
||
| 131 | ); |
||
| 132 | |||
| 133 | // The provided topic must match the one stored in the DB for this particular attachment, also. |
||
| 134 | if ($smcFunc['db_num_rows']($request2) == 0) |
||
| 135 | { |
||
| 136 | header('HTTP/1.0 404 File Not Found'); |
||
| 137 | die('404 File Not Found'); |
||
| 138 | } |
||
| 139 | |||
| 140 | $smcFunc['db_free_result']($request2); |
||
| 141 | } |
||
| 142 | |||
| 143 | // set filePath and ETag time |
||
| 144 | $file['filePath'] = getAttachmentFilename($file['filename'], $attachId, $file['id_folder'], false, $file['file_hash']); |
||
| 145 | // ensure variant attachment compatibility |
||
| 146 | $filePath = pathinfo($file['filePath']); |
||
| 147 | $file['filePath'] = !file_exists($file['filePath']) ? substr($file['filePath'], 0, -(strlen($filePath['extension']) + 1)) : $file['filePath']; |
||
| 148 | $file['etag'] = '"' . md5_file($file['filePath']) . '"'; |
||
| 149 | |||
| 150 | // now get the thumbfile! |
||
| 151 | $thumbFile = array(); |
||
| 152 | if (!empty($file['id_thumb'])) |
||
| 153 | { |
||
| 154 | $request = $smcFunc['db_query']('', ' |
||
| 155 | SELECT id_folder, filename, file_hash, fileext, id_attach, attachment_type, mime_type, approved, id_member |
||
| 156 | FROM {db_prefix}attachments |
||
| 157 | WHERE id_attach = {int:thumb_id} |
||
| 158 | LIMIT 1', |
||
| 159 | array( |
||
| 160 | 'thumb_id' => $file['id_thumb'], |
||
| 161 | ) |
||
| 162 | ); |
||
| 163 | |||
| 164 | $thumbFile = $smcFunc['db_fetch_assoc']($request); |
||
| 165 | $smcFunc['db_free_result']($request); |
||
| 166 | |||
| 167 | // Got something! replace the $file var with the thumbnail info. |
||
| 168 | View Code Duplication | if ($thumbFile) |
|
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. Loading history...
|
|||
| 169 | { |
||
| 170 | $attachId = $thumbFile['id_attach']; |
||
| 171 | |||
| 172 | // set filePath and ETag time |
||
| 173 | $thumbFile['filePath'] = getAttachmentFilename($thumbFile['filename'], $attachId, $thumbFile['id_folder'], false, $thumbFile['file_hash']); |
||
| 174 | $thumbFile['etag'] = '"' . md5_file($thumbFile['filePath']) . '"'; |
||
| 175 | } |
||
| 176 | } |
||
| 177 | |||
| 178 | // Cache it. |
||
| 179 | if (!empty($file) || !empty($thumbFile)) |
||
| 180 | cache_put_data('attachment_lookup_id-' . $file['id_attach'], array($file, $thumbFile), mt_rand(850, 900)); |
||
| 181 | } |
||
| 182 | |||
| 183 | // Replace the normal file with its thumbnail if it has one! |
||
| 184 | if (!empty($showThumb) && !empty($thumbFile)) |
||
| 185 | $file = $thumbFile; |
||
| 186 | |||
| 187 | // No point in a nicer message, because this is supposed to be an attachment anyway... |
||
| 188 | View Code Duplication | if (!file_exists($file['filePath'])) |
|
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. Loading history...
|
|||
| 189 | { |
||
| 190 | header((preg_match('~HTTP/1\.[01]~i', $_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0') . ' 404 Not Found'); |
||
| 191 | header('Content-Type: text/plain; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set'])); |
||
| 192 | |||
| 193 | // We need to die like this *before* we send any anti-caching headers as below. |
||
| 194 | die('File not found.'); |
||
| 195 | } |
||
| 196 | |||
| 197 | // If it hasn't been modified since the last time this attachment was retrieved, there's no need to display it again. |
||
| 198 | View Code Duplication | if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) |
|
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. Loading history...
|
|||
| 199 | { |
||
| 200 | list($modified_since) = explode(';', $_SERVER['HTTP_IF_MODIFIED_SINCE']); |
||
| 201 | if (strtotime($modified_since) >= filemtime($file['filePath'])) |
||
| 202 | { |
||
| 203 | ob_end_clean(); |
||
| 204 | |||
| 205 | // Answer the question - no, it hasn't been modified ;). |
||
| 206 | header('HTTP/1.1 304 Not Modified'); |
||
| 207 | exit; |
||
| 208 | } |
||
| 209 | } |
||
| 210 | |||
| 211 | // Check whether the ETag was sent back, and cache based on that... |
||
| 212 | $eTag = '"' . substr($_REQUEST['attach'] . $file['filePath'] . filemtime($file['filePath']), 0, 64) . '"'; |
||
| 213 | View Code Duplication | if (!empty($_SERVER['HTTP_IF_NONE_MATCH']) && strpos($_SERVER['HTTP_IF_NONE_MATCH'], $eTag) !== false) |
|
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. Loading history...
|
|||
| 214 | { |
||
| 215 | ob_end_clean(); |
||
| 216 | |||
| 217 | header('HTTP/1.1 304 Not Modified'); |
||
| 218 | exit; |
||
| 219 | } |
||
| 220 | |||
| 221 | // If this is a partial download, we need to determine what data range to send |
||
| 222 | $range = 0; |
||
| 223 | $size = filesize($file['filePath']); |
||
| 224 | if (isset($_SERVER['HTTP_RANGE'])) |
||
| 225 | { |
||
| 226 | list($a, $range) = explode("=", $_SERVER['HTTP_RANGE'], 2); |
||
| 227 | list($range) = explode(",", $range, 2); |
||
| 228 | list($range, $range_end) = explode("-", $range); |
||
| 229 | $range = intval($range); |
||
| 230 | $range_end = !$range_end ? $size - 1 : intval($range_end); |
||
| 231 | $new_length = $range_end - $range + 1; |
||
| 232 | } |
||
| 233 | |||
| 234 | // Update the download counter (unless it's a thumbnail or resuming an incomplete download). |
||
| 235 | if ($file['attachment_type'] != 3 && empty($showThumb) && $range === 0) |
||
| 236 | $smcFunc['db_query']('attach_download_increase', ' |
||
| 237 | UPDATE LOW_PRIORITY {db_prefix}attachments |
||
| 238 | SET downloads = downloads + 1 |
||
| 239 | WHERE id_attach = {int:id_attach}', |
||
| 240 | array( |
||
| 241 | 'id_attach' => $attachId, |
||
| 242 | ) |
||
| 243 | ); |
||
| 244 | |||
| 245 | // Send the attachment headers. |
||
| 246 | header('Pragma: '); |
||
| 247 | |||
| 248 | if (!isBrowser('gecko')) |
||
| 249 | header('Content-Transfer-Encoding: binary'); |
||
| 250 | |||
| 251 | header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 525600 * 60) . ' GMT'); |
||
| 252 | header('Last-Modified: ' . gmdate('D, d M Y H:i:s', filemtime($file['filePath'])) . ' GMT'); |
||
| 253 | header('Accept-Ranges: bytes'); |
||
| 254 | header('Connection: close'); |
||
| 255 | header('ETag: ' . $eTag); |
||
| 256 | |||
| 257 | // Make sure the mime type warrants an inline display. |
||
| 258 | if (isset($_REQUEST['image']) && !empty($file['mime_type']) && strpos($file['mime_type'], 'image/') !== 0) |
||
| 259 | unset($_REQUEST['image']); |
||
| 260 | |||
| 261 | // Does this have a mime type? |
||
| 262 | elseif (!empty($file['mime_type']) && (isset($_REQUEST['image']) || !in_array($file['fileext'], array('jpg', 'gif', 'jpeg', 'x-ms-bmp', 'png', 'psd', 'tiff', 'iff')))) |
||
| 263 | header('Content-Type: ' . strtr($file['mime_type'], array('image/bmp' => 'image/x-ms-bmp'))); |
||
| 264 | |||
| 265 | View Code Duplication | else |
|
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. Loading history...
|
|||
| 266 | { |
||
| 267 | header('Content-Type: ' . (isBrowser('ie') || isBrowser('opera') ? 'application/octetstream' : 'application/octet-stream')); |
||
| 268 | if (isset($_REQUEST['image'])) |
||
| 269 | unset($_REQUEST['image']); |
||
| 270 | } |
||
| 271 | |||
| 272 | // Convert the file to UTF-8, cuz most browsers dig that. |
||
| 273 | $utf8name = !$context['utf8'] && function_exists('iconv') ? iconv($context['character_set'], 'UTF-8', $file['filename']) : (!$context['utf8'] && function_exists('mb_convert_encoding') ? mb_convert_encoding($file['filename'], 'UTF-8', $context['character_set']) : $file['filename']); |
||
| 274 | $disposition = !isset($_REQUEST['image']) ? 'attachment' : 'inline'; |
||
| 275 | |||
| 276 | // Different browsers like different standards... |
||
| 277 | View Code Duplication | if (isBrowser('firefox')) |
|
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. Loading history...
|
|||
| 278 | header('Content-Disposition: ' . $disposition . '; filename*=UTF-8\'\'' . rawurlencode(preg_replace_callback('~&#(\d{3,8});~', 'fixchar__callback', $utf8name))); |
||
| 279 | |||
| 280 | elseif (isBrowser('opera')) |
||
| 281 | header('Content-Disposition: ' . $disposition . '; filename="' . preg_replace_callback('~&#(\d{3,8});~', 'fixchar__callback', $utf8name) . '"'); |
||
| 282 | |||
| 283 | elseif (isBrowser('ie')) |
||
| 284 | header('Content-Disposition: ' . $disposition . '; filename="' . urlencode(preg_replace_callback('~&#(\d{3,8});~', 'fixchar__callback', $utf8name)) . '"'); |
||
| 285 | |||
| 286 | else |
||
| 287 | header('Content-Disposition: ' . $disposition . '; filename="' . $utf8name . '"'); |
||
| 288 | |||
| 289 | // If this has an "image extension" - but isn't actually an image - then ensure it isn't cached cause of silly IE. |
||
| 290 | View Code Duplication | if (!isset($_REQUEST['image']) && in_array($file['fileext'], array('gif', 'jpg', 'bmp', 'png', 'jpeg', 'tiff'))) |
|
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. Loading history...
|
|||
| 291 | header('Cache-Control: no-cache'); |
||
| 292 | |||
| 293 | else |
||
| 294 | header('Cache-Control: max-age=' . (525600 * 60) . ', private'); |
||
| 295 | |||
| 296 | // Multipart and resuming support |
||
| 297 | if (isset($_SERVER['HTTP_RANGE'])) |
||
| 298 | { |
||
| 299 | header("HTTP/1.1 206 Partial Content"); |
||
| 300 | header("Content-Length: $new_length"); |
||
| 301 | header("Content-Range: bytes $range-$range_end/$size"); |
||
| 302 | } |
||
| 303 | else |
||
| 304 | header("Content-Length: " . $size); |
||
| 305 | |||
| 306 | |||
| 307 | // Try to buy some time... |
||
| 308 | @set_time_limit(600); |
||
| 309 | |||
| 310 | // For multipart/resumable downloads, send the requested chunk(s) of the file |
||
| 311 | if (isset($_SERVER['HTTP_RANGE'])) |
||
| 312 | { |
||
| 313 | while (@ob_get_level() > 0) |
||
| 314 | @ob_end_clean(); |
||
| 315 | |||
| 316 | // 40 kilobytes is a good-ish amount |
||
| 317 | $chunksize = 40 * 1024; |
||
| 318 | $bytes_sent = 0; |
||
| 319 | |||
| 320 | $fp = fopen($file['filePath'], 'rb'); |
||
| 321 | |||
| 322 | fseek($fp, $range); |
||
| 323 | |||
| 324 | while (!feof($fp) && (!connection_aborted()) && ($bytes_sent < $new_length)) |
||
| 325 | { |
||
| 326 | $buffer = fread($fp, $chunksize); |
||
| 327 | echo($buffer); |
||
| 328 | flush(); |
||
| 329 | $bytes_sent += strlen($buffer); |
||
| 330 | } |
||
| 331 | fclose($fp); |
||
| 332 | } |
||
| 333 | |||
| 334 | // Since we don't do output compression for files this large... |
||
| 335 | elseif ($size > 4194304) |
||
| 336 | { |
||
| 337 | // Forcibly end any output buffering going on. |
||
| 338 | while (@ob_get_level() > 0) |
||
| 339 | @ob_end_clean(); |
||
| 340 | |||
| 341 | $fp = fopen($file['filePath'], 'rb'); |
||
| 342 | while (!feof($fp)) |
||
| 343 | { |
||
| 344 | echo fread($fp, 8192); |
||
| 345 | flush(); |
||
| 346 | } |
||
| 347 | fclose($fp); |
||
| 348 | } |
||
| 349 | |||
| 350 | // 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. |
||
| 351 | elseif (@readfile($file['filePath']) === null) |
||
| 352 | echo file_get_contents($file['filePath']); |
||
| 353 | |||
| 354 | die(); |
||
| 355 | } |
||
| 356 | |||
| 357 | ?> |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.