aboutsummaryrefslogtreecommitdiff
path: root/src/libutil/canon-path.cc
blob: f678fae94762ba289750baf29f5003b7254c6796 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#include "canon-path.hh"
#include "file-system.hh"

namespace nix {

CanonPath CanonPath::root = CanonPath("/");

CanonPath::CanonPath(std::string_view raw)
    : path(absPath((Path) raw, "/"))
{ }

CanonPath::CanonPath(std::string_view raw, const CanonPath & root)
    : path(absPath((Path) raw, root.abs()))
{ }

CanonPath CanonPath::fromCwd(std::string_view path)
{
    return CanonPath(unchecked_t(), absPath((Path) path));
}

std::optional<CanonPath> CanonPath::parent() const
{
    if (isRoot()) return std::nullopt;
    return CanonPath(unchecked_t(), path.substr(0, std::max((size_t) 1, path.rfind('/'))));
}

void CanonPath::pop()
{
    assert(!isRoot());
    path.resize(std::max((size_t) 1, path.rfind('/')));
}

bool CanonPath::isWithin(const CanonPath & parent) const
{
    return !(
        path.size() < parent.path.size()
        || path.substr(0, parent.path.size()) != parent.path
        || (parent.path.size() > 1 && path.size() > parent.path.size()
            && path[parent.path.size()] != '/'));
}

CanonPath CanonPath::removePrefix(const CanonPath & prefix) const
{
    assert(isWithin(prefix));
    if (prefix.isRoot()) return *this;
    if (path.size() == prefix.path.size()) return root;
    return CanonPath(unchecked_t(), path.substr(prefix.path.size()));
}

void CanonPath::extend(const CanonPath & x)
{
    if (x.isRoot()) return;
    if (isRoot())
        path += x.rel();
    else
        path += x.abs();
}

CanonPath CanonPath::operator + (const CanonPath & x) const
{
    auto res = *this;
    res.extend(x);
    return res;
}

void CanonPath::push(std::string_view c)
{
    assert(c.find('/') == c.npos);
    assert(c != "." && c != "..");
    if (!isRoot()) path += '/';
    path += c;
}

CanonPath CanonPath::operator + (std::string_view c) const
{
    auto res = *this;
    res.push(c);
    return res;
}

bool CanonPath::isAllowed(const std::set<CanonPath> & allowed) const
{
    /* Check if `this` is an exact match or the parent of an
       allowed path. */
    auto lb = allowed.lower_bound(*this);
    if (lb != allowed.end()) {
        if (lb->isWithin(*this))
            return true;
    }

    /* Check if a parent of `this` is allowed. */
    auto path = *this;
    while (!path.isRoot()) {
        path.pop();
        if (allowed.count(path))
            return true;
    }

    return false;
}

std::ostream & operator << (std::ostream & stream, const CanonPath & path)
{
    stream << path.abs();
    return stream;
}

std::string CanonPath::makeRelative(const CanonPath & path) const
{
    auto p1 = begin();
    auto p2 = path.begin();

    for (; p1 != end() && p2 != path.end() && *p1 == *p2; ++p1, ++p2) ;

    if (p1 == end() && p2 == path.end())
        return ".";
    else if (p1 == end())
        return std::string(p2.remaining);
    else {
        std::string res;
        while (p1 != end()) {
            ++p1;
            if (!res.empty()) res += '/';
            res += "..";
        }
        if (p2 != path.end()) {
            if (!res.empty()) res += '/';
            res += p2.remaining;
        }
        return res;
    }
}

}