cmFileCommand.cxx 105 KB
Newer Older
1
2
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
3
#include "cmFileCommand.h"
Brad King's avatar
Brad King committed
4

5
6
7
8
9
10
#include "cm_kwiml.h"
#include "cmsys/Directory.hxx"
#include "cmsys/FStream.hxx"
#include "cmsys/Glob.hxx"
#include "cmsys/RegularExpression.hxx"
#include "cmsys/String.hxx"
Daniel Pfeifer's avatar
Daniel Pfeifer committed
11
12
#include <algorithm>
#include <assert.h>
13
#include <memory> // IWYU pragma: keep
Daniel Pfeifer's avatar
Daniel Pfeifer committed
14
15
16
17
#include <sstream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Ben Boeckel's avatar
Ben Boeckel committed
18
#include <vector>
Daniel Pfeifer's avatar
Daniel Pfeifer committed
19

20
#include "cmAlgorithms.h"
Daniel Pfeifer's avatar
Daniel Pfeifer committed
21
#include "cmCommandArgumentsHelper.h"
22
#include "cmCryptoHash.h"
23
#include "cmFSPermissions.h"
Daniel Pfeifer's avatar
Daniel Pfeifer committed
24
#include "cmFileLockPool.h"
25
#include "cmFileTimeComparison.h"
Daniel Pfeifer's avatar
Daniel Pfeifer committed
26
#include "cmGeneratorExpression.h"
Stephen Kelly's avatar
Stephen Kelly committed
27
#include "cmGlobalGenerator.h"
28
29
#include "cmHexFileConverter.h"
#include "cmInstallType.h"
Daniel Pfeifer's avatar
Daniel Pfeifer committed
30
#include "cmListFileCache.h"
31
#include "cmMakefile.h"
Daniel Pfeifer's avatar
Daniel Pfeifer committed
32
33
#include "cmPolicies.h"
#include "cmSystemTools.h"
34
#include "cmTimestamp.h"
35
#include "cm_sys_stat.h"
36
#include "cmake.h"
37

38
#if defined(CMAKE_BUILD_WITH_CMAKE)
39
40
41
#  include "cmCurl.h"
#  include "cmFileLockResult.h"
#  include "cm_curl.h"
42
43
#endif

44
#if defined(CMAKE_USE_ELF_PARSER)
45
#  include "cmELF.h"
46
47
#endif

48
#if defined(_WIN32)
49
#  include <windows.h>
50
51
#endif

Daniel Pfeifer's avatar
Daniel Pfeifer committed
52
class cmSystemToolsFileTime;
53

54
using namespace cmFSPermissions;
55

56
#if defined(_WIN32)
57
58
59
60
61
// libcurl doesn't support file:// urls for unicode filenames on Windows.
// Convert string from UTF-8 to ACP if this is a file:// URL.
static std::string fix_file_url_windows(const std::string& url)
{
  std::string ret = url;
62
  if (strncmp(url.c_str(), "file://", 7) == 0) {
Stephen Kelly's avatar
Stephen Kelly committed
63
    std::wstring wurl = cmsys::Encoding::ToWide(url);
64
65
66
67
    if (!wurl.empty()) {
      int mblen =
        WideCharToMultiByte(CP_ACP, 0, wurl.c_str(), -1, NULL, 0, NULL, NULL);
      if (mblen > 0) {
68
        std::vector<char> chars(mblen);
69
70
71
        mblen = WideCharToMultiByte(CP_ACP, 0, wurl.c_str(), -1, &chars[0],
                                    mblen, NULL, NULL);
        if (mblen > 0) {
72
73
74
75
          ret = &chars[0];
        }
      }
    }
76
  }
77
78
79
80
  return ret;
}
#endif

81
// cmLibraryCommand
82
83
bool cmFileCommand::InitialPass(std::vector<std::string> const& args,
                                cmExecutionStatus&)
