Coverage Report

Created: 2026-02-14 09:00

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
829k
{
38
    /* Skip UTF-8 BOM (#5630) */
39
829k
    const GByte *pabyData = reinterpret_cast<const GByte *>(pszText);
40
829k
    if (pabyData[0] == 0xEF && pabyData[1] == 0xBB && pabyData[2] == 0xBF)
41
217
        pszText += 3;
42
829k
}
43
44
/************************************************************************/
45
/*                            IsJSONObject()                            */
46
/************************************************************************/
47
48
static bool IsJSONObject(const char *pszText)
49
808k
{
50
808k
    if (nullptr == pszText)
51
0
        return false;
52
53
808k
    SkipUTF8BOM(pszText);
54
55
    /* -------------------------------------------------------------------- */
56
    /*      This is a primitive test, but we need to perform it fast.       */
57
    /* -------------------------------------------------------------------- */
58
1.35M
    while (*pszText != '\0' && isspace(static_cast<unsigned char>(*pszText)))
59
545k
        pszText++;
60
61
808k
    const char *const apszPrefix[] = {"loadGeoJSON(", "jsonp("};
62
2.42M
    for (size_t iP = 0; iP < sizeof(apszPrefix) / sizeof(apszPrefix[0]); iP++)
63
1.61M
    {
64
1.61M
        if (strncmp(pszText, apszPrefix[iP], strlen(apszPrefix[iP])) == 0)
65
909
        {
66
909
            pszText += strlen(apszPrefix[iP]);
67
909
            break;
68
909
        }
69
1.61M
    }
70
71
808k
    if (*pszText != '{')
72
717k
        return false;
73
74
90.3k
    return true;
75
808k
}
76
77
/************************************************************************/
78
/*                          GetTopLevelType()                           */
79
/************************************************************************/
80
81
static std::string GetTopLevelType(const char *pszText)
82
40.5k
{
83
40.5k
    if (!strstr(pszText, "\"type\""))
84
18.9k
        return std::string();
85
86
21.5k
    SkipUTF8BOM(pszText);
87
88
21.5k
    struct MyParser : public CPLJSonStreamingParser
89
21.5k
    {
90
21.5k
        std::string m_osLevel{};
91
21.5k
        bool m_bInTopLevelType = false;
92
21.5k
        std::string m_osTopLevelTypeValue{};
93
94
21.5k
        void StartObjectMember(std::string_view sKey) override
95
114k
        {
96
114k
            m_bInTopLevelType = false;
97
114k
            if (sKey == "type" && m_osLevel == "{")
98
15.4k
            {
99
15.4k
                m_bInTopLevelType = true;
100
15.4k
            }
101
114k
        }
102
103
21.5k
        void String(std::string_view sValue) override
104
44.5k
        {
105
44.5k
            if (m_bInTopLevelType)
106
14.3k
            {
107
14.3k
                m_osTopLevelTypeValue = sValue;
108
14.3k
                StopParsing();
109
14.3k
            }
110
44.5k
        }
111
112
21.5k
        void StartObject() override
113
51.0k
        {
114
51.0k
            m_osLevel += '{';
115
51.0k
            m_bInTopLevelType = false;
116
51.0k
        }
117
118
21.5k
        void EndObject() override
119
21.5k
        {
120
12.3k
            if (!m_osLevel.empty())
121
12.3k
                m_osLevel.pop_back();
122
12.3k
            m_bInTopLevelType = false;
123
12.3k
        }
124
125
21.5k
        void StartArray() override
126
38.2k
        {
127
38.2k
            m_osLevel += '[';
128
38.2k
            m_bInTopLevelType = false;
129
38.2k
        }
130
131
21.5k
        void EndArray() override
132
27.2k
        {
133
27.2k
            if (!m_osLevel.empty())
134
27.2k
                m_osLevel.pop_back();
135
27.2k
            m_bInTopLevelType = false;
136
27.2k
        }
137
21.5k
    };
138
139
21.5k
    MyParser oParser;
140
21.5k
    oParser.Parse(std::string_view(pszText), true);
141
21.5k
    return oParser.m_osTopLevelTypeValue;
142
40.5k
}
143
144
/************************************************************************/
145
/*                           GetCompactJSon()                           */
146
/************************************************************************/
147
148
static CPLString GetCompactJSon(const char *pszText, size_t nMaxSize)
149
76.7k
{
150
    /* Skip UTF-8 BOM (#5630) */
151
76.7k
    const GByte *pabyData = reinterpret_cast<const GByte *>(pszText);
152
76.7k
    if (pabyData[0] == 0xEF && pabyData[1] == 0xBB && pabyData[2] == 0xBF)
153
45
        pszText += 3;
154
155
76.7k
    CPLString osWithoutSpace;
156
76.7k
    bool bInString = false;
157
424M
    for (int i = 0; pszText[i] != '\0' && osWithoutSpace.size() < nMaxSize; i++)
158
424M
    {
159
424M
        if (bInString)
160
194M
        {
161
194M
            if (pszText[i] == '\\')
162
413k
            {
163
413k
                osWithoutSpace += pszText[i];
164
413k
                if (pszText[i + 1] == '\0')
165
288
                    break;
166
412k
                osWithoutSpace += pszText[i + 1];
167
412k
                i++;
168
412k
            }
169
193M
            else if (pszText[i] == '"')
170
3.44M
            {
171
3.44M
                bInString = false;
172
3.44M
                osWithoutSpace += '"';
173
3.44M
            }
174
190M
            else
175
190M
            {
176
190M
                osWithoutSpace += pszText[i];
177
190M
            }
178
194M
        }
179
230M
        else if (pszText[i] == '"')
180
3.48M
        {
181
3.48M
            bInString = true;
182
3.48M
            osWithoutSpace += '"';
183
3.48M
        }
184
226M
        else if (!isspace(static_cast<unsigned char>(pszText[i])))
185
193M
        {
186
193M
            osWithoutSpace += pszText[i];
187
193M
        }
188
424M
    }
189
76.7k
    return osWithoutSpace;
190
76.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
418k
{
200
418k
    bMightBeSequence = false;
201
418k
    bReadMoreBytes = false;
202
203
418k
    if (!IsJSONObject(pszText))
204
385k
        return false;
205
206
33.0k
    const std::string osTopLevelType = GetTopLevelType(pszText);
207
33.0k
    if (osTopLevelType == "Topology")
208
94
        return false;
209
210
32.9k
    if (poOpenInfo->IsSingleAllowedDriver(pszExpectedDriverName) &&
211
0
        GDALGetDriverByName(pszExpectedDriverName))
212
0
    {
213
0
        return true;
214
0
    }
215
216
32.9k
    if ((!poOpenInfo->papszAllowedDrivers ||
217
0
         CSLFindString(poOpenInfo->papszAllowedDrivers, "JSONFG") >= 0) &&
218
32.9k
        GDALGetDriverByName("JSONFG") && JSONFGIsObject(pszText, poOpenInfo))
219
1.29k
    {
220
1.29k
        return false;
221
1.29k
    }
222
223
31.6k
    if (osTopLevelType == "FeatureCollection")
224
3.83k
    {
225
3.83k
        return true;
226
3.83k
    }
227
228
27.7k
    const std::string osWithoutSpace = GetCompactJSon(pszText, strlen(pszText));
229
27.7k
    if (osWithoutSpace.find("{\"features\":[") == 0 &&
230
6.82k
        osWithoutSpace.find(szESRIJSonFeaturesGeometryRings) ==
231
6.82k
            std::string::npos &&
232
6.82k
        osWithoutSpace.find(szESRIJSonFeaturesAttributes) == std::string::npos)
233
6.80k
    {
234
6.80k
        return true;
235
6.80k
    }
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
20.9k
    if (osWithoutSpace.find(",\"features\":[") != std::string::npos)
244
3.07k
    {
245
3.07k
        return !ESRIJSONIsObject(pszText, poOpenInfo);
246
3.07k
    }
247
248
    // See https://github.com/OSGeo/gdal/issues/2720
249
17.9k
    if (osWithoutSpace.find("{\"coordinates\":[") == 0 ||
250
        // and https://github.com/OSGeo/gdal/issues/2787
251
17.4k
        osWithoutSpace.find("{\"geometry\":{\"coordinates\":[") == 0 ||
252
        // and https://github.com/qgis/QGIS/issues/61266
253
17.3k
        osWithoutSpace.find(
254
17.3k
            "{\"geometry\":{\"type\":\"Point\",\"coordinates\":[") == 0 ||
255
17.3k
        osWithoutSpace.find(
256
17.3k
            "{\"geometry\":{\"type\":\"LineString\",\"coordinates\":[") == 0 ||
257
17.3k
        osWithoutSpace.find(
258
17.3k
            "{\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[") == 0 ||
259
17.3k
        osWithoutSpace.find(
260
17.3k
            "{\"geometry\":{\"type\":\"MultiPoint\",\"coordinates\":[") == 0 ||
261
17.3k
        osWithoutSpace.find(
262
17.3k
            "{\"geometry\":{\"type\":\"MultiLineString\",\"coordinates\":[") ==
263
17.3k
            0 ||
264
17.3k
        osWithoutSpace.find(
265
17.3k
            "{\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[") ==
266
17.3k
            0 ||
267
17.3k
        osWithoutSpace.find("{\"geometry\":{\"type\":\"GeometryCollection\","
268
17.3k
                            "\"geometries\":[") == 0)
269
594
    {
270
594
        return true;
271
594
    }
272
273
17.3k
    if (osTopLevelType == "Feature" || osTopLevelType == "Point" ||
274
9.74k
        osTopLevelType == "LineString" || osTopLevelType == "Polygon" ||
275
9.67k
        osTopLevelType == "MultiPoint" || osTopLevelType == "MultiLineString" ||
276
9.67k
        osTopLevelType == "MultiPolygon" ||
277
9.67k
        osTopLevelType == "GeometryCollection")
278
7.75k
    {
279
7.75k
        bMightBeSequence = true;
280
7.75k
        return true;
281
7.75k
    }
282
283
    // See https://github.com/OSGeo/gdal/issues/3280
284
9.56k
    if (osWithoutSpace.find("{\"properties\":{") == 0)
285
3.20k
    {
286
3.20k
        bMightBeSequence = true;
287
3.20k
        bReadMoreBytes = true;
288
3.20k
        return false;
289
3.20k
    }
290
291
6.36k
    return false;
292
9.56k
}
293
294
static bool IsGeoJSONLikeObject(const char *pszText, GDALOpenInfo *poOpenInfo,
295
                                const char *pszExpectedDriverName)
296
3.76k
{
297
3.76k
    bool bMightBeSequence;
298
3.76k
    bool bReadMoreBytes;
299
3.76k
    return IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes,
300
3.76k
                               poOpenInfo, pszExpectedDriverName);
301
3.76k
}
302
303
/************************************************************************/
304
/*                          ESRIJSONIsObject()                          */
305
/************************************************************************/
306
307
bool ESRIJSONIsObject(const char *pszText, GDALOpenInfo *poOpenInfo)
308
119k
{
309
119k
    if (!IsJSONObject(pszText))
310
108k
        return false;
311
312
11.0k
    if (poOpenInfo->IsSingleAllowedDriver("ESRIJSON") &&
313
0
        GDALGetDriverByName("ESRIJSON"))
314
0
    {
315
0
        return true;
316
0
    }
317
318
11.0k
    if (  // ESRI Json geometry
319
11.0k
        (strstr(pszText, "\"geometryType\"") != nullptr &&
320
380
         strstr(pszText, "\"esriGeometry") != nullptr)
321
322
        // ESRI Json "FeatureCollection"
323
10.8k
        || strstr(pszText, "\"fieldAliases\"") != nullptr
324
325
        // ESRI Json "FeatureCollection"
326
10.2k
        || (strstr(pszText, "\"fields\"") != nullptr &&
327
172
            strstr(pszText, "\"esriFieldType") != nullptr))
328
831
    {
329
831
        return true;
330
831
    }
331
332
10.1k
    const std::string osWithoutSpace = GetCompactJSon(pszText, strlen(pszText));
333
10.1k
    if (osWithoutSpace.find(szESRIJSonFeaturesGeometryRings) !=
334
10.1k
            std::string::npos ||
335
10.1k
        osWithoutSpace.find(szESRIJSonFeaturesAttributes) !=
336
10.1k
            std::string::npos ||
337
10.1k
        osWithoutSpace.find("\"spatialReference\":{\"wkid\":") !=
338
10.1k
            std::string::npos)
339
214
    {
340
214
        return true;
341
214
    }
342
343
9.98k
    return false;
344
10.1k
}
345
346
/************************************************************************/
347
/*                          TopoJSONIsObject()                          */
348
/************************************************************************/
349
350
bool TopoJSONIsObject(const char *pszText, GDALOpenInfo *poOpenInfo)
351
115k
{
352
115k
    if (!IsJSONObject(pszText))
353
108k
        return false;
354
355
7.50k
    if (poOpenInfo->IsSingleAllowedDriver("TopoJSON") &&
356
0
        GDALGetDriverByName("TopoJSON"))
357
0
    {
358
0
        return true;
359
0
    }
360
361
7.50k
    return GetTopLevelType(pszText) == "Topology";
362
7.50k
}
363
364
/************************************************************************/
365
/*                   IsLikelyNewlineSequenceGeoJSON()                   */
366
/************************************************************************/
367
368
static GDALIdentifyEnum
369
IsLikelyNewlineSequenceGeoJSON(VSILFILE *fpL, const GByte *pabyHeader,
370
                               const char *pszFileContent)
