Coverage Report

Created: 2026-03-12 06:35

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