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