371
7.30k
{
372
7.30k
    const size_t nBufferSize = 4096 * 10;
373
7.30k
    std::vector<GByte> abyBuffer;
374
7.30k
    abyBuffer.resize(nBufferSize + 1);
375
376
7.30k
    int nCurlLevel = 0;
377
7.30k
    bool bInString = false;
378
7.30k
    bool bLastIsEscape = false;
379
7.30k
    bool bFirstIter = true;
380
7.30k
    bool bEOLFound = false;
381
7.30k
    int nCountObject = 0;
382
12.5k
    while (true)
383
12.5k
    {
384
12.5k
        size_t nRead;
385
12.5k
        bool bEnd = false;
386
12.5k
        if (bFirstIter)
387
7.30k
        {
388
7.30k
            const char *pszText =
389
7.30k
                pszFileContent ? pszFileContent
390
7.30k
                               : reinterpret_cast<const char *>(pabyHeader);
391
7.30k
            assert(pszText);
392
7.30k
            nRead = std::min(strlen(pszText), nBufferSize);
393
7.30k
            memcpy(abyBuffer.data(), pszText, nRead);
394
7.30k
            bFirstIter = false;
395
7.30k
            if (fpL)
396
6.93k
            {
397
6.93k
                VSIFSeekL(fpL, static_cast<vsi_l_offset>(nRead), SEEK_SET);
398
6.93k
            }
399
7.30k
        }
400
5.21k
        else
401
5.21k
        {
402
5.21k
            nRead = VSIFReadL(abyBuffer.data(), 1, nBufferSize, fpL);
403
5.21k
            bEnd = nRead < nBufferSize;
404
5.21k
        }
405
80.2M
        for (size_t i = 0; i < nRead; i++)
406
80.2M
        {
407
80.2M
            if (nCurlLevel == 0)
408
20.9k
            {
409
20.9k
                if (abyBuffer[i] == '{')
410
12.5k
                {
411
12.5k
                    nCountObject++;
412
12.5k
                    if (nCountObject == 2)
413
5.20k
                    {
414
5.20k
                        break;
415
5.20k
                    }
416
7.30k
                    nCurlLevel++;
417
7.30k
                }
418
8.39k
                else if (nCountObject == 1 && abyBuffer[i] == '\n')
419
5.55k
                {
420
5.55k
                    bEOLFound = true;
421
5.55k
                }
422
2.84k
                else if (!isspace(static_cast<unsigned char>(abyBuffer[i])))
423
518
                {
424
518
                    return GDAL_IDENTIFY_FALSE;
425
518
                }
426
20.9k
            }
427
80.1M
            else if (bInString)
428
38.7M
            {
429
38.7M
                if (bLastIsEscape)
430
56.7k
                {
431
56.7k
                    bLastIsEscape = false;
432
56.7k
                }
433
38.6M
                else if (abyBuffer[i] == '\\')
434
56.7k
                {
435
56.7k
                    bLastIsEscape = true;
436
56.7k
                }
437
38.5M
                else if (abyBuffer[i] == '"')
438
581k
                {
439
581k
                    bInString = false;
440
581k
                }
441
38.7M
            }
442
41.4M
            else if (abyBuffer[i] == '"')
443
582k
            {
444
582k
                bInString = true;
445
582k
            }
446
40.9M
            else if (abyBuffer[i] == '{')
447
171k
            {
448
171k
                nCurlLevel++;
449
171k
            }
450
40.7M
            else if (abyBuffer[i] == '}')
451
30.6k
            {
452
30.6k
                nCurlLevel--;
453
30.6k
            }
454
80.2M
        }
455
11.9k
        if (!fpL || bEnd || nCountObject == 2)
456
6.78k
            break;
457
11.9k
    }
458
6.78k
    if (bEOLFound && nCountObject == 2)
459
5.18k
        return GDAL_IDENTIFY_TRUE;
460
1.60k
    return GDAL_IDENTIFY_UNKNOWN;
461
6.78k
}
462
463
/************************************************************************/
464
/*                        GeoJSONFileIsObject()                         */
465
/************************************************************************/
466
467
static bool GeoJSONFileIsObject(GDALOpenInfo *poOpenInfo)
468
123k
{
469
    // By default read first 6000 bytes.
470
    // 6000 was chosen as enough bytes to
471
    // enable all current tests to pass.
472
473
123k
    if (poOpenInfo->fpL == nullptr || !poOpenInfo->TryToIngest(6000))
474
38.0k
    {
475
38.0k
        return false;
476
38.0k
    }
477
478
84.9k
    bool bMightBeSequence = false;
479
84.9k
    bool bReadMoreBytes = false;
480
84.9k
    if (!IsGeoJSONLikeObject(
481
84.9k
            reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
482
84.9k
            bMightBeSequence, bReadMoreBytes, poOpenInfo, "GeoJSON"))
483
76.2k
    {
484
76.2k
        if (!(bReadMoreBytes && poOpenInfo->nHeaderBytes >= 6000 &&
485
784
              poOpenInfo->TryToIngest(1000 * 1000) &&
486
784
              !IsGeoJSONLikeObject(
487
784
                  reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
488
784
                  bMightBeSequence, bReadMoreBytes, poOpenInfo, "GeoJSON")))
489
75.4k
        {
490
75.4k
            return false;
491
75.4k
        }
492
76.2k
    }
493
494
9.52k
    return !(bMightBeSequence && IsLikelyNewlineSequenceGeoJSON(
495
3.24k
                                     poOpenInfo->fpL, poOpenInfo->pabyHeader,
496
3.24k
                                     nullptr) == GDAL_IDENTIFY_TRUE);
497
84.9k
}
498
499
/************************************************************************/
500
/*                          GeoJSONIsObject()                           */
501
/************************************************************************/
502
503
bool GeoJSONIsObject(const char *pszText, GDALOpenInfo *poOpenInfo)
504
126k
{
505
126k
    bool bMightBeSequence = false;
506
126k
    bool bReadMoreBytes = false;
507
126k
    if (!IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes,
508
126k
                             poOpenInfo, "GeoJSON"))
