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 581473 : static bool IsEitherSlash(char c)
36 : {
37 581473 : return c == '/' || c == '\\';
38 : }
39 :
40 : /************************************************************************/
41 : /* ~VSIArchiveEntryFileOffset() */
42 : /************************************************************************/
43 :
44 1503 : VSIArchiveEntryFileOffset::~VSIArchiveEntryFileOffset()
45 : {
46 1503 : }
47 :
48 : /************************************************************************/
49 : /* ~VSIArchiveReader() */
50 : /************************************************************************/
51 :
52 5044 : VSIArchiveReader::~VSIArchiveReader()
53 : {
54 5044 : }
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 2608 : VSIArchiveFilesystemHandler::VSIArchiveFilesystemHandler()
75 : {
76 2608 : hMutex = nullptr;
77 2608 : }
78 :
79 : /************************************************************************/
80 : /* ~VSIArchiveFilesystemHandler() */
81 : /************************************************************************/
82 :
83 1866 : VSIArchiveFilesystemHandler::~VSIArchiveFilesystemHandler()
84 :
85 : {
86 1916 : for (const auto &iter : oFileList)
87 : {
88 50 : delete iter.second;
89 : }
90 :
91 1866 : if (hMutex != nullptr)
92 17 : CPLDestroyMutex(hMutex);
93 1866 : hMutex = nullptr;
94 1866 : }
95 :
96 : /************************************************************************/
97 : /* GetStrippedFilename() */
98 : /************************************************************************/
99 :
100 4959 : static CPLString GetStrippedFilename(const CPLString &osFileName, bool &bIsDir)
101 : {
102 4959 : bIsDir = false;
103 4959 : const char *fileName = osFileName.c_str();
104 :
105 : // Remove ./ pattern at the beginning of a filename.
106 4959 : if (fileName[0] == '.' && fileName[1] == '/')
107 : {
108 0 : fileName += 2;
109 0 : if (fileName[0] == '\0')
110 0 : return CPLString();
111 : }
112 :
113 4959 : char *pszStrippedFileName = CPLStrdup(fileName);
114 4959 : char *pszIter = nullptr;
115 180199 : for (pszIter = pszStrippedFileName; *pszIter; pszIter++)
116 : {
117 175240 : if (*pszIter == '\\')
118 0 : *pszIter = '/';
119 : }
120 :
121 4959 : const size_t nLen = strlen(fileName);
122 4959 : bIsDir = nLen > 0 && fileName[nLen - 1] == '/';
123 4959 : if (bIsDir)
124 : {
125 : // Remove trailing slash.
126 198 : pszStrippedFileName[nLen - 1] = '\0';
127 : }
128 9918 : CPLString osRet(pszStrippedFileName);
129 4959 : CPLFree(pszStrippedFileName);
130 4959 : return osRet;
131 : }
132 :
133 : /************************************************************************/
134 : /* GetContentOfArchive() */
135 : /************************************************************************/
136 :
137 : const VSIArchiveContent *
138 8212 : VSIArchiveFilesystemHandler::GetContentOfArchive(const char *archiveFilename,
139 : VSIArchiveReader *poReader)
140 : {
141 16424 : CPLMutexHolder oHolder(&hMutex);
142 :
143 : VSIStatBufL sStat;
144 8212 : if (VSIStatL(archiveFilename, &sStat) != 0)
145 0 : return nullptr;
146 :
147 8212 : if (oFileList.find(archiveFilename) != oFileList.end())
148 : {
149 8009 : VSIArchiveContent *content = oFileList[archiveFilename];
150 8009 : if (static_cast<time_t>(sStat.st_mtime) > content->mTime ||
151 8008 : 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 8004 : return content;
162 : }
163 : }
164 :
165 208 : bool bMustClose = poReader == nullptr;
166 208 : if (poReader == nullptr)
167 : {
168 207 : poReader = CreateReader(archiveFilename);
169 207 : if (!poReader)
170 7 : return nullptr;
171 : }
172 :
173 201 : if (poReader->GotoFirstFile() == FALSE)
174 : {
175 0 : if (bMustClose)
176 0 : delete (poReader);
177 0 : return nullptr;
178 : }
179 :
180 201 : VSIArchiveContent *content = new VSIArchiveContent;
181 201 : content->mTime = sStat.st_mtime;
182 201 : content->nFileSize = static_cast<vsi_l_offset>(sStat.st_size);
183 201 : content->nEntries = 0;
184 201 : content->entries = nullptr;
185 201 : oFileList[archiveFilename] = content;
186 :
187 201 : std::set<CPLString> oSet;
188 :
189 4618 : do
190 : {
191 4819 : const CPLString osFileName = poReader->GetFileName();
192 4819 : bool bIsDir = false;
193 : const CPLString osStrippedFilename =
194 4819 : GetStrippedFilename(osFileName, bIsDir);
195 9638 : if (osStrippedFilename.empty() || osStrippedFilename[0] == '/' ||
196 4819 : osStrippedFilename.find("//") != std::string::npos)
197 : {
198 0 : continue;
199 : }
200 :
201 4819 : if (oSet.find(osStrippedFilename) == oSet.end())
202 : {
203 4819 : oSet.insert(osStrippedFilename);
204 :
205 : // Add intermediate directory structure.
206 4819 : const char *pszBegin = osStrippedFilename.c_str();
207 178083 : for (const char *pszIter = pszBegin; *pszIter; pszIter++)
208 : {
209 173264 : if (*pszIter == '/')
210 : {
211 6350 : char *pszStrippedFileName2 = CPLStrdup(osStrippedFilename);
212 6350 : pszStrippedFileName2[pszIter - pszBegin] = 0;
213 6350 : 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 5103 : CPLFree(pszStrippedFileName2);
242 : }
243 : }
244 : }
245 :
246 4819 : content->entries = static_cast<VSIArchiveEntry *>(
247 9638 : CPLRealloc(content->entries,
248 4819 : sizeof(VSIArchiveEntry) * (content->nEntries + 1)));
249 9638 : content->entries[content->nEntries].fileName =
250 4819 : CPLStrdup(osStrippedFilename);
251 9638 : content->entries[content->nEntries].nModifiedTime =
252 4819 : poReader->GetModifiedTime();
253 9638 : content->entries[content->nEntries].uncompressed_size =
254 4819 : poReader->GetFileSize();
255 4819 : content->entries[content->nEntries].bIsDir = bIsDir;
256 9638 : content->entries[content->nEntries].file_pos =
257 4819 : 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 4819 : content->nEntries++;
265 : }
266 :
267 4819 : } while (poReader->GotoNextFile());
268 :
269 201 : if (bMustClose)
270 200 : delete (poReader);
271 :
272 201 : return content;
273 : }
274 :
275 : /************************************************************************/
276 : /* FindFileInArchive() */
277 : /************************************************************************/
278 :
279 6831 : int VSIArchiveFilesystemHandler::FindFileInArchive(
280 : const char *archiveFilename, const char *fileInArchiveName,
281 : const VSIArchiveEntry **archiveEntry)
282 : {
283 6831 : if (fileInArchiveName == nullptr)
284 0 : return FALSE;
285 :
286 6831 : const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
287 6831 : if (content)
288 : {
289 1052560 : for (int i = 0; i < content->nEntries; i++)
290 : {
291 1052240 : if (strcmp(fileInArchiveName, content->entries[i].fileName) == 0)
292 : {
293 6510 : if (archiveEntry)
294 6510 : *archiveEntry = &content->entries[i];
295 6510 : return TRUE;
296 : }
297 : }
298 : }
299 321 : return FALSE;
300 : }
301 :
302 : /************************************************************************/
303 : /* CompactFilename() */
304 : /************************************************************************/
305 :
306 12767 : static CPLString CompactFilename(const char *pszArchiveInFileNameIn)
307 : {
308 12767 : char *pszArchiveInFileName = CPLStrdup(pszArchiveInFileNameIn);
309 :
310 : // Replace a/../b by b and foo/a/../b by foo/b.
311 : while (true)
312 : {
313 12767 : char *pszPrevDir = strstr(pszArchiveInFileName, "/../");
314 12767 : if (pszPrevDir == nullptr || pszPrevDir == pszArchiveInFileName)
315 : break;
316 :
317 0 : char *pszPrevSlash = pszPrevDir - 1;
318 0 : while (pszPrevSlash != pszArchiveInFileName && *pszPrevSlash != '/')
319 0 : pszPrevSlash--;
320 0 : if (pszPrevSlash == pszArchiveInFileName)
321 0 : memmove(pszArchiveInFileName, pszPrevDir + 4,
322 0 : strlen(pszPrevDir + 4) + 1);
323 : else
324 0 : memmove(pszPrevSlash + 1, pszPrevDir + 4,
325 0 : strlen(pszPrevDir + 4) + 1);
326 0 : }
327 :
328 12767 : CPLString osFileInArchive = pszArchiveInFileName;
329 12767 : CPLFree(pszArchiveInFileName);
330 12767 : return osFileInArchive;
331 : }
332 :
333 : /************************************************************************/
334 : /* SplitFilename() */
335 : /************************************************************************/
336 :
337 14548 : char *VSIArchiveFilesystemHandler::SplitFilename(const char *pszFilename,
338 : CPLString &osFileInArchive,
339 : int bCheckMainFileExists)
340 : {
341 : // TODO(schwehr): Cleanup redundant calls to GetPrefix and strlen.
342 14548 : if (strcmp(pszFilename, GetPrefix()) == 0)
343 4 : return nullptr;
344 :
345 14544 : int i = 0;
346 :
347 : // Detect extended syntax: /vsiXXX/{archive_filename}/file_in_archive.
348 14544 : if (pszFilename[strlen(GetPrefix()) + 1] == '{')
349 : {
350 1317 : pszFilename += strlen(GetPrefix()) + 1;
351 1317 : int nCountCurlies = 0;
352 48753 : while (pszFilename[i])
353 : {
354 48752 : if (pszFilename[i] == '{')
355 1320 : nCountCurlies++;
356 47432 : else if (pszFilename[i] == '}')
357 : {
358 1319 : nCountCurlies--;
359 1319 : if (nCountCurlies == 0)
360 1316 : break;
361 : }
362 47436 : i++;
363 : }
364 1317 : if (nCountCurlies > 0)
365 1 : return nullptr;
366 1316 : char *archiveFilename = CPLStrdup(pszFilename + 1);
367 1316 : archiveFilename[i - 1] = 0;
368 :
369 1316 : bool bArchiveFileExists = false;
370 1316 : if (!bCheckMainFileExists)
371 : {
372 35 : bArchiveFileExists = true;
373 : }
374 : else
375 : {
376 2562 : CPLMutexHolder oHolder(&hMutex);
377 :
378 1281 : if (oFileList.find(archiveFilename) != oFileList.end())
379 : {
380 1146 : bArchiveFileExists = true;
381 : }
382 : }
383 :
384 1316 : if (!bArchiveFileExists)
385 : {
386 : VSIStatBufL statBuf;
387 : VSIFilesystemHandler *poFSHandler =
388 135 : VSIFileManager::GetHandler(archiveFilename);
389 405 : if (poFSHandler->Stat(archiveFilename, &statBuf,
390 : VSI_STAT_EXISTS_FLAG |
391 269 : VSI_STAT_NATURE_FLAG) == 0 &&
392 134 : !VSI_ISDIR(statBuf.st_mode))
393 : {
394 134 : bArchiveFileExists = true;
395 : }
396 : }
397 :
398 1316 : if (bArchiveFileExists)
399 : {
400 1315 : if (IsEitherSlash(pszFilename[i + 1]))
401 : {
402 1259 : osFileInArchive = CompactFilename(pszFilename + i + 2);
403 : }
404 56 : else if (pszFilename[i + 1] == '\0')
405 : {
406 55 : osFileInArchive = "";
407 : }
408 : else
409 : {
410 1 : CPLFree(archiveFilename);
411 1 : return nullptr;
412 : }
413 :
414 : // Remove trailing slash.
415 1314 : if (!osFileInArchive.empty())
416 : {
417 1255 : const char lastC = osFileInArchive.back();
418 1255 : if (IsEitherSlash(lastC))
419 2 : osFileInArchive.pop_back();
420 : }
421 :
422 1314 : return archiveFilename;
423 : }
424 :
425 1 : CPLFree(archiveFilename);
426 1 : return nullptr;
427 : }
428 :
429 : // Allow natural chaining of VSI drivers without requiring double slash.
430 :
431 26454 : CPLString osDoubleVsi(GetPrefix());
432 13227 : osDoubleVsi += "/vsi";
433 :
434 13227 : if (strncmp(pszFilename, osDoubleVsi.c_str(), osDoubleVsi.size()) == 0)
435 4080 : pszFilename += strlen(GetPrefix());
436 : else
437 9147 : pszFilename += strlen(GetPrefix()) + 1;
438 :
439 : // Parsing strings like
440 : // /vsitar//vsitar//vsitar//vsitar//vsitar//vsitar//vsitar//vsitar/a.tgzb.tgzc.tgzd.tgze.tgzf.tgz.h.tgz.i.tgz
441 : // takes a huge amount of time, so limit the number of nesting of such
442 : // file systems.
443 13227 : int *pnCounter = static_cast<int *>(CPLGetTLS(CTLS_ABSTRACTARCHIVE_SPLIT));
444 13227 : if (pnCounter == nullptr)
445 : {
446 24 : pnCounter = static_cast<int *>(CPLMalloc(sizeof(int)));
447 24 : *pnCounter = 0;
448 24 : CPLSetTLS(CTLS_ABSTRACTARCHIVE_SPLIT, pnCounter, TRUE);
449 : }
450 13227 : if (*pnCounter == 3)
451 : {
452 64 : CPLError(CE_Failure, CPLE_AppDefined,
453 : "Too deep recursion level in "
454 : "VSIArchiveFilesystemHandler::SplitFilename()");
455 64 : return nullptr;
456 : }
457 :
458 26326 : const std::vector<CPLString> oExtensions = GetExtensions();
459 13163 : int nAttempts = 0;
460 408042 : while (pszFilename[i])
461 : {
462 407856 : int nToSkip = 0;
463 :
464 2766350 : for (std::vector<CPLString>::const_iterator iter = oExtensions.begin();
465 5124840 : iter != oExtensions.end(); ++iter)
466 : {
467 2372110 : const CPLString &osExtension = *iter;
468 2372110 : if (EQUALN(pszFilename + i, osExtension.c_str(),
469 : osExtension.size()))
470 : {
471 13618 : nToSkip = static_cast<int>(osExtension.size());
472 13618 : break;
473 : }
474 : }
475 :
476 : #ifdef DEBUG
477 : // For AFL, so that .cur_input is detected as the archive filename.
478 407856 : if (EQUALN(pszFilename + i, ".cur_input", strlen(".cur_input")))
479 : {
480 14 : nToSkip = static_cast<int>(strlen(".cur_input"));
481 : }
482 : #endif
483 :
484 407856 : if (nToSkip != 0)
485 : {
486 13632 : nAttempts++;
487 : // Arbitrary threshold to avoid DoS with things like
488 : // /vsitar/my.tar/my.tar/my.tar/my.tar/my.tar/my.tar/my.tar
489 13632 : if (nAttempts == 5)
490 : {
491 21 : break;
492 : }
493 : VSIStatBufL statBuf;
494 13611 : char *archiveFilename = CPLStrdup(pszFilename);
495 13611 : bool bArchiveFileExists = false;
496 :
497 13611 : if (IsEitherSlash(archiveFilename[i + nToSkip]))
498 : {
499 11963 : archiveFilename[i + nToSkip] = 0;
500 : }
501 :
502 13611 : if (!bCheckMainFileExists)
503 : {
504 954 : bArchiveFileExists = true;
505 : }
506 : else
507 : {
508 25314 : CPLMutexHolder oHolder(&hMutex);
509 :
510 12657 : if (oFileList.find(archiveFilename) != oFileList.end())
511 : {
512 11611 : bArchiveFileExists = true;
513 : }
514 : }
515 :
516 13611 : if (!bArchiveFileExists)
517 : {
518 1046 : (*pnCounter)++;
519 :
520 : VSIFilesystemHandler *poFSHandler =
521 1046 : VSIFileManager::GetHandler(archiveFilename);
522 3138 : if (poFSHandler->Stat(archiveFilename, &statBuf,
523 : VSI_STAT_EXISTS_FLAG |
524 1857 : VSI_STAT_NATURE_FLAG) == 0 &&
525 811 : !VSI_ISDIR(statBuf.st_mode))
526 : {
527 391 : bArchiveFileExists = true;
528 : }
529 :
530 1046 : (*pnCounter)--;
531 : }
532 :
533 13611 : if (bArchiveFileExists)
534 : {
535 12956 : if (IsEitherSlash(pszFilename[i + nToSkip]))
536 : {
537 : osFileInArchive =
538 11508 : CompactFilename(pszFilename + i + nToSkip + 1);
539 : }
540 : else
541 : {
542 1448 : osFileInArchive = "";
543 : }
544 :
545 : // Remove trailing slash.
546 12956 : if (!osFileInArchive.empty())
547 : {
548 11502 : const char lastC = osFileInArchive.back();
549 11502 : if (IsEitherSlash(lastC))
550 39 : osFileInArchive.pop_back();
551 : }
552 :
553 12956 : return archiveFilename;
554 : }
555 655 : CPLFree(archiveFilename);
556 : }
557 394879 : i++;
558 : }
559 207 : return nullptr;
560 : }
561 :
562 : /************************************************************************/
563 : /* OpenArchiveFile() */
564 : /************************************************************************/
565 :
566 : VSIArchiveReader *
567 4350 : VSIArchiveFilesystemHandler::OpenArchiveFile(const char *archiveFilename,
568 : const char *fileInArchiveName)
569 : {
570 4350 : VSIArchiveReader *poReader = CreateReader(archiveFilename);
571 :
572 4350 : if (poReader == nullptr)
573 : {
574 6 : return nullptr;
575 : }
576 :
577 4344 : if (fileInArchiveName == nullptr || strlen(fileInArchiveName) == 0)
578 : {
579 73 : if (poReader->GotoFirstFile() == FALSE)
580 : {
581 0 : delete (poReader);
582 1 : return nullptr;
583 : }
584 :
585 : // Skip optional leading subdir.
586 73 : const CPLString osFileName = poReader->GetFileName();
587 73 : if (osFileName.empty() || IsEitherSlash(osFileName.back()))
588 : {
589 2 : if (poReader->GotoNextFile() == FALSE)
590 : {
591 0 : delete (poReader);
592 0 : return nullptr;
593 : }
594 : }
595 :
596 73 : if (poReader->GotoNextFile())
597 : {
598 1 : CPLString msg;
599 : msg.Printf("Support only 1 file in archive file %s when "
600 : "no explicit in-archive filename is specified",
601 1 : archiveFilename);
602 : const VSIArchiveContent *content =
603 1 : GetContentOfArchive(archiveFilename, poReader);
604 1 : if (content)
605 : {
606 1 : msg += "\nYou could try one of the following :\n";
607 6 : for (int i = 0; i < content->nEntries; i++)
608 : {
609 10 : msg += CPLString().Printf(" %s/{%s}/%s\n", GetPrefix(),
610 : archiveFilename,
611 5 : content->entries[i].fileName);
612 : }
613 : }
614 :
615 1 : CPLError(CE_Failure, CPLE_NotSupported, "%s", msg.c_str());
616 :
617 1 : delete (poReader);
618 1 : return nullptr;
619 72 : }
620 : }
621 : else
622 : {
623 : // Optimization: instead of iterating over all files which can be
624 : // slow on .tar.gz files, try reading the first one first.
625 : // This can help if it is really huge.
626 : {
627 4271 : CPLMutexHolder oHolder(&hMutex);
628 :
629 4271 : if (oFileList.find(archiveFilename) == oFileList.end())
630 : {
631 140 : if (poReader->GotoFirstFile() == FALSE)
632 : {
633 0 : delete (poReader);
634 54 : return nullptr;
635 : }
636 :
637 140 : const CPLString osFileName = poReader->GetFileName();
638 140 : bool bIsDir = false;
639 : const CPLString osStrippedFilename =
640 140 : GetStrippedFilename(osFileName, bIsDir);
641 140 : if (!osStrippedFilename.empty())
642 : {
643 : const bool bMatch =
644 140 : strcmp(osStrippedFilename, fileInArchiveName) == 0;
645 140 : if (bMatch)
646 : {
647 54 : if (bIsDir)
648 : {
649 0 : delete (poReader);
650 0 : return nullptr;
651 : }
652 54 : return poReader;
653 : }
654 : }
655 : }
656 : }
657 :
658 4217 : const VSIArchiveEntry *archiveEntry = nullptr;
659 12651 : if (FindFileInArchive(archiveFilename, fileInArchiveName,
660 8270 : &archiveEntry) == FALSE ||
661 4053 : archiveEntry->bIsDir)
662 : {
663 168 : delete (poReader);
664 168 : return nullptr;
665 : }
666 4049 : if (!poReader->GotoFileOffset(archiveEntry->file_pos))
667 : {
668 0 : delete poReader;
669 0 : return nullptr;
670 : }
671 : }
672 4121 : return poReader;
673 : }
674 :
675 : /************************************************************************/
676 : /* Stat() */
677 : /************************************************************************/
678 :
679 3186 : int VSIArchiveFilesystemHandler::Stat(const char *pszFilename,
680 : VSIStatBufL *pStatBuf, int /* nFlags */)
681 : {
682 3186 : memset(pStatBuf, 0, sizeof(VSIStatBufL));
683 :
684 6372 : CPLString osFileInArchive;
685 3186 : char *archiveFilename = SplitFilename(pszFilename, osFileInArchive, TRUE);
686 3186 : if (archiveFilename == nullptr)
687 85 : return -1;
688 :
689 3101 : int ret = -1;
690 3101 : if (!osFileInArchive.empty())
691 : {
692 : #ifdef DEBUG_VERBOSE
693 : CPLDebug("VSIArchive", "Looking for %s %s", archiveFilename,
694 : osFileInArchive.c_str());
695 : #endif
696 :
697 2614 : const VSIArchiveEntry *archiveEntry = nullptr;
698 2614 : if (FindFileInArchive(archiveFilename, osFileInArchive, &archiveEntry))
699 : {
700 : // Patching st_size with uncompressed file size.
701 2457 : pStatBuf->st_size = archiveEntry->uncompressed_size;
702 2457 : pStatBuf->st_mtime =
703 2457 : static_cast<time_t>(archiveEntry->nModifiedTime);
704 2457 : if (archiveEntry->bIsDir)
705 1146 : pStatBuf->st_mode = S_IFDIR;
706 : else
707 1311 : pStatBuf->st_mode = S_IFREG;
708 2457 : ret = 0;
709 : }
710 : }
711 : else
712 : {
713 487 : VSIArchiveReader *poReader = CreateReader(archiveFilename);
714 487 : CPLFree(archiveFilename);
715 487 : archiveFilename = nullptr;
716 :
717 487 : if (poReader != nullptr && poReader->GotoFirstFile())
718 : {
719 : // Skip optional leading subdir.
720 482 : const CPLString osFileName = poReader->GetFileName();
721 482 : if (IsEitherSlash(osFileName.back()))
722 : {
723 16 : if (poReader->GotoNextFile() == FALSE)
724 : {
725 0 : delete (poReader);
726 0 : return -1;
727 : }
728 : }
729 :
730 482 : if (poReader->GotoNextFile())
731 : {
732 : // Several files in archive --> treat as dir.
733 454 : pStatBuf->st_size = 0;
734 454 : pStatBuf->st_mode = S_IFDIR;
735 : }
736 : else
737 : {
738 : // Patching st_size with uncompressed file size.
739 28 : pStatBuf->st_size = poReader->GetFileSize();
740 28 : pStatBuf->st_mtime =
741 28 : static_cast<time_t>(poReader->GetModifiedTime());
742 28 : pStatBuf->st_mode = S_IFREG;
743 : }
744 :
745 482 : ret = 0;
746 : }
747 :
748 487 : delete (poReader);
749 : }
750 :
751 3101 : CPLFree(archiveFilename);
752 3101 : return ret;
753 : }
754 :
755 : /************************************************************************/
756 : /* Unlink() */
757 : /************************************************************************/
758 :
759 2 : int VSIArchiveFilesystemHandler::Unlink(const char * /* pszFilename */)
760 : {
761 2 : return -1;
762 : }
763 :
764 : /************************************************************************/
765 : /* Rename() */
766 : /************************************************************************/
767 :
768 0 : int VSIArchiveFilesystemHandler::Rename(const char * /* oldpath */,
769 : const char * /* newpath */)
770 : {
771 0 : return -1;
772 : }
773 :
774 : /************************************************************************/
775 : /* Mkdir() */
776 : /************************************************************************/
777 :
778 0 : int VSIArchiveFilesystemHandler::Mkdir(const char * /* pszDirname */,
779 : long /* nMode */)
780 : {
781 0 : return -1;
782 : }
783 :
784 : /************************************************************************/
785 : /* Rmdir() */
786 : /************************************************************************/
787 :
788 0 : int VSIArchiveFilesystemHandler::Rmdir(const char * /* pszDirname */)
789 : {
790 0 : return -1;
791 : }
792 :
793 : /************************************************************************/
794 : /* ReadDirEx() */
795 : /************************************************************************/
796 :
797 1380 : char **VSIArchiveFilesystemHandler::ReadDirEx(const char *pszDirname,
798 : int nMaxFiles)
799 : {
800 2760 : CPLString osInArchiveSubDir;
801 1380 : char *archiveFilename = SplitFilename(pszDirname, osInArchiveSubDir, TRUE);
802 1380 : if (archiveFilename == nullptr)
803 0 : return nullptr;
804 :
805 1380 : const int lenInArchiveSubDir = static_cast<int>(osInArchiveSubDir.size());
806 :
807 2760 : CPLStringList oDir;
808 :
809 1380 : const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
810 1380 : if (!content)
811 : {
812 4 : CPLFree(archiveFilename);
813 4 : return nullptr;
814 : }
815 :
816 : #ifdef DEBUG_VERBOSE
817 : CPLDebug("VSIArchive", "Read dir %s", pszDirname);
818 : #endif
819 1079240 : for (int i = 0; i < content->nEntries; i++)
820 : {
821 1077870 : const char *fileName = content->entries[i].fileName;
822 : /* Only list entries at the same level of inArchiveSubDir */
823 2119330 : if (lenInArchiveSubDir != 0 &&
824 1581740 : strncmp(fileName, osInArchiveSubDir, lenInArchiveSubDir) == 0 &&
825 2659610 : IsEitherSlash(fileName[lenInArchiveSubDir]) &&
826 539115 : fileName[lenInArchiveSubDir + 1] != 0)
827 : {
828 539115 : const char *slash = strchr(fileName + lenInArchiveSubDir + 1, '/');
829 539115 : if (slash == nullptr)
830 39007 : slash = strchr(fileName + lenInArchiveSubDir + 1, '\\');
831 539115 : if (slash == nullptr || slash[1] == 0)
832 : {
833 39007 : char *tmpFileName = CPLStrdup(fileName);
834 39007 : if (slash != nullptr)
835 : {
836 0 : tmpFileName[strlen(tmpFileName) - 1] = 0;
837 : }
838 : #ifdef DEBUG_VERBOSE
839 : CPLDebug("VSIArchive", "Add %s as in directory %s",
840 : tmpFileName + lenInArchiveSubDir + 1, pszDirname);
841 : #endif
842 39007 : oDir.AddString(tmpFileName + lenInArchiveSubDir + 1);
843 39007 : CPLFree(tmpFileName);
844 : }
845 : }
846 538752 : else if (lenInArchiveSubDir == 0 && strchr(fileName, '/') == nullptr &&
847 441 : strchr(fileName, '\\') == nullptr)
848 : {
849 : // Only list toplevel files and directories.
850 : #ifdef DEBUG_VERBOSE
851 : CPLDebug("VSIArchive", "Add %s as in directory %s", fileName,
852 : pszDirname);
853 : #endif
854 441 : oDir.AddString(fileName);
855 : }
856 :
857 1077870 : if (nMaxFiles > 0 && oDir.Count() > nMaxFiles)
858 1 : break;
859 : }
860 :
861 1376 : CPLFree(archiveFilename);
862 1376 : return oDir.StealList();
863 : }
864 :
865 : /************************************************************************/
866 : /* IsLocal() */
867 : /************************************************************************/
868 :
869 0 : bool VSIArchiveFilesystemHandler::IsLocal(const char *pszPath)
870 : {
871 0 : if (!STARTS_WITH(pszPath, GetPrefix()))
872 0 : return false;
873 0 : const char *pszBaseFileName = pszPath + strlen(GetPrefix());
874 : VSIFilesystemHandler *poFSHandler =
875 0 : VSIFileManager::GetHandler(pszBaseFileName);
876 0 : return poFSHandler->IsLocal(pszPath);
877 : }
878 :
879 : //! @endcond
|