• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PredatorCZ / PreCore / 559

25 Jul 2025 03:19PM UTC coverage: 53.212% (+0.006%) from 53.206%
559

push

github

PredatorCZ
fix file creation exception for newfile

0 of 1 new or added line in 1 file covered. (0.0%)

331 existing lines in 4 files now uncovered.

4241 of 7970 relevant lines covered (53.21%)

10412.33 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

10.19
/src/app/in_context.cpp
1
/*  Spike is universal dedicated module handler
2
    This source contains context for input data
3

4
    Copyright 2021-2023 Lukas Cone
5

6
    Licensed under the Apache License, Version 2.0 (the "License");
7
    you may not use this file except in compliance with the License.
8
    You may obtain a copy of the License at
9

10
        http://www.apache.org/licenses/LICENSE-2.0
11

12
    Unless required by applicable law or agreed to in writing, software
13
    distributed under the License is distributed on an "AS IS" BASIS,
14
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
    See the License for the specific language governing permissions and
16
    limitations under the License.
17
*/
18

19
#include "spike/app/context.hpp"
20
#include "spike/app/out_context.hpp"
21
#include "spike/app/texel.hpp"
22
#include "spike/app/tmp_storage.hpp"
23
#include "spike/format/ZIP_istream.inl"
24
#include "spike/io/binreader.hpp"
25
#include "spike/io/binwritter.hpp"
26
#include "spike/io/directory_scanner.hpp"
27
#include "spike/io/fileinfo.hpp"
28
#include "spike/io/stat.hpp"
29
#include "spike/master_printer.hpp"
30
#include <list>
31
#include <mutex>
32
#include <optional>
33
#include <spanstream>
34

35
static std::mutex simpleIOLock;
36

37
const std::vector<std::string> &AppContextShare::SupplementalFiles() {
×
38
  if (!supplementals) {
×
39
    throw std::runtime_error(
×
40
        "Invalid call of SupplementalFiles, module is not for batch.");
×
41
  }
42

43
  return supplementals.value();
×
44
}
45

46
const std::vector<std::string> &ZIPIOContext::SupplementalFiles() {
×
47
  if (!supplementals) {
×
48
    throw std::runtime_error(
×
49
        "Invalid call of SupplementalFiles, module is not for batch.");
×
50
  }
51

52
  return supplementals.value();
×
53
}
54

55
struct AppContextShareImpl : AppContextShare {
56
  NewTexelContext *NewImage(NewTexelContextCreate ctx,
56✔
57
                            const std::string *path) override {
58
    if (texelContext) {
56✔
59
      texelContext->Finish();
28✔
60
    }
61

62
    texelContext = CreateTexelContext(ctx, this);
56✔
63

64
    if (path) {
56✔
65
      texelContext->pathOverride.Load(*path);
28✔
66
    }
67

68
    return texelContext.get();
56✔
69
  }
70

71
  std::pair<std::string, size_t> IdealPath(const std::string &path) {
190✔
72
    std::string filePath;
73
    size_t delimeter = 0;
74

75
    if (basePathParts.empty()) {
190✔
76
      filePath = std::string(basePath.GetFullPath()) + path;
380✔
77
      delimeter = basePath.GetFullPath().size();
78
    } else {
79
      std::string pathCopy(path);
80

81
      if (substPath.size() && path.starts_with(substPath)) {
×
82
        pathCopy.erase(0, substPath.size());
×
83
      }
84
      AFileInfo pathInfo(pathCopy);
×
85
      auto exploded = pathInfo.Explode();
×
86
      const size_t numItems = std::min(exploded.size(), basePathParts.size());
×
87

88
      if (basePath.GetFullPath().at(0) == '/') {
×
89
        filePath.push_back('/');
×
90
      }
91

92
      size_t i = 0;
93

94
      for (; i < numItems; i++) {
×
95
        if (basePathParts[i] == exploded[i]) {
×
96
          filePath.append(exploded[i]);
97
          filePath.push_back('/');
×
98
          continue;
99
        }
100

101
        break;
102
      }
103

104
      for (size_t j = i; j < basePathParts.size(); j++) {
×
105
        filePath.append(basePathParts[j]);
106
        filePath.push_back('/');
×
107
      }
108

109
      delimeter = filePath.size();
110

111
      for (; i < exploded.size(); i++) {
×
112
        filePath.append(exploded[i]);
113
        filePath.push_back('/');
×
114
      }
115

116
      filePath.pop_back();
117
    }
118

119
    return {filePath, delimeter};
190✔
120
  }
121

122
  NewFileContext NewFile(const std::string &path) override {
190✔
123
    const auto [filePath, delimeter] = IdealPath(path);
190✔
124

125
    try {
126
      outFile = BinWritter(filePath);
379✔
127
    } catch (const es::FileInvalidAccessError &e) {
1✔
128
      mkdirs(filePath);
1✔
129
      outFile = BinWritter(filePath);
1✔
130
    }
1✔
131

132
    if (forEachFile) {
190✔
133
      forEachFile();
134
    }
135

136
    return {outFile.BaseStream(), filePath, delimeter};
190✔
137
  }
380✔
138

UNCOV
139
  void BaseOutputPath(std::string basePath_) override {
×
140
    if (size_t found = basePath_.find_first_of(':'); found != basePath_.npos) {
×
141
      substPath = basePath_.substr(0, found);
×
UNCOV
142
      basePath_.erase(0, found + 1);
×
143
    }
144

145
    if (basePath_.empty()) {
×
UNCOV
146
      throw std::runtime_error("Output path cannot be empty");
×
147
    }
148

UNCOV
149
    if (basePath_.back() != '/') {
×
UNCOV
150
      basePath_.push_back('/');
×
151
    }
UNCOV
152
    basePath = AFileInfo(basePath_);
×
153
    basePathParts = basePath.Explode();
×
154
  }
155

UNCOV
156
  JenHash Hash() override { return JenHash(FullPath()); }
×
157

UNCOV
158
  std::string FullPath() override {
×
UNCOV
159
    return std::string(basePath.GetFullPath()) +
×
UNCOV
160
           std::string(workingFile.GetFullPath());
×
161
  }
162

163
  ~AppContextShareImpl() {
28✔
164
    if (texelContext) {
28✔
165
      texelContext->Finish();
28✔
166
    }
167
  }
56✔
UNCOV
168

×
169
  BinWritter outFile;
170
  AFileInfo basePath;
171
  std::string substPath;
172
  std::vector<std::string_view> basePathParts;
173
  std::unique_ptr<NewTexelContextImpl> texelContext;
28✔
174
};
28✔
175

