Skip to content

Commit

Permalink
merge master, Optimize static handler, add more tests (#5492)
Browse files Browse the repository at this point in the history
  • Loading branch information
matyhtf committed Sep 24, 2024
1 parent 3ed1b8b commit 5c801a1
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 69 deletions.
8 changes: 8 additions & 0 deletions include/swoole.h
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,14 @@ static inline unsigned int swoole_strcasect(const char *pstr, size_t plen, const
return (plen >= slen) && (strncasecmp(pstr, sstr, slen) == 0);
}

static inline unsigned int swoole_str_starts_with(const char *pstr, size_t plen, const char *sstr, size_t slen) {
return (plen >= slen) && (strncmp(pstr, sstr, slen) == 0);
}

static inline unsigned int swoole_str_istarts_with(const char *pstr, size_t plen, const char *sstr, size_t slen) {
return (plen >= slen) && (strncasecmp(pstr, sstr, slen) == 0);
}

static inline const char *swoole_strnstr(const char *haystack,
uint32_t haystack_length,
const char *needle,
Expand Down
53 changes: 42 additions & 11 deletions include/swoole_static_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ class StaticHandler {
bool get_dir_files();
bool set_filename(const std::string &filename);

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

bool has_index_file() {
return !index_file.empty();
}
Expand All @@ -77,7 +86,7 @@ class StaticHandler {

std::string get_date();

inline time_t get_file_mtime() {
time_t get_file_mtime() {
#ifdef __MACH__
return file_stat.st_mtimespec.tv_sec;
#else
Expand All @@ -87,19 +96,19 @@ class StaticHandler {

std::string get_date_last_modified();

inline const char *get_filename() {
const char *get_filename() {
return filename;
}

inline const char *get_boundary() {
const char *get_boundary() {
if (boundary.empty()) {
boundary = std::string(SW_HTTP_SERVER_BOUNDARY_PREKEY);
swoole_random_string(boundary, SW_HTTP_SERVER_BOUNDARY_TOTAL_SIZE - sizeof(SW_HTTP_SERVER_BOUNDARY_PREKEY));
}
return boundary.c_str();
}

inline const char *get_content_type() {
const char *get_content_type() {
if (tasks.size() > 1) {
content_type = std::string("multipart/byteranges; boundary=") + get_boundary();
return content_type.c_str();
Expand All @@ -108,31 +117,53 @@ class StaticHandler {
}
}

inline const char *get_mimetype() {
const char *get_mimetype() {
return swoole::mime_type::get(get_filename()).c_str();
}

inline std::string get_filename_std_string() {
std::string get_filename_std_string() {
return std::string(filename, l_filename);
}

inline size_t get_filesize() {
bool get_absolute_path();

size_t get_filesize() {
return file_stat.st_size;
}

inline const std::vector<task_t> &get_tasks() {
const std::vector<task_t> &get_tasks() {
return tasks;
}

inline bool is_dir() {
bool is_dir() {
return S_ISDIR(file_stat.st_mode);
}

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

bool is_file() {
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() {
return content_length;
}

inline const char *get_end_part() {
const char *get_end_part() {
return end_part.c_str();
}

Expand Down
102 changes: 45 additions & 57 deletions src/server/static_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ bool StaticHandler::is_modified(const std::string &date_if_modified_since) {
} else if (strptime(date_tmp, SW_HTTP_ASCTIME_DATE, &tm3) != nullptr) {
date_format = SW_HTTP_ASCTIME_DATE;
}
return date_format && mktime(&tm3) - (int) serv->timezone_ >= get_file_mtime();
return date_format && mktime(&tm3) - (time_t) serv->timezone_ >= get_file_mtime();
}

bool StaticHandler::is_modified_range(const std::string &date_range) {
Expand Down 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;
}
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,13 +112,14 @@ 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++) {
if (swoole_strcasect(url, url_length, i->c_str(), i->size())) {
if (swoole_str_istarts_with(url, url_length, i->c_str(), i->size())) {
last = true;
}
}
Expand All @@ -117,8 +128,8 @@ bool StaticHandler::hit() {
}
}

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

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

if (swoole_strnpos(filename, n, SW_STRL("..")) == -1) {
goto _detect_mime_type;
}

char real_path[PATH_MAX];
if (!realpath(filename, real_path)) {
if (last) {
status_code = SW_HTTP_NOT_FOUND;
return true;
} else {
return false;
}
}

if (real_path[document_root.length()] != '/') {
return false;
}

if (swoole_streq(real_path, strlen(real_path), document_root.c_str(), document_root.length()) != 0) {
return false;
}

// non-static file
_detect_mime_type:
// file does not exist
check_stat:
// The file does not exist
if (lstat(filename, &file_stat) < 0) {
if (last) {
status_code = SW_HTTP_NOT_FOUND;
return true;
} else {
return false;
}
return catch_error();
}

if (S_ISLNK(file_stat.st_mode)) {
char buf[PATH_MAX];
ssize_t byte = ::readlink(filename, buf, sizeof(buf) - 1);
if (byte <= 0) {
return false;
// 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();
}
if (lstat(filename, &file_stat) < 0) {
return catch_error();
}
}
} 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 All @@ -186,11 +174,11 @@ bool StaticHandler::hit() {
return true;
}

if (!swoole::mime_type::exists(filename) && !last) {
if (!mime_type::exists(filename) && !last) {
return false;
}

if (!S_ISREG(file_stat.st_mode)) {
if (!is_file()) {
return false;
}

Expand Down Expand Up @@ -271,23 +259,23 @@ bool StaticHandler::get_dir_files() {
return true;
}

bool StaticHandler::set_filename(const std::string &filename) {
char *p = this->filename + l_filename;
bool StaticHandler::set_filename(const std::string &_filename) {
char *p = filename + l_filename;

if (*p != '/') {
*p = '/';
p += 1;
}

memcpy(p, filename.c_str(), filename.length());
p += filename.length();
memcpy(p, _filename.c_str(), _filename.length());
p += _filename.length();
*p = 0;

if (lstat(this->filename, &file_stat) < 0) {
if (lstat(filename, &file_stat) < 0) {
return false;
}

if (!S_ISREG(file_stat.st_mode)) {
if (!is_file()) {
return false;
}

Expand All @@ -301,7 +289,7 @@ void StaticHandler::parse_range(const char *range, const char *if_range) {
if (range && '\0' != *range) {
const char *p = range;
// bytes=
if (!SW_STRCASECT(p, strlen(range), "bytes=")) {
if (!SW_STR_ISTARTS_WITH(p, strlen(range), "bytes=")) {
_task.offset = 0;
_task.length = content_length = get_filesize();
tasks.push_back(_task);
Expand All @@ -327,7 +315,7 @@ void StaticHandler::parse_range(const char *range, const char *if_range) {
}

while (*p >= '0' && *p <= '9') {
if (start >= cutoff && (start > cutoff || (size_t)(*p - '0') > cutlim)) {
if (start >= cutoff && (start > cutoff || (size_t) (*p - '0') > cutlim)) {
status_code = SW_HTTP_RANGE_NOT_SATISFIABLE;
return;
}
Expand Down Expand Up @@ -364,7 +352,7 @@ void StaticHandler::parse_range(const char *range, const char *if_range) {
}

while (*p >= '0' && *p <= '9') {
if (end >= cutoff && (end > cutoff || (size_t)(*p - '0') > cutlim)) {
if (end >= cutoff && (end > cutoff || (size_t) (*p - '0') > cutlim)) {
status_code = SW_HTTP_RANGE_NOT_SATISFIABLE;
return;
}
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
Loading

0 comments on commit 5c801a1

Please sign in to comment.