Coverage Report

Created: 2025-11-15 08:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}