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 359 : void GMLASResourceCache::SetCacheDirectory(const std::string &osCacheDirectory)
25 : {
26 359 : m_osCacheDirectory = osCacheDirectory;
27 359 : }
28 :
29 : /************************************************************************/
30 : /* RecursivelyCreateDirectoryIfNeeded() */
31 : /************************************************************************/
32 :
33 68 : bool GMLASResourceCache::RecursivelyCreateDirectoryIfNeeded(
34 : const std::string &osDirname)
35 : {
36 : VSIStatBufL sStat;
37 68 : if (VSIStatL(osDirname.c_str(), &sStat) == 0)
38 : {
39 60 : return true;
40 : }
41 :
42 16 : std::string osParent = CPLGetDirname(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 825 : bool GMLASResourceCache::RecursivelyCreateDirectoryIfNeeded()
52 : {
53 825 : if (!m_bHasCheckedCacheDirectory)
54 : {
55 60 : m_bHasCheckedCacheDirectory = true;
56 60 : 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 824 : return true;
65 : }
66 :
67 : /************************************************************************/
68 : /* GetCachedFilename() */
69 : /************************************************************************/
70 :
71 959 : std::string GMLASResourceCache::GetCachedFilename(const std::string &osResource)
72 : {
73 959 : std::string osLaunderedName(osResource);
74 959 : if (STARTS_WITH(osLaunderedName.c_str(), "http://"))
75 374 : osLaunderedName = osLaunderedName.substr(strlen("http://"));
76 585 : else if (STARTS_WITH(osLaunderedName.c_str(), "https://"))
77 585 : osLaunderedName = osLaunderedName.substr(strlen("https://"));
78 46384 : for (size_t i = 0; i < osLaunderedName.size(); i++)
79 : {
80 53774 : if (!isalnum(static_cast<unsigned char>(osLaunderedName[i])) &&
81 8349 : osLaunderedName[i] != '.')
82 5095 : 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 959 : 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 959 : const size_t nTypicalMaxSizeForDirName = 60;
95 : const size_t nSizeForDirName =
96 1188 : (m_osCacheDirectory.size() > nTypicalMaxSizeForDirName &&
97 229 : m_osCacheDirectory.size() < nWindowsMaxFilenameSize - strlen(".tmp") -
98 : 2 * CPL_SHA256_HASH_SIZE)
99 1188 : ? m_osCacheDirectory.size()
100 959 : : nTypicalMaxSizeForDirName;
101 959 : CPLAssert(nWindowsMaxFilenameSize >= nSizeForDirName);
102 959 : const size_t nMaxFilenameSize = nWindowsMaxFilenameSize - nSizeForDirName;
103 :
104 959 : CPLAssert(nMaxFilenameSize >= strlen(".tmp"));
105 959 : 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 CPLFormFilename(m_osCacheDirectory.c_str(), osLaunderedName.c_str(),
119 1918 : 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 : static bool bHasWarned = false;
180 0 : if (!bHasWarned)
181 : {
182 0 : bHasWarned = true;
183 0 : CPLDebug("GMLAS", "Cannot get GML schemas from %s", pszHTTPZIP);
184 : }
185 : }
186 4 : return bSuccess;
187 : }
188 :
189 : /************************************************************************/
190 : /* CacheAllISO20070417() */
191 : /************************************************************************/
192 :
193 2 : bool GMLASXSDCache::CacheAllISO20070417()
194 : {
195 : // As of today (2024-01-02), the schemas in https://schemas.opengis.net/iso/19139/20070417/
196 : // are actually the same as the ones in the iso19139-20070417_5-v20220526.zip archive
197 : // in https://schemas.opengis.net/iso/19139/iso19139-20070417.zip archive.
198 : // Download the later and unzip it for faster fetching of ISO schemas.
199 :
200 2 : bool bSuccess = false;
201 2 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
202 :
203 2 : const char *pszHTTPZIP =
204 : "https://schemas.opengis.net/iso/19139/iso19139-20070417.zip";
205 2 : CPLHTTPResult *psResult = CPLHTTPFetch(pszHTTPZIP, nullptr);
206 2 : if (psResult && psResult->nDataLen)
207 : {
208 : const std::string osZIPFilename(
209 4 : VSIMemGenerateHiddenFilename("temp.zip"));
210 : auto fpZIP =
211 2 : VSIFileFromMemBuffer(osZIPFilename.c_str(), psResult->pabyData,
212 2 : psResult->nDataLen, FALSE);
213 2 : if (fpZIP)
214 : {
215 2 : VSIFCloseL(fpZIP);
216 :
217 : const std::string osVSIZIPFilename(
218 2 : "/vsizip//vsizip/" + osZIPFilename +
219 4 : "/iso19139-20070417_5-v20220526.zip");
220 : const CPLStringList aosFiles(
221 4 : VSIReadDirRecursive(osVSIZIPFilename.c_str()));
222 142 : for (int i = 0; i < aosFiles.size(); ++i)
223 : {
224 274 : if (STARTS_WITH(aosFiles[i], "iso/19139/20070417/") &&
225 134 : strstr(aosFiles[i], ".xsd"))
226 : {
227 : const std::string osFilename(
228 144 : std::string("https://schemas.opengis.net/") +
229 216 : aosFiles[i]);
230 : const std::string osCachedFileName(
231 216 : GetCachedFilename(osFilename.c_str()));
232 :
233 144 : std::string osTmpfilename(osCachedFileName + ".tmp");
234 72 : if (CPLCopyFile(
235 : osTmpfilename.c_str(),
236 144 : (osVSIZIPFilename + "/" + aosFiles[i]).c_str()) ==
237 : 0)
238 : {
239 72 : VSIRename(osTmpfilename.c_str(),
240 : osCachedFileName.c_str());
241 72 : bSuccess = true;
242 : }
243 : }
244 : }
245 : }
246 2 : VSIUnlink(osZIPFilename.c_str());
247 : }
248 2 : CPLHTTPDestroyResult(psResult);
249 2 : if (!bSuccess)
250 : {
251 : static bool bHasWarned = false;
252 0 : if (!bHasWarned)
253 : {
254 0 : bHasWarned = true;
255 0 : CPLDebug("GMLAS", "Cannot get ISO schemas from %s", pszHTTPZIP);
256 : }
257 : }
258 4 : return bSuccess;
259 : }
260 :
261 : /************************************************************************/
262 : /* Open() */
263 : /************************************************************************/
264 :
265 1099 : VSILFILE *GMLASXSDCache::Open(const std::string &osResource,
266 : const std::string &osBasePath,
267 : std::string &osOutFilename)
268 : {
269 1099 : osOutFilename = osResource;
270 1099 : if (!STARTS_WITH(osResource.c_str(), "http://") &&
271 1002 : !STARTS_WITH(osResource.c_str(), "https://") &&
272 2101 : CPLIsFilenameRelative(osResource.c_str()) && !osResource.empty())
273 : {
274 : /* Transform a/b + ../c --> a/c */
275 930 : std::string osResourceModified(osResource);
276 930 : std::string osBasePathModified(osBasePath);
277 465 : while ((STARTS_WITH(osResourceModified.c_str(), "../") ||
278 466 : STARTS_WITH(osResourceModified.c_str(), "..\\")) &&
279 1 : !osBasePathModified.empty())
280 : {
281 0 : osBasePathModified = CPLGetDirname(osBasePathModified.c_str());
282 0 : osResourceModified = osResourceModified.substr(3);
283 : }
284 :
285 : osOutFilename = CPLFormFilename(osBasePathModified.c_str(),
286 465 : osResourceModified.c_str(), nullptr);
287 : }
288 :
289 1099 : CPLDebug("GMLAS", "Resolving %s (%s) to %s", osResource.c_str(),
290 : osBasePath.c_str(), osOutFilename.c_str());
291 :
292 1099 : VSILFILE *fp = nullptr;
293 1099 : bool bHasTriedZIPArchive = false;
294 1103 : retry:
295 1103 : if (!m_osCacheDirectory.empty() &&
296 1102 : (STARTS_WITH(osOutFilename.c_str(), "http://") ||
297 2660 : STARTS_WITH(osOutFilename.c_str(), "https://")) &&
298 808 : RecursivelyCreateDirectoryIfNeeded())
299 : {
300 : const std::string osCachedFileName(
301 1614 : GetCachedFilename(osOutFilename.c_str()));
302 808 : if (!m_bRefresh || m_aoSetRefreshedFiles.find(osCachedFileName) !=
303 808 : m_aoSetRefreshedFiles.end())
304 : {
305 806 : fp = VSIFOpenL(osCachedFileName.c_str(), "rb");
306 : }
307 807 : if (fp != nullptr)
308 : {
309 791 : CPLDebug("GMLAS", "Use cached %s", osCachedFileName.c_str());
310 : }
311 16 : else if (m_bAllowDownload)
312 : {
313 15 : if (m_bRefresh)
314 1 : m_aoSetRefreshedFiles.insert(osCachedFileName);
315 :
316 42 : else if (!bHasTriedZIPArchive &&
317 14 : strstr(osOutFilename.c_str(),
318 28 : "://schemas.opengis.net/gml/3.2.1/") &&
319 2 : CPLTestBool(CPLGetConfigOption(
320 : "OGR_GMLAS_USE_SCHEMAS_FROM_OGC_ZIP", "YES")))
321 : {
322 2 : bHasTriedZIPArchive = true;
323 2 : if (CacheAllGML321())
324 4 : goto retry;
325 : }
326 :
327 36 : else if (!bHasTriedZIPArchive &&
328 12 : strstr(osOutFilename.c_str(),
329 24 : "://schemas.opengis.net/iso/19139/20070417/") &&
330 2 : CPLTestBool(CPLGetConfigOption(
331 : "OGR_GMLAS_USE_SCHEMAS_FROM_OGC_ZIP", "YES")))
332 : {
333 2 : bHasTriedZIPArchive = true;
334 2 : if (CacheAllISO20070417())
335 2 : goto retry;
336 : }
337 :
338 : CPLHTTPResult *psResult =
339 11 : CPLHTTPFetch(osOutFilename.c_str(), nullptr);
340 11 : if (psResult == nullptr || psResult->nDataLen == 0)
341 : {
342 2 : CPLError(CE_Failure, CPLE_FileIO, "Cannot resolve %s",
343 : osResource.c_str());
344 2 : CPLHTTPDestroyResult(psResult);
345 2 : return nullptr;
346 : }
347 :
348 18 : std::string osTmpfilename(osCachedFileName + ".tmp");
349 9 : VSILFILE *fpTmp = VSIFOpenL(osTmpfilename.c_str(), "wb");
350 9 : if (fpTmp)
351 : {
352 18 : const auto nRet = VSIFWriteL(psResult->pabyData,
353 9 : psResult->nDataLen, 1, fpTmp);
354 9 : VSIFCloseL(fpTmp);
355 9 : if (nRet == 1)
356 : {
357 9 : VSIRename(osTmpfilename.c_str(), osCachedFileName.c_str());
358 9 : fp = VSIFOpenL(osCachedFileName.c_str(), "rb");
359 : }
360 : }
361 :
362 9 : CPLHTTPDestroyResult(psResult);
363 : }
364 : }
365 : else
366 : {
367 590 : if (STARTS_WITH(osOutFilename.c_str(), "http://") ||
368 294 : STARTS_WITH(osOutFilename.c_str(), "https://"))
369 : {
370 2 : if (m_bAllowDownload)
371 : {
372 : CPLHTTPResult *psResult =
373 2 : CPLHTTPFetch(osOutFilename.c_str(), nullptr);
374 2 : if (psResult == nullptr || psResult->nDataLen == 0)
375 : {
376 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot resolve %s",
377 : osResource.c_str());
378 0 : CPLHTTPDestroyResult(psResult);
379 0 : return nullptr;
380 : }
381 :
382 4 : fp = VSIFileFromMemBuffer(nullptr, psResult->pabyData,
383 2 : psResult->nDataLen, TRUE);
384 2 : if (fp)
385 : {
386 : // Steal the memory buffer from HTTP result
387 2 : psResult->pabyData = nullptr;
388 2 : psResult->nDataLen = 0;
389 2 : psResult->nDataAlloc = 0;
390 : }
391 2 : CPLHTTPDestroyResult(psResult);
392 : }
393 : }
394 : else
395 : {
396 294 : fp = VSIFOpenL(osOutFilename.c_str(), "rb");
397 : }
398 : }
399 :
400 1097 : if (fp == nullptr)
401 : {
402 5 : CPLError(CE_Failure, CPLE_FileIO, "Cannot resolve %s",
403 : osResource.c_str());
404 : }
405 :
406 1097 : return fp;
407 : }
|