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