Coverage Report

Created: 2025-06-13 06:18

/src/gdal/ogr/ogrsf_frmts/shape/ogrshapedriver.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  OpenGIS Simple Features Reference Implementation
4
 * Purpose:  Implements OGRShapeDriver class.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 1999,  Les Technologies SoftMap Inc.
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "ogrshape.h"
14
15
#include <cstring>
16
17
#include "cpl_conv.h"
18
#include "cpl_error.h"
19
#include "cpl_port.h"
20
#include "cpl_string.h"
21
#include "cpl_vsi.h"
22
#include "gdal.h"
23
#include "gdal_priv.h"
24
#include "ogrsf_frmts.h"
25
26
/************************************************************************/
27
/*                              Identify()                              */
28
/************************************************************************/
29
30
static int OGRShapeDriverIdentify(GDALOpenInfo *poOpenInfo)
31
0
{
32
    // Files not ending with .shp, .shx, .dbf, .shz or .shp.zip are not
33
    // handled by this driver.
34
0
    if (!poOpenInfo->bStatOK)
35
0
        return FALSE;
36
0
    if (poOpenInfo->bIsDirectory)
37
0
    {
38
0
        if (STARTS_WITH(poOpenInfo->pszFilename, "/vsizip/") &&
39
0
            (strstr(poOpenInfo->pszFilename, ".shp.zip") ||
40
0
             strstr(poOpenInfo->pszFilename, ".SHP.ZIP")))
41
0
        {
42
0
            return TRUE;
43
0
        }
44
45
0
        return GDAL_IDENTIFY_UNKNOWN;  // Unsure.
46
0
    }
47
0
    if (poOpenInfo->fpL == nullptr)
48
0
    {
49
0
        return FALSE;
50
0
    }
51
0
    const std::string &osExt = poOpenInfo->osExtension;
52
0
    if (EQUAL(osExt.c_str(), "SHP") || EQUAL(osExt.c_str(), "SHX"))
53
0
    {
54
0
        return poOpenInfo->nHeaderBytes >= 4 &&
55
0
               (memcmp(poOpenInfo->pabyHeader, "\x00\x00\x27\x0A", 4) == 0 ||
56
0
                memcmp(poOpenInfo->pabyHeader, "\x00\x00\x27\x0D", 4) == 0);
57
0
    }
58
0
    if (EQUAL(osExt.c_str(), "DBF"))
59
0
    {
60
0
        if (poOpenInfo->nHeaderBytes < 32)
61
0
            return FALSE;
62
0
        const GByte *pabyBuf = poOpenInfo->pabyHeader;
63
0
        const unsigned int nHeadLen = pabyBuf[8] + pabyBuf[9] * 256;
64
0
        const unsigned int nRecordLength = pabyBuf[10] + pabyBuf[11] * 256;
65
0
        if (nHeadLen < 32)
66
0
            return FALSE;
67
        // The header length of some .dbf files can be a non-multiple of 32
68
        // See https://trac.osgeo.org/gdal/ticket/6035
69
        // Hopefully there are not so many .dbf files around that are not real
70
        // DBFs
71
        // if( (nHeadLen % 32) != 0 && (nHeadLen % 32) != 1 )
72
        //     return FALSE;
73
0
        const unsigned int nFields = (nHeadLen - 32) / 32;
74
0
        if (nRecordLength < nFields)
75
0
            return FALSE;
76
0
        return TRUE;
77
0
    }
78
0
    if (EQUAL(osExt.c_str(), "shz") ||
79
0
        (EQUAL(osExt.c_str(), "zip") &&
80
0
         (CPLString(poOpenInfo->pszFilename).endsWith(".shp.zip") ||
81
0
          CPLString(poOpenInfo->pszFilename).endsWith(".SHP.ZIP"))))
82
0
    {
83
0
        return poOpenInfo->nHeaderBytes >= 4 &&
84
0
               memcmp(poOpenInfo->pabyHeader, "\x50\x4B\x03\x04", 4) == 0;
85
0
    }
86
0
#ifdef DEBUG
87
    // For AFL, so that .cur_input is detected as the archive filename.
88
0
    if (!STARTS_WITH(poOpenInfo->pszFilename, "/vsitar/") &&
89
0
        EQUAL(CPLGetFilename(poOpenInfo->pszFilename), ".cur_input"))
90
0
    {
91
0
        return GDAL_IDENTIFY_UNKNOWN;
92
0
    }
93
0
#endif
94
0
    return FALSE;
95
0
}
96
97
/************************************************************************/
98
/*                                Open()                                */
99
/************************************************************************/
100
101
static GDALDataset *OGRShapeDriverOpen(GDALOpenInfo *poOpenInfo)
102
103
0
{
104
0
    if (OGRShapeDriverIdentify(poOpenInfo) == FALSE)
105
0
        return nullptr;
106
107
0
#ifdef DEBUG
108
    // For AFL, so that .cur_input is detected as the archive filename.
109
0
    if (poOpenInfo->fpL != nullptr &&
110
0
        !STARTS_WITH(poOpenInfo->pszFilename, "/vsitar/") &&
111
0
        EQUAL(CPLGetFilename(poOpenInfo->pszFilename), ".cur_input"))
112
0
    {
113
0
        GDALOpenInfo oOpenInfo(
114
0
            (CPLString("/vsitar/") + poOpenInfo->pszFilename).c_str(),
115
0
            poOpenInfo->nOpenFlags);
116
0
        oOpenInfo.papszOpenOptions = poOpenInfo->papszOpenOptions;
117
0
        return OGRShapeDriverOpen(&oOpenInfo);
118
0
    }
119
0
#endif
120
121
0
    CPLString osExt(CPLGetExtensionSafe(poOpenInfo->pszFilename));
122
0
    if (!STARTS_WITH(poOpenInfo->pszFilename, "/vsizip/") &&
123
0
        (EQUAL(osExt, "shz") ||
124
0
         (EQUAL(osExt, "zip") &&
125
0
          (CPLString(poOpenInfo->pszFilename).endsWith(".shp.zip") ||
126
0
           CPLString(poOpenInfo->pszFilename).endsWith(".SHP.ZIP")))))
127
0
    {
128
0
        GDALOpenInfo oOpenInfo(
129
0
            (CPLString("/vsizip/{") + poOpenInfo->pszFilename + '}').c_str(),
130
0
            GA_ReadOnly);
131
0
        if (OGRShapeDriverIdentify(&oOpenInfo) == FALSE)
132
0
            return nullptr;
133
0
        oOpenInfo.eAccess = poOpenInfo->eAccess;
134
0
        auto poDS = std::make_unique<OGRShapeDataSource>();
135
136
0
        if (!poDS->OpenZip(&oOpenInfo, poOpenInfo->pszFilename))
137
0
        {
138
0
            return nullptr;
139
0
        }
140
141
0
        return poDS.release();
142
0
    }
143
144
0
    auto poDS = std::make_unique<OGRShapeDataSource>();
145
146
0
    if (!poDS->Open(poOpenInfo, true))
147
0
    {
148
0
        return nullptr;
149
0
    }
150
151
0
    return poDS.release();
152
0
}
153
154
/************************************************************************/
155
/*                               Create()                               */
156
/************************************************************************/
157
158
static GDALDataset *OGRShapeDriverCreate(const char *pszName, int /* nBands */,
159
                                         int /* nXSize */, int /* nYSize */,
160
                                         GDALDataType /* eDT */,
161
                                         char ** /* papszOptions */)
