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
24namespace Poco {
25
26
27Glob::Glob(const std::string& pattern, int options)
28 : _pattern(pattern), _options(options)
29{
30}
31
32
33Glob::~Glob()
34{
35}
36
37
38bool 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
53void 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
59void 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
65void 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
87void 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
99bool 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
152bool Glob::matchAfterAsterisk(TextIterator itp, const TextIterator& endp, TextIterator its, const TextIterator& ends)
153{
154 return match(itp, endp, its, ends);
155}
156
157
158bool 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
209void 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
253bool 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