28✔
176
struct SimpleIOContext : AppContextShareImpl {
177
  SimpleIOContext(const std::string &path,
56✔
178
                  std::optional<std::vector<std::string>> supplementals_) {
179
    mainFile.Open(path);
180
    workingFile.Load(path);
181
    supplementals = std::move(supplementals_);
182

183
    if (!cliSettings.out.empty()) {
184
      BaseOutputPath(cliSettings.out);
185
    }
186
  }
187
  std::istream *OpenFile(const std::string &path);
28✔
188

924✔
189
  AppContextStream RequestFile(const std::string &path) override;
28✔
190

28✔
191
  AppContextFoundStream FindFile(const std::string &rootFolder,
192
                                 const std::string &pattern) override;
193
  std::istream &GetStream() override;
28✔
UNCOV
194
  std::string GetBuffer(size_t size, size_t begin) override;
×
195

196
  void DisposeFile(std::istream *str) override;
28✔
197

198
  AppExtractContext *ExtractContext(std::string_view name) override {
199
    if (ectx) [[unlikely]] {
200
      return ectx.get();
201
    }
202

203
    std::string arcPath(workingFile.GetFolder());
204
    arcPath.append(name);
205

206
    auto [outPath, delimeter] = IdealPath(arcPath);
207

208
    if (mainSettings.extractSettings.makeZIP) {
×
UNCOV
209
      if (workingFile.GetExtension() == ".zip") {
×
UNCOV
210
        outPath.append("_out");
×
211
      }
212

213
      outPath.append(".zip");
×
214

215
      auto uniq = std::make_unique<ZIPExtactContext>(outPath);
UNCOV
216
      uniq->forEachFile = forEachFile;
×
217
      ectx = std::move(uniq);
218
    } else {
×
UNCOV
219
      if (!mainSettings.extractSettings.folderPerArc) {
×
220
        outPath = workingFile.GetFolder();
×
221
      } else {
222
        es::mkdir(outPath);
UNCOV
223
        outPath.push_back('/');
×
224
      }
UNCOV
225

×
UNCOV
226
      auto uniq = std::make_unique<IOExtractContext>(outPath);
×
227
      uniq->forEachFile = forEachFile;
228
      ectx = std::move(uniq);
UNCOV
229
    }
×
230

231
    return ectx.get();
232
  }
UNCOV
233

×
234
  AppExtractContext *ExtractContext() override {
235
    return ExtractContext(workingFile.GetFilename());
UNCOV
236
  }
×
UNCOV
237

×
238
  void Finish() override {
239
    if (ectx && mainSettings.extractSettings.makeZIP) {
240
      static_cast<ZIPExtactContext *>(ectx.get())->FinishZIP([] {
241
        printinfo("Generating cache.");
242
      });
243
    }
244
  }
×
UNCOV
245

×
246
private:
247
  BinReader mainFile;
UNCOV
248
  BinReader streamedFiles[32];
×
UNCOV
249
  uint32 usedFiles = 0;
×
250
  std::unique_ptr<AppExtractContext> ectx;
UNCOV
251
};
×
UNCOV
252

