Skip to content

Commit

Permalink
fix
Browse files Browse the repository at this point in the history
  • Loading branch information
matyhtf committed Sep 23, 2024
1 parent 8b01474 commit f320540
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 33 deletions.
22 changes: 18 additions & 4 deletions include/swoole_static_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ class StaticHandler {
bool get_dir_files();
bool set_filename(const std::string &filename);

bool catch_error(int code) {
bool catch_error() {
if (last) {
status_code = code;
status_code = SW_HTTP_NOT_FOUND;
return true;
} else {
return false;

Check warning on line 75 in include/swoole_static_handler.h

View check run for this annotation

Codecov / codecov/patch

include/swoole_static_handler.h#L75

Added line #L75 was not covered by tests
Expand Down Expand Up @@ -125,6 +125,8 @@ class StaticHandler {
return std::string(filename, l_filename);
}

bool get_absolute_path();

size_t get_filesize() {
return file_stat.st_size;
}
Expand All @@ -138,11 +140,23 @@ class StaticHandler {
}

bool is_link() {
return S_ISLNK(file_stat.st_mode);
return S_ISLNK(file_stat.st_mode);
}

bool is_file() {
return S_ISREG(file_stat.st_mode);
return S_ISREG(file_stat.st_mode);
}

bool is_absolute_path() {
return swoole_strnpos(filename, l_filename, SW_STRL("..")) == -1;
}

bool is_located_in_document_root() {
const std::string &document_root = serv->get_document_root();
const size_t l_document_root = document_root.length();

return l_filename > l_document_root && filename[l_document_root] == '/' &&
swoole_str_starts_with(filename, l_filename, document_root.c_str(), l_document_root);
}

size_t get_content_length() {
Expand Down
60 changes: 32 additions & 28 deletions src/server/static_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ std::string StaticHandler::get_date_last_modified() {
return std::string(date_last_modified);
}

bool StaticHandler::get_absolute_path() {
char abs_path[PATH_MAX];
if (!realpath(filename, abs_path)) {
return false;

Check warning on line 93 in src/server/static_handler.cc

View check run for this annotation

Codecov / codecov/patch

src/server/static_handler.cc#L93

Added line #L93 was not covered by tests
}
strncpy(filename, abs_path, sizeof(abs_path));
l_filename = strlen(filename);
return true;
}

bool StaticHandler::hit() {
char *p = filename;
const char *url = request_url.c_str();
Expand All @@ -102,9 +112,10 @@ bool StaticHandler::hit() {
size_t n = params ? params - url : url_length;

const std::string &document_root = serv->get_document_root();
const size_t l_document_root = document_root.length();

memcpy(p, document_root.c_str(), document_root.length());
p += document_root.length();
memcpy(p, document_root.c_str(), l_document_root);
p += l_document_root;

if (serv->locations->size() > 0) {
for (auto i = serv->locations->begin(); i != serv->locations->end(); i++) {
Expand All @@ -117,8 +128,8 @@ bool StaticHandler::hit() {
}
}

if (document_root.length() + n >= PATH_MAX) {
return catch_error(SW_HTTP_FORBIDDEN);
if (l_document_root + n >= PATH_MAX) {
return catch_error();

Check warning on line 132 in src/server/static_handler.cc

View check run for this annotation

Codecov / codecov/patch

src/server/static_handler.cc#L132

Added line #L132 was not covered by tests
}

memcpy(p, url, n);
Expand All @@ -132,34 +143,27 @@ bool StaticHandler::hit() {
l_filename = http_server::url_decode(filename, p - filename);
filename[l_filename] = '\0';

if (swoole_strnpos(filename, l_filename, SW_STRL("..")) != -1) {
char abs_path[PATH_MAX];
if (!realpath(filename, abs_path)) {
return catch_error(SW_HTTP_NOT_FOUND);
}
strncpy(filename, abs_path, sizeof(abs_path));
l_filename = strlen(filename);
}

if (!swoole_str_starts_with(filename, l_filename, document_root.c_str(), document_root.length())) {
return catch_error(SW_HTTP_FORBIDDEN);
}

check_stat:
// file does not exist
// The file does not exist
if (lstat(filename, &file_stat) < 0) {
return catch_error(SW_HTTP_NOT_FOUND);
return catch_error();
}

if (is_link()) {
char buf[PATH_MAX];
ssize_t byte = ::readlink(filename, buf, sizeof(buf) - 1);
if (byte <= 0) {
return catch_error(SW_HTTP_NOT_FOUND);
// The filename is relative path, allows for the resolution of symbolic links.
// This path is formed by concatenating the document root and that is permitted for access.
if (is_absolute_path()) {
if (is_link()) {
// Use the realpath function to resolve a symbolic link to its actual path.
if (!get_absolute_path()) {
return catch_error();

Check warning on line 157 in src/server/static_handler.cc

View check run for this annotation

Codecov / codecov/patch

src/server/static_handler.cc#L157

Added line #L157 was not covered by tests
}
if (lstat(filename, &file_stat) < 0) {
return catch_error();

Check warning on line 160 in src/server/static_handler.cc

View check run for this annotation

Codecov / codecov/patch

src/server/static_handler.cc#L160

Added line #L160 was not covered by tests
}
}
} else {
if (!get_absolute_path() || !is_located_in_document_root()) {
return catch_error();
}
buf[byte] = 0;
swoole_strlcpy(filename, buf, sizeof(filename));
goto check_stat;
}

if (serv->http_index_files && !serv->http_index_files->empty() && is_dir()) {
Expand Down
59 changes: 59 additions & 0 deletions tests/swoole_http_server/static_handler/read_link_2.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
--TEST--
swoole_http_server/static_handler: link to a file outside the document root
--SKIPIF--
<?php
require __DIR__ . '/../../include/skipif.inc'; ?>
--FILE--
<?php
require __DIR__ . '/../../include/bootstrap.php';

use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\Http\Server;

$doc_root = __DIR__ . '/docroot';
$image_dir = 'image/';
mkdir($doc_root);
mkdir($doc_root . '/' . $image_dir);
$image_link = $doc_root . '/' . $image_dir . '/image.jpg';
symlink(TEST_IMAGE, $image_link);

$cleanup_fn = function () use ($doc_root, $image_dir, $image_link) {
if (is_file($image_link)) {
unlink($image_link);
}
rmdir($doc_root . '/' . $image_dir);
rmdir($doc_root);
};

$pm = new ProcessManager;
$pm->parentFunc = function () use ($pm, $doc_root, $image_dir, $image_link) {
Swoole\Coroutine\run(function () use ($pm, $doc_root, $image_dir, $image_link) {
$data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/{$image_dir}/image.jpg");
Assert::assert(md5($data) === md5_file(TEST_IMAGE));
});
$pm->kill();
echo "DONE\n";
};
$pm->childFunc = function () use ($pm, $doc_root, $image_dir, $image_link) {
$http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE);
$http->set([
'log_file' => '/dev/null',
'open_http2_protocol' => true,
'enable_static_handler' => true,
'document_root' => $doc_root,
'static_handler_locations' => ['/image']
]);
$http->on('workerStart', function () use ($pm) {
$pm->wakeup();
});
$http->on('request', function (Request $request, Response $response) {
});
$http->start();
};
$pm->childFirst();
$pm->run();
$cleanup_fn();
?>
--EXPECT--
DONE
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ $pm->childFunc = function () use ($pm) {
'log_file' => '/dev/null',
'open_http2_protocol' => true,
'enable_static_handler' => true,
'document_root' => dirname(dirname(dirname(__DIR__))) . '/',
'document_root' => dirname(__DIR__, 3) . '/',
'static_handler_locations' => ['/examples']
]);
$http->on('workerStart', function () use ($pm) {
Expand Down
60 changes: 60 additions & 0 deletions tests/swoole_http_server/static_handler/relative_path_3.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
--TEST--
swoole_http_server/static_handler: doc root with same prefix
--SKIPIF--
<?php
require __DIR__ . '/../../include/skipif.inc'; ?>
--FILE--
<?php
require __DIR__ . '/../../include/bootstrap.php';

use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\Http\Server;

$doc1_root = __DIR__ . '/docroot';
$doc2_root = __DIR__ . '/docroot2';
mkdir($doc1_root);
mkdir($doc2_root);
file_put_contents($doc1_root . '/image.jpg', file_get_contents(TEST_IMAGE));
file_put_contents($doc2_root . '/uuid.txt', uniqid());

$cleanup_fn = function () use ($doc1_root, $doc2_root) {
unlink($doc1_root . '/image.jpg');
unlink($doc2_root . '/uuid.txt');
rmdir($doc1_root);
rmdir($doc2_root);
};

$pm = new ProcessManager;
$pm->parentFunc = function () use ($pm, $doc1_root, $doc2_root) {
Swoole\Coroutine\run(function () use ($pm, $doc1_root, $doc2_root) {
$data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/../docroot/image.jpg");
Assert::assert(md5($data) === md5_file(TEST_IMAGE));

$data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/../docroot2/uuid.txt");
Assert::isEmpty($data);
});
$pm->kill();
echo "DONE\n";
};
$pm->childFunc = function () use ($pm, $doc1_root, $doc2_root) {
$http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE);
$http->set([
'log_file' => '/dev/null',
'open_http2_protocol' => true,
'enable_static_handler' => true,
'document_root' => $doc1_root,
]);
$http->on('workerStart', function () use ($pm) {
$pm->wakeup();
});
$http->on('request', function (Request $request, Response $response) {
});
$http->start();
};
$pm->childFirst();
$pm->run();
$cleanup_fn();
?>
--EXPECT--
DONE

0 comments on commit f320540

Please sign in to comment.