162
0
{
163
0
    bool bSingleNewFile = false;
164
0
    const CPLString osExt(CPLGetExtensionSafe(pszName));
165
166
    /* -------------------------------------------------------------------- */
167
    /*      Is the target a valid existing directory?                       */
168
    /* -------------------------------------------------------------------- */
169
0
    VSIStatBufL stat;
170
0
    if (VSIStatL(pszName, &stat) == 0)
171
0
    {
172
0
        if (!VSI_ISDIR(stat.st_mode))
173
0
        {
174
0
            CPLError(CE_Failure, CPLE_AppDefined, "%s is not a directory.",
175
0
                     pszName);
176
177
0
            return nullptr;
178
0
        }
179
0
    }
180
181
    /* -------------------------------------------------------------------- */
182
    /*      Does it end in the extension .shp indicating the user likely    */
183
    /*      wants to create a single file set?                              */
184
    /* -------------------------------------------------------------------- */
185
0
    else if (EQUAL(osExt, "shp") || EQUAL(osExt, "dbf"))
186
0
    {
187
0
        bSingleNewFile = true;
188
0
    }
189
190
0
    else if (EQUAL(osExt, "shz") ||
191
0
             (EQUAL(osExt, "zip") && (CPLString(pszName).endsWith(".shp.zip") ||
192
0
                                      CPLString(pszName).endsWith(".SHP.ZIP"))))
193
0
    {
194
0
        auto poDS = std::make_unique<OGRShapeDataSource>();
195
196
0
        if (!poDS->CreateZip(pszName))
197
0
        {
198
0
            return nullptr;
199
0
        }
200
201
0
        return poDS.release();
202
0
    }
203
204
    /* -------------------------------------------------------------------- */
205
    /*      Otherwise try to create a new directory.                        */
206
    /* -------------------------------------------------------------------- */
207
0
    else
208
0
    {
209
0
        if (VSIMkdir(pszName, 0755) != 0)
210
0
        {
211
0
            CPLError(CE_Failure, CPLE_AppDefined,
212
0
                     "Failed to create directory %s "
213
0
                     "for shapefile datastore.",
214
0
                     pszName);
215
216
0
            return nullptr;
217
0
        }
218
0
    }
219
220
    /* -------------------------------------------------------------------- */
221
    /*      Return a new OGRDataSource()                                    */
222
    /* -------------------------------------------------------------------- */
223
0
    auto poDS = std::make_unique<OGRShapeDataSource>();
224
225
0
    GDALOpenInfo oOpenInfo(pszName, GA_Update);
226
0
    if (!poDS->Open(&oOpenInfo, false, bSingleNewFile))
227
0
    {
228
0
        return nullptr;
229
0
    }
230
231
0
    return poDS.release();
232
0
}
233
234
/************************************************************************/
235
/*                           Delete()                                   */
236
/************************************************************************/
237
238
static CPLErr OGRShapeDriverDelete(const char *pszDataSource)
239
240
0
{
241
0
    VSIStatBufL sStatBuf;
242
243
0
    if (VSIStatL(pszDataSource, &sStatBuf) != 0)
244
0
    {
245
0
        CPLError(CE_Failure, CPLE_AppDefined,
246
0
                 "%s does not appear to be a file or directory.",
247
0
                 pszDataSource);
248
249
0
        return CE_Failure;
250
0
    }
251
252
0
    const CPLString osExt(CPLGetExtensionSafe(pszDataSource));
253
0
    if (VSI_ISREG(sStatBuf.st_mode) &&
254
0
        (EQUAL(osExt, "shz") ||
255
0
         (EQUAL(osExt, "zip") &&
256
0
          (CPLString(pszDataSource).endsWith(".shp.zip") ||
257
0
           CPLString(pszDataSource).endsWith(".SHP.ZIP")))))
258
0
    {
259
0
        VSIUnlink(pszDataSource);
260
0
        return CE_None;
261
0
    }
262
263
0
    const char *const *papszExtensions =
264
0
        OGRShapeDataSource::GetExtensionsForDeletion();
265
266
0
    if (VSI_ISREG(sStatBuf.st_mode) &&
267
0
        (EQUAL(osExt, "shp") || EQUAL(osExt, "shx") || EQUAL(osExt, "dbf")))
268
0
    {
269
0
        for (int iExt = 0; papszExtensions[iExt] != nullptr; iExt++)
270
0
        {
271
0
            const std::string osFile =
272
0
                CPLResetExtensionSafe(pszDataSource, papszExtensions[iExt]);
273
0
            if (VSIStatL(osFile.c_str(), &sStatBuf) == 0)
274
0
                VSIUnlink(osFile.c_str());
275
0
        }
276
0
    }
277
0
    else if (VSI_ISDIR(sStatBuf.st_mode))
278
0
    {
279
0
        const CPLStringList aosDirEntries(VSIReadDir(pszDataSource));
280
281
0
        for (const char *pszEntry : cpl::Iterate(aosDirEntries))
282
0
        {
283
0
            if (CSLFindString(papszExtensions,
284
0
                              CPLGetExtensionSafe(pszEntry).c_str()) != -1)
285
0
            {
286
0
                VSIUnlink(CPLFormFilenameSafe(pszDataSource, pszEntry, nullptr)
287
0
                              .c_str());
288
0
            }
289
0
        }
290
291
0
        VSIRmdir(pszDataSource);
292
0
    }
293
294
0
    return CE_None;
295
0
}
296
297
/************************************************************************/
298
/*                          RegisterOGRShape()                          */
299
/************************************************************************/
300
301
void RegisterOGRShape()
302
303
0
{
304
0
    if (GDALGetDriverByName("ESRI Shapefile") != nullptr)
305
0
        return;
306
307
0
    GDALDriver *poDriver = new GDALDriver();
308
309
0
    poDriver->SetDescription("ESRI Shapefile");
310
0
    poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
311
0
    poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
312
0
    poDriver->SetMetadataItem(GDAL_DCAP_DELETE_LAYER, "YES");
313
0
    poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
314
0
    poDriver->SetMetadataItem(GDAL_DCAP_DELETE_FIELD, "YES");
315
0
    poDriver->SetMetadataItem(GDAL_DCAP_REORDER_FIELDS, "YES");
316
0
    poDriver->SetMetadataItem(GDAL_DCAP_MEASURED_GEOMETRIES, "YES");
317
0
    poDriver->SetMetadataItem(GDAL_DCAP_Z_GEOMETRIES, "YES");
318
0
    poDriver->SetMetadataItem(GDAL_DMD_GEOMETRY_FLAGS,
319
0
                              "EquatesMultiAndSingleLineStringDuringWrite "
320
0
                              "EquatesMultiAndSinglePolygonDuringWrite");
321
322
0
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "ESRI Shapefile");
323
0
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "shp");
324
0
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "shp dbf shz shp.zip");
325
0
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
326
0
                              "drivers/vector/shapefile.html");
