Skip to content

Commit ef612f7

Browse files
committed
misc. for web serve
1 parent 88b8e96 commit ef612f7

File tree

1 file changed

+96
-14
lines changed

1 file changed

+96
-14
lines changed

src/Web.php

Lines changed: 96 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use ArrayAccess\TrayDigita\Kernel\Decorator;
1010
use ArrayAccess\TrayDigita\Kernel\Interfaces\KernelInterface;
1111
use ArrayAccess\TrayDigita\Util\Filter\ContainerHelper;
12+
use ArrayAccess\TrayDigita\Util\Filter\MimeType;
1213
use Psr\Http\Message\ServerRequestFactoryInterface;
1314
use Psr\Http\Message\StreamFactoryInterface;
1415
use RuntimeException;
@@ -42,11 +43,65 @@
4243

4344
final class Web
4445
{
46+
/**
47+
* @var string[] $blackListedExtensions List of blacklisted extensions
48+
*/
49+
protected static array $blackListedExtensions = [
50+
'php',
51+
'cgi',
52+
'pl',
53+
'py',
54+
'rb',
55+
'sh',
56+
'jsp',
57+
'asp',
58+
'aspx',
59+
'cfm',
60+
];
61+
4562
// no construct
4663
final private function __construct()
4764
{
4865
}
4966

67+
/**
68+
* Get the blacklisted extensions
69+
*
70+
* @return string[]
71+
*/
72+
public static function getBlackListedExtensions(): array
73+
{
74+
return self::$blackListedExtensions;
75+
}
76+
77+
/**
78+
* Check if the extension is blacklisted
79+
*
80+
* @param string $extension
81+
* @return bool
82+
*/
83+
public static function isExtensionBlackListed(string $extension): bool
84+
{
85+
return in_array(strtolower($extension), self::getBlackListedExtensions(), true);
86+
}
87+
88+
/**
89+
* Set the blacklisted extensions
90+
*
91+
* @param array<string> $blackListedExtensions
92+
*/
93+
public static function setBlackListedExtensions(array $blackListedExtensions): void
94+
{
95+
$blackListedExtensions = array_filter($blackListedExtensions, 'is_string');
96+
$blackListedExtensions = array_map('strtolower', $blackListedExtensions);
97+
$blackListedExtensions = array_values(array_filter(array_unique($blackListedExtensions)));
98+
if (!in_array('php', $blackListedExtensions, true)) {
99+
// always disallow php
100+
$blackListedExtensions[] = 'php';
101+
}
102+
self::$blackListedExtensions = $blackListedExtensions;
103+
}
104+
50105
/**
51106
* @return \Psr\Http\Message\ResponseInterface|false
52107
* @noinspection PhpMissingReturnTypeInspection
@@ -125,26 +180,53 @@ final public static function serve()
125180

126181
$requestUri = $_SERVER['REQUEST_URI'];
127182
$requestUriNoQuery = explode('?', $requestUri, 2)[0];
183+
$originalScriptFileName = $_SERVER['SCRIPT_FILENAME'];
184+
$originalScriptName = $_SERVER['SCRIPT_NAME']??$_SERVER['PHP_SELF']??null;
185+
$isScriptFileNameMatch = $originalScriptFileName === __FILE__;
186+
$baseName = basename($requestUriNoQuery);
187+
$extension = str_contains('.', $baseName)
188+
? pathinfo($baseName, PATHINFO_EXTENSION)
189+
: null;
190+
$extension = $extension && preg_match('~^[a-zA-Z]+$~', $extension)
191+
? strtolower($extension)
192+
: null;
128193

129194
/**
130195
* If extension matches & exists return false
131196
* that means the builtin web server will serve static assets
132197
*/
133198
// check mimetypes
134-
if (preg_match(
135-
'~\.(?:
136-
ics|ico|je|jpe?g|avif|heic|png|gif|webp|svg|tiff?|bmp # image
137-
|css|jsx?|x?html?|xml|xsl|xsd|ja?son # web assets
138-
|te?xt|docx?|pptx?|xlsx?|csv|pdf|swf|pps|txt # document
139-
|mp[34]|og[gvpa]|mpe?g|3gp|avi|mov|flac|flv|webm|wmv # media
140-
)$~ix',
141-
$requestUriNoQuery
142-
) && is_file($publicDirectory . '/' . $requestUriNoQuery)) {
199+
if ($extension && self::isExtensionBlackListed($extension)
200+
&& is_file($publicDirectory . '/' . $requestUriNoQuery)
201+
&& is_readable($publicDirectory . '/' . $requestUriNoQuery)
202+
) {
203+
$realPath = realpath($publicDirectory . '/' . $requestUriNoQuery);
204+
$realPathOriginal = $originalScriptName
205+
? realpath($publicDirectory . '/' . $originalScriptName)
206+
: null;
207+
if (!headers_sent()) {
208+
$mimeType = MimeType::extension($extension) ?: 'application/octet-stream';
209+
header('Content-Type: ' . $mimeType);
210+
}
211+
// if the script file name matches the original script file name
212+
if (!$isScriptFileNameMatch
213+
&& $realPath
214+
&& (
215+
$realPath === $originalScriptFileName
216+
|| (
217+
// resolve the real path of the original script name
218+
$originalScriptName && $realPath === $realPathOriginal
219+
)
220+
)
221+
) {
222+
readfile($realPath);
223+
}
224+
143225
// serve the static assets with return : false
144226
return $lastResult = false;
145227
}
146228

147-
$_SERVER['PHP_SELF'] = '/' . basename(__FILE__);
229+
$_SERVER['PHP_SELF'] = DIRECTORY_SEPARATOR. basename(__FILE__);
148230
$_SERVER['SCRIPT_NAME'] = $_SERVER['PHP_SELF'];
149231
}
150232

@@ -235,16 +317,16 @@ class_alias('Exception', 'Throwable');
235317
$config = ContainerHelper::use(Config::class, $kernel->getHttpKernel()->getContainer());
236318
$config = $config->get('environment');
237319
$enable = $config instanceof Config && (
238-
$config->get('displayErrorDetails') === true
239-
|| $config->get('debug') === true
240-
);
320+
$config->get('displayErrorDetails') === true
321+
|| $config->get('debug') === true
322+
);
241323
}
242324
$additionalText = ! $enable ? "<p><code>$message</code></p>" : <<<HTML
243325
<p><code>$message</code></p>
244326
<p><code>$file:($line)</code></p>
245327
<pre>$trace</pre>
246328
HTML;
247-
echo <<<HTML
329+
echo <<<HTML
248330
<!DOCTYPE html>
249331
<html lang="en">
250332
<head>

0 commit comments

Comments
 (0)