×
253
std::istream *SimpleIOContext::OpenFile(const std::string &path) {
254
  std::lock_guard<std::mutex> guard(simpleIOLock);
255
  for (size_t b = 0; b < 32; b++) {
256
    uint32 bit = 1 << b;
257
    if (!(usedFiles & bit)) {
258
      streamedFiles[b].Open(path);
259
      usedFiles ^= bit;
260
      return &streamedFiles[b].BaseStream();
261
    }
262
  }
263

×
264
  throw std::out_of_range("Maximum opened files reached!");
265
}
×
UNCOV
266

×
UNCOV
267
AppContextStream SimpleIOContext::RequestFile(const std::string &path) {
×
UNCOV
268
  AFileInfo wFile(workingFile);
×
269
  AFileInfo pFile(path);
×
UNCOV
270
  auto catchedFile = pFile.CatchBranch(wFile.GetFolder());
×
271
  return {OpenFile(catchedFile), this};
272
}
273

274
AppContextFoundStream SimpleIOContext::FindFile(const std::string &rootFolder,
×
275
                                                const std::string &pattern) {
276
  DirectoryScanner sc;
UNCOV
277
  sc.AddFilter(pattern);
×
278
  sc.Scan(rootFolder);
279

×
UNCOV
280
  if (sc.Files().empty()) {
×
281
    throw es::FileNotFoundError(pattern);
×
282
  } else if (sc.Files().size() > 1) {
283
    std::string *winner = nullptr;
UNCOV
284
    size_t minFolder = 0x10000;
×
285
    size_t minLevel = 0x10000;
286

×
287
    for (auto &f : sc) {
×
UNCOV
288
      size_t foundIdx = f.find_last_of('/');
×
289

UNCOV
290
      if (foundIdx == f.npos) {
×
UNCOV
291
        throw std::runtime_error("Too many files found for pattern: " +
×
292
                                 pattern);
×
293
      }
294

295
      if (foundIdx < minFolder) {
296
        winner = &f;
297
        minFolder = foundIdx;
×
298
        minLevel = std::count(f.begin(), f.end(), '/');
299
      } else if (foundIdx == minFolder) {
300
        if (size_t clevel = std::count(f.begin(), f.end(), '/');
×
UNCOV
301
            clevel < minLevel) {
×
UNCOV
302
          winner = &f;
×
303
          minFolder = foundIdx;
304
          minLevel = clevel;
305
        } else if (clevel == minLevel) {
×
306
          throw std::runtime_error("Too many files found for pattern: " +
307
                                   pattern);
UNCOV
308
        }
×
UNCOV
309
      }
×
310
    }
×
311

312
    if (!winner) {
313
      throw std::runtime_error("Too many files found for pattern: " + pattern);
314
    }
UNCOV
315

×
UNCOV
316
    return {OpenFile(*winner), this, AFileInfo(*winner)};
×
317
  }
×
318

319
  return {OpenFile(sc.Files().front()), this, AFileInfo(sc.Files().front())};
320
}
321

UNCOV
322
void SimpleIOContext::DisposeFile(std::istream *str) {
×
UNCOV
323
  size_t index = 0;
×
324

325
  for (auto &f : streamedFiles) {
UNCOV
326
    if (&f.BaseStream() == str) {
×
327
      uint32 bit = 1 << index;
328

UNCOV
329
      if (!(usedFiles & bit)) {
×
330
        throw std::runtime_error("Stream already freed.");
331
      }
332

×
333
      es::Dispose(f);
334
      usedFiles ^= bit;
335
      return;
×
UNCOV
336
    }
×
UNCOV
337

×
338
    index++;
339
  }
×
340

×
341
  throw std::runtime_error("Requested stream not found!");
342
}
343

×
UNCOV
344
std::istream &SimpleIOContext::GetStream() { return mainFile.BaseStream(); }
×
UNCOV
345

