I'm trying to detect whether a certain path is under some blacklisted path. I've found examples in Check if an std::filesystem::path is inside a directory thread.
My example is the following (p
variables are target paths, and b
variables are blacklisted folders):
#include <iostream>
#include <vector>
#include <memory>
#include <filesystem>
int main()
{
using Path = std::filesystem::path;
Path p1 = "/folder1/file.txt";
Path b1 = "/folder1";
auto finalPath = b1 / p1;
auto [iter1, iter2] = std::mismatch(b1.begin(), b1.end(), finalPath.begin());
if( iter1 == b1.end()){
std::cout << "Yes\n"; // True
} else {
std::cout << "No\n";
}
p1 = "/folder1/file.txt";
b1 = "/folder1/";
finalPath = b1 / p1;
auto [iter3, iter4] = std::mismatch(b1.begin(), b1.end(), finalPath.begin());
if (iter3 == b1.end()) {
std::cout << "Yes\n";
} else {
std::cout << "No\n"; // False
}
return 0;
}
In this example the first comparison is true
and the second is false
. The only difference is trailing path-separator in blacklisted folder. It doesn't seem that second variant returns iterator for the first mismatched symbol. Why that?
Also one of code snippets in above-mentioned thread suggest to compare resulting first iterator to mismatched symbol with the end() - 1
iterator like so:
if (iter3 == std::prev(b1.end())) {
It may help, but cppreference says path's iterator is not necessarily a bidirectional iterator, so, if I understand it correctly, prev
may not work, and MSVC explicitly forbids that.
How to fix this code?
For brevity I assume that paths don't need to be lexically_normal
.
It's bad practice to have "/folder1/"
with a trailing component separator, since that will make an undesirable empty final component for the path.
But if that bad practice is rampant in your code, you could trim it out of base before comparing.
bool is_subpath(std::filesystem::path const& path, std::filesystem::path base) {
if (base.filename().empty()) base = base.parent_path();
auto const& [_, base_match] = std::mismatch(path.begin(), path.end(), base.begin(), base.end());
return base_match == base.end();
}
Or use Igor's approach of iterating over the path to locate the penultimate end before the empty final component. That would allow passing base as a const&
.