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/geojson/ogrgeojsonutils.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  OpenGIS Simple Features Reference Implementation
4
 * Purpose:  Implementation of private utilities used within OGR GeoJSON Driver.
5
 * Author:   Mateusz Loskot, mateusz@loskot.net
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2007, Mateusz Loskot
9
 * Copyright (c) 2010-2013, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "ogrgeojsonutils.h"
15
#include <assert.h>
16
#include "cpl_port.h"
17
#include "cpl_conv.h"
18
#include "cpl_json_streaming_parser.h"
19
#include "ogr_geometry.h"
20
#include <json.h>  // JSON-C
21
22
#include <algorithm>
23
#include <memory>
24
25
constexpr const char szESRIJSonFeaturesGeometryRings[] =
26
    "\"features\":[{\"geometry\":{\"rings\":[";
27
28
// Cf https://github.com/OSGeo/gdal/issues/9996#issuecomment-2129845692
29
constexpr const char szESRIJSonFeaturesAttributes[] =
30
    "\"features\":[{\"attributes\":{";
31
32
/************************************************************************/
33
/*                           SkipUTF8BOM()                              */
34
/************************************************************************/
35
36
static void SkipUTF8BOM(const char *&pszText)
37
917k
{
38
    /* Skip UTF-8 BOM (#5630) */
39
917k
    const GByte *pabyData = reinterpret_cast<const GByte *>(pszText);
40
917k
    if (pabyData[0] == 0xEF && pabyData[1] == 0xBB && pabyData[2] == 0xBF)
41
181
        pszText += 3;
42
917k
}
43
44
/************************************************************************/
45
/*                           IsJSONObject()                             */
46
/************************************************************************/
47
48
static bool IsJSONObject(const char *pszText)
49
894k
{
50
894k
    if (nullptr == pszText)
51
0
        return false;
52
53
894k
    SkipUTF8BOM(pszText);
54
55
    /* -------------------------------------------------------------------- */
56
    /*      This is a primitive test, but we need to perform it fast.       */
57
    /* -------------------------------------------------------------------- */
58
1.40M
    while (*pszText != '\0' && isspace(static_cast<unsigned char>(*pszText)))
59
506k
        pszText++;
60
61
894k
    const char *const apszPrefix[] = {"loadGeoJSON(", "jsonp("};
62
2.68M
    for (size_t iP = 0; iP < sizeof(apszPrefix) / sizeof(apszPrefix[0]); iP++)
63
1.78M
    {
64
1.78M
        if (strncmp(pszText, apszPrefix[iP], strlen(apszPrefix[iP])) == 0)
65
1.02k
        {
66
1.02k
            pszText += strlen(apszPrefix[iP]);
67
1.02k
            break;
68
1.02k
        }
69
1.78M
    }
70
71
894k
    if (*pszText != '{')
72
797k
        return false;
73
74
96.5k
    return true;
75
894k
}
76
77
/************************************************************************/
78
/*                           GetTopLevelType()                          */
79
/************************************************************************/
80
81
static std::string GetTopLevelType(const char *pszText)
82
43.3k
{
83
43.3k
    if (!strstr(pszText, "\"type\""))
84
20.2k
        return std::string();
85
86
23.1k
    SkipUTF8BOM(pszText);
87
88
23.1k
    struct MyParser : public CPLJSonStreamingParser
89
23.1k
    {
90
23.1k
        std::string m_osLevel{};
91
23.1k
        bool m_bInTopLevelType = false;
92
23.1k
        std::string m_osTopLevelTypeValue{};
93
94
23.1k
        void StartObjectMember(std::string_view sKey) override
95
106k
        {
96
106k
            m_bInTopLevelType = false;
97
106k
            if (sKey == "type" && m_osLevel == "{")
98
16.6k
            {
99
16.6k
                m_bInTopLevelType = true;
100
16.6k
            }
101
106k
        }
102
103
23.1k
        void String(std::string_view sValue) override
104
44.1k
        {
105
44.1k
            if (m_bInTopLevelType)
106
15.4k
            {
107
15.4k
                m_osTopLevelTypeValue = sValue;
108
15.4k
                StopParsing();
109
15.4k
            }
110
44.1k
        }
111
112
23.1k
        void StartObject() override
113
47.8k
        {
114
47.8k
            m_osLevel += '{';
115
47.8k
            m_bInTopLevelType = false;
116
47.8k
        }
117
118
23.1k
        void EndObject() override
119
23.1k
        {
120
11.9k
            if (!m_osLevel.empty())
121
11.9k
                m_osLevel.pop_back();
122
11.9k
            m_bInTopLevelType = false;
123
11.9k
        }
124
125
23.1k
        void StartArray() override
126
35.4k
        {
127
35.4k
            m_osLevel += '[';
128
35.4k
            m_bInTopLevelType = false;
129
35.4k
        }
130
131
23.1k
        void EndArray() override
132
27.3k
        {
133
27.3k
            if (!m_osLevel.empty())
134
27.3k
                m_osLevel.pop_back();
135
27.3k
            m_bInTopLevelType = false;
136
27.3k
        }
137
23.1k
    };
138
139
23.1k
    MyParser oParser;
140
23.1k
    oParser.Parse(std::string_view(pszText), true);
141
23.1k
    return oParser.m_osTopLevelTypeValue;
142
43.3k
}
143
144
/************************************************************************/
145
/*                           GetCompactJSon()                           */
146
/************************************************************************/
147
148
static CPLString GetCompactJSon(const char *pszText, size_t nMaxSize)
149
82.7k
{
150
    /* Skip UTF-8 BOM (#5630) */
151
82.7k
    const GByte *pabyData = reinterpret_cast<const GByte *>(pszText);
152
82.7k
    if (pabyData[0] == 0xEF && pabyData[1] == 0xBB && pabyData[2] == 0xBF)
153
31
        pszText += 3;
154
155
82.7k
    CPLString osWithoutSpace;
156
82.7k
    bool bInString = false;
157
430M
    for (int i = 0; pszText[i] != '\0' && osWithoutSpace.size() < nMaxSize; i++)
158
430M
    {
159
430M
        if (bInString)
160
194M
        {
161
194M
            if (pszText[i] == '\\')
162
501k
            {
163
501k
                osWithoutSpace += pszText[i];
164
501k
                if (pszText[i + 1] == '\0')
165
357
                    break;
166
501k
                osWithoutSpace += pszText[i + 1];
167
501k
                i++;
168
501k
            }
169
193M
            else if (pszText[i] == '"')
170
3.32M
            {
171
3.32M
                bInString = false;
172
3.32M
                osWithoutSpace += '"';
173
3.32M
            }
174
190M
            else
175
190M
            {
176
190M
                osWithoutSpace += pszText[i];
177
190M
            }
178
194M
        }
179
236M
        else if (pszText[i] == '"')
180
3.36M
        {
181
3.36M
            bInString = true;
182
3.36M
            osWithoutSpace += '"';
183
3.36M
        }
184
232M
        else if (!isspace(static_cast<unsigned char>(pszText[i])))
185
198M
        {
186
198M
            osWithoutSpace += pszText[i];
187
198M
        }
188
430M
    }
189
82.7k
    return osWithoutSpace;
190
82.7k
}
191
192
/************************************************************************/
193
/*                          IsGeoJSONLikeObject()                       */
194
/************************************************************************/
195
196
static bool IsGeoJSONLikeObject(const char *pszText, bool &bMightBeSequence,
197
                                bool &bReadMoreBytes, GDALOpenInfo *poOpenInfo,
198
                                const char *pszExpectedDriverName)