×
346
std::string SimpleIOContext::GetBuffer(size_t size, size_t begin) {
347
  mainFile.Push();
UNCOV
348
  mainFile.Seek(begin);
×
349
  std::string buffer;
350
  mainFile.ReadContainer(
351
      buffer, size == size_t(-1) ? (mainFile.GetSize() - begin) : size);
×
352
  mainFile.Pop();
353

354
  return buffer;
28✔
355
}
356

×
UNCOV
357
std::shared_ptr<AppContextShare>
×
358
MakeIOContext(const std::string &path,
359
              std::optional<std::vector<std::string>> supplementals) {
UNCOV
360
  return std::make_unique<SimpleIOContext>(path, supplementals);
×
UNCOV
361
}
×
362

363
struct ZIPIOContextInstance : AppContextShareImpl {
UNCOV
364
  ZIPIOContextInstance(const ZIPIOContextInstance &) = delete;
×
365
  ZIPIOContextInstance(ZIPIOContextInstance &&) = delete;
366
  ZIPIOContextInstance(ZIPIOContext *base_, ZIPIOEntry entry_)
367
      : base(base_), entry(entry_) {
368
    workingFile.Load(entry_.AsView());
28✔
369
    BaseOutputPath(base->basePath);
370
  }
56✔
371

372
  ~ZIPIOContextInstance() {
373
    if (stream) {
374
      base->DisposeFile(stream);
375
    }
UNCOV
376
  }
×
377

×
378
  AppContextStream RequestFile(const std::string &path) override {
×
379
    return base->RequestFile(path);
×
380
  }
381
  void DisposeFile(std::istream *file) override { base->DisposeFile(file); }
382
  AppContextFoundStream FindFile(const std::string &rootFolder,
×
UNCOV
383
                                 const std::string &pattern) override {
×
UNCOV
384
    return base->FindFile(rootFolder, pattern);
×
385
  }
386

387
  std::istream &GetStream() override {
×
388
    if (!stream) {
389
      stream = base->OpenFile(entry);
390
    }
391
    return *stream;
UNCOV
392
  }
×
393
  std::string GetBuffer(size_t size, size_t begin) override {
×
394
    BinReaderRef rd(GetStream());
×
395
    rd.Push();
396
    rd.Seek(begin);
397
    std::string buffer;
UNCOV
398
    rd.ReadContainer(buffer,
×
399
                     size == size_t(-1) ? (rd.GetSize() - begin) : size);
×
400
    rd.Pop();
UNCOV
401
    return buffer;
×
402
  }
×
403

404
  AppExtractContext *ExtractContext(std::string_view name) override {
×
405
    if (ectx) [[unlikely]] {
406
      return ectx.get();
UNCOV
407
    }
×
408

×
409
    if (mainSettings.extractSettings.makeZIP) {
×
410
      base->InitMerger();
UNCOV
411
      entriesPath = RequestTempFile();
×
412
      auto uniq = std::make_unique<ZIPExtactContext>(entriesPath, false);
413
      if (mainSettings.extractSettings.folderPerArc) {
×
414
        uniq->prefixPath = workingFile.GetFolder();
×
415
        uniq->prefixPath += name;
416
        uniq->prefixPath.push_back('/');
417
      }
UNCOV
418
      uniq->forEachFile = forEachFile;
×
419
      ectx = std::move(uniq);
×
420
    } else {
421
      std::string outPath;
×
422
      if (!mainSettings.extractSettings.folderPerArc) {
423
        outPath = workingFile.GetFolder();
424
      } else {
×
425
        outPath = std::string(basePath.GetFullPath());
×
426
        outPath += workingFile.GetFolder();
×
427
        outPath += name;
428
        mkdirs(outPath);
429
        outPath.push_back('/');
×
UNCOV
430
      }
×
431

×
UNCOV
432
      auto uniq = std::make_unique<IOExtractContext>(outPath);
×
433
      uniq->forEachFile = forEachFile;
×
UNCOV
434
      ectx = std::move(uniq);
×
435
    }
UNCOV
436

×
437
    return ectx.get();
UNCOV
438
  }
×
439

440
  AppExtractContext *ExtractContext() override {
441
    return ExtractContext(workingFile.GetFilename());
UNCOV
442
  }
×
443

444
  void Finish() override {
UNCOV
445
    if (ectx && mainSettings.extractSettings.makeZIP) {
×
446
      base->Merge(static_cast<ZIPExtactContext *>(ectx.get()), entriesPath);
447
      es::Dispose(ectx);
448
      es::RemoveFile(entriesPath);
×
UNCOV
449
    }
×
450
  }
451

452
  ZIPIOContext *base;
×
UNCOV
453
  std::istream *stream = nullptr;
×
454
  ZipEntry entry;
455
  std::unique_ptr<AppExtractContext> ectx;
456
  std::string entriesPath;
UNCOV
457
};
×
458