509
123k
    {
510
123k
        return false;
511
123k
    }
512
513
3.51k
    return !(bMightBeSequence &&
514
374
             IsLikelyNewlineSequenceGeoJSON(nullptr, nullptr, pszText) ==
515
374
                 GDAL_IDENTIFY_TRUE);
516
126k
}
517
518
/************************************************************************/
519
/*                       GeoJSONSeqFileIsObject()                       */
520
/************************************************************************/
521
522
static bool GeoJSONSeqFileIsObject(GDALOpenInfo *poOpenInfo)
523
121k
{
524
    // By default read first 6000 bytes.
525
    // 6000 was chosen as enough bytes to
526
    // enable all current tests to pass.
527
528
121k
    if (poOpenInfo->fpL == nullptr || !poOpenInfo->TryToIngest(6000))
529
37.9k
    {
530
37.9k
        return false;
531
37.9k
    }
532
533
83.5k
    const char *pszText =
534
83.5k
        reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
535
83.5k
    if (pszText[0] == '\x1e')
536
3.76k
        return IsGeoJSONLikeObject(pszText + 1, poOpenInfo, "GeoJSONSeq");
537
538
79.7k
    bool bMightBeSequence = false;
539
79.7k
    bool bReadMoreBytes = false;
540
79.7k
    if (!IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes,
541
79.7k
                             poOpenInfo, "GeoJSONSeq"))
