Line data Source code
1 : /******************************************************************************
2 : * Project: OGR
3 : * Purpose: OGRGMLASDriver implementation
4 : * Author: Even Rouault, <even dot rouault at spatialys dot com>
5 : *
6 : * Initial development funded by the European Earth observation programme
7 : * Copernicus
8 : *
9 : ******************************************************************************
10 : * Copyright (c) 2016, Even Rouault, <even dot rouault at spatialys dot com>
11 : *
12 : * SPDX-License-Identifier: MIT
13 : ****************************************************************************/
14 :
15 : #include "ogr_gmlas.h"
16 :
17 : #include "cpl_http.h"
18 : #include "cpl_sha256.h"
19 :
20 : /************************************************************************/
21 : /* SetCacheDirectory() */
22 : /************************************************************************/
23 :
24 361 : void GMLASResourceCache::SetCacheDirectory(const std::string &osCacheDirectory)
25 : {
26 361 : m_osCacheDirectory = osCacheDirectory;
27 361 : }
28 :
29 : /************************************************************************/
30 : /* RecursivelyCreateDirectoryIfNeeded() */
31 : /************************************************************************/
32 :
33 69 : bool GMLASResourceCache::RecursivelyCreateDirectoryIfNeeded(
34 : const std::string &osDirname)
35 : {
36 : VSIStatBufL sStat;
37 69 : if (VSIStatL(osDirname.c_str(), &sStat) == 0)
38 : {
39 61 : return true;
40 : }
41 :
42 16 : std::string osParent = CPLGetDirnameSafe(osDirname.c_str());
43 8 : if (!osParent.empty() && osParent != ".")
44 : {
45 8 : if (!RecursivelyCreateDirectoryIfNeeded(osParent.c_str()))
46 2 : return false;
47 : }
48 6 : return VSIMkdir(osDirname.c_str(), 0755) == 0;
49 : }
50 :
51 914 : bool GMLASResourceCache::RecursivelyCreateDirectoryIfNeeded()
52 : {
53 914 : if (!m_bHasCheckedCacheDirectory)
54 : {
55 61 : m_bHasCheckedCacheDirectory = true;
56 61 : if (!RecursivelyCreateDirectoryIfNeeded(m_osCacheDirectory))
57 : {
58 1 : CPLError(CE_Warning, CPLE_AppDefined, "Cannot create %s",
59 : m_osCacheDirectory.c_str());
60 1 : m_osCacheDirectory.clear();
61 1 : return false;
62 : }
63 : }
64 913 : return true;
65 : }
66 :
67 : /************************************************************************/
68 : /* GetCachedFilename() */
69 : /************************************************************************/
70 :
71 1048 : std::string GMLASResourceCache::GetCachedFilename(const std::string &osResource)
72 : {
73 2096 : std::string osLaunderedName(osResource);
74 1048 : if (STARTS_WITH(osLaunderedName.c_str(), "http://"))
75 463 : osLaunderedName = osLaunderedName.substr(strlen("http://"));
76 585 : else if (STARTS_WITH(osLaunderedName.c_str(), "https://"))
77 585 : osLaunderedName = osLaunderedName.substr(strlen("https://"));
78 50689 : for (size_t i = 0; i < osLaunderedName.size(); i++)
79 : {
80 58780 : if (!isalnum(static_cast<unsigned char>(osLaunderedName[i])) &&
81 9139 : osLaunderedName[i] != '.')
82 5453 : osLaunderedName[i] = '_';
83 : }
84 :
85 : // If filename is too long, then truncate it and put a hash at the end
86 : // We try to make sure that the whole filename (including the cache path)
87 : // fits into 255 characters, for windows compat
88 :
89 1048 : const size_t nWindowsMaxFilenameSize = 255;
90 : // 60 is arbitrary but should be sufficient for most people. We could
91 : // always take into account m_osCacheDirectory.size(), but if we want to
92 : // to be able to share caches between computers, then this would be
93 : // impractical.
94 1048 : const size_t nTypicalMaxSizeForDirName = 60;
95 : const size_t nSizeForDirName =
96 1277 : (m_osCacheDirectory.size() > nTypicalMaxSizeForDirName &&
97 229 : m_osCacheDirectory.size() < nWindowsMaxFilenameSize - strlen(".tmp") -
98 : 2 * CPL_SHA256_HASH_SIZE)
99 1277 : ? m_osCacheDirectory.size()
100 1048 : : nTypicalMaxSizeForDirName;
101 1048 : CPLAssert(nWindowsMaxFilenameSize >= nSizeForDirName);
102 1048 : const size_t nMaxFilenameSize = nWindowsMaxFilenameSize - nSizeForDirName;
103 :
104 1048 : CPLAssert(nMaxFilenameSize >= strlen(".tmp"));
105 1048 : if (osLaunderedName.size() >= nMaxFilenameSize - strlen(".tmp"))
106 : {
107 : GByte abyHash[CPL_SHA256_HASH_SIZE];
108 3 : CPL_SHA256(osResource.c_str(), osResource.size(), abyHash);
109 3 : char *pszHash = CPLBinaryToHex(CPL_SHA256_HASH_SIZE, abyHash);
110 3 : osLaunderedName.resize(nMaxFilenameSize - strlen(".tmp") -
111 : 2 * CPL_SHA256_HASH_SIZE);
112 3 : osLaunderedName += pszHash;
113 3 : CPLFree(pszHash);
114 3 : CPLDebug("GMLAS", "Cached filename truncated to %s",
115 : osLaunderedName.c_str());
116 : }
117 :
118 : return CPLFormFilenameSafe(m_osCacheDirectory.c_str(),
119 2096 : osLaunderedName.c_str(), nullptr);
120 : }
121 :
122 : /************************************************************************/
123 : /* CacheAllGML321() */
124 : /************************************************************************/
125 :
126 2 : bool GMLASXSDCache::CacheAllGML321()
127 : {
128 : // As of today (2024-01-02), the schemas in https://schemas.opengis.net/gml/3.2.1
129 : // are actually the same as the ones in the https://schemas.opengis.net/gml/gml-3_2_2.zip archive.
130 : // Download the later and unzip it for faster fetching of GML schemas.
131 :
132 2 : bool bSuccess = false;
133 2 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
134 :
135 2 : const char *pszHTTPZIP = "https://schemas.opengis.net/gml/gml-3_2_2.zip";
136 2 : CPLHTTPResult *psResult = CPLHTTPFetch(pszHTTPZIP, nullptr);
137 2 : if (psResult && psResult->nDataLen)
138 : {
139 : const std::string osZIPFilename(
140 4 : VSIMemGenerateHiddenFilename("temp.zip"));
141 : auto fpZIP =
142 2 : VSIFileFromMemBuffer(osZIPFilename.c_str(), psResult->pabyData,
143 2 : psResult->nDataLen, FALSE);
144 2 : if (fpZIP)
145 : {
146 2 : VSIFCloseL(fpZIP);
147 :
148 4 : const std::string osVSIZIPFilename("/vsizip/" + osZIPFilename);
149 : const CPLStringList aosFiles(
150 4 : VSIReadDirRecursive(osVSIZIPFilename.c_str()));
151 70 : for (int i = 0; i < aosFiles.size(); ++i)
152 : {
153 68 : if (strstr(aosFiles[i], ".xsd"))
154 : {
155 : const std::string osFilename(
156 116 : std::string("https://schemas.opengis.net/gml/3.2.1/") +
157 174 : CPLGetFilename(aosFiles[i]));
158 : const std::string osCachedFileName(
159 174 : GetCachedFilename(osFilename.c_str()));
160 :
161 116 : std::string osTmpfilename(osCachedFileName + ".tmp");
162 58 : if (CPLCopyFile(
163 : osTmpfilename.c_str(),
164 116 : (osVSIZIPFilename + "/" + aosFiles[i]).c_str()) ==
165 : 0)
166 : {
167 58 : VSIRename(osTmpfilename.c_str(),
168 : osCachedFileName.c_str());
169 58 : bSuccess = true;
170 : }
171 : }
172 : }
173 : }
174 2 : VSIUnlink(osZIPFilename.c_str());
175 : }
176 2 : CPLHTTPDestroyResult(psResult);
177 2 : if (!bSuccess)
178 : {
179 0 : CPLDebugOnce("GMLAS", "Cannot get GML schemas from %s", pszHTTPZIP);
180 : }
181 4 : return bSuccess;
182 : }
183 :
184 : /************************************************************************/
185 : /* CacheAllISO20070417() */
186 : /************************************************************************/
187 :
188 2 : bool GMLASXSDCache::CacheAllISO20070417()
189 : {
190 : // As of today (2024-01-02), the schemas in https://schemas.opengis.net/iso/19139/20070417/
191 : // are actually the same as the ones in the iso19139-20070417_5-v20220526.zip archive
192 : // in https://schemas.opengis.net/iso/19139/iso19139-20070417.zip archive.
193 : // Download the later and unzip it for faster fetching of ISO schemas.
194 :
195 2 : bool bSuccess = false;
196 2 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
197 :
198 2 : const char *pszHTTPZIP =
199 : "https://schemas.opengis.net/iso/19139/iso19139-20070417.zip";
200 2 : CPLHTTPResult *psResult = CPLHTTPFetch(pszHTTPZIP, nullptr);
201 2 : if (psResult && psResult->nDataLen)
202 : {
203 : const std::string osZIPFilename(
204 4 : VSIMemGenerateHiddenFilename("temp.zip"));
205 : auto fpZIP =
206 2 : VSIFileFromMemBuffer(osZIPFilename.c_str(), psResult->pabyData,
207 2 : psResult->nDataLen, FALSE);
208 2 : if (fpZIP)
209 : {
210 2 : VSIFCloseL(fpZIP);
211 :
212 : const std::string osVSIZIPFilename(
213 2 : "/vsizip//vsizip/" + osZIPFilename +
214 4 : "/iso19139-20070417_5-v20220526.zip");
215 : const CPLStringList aosFiles(
216 4 : VSIReadDirRecursive(osVSIZIPFilename.c_str()));
217 142 : for (int i = 0; i < aosFiles.size(); ++i)
218 : {
219 274 : if (STARTS_WITH(aosFiles[i], "iso/19139/20070417/") &&
220 134 : strstr(aosFiles[i], ".xsd"))
221 : {
222 : const std::string osFilename(
223 144 : std::string("https://schemas.opengis.net/") +
224 216 : aosFiles[i]);
225 : const std::string osCachedFileName(
226 216 : GetCachedFilename(osFilename.c_str()));
227 :
228 144 : std::string osTmpfilename(osCachedFileName + ".tmp");
229 72 : if (CPLCopyFile(
230 : osTmpfilename.c_str(),
231 144 : (osVSIZIPFilename + "/" + aosFiles[i]).c_str()) ==
232 : 0)
233 : {
234 72 : VSIRename(osTmpfilename.c_str(),
235 : osCachedFileName.c_str());
236 72 : bSuccess = true;
237 : }
238 : }
239 : }
240 : }
241 2 : VSIUnlink(osZIPFilename.c_str());
242 : }
243 2 : CPLHTTPDestroyResult(psResult);
244 2 : if (!bSuccess)
245 : {
246 0 : CPLDebugOnce("GMLAS", "Cannot get ISO schemas from %s", pszHTTPZIP);
247 : }
248 4 : return bSuccess;
249 : }
250 :
251 : /************************************************************************/
252 : /* Open() */
253 : /************************************************************************/
254 :
255 1188 : VSILFILE *GMLASXSDCache::Open(const std::string &osResource,
256 : const std::string &osBasePath,
257 : std::string &osOutFilename)
258 : {
259 1188 : osOutFilename = osResource;
260 1188 : if (!STARTS_WITH(osResource.c_str(), "http://") &&
261 1079 : !STARTS_WITH(osResource.c_str(), "https://") &&
262 2267 : CPLIsFilenameRelative(osResource.c_str()) && !osResource.empty())
263 : {
264 : /* Transform a/b + ../c --> a/c */
265 1084 : std::string osResourceModified(osResource);
266 542 : std::string osBasePathModified(osBasePath);
267 542 : while ((STARTS_WITH(osResourceModified.c_str(), "../") ||
268 543 : STARTS_WITH(osResourceModified.c_str(), "..\\")) &&
269 1 : !osBasePathModified.empty())
270 : {
271 0 : osBasePathModified = CPLGetDirnameSafe(osBasePathModified.c_str());
272 0 : osResourceModified = osResourceModified.substr(3);
273 : }
274 :
275 1084 : osOutFilename = CPLFormFilenameSafe(
276 542 : osBasePathModified.c_str(), osResourceModified.c_str(), nullptr);
277 : }
278 :
279 1188 : CPLDebug("GMLAS", "Resolving %s (%s) to %s", osResource.c_str(),
280 : osBasePath.c_str(), osOutFilename.c_str());
281 :
282 1188 : VSILFILE *fp = nullptr;
283 1188 : bool bHasTriedZIPArchive = false;
284 1192 : retry:
285 1192 : if (!m_osCacheDirectory.empty() &&
286 1191 : (STARTS_WITH(osOutFilename.c_str(), "http://") ||
287 2838 : STARTS_WITH(osOutFilename.c_str(), "https://")) &&
288 897 : RecursivelyCreateDirectoryIfNeeded())
289 : {
290 : const std::string osCachedFileName(
291 1792 : GetCachedFilename(osOutFilename.c_str()));
292 897 : if (!m_bRefresh || m_aoSetRefreshedFiles.find(osCachedFileName) !=
293 897 : m_aoSetRefreshedFiles.end())
294 : {
295 895 : fp = VSIFOpenL(osCachedFileName.c_str(), "rb");
296 : }
297 896 : if (fp != nullptr)
298 : {
299 846 : CPLDebug("GMLAS", "Use cached %s", osCachedFileName.c_str());
300 : }
301 50 : else if (m_bAllowDownload)
302 : {
303 49 : if (m_bRefresh)
304 1 : m_aoSetRefreshedFiles.insert(osCachedFileName);
305 :
306 144 : else if (!bHasTriedZIPArchive &&
307 48 : strstr(osOutFilename.c_str(),
308 96 : "://schemas.opengis.net/gml/3.2.1/") &&
309 2 : CPLTestBool(CPLGetConfigOption(
310 : "OGR_GMLAS_USE_SCHEMAS_FROM_OGC_ZIP", "YES")))
311 : {
312 2 : bHasTriedZIPArchive = true;
313 2 : if (CacheAllGML321())
314 4 : goto retry;
315 : }
316 :
317 138 : else if (!bHasTriedZIPArchive &&
318 46 : strstr(osOutFilename.c_str(),
319 92 : "://schemas.opengis.net/iso/19139/20070417/") &&
320 2 : CPLTestBool(CPLGetConfigOption(
321 : "OGR_GMLAS_USE_SCHEMAS_FROM_OGC_ZIP", "YES")))
322 : {
323 2 : bHasTriedZIPArchive = true;
324 2 : if (CacheAllISO20070417())
325 2 : goto retry;
326 : }
327 :
328 : CPLHTTPResult *psResult =
329 45 : CPLHTTPFetch(osOutFilename.c_str(), nullptr);
330 45 : if (psResult == nullptr || psResult->nDataLen == 0)
331 : {
332 2 : CPLError(CE_Failure, CPLE_FileIO, "Cannot resolve %s",
333 : osResource.c_str());
334 2 : CPLHTTPDestroyResult(psResult);
335 2 : return nullptr;
336 : }
337 :
338 86 : std::string osTmpfilename(osCachedFileName + ".tmp");
339 43 : VSILFILE *fpTmp = VSIFOpenL(osTmpfilename.c_str(), "wb");
340 43 : if (fpTmp)
341 : {
342 86 : const auto nRet = VSIFWriteL(psResult->pabyData,
343 43 : psResult->nDataLen, 1, fpTmp);
344 43 : VSIFCloseL(fpTmp);
345 43 : if (nRet == 1)
346 : {
347 43 : VSIRename(osTmpfilename.c_str(), osCachedFileName.c_str());
348 43 : fp = VSIFOpenL(osCachedFileName.c_str(), "rb");
349 : }
350 : }
351 :
352 43 : CPLHTTPDestroyResult(psResult);
353 : }
354 : }
355 : else
356 : {
357 590 : if (STARTS_WITH(osOutFilename.c_str(), "http://") ||
358 294 : STARTS_WITH(osOutFilename.c_str(), "https://"))
359 : {
360 2 : if (m_bAllowDownload)
361 : {
362 : CPLHTTPResult *psResult =
363 2 : CPLHTTPFetch(osOutFilename.c_str(), nullptr);
364 2 : if (psResult == nullptr || psResult->nDataLen == 0)
365 : {
366 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot resolve %s",
367 : osResource.c_str());
368 0 : CPLHTTPDestroyResult(psResult);
369 0 : return nullptr;
370 : }
371 :
372 4 : fp = VSIFileFromMemBuffer(nullptr, psResult->pabyData,
373 2 : psResult->nDataLen, TRUE);
374 2 : if (fp)
375 : {
376 : // Steal the memory buffer from HTTP result
377 2 : psResult->pabyData = nullptr;
378 2 : psResult->nDataLen = 0;
379 2 : psResult->nDataAlloc = 0;
380 : }
381 2 : CPLHTTPDestroyResult(psResult);
382 : }
383 : }
384 : else
385 : {
386 294 : fp = VSIFOpenL(osOutFilename.c_str(), "rb");
387 : }
388 : }
389 :
390 1186 : if (fp == nullptr)
391 : {
392 5 : CPLError(CE_Failure, CPLE_FileIO, "Cannot resolve %s",
393 : osResource.c_str());
394 : }
395 :
396 1186 : return fp;
397 : }
|