459
std::shared_ptr<AppContextShare> ZIPIOContext::Instance(ZIPIOEntry entry) {
460
  return std::make_unique<ZIPIOContextInstance>(this, entry);
×
461
}
×
462

463
static std::mutex ZIPLock;
UNCOV
464

×
UNCOV
465
struct ZIPDataHolder {
×
UNCOV
466
  virtual ~ZIPDataHolder() = default;
×
467
};
UNCOV
468

×
469
struct ZIPIOContext_implbase : ZIPIOContext {
470
  ZIPIOContext_implbase(const std::string &file) : zipMount(file) {}
471
  std::istream *OpenFile(const ZipEntry &entry) override;
472
  std::string GetChunk(const ZipEntry &entry, size_t offset,
473
                       size_t size) const override;
474
  void DisposeFile(std::istream *str) override;
475

476
  void Merge(ZIPExtactContext *eCtx, const std::string &records) override {
477
    std::lock_guard<std::mutex> lg(mergerMtx);
478
    merger->Merge(*eCtx, records);
UNCOV
479
  }
×
UNCOV
480

×
481
  void InitMerger() override {
482
    std::lock_guard<std::mutex> lg(mergerMtx);
483
    if (!merger) {
484
      merger.emplace(basePath + "_out.zip", RequestTempFile());
485
    }
486
  }
487

488
  void Finish() override {
489
    if (merger) {
UNCOV
490
      merger->FinishMerge([] { printinfo("Generating cache."); });
×
491
    }
492
  }
493

494
protected:
495
  std::list<std::spanstream> openedFiles;
496
  es::MappedFile zipMount;
×
497
  std::optional<ZIPMerger> merger;
×
498
  std::mutex mergerMtx;
×
499
};
500

UNCOV
501
struct ZIPMemoryStream : ZIPDataHolder {
×
UNCOV
502
  std::istringstream stream;
×
503

×
504
  ZIPMemoryStream(std::string &&input)
×
505
      : stream(input, std::ios::in | std::ios::binary) {}
506
};
507

UNCOV
508
struct ZIPFileStream : ZIPDataHolder {
×
UNCOV
509
  BinReader rd;
×
UNCOV
510
  std::string path;
×
511
  ZIPFileStream(const std::string &path_) : rd(path_), path(path_) {}
512
  ~ZIPFileStream() {
513
    es::Dispose(rd);
514
    try {
515
      es::RemoveFile(path);
516
    } catch (const std::exception &e) {
517
      printerror(e.what());
518
    }
519
  }
520
};
521

522
std::istream *ZIPIOContext_implbase::OpenFile(const ZipEntry &entry) {
523
  auto dataBegin = static_cast<char *>(zipMount.data) + entry.offset;
524
  auto dataEnd = dataBegin + entry.size;
525

526
  std::lock_guard<std::mutex> guard(ZIPLock);
527
  auto &str = openedFiles.emplace_back(std::span<char>(dataBegin, dataEnd),
528
                                       std::ios::binary | std::ios::in);
529
  return &str;
530
}
531

532
std::string ZIPIOContext_implbase::GetChunk(const ZipEntry &entry,
533
                                            size_t offset, size_t size) const {
534
  auto dataBegin = static_cast<char *>(zipMount.data) + entry.offset + offset;
535
  auto dataEnd = dataBegin + size;
536

537
  return {dataBegin, dataEnd};
538
}
539

540
void ZIPIOContext_implbase::DisposeFile(std::istream *str) {
541
  std::lock_guard<std::mutex> guard(ZIPLock);
542
  openedFiles.remove_if([&](auto &spanStr) {
×
543
    return static_cast<std::istream *>(&spanStr) == str;
×
544
  });
×
545
}
546

547
struct ZIPIOContextIter_impl : ZIPIOEntryRawIterator {
×
UNCOV
548
  using map_type = std::map<std::string_view, ZipEntry>;
×
549
  ZIPIOContextIter_impl(const map_type &map)
×
550
      : base(&map), current(map.begin()), end(map.end()) {}
551
  ZIPIOEntry Fist() const override {
552
    if (current == end) {
×
553
      return {};
UNCOV
554
    }
×
555
    return {current->second, current->first};
×
556
  }
557
  ZIPIOEntry Next() const override {
×
558
    if (current == end) {
559
      return {};
UNCOV
560
    }
×
561
    current++;
UNCOV
562
    if (current == end) {
×
UNCOV
563
      return {};
×
564
    }
565

566
    return {current->second, current->first};
567
  }
568
  size_t Count() const override { return base->size(); }
569

UNCOV
570
  const map_type *base;
×
UNCOV
571
  mutable map_type::const_iterator current;
×
572
  map_type::const_iterator end;
×
573
};
×
574