542
73.6k
    {
543
73.6k
        if (!(bReadMoreBytes && poOpenInfo->nHeaderBytes >= 6000 &&
544
393
              poOpenInfo->TryToIngest(1000 * 1000) &&
545
393
              IsGeoJSONLikeObject(
546
393
                  reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
547
393
                  bMightBeSequence, bReadMoreBytes, poOpenInfo, "GeoJSONSeq")))
548
73.6k
        {
549
73.6k
            return false;
550
73.6k
        }
551
73.6k
    }
552
553
6.14k
    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.14k
    return bMightBeSequence && IsLikelyNewlineSequenceGeoJSON(
562
3.69k
                                   poOpenInfo->fpL, poOpenInfo->pabyHeader,
563
3.69k
                                   nullptr) == GDAL_IDENTIFY_TRUE;
564
6.14k
}
565
566
bool GeoJSONSeqIsObject(const char *pszText, GDALOpenInfo *poOpenInfo)
567
121k
{
568
121k
    if (pszText[0] == '\x1e')
569
0
        return IsGeoJSONLikeObject(pszText + 1, poOpenInfo, "GeoJSONSeq");
570
571
121k
    bool bMightBeSequence = false;
572
121k
    bool bReadMoreBytes = false;
573
121k
    if (!IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes,
574
121k
                             poOpenInfo, "GeoJSONSeq"))
