LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/geojson - ogrgeojsonutils.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 493 553 89.2 %
Date: 2025-10-21 22:35:35 Functions: 34 34 100.0 %

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

Generated by: LCOV version 1.14