Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ ret = svr.remove_mount_point("/");
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
// User defined file extension and MIME type mappings
Expand Down
10 changes: 7 additions & 3 deletions httplib.h
Original file line number Diff line number Diff line change
Expand Up @@ -6007,16 +6007,20 @@ inline bool is_valid_close_payload(const std::string &payload) {
namespace detail {

// 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
// ".." and names Windows trims into another file's name), so the file served
// is the one that req.path names to the handlers that ran before it.
// trailing '/', no segment ending in '.' or a space (which covers "." and
// ".." and names Windows trims into another file's name) and, on Windows, no
// ':' (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) {
if (path.empty() || path[0] != '/') { return false; }

auto beg = size_t{1};
for (auto i = beg; i <= path.size(); i++) {
if (i < path.size() && path[i] != '/') {
if (path[i] == '\0' || path[i] == '\\') { return false; }
#ifdef _WIN32
if (path[i] == ':') { return false; }
#endif
continue;
}
auto len = i - beg;
Expand Down
15 changes: 14 additions & 1 deletion test/test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
EXPECT_TRUE(detail::can_compress_content_type("application/json"));
EXPECT_TRUE(detail::can_compress_content_type("text/plain; charset=utf-8"));
Expand Down Expand Up @@ -12760,7 +12772,8 @@ TEST(MountTest, AliasedPathDoesNotPassPreRoutingGuard) {
for (auto path :
{"/./dir/test.html", "/dir/./test.html", "//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);
auto res = cli.Get(path);
ASSERT_TRUE(res) << "Error: " << to_string(res.error());
Expand Down
Loading