199
463k
{
200
463k
    bMightBeSequence = false;
201
463k
    bReadMoreBytes = false;
202
203
463k
    if (!IsJSONObject(pszText))
204
427k
        return false;
205
206
35.3k
    const std::string osTopLevelType = GetTopLevelType(pszText);
207
35.3k
    if (osTopLevelType == "Topology")
208
84
        return false;
209
210
35.3k
    if (poOpenInfo->IsSingleAllowedDriver(pszExpectedDriverName) &&
211
0
        GDALGetDriverByName(pszExpectedDriverName))
212
0
    {
213
0
        return true;
214
0
    }
215
216
35.3k
    if ((!poOpenInfo->papszAllowedDrivers ||
217
0
         CSLFindString(poOpenInfo->papszAllowedDrivers, "JSONFG") >= 0) &&
218
35.3k
        GDALGetDriverByName("JSONFG") && JSONFGIsObject(pszText, poOpenInfo))
219
1.34k
    {
220
1.34k
        return false;
221
1.34k
    }
222
223
33.9k
    if (osTopLevelType == "FeatureCollection")
224
3.49k
    {
225
3.49k
        return true;
226
3.49k
    }
227
228
30.4k
    const std::string osWithoutSpace = GetCompactJSon(pszText, strlen(pszText));
229
30.4k
    if (osWithoutSpace.find("{\"features\":[") == 0 &&
230
7.12k
        osWithoutSpace.find(szESRIJSonFeaturesGeometryRings) ==
231
7.12k
            std::string::npos &&
232
7.12k
        osWithoutSpace.find(szESRIJSonFeaturesAttributes) == std::string::npos)
233
7.09k
    {
234
7.09k
        return true;
235
7.09k
    }
236
237
    // See
238
    // https://raw.githubusercontent.com/icepack/icepack-data/master/meshes/larsen/larsen_inflow.geojson
239
    // "{"crs":...,"features":[..."
240
    // or
241
    // https://gist.githubusercontent.com/NiklasDallmann/27e339dd78d1942d524fbcd179f9fdcf/raw/527a8319d75a9e29446a32a19e4c902213b0d668/42XR9nLAh8Poh9Xmniqh3Bs9iisNm74mYMC56v3Wfyo=_isochrones_fails.geojson
242
    // "{"bbox":...,"features":[..."
243
23.3k
    if (osWithoutSpace.find(",\"features\":[") != std::string::npos)
244
3.13k
    {
245
3.13k
        return !ESRIJSONIsObject(pszText, poOpenInfo);
246
3.13k
    }
247
248
    // See https://github.com/OSGeo/gdal/issues/2720
249
20.2k
    if (osWithoutSpace.find("{\"coordinates\":[") == 0 ||
250
        // and https://github.com/OSGeo/gdal/issues/2787
251
19.7k
        osWithoutSpace.find("{\"geometry\":{\"coordinates\":[") == 0 ||
252
        // and https://github.com/qgis/QGIS/issues/61266
253
19.7k
        osWithoutSpace.find(
254
19.7k
            "{\"geometry\":{\"type\":\"Point\",\"coordinates\":[") == 0 ||
255
19.7k
        osWithoutSpace.find(
256
19.7k
            "{\"geometry\":{\"type\":\"LineString\",\"coordinates\":[") == 0 ||
257
19.6k
        osWithoutSpace.find(
258
19.6k
            "{\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[") == 0 ||
259
19.6k
        osWithoutSpace.find(
260
19.6k
            "{\"geometry\":{\"type\":\"MultiPoint\",\"coordinates\":[") == 0 ||
261
19.6k
        osWithoutSpace.find(
262
19.6k
            "{\"geometry\":{\"type\":\"MultiLineString\",\"coordinates\":[") ==
263
19.6k
            0 ||
264
19.6k
        osWithoutSpace.find(
265
19.6k
            "{\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[") ==
266
19.6k
            0 ||
267
19.6k
        osWithoutSpace.find("{\"geometry\":{\"type\":\"GeometryCollection\","
268
19.6k
                            "\"geometries\":[") == 0)
269
638
    {
270
638
        return true;
271
638
    }
272
273
19.6k
    if (osTopLevelType == "Feature" || osTopLevelType == "Point" ||
274
10.6k
        osTopLevelType == "LineString" || osTopLevelType == "Polygon" ||
275
10.5k
        osTopLevelType == "MultiPoint" || osTopLevelType == "MultiLineString" ||
276
10.5k
        osTopLevelType == "MultiPolygon" ||
277
10.5k
        osTopLevelType == "GeometryCollection")
278
9.14k
    {
279
9.14k
        bMightBeSequence = true;
280
9.14k
        return true;
281
9.14k
    }
282
283
    // See https://github.com/OSGeo/gdal/issues/3280
284
10.4k
    if (osWithoutSpace.find("{\"properties\":{") == 0)
285
3.29k
    {
286
3.29k
        bMightBeSequence = true;
287
3.29k
        bReadMoreBytes = true;
288
3.29k
        return false;
289
3.29k
    }
290
291
7.17k
    return false;
292
10.4k
}
293
294
static bool IsGeoJSONLikeObject(const char *pszText, GDALOpenInfo *poOpenInfo,
295
                                const char *pszExpectedDriverName)
