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