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 * Permission is hereby granted, free of charge, to any person obtaining a
13 * copy of this software and associated documentation files (the "Software"),
14 * to deal in the Software without restriction, including without limitation
15 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
16 * and/or sell copies of the Software, and to permit persons to whom the
17 * Software is furnished to do so, subject to the following conditions:
18 *
19 * The above copyright notice and this permission notice shall be included
20 * in all copies or substantial portions of the Software.
21 *
22 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
23 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
25 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28 * DEALINGS IN THE SOFTWARE.
29 ****************************************************************************/
30
31 // Must be first for DEBUG_BOOL case
32 #include "ogr_gmlas.h"
33
34 #include "cpl_sha256.h"
35
36 CPL_CVSID("$Id: ogrgmlasxsdcache.cpp 7e07230bbff24eb333608de4dbd460b7312839d0 2017-12-11 19:08:47Z Even Rouault $")
37
38 /************************************************************************/
39 /* GMLASResourceCache() */
40 /************************************************************************/
41
GMLASResourceCache()42 GMLASResourceCache::GMLASResourceCache()
43 : m_bHasCheckedCacheDirectory(false)
44 , m_bRefresh(false)
45 , m_bAllowDownload(true)
46 {
47 }
48
49 /************************************************************************/
50 /* ~GMLASResourceCache() */
51 /************************************************************************/
52
~GMLASResourceCache()53 GMLASResourceCache::~GMLASResourceCache()
54 {
55 }
56
57 /************************************************************************/
58 /* SetCacheDirectory() */
59 /************************************************************************/
60
SetCacheDirectory(const CPLString & osCacheDirectory)61 void GMLASResourceCache::SetCacheDirectory(const CPLString& osCacheDirectory)
62 {
63 m_osCacheDirectory = osCacheDirectory;
64 }
65
66 /************************************************************************/
67 /* RecursivelyCreateDirectoryIfNeeded() */
68 /************************************************************************/
69
RecursivelyCreateDirectoryIfNeeded(const CPLString & osDirname)70 bool GMLASResourceCache::RecursivelyCreateDirectoryIfNeeded(
71 const CPLString& osDirname)
72 {
73 VSIStatBufL sStat;
74 if( VSIStatL(osDirname, &sStat) == 0 )
75 {
76 return true;
77 }
78
79 CPLString osParent = CPLGetDirname(osDirname);
80 if( !osParent.empty() && osParent != "." )
81 {
82 if( !RecursivelyCreateDirectoryIfNeeded(osParent) )
83 return false;
84 }
85 return VSIMkdir( osDirname, 0755 ) == 0;
86 }
87
RecursivelyCreateDirectoryIfNeeded()88 bool GMLASResourceCache::RecursivelyCreateDirectoryIfNeeded()
89 {
90 if( !m_bHasCheckedCacheDirectory )
91 {
92 m_bHasCheckedCacheDirectory = true;
93 if( !RecursivelyCreateDirectoryIfNeeded(m_osCacheDirectory) )
94 {
95 CPLError(CE_Warning, CPLE_AppDefined,
96 "Cannot create %s", m_osCacheDirectory.c_str());
97 m_osCacheDirectory.clear();
98 return false;
99 }
100 }
101 return true;
102 }
103
104 /************************************************************************/
105 /* GetCachedFilename() */
106 /************************************************************************/
107
GetCachedFilename(const CPLString & osResource)108 CPLString GMLASResourceCache::GetCachedFilename(const CPLString& osResource)
109 {
110 CPLString osLaunderedName(osResource);
111 if( osLaunderedName.find("/vsicurl_streaming/") == 0 )
112 osLaunderedName = osLaunderedName.substr(
113 strlen("/vsicurl_streaming/") );
114 if( osLaunderedName.find("http://") == 0 )
115 osLaunderedName = osLaunderedName.substr( strlen("http://") );
116 else if( osLaunderedName.find("https://") == 0 )
117 osLaunderedName = osLaunderedName.substr( strlen("https://") );
118 for(size_t i=0; i<osLaunderedName.size(); i++)
119 {
120 if( !isalnum(osLaunderedName[i]) && osLaunderedName[i] != '.' )
121 osLaunderedName[i] = '_';
122 }
123
124 // If filename is too long, then truncate it and put a hash at the end
125 // We try to make sure that the whole filename (including the cache path)
126 // fits into 255 characters, for windows compat
127
128 const size_t nWindowsMaxFilenameSize = 255;
129 // 60 is arbitrary but should be sufficient for most people. We could
130 // always take into account m_osCacheDirectory.size(), but if we want to
131 // to be able to share caches between computers, then this would be impractical.
132 const size_t nTypicalMaxSizeForDirName = 60;
133 const size_t nSizeForDirName =
134 (m_osCacheDirectory.size() > nTypicalMaxSizeForDirName &&
135 m_osCacheDirectory.size() <
136 nWindowsMaxFilenameSize - strlen(".tmp") - 2 * CPL_SHA256_HASH_SIZE) ?
137 m_osCacheDirectory.size() : nTypicalMaxSizeForDirName;
138 CPLAssert( nWindowsMaxFilenameSize >= nSizeForDirName );
139 const size_t nMaxFilenameSize = nWindowsMaxFilenameSize - nSizeForDirName;
140
141 CPLAssert( nMaxFilenameSize >= strlen(".tmp") );
142 if( osLaunderedName.size() >= nMaxFilenameSize - strlen(".tmp") )
143 {
144 GByte abyHash[CPL_SHA256_HASH_SIZE];
145 CPL_SHA256(osResource, osResource.size(), abyHash);
146 char* pszHash = CPLBinaryToHex(CPL_SHA256_HASH_SIZE, abyHash);
147 osLaunderedName.resize(nMaxFilenameSize - strlen(".tmp") - 2 * CPL_SHA256_HASH_SIZE);
148 osLaunderedName += pszHash;
149 CPLFree(pszHash);
150 CPLDebug("GMLAS", "Cached filename truncated to %s",
151 osLaunderedName.c_str());
152 }
153
154 return CPLFormFilename( m_osCacheDirectory, osLaunderedName, nullptr );
155 }
156
157 /************************************************************************/
158 /* GMLASXSDCache() */
159 /************************************************************************/
160
GMLASXSDCache()161 GMLASXSDCache::GMLASXSDCache()
162 {
163 }
164
165 /************************************************************************/
166 /* ~GMLASXSDCache() */
167 /************************************************************************/
168
~GMLASXSDCache()169 GMLASXSDCache::~GMLASXSDCache()
170 {
171 }
172 /************************************************************************/
173 /* Open() */
174 /************************************************************************/
175
Open(const CPLString & osResource,const CPLString & osBasePath,CPLString & osOutFilename)176 VSILFILE* GMLASXSDCache::Open( const CPLString& osResource,
177 const CPLString& osBasePath,
178 CPLString& osOutFilename )
179 {
180 osOutFilename = osResource;
181 if( osResource.find("http://") == 0 ||
182 osResource.find("https://") == 0 )
183 {
184 osOutFilename = "/vsicurl_streaming/" + osResource;
185 }
186 else if( CPLIsFilenameRelative( osResource ) && !osResource.empty() )
187 {
188 /* Transform a/b + ../c --> a/c */
189 CPLString osResourceModified(osResource);
190 CPLString osBasePathModified(osBasePath);
191 while( (osResourceModified.find("../") == 0 ||
192 osResourceModified.find("..\\") == 0) &&
193 !osBasePathModified.empty() )
194 {
195 osBasePathModified = CPLGetDirname(osBasePathModified);
196 osResourceModified = osResourceModified.substr(3);
197 }
198
199 osOutFilename = CPLFormFilename(osBasePathModified,
200 osResourceModified, nullptr);
201 }
202
203 CPLDebug("GMLAS", "Resolving %s (%s) to %s",
204 osResource.c_str(),
205 osBasePath.c_str(),
206 osOutFilename.c_str());
207
208 VSILFILE* fp = nullptr;
209 if( !m_osCacheDirectory.empty() &&
210 osOutFilename.find("/vsicurl_streaming/") == 0 &&
211 RecursivelyCreateDirectoryIfNeeded() )
212 {
213 CPLString osCachedFileName(GetCachedFilename(osOutFilename));
214 if( !m_bRefresh ||
215 m_aoSetRefreshedFiles.find(osCachedFileName) !=
216 m_aoSetRefreshedFiles.end() )
217 {
218 fp = VSIFOpenL( osCachedFileName, "rb");
219 }
220 if( fp != nullptr )
221 {
222 CPLDebug("GMLAS", "Use cached %s", osCachedFileName.c_str());
223 }
224 else if( m_bAllowDownload )
225 {
226 if( m_bRefresh )
227 m_aoSetRefreshedFiles.insert(osCachedFileName);
228
229 CPLString osTmpfilename( osCachedFileName + ".tmp" );
230 if( CPLCopyFile( osTmpfilename, osOutFilename) == 0 )
231 {
232 // Due to the caching done by /vsicurl_streaming/, if the
233 // web server is no longer available but was before in the
234 // same process, then file opening will succeed. Hence we
235 // check that the downloaded file is not 0. This will only
236 // happen in practice with the unit tests.
237 VSIStatBufL sStat;
238 if( VSIStatL(osTmpfilename, &sStat) == 0 &&
239 sStat.st_size != 0 )
240 {
241 VSIRename( osTmpfilename, osCachedFileName );
242 fp = VSIFOpenL(osCachedFileName, "rb");
243 }
244 else
245 {
246 VSIUnlink(osTmpfilename);
247 }
248 }
249 }
250 }
251 else
252 {
253 if( m_bAllowDownload ||
254 osOutFilename.find("/vsicurl_streaming/") != 0 )
255 {
256 fp = VSIFOpenL(osOutFilename, "rb");
257 }
258 }
259
260 if( fp == nullptr )
261 {
262 CPLError(CE_Failure, CPLE_FileIO,
263 "Cannot resolve %s", osResource.c_str());
264 }
265
266 return fp;
267 }
268