296
4.24k
{
297
4.24k
    bool bMightBeSequence;
298
4.24k
    bool bReadMoreBytes;
299
4.24k
    return IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes,
300
4.24k
                               poOpenInfo, pszExpectedDriverName);
301
4.24k
}
302
303
/************************************************************************/
304
/*                       ESRIJSONIsObject()                             */
305
/************************************************************************/
306
307
bool ESRIJSONIsObject(const char *pszText, GDALOpenInfo *poOpenInfo)
308
132k
{
309
132k
    if (!IsJSONObject(pszText))
310
120k
        return false;
311
312
11.6k
    if (poOpenInfo->IsSingleAllowedDriver("ESRIJSON") &&
313
0
        GDALGetDriverByName("ESRIJSON"))
314
0
    {
315
0
        return true;
316
0
    }
317
318
11.6k
    if (  // ESRI Json geometry
319
11.6k
        (strstr(pszText, "\"geometryType\"") != nullptr &&
320
438
         strstr(pszText, "\"esriGeometry") != nullptr)
321
322
        // ESRI Json "FeatureCollection"
323
11.4k
        || strstr(pszText, "\"fieldAliases\"") != nullptr
324
325
        // ESRI Json "FeatureCollection"
326
10.7k
        || (strstr(pszText, "\"fields\"") != nullptr &&
327
203
            strstr(pszText, "\"esriFieldType") != nullptr))
328
960
    {
329
960
        return true;
330
960
    }
331
332
10.7k
    const std::string osWithoutSpace = GetCompactJSon(pszText, strlen(pszText));
333
10.7k
    if (osWithoutSpace.find(szESRIJSonFeaturesGeometryRings) !=
334
10.7k
            std::string::npos ||
335
10.6k
        osWithoutSpace.find(szESRIJSonFeaturesAttributes) !=
336
10.6k
            std::string::npos ||
337
10.6k
        osWithoutSpace.find("\"spatialReference\":{\"wkid\":") !=
338
10.6k
            std::string::npos)
339
250
    {
340
250
        return true;
341
250
    }
342
343
10.4k
    return false;
344
10.7k
}
345
346
/************************************************************************/
347
/*                       TopoJSONIsObject()                             */
348
/************************************************************************/
349
350
bool TopoJSONIsObject(const char *pszText, GDALOpenInfo *poOpenInfo)
351
128k
{
352
128k
    if (!IsJSONObject(pszText))
353
120k
        return false;
354
355
8.00k
    if (poOpenInfo->IsSingleAllowedDriver("TopoJSON") &&
356
0
        GDALGetDriverByName("TopoJSON"))
357
0
    {
358
0
        return true;
359
0
    }
360
361
8.00k
    return GetTopLevelType(pszText) == "Topology";
362
8.00k
}
363
364
/************************************************************************/
365
/*                      IsLikelyNewlineSequenceGeoJSON()                */
366
/************************************************************************/
367
368
static GDALIdentifyEnum
369
IsLikelyNewlineSequenceGeoJSON(VSILFILE *fpL, const GByte *pabyHeader,
370
                               const char *pszFileContent)
371
8.22k
{
372
8.22k
    const size_t nBufferSize = 4096 * 10;
373
8.22k
    std::vector<GByte> abyBuffer;
374
8.22k
    abyBuffer.resize(nBufferSize + 1);
375
376
8.22k
    int nCurlLevel = 0;
377
8.22k
    bool bInString = false;
378
8.22k
    bool bLastIsEscape = false;
379
8.22k
    bool bFirstIter = true;
380
8.22k
    bool bEOLFound = false;
381
8.22k
    int nCountObject = 0;
382
13.7k
    while (true)
383
13.7k
    {
384
13.7k
        size_t nRead;
385
13.7k
        bool bEnd = false;
386
13.7k
        if (bFirstIter)
387
8.22k
        {
388
8.22k
            const char *pszText =
389
8.22k
                pszFileContent ? pszFileContent
390
8.22k
                               : reinterpret_cast<const char *>(pabyHeader);
391
8.22k
            assert(pszText);
392
8.22k
            nRead = std::min(strlen(pszText), nBufferSize);
393
8.22k
            memcpy(abyBuffer.data(), pszText, nRead);
394
8.22k
            bFirstIter = false;
395
8.22k
            if (fpL)
396
7.81k
            {
397
7.81k
                VSIFSeekL(fpL, nRead, SEEK_SET);
398
7.81k
            }
399
8.22k
        }
400
5.50k
        else
401
5.50k
        {
402
5.50k
            nRead = VSIFReadL(abyBuffer.data(), 1, nBufferSize, fpL);
403
5.50k
            bEnd = nRead < nBufferSize;
404
5.50k
        }
405
77.9M
        for (size_t i = 0; i < nRead; i++)
406
77.9M
        {
407
77.9M
            if (nCurlLevel == 0)
408
23.4k
            {
409
23.4k
                if (abyBuffer[i] == '{')
410
14.2k
                {
411
14.2k
                    nCountObject++;
412
14.2k
                    if (nCountObject == 2)
413
5.99k
                    {
414
5.99k
                        break;
415
5.99k
                    }
416
8.22k
                    nCurlLevel++;
417
8.22k
                }
418
9.18k
                else if (nCountObject == 1 && abyBuffer[i] == '\n')
419
6.60k
                {
420
6.60k
                    bEOLFound = true;
421
6.60k
                }
422
2.58k
                else if (!isspace(static_cast<unsigned char>(abyBuffer[i])))
423
602
                {
424
602
                    return GDAL_IDENTIFY_FALSE;
425
602
                }
426
23.4k
            }
427
77.9M
            else if (bInString)
428
35.9M
            {
429
35.9M
                if (bLastIsEscape)
430
68.8k
                {
431
68.8k
                    bLastIsEscape = false;
432
68.8k
                }
433
35.8M
                else if (abyBuffer[i] == '\\')
434
68.8k
                {
435
68.8k
                    bLastIsEscape = true;
436
68.8k
                }
437
35.8M
                else if (abyBuffer[i] == '"')
438
526k
                {
439
526k
                    bInString = false;
440
526k
                }
441
35.9M
            }
442
41.9M
            else if (abyBuffer[i] == '"')
443
527k
            {
444
527k
                bInString = true;
445
527k
            }
446
41.4M
            else if (abyBuffer[i] == '{')
447
193k
            {
448
193k
                nCurlLevel++;
449
193k
            }
450
41.2M
            else if (abyBuffer[i] == '}')
451
32.3k
            {
452
32.3k
                nCurlLevel--;
453
32.3k
            }
454
77.9M
        }
455
13.1k
        if (!fpL || bEnd || nCountObject == 2)
456
7.62k
            break;
457
13.1k
    }
458
7.62k
    if (bEOLFound && nCountObject == 2)
459
5.95k
        return GDAL_IDENTIFY_TRUE;
460
1.66k
    return GDAL_IDENTIFY_UNKNOWN;
461
7.62k
}
462
463
/************************************************************************/
464
/*                           GeoJSONFileIsObject()                      */
465
/************************************************************************/
466
467
static bool GeoJSONFileIsObject(GDALOpenInfo *poOpenInfo)
468
136k
{
469
    // By default read first 6000 bytes.
470
    // 6000 was chosen as enough bytes to
471
    // enable all current tests to pass.
472
473
136k
    if (poOpenInfo->fpL == nullptr || !poOpenInfo->TryToIngest(6000))
474
42.5k
    {
475
42.5k
        return false;
476
42.5k
    }
477
478
93.8k
    bool bMightBeSequence = false;
479
93.8k
    bool bReadMoreBytes = false;
480
93.8k
    if (!IsGeoJSONLikeObject(
481
93.8k
            reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
482
93.8k
            bMightBeSequence, bReadMoreBytes, poOpenInfo, "GeoJSON"))
483
84.7k
    {
484
84.7k
        if (!(bReadMoreBytes && poOpenInfo->nHeaderBytes >= 6000 &&
485
770
              poOpenInfo->TryToIngest(1000 * 1000) &&
486
770
              !IsGeoJSONLikeObject(
487
770
                  reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
488
770
                  bMightBeSequence, bReadMoreBytes, poOpenInfo, "GeoJSON")))
489
83.9k
        {
490
83.9k
            return false;
491
83.9k
        }
492
84.7k
    }
493
494
9.89k
    return !(bMightBeSequence && IsLikelyNewlineSequenceGeoJSON(
495
3.56k
                                     poOpenInfo->fpL, poOpenInfo->pabyHeader,
496
3.56k
                                     nullptr) == GDAL_IDENTIFY_TRUE);
497
93.8k
}
498
499
/************************************************************************/
500
/*                           GeoJSONIsObject()                          */
501
/************************************************************************/
502
503
bool GeoJSONIsObject(const char *pszText, GDALOpenInfo *poOpenInfo)
504
140k
{
505
140k
    bool bMightBeSequence = false;
506
140k
    bool bReadMoreBytes = false;
507
140k
    if (!IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes,
508
140k
                             poOpenInfo, "GeoJSON"))