575
struct ZIPIOContext_impl : ZIPIOContext_implbase {
576
  AppContextStream RequestFile(const std::string &path) override;
577

×
578
  AppContextFoundStream FindFile(const std::string &rootFolder,
×
UNCOV
579
                                 const std::string &pattern) override;
×
580

581
  ZIPIOContextIterator Iter(ZIPIOEntryType) const override {
UNCOV
582
    return {std::make_unique<ZIPIOContextIter_impl>(vfs)};
×
583
  }
×
584

585
  ZIPIOContext_impl(const std::string &file, const PathFilter &pathFilter_,
586
                    const PathFilter &moduleFilter_)
587
      : ZIPIOContext_implbase(file), pathFilter(&pathFilter_),
UNCOV
588
        moduleFilter(&moduleFilter_) {
×
589
    Read();
590
    pathFilter = moduleFilter = nullptr;
591
  }
592

593
  ZIPIOContext_impl(const std::string &file) : ZIPIOContext_implbase(file) {
594
    Read();
595
  }
596

597
private:
598
  void Read();
599
  const PathFilter *pathFilter = nullptr;
600
  const PathFilter *moduleFilter = nullptr;
UNCOV
601
  std::map<std::string_view, ZipEntry> vfs;
×
602
};
×
603

604
AppContextStream ZIPIOContext_impl::RequestFile(const std::string &path) {
605
  auto found = vfs.find(path);
×
606

UNCOV
607
  if (es::IsEnd(vfs, found)) {
×
608
    throw es::FileNotFoundError(path);
×
609
  }
×
UNCOV
610

×
611
  return {OpenFile(found->second), this};
612
}
UNCOV
613

×
UNCOV
614
AppContextFoundStream ZIPIOContext_impl::FindFile(const std::string &,
×
615
                                                  const std::string &pattern) {
616
  PathFilter filter;
617
  filter.AddFilter(pattern);
618

619
  for (auto &f : vfs) {
620
    std::string_view kvi(f.first);
621
    size_t lastSlash = kvi.find_last_of('/');
622
    kvi.remove_prefix(lastSlash + 1);
623

UNCOV
624
    if (filter.IsFiltered(kvi)) {
×
UNCOV
625
      return {OpenFile(f.second), this, AFileInfo(f.first)};
×
626
    }
UNCOV
627
  }
×
UNCOV
628

×
629
  throw es::FileNotFoundError(pattern);
630
}
631