327
0
    poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE");
328
0
    poDriver->SetMetadataItem(GDAL_DMD_NUMERIC_FIELD_WIDTH_INCLUDES_SIGN,
329
0
                              "YES");
330
0
    poDriver->SetMetadataItem(
331
0
        GDAL_DMD_NUMERIC_FIELD_WIDTH_INCLUDES_DECIMAL_SEPARATOR, "YES");
332
333
0
    poDriver->SetMetadataItem(
334
0
        GDAL_DMD_OPENOPTIONLIST,
335
0
        "<OpenOptionList>"
336
0
        "  <Option name='ENCODING' type='string' description='to override the "
337
0
        "encoding interpretation of the DBF with any encoding supported by "
338
0
        "CPLRecode or to \"\" to avoid any recoding'/>"
339
0
        "  <Option name='DBF_DATE_LAST_UPDATE' type='string' "
340
0
        "description='Modification date to write in DBF header with YYYY-MM-DD "
341
0
        "format'/>"
342
0
        "  <Option name='ADJUST_TYPE' type='boolean' description='Whether to "
343
0
        "read whole .dbf to adjust Real->Integer/Integer64 or "
344
0
        "Integer64->Integer field types if possible' default='NO'/>"
345
0
        "  <Option name='ADJUST_GEOM_TYPE' type='string-select' "
346
0
        "description='Whether and how to adjust layer geometry type from "
347
0
        "actual shapes' default='FIRST_SHAPE'>"
348
0
        "    <Value>NO</Value>"
349
0
        "    <Value>FIRST_SHAPE</Value>"
350
0
        "    <Value>ALL_SHAPES</Value>"
351
0
        "  </Option>"
352
0
        "  <Option name='AUTO_REPACK' type='boolean' description='Whether the "
353
0
        "shapefile should be automatically repacked when needed' "
354
0
        "default='YES'/>"
355
0
        "  <Option name='DBF_EOF_CHAR' type='boolean' description='Whether to "
356
0
        "write the 0x1A end-of-file character in DBF files' default='YES'/>"
357
0
        "</OpenOptionList>");