84
{
85
  if (args.size() < 2) {
86
87
    this->SetError("must be called with at least two arguments.");
    return false;
88
  }
89
  std::string const& subCommand = args[0];
90
  if (subCommand == "WRITE") {
91
    return this->HandleWriteCommand(args, false);
Daniel Pfeifer's avatar
Daniel Pfeifer committed
92
93
  }
  if (subCommand == "APPEND") {
94
    return this->HandleWriteCommand(args, true);
Daniel Pfeifer's avatar
Daniel Pfeifer committed
95
96
  }
  if (subCommand == "DOWNLOAD") {
97
    return this->HandleDownloadCommand(args);
Daniel Pfeifer's avatar
Daniel Pfeifer committed
98
99
  }
  if (subCommand == "UPLOAD") {
David Cole's avatar
David Cole committed
100
    return this->HandleUploadCommand(args);
Daniel Pfeifer's avatar
Daniel Pfeifer committed
101
102
  }
  if (subCommand == "READ") {
103
    return this->HandleReadCommand(args);
Daniel Pfeifer's avatar
Daniel Pfeifer committed
104
105
106
  }
  if (subCommand == "MD5" || subCommand == "SHA1" || subCommand == "SHA224" ||
      subCommand == "SHA256" || subCommand == "SHA384" ||
107
108
109
      subCommand == "SHA512" || subCommand == "SHA3_224" ||
      subCommand == "SHA3_256" || subCommand == "SHA3_384" ||
      subCommand == "SHA3_512") {
110
    return this->HandleHashCommand(args);
Daniel Pfeifer's avatar
Daniel Pfeifer committed
111
112
  }
  if (subCommand == "STRINGS") {
Brad King's avatar
Brad King committed
113
    return this->HandleStringsCommand(args);
114
115
  }
  if (subCommand == "GLOB") {
Andy Cedilnik's avatar
Andy Cedilnik committed
116
    return this->HandleGlobCommand(args, false);
117
118
  }
  if (subCommand == "GLOB_RECURSE") {
Andy Cedilnik's avatar
Andy Cedilnik committed
119
    return this->HandleGlobCommand(args, true);
120
121
  }
  if (subCommand == "MAKE_DIRECTORY") {
122
    return this->HandleMakeDirectoryCommand(args);
123
124
  }
  if (subCommand == "RENAME") {
125
    return this->HandleRename(args);
126
127
  }
  if (subCommand == "REMOVE") {
128
    return this->HandleRemove(args, false);
129
130
  }
  if (subCommand == "REMOVE_RECURSE") {
131
    return this->HandleRemove(args, true);
132
133
  }
  if (subCommand == "COPY") {
134
    return this->HandleCopyCommand(args);
135
136
  }
  if (subCommand == "INSTALL") {
137
    return this->HandleInstallCommand(args);
138
139
  }
  if (subCommand == "DIFFERENT") {
140
    return this->HandleDifferentCommand(args);
141
142
  }
  if (subCommand == "RPATH_CHANGE" || subCommand == "CHRPATH") {
143
    return this->HandleRPathChangeCommand(args);
144
145
  }
  if (subCommand == "RPATH_CHECK") {
146
    return this->HandleRPathCheckCommand(args);
147
148
  }
  if (subCommand == "RPATH_REMOVE") {
149
    return this->HandleRPathRemoveCommand(args);
150
  }
151
152
153
  if (subCommand == "READ_ELF") {
    return this->HandleReadElfCommand(args);
  }
154
  if (subCommand == "RELATIVE_PATH") {
155
    return this->HandleRelativePathCommand(args);
156
157
  }
  if (subCommand == "TO_CMAKE_PATH") {
158
    return this->HandleCMakePathCommand(args, false);
159
160
  }
  if (subCommand == "TO_NATIVE_PATH") {
161
    return this->HandleCMakePathCommand(args, true);
162
  }
163
164
165
166
167
168
  if (subCommand == "TOUCH") {
    return this->HandleTouchCommand(args, true);
  }
  if (subCommand == "TOUCH_NOCREATE") {
    return this->HandleTouchCommand(args, false);
  }
169
  if (subCommand == "TIMESTAMP") {
170
    return this->HandleTimestampCommand(args);
171
172
  }
  if (subCommand == "GENERATE") {
173
    return this->HandleGenerateCommand(args);
174
175
  }
  if (subCommand == "LOCK") {
176
    return this->HandleLockCommand(args);
177
  }
178

179
  std::string e = "does not recognize sub-command " + subCommand;
Stephen Kelly's avatar
Stephen Kelly committed
180
  this->SetError(e);
181
  return false;
182
183
}

184
bool cmFileCommand::HandleWriteCommand(std::vector<std::string> const& args,
185
                                       bool append)