575
121k
    {
576
121k
        return false;
577
121k
    }
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
79.1k
{
598
    // 6000 somewhat arbitrary. Based on other JSON-like drivers
599
79.1k
    if (poOpenInfo->fpL == nullptr || !poOpenInfo->TryToIngest(6000))
600
36.7k
    {
601
36.7k
        return false;
602
36.7k
    }
603
604
42.4k
    const char *pszText =
605
42.4k
        reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
606
42.4k
    return JSONFGIsObject(pszText, poOpenInfo);
607
79.1k
}
608
609
bool JSONFGIsObject(const char *pszText, GDALOpenInfo *poOpenInfo)
610
154k
{
611
154k
    if (!IsJSONObject(pszText))
612
116k
        return false;
613
614
38.7k
    if (poOpenInfo->IsSingleAllowedDriver("JSONFG") &&
615
0
        GDALGetDriverByName("JSONFG"))
616
0
    {
617
0
        return true;
618
0
    }
619
620
38.7k
    const std::string osWithoutSpace = GetCompactJSon(pszText, strlen(pszText));
621
622
    // In theory, conformsTo should be required, but let be lax...
623
38.7k
    if (osWithoutSpace.find("conformsTo") != std::string::npos)
624
1.00k
    {
625
1.00k
        if (osWithoutSpace.find("\"[ogc-json-fg-1-") != std::string::npos ||
626
809
            osWithoutSpace.find("\"http://www.opengis.net/spec/json-fg-1/") !=
627
809
                std::string::npos ||
628
809
            osWithoutSpace.find(
629
809
                "\"http:\\/\\/www.opengis.net\\/spec\\/json-fg-1\\/") !=
630
809
                std::string::npos)
631
194
        {
632
194
            return true;
633
194
        }
634
1.00k
    }
635
636
38.5k
    if (osWithoutSpace.find("\"place\":{\"type\":") != std::string::npos ||
637
38.2k
        osWithoutSpace.find("\"place\":{\"coordinates\":") !=
638
38.2k
            std::string::npos ||
639
38.1k
        osWithoutSpace.find("\"time\":{\"date\":") != std::string::npos ||
640
37.6k
        osWithoutSpace.find("\"time\":{\"timestamp\":") != std::string::npos ||
641
37.6k
        osWithoutSpace.find("\"time\":{\"interval\":") != std::string::npos ||
642
37.5k
        osWithoutSpace.find("\"type\":\"CircularString\"") !=
643
37.5k
            std::string::npos ||
644
37.4k
        osWithoutSpace.find("\"type\":\"CompoundCurve\"") !=
645
37.4k
            std::string::npos ||
646
37.3k
        osWithoutSpace.find("\"type\":\"CurvePolygon\"") != std::string::npos ||
647
37.2k
        osWithoutSpace.find("\"type\":\"MultiCurve\"") != std::string::npos ||
648
37.1k
        osWithoutSpace.find("\"type\":\"MultiSurface\"") != std::string::npos)
649
1.40k
    {
650
1.40k
        return true;
651
1.40k
    }
652
653
37.1k
    if (osWithoutSpace.find("\"coordRefSys\":") != std::string::npos ||
654
35.2k
        osWithoutSpace.find("\"featureType\":") != std::string::npos)
655
3.17k
    {
656
        // Check that coordRefSys and/or featureType are either at the
657
        // FeatureCollection or Feature level
658
3.17k
        struct MyParser : public CPLJSonStreamingParser
659
3.17k
        {
660
3.17k
            bool m_bFoundJSONFGFeatureType = false;
661
3.17k
            bool m_bFoundJSONFGCoordrefSys = false;
662
3.17k
            std::string m_osLevel{};
663
664
3.17k
            void StartObjectMember(std::string_view sKey) override
665
47.4k
            {
666
47.4k
                if (sKey == "featureType")
667
1.02k
                {
668
1.02k
                    m_bFoundJSONFGFeatureType =
669
1.02k
                        (m_osLevel == "{" ||   // At FeatureCollection level
670
906
                         m_osLevel == "{[{");  // At Feature level
671
1.02k
                    if (m_bFoundJSONFGFeatureType)
672
214
                        StopParsing();
673
1.02k
                }
674
46.3k
                else if (sKey == "coordRefSys")
675
2.01k
                {
676
2.01k
                    m_bFoundJSONFGCoordrefSys =
677
2.01k
                        (m_osLevel == "{" ||   // At FeatureCollection level
678
1.50k
                         m_osLevel == "{[{");  // At Feature level
679
2.01k
                    if (m_bFoundJSONFGCoordrefSys)
680
600
                        StopParsing();
681
2.01k
                }
682
47.4k
            }
683
684
3.17k
            void StartObject() override
685
21.8k
            {
686
21.8k
                m_osLevel += '{';
687
21.8k
            }
688
689
3.17k
            void EndObject() override
690
4.11k
            {
691
4.11k
                if (!m_osLevel.empty())
692
4.11k
                    m_osLevel.pop_back();
693
4.11k
            }
694
695
3.17k
            void StartArray() override
696
17.5k
            {
697
17.5k
                m_osLevel += '[';
698
17.5k
            }
699
700
3.17k
            void EndArray() override
701
9.18k
            {
702
9.18k
                if (!m_osLevel.empty())
703
9.18k
                    m_osLevel.pop_back();
704
9.18k
            }
705
3.17k
        };
706
707
3.17k
        MyParser oParser;
708
3.17k
        oParser.Parse(std::string_view(pszText), true);
709
3.17k
        if (oParser.m_bFoundJSONFGFeatureType ||
710
2.95k
            oParser.m_bFoundJSONFGCoordrefSys)
711
814
        {
712
814
            return true;
713
814
        }
714
3.17k
    }
715
716
36.3k
    return false;
717
37.1k
}
718
719
/************************************************************************/
720
/*                        IsLikelyESRIJSONURL()                         */
721
/************************************************************************/
722
723
static bool IsLikelyESRIJSONURL(const char *pszURL)
724
6.80k
{
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.80k
    return (strstr(pszURL, "f=json") != nullptr ||
728
6.75k
            strstr(pszURL, "f=pjson") != nullptr ||
729
6.70k
            strstr(pszURL, "resultRecordCount=") != nullptr) &&
730
336
           strstr(pszURL, "/items?") == nullptr;
731
6.80k
}
732
733
/************************************************************************/
734
/*                        GeoJSONGetSourceType()                        */
735
/************************************************************************/
736
737
GeoJSONSourceType GeoJSONGetSourceType(GDALOpenInfo *poOpenInfo)
738
124k
{
739
124k
    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
124k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSON:http://") ||
745
124k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSON:https://") ||
746
124k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSON:ftp://"))
747
0
    {
748
0
        srcType = eGeoJSONSourceService;
749
0
    }
750
124k
    else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "http://") ||