358
359
0
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST,
360
0
                              "<CreationOptionList/>");
361
0
    poDriver->SetMetadataItem(
362
0
        GDAL_DS_LAYER_CREATIONOPTIONLIST,
363
0
        "<LayerCreationOptionList>"
364
0
        "  <Option name='SHPT' type='string-select' description='type of "
365
0
        "shape' default='automatically detected'>"
366
0
        "    <Value>POINT</Value>"
367
0
        "    <Value>ARC</Value>"
368
0
        "    <Value>POLYGON</Value>"
369
0
        "    <Value>MULTIPOINT</Value>"
370
0
        "    <Value>POINTZ</Value>"
371
0
        "    <Value>ARCZ</Value>"
372
0
        "    <Value>POLYGONZ</Value>"
373
0
        "    <Value>MULTIPOINTZ</Value>"
374
0
        "    <Value>POINTM</Value>"
375
0
        "    <Value>ARCM</Value>"
376
0
        "    <Value>POLYGONM</Value>"
377
0
        "    <Value>MULTIPOINTM</Value>"
378
0
        "    <Value>POINTZM</Value>"
379
0
        "    <Value>ARCZM</Value>"
380
0
        "    <Value>POLYGONZM</Value>"
381
0
        "    <Value>MULTIPOINTZM</Value>"
382
0
        "    <Value>MULTIPATCH</Value>"
383
0
        "    <Value>NONE</Value>"
384
0
        "    <Value>NULL</Value>"
385
0
        "  </Option>"
386
0
        "  <Option name='2GB_LIMIT' type='boolean' description='Restrict .shp "
387
0
        "and .dbf to 2GB' default='NO'/>"
388
0
        "  <Option name='ENCODING' type='string' description='DBF encoding' "
389
0
        "default='LDID/87'/>"
390
0
        "  <Option name='RESIZE' type='boolean' description='To resize fields "
391
0
        "to their optimal size.' default='NO'/>"
392
0
        "  <Option name='SPATIAL_INDEX' type='boolean' description='To create "
393
0
        "a spatial index.' default='NO'/>"
394
0
        "  <Option name='DBF_DATE_LAST_UPDATE' type='string' "
395
0
        "description='Modification date to write in DBF header with YYYY-MM-DD "
396
0
        "format'/>"
397
0
        "  <Option name='AUTO_REPACK' type='boolean' description='Whether the "
398
0
        "shapefile should be automatically repacked when needed' "
399
0
        "default='YES'/>"
400
0
        "  <Option name='DBF_EOF_CHAR' type='boolean' description='Whether to "
401
0
        "write the 0x1A end-of-file character in DBF files' default='YES'/>"
402
0
        "</LayerCreationOptionList>");