509
136k
    {
510
136k
        return false;
511
136k
    }
512
513
3.57k
    return !(bMightBeSequence &&
514
415
             IsLikelyNewlineSequenceGeoJSON(nullptr, nullptr, pszText) ==
515
415
                 GDAL_IDENTIFY_TRUE);
516
140k
}
517
518
/************************************************************************/
519
/*                        GeoJSONSeqFileIsObject()                      */
520
/************************************************************************/
521
522
static bool GeoJSONSeqFileIsObject(GDALOpenInfo *poOpenInfo)
523
135k
{
524
    // By default read first 6000 bytes.
525
    // 6000 was chosen as enough bytes to
526
    // enable all current tests to pass.
527
528
135k
    if (poOpenInfo->fpL == nullptr || !poOpenInfo->TryToIngest(6000))
529
42.4k
    {
530
42.4k
        return false;
531
42.4k
    }
532
533
92.7k
    const char *pszText =
534
92.7k
        reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
535
92.7k
    if (pszText[0] == '\x1e')
536
4.24k
        return IsGeoJSONLikeObject(pszText + 1, poOpenInfo, "GeoJSONSeq");
537
538
88.5k
    bool bMightBeSequence = false;
539
88.5k
    bool bReadMoreBytes = false;
540
88.5k
    if (!IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes,
541
88.5k
                             poOpenInfo, "GeoJSONSeq"))
542
81.8k
    {
543
81.8k
        if (!(bReadMoreBytes && poOpenInfo->nHeaderBytes >= 6000 &&
544
386
              poOpenInfo->TryToIngest(1000 * 1000) &&
545
386
              IsGeoJSONLikeObject(
546
386
                  reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
547
386
                  bMightBeSequence, bReadMoreBytes, poOpenInfo, "GeoJSONSeq")))
548
81.8k
        {
549
81.8k
            return false;
550
81.8k
        }
551
81.8k
    }
552
553
6.68k
    if (poOpenInfo->IsSingleAllowedDriver("GeoJSONSeq") &&
554
0
        IsLikelyNewlineSequenceGeoJSON(poOpenInfo->fpL, poOpenInfo->pabyHeader,
555
0
                                       nullptr) != GDAL_IDENTIFY_FALSE &&
556
0
        GDALGetDriverByName("GeoJSONSeq"))
557
0
    {
558
0
        return true;
559
0
    }
560
561
6.68k
    return bMightBeSequence && IsLikelyNewlineSequenceGeoJSON(
562
4.25k
                                   poOpenInfo->fpL, poOpenInfo->pabyHeader,
563
4.25k
                                   nullptr) == GDAL_IDENTIFY_TRUE;
564
6.68k
}
565
566
bool GeoJSONSeqIsObject(const char *pszText, GDALOpenInfo *poOpenInfo)
567
135k
{
568
135k
    if (pszText[0] == '\x1e')
569
1
        return IsGeoJSONLikeObject(pszText + 1, poOpenInfo, "GeoJSONSeq");
570
571
135k
    bool bMightBeSequence = false;
572
135k
    bool bReadMoreBytes = false;
573
135k
    if (!IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes,
574
135k
                             poOpenInfo, "GeoJSONSeq"))
575
135k
    {
576
135k
        return false;
577
135k
    }
578
579
0
    if (poOpenInfo->IsSingleAllowedDriver("GeoJSONSeq") &&
580
0
        IsLikelyNewlineSequenceGeoJSON(nullptr, nullptr, pszText) !=
581
0
            GDAL_IDENTIFY_FALSE &&
582
0
        GDALGetDriverByName("GeoJSONSeq"))
583
0
    {
584
0
        return true;
585
0
    }
586
587
0
    return bMightBeSequence &&
588
0
           IsLikelyNewlineSequenceGeoJSON(nullptr, nullptr, pszText) ==
