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 : * Permission is hereby granted, free of charge, to any person obtaining a
11 : * copy of this software and associated documentation files (the "Software"),
12 : * to deal in the Software without restriction, including without limitation
13 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14 : * and/or sell copies of the Software, and to permit persons to whom the
15 : * Software is furnished to do so, subject to the following conditions:
16 : *
17 : * The above copyright notice and this permission notice shall be included
18 : * in all copies or substantial portions of the Software.
19 : *
20 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 : * DEALINGS IN THE SOFTWARE.
27 : ****************************************************************************/
28 :
29 : #include "cpl_port.h"
30 : #include "cpl_vsi_virtual.h"
31 :
32 : #include <cstring>
33 : #if HAVE_SYS_STAT_H
34 : #include <sys/stat.h>
35 : #endif
36 : #include <ctime>
37 : #include <map>
38 : #include <set>
39 : #include <string>
40 : #include <utility>
41 : #include <vector>
42 :
43 : #include "cpl_conv.h"
44 : #include "cpl_error.h"
45 : #include "cpl_multiproc.h"
46 : #include "cpl_string.h"
47 : #include "cpl_vsi.h"
48 :
49 : //! @cond Doxygen_Suppress
50 :
51 577126 : static bool IsEitherSlash(char c)
52 : {
53 577126 : return c == '/' || c == '\\';
54 : }
55 :
56 : /************************************************************************/
57 : /* ~VSIArchiveEntryFileOffset() */
58 : /************************************************************************/
59 :
60 1507 : VSIArchiveEntryFileOffset::~VSIArchiveEntryFileOffset()
61 : {
62 1507 : }
63 :
64 : /************************************************************************/
65 : /* ~VSIArchiveReader() */
66 : /************************************************************************/
67 :
68 4517 : VSIArchiveReader::~VSIArchiveReader()
69 : {
70 4517 : }
71 :
72 : /************************************************************************/
73 : /* ~VSIArchiveContent() */
74 : /************************************************************************/
75 :
76 128 : VSIArchiveContent::~VSIArchiveContent()
77 : {
78 1648 : for (int i = 0; i < nEntries; i++)
79 : {
80 1584 : delete entries[i].file_pos;
81 1584 : CPLFree(entries[i].fileName);
82 : }
83 64 : CPLFree(entries);
84 64 : }
85 :
86 : /************************************************************************/
87 : /* VSIArchiveFilesystemHandler() */
88 : /************************************************************************/
89 :
90 2450 : VSIArchiveFilesystemHandler::VSIArchiveFilesystemHandler()
91 : {
92 2450 : hMutex = nullptr;
93 2450 : }
94 :
95 : /************************************************************************/
96 : /* ~VSIArchiveFilesystemHandler() */
97 : /************************************************************************/
98 :
99 1698 : VSIArchiveFilesystemHandler::~VSIArchiveFilesystemHandler()
100 :
101 : {
102 1746 : for (const auto &iter : oFileList)
103 : {
104 48 : delete iter.second;
105 : }
106 :
107 1698 : if (hMutex != nullptr)
108 15 : CPLDestroyMutex(hMutex);
109 1698 : hMutex = nullptr;
110 1698 : }
111 :
112 : /************************************************************************/
113 : /* GetStrippedFilename() */
114 : /************************************************************************/
115 :
116 4364 : static CPLString GetStrippedFilename(const CPLString &osFileName, bool &bIsDir)
117 : {
118 4364 : bIsDir = false;
119 4364 : const char *fileName = osFileName.c_str();
120 :
121 : // Remove ./ pattern at the beginning of a filename.
122 4364 : if (fileName[0] == '.' && fileName[1] == '/')
123 : {
124 0 : fileName += 2;
125 0 : if (fileName[0] == '\0')
126 0 : return CPLString();
127 : }
128 :
129 4364 : char *pszStrippedFileName = CPLStrdup(fileName);
130 4364 : char *pszIter = nullptr;
131 158967 : for (pszIter = pszStrippedFileName; *pszIter; pszIter++)
132 : {
133 154603 : if (*pszIter == '\\')
134 0 : *pszIter = '/';
135 : }
136 :
137 4364 : const size_t nLen = strlen(fileName);
138 4364 : bIsDir = nLen > 0 && fileName[nLen - 1] == '/';
139 4364 : if (bIsDir)
140 : {
141 : // Remove trailing slash.
142 173 : pszStrippedFileName[nLen - 1] = '\0';
143 : }
144 8728 : CPLString osRet(pszStrippedFileName);
145 4364 : CPLFree(pszStrippedFileName);
146 4364 : return osRet;
147 : }
148 :
149 : /************************************************************************/
150 : /* GetContentOfArchive() */
151 : /************************************************************************/
152 :
153 : const VSIArchiveContent *
154 7717 : VSIArchiveFilesystemHandler::GetContentOfArchive(const char *archiveFilename,
155 : VSIArchiveReader *poReader)
156 : {
157 15434 : CPLMutexHolder oHolder(&hMutex);
158 :
159 : VSIStatBufL sStat;
160 7717 : if (VSIStatL(archiveFilename, &sStat) != 0)
161 0 : return nullptr;
162 :
163 7717 : if (oFileList.find(archiveFilename) != oFileList.end())
164 : {
165 7531 : VSIArchiveContent *content = oFileList[archiveFilename];
166 7531 : if (static_cast<time_t>(sStat.st_mtime) > content->mTime ||
167 7528 : static_cast<vsi_l_offset>(sStat.st_size) != content->nFileSize)
168 : {
169 7 : CPLDebug("VSIArchive",
170 : "The content of %s has changed since it was cached",
171 : archiveFilename);
172 7 : delete content;
173 7 : oFileList.erase(archiveFilename);
174 : }
175 : else
176 : {
177 7524 : return content;
178 : }
179 : }
180 :
181 193 : bool bMustClose = poReader == nullptr;
182 193 : if (poReader == nullptr)
183 : {
184 192 : poReader = CreateReader(archiveFilename);
185 192 : if (!poReader)
186 6 : return nullptr;
187 : }
188 :
189 187 : if (poReader->GotoFirstFile() == FALSE)
190 : {
191 0 : if (bMustClose)
192 0 : delete (poReader);
193 0 : return nullptr;
194 : }
195 :
196 187 : VSIArchiveContent *content = new VSIArchiveContent;
197 187 : content->mTime = sStat.st_mtime;
198 187 : content->nFileSize = static_cast<vsi_l_offset>(sStat.st_size);
199 187 : content->nEntries = 0;
200 187 : content->entries = nullptr;
201 187 : oFileList[archiveFilename] = content;
202 :
203 187 : std::set<CPLString> oSet;
204 :
205 4053 : do
206 : {
207 4240 : const CPLString osFileName = poReader->GetFileName();
208 4240 : bool bIsDir = false;
209 : const CPLString osStrippedFilename =
210 4240 : GetStrippedFilename(osFileName, bIsDir);
211 8480 : if (osStrippedFilename.empty() || osStrippedFilename[0] == '/' ||
212 4240 : osStrippedFilename.find("//") != std::string::npos)
213 : {
214 0 : continue;
215 : }
216 :
217 4240 : if (oSet.find(osStrippedFilename) == oSet.end())
218 : {
219 4240 : oSet.insert(osStrippedFilename);
220 :
221 : // Add intermediate directory structure.
222 4240 : const char *pszBegin = osStrippedFilename.c_str();
223 157105 : for (const char *pszIter = pszBegin; *pszIter; pszIter++)
224 : {
225 152865 : if (*pszIter == '/')
226 : {
227 5596 : char *pszStrippedFileName2 = CPLStrdup(osStrippedFilename);
228 5596 : pszStrippedFileName2[pszIter - pszBegin] = 0;
229 5596 : if (oSet.find(pszStrippedFileName2) == oSet.end())
230 : {
231 1229 : oSet.insert(pszStrippedFileName2);
232 :
233 1229 : content->entries =
234 2458 : static_cast<VSIArchiveEntry *>(CPLRealloc(
235 1229 : content->entries, sizeof(VSIArchiveEntry) *
236 1229 : (content->nEntries + 1)));
237 1229 : content->entries[content->nEntries].fileName =
238 : pszStrippedFileName2;
239 2458 : content->entries[content->nEntries].nModifiedTime =
240 1229 : poReader->GetModifiedTime();
241 1229 : content->entries[content->nEntries].uncompressed_size =
242 : 0;
243 1229 : content->entries[content->nEntries].bIsDir = TRUE;
244 1229 : content->entries[content->nEntries].file_pos = nullptr;
245 : #ifdef DEBUG_VERBOSE
246 : const int nEntries = content->nEntries;
247 : CPLDebug("VSIArchive",
248 : "[%d] %s : " CPL_FRMT_GUIB " bytes",
249 : content->nEntries + 1,
250 : content->entries[nEntries].fileName,
251 : content->entries[nEntries].uncompressed_size);
252 : #endif
253 1229 : content->nEntries++;
254 : }
255 : else
256 : {
257 4367 : CPLFree(pszStrippedFileName2);
258 : }
259 : }
260 : }
261 :
262 4240 : content->entries = static_cast<VSIArchiveEntry *>(
263 8480 : CPLRealloc(content->entries,
264 4240 : sizeof(VSIArchiveEntry) * (content->nEntries + 1)));
265 8480 : content->entries[content->nEntries].fileName =
266 4240 : CPLStrdup(osStrippedFilename);
267 8480 : content->entries[content->nEntries].nModifiedTime =
268 4240 : poReader->GetModifiedTime();
269 8480 : content->entries[content->nEntries].uncompressed_size =
270 4240 : poReader->GetFileSize();
271 4240 : content->entries[content->nEntries].bIsDir = bIsDir;
272 8480 : content->entries[content->nEntries].file_pos =
273 4240 : poReader->GetFileOffset();
274 : #ifdef DEBUG_VERBOSE
275 : CPLDebug("VSIArchive", "[%d] %s : " CPL_FRMT_GUIB " bytes",
276 : content->nEntries + 1,
277 : content->entries[content->nEntries].fileName,
278 : content->entries[content->nEntries].uncompressed_size);
279 : #endif
280 4240 : content->nEntries++;
281 : }
282 :
283 4240 : } while (poReader->GotoNextFile());
284 :
285 187 : if (bMustClose)
286 186 : delete (poReader);
287 :
288 187 : return content;
289 : }
290 :
291 : /************************************************************************/
292 : /* FindFileInArchive() */
293 : /************************************************************************/
294 :
295 6360 : int VSIArchiveFilesystemHandler::FindFileInArchive(
296 : const char *archiveFilename, const char *fileInArchiveName,
297 : const VSIArchiveEntry **archiveEntry)
298 : {
299 6360 : if (fileInArchiveName == nullptr)
300 0 : return FALSE;
301 :
302 6360 : const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
303 6360 : if (content)
304 : {
305 1045120 : for (int i = 0; i < content->nEntries; i++)
306 : {
307 1044800 : if (strcmp(fileInArchiveName, content->entries[i].fileName) == 0)
308 : {
309 6043 : if (archiveEntry)
310 6043 : *archiveEntry = &content->entries[i];
311 6043 : return TRUE;
312 : }
313 : }
314 : }
315 317 : return FALSE;
316 : }
317 :
318 : /************************************************************************/
319 : /* CompactFilename() */
320 : /************************************************************************/
321 :
322 12003 : static CPLString CompactFilename(const char *pszArchiveInFileNameIn)
323 : {
324 12003 : char *pszArchiveInFileName = CPLStrdup(pszArchiveInFileNameIn);
325 :
326 : // Replace a/../b by b and foo/a/../b by foo/b.
327 : while (true)
328 : {
329 12003 : char *pszPrevDir = strstr(pszArchiveInFileName, "/../");
330 12003 : if (pszPrevDir == nullptr || pszPrevDir == pszArchiveInFileName)
331 : break;
332 :
333 0 : char *pszPrevSlash = pszPrevDir - 1;
334 0 : while (pszPrevSlash != pszArchiveInFileName && *pszPrevSlash != '/')
335 0 : pszPrevSlash--;
336 0 : if (pszPrevSlash == pszArchiveInFileName)
337 0 : memmove(pszArchiveInFileName, pszPrevDir + 4,
338 0 : strlen(pszPrevDir + 4) + 1);
339 : else
340 0 : memmove(pszPrevSlash + 1, pszPrevDir + 4,
341 0 : strlen(pszPrevDir + 4) + 1);
342 0 : }
343 :
344 12003 : CPLString osFileInArchive = pszArchiveInFileName;
345 12003 : CPLFree(pszArchiveInFileName);
346 12003 : return osFileInArchive;
347 : }
348 :
349 : /************************************************************************/
350 : /* SplitFilename() */
351 : /************************************************************************/
352 :
353 13290 : char *VSIArchiveFilesystemHandler::SplitFilename(const char *pszFilename,
354 : CPLString &osFileInArchive,
355 : int bCheckMainFileExists)
356 : {
357 : // TODO(schwehr): Cleanup redundant calls to GetPrefix and strlen.
358 13290 : if (strcmp(pszFilename, GetPrefix()) == 0)
359 4 : return nullptr;
360 :
361 13286 : int i = 0;
362 :
363 : // Detect extended syntax: /vsiXXX/{archive_filename}/file_in_archive.
364 13286 : if (pszFilename[strlen(GetPrefix()) + 1] == '{')
365 : {
366 1210 : pszFilename += strlen(GetPrefix()) + 1;
367 1210 : int nCountCurlies = 0;
368 45390 : while (pszFilename[i])
369 : {
370 45389 : if (pszFilename[i] == '{')
371 1213 : nCountCurlies++;
372 44176 : else if (pszFilename[i] == '}')
373 : {
374 1212 : nCountCurlies--;
375 1212 : if (nCountCurlies == 0)
376 1209 : break;
377 : }
378 44180 : i++;
379 : }
380 1210 : if (nCountCurlies > 0)
381 1 : return nullptr;
382 1209 : char *archiveFilename = CPLStrdup(pszFilename + 1);
383 1209 : archiveFilename[i - 1] = 0;
384 :
385 1209 : bool bArchiveFileExists = false;
386 1209 : if (!bCheckMainFileExists)
387 : {
388 35 : bArchiveFileExists = true;
389 : }
390 : else
391 : {
392 2348 : CPLMutexHolder oHolder(&hMutex);
393 :
394 1174 : if (oFileList.find(archiveFilename) != oFileList.end())
395 : {
396 1053 : bArchiveFileExists = true;
397 : }
398 : }
399 :
400 1209 : if (!bArchiveFileExists)
401 : {
402 : VSIStatBufL statBuf;
403 : VSIFilesystemHandler *poFSHandler =
404 121 : VSIFileManager::GetHandler(archiveFilename);
405 363 : if (poFSHandler->Stat(archiveFilename, &statBuf,
406 : VSI_STAT_EXISTS_FLAG |
407 241 : VSI_STAT_NATURE_FLAG) == 0 &&
408 120 : !VSI_ISDIR(statBuf.st_mode))
409 : {
410 120 : bArchiveFileExists = true;
411 : }
412 : }
413 :
414 1209 : if (bArchiveFileExists)
415 : {
416 1208 : if (IsEitherSlash(pszFilename[i + 1]))
417 : {
418 1152 : osFileInArchive = CompactFilename(pszFilename + i + 2);
419 : }
420 56 : else if (pszFilename[i + 1] == '\0')
421 : {
422 55 : osFileInArchive = "";
423 : }
424 : else
425 : {
426 1 : CPLFree(archiveFilename);
427 1 : return nullptr;
428 : }
429 :
430 : // Remove trailing slash.
431 1207 : if (!osFileInArchive.empty())
432 : {
433 1148 : const char lastC = osFileInArchive.back();
434 1148 : if (IsEitherSlash(lastC))
435 2 : osFileInArchive.resize(osFileInArchive.size() - 1);
436 : }
437 :
438 1207 : return archiveFilename;
439 : }
440 :
441 1 : CPLFree(archiveFilename);
442 1 : return nullptr;
443 : }
444 :
445 : // Allow natural chaining of VSI drivers without requiring double slash.
446 :
447 24152 : CPLString osDoubleVsi(GetPrefix());
448 12076 : osDoubleVsi += "/vsi";
449 :
450 12076 : if (strncmp(pszFilename, osDoubleVsi.c_str(), osDoubleVsi.size()) == 0)
451 4066 : pszFilename += strlen(GetPrefix());
452 : else
453 8010 : pszFilename += strlen(GetPrefix()) + 1;
454 :
455 : // Parsing strings like
456 : // /vsitar//vsitar//vsitar//vsitar//vsitar//vsitar//vsitar//vsitar/a.tgzb.tgzc.tgzd.tgze.tgzf.tgz.h.tgz.i.tgz
457 : // takes a huge amount of time, so limit the number of nesting of such
458 : // file systems.
459 12076 : int *pnCounter = static_cast<int *>(CPLGetTLS(CTLS_ABSTRACTARCHIVE_SPLIT));
460 12076 : if (pnCounter == nullptr)
461 : {
462 22 : pnCounter = static_cast<int *>(CPLMalloc(sizeof(int)));
463 22 : *pnCounter = 0;
464 22 : CPLSetTLS(CTLS_ABSTRACTARCHIVE_SPLIT, pnCounter, TRUE);
465 : }
466 12076 : if (*pnCounter == 3)
467 : {
468 64 : CPLError(CE_Failure, CPLE_AppDefined,
469 : "Too deep recursion level in "
470 : "VSIArchiveFilesystemHandler::SplitFilename()");
471 64 : return nullptr;
472 : }
473 :
474 24024 : const std::vector<CPLString> oExtensions = GetExtensions();
475 12012 : int nAttempts = 0;
476 346214 : while (pszFilename[i])
477 : {
478 346045 : int nToSkip = 0;
479 :
480 2342900 : for (std::vector<CPLString>::const_iterator iter = oExtensions.begin();
481 4339750 : iter != oExtensions.end(); ++iter)
482 : {
483 2009100 : const CPLString &osExtension = *iter;
484 2009100 : if (EQUALN(pszFilename + i, osExtension.c_str(),
485 : osExtension.size()))
486 : {
487 12248 : nToSkip = static_cast<int>(osExtension.size());
488 12248 : break;
489 : }
490 : }
491 :
492 : #ifdef DEBUG
493 : // For AFL, so that .cur_input is detected as the archive filename.
494 346045 : if (EQUALN(pszFilename + i, ".cur_input", strlen(".cur_input")))
495 : {
496 14 : nToSkip = static_cast<int>(strlen(".cur_input"));
497 : }
498 : #endif
499 :
500 346045 : if (nToSkip != 0)
501 : {
502 12262 : nAttempts++;
503 : // Arbitrary threshold to avoid DoS with things like
504 : // /vsitar/my.tar/my.tar/my.tar/my.tar/my.tar/my.tar/my.tar
505 12262 : if (nAttempts == 5)
506 : {
507 21 : break;
508 : }
509 : VSIStatBufL statBuf;
510 12241 : char *archiveFilename = CPLStrdup(pszFilename);
511 12241 : bool bArchiveFileExists = false;
512 :
513 12241 : if (IsEitherSlash(archiveFilename[i + nToSkip]))
514 : {
515 11096 : archiveFilename[i + nToSkip] = 0;
516 : }
517 :
518 12241 : if (!bCheckMainFileExists)
519 : {
520 914 : bArchiveFileExists = true;
521 : }
522 : else
523 : {
524 22654 : CPLMutexHolder oHolder(&hMutex);
525 :
526 11327 : if (oFileList.find(archiveFilename) != oFileList.end())
527 : {
528 10582 : bArchiveFileExists = true;
529 : }
530 : }
531 :
532 12241 : if (!bArchiveFileExists)
533 : {
534 745 : (*pnCounter)++;
535 :
536 : VSIFilesystemHandler *poFSHandler =
537 745 : VSIFileManager::GetHandler(archiveFilename);
538 2235 : if (poFSHandler->Stat(archiveFilename, &statBuf,
539 : VSI_STAT_EXISTS_FLAG |
540 1281 : VSI_STAT_NATURE_FLAG) == 0 &&
541 536 : !VSI_ISDIR(statBuf.st_mode))
542 : {
543 326 : bArchiveFileExists = true;
544 : }
545 :
546 745 : (*pnCounter)--;
547 : }
548 :
549 12241 : if (bArchiveFileExists)
550 : {
551 11822 : if (IsEitherSlash(pszFilename[i + nToSkip]))
552 : {
553 : osFileInArchive =
554 10851 : CompactFilename(pszFilename + i + nToSkip + 1);
555 : }
556 : else
557 : {
558 971 : osFileInArchive = "";
559 : }
560 :
561 : // Remove trailing slash.
562 11822 : if (!osFileInArchive.empty())
563 : {
564 10845 : const char lastC = osFileInArchive.back();
565 10845 : if (IsEitherSlash(lastC))
566 39 : osFileInArchive.resize(osFileInArchive.size() - 1);
567 : }
568 :
569 11822 : return archiveFilename;
570 : }
571 419 : CPLFree(archiveFilename);
572 : }
573 334202 : i++;
574 : }
575 190 : return nullptr;
576 : }
577 :
578 : /************************************************************************/
579 : /* OpenArchiveFile() */
580 : /************************************************************************/
581 :
582 : VSIArchiveReader *
583 4064 : VSIArchiveFilesystemHandler::OpenArchiveFile(const char *archiveFilename,
584 : const char *fileInArchiveName)
585 : {
586 4064 : VSIArchiveReader *poReader = CreateReader(archiveFilename);
587 :
588 4064 : if (poReader == nullptr)
589 : {
590 5 : return nullptr;
591 : }
592 :
593 4059 : if (fileInArchiveName == nullptr || strlen(fileInArchiveName) == 0)
594 : {
595 57 : if (poReader->GotoFirstFile() == FALSE)
596 : {
597 0 : delete (poReader);
598 1 : return nullptr;
599 : }
600 :
601 : // Skip optional leading subdir.
602 57 : const CPLString osFileName = poReader->GetFileName();
603 57 : if (osFileName.empty() || IsEitherSlash(osFileName.back()))
604 : {
605 2 : if (poReader->GotoNextFile() == FALSE)
606 : {
607 0 : delete (poReader);
608 0 : return nullptr;
609 : }
610 : }
611 :
612 57 : if (poReader->GotoNextFile())
613 : {
614 1 : CPLString msg;
615 : msg.Printf("Support only 1 file in archive file %s when "
616 : "no explicit in-archive filename is specified",
617 1 : archiveFilename);
618 : const VSIArchiveContent *content =
619 1 : GetContentOfArchive(archiveFilename, poReader);
620 1 : if (content)
621 : {
622 1 : msg += "\nYou could try one of the following :\n";
623 6 : for (int i = 0; i < content->nEntries; i++)
624 : {
625 10 : msg += CPLString().Printf(" %s/{%s}/%s\n", GetPrefix(),
626 : archiveFilename,
627 5 : content->entries[i].fileName);
628 : }
629 : }
630 :
631 1 : CPLError(CE_Failure, CPLE_NotSupported, "%s", msg.c_str());
632 :
633 1 : delete (poReader);
634 1 : return nullptr;
635 56 : }
636 : }
637 : else
638 : {
639 : // Optimization: instead of iterating over all files which can be
640 : // slow on .tar.gz files, try reading the first one first.
641 : // This can help if it is really huge.
642 : {
643 4002 : CPLMutexHolder oHolder(&hMutex);
644 :
645 4002 : if (oFileList.find(archiveFilename) == oFileList.end())
646 : {
647 124 : if (poReader->GotoFirstFile() == FALSE)
648 : {
649 0 : delete (poReader);
650 46 : return nullptr;
651 : }
652 :
653 124 : const CPLString osFileName = poReader->GetFileName();
654 124 : bool bIsDir = false;
655 : const CPLString osStrippedFilename =
656 124 : GetStrippedFilename(osFileName, bIsDir);
657 124 : if (!osStrippedFilename.empty())
658 : {
659 : const bool bMatch =
660 124 : strcmp(osStrippedFilename, fileInArchiveName) == 0;
661 124 : if (bMatch)
662 : {
663 46 : if (bIsDir)
664 : {
665 0 : delete (poReader);
666 0 : return nullptr;
667 : }
668 46 : return poReader;
669 : }
670 : }
671 : }
672 : }
673 :
674 3956 : const VSIArchiveEntry *archiveEntry = nullptr;
675 11868 : if (FindFileInArchive(archiveFilename, fileInArchiveName,
676 7751 : &archiveEntry) == FALSE ||
677 3795 : archiveEntry->bIsDir)
678 : {
679 165 : delete (poReader);
680 165 : return nullptr;
681 : }
682 3791 : if (!poReader->GotoFileOffset(archiveEntry->file_pos))
683 : {
684 0 : delete poReader;
685 0 : return nullptr;
686 : }
687 : }
688 3847 : return poReader;
689 : }
690 :
691 : /************************************************************************/
692 : /* Stat() */
693 : /************************************************************************/
694 :
695 2750 : int VSIArchiveFilesystemHandler::Stat(const char *pszFilename,
696 : VSIStatBufL *pStatBuf, int /* nFlags */)
697 : {
698 2750 : memset(pStatBuf, 0, sizeof(VSIStatBufL));
699 :
700 5500 : CPLString osFileInArchive;
701 2750 : char *archiveFilename = SplitFilename(pszFilename, osFileInArchive, TRUE);
702 2750 : if (archiveFilename == nullptr)
703 85 : return -1;
704 :
705 2665 : int ret = -1;
706 2665 : if (!osFileInArchive.empty())
707 : {
708 : #ifdef DEBUG_VERBOSE
709 : CPLDebug("VSIArchive", "Looking for %s %s", archiveFilename,
710 : osFileInArchive.c_str());
711 : #endif
712 :
713 2404 : const VSIArchiveEntry *archiveEntry = nullptr;
714 2404 : if (FindFileInArchive(archiveFilename, osFileInArchive, &archiveEntry))
715 : {
716 : // Patching st_size with uncompressed file size.
717 2248 : pStatBuf->st_size = archiveEntry->uncompressed_size;
718 2248 : pStatBuf->st_mtime =
719 2248 : static_cast<time_t>(archiveEntry->nModifiedTime);
720 2248 : if (archiveEntry->bIsDir)
721 1127 : pStatBuf->st_mode = S_IFDIR;
722 : else
723 1121 : pStatBuf->st_mode = S_IFREG;
724 2248 : ret = 0;
725 : }
726 : }
727 : else
728 : {
729 261 : VSIArchiveReader *poReader = CreateReader(archiveFilename);
730 261 : CPLFree(archiveFilename);
731 261 : archiveFilename = nullptr;
732 :
733 261 : if (poReader != nullptr && poReader->GotoFirstFile())
734 : {
735 : // Skip optional leading subdir.
736 256 : const CPLString osFileName = poReader->GetFileName();
737 256 : if (IsEitherSlash(osFileName.back()))
738 : {
739 16 : if (poReader->GotoNextFile() == FALSE)
740 : {
741 0 : delete (poReader);
742 0 : return -1;
743 : }
744 : }
745 :
746 256 : if (poReader->GotoNextFile())
747 : {
748 : // Several files in archive --> treat as dir.
749 243 : pStatBuf->st_size = 0;
750 243 : pStatBuf->st_mode = S_IFDIR;
751 : }
752 : else
753 : {
754 : // Patching st_size with uncompressed file size.
755 13 : pStatBuf->st_size = poReader->GetFileSize();
756 13 : pStatBuf->st_mtime =
757 13 : static_cast<time_t>(poReader->GetModifiedTime());
758 13 : pStatBuf->st_mode = S_IFREG;
759 : }
760 :
761 256 : ret = 0;
762 : }
763 :
764 261 : delete (poReader);
765 : }
766 :
767 2665 : CPLFree(archiveFilename);
768 2665 : return ret;
769 : }
770 :
771 : /************************************************************************/
772 : /* Unlink() */
773 : /************************************************************************/
774 :
775 2 : int VSIArchiveFilesystemHandler::Unlink(const char * /* pszFilename */)
776 : {
777 2 : return -1;
778 : }
779 :
780 : /************************************************************************/
781 : /* Rename() */
782 : /************************************************************************/
783 :
784 0 : int VSIArchiveFilesystemHandler::Rename(const char * /* oldpath */,
785 : const char * /* newpath */)
786 : {
787 0 : return -1;
788 : }
789 :
790 : /************************************************************************/
791 : /* Mkdir() */
792 : /************************************************************************/
793 :
794 0 : int VSIArchiveFilesystemHandler::Mkdir(const char * /* pszDirname */,
795 : long /* nMode */)
796 : {
797 0 : return -1;
798 : }
799 :
800 : /************************************************************************/
801 : /* Rmdir() */
802 : /************************************************************************/
803 :
804 0 : int VSIArchiveFilesystemHandler::Rmdir(const char * /* pszDirname */)
805 : {
806 0 : return -1;
807 : }
808 :
809 : /************************************************************************/
810 : /* ReadDirEx() */
811 : /************************************************************************/
812 :
813 1356 : char **VSIArchiveFilesystemHandler::ReadDirEx(const char *pszDirname,
814 : int nMaxFiles)
815 : {
816 2712 : CPLString osInArchiveSubDir;
817 1356 : char *archiveFilename = SplitFilename(pszDirname, osInArchiveSubDir, TRUE);
818 1356 : if (archiveFilename == nullptr)
819 0 : return nullptr;
820 :
821 1356 : const int lenInArchiveSubDir = static_cast<int>(osInArchiveSubDir.size());
822 :
823 2712 : CPLStringList oDir;
824 :
825 1356 : const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
826 1356 : if (!content)
827 : {
828 4 : CPLFree(archiveFilename);
829 4 : return nullptr;
830 : }
831 :
832 : #ifdef DEBUG_VERBOSE
833 : CPLDebug("VSIArchive", "Read dir %s", pszDirname);
834 : #endif
835 1077130 : for (int i = 0; i < content->nEntries; i++)
836 : {
837 1075780 : const char *fileName = content->entries[i].fileName;
838 : /* Only list entries at the same level of inArchiveSubDir */
839 2115680 : if (lenInArchiveSubDir != 0 &&
840 1579450 : strncmp(fileName, osInArchiveSubDir, lenInArchiveSubDir) == 0 &&
841 2655220 : IsEitherSlash(fileName[lenInArchiveSubDir]) &&
842 538404 : fileName[lenInArchiveSubDir + 1] != 0)
843 : {
844 538404 : const char *slash = strchr(fileName + lenInArchiveSubDir + 1, '/');
845 538404 : if (slash == nullptr)
846 38497 : slash = strchr(fileName + lenInArchiveSubDir + 1, '\\');
847 538404 : if (slash == nullptr || slash[1] == 0)
848 : {
849 38497 : char *tmpFileName = CPLStrdup(fileName);
850 38497 : if (slash != nullptr)
851 : {
852 0 : tmpFileName[strlen(tmpFileName) - 1] = 0;
853 : }
854 : #ifdef DEBUG_VERBOSE
855 : CPLDebug("VSIArchive", "Add %s as in directory %s",
856 : tmpFileName + lenInArchiveSubDir + 1, pszDirname);
857 : #endif
858 38497 : oDir.AddString(tmpFileName + lenInArchiveSubDir + 1);
859 38497 : CPLFree(tmpFileName);
860 : }
861 : }
862 537374 : else if (lenInArchiveSubDir == 0 && strchr(fileName, '/') == nullptr &&
863 427 : strchr(fileName, '\\') == nullptr)
864 : {
865 : // Only list toplevel files and directories.
866 : #ifdef DEBUG_VERBOSE
867 : CPLDebug("VSIArchive", "Add %s as in directory %s", fileName,
868 : pszDirname);
869 : #endif
870 427 : oDir.AddString(fileName);
871 : }
872 :
873 1075780 : if (nMaxFiles > 0 && oDir.Count() > nMaxFiles)
874 1 : break;
875 : }
876 :
877 1352 : CPLFree(archiveFilename);
878 1352 : return oDir.StealList();
879 : }
880 :
881 : /************************************************************************/
882 : /* IsLocal() */
883 : /************************************************************************/
884 :
885 0 : bool VSIArchiveFilesystemHandler::IsLocal(const char *pszPath)
886 : {
887 0 : if (!STARTS_WITH(pszPath, GetPrefix()))
888 0 : return false;
889 0 : const char *pszBaseFileName = pszPath + strlen(GetPrefix());
890 : VSIFilesystemHandler *poFSHandler =
891 0 : VSIFileManager::GetHandler(pszBaseFileName);
892 0 : return poFSHandler->IsLocal(pszPath);
893 : }
894 :
895 : //! @endcond
|