403
404
0
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES,
405
0
                              "Integer Integer64 Real String Date");
406
0
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES, "Boolean");
407
0
    poDriver->SetMetadataItem(GDAL_DMD_MAX_STRING_LENGTH, "254");
408
0
    poDriver->SetMetadataItem(GDAL_DMD_CREATION_FIELD_DEFN_FLAGS,
409
0
                              "WidthPrecision");
410
0
    poDriver->SetMetadataItem(GDAL_DMD_ALTER_FIELD_DEFN_FLAGS,
411
0
                              "Name Type WidthPrecision");
412
0
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
413
0
    poDriver->SetMetadataItem(GDAL_DCAP_RENAME_LAYERS, "YES");
414
415
0
    poDriver->SetMetadataItem(GDAL_DMD_ALTER_GEOM_FIELD_DEFN_FLAGS, "SRS");
416
417
0
    poDriver->SetMetadataItem(GDAL_DCAP_UPDATE, "YES");
418
0
    poDriver->SetMetadataItem(GDAL_DMD_UPDATE_ITEMS, "Features");
419
420
0
    poDriver->pfnOpen = OGRShapeDriverOpen;
421
0
    poDriver->pfnIdentify = OGRShapeDriverIdentify;
422
0
    poDriver->pfnCreate = OGRShapeDriverCreate;
423
0
    poDriver->pfnDelete = OGRShapeDriverDelete;
424
425
0
    GetGDALDriverManager()->RegisterDriver(poDriver);
426
0
}