589
0
               GDAL_IDENTIFY_TRUE;
590
0
}
591
592
/************************************************************************/
593
/*                        JSONFGFileIsObject()                          */
594
/************************************************************************/
595
596
static bool JSONFGFileIsObject(GDALOpenInfo *poOpenInfo)
597
87.5k
{
598
    // 6000 somewhat arbitrary. Based on other JSON-like drivers
599
87.5k
    if (poOpenInfo->fpL == nullptr || !poOpenInfo->TryToIngest(6000))
600
40.9k
    {
601
40.9k
        return false;
602
40.9k
    }
603
604
46.6k
    const char *pszText =
605
46.6k
        reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
606
46.6k
    return JSONFGIsObject(pszText, poOpenInfo);
607
87.5k
}
608
609
bool JSONFGIsObject(const char *pszText, GDALOpenInfo *poOpenInfo)
610
169k
{
611
169k
    if (!IsJSONObject(pszText))
612
128k
        return false;
613
614
41.5k
    if (poOpenInfo->IsSingleAllowedDriver("JSONFG") &&
615
0
        GDALGetDriverByName("JSONFG"))
616
0
    {
617
0
        return true;
618
0
    }
619
620
41.5k
    const std::string osWithoutSpace = GetCompactJSon(pszText, strlen(pszText));
621
622
    // In theory, conformsTo should be required, but let be lax...
623
41.5k
    if (osWithoutSpace.find("conformsTo") != std::string::npos)
624
1.25k
    {
625
1.25k
        if (osWithoutSpace.find("\"[ogc-json-fg-1-") != std::string::npos ||
626
1.07k
            osWithoutSpace.find("\"http://www.opengis.net/spec/json-fg-1/") !=
627
1.07k
                std::string::npos ||
628
1.04k
            osWithoutSpace.find(
629
1.04k
                "\"http:\\/\\/www.opengis.net\\/spec\\/json-fg-1\\/") !=
630
1.04k
                std::string::npos)
631
215
        {
632
215
            return true;
633
215
        }
634
1.25k
    }
635
636
41.3k
    if (osWithoutSpace.find("\"place\":{\"type\":") != std::string::npos ||
637
40.9k
        osWithoutSpace.find("\"place\":{\"coordinates\":") !=
638
40.9k
            std::string::npos ||
639
40.8k
        osWithoutSpace.find("\"time\":{\"date\":") != std::string::npos ||
640
40.3k
        osWithoutSpace.find("\"time\":{\"timestamp\":") != std::string::npos ||
641
40.2k
        osWithoutSpace.find("\"time\":{\"interval\":") != std::string::npos ||
642
40.1k
        osWithoutSpace.find("\"type\":\"CircularString\"") !=
643
40.1k
            std::string::npos ||
644
40.0k
        osWithoutSpace.find("\"type\":\"CompoundCurve\"") !=
645
40.0k
            std::string::npos ||
646
40.0k
        osWithoutSpace.find("\"type\":\"CurvePolygon\"") != std::string::npos ||
647
39.9k
        osWithoutSpace.find("\"type\":\"MultiCurve\"") != std::string::npos ||
648
39.9k
        osWithoutSpace.find("\"type\":\"MultiSurface\"") != std::string::npos)
649
1.40k
    {
650
1.40k
        return true;
651
1.40k
    }
652
653
39.9k
    if (osWithoutSpace.find("\"coordRefSys\":") != std::string::npos ||
654
37.7k
        osWithoutSpace.find("\"featureType\":") != std::string::npos)
655
3.28k
    {
656
        // Check that coordRefSys and/or featureType are either at the
657
        // FeatureCollection or Feature level
658
3.28k
        struct MyParser : public CPLJSonStreamingParser
659
3.28k
        {
660
3.28k
            bool m_bFoundJSONFGFeatureType = false;
661
3.28k
            bool m_bFoundJSONFGCoordrefSys = false;
662
3.28k
            std::string m_osLevel{};
663
664
3.28k
            void StartObjectMember(std::string_view sKey) override
665
43.6k
            {
666
43.6k
                if (sKey == "featureType")
667
1.23k
                {
668
1.23k
                    m_bFoundJSONFGFeatureType =
669
1.23k
                        (m_osLevel == "{" ||   // At FeatureCollection level
670
1.15k
                         m_osLevel == "{[{");  // At Feature level
671
1.23k
                    if (m_bFoundJSONFGFeatureType)
672
199
                        StopParsing();
673
1.23k
                }
674
42.4k
                else if (sKey == "coordRefSys")
675
2.40k
                {
676
2.40k
                    m_bFoundJSONFGCoordrefSys =
677
2.40k
                        (m_osLevel == "{" ||   // At FeatureCollection level
678
1.80k
                         m_osLevel == "{[{");  // At Feature level
679
2.40k
                    if (m_bFoundJSONFGCoordrefSys)
680
697
                        StopParsing();
681
2.40k
                }
682
43.6k
            }
683
684
3.28k
            void StartObject() override
685
22.7k
            {
686
22.7k
                m_osLevel += '{';
687
22.7k
            }
688
689
3.28k
            void EndObject() override
690
3.53k
            {
691
3.53k
                if (!m_osLevel.empty())
692
3.53k
                    m_osLevel.pop_back();
693
3.53k
            }
694
695
3.28k
            void StartArray() override
696
16.9k
            {
697
16.9k
                m_osLevel += '[';
698
16.9k
            }
699
700
3.28k
            void EndArray() override
701
6.83k
            {
702
6.83k
                if (!m_osLevel.empty())
703
6.83k
                    m_osLevel.pop_back();
704
6.83k
            }
705
3.28k
        };
706
707
3.28k
        MyParser oParser;
708
3.28k
        oParser.Parse(std::string_view(pszText), true);
709
3.28k
        if (oParser.m_bFoundJSONFGFeatureType ||
710
3.08k
            oParser.m_bFoundJSONFGCoordrefSys)
711
896
        {
712
896
            return true;
713
896
        }
714
3.28k
    }
715
716
39.0k
    return false;
717
39.9k
}
718
719
/************************************************************************/
720
/*                           IsLikelyESRIJSONURL()                      */
721
/************************************************************************/
722
723
static bool IsLikelyESRIJSONURL(const char *pszURL)
724
6.86k
{
725
    // URLs with f=json are strong candidates for ESRI JSON services
726
    // except if they have "/items?", in which case they are likely OAPIF
727
6.86k
    return (strstr(pszURL, "f=json") != nullptr ||
728
6.82k
            strstr(pszURL, "f=pjson") != nullptr ||
729
6.80k
            strstr(pszURL, "resultRecordCount=") != nullptr) &&
730
369
           strstr(pszURL, "/items?") == nullptr;
731
6.86k
}
732
733
/************************************************************************/
734
/*                           GeoJSONGetSourceType()                     */
735
/************************************************************************/
736
737
GeoJSONSourceType GeoJSONGetSourceType(GDALOpenInfo *poOpenInfo)
738
137k
{
739
137k
    GeoJSONSourceType srcType = eGeoJSONSourceUnknown;
740
741
    // NOTE: Sometimes URL ends with .geojson token, for example
742
    //       http://example/path/2232.geojson
743
    //       It's important to test beginning of source first.
744
137k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSON:http://") ||
745
137k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSON:https://") ||
746
137k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSON:ftp://"))
747
2
    {
748
2
        srcType = eGeoJSONSourceService;
749
2
    }
750
137k
    else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "http://") ||
751
137k
             STARTS_WITH_CI(poOpenInfo->pszFilename, "https://") ||
752
137k
             STARTS_WITH_CI(poOpenInfo->pszFilename, "ftp://"))
753
1.46k
    {
754
1.46k
        if (poOpenInfo->IsSingleAllowedDriver("GeoJSON"))
755
0
        {
756
0
            return eGeoJSONSourceService;
757
0
        }
758
1.46k
        if ((strstr(poOpenInfo->pszFilename, "SERVICE=WFS") ||
759
1.45k
             strstr(poOpenInfo->pszFilename, "service=WFS") ||
760
1.43k
             strstr(poOpenInfo->pszFilename, "service=wfs")) &&
761
82
            !strstr(poOpenInfo->pszFilename, "json"))
762
78
        {
763
78
            return eGeoJSONSourceUnknown;
764
78
        }
765
1.38k
        if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename))
766
60
        {
767
60
            return eGeoJSONSourceUnknown;
768
60
        }
769
1.32k
        srcType = eGeoJSONSourceService;
770
1.32k
    }
771
136k
    else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GeoJSON:"))
772
0
    {
773
0
        VSIStatBufL sStat;
774
0
        if (VSIStatL(poOpenInfo->pszFilename + strlen("GeoJSON:"), &sStat) == 0)
775
0
        {
776
0
            return eGeoJSONSourceFile;
777
0
        }
778
0
        const char *pszText = poOpenInfo->pszFilename + strlen("GeoJSON:");
779
0
        if (GeoJSONIsObject(pszText, poOpenInfo))
780
0
            return eGeoJSONSourceText;
781
0
        return eGeoJSONSourceUnknown;
782
0
    }
783
136k
    else if (GeoJSONIsObject(poOpenInfo->pszFilename, poOpenInfo))
784
0
    {
785
0
        srcType = eGeoJSONSourceText;
786
0
    }
787
136k
    else if (GeoJSONFileIsObject(poOpenInfo))
788
7.90k
    {
789
7.90k
        srcType = eGeoJSONSourceFile;
790
7.90k
    }
791
792
137k
    return srcType;
793
137k
}
794
795
/************************************************************************/
796
/*                     ESRIJSONDriverGetSourceType()                    */
797
/************************************************************************/
798
799
GeoJSONSourceType ESRIJSONDriverGetSourceType(GDALOpenInfo *poOpenInfo)
800
130k
{
801
130k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "ESRIJSON:http://") ||
802
130k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "ESRIJSON:https://") ||
803
130k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "ESRIJSON:ftp://"))
804
12
    {
805
12
        return eGeoJSONSourceService;
806
12
    }
807
130k
    else if (STARTS_WITH(poOpenInfo->pszFilename, "http://") ||
808
129k
             STARTS_WITH(poOpenInfo->pszFilename, "https://") ||
809
129k
             STARTS_WITH(poOpenInfo->pszFilename, "ftp://"))
810
860
    {
811
860
        if (poOpenInfo->IsSingleAllowedDriver("ESRIJSON"))
812
0
        {
813
0
            return eGeoJSONSourceService;
814
0
        }
815
860
        if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename))
816
120
        {
817
120
            return eGeoJSONSourceService;
818
120
        }
819
740
        return eGeoJSONSourceUnknown;
820
860
    }
821
822
129k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "ESRIJSON:"))
823
17
    {
824
17
        VSIStatBufL sStat;
825
17
        if (VSIStatL(poOpenInfo->pszFilename + strlen("ESRIJSON:"), &sStat) ==
826
17
            0)
827
0
        {
828
0
            return eGeoJSONSourceFile;
829
0
        }
830
17
        const char *pszText = poOpenInfo->pszFilename + strlen("ESRIJSON:");
831
17
        if (ESRIJSONIsObject(pszText, poOpenInfo))
832
0
            return eGeoJSONSourceText;
833
17
        return eGeoJSONSourceUnknown;
834
17
    }
835
836
129k
    if (poOpenInfo->fpL == nullptr)
837
42.5k
    {
838
42.5k
        const char *pszText = poOpenInfo->pszFilename;
839
42.5k
        if (ESRIJSONIsObject(pszText, poOpenInfo))
840
120
            return eGeoJSONSourceText;
841
42.4k
        return eGeoJSONSourceUnknown;
842
42.5k
    }
843
844
    // By default read first 6000 bytes.
845
    // 6000 was chosen as enough bytes to
846
    // enable all current tests to pass.
847
86.6k
    if (!poOpenInfo->TryToIngest(6000))
848
0
    {
849
0
        return eGeoJSONSourceUnknown;
850
0
    }
851
852
86.6k
    if (poOpenInfo->pabyHeader != nullptr &&
853
86.6k
        ESRIJSONIsObject(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
854
86.6k
                         poOpenInfo))
855
942
    {
856
942
        return eGeoJSONSourceFile;
857
942
    }
858
85.7k
    return eGeoJSONSourceUnknown;
859
86.6k
}
860
861
/************************************************************************/
862
/*                     TopoJSONDriverGetSourceType()                    */
863
/************************************************************************/
864
865
GeoJSONSourceType TopoJSONDriverGetSourceType(GDALOpenInfo *poOpenInfo)
866
130k
{
867
130k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "TopoJSON:http://") ||
868
130k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "TopoJSON:https://") ||
869
130k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "TopoJSON:ftp://"))
870
2
    {
871
2
        return eGeoJSONSourceService;
872
2
    }