751
124k
             STARTS_WITH_CI(poOpenInfo->pszFilename, "https://") ||
752
124k
             STARTS_WITH_CI(poOpenInfo->pszFilename, "ftp://"))
753
1.45k
    {
754
1.45k
        if (poOpenInfo->IsSingleAllowedDriver("GeoJSON"))
755
0
        {
756
0
            return eGeoJSONSourceService;
757
0
        }
758
1.45k
        if ((strstr(poOpenInfo->pszFilename, "SERVICE=WFS") ||
759
1.43k
             strstr(poOpenInfo->pszFilename, "service=WFS") ||
760
1.42k
             strstr(poOpenInfo->pszFilename, "service=wfs")) &&
761
81
            !strstr(poOpenInfo->pszFilename, "json"))
762
70
        {
763
70
            return eGeoJSONSourceUnknown;
764
70
        }
765
1.38k
        if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename))
766
56
        {
767
56
            return eGeoJSONSourceUnknown;
768
56
        }
769
1.33k
        srcType = eGeoJSONSourceService;
770
1.33k
    }
771
123k
    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
123k
    else if (GeoJSONIsObject(poOpenInfo->pszFilename, poOpenInfo))
784
0
    {
785
0
        srcType = eGeoJSONSourceText;
786
0
    }
787
123k
    else if (GeoJSONFileIsObject(poOpenInfo))
788
7.80k
    {
789
7.80k
        srcType = eGeoJSONSourceFile;
790
7.80k
    }
791
792
124k
    return srcType;
793
124k
}
794
795
/************************************************************************/
796
/*                    ESRIJSONDriverGetSourceType()                     */
797
/************************************************************************/
798
799
GeoJSONSourceType ESRIJSONDriverGetSourceType(GDALOpenInfo *poOpenInfo)
800
117k
{
801
117k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "ESRIJSON:http://") ||
802
117k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "ESRIJSON:https://") ||
803
117k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "ESRIJSON:ftp://"))
804
10
    {
805
10
        return eGeoJSONSourceService;
806
10
    }
807
117k
    else if (STARTS_WITH(poOpenInfo->pszFilename, "http://") ||
808
116k
             STARTS_WITH(poOpenInfo->pszFilename, "https://") ||
809
116k
             STARTS_WITH(poOpenInfo->pszFilename, "ftp://"))
810
845
    {
811
845
        if (poOpenInfo->IsSingleAllowedDriver("ESRIJSON"))
812
0
        {
813
0
            return eGeoJSONSourceService;
814
0
        }
815
845
        if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename))
816
112
        {
817
112
            return eGeoJSONSourceService;
818
112
        }
819
733
        return eGeoJSONSourceUnknown;
820
845
    }
821
822
116k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "ESRIJSON:"))
823
16
    {
824
16
        VSIStatBufL sStat;
825
16
        if (VSIStatL(poOpenInfo->pszFilename + strlen("ESRIJSON:"), &sStat) ==
826
16
            0)
827
0
        {
828
0
            return eGeoJSONSourceFile;
829
0
        }
830
16
        const char *pszText = poOpenInfo->pszFilename + strlen("ESRIJSON:");
831
16
        if (ESRIJSONIsObject(pszText, poOpenInfo))
832
0
            return eGeoJSONSourceText;
833
16
        return eGeoJSONSourceUnknown;
834
16
    }
835
836
116k
    if (poOpenInfo->fpL == nullptr)
837
38.0k
    {
838
38.0k
        const char *pszText = poOpenInfo->pszFilename;
839
38.0k
        if (ESRIJSONIsObject(pszText, poOpenInfo))
840
102
            return eGeoJSONSourceText;
841
37.9k
        return eGeoJSONSourceUnknown;
842
38.0k
    }
843
844
    // By default read first 6000 bytes.
845
    // 6000 was chosen as enough bytes to
846
    // enable all current tests to pass.
847
78.2k
    if (!poOpenInfo->TryToIngest(6000))