186
187
188
189
190
191
{
  std::vector<std::string>::const_iterator i = args.begin();

  i++; // Get rid of subcommand

  std::string fileName = *i;
192
  if (!cmsys::SystemTools::FileIsFullPath(*i)) {
193
    fileName = this->Makefile->GetCurrentSourceDirectory();
194
    fileName += "/" + *i;
195
  }
196

197
198
  i++;

199
  if (!this->Makefile->CanIWriteThisFile(fileName)) {
200
201
    std::string e =
      "attempted to write a file: " + fileName + " into a source directory.";
Stephen Kelly's avatar
Stephen Kelly committed
202
    this->SetError(e);
203
204
    cmSystemTools::SetFatalErrorOccured();
    return false;
205
  }
206
  std::string dir = cmSystemTools::GetFilenamePath(fileName);
207
  cmSystemTools::MakeDirectory(dir);
208

209
  mode_t mode = 0;
210
211

  // Set permissions to writable
212
  if (cmSystemTools::GetPermissions(fileName.c_str(), mode)) {
213
    cmSystemTools::SetPermissions(fileName.c_str(),
214
215
#if defined(_MSC_VER) || defined(__MINGW32__)
                                  mode | S_IWRITE
216
#else
217
                                  mode | S_IWUSR | S_IWGRP
218
#endif
219
    );
220
  }
221
222
  // If GetPermissions fails, pretend like it is ok. File open will fail if
  // the file is not writable
223
224
225
  cmsys::ofstream file(fileName.c_str(),
                       append ? std::ios::app : std::ios::out);
  if (!file) {
226
227
228
229
    std::string error = "failed to open for writing (";
    error += cmSystemTools::GetLastSystemError();
    error += "):\n  ";
    error += fileName;
Stephen Kelly's avatar
Stephen Kelly committed
230
    this->SetError(error);
231
    return false;
232
  }
233
  std::string message = cmJoin(cmMakeRange(i, args.end()), std::string());
234
  file << message;
235
236
237
238
239
240
241
242
  if (!file) {
    std::string error = "write failed (";
    error += cmSystemTools::GetLastSystemError();
    error += "):\n  ";
    error += fileName;
    this->SetError(error);
    return false;
  }
243
  file.close();
244
  if (mode) {
245
    cmSystemTools::SetPermissions(fileName.c_str(), mode);
246
  }
247
248
249
250
251
  return true;
}

