Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: OpenGIS Simple Features Reference Implementation
4 : * Purpose: Implements OGRGPSBabelDataSource class.
5 : * Author: Even Rouault, <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2010-2013, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_conv.h"
14 : #include "cpl_string.h"
15 : #include "cpl_error.h"
16 : #include "cpl_spawn.h"
17 : #include "ogr_gpsbabel.h"
18 :
19 : #include <cstring>
20 : #include <algorithm>
21 :
22 : /************************************************************************/
23 : /* OGRGPSBabelDataSource() */
24 : /************************************************************************/
25 :
26 10 : OGRGPSBabelDataSource::OGRGPSBabelDataSource()
27 : {
28 10 : }
29 :
30 : /************************************************************************/
31 : /* ~OGRGPSBabelDataSource() */
32 : /************************************************************************/
33 :
34 20 : OGRGPSBabelDataSource::~OGRGPSBabelDataSource()
35 :
36 : {
37 10 : CPLFree(pszGPSBabelDriverName);
38 10 : CPLFree(pszFilename);
39 :
40 10 : OGRGPSBabelDataSource::CloseDependentDatasets();
41 :
42 10 : if (!osTmpFileName.empty())
43 6 : VSIUnlink(osTmpFileName.c_str());
44 20 : }
45 :
46 : /************************************************************************/
47 : /* CloseDependentDatasets() */
48 : /************************************************************************/
49 :
50 10 : int OGRGPSBabelDataSource::CloseDependentDatasets()
51 : {
52 10 : if (poGPXDS == nullptr)
53 5 : return FALSE;
54 :
55 5 : GDALClose(poGPXDS);
56 5 : poGPXDS = nullptr;
57 5 : return TRUE;
58 : }
59 :
60 : /************************************************************************/
61 : /* GetArgv() */
62 : /************************************************************************/
63 :
64 5 : static char **GetArgv(int bExplicitFeatures, int bWaypoints, int bRoutes,
65 : int bTracks, const char *pszGPSBabelDriverName,
66 : const char *pszFilename)
67 : {
68 5 : char **argv = CSLAddString(nullptr, "gpsbabel");
69 5 : if (bExplicitFeatures)
70 : {
71 3 : if (bWaypoints)
72 1 : argv = CSLAddString(argv, "-w");
73 3 : if (bRoutes)
74 1 : argv = CSLAddString(argv, "-r");
75 3 : if (bTracks)
76 1 : argv = CSLAddString(argv, "-t");
77 : }
78 5 : argv = CSLAddString(argv, "-i");
79 5 : argv = CSLAddString(argv, pszGPSBabelDriverName);
80 5 : argv = CSLAddString(argv, "-f");
81 5 : argv = CSLAddString(argv, pszFilename);
82 5 : argv = CSLAddString(argv, "-o");
83 5 : argv = CSLAddString(argv, "gpx,gpxver=1.1");
84 5 : argv = CSLAddString(argv, "-F");
85 5 : argv = CSLAddString(argv, "-");
86 :
87 5 : return argv;
88 : }
89 :
90 : /************************************************************************/
91 : /* IsSpecialFile() */
92 : /************************************************************************/
93 :
94 7 : bool OGRGPSBabelDataSource::IsSpecialFile(const char *pszFilename)
95 : {
96 14 : return STARTS_WITH(pszFilename, "/dev/") ||
97 14 : STARTS_WITH(pszFilename, "usb:") ||
98 14 : (STARTS_WITH(pszFilename, "COM") && atoi(pszFilename + 3) > 0);
99 : }
100 :
101 : /************************************************************************/
102 : /* IsValidDriverName() */
103 : /************************************************************************/
104 :
105 8 : bool OGRGPSBabelDataSource::IsValidDriverName(const char *pszGPSBabelDriverName)
106 : {
107 34 : for (int i = 0; pszGPSBabelDriverName[i] != '\0'; i++)
108 : {
109 26 : char ch = pszGPSBabelDriverName[i];
110 26 : if (!((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') ||
111 0 : (ch >= '0' && ch <= '9') || ch == '_' || ch == '=' || ch == '.' ||
112 : ch == ','))
113 : {
114 0 : CPLError(CE_Failure, CPLE_AppDefined,
115 : "Invalid GPSBabel driver name");
116 0 : return false;
117 : }
118 : }
119 8 : return true;
120 : }
121 :
122 : /************************************************************************/
123 : /* Open() */
124 : /************************************************************************/
125 :
126 10 : int OGRGPSBabelDataSource::Open(const char *pszDatasourceName,
127 : const char *pszGPSBabelDriverNameIn,
128 : char **papszOpenOptionsIn)
129 :
130 : {
131 10 : constexpr const char *GPSBABEL_PREFIX = "GPSBABEL:";
132 10 : if (!STARTS_WITH_CI(pszDatasourceName, GPSBABEL_PREFIX))
133 : {
134 1 : CPLAssert(pszGPSBabelDriverNameIn);
135 1 : pszGPSBabelDriverName = CPLStrdup(pszGPSBabelDriverNameIn);
136 1 : pszFilename = CPLStrdup(pszDatasourceName);
137 : }
138 : else
139 : {
140 9 : if (CSLFetchNameValue(papszOpenOptionsIn, "FILENAME"))
141 0 : pszFilename =
142 0 : CPLStrdup(CSLFetchNameValue(papszOpenOptionsIn, "FILENAME"));
143 :
144 9 : if (CSLFetchNameValue(papszOpenOptionsIn, "GPSBABEL_DRIVER"))
145 : {
146 0 : if (pszFilename == nullptr)
147 : {
148 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing FILENAME");
149 0 : return FALSE;
150 : }
151 :
152 0 : pszGPSBabelDriverName =
153 0 : CPLStrdup(CSLFetchNameValue(papszOpenOptionsIn, "DRIVER"));
154 :
155 : /* A bit of validation to avoid command line injection */
156 0 : if (!IsValidDriverName(pszGPSBabelDriverName))
157 0 : return FALSE;
158 : }
159 : }
160 :
161 10 : bool bExplicitFeatures = false;
162 10 : bool bWaypoints = true;
163 10 : bool bTracks = true;
164 10 : bool bRoutes = true;
165 :
166 10 : if (pszGPSBabelDriverName == nullptr)
167 : {
168 9 : const char *pszDatasourceNameAfterPrefix =
169 : pszDatasourceName + strlen(GPSBABEL_PREFIX);
170 9 : const char *pszSep = strchr(pszDatasourceNameAfterPrefix, ':');
171 9 : if (pszSep == nullptr)
172 : {
173 2 : CPLError(CE_Failure, CPLE_AppDefined,
174 : "Wrong syntax. Expected GPSBabel:driver_name:file_name");
175 2 : return FALSE;
176 : }
177 :
178 7 : pszGPSBabelDriverName = CPLStrdup(pszDatasourceNameAfterPrefix);
179 7 : pszGPSBabelDriverName[pszSep - pszDatasourceNameAfterPrefix] = '\0';
180 :
181 : /* A bit of validation to avoid command line injection */
182 7 : if (!IsValidDriverName(pszGPSBabelDriverName))
183 0 : return FALSE;
184 :
185 : /* Parse optional features= option */
186 7 : const char *pszAfterSep = pszSep + 1;
187 7 : constexpr const char *FEATURES_EQUAL = "features=";
188 7 : if (STARTS_WITH_CI(pszAfterSep, FEATURES_EQUAL))
189 : {
190 5 : const char *pszAfterFeaturesEqual =
191 : pszAfterSep + strlen(FEATURES_EQUAL);
192 5 : const char *pszNextSep = strchr(pszAfterFeaturesEqual, ':');
193 5 : if (pszNextSep == nullptr)
194 : {
195 1 : CPLError(CE_Failure, CPLE_AppDefined,
196 : "Wrong syntax. Expected "
197 : "GPSBabel:driver_name[,options]*:["
198 : "features=waypoints,tracks,routes:]file_name");
199 1 : return FALSE;
200 : }
201 :
202 4 : char *pszFeatures = CPLStrdup(pszAfterFeaturesEqual);
203 4 : pszFeatures[pszNextSep - pszAfterFeaturesEqual] = 0;
204 4 : char **papszTokens = CSLTokenizeString(pszFeatures);
205 4 : char **papszIter = papszTokens;
206 4 : bool bErr = false;
207 4 : bExplicitFeatures = true;
208 4 : bWaypoints = false;
209 4 : bTracks = false;
210 4 : bRoutes = false;
211 8 : while (papszIter && *papszIter)
212 : {
213 4 : if (EQUAL(*papszIter, "waypoints"))
214 1 : bWaypoints = true;
215 3 : else if (EQUAL(*papszIter, "tracks"))
216 1 : bTracks = true;
217 2 : else if (EQUAL(*papszIter, "routes"))
218 1 : bRoutes = true;
219 : else
220 : {
221 1 : CPLError(CE_Failure, CPLE_AppDefined,
222 : "Wrong value for 'features' options");
223 1 : bErr = true;
224 : }
225 4 : papszIter++;
226 : }
227 4 : CSLDestroy(papszTokens);
228 4 : CPLFree(pszFeatures);
229 :
230 4 : if (bErr)
231 1 : return FALSE;
232 :
233 3 : pszAfterSep = pszNextSep + 1;
234 : }
235 :
236 5 : if (pszFilename == nullptr)
237 5 : pszFilename = CPLStrdup(pszAfterSep);
238 : }
239 :
240 : const char *pszOptionUseTempFile =
241 6 : CPLGetConfigOption("USE_TEMPFILE", nullptr);
242 6 : if (pszOptionUseTempFile && CPLTestBool(pszOptionUseTempFile))
243 0 : osTmpFileName = CPLGenerateTempFilename(nullptr);
244 : else
245 6 : osTmpFileName = VSIMemGenerateHiddenFilename("gpsbabel");
246 :
247 6 : bool bRet = false;
248 6 : if (IsSpecialFile(pszFilename))
249 : {
250 : /* Special file : don't try to open it */
251 0 : char **argv = GetArgv(bExplicitFeatures, bWaypoints, bRoutes, bTracks,
252 0 : pszGPSBabelDriverName, pszFilename);
253 0 : VSILFILE *tmpfp = VSIFOpenL(osTmpFileName.c_str(), "wb");
254 0 : bRet = (CPLSpawn(argv, nullptr, tmpfp, TRUE) == 0);
255 0 : VSIFCloseL(tmpfp);
256 0 : tmpfp = nullptr;
257 0 : CSLDestroy(argv);
258 0 : argv = nullptr;
259 : }
260 : else
261 : {
262 6 : VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
263 6 : if (fp == nullptr)
264 : {
265 1 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot open file %s",
266 : pszFilename);
267 1 : return FALSE;
268 : }
269 :
270 10 : char **argv = GetArgv(bExplicitFeatures, bWaypoints, bRoutes, bTracks,
271 5 : pszGPSBabelDriverName, "-");
272 :
273 5 : VSILFILE *tmpfp = VSIFOpenL(osTmpFileName.c_str(), "wb");
274 :
275 5 : CPLPushErrorHandler(CPLQuietErrorHandler);
276 5 : bRet = (CPLSpawn(argv, fp, tmpfp, TRUE) == 0);
277 5 : CPLPopErrorHandler();
278 :
279 5 : CSLDestroy(argv);
280 5 : argv = nullptr;
281 :
282 5 : CPLErr nLastErrorType = CPLGetLastErrorType();
283 5 : CPLErrorNum nLastErrorNo = CPLGetLastErrorNo();
284 5 : CPLString osLastErrorMsg = CPLGetLastErrorMsg();
285 :
286 5 : VSIFCloseL(tmpfp);
287 5 : tmpfp = nullptr;
288 :
289 5 : VSIFCloseL(fp);
290 5 : fp = nullptr;
291 :
292 5 : if (!bRet)
293 : {
294 0 : if (strstr(osLastErrorMsg.c_str(),
295 0 : "This format cannot be used in piped commands") ==
296 : nullptr)
297 : {
298 0 : CPLError(nLastErrorType, nLastErrorNo, "%s",
299 : osLastErrorMsg.c_str());
300 : }
301 : else
302 : {
303 : VSIStatBuf sStatBuf;
304 0 : if (VSIStat(pszFilename, &sStatBuf) != 0)
305 : {
306 0 : CPLError(CE_Failure, CPLE_NotSupported,
307 : "Driver %s only supports real (non virtual) "
308 : "files",
309 : pszGPSBabelDriverName);
310 0 : return FALSE;
311 : }
312 :
313 : /* Try without piping in */
314 0 : argv = GetArgv(bExplicitFeatures, bWaypoints, bRoutes, bTracks,
315 0 : pszGPSBabelDriverName, pszFilename);
316 0 : tmpfp = VSIFOpenL(osTmpFileName.c_str(), "wb");
317 0 : bRet = (CPLSpawn(argv, nullptr, tmpfp, TRUE) == 0);
318 0 : VSIFCloseL(tmpfp);
319 0 : tmpfp = nullptr;
320 :
321 0 : CSLDestroy(argv);
322 0 : argv = nullptr;
323 : }
324 : }
325 : }
326 :
327 5 : if (bRet)
328 : {
329 5 : poGPXDS = static_cast<GDALDataset *>(GDALOpenEx(
330 : osTmpFileName.c_str(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr));
331 5 : if (poGPXDS)
332 : {
333 5 : if (bWaypoints)
334 : {
335 3 : OGRLayer *poLayer = poGPXDS->GetLayerByName("waypoints");
336 3 : if (poLayer != nullptr && poLayer->GetFeatureCount() != 0)
337 3 : apoLayers[nLayers++] = poLayer;
338 : }
339 :
340 5 : if (bRoutes)
341 : {
342 3 : OGRLayer *poLayer = poGPXDS->GetLayerByName("routes");
343 3 : if (poLayer != nullptr && poLayer->GetFeatureCount() != 0)
344 1 : apoLayers[nLayers++] = poLayer;
345 3 : poLayer = poGPXDS->GetLayerByName("route_points");
346 3 : if (poLayer != nullptr && poLayer->GetFeatureCount() != 0)
347 1 : apoLayers[nLayers++] = poLayer;
348 : }
349 :
350 5 : if (bTracks)
351 : {
352 3 : OGRLayer *poLayer = poGPXDS->GetLayerByName("tracks");
353 3 : if (poLayer != nullptr && poLayer->GetFeatureCount() != 0)
354 3 : apoLayers[nLayers++] = poLayer;
355 3 : poLayer = poGPXDS->GetLayerByName("track_points");
356 3 : if (poLayer != nullptr && poLayer->GetFeatureCount() != 0)
357 1 : apoLayers[nLayers++] = poLayer;
358 : }
359 : }
360 : }
361 :
362 5 : return nLayers > 0;
363 : }
364 :
365 : /************************************************************************/
366 : /* GetLayer() */
367 : /************************************************************************/
368 :
369 3 : OGRLayer *OGRGPSBabelDataSource::GetLayer(int iLayer)
370 :
371 : {
372 3 : if (iLayer < 0 || iLayer >= nLayers)
373 0 : return nullptr;
374 :
375 3 : return apoLayers[iLayer];
376 : }
|