848
0
    {
849
0
        return eGeoJSONSourceUnknown;
850
0
    }
851
852
78.2k
    if (poOpenInfo->pabyHeader != nullptr &&
853
78.2k
        ESRIJSONIsObject(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
854
78.2k
                         poOpenInfo))
855
816
    {
856
816
        return eGeoJSONSourceFile;
857
816
    }
858
77.3k
    return eGeoJSONSourceUnknown;
859
78.2k
}
860
861
/************************************************************************/
862
/*                    TopoJSONDriverGetSourceType()                     */
863
/************************************************************************/
864
865
GeoJSONSourceType TopoJSONDriverGetSourceType(GDALOpenInfo *poOpenInfo)
866
117k
{
867
117k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "TopoJSON:http://") ||
868
117k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "TopoJSON:https://") ||
869
117k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "TopoJSON:ftp://"))
870
8
    {
871
8
        return eGeoJSONSourceService;
872
8
    }
873
117k
    else if (STARTS_WITH(poOpenInfo->pszFilename, "http://") ||
874
117k
             STARTS_WITH(poOpenInfo->pszFilename, "https://") ||
875
116k
             STARTS_WITH(poOpenInfo->pszFilename, "ftp://"))
876
1.52k
    {
877
1.52k
        if (poOpenInfo->IsSingleAllowedDriver("TOPOJSON"))
878
0
        {
879
0
            return eGeoJSONSourceService;
880
0
        }
881
1.52k
        if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename))
882
56
        {
883
56
            return eGeoJSONSourceUnknown;
884
56
        }
885
1.46k
        return eGeoJSONSourceService;
886
1.52k
    }
887
888
115k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "TopoJSON:"))
889
76
    {
890
76
        VSIStatBufL sStat;
891
76
        if (VSIStatL(poOpenInfo->pszFilename + strlen("TopoJSON:"), &sStat) ==
892
76
            0)
893
0
        {
894
0
            return eGeoJSONSourceFile;
895
0
        }
896
76
        const char *pszText = poOpenInfo->pszFilename + strlen("TopoJSON:");
897
76
        if (TopoJSONIsObject(pszText, poOpenInfo))
898
0
            return eGeoJSONSourceText;
899
76
        return eGeoJSONSourceUnknown;
900
76
    }
901
902
115k
    if (poOpenInfo->fpL == nullptr)
903
37.9k
    {
904
37.9k
        const char *pszText = poOpenInfo->pszFilename;
905
37.9k
        if (TopoJSONIsObject(pszText, poOpenInfo))
906
0
            return eGeoJSONSourceText;
907
37.9k
        return eGeoJSONSourceUnknown;
908
37.9k
    }
909
910
    // By default read first 6000 bytes.
911
    // 6000 was chosen as enough bytes to
912
    // enable all current tests to pass.
913
77.8k
    if (!poOpenInfo->TryToIngest(6000))
914
0
    {
915
0
        return eGeoJSONSourceUnknown;
916
0
    }
917
918
77.8k
    if (poOpenInfo->pabyHeader != nullptr &&
919
77.8k
        TopoJSONIsObject(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
920
77.8k
                         poOpenInfo))
921
94
    {
922
94
        return eGeoJSONSourceFile;
923
94
    }
924
77.7k
    return eGeoJSONSourceUnknown;
925
77.8k
}
926
927
/************************************************************************/
928
/*                      GeoJSONSeqGetSourceType()                       */
929
/************************************************************************/
930
931
GeoJSONSourceType GeoJSONSeqGetSourceType(GDALOpenInfo *poOpenInfo)
932
123k
{
933
123k
    GeoJSONSourceType srcType = eGeoJSONSourceUnknown;
934
935
123k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSONSeq:http://") ||
936
123k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSONSeq:https://") ||
937
123k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSONSeq:ftp://"))
938
0
    {
939
0
        srcType = eGeoJSONSourceService;
940
0
    }
941
123k
    else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "http://") ||
942
123k
             STARTS_WITH_CI(poOpenInfo->pszFilename, "https://") ||
943
122k
             STARTS_WITH_CI(poOpenInfo->pszFilename, "ftp://"))
944
1.52k
    {
945
1.52k
        if (poOpenInfo->IsSingleAllowedDriver("GeoJSONSeq"))
946
0
        {
947
0
            return eGeoJSONSourceService;
948
0
        }
949
1.52k
        if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename))
950
56
        {
951
56
            return eGeoJSONSourceUnknown;
952
56
        }
953
1.47k
        srcType = eGeoJSONSourceService;
954
1.47k
    }
955
121k
    else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSONSeq:"))
956
220
    {
957
220
        VSIStatBufL sStat;
958
220
        if (VSIStatL(poOpenInfo->pszFilename + strlen("GEOJSONSeq:"), &sStat) ==
959
220
            0)
960
208
        {
961
208
            return eGeoJSONSourceFile;
962
208
        }
963
12
        const char *pszText = poOpenInfo->pszFilename + strlen("GEOJSONSeq:");
964
12
        if (GeoJSONSeqIsObject(pszText, poOpenInfo))
965
0
            return eGeoJSONSourceText;
966
12
        return eGeoJSONSourceUnknown;
967
12
    }
968
121k
    else if (GeoJSONSeqIsObject(poOpenInfo->pszFilename, poOpenInfo))
969
0
    {
970
0
        srcType = eGeoJSONSourceText;
971
0
    }
972
121k
    else if (GeoJSONSeqFileIsObject(poOpenInfo))
973
6.98k
    {
974
6.98k
        srcType = eGeoJSONSourceFile;
975
6.98k
    }
976
977
122k
    return srcType;
978
123k
}
979
980
/************************************************************************/
981
/*                     JSONFGDriverGetSourceType()                      */
982
/************************************************************************/
983
984
GeoJSONSourceType JSONFGDriverGetSourceType(GDALOpenInfo *poOpenInfo)
985
81.2k
{
986
81.2k
    GeoJSONSourceType srcType = eGeoJSONSourceUnknown;
987
988
81.2k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "JSONFG:http://") ||
989
81.2k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "JSONFG:https://") ||
990
81.1k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "JSONFG:ftp://"))
991
112
    {
992
112
        srcType = eGeoJSONSourceService;
993
112
    }
994
81.1k
    else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "http://") ||
995
81.1k
             STARTS_WITH_CI(poOpenInfo->pszFilename, "https://") ||
996
80.6k
             STARTS_WITH_CI(poOpenInfo->pszFilename, "ftp://"))
997
1.52k
    {
998
1.52k
        if (poOpenInfo->IsSingleAllowedDriver("JSONFG"))
999
0
        {
1000
0
            return eGeoJSONSourceService;
1001
0
        }
1002
1.52k
        if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename))
1003
56
        {
1004
56
            return eGeoJSONSourceUnknown;
1005
56
        }
1006
1.47k
        srcType = eGeoJSONSourceService;
1007
1.47k
    }
1008
79.6k
    else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "JSONFG:"))
1009
338
    {
1010
338
        VSIStatBufL sStat;
1011
338
        const size_t nJSONFGPrefixLen = strlen("JSONFG:");
1012
338
        if (VSIStatL(poOpenInfo->pszFilename + nJSONFGPrefixLen, &sStat) == 0)
1013
138
        {
1014
138
            return eGeoJSONSourceFile;
1015
138
        }
1016
200
        const char *pszText = poOpenInfo->pszFilename + nJSONFGPrefixLen;
1017
200
        if (JSONFGIsObject(pszText, poOpenInfo))
1018
18
            return eGeoJSONSourceText;
1019
182
        return eGeoJSONSourceUnknown;
1020
200
    }
1021
79.2k
    else if (JSONFGIsObject(poOpenInfo->pszFilename, poOpenInfo))
1022
92
    {
1023
92
        srcType = eGeoJSONSourceText;
1024
92
    }
1025
79.1k
    else if (JSONFGFileIsObject(poOpenInfo))
1026
1.01k
    {
1027
1.01k
        srcType = eGeoJSONSourceFile;
1028
1.01k
    }
1029
1030
80.8k
    return srcType;
1031
81.2k
}
1032
1033
/************************************************************************/
1034
/*                  GeoJSONStringPropertyToFieldType()                  */
1035
/************************************************************************/
1036
1037
OGRFieldType GeoJSONStringPropertyToFieldType(json_object *poObject,
1038
                                              int &nTZFlag)
1039
37.9k
{
1040
37.9k
    if (poObject == nullptr)
1041
893
    {
1042
893
        return OFTString;
1043
893
    }
1044
37.0k
    const char *pszStr = json_object_get_string(poObject);
1045
1046
37.0k
    nTZFlag = 0;
1047
37.0k
    OGRField sWrkField;
1048
37.0k
    CPLPushErrorHandler(CPLQuietErrorHandler);
1049
37.0k
    const bool bSuccess = CPL_TO_BOOL(OGRParseDate(pszStr, &sWrkField, 0));
1050
37.0k
    CPLPopErrorHandler();
1051
37.0k
    CPLErrorReset();
1052
37.0k
    if (bSuccess)
1053
448
    {
1054
448
        const bool bHasDate =
1055
448
            strchr(pszStr, '/') != nullptr || strchr(pszStr, '-') != nullptr;
1056
448
        const bool bHasTime = strchr(pszStr, ':') != nullptr;
1057
448
        nTZFlag = sWrkField.Date.TZFlag;
1058
448
        if (bHasDate && bHasTime)
1059
448
            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
448
    }
1066
36.5k
    return OFTString;
1067
37.0k
}
1068
1069
/************************************************************************/
1070
/*               GeoJSONHTTPFetchWithContentTypeHeader()                */
1071
/************************************************************************/
1072
1073
CPLHTTPResult *GeoJSONHTTPFetchWithContentTypeHeader(const char *pszURL)
1074
2.19k
{
1075
2.19k
    std::string osHeaders;
1076
2.19k
    const char *pszGDAL_HTTP_HEADERS =
1077
2.19k
        CPLGetConfigOption("GDAL_HTTP_HEADERS", nullptr);
1078
2.19k
    bool bFoundAcceptHeader = false;
1079
2.19k
    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.19k
    if (!bFoundAcceptHeader)
1115
2.19k
    {
1116
2.19k
        if (!osHeaders.empty())
1117
0
            osHeaders += "\r\n";
1118
2.19k
        osHeaders += "Accept: text/plain, application/json";
1119
2.19k
    }
1120
1121
2.19k
    CPLStringList aosOptions;
1122
2.19k
    aosOptions.SetNameValue("HEADERS", osHeaders.c_str());
1123
2.19k
    CPLHTTPResult *pResult = CPLHTTPFetch(pszURL, aosOptions.List());
1124
1125
2.19k
    if (nullptr == pResult || 0 == pResult->nDataLen ||
1126
0
        0 != CPLGetLastErrorNo())
1127
2.19k
    {
1128
2.19k
        CPLHTTPDestroyResult(pResult);
1129
2.19k
        return nullptr;
1130
2.19k
    }
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
}