bool cmFileCommand::HandleReadCommand(std::vector<std::string> const& args)
{
252
  if (args.size() < 3) {
Ken Martin's avatar
Ken Martin committed
253
254
    this->SetError("READ must be called with at least two additional "
                   "arguments");
255
    return false;
256
  }
257

258
259
260
  cmCommandArgumentsHelper argHelper;
  cmCommandArgumentGroup group;

261
  cmCAString readArg(&argHelper, "READ");
Daniel Pfeifer's avatar
Daniel Pfeifer committed
262
263
  cmCAString fileNameArg(&argHelper, nullptr);
  cmCAString resultArg(&argHelper, nullptr);
264

265
266
267
  cmCAString offsetArg(&argHelper, "OFFSET", &group);
  cmCAString limitArg(&argHelper, "LIMIT", &group);
  cmCAEnabler hexOutputArg(&argHelper, "HEX", &group);
Daniel Pfeifer's avatar
Daniel Pfeifer committed
268
  readArg.Follows(nullptr);
269
270
271
  fileNameArg.Follows(&readArg);
  resultArg.Follows(&fileNameArg);
  group.Follows(&resultArg);
Daniel Pfeifer's avatar
Daniel Pfeifer committed
272
  argHelper.Parse(&args, nullptr);
273
274

  std::string fileName = fileNameArg.GetString();
275
  if (!cmsys::SystemTools::FileIsFullPath(fileName)) {
276
    fileName = this->Makefile->GetCurrentSourceDirectory();
277
    fileName += "/" + fileNameArg.GetString();
278
  }
279

280
281
  std::string variable = resultArg.GetString();

282
// Open the specified file.
283
#if defined(_WIN32) || defined(__CYGWIN__)
284
  cmsys::ifstream file(
285
286
    fileName.c_str(),
    std::ios::in |
287
      (hexOutputArg.IsEnabled() ? std::ios::binary : std::ios::in));
288
#else
289
  cmsys::ifstream file(fileName.c_str());
290
291
#endif

292
  if (!file) {
293
294
295
296
    std::string error = "failed to open for reading (";
    error += cmSystemTools::GetLastSystemError();
    error += "):\n  ";
    error += fileName;
Stephen Kelly's avatar
Stephen Kelly committed
297
    this->SetError(error);
298
    return false;
299
  }
300

301
  // is there a limit?
Ken Martin's avatar
Ken Martin committed
302
  long sizeLimit = -1;
303
  if (!limitArg.GetString().empty()) {
304
    sizeLimit = atoi(limitArg.GetCString());
305
  }
306
307
308

  // is there an offset?
  long offset = 0;
309
  if (!offsetArg.GetString().empty()) {
310
    offset = atoi(offsetArg.GetCString());
311
  }
Ken Martin's avatar
Ken Martin committed
312

313
  file.seekg(offset, std::ios::beg); // explicit ios::beg for IBM VisualAge 6
314

315
  std::string output;
316

317
  if (hexOutputArg.IsEnabled()) {
318
    // Convert part of the file into hex code
319
    char c;
320
    while ((sizeLimit != 0) && (file.get(c))) {
321
      char hex[4];
322
      sprintf(hex, "%.2x", c & 0xff);
323
      output += hex;
324
      if (sizeLimit > 0) {
Ken Martin's avatar
Ken Martin committed
325
326
        sizeLimit--;
      }
327
    }
328
  } else {
329
330
    std::string line;
    bool has_newline = false;
331
332
333
    while (
      sizeLimit != 0 &&
      cmSystemTools::GetLineFromStream(file, line, &has_newline, sizeLimit)) {
334
      if (sizeLimit > 0) {
335
        sizeLimit = sizeLimit - static_cast<long>(line.size());
336
        if (has_newline) {
337
          sizeLimit--;
338
339
        }
        if (sizeLimit < 0) {
340
341
          sizeLimit = 0;
        }
342
      }
343
      output += line;
344
      if (has_newline) {
345
        output += "\n";
346
347
      }
    }
348
  }
Stephen Kelly's avatar
Stephen Kelly committed
349
  this->Makefile->AddDefinition(variable, output.c_str());
350
351
352
  return true;
}

353
354
bool cmFileCommand::HandleHashCommand(std::vector<std::string> const& args)
{
355
#if defined(CMAKE_BUILD_WITH_CMAKE)
356
  if (args.size() != 3) {
357
    std::ostringstream e;
358
    e << args[0] << " requires a file name and output variable";
Stephen Kelly's avatar
Stephen Kelly committed
359
    this->SetError(e.str());
360
    return false;
361
  }
362

363
364
  std::unique_ptr<cmCryptoHash> hash(cmCryptoHash::New(args[0].c_str()));
  if (hash) {
Stephen Kelly's avatar
Stephen Kelly committed
365
    std::string out = hash->HashFile(args[1]);
366
    if (!out.empty()) {
Stephen Kelly's avatar
Stephen Kelly committed
367
      this->Makefile->AddDefinition(args[2], out.c_str());
368
      return true;
369
    }
370
    std::ostringstream e;
371
372
    e << args[0] << " failed to read file \"" << args[1]
      << "\": " << cmSystemTools::GetLastSystemError();
Stephen Kelly's avatar
Stephen Kelly committed
373
    this->SetError(e.str());
374
  }
375
  return false;
376
#else
377
  std::ostringstream e;
378
379
380
381
  e << args[0] << " not available during bootstrap";
  this->SetError(e.str().c_str());
  return false;
#endif
382
383
}

