1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing#kwsys for details. */
3 #include "kwsysPrivate.h"
4 #include KWSYS_HEADER(Glob.hxx)
5
6 #include KWSYS_HEADER(Configure.hxx)
7
8 #include KWSYS_HEADER(RegularExpression.hxx)
9 #include KWSYS_HEADER(SystemTools.hxx)
10 #include KWSYS_HEADER(Directory.hxx)
11
12 // Work-around CMake dependency scanning limitation. This must
13 // duplicate the above list of headers.
14 #if 0
15 #include "Configure.hxx.in"
16 #include "Directory.hxx.in"
17 #include "Glob.hxx.in"
18 #include "RegularExpression.hxx.in"
19 #include "SystemTools.hxx.in"
20 #endif
21
22 #include <algorithm>
23 #include <string>
24 #include <vector>
25
26 #include <ctype.h>
27 #include <stdio.h>
28 #include <string.h>
29 namespace KWSYS_NAMESPACE {
30 #if defined(_WIN32) || defined(__APPLE__) || defined(__CYGWIN__)
31 // On Windows and Apple, no difference between lower and upper case
32 #define KWSYS_GLOB_CASE_INDEPENDENT
33 #endif
34
35 #if defined(_WIN32) || defined(__CYGWIN__)
36 // Handle network paths
37 #define KWSYS_GLOB_SUPPORT_NETWORK_PATHS
38 #endif
39
40 class GlobInternals
41 {
42 public:
43 std::vector<std::string> Files;
44 std::vector<kwsys::RegularExpression> Expressions;
45 };
46
Glob()47 Glob::Glob()
48 {
49 this->Internals = new GlobInternals;
50 this->Recurse = false;
51 this->Relative = "";
52
53 this->RecurseThroughSymlinks = true;
54 // RecurseThroughSymlinks is true by default for backwards compatibility,
55 // not because it's a good idea...
56 this->FollowedSymlinkCount = 0;
57
58 // Keep separate variables for directory listing for back compatibility
59 this->ListDirs = true;
60 this->RecurseListDirs = false;
61 }
62
~Glob()63 Glob::~Glob()
64 {
65 delete this->Internals;
66 }
67
GetFiles()68 std::vector<std::string>& Glob::GetFiles()
69 {
70 return this->Internals->Files;
71 }
72
PatternToRegex(const std::string & pattern,bool require_whole_string,bool preserve_case)73 std::string Glob::PatternToRegex(const std::string& pattern,
74 bool require_whole_string, bool preserve_case)
75 {
76 // Incrementally build the regular expression from the pattern.
77 std::string regex = require_whole_string ? "^" : "";
78 std::string::const_iterator pattern_first = pattern.begin();
79 std::string::const_iterator pattern_last = pattern.end();
80 for (std::string::const_iterator i = pattern_first; i != pattern_last; ++i) {
81 int c = *i;
82 if (c == '*') {
83 // A '*' (not between brackets) matches any string.
84 // We modify this to not match slashes since the original glob
85 // pattern documentation was meant for matching file name
86 // components separated by slashes.
87 regex += "[^/]*";
88 } else if (c == '?') {
89 // A '?' (not between brackets) matches any single character.
90 // We modify this to not match slashes since the original glob
91 // pattern documentation was meant for matching file name
92 // components separated by slashes.
93 regex += "[^/]";
94 } else if (c == '[') {
95 // Parse out the bracket expression. It begins just after the
96 // opening character.
97 std::string::const_iterator bracket_first = i + 1;
98 std::string::const_iterator bracket_last = bracket_first;
99
100 // The first character may be complementation '!' or '^'.
101 if (bracket_last != pattern_last &&
102 (*bracket_last == '!' || *bracket_last == '^')) {
103 ++bracket_last;
104 }
105
106 // If the next character is a ']' it is included in the brackets
107 // because the bracket string may not be empty.
108 if (bracket_last != pattern_last && *bracket_last == ']') {
109 ++bracket_last;
110 }
111
112 // Search for the closing ']'.
113 while (bracket_last != pattern_last && *bracket_last != ']') {
114 ++bracket_last;
115 }
116
117 // Check whether we have a complete bracket string.
118 if (bracket_last == pattern_last) {
119 // The bracket string did not end, so it was opened simply by
120 // a '[' that is supposed to be matched literally.
121 regex += "\\[";
122 } else {
123 // Convert the bracket string to its regex equivalent.
124 std::string::const_iterator k = bracket_first;
125
126 // Open the regex block.
127 regex += "[";
128
129 // A regex range complement uses '^' instead of '!'.
130 if (k != bracket_last && *k == '!') {
131 regex += "^";
132 ++k;
133 }
134
135 // Convert the remaining characters.
136 for (; k != bracket_last; ++k) {
137 // Backslashes must be escaped.
138 if (*k == '\\') {
139 regex += "\\";
140 }
141
142 // Store this character.
143 regex += *k;
144 }
145
146 // Close the regex block.
147 regex += "]";
148
149 // Jump to the end of the bracket string.
150 i = bracket_last;
151 }
152 } else {
153 // A single character matches itself.
154 int ch = c;
155 if (!(('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') ||
156 ('0' <= ch && ch <= '9'))) {
157 // Escape the non-alphanumeric character.
158 regex += "\\";
159 }
160 #if defined(KWSYS_GLOB_CASE_INDEPENDENT)
161 else {
162 // On case-insensitive systems file names are converted to lower
163 // case before matching.
164 if (!preserve_case) {
165 ch = tolower(ch);
166 }
167 }
168 #endif
169 (void)preserve_case;
170 // Store the character.
171 regex.append(1, static_cast<char>(ch));
172 }
173 }
174
175 if (require_whole_string) {
176 regex += "$";
177 }
178 return regex;
179 }
180
RecurseDirectory(std::string::size_type start,const std::string & dir,GlobMessages * messages)181 bool Glob::RecurseDirectory(std::string::size_type start,
182 const std::string& dir, GlobMessages* messages)
183 {
184 kwsys::Directory d;
185 if (!d.Load(dir)) {
186 return true;
187 }
188 unsigned long cc;
189 std::string realname;
190 std::string fname;
191 for (cc = 0; cc < d.GetNumberOfFiles(); cc++) {
192 fname = d.GetFile(cc);
193 if (fname == "." || fname == "..") {
194 continue;
195 }
196
197 if (start == 0) {
198 realname = dir + fname;
199 } else {
200 realname = dir + "/" + fname;
201 }
202
203 #if defined(KWSYS_GLOB_CASE_INDEPENDENT)
204 // On Windows and Apple, no difference between lower and upper case
205 fname = kwsys::SystemTools::LowerCase(fname);
206 #endif
207
208 bool isDir = kwsys::SystemTools::FileIsDirectory(realname);
209 bool isSymLink = kwsys::SystemTools::FileIsSymlink(realname);
210
211 if (isDir && (!isSymLink || this->RecurseThroughSymlinks)) {
212 if (isSymLink) {
213 ++this->FollowedSymlinkCount;
214 std::string realPathErrorMessage;
215 std::string canonicalPath(
216 SystemTools::GetRealPath(dir, &realPathErrorMessage));
217
218 if (!realPathErrorMessage.empty()) {
219 if (messages) {
220 messages->push_back(Message(
221 Glob::error, "Canonical path generation from path '" + dir +
222 "' failed! Reason: '" + realPathErrorMessage + "'"));
223 }
224 return false;
225 }
226
227 if (std::find(this->VisitedSymlinks.begin(),
228 this->VisitedSymlinks.end(),
229 canonicalPath) == this->VisitedSymlinks.end()) {
230 if (this->RecurseListDirs) {
231 // symlinks are treated as directories
232 this->AddFile(this->Internals->Files, realname);
233 }
234
235 this->VisitedSymlinks.push_back(canonicalPath);
236 if (!this->RecurseDirectory(start + 1, realname, messages)) {
237 this->VisitedSymlinks.pop_back();
238
239 return false;
240 }
241 this->VisitedSymlinks.pop_back();
242 }
243 // else we have already visited this symlink - prevent cyclic recursion
244 else if (messages) {
245 std::string message;
246 for (std::vector<std::string>::const_iterator pathIt =
247 std::find(this->VisitedSymlinks.begin(),
248 this->VisitedSymlinks.end(), canonicalPath);
249 pathIt != this->VisitedSymlinks.end(); ++pathIt) {
250 message += *pathIt + "\n";
251 }
252 message += canonicalPath + "/" + fname;
253 messages->push_back(Message(Glob::cyclicRecursion, message));
254 }
255 } else {
256 if (this->RecurseListDirs) {
257 this->AddFile(this->Internals->Files, realname);
258 }
259 if (!this->RecurseDirectory(start + 1, realname, messages)) {
260 return false;
261 }
262 }
263 } else {
264 if (!this->Internals->Expressions.empty() &&
265 this->Internals->Expressions.rbegin()->find(fname)) {
266 this->AddFile(this->Internals->Files, realname);
267 }
268 }
269 }
270
271 return true;
272 }
273
ProcessDirectory(std::string::size_type start,const std::string & dir,GlobMessages * messages)274 void Glob::ProcessDirectory(std::string::size_type start,
275 const std::string& dir, GlobMessages* messages)
276 {
277 // std::cout << "ProcessDirectory: " << dir << std::endl;
278 bool last = (start == this->Internals->Expressions.size() - 1);
279 if (last && this->Recurse) {
280 this->RecurseDirectory(start, dir, messages);
281 return;
282 }
283
284 if (start >= this->Internals->Expressions.size()) {
285 return;
286 }
287
288 kwsys::Directory d;
289 if (!d.Load(dir)) {
290 return;
291 }
292 unsigned long cc;
293 std::string realname;
294 std::string fname;
295 for (cc = 0; cc < d.GetNumberOfFiles(); cc++) {
296 fname = d.GetFile(cc);
297 if (fname == "." || fname == "..") {
298 continue;
299 }
300
301 if (start == 0) {
302 realname = dir + fname;
303 } else {
304 realname = dir + "/" + fname;
305 }
306
307 #if defined(KWSYS_GLOB_CASE_INDEPENDENT)
308 // On case-insensitive file systems convert to lower case for matching.
309 fname = kwsys::SystemTools::LowerCase(fname);
310 #endif
311
312 // std::cout << "Look at file: " << fname << std::endl;
313 // std::cout << "Match: "
314 // << this->Internals->TextExpressions[start].c_str() << std::endl;
315 // std::cout << "Real name: " << realname << std::endl;
316
317 if ((!last && !kwsys::SystemTools::FileIsDirectory(realname)) ||
318 (!this->ListDirs && last &&
319 kwsys::SystemTools::FileIsDirectory(realname))) {
320 continue;
321 }
322
323 if (this->Internals->Expressions[start].find(fname)) {
324 if (last) {
325 this->AddFile(this->Internals->Files, realname);
326 } else {
327 this->ProcessDirectory(start + 1, realname, messages);
328 }
329 }
330 }
331 }
332
FindFiles(const std::string & inexpr,GlobMessages * messages)333 bool Glob::FindFiles(const std::string& inexpr, GlobMessages* messages)
334 {
335 std::string cexpr;
336 std::string::size_type cc;
337 std::string expr = inexpr;
338
339 this->Internals->Expressions.clear();
340 this->Internals->Files.clear();
341
342 if (!kwsys::SystemTools::FileIsFullPath(expr)) {
343 expr = kwsys::SystemTools::GetCurrentWorkingDirectory();
344 expr += "/" + inexpr;
345 }
346 std::string fexpr = expr;
347
348 std::string::size_type skip = 0;
349 std::string::size_type last_slash = 0;
350 for (cc = 0; cc < expr.size(); cc++) {
351 if (cc > 0 && expr[cc] == '/' && expr[cc - 1] != '\\') {
352 last_slash = cc;
353 }
354 if (cc > 0 && (expr[cc] == '[' || expr[cc] == '?' || expr[cc] == '*') &&
355 expr[cc - 1] != '\\') {
356 break;
357 }
358 }
359 if (last_slash > 0) {
360 // std::cout << "I can skip: " << fexpr.substr(0, last_slash)
361 // << std::endl;
362 skip = last_slash;
363 }
364 if (skip == 0) {
365 #if defined(KWSYS_GLOB_SUPPORT_NETWORK_PATHS)
366 // Handle network paths
367 if (expr[0] == '/' && expr[1] == '/') {
368 int cnt = 0;
369 for (cc = 2; cc < expr.size(); cc++) {
370 if (expr[cc] == '/') {
371 cnt++;
372 if (cnt == 2) {
373 break;
374 }
375 }
376 }
377 skip = int(cc + 1);
378 } else
379 #endif
380 // Handle drive letters on Windows
381 if (expr[1] == ':' && expr[0] != '/') {
382 skip = 2;
383 }
384 }
385
386 if (skip > 0) {
387 expr = expr.substr(skip);
388 }
389
390 cexpr = "";
391 for (cc = 0; cc < expr.size(); cc++) {
392 int ch = expr[cc];
393 if (ch == '/') {
394 if (!cexpr.empty()) {
395 this->AddExpression(cexpr);
396 }
397 cexpr = "";
398 } else {
399 cexpr.append(1, static_cast<char>(ch));
400 }
401 }
402 if (!cexpr.empty()) {
403 this->AddExpression(cexpr);
404 }
405
406 // Handle network paths
407 if (skip > 0) {
408 this->ProcessDirectory(0, fexpr.substr(0, skip) + "/", messages);
409 } else {
410 this->ProcessDirectory(0, "/", messages);
411 }
412 return true;
413 }
414
AddExpression(const std::string & expr)415 void Glob::AddExpression(const std::string& expr)
416 {
417 this->Internals->Expressions.push_back(
418 kwsys::RegularExpression(this->PatternToRegex(expr)));
419 }
420
SetRelative(const char * dir)421 void Glob::SetRelative(const char* dir)
422 {
423 if (!dir) {
424 this->Relative = "";
425 return;
426 }
427 this->Relative = dir;
428 }
429
GetRelative()430 const char* Glob::GetRelative()
431 {
432 if (this->Relative.empty()) {
433 return KWSYS_NULLPTR;
434 }
435 return this->Relative.c_str();
436 }
437
AddFile(std::vector<std::string> & files,const std::string & file)438 void Glob::AddFile(std::vector<std::string>& files, const std::string& file)
439 {
440 if (!this->Relative.empty()) {
441 files.push_back(kwsys::SystemTools::RelativePath(this->Relative, file));
442 } else {
443 files.push_back(file);
444 }
445 }
446
447 } // namespace KWSYS_NAMESPACE
448