Coverage Report

Created: 2026-02-09 06:05

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