11 $modul = (string)(
$_GET[
'dbx_modul'] ??
'');
12 $run1 = (string)(
$_GET[
'dbx_run1'] ??
'');
13 $mid = isset(
$_GET[
'dbx_mid']) ? (int)
$_GET[
'dbx_mid'] : 0;
14 $thumb = isset(
$_GET[
'dbx_thumb']) && (string)
$_GET[
'dbx_thumb'] ===
'1';
15 if ($modul !==
'dbxContent' || $run1 !==
'media' || $mid <= 0)
return;
18 $db =
dbx()->get_system_obj(
'dbxDB');
20 ?
$db->select1(
'dbxMedia', array(
'id' => $mid,
'active' => 1),
'*', 0)
22 if (!is_array($row) || (
int)($row[
'id'] ?? 0) <= 0) {
23 $this->fail(404, $mid);
26 $relKey = $thumb && !empty($row[
'thumb_file_path']) ?
'thumb_file_path' :
'file_path';
27 $rel = $this->safe_relative_path((
string)($row[$relKey] ??
''));
29 http_response_code(403);
35 $real = $this->readable_file(
$base,
$root, $rel);
36 $servedThumb = $thumb && $relKey ===
'thumb_file_path';
38 if ($real ===
null && $servedThumb && !empty($row[
'file_path'])) {
39 $fallbackRel = $this->safe_relative_path((
string)$row[
'file_path']);
40 if ($fallbackRel !==
null) {
41 $fallbackReal = $this->readable_file(
$base,
$root, $fallbackRel);
42 if ($fallbackReal !==
null) {
43 $real = $fallbackReal;
51 $this->fail(404, $mid, $rel);
54 $mime = $servedThumb ?
'' : trim((
string)($row[
'mime'] ??
''));
56 $detected = function_exists(
'mime_content_type') ? mime_content_type($real) :
'';
57 $mime = $detected ?:
'application/octet-stream';
60 $fileName = trim((
string)($row[
'file_name'] ??
''));
61 if ($fileName ===
'') $fileName = basename($real);
62 $this->stream_file($real, $mime, $fileName);
63 }
catch (\Throwable $e) {
64 dbx()->write_php_error_log(get_class($e), $e->getMessage(), $e->getFile(), $e->getLine());
65 http_response_code(500);
70 private function missing_key(
int $mid,
string $rel =
''): string {
71 if (trim($rel) !==
'') return ltrim(str_replace(
'\\',
'/', $rel),
'/');
72 $uri = trim((
string)(
$_SERVER[
'REQUEST_URI'] ??
''));
73 return $uri !==
'' ? $uri :
'index.php?dbx_modul=dbxContent&dbx_run1=media&dbx_mid=' . $mid;
76 private function fail(
int $code,
int $mid,
string $rel =
''): void {
78 http_response_code($code);
82 private function safe_relative_path(
string $rel): ?string {
83 $rel = ltrim(str_replace(
'\\',
'/', rawurldecode($rel)),
'/');
84 if ($rel ===
'' || strpos($rel,
'..') !==
false || !preg_match(
'~^(media|dbxContent/media)/~i', $rel)) {
90 private function readable_file(
$base,
string $root,
string $rel): ?string {
93 if (!$real || !is_file($real) || !is_readable($real))
return null;
95 $basePath = rtrim(str_replace(
'\\',
'/', (
string)
$base),
'/') .
'/';
96 $realPath = str_replace(
'\\',
'/', $real);
97 if (!str_starts_with($realPath, $basePath))
return null;
101 private function stream_file(
string $file,
string $mime,
string $fileName): void {
102 while (ob_get_level() > 0) {
103 if (!@ob_end_clean())
break;
105 if (session_status() === PHP_SESSION_ACTIVE) session_write_close();
107 $size = (int)filesize(
$file);
108 $mtime = (int)(filemtime(
$file) ?: time());
109 $etag =
'"' . md5(
$file .
'|' . $size .
'|' . $mtime) .
'"';
110 $lastModified = gmdate(
'D, d M Y H:i:s', $mtime) .
' GMT';
111 $ifNoneMatch = trim((
string)(
$_SERVER[
'HTTP_IF_NONE_MATCH'] ??
''));
112 $ifModifiedSince = trim((
string)(
$_SERVER[
'HTTP_IF_MODIFIED_SINCE'] ??
''));
113 if ($ifNoneMatch === $etag || ($ifModifiedSince !==
'' && strtotime($ifModifiedSince) >= $mtime)) {
114 http_response_code(304);
115 header(
'ETag: ' . $etag);
116 header(
'Last-Modified: ' . $lastModified);
117 header(
'Cache-Control: private, no-cache');
122 $end = $size > 0 ? $size - 1 : 0;
123 $range = trim((
string)(
$_SERVER[
'HTTP_RANGE'] ??
''));
124 if ($range !==
'' && preg_match(
'/bytes=(\d*)-(\d*)/i', $range, $match)) {
125 if ($match[1] !==
'') $start = max(0, (
int)$match[1]);
126 if ($match[2] !==
'') $end = min($end, (
int)$match[2]);
127 if ($match[1] ===
'' && $match[2] !==
'') {
128 $start = max(0, $size - max(0, (
int)$match[2]));
130 if ($start <= $end && $size > 0) {
131 http_response_code(206);
132 header(
'Content-Range: bytes ' . $start .
'-' . $end .
'/' . $size);
134 http_response_code(416);
135 header(
'Content-Range: bytes */' . $size);
140 $fileName = str_replace(
'"',
'', basename($fileName));
141 header(
'Content-Type: ' . $mime);
142 header(
'Content-Length: ' . max(0, $end - $start + 1));
143 header(
'Content-Disposition: inline; filename="' . $fileName .
'"');
144 header(
'Accept-Ranges: bytes');
145 header(
'ETag: ' . $etag);
146 header(
'Last-Modified: ' . $lastModified);
147 header(
'Cache-Control: private, no-cache');
148 header(
'X-Content-Type-Options: nosniff');
150 if (strtoupper((
string)(
$_SERVER[
'REQUEST_METHOD'] ??
'GET')) ===
'HEAD') {
156 if ($start > 0) fseek(
$out, $start);
157 $remaining = $end - $start + 1;
158 while ($remaining > 0 && !feof(
$out)) {
159 $chunk = fread(
$out, min(8192, $remaining));
160 if ($chunk ===
false || $chunk ===
'')
break;
162 $remaining -= strlen($chunk);
163 if (function_exists(
'connection_aborted') && connection_aborted())
break;