Verzeichnisstruktur phpBB-3.3.16
- Veröffentlicht
- 27.04.2026
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 '<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: private');
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 // Send restrictive CSP for file served to browser
205 header("Content-Security-Policy: default-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self'; script-src 'none'; object-src 'none'; frame-src 'none';");
206
207 if (phpbb_is_greater_ie_version($user->browser, 7))
208 {
209 header('X-Content-Type-Options: nosniff');
210 }
211
212 if (empty($user->browser) || ((strpos(strtolower($user->browser), 'msie') !== false) && !phpbb_is_greater_ie_version($user->browser, 7)))
213 {
214 header('Content-Disposition: attachment; ' . header_filename(html_entity_decode($attachment['real_filename'], ENT_COMPAT)));
215 if (empty($user->browser) || (strpos(strtolower($user->browser), 'msie 6.0') !== false))
216 {
217 header('Expires: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT');
218 }
219 }
220 else
221 {
222 $sec_fetch_dest = $request->header('Sec-Fetch-Dest');
223
224 // Only set inline if category is set to image, mimetype says it's an image, and browser either sends no Sec-Fetch-Dest header or explicitly marks the request as an image
225 if ($category == ATTACHMENT_CATEGORY_IMAGE && phpbb_allow_serve_inline($attachment['mimetype'], $sec_fetch_dest))
226 {
227 $disposition = 'inline';
228 }
229 else
230 {
231 $disposition = 'attachment';
232 }
233 header('Content-Disposition: ' . $disposition . '; ' . header_filename(html_entity_decode($attachment['real_filename'], ENT_COMPAT)));
234 if (phpbb_is_greater_ie_version($user->browser, 7) && (strpos($attachment['mimetype'], 'image') !== 0))
235 {
236 header('X-Download-Options: noopen');
237 }
238 }
239
240 // Close the db connection before sending the file etc.
241 file_gc(false);
242
243 if (!set_modified_headers($attachment['filetime'], $user->browser))
244 {
245 // We make sure those have to be enabled manually by defining a constant
246 // because of the potential disclosure of full attachment path
247 // in case support for features is absent in the webserver software.
248 if (defined('PHPBB_ENABLE_X_ACCEL_REDIRECT') && PHPBB_ENABLE_X_ACCEL_REDIRECT)
249 {
250 // X-Accel-Redirect - http://wiki.nginx.org/XSendfile
251 header('X-Accel-Redirect: ' . $user->page['root_script_path'] . $upload_dir . '/' . $attachment['physical_filename']);
252 exit;
253 }
254 else if (defined('PHPBB_ENABLE_X_SENDFILE') && PHPBB_ENABLE_X_SENDFILE && !phpbb_http_byte_range($size))
255 {
256 // X-Sendfile - http://blog.lighttpd.net/articles/2006/07/02/x-sendfile
257 // Lighttpd's X-Sendfile does not support range requests as of 1.4.26
258 // and always requires an absolute path.
259 header('X-Sendfile: ' . __DIR__ . "/../$upload_dir/{$attachment['physical_filename']}");
260 exit;
261 }
262
263 if ($size)
264 {
265 header("Content-Length: $size");
266 }
267
268 // Try to deliver in chunks
269 @set_time_limit(0);
270
271 $fp = @fopen($filename, 'rb');
272
273 if ($fp !== false)
274 {
275 // Deliver file partially if requested
276 if ($range = phpbb_http_byte_range($size))
277 {
278 fseek($fp, $range['byte_pos_start']);
279
280 send_status_line(206, 'Partial Content');
281 header('Content-Range: bytes ' . $range['byte_pos_start'] . '-' . $range['byte_pos_end'] . '/' . $range['bytes_total']);
282 header('Content-Length: ' . $range['bytes_requested']);
283
284 // Read until we reach the end of the requested range
285 $bytes_to_read = $range['bytes_requested'];
286 while ($bytes_to_read > 0 && !feof($fp))
287 {
288 $chunk = ($bytes_to_read > 8192) ? 8192 : $bytes_to_read;
289 echo fread($fp, $chunk);
290 $bytes_to_read -= $chunk;
291 }
292 }
293 else
294 {
295 while (!feof($fp))
296 {
297 echo fread($fp, 8192);
298 }
299 }
300 fclose($fp);
301 }
302 else
303 {
304 @readfile($filename);
305 }
306
307 flush();
308 }
309
310 exit;
311 }
312
313 /**
314 * Get a browser friendly UTF-8 encoded filename
315 */
316 function header_filename($file)
317 {
318 global $request;
319
320 $user_agent = $request->header('User-Agent');
321
322 // There be dragons here.
323 // Not many follows the RFC...
324 if (strpos($user_agent, 'MSIE') !== false || strpos($user_agent, 'Konqueror') !== false)
325 {
326 return "filename=" . rawurlencode($file);
327 }
328
329 // follow the RFC for extended filename for the rest
330 return "filename*=UTF-8''" . rawurlencode($file);
331 }
332
333 /**
334 * Check if downloading item is allowed
335 */
336 function download_allowed()
337 {
338 global $config, $user, $db, $request;
339
340 if (!$config['secure_downloads'])
341 {
342 return true;
343 }
344
345 $url = html_entity_decode($request->header('Referer'), ENT_COMPAT);
346
347 if (!$url)
348 {
349 return ($config['secure_allow_empty_referer']) ? true : false;
350 }
351
352 // Split URL into domain and script part
353 $url = @parse_url($url);
354
355 if ($url === false)
356 {
357 return ($config['secure_allow_empty_referer']) ? true : false;
358 }
359
360 $hostname = $url['host'];
361 unset($url);
362
363 $allowed = ($config['secure_allow_deny']) ? false : true;
364 $iplist = array();
365
366 if (($ip_ary = @gethostbynamel($hostname)) !== false)
367 {
368 foreach ($ip_ary as $ip)
369 {
370 if ($ip)
371 {
372 $iplist[] = $ip;
373 }
374 }
375 }
376
377 // Check for own server...
378 $server_name = $user->host;
379
380 // Forcing server vars is the only way to specify/override the protocol
381 if ($config['force_server_vars'] || !$server_name)
382 {
383 $server_name = $config['server_name'];
384 }
385
386 if (preg_match('#^.*?' . preg_quote($server_name, '#') . '.*?$#i', $hostname))
387 {
388 $allowed = true;
389 }
390
391 // Get IP's and Hostnames
392 if (!$allowed)
393 {
394 $sql = 'SELECT site_ip, site_hostname, ip_exclude
395 FROM ' . SITELIST_TABLE;
396 $result = $db->sql_query($sql);
397
398 while ($row = $db->sql_fetchrow($result))
399 {
400 $site_ip = trim($row['site_ip']);
401 $site_hostname = trim($row['site_hostname']);
402
403 if ($site_ip)
404 {
405 foreach ($iplist as $ip)
406 {
407 if (preg_match('#^' . str_replace('\*', '.*?', preg_quote($site_ip, '#')) . '$#i', $ip))
408 {
409 if ($row['ip_exclude'])
410 {
411 $allowed = ($config['secure_allow_deny']) ? false : true;
412 break 2;
413 }
414 else
415 {
416 $allowed = ($config['secure_allow_deny']) ? true : false;
417 }
418 }
419 }
420 }
421
422 if ($site_hostname)
423 {
424 if (preg_match('#^' . str_replace('\*', '.*?', preg_quote($site_hostname, '#')) . '$#i', $hostname))
425 {
426 if ($row['ip_exclude'])
427 {
428 $allowed = ($config['secure_allow_deny']) ? false : true;
429 break;
430 }
431 else
432 {
433 $allowed = ($config['secure_allow_deny']) ? true : false;
434 }
435 }
436 }
437 }
438 $db->sql_freeresult($result);
439 }
440
441 return $allowed;
442 }
443
444 /**
445 * Check if the browser has the file already and set the appropriate headers-
446 * @returns false if a resend is in order.
447 */
448 function set_modified_headers($stamp, $browser)
449 {
450 global $request;
451
452 // let's see if we have to send the file at all
453 $last_load = $request->header('If-Modified-Since') ? strtotime(trim($request->header('If-Modified-Since'))) : false;
454
455 if (strpos(strtolower($browser), 'msie 6.0') === false && !phpbb_is_greater_ie_version($browser, 7))
456 {
457 if ($last_load !== false && $last_load >= $stamp)
458 {
459 send_status_line(304, 'Not Modified');
460 // seems that we need those too ... browsers
461 header('Cache-Control: private');
462 header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 31536000) . ' GMT');
463 return true;
464 }
465 else
466 {
467 header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $stamp) . ' GMT');
468 }
469 }
470 return false;
471 }
472
473 /**
474 * Garbage Collection
475 *
476 * @param bool $exit Whether to die or not.
477 *
478 * @return null
479 */
480 function file_gc($exit = true)
481 {
482 global $cache, $db;
483
484 if (!empty($cache))
485 {
486 $cache->unload();
487 }
488
489 $db->sql_close();
490
491 if ($exit)
492 {
493 exit;
494 }
495 }
496
497 /**
498 * HTTP range support (RFC 2616 Section 14.35)
499 *
500 * Allows browsers to request partial file content
501 * in case a download has been interrupted.
502 *
503 * @param int $filesize the size of the file in bytes we are about to deliver
504 *
505 * @return mixed false if the whole file has to be delivered
506 * associative array on success
507 */
508 function phpbb_http_byte_range($filesize)
509 {
510 // Only call find_range_request() once.
511 static $request_array;
512
513 if (!$filesize)
514 {
515 return false;
516 }
517
518 if (!isset($request_array))
519 {
520 $request_array = phpbb_find_range_request();
521 }
522
523 return (empty($request_array)) ? false : phpbb_parse_range_request($request_array, $filesize);
524 }
525
526 /**
527 * Searches for HTTP range request in request headers.
528 *
529 * @return mixed false if no request found
530 * array of strings containing the requested ranges otherwise
531 * e.g. array(0 => '0-0', 1 => '123-125')
532 */
533 function phpbb_find_range_request()
534 {
535 global $request;
536
537 $value = $request->header('Range');
538
539 // Make sure range request starts with "bytes="
540 if (strpos($value, 'bytes=') === 0)
541 {
542 // Strip leading 'bytes='
543 // Multiple ranges can be separated by a comma
544 return explode(',', substr($value, 6));
545 }
546
547 return false;
548 }
549
550 /**
551 * Analyses a range request array.
552 *
553 * A range request can contain multiple ranges,
554 * we however only handle the first request and
555 * only support requests from a given byte to the end of the file.
556 *
557 * @param array $request_array array of strings containing the requested ranges
558 * @param int $filesize the full size of the file in bytes that has been requested
559 *
560 * @return mixed false if the whole file has to be delivered
561 * associative array on success
562 * byte_pos_start the first byte position, can be passed to fseek()
563 * byte_pos_end the last byte position
564 * bytes_requested the number of bytes requested
565 * bytes_total the full size of the file
566 */
567 function phpbb_parse_range_request($request_array, $filesize)
568 {
569 $first_byte_pos = -1;
570 $last_byte_pos = -1;
571
572 // Go through all ranges
573 foreach ($request_array as $range_string)
574 {
575 $range = explode('-', trim($range_string));
576
577 // "-" is invalid, "0-0" however is valid and means the very first byte.
578 if (count($range) != 2 || $range[0] === '' && $range[1] === '')
579 {
580 continue;
581 }
582
583 // Substitute defaults
584 if ($range[0] === '')
585 {
586 $range[0] = 0;
587 }
588
589 if ($range[1] === '')
590 {
591 $range[1] = $filesize - 1;
592 }
593
594 if ($last_byte_pos >= 0 && $last_byte_pos + 1 != $range[0])
595 {
596 // We only support contiguous ranges, no multipart stuff :(
597 return false;
598 }
599
600 if ($range[1] && $range[1] < $range[0])
601 {
602 // The requested range contains 0 bytes.
603 continue;
604 }
605
606 // Return bytes from $range[0] to $range[1]
607 if ($first_byte_pos < 0)
608 {
609 $first_byte_pos = (int) $range[0];
610 }
611
612 $last_byte_pos = (int) $range[1];
613
614 if ($first_byte_pos >= $filesize)
615 {
616 // Requested range not satisfiable
617 return false;
618 }
619
620 // Adjust last-byte-pos if it is absent or greater than the content.
621 if ($range[1] === '' || $last_byte_pos >= $filesize)
622 {
623 $last_byte_pos = $filesize - 1;
624 }
625 }
626
627 if ($first_byte_pos < 0 || $last_byte_pos < 0)
628 {
629 return false;
630 }
631
632 return array(
633 'byte_pos_start' => $first_byte_pos,
634 'byte_pos_end' => $last_byte_pos,
635 'bytes_requested' => $last_byte_pos - $first_byte_pos + 1,
636 'bytes_total' => $filesize,
637 );
638 }
639
640 /**
641 * Increments the download count of all provided attachments
642 *
643 * @param \phpbb\db\driver\driver_interface $db The database object
644 * @param array|int $ids The attach_id of each attachment
645 *
646 * @return null
647 */
648 function phpbb_increment_downloads($db, $ids)
649 {
650 if (!is_array($ids))
651 {
652 $ids = array($ids);
653 }
654
655 $sql = 'UPDATE ' . ATTACHMENTS_TABLE . '
656 SET download_count = download_count + 1
657 WHERE ' . $db->sql_in_set('attach_id', $ids);
658 $db->sql_query($sql);
659 }
660
661 /**
662 * Handles authentication when downloading attachments from a post or topic
663 *
664 * @param \phpbb\db\driver\driver_interface $db The database object
665 * @param \phpbb\auth\auth $auth The authentication object
666 * @param int $topic_id The id of the topic that we are downloading from
667 *
668 * @return null
669 */
670 function phpbb_download_handle_forum_auth($db, $auth, $topic_id)
671 {
672 global $phpbb_container;
673
674 $sql_array = [
675 'SELECT' => 't.forum_id, t.topic_poster, t.topic_visibility, f.forum_name, f.forum_password, f.parent_id',
676 'FROM' => [
677 TOPICS_TABLE => 't',
678 FORUMS_TABLE => 'f',
679 ],
680 'WHERE' => 't.topic_id = ' . (int) $topic_id . '
681 AND t.forum_id = f.forum_id',
682 ];
683
684 $sql = $db->sql_build_query('SELECT', $sql_array);
685 $result = $db->sql_query($sql);
686 $row = $db->sql_fetchrow($result);
687 $db->sql_freeresult($result);
688
689 $phpbb_content_visibility = $phpbb_container->get('content.visibility');
690
691 if ($row && !$phpbb_content_visibility->is_visible('topic', $row['forum_id'], $row))
692 {
693 send_status_line(404, 'Not Found');
694 trigger_error('ERROR_NO_ATTACHMENT');
695 }
696 else if ($row && $auth->acl_get('u_download') && $auth->acl_get('f_download', $row['forum_id']))
697 {
698 if ($row['forum_password'])
699 {
700 // Do something else ... ?
701 login_forum_box($row);
702 }
703 }
704 else
705 {
706 send_status_line(403, 'Forbidden');
707 trigger_error('SORRY_AUTH_VIEW_ATTACH');
708 }
709 }
710
711 /**
712 * Handles authentication when downloading attachments from PMs
713 *
714 * @param \phpbb\db\driver\driver_interface $db The database object
715 * @param \phpbb\auth\auth $auth The authentication object
716 * @param int $user_id The user id
717 * @param int $msg_id The id of the PM that we are downloading from
718 *
719 * @return null
720 */
721 function phpbb_download_handle_pm_auth($db, $auth, $user_id, $msg_id)
722 {
723 global $phpbb_dispatcher;
724
725 if (!$auth->acl_get('u_pm_download'))
726 {
727 send_status_line(403, 'Forbidden');
728 trigger_error('SORRY_AUTH_VIEW_ATTACH');
729 }
730
731 $allowed = phpbb_download_check_pm_auth($db, $user_id, $msg_id);
732
733 /**
734 * Event to modify PM attachments download auth
735 *
736 * @event core.modify_pm_attach_download_auth
737 * @var bool allowed Whether the user is allowed to download from that PM or not
738 * @var int msg_id The id of the PM to download from
739 * @var int user_id The user id for auth check
740 * @since 3.1.11-RC1
741 */
742 $vars = array('allowed', 'msg_id', 'user_id');
743 extract($phpbb_dispatcher->trigger_event('core.modify_pm_attach_download_auth', compact($vars)));
744
745 if (!$allowed)
746 {
747 send_status_line(403, 'Forbidden');
748 trigger_error('ERROR_NO_ATTACHMENT');
749 }
750 }
751
752 /**
753 * Checks whether a user can download from a particular PM
754 *
755 * @param \phpbb\db\driver\driver_interface $db The database object
756 * @param int $user_id The user id
757 * @param int $msg_id The id of the PM that we are downloading from
758 *
759 * @return bool Whether the user is allowed to download from that PM or not
760 */
761 function phpbb_download_check_pm_auth($db, $user_id, $msg_id)
762 {
763 // Check if the attachment is within the users scope...
764 $sql = 'SELECT msg_id
765 FROM ' . PRIVMSGS_TO_TABLE . '
766 WHERE msg_id = ' . (int) $msg_id . '
767 AND (
768 user_id = ' . (int) $user_id . '
769 OR author_id = ' . (int) $user_id . '
770 )';
771 $result = $db->sql_query_limit($sql, 1);
772 $allowed = (bool) $db->sql_fetchfield('msg_id');
773 $db->sql_freeresult($result);
774
775 return $allowed;
776 }
777
778 /**
779 * Check if the browser is internet explorer version 7+
780 *
781 * @param string $user_agent User agent HTTP header
782 * @param int $version IE version to check against
783 *
784 * @return bool true if internet explorer version is greater than $version
785 */
786 function phpbb_is_greater_ie_version($user_agent, $version)
787 {
788 if (preg_match('/msie (\d+)/', strtolower($user_agent), $matches))
789 {
790 $ie_version = (int) $matches[1];
791 return ($ie_version > $version);
792 }
793 else
794 {
795 return false;
796 }
797 }
798
799 /**
800 * Return whether image type should be allowed to be displayed inline based on the mimetype and potentially Sec-Fetch-Dest header
801 *
802 * @param string $mimetype Image mime type
803 * @param string $sec_fetch_dest Sec-Fetch-Dest header field content
804 * @return bool
805 */
806 function phpbb_allow_serve_inline(string $mimetype, string $sec_fetch_dest): bool
807 {
808 // Allow image types that are known to be supported by all major browsers, and that are known to be safe when rendered inline
809 $allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif', 'image/bmp', 'image/tiff'];
810 if (in_array($mimetype, $allowed_types, true))
811 {
812 return true;
813 }
814
815 return strpos($mimetype, 'image') === 0 && (empty($sec_fetch_dest) || $sec_fetch_dest === 'image');
816 }
817