mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-26 12:24:46 +00:00
Refuse a ':' in a path a mount point serves on Windows (#2643)
On NTFS a ':' in a path names a stream of the file, so /dir/test.html::$DATA opened the default stream of dir/test.html and passed a set_pre_routing_handler() guard on /dir/test.html, and /setup.exe:Zone.Identifier served the stream Windows attaches to downloaded files. is_valid_path() now refuses a ':' on Windows. Elsewhere a ':' is an ordinary file name character, so it is still served: a file such as 2024-01-01T10:00:00.log would otherwise answer 404. The test pins both sides.
This commit is contained in:
1 parent
2f8b9f6f57
commit
8710fbd25d
3 files changed
+22
-5
No files matched your search
@@ -409,7 +409,7 @@ ret = svr.remove_mount_point("/");
|
|||||||
ret = svr.remove_mount_point("/public");
|
ret = svr.remove_mount_point("/public");
|
||||||
```
|
```
|
||||||
|
|
||||||
A mount point serves a path only when it has no `.`, `..` or empty segment other than a trailing `/`, and no segment ending in `.` or a space, so a check on `req.path` in `set_pre_routing_handler()` sees the path of the file that is served. A path ending in `/`, and the mount point itself such as `/public`, serve the `index.html` in that directory. The file system can still give a file other names: another case on a case-insensitive file system, such as the default ones on macOS and Windows, and on Windows an 8.3 short name such as `PRIVAT~1`. Compare paths without regard to case on such a file system, and on Windows keep files that need a guard out of mounted directories.
|
A mount point serves a path only when it has no `.`, `..` or empty segment other than a trailing `/`, no segment ending in `.` or a space and, on Windows, no `:` (it names an NTFS stream), so a check on `req.path` in `set_pre_routing_handler()` sees the path of the file that is served. A path ending in `/`, and the mount point itself such as `/public`, serve the `index.html` in that directory. The file system can still give a file other names: another case on a case-insensitive file system, such as the default ones on macOS and Windows, and on Windows an 8.3 short name such as `PRIVAT~1`. Compare paths without regard to case on such a file system, and on Windows keep files that need a guard out of mounted directories.
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// User defined file extension and MIME type mappings
|
// User defined file extension and MIME type mappings
|
||||||
|
|||||||
@@ -6007,9 +6007,10 @@ inline bool is_valid_close_payload(const std::string &payload) {
|
|||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
// A path a mount point serves: absolute, with no empty segment except a
|
// A path a mount point serves: absolute, with no empty segment except a
|
||||||
// trailing '/' and no segment ending in '.' or a space (which covers "." and
|
// trailing '/', no segment ending in '.' or a space (which covers "." and
|
||||||
// ".." and names Windows trims into another file's name), so the file served
|
// ".." and names Windows trims into another file's name) and, on Windows, no
|
||||||
// is the one that req.path names to the handlers that ran before it.
|
// ':' (which names an NTFS stream, so "a.txt::$DATA" is a.txt), so the file
|
||||||
|
// served is the one that req.path names to the handlers that ran before it.
|
||||||
inline bool is_valid_path(const std::string &path) {
|
inline bool is_valid_path(const std::string &path) {
|
||||||
if (path.empty() || path[0] != '/') { return false; }
|
if (path.empty() || path[0] != '/') { return false; }
|
||||||
|
|
||||||
@@ -6017,6 +6018,9 @@ inline bool is_valid_path(const std::string &path) {
|
|||||||
for (auto i = beg; i <= path.size(); i++) {
|
for (auto i = beg; i <= path.size(); i++) {
|
||||||
if (i < path.size() && path[i] != '/') {
|
if (i < path.size() && path[i] != '/') {
|
||||||
if (path[i] == '\0' || path[i] == '\\') { return false; }
|
if (path[i] == '\0' || path[i] == '\\') { return false; }
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (path[i] == ':') { return false; }
|
||||||
|
#endif
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto len = i - beg;
|
auto len = i - beg;
|
||||||
|
|||||||
+14
-1
@@ -707,6 +707,18 @@ TEST(IsValidPathTest, RejectsSegmentsEndingInDotOrSpace) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ':' names an NTFS stream on Windows and is an ordinary character in a file
|
||||||
|
// name anywhere else.
|
||||||
|
TEST(IsValidPathTest, ColonIsRejectedOnlyOnWindows) {
|
||||||
|
for (auto path : {"/a.txt::$DATA", "/a.txt:Zone.Identifier", "/dir:x/a"}) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
EXPECT_FALSE(detail::is_valid_path(path)) << path;
|
||||||
|
#else
|
||||||
|
EXPECT_TRUE(detail::is_valid_path(path)) << path;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST(CanCompressContentTypeTest, ComparesWholeTypes) {
|
TEST(CanCompressContentTypeTest, ComparesWholeTypes) {
|
||||||
EXPECT_TRUE(detail::can_compress_content_type("application/json"));
|
EXPECT_TRUE(detail::can_compress_content_type("application/json"));
|
||||||
EXPECT_TRUE(detail::can_compress_content_type("text/plain; charset=utf-8"));
|
EXPECT_TRUE(detail::can_compress_content_type("text/plain; charset=utf-8"));
|
||||||
@@ -12760,7 +12772,8 @@ TEST(MountTest, AliasedPathDoesNotPassPreRoutingGuard) {
|
|||||||
for (auto path :
|
for (auto path :
|
||||||
{"/./dir/test.html", "/dir/./test.html", "//dir/test.html",
|
{"/./dir/test.html", "/dir/./test.html", "//dir/test.html",
|
||||||
"/dir//test.html", "/dir/../dir/test.html", "/%2e/dir/test.html",
|
"/dir//test.html", "/dir/../dir/test.html", "/%2e/dir/test.html",
|
||||||
"/dir/test.html.", "/dir/test.html%20"}) {
|
"/dir/test.html.", "/dir/test.html%20", "/dir/test.html::$DATA",
|
||||||
|
"/dir/test.html%3A%3A$DATA"}) {
|
||||||
SCOPED_TRACE(path);
|
SCOPED_TRACE(path);
|
||||||
auto res = cli.Get(path);
|
auto res = cli.Get(path);
|
||||||
ASSERT_TRUE(res) << "Error: " << to_string(res.error());
|
ASSERT_TRUE(res) << "Error: " << to_string(res.error());
|
||||||
|
|||||||
Reference in new issue
Block a user