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