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 582403 : static bool IsEitherSlash(char c)
36 : {
37 582403 : return c == '/' || c == '\\';
38 : }
39 :
40 : /************************************************************************/
41 : /* ~VSIArchiveEntryFileOffset() */
42 : /************************************************************************/
43 :
44 1513 : VSIArchiveEntryFileOffset::~VSIArchiveEntryFileOffset()
45 : {
46 1513 : }
47 :
48 : /************************************************************************/
49 : /* ~VSIArchiveReader() */
50 : /************************************************************************/
51 :
52 5101 : VSIArchiveReader::~VSIArchiveReader()
53 : {
54 5101 : }
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 3232 : VSIArchiveFilesystemHandler::VSIArchiveFilesystemHandler()
75 : {
76 3232 : hMutex = nullptr;
77 3232 : }
78 :
79 : /************************************************************************/
80 : /* ~VSIArchiveFilesystemHandler() */
81 : /************************************************************************/
82 :
83 2222 : VSIArchiveFilesystemHandler::~VSIArchiveFilesystemHandler()
84 :
85 : {
86 2272 : for (const auto &iter : oFileList)
87 : {
88 50 : delete iter.second;
89 : }
90 :
91 2222 : if (hMutex != nullptr)
92 17 : CPLDestroyMutex(hMutex);
93 2222 : hMutex = nullptr;
94 2222 : }
95 :
96 : /************************************************************************/
97 : /* GetStrippedFilename() */
98 : /************************************************************************/
99 :
100 5189 : static CPLString GetStrippedFilename(const CPLString &osFileName, bool &bIsDir)
101 : {
102 5189 : bIsDir = false;
103 5189 : const char *fileName = osFileName.c_str();
104 :
105 : // Remove ./ pattern at the beginning of a filename.
106 5189 : if (fileName[0] == '.' && fileName[1] == '/')
107 : {
108 0 : fileName += 2;
109 0 : if (fileName[0] == '\0')
110 0 : return CPLString();
111 : }
112 :
113 5189 : char *pszStrippedFileName = CPLStrdup(fileName);
114 5189 : char *pszIter = nullptr;
115 184947 : for (pszIter = pszStrippedFileName; *pszIter; pszIter++)
116 : {
117 179758 : if (*pszIter == '\\')
118 0 : *pszIter = '/';
119 : }
120 :
121 5189 : const size_t nLen = strlen(fileName);
122 5189 : bIsDir = nLen > 0 && fileName[nLen - 1] == '/';
123 5189 : if (bIsDir)
124 : {
125 : // Remove trailing slash.
126 202 : pszStrippedFileName[nLen - 1] = '\0';
127 : }
128 10378 : CPLString osRet(pszStrippedFileName);
129 5189 : CPLFree(pszStrippedFileName);
130 5189 : return osRet;
131 : }
132 :
133 : /************************************************************************/
134 : /* GetContentOfArchive() */
135 : /************************************************************************/
136 :
137 : const VSIArchiveContent *
138 8461 : VSIArchiveFilesystemHandler::GetContentOfArchive(const char *archiveFilename,
139 : VSIArchiveReader *poReader)
140 : {
141 16922 : CPLMutexHolder oHolder(&hMutex);
142 :
143 : VSIStatBufL sStat;
144 8461 : if (VSIStatL(archiveFilename, &sStat) != 0)
145 0 : return nullptr;
146 :
147 8461 : if (oFileList.find(archiveFilename) != oFileList.end())
148 : {
149 8246 : VSIArchiveContent *content = oFileList[archiveFilename];
150 8246 : if (static_cast<time_t>(sStat.st_mtime) > content->mTime ||
151 8245 : 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 8238 : return content;
162 : }
163 : }
164 :
165 223 : bool bMustClose = poReader == nullptr;
166 223 : if (poReader == nullptr)
167 : {
168 222 : poReader = CreateReader(archiveFilename);
169 222 : if (!poReader)
170 8 : return nullptr;
171 : }
172 :
173 215 : if (poReader->GotoFirstFile() == FALSE)
174 : {
175 0 : if (bMustClose)
176 0 : delete (poReader);
177 0 : return nullptr;
178 : }
179 :
180 215 : VSIArchiveContent *content = new VSIArchiveContent;
181 215 : content->mTime = sStat.st_mtime;
182 215 : content->nFileSize = static_cast<vsi_l_offset>(sStat.st_size);
183 215 : content->nEntries = 0;
184 215 : content->entries = nullptr;
185 215 : oFileList[archiveFilename] = content;
186 :
187 215 : std::set<CPLString> oSet;
188 :
189 4832 : do
190 : {
191 5047 : const CPLString osFileName = poReader->GetFileName();
192 5047 : bool bIsDir = false;
193 : const CPLString osStrippedFilename =
194 5047 : GetStrippedFilename(osFileName, bIsDir);
195 10094 : if (osStrippedFilename.empty() || osStrippedFilename[0] == '/' ||
196 5047 : osStrippedFilename.find("//") != std::string::npos)
197 : {
198 0 : continue;
199 : }
200 :
201 5047 : if (oSet.find(osStrippedFilename) == oSet.end())
202 : {
203 5047 : oSet.insert(osStrippedFilename);
204 :
205 : // Add intermediate directory structure.
206 5047 : const char *pszBegin = osStrippedFilename.c_str();
207 182818 : for (const char *pszIter = pszBegin; *pszIter; pszIter++)
208 : {
209 177771 : 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 5047 : content->entries = static_cast<VSIArchiveEntry *>(
247 10094 : CPLRealloc(content->entries,
248 5047 : sizeof(VSIArchiveEntry) * (content->nEntries + 1)));
249 10094 : content->entries[content->nEntries].fileName =
250 5047 : CPLStrdup(osStrippedFilename);
251 10094 : content->entries[content->nEntries].nModifiedTime =
252 5047 : poReader->GetModifiedTime();
253 10094 : content->entries[content->nEntries].uncompressed_size =
254 5047 : poReader->GetFileSize();
255 5047 : content->entries[content->nEntries].bIsDir = bIsDir;
256 10094 : content->entries[content->nEntries].file_pos =
257 5047 : 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 5047 : content->nEntries++;
265 : }
266 :
267 5047 : } while (poReader->GotoNextFile());
268 :
269 215 : if (bMustClose)
270 214 : delete (poReader);
271 :
272 215 : return content;
273 : }
274 :
275 : /************************************************************************/
276 : /* FindFileInArchive() */
277 : /************************************************************************/
278 :
279 7064 : int VSIArchiveFilesystemHandler::FindFileInArchive(
280 : const char *archiveFilename, const char *fileInArchiveName,
281 : const VSIArchiveEntry **archiveEntry)
282 : {
283 7064 : if (fileInArchiveName == nullptr)
284 0 : return FALSE;
285 :
286 7064 : const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
287 7064 : if (content)
288 : {
289 1072480 : for (int i = 0; i < content->nEntries; i++)
290 : {
291 1072150 : if (strcmp(fileInArchiveName, content->entries[i].fileName) == 0)
292 : {
293 6736 : if (archiveEntry)
294 6736 : *archiveEntry = &content->entries[i];
295 6736 : return TRUE;
296 : }
297 : }
298 : }
299 328 : return FALSE;
300 : }
301 :
302 : /************************************************************************/
303 : /* CompactFilename() */
304 : /************************************************************************/
305 :
306 13203 : static std::string CompactFilename(const char *pszArchiveInFileNameIn)
307 : {
308 26406 : std::string osRet(pszArchiveInFileNameIn);
309 :
310 : // Replace a/../b by b and foo/a/../b by foo/b.
311 : while (true)
312 : {
313 13203 : size_t nSlashDotDot = osRet.find("/../");
314 13203 : 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 13203 : return osRet;
326 : }
327 :
328 : /************************************************************************/
329 : /* SplitFilename() */
330 : /************************************************************************/
331 :
332 15013 : char *VSIArchiveFilesystemHandler::SplitFilename(const char *pszFilename,
333 : CPLString &osFileInArchive,
334 : int bCheckMainFileExists)
335 : {
336 : // TODO(schwehr): Cleanup redundant calls to GetPrefix and strlen.
337 15013 : if (strcmp(pszFilename, GetPrefix()) == 0)
338 4 : return nullptr;
339 :
340 15009 : int i = 0;
341 :
342 : // Detect extended syntax: /vsiXXX/{archive_filename}/file_in_archive.
343 15009 : if (pszFilename[strlen(GetPrefix()) + 1] == '{')
344 : {
345 1275 : pszFilename += strlen(GetPrefix()) + 1;
346 1275 : int nCountCurlies = 0;
347 48180 : while (pszFilename[i])
348 : {
349 48179 : if (pszFilename[i] == '{')
350 1278 : nCountCurlies++;
351 46901 : else if (pszFilename[i] == '}')
352 : {
353 1277 : nCountCurlies--;
354 1277 : if (nCountCurlies == 0)
355 1274 : break;
356 : }
357 46905 : i++;
358 : }
359 1275 : if (nCountCurlies > 0)
360 1 : return nullptr;
361 1274 : char *archiveFilename = CPLStrdup(pszFilename + 1);
362 1274 : archiveFilename[i - 1] = 0;
363 :
364 1274 : bool bArchiveFileExists = false;
365 1274 : if (!bCheckMainFileExists)
366 : {
367 40 : bArchiveFileExists = true;
368 : }
369 : else
370 : {
371 2468 : CPLMutexHolder oHolder(&hMutex);
372 :
373 1234 : if (oFileList.find(archiveFilename) != oFileList.end())
374 : {
375 1093 : bArchiveFileExists = true;
376 : }
377 : }
378 :
379 1274 : if (!bArchiveFileExists)
380 : {
381 : VSIStatBufL statBuf;
382 : VSIFilesystemHandler *poFSHandler =
383 141 : VSIFileManager::GetHandler(archiveFilename);
384 423 : if (poFSHandler->Stat(archiveFilename, &statBuf,
385 : VSI_STAT_EXISTS_FLAG |
386 281 : VSI_STAT_NATURE_FLAG) == 0 &&
387 140 : !VSI_ISDIR(statBuf.st_mode))
388 : {
389 140 : bArchiveFileExists = true;
390 : }
391 : }
392 :
393 1274 : if (bArchiveFileExists)
394 : {
395 1273 : if (IsEitherSlash(pszFilename[i + 1]))
396 : {
397 1206 : 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 1272 : if (!osFileInArchive.empty())
411 : {
412 1202 : const char lastC = osFileInArchive.back();
413 1202 : if (IsEitherSlash(lastC))
414 2 : osFileInArchive.pop_back();
415 : }
416 :
417 1272 : 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 27468 : CPLString osDoubleVsi(GetPrefix());
427 13734 : osDoubleVsi += "/vsi";
428 :
429 13734 : if (strncmp(pszFilename, osDoubleVsi.c_str(), osDoubleVsi.size()) == 0)
430 4080 : pszFilename += strlen(GetPrefix());
431 : else
432 9654 : 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 13734 : int *pnCounter = static_cast<int *>(CPLGetTLS(CTLS_ABSTRACTARCHIVE_SPLIT));
439 13734 : 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 13734 : 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 27340 : const std::vector<CPLString> oExtensions = GetExtensions();
454 13670 : int nAttempts = 0;
455 425639 : while (pszFilename[i])
456 : {
457 425447 : int nToSkip = 0;
458 :
459 2886040 : for (std::vector<CPLString>::const_iterator iter = oExtensions.begin();
460 5346640 : iter != oExtensions.end(); ++iter)
461 : {
462 2474720 : const CPLString &osExtension = *iter;
463 2474720 : if (EQUALN(pszFilename + i, osExtension.c_str(),
464 : osExtension.size()))
465 : {
466 14125 : nToSkip = static_cast<int>(osExtension.size());
467 14125 : break;
468 : }
469 : }
470 :
471 : #ifdef DEBUG
472 : // For AFL, so that .cur_input is detected as the archive filename.
473 425447 : if (EQUALN(pszFilename + i, ".cur_input", strlen(".cur_input")))
474 : {
475 14 : nToSkip = static_cast<int>(strlen(".cur_input"));
476 : }
477 : #endif
478 :
479 425447 : if (nToSkip != 0)
480 : {
481 14139 : 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 14139 : if (nAttempts == 5)
485 : {
486 21 : break;
487 : }
488 : VSIStatBufL statBuf;
489 14118 : char *archiveFilename = CPLStrdup(pszFilename);
490 14118 : bool bArchiveFileExists = false;
491 :
492 14118 : if (IsEitherSlash(archiveFilename[i + nToSkip]))
493 : {
494 12452 : archiveFilename[i + nToSkip] = 0;
495 : }
496 :
497 14118 : if (!bCheckMainFileExists)
498 : {
499 956 : bArchiveFileExists = true;
500 : }
501 : else
502 : {
503 26324 : CPLMutexHolder oHolder(&hMutex);
504 :
505 13162 : if (oFileList.find(archiveFilename) != oFileList.end())
506 : {
507 12088 : bArchiveFileExists = true;
508 : }
509 : }
510 :
511 14118 : if (!bArchiveFileExists)
512 : {
513 1074 : (*pnCounter)++;
514 :
515 : VSIFilesystemHandler *poFSHandler =
516 1074 : VSIFileManager::GetHandler(archiveFilename);
517 3222 : if (poFSHandler->Stat(archiveFilename, &statBuf,
518 : VSI_STAT_EXISTS_FLAG |
519 1907 : VSI_STAT_NATURE_FLAG) == 0 &&
520 833 : !VSI_ISDIR(statBuf.st_mode))
521 : {
522 413 : bArchiveFileExists = true;
523 : }
524 :
525 1074 : (*pnCounter)--;
526 : }
527 :
528 14118 : if (bArchiveFileExists)
529 : {
530 13457 : if (IsEitherSlash(pszFilename[i + nToSkip]))
531 : {
532 : osFileInArchive =
533 11997 : CompactFilename(pszFilename + i + nToSkip + 1);
534 : }
535 : else
536 : {
537 1460 : osFileInArchive = "";
538 : }
539 :
540 : // Remove trailing slash.
541 13457 : if (!osFileInArchive.empty())
542 : {
543 11989 : const char lastC = osFileInArchive.back();
544 11989 : if (IsEitherSlash(lastC))
545 39 : osFileInArchive.resize(osFileInArchive.size() - 1);
546 : }
547 :
548 13457 : return archiveFilename;
549 : }
550 661 : CPLFree(archiveFilename);
551 : }
552 411969 : i++;
553 : }
554 213 : return nullptr;
555 : }
556 :
557 : /************************************************************************/
558 : /* OpenArchiveFile() */
559 : /************************************************************************/
560 :
561 : VSIArchiveReader *
562 4394 : VSIArchiveFilesystemHandler::OpenArchiveFile(const char *archiveFilename,
563 : const char *fileInArchiveName)
564 : {
565 4394 : VSIArchiveReader *poReader = CreateReader(archiveFilename);
566 :
567 4394 : if (poReader == nullptr)
568 : {
569 6 : return nullptr;
570 : }
571 :
572 4388 : 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 4315 : CPLMutexHolder oHolder(&hMutex);
623 :
624 4315 : if (oFileList.find(archiveFilename) == oFileList.end())
625 : {
626 142 : if (poReader->GotoFirstFile() == FALSE)
627 : {
628 0 : delete (poReader);
629 55 : return nullptr;
630 : }
631 :
632 142 : const CPLString osFileName = poReader->GetFileName();
633 142 : bool bIsDir = false;
634 : const CPLString osStrippedFilename =
635 142 : GetStrippedFilename(osFileName, bIsDir);
636 142 : if (!osStrippedFilename.empty())
637 : {
638 : const bool bMatch =
639 142 : strcmp(osStrippedFilename, fileInArchiveName) == 0;
640 142 : if (bMatch)
641 : {
642 55 : if (bIsDir)
643 : {
644 0 : delete (poReader);
645 0 : return nullptr;
646 : }
647 55 : return poReader;
648 : }
649 : }
650 : }
651 : }
652 :
653 4260 : const VSIArchiveEntry *archiveEntry = nullptr;
654 12780 : if (FindFileInArchive(archiveFilename, fileInArchiveName,
655 8351 : &archiveEntry) == FALSE ||
656 4091 : archiveEntry->bIsDir)
657 : {
658 173 : delete (poReader);
659 173 : return nullptr;
660 : }
661 4087 : if (!poReader->GotoFileOffset(archiveEntry->file_pos))
662 : {
663 0 : delete poReader;
664 0 : return nullptr;
665 : }
666 : }
667 4159 : return poReader;
668 : }
669 :
670 : /************************************************************************/
671 : /* Stat() */
672 : /************************************************************************/
673 :
674 3374 : int VSIArchiveFilesystemHandler::Stat(const char *pszFilename,
675 : VSIStatBufL *pStatBuf, int /* nFlags */)
676 : {
677 3374 : memset(pStatBuf, 0, sizeof(VSIStatBufL));
678 :
679 6748 : CPLString osFileInArchive;
680 3374 : char *archiveFilename = SplitFilename(pszFilename, osFileInArchive, TRUE);
681 3374 : if (archiveFilename == nullptr)
682 85 : return -1;
683 :
684 3289 : int ret = -1;
685 3289 : if (!osFileInArchive.empty())
686 : {
687 : #ifdef DEBUG_VERBOSE
688 : CPLDebug("VSIArchive", "Looking for %s %s", archiveFilename,
689 : osFileInArchive.c_str());
690 : #endif
691 :
692 2804 : const VSIArchiveEntry *archiveEntry = nullptr;
693 2804 : if (FindFileInArchive(archiveFilename, osFileInArchive, &archiveEntry))
694 : {
695 : // Patching st_size with uncompressed file size.
696 2645 : pStatBuf->st_size = archiveEntry->uncompressed_size;
697 2645 : pStatBuf->st_mtime =
698 2645 : static_cast<time_t>(archiveEntry->nModifiedTime);
699 2645 : if (archiveEntry->bIsDir)
700 1148 : pStatBuf->st_mode = S_IFDIR;
701 : else
702 1497 : pStatBuf->st_mode = S_IFREG;
703 2645 : ret = 0;
704 : }
705 : }
706 : else
707 : {
708 485 : VSIArchiveReader *poReader = CreateReader(archiveFilename);
709 485 : CPLFree(archiveFilename);
710 485 : archiveFilename = nullptr;
711 :
712 485 : if (poReader != nullptr && poReader->GotoFirstFile())
713 : {
714 : // Skip optional leading subdir.
715 480 : const CPLString osFileName = poReader->GetFileName();
716 480 : if (IsEitherSlash(osFileName.back()))
717 : {
718 11 : if (poReader->GotoNextFile() == FALSE)
719 : {
720 0 : delete (poReader);
721 0 : return -1;
722 : }
723 : }
724 :
725 480 : if (poReader->GotoNextFile())
726 : {
727 : // Several files in archive --> treat as dir.
728 452 : pStatBuf->st_size = 0;
729 452 : pStatBuf->st_mode = S_IFDIR;
730 : }
731 : else
732 : {
733 : // Patching st_size with uncompressed file size.
734 28 : pStatBuf->st_size = poReader->GetFileSize();
735 28 : pStatBuf->st_mtime =
736 28 : static_cast<time_t>(poReader->GetModifiedTime());
737 28 : pStatBuf->st_mode = S_IFREG;
738 : }
739 :
740 480 : ret = 0;
741 : }
742 :
743 485 : delete (poReader);
744 : }
745 :
746 3289 : CPLFree(archiveFilename);
747 3289 : return ret;
748 : }
749 :
750 : /************************************************************************/
751 : /* ReadDirEx() */
752 : /************************************************************************/
753 :
754 1396 : char **VSIArchiveFilesystemHandler::ReadDirEx(const char *pszDirname,
755 : int nMaxFiles)
756 : {
757 2792 : CPLString osInArchiveSubDir;
758 1396 : char *archiveFilename = SplitFilename(pszDirname, osInArchiveSubDir, TRUE);
759 1396 : if (archiveFilename == nullptr)
760 0 : return nullptr;
761 :
762 1396 : const int lenInArchiveSubDir = static_cast<int>(osInArchiveSubDir.size());
763 :
764 2792 : CPLStringList oDir;
765 :
766 1396 : const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
767 1396 : if (!content)
768 : {
769 5 : CPLFree(archiveFilename);
770 5 : return nullptr;
771 : }
772 :
773 : #ifdef DEBUG_VERBOSE
774 : CPLDebug("VSIArchive", "Read dir %s", pszDirname);
775 : #endif
776 1080560 : for (int i = 0; i < content->nEntries; i++)
777 : {
778 1079170 : const char *fileName = content->entries[i].fileName;
779 : /* Only list entries at the same level of inArchiveSubDir */
780 2120180 : if (lenInArchiveSubDir != 0 &&
781 1580820 : strncmp(fileName, osInArchiveSubDir, lenInArchiveSubDir) == 0 &&
782 2659990 : IsEitherSlash(fileName[lenInArchiveSubDir]) &&
783 538645 : fileName[lenInArchiveSubDir + 1] != 0)
784 : {
785 538645 : const char *slash = strchr(fileName + lenInArchiveSubDir + 1, '/');
786 538645 : if (slash == nullptr)
787 38527 : slash = strchr(fileName + lenInArchiveSubDir + 1, '\\');
788 538645 : if (slash == nullptr || slash[1] == 0)
789 : {
790 38527 : char *tmpFileName = CPLStrdup(fileName);
791 38527 : if (slash != nullptr)
792 : {
793 0 : tmpFileName[strlen(tmpFileName) - 1] = 0;
794 : }
795 : #ifdef DEBUG_VERBOSE
796 : CPLDebug("VSIArchive", "Add %s as in directory %s",
797 : tmpFileName + lenInArchiveSubDir + 1, pszDirname);
798 : #endif
799 38527 : oDir.AddString(tmpFileName + lenInArchiveSubDir + 1);
800 38527 : CPLFree(tmpFileName);
801 : }
802 : }
803 540521 : else if (lenInArchiveSubDir == 0 && strchr(fileName, '/') == nullptr &&
804 2428 : strchr(fileName, '\\') == nullptr)
805 : {
806 : // Only list toplevel files and directories.
807 : #ifdef DEBUG_VERBOSE
808 : CPLDebug("VSIArchive", "Add %s as in directory %s", fileName,
809 : pszDirname);
810 : #endif
811 2428 : oDir.AddString(fileName);
812 : }
813 :
814 1079170 : if (nMaxFiles > 0 && oDir.Count() > nMaxFiles)
815 1 : break;
816 : }
817 :
818 1391 : CPLFree(archiveFilename);
819 1391 : return oDir.StealList();
820 : }
821 :
822 : /************************************************************************/
823 : /* IsLocal() */
824 : /************************************************************************/
825 :
826 0 : bool VSIArchiveFilesystemHandler::IsLocal(const char *pszPath)
827 : {
828 0 : if (!STARTS_WITH(pszPath, GetPrefix()))
829 0 : return false;
830 0 : const char *pszBaseFileName = pszPath + strlen(GetPrefix());
831 : VSIFilesystemHandler *poFSHandler =
832 0 : VSIFileManager::GetHandler(pszBaseFileName);
833 0 : return poFSHandler->IsLocal(pszPath);
834 : }
835 :
836 : //! @endcond
|