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