1 | // |
2 | // Glob.cpp |
3 | // |
4 | // Library: Foundation |
5 | // Package: Filesystem |
6 | // Module: Glob |
7 | // |
8 | // Copyright (c) 2004-2006, Applied Informatics Software Engineering GmbH. |
9 | // and Contributors. |
10 | // |
11 | // SPDX-License-Identifier: BSL-1.0 |
12 | // |
13 | |
14 | |
15 | #include "Poco/Glob.h" |
16 | #include "Poco/Path.h" |
17 | #include "Poco/Exception.h" |
18 | #include "Poco/DirectoryIterator.h" |
19 | #include "Poco/File.h" |
20 | #include "Poco/UTF8Encoding.h" |
21 | #include "Poco/Unicode.h" |
22 | |
23 | |
24 | namespace Poco { |
25 | |
26 | |
27 | Glob::Glob(const std::string& pattern, int options) |
28 | : _pattern(pattern), _options(options) |
29 | { |
30 | } |
31 | |
32 | |
33 | Glob::~Glob() |
34 | { |
35 | } |
36 | |
37 | |
38 | bool Glob::match(const std::string& subject) |
39 | { |
40 | UTF8Encoding utf8; |
41 | TextIterator itp(_pattern, utf8); |
42 | TextIterator endp(_pattern); |
43 | TextIterator its(subject, utf8); |
44 | TextIterator ends(subject); |
45 | |
46 | if ((_options & GLOB_DOT_SPECIAL) && its != ends && *its == '.' && (*itp == '?' || *itp == '*')) |
47 | return false; |
48 | else |
49 | return match(itp, endp, its, ends); |
50 | } |
51 | |
52 | |
53 | void Glob::glob(const std::string& pathPattern, std::set<std::string>& files, int options) |
54 | { |
55 | glob(Path(Path::expand(pathPattern), Path::PATH_GUESS), files, options); |
56 | } |
57 | |
58 | |
59 | void Glob::glob(const char* pathPattern, std::set<std::string>& files, int options) |
60 | { |
61 | glob(Path(Path::expand(pathPattern), Path::PATH_GUESS), files, options); |
62 | } |
63 | |
64 | |
65 | void Glob::glob(const Path& pathPattern, std::set<std::string>& files, int options) |
66 | { |
67 | Path pattern(pathPattern); |
68 | pattern.makeDirectory(); // to simplify pattern handling later on |
69 | Path base(pattern); |
70 | Path absBase(base); |
71 | absBase.makeAbsolute(); |
72 | // In case of UNC paths we must not pop the topmost directory |
73 | // (which must not contain wildcards), otherwise collect() will fail |
74 | // as one cannot create a DirectoryIterator with only a node name ("\\srv\"). |
75 | int minDepth = base.getNode().empty() ? 0 : 1; |
76 | while (base.depth() > minDepth && base[base.depth() - 1] != ".." ) |
77 | { |
78 | base.popDirectory(); |
79 | absBase.popDirectory(); |
80 | } |
81 | if (pathPattern.isDirectory()) |
82 | options |= GLOB_DIRS_ONLY; |
83 | collect(pattern, absBase, base, pathPattern[base.depth()], files, options); |
84 | } |
85 | |
86 | |
87 | void Glob::glob(const Path& pathPattern, const Path& basePath, std::set<std::string>& files, int options) |
88 | { |
89 | Path pattern(pathPattern); |
90 | pattern.makeDirectory(); // to simplify pattern handling later on |
91 | Path absBase(basePath); |
92 | absBase.makeAbsolute(); |
93 | if (pathPattern.isDirectory()) |
94 | options |= GLOB_DIRS_ONLY; |
95 | collect(pattern, absBase, basePath, pathPattern[basePath.depth()], files, options); |
96 | } |
97 | |
98 | |
99 | bool Glob::match(TextIterator& itp, const TextIterator& endp, TextIterator& its, const TextIterator& ends) |
100 | { |
101 | while (itp != endp) |
102 | { |
103 | if (its == ends) |
104 | { |
105 | while (itp != endp && *itp == '*') ++itp; |
106 | break; |
107 | } |
108 | switch (*itp) |
109 | { |
110 | case '?': |
111 | ++itp; ++its; |
112 | break; |
113 | case '*': |
114 | if (++itp != endp) |
115 | { |
116 | while (its != ends && !matchAfterAsterisk(itp, endp, its, ends)) ++its; |
117 | return its != ends; |
118 | } |
119 | return true; |
120 | case '[': |
121 | if (++itp != endp) |
122 | { |
123 | bool invert = *itp == '!'; |
124 | if (invert) ++itp; |
125 | if (itp != endp) |
126 | { |
127 | bool mtch = matchSet(itp, endp, *its++); |
128 | if ((invert && mtch) || (!invert && !mtch)) return false; |
129 | break; |
130 | } |
131 | } |
132 | throw SyntaxException("bad range syntax in glob pattern" ); |
133 | case '\\': |
134 | if (++itp == endp) throw SyntaxException("backslash must be followed by character in glob pattern" ); |
135 | // fallthrough |
136 | default: |
137 | if (_options & GLOB_CASELESS) |
138 | { |
139 | if (Unicode::toLower(*itp) != Unicode::toLower(*its)) return false; |
140 | } |
141 | else |
142 | { |
143 | if (*itp != *its) return false; |
144 | } |
145 | ++itp; ++its; |
146 | } |
147 | } |
148 | return itp == endp && its == ends; |
149 | } |
150 | |
151 | |
152 | bool Glob::matchAfterAsterisk(TextIterator itp, const TextIterator& endp, TextIterator its, const TextIterator& ends) |
153 | { |
154 | return match(itp, endp, its, ends); |
155 | } |
156 | |
157 | |
158 | bool Glob::matchSet(TextIterator& itp, const TextIterator& endp, int c) |
159 | { |
160 | if (_options & GLOB_CASELESS) |
161 | c = Unicode::toLower(c); |
162 | |
163 | while (itp != endp) |
164 | { |
165 | switch (*itp) |
166 | { |
167 | case ']': |
168 | ++itp; |
169 | return false; |
170 | case '\\': |
171 | if (++itp == endp) throw SyntaxException("backslash must be followed by character in glob pattern" ); |
172 | } |
173 | int first = *itp; |
174 | int last = first; |
175 | if (++itp != endp && *itp == '-') |
176 | { |
177 | if (++itp != endp) |
178 | last = *itp++; |
179 | else |
180 | throw SyntaxException("bad range syntax in glob pattern" ); |
181 | } |
182 | if (_options & GLOB_CASELESS) |
183 | { |
184 | first = Unicode::toLower(first); |
185 | last = Unicode::toLower(last); |
186 | } |
187 | if (first <= c && c <= last) |
188 | { |
189 | while (itp != endp) |
190 | { |
191 | switch (*itp) |
192 | { |
193 | case ']': |
194 | ++itp; |
195 | return true; |
196 | case '\\': |
197 | if (++itp == endp) break; |
198 | default: |
199 | ++itp; |
200 | } |
201 | } |
202 | throw SyntaxException("range must be terminated by closing bracket in glob pattern" ); |
203 | } |
204 | } |
205 | return false; |
206 | } |
207 | |
208 | |
209 | void Glob::collect(const Path& pathPattern, const Path& base, const Path& current, const std::string& pattern, std::set<std::string>& files, int options) |
210 | { |
211 | try |
212 | { |
213 | std::string pp = pathPattern.toString(); |
214 | std::string basep = base.toString(); |
215 | std::string curp = current.toString(); |
216 | Glob g(pattern, options); |
217 | DirectoryIterator it(base); |
218 | DirectoryIterator end; |
219 | while (it != end) |
220 | { |
221 | const std::string& name = it.name(); |
222 | if (g.match(name)) |
223 | { |
224 | Path p(current); |
225 | if (p.depth() < pathPattern.depth() - 1) |
226 | { |
227 | p.pushDirectory(name); |
228 | collect(pathPattern, it.path(), p, pathPattern[p.depth()], files, options); |
229 | } |
230 | else |
231 | { |
232 | p.setFileName(name); |
233 | if (isDirectory(p, (options & GLOB_FOLLOW_SYMLINKS) != 0)) |
234 | { |
235 | p.makeDirectory(); |
236 | files.insert(p.toString()); |
237 | } |
238 | else if (!(options & GLOB_DIRS_ONLY)) |
239 | { |
240 | files.insert(p.toString()); |
241 | } |
242 | } |
243 | } |
244 | ++it; |
245 | } |
246 | } |
247 | catch (Exception&) |
248 | { |
249 | } |
250 | } |
251 | |
252 | |
253 | bool Glob::isDirectory(const Path& path, bool followSymlink) |
254 | { |
255 | File f(path); |
256 | bool isDir = false; |
257 | try |
258 | { |
259 | isDir = f.isDirectory(); |
260 | } |
261 | catch (Poco::Exception&) |
262 | { |
263 | return false; |
264 | } |
265 | if (isDir) |
266 | { |
267 | return true; |
268 | } |
269 | else if (followSymlink && f.isLink()) |
270 | { |
271 | try |
272 | { |
273 | // Test if link resolves to a directory. |
274 | DirectoryIterator it(f); |
275 | return true; |
276 | } |
277 | catch (Exception&) |
278 | { |
279 | } |
280 | } |
281 | return false; |
282 | } |
283 | |
284 | |
285 | } // namespace Poco |
286 | |