×
632
// Warning: Unaligned accesses
633
// Note: Multiple central directories? (unlikely)
634
void ZIPIOContext_impl::Read() {
×
635
  auto curEnd = static_cast<char *>(zipMount.data) + zipMount.fileSize -
UNCOV
636
                (sizeof(ZIPCentralDir) - 2);
×
637
  auto curLocator = reinterpret_cast<const ZIPCentralDir *>(curEnd);
×
638

639
  if (curLocator->id != ZIPCentralDir::ID) {
×
640
    int numIters = 4096;
×
641
    while (numIters > 0) {
UNCOV
642
      curEnd--;
×
643
      numIters--;
644

×
UNCOV
645
      curLocator = reinterpret_cast<const ZIPCentralDir *>(curEnd);
×
646
      if (curLocator->id == ZIPCentralDir::ID) {
647
        break;
648
      }
649
    }
×
650
  }
651

652
  if (curLocator->id != ZIPCentralDir::ID) {
653
    throw std::runtime_error("Cannot find ZIP central directory");
654
  }
×
UNCOV
655

×
656
  uint64 dirOffset = 0;
657
  uint64 numEntries = 0;
658
  uint64 dirSize = 0;
UNCOV
659

×
660
  if (curLocator->dirOffset == -1U || curLocator->numDirEntries == uint16(-1) ||
661
      curLocator->dirSize == -1U) {
×
UNCOV
662
    curEnd -= sizeof(ZIP64CentralDir) - 8;
×
UNCOV
663
    auto curLocatorX64 = reinterpret_cast<const ZIP64CentralDir *>(curEnd);
×
664
    if (curLocatorX64->id != ZIP64CentralDir::ID) {
665
      int numIters = 4096;
UNCOV
666
      while (numIters > 0) {
×
667
        curEnd--;
668
        numIters--;
669

670
        curLocatorX64 = reinterpret_cast<const ZIP64CentralDir *>(curEnd);
671
        if (curLocatorX64->id == ZIP64CentralDir::ID) {
UNCOV
672
          break;
×
UNCOV
673
        }
×
674
      }
675
    }
676

677
    if (curLocatorX64->id != ZIP64CentralDir::ID) {
678
      throw std::runtime_error("Cannot find ZIPx64 central directory");
679
    }
UNCOV
680

×
681
    std::spanstream entriesSpan(
×
682
        std::span<char>(curEnd,
×
683
                        static_cast<char *>(zipMount.data) + zipMount.fileSize),
UNCOV
684
        std::ios::binary | std::ios::in);
×
685
    BinReaderRef rd(entriesSpan);
686
    ZIP64CentralDir x64CentraDir;
×
UNCOV
687
    rd.Read(x64CentraDir);
×
UNCOV
688

×
689
    dirOffset = x64CentraDir.dirOffset;
690
    numEntries = x64CentraDir.numDirEntries;
UNCOV
691
    dirSize = x64CentraDir.dirSize;
×
692
  } else {
693
    dirOffset = curLocator->dirOffset;
694
    numEntries = curLocator->numDirEntries;
695
    dirSize = curLocator->dirSize;
696
  }
UNCOV
697

×
UNCOV
698
  auto entriesBegin = static_cast<char *>(zipMount.data) + dirOffset;
×
699
  auto entriesEnd = entriesBegin + dirSize;
700
  std::spanstream entriesSpan(std::span<char>(entriesBegin, entriesEnd),
701
                              std::ios::binary | std::ios::in);
702
  BinReaderRef rd(entriesSpan);
703
  std::spanstream localStreamSpan(
704
      std::span<char>(static_cast<char *>(zipMount.data), entriesBegin),
×
705
      std::ios::binary | std::ios::in);
706
  BinReaderRef localRd(localStreamSpan);
707

708
  for (size_t d = 0; d < numEntries; d++) {
709
    uint32 id;
×
710
    rd.Push();
×
UNCOV
711
    rd.Read(id);
×
UNCOV
712
    rd.Pop();
×
713

×
UNCOV
714
    switch (id) {
×
UNCOV
715
    case ZIPFile::ID: {
×
716
      ZIPFile hdr;
717
      rd.Read(hdr);
UNCOV
718

×
719
      [&] {
720
        std::string_view entryName(entriesBegin + rd.Tell(), hdr.fileNameSize);
UNCOV
721
        rd.Skip(hdr.fileNameSize);
×
722

723
        if (!hdr.compressedSize) {
UNCOV
724
          return;
×
UNCOV
725
        }
×
726

727
        if (hdr.flags[ZIPLocalFlag::Encrypted]) {
UNCOV
728
          throw std::runtime_error("ZIP cannot have encrypted files!");
×
729
        }
730

731
        if (hdr.compression != ZIPCompressionMethod::Store) {
732
          throw std::runtime_error("ZIP cannot have compressed files!");
733
        }
734

×
735
        if (!hdr.fileNameSize) {
736
          throw std::runtime_error("ZIP local file's path must be specified!");
UNCOV
737
        }
×
738

739
        size_t entrySize = hdr.compressedSize;
×
UNCOV
740
        size_t localOffset = hdr.localHeaderOffset;
×
UNCOV
741

×
742
        if (hdr.compressedSize == -1U || hdr.uncompressedSize == -1U ||
743
            hdr.localHeaderOffset == -1U) {
×
UNCOV
744
          const size_t extraEnd = rd.Push() + hdr.extraFieldSize;
×
745

746
          while (rd.Tell() < extraEnd) {
747
            ZIP64Extra extra;
×
UNCOV
748
            rd.Read(extra.id);
×
749
            rd.Read(extra.size);
750

751
            if (extra.id == 1) {
×
UNCOV
752
              if (hdr.uncompressedSize == -1U) {
×
753
                rd.Read(extra.uncompressedSize);
754
                entrySize = extra.uncompressedSize;
755
              }
×
UNCOV
756
              if (hdr.compressedSize == -1U) {
×
757
                rd.Read(extra.compressedSize);
758
              }
759
              if (hdr.localHeaderOffset == -1U) {
×
UNCOV
760
                rd.Read(extra.localHeaderOffset);
×
761
                localOffset = extra.localHeaderOffset;
UNCOV
762
              }
×
763
              break;
UNCOV
764
            } else {
×
765
              rd.Skip(extra.size);
766
            }
×
767
          }
768

769
          rd.Pop();
770
        }
771

×
UNCOV
772
        if (pathFilter && !pathFilter->IsFiltered(entryName)) {
×
773
          return;
774
        }
×
775

776
        if (moduleFilter && !moduleFilter->IsFiltered(entryName)) {
×
777
          return;
778
        }
UNCOV
779

×
780
        ZIPLocalFile localEntry;
UNCOV
781
        localRd.Seek(localOffset);
×
782
        localRd.Read(localEntry);
UNCOV
783
        ZipEntry entry;
×
784
        entry.size = entrySize;
UNCOV
785
        entry.offset = localRd.Tell() + localEntry.extraFieldSize +
×
786
                       localEntry.fileNameSize;
787
        vfs.emplace(entryName, entry);
788
      }();
789

790
      rd.Skip(hdr.extraFieldSize + hdr.fileCommentSize);
791
      break;
UNCOV
792
    }
×
793
    default:
794
      using std::to_string;
795
      throw std::runtime_error("Invalid dir entry " + to_string(id) + " at " +
796
                               to_string(rd.Tell() + dirOffset));
×
797
    }
798
  }
799
}
800