Brad King's avatar
Brad King committed
384
385
bool cmFileCommand::HandleStringsCommand(std::vector<std::string> const& args)
{
386
  if (args.size() < 3) {
Brad King's avatar
Brad King committed
387
388
    this->SetError("STRINGS requires a file name and output variable");
    return false;
389
  }
Brad King's avatar
Brad King committed
390
391
392

  // Get the file to read.
  std::string fileName = args[1];
393
  if (!cmsys::SystemTools::FileIsFullPath(fileName)) {
394
    fileName = this->Makefile->GetCurrentSourceDirectory();
Brad King's avatar
Brad King committed
395
    fileName += "/" + args[1];
396
  }
Brad King's avatar
Brad King committed
397
398
399
400
401

  // Get the variable in which to store the results.
  std::string outVar = args[2];

  // Parse the options.
402
403
404
405
406
407
408
409
410
411
412
413
  enum
  {
    arg_none,
    arg_limit_input,
    arg_limit_output,
    arg_limit_count,
    arg_length_minimum,
    arg_length_maximum,
    arg__maximum,
    arg_regex,
    arg_encoding
  };
Brad King's avatar
Brad King committed
414
415
416
417
418
419
420
421
  unsigned int minlen = 0;
  unsigned int maxlen = 0;
  int limit_input = -1;
  int limit_output = -1;
  unsigned int limit_count = 0;
  cmsys::RegularExpression regex;
  bool have_regex = false;
  bool newline_consume = false;
Alexander Neundorf's avatar
   
Alexander Neundorf committed
422
  bool hex_conversion_enabled = true;
423
424
425
426
427
428
429
430
431
  enum
  {
    encoding_none = cmsys::FStream::BOM_None,
    encoding_utf8 = cmsys::FStream::BOM_UTF8,
    encoding_utf16le = cmsys::FStream::BOM_UTF16LE,
    encoding_utf16be = cmsys::FStream::BOM_UTF16BE,
    encoding_utf32le = cmsys::FStream::BOM_UTF32LE,
    encoding_utf32be = cmsys::FStream::BOM_UTF32BE
  };
432
  int encoding = encoding_none;
Brad King's avatar
Brad King committed
433
  int arg_mode = arg_none;
434
435
  for (unsigned int i = 3; i < args.size(); ++i) {
    if (args[i] == "LIMIT_INPUT") {
Brad King's avatar
Brad King committed
436
      arg_mode = arg_limit_input;
437
    } else if (args[i] == "LIMIT_OUTPUT") {
Brad King's avatar
Brad King committed
438
      arg_mode = arg_limit_output;
439
    } else if (args[i] == "LIMIT_COUNT") {
Brad King's avatar
Brad King committed
440
      arg_mode = arg_limit_count;
441
    } else if (args[i] == "LENGTH_MINIMUM") {
Brad King's avatar
Brad King committed
442
      arg_mode = arg_length_minimum;
443
    } else if (args[i] == "LENGTH_MAXIMUM") {
Brad King's avatar
Brad King committed
444
      arg_mode = arg_length_maximum;
445
    } else if (args[i] == "REGEX") {
Brad King's avatar
Brad King committed
446
      arg_mode = arg_regex;
447
    } else if (args[i] == "NEWLINE_CONSUME") {
Brad King's avatar
Brad King committed
448
449
      newline_consume = true;
      arg_mode = arg_none;
450
    } else if (args[i] == "NO_HEX_CONVERSION") {
Alexander Neundorf's avatar
   
Alexander Neundorf committed
451
452
      hex_conversion_enabled = false;
      arg_mode = arg_none;
453
    } else if (args[i] == "ENCODING") {
454
      arg_mode = arg_encoding;
455
456
457
    } else if (arg_mode == arg_limit_input) {
      if (sscanf(args[i].c_str(), "%d", &limit_input) != 1 ||
          limit_input < 0) {
458
        std::ostringstream e;
459
460
        e << "STRINGS option LIMIT_INPUT value \"" << args[i]
          << "\" is not an unsigned integer.";
Stephen Kelly's avatar
Stephen Kelly committed
461
        this->SetError(e.str());
Brad King's avatar
Brad King committed
462
463
        return false;
      }
464
465
466
467
      arg_mode = arg_none;
    } else if (arg_mode == arg_limit_output) {
      if (sscanf(args[i].c_str(), "%d", &limit_output) != 1 ||
          limit_output < 0) {
468
        std::ostringstream e;
469
470
        e << "STRINGS option LIMIT_OUTPUT value \"" << args[i]
          << "\" is not an unsigned integer.";
Stephen Kelly's avatar
Stephen Kelly committed
471
        this->SetError(e.str());
Brad King's avatar
Brad King committed
472
473
        return false;
      }
474
475
      arg_mode = arg_none;
    } else if (arg_mode == arg_limit_count) {
Brad King's avatar
Brad King committed
476
      int count;
477
      if (sscanf(args[i].c_str(), "%d", &count) != 1 || count < 0) {
478
        std::ostringstream e;
479
480
        e << "STRINGS option LIMIT_COUNT value \"" << args[i]
          << "\" is not an unsigned integer.";
Stephen Kelly's avatar
Stephen Kelly committed
481
        this->SetError(e.str());
Brad King's avatar
Brad King committed
482
        return false;
483
      }
Brad King's avatar
Brad King committed
484
485
      limit_count = count;
      arg_mode = arg_none;
486
    } else if (arg_mode == arg_length_minimum) {
Brad King's avatar
Brad King committed
487
      int len;
488
      if (sscanf(args[i].c_str(), "%d", &len) != 1 || len < 0) {
489
        std::ostringstream e;
490
491
        e << "STRINGS option LENGTH_MINIMUM value \"" << args[i]
          << "\" is not an unsigned integer.";
Stephen Kelly's avatar
Stephen Kelly committed
492
        this->SetError(e.str());
Brad King's avatar
Brad King committed
493
        return false;
494
      }
Brad King's avatar
Brad King committed
495
496
      minlen = len;
      arg_mode = arg_none;
497
    } else if (arg_mode == arg_length_maximum) {
Brad King's avatar
Brad King committed
498
      int len;
499
      if (sscanf(args[i].c_str(), "%d", &len) != 1 || len < 0) {
500
        std::ostringstream e;
501
502
        e << "STRINGS option LENGTH_MAXIMUM value \"" << args[i]
          << "\" is not an unsigned integer.";
Stephen Kelly's avatar
Stephen Kelly committed
503
        this->SetError(e.str());
Brad King's avatar
Brad King committed
504
        return false;
505
      }
Brad King's avatar
Brad King committed
506
507
      maxlen = len;
      arg_mode = arg_none;
508
509
    } else if (arg_mode == arg_regex) {
      if (!regex.compile(args[i].c_str())) {
510
        std::ostringstream e;
511
512
        e << "STRINGS option REGEX value \"" << args[i]
          << "\" could not be compiled.";
Stephen Kelly's avatar
Stephen Kelly committed
513
        this->SetError(e.str());
Brad King's avatar
Brad King committed
514
        return false;
515
      }
Brad King's avatar
Brad King committed
516
517
      have_regex = true;
      arg_mode = arg_none;
518
519
    } else if (arg_mode == arg_encoding) {
      if (args[i] == "UTF-8") {
520
        encoding = encoding_utf8;
521
      } else if (args[i] == "UTF-16LE") {
522
        encoding = encoding_utf16le;
523
      } else if (args[i] == "UTF-16BE") {
524
        encoding = encoding_utf16be;
525
      } else if (args[i] == "UTF-32LE") {
526
        encoding = encoding_utf32le;
527
      } else if (args[i] == "UTF-32BE") {
528
        encoding = encoding_utf32be;
529
      } else {
530
        std::ostringstream e;
531
        e << "STRINGS option ENCODING \"" << args[i] << "\" not recognized.";
532
533
534
        this->SetError(e.str());
        return false;
      }
535
536
      arg_mode = arg_none;
    } else {
537
      std::ostringstream e;
538
      e << "STRINGS given unknown argument \"" << args[i] << "\"";
Stephen Kelly's avatar
Stephen Kelly committed
539
      this->SetError(e.str());
Brad King's avatar
Brad King committed
540
541
      return false;
    }
542
  }
Alexander Neundorf's avatar
   
Alexander Neundorf committed
543

544
  if (hex_conversion_enabled) {
Alexander Neundorf's avatar
   
Alexander Neundorf committed
545
    // TODO: should work without temp file, but just on a memory buffer
546
    std::string binaryFileName = this->Makefile->GetCurrentBinaryDirectory();
Alexander Neundorf's avatar
   
Alexander Neundorf committed
547
548
    binaryFileName += cmake::GetCMakeFilesDirectory();
    binaryFileName += "/FileCommandStringsBinaryFile";
549
550
    if (cmHexFileConverter::TryConvert(fileName.c_str(),
                                       binaryFileName.c_str())) {
Alexander Neundorf's avatar
   
Alexander Neundorf committed
551
      fileName = binaryFileName;
Alexander Neundorf's avatar
   
Alexander Neundorf committed
552
    }
553
  }
Alexander Neundorf's avatar
   
Alexander Neundorf committed
554

555
// Open the specified file.
Brad King's avatar
Brad King committed
556
#if defined(_WIN32) || defined(__CYGWIN__)
557
  cmsys::ifstream fin(fileName.c_str(), std::ios::in | std::ios::binary);
Brad King's avatar
Brad King committed
558
#else
559
  cmsys::ifstream fin(fileName.c_str());
Brad King's avatar
Brad King committed
560
#endif
561
  if (!fin) {
562
    std::ostringstream e;
Brad King's avatar
Brad King committed
563
    e << "STRINGS file \"" << fileName << "\" cannot be read.";
Stephen Kelly's avatar
Stephen Kelly committed
564
    this->SetError(e.str());
Brad King's avatar
Brad King committed
565
    return false;
566
  }
Brad King's avatar
Brad King committed
567

568
  // If BOM is found and encoding was not specified, use the BOM
569
  int bom_found = cmsys::FStream::ReadBOM(fin);
570
  if (encoding == encoding_none && bom_found != cmsys::FStream::BOM_None) {
571
    encoding = bom_found;
572
  }
573
574

  unsigned int bytes_rem = 0;
575
  if (encoding == encoding_utf16le || encoding == encoding_utf16be) {
576
    bytes_rem = 1;
577
578
  }
  if (encoding == encoding_utf32le || encoding == encoding_utf32be) {
579
    bytes_rem = 3;
580
  }
581

Brad King's avatar
Brad King committed
582
583
584
585
  // Parse strings out of the file.
  int output_size = 0;
  std::vector<std::string> strings;
  std::string s;
586
587
588
  while ((!limit_count || strings.size() < limit_count) &&
         (limit_input < 0 || static_cast<int>(fin.tellg()) < limit_input) &&
         fin) {
589
590
591
    std::string current_str;

    int c = fin.get();
592
    for (unsigned int i = 0; i < bytes_rem; ++i) {
593
      int c1 = fin.get();
594
      if (!fin) {
595
596
597
        fin.putback(static_cast<char>(c1));
        break;
      }
598
599
600
      c = (c << 8) | c1;
    }
    if (encoding == encoding_utf16le) {
601
      c = ((c & 0xFF) << 8) | ((c & 0xFF00) >> 8);
602
603
604
605
    } else if (encoding == encoding_utf32le) {
      c = (((c & 0xFF) << 24) | ((c & 0xFF00) << 8) | ((c & 0xFF0000) >> 8) |
           ((c & 0xFF000000) >> 24));
    }
606

607
    if (c == '\r') {
608
609
      // Ignore CR character to make output always have UNIX newlines.
      continue;
610
    }
611

612
613
    if ((c >= 0x20 && c < 0x7F) || c == '\t' ||
        (c == '\n' && newline_consume)) {
614
615
616
617
      // This is an ASCII character that may be part of a string.
      // Cast added to avoid compiler warning. Cast is ok because
      // c is guaranteed to fit in char by the above if...
      current_str += static_cast<char>(c);
618
    } else if (encoding == encoding_utf8) {
619
      // Check for UTF-8 encoded string (up to 4 octets)
620
      static const unsigned char utf8_check_table[3][2] = {
621
622
623
        { 0xE0, 0xC0 },
        { 0xF0, 0xE0 },
        { 0xF8, 0xF0 },
624
      };
625
626
627

      // how many octets are there?
      unsigned int num_utf8_bytes = 0;
628
      for (unsigned int j = 0; num_utf8_bytes == 0 && j < 3; j++) {
Daniel Pfeifer's avatar
Daniel Pfeifer committed
629
        if ((c & utf8_check_table[j][0]) == utf8_check_table[j][1]) {
630
          num_utf8_bytes = j + 2;
Daniel Pfeifer's avatar
Daniel Pfeifer committed
631
        }
632
      }
633
634

      // get subsequent octets and check that they are valid
635
636
      for (unsigned int j = 0; j < num_utf8_bytes; j++) {
        if (j != 0) {
637
          c = fin.get();
638
          if (!fin || (c & 0xC0) != 0x80) {
639
640
641
642
            fin.putback(static_cast<char>(c));
            break;
          }
        }
643
644
        current_str += static_cast<char>(c);
      }
645
646
647

      // if this was an invalid utf8 sequence, discard the data, and put
      // back subsequent characters
648
649
      if ((current_str.length() != num_utf8_bytes)) {
        for (unsigned int j = 0; j < current_str.size() - 1; j++) {
650
651
652
          c = current_str[current_str.size() - 1 - j];
          fin.putback(static_cast<char>(c));
        }
653
        current_str.clear();
654
      }
655
    }
656

657
    if (c == '\n' && !newline_consume) {
Brad King's avatar
Brad King committed
658
659
660
      // The current line has been terminated.  Check if the current
      // string matches the requirements.  The length may now be as
      // low as zero since blank lines are allowed.
661
      if (s.length() >= minlen && (!have_regex || regex.find(s.c_str()))) {
Brad King's avatar
Brad King committed
662
        output_size += static_cast<int>(s.size()) + 1;
663
        if (limit_output >= 0 && output_size >= limit_output) {
664
          s.clear();
Brad King's avatar
Brad King committed
665
666
          break;
        }
667
668
        strings.push_back(s);
      }
Brad King's avatar
Brad King committed
669
670

      // Reset the string to empty.
671
      s.clear();
672
    } else if (current_str.empty()) {
673
674
675
      // A non-string character has been found.  Check if the current
      // string matches the requirements.  We require that the length
      // be at least one no matter what the user specified.
676
677
      if (s.length() >= minlen && !s.empty() &&
          (!have_regex || regex.find(s.c_str()))) {
678
        output_size += static_cast<int>(s.size()) + 1;
679
        if (limit_output >= 0 && output_size >= limit_output) {
680
          s.clear();
681
682
          break;
        }
683
684
        strings.push_back(s);
      }
685
686

      // Reset the string to empty.
687
      s.clear();
688
    } else {
689
      s += current_str;
690
    }
Brad King's avatar
Brad King committed
691

692
    if (maxlen > 0 && s.size() == maxlen) {
693
      // Terminate a string if the maximum length is reached.
694
      if (s.length() >= minlen && (!have_regex || regex.find(s.c_str()))) {
Brad King's avatar
Brad King committed
695
        output_size += static_cast<int>(s.size()) + 1;
696
        if (limit_output >= 0 && output_size >= limit_output) {
697
          s.clear();
Brad King's avatar
Brad King committed
698
699
          break;
        }
700
        strings.push_back(s);
Brad King's avatar
Brad King committed
701
      }
702
      s.clear();
Brad King's avatar
Brad King committed
703
    }
704
  }
Brad King's avatar
Brad King committed
705
706
707
708

  // If there is a non-empty current string we have hit the end of the
  // input file or the input size limit.  Check if the current string
  // matches the requirements.
709
710
  if ((!limit_count || strings.size() < limit_count) && !s.empty() &&
      s.length() >= minlen && (!have_regex || regex.find(s.c_str()))) {
Brad King's avatar
Brad King committed
711
    output_size += static_cast<int>(s.size()) + 1;
712
    if (limit_output < 0 || output_size < limit_output) {
Brad King's avatar
Brad King committed
713
714
      strings.push_back(s);
    }
715
  }
Brad King's avatar
Brad King committed
716
717
718
719

  // Encode the result in a CMake list.
  const char* sep = "";
  std::string output;
720
  for (std::string const& sr : strings) {
Brad King's avatar
Brad King committed
721
722
723
724
725
726
    // Separate the strings in the output to make it a list.
    output += sep;
    sep = ";";

    // Store the string in the output, but escape semicolons to
    // make sure it is a list.
727
728
    for (char i : sr) {
      if (i == ';') {
Brad King's avatar
Brad King committed
729
730
        output += '\\';
      }
731
      output += i;
Brad King's avatar
Brad King committed
732
    }
733
  }
Brad King's avatar
Brad King committed
734
735

  // Save the output in a makefile variable.
Stephen Kelly's avatar
Stephen Kelly committed
736
  this->Makefile->AddDefinition(outVar, output.c_str());
Brad King's avatar
Brad King committed
737
738
739
  return true;
}

Andy Cedilnik's avatar
Andy Cedilnik committed
740
bool cmFileCommand::HandleGlobCommand(std::vector<std::string> const& args,