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 582187 : static bool IsEitherSlash(char c)
36 : {
37 582187 : return c == '/' || c == '\\';
38 : }
39 :
40 : /************************************************************************/
41 : /* ~VSIArchiveEntryFileOffset() */
42 : /************************************************************************/
43 :
44 1510 : VSIArchiveEntryFileOffset::~VSIArchiveEntryFileOffset()
45 : {
46 1510 : }
47 :
48 : /************************************************************************/
49 : /* ~VSIArchiveReader() */
50 : /************************************************************************/
51 :
52 5057 : VSIArchiveReader::~VSIArchiveReader()
53 : {
54 5057 : }
55 :
56 : /************************************************************************/
57 : /* ~VSIArchiveContent() */
58 : /************************************************************************/
59 :
60 130 : VSIArchiveContent::~VSIArchiveContent()
61 : {
62 1650 : for (int i = 0; i < nEntries; i++)
63 : {
64 1585 : delete entries[i].file_pos;
65 1585 : CPLFree(entries[i].fileName);
66 : }
67 65 : CPLFree(entries);
68 65 : }
69 :
70 : /************************************************************************/
71 : /* VSIArchiveFilesystemHandler() */
72 : /************************************************************************/
73 :
74 2792 : VSIArchiveFilesystemHandler::VSIArchiveFilesystemHandler()
75 : {
76 2792 : hMutex = nullptr;
77 2792 : }
78 :
79 : /************************************************************************/
80 : /* ~VSIArchiveFilesystemHandler() */
81 : /************************************************************************/
82 :
83 1844 : VSIArchiveFilesystemHandler::~VSIArchiveFilesystemHandler()
84 :
85 : {
86 1894 : for (const auto &iter : oFileList)
87 : {
88 50 : delete iter.second;
89 : }
90 :
91 1844 : if (hMutex != nullptr)
92 17 : CPLDestroyMutex(hMutex);
93 1844 : hMutex = nullptr;
94 1844 : }
95 :
96 : /************************************************************************/
97 : /* GetStrippedFilename() */
98 : /************************************************************************/
99 :
100 5165 : static CPLString GetStrippedFilename(const CPLString &osFileName, bool &bIsDir)
101 : {
102 5165 : bIsDir = false;
103 5165 : const char *fileName = osFileName.c_str();
104 :
105 : // Remove ./ pattern at the beginning of a filename.
106 5165 : if (fileName[0] == '.' && fileName[1] == '/')
107 : {
108 0 : fileName += 2;
109 0 : if (fileName[0] == '\0')
110 0 : return CPLString();
111 : }
112 :
113 5165 : char *pszStrippedFileName = CPLStrdup(fileName);
114 5165 : char *pszIter = nullptr;
115 184286 : for (pszIter = pszStrippedFileName; *pszIter; pszIter++)
116 : {
117 179121 : if (*pszIter == '\\')
118 0 : *pszIter = '/';
119 : }
120 :
121 5165 : const size_t nLen = strlen(fileName);
122 5165 : bIsDir = nLen > 0 && fileName[nLen - 1] == '/';
123 5165 : if (bIsDir)
124 : {
125 : // Remove trailing slash.
126 202 : pszStrippedFileName[nLen - 1] = '\0';
127 : }
128 10330 : CPLString osRet(pszStrippedFileName);
129 5165 : CPLFree(pszStrippedFileName);
130 5165 : return osRet;
131 : }
132 :
133 : /************************************************************************/
134 : /* GetContentOfArchive() */
135 : /************************************************************************/
136 :
137 : const VSIArchiveContent *
138 8425 : VSIArchiveFilesystemHandler::GetContentOfArchive(const char *archiveFilename,
139 : VSIArchiveReader *poReader)
140 : {
141 16850 : CPLMutexHolder oHolder(&hMutex);
142 :
143 : VSIStatBufL sStat;
144 8425 : if (VSIStatL(archiveFilename, &sStat) != 0)
145 0 : return nullptr;
146 :
147 8425 : if (oFileList.find(archiveFilename) != oFileList.end())
148 : {
149 8219 : VSIArchiveContent *content = oFileList[archiveFilename];
150 8219 : if (static_cast<time_t>(sStat.st_mtime) > content->mTime ||
151 8218 : static_cast<vsi_l_offset>(sStat.st_size) != content->nFileSize)
152 : {
153 5 : CPLDebug("VSIArchive",
154 : "The content of %s has changed since it was cached",
155 : archiveFilename);
156 5 : delete content;
157 5 : oFileList.erase(archiveFilename);
158 : }
159 : else
160 : {
161 8214 : return content;
162 : }
163 : }
164 :
165 211 : bool bMustClose = poReader == nullptr;
166 211 : if (poReader == nullptr)
167 : {
168 210 : poReader = CreateReader(archiveFilename);
169 210 : if (!poReader)
170 7 : return nullptr;
171 : }
172 :
173 204 : if (poReader->GotoFirstFile() == FALSE)
174 : {
175 0 : if (bMustClose)
176 0 : delete (poReader);
177 0 : return nullptr;
178 : }
179 :
180 204 : VSIArchiveContent *content = new VSIArchiveContent;
181 204 : content->mTime = sStat.st_mtime;
182 204 : content->nFileSize = static_cast<vsi_l_offset>(sStat.st_size);
183 204 : content->nEntries = 0;
184 204 : content->entries = nullptr;
185 204 : oFileList[archiveFilename] = content;
186 :
187 204 : std::set<CPLString> oSet;
188 :
189 4820 : do
190 : {
191 5024 : const CPLString osFileName = poReader->GetFileName();
192 5024 : bool bIsDir = false;
193 : const CPLString osStrippedFilename =
194 5024 : GetStrippedFilename(osFileName, bIsDir);
195 10048 : if (osStrippedFilename.empty() || osStrippedFilename[0] == '/' ||
196 5024 : osStrippedFilename.find("//") != std::string::npos)
197 : {
198 0 : continue;
199 : }
200 :
201 5024 : if (oSet.find(osStrippedFilename) == oSet.end())
202 : {
203 5024 : oSet.insert(osStrippedFilename);
204 :
205 : // Add intermediate directory structure.
206 5024 : const char *pszBegin = osStrippedFilename.c_str();
207 182159 : for (const char *pszIter = pszBegin; *pszIter; pszIter++)
208 : {
209 177135 : if (*pszIter == '/')
210 : {
211 6358 : char *pszStrippedFileName2 = CPLStrdup(osStrippedFilename);
212 6358 : pszStrippedFileName2[pszIter - pszBegin] = 0;
213 6358 : if (oSet.find(pszStrippedFileName2) == oSet.end())
214 : {
215 1247 : oSet.insert(pszStrippedFileName2);
216 :
217 1247 : content->entries =
218 2494 : static_cast<VSIArchiveEntry *>(CPLRealloc(
219 1247 : content->entries, sizeof(VSIArchiveEntry) *
220 1247 : (content->nEntries + 1)));
221 1247 : content->entries[content->nEntries].fileName =
222 : pszStrippedFileName2;
223 2494 : content->entries[content->nEntries].nModifiedTime =
224 1247 : poReader->GetModifiedTime();
225 1247 : content->entries[content->nEntries].uncompressed_size =
226 : 0;
227 1247 : content->entries[content->nEntries].bIsDir = TRUE;
228 1247 : 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 1247 : content->nEntries++;
238 : }
239 : else
240 : {
241 5111 : CPLFree(pszStrippedFileName2);
242 : }
243 : }
244 : }
245 :
246 5024 : content->entries = static_cast<VSIArchiveEntry *>(
247 10048 : CPLRealloc(content->entries,
248 5024 : sizeof(VSIArchiveEntry) * (content->nEntries + 1)));
249 10048 : content->entries[content->nEntries].fileName =
250 5024 : CPLStrdup(osStrippedFilename);
251 10048 : content->entries[content->nEntries].nModifiedTime =
252 5024 : poReader->GetModifiedTime();
253 10048 : content->entries[content->nEntries].uncompressed_size =
254 5024 : poReader->GetFileSize();
255 5024 : content->entries[content->nEntries].bIsDir = bIsDir;
256 10048 : content->entries[content->nEntries].file_pos =
257 5024 : 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 5024 : content->nEntries++;
265 : }
266 :
267 5024 : } while (poReader->GotoNextFile());
268 :
269 204 : if (bMustClose)
270 203 : delete (poReader);
271 :
272 204 : return content;
273 : }
274 :
275 : /************************************************************************/
276 : /* FindFileInArchive() */
277 : /************************************************************************/
278 :
279 7044 : int VSIArchiveFilesystemHandler::FindFileInArchive(
280 : const char *archiveFilename, const char *fileInArchiveName,
281 : const VSIArchiveEntry **archiveEntry)
282 : {
283 7044 : if (fileInArchiveName == nullptr)
284 0 : return FALSE;
285 :
286 7044 : const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
287 7044 : if (content)
288 : {
289 1072940 : for (int i = 0; i < content->nEntries; i++)
290 : {
291 1072620 : if (strcmp(fileInArchiveName, content->entries[i].fileName) == 0)
292 : {
293 6720 : if (archiveEntry)
294 6720 : *archiveEntry = &content->entries[i];
295 6720 : return TRUE;
296 : }
297 : }
298 : }
299 324 : return FALSE;
300 : }
301 :
302 : /************************************************************************/
303 : /* CompactFilename() */
304 : /************************************************************************/
305 :
306 13173 : static std::string CompactFilename(const char *pszArchiveInFileNameIn)
307 : {
308 26346 : std::string osRet(pszArchiveInFileNameIn);
309 :
310 : // Replace a/../b by b and foo/a/../b by foo/b.
311 : while (true)
312 : {
313 13173 : size_t nSlashDotDot = osRet.find("/../");
314 13173 : 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 13173 : return osRet;
326 : }
327 :
328 : /************************************************************************/
329 : /* SplitFilename() */
330 : /************************************************************************/
331 :
332 14952 : char *VSIArchiveFilesystemHandler::SplitFilename(const char *pszFilename,
333 : CPLString &osFileInArchive,
334 : int bCheckMainFileExists)
335 : {
336 : // TODO(schwehr): Cleanup redundant calls to GetPrefix and strlen.
337 14952 : if (strcmp(pszFilename, GetPrefix()) == 0)
338 4 : return nullptr;
339 :
340 14948 : int i = 0;
341 :
342 : // Detect extended syntax: /vsiXXX/{archive_filename}/file_in_archive.
343 14948 : if (pszFilename[strlen(GetPrefix()) + 1] == '{')
344 : {
345 1317 : pszFilename += strlen(GetPrefix()) + 1;
346 1317 : int nCountCurlies = 0;
347 48753 : while (pszFilename[i])
348 : {
349 48752 : if (pszFilename[i] == '{')
350 1320 : nCountCurlies++;
351 47432 : else if (pszFilename[i] == '}')
352 : {
353 1319 : nCountCurlies--;
354 1319 : if (nCountCurlies == 0)
355 1316 : break;
356 : }
357 47436 : i++;
358 : }
359 1317 : if (nCountCurlies > 0)
360 1 : return nullptr;
361 1316 : char *archiveFilename = CPLStrdup(pszFilename + 1);
362 1316 : archiveFilename[i - 1] = 0;
363 :
364 1316 : bool bArchiveFileExists = false;
365 1316 : if (!bCheckMainFileExists)
366 : {
367 35 : bArchiveFileExists = true;
368 : }
369 : else
370 : {
371 2562 : CPLMutexHolder oHolder(&hMutex);
372 :
373 1281 : if (oFileList.find(archiveFilename) != oFileList.end())
374 : {
375 1146 : bArchiveFileExists = true;
376 : }
377 : }
378 :
379 1316 : if (!bArchiveFileExists)
380 : {
381 : VSIStatBufL statBuf;
382 : VSIFilesystemHandler *poFSHandler =
383 135 : VSIFileManager::GetHandler(archiveFilename);
384 405 : if (poFSHandler->Stat(archiveFilename, &statBuf,
385 : VSI_STAT_EXISTS_FLAG |
386 269 : VSI_STAT_NATURE_FLAG) == 0 &&
387 134 : !VSI_ISDIR(statBuf.st_mode))
388 : {
389 134 : bArchiveFileExists = true;
390 : }
391 : }
392 :
393 1316 : if (bArchiveFileExists)
394 : {
395 1315 : if (IsEitherSlash(pszFilename[i + 1]))
396 : {
397 1259 : osFileInArchive = CompactFilename(pszFilename + i + 2);
398 : }
399 56 : else if (pszFilename[i + 1] == '\0')
400 : {
401 55 : osFileInArchive = "";
402 : }
403 : else
404 : {
405 1 : CPLFree(archiveFilename);
406 1 : return nullptr;
407 : }
408 :
409 : // Remove trailing slash.
410 1314 : if (!osFileInArchive.empty())
411 : {
412 1255 : const char lastC = osFileInArchive.back();
413 1255 : if (IsEitherSlash(lastC))
414 2 : osFileInArchive.pop_back();
415 : }
416 :
417 1314 : 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 27262 : CPLString osDoubleVsi(GetPrefix());
427 13631 : osDoubleVsi += "/vsi";
428 :
429 13631 : if (strncmp(pszFilename, osDoubleVsi.c_str(), osDoubleVsi.size()) == 0)
430 4080 : pszFilename += strlen(GetPrefix());
431 : else
432 9551 : 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 13631 : int *pnCounter = static_cast<int *>(CPLGetTLS(CTLS_ABSTRACTARCHIVE_SPLIT));
439 13631 : if (pnCounter == nullptr)
440 : {
441 25 : pnCounter = static_cast<int *>(CPLMalloc(sizeof(int)));
442 25 : *pnCounter = 0;
443 25 : CPLSetTLS(CTLS_ABSTRACTARCHIVE_SPLIT, pnCounter, TRUE);
444 : }
445 13631 : 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 27134 : const std::vector<CPLString> oExtensions = GetExtensions();
454 13567 : int nAttempts = 0;
455 419122 : while (pszFilename[i])
456 : {
457 418936 : int nToSkip = 0;
458 :
459 2841080 : for (std::vector<CPLString>::const_iterator iter = oExtensions.begin();
460 5263230 : iter != oExtensions.end(); ++iter)
461 : {
462 2436170 : const CPLString &osExtension = *iter;
463 2436170 : if (EQUALN(pszFilename + i, osExtension.c_str(),
464 : osExtension.size()))
465 : {
466 14022 : nToSkip = static_cast<int>(osExtension.size());
467 14022 : break;
468 : }
469 : }
470 :
471 : #ifdef DEBUG
472 : // For AFL, so that .cur_input is detected as the archive filename.
473 418936 : if (EQUALN(pszFilename + i, ".cur_input", strlen(".cur_input")))
474 : {
475 14 : nToSkip = static_cast<int>(strlen(".cur_input"));
476 : }
477 : #endif
478 :
479 418936 : if (nToSkip != 0)
480 : {
481 14036 : 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 14036 : if (nAttempts == 5)
485 : {
486 21 : break;
487 : }
488 : VSIStatBufL statBuf;
489 14015 : char *archiveFilename = CPLStrdup(pszFilename);
490 14015 : bool bArchiveFileExists = false;
491 :
492 14015 : if (IsEitherSlash(archiveFilename[i + nToSkip]))
493 : {
494 12369 : archiveFilename[i + nToSkip] = 0;
495 : }
496 :
497 14015 : if (!bCheckMainFileExists)
498 : {
499 954 : bArchiveFileExists = true;
500 : }
501 : else
502 : {
503 26122 : CPLMutexHolder oHolder(&hMutex);
504 :
505 13061 : if (oFileList.find(archiveFilename) != oFileList.end())
506 : {
507 12008 : bArchiveFileExists = true;
508 : }
509 : }
510 :
511 14015 : if (!bArchiveFileExists)
512 : {
513 1053 : (*pnCounter)++;
514 :
515 : VSIFilesystemHandler *poFSHandler =
516 1053 : VSIFileManager::GetHandler(archiveFilename);
517 3159 : if (poFSHandler->Stat(archiveFilename, &statBuf,
518 : VSI_STAT_EXISTS_FLAG |
519 1871 : VSI_STAT_NATURE_FLAG) == 0 &&
520 818 : !VSI_ISDIR(statBuf.st_mode))
521 : {
522 398 : bArchiveFileExists = true;
523 : }
524 :
525 1053 : (*pnCounter)--;
526 : }
527 :
528 14015 : if (bArchiveFileExists)
529 : {
530 13360 : if (IsEitherSlash(pszFilename[i + nToSkip]))
531 : {
532 : osFileInArchive =
533 11914 : CompactFilename(pszFilename + i + nToSkip + 1);
534 : }
535 : else
536 : {
537 1446 : osFileInArchive = "";
538 : }
539 :
540 : // Remove trailing slash.
541 13360 : if (!osFileInArchive.empty())
542 : {
543 11906 : const char lastC = osFileInArchive.back();
544 11906 : if (IsEitherSlash(lastC))
545 39 : osFileInArchive.resize(osFileInArchive.size() - 1);
546 : }
547 :
548 13360 : return archiveFilename;
549 : }
550 655 : CPLFree(archiveFilename);
551 : }
552 405555 : i++;
553 : }
554 207 : return nullptr;
555 : }
556 :
557 : /************************************************************************/
558 : /* OpenArchiveFile() */
559 : /************************************************************************/
560 :
561 : VSIArchiveReader *
562 4364 : VSIArchiveFilesystemHandler::OpenArchiveFile(const char *archiveFilename,
563 : const char *fileInArchiveName)
564 : {
565 4364 : VSIArchiveReader *poReader = CreateReader(archiveFilename);
566 :
567 4364 : if (poReader == nullptr)
568 : {
569 6 : return nullptr;
570 : }
571 :
572 4358 : 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 4285 : CPLMutexHolder oHolder(&hMutex);
623 :
624 4285 : if (oFileList.find(archiveFilename) == oFileList.end())
625 : {
626 141 : if (poReader->GotoFirstFile() == FALSE)
627 : {
628 0 : delete (poReader);
629 54 : return nullptr;
630 : }
631 :
632 141 : const CPLString osFileName = poReader->GetFileName();
633 141 : bool bIsDir = false;
634 : const CPLString osStrippedFilename =
635 141 : GetStrippedFilename(osFileName, bIsDir);
636 141 : if (!osStrippedFilename.empty())
637 : {
638 : const bool bMatch =
639 141 : strcmp(osStrippedFilename, fileInArchiveName) == 0;
640 141 : if (bMatch)
641 : {
642 54 : if (bIsDir)
643 : {
644 0 : delete (poReader);
645 0 : return nullptr;
646 : }
647 54 : return poReader;
648 : }
649 : }
650 : }
651 : }
652 :
653 4231 : const VSIArchiveEntry *archiveEntry = nullptr;
654 12693 : if (FindFileInArchive(archiveFilename, fileInArchiveName,
655 8297 : &archiveEntry) == FALSE ||
656 4066 : archiveEntry->bIsDir)
657 : {
658 169 : delete (poReader);
659 169 : return nullptr;
660 : }
661 4062 : if (!poReader->GotoFileOffset(archiveEntry->file_pos))
662 : {
663 0 : delete poReader;
664 0 : return nullptr;
665 : }
666 : }
667 4134 : return poReader;
668 : }
669 :
670 : /************************************************************************/
671 : /* Stat() */
672 : /************************************************************************/
673 :
674 3381 : int VSIArchiveFilesystemHandler::Stat(const char *pszFilename,
675 : VSIStatBufL *pStatBuf, int /* nFlags */)
676 : {
677 3381 : memset(pStatBuf, 0, sizeof(VSIStatBufL));
678 :
679 6762 : CPLString osFileInArchive;
680 3381 : char *archiveFilename = SplitFilename(pszFilename, osFileInArchive, TRUE);
681 3381 : if (archiveFilename == nullptr)
682 85 : return -1;
683 :
684 3296 : int ret = -1;
685 3296 : if (!osFileInArchive.empty())
686 : {
687 : #ifdef DEBUG_VERBOSE
688 : CPLDebug("VSIArchive", "Looking for %s %s", archiveFilename,
689 : osFileInArchive.c_str());
690 : #endif
691 :
692 2813 : const VSIArchiveEntry *archiveEntry = nullptr;
693 2813 : if (FindFileInArchive(archiveFilename, osFileInArchive, &archiveEntry))
694 : {
695 : // Patching st_size with uncompressed file size.
696 2654 : pStatBuf->st_size = archiveEntry->uncompressed_size;
697 2654 : pStatBuf->st_mtime =
698 2654 : static_cast<time_t>(archiveEntry->nModifiedTime);
699 2654 : if (archiveEntry->bIsDir)
700 1142 : pStatBuf->st_mode = S_IFDIR;
701 : else
702 1512 : pStatBuf->st_mode = S_IFREG;
703 2654 : ret = 0;
704 : }
705 : }
706 : else
707 : {
708 483 : VSIArchiveReader *poReader = CreateReader(archiveFilename);
709 483 : CPLFree(archiveFilename);
710 483 : archiveFilename = nullptr;
711 :
712 483 : if (poReader != nullptr && poReader->GotoFirstFile())
713 : {
714 : // Skip optional leading subdir.
715 478 : const CPLString osFileName = poReader->GetFileName();
716 478 : if (IsEitherSlash(osFileName.back()))
717 : {
718 11 : if (poReader->GotoNextFile() == FALSE)
719 : {
720 0 : delete (poReader);
721 0 : return -1;
722 : }
723 : }
724 :
725 478 : if (poReader->GotoNextFile())
726 : {
727 : // Several files in archive --> treat as dir.
728 450 : pStatBuf->st_size = 0;
729 450 : 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 478 : ret = 0;
741 : }
742 :
743 483 : delete (poReader);
744 : }
745 :
746 3296 : CPLFree(archiveFilename);
747 3296 : return ret;
748 : }
749 :
750 : /************************************************************************/
751 : /* Unlink() */
752 : /************************************************************************/
753 :
754 2 : int VSIArchiveFilesystemHandler::Unlink(const char * /* pszFilename */)
755 : {
756 2 : return -1;
757 : }
758 :
759 : /************************************************************************/
760 : /* Rename() */
761 : /************************************************************************/
762 :
763 0 : int VSIArchiveFilesystemHandler::Rename(const char * /* oldpath */,
764 : const char * /* newpath */)
765 : {
766 0 : return -1;
767 : }
768 :
769 : /************************************************************************/
770 : /* Mkdir() */
771 : /************************************************************************/
772 :
773 0 : int VSIArchiveFilesystemHandler::Mkdir(const char * /* pszDirname */,
774 : long /* nMode */)
775 : {
776 0 : return -1;
777 : }
778 :
779 : /************************************************************************/
780 : /* Rmdir() */
781 : /************************************************************************/
782 :
783 0 : int VSIArchiveFilesystemHandler::Rmdir(const char * /* pszDirname */)
784 : {
785 0 : return -1;
786 : }
787 :
788 : /************************************************************************/
789 : /* ReadDirEx() */
790 : /************************************************************************/
791 :
792 1380 : char **VSIArchiveFilesystemHandler::ReadDirEx(const char *pszDirname,
793 : int nMaxFiles)
794 : {
795 2760 : CPLString osInArchiveSubDir;
796 1380 : char *archiveFilename = SplitFilename(pszDirname, osInArchiveSubDir, TRUE);
797 1380 : if (archiveFilename == nullptr)
798 0 : return nullptr;
799 :
800 1380 : const int lenInArchiveSubDir = static_cast<int>(osInArchiveSubDir.size());
801 :
802 2760 : CPLStringList oDir;
803 :
804 1380 : const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
805 1380 : if (!content)
806 : {
807 4 : CPLFree(archiveFilename);
808 4 : return nullptr;
809 : }
810 :
811 : #ifdef DEBUG_VERBOSE
812 : CPLDebug("VSIArchive", "Read dir %s", pszDirname);
813 : #endif
814 1080470 : for (int i = 0; i < content->nEntries; i++)
815 : {
816 1079100 : const char *fileName = content->entries[i].fileName;
817 : /* Only list entries at the same level of inArchiveSubDir */
818 2120060 : if (lenInArchiveSubDir != 0 &&
819 1580750 : strncmp(fileName, osInArchiveSubDir, lenInArchiveSubDir) == 0 &&
820 2659850 : IsEitherSlash(fileName[lenInArchiveSubDir]) &&
821 538625 : fileName[lenInArchiveSubDir + 1] != 0)
822 : {
823 538625 : const char *slash = strchr(fileName + lenInArchiveSubDir + 1, '/');
824 538625 : if (slash == nullptr)
825 38517 : slash = strchr(fileName + lenInArchiveSubDir + 1, '\\');
826 538625 : if (slash == nullptr || slash[1] == 0)
827 : {
828 38517 : char *tmpFileName = CPLStrdup(fileName);
829 38517 : if (slash != nullptr)
830 : {
831 0 : tmpFileName[strlen(tmpFileName) - 1] = 0;
832 : }
833 : #ifdef DEBUG_VERBOSE
834 : CPLDebug("VSIArchive", "Add %s as in directory %s",
835 : tmpFileName + lenInArchiveSubDir + 1, pszDirname);
836 : #endif
837 38517 : oDir.AddString(tmpFileName + lenInArchiveSubDir + 1);
838 38517 : CPLFree(tmpFileName);
839 : }
840 : }
841 540470 : else if (lenInArchiveSubDir == 0 && strchr(fileName, '/') == nullptr &&
842 2412 : strchr(fileName, '\\') == nullptr)
843 : {
844 : // Only list toplevel files and directories.
845 : #ifdef DEBUG_VERBOSE
846 : CPLDebug("VSIArchive", "Add %s as in directory %s", fileName,
847 : pszDirname);
848 : #endif
849 2412 : oDir.AddString(fileName);
850 : }
851 :
852 1079100 : if (nMaxFiles > 0 && oDir.Count() > nMaxFiles)
853 1 : break;
854 : }
855 :
856 1376 : CPLFree(archiveFilename);
857 1376 : return oDir.StealList();
858 : }
859 :
860 : /************************************************************************/
861 : /* IsLocal() */
862 : /************************************************************************/
863 :
864 0 : bool VSIArchiveFilesystemHandler::IsLocal(const char *pszPath)
865 : {
866 0 : if (!STARTS_WITH(pszPath, GetPrefix()))
867 0 : return false;
868 0 : const char *pszBaseFileName = pszPath + strlen(GetPrefix());
869 : VSIFilesystemHandler *poFSHandler =
870 0 : VSIFileManager::GetHandler(pszBaseFileName);
871 0 : return poFSHandler->IsLocal(pszPath);
872 : }
873 :
874 : //! @endcond
|