Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: CPL - Common Portability Library
4 : * Purpose: Implement VSI large file api for archive files.
5 : * Author: Even Rouault, even.rouault at spatialys.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2010-2014, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_port.h"
14 : #include "cpl_vsi_virtual.h"
15 :
16 : #include <cstring>
17 : #include <ctime>
18 : #include <fcntl.h>
19 : #include <map>
20 : #include <memory>
21 : #include <set>
22 : #include <string>
23 : #include <utility>
24 : #include <vector>
25 :
26 : #include "cpl_conv.h"
27 : #include "cpl_error.h"
28 : #include "cpl_multiproc.h"
29 : #include "cpl_string.h"
30 : #include "cpl_vsi.h"
31 :
32 : //! @cond Doxygen_Suppress
33 :
34 43701 : static bool IsEitherSlash(char c)
35 : {
36 43701 : return c == '/' || c == '\\';
37 : }
38 :
39 : /************************************************************************/
40 : /* ~VSIArchiveEntryFileOffset() */
41 : /************************************************************************/
42 :
43 : VSIArchiveEntryFileOffset::~VSIArchiveEntryFileOffset() = default;
44 :
45 : /************************************************************************/
46 : /* ~VSIArchiveReader() */
47 : /************************************************************************/
48 :
49 : VSIArchiveReader::~VSIArchiveReader() = default;
50 :
51 : /************************************************************************/
52 : /* ~VSIArchiveContent() */
53 : /************************************************************************/
54 :
55 : VSIArchiveContent::~VSIArchiveContent() = default;
56 :
57 : /************************************************************************/
58 : /* VSIArchiveFilesystemHandler() */
59 : /************************************************************************/
60 :
61 : VSIArchiveFilesystemHandler::VSIArchiveFilesystemHandler() = default;
62 :
63 : /************************************************************************/
64 : /* ~VSIArchiveFilesystemHandler() */
65 : /************************************************************************/
66 :
67 : VSIArchiveFilesystemHandler::~VSIArchiveFilesystemHandler() = default;
68 :
69 : /************************************************************************/
70 : /* GetStrippedFilename() */
71 : /************************************************************************/
72 :
73 5260 : static std::string GetStrippedFilename(const std::string &osFileName,
74 : bool &bIsDir)
75 : {
76 5260 : bIsDir = false;
77 5260 : int nStartPos = 0;
78 :
79 : // Remove ./ pattern at the beginning of a filename.
80 5260 : if (osFileName.size() >= 2 && osFileName[0] == '.' && osFileName[1] == '/')
81 : {
82 0 : nStartPos = 2;
83 0 : if (osFileName.size() == 2)
84 0 : return std::string();
85 : }
86 :
87 10520 : std::string ret(osFileName, nStartPos);
88 186095 : for (char &c : ret)
89 : {
90 180835 : if (c == '\\')
91 0 : c = '/';
92 : }
93 :
94 5260 : bIsDir = !ret.empty() && ret.back() == '/';
95 5260 : if (bIsDir)
96 : {
97 : // Remove trailing slash.
98 241 : ret.pop_back();
99 : }
100 5260 : return ret;
101 : }
102 :
103 : /************************************************************************/
104 : /* BuildDirectoryIndex() */
105 : /************************************************************************/
106 :
107 225 : static void BuildDirectoryIndex(VSIArchiveContent *content)
108 : {
109 225 : content->dirIndex.clear();
110 225 : const int nEntries = static_cast<int>(content->entries.size());
111 6598 : for (int i = 0; i < nEntries; i++)
112 : {
113 6373 : const char *fileName = content->entries[i].fileName.c_str();
114 12746 : std::string parentDir = CPLGetPathSafe(fileName);
115 6373 : content->dirIndex[parentDir].push_back(i);
116 : }
117 225 : }
118 :
119 : /************************************************************************/
120 : /* GetContentOfArchive() */
121 : /************************************************************************/
122 :
123 : const VSIArchiveContent *
124 8642 : VSIArchiveFilesystemHandler::GetContentOfArchive(const char *archiveFilename,
125 : VSIArchiveReader *poReader)
126 : {
127 17284 : std::unique_lock oLock(oMutex);
128 :
129 : VSIStatBufL sStat;
130 8642 : if (VSIStatL(archiveFilename, &sStat) != 0)
131 0 : return nullptr;
132 :
133 8642 : auto oIter = oFileList.find(archiveFilename);
134 8642 : if (oIter != oFileList.end())
135 : {
136 8417 : const VSIArchiveContent *content = oIter->second.get();
137 8417 : if (static_cast<time_t>(sStat.st_mtime) > content->mTime ||
138 8416 : static_cast<vsi_l_offset>(sStat.st_size) != content->nFileSize)
139 : {
140 8 : CPLDebug("VSIArchive",
141 : "The content of %s has changed since it was cached",
142 : archiveFilename);
143 8 : oFileList.erase(archiveFilename);
144 : }
145 : else
146 : {
147 8409 : return content;
148 : }
149 : }
150 :
151 233 : std::unique_ptr<VSIArchiveReader> temporaryReader; // keep in that scope
152 233 : if (poReader == nullptr)
153 : {
154 232 : temporaryReader = CreateReader(archiveFilename);
155 232 : poReader = temporaryReader.get();
156 232 : if (!poReader)
157 8 : return nullptr;
158 : }
159 :
160 225 : if (poReader->GotoFirstFile() == FALSE)
161 : {
162 0 : return nullptr;
163 : }
164 :
165 450 : auto content = std::make_unique<VSIArchiveContent>();
166 225 : content->mTime = sStat.st_mtime;
167 225 : content->nFileSize = static_cast<vsi_l_offset>(sStat.st_size);
168 :
169 225 : std::set<std::string> oSet;
170 :
171 4891 : do
172 : {
173 5116 : bool bIsDir = false;
174 : std::string osStrippedFilename =
175 5116 : GetStrippedFilename(poReader->GetFileName(), bIsDir);
176 10232 : if (osStrippedFilename.empty() || osStrippedFilename[0] == '/' ||
177 5116 : osStrippedFilename.find("//") != std::string::npos)
178 : {
179 0 : continue;
180 : }
181 :
182 5116 : if (oSet.find(osStrippedFilename) == oSet.end())
183 : {
184 5116 : oSet.insert(osStrippedFilename);
185 :
186 : // Add intermediate directory structure.
187 183907 : for (size_t i = 0; i < osStrippedFilename.size(); ++i)
188 : {
189 178791 : if (osStrippedFilename[i] == '/')
190 : {
191 12946 : std::string osSubdirName(osStrippedFilename, 0, i);
192 6473 : if (oSet.find(osSubdirName) == oSet.end())
193 : {
194 1257 : oSet.insert(osSubdirName);
195 :
196 2514 : VSIArchiveEntry entry;
197 1257 : entry.fileName = std::move(osSubdirName);
198 1257 : entry.nModifiedTime = poReader->GetModifiedTime();
199 1257 : entry.bIsDir = true;
200 : #ifdef DEBUG_VERBOSE
201 : CPLDebug(
202 : "VSIArchive", "[%d] %s : " CPL_FRMT_GUIB " bytes",
203 : static_cast<int>(content->entries.size() + 1),
204 : entry.fileName.c_str(), entry.uncompressed_size);
205 : #endif
206 1257 : content->entries.push_back(std::move(entry));
207 : }
208 : }
209 : }
210 :
211 10232 : VSIArchiveEntry entry;
212 5116 : entry.fileName = std::move(osStrippedFilename);
213 5116 : entry.nModifiedTime = poReader->GetModifiedTime();
214 5116 : entry.uncompressed_size = poReader->GetFileSize();
215 5116 : entry.bIsDir = bIsDir;
216 5116 : entry.file_pos.reset(poReader->GetFileOffset());
217 : #ifdef DEBUG_VERBOSE
218 : CPLDebug("VSIArchive", "[%d] %s : " CPL_FRMT_GUIB " bytes",
219 : static_cast<int>(content->entries.size() + 1),
220 : entry.fileName.c_str(), entry.uncompressed_size);
221 : #endif
222 5116 : content->entries.push_back(std::move(entry));
223 : }
224 :
225 5116 : } while (poReader->GotoNextFile());
226 :
227 : // Build directory index for fast lookups
228 225 : BuildDirectoryIndex(content.get());
229 :
230 : return oFileList
231 450 : .insert(std::pair<CPLString, std::unique_ptr<VSIArchiveContent>>(
232 450 : archiveFilename, std::move(content)))
233 225 : .first->second.get();
234 : }
235 :
236 : /************************************************************************/
237 : /* FindFileInArchive() */
238 : /************************************************************************/
239 :
240 7242 : bool VSIArchiveFilesystemHandler::FindFileInArchive(
241 : const char *archiveFilename, const char *fileInArchiveName,
242 : const VSIArchiveEntry **archiveEntry)
243 : {
244 7242 : CPLAssert(fileInArchiveName);
245 :
246 7242 : const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
247 7242 : if (content)
248 : {
249 7239 : const std::string parentDir = CPLGetPathSafe(fileInArchiveName);
250 :
251 : // Use directory index to search within parent directory's children
252 7239 : auto dirIter = content->dirIndex.find(parentDir);
253 7239 : if (dirIter != content->dirIndex.end())
254 : {
255 7226 : const std::vector<int> &childIndices = dirIter->second;
256 557566 : for (int childIdx : childIndices)
257 : {
258 557160 : if (content->entries[childIdx].fileName == fileInArchiveName)
259 : {
260 6820 : if (archiveEntry)
261 6820 : *archiveEntry = &content->entries[childIdx];
262 6820 : return true;
263 : }
264 : }
265 : }
266 : }
267 422 : return false;
268 : }
269 :
270 : /************************************************************************/
271 : /* CompactFilename() */
272 : /************************************************************************/
273 :
274 13593 : static std::string CompactFilename(const char *pszArchiveInFileNameIn)
275 : {
276 27186 : std::string osRet(pszArchiveInFileNameIn);
277 :
278 : // Replace a/../b by b and foo/a/../b by foo/b.
279 : while (true)
280 : {
281 13593 : size_t nSlashDotDot = osRet.find("/../");
282 13593 : if (nSlashDotDot == std::string::npos || nSlashDotDot == 0)
283 : break;
284 0 : size_t nPos = nSlashDotDot - 1;
285 0 : while (nPos > 0 && osRet[nPos] != '/')
286 0 : --nPos;
287 0 : if (nPos == 0)
288 0 : osRet = osRet.substr(nSlashDotDot + strlen("/../"));
289 : else
290 0 : osRet = osRet.substr(0, nPos + 1) +
291 0 : osRet.substr(nSlashDotDot + strlen("/../"));
292 0 : }
293 13593 : return osRet;
294 : }
295 :
296 : /************************************************************************/
297 : /* SplitFilename() */
298 : /************************************************************************/
299 :
300 15431 : char *VSIArchiveFilesystemHandler::SplitFilename(const char *pszFilename,
301 : CPLString &osFileInArchive,
302 : bool bCheckMainFileExists,
303 : bool bSetError) const
304 : {
305 : // TODO(schwehr): Cleanup redundant calls to GetPrefix and strlen.
306 15431 : if (strcmp(pszFilename, GetPrefix()) == 0)
307 4 : return nullptr;
308 :
309 15427 : int i = 0;
310 :
311 : // Detect extended syntax: /vsiXXX/{archive_filename}/file_in_archive.
312 15427 : if (pszFilename[strlen(GetPrefix()) + 1] == '{')
313 : {
314 1368 : pszFilename += strlen(GetPrefix()) + 1;
315 1368 : int nCountCurlies = 0;
316 52628 : while (pszFilename[i])
317 : {
318 52627 : if (pszFilename[i] == '{')
319 1371 : nCountCurlies++;
320 51256 : else if (pszFilename[i] == '}')
321 : {
322 1370 : nCountCurlies--;
323 1370 : if (nCountCurlies == 0)
324 1367 : break;
325 : }
326 51260 : i++;
327 : }
328 1368 : if (nCountCurlies > 0)
329 1 : return nullptr;
330 1367 : char *archiveFilename = CPLStrdup(pszFilename + 1);
331 1367 : archiveFilename[i - 1] = 0;
332 :
333 1367 : bool bArchiveFileExists = false;
334 1367 : if (!bCheckMainFileExists)
335 : {
336 40 : bArchiveFileExists = true;
337 : }
338 : else
339 : {
340 2654 : std::unique_lock oLock(oMutex);
341 :
342 1327 : if (oFileList.find(archiveFilename) != oFileList.end())
343 : {
344 1186 : bArchiveFileExists = true;
345 : }
346 : }
347 :
348 1367 : if (!bArchiveFileExists)
349 : {
350 : VSIStatBufL statBuf;
351 : VSIFilesystemHandler *poFSHandler =
352 141 : VSIFileManager::GetHandler(archiveFilename);
353 141 : int nFlags = VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG;
354 141 : if (bSetError)
355 31 : nFlags |= VSI_STAT_SET_ERROR_FLAG;
356 281 : if (poFSHandler->Stat(archiveFilename, &statBuf, nFlags) == 0 &&
357 140 : !VSI_ISDIR(statBuf.st_mode))
358 : {
359 140 : bArchiveFileExists = true;
360 : }
361 : }
362 :
363 1367 : if (bArchiveFileExists)
364 : {
365 1366 : if (IsEitherSlash(pszFilename[i + 1]))
366 : {
367 1299 : osFileInArchive = CompactFilename(pszFilename + i + 2);
368 : }
369 67 : else if (pszFilename[i + 1] == '\0')
370 : {
371 66 : osFileInArchive = "";
372 : }
373 : else
374 : {
375 1 : CPLFree(archiveFilename);
376 1 : return nullptr;
377 : }
378 :
379 : // Remove trailing slash.
380 1365 : if (!osFileInArchive.empty())
381 : {
382 1295 : const char lastC = osFileInArchive.back();
383 1295 : if (IsEitherSlash(lastC))
384 2 : osFileInArchive.pop_back();
385 : }
386 :
387 1365 : return archiveFilename;
388 : }
389 :
390 1 : CPLFree(archiveFilename);
391 1 : return nullptr;
392 : }
393 :
394 : // Allow natural chaining of VSI drivers without requiring double slash.
395 :
396 28118 : CPLString osDoubleVsi(GetPrefix());
397 14059 : osDoubleVsi += "/vsi";
398 :
399 14059 : if (strncmp(pszFilename, osDoubleVsi.c_str(), osDoubleVsi.size()) == 0)
400 4071 : pszFilename += strlen(GetPrefix());
401 : else
402 9988 : pszFilename += strlen(GetPrefix()) + 1;
403 :
404 : // Parsing strings like
405 : // /vsitar//vsitar//vsitar//vsitar//vsitar//vsitar//vsitar//vsitar/a.tgzb.tgzc.tgzd.tgze.tgzf.tgz.h.tgz.i.tgz
406 : // takes a huge amount of time, so limit the number of nesting of such
407 : // file systems.
408 14059 : int *pnCounter = static_cast<int *>(CPLGetTLS(CTLS_ABSTRACTARCHIVE_SPLIT));
409 14059 : if (pnCounter == nullptr)
410 : {
411 26 : pnCounter = static_cast<int *>(CPLMalloc(sizeof(int)));
412 26 : *pnCounter = 0;
413 26 : CPLSetTLS(CTLS_ABSTRACTARCHIVE_SPLIT, pnCounter, TRUE);
414 : }
415 14059 : if (*pnCounter == 3)
416 : {
417 64 : CPLError(CE_Failure, CPLE_AppDefined,
418 : "Too deep recursion level in "
419 : "VSIArchiveFilesystemHandler::SplitFilename()");
420 64 : return nullptr;
421 : }
422 :
423 27990 : const std::vector<CPLString> oExtensions = GetExtensions();
424 13995 : int nAttempts = 0;
425 442238 : while (pszFilename[i])
426 : {
427 442022 : int nToSkip = 0;
428 :
429 3000120 : for (std::vector<CPLString>::const_iterator iter = oExtensions.begin();
430 5558210 : iter != oExtensions.end(); ++iter)
431 : {
432 2572540 : const CPLString &osExtension = *iter;
433 2572540 : if (EQUALN(pszFilename + i, osExtension.c_str(),
434 : osExtension.size()))
435 : {
436 14450 : nToSkip = static_cast<int>(osExtension.size());
437 14450 : break;
438 : }
439 : }
440 :
441 : #ifdef DEBUG
442 : // For AFL, so that .cur_input is detected as the archive filename.
443 442022 : if (EQUALN(pszFilename + i, ".cur_input", strlen(".cur_input")))
444 : {
445 14 : nToSkip = static_cast<int>(strlen(".cur_input"));
446 : }
447 : #endif
448 :
449 442022 : if (nToSkip != 0)
450 : {
451 14464 : nAttempts++;
452 : // Arbitrary threshold to avoid DoS with things like
453 : // /vsitar/my.tar/my.tar/my.tar/my.tar/my.tar/my.tar/my.tar
454 14464 : if (nAttempts == 5)
455 : {
456 21 : break;
457 : }
458 : VSIStatBufL statBuf;
459 14443 : char *archiveFilename = CPLStrdup(pszFilename);
460 14443 : bool bArchiveFileExists = false;
461 :
462 14443 : if (IsEitherSlash(archiveFilename[i + nToSkip]))
463 : {
464 12773 : archiveFilename[i + nToSkip] = 0;
465 : }
466 :
467 14443 : if (!bCheckMainFileExists)
468 : {
469 1016 : bArchiveFileExists = true;
470 : }
471 : else
472 : {
473 26854 : std::unique_lock oLock(oMutex);
474 :
475 13427 : if (oFileList.find(archiveFilename) != oFileList.end())
476 : {
477 12307 : bArchiveFileExists = true;
478 : }
479 : }
480 :
481 14443 : if (!bArchiveFileExists)
482 : {
483 1120 : (*pnCounter)++;
484 :
485 : VSIFilesystemHandler *poFSHandler =
486 1120 : VSIFileManager::GetHandler(archiveFilename);
487 3360 : if (poFSHandler->Stat(archiveFilename, &statBuf,
488 : VSI_STAT_EXISTS_FLAG |
489 1975 : VSI_STAT_NATURE_FLAG) == 0 &&
490 855 : !VSI_ISDIR(statBuf.st_mode))
491 : {
492 435 : bArchiveFileExists = true;
493 : }
494 :
495 1120 : (*pnCounter)--;
496 : }
497 :
498 14443 : if (bArchiveFileExists)
499 : {
500 13758 : if (IsEitherSlash(pszFilename[i + nToSkip]))
501 : {
502 : osFileInArchive =
503 12294 : CompactFilename(pszFilename + i + nToSkip + 1);
504 : }
505 : else
506 : {
507 1464 : osFileInArchive = "";
508 : }
509 :
510 : // Remove trailing slash.
511 13758 : if (!osFileInArchive.empty())
512 : {
513 12286 : const char lastC = osFileInArchive.back();
514 12286 : if (IsEitherSlash(lastC))
515 63 : osFileInArchive.resize(osFileInArchive.size() - 1);
516 : }
517 :
518 13758 : return archiveFilename;
519 : }
520 685 : CPLFree(archiveFilename);
521 : }
522 428243 : i++;
523 : }
524 237 : return nullptr;
525 : }
526 :
527 : /************************************************************************/
528 : /* OpenArchiveFile() */
529 : /************************************************************************/
530 :
531 : std::unique_ptr<VSIArchiveReader>
532 4426 : VSIArchiveFilesystemHandler::OpenArchiveFile(const char *archiveFilename,
533 : const char *fileInArchiveName)
534 : {
535 8852 : auto poReader = CreateReader(archiveFilename);
536 :
537 4426 : if (poReader == nullptr)
538 : {
539 6 : return nullptr;
540 : }
541 :
542 4420 : if (fileInArchiveName == nullptr || strlen(fileInArchiveName) == 0)
543 : {
544 73 : if (poReader->GotoFirstFile() == FALSE)
545 : {
546 1 : return nullptr;
547 : }
548 :
549 : // Skip optional leading subdir.
550 73 : const CPLString osFileName = poReader->GetFileName();
551 73 : if (osFileName.empty() || IsEitherSlash(osFileName.back()))
552 : {
553 2 : if (poReader->GotoNextFile() == FALSE)
554 : {
555 0 : return nullptr;
556 : }
557 : }
558 :
559 73 : if (poReader->GotoNextFile())
560 : {
561 2 : CPLString msg;
562 : msg.Printf("Support only 1 file in archive file %s when "
563 : "no explicit in-archive filename is specified",
564 1 : archiveFilename);
565 : const VSIArchiveContent *content =
566 1 : GetContentOfArchive(archiveFilename, poReader.get());
567 1 : if (content)
568 : {
569 1 : msg += "\nYou could try one of the following :\n";
570 6 : for (const auto &entry : content->entries)
571 : {
572 10 : msg += CPLString().Printf(" %s/{%s}/%s\n", GetPrefix(),
573 : archiveFilename,
574 5 : entry.fileName.c_str());
575 : }
576 : }
577 :
578 1 : CPLError(CE_Failure, CPLE_NotSupported, "%s", msg.c_str());
579 :
580 1 : return nullptr;
581 72 : }
582 : }
583 : else
584 : {
585 : // Optimization: instead of iterating over all files which can be
586 : // slow on .tar.gz files, try reading the first one first.
587 : // This can help if it is really huge.
588 : {
589 4347 : std::unique_lock oLock(oMutex);
590 :
591 4347 : if (oFileList.find(archiveFilename) == oFileList.end())
592 : {
593 144 : if (poReader->GotoFirstFile() == FALSE)
594 : {
595 57 : return nullptr;
596 : }
597 :
598 144 : const CPLString osFileName = poReader->GetFileName();
599 144 : bool bIsDir = false;
600 : const CPLString osStrippedFilename =
601 144 : GetStrippedFilename(osFileName, bIsDir);
602 144 : if (!osStrippedFilename.empty())
603 : {
604 : const bool bMatch =
605 144 : strcmp(osStrippedFilename, fileInArchiveName) == 0;
606 144 : if (bMatch)
607 : {
608 57 : if (bIsDir)
609 : {
610 2 : return nullptr;
611 : }
612 55 : return poReader;
613 : }
614 : }
615 : }
616 : }
617 :
618 4290 : const VSIArchiveEntry *archiveEntry = nullptr;
619 4290 : if (FindFileInArchive(archiveFilename, fileInArchiveName,
620 8398 : &archiveEntry) == FALSE ||
621 4108 : archiveEntry->bIsDir)
622 : {
623 186 : return nullptr;
624 : }
625 4104 : if (!poReader->GotoFileOffset(archiveEntry->file_pos.get()))
626 : {
627 0 : return nullptr;
628 : }
629 : }
630 4176 : return poReader;
631 : }
632 :
633 : /************************************************************************/
634 : /* Stat() */
635 : /************************************************************************/
636 :
637 3522 : int VSIArchiveFilesystemHandler::Stat(const char *pszFilename,
638 : VSIStatBufL *pStatBuf, int nFlags)
639 : {
640 3522 : memset(pStatBuf, 0, sizeof(VSIStatBufL));
641 :
642 7044 : CPLString osFileInArchive;
643 : char *archiveFilename =
644 7044 : SplitFilename(pszFilename, osFileInArchive, true,
645 3522 : (nFlags & VSI_STAT_SET_ERROR_FLAG) != 0);
646 3522 : if (archiveFilename == nullptr)
647 85 : return -1;
648 :
649 3437 : int ret = -1;
650 3437 : if (!osFileInArchive.empty())
651 : {
652 : #ifdef DEBUG_VERBOSE
653 : CPLDebug("VSIArchive", "Looking for %s %s", archiveFilename,
654 : osFileInArchive.c_str());
655 : #endif
656 :
657 2952 : const VSIArchiveEntry *archiveEntry = nullptr;
658 2952 : if (FindFileInArchive(archiveFilename, osFileInArchive, &archiveEntry))
659 : {
660 : // Patching st_size with uncompressed file size.
661 2712 : pStatBuf->st_size = archiveEntry->uncompressed_size;
662 2712 : pStatBuf->st_mtime =
663 2712 : static_cast<time_t>(archiveEntry->nModifiedTime);
664 2712 : if (archiveEntry->bIsDir)
665 1156 : pStatBuf->st_mode = S_IFDIR;
666 : else
667 1556 : pStatBuf->st_mode = S_IFREG;
668 2712 : ret = 0;
669 : }
670 : }
671 : else
672 : {
673 485 : auto poReader = CreateReader(archiveFilename);
674 485 : CPLFree(archiveFilename);
675 485 : archiveFilename = nullptr;
676 :
677 485 : if (poReader != nullptr && poReader->GotoFirstFile())
678 : {
679 : // Skip optional leading subdir.
680 480 : const CPLString osFileName = poReader->GetFileName();
681 480 : if (IsEitherSlash(osFileName.back()))
682 : {
683 11 : if (poReader->GotoNextFile() == FALSE)
684 : {
685 0 : return -1;
686 : }
687 : }
688 :
689 480 : if (poReader->GotoNextFile())
690 : {
691 : // Several files in archive --> treat as dir.
692 452 : pStatBuf->st_size = 0;
693 452 : pStatBuf->st_mode = S_IFDIR;
694 : }
695 : else
696 : {
697 : // Patching st_size with uncompressed file size.
698 28 : pStatBuf->st_size = poReader->GetFileSize();
699 28 : pStatBuf->st_mtime =
700 28 : static_cast<time_t>(poReader->GetModifiedTime());
701 28 : pStatBuf->st_mode = S_IFREG;
702 : }
703 :
704 480 : ret = 0;
705 : }
706 : }
707 :
708 3437 : CPLFree(archiveFilename);
709 3437 : return ret;
710 : }
711 :
712 : /************************************************************************/
713 : /* ReadDirEx() */
714 : /************************************************************************/
715 :
716 1399 : char **VSIArchiveFilesystemHandler::ReadDirEx(const char *pszDirname,
717 : int nMaxFiles)
718 : {
719 2798 : CPLString osInArchiveSubDir;
720 : char *archiveFilename =
721 1399 : SplitFilename(pszDirname, osInArchiveSubDir, true, true);
722 1399 : if (archiveFilename == nullptr)
723 0 : return nullptr;
724 :
725 1399 : const size_t lenInArchiveSubDir = osInArchiveSubDir.size();
726 :
727 2798 : CPLStringList oDir;
728 :
729 1399 : const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
730 1399 : if (!content)
731 : {
732 5 : CPLFree(archiveFilename);
733 5 : return nullptr;
734 : }
735 :
736 : #ifdef DEBUG_VERBOSE
737 : CPLDebug("VSIArchive", "Read dir %s", pszDirname);
738 : #endif
739 :
740 2788 : std::string searchDir;
741 1394 : if (lenInArchiveSubDir != 0)
742 1167 : searchDir = std::move(osInArchiveSubDir);
743 :
744 : // Use directory index to find the list of children for this directory
745 1394 : auto dirIter = content->dirIndex.find(searchDir);
746 1394 : if (dirIter == content->dirIndex.end())
747 : {
748 : // Directory not found in index - no children
749 0 : CPLFree(archiveFilename);
750 0 : return oDir.StealList();
751 : }
752 1394 : const std::vector<int> &childIndices = dirIter->second;
753 :
754 : // Scan the children of this directory
755 42354 : for (int childIdx : childIndices)
756 : {
757 40961 : const char *fileName = content->entries[childIdx].fileName.c_str();
758 :
759 40961 : const char *baseName = fileName;
760 40961 : if (lenInArchiveSubDir != 0)
761 : {
762 : // Skip the directory prefix and slash to get just the child name
763 38531 : baseName = fileName + lenInArchiveSubDir + 1;
764 : }
765 40961 : oDir.AddStringDirectly(CPLStrdup(baseName));
766 :
767 40961 : if (nMaxFiles > 0 && oDir.Count() > nMaxFiles)
768 1 : break;
769 : }
770 1394 : CPLFree(archiveFilename);
771 1394 : return oDir.StealList();
772 : }
773 :
774 : /************************************************************************/
775 : /* IsLocal() */
776 : /************************************************************************/
777 :
778 0 : bool VSIArchiveFilesystemHandler::IsLocal(const char *pszPath) const
779 : {
780 0 : if (!STARTS_WITH(pszPath, GetPrefix()))
781 0 : return false;
782 0 : const char *pszBaseFileName = pszPath + strlen(GetPrefix());
783 : VSIFilesystemHandler *poFSHandler =
784 0 : VSIFileManager::GetHandler(pszBaseFileName);
785 0 : return poFSHandler->IsLocal(pszPath);
786 : }
787 :
788 : /************************************************************************/
789 : /* IsArchive() */
790 : /************************************************************************/
791 :
792 0 : bool VSIArchiveFilesystemHandler::IsArchive(const char *pszPath) const
793 : {
794 0 : if (!STARTS_WITH(pszPath, GetPrefix()))
795 0 : return false;
796 0 : CPLString osFileInArchive;
797 0 : return SplitFilename(pszPath, osFileInArchive, false, false) != nullptr &&
798 0 : osFileInArchive.empty();
799 : }
800 :
801 : //! @endcond
|