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 581507 : static bool IsEitherSlash(char c)
36 : {
37 581507 : return c == '/' || c == '\\';
38 : }
39 :
40 : /************************************************************************/
41 : /* ~VSIArchiveEntryFileOffset() */
42 : /************************************************************************/
43 :
44 1510 : VSIArchiveEntryFileOffset::~VSIArchiveEntryFileOffset()
45 : {
46 1510 : }
47 :
48 : /************************************************************************/
49 : /* ~VSIArchiveReader() */
50 : /************************************************************************/
51 :
52 5054 : VSIArchiveReader::~VSIArchiveReader()
53 : {
54 5054 : }
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 2784 : VSIArchiveFilesystemHandler::VSIArchiveFilesystemHandler()
75 : {
76 2784 : hMutex = nullptr;
77 2784 : }
78 :
79 : /************************************************************************/
80 : /* ~VSIArchiveFilesystemHandler() */
81 : /************************************************************************/
82 :
83 1882 : VSIArchiveFilesystemHandler::~VSIArchiveFilesystemHandler()
84 :
85 : {
86 1932 : for (const auto &iter : oFileList)
87 : {
88 50 : delete iter.second;
89 : }
90 :
91 1882 : if (hMutex != nullptr)
92 17 : CPLDestroyMutex(hMutex);
93 1882 : hMutex = nullptr;
94 1882 : }
95 :
96 : /************************************************************************/
97 : /* GetStrippedFilename() */
98 : /************************************************************************/
99 :
100 4967 : static CPLString GetStrippedFilename(const CPLString &osFileName, bool &bIsDir)
101 : {
102 4967 : bIsDir = false;
103 4967 : const char *fileName = osFileName.c_str();
104 :
105 : // Remove ./ pattern at the beginning of a filename.
106 4967 : if (fileName[0] == '.' && fileName[1] == '/')
107 : {
108 0 : fileName += 2;
109 0 : if (fileName[0] == '\0')
110 0 : return CPLString();
111 : }
112 :
113 4967 : char *pszStrippedFileName = CPLStrdup(fileName);
114 4967 : char *pszIter = nullptr;
115 180338 : for (pszIter = pszStrippedFileName; *pszIter; pszIter++)
116 : {
117 175371 : if (*pszIter == '\\')
118 0 : *pszIter = '/';
119 : }
120 :
121 4967 : const size_t nLen = strlen(fileName);
122 4967 : bIsDir = nLen > 0 && fileName[nLen - 1] == '/';
123 4967 : if (bIsDir)
124 : {
125 : // Remove trailing slash.
126 202 : pszStrippedFileName[nLen - 1] = '\0';
127 : }
128 9934 : CPLString osRet(pszStrippedFileName);
129 4967 : CPLFree(pszStrippedFileName);
130 4967 : return osRet;
131 : }
132 :
133 : /************************************************************************/
134 : /* GetContentOfArchive() */
135 : /************************************************************************/
136 :
137 : const VSIArchiveContent *
138 8222 : VSIArchiveFilesystemHandler::GetContentOfArchive(const char *archiveFilename,
139 : VSIArchiveReader *poReader)
140 : {
141 16444 : CPLMutexHolder oHolder(&hMutex);
142 :
143 : VSIStatBufL sStat;
144 8222 : if (VSIStatL(archiveFilename, &sStat) != 0)
145 0 : return nullptr;
146 :
147 8222 : if (oFileList.find(archiveFilename) != oFileList.end())
148 : {
149 8017 : VSIArchiveContent *content = oFileList[archiveFilename];
150 8017 : if (static_cast<time_t>(sStat.st_mtime) > content->mTime ||
151 8016 : 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 8012 : return content;
162 : }
163 : }
164 :
165 210 : bool bMustClose = poReader == nullptr;
166 210 : if (poReader == nullptr)
167 : {
168 209 : poReader = CreateReader(archiveFilename);
169 209 : if (!poReader)
170 7 : return nullptr;
171 : }
172 :
173 203 : if (poReader->GotoFirstFile() == FALSE)
174 : {
175 0 : if (bMustClose)
176 0 : delete (poReader);
177 0 : return nullptr;
178 : }
179 :
180 203 : VSIArchiveContent *content = new VSIArchiveContent;
181 203 : content->mTime = sStat.st_mtime;
182 203 : content->nFileSize = static_cast<vsi_l_offset>(sStat.st_size);
183 203 : content->nEntries = 0;
184 203 : content->entries = nullptr;
185 203 : oFileList[archiveFilename] = content;
186 :
187 203 : std::set<CPLString> oSet;
188 :
189 4623 : do
190 : {
191 4826 : const CPLString osFileName = poReader->GetFileName();
192 4826 : bool bIsDir = false;
193 : const CPLString osStrippedFilename =
194 4826 : GetStrippedFilename(osFileName, bIsDir);
195 9652 : if (osStrippedFilename.empty() || osStrippedFilename[0] == '/' ||
196 4826 : osStrippedFilename.find("//") != std::string::npos)
197 : {
198 0 : continue;
199 : }
200 :
201 4826 : if (oSet.find(osStrippedFilename) == oSet.end())
202 : {
203 4826 : oSet.insert(osStrippedFilename);
204 :
205 : // Add intermediate directory structure.
206 4826 : const char *pszBegin = osStrippedFilename.c_str();
207 178211 : for (const char *pszIter = pszBegin; *pszIter; pszIter++)
208 : {
209 173385 : 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 4826 : content->entries = static_cast<VSIArchiveEntry *>(
247 9652 : CPLRealloc(content->entries,
248 4826 : sizeof(VSIArchiveEntry) * (content->nEntries + 1)));
249 9652 : content->entries[content->nEntries].fileName =
250 4826 : CPLStrdup(osStrippedFilename);
251 9652 : content->entries[content->nEntries].nModifiedTime =
252 4826 : poReader->GetModifiedTime();
253 9652 : content->entries[content->nEntries].uncompressed_size =
254 4826 : poReader->GetFileSize();
255 4826 : content->entries[content->nEntries].bIsDir = bIsDir;
256 9652 : content->entries[content->nEntries].file_pos =
257 4826 : 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 4826 : content->nEntries++;
265 : }
266 :
267 4826 : } while (poReader->GotoNextFile());
268 :
269 203 : if (bMustClose)
270 202 : delete (poReader);
271 :
272 203 : return content;
273 : }
274 :
275 : /************************************************************************/
276 : /* FindFileInArchive() */
277 : /************************************************************************/
278 :
279 6840 : int VSIArchiveFilesystemHandler::FindFileInArchive(
280 : const char *archiveFilename, const char *fileInArchiveName,
281 : const VSIArchiveEntry **archiveEntry)
282 : {
283 6840 : if (fileInArchiveName == nullptr)
284 0 : return FALSE;
285 :
286 6840 : const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
287 6840 : if (content)
288 : {
289 1052580 : for (int i = 0; i < content->nEntries; i++)
290 : {
291 1052260 : if (strcmp(fileInArchiveName, content->entries[i].fileName) == 0)
292 : {
293 6518 : if (archiveEntry)
294 6518 : *archiveEntry = &content->entries[i];
295 6518 : return TRUE;
296 : }
297 : }
298 : }
299 322 : return FALSE;
300 : }
301 :
302 : /************************************************************************/
303 : /* CompactFilename() */
304 : /************************************************************************/
305 :
306 12777 : static std::string CompactFilename(const char *pszArchiveInFileNameIn)
307 : {
308 25554 : std::string osRet(pszArchiveInFileNameIn);
309 :
310 : // Replace a/../b by b and foo/a/../b by foo/b.
311 : while (true)
312 : {
313 12777 : size_t nSlashDotDot = osRet.find("/../");
314 12777 : 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 12777 : return osRet;
326 : }
327 :
328 : /************************************************************************/
329 : /* SplitFilename() */
330 : /************************************************************************/
331 :
332 14560 : char *VSIArchiveFilesystemHandler::SplitFilename(const char *pszFilename,
333 : CPLString &osFileInArchive,
334 : int bCheckMainFileExists)
335 : {
336 : // TODO(schwehr): Cleanup redundant calls to GetPrefix and strlen.
337 14560 : if (strcmp(pszFilename, GetPrefix()) == 0)
338 4 : return nullptr;
339 :
340 14556 : int i = 0;
341 :
342 : // Detect extended syntax: /vsiXXX/{archive_filename}/file_in_archive.
343 14556 : 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 26478 : CPLString osDoubleVsi(GetPrefix());
427 13239 : osDoubleVsi += "/vsi";
428 :
429 13239 : if (strncmp(pszFilename, osDoubleVsi.c_str(), osDoubleVsi.size()) == 0)
430 4080 : pszFilename += strlen(GetPrefix());
431 : else
432 9159 : 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 13239 : int *pnCounter = static_cast<int *>(CPLGetTLS(CTLS_ABSTRACTARCHIVE_SPLIT));
439 13239 : 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 13239 : 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 26350 : const std::vector<CPLString> oExtensions = GetExtensions();
454 13175 : int nAttempts = 0;
455 408278 : while (pszFilename[i])
456 : {
457 408092 : int nToSkip = 0;
458 :
459 2767530 : for (std::vector<CPLString>::const_iterator iter = oExtensions.begin();
460 5126960 : iter != oExtensions.end(); ++iter)
461 : {
462 2373060 : const CPLString &osExtension = *iter;
463 2373060 : if (EQUALN(pszFilename + i, osExtension.c_str(),
464 : osExtension.size()))
465 : {
466 13630 : nToSkip = static_cast<int>(osExtension.size());
467 13630 : break;
468 : }
469 : }
470 :
471 : #ifdef DEBUG
472 : // For AFL, so that .cur_input is detected as the archive filename.
473 408092 : if (EQUALN(pszFilename + i, ".cur_input", strlen(".cur_input")))
474 : {
475 14 : nToSkip = static_cast<int>(strlen(".cur_input"));
476 : }
477 : #endif
478 :
479 408092 : if (nToSkip != 0)
480 : {
481 13644 : 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 13644 : if (nAttempts == 5)
485 : {
486 21 : break;
487 : }
488 : VSIStatBufL statBuf;
489 13623 : char *archiveFilename = CPLStrdup(pszFilename);
490 13623 : bool bArchiveFileExists = false;
491 :
492 13623 : if (IsEitherSlash(archiveFilename[i + nToSkip]))
493 : {
494 11973 : archiveFilename[i + nToSkip] = 0;
495 : }
496 :
497 13623 : if (!bCheckMainFileExists)
498 : {
499 954 : bArchiveFileExists = true;
500 : }
501 : else
502 : {
503 25338 : CPLMutexHolder oHolder(&hMutex);
504 :
505 12669 : if (oFileList.find(archiveFilename) != oFileList.end())
506 : {
507 11620 : bArchiveFileExists = true;
508 : }
509 : }
510 :
511 13623 : if (!bArchiveFileExists)
512 : {
513 1049 : (*pnCounter)++;
514 :
515 : VSIFilesystemHandler *poFSHandler =
516 1049 : VSIFileManager::GetHandler(archiveFilename);
517 3147 : if (poFSHandler->Stat(archiveFilename, &statBuf,
518 : VSI_STAT_EXISTS_FLAG |
519 1863 : VSI_STAT_NATURE_FLAG) == 0 &&
520 814 : !VSI_ISDIR(statBuf.st_mode))
521 : {
522 394 : bArchiveFileExists = true;
523 : }
524 :
525 1049 : (*pnCounter)--;
526 : }
527 :
528 13623 : if (bArchiveFileExists)
529 : {
530 12968 : if (IsEitherSlash(pszFilename[i + nToSkip]))
531 : {
532 : osFileInArchive =
533 11518 : CompactFilename(pszFilename + i + nToSkip + 1);
534 : }
535 : else
536 : {
537 1450 : osFileInArchive = "";
538 : }
539 :
540 : // Remove trailing slash.
541 12968 : if (!osFileInArchive.empty())
542 : {
543 11512 : const char lastC = osFileInArchive.back();
544 11512 : if (IsEitherSlash(lastC))
545 39 : osFileInArchive.resize(osFileInArchive.size() - 1);
546 : }
547 :
548 12968 : return archiveFilename;
549 : }
550 655 : CPLFree(archiveFilename);
551 : }
552 395103 : i++;
553 : }
554 207 : return nullptr;
555 : }
556 :
557 : /************************************************************************/
558 : /* OpenArchiveFile() */
559 : /************************************************************************/
560 :
561 : VSIArchiveReader *
562 4358 : VSIArchiveFilesystemHandler::OpenArchiveFile(const char *archiveFilename,
563 : const char *fileInArchiveName)
564 : {
565 4358 : VSIArchiveReader *poReader = CreateReader(archiveFilename);
566 :
567 4358 : if (poReader == nullptr)
568 : {
569 6 : return nullptr;
570 : }
571 :
572 4352 : 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 4279 : CPLMutexHolder oHolder(&hMutex);
623 :
624 4279 : 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 4225 : const VSIArchiveEntry *archiveEntry = nullptr;
654 12675 : if (FindFileInArchive(archiveFilename, fileInArchiveName,
655 8285 : &archiveEntry) == FALSE ||
656 4060 : archiveEntry->bIsDir)
657 : {
658 169 : delete (poReader);
659 169 : return nullptr;
660 : }
661 4056 : if (!poReader->GotoFileOffset(archiveEntry->file_pos))
662 : {
663 0 : delete poReader;
664 0 : return nullptr;
665 : }
666 : }
667 4128 : return poReader;
668 : }
669 :
670 : /************************************************************************/
671 : /* Stat() */
672 : /************************************************************************/
673 :
674 3187 : int VSIArchiveFilesystemHandler::Stat(const char *pszFilename,
675 : VSIStatBufL *pStatBuf, int /* nFlags */)
676 : {
677 3187 : memset(pStatBuf, 0, sizeof(VSIStatBufL));
678 :
679 6374 : CPLString osFileInArchive;
680 3187 : char *archiveFilename = SplitFilename(pszFilename, osFileInArchive, TRUE);
681 3187 : if (archiveFilename == nullptr)
682 85 : return -1;
683 :
684 3102 : int ret = -1;
685 3102 : if (!osFileInArchive.empty())
686 : {
687 : #ifdef DEBUG_VERBOSE
688 : CPLDebug("VSIArchive", "Looking for %s %s", archiveFilename,
689 : osFileInArchive.c_str());
690 : #endif
691 :
692 2615 : const VSIArchiveEntry *archiveEntry = nullptr;
693 2615 : if (FindFileInArchive(archiveFilename, osFileInArchive, &archiveEntry))
694 : {
695 : // Patching st_size with uncompressed file size.
696 2458 : pStatBuf->st_size = archiveEntry->uncompressed_size;
697 2458 : pStatBuf->st_mtime =
698 2458 : static_cast<time_t>(archiveEntry->nModifiedTime);
699 2458 : if (archiveEntry->bIsDir)
700 1146 : pStatBuf->st_mode = S_IFDIR;
701 : else
702 1312 : pStatBuf->st_mode = S_IFREG;
703 2458 : ret = 0;
704 : }
705 : }
706 : else
707 : {
708 487 : VSIArchiveReader *poReader = CreateReader(archiveFilename);
709 487 : CPLFree(archiveFilename);
710 487 : archiveFilename = nullptr;
711 :
712 487 : if (poReader != nullptr && poReader->GotoFirstFile())
713 : {
714 : // Skip optional leading subdir.
715 482 : const CPLString osFileName = poReader->GetFileName();
716 482 : if (IsEitherSlash(osFileName.back()))
717 : {
718 16 : if (poReader->GotoNextFile() == FALSE)
719 : {
720 0 : delete (poReader);
721 0 : return -1;
722 : }
723 : }
724 :
725 482 : if (poReader->GotoNextFile())
726 : {
727 : // Several files in archive --> treat as dir.
728 454 : pStatBuf->st_size = 0;
729 454 : 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 482 : ret = 0;
741 : }
742 :
743 487 : delete (poReader);
744 : }
745 :
746 3102 : CPLFree(archiveFilename);
747 3102 : 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 1381 : char **VSIArchiveFilesystemHandler::ReadDirEx(const char *pszDirname,
793 : int nMaxFiles)
794 : {
795 2762 : CPLString osInArchiveSubDir;
796 1381 : char *archiveFilename = SplitFilename(pszDirname, osInArchiveSubDir, TRUE);
797 1381 : if (archiveFilename == nullptr)
798 0 : return nullptr;
799 :
800 1381 : const int lenInArchiveSubDir = static_cast<int>(osInArchiveSubDir.size());
801 :
802 2762 : CPLStringList oDir;
803 :
804 1381 : const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
805 1381 : 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 1079240 : for (int i = 0; i < content->nEntries; i++)
815 : {
816 1077870 : const char *fileName = content->entries[i].fileName;
817 : /* Only list entries at the same level of inArchiveSubDir */
818 2119330 : if (lenInArchiveSubDir != 0 &&
819 1581740 : strncmp(fileName, osInArchiveSubDir, lenInArchiveSubDir) == 0 &&
820 2659610 : IsEitherSlash(fileName[lenInArchiveSubDir]) &&
821 539115 : fileName[lenInArchiveSubDir + 1] != 0)
822 : {
823 539115 : const char *slash = strchr(fileName + lenInArchiveSubDir + 1, '/');
824 539115 : if (slash == nullptr)
825 39007 : slash = strchr(fileName + lenInArchiveSubDir + 1, '\\');
826 539115 : if (slash == nullptr || slash[1] == 0)
827 : {
828 39007 : char *tmpFileName = CPLStrdup(fileName);
829 39007 : 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 39007 : oDir.AddString(tmpFileName + lenInArchiveSubDir + 1);
838 39007 : CPLFree(tmpFileName);
839 : }
840 : }
841 538753 : else if (lenInArchiveSubDir == 0 && strchr(fileName, '/') == nullptr &&
842 442 : 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 442 : oDir.AddString(fileName);
850 : }
851 :
852 1077870 : if (nMaxFiles > 0 && oDir.Count() > nMaxFiles)
853 1 : break;
854 : }
855 :
856 1377 : CPLFree(archiveFilename);
857 1377 : 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
|