873
130k
    else if (STARTS_WITH(poOpenInfo->pszFilename, "http://") ||
874
129k
             STARTS_WITH(poOpenInfo->pszFilename, "https://") ||
875
129k
             STARTS_WITH(poOpenInfo->pszFilename, "ftp://"))
876
1.54k
    {
877
1.54k
        if (poOpenInfo->IsSingleAllowedDriver("TOPOJSON"))
878
0
        {
879
0
            return eGeoJSONSourceService;
880
0
        }
881
1.54k
        if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename))
882
60
        {
883
60
            return eGeoJSONSourceUnknown;
884
60
        }
885
1.48k
        return eGeoJSONSourceService;
886
1.54k
    }
887
888
128k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "TopoJSON:"))
889
94
    {
890
94
        VSIStatBufL sStat;
891
94
        if (VSIStatL(poOpenInfo->pszFilename + strlen("TopoJSON:"), &sStat) ==
892
94
            0)
893
0
        {
894
0
            return eGeoJSONSourceFile;
895
0
        }
896
94
        const char *pszText = poOpenInfo->pszFilename + strlen("TopoJSON:");
897
94
        if (TopoJSONIsObject(pszText, poOpenInfo))
898
0
            return eGeoJSONSourceText;
899
94
        return eGeoJSONSourceUnknown;
900
94
    }
901
902
128k
    if (poOpenInfo->fpL == nullptr)
903
42.4k
    {
904
42.4k
        const char *pszText = poOpenInfo->pszFilename;
905
42.4k
        if (TopoJSONIsObject(pszText, poOpenInfo))
906
0
            return eGeoJSONSourceText;
907
42.4k
        return eGeoJSONSourceUnknown;
908
42.4k
    }
909
910
    // By default read first 6000 bytes.
911
    // 6000 was chosen as enough bytes to
912
    // enable all current tests to pass.
913
86.1k
    if (!poOpenInfo->TryToIngest(6000))
914
0
    {
915
0
        return eGeoJSONSourceUnknown;
916
0
    }
917
918
86.1k
    if (poOpenInfo->pabyHeader != nullptr &&
919
86.1k
        TopoJSONIsObject(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
920
86.1k
                         poOpenInfo))
921
84
    {
922
84
        return eGeoJSONSourceFile;
923
84
    }
924
86.0k
    return eGeoJSONSourceUnknown;
925
86.1k
}
926
927
/************************************************************************/
928
/*                          GeoJSONSeqGetSourceType()                   */
929
/************************************************************************/
930
931
GeoJSONSourceType GeoJSONSeqGetSourceType(GDALOpenInfo *poOpenInfo)
932
136k
{
933
136k
    GeoJSONSourceType srcType = eGeoJSONSourceUnknown;
934
935
136k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSONSeq:http://") ||
936
136k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSONSeq:https://") ||
937
136k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSONSeq:ftp://"))
938
0
    {
939
0
        srcType = eGeoJSONSourceService;
940
0
    }
941
136k
    else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "http://") ||
942
136k
             STARTS_WITH_CI(poOpenInfo->pszFilename, "https://") ||
943
136k
             STARTS_WITH_CI(poOpenInfo->pszFilename, "ftp://"))
944
1.54k
    {
945
1.54k
        if (poOpenInfo->IsSingleAllowedDriver("GeoJSONSeq"))
946
0
        {
947
0
            return eGeoJSONSourceService;
948
0
        }
949
1.54k
        if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename))
950
60
        {
951
60
            return eGeoJSONSourceUnknown;
952
60
        }
953
1.48k
        srcType = eGeoJSONSourceService;
954
1.48k
    }
955
135k
    else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSONSeq:"))
956
174
    {
957
174
        VSIStatBufL sStat;
958
174
        if (VSIStatL(poOpenInfo->pszFilename + strlen("GEOJSONSeq:"), &sStat) ==
959
174
            0)
960
156
        {
961
156
            return eGeoJSONSourceFile;
962
156
        }
963
18
        const char *pszText = poOpenInfo->pszFilename + strlen("GEOJSONSeq:");
964
18
        if (GeoJSONSeqIsObject(pszText, poOpenInfo))
965
0
            return eGeoJSONSourceText;
966
18
        return eGeoJSONSourceUnknown;
967
18
    }
968
135k
    else if (GeoJSONSeqIsObject(poOpenInfo->pszFilename, poOpenInfo))
969
0
    {
970
0
        srcType = eGeoJSONSourceText;
971
0
    }
972
135k
    else if (GeoJSONSeqFileIsObject(poOpenInfo))
973
7.91k
    {
974
7.91k
        srcType = eGeoJSONSourceFile;
975
7.91k
    }
976
977
136k
    return srcType;
978
136k
}
979
980
/************************************************************************/
981
/*                      JSONFGDriverGetSourceType()                     */
982
/************************************************************************/
983
984
GeoJSONSourceType JSONFGDriverGetSourceType(GDALOpenInfo *poOpenInfo)
985
89.7k
{
986
89.7k
    GeoJSONSourceType srcType = eGeoJSONSourceUnknown;
987
988
89.7k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "JSONFG:http://") ||
989
89.7k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "JSONFG:https://") ||
990
89.6k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "JSONFG:ftp://"))
991
96
    {
992
96
        srcType = eGeoJSONSourceService;
993
96
    }
994
89.6k
    else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "http://") ||
995
89.6k
             STARTS_WITH_CI(poOpenInfo->pszFilename, "https://") ||
996
89.0k
             STARTS_WITH_CI(poOpenInfo->pszFilename, "ftp://"))
997
1.54k
    {
998
1.54k
        if (poOpenInfo->IsSingleAllowedDriver("JSONFG"))
999
0
        {
1000
0
            return eGeoJSONSourceService;
1001
0
        }
1002
1.54k
        if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename))
1003
60
        {
1004
60
            return eGeoJSONSourceUnknown;
1005
60
        }
1006
1.48k
        srcType = eGeoJSONSourceService;
1007
1.48k
    }
1008
88.0k
    else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "JSONFG:"))
1009
358
    {
1010
358
        VSIStatBufL sStat;
1011
358
        const size_t nJSONFGPrefixLen = strlen("JSONFG:");
1012
358
        if (VSIStatL(poOpenInfo->pszFilename + nJSONFGPrefixLen, &sStat) == 0)
1013
166
        {
1014
166
            return eGeoJSONSourceFile;
1015
166
        }
1016
192
        const char *pszText = poOpenInfo->pszFilename + nJSONFGPrefixLen;
1017
192
        if (JSONFGIsObject(pszText, poOpenInfo))
1018
26
            return eGeoJSONSourceText;
1019
166
        return eGeoJSONSourceUnknown;
1020
192
    }
1021
87.7k
    else if (JSONFGIsObject(poOpenInfo->pszFilename, poOpenInfo))
1022
174
    {
1023
174
        srcType = eGeoJSONSourceText;
1024
174
    }
1025
87.5k
    else if (JSONFGFileIsObject(poOpenInfo))
1026
968
    {
1027
968
        srcType = eGeoJSONSourceFile;
1028
968
    }
1029
1030
89.3k
    return srcType;
1031
89.7k
}
1032
1033
/************************************************************************/
1034
/*                        GeoJSONStringPropertyToFieldType()            */
1035
/************************************************************************/
1036
1037
OGRFieldType GeoJSONStringPropertyToFieldType(json_object *poObject,
1038
                                              int &nTZFlag)
1039
48.1k
{
1040
48.1k
    if (poObject == nullptr)
1041
978
    {
1042
978
        return OFTString;
1043
978
    }
1044
47.1k
    const char *pszStr = json_object_get_string(poObject);
1045
1046
47.1k
    nTZFlag = 0;
1047
47.1k
    OGRField sWrkField;
1048
47.1k
    CPLPushErrorHandler(CPLQuietErrorHandler);
1049
47.1k
    const bool bSuccess = CPL_TO_BOOL(OGRParseDate(pszStr, &sWrkField, 0));
1050
47.1k
    CPLPopErrorHandler();
1051
47.1k
    CPLErrorReset();
1052
47.1k
    if (bSuccess)
1053
502
    {
1054
502
        const bool bHasDate =
1055
502
            strchr(pszStr, '/') != nullptr || strchr(pszStr, '-') != nullptr;
1056
502
        const bool bHasTime = strchr(pszStr, ':') != nullptr;
1057
502
        nTZFlag = sWrkField.Date.TZFlag;
1058
502
        if (bHasDate && bHasTime)
1059
502
            return OFTDateTime;
1060
0
        else if (bHasDate)
1061
0
            return OFTDate;
1062
0
        else
1063
0
            return OFTTime;
1064
        // TODO: What if both are false?
1065
502
    }
1066
46.6k
    return OFTString;
1067
47.1k
}
1068
1069
/************************************************************************/
1070
/*                   GeoJSONHTTPFetchWithContentTypeHeader()            */
1071
/************************************************************************/
1072
1073
CPLHTTPResult *GeoJSONHTTPFetchWithContentTypeHeader(const char *pszURL)
1074
2.21k
{
1075
2.21k
    std::string osHeaders;
1076
2.21k
    const char *pszGDAL_HTTP_HEADERS =
1077
2.21k
        CPLGetConfigOption("GDAL_HTTP_HEADERS", nullptr);
1078
2.21k
    bool bFoundAcceptHeader = false;
1079
2.21k
    if (pszGDAL_HTTP_HEADERS)
1080
0
    {
1081
0
        bool bHeadersDone = false;
1082
        // Compatibility hack for "HEADERS=Accept: text/plain, application/json"
1083
0
        if (strstr(pszGDAL_HTTP_HEADERS, "\r\n") == nullptr)
1084
0
        {
1085
0
            const char *pszComma = strchr(pszGDAL_HTTP_HEADERS, ',');
1086
0
            if (pszComma != nullptr && strchr(pszComma, ':') == nullptr)
1087
0
            {
1088
0
                osHeaders = pszGDAL_HTTP_HEADERS;
1089
0
                bFoundAcceptHeader =
1090
0
                    STARTS_WITH_CI(pszGDAL_HTTP_HEADERS, "Accept:");
1091
0
                bHeadersDone = true;
1092
0
            }
1093
0
        }
1094
0
        if (!bHeadersDone)
1095
0
        {
1096
            // We accept both raw headers with \r\n as a separator, or as
1097
            // a comma separated list of foo: bar values.
1098
0
            const CPLStringList aosTokens(
1099
0
                strstr(pszGDAL_HTTP_HEADERS, "\r\n")
1100
0
                    ? CSLTokenizeString2(pszGDAL_HTTP_HEADERS, "\r\n", 0)
1101
0
                    : CSLTokenizeString2(pszGDAL_HTTP_HEADERS, ",",
1102
0
                                         CSLT_HONOURSTRINGS));
1103
0
            for (int i = 0; i < aosTokens.size(); ++i)
1104
0
            {
1105
0
                if (!osHeaders.empty())
1106
0
                    osHeaders += "\r\n";
1107
0
                if (!bFoundAcceptHeader)
1108
0
                    bFoundAcceptHeader =
1109
0
                        STARTS_WITH_CI(aosTokens[i], "Accept:");
1110
0
                osHeaders += aosTokens[i];
1111
0
            }
1112
0
        }
1113
0
    }
1114
2.21k
    if (!bFoundAcceptHeader)
1115
2.21k
    {
1116
2.21k
        if (!osHeaders.empty())
1117
0
            osHeaders += "\r\n";
1118
2.21k
        osHeaders += "Accept: text/plain, application/json";
1119
2.21k
    }
1120
1121
2.21k
    CPLStringList aosOptions;
1122
2.21k
    aosOptions.SetNameValue("HEADERS", osHeaders.c_str());
1123
2.21k
    CPLHTTPResult *pResult = CPLHTTPFetch(pszURL, aosOptions.List());
1124
1125
2.21k
    if (nullptr == pResult || 0 == pResult->nDataLen ||
1126
3
        0 != CPLGetLastErrorNo())
1127
2.21k
    {
1128
2.21k
        CPLHTTPDestroyResult(pResult);
1129
2.21k
        return nullptr;
1130
2.21k
    }
1131
1132
0
    if (0 != pResult->nStatus)
1133
0
    {
1134
0
        CPLError(CE_Failure, CPLE_AppDefined, "Curl reports error: %d: %s",
1135
0
                 pResult->nStatus, pResult->pszErrBuf);
1136
0
        CPLHTTPDestroyResult(pResult);
1137
0
        return nullptr;
1138
0
    }
1139
1140
0
    return pResult;
1141
0
}