Skip to content

Commit a4910b6

Browse files
author
Max Eckel
committed
Allow for swoole to serve static files trough symlink
1 parent c306e59 commit a4910b6

File tree

4 files changed

+82
-3
lines changed

4 files changed

+82
-3
lines changed

src/Swoole/SwooleClient.php

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,67 @@ public function canServeRequestAsStaticFile(Request $request, RequestContext $co
5656

5757
$publicPath = $context->publicPath;
5858

59+
$realpath = realpath($publicPath.'/'.$request->path());
60+
61+
if($this->checkSymlinkInPath($publicPath, $realpath, $request->path())) {
62+
$realpath = $publicPath.'/'.$request->path();
63+
}
64+
5965
return $this->fileIsServable(
6066
$publicPath,
61-
realpath($publicPath.'/'.$request->path()),
67+
$realpath,
6268
);
6369
}
6470

6571
/**
66-
* Determine if the given file is servable.
72+
* Checks whether the request path contains a Symlink.
73+
* When a Symlink is found, it is checked against the the resolved real path,
74+
* in order to protect against directory traversal.
6775
*
6876
* @param string $publicPath
69-
* @param string $pathToFile
77+
* @param string $realPath
78+
* @param string $requestPath
79+
* @return bool
80+
*/
81+
private function checkSymlinkInPath(string $publicPath, string $realPath, string $requestPath): bool
82+
{
83+
$resolvedPathIfSymlink = $this->pathContainsSymlink($publicPath, $requestPath);
84+
85+
if (! $resolvedPathIfSymlink) {
86+
return false;
87+
}
88+
89+
return str_ends_with($realPath, $resolvedPathIfSymlink);
90+
}
91+
92+
/**
93+
* Determine whether the path contains a symlink.
94+
* When a symlink is found, the path after it is returned.
95+
*
96+
* @param string $publicPath
97+
* @param string $path
98+
* @return string|bool
99+
*/
100+
private function pathContainsSymlink(string $publicPath, string $path): string|bool
101+
{
102+
$dirs = explode('/', $path);
103+
104+
while ($dir = array_shift($dirs)) {
105+
$publicPath .= '/'.$dir;
106+
107+
if (is_link($publicPath)) {
108+
return implode('/', $dirs);
109+
}
110+
}
111+
112+
return false;
113+
}
114+
115+
/**
116+
* Determine if the given file is servable.
117+
*
118+
* @param string $publicPath
119+
* @param string $pathToFile
70120
* @return bool
71121
*/
72122
protected function fileIsServable(string $publicPath, string $pathToFile): bool

tests/SwooleClientTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,34 @@ public function test_static_file_can_be_served()
116116
$client->serveStaticFile($request, $context);
117117
}
118118

119+
/** @test */
120+
public function test_can_serve_static_files_through_symlink()
121+
{
122+
$client = new SwooleClient;
123+
124+
$request = Request::create('/symlink/foo.txt', 'GET');
125+
126+
$context = new RequestContext([
127+
'publicPath' => __DIR__.'/public/files',
128+
]);
129+
130+
$this->assertTrue($client->canServeRequestAsStaticFile($request, $context));
131+
}
132+
133+
/** @test */
134+
public function test_cant_serve_static_files_through_symlink_using_directory_traversal()
135+
{
136+
$client = new SwooleClient;
137+
138+
$request = Request::create('/symlink/../files/bar.txt', 'GET');
139+
140+
$context = new RequestContext([
141+
'publicPath' => __DIR__.'/public/files',
142+
]);
143+
144+
$this->assertFalse($client->canServeRequestAsStaticFile($request, $context));
145+
}
146+
119147
/** @doesNotPerformAssertions @test */
120148
public function test_respond_method_send_response_to_swoole()
121149
{

tests/public/files/symlink

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../symlinkedFolder

tests/public/symlinkedFolder/foo.txt

Whitespace-only changes.

0 commit comments

Comments
 (0)