801
ZIPIOEntry::operator bool() const {
×
802
  return std::visit([](auto &name) { return !name.empty(); }, name);
×
803
}
UNCOV
804

×
805
struct ZIPIOContextCached : ZIPIOContext_implbase {
×
UNCOV
806
  AppContextStream RequestFile(const std::string &path) override {
×
UNCOV
807

×
808
    auto found = cache.RequestFile(path);
×
809

810
    if (!found.size) {
×
811
      throw es::FileNotFoundError(path);
812
    }
UNCOV
813

×
814
    return {OpenFile(found), this};
UNCOV
815
  }
×
816

×
817
  AppContextFoundStream FindFile(const std::string &,
818
                                 const std::string &pattern) override {
819
    auto found = cache.FindFile(pattern);
820

821
    if (!found.size) {
×
UNCOV
822
      throw es::FileNotFoundError(pattern);
×
823
    }
824

825
    return std::visit(
826
        [&](auto &item) -> AppContextFoundStream {
×
827
          return {OpenFile(found), this, AFileInfo(item)};
UNCOV
828
        },
×
829
        found.name);
UNCOV
830
  }
×
UNCOV
831

×
832
  ZIPIOContextIterator Iter(ZIPIOEntryType type) const override {
833
    return {cache.Iter(type)};
834
  }
×
835

836
  ZIPIOContextCached(const std::string &file, es::MappedFile &&cacheFile)
837
      : ZIPIOContext_implbase(file), cacheMount(std::move(cacheFile)) {
×
838
    cache.Mount(cacheMount.data);
UNCOV
839
    auto &cacheHdr = reinterpret_cast<const CacheBaseHeader &>(cache.Header());
×
840
    auto zipData = static_cast<const char *>(zipMount.data);
841
    auto zipHeader = reinterpret_cast<const CacheBaseHeader *>(
×
842
        zipData + cacheHdr.zipCheckupOffset);
×
843

844
    if (memcmp(zipHeader, &cacheHdr, sizeof(cacheHdr))) {
845
      throw std::runtime_error("Cache header and zip checkup are different.");
UNCOV
846
    }
×
847
  }
×
848

UNCOV
849
private:
×
850
  Cache cache;
×
851
  es::MappedFile cacheMount;
UNCOV
852
};
×
853

×
854
std::unique_ptr<ZIPIOContext> MakeZIPContext(const std::string &file,
UNCOV
855
                                             const PathFilter &pathFilter,
×
856
                                             const PathFilter &moduleFilter) {
857
  return std::make_unique<ZIPIOContext_impl>(file, pathFilter, moduleFilter);
858
}
×
859

×
860
std::unique_ptr<ZIPIOContext> MakeZIPContext(const std::string &file) {
861
  std::string cacheFile = file + ".cache";
862
  es::MappedFile mf;
×
863
  try {
×
UNCOV
864
    mf = es::MappedFile(cacheFile);
×
865
  } catch (const std::exception &e) {
×
866
    printwarning("Failed loading cache: " << e.what());
×
UNCOV
867
    return std::make_unique<ZIPIOContext_impl>(file);
×
UNCOV
868
  }
×
869
  try {
UNCOV
870
    printinfo("Found zip cache: " << cacheFile);
×
UNCOV
871
    return std::make_unique<ZIPIOContextCached>(file, std::move(mf));
×
872
  } catch (const std::exception &e) {
873
    printwarning("Failed loading cache: " << e.what());
874
    return std::make_unique<ZIPIOContext_impl>(file);
875
  }
876
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc