/src/gdal/port/cpl_vsil_abstract_archive.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | static bool IsEitherSlash(char c) |
36 | 20.8k | { |
37 | 20.8k | return c == '/' || c == '\\'; |
38 | 20.8k | } |
39 | | |
40 | | /************************************************************************/ |
41 | | /* ~VSIArchiveEntryFileOffset() */ |
42 | | /************************************************************************/ |
43 | | |
44 | | VSIArchiveEntryFileOffset::~VSIArchiveEntryFileOffset() |
45 | 0 | { |
46 | 0 | } |
47 | | |
48 | | /************************************************************************/ |
49 | | /* ~VSIArchiveReader() */ |
50 | | /************************************************************************/ |
51 | | |
52 | | VSIArchiveReader::~VSIArchiveReader() |
53 | 2.06k | { |
54 | 2.06k | } |
55 | | |
56 | | /************************************************************************/ |
57 | | /* ~VSIArchiveContent() */ |
58 | | /************************************************************************/ |
59 | | |
60 | | VSIArchiveContent::~VSIArchiveContent() |
61 | 0 | { |
62 | 0 | for (int i = 0; i < nEntries; i++) |
63 | 0 | { |
64 | 0 | delete entries[i].file_pos; |
65 | 0 | CPLFree(entries[i].fileName); |
66 | 0 | } |
67 | 0 | CPLFree(entries); |
68 | 0 | } |
69 | | |
70 | | /************************************************************************/ |
71 | | /* VSIArchiveFilesystemHandler() */ |
72 | | /************************************************************************/ |
73 | | |
74 | | VSIArchiveFilesystemHandler::VSIArchiveFilesystemHandler() |
75 | 6 | { |
76 | 6 | hMutex = nullptr; |
77 | 6 | } |
78 | | |
79 | | /************************************************************************/ |
80 | | /* ~VSIArchiveFilesystemHandler() */ |
81 | | /************************************************************************/ |
82 | | |
83 | | VSIArchiveFilesystemHandler::~VSIArchiveFilesystemHandler() |
84 | | |
85 | 0 | { |
86 | 0 | for (const auto &iter : oFileList) |
87 | 0 | { |
88 | 0 | delete iter.second; |
89 | 0 | } |
90 | |
|
91 | 0 | if (hMutex != nullptr) |
92 | 0 | CPLDestroyMutex(hMutex); |
93 | 0 | hMutex = nullptr; |
94 | 0 | } |
95 | | |
96 | | /************************************************************************/ |
97 | | /* GetStrippedFilename() */ |
98 | | /************************************************************************/ |
99 | | |
100 | | static CPLString GetStrippedFilename(const CPLString &osFileName, bool &bIsDir) |
101 | 0 | { |
102 | 0 | bIsDir = false; |
103 | 0 | const char *fileName = osFileName.c_str(); |
104 | | |
105 | | // Remove ./ pattern at the beginning of a filename. |
106 | 0 | if (fileName[0] == '.' && fileName[1] == '/') |
107 | 0 | { |
108 | 0 | fileName += 2; |
109 | 0 | if (fileName[0] == '\0') |
110 | 0 | return CPLString(); |
111 | 0 | } |
112 | | |
113 | 0 | char *pszStrippedFileName = CPLStrdup(fileName); |
114 | 0 | char *pszIter = nullptr; |
115 | 0 | for (pszIter = pszStrippedFileName; *pszIter; pszIter++) |
116 | 0 | { |
117 | 0 | if (*pszIter == '\\') |
118 | 0 | *pszIter = '/'; |
119 | 0 | } |
120 | |
|
121 | 0 | const size_t nLen = strlen(fileName); |
122 | 0 | bIsDir = nLen > 0 && fileName[nLen - 1] == '/'; |
123 | 0 | if (bIsDir) |
124 | 0 | { |
125 | | // Remove trailing slash. |
126 | 0 | pszStrippedFileName[nLen - 1] = '\0'; |
127 | 0 | } |
128 | 0 | CPLString osRet(pszStrippedFileName); |
129 | 0 | CPLFree(pszStrippedFileName); |
130 | 0 | return osRet; |
131 | 0 | } |
132 | | |
133 | | /************************************************************************/ |
134 | | /* GetContentOfArchive() */ |
135 | | /************************************************************************/ |
136 | | |
137 | | const VSIArchiveContent * |
138 | | VSIArchiveFilesystemHandler::GetContentOfArchive(const char *archiveFilename, |
139 | | VSIArchiveReader *poReader) |
140 | 949 | { |
141 | 949 | CPLMutexHolder oHolder(&hMutex); |
142 | | |
143 | 949 | VSIStatBufL sStat; |
144 | 949 | if (VSIStatL(archiveFilename, &sStat) != 0) |
145 | 175 | return nullptr; |
146 | | |
147 | 774 | if (oFileList.find(archiveFilename) != oFileList.end()) |
148 | 0 | { |
149 | 0 | VSIArchiveContent *content = oFileList[archiveFilename]; |
150 | 0 | if (static_cast<time_t>(sStat.st_mtime) > content->mTime || |
151 | 0 | static_cast<vsi_l_offset>(sStat.st_size) != content->nFileSize) |
152 | 0 | { |
153 | 0 | CPLDebug("VSIArchive", |
154 | 0 | "The content of %s has changed since it was cached", |
155 | 0 | archiveFilename); |
156 | 0 | delete content; |
157 | 0 | oFileList.erase(archiveFilename); |
158 | 0 | } |
159 | 0 | else |
160 | 0 | { |
161 | 0 | return content; |
162 | 0 | } |
163 | 0 | } |
164 | | |
165 | 774 | bool bMustClose = poReader == nullptr; |
166 | 774 | if (poReader == nullptr) |
167 | 774 | { |
168 | 774 | poReader = CreateReader(archiveFilename); |
169 | 774 | if (!poReader) |
170 | 774 | return nullptr; |
171 | 774 | } |
172 | | |
173 | 0 | if (poReader->GotoFirstFile() == FALSE) |
174 | 0 | { |
175 | 0 | if (bMustClose) |
176 | 0 | delete (poReader); |
177 | 0 | return nullptr; |
178 | 0 | } |
179 | | |
180 | 0 | VSIArchiveContent *content = new VSIArchiveContent; |
181 | 0 | content->mTime = sStat.st_mtime; |
182 | 0 | content->nFileSize = static_cast<vsi_l_offset>(sStat.st_size); |
183 | 0 | content->nEntries = 0; |
184 | 0 | content->entries = nullptr; |
185 | 0 | oFileList[archiveFilename] = content; |
186 | |
|
187 | 0 | std::set<CPLString> oSet; |
188 | |
|
189 | 0 | do |
190 | 0 | { |
191 | 0 | const CPLString osFileName = poReader->GetFileName(); |
192 | 0 | bool bIsDir = false; |
193 | 0 | const CPLString osStrippedFilename = |
194 | 0 | GetStrippedFilename(osFileName, bIsDir); |
195 | 0 | if (osStrippedFilename.empty() || osStrippedFilename[0] == '/' || |
196 | 0 | osStrippedFilename.find("//") != std::string::npos) |
197 | 0 | { |
198 | 0 | continue; |
199 | 0 | } |
200 | | |
201 | 0 | if (oSet.find(osStrippedFilename) == oSet.end()) |
202 | 0 | { |
203 | 0 | oSet.insert(osStrippedFilename); |
204 | | |
205 | | // Add intermediate directory structure. |
206 | 0 | const char *pszBegin = osStrippedFilename.c_str(); |
207 | 0 | for (const char *pszIter = pszBegin; *pszIter; pszIter++) |
208 | 0 | { |
209 | 0 | if (*pszIter == '/') |
210 | 0 | { |
211 | 0 | char *pszStrippedFileName2 = CPLStrdup(osStrippedFilename); |
212 | 0 | pszStrippedFileName2[pszIter - pszBegin] = 0; |
213 | 0 | if (oSet.find(pszStrippedFileName2) == oSet.end()) |
214 | 0 | { |
215 | 0 | oSet.insert(pszStrippedFileName2); |
216 | |
|
217 | 0 | content->entries = |
218 | 0 | static_cast<VSIArchiveEntry *>(CPLRealloc( |
219 | 0 | content->entries, sizeof(VSIArchiveEntry) * |
220 | 0 | (content->nEntries + 1))); |
221 | 0 | content->entries[content->nEntries].fileName = |
222 | 0 | pszStrippedFileName2; |
223 | 0 | content->entries[content->nEntries].nModifiedTime = |
224 | 0 | poReader->GetModifiedTime(); |
225 | 0 | content->entries[content->nEntries].uncompressed_size = |
226 | 0 | 0; |
227 | 0 | content->entries[content->nEntries].bIsDir = TRUE; |
228 | 0 | 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 | 0 | content->nEntries++; |
238 | 0 | } |
239 | 0 | else |
240 | 0 | { |
241 | 0 | CPLFree(pszStrippedFileName2); |
242 | 0 | } |
243 | 0 | } |
244 | 0 | } |
245 | |
|
246 | 0 | content->entries = static_cast<VSIArchiveEntry *>( |
247 | 0 | CPLRealloc(content->entries, |
248 | 0 | sizeof(VSIArchiveEntry) * (content->nEntries + 1))); |
249 | 0 | content->entries[content->nEntries].fileName = |
250 | 0 | CPLStrdup(osStrippedFilename); |
251 | 0 | content->entries[content->nEntries].nModifiedTime = |
252 | 0 | poReader->GetModifiedTime(); |
253 | 0 | content->entries[content->nEntries].uncompressed_size = |
254 | 0 | poReader->GetFileSize(); |
255 | 0 | content->entries[content->nEntries].bIsDir = bIsDir; |
256 | 0 | content->entries[content->nEntries].file_pos = |
257 | 0 | 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 | 0 | content->nEntries++; |
265 | 0 | } |
266 | |
|
267 | 0 | } while (poReader->GotoNextFile()); |
268 | | |
269 | 0 | if (bMustClose) |
270 | 0 | delete (poReader); |
271 | |
|
272 | 0 | return content; |
273 | 0 | } |
274 | | |
275 | | /************************************************************************/ |
276 | | /* FindFileInArchive() */ |
277 | | /************************************************************************/ |
278 | | |
279 | | int VSIArchiveFilesystemHandler::FindFileInArchive( |
280 | | const char *archiveFilename, const char *fileInArchiveName, |
281 | | const VSIArchiveEntry **archiveEntry) |
282 | 949 | { |
283 | 949 | if (fileInArchiveName == nullptr) |
284 | 0 | return FALSE; |
285 | | |
286 | 949 | const VSIArchiveContent *content = GetContentOfArchive(archiveFilename); |
287 | 949 | if (content) |
288 | 0 | { |
289 | 0 | for (int i = 0; i < content->nEntries; i++) |
290 | 0 | { |
291 | 0 | if (strcmp(fileInArchiveName, content->entries[i].fileName) == 0) |
292 | 0 | { |
293 | 0 | if (archiveEntry) |
294 | 0 | *archiveEntry = &content->entries[i]; |
295 | 0 | return TRUE; |
296 | 0 | } |
297 | 0 | } |
298 | 0 | } |
299 | 949 | return FALSE; |
300 | 949 | } |
301 | | |
302 | | /************************************************************************/ |
303 | | /* CompactFilename() */ |
304 | | /************************************************************************/ |
305 | | |
306 | | static std::string CompactFilename(const char *pszArchiveInFileNameIn) |
307 | 2.65k | { |
308 | 2.65k | std::string osRet(pszArchiveInFileNameIn); |
309 | | |
310 | | // Replace a/../b by b and foo/a/../b by foo/b. |
311 | 9.59k | while (true) |
312 | 9.59k | { |
313 | 9.59k | size_t nSlashDotDot = osRet.find("/../"); |
314 | 9.59k | if (nSlashDotDot == std::string::npos || nSlashDotDot == 0) |
315 | 2.65k | break; |
316 | 6.94k | size_t nPos = nSlashDotDot - 1; |
317 | 136k | while (nPos > 0 && osRet[nPos] != '/') |
318 | 130k | --nPos; |
319 | 6.94k | if (nPos == 0) |
320 | 2.47k | osRet = osRet.substr(nSlashDotDot + strlen("/../")); |
321 | 4.47k | else |
322 | 4.47k | osRet = osRet.substr(0, nPos + 1) + |
323 | 4.47k | osRet.substr(nSlashDotDot + strlen("/../")); |
324 | 6.94k | } |
325 | 2.65k | return osRet; |
326 | 2.65k | } |
327 | | |
328 | | /************************************************************************/ |
329 | | /* SplitFilename() */ |
330 | | /************************************************************************/ |
331 | | |
332 | | char *VSIArchiveFilesystemHandler::SplitFilename(const char *pszFilename, |
333 | | CPLString &osFileInArchive, |
334 | | int bCheckMainFileExists) |
335 | 10.4k | { |
336 | | // TODO(schwehr): Cleanup redundant calls to GetPrefix and strlen. |
337 | 10.4k | if (strcmp(pszFilename, GetPrefix()) == 0) |
338 | 2 | return nullptr; |
339 | | |
340 | 10.4k | int i = 0; |
341 | | |
342 | | // Detect extended syntax: /vsiXXX/{archive_filename}/file_in_archive. |
343 | 10.4k | if (pszFilename[strlen(GetPrefix()) + 1] == '{') |
344 | 4.67k | { |
345 | 4.67k | pszFilename += strlen(GetPrefix()) + 1; |
346 | 4.67k | int nCountCurlies = 0; |
347 | 335k | while (pszFilename[i]) |
348 | 335k | { |
349 | 335k | if (pszFilename[i] == '{') |
350 | 5.66k | nCountCurlies++; |
351 | 329k | else if (pszFilename[i] == '}') |
352 | 5.41k | { |
353 | 5.41k | nCountCurlies--; |
354 | 5.41k | if (nCountCurlies == 0) |
355 | 4.52k | break; |
356 | 5.41k | } |
357 | 330k | i++; |
358 | 330k | } |
359 | 4.67k | if (nCountCurlies > 0) |
360 | 143 | return nullptr; |
361 | 4.52k | char *archiveFilename = CPLStrdup(pszFilename + 1); |
362 | 4.52k | archiveFilename[i - 1] = 0; |
363 | | |
364 | 4.52k | bool bArchiveFileExists = false; |
365 | 4.52k | if (!bCheckMainFileExists) |
366 | 0 | { |
367 | 0 | bArchiveFileExists = true; |
368 | 0 | } |
369 | 4.52k | else |
370 | 4.52k | { |
371 | 4.52k | CPLMutexHolder oHolder(&hMutex); |
372 | | |
373 | 4.52k | if (oFileList.find(archiveFilename) != oFileList.end()) |
374 | 0 | { |
375 | 0 | bArchiveFileExists = true; |
376 | 0 | } |
377 | 4.52k | } |
378 | | |
379 | 4.52k | if (!bArchiveFileExists) |
380 | 4.52k | { |
381 | 4.52k | VSIStatBufL statBuf; |
382 | 4.52k | VSIFilesystemHandler *poFSHandler = |
383 | 4.52k | VSIFileManager::GetHandler(archiveFilename); |
384 | 4.52k | if (poFSHandler->Stat(archiveFilename, &statBuf, |
385 | 4.52k | VSI_STAT_EXISTS_FLAG | |
386 | 4.52k | VSI_STAT_NATURE_FLAG) == 0 && |
387 | 4.52k | !VSI_ISDIR(statBuf.st_mode)) |
388 | 3.03k | { |
389 | 3.03k | bArchiveFileExists = true; |
390 | 3.03k | } |
391 | 4.52k | } |
392 | | |
393 | 4.52k | if (bArchiveFileExists) |
394 | 3.03k | { |
395 | 3.03k | if (IsEitherSlash(pszFilename[i + 1])) |
396 | 2.27k | { |
397 | 2.27k | osFileInArchive = CompactFilename(pszFilename + i + 2); |
398 | 2.27k | } |
399 | 758 | else if (pszFilename[i + 1] == '\0') |
400 | 1 | { |
401 | 1 | osFileInArchive = ""; |
402 | 1 | } |
403 | 757 | else |
404 | 757 | { |
405 | 757 | CPLFree(archiveFilename); |
406 | 757 | return nullptr; |
407 | 757 | } |
408 | | |
409 | | // Remove trailing slash. |
410 | 2.27k | if (!osFileInArchive.empty()) |
411 | 2.23k | { |
412 | 2.23k | const char lastC = osFileInArchive.back(); |
413 | 2.23k | if (IsEitherSlash(lastC)) |
414 | 450 | osFileInArchive.pop_back(); |
415 | 2.23k | } |
416 | | |
417 | 2.27k | return archiveFilename; |
418 | 3.03k | } |
419 | | |
420 | 1.49k | CPLFree(archiveFilename); |
421 | 1.49k | return nullptr; |
422 | 4.52k | } |
423 | | |
424 | | // Allow natural chaining of VSI drivers without requiring double slash. |
425 | | |
426 | 5.78k | CPLString osDoubleVsi(GetPrefix()); |
427 | 5.78k | osDoubleVsi += "/vsi"; |
428 | | |
429 | 5.78k | if (strncmp(pszFilename, osDoubleVsi.c_str(), osDoubleVsi.size()) == 0) |
430 | 3.59k | pszFilename += strlen(GetPrefix()); |
431 | 2.19k | else |
432 | 2.19k | 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 | 5.78k | int *pnCounter = static_cast<int *>(CPLGetTLS(CTLS_ABSTRACTARCHIVE_SPLIT)); |
439 | 5.78k | if (pnCounter == nullptr) |
440 | 1 | { |
441 | 1 | pnCounter = static_cast<int *>(CPLMalloc(sizeof(int))); |
442 | 1 | *pnCounter = 0; |
443 | 1 | CPLSetTLS(CTLS_ABSTRACTARCHIVE_SPLIT, pnCounter, TRUE); |
444 | 1 | } |
445 | 5.78k | if (*pnCounter == 3) |
446 | 303 | { |
447 | 303 | CPLError(CE_Failure, CPLE_AppDefined, |
448 | 303 | "Too deep recursion level in " |
449 | 303 | "VSIArchiveFilesystemHandler::SplitFilename()"); |
450 | 303 | return nullptr; |
451 | 303 | } |
452 | | |
453 | 5.48k | const std::vector<CPLString> oExtensions = GetExtensions(); |
454 | 5.48k | int nAttempts = 0; |
455 | 1.19M | while (pszFilename[i]) |
456 | 1.19M | { |
457 | 1.19M | int nToSkip = 0; |
458 | | |
459 | 1.19M | for (std::vector<CPLString>::const_iterator iter = oExtensions.begin(); |
460 | 5.76M | iter != oExtensions.end(); ++iter) |
461 | 4.58M | { |
462 | 4.58M | const CPLString &osExtension = *iter; |
463 | 4.58M | if (EQUALN(pszFilename + i, osExtension.c_str(), |
464 | 4.58M | osExtension.size())) |
465 | 13.8k | { |
466 | 13.8k | nToSkip = static_cast<int>(osExtension.size()); |
467 | 13.8k | break; |
468 | 13.8k | } |
469 | 4.58M | } |
470 | | |
471 | 1.19M | #ifdef DEBUG |
472 | | // For AFL, so that .cur_input is detected as the archive filename. |
473 | 1.19M | if (EQUALN(pszFilename + i, ".cur_input", strlen(".cur_input"))) |
474 | 1.24k | { |
475 | 1.24k | nToSkip = static_cast<int>(strlen(".cur_input")); |
476 | 1.24k | } |
477 | 1.19M | #endif |
478 | | |
479 | 1.19M | if (nToSkip != 0) |
480 | 15.0k | { |
481 | 15.0k | 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 | 15.0k | if (nAttempts == 5) |
485 | 527 | { |
486 | 527 | break; |
487 | 527 | } |
488 | 14.5k | VSIStatBufL statBuf; |
489 | 14.5k | char *archiveFilename = CPLStrdup(pszFilename); |
490 | 14.5k | bool bArchiveFileExists = false; |
491 | | |
492 | 14.5k | if (IsEitherSlash(archiveFilename[i + nToSkip])) |
493 | 927 | { |
494 | 927 | archiveFilename[i + nToSkip] = 0; |
495 | 927 | } |
496 | | |
497 | 14.5k | if (!bCheckMainFileExists) |
498 | 0 | { |
499 | 0 | bArchiveFileExists = true; |
500 | 0 | } |
501 | 14.5k | else |
502 | 14.5k | { |
503 | 14.5k | CPLMutexHolder oHolder(&hMutex); |
504 | | |
505 | 14.5k | if (oFileList.find(archiveFilename) != oFileList.end()) |
506 | 0 | { |
507 | 0 | bArchiveFileExists = true; |
508 | 0 | } |
509 | 14.5k | } |
510 | | |
511 | 14.5k | if (!bArchiveFileExists) |
512 | 14.5k | { |
513 | 14.5k | (*pnCounter)++; |
514 | | |
515 | 14.5k | VSIFilesystemHandler *poFSHandler = |
516 | 14.5k | VSIFileManager::GetHandler(archiveFilename); |
517 | 14.5k | if (poFSHandler->Stat(archiveFilename, &statBuf, |
518 | 14.5k | VSI_STAT_EXISTS_FLAG | |
519 | 14.5k | VSI_STAT_NATURE_FLAG) == 0 && |
520 | 14.5k | !VSI_ISDIR(statBuf.st_mode)) |
521 | 721 | { |
522 | 721 | bArchiveFileExists = true; |
523 | 721 | } |
524 | | |
525 | 14.5k | (*pnCounter)--; |
526 | 14.5k | } |
527 | | |
528 | 14.5k | if (bArchiveFileExists) |
529 | 721 | { |
530 | 721 | if (IsEitherSlash(pszFilename[i + nToSkip])) |
531 | 378 | { |
532 | 378 | osFileInArchive = |
533 | 378 | CompactFilename(pszFilename + i + nToSkip + 1); |
534 | 378 | } |
535 | 343 | else |
536 | 343 | { |
537 | 343 | osFileInArchive = ""; |
538 | 343 | } |
539 | | |
540 | | // Remove trailing slash. |
541 | 721 | if (!osFileInArchive.empty()) |
542 | 318 | { |
543 | 318 | const char lastC = osFileInArchive.back(); |
544 | 318 | if (IsEitherSlash(lastC)) |
545 | 126 | osFileInArchive.resize(osFileInArchive.size() - 1); |
546 | 318 | } |
547 | | |
548 | 721 | return archiveFilename; |
549 | 721 | } |
550 | 13.8k | CPLFree(archiveFilename); |
551 | 13.8k | } |
552 | 1.19M | i++; |
553 | 1.19M | } |
554 | 4.76k | return nullptr; |
555 | 5.48k | } |
556 | | |
557 | | /************************************************************************/ |
558 | | /* OpenArchiveFile() */ |
559 | | /************************************************************************/ |
560 | | |
561 | | VSIArchiveReader * |
562 | | VSIArchiveFilesystemHandler::OpenArchiveFile(const char *archiveFilename, |
563 | | const char *fileInArchiveName) |
564 | 1.05k | { |
565 | 1.05k | VSIArchiveReader *poReader = CreateReader(archiveFilename); |
566 | | |
567 | 1.05k | if (poReader == nullptr) |
568 | 1.05k | { |
569 | 1.05k | return nullptr; |
570 | 1.05k | } |
571 | | |
572 | 0 | if (fileInArchiveName == nullptr || strlen(fileInArchiveName) == 0) |
573 | 0 | { |
574 | 0 | if (poReader->GotoFirstFile() == FALSE) |
575 | 0 | { |
576 | 0 | delete (poReader); |
577 | 0 | return nullptr; |
578 | 0 | } |
579 | | |
580 | | // Skip optional leading subdir. |
581 | 0 | const CPLString osFileName = poReader->GetFileName(); |
582 | 0 | if (osFileName.empty() || IsEitherSlash(osFileName.back())) |
583 | 0 | { |
584 | 0 | if (poReader->GotoNextFile() == FALSE) |
585 | 0 | { |
586 | 0 | delete (poReader); |
587 | 0 | return nullptr; |
588 | 0 | } |
589 | 0 | } |
590 | | |
591 | 0 | if (poReader->GotoNextFile()) |
592 | 0 | { |
593 | 0 | CPLString msg; |
594 | 0 | msg.Printf("Support only 1 file in archive file %s when " |
595 | 0 | "no explicit in-archive filename is specified", |
596 | 0 | archiveFilename); |
597 | 0 | const VSIArchiveContent *content = |
598 | 0 | GetContentOfArchive(archiveFilename, poReader); |
599 | 0 | if (content) |
600 | 0 | { |
601 | 0 | msg += "\nYou could try one of the following :\n"; |
602 | 0 | for (int i = 0; i < content->nEntries; i++) |
603 | 0 | { |
604 | 0 | msg += CPLString().Printf(" %s/{%s}/%s\n", GetPrefix(), |
605 | 0 | archiveFilename, |
606 | 0 | content->entries[i].fileName); |
607 | 0 | } |
608 | 0 | } |
609 | |
|
610 | 0 | CPLError(CE_Failure, CPLE_NotSupported, "%s", msg.c_str()); |
611 | |
|
612 | 0 | delete (poReader); |
613 | 0 | return nullptr; |
614 | 0 | } |
615 | 0 | } |
616 | 0 | else |
617 | 0 | { |
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 | 0 | { |
622 | 0 | CPLMutexHolder oHolder(&hMutex); |
623 | |
|
624 | 0 | if (oFileList.find(archiveFilename) == oFileList.end()) |
625 | 0 | { |
626 | 0 | if (poReader->GotoFirstFile() == FALSE) |
627 | 0 | { |
628 | 0 | delete (poReader); |
629 | 0 | return nullptr; |
630 | 0 | } |
631 | | |
632 | 0 | const CPLString osFileName = poReader->GetFileName(); |
633 | 0 | bool bIsDir = false; |
634 | 0 | const CPLString osStrippedFilename = |
635 | 0 | GetStrippedFilename(osFileName, bIsDir); |
636 | 0 | if (!osStrippedFilename.empty()) |
637 | 0 | { |
638 | 0 | const bool bMatch = |
639 | 0 | strcmp(osStrippedFilename, fileInArchiveName) == 0; |
640 | 0 | if (bMatch) |
641 | 0 | { |
642 | 0 | if (bIsDir) |
643 | 0 | { |
644 | 0 | delete (poReader); |
645 | 0 | return nullptr; |
646 | 0 | } |
647 | 0 | return poReader; |
648 | 0 | } |
649 | 0 | } |
650 | 0 | } |
651 | 0 | } |
652 | | |
653 | 0 | const VSIArchiveEntry *archiveEntry = nullptr; |
654 | 0 | if (FindFileInArchive(archiveFilename, fileInArchiveName, |
655 | 0 | &archiveEntry) == FALSE || |
656 | 0 | archiveEntry->bIsDir) |
657 | 0 | { |
658 | 0 | delete (poReader); |
659 | 0 | return nullptr; |
660 | 0 | } |
661 | 0 | if (!poReader->GotoFileOffset(archiveEntry->file_pos)) |
662 | 0 | { |
663 | 0 | delete poReader; |
664 | 0 | return nullptr; |
665 | 0 | } |
666 | 0 | } |
667 | 0 | return poReader; |
668 | 0 | } |
669 | | |
670 | | /************************************************************************/ |
671 | | /* Stat() */ |
672 | | /************************************************************************/ |
673 | | |
674 | | int VSIArchiveFilesystemHandler::Stat(const char *pszFilename, |
675 | | VSIStatBufL *pStatBuf, int /* nFlags */) |
676 | 5.16k | { |
677 | 5.16k | memset(pStatBuf, 0, sizeof(VSIStatBufL)); |
678 | | |
679 | 5.16k | CPLString osFileInArchive; |
680 | 5.16k | char *archiveFilename = SplitFilename(pszFilename, osFileInArchive, TRUE); |
681 | 5.16k | if (archiveFilename == nullptr) |
682 | 3.97k | return -1; |
683 | | |
684 | 1.18k | int ret = -1; |
685 | 1.18k | if (!osFileInArchive.empty()) |
686 | 949 | { |
687 | | #ifdef DEBUG_VERBOSE |
688 | | CPLDebug("VSIArchive", "Looking for %s %s", archiveFilename, |
689 | | osFileInArchive.c_str()); |
690 | | #endif |
691 | | |
692 | 949 | const VSIArchiveEntry *archiveEntry = nullptr; |
693 | 949 | if (FindFileInArchive(archiveFilename, osFileInArchive, &archiveEntry)) |
694 | 0 | { |
695 | | // Patching st_size with uncompressed file size. |
696 | 0 | pStatBuf->st_size = archiveEntry->uncompressed_size; |
697 | 0 | pStatBuf->st_mtime = |
698 | 0 | static_cast<time_t>(archiveEntry->nModifiedTime); |
699 | 0 | if (archiveEntry->bIsDir) |
700 | 0 | pStatBuf->st_mode = S_IFDIR; |
701 | 0 | else |
702 | 0 | pStatBuf->st_mode = S_IFREG; |
703 | 0 | ret = 0; |
704 | 0 | } |
705 | 949 | } |
706 | 239 | else |
707 | 239 | { |
708 | 239 | VSIArchiveReader *poReader = CreateReader(archiveFilename); |
709 | 239 | CPLFree(archiveFilename); |
710 | 239 | archiveFilename = nullptr; |
711 | | |
712 | 239 | if (poReader != nullptr && poReader->GotoFirstFile()) |
713 | 0 | { |
714 | | // Skip optional leading subdir. |
715 | 0 | const CPLString osFileName = poReader->GetFileName(); |
716 | 0 | if (IsEitherSlash(osFileName.back())) |
717 | 0 | { |
718 | 0 | if (poReader->GotoNextFile() == FALSE) |
719 | 0 | { |
720 | 0 | delete (poReader); |
721 | 0 | return -1; |
722 | 0 | } |
723 | 0 | } |
724 | | |
725 | 0 | if (poReader->GotoNextFile()) |
726 | 0 | { |
727 | | // Several files in archive --> treat as dir. |
728 | 0 | pStatBuf->st_size = 0; |
729 | 0 | pStatBuf->st_mode = S_IFDIR; |
730 | 0 | } |
731 | 0 | else |
732 | 0 | { |
733 | | // Patching st_size with uncompressed file size. |
734 | 0 | pStatBuf->st_size = poReader->GetFileSize(); |
735 | 0 | pStatBuf->st_mtime = |
736 | 0 | static_cast<time_t>(poReader->GetModifiedTime()); |
737 | 0 | pStatBuf->st_mode = S_IFREG; |
738 | 0 | } |
739 | |
|
740 | 0 | ret = 0; |
741 | 0 | } |
742 | | |
743 | 239 | delete (poReader); |
744 | 239 | } |
745 | | |
746 | 1.18k | CPLFree(archiveFilename); |
747 | 1.18k | return ret; |
748 | 1.18k | } |
749 | | |
750 | | /************************************************************************/ |
751 | | /* ReadDirEx() */ |
752 | | /************************************************************************/ |
753 | | |
754 | | char **VSIArchiveFilesystemHandler::ReadDirEx(const char *pszDirname, |
755 | | int nMaxFiles) |
756 | 0 | { |
757 | 0 | CPLString osInArchiveSubDir; |
758 | 0 | char *archiveFilename = SplitFilename(pszDirname, osInArchiveSubDir, TRUE); |
759 | 0 | if (archiveFilename == nullptr) |
760 | 0 | return nullptr; |
761 | | |
762 | 0 | const int lenInArchiveSubDir = static_cast<int>(osInArchiveSubDir.size()); |
763 | |
|
764 | 0 | CPLStringList oDir; |
765 | |
|
766 | 0 | const VSIArchiveContent *content = GetContentOfArchive(archiveFilename); |
767 | 0 | if (!content) |
768 | 0 | { |
769 | 0 | CPLFree(archiveFilename); |
770 | 0 | return nullptr; |
771 | 0 | } |
772 | | |
773 | | #ifdef DEBUG_VERBOSE |
774 | | CPLDebug("VSIArchive", "Read dir %s", pszDirname); |
775 | | #endif |
776 | 0 | for (int i = 0; i < content->nEntries; i++) |
777 | 0 | { |
778 | 0 | const char *fileName = content->entries[i].fileName; |
779 | | /* Only list entries at the same level of inArchiveSubDir */ |
780 | 0 | if (lenInArchiveSubDir != 0 && |
781 | 0 | strncmp(fileName, osInArchiveSubDir, lenInArchiveSubDir) == 0 && |
782 | 0 | IsEitherSlash(fileName[lenInArchiveSubDir]) && |
783 | 0 | fileName[lenInArchiveSubDir + 1] != 0) |
784 | 0 | { |
785 | 0 | const char *slash = strchr(fileName + lenInArchiveSubDir + 1, '/'); |
786 | 0 | if (slash == nullptr) |
787 | 0 | slash = strchr(fileName + lenInArchiveSubDir + 1, '\\'); |
788 | 0 | if (slash == nullptr || slash[1] == 0) |
789 | 0 | { |
790 | 0 | char *tmpFileName = CPLStrdup(fileName); |
791 | 0 | if (slash != nullptr) |
792 | 0 | { |
793 | 0 | tmpFileName[strlen(tmpFileName) - 1] = 0; |
794 | 0 | } |
795 | | #ifdef DEBUG_VERBOSE |
796 | | CPLDebug("VSIArchive", "Add %s as in directory %s", |
797 | | tmpFileName + lenInArchiveSubDir + 1, pszDirname); |
798 | | #endif |
799 | 0 | oDir.AddString(tmpFileName + lenInArchiveSubDir + 1); |
800 | 0 | CPLFree(tmpFileName); |
801 | 0 | } |
802 | 0 | } |
803 | 0 | else if (lenInArchiveSubDir == 0 && strchr(fileName, '/') == nullptr && |
804 | 0 | strchr(fileName, '\\') == nullptr) |
805 | 0 | { |
806 | | // Only list toplevel files and directories. |
807 | | #ifdef DEBUG_VERBOSE |
808 | | CPLDebug("VSIArchive", "Add %s as in directory %s", fileName, |
809 | | pszDirname); |
810 | | #endif |
811 | 0 | oDir.AddString(fileName); |
812 | 0 | } |
813 | |
|
814 | 0 | if (nMaxFiles > 0 && oDir.Count() > nMaxFiles) |
815 | 0 | break; |
816 | 0 | } |
817 | |
|
818 | 0 | CPLFree(archiveFilename); |
819 | 0 | return oDir.StealList(); |
820 | 0 | } |
821 | | |
822 | | /************************************************************************/ |
823 | | /* IsLocal() */ |
824 | | /************************************************************************/ |
825 | | |
826 | | bool VSIArchiveFilesystemHandler::IsLocal(const char *pszPath) |
827 | 0 | { |
828 | 0 | if (!STARTS_WITH(pszPath, GetPrefix())) |
829 | 0 | return false; |
830 | 0 | const char *pszBaseFileName = pszPath + strlen(GetPrefix()); |
831 | 0 | VSIFilesystemHandler *poFSHandler = |
832 | 0 | VSIFileManager::GetHandler(pszBaseFileName); |
833 | 0 | return poFSHandler->IsLocal(pszPath); |
834 | 0 | } |
835 | | |
836 | | //! @endcond |