LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/wfs - ogrwfsdatasource.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1051 1248 84.2 %
Date: 2024-05-03 15:49:35 Functions: 38 39 97.4 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  WFS Translator
       4             :  * Purpose:  Implements OGRWFSDataSource class
       5             :  * Author:   Even Rouault, even dot rouault at spatialys.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2010-2013, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * Permission is hereby granted, free of charge, to any person obtaining a
      11             :  * copy of this software and associated documentation files (the "Software"),
      12             :  * to deal in the Software without restriction, including without limitation
      13             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      14             :  * and/or sell copies of the Software, and to permit persons to whom the
      15             :  * Software is furnished to do so, subject to the following conditions:
      16             :  *
      17             :  * The above copyright notice and this permission notice shall be included
      18             :  * in all copies or substantial portions of the Software.
      19             :  *
      20             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      21             :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      22             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      23             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      24             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      25             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      26             :  * DEALINGS IN THE SOFTWARE.
      27             :  ****************************************************************************/
      28             : 
      29             : #include "cpl_port.h"
      30             : #include "ogr_wfs.h"
      31             : #include "ogr_api.h"
      32             : #include "cpl_minixml.h"
      33             : #include "cpl_http.h"
      34             : #include "gmlutils.h"
      35             : #include "parsexsd.h"
      36             : #include "ogr_swq.h"
      37             : #include "ogr_p.h"
      38             : 
      39             : #include <algorithm>
      40             : 
      41             : constexpr int DEFAULT_BASE_START_INDEX = 0;
      42             : constexpr int DEFAULT_PAGE_SIZE = 100;
      43             : 
      44             : typedef struct
      45             : {
      46             :     const char *pszPath;
      47             :     const char *pszMDI;
      48             : } MetadataItem;
      49             : 
      50             : static const MetadataItem asMetadata[] = {
      51             :     {"Service.Title", "TITLE"},                        /*1.0 */
      52             :     {"ServiceIdentification.Title", "TITLE"},          /* 1.1 or 2.0 */
      53             :     {"Service.Abstract", "ABSTRACT"},                  /* 1.0 */
      54             :     {"ServiceIdentification.Abstract", "ABSTRACT"},    /* 1.1 or 2.0 */
      55             :     {"ServiceProvider.ProviderName", "PROVIDER_NAME"}, /* 1.1 or 2.0 */
      56             : };
      57             : 
      58             : /************************************************************************/
      59             : /*                            WFSFindNode()                             */
      60             : /************************************************************************/
      61             : 
      62         153 : const CPLXMLNode *WFSFindNode(const CPLXMLNode *psXML, const char *pszRootName)
      63             : {
      64         153 :     const CPLXMLNode *psIter = psXML;
      65          10 :     do
      66             :     {
      67         163 :         if (psIter->eType == CXT_Element)
      68             :         {
      69         155 :             const char *pszNodeName = psIter->pszValue;
      70         155 :             const char *pszSep = strchr(pszNodeName, ':');
      71         155 :             if (pszSep)
      72         117 :                 pszNodeName = pszSep + 1;
      73         155 :             if (EQUAL(pszNodeName, pszRootName))
      74             :             {
      75         138 :                 return psIter;
      76             :             }
      77             :         }
      78          25 :         psIter = psIter->psNext;
      79          25 :     } while (psIter);
      80             : 
      81          15 :     psIter = psXML->psChild;
      82          28 :     while (psIter)
      83             :     {
      84          21 :         if (psIter->eType == CXT_Element)
      85             :         {
      86          19 :             const char *pszNodeName = psIter->pszValue;
      87          19 :             const char *pszSep = strchr(pszNodeName, ':');
      88          19 :             if (pszSep)
      89           1 :                 pszNodeName = pszSep + 1;
      90          19 :             if (EQUAL(pszNodeName, pszRootName))
      91             :             {
      92           8 :                 return psIter;
      93             :             }
      94             :         }
      95          13 :         psIter = psIter->psNext;
      96             :     }
      97           7 :     return nullptr;
      98             : }
      99             : 
     100             : /************************************************************************/
     101             : /*                       OGRWFSWrappedResultLayer                       */
     102             : /************************************************************************/
     103             : 
     104             : class OGRWFSWrappedResultLayer final : public OGRLayer
     105             : {
     106             :     GDALDataset *poDS;
     107             :     OGRLayer *poLayer;
     108             : 
     109             :   public:
     110           4 :     OGRWFSWrappedResultLayer(GDALDataset *poDSIn, OGRLayer *poLayerIn)
     111           4 :         : poDS(poDSIn), poLayer(poLayerIn)
     112             :     {
     113           4 :     }
     114             : 
     115           8 :     ~OGRWFSWrappedResultLayer()
     116           4 :     {
     117           4 :         delete poDS;
     118           8 :     }
     119             : 
     120           2 :     virtual void ResetReading() override
     121             :     {
     122           2 :         poLayer->ResetReading();
     123           2 :     }
     124             : 
     125           4 :     virtual OGRFeature *GetNextFeature() override
     126             :     {
     127           4 :         return poLayer->GetNextFeature();
     128             :     }
     129             : 
     130           2 :     virtual OGRErr SetNextByIndex(GIntBig nIndex) override
     131             :     {
     132           2 :         return poLayer->SetNextByIndex(nIndex);
     133             :     }
     134             : 
     135           2 :     virtual OGRFeature *GetFeature(GIntBig nFID) override
     136             :     {
     137           2 :         return poLayer->GetFeature(nFID);
     138             :     }
     139             : 
     140           2 :     virtual OGRFeatureDefn *GetLayerDefn() override
     141             :     {
     142           2 :         return poLayer->GetLayerDefn();
     143             :     }
     144             : 
     145           2 :     virtual GIntBig GetFeatureCount(int bForce = TRUE) override
     146             :     {
     147           2 :         return poLayer->GetFeatureCount(bForce);
     148             :     }
     149             : 
     150           2 :     virtual int TestCapability(const char *pszCap) override
     151             :     {
     152           2 :         return poLayer->TestCapability(pszCap);
     153             :     }
     154             : };
     155             : 
     156             : /************************************************************************/
     157             : /*                          OGRWFSDataSource()                          */
     158             : /************************************************************************/
     159             : 
     160         141 : OGRWFSDataSource::OGRWFSDataSource()
     161             :     : pszName(nullptr), bRewriteFile(false), psFileXML(nullptr),
     162             :       papoLayers(nullptr), nLayers(0), bUpdate(false),
     163             :       bGetFeatureSupportHits(false), bNeedNAMESPACE(false),
     164             :       bHasMinOperators(false), bHasNullCheck(false),
     165             :       // Advertized by deegree but not implemented.
     166             :       bPropertyIsNotEqualToSupported(true),
     167             :       bUseFeatureId(false),  // CubeWerx doesn't like GmlObjectId.
     168             :       bGmlObjectIdNeedsGMLPrefix(false), bRequiresEnvelopeSpatialFilter(false),
     169             :       bTransactionSupport(false), papszIdGenMethods(nullptr), bUseHttp10(false),
     170             :       papszHttpOptions(nullptr),
     171             :       bPagingAllowed(
     172         141 :           CPLTestBool(CPLGetConfigOption("OGR_WFS_PAGING_ALLOWED", "OFF"))),
     173             :       nPageSize(DEFAULT_PAGE_SIZE), nBaseStartIndex(DEFAULT_BASE_START_INDEX),
     174             :       bStandardJoinsWFS2(false),
     175         141 :       bLoadMultipleLayerDefn(CPLTestBool(
     176             :           CPLGetConfigOption("OGR_WFS_LOAD_MULTIPLE_LAYER_DEFN", "TRUE"))),
     177             :       poLayerMetadataDS(nullptr), poLayerMetadataLayer(nullptr),
     178             :       poLayerGetCapabilitiesDS(nullptr), poLayerGetCapabilitiesLayer(nullptr),
     179             :       bKeepLayerNamePrefix(false), bEmptyAsNull(true),
     180         423 :       bInvertAxisOrderIfLatLong(true), bExposeGMLId(true)
     181             : {
     182         141 :     if (bPagingAllowed)
     183             :     {
     184             :         const char *pszOption =
     185           2 :             CPLGetConfigOption("OGR_WFS_PAGE_SIZE", nullptr);
     186           2 :         if (pszOption != nullptr)
     187             :         {
     188           2 :             nPageSize = atoi(pszOption);
     189           2 :             if (nPageSize <= 0)
     190           0 :                 nPageSize = DEFAULT_PAGE_SIZE;
     191             :         }
     192             : 
     193           2 :         pszOption = CPLGetConfigOption("OGR_WFS_BASE_START_INDEX", nullptr);
     194           2 :         if (pszOption != nullptr)
     195           0 :             nBaseStartIndex = atoi(pszOption);
     196             :     }
     197             : 
     198         141 :     apszGetCapabilities[0] = nullptr;
     199         141 :     apszGetCapabilities[1] = nullptr;
     200         141 : }
     201             : 
     202             : /************************************************************************/
     203             : /*                         ~OGRWFSDataSource()                          */
     204             : /************************************************************************/
     205             : 
     206         282 : OGRWFSDataSource::~OGRWFSDataSource()
     207             : 
     208             : {
     209         141 :     if (psFileXML)
     210             :     {
     211           6 :         if (bRewriteFile)
     212             :         {
     213           1 :             CPLSerializeXMLTreeToFile(psFileXML, pszName);
     214             :         }
     215             : 
     216           6 :         CPLDestroyXMLNode(psFileXML);
     217             :     }
     218             : 
     219         346 :     for (int i = 0; i < nLayers; i++)
     220         205 :         delete papoLayers[i];
     221         141 :     CPLFree(papoLayers);
     222             : 
     223         141 :     if (!osLayerMetadataTmpFileName.empty())
     224           1 :         VSIUnlink(osLayerMetadataTmpFileName);
     225         141 :     delete poLayerMetadataDS;
     226         141 :     delete poLayerGetCapabilitiesDS;
     227             : 
     228         141 :     CPLFree(pszName);
     229         141 :     CSLDestroy(papszIdGenMethods);
     230         141 :     CSLDestroy(papszHttpOptions);
     231         282 : }
     232             : 
     233             : /************************************************************************/
     234             : /*                           TestCapability()                           */
     235             : /************************************************************************/
     236             : 
     237           0 : int OGRWFSDataSource::TestCapability(CPL_UNUSED const char *pszCap)
     238             : {
     239           0 :     return FALSE;
     240             : }
     241             : 
     242             : /************************************************************************/
     243             : /*                              GetLayer()                              */
     244             : /************************************************************************/
     245             : 
     246         121 : OGRLayer *OGRWFSDataSource::GetLayer(int iLayer)
     247             : 
     248             : {
     249         121 :     if (iLayer < 0 || iLayer >= nLayers)
     250           0 :         return nullptr;
     251             :     else
     252         121 :         return papoLayers[iLayer];
     253             : }
     254             : 
     255             : /************************************************************************/
     256             : /*                          GetLayerByName()                            */
     257             : /************************************************************************/
     258             : 
     259         474 : OGRLayer *OGRWFSDataSource::GetLayerByName(const char *pszNameIn)
     260             : {
     261         474 :     if (!pszNameIn)
     262           0 :         return nullptr;
     263             : 
     264         474 :     if (EQUAL(pszNameIn, "WFSLayerMetadata"))
     265             :     {
     266           1 :         if (!osLayerMetadataTmpFileName.empty())
     267           0 :             return poLayerMetadataLayer;
     268             : 
     269             :         osLayerMetadataTmpFileName =
     270           1 :             CPLSPrintf("/vsimem/tempwfs_%p/WFSLayerMetadata.csv", this);
     271           1 :         osLayerMetadataCSV = "layer_name,title,abstract\n" + osLayerMetadataCSV;
     272             : 
     273           1 :         VSIFCloseL(VSIFileFromMemBuffer(osLayerMetadataTmpFileName,
     274           1 :                                         (GByte *)osLayerMetadataCSV.c_str(),
     275           1 :                                         osLayerMetadataCSV.size(), FALSE));
     276           1 :         poLayerMetadataDS = (OGRDataSource *)OGROpen(osLayerMetadataTmpFileName,
     277             :                                                      FALSE, nullptr);
     278           1 :         if (poLayerMetadataDS)
     279           1 :             poLayerMetadataLayer = poLayerMetadataDS->GetLayer(0);
     280           1 :         return poLayerMetadataLayer;
     281             :     }
     282         473 :     else if (EQUAL(pszNameIn, "WFSGetCapabilities"))
     283             :     {
     284           1 :         if (poLayerGetCapabilitiesLayer != nullptr)
     285           0 :             return poLayerGetCapabilitiesLayer;
     286             : 
     287             :         GDALDriver *poMEMDrv =
     288           1 :             OGRSFDriverRegistrar::GetRegistrar()->GetDriverByName("Memory");
     289           1 :         if (poMEMDrv == nullptr)
     290             :         {
     291           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     292             :                      "Cannot load 'Memory' driver");
     293           0 :             return nullptr;
     294             :         }
     295             : 
     296           1 :         poLayerGetCapabilitiesDS = poMEMDrv->Create("WFSGetCapabilities", 0, 0,
     297             :                                                     0, GDT_Unknown, nullptr);
     298           1 :         poLayerGetCapabilitiesLayer = poLayerGetCapabilitiesDS->CreateLayer(
     299             :             "WFSGetCapabilities", nullptr, wkbNone, nullptr);
     300           1 :         OGRFieldDefn oFDefn("content", OFTString);
     301           1 :         poLayerGetCapabilitiesLayer->CreateField(&oFDefn);
     302             :         OGRFeature *poFeature =
     303           1 :             new OGRFeature(poLayerGetCapabilitiesLayer->GetLayerDefn());
     304           1 :         poFeature->SetField(0, osGetCapabilities);
     305           1 :         CPL_IGNORE_RET_VAL(
     306           1 :             poLayerGetCapabilitiesLayer->CreateFeature(poFeature));
     307           1 :         delete poFeature;
     308             : 
     309           1 :         return poLayerGetCapabilitiesLayer;
     310             :     }
     311             : 
     312         472 :     int nIndex = GetLayerIndex(pszNameIn);
     313         472 :     if (nIndex < 0)
     314           6 :         return nullptr;
     315             :     else
     316         466 :         return papoLayers[nIndex];
     317             : }
     318             : 
     319             : /************************************************************************/
     320             : /*                        GetMetadataDomainList()                       */
     321             : /************************************************************************/
     322             : 
     323           1 : char **OGRWFSDataSource::GetMetadataDomainList()
     324             : {
     325           1 :     return BuildMetadataDomainList(GDALDataset::GetMetadataDomainList(), TRUE,
     326           1 :                                    "", "xml:capabilities", nullptr);
     327             : }
     328             : 
     329             : /************************************************************************/
     330             : /*                           GetMetadata()                              */
     331             : /************************************************************************/
     332             : 
     333           3 : char **OGRWFSDataSource::GetMetadata(const char *pszDomain)
     334             : {
     335           3 :     if (pszDomain != nullptr && EQUAL(pszDomain, "xml:capabilities"))
     336             :     {
     337           2 :         apszGetCapabilities[0] = osGetCapabilities.c_str();
     338           2 :         apszGetCapabilities[1] = nullptr;
     339           2 :         return (char **)apszGetCapabilities;
     340             :     }
     341           1 :     return GDALDataset::GetMetadata(pszDomain);
     342             : }
     343             : 
     344             : /************************************************************************/
     345             : /*                          GetLayerIndex()                             */
     346             : /************************************************************************/
     347             : 
     348         480 : int OGRWFSDataSource::GetLayerIndex(const char *pszNameIn)
     349             : {
     350         480 :     bool bHasFoundLayerWithColon = false;
     351             : 
     352             :     /* first a case sensitive check */
     353         765 :     for (int i = 0; i < nLayers; i++)
     354             :     {
     355         711 :         OGRWFSLayer *poLayer = papoLayers[i];
     356             : 
     357         711 :         if (strcmp(pszNameIn, poLayer->GetName()) == 0)
     358         426 :             return i;
     359             : 
     360         285 :         bHasFoundLayerWithColon |= strchr(poLayer->GetName(), ':') != nullptr;
     361             :     }
     362             : 
     363             :     /* then case insensitive */
     364         158 :     for (int i = 0; i < nLayers; i++)
     365             :     {
     366         104 :         OGRWFSLayer *poLayer = papoLayers[i];
     367             : 
     368         104 :         if (EQUAL(pszNameIn, poLayer->GetName()))
     369           0 :             return i;
     370             :     }
     371             : 
     372             :     /* now try looking after the colon character */
     373          54 :     if (!bKeepLayerNamePrefix && bHasFoundLayerWithColon &&
     374          48 :         strchr(pszNameIn, ':') == nullptr)
     375             :     {
     376          72 :         for (int i = 0; i < nLayers; i++)
     377             :         {
     378          72 :             OGRWFSLayer *poLayer = papoLayers[i];
     379             : 
     380          72 :             const char *pszAfterColon = strchr(poLayer->GetName(), ':');
     381          72 :             if (pszAfterColon && EQUAL(pszNameIn, pszAfterColon + 1))
     382          48 :                 return i;
     383             :         }
     384             :     }
     385             : 
     386           6 :     return -1;
     387             : }
     388             : 
     389             : /************************************************************************/
     390             : /*                    FindSubStringInsensitive()                        */
     391             : /************************************************************************/
     392             : 
     393         439 : const char *FindSubStringInsensitive(const char *pszStr, const char *pszSubStr)
     394             : {
     395         439 :     size_t nSubStrPos = CPLString(pszStr).ifind(pszSubStr);
     396         439 :     if (nSubStrPos == std::string::npos)
     397         435 :         return nullptr;
     398           4 :     return pszStr + nSubStrPos;
     399             : }
     400             : 
     401             : /************************************************************************/
     402             : /*                 DetectIfGetFeatureSupportHits()                      */
     403             : /************************************************************************/
     404             : 
     405          92 : static bool DetectIfGetFeatureSupportHits(const CPLXMLNode *psRoot)
     406             : {
     407             :     const CPLXMLNode *psOperationsMetadata =
     408          92 :         CPLGetXMLNode(psRoot, "OperationsMetadata");
     409          92 :     if (!psOperationsMetadata)
     410             :     {
     411          59 :         CPLDebug("WFS", "Could not find <OperationsMetadata>");
     412          59 :         return false;
     413             :     }
     414             : 
     415          33 :     const CPLXMLNode *psChild = psOperationsMetadata->psChild;
     416          58 :     while (psChild)
     417             :     {
     418         123 :         if (psChild->eType == CXT_Element &&
     419          82 :             strcmp(psChild->pszValue, "Operation") == 0 &&
     420          41 :             strcmp(CPLGetXMLValue(psChild, "name", ""), "GetFeature") == 0)
     421             :         {
     422          16 :             break;
     423             :         }
     424          25 :         psChild = psChild->psNext;
     425             :     }
     426          33 :     if (!psChild)
     427             :     {
     428          17 :         CPLDebug("WFS", "Could not find <Operation name=\"GetFeature\">");
     429          17 :         return false;
     430             :     }
     431             : 
     432          16 :     psChild = psChild->psChild;
     433          38 :     while (psChild)
     434             :     {
     435          98 :         if (psChild->eType == CXT_Element &&
     436          54 :             strcmp(psChild->pszValue, "Parameter") == 0 &&
     437          16 :             strcmp(CPLGetXMLValue(psChild, "name", ""), "resultType") == 0)
     438             :         {
     439          16 :             break;
     440             :         }
     441          22 :         psChild = psChild->psNext;
     442             :     }
     443          16 :     if (!psChild)
     444             :     {
     445           0 :         CPLDebug("WFS", "Could not find <Parameter name=\"resultType\">");
     446           0 :         return false;
     447             :     }
     448             : 
     449          16 :     psChild = psChild->psChild;
     450          48 :     while (psChild)
     451             :     {
     452          47 :         if (psChild->eType == CXT_Element &&
     453          31 :             strcmp(psChild->pszValue, "Value") == 0)
     454             :         {
     455          31 :             CPLXMLNode *psChild2 = psChild->psChild;
     456          47 :             while (psChild2)
     457             :             {
     458          31 :                 if (psChild2->eType == CXT_Text &&
     459          31 :                     strcmp(psChild2->pszValue, "hits") == 0)
     460             :                 {
     461          15 :                     CPLDebug("WFS", "GetFeature operation supports hits");
     462          15 :                     return true;
     463             :                 }
     464          16 :                 psChild2 = psChild2->psNext;
     465             :             }
     466             :         }
     467          32 :         psChild = psChild->psNext;
     468             :     }
     469             : 
     470           1 :     return false;
     471             : }
     472             : 
     473             : /************************************************************************/
     474             : /*                   DetectRequiresEnvelopeSpatialFilter()              */
     475             : /************************************************************************/
     476             : 
     477         131 : bool OGRWFSDataSource::DetectRequiresEnvelopeSpatialFilter(
     478             :     const CPLXMLNode *psRoot)
     479             : {
     480             :     // This is a heuristic to detect Deegree 3 servers, such as
     481             :     // http://deegree3-demo.deegree.org:80/deegree-utah-demo/services that are
     482             :     // very GML3 strict, and don't like <gml:Box> in a <Filter><BBOX> request,
     483             :     // but requires instead <gml:Envelope>, but some servers (such as MapServer)
     484             :     // don't like <gml:Envelope> so we are obliged to detect the kind of server.
     485             : 
     486         131 :     const CPLXMLNode *psGeometryOperands = CPLGetXMLNode(
     487             :         psRoot, "Filter_Capabilities.Spatial_Capabilities.GeometryOperands");
     488         131 :     if (!psGeometryOperands)
     489             :     {
     490          91 :         return false;
     491             :     }
     492             : 
     493          40 :     int nCount = 0;
     494          40 :     const CPLXMLNode *psChild = psGeometryOperands->psChild;
     495         200 :     while (psChild)
     496             :     {
     497         160 :         nCount++;
     498         160 :         psChild = psChild->psNext;
     499             :     }
     500             :     // Magic number... Might be fragile.
     501          40 :     return nCount == 19;
     502             : }
     503             : 
     504             : /************************************************************************/
     505             : /*                       GetPostTransactionURL()                        */
     506             : /************************************************************************/
     507             : 
     508          62 : CPLString OGRWFSDataSource::GetPostTransactionURL()
     509             : {
     510          62 :     if (!osPostTransactionURL.empty())
     511          62 :         return osPostTransactionURL;
     512             : 
     513           0 :     osPostTransactionURL = osBaseURL;
     514           0 :     const char *pszPostTransactionURL = osPostTransactionURL.c_str();
     515           0 :     const char *pszEsperluet = strchr(pszPostTransactionURL, '?');
     516           0 :     if (pszEsperluet)
     517           0 :         osPostTransactionURL.resize(pszEsperluet - pszPostTransactionURL);
     518             : 
     519           0 :     return osPostTransactionURL;
     520             : }
     521             : 
     522             : /************************************************************************/
     523             : /*                    DetectTransactionSupport()                        */
     524             : /************************************************************************/
     525             : 
     526         132 : bool OGRWFSDataSource::DetectTransactionSupport(const CPLXMLNode *psRoot)
     527             : {
     528             :     const CPLXMLNode *psTransactionWFS100 =
     529         132 :         CPLGetXMLNode(psRoot, "Capability.Request.Transaction");
     530         132 :     if (psTransactionWFS100)
     531             :     {
     532             :         const CPLXMLNode *psPostURL =
     533           0 :             CPLGetXMLNode(psTransactionWFS100, "DCPType.HTTP.Post");
     534           0 :         if (psPostURL)
     535             :         {
     536             :             const char *pszPOSTURL =
     537           0 :                 CPLGetXMLValue(psPostURL, "onlineResource", nullptr);
     538           0 :             if (pszPOSTURL)
     539             :             {
     540           0 :                 osPostTransactionURL = pszPOSTURL;
     541             :             }
     542             :         }
     543             : 
     544           0 :         bTransactionSupport = true;
     545           0 :         return true;
     546             :     }
     547             : 
     548             :     const CPLXMLNode *psOperationsMetadata =
     549         132 :         CPLGetXMLNode(psRoot, "OperationsMetadata");
     550         132 :     if (!psOperationsMetadata)
     551             :     {
     552          66 :         return false;
     553             :     }
     554             : 
     555          66 :     const CPLXMLNode *psChild = psOperationsMetadata->psChild;
     556         183 :     while (psChild)
     557             :     {
     558         408 :         if (psChild->eType == CXT_Element &&
     559         216 :             strcmp(psChild->pszValue, "Operation") == 0 &&
     560          80 :             strcmp(CPLGetXMLValue(psChild, "name", ""), "Transaction") == 0)
     561             :         {
     562          19 :             break;
     563             :         }
     564         117 :         psChild = psChild->psNext;
     565             :     }
     566          66 :     if (!psChild)
     567             :     {
     568          47 :         CPLDebug("WFS", "No transaction support");
     569          47 :         return false;
     570             :     }
     571             : 
     572          19 :     bTransactionSupport = true;
     573          19 :     CPLDebug("WFS", "Transaction support !");
     574             : 
     575          19 :     const CPLXMLNode *psPostURL = CPLGetXMLNode(psChild, "DCP.HTTP.Post");
     576          19 :     if (psPostURL)
     577             :     {
     578          17 :         const char *pszPOSTURL = CPLGetXMLValue(psPostURL, "href", nullptr);
     579          17 :         if (pszPOSTURL)
     580          17 :             osPostTransactionURL = pszPOSTURL;
     581             :     }
     582             : 
     583          19 :     psChild = psChild->psChild;
     584          62 :     while (psChild)
     585             :     {
     586         119 :         if (psChild->eType == CXT_Element &&
     587          52 :             strcmp(psChild->pszValue, "Parameter") == 0 &&
     588           6 :             strcmp(CPLGetXMLValue(psChild, "name", ""), "idgen") == 0)
     589             :         {
     590           3 :             break;
     591             :         }
     592          43 :         psChild = psChild->psNext;
     593             :     }
     594          19 :     if (!psChild)
     595             :     {
     596          16 :         papszIdGenMethods = CSLAddString(nullptr, "GenerateNew");
     597          16 :         return true;
     598             :     }
     599             : 
     600           3 :     psChild = psChild->psChild;
     601          13 :     while (psChild)
     602             :     {
     603          10 :         if (psChild->eType == CXT_Element &&
     604           7 :             strcmp(psChild->pszValue, "Value") == 0)
     605             :         {
     606           7 :             const CPLXMLNode *psChild2 = psChild->psChild;
     607          14 :             while (psChild2)
     608             :             {
     609           7 :                 if (psChild2->eType == CXT_Text)
     610             :                 {
     611           7 :                     papszIdGenMethods =
     612           7 :                         CSLAddString(papszIdGenMethods, psChild2->pszValue);
     613             :                 }
     614           7 :                 psChild2 = psChild2->psNext;
     615             :             }
     616             :         }
     617          10 :         psChild = psChild->psNext;
     618             :     }
     619             : 
     620           3 :     return true;
     621             : }
     622             : 
     623             : /************************************************************************/
     624             : /*                    DetectSupportPagingWFS2()                         */
     625             : /************************************************************************/
     626             : 
     627          39 : bool OGRWFSDataSource::DetectSupportPagingWFS2(
     628             :     const CPLXMLNode *psGetCapabilitiesResponse,
     629             :     const CPLXMLNode *psConfigurationRoot)
     630             : {
     631          39 :     const char *pszPagingAllowed = CPLGetConfigOption(
     632             :         "OGR_WFS_PAGING_ALLOWED",
     633             :         CPLGetXMLValue(psConfigurationRoot, "PagingAllowed", nullptr));
     634          39 :     if (pszPagingAllowed != nullptr && !CPLTestBool(pszPagingAllowed))
     635           0 :         return false;
     636             : 
     637             :     const CPLXMLNode *psOperationsMetadata =
     638          39 :         CPLGetXMLNode(psGetCapabilitiesResponse, "OperationsMetadata");
     639          39 :     if (!psOperationsMetadata)
     640             :     {
     641           6 :         return false;
     642             :     }
     643             : 
     644          33 :     const CPLXMLNode *psChild = psOperationsMetadata->psChild;
     645          66 :     while (psChild)
     646             :     {
     647         195 :         if (psChild->eType == CXT_Element &&
     648          97 :             strcmp(psChild->pszValue, "Constraint") == 0 &&
     649          32 :             strcmp(CPLGetXMLValue(psChild, "name", ""),
     650             :                    "ImplementsResultPaging") == 0)
     651             :         {
     652          32 :             if (!EQUAL(CPLGetXMLValue(psChild, "DefaultValue", ""), "TRUE"))
     653             :             {
     654           0 :                 psChild = nullptr;
     655           0 :                 break;
     656             :             }
     657          32 :             break;
     658             :         }
     659          33 :         psChild = psChild->psNext;
     660             :     }
     661          33 :     if (!psChild)
     662             :     {
     663           1 :         CPLDebug("WFS", "No paging support");
     664           1 :         return false;
     665             :     }
     666             : 
     667          32 :     psChild = psOperationsMetadata->psChild;
     668          32 :     while (psChild)
     669             :     {
     670          96 :         if (psChild->eType == CXT_Element &&
     671          64 :             strcmp(psChild->pszValue, "Operation") == 0 &&
     672          32 :             strcmp(CPLGetXMLValue(psChild, "name", ""), "GetFeature") == 0)
     673             :         {
     674          32 :             break;
     675             :         }
     676           0 :         psChild = psChild->psNext;
     677             :     }
     678             : 
     679          32 :     const char *pszPageSize = CPLGetConfigOption(
     680             :         "OGR_WFS_PAGE_SIZE",
     681             :         CPLGetXMLValue(psConfigurationRoot, "PageSize", nullptr));
     682          32 :     if (psChild && pszPageSize == nullptr)
     683             :     {
     684          30 :         psChild = psChild->psChild;
     685          64 :         while (psChild)
     686             :         {
     687         162 :             if (psChild->eType == CXT_Element &&
     688          94 :                 strcmp(psChild->pszValue, "Constraint") == 0 &&
     689          30 :                 strcmp(CPLGetXMLValue(psChild, "name", ""), "CountDefault") ==
     690             :                     0)
     691             :             {
     692          30 :                 int nVal = atoi(CPLGetXMLValue(psChild, "DefaultValue", "0"));
     693          30 :                 if (nVal > 0)
     694             :                 {
     695          30 :                     nPageSize = nVal;
     696             :                     const int nPageSizeURL =
     697          30 :                         atoi(CPLURLGetValue(osBaseURL, "COUNT"));
     698          30 :                     if (nPageSizeURL > 0 && nPageSizeURL < nPageSize)
     699             :                     {
     700           0 :                         nPageSize = nPageSizeURL;
     701             :                     }
     702             :                 }
     703             : 
     704          30 :                 break;
     705             :             }
     706          34 :             psChild = psChild->psNext;
     707             :         }
     708             :     }
     709          32 :     if (pszPageSize != nullptr)
     710             :     {
     711           2 :         nPageSize = atoi(pszPageSize);
     712           2 :         if (nPageSize <= 0)
     713           0 :             nPageSize = DEFAULT_PAGE_SIZE;
     714             :     }
     715             : 
     716          32 :     CPLDebug("WFS", "Paging support with page size %d", nPageSize);
     717          32 :     bPagingAllowed = true;
     718             : 
     719          32 :     return true;
     720             : }
     721             : 
     722             : /************************************************************************/
     723             : /*                   DetectSupportStandardJoinsWFS2()                   */
     724             : /************************************************************************/
     725             : 
     726          39 : bool OGRWFSDataSource::DetectSupportStandardJoinsWFS2(const CPLXMLNode *psRoot)
     727             : {
     728             :     const CPLXMLNode *psOperationsMetadata =
     729          39 :         CPLGetXMLNode(psRoot, "OperationsMetadata");
     730          39 :     if (!psOperationsMetadata)
     731             :     {
     732           6 :         return false;
     733             :     }
     734             : 
     735          33 :     const CPLXMLNode *psChild = psOperationsMetadata->psChild;
     736          98 :     while (psChild)
     737             :     {
     738         267 :         if (psChild->eType == CXT_Element &&
     739         145 :             strcmp(psChild->pszValue, "Constraint") == 0 &&
     740          56 :             strcmp(CPLGetXMLValue(psChild, "name", ""),
     741             :                    "ImplementsStandardJoins") == 0)
     742             :         {
     743          24 :             if (!EQUAL(CPLGetXMLValue(psChild, "DefaultValue", ""), "TRUE"))
     744             :             {
     745           0 :                 psChild = nullptr;
     746           0 :                 break;
     747             :             }
     748          24 :             break;
     749             :         }
     750          65 :         psChild = psChild->psNext;
     751             :     }
     752          33 :     if (!psChild)
     753             :     {
     754           9 :         CPLDebug("WFS", "No ImplementsStandardJoins support");
     755           9 :         return false;
     756             :     }
     757          24 :     bStandardJoinsWFS2 = true;
     758          24 :     return true;
     759             : }
     760             : 
     761             : /************************************************************************/
     762             : /*                      FindComparisonOperator()                        */
     763             : /************************************************************************/
     764             : 
     765         321 : static bool FindComparisonOperator(const CPLXMLNode *psNode, const char *pszVal)
     766             : {
     767         321 :     const CPLXMLNode *psChild = psNode->psChild;
     768        1569 :     while (psChild)
     769             :     {
     770        1555 :         if (psChild->eType == CXT_Element &&
     771        1555 :             strcmp(psChild->pszValue, "ComparisonOperator") == 0)
     772             :         {
     773        1555 :             if (strcmp(CPLGetXMLValue(psChild, nullptr, ""), pszVal) == 0)
     774         307 :                 return true;
     775             : 
     776             :             /* For WFS 2.0.0 */
     777        1248 :             const char *pszName = CPLGetXMLValue(psChild, "name", nullptr);
     778        1248 :             if (pszName != nullptr && STARTS_WITH(pszName, "PropertyIs") &&
     779           0 :                 strcmp(pszName + 10, pszVal) == 0)
     780           0 :                 return true;
     781             :         }
     782        1248 :         psChild = psChild->psNext;
     783             :     }
     784          14 :     return false;
     785             : }
     786             : 
     787             : /************************************************************************/
     788             : /*                          LoadFromFile()                              */
     789             : /************************************************************************/
     790             : 
     791         142 : CPLXMLNode *OGRWFSDataSource::LoadFromFile(const char *pszFilename)
     792             : {
     793             :     VSIStatBufL sStatBuf;
     794         142 :     if (VSIStatExL(pszFilename, &sStatBuf,
     795         153 :                    VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) != 0 ||
     796          11 :         VSI_ISDIR(sStatBuf.st_mode))
     797         131 :         return nullptr;
     798             : 
     799          11 :     VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
     800             : 
     801          11 :     if (fp == nullptr)
     802           0 :         return nullptr;
     803             : 
     804          11 :     char achHeader[1024] = {};
     805             :     const int nRead =
     806          11 :         static_cast<int>(VSIFReadL(achHeader, 1, sizeof(achHeader) - 1, fp));
     807          11 :     if (nRead == 0)
     808             :     {
     809           0 :         VSIFCloseL(fp);
     810           0 :         return nullptr;
     811             :     }
     812          11 :     achHeader[nRead] = 0;
     813             : 
     814          11 :     if (!STARTS_WITH_CI(achHeader, "<OGRWFSDataSource>") &&
     815           4 :         strstr(achHeader, "<WFS_Capabilities") == nullptr &&
     816           0 :         strstr(achHeader, "<wfs:WFS_Capabilities") == nullptr)
     817             :     {
     818           0 :         VSIFCloseL(fp);
     819           0 :         return nullptr;
     820             :     }
     821             : 
     822             :     /* -------------------------------------------------------------------- */
     823             :     /*      It is the right file, now load the full XML definition.         */
     824             :     /* -------------------------------------------------------------------- */
     825          11 :     VSIFSeekL(fp, 0, SEEK_END);
     826          11 :     const int nLen = (int)VSIFTellL(fp);
     827          11 :     VSIFSeekL(fp, 0, SEEK_SET);
     828             : 
     829          11 :     char *pszXML = (char *)VSI_MALLOC_VERBOSE(nLen + 1);
     830          11 :     if (pszXML == nullptr)
     831             :     {
     832           0 :         VSIFCloseL(fp);
     833           0 :         return nullptr;
     834             :     }
     835          11 :     pszXML[nLen] = '\0';
     836          11 :     if (((int)VSIFReadL(pszXML, 1, nLen, fp)) != nLen)
     837             :     {
     838           0 :         CPLFree(pszXML);
     839           0 :         VSIFCloseL(fp);
     840             : 
     841           0 :         return nullptr;
     842             :     }
     843          11 :     VSIFCloseL(fp);
     844             : 
     845          11 :     if (strstr(pszXML, "CubeWerx"))
     846             :     {
     847             :         /* At least true for CubeWerx Suite 4.15.1 */
     848           0 :         bUseFeatureId = true;
     849             :     }
     850          11 :     else if (strstr(pszXML, "deegree"))
     851             :     {
     852           0 :         bGmlObjectIdNeedsGMLPrefix = true;
     853             :     }
     854             : 
     855          11 :     CPLXMLNode *psXML = CPLParseXMLString(pszXML);
     856          11 :     CPLFree(pszXML);
     857             : 
     858          11 :     return psXML;
     859             : }
     860             : 
     861             : /************************************************************************/
     862             : /*                          SendGetCapabilities()                       */
     863             : /************************************************************************/
     864             : 
     865         132 : CPLHTTPResult *OGRWFSDataSource::SendGetCapabilities(const char *pszBaseURL,
     866             :                                                      CPLString &osTypeName)
     867             : {
     868         264 :     CPLString osURL(pszBaseURL);
     869             : 
     870         132 :     osURL = CPLURLAddKVP(osURL, "SERVICE", "WFS");
     871         132 :     osURL = CPLURLAddKVP(osURL, "REQUEST", "GetCapabilities");
     872         132 :     osTypeName = CPLURLGetValue(osURL, "TYPENAME");
     873         132 :     if (osTypeName.empty())
     874         132 :         osTypeName = CPLURLGetValue(osURL, "TYPENAMES");
     875         132 :     osURL = CPLURLAddKVP(osURL, "TYPENAME", nullptr);
     876         132 :     osURL = CPLURLAddKVP(osURL, "TYPENAMES", nullptr);
     877         132 :     osURL = CPLURLAddKVP(osURL, "FILTER", nullptr);
     878         132 :     osURL = CPLURLAddKVP(osURL, "PROPERTYNAME", nullptr);
     879         132 :     osURL = CPLURLAddKVP(osURL, "MAXFEATURES", nullptr);
     880         132 :     osURL = CPLURLAddKVP(osURL, "OUTPUTFORMAT", nullptr);
     881             : 
     882         132 :     CPLDebug("WFS", "%s", osURL.c_str());
     883             : 
     884         132 :     CPLHTTPResult *psResult = HTTPFetch(osURL, nullptr);
     885         132 :     if (psResult == nullptr)
     886             :     {
     887           3 :         return nullptr;
     888             :     }
     889             : 
     890         129 :     if (strstr((const char *)psResult->pabyData, "<ServiceExceptionReport") !=
     891         128 :             nullptr ||
     892         128 :         strstr((const char *)psResult->pabyData, "<ows:ExceptionReport") !=
     893         128 :             nullptr ||
     894         128 :         strstr((const char *)psResult->pabyData, "<ExceptionReport") != nullptr)
     895             :     {
     896           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Error returned by server : %s",
     897             :                  psResult->pabyData);
     898           1 :         CPLHTTPDestroyResult(psResult);
     899           1 :         return nullptr;
     900             :     }
     901             : 
     902         128 :     return psResult;
     903             : }
     904             : 
     905             : /************************************************************************/
     906             : /*                                Open()                                */
     907             : /************************************************************************/
     908             : 
     909         142 : int OGRWFSDataSource::Open(const char *pszFilename, int bUpdateIn,
     910             :                            char **papszOpenOptionsIn)
     911             : 
     912             : {
     913         142 :     bUpdate = CPL_TO_BOOL(bUpdateIn);
     914         142 :     CPLFree(pszName);
     915         142 :     pszName = CPLStrdup(pszFilename);
     916             : 
     917         142 :     const CPLXMLNode *psWFSCapabilities = nullptr;
     918         142 :     CPLXMLNode *psXML = LoadFromFile(pszFilename);
     919         284 :     CPLString osTypeName;
     920         142 :     const char *pszBaseURL = nullptr;
     921             : 
     922         142 :     bEmptyAsNull = CPLFetchBool(papszOpenOptionsIn, "EMPTY_AS_NULL", true);
     923             : 
     924         142 :     const CPLXMLNode *psConfigurationRoot = nullptr;
     925             : 
     926         142 :     if (psXML == nullptr)
     927             :     {
     928         133 :         if (!STARTS_WITH_CI(pszFilename, "WFS:") &&
     929           1 :             FindSubStringInsensitive(pszFilename, "SERVICE=WFS") == nullptr)
     930             :         {
     931           6 :             return FALSE;
     932             :         }
     933             : 
     934         131 :         pszBaseURL = CSLFetchNameValue(papszOpenOptionsIn, "URL");
     935         131 :         if (pszBaseURL == nullptr)
     936             :         {
     937         131 :             pszBaseURL = pszFilename;
     938         131 :             if (STARTS_WITH_CI(pszFilename, "WFS:"))
     939         131 :                 pszBaseURL += 4;
     940             :         }
     941             : 
     942         131 :         osBaseURL = pszBaseURL;
     943             : 
     944         131 :         if (!STARTS_WITH(pszBaseURL, "http://") &&
     945         129 :             !STARTS_WITH(pszBaseURL, "https://") &&
     946         129 :             !STARTS_WITH(pszBaseURL, "/vsimem/"))
     947           0 :             return FALSE;
     948             : 
     949         131 :         CPLString strOriginalTypeName = "";
     950             :         CPLHTTPResult *psResult =
     951         131 :             SendGetCapabilities(pszBaseURL, strOriginalTypeName);
     952         131 :         osTypeName = WFS_DecodeURL(strOriginalTypeName);
     953         131 :         if (psResult == nullptr)
     954             :         {
     955           4 :             return FALSE;
     956             :         }
     957             : 
     958         127 :         if (strstr((const char *)psResult->pabyData, "CubeWerx"))
     959             :         {
     960             :             /* At least true for CubeWerx Suite 4.15.1 */
     961           0 :             bUseFeatureId = true;
     962             :         }
     963         127 :         else if (strstr((const char *)psResult->pabyData, "deegree"))
     964             :         {
     965           0 :             bGmlObjectIdNeedsGMLPrefix = true;
     966             :         }
     967             : 
     968         127 :         psXML = CPLParseXMLString((const char *)psResult->pabyData);
     969         127 :         if (psXML == nullptr)
     970             :         {
     971           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
     972             :                      psResult->pabyData);
     973           1 :             CPLHTTPDestroyResult(psResult);
     974           1 :             return FALSE;
     975             :         }
     976         126 :         osGetCapabilities = (const char *)psResult->pabyData;
     977             : 
     978         126 :         CPLHTTPDestroyResult(psResult);
     979             :     }
     980          13 :     else if (WFSFindNode(psXML, "OGRWFSDataSource") == nullptr &&
     981           3 :              WFSFindNode(psXML, "WFS_Capabilities") != nullptr)
     982             :     {
     983             :         /* This is directly the Capabilities document */
     984             :         char *pszXML =
     985           3 :             CPLSerializeXMLTree(WFSFindNode(psXML, "WFS_Capabilities"));
     986           3 :         osGetCapabilities = pszXML;
     987           3 :         CPLFree(pszXML);
     988             :     }
     989             :     else
     990             :     {
     991           7 :         psConfigurationRoot = WFSFindNode(psXML, "OGRWFSDataSource");
     992           7 :         if (psConfigurationRoot == nullptr)
     993             :         {
     994           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     995             :                      "Cannot find <OGRWFSDataSource>");
     996           0 :             CPLDestroyXMLNode(psXML);
     997           1 :             return FALSE;
     998             :         }
     999             : 
    1000           7 :         pszBaseURL = CPLGetXMLValue(psConfigurationRoot, "URL", nullptr);
    1001           7 :         if (pszBaseURL == nullptr)
    1002             :         {
    1003           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot find <URL>");
    1004           0 :             CPLDestroyXMLNode(psXML);
    1005           0 :             return FALSE;
    1006             :         }
    1007           7 :         osBaseURL = pszBaseURL;
    1008             : 
    1009             :         /* --------------------------------------------------------------------
    1010             :          */
    1011             :         /*      Capture other parameters. */
    1012             :         /* --------------------------------------------------------------------
    1013             :          */
    1014             :         const char *pszParam =
    1015           7 :             CPLGetXMLValue(psConfigurationRoot, "Timeout", nullptr);
    1016           7 :         if (pszParam)
    1017           0 :             papszHttpOptions =
    1018           0 :                 CSLSetNameValue(papszHttpOptions, "TIMEOUT", pszParam);
    1019             : 
    1020           7 :         pszParam = CPLGetXMLValue(psConfigurationRoot, "HTTPAUTH", nullptr);
    1021           7 :         if (pszParam)
    1022           0 :             papszHttpOptions =
    1023           0 :                 CSLSetNameValue(papszHttpOptions, "HTTPAUTH", pszParam);
    1024             : 
    1025           7 :         pszParam = CPLGetXMLValue(psConfigurationRoot, "USERPWD", nullptr);
    1026           7 :         if (pszParam)
    1027           0 :             papszHttpOptions =
    1028           0 :                 CSLSetNameValue(papszHttpOptions, "USERPWD", pszParam);
    1029             : 
    1030           7 :         pszParam = CPLGetXMLValue(psConfigurationRoot, "COOKIE", nullptr);
    1031           7 :         if (pszParam)
    1032           0 :             papszHttpOptions =
    1033           0 :                 CSLSetNameValue(papszHttpOptions, "COOKIE", pszParam);
    1034             : 
    1035           7 :         pszParam = CPLGetXMLValue(psConfigurationRoot, "Version", nullptr);
    1036           7 :         if (pszParam)
    1037           0 :             osVersion = pszParam;
    1038             : 
    1039             :         pszParam =
    1040           7 :             CPLGetXMLValue(psConfigurationRoot, "BaseStartIndex", nullptr);
    1041           7 :         if (pszParam)
    1042           0 :             nBaseStartIndex = atoi(pszParam);
    1043             : 
    1044           7 :         CPLString strOriginalTypeName = CPLURLGetValue(pszBaseURL, "TYPENAME");
    1045           7 :         if (strOriginalTypeName.empty())
    1046           7 :             strOriginalTypeName = CPLURLGetValue(pszBaseURL, "TYPENAMES");
    1047           7 :         osTypeName = WFS_DecodeURL(strOriginalTypeName);
    1048             : 
    1049             :         psWFSCapabilities =
    1050           7 :             WFSFindNode(psConfigurationRoot, "WFS_Capabilities");
    1051           7 :         if (psWFSCapabilities == nullptr)
    1052             :         {
    1053             :             CPLHTTPResult *psResult =
    1054           1 :                 SendGetCapabilities(pszBaseURL, strOriginalTypeName);
    1055           1 :             if (psResult == nullptr)
    1056             :             {
    1057           0 :                 CPLDestroyXMLNode(psXML);
    1058           0 :                 return FALSE;
    1059             :             }
    1060             : 
    1061             :             CPLXMLNode *psXML2 =
    1062           1 :                 CPLParseXMLString((const char *)psResult->pabyData);
    1063           1 :             if (psXML2 == nullptr)
    1064             :             {
    1065           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1066             :                          "Invalid XML content : %s", psResult->pabyData);
    1067           0 :                 CPLHTTPDestroyResult(psResult);
    1068           0 :                 CPLDestroyXMLNode(psXML);
    1069           0 :                 return FALSE;
    1070             :             }
    1071             : 
    1072           1 :             CPLHTTPDestroyResult(psResult);
    1073             : 
    1074           1 :             psWFSCapabilities = WFSFindNode(psXML2, "WFS_Capabilities");
    1075           1 :             if (psWFSCapabilities == nullptr)
    1076             :             {
    1077           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1078             :                          "Cannot find <WFS_Capabilities>");
    1079           0 :                 CPLDestroyXMLNode(psXML);
    1080           0 :                 CPLDestroyXMLNode(psXML2);
    1081           0 :                 return FALSE;
    1082             :             }
    1083             : 
    1084           1 :             CPLAddXMLChild(psXML, CPLCloneXMLTree(psWFSCapabilities));
    1085             : 
    1086             :             const bool bOK =
    1087           1 :                 CPL_TO_BOOL(CPLSerializeXMLTreeToFile(psXML, pszFilename));
    1088             : 
    1089           1 :             CPLDestroyXMLNode(psXML);
    1090           1 :             CPLDestroyXMLNode(psXML2);
    1091             : 
    1092           1 :             if (bOK)
    1093           1 :                 return Open(pszFilename, bUpdate, papszOpenOptionsIn);
    1094             : 
    1095           0 :             return FALSE;
    1096             :         }
    1097             :         else
    1098             :         {
    1099           6 :             psFileXML = psXML;
    1100             : 
    1101             :             /* To avoid to have nodes after WFSCapabilities */
    1102           6 :             CPLXMLNode *psAfterWFSCapabilities = psWFSCapabilities->psNext;
    1103           6 :             const_cast<CPLXMLNode *>(psWFSCapabilities)->psNext = nullptr;
    1104           6 :             char *pszXML = CPLSerializeXMLTree(psWFSCapabilities);
    1105           6 :             const_cast<CPLXMLNode *>(psWFSCapabilities)->psNext =
    1106             :                 psAfterWFSCapabilities;
    1107           6 :             osGetCapabilities = pszXML;
    1108           6 :             CPLFree(pszXML);
    1109             :         }
    1110             :     }
    1111             : 
    1112         135 :     bInvertAxisOrderIfLatLong = CPLTestBool(CSLFetchNameValueDef(
    1113             :         papszOpenOptionsIn, "INVERT_AXIS_ORDER_IF_LAT_LONG",
    1114             :         CPLGetConfigOption("GML_INVERT_AXIS_ORDER_IF_LAT_LONG", "YES")));
    1115             :     osConsiderEPSGAsURN = CSLFetchNameValueDef(
    1116             :         papszOpenOptionsIn, "CONSIDER_EPSG_AS_URN",
    1117         135 :         CPLGetConfigOption("GML_CONSIDER_EPSG_AS_URN", "AUTO"));
    1118         135 :     bExposeGMLId = CPLTestBool(
    1119             :         CSLFetchNameValueDef(papszOpenOptionsIn, "EXPOSE_GML_ID",
    1120             :                              CPLGetConfigOption("GML_EXPOSE_GML_ID", "YES")));
    1121             : 
    1122         135 :     CPLXMLNode *psStrippedXML = CPLCloneXMLTree(psXML);
    1123         135 :     CPLStripXMLNamespace(psStrippedXML, nullptr, TRUE);
    1124         135 :     psWFSCapabilities = CPLGetXMLNode(psStrippedXML, "=WFS_Capabilities");
    1125         135 :     if (psWFSCapabilities == nullptr)
    1126             :     {
    1127             :         psWFSCapabilities =
    1128           8 :             CPLGetXMLNode(psStrippedXML, "=OGRWFSDataSource.WFS_Capabilities");
    1129             :     }
    1130         135 :     if (psWFSCapabilities == nullptr)
    1131             :     {
    1132           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find <WFS_Capabilities>");
    1133           2 :         if (!psFileXML)
    1134           2 :             CPLDestroyXMLNode(psXML);
    1135           2 :         CPLDestroyXMLNode(psStrippedXML);
    1136           2 :         return FALSE;
    1137             :     }
    1138             : 
    1139         133 :     if (pszBaseURL == nullptr)
    1140             :     {
    1141             :         /* This is directly the Capabilities document */
    1142           2 :         pszBaseURL = CPLGetXMLValue(
    1143             :             psWFSCapabilities, "OperationsMetadata.Operation.DCP.HTTP.Get.href",
    1144             :             nullptr);
    1145           2 :         if (pszBaseURL == nullptr) /* WFS 1.0.0 variant */
    1146           1 :             pszBaseURL = CPLGetXMLValue(psWFSCapabilities,
    1147             :                                         "Capability.Request.GetCapabilities."
    1148             :                                         "DCPType.HTTP.Get.onlineResource",
    1149             :                                         nullptr);
    1150             : 
    1151           2 :         if (pszBaseURL == nullptr)
    1152             :         {
    1153           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot find base URL");
    1154           1 :             if (!psFileXML)
    1155           1 :                 CPLDestroyXMLNode(psXML);
    1156           1 :             CPLDestroyXMLNode(psStrippedXML);
    1157           1 :             return FALSE;
    1158             :         }
    1159             : 
    1160           1 :         osBaseURL = pszBaseURL;
    1161             :     }
    1162             : 
    1163         132 :     pszBaseURL = nullptr;
    1164             : 
    1165         792 :     for (int i = 0; i < (int)(sizeof(asMetadata) / sizeof(asMetadata[0])); i++)
    1166             :     {
    1167             :         const char *pszVal =
    1168         660 :             CPLGetXMLValue(psWFSCapabilities, asMetadata[i].pszPath, nullptr);
    1169         660 :         if (pszVal)
    1170          11 :             SetMetadataItem(asMetadata[i].pszMDI, pszVal);
    1171             :     }
    1172             : 
    1173         132 :     if (osVersion.empty())
    1174         132 :         osVersion = CPLGetXMLValue(psWFSCapabilities, "version", "1.0.0");
    1175         132 :     if (strcmp(osVersion.c_str(), "1.0.0") == 0)
    1176             :     {
    1177           1 :         bUseFeatureId = true;
    1178             :     }
    1179             :     else
    1180             :     {
    1181             :         /* Some servers happen to support RESULTTYPE=hits in 1.0.0, but there */
    1182             :         /* is no way to advertises this */
    1183         131 :         if (atoi(osVersion) >= 2)
    1184          39 :             bGetFeatureSupportHits = true; /* WFS >= 2.0.0 supports hits */
    1185             :         else
    1186          92 :             bGetFeatureSupportHits =
    1187          92 :                 DetectIfGetFeatureSupportHits(psWFSCapabilities);
    1188         131 :         bRequiresEnvelopeSpatialFilter =
    1189         131 :             DetectRequiresEnvelopeSpatialFilter(psWFSCapabilities);
    1190             :     }
    1191             : 
    1192         132 :     if (atoi(osVersion) >= 2)
    1193             :     {
    1194          78 :         CPLString osMaxFeatures = CPLURLGetValue(osBaseURL, "COUNT");
    1195             :         /* Ok, people are used to MAXFEATURES, so be nice to recognize it if it
    1196             :          * is used for WFS 2.0 ... */
    1197          39 :         if (osMaxFeatures.empty())
    1198             :         {
    1199          39 :             osMaxFeatures = CPLURLGetValue(osBaseURL, "MAXFEATURES");
    1200          39 :             if (!osMaxFeatures.empty() &&
    1201           0 :                 CPLTestBool(
    1202             :                     CPLGetConfigOption("OGR_WFS_FIX_MAXFEATURES", "YES")))
    1203             :             {
    1204           0 :                 CPLDebug("WFS", "MAXFEATURES wrongly used for WFS 2.0. Using "
    1205             :                                 "COUNT instead");
    1206           0 :                 osBaseURL = CPLURLAddKVP(osBaseURL, "MAXFEATURES", nullptr);
    1207           0 :                 osBaseURL = CPLURLAddKVP(osBaseURL, "COUNT", osMaxFeatures);
    1208             :             }
    1209             :         }
    1210             : 
    1211          39 :         DetectSupportPagingWFS2(psWFSCapabilities, psConfigurationRoot);
    1212          39 :         DetectSupportStandardJoinsWFS2(psWFSCapabilities);
    1213             :     }
    1214             : 
    1215         132 :     DetectTransactionSupport(psWFSCapabilities);
    1216             : 
    1217         132 :     if (bUpdate && !bTransactionSupport)
    1218             :     {
    1219           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1220             :                  "Server is read-only WFS; no WFS-T feature advertized");
    1221           1 :         if (!psFileXML)
    1222           1 :             CPLDestroyXMLNode(psXML);
    1223           1 :         CPLDestroyXMLNode(psStrippedXML);
    1224           1 :         return FALSE;
    1225             :     }
    1226             : 
    1227         131 :     const CPLXMLNode *psFilterCap = CPLGetXMLNode(
    1228             :         psWFSCapabilities, "Filter_Capabilities.Scalar_Capabilities");
    1229         131 :     if (psFilterCap)
    1230             :     {
    1231          46 :         bHasMinOperators =
    1232          52 :             CPLGetXMLNode(psFilterCap, "LogicalOperators") != nullptr ||
    1233           6 :             CPLGetXMLNode(psFilterCap, "Logical_Operators") != nullptr;
    1234          46 :         if (CPLGetXMLNode(psFilterCap, "ComparisonOperators"))
    1235          40 :             psFilterCap = CPLGetXMLNode(psFilterCap, "ComparisonOperators");
    1236           6 :         else if (CPLGetXMLNode(psFilterCap, "Comparison_Operators"))
    1237           0 :             psFilterCap = CPLGetXMLNode(psFilterCap, "Comparison_Operators");
    1238             :         else
    1239           6 :             psFilterCap = nullptr;
    1240          46 :         if (psFilterCap)
    1241             :         {
    1242          40 :             if (CPLGetXMLNode(psFilterCap, "Simple_Comparisons") == nullptr)
    1243             :             {
    1244          40 :                 bHasMinOperators &=
    1245          40 :                     FindComparisonOperator(psFilterCap, "LessThan");
    1246          40 :                 bHasMinOperators &=
    1247          40 :                     FindComparisonOperator(psFilterCap, "GreaterThan");
    1248          40 :                 if (atoi(osVersion) >= 2)
    1249             :                 {
    1250           6 :                     bHasMinOperators &= FindComparisonOperator(
    1251           6 :                         psFilterCap, "LessThanOrEqualTo");
    1252           6 :                     bHasMinOperators &= FindComparisonOperator(
    1253           6 :                         psFilterCap, "GreaterThanOrEqualTo");
    1254             :                 }
    1255             :                 else
    1256             :                 {
    1257          34 :                     bHasMinOperators &=
    1258          34 :                         FindComparisonOperator(psFilterCap, "LessThanEqualTo");
    1259          34 :                     bHasMinOperators &= FindComparisonOperator(
    1260          34 :                         psFilterCap, "GreaterThanEqualTo");
    1261             :                 }
    1262          40 :                 bHasMinOperators &=
    1263          40 :                     FindComparisonOperator(psFilterCap, "EqualTo");
    1264          40 :                 bHasMinOperators &=
    1265          40 :                     FindComparisonOperator(psFilterCap, "NotEqualTo");
    1266          40 :                 bHasMinOperators &= FindComparisonOperator(psFilterCap, "Like");
    1267             :             }
    1268             :             else
    1269             :             {
    1270           0 :                 bHasMinOperators &=
    1271           0 :                     CPLGetXMLNode(psFilterCap, "Simple_Comparisons") !=
    1272           0 :                         nullptr &&
    1273           0 :                     CPLGetXMLNode(psFilterCap, "Like") != nullptr;
    1274             :             }
    1275          40 :             bHasNullCheck =
    1276          40 :                 FindComparisonOperator(psFilterCap, "NullCheck") ||
    1277          41 :                 FindComparisonOperator(psFilterCap, "Null") || /* WFS 2.0.0 */
    1278           1 :                 CPLGetXMLNode(psFilterCap, "NullCheck") != nullptr;
    1279             :         }
    1280             :         else
    1281             :         {
    1282           6 :             bHasMinOperators = false;
    1283             :         }
    1284             :     }
    1285             : 
    1286             :     const CPLXMLNode *psChild =
    1287         131 :         CPLGetXMLNode(psWFSCapabilities, "FeatureTypeList");
    1288         131 :     if (psChild == nullptr)
    1289             :     {
    1290           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find <FeatureTypeList>");
    1291           1 :         if (!psFileXML)
    1292           1 :             CPLDestroyXMLNode(psXML);
    1293           1 :         CPLDestroyXMLNode(psStrippedXML);
    1294           1 :         return FALSE;
    1295             :     }
    1296             : 
    1297             :     /* Check if there are layer names whose identical except their prefix */
    1298         130 :     std::set<CPLString> aosSetLayerNames;
    1299         355 :     for (CPLXMLNode *psChildIter = psChild->psChild; psChildIter != nullptr;
    1300         225 :          psChildIter = psChildIter->psNext)
    1301             :     {
    1302         227 :         if (psChildIter->eType == CXT_Element &&
    1303         227 :             strcmp(psChildIter->pszValue, "FeatureType") == 0)
    1304             :         {
    1305             :             const char *l_pszName =
    1306         223 :                 CPLGetXMLValue(psChildIter, "Name", nullptr);
    1307         223 :             if (l_pszName != nullptr)
    1308             :             {
    1309         205 :                 const char *pszShortName = strchr(l_pszName, ':');
    1310         205 :                 if (pszShortName)
    1311          33 :                     l_pszName = pszShortName + 1;
    1312         205 :                 if (aosSetLayerNames.find(l_pszName) != aosSetLayerNames.end())
    1313             :                 {
    1314           2 :                     bKeepLayerNamePrefix = true;
    1315           2 :                     CPLDebug("WFS",
    1316             :                              "At least 2 layers have names that are only "
    1317             :                              "distinguishable by keeping the prefix");
    1318           2 :                     break;
    1319             :                 }
    1320         203 :                 aosSetLayerNames.insert(l_pszName);
    1321             :             }
    1322             :         }
    1323             :     }
    1324             : 
    1325         130 :     char **papszTypenames = nullptr;
    1326         130 :     if (!osTypeName.empty())
    1327             :         papszTypenames =
    1328           0 :             CSLTokenizeStringComplex(osTypeName, ",", FALSE, FALSE);
    1329             : 
    1330         357 :     for (CPLXMLNode *psChildIter = psChild->psChild; psChildIter != nullptr;
    1331         227 :          psChildIter = psChildIter->psNext)
    1332             :     {
    1333         227 :         if (psChildIter->eType == CXT_Element &&
    1334         227 :             strcmp(psChildIter->pszValue, "FeatureType") == 0)
    1335             :         {
    1336         223 :             const char *pszNS = nullptr;
    1337         223 :             const char *pszNSVal = nullptr;
    1338         223 :             CPLXMLNode *psFeatureTypeIter = psChildIter->psChild;
    1339         912 :             while (psFeatureTypeIter != nullptr)
    1340             :             {
    1341         689 :                 if (psFeatureTypeIter->eType == CXT_Attribute)
    1342             :                 {
    1343          28 :                     pszNS = psFeatureTypeIter->pszValue;
    1344          28 :                     pszNSVal = psFeatureTypeIter->psChild->pszValue;
    1345             :                 }
    1346         689 :                 psFeatureTypeIter = psFeatureTypeIter->psNext;
    1347             :             }
    1348             : 
    1349             :             const char *l_pszName =
    1350         223 :                 CPLGetXMLValue(psChildIter, "Name", nullptr);
    1351             :             const char *pszTitle =
    1352         223 :                 CPLGetXMLValue(psChildIter, "Title", nullptr);
    1353             :             const char *pszAbstract =
    1354         223 :                 CPLGetXMLValue(psChildIter, "Abstract", nullptr);
    1355         223 :             if (l_pszName != nullptr &&
    1356           0 :                 (papszTypenames == nullptr ||
    1357           0 :                  CSLFindString(papszTypenames, l_pszName) != -1))
    1358             :             {
    1359             :                 const char *pszDefaultSRS =
    1360         205 :                     CPLGetXMLValue(psChildIter, "DefaultSRS", nullptr);
    1361         205 :                 if (pszDefaultSRS == nullptr)
    1362          18 :                     pszDefaultSRS = CPLGetXMLValue(psChildIter, "SRS", nullptr);
    1363         205 :                 if (pszDefaultSRS == nullptr)
    1364          18 :                     pszDefaultSRS = CPLGetXMLValue(psChildIter, "DefaultCRS",
    1365             :                                                    nullptr); /* WFS 2.0.0 */
    1366             : 
    1367             :                 const CPLXMLNode *psOtherSRS =
    1368         205 :                     CPLGetXMLNode(psChildIter, "OtherSRS");  // WFS 1.1
    1369         205 :                 if (psOtherSRS == nullptr)
    1370             :                     psOtherSRS =
    1371         204 :                         CPLGetXMLNode(psChildIter, "OtherCRS");  // WFS 2.0
    1372             : 
    1373         410 :                 std::vector<std::string> aosSupportedCRSList{};
    1374         410 :                 OGRLayer::GetSupportedSRSListRetType apoSupportedCRSList;
    1375         205 :                 if (psOtherSRS)
    1376             :                 {
    1377             :                     {
    1378             :                         auto poSRS =
    1379             :                             std::unique_ptr<OGRSpatialReference,
    1380             :                                             OGRSpatialReferenceReleaser>(
    1381           2 :                                 new OGRSpatialReference());
    1382           1 :                         if (poSRS->SetFromUserInput(
    1383             :                                 pszDefaultSRS,
    1384             :                                 OGRSpatialReference::
    1385           1 :                                     SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
    1386             :                             OGRERR_NONE)
    1387             :                         {
    1388           1 :                             aosSupportedCRSList.emplace_back(pszDefaultSRS);
    1389           1 :                             apoSupportedCRSList.emplace_back(std::move(poSRS));
    1390             :                         }
    1391             :                     }
    1392             : 
    1393             :                     CPLErrorStateBackuper oErrorStateBackuper(
    1394           2 :                         CPLQuietErrorHandler);
    1395           4 :                     for (const CPLXMLNode *psIter = psOtherSRS; psIter;
    1396           3 :                          psIter = psIter->psNext)
    1397             :                     {
    1398           3 :                         if (psIter->eType == CXT_Element)
    1399             :                         {
    1400             :                             const char *pszSRS =
    1401           3 :                                 CPLGetXMLValue(psIter, "", nullptr);
    1402           3 :                             if (pszSRS)
    1403             :                             {
    1404             :                                 auto poSRS = std::unique_ptr<
    1405             :                                     OGRSpatialReference,
    1406             :                                     OGRSpatialReferenceReleaser>(
    1407           4 :                                     new OGRSpatialReference());
    1408           4 :                                 if (poSRS->SetFromUserInput(
    1409           2 :                                         EQUAL(pszSRS, "CRS:84") ? "OGC:CRS84"
    1410             :                                                                 : pszSRS,
    1411             :                                         OGRSpatialReference::
    1412           2 :                                             SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
    1413             :                                     OGRERR_NONE)
    1414             :                                 {
    1415           2 :                                     aosSupportedCRSList.emplace_back(pszSRS);
    1416             :                                     apoSupportedCRSList.emplace_back(
    1417           2 :                                         std::move(poSRS));
    1418             :                                 }
    1419             :                                 else
    1420             :                                 {
    1421           0 :                                     CPLDebug("WFS", "Invalid CRS %s", pszSRS);
    1422             :                                 }
    1423             :                             }
    1424             :                         }
    1425             :                     }
    1426             :                 }
    1427             : 
    1428             :                 CPLXMLNode *psOutputFormats =
    1429         205 :                     CPLGetXMLNode(psChildIter, "OutputFormats");
    1430         410 :                 CPLString osOutputFormat;
    1431         205 :                 if (psOutputFormats)
    1432             :                 {
    1433           8 :                     std::vector<CPLString> osFormats;
    1434           4 :                     CPLXMLNode *psOutputFormatIter = psOutputFormats->psChild;
    1435           8 :                     while (psOutputFormatIter)
    1436             :                     {
    1437           4 :                         if (psOutputFormatIter->eType == CXT_Element &&
    1438           4 :                             EQUAL(psOutputFormatIter->pszValue, "Format") &&
    1439           4 :                             psOutputFormatIter->psChild != nullptr &&
    1440           4 :                             psOutputFormatIter->psChild->eType == CXT_Text)
    1441             :                         {
    1442           4 :                             osFormats.push_back(
    1443           4 :                                 psOutputFormatIter->psChild->pszValue);
    1444             :                         }
    1445           4 :                         psOutputFormatIter = psOutputFormatIter->psNext;
    1446             :                     }
    1447             : 
    1448           8 :                     if (strcmp(osVersion.c_str(), "1.1.0") == 0 &&
    1449           4 :                         !osFormats.empty())
    1450             :                     {
    1451           4 :                         bool bFoundGML31 = false;
    1452           4 :                         for (size_t i = 0; i < osFormats.size(); i++)
    1453             :                         {
    1454           4 :                             if (strstr(osFormats[i].c_str(), "3.1") != nullptr)
    1455             :                             {
    1456           4 :                                 bFoundGML31 = true;
    1457           4 :                                 break;
    1458             :                             }
    1459             :                         }
    1460             : 
    1461             :                         /* If we didn't find any mention to GML 3.1, then
    1462             :                          * arbitrarily */
    1463             :                         /* use the first output format */
    1464           4 :                         if (!bFoundGML31)
    1465           0 :                             osOutputFormat = osFormats[0].c_str();
    1466             :                     }
    1467             :                 }
    1468             : 
    1469         205 :                 OGRSpatialReference *poSRS = nullptr;
    1470         205 :                 bool bAxisOrderAlreadyInverted = false;
    1471             : 
    1472             :                 /* If a SRSNAME parameter has been encoded in the URL, use it as
    1473             :                  * the SRS */
    1474         410 :                 CPLString osSRSName = CPLURLGetValue(osBaseURL, "SRSNAME");
    1475         205 :                 if (!osSRSName.empty())
    1476             :                 {
    1477           0 :                     pszDefaultSRS = osSRSName.c_str();
    1478             :                 }
    1479             : 
    1480             :                 // EPSG:404000 is a GeoServer joke to indicate a unknown SRS
    1481             :                 // https://osgeo-org.atlassian.net/browse/GEOS-8993
    1482         205 :                 if (pszDefaultSRS && !EQUAL(pszDefaultSRS, "EPSG:404000") &&
    1483         187 :                     !EQUAL(pszDefaultSRS, "urn:ogc:def:crs:EPSG::404000"))
    1484             :                 {
    1485         374 :                     OGRSpatialReference oSRS;
    1486         187 :                     if (oSRS.SetFromUserInput(
    1487             :                             pszDefaultSRS,
    1488             :                             OGRSpatialReference::
    1489         187 :                                 SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
    1490             :                         OGRERR_NONE)
    1491             :                     {
    1492         187 :                         poSRS = oSRS.Clone();
    1493         187 :                         poSRS->SetAxisMappingStrategy(
    1494         187 :                             bInvertAxisOrderIfLatLong
    1495             :                                 ? OAMS_TRADITIONAL_GIS_ORDER
    1496             :                                 : OAMS_AUTHORITY_COMPLIANT);
    1497         351 :                         if (GML_IsSRSLatLongOrder(pszDefaultSRS) &&
    1498         164 :                             bInvertAxisOrderIfLatLong)
    1499             :                         {
    1500         164 :                             bAxisOrderAlreadyInverted = true;
    1501             :                         }
    1502             :                     }
    1503             :                 }
    1504             : 
    1505         205 :                 CPLXMLNode *psBBox = nullptr;
    1506         205 :                 CPLXMLNode *psLatLongBBox = nullptr;
    1507             :                 /* bool bFoundBBox = false; */
    1508         205 :                 double dfMinX = 0.0;
    1509         205 :                 double dfMinY = 0.0;
    1510         205 :                 double dfMaxX = 0.0;
    1511         205 :                 double dfMaxY = 0.0;
    1512         205 :                 if ((psBBox = CPLGetXMLNode(psChildIter, "WGS84BoundingBox")) !=
    1513             :                     nullptr)
    1514             :                 {
    1515             :                     const char *pszLC =
    1516         187 :                         CPLGetXMLValue(psBBox, "LowerCorner", nullptr);
    1517             :                     const char *pszUC =
    1518         187 :                         CPLGetXMLValue(psBBox, "UpperCorner", nullptr);
    1519         187 :                     if (pszLC != nullptr && pszUC != nullptr)
    1520             :                     {
    1521         374 :                         CPLString osConcat(pszLC);
    1522         187 :                         osConcat += " ";
    1523         187 :                         osConcat += pszUC;
    1524         187 :                         char **papszTokens = CSLTokenizeStringComplex(
    1525             :                             osConcat, " ,", FALSE, FALSE);
    1526         187 :                         if (CSLCount(papszTokens) == 4)
    1527             :                         {
    1528             :                             // bFoundBBox = true;
    1529         187 :                             dfMinX = CPLAtof(papszTokens[0]);
    1530         187 :                             dfMinY = CPLAtof(papszTokens[1]);
    1531         187 :                             dfMaxX = CPLAtof(papszTokens[2]);
    1532         187 :                             dfMaxY = CPLAtof(papszTokens[3]);
    1533             :                         }
    1534         187 :                         CSLDestroy(papszTokens);
    1535             :                     }
    1536             :                 }
    1537          18 :                 else if ((psLatLongBBox = CPLGetXMLNode(
    1538          18 :                               psChildIter, "LatLongBoundingBox")) != nullptr)
    1539             :                 {
    1540             :                     const char *pszMinX =
    1541           0 :                         CPLGetXMLValue(psLatLongBBox, "minx", nullptr);
    1542             :                     const char *pszMinY =
    1543           0 :                         CPLGetXMLValue(psLatLongBBox, "miny", nullptr);
    1544             :                     const char *pszMaxX =
    1545           0 :                         CPLGetXMLValue(psLatLongBBox, "maxx", nullptr);
    1546             :                     const char *pszMaxY =
    1547           0 :                         CPLGetXMLValue(psLatLongBBox, "maxy", nullptr);
    1548           0 :                     if (pszMinX != nullptr && pszMinY != nullptr &&
    1549           0 :                         pszMaxX != nullptr && pszMaxY != nullptr)
    1550             :                     {
    1551             :                         // bFoundBBox = true;
    1552           0 :                         dfMinX = CPLAtof(pszMinX);
    1553           0 :                         dfMinY = CPLAtof(pszMinY);
    1554           0 :                         dfMaxX = CPLAtof(pszMaxX);
    1555           0 :                         dfMaxY = CPLAtof(pszMaxY);
    1556             :                     }
    1557             :                 }
    1558             : 
    1559         205 :                 char *pszCSVEscaped = CPLEscapeString(l_pszName, -1, CPLES_CSV);
    1560         205 :                 osLayerMetadataCSV += pszCSVEscaped;
    1561         205 :                 CPLFree(pszCSVEscaped);
    1562             : 
    1563         205 :                 osLayerMetadataCSV += ",";
    1564         205 :                 if (pszTitle)
    1565             :                 {
    1566          30 :                     pszCSVEscaped = CPLEscapeString(pszTitle, -1, CPLES_CSV);
    1567          30 :                     osLayerMetadataCSV += pszCSVEscaped;
    1568          30 :                     CPLFree(pszCSVEscaped);
    1569             :                 }
    1570         205 :                 osLayerMetadataCSV += ",";
    1571         205 :                 if (pszAbstract)
    1572             :                 {
    1573          24 :                     pszCSVEscaped = CPLEscapeString(pszAbstract, -1, CPLES_CSV);
    1574          24 :                     osLayerMetadataCSV += pszCSVEscaped;
    1575          24 :                     CPLFree(pszCSVEscaped);
    1576             :                 }
    1577         205 :                 osLayerMetadataCSV += "\n";
    1578             : 
    1579             :                 OGRWFSLayer *poLayer =
    1580             :                     new OGRWFSLayer(this, poSRS, bAxisOrderAlreadyInverted,
    1581         205 :                                     osBaseURL, l_pszName, pszNS, pszNSVal);
    1582         205 :                 if (!osOutputFormat.empty())
    1583           0 :                     poLayer->SetRequiredOutputFormat(osOutputFormat);
    1584             : 
    1585         205 :                 if (pszTitle)
    1586          30 :                     poLayer->SetMetadataItem("TITLE", pszTitle);
    1587         205 :                 if (pszAbstract)
    1588          24 :                     poLayer->SetMetadataItem("ABSTRACT", pszAbstract);
    1589         205 :                 CPLXMLNode *psKeywords = CPLGetXMLNode(psChildIter, "Keywords");
    1590         205 :                 if (psKeywords)
    1591             :                 {
    1592          20 :                     int nKeywordCounter = 1;
    1593          20 :                     for (CPLXMLNode *psKeyword = psKeywords->psChild;
    1594          75 :                          psKeyword != nullptr; psKeyword = psKeyword->psNext)
    1595             :                     {
    1596          55 :                         if (psKeyword->eType == CXT_Element &&
    1597          55 :                             psKeyword->psChild != nullptr)
    1598             :                         {
    1599          55 :                             poLayer->SetMetadataItem(
    1600             :                                 CPLSPrintf("KEYWORD_%d", nKeywordCounter),
    1601          55 :                                 psKeyword->psChild->pszValue);
    1602          55 :                             nKeywordCounter++;
    1603             :                         }
    1604           0 :                         else if (psKeyword->eType == CXT_Text)
    1605             :                         {
    1606           0 :                             poLayer->SetMetadataItem("KEYWORDS",
    1607           0 :                                                      psKeyword->pszValue);
    1608             :                         }
    1609             :                     }
    1610             :                 }
    1611             : 
    1612         205 :                 if (poSRS)
    1613             :                 {
    1614         187 :                     char *pszProj4 = nullptr;
    1615         187 :                     if (poSRS->exportToProj4(&pszProj4) == OGRERR_NONE)
    1616             :                     {
    1617             :                         /* See http://trac.osgeo.org/gdal/ticket/4041 */
    1618         374 :                         const bool bTrustBounds = CPLFetchBool(
    1619             :                             papszOpenOptionsIn, "TRUST_CAPABILITIES_BOUNDS",
    1620         187 :                             CPLTestBool(CPLGetConfigOption(
    1621             :                                 "OGR_WFS_TRUST_CAPABILITIES_BOUNDS", "FALSE")));
    1622             : 
    1623         187 :                         if (((bTrustBounds ||
    1624         170 :                               (dfMinX == -180 && dfMinY == -90 &&
    1625         137 :                                dfMaxX == 180 && dfMaxY == 90)) &&
    1626         154 :                              strcmp(pszProj4,
    1627             :                                     "+proj=longlat +datum=WGS84 +no_defs") ==
    1628          43 :                                  0) ||
    1629          43 :                             strcmp(pszDefaultSRS,
    1630             :                                    "urn:ogc:def:crs:OGC:1.3:CRS84") == 0)
    1631             :                         {
    1632         144 :                             poLayer->SetWGS84Extents(dfMinX, dfMinY, dfMaxX,
    1633             :                                                      dfMaxY);
    1634         144 :                             poLayer->SetExtents(dfMinX, dfMinY, dfMaxX, dfMaxY);
    1635             :                         }
    1636             : 
    1637          43 :                         else if (bTrustBounds)
    1638             :                         {
    1639          16 :                             OGRSpatialReference oWGS84;
    1640           8 :                             oWGS84.SetWellKnownGeogCS("WGS84");
    1641           8 :                             oWGS84.SetAxisMappingStrategy(
    1642             :                                 OAMS_TRADITIONAL_GIS_ORDER);
    1643           8 :                             CPLPushErrorHandler(CPLQuietErrorHandler);
    1644             :                             auto poCT =
    1645             :                                 std::unique_ptr<OGRCoordinateTransformation>(
    1646             :                                     OGRCreateCoordinateTransformation(&oWGS84,
    1647          16 :                                                                       poSRS));
    1648           8 :                             if (poCT)
    1649             :                             {
    1650           8 :                                 poLayer->SetWGS84Extents(dfMinX, dfMinY, dfMaxX,
    1651             :                                                          dfMaxY);
    1652           8 :                                 poCT->TransformBounds(dfMinX, dfMinY, dfMaxX,
    1653             :                                                       dfMaxY, &dfMinX, &dfMinY,
    1654           8 :                                                       &dfMaxX, &dfMaxY, 20);
    1655           8 :                                 poLayer->SetExtents(dfMinX, dfMinY, dfMaxX,
    1656             :                                                     dfMaxY);
    1657             :                             }
    1658           8 :                             CPLPopErrorHandler();
    1659           8 :                             CPLErrorReset();
    1660             :                         }
    1661             :                     }
    1662         187 :                     CPLFree(pszProj4);
    1663             :                 }
    1664         205 :                 poLayer->SetSupportedSRSList(std::move(aosSupportedCRSList),
    1665         205 :                                              std::move(apoSupportedCRSList));
    1666             : 
    1667         410 :                 papoLayers = (OGRWFSLayer **)CPLRealloc(
    1668         205 :                     papoLayers, sizeof(OGRWFSLayer *) * (nLayers + 1));
    1669         205 :                 papoLayers[nLayers++] = poLayer;
    1670             : 
    1671         205 :                 if (psFileXML != nullptr)
    1672             :                 {
    1673          10 :                     CPLXMLNode *psIter = psXML->psChild;
    1674          36 :                     while (psIter)
    1675             :                     {
    1676          34 :                         if (psIter->eType == CXT_Element && psIter->psChild &&
    1677          82 :                             EQUAL(psIter->pszValue, "OGRWFSLayer") &&
    1678          14 :                             strcmp(CPLGetXMLValue(psIter, "name", ""),
    1679             :                                    l_pszName) == 0)
    1680             :                         {
    1681             :                             const CPLXMLNode *psSchema =
    1682           8 :                                 WFSFindNode(psIter->psChild, "schema");
    1683           8 :                             if (psSchema)
    1684             :                             {
    1685             :                                 OGRFeatureDefn *poSrcFDefn =
    1686           8 :                                     poLayer->ParseSchema(psSchema);
    1687           8 :                                 if (poSrcFDefn)
    1688           8 :                                     poLayer->BuildLayerDefn(poSrcFDefn);
    1689             :                             }
    1690           8 :                             break;
    1691             :                         }
    1692          26 :                         psIter = psIter->psNext;
    1693             :                     }
    1694             :                 }
    1695             :             }
    1696             :         }
    1697             :     }
    1698             : 
    1699         130 :     CSLDestroy(papszTypenames);
    1700             : 
    1701         130 :     if (!psFileXML)
    1702         124 :         CPLDestroyXMLNode(psXML);
    1703         130 :     CPLDestroyXMLNode(psStrippedXML);
    1704             : 
    1705         130 :     return TRUE;
    1706             : }
    1707             : 
    1708             : /************************************************************************/
    1709             : /*                       LoadMultipleLayerDefn()                        */
    1710             : /************************************************************************/
    1711             : 
    1712             : /* TinyOWS doesn't support POST, but MapServer, GeoServer and Deegree do */
    1713             : #define USE_GET_FOR_DESCRIBE_FEATURE_TYPE 1
    1714             : 
    1715          46 : void OGRWFSDataSource::LoadMultipleLayerDefn(const char *pszLayerName,
    1716             :                                              char *pszNS, char *pszNSVal)
    1717             : {
    1718          46 :     if (!bLoadMultipleLayerDefn)
    1719          16 :         return;
    1720             : 
    1721          40 :     if (aoSetAlreadyTriedLayers.find(pszLayerName) !=
    1722          80 :         aoSetAlreadyTriedLayers.end())
    1723           0 :         return;
    1724             : 
    1725          40 :     std::string osPrefix(pszLayerName);
    1726          40 :     const auto nColumnPos = osPrefix.find(':');
    1727          40 :     if (nColumnPos == std::string::npos)
    1728          34 :         osPrefix.clear();
    1729             :     else
    1730           6 :         osPrefix.resize(nColumnPos);
    1731             : 
    1732             :     OGRWFSLayer *poRefLayer =
    1733          40 :         dynamic_cast<OGRWFSLayer *>(GetLayerByName(pszLayerName));
    1734          40 :     if (poRefLayer == nullptr)
    1735           0 :         return;
    1736             : 
    1737          40 :     const char *pszRequiredOutputFormat = poRefLayer->GetRequiredOutputFormat();
    1738             : 
    1739             : #if USE_GET_FOR_DESCRIBE_FEATURE_TYPE == 1
    1740          40 :     CPLString osLayerToFetch(pszLayerName);
    1741             : #else
    1742             :     CPLString osTypeNameToPost;
    1743             :     osTypeNameToPost += "  <TypeName>";
    1744             :     osTypeNameToPost += pszLayerName;
    1745             :     osTypeNameToPost += "</TypeName>\n";
    1746             : #endif
    1747             : 
    1748          40 :     int nLayersToFetch = 1;
    1749          40 :     aoSetAlreadyTriedLayers.insert(pszLayerName);
    1750             : 
    1751         124 :     for (int i = 0; i < nLayers; i++)
    1752             :     {
    1753          84 :         if (!papoLayers[i]->HasLayerDefn())
    1754             :         {
    1755             :             /* We must be careful to requests only layers with the same
    1756             :              * prefix/namespace */
    1757          84 :             const char *l_pszName = papoLayers[i]->GetName();
    1758         156 :             if (((osPrefix.empty() && strchr(l_pszName, ':') == nullptr) ||
    1759          12 :                  (!osPrefix.empty() &&
    1760          12 :                   strncmp(l_pszName, osPrefix.c_str(), osPrefix.size()) == 0 &&
    1761         250 :                   l_pszName[osPrefix.size()] == ':')) &&
    1762          82 :                 ((pszRequiredOutputFormat == nullptr &&
    1763          82 :                   papoLayers[i]->GetRequiredOutputFormat() == nullptr) ||
    1764           0 :                  (pszRequiredOutputFormat != nullptr &&
    1765           0 :                   papoLayers[i]->GetRequiredOutputFormat() != nullptr &&
    1766           0 :                   strcmp(pszRequiredOutputFormat,
    1767           0 :                          papoLayers[i]->GetRequiredOutputFormat()) == 0)))
    1768             :             {
    1769          82 :                 if (aoSetAlreadyTriedLayers.find(l_pszName) !=
    1770         164 :                     aoSetAlreadyTriedLayers.end())
    1771          40 :                     continue;
    1772          42 :                 aoSetAlreadyTriedLayers.insert(l_pszName);
    1773             : 
    1774             : #if USE_GET_FOR_DESCRIBE_FEATURE_TYPE == 1
    1775          42 :                 if (nLayersToFetch > 0)
    1776          42 :                     osLayerToFetch += ",";
    1777          42 :                 osLayerToFetch += papoLayers[i]->GetName();
    1778             : #else
    1779             :                 osTypeNameToPost += "  <TypeName>";
    1780             :                 osTypeNameToPost += l_pszName;
    1781             :                 osTypeNameToPost += "</TypeName>\n";
    1782             : #endif
    1783          42 :                 nLayersToFetch++;
    1784             : 
    1785             :                 /* Avoid fetching to many layer definition at a time */
    1786          42 :                 if (nLayersToFetch >= 50)
    1787           0 :                     break;
    1788             :             }
    1789             :         }
    1790             :     }
    1791             : 
    1792             : #if USE_GET_FOR_DESCRIBE_FEATURE_TYPE == 1
    1793          40 :     CPLString osURL(osBaseURL);
    1794          40 :     osURL = CPLURLAddKVP(osURL, "SERVICE", "WFS");
    1795          40 :     osURL = CPLURLAddKVP(osURL, "VERSION", GetVersion());
    1796          40 :     osURL = CPLURLAddKVP(osURL, "REQUEST", "DescribeFeatureType");
    1797          40 :     osURL = CPLURLAddKVP(osURL, "TYPENAME", WFS_EscapeURL(osLayerToFetch));
    1798          40 :     osURL = CPLURLAddKVP(osURL, "PROPERTYNAME", nullptr);
    1799          40 :     osURL = CPLURLAddKVP(osURL, "MAXFEATURES", nullptr);
    1800          40 :     osURL = CPLURLAddKVP(osURL, "FILTER", nullptr);
    1801          80 :     osURL = CPLURLAddKVP(osURL, "OUTPUTFORMAT",
    1802             :                          pszRequiredOutputFormat
    1803          40 :                              ? WFS_EscapeURL(pszRequiredOutputFormat).c_str()
    1804          40 :                              : nullptr);
    1805             : 
    1806          40 :     if (pszNS && GetNeedNAMESPACE())
    1807             :     {
    1808             :         /* Older Deegree version require NAMESPACE */
    1809             :         /* This has been now corrected */
    1810           0 :         CPLString osValue("xmlns(");
    1811           0 :         osValue += pszNS;
    1812           0 :         osValue += "=";
    1813           0 :         osValue += pszNSVal;
    1814           0 :         osValue += ")";
    1815           0 :         osURL = CPLURLAddKVP(osURL, "NAMESPACE", WFS_EscapeURL(osValue));
    1816             :     }
    1817             : 
    1818          40 :     CPLHTTPResult *psResult = HTTPFetch(osURL, nullptr);
    1819             : #else
    1820             :     CPLString osPost;
    1821             :     osPost += "<?xml version=\"1.0\"?>\n";
    1822             :     osPost +=
    1823             :         "<wfs:DescribeFeatureType xmlns:wfs=\"http://www.opengis.net/wfs\"\n";
    1824             :     osPost += "                 "
    1825             :               "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n";
    1826             :     osPost += "                 service=\"WFS\" version=\"";
    1827             :     osPost += GetVersion();
    1828             :     osPost += "\"\n";
    1829             :     osPost += "                 xmlns:gml=\"http://www.opengis.net/gml\"\n";
    1830             :     osPost += "                 xmlns:ogc=\"http://www.opengis.net/ogc\"\n";
    1831             :     if (pszNS && pszNSVal)
    1832             :     {
    1833             :         osPost += "                 xmlns:";
    1834             :         osPost += pszNS;
    1835             :         osPost += "=\"";
    1836             :         osPost += pszNSVal;
    1837             :         osPost += "\"\n";
    1838             :     }
    1839             :     osPost +=
    1840             :         "                 xsi:schemaLocation=\"http://www.opengis.net/wfs "
    1841             :         "http://schemas.opengis.net/wfs/";
    1842             :     osPost += GetVersion();
    1843             :     osPost += "/wfs.xsd\"";
    1844             :     const char *pszRequiredOutputFormat = poRefLayer->GetRequiredOutputFormat();
    1845             :     if (pszRequiredOutputFormat)
    1846             :     {
    1847             :         osPost += "\n";
    1848             :         osPost += "                 outputFormat=\"";
    1849             :         osPost += pszRequiredOutputFormat;
    1850             :         osPost += "\"";
    1851             :     }
    1852             :     osPost += ">\n";
    1853             :     osPost += osTypeNameToPost;
    1854             :     osPost += "</wfs:DescribeFeatureType>\n";
    1855             : 
    1856             :     // CPLDebug("WFS", "%s", osPost.c_str());
    1857             : 
    1858             :     char **papszOptions = NULL;
    1859             :     papszOptions = CSLAddNameValue(papszOptions, "POSTFIELDS", osPost.c_str());
    1860             :     papszOptions =
    1861             :         CSLAddNameValue(papszOptions, "HEADERS",
    1862             :                         "Content-Type: application/xml; charset=UTF-8");
    1863             : 
    1864             :     CPLHTTPResult *psResult = HTTPFetch(GetPostTransactionURL(), papszOptions);
    1865             :     CSLDestroy(papszOptions);
    1866             : #endif
    1867             : 
    1868          40 :     if (psResult == nullptr)
    1869             :     {
    1870           4 :         bLoadMultipleLayerDefn = false;
    1871           4 :         return;
    1872             :     }
    1873             : 
    1874          36 :     if (strstr((const char *)psResult->pabyData, "<ServiceExceptionReport") !=
    1875             :         nullptr)
    1876             :     {
    1877           2 :         if (IsOldDeegree((const char *)psResult->pabyData))
    1878             :         {
    1879             :             /* just silently forgive */
    1880             :         }
    1881             :         else
    1882             :         {
    1883           2 :             CPLError(CE_Failure, CPLE_AppDefined,
    1884             :                      "Error returned by server : %s", psResult->pabyData);
    1885             :         }
    1886           2 :         CPLHTTPDestroyResult(psResult);
    1887           2 :         bLoadMultipleLayerDefn = false;
    1888           2 :         return;
    1889             :     }
    1890             : 
    1891          34 :     CPLXMLNode *psXML = CPLParseXMLString((const char *)psResult->pabyData);
    1892          34 :     if (psXML == nullptr)
    1893             :     {
    1894           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
    1895             :                  psResult->pabyData);
    1896           2 :         CPLHTTPDestroyResult(psResult);
    1897           2 :         bLoadMultipleLayerDefn = false;
    1898           2 :         return;
    1899             :     }
    1900          32 :     CPLHTTPDestroyResult(psResult);
    1901             : 
    1902          32 :     const CPLXMLNode *psSchema = WFSFindNode(psXML, "schema");
    1903          32 :     if (psSchema == nullptr)
    1904             :     {
    1905           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find <Schema>");
    1906           2 :         CPLDestroyXMLNode(psXML);
    1907           2 :         bLoadMultipleLayerDefn = false;
    1908           2 :         return;
    1909             :     }
    1910             : 
    1911          60 :     CPLString osTmpFileName;
    1912             : 
    1913          30 :     osTmpFileName = CPLSPrintf("/vsimem/tempwfs_%p/file.xsd", this);
    1914          30 :     CPLSerializeXMLTreeToFile(psSchema, osTmpFileName);
    1915             : 
    1916          60 :     std::vector<GMLFeatureClass *> aosClasses;
    1917          30 :     bool bFullyUnderstood = false;
    1918          30 :     GMLParseXSD(osTmpFileName, aosClasses, bFullyUnderstood);
    1919             : 
    1920          30 :     int nLayersFound = 0;
    1921          30 :     if (!(int)aosClasses.empty())
    1922             :     {
    1923             :         std::vector<GMLFeatureClass *>::const_iterator oIter =
    1924          30 :             aosClasses.begin();
    1925             :         std::vector<GMLFeatureClass *>::const_iterator oEndIter =
    1926          30 :             aosClasses.end();
    1927          86 :         while (oIter != oEndIter)
    1928             :         {
    1929          56 :             GMLFeatureClass *poClass = *oIter;
    1930          56 :             ++oIter;
    1931             : 
    1932          56 :             OGRWFSLayer *poLayer = nullptr;
    1933             : 
    1934          56 :             if (bKeepLayerNamePrefix && pszNS != nullptr &&
    1935           0 :                 strchr(poClass->GetName(), ':') == nullptr)
    1936             :             {
    1937           0 :                 CPLString osWithPrefix(pszNS);
    1938           0 :                 osWithPrefix += ":";
    1939           0 :                 osWithPrefix += poClass->GetName();
    1940           0 :                 poLayer = (OGRWFSLayer *)GetLayerByName(osWithPrefix);
    1941             :             }
    1942             :             else
    1943          56 :                 poLayer = (OGRWFSLayer *)GetLayerByName(poClass->GetName());
    1944             : 
    1945          56 :             if (poLayer)
    1946             :             {
    1947          54 :                 if (!poLayer->HasLayerDefn())
    1948             :                 {
    1949          54 :                     nLayersFound++;
    1950             : 
    1951          54 :                     CPLXMLNode *psSchemaForLayer = CPLCloneXMLTree(psSchema);
    1952          54 :                     CPLStripXMLNamespace(psSchemaForLayer, nullptr, TRUE);
    1953          54 :                     CPLXMLNode *psIter = psSchemaForLayer->psChild;
    1954          54 :                     bool bHasAlreadyImportedGML = false;
    1955          54 :                     bool bFoundComplexType = false;
    1956          54 :                     bool bFoundElement = false;
    1957         590 :                     while (psIter != nullptr)
    1958             :                     {
    1959         536 :                         CPLXMLNode *psIterNext = psIter->psNext;
    1960         536 :                         if (psIter->eType == CXT_Element &&
    1961         266 :                             strcmp(psIter->pszValue, "complexType") == 0)
    1962             :                         {
    1963             :                             const char *l_pszName =
    1964         106 :                                 CPLGetXMLValue(psIter, "name", "");
    1965         212 :                             CPLString osExpectedName(poLayer->GetShortName());
    1966         106 :                             osExpectedName += "Type";
    1967         212 :                             CPLString osExpectedName2(poLayer->GetShortName());
    1968         106 :                             osExpectedName2 += "_Type";
    1969         106 :                             if (strcmp(l_pszName, osExpectedName) == 0 ||
    1970         158 :                                 strcmp(l_pszName, osExpectedName2) == 0 ||
    1971          52 :                                 strcmp(l_pszName, poLayer->GetShortName()) == 0)
    1972             :                             {
    1973          54 :                                 bFoundComplexType = true;
    1974             :                             }
    1975             :                             else
    1976             :                             {
    1977          52 :                                 CPLRemoveXMLChild(psSchemaForLayer, psIter);
    1978          52 :                                 CPLDestroyXMLNode(psIter);
    1979         106 :                             }
    1980             :                         }
    1981         430 :                         else if (psIter->eType == CXT_Element &&
    1982         160 :                                  strcmp(psIter->pszValue, "element") == 0)
    1983             :                         {
    1984             :                             const char *l_pszName =
    1985         106 :                                 CPLGetXMLValue(psIter, "name", "");
    1986         212 :                             CPLString osExpectedName(poLayer->GetShortName());
    1987         106 :                             osExpectedName += "Type";
    1988         212 :                             CPLString osExpectedName2(poLayer->GetShortName());
    1989         106 :                             osExpectedName2 += "_Type";
    1990             : 
    1991             :                             const char *pszType =
    1992         106 :                                 CPLGetXMLValue(psIter, "type", "");
    1993         212 :                             CPLString osExpectedType(poLayer->GetName());
    1994         106 :                             osExpectedType += "Type";
    1995         212 :                             CPLString osExpectedType2(poLayer->GetName());
    1996         106 :                             osExpectedType2 += "_Type";
    1997         106 :                             if (strcmp(pszType, osExpectedType) == 0 ||
    1998          98 :                                 strcmp(pszType, osExpectedType2) == 0 ||
    1999         302 :                                 strcmp(pszType, poLayer->GetName()) == 0 ||
    2000          98 :                                 (strchr(pszType, ':') &&
    2001          98 :                                  (strcmp(strchr(pszType, ':') + 1,
    2002          52 :                                          osExpectedType) == 0 ||
    2003          52 :                                   strcmp(strchr(pszType, ':') + 1,
    2004             :                                          osExpectedType2) == 0)))
    2005             :                             {
    2006          54 :                                 bFoundElement = true;
    2007             :                             }
    2008         104 :                             else if (*pszType == '\0' &&
    2009           0 :                                      CPLGetXMLNode(psIter, "complexType") !=
    2010          52 :                                          nullptr &&
    2011           0 :                                      (strcmp(l_pszName, osExpectedName) == 0 ||
    2012           0 :                                       strcmp(l_pszName, osExpectedName2) == 0 ||
    2013           0 :                                       strcmp(l_pszName,
    2014             :                                              poLayer->GetShortName()) == 0))
    2015             :                             {
    2016           0 :                                 bFoundElement = true;
    2017           0 :                                 bFoundComplexType = true;
    2018             :                             }
    2019             :                             else
    2020             :                             {
    2021          52 :                                 CPLRemoveXMLChild(psSchemaForLayer, psIter);
    2022          52 :                                 CPLDestroyXMLNode(psIter);
    2023         106 :                             }
    2024             :                         }
    2025         702 :                         else if (psIter->eType == CXT_Element &&
    2026         378 :                                  strcmp(psIter->pszValue, "import") == 0 &&
    2027          54 :                                  strcmp(CPLGetXMLValue(psIter, "namespace", ""),
    2028             :                                         "http://www.opengis.net/gml") == 0)
    2029             :                         {
    2030          54 :                             if (bHasAlreadyImportedGML)
    2031             :                             {
    2032           0 :                                 CPLRemoveXMLChild(psSchemaForLayer, psIter);
    2033           0 :                                 CPLDestroyXMLNode(psIter);
    2034             :                             }
    2035             :                             else
    2036             :                             {
    2037          54 :                                 bHasAlreadyImportedGML = true;
    2038             :                             }
    2039             :                         }
    2040         536 :                         psIter = psIterNext;
    2041             :                     }
    2042             : 
    2043          54 :                     if (bFoundComplexType && bFoundElement)
    2044             :                     {
    2045             :                         OGRFeatureDefn *poSrcFDefn =
    2046          54 :                             poLayer->ParseSchema(psSchemaForLayer);
    2047          54 :                         if (poSrcFDefn)
    2048             :                         {
    2049          54 :                             poLayer->BuildLayerDefn(poSrcFDefn);
    2050          54 :                             SaveLayerSchema(poLayer->GetName(),
    2051             :                                             psSchemaForLayer);
    2052             :                         }
    2053             :                     }
    2054             : 
    2055          54 :                     CPLDestroyXMLNode(psSchemaForLayer);
    2056             :                 }
    2057             :                 else
    2058             :                 {
    2059           0 :                     CPLDebug("WFS",
    2060             :                              "Found several time schema for layer %s in "
    2061             :                              "server response. Should not happen",
    2062             :                              poClass->GetName());
    2063             :                 }
    2064             :             }
    2065          56 :             delete poClass;
    2066             :         }
    2067             :     }
    2068             : 
    2069          30 :     if (nLayersFound != nLayersToFetch)
    2070             :     {
    2071           4 :         CPLDebug("WFS", "Turn off loading of multiple layer definitions at a "
    2072             :                         "single time");
    2073           4 :         bLoadMultipleLayerDefn = false;
    2074             :     }
    2075             : 
    2076          30 :     VSIUnlink(osTmpFileName);
    2077             : 
    2078          30 :     CPLDestroyXMLNode(psXML);
    2079             : }
    2080             : 
    2081             : /************************************************************************/
    2082             : /*                         SaveLayerSchema()                            */
    2083             : /************************************************************************/
    2084             : 
    2085         130 : void OGRWFSDataSource::SaveLayerSchema(const char *pszLayerName,
    2086             :                                        const CPLXMLNode *psSchema)
    2087             : {
    2088         130 :     if (psFileXML != nullptr)
    2089             :     {
    2090           1 :         bRewriteFile = true;
    2091             :         CPLXMLNode *psLayerNode =
    2092           1 :             CPLCreateXMLNode(nullptr, CXT_Element, "OGRWFSLayer");
    2093           1 :         CPLSetXMLValue(psLayerNode, "#name", pszLayerName);
    2094           1 :         CPLAddXMLChild(psLayerNode, CPLCloneXMLTree(psSchema));
    2095           1 :         CPLAddXMLChild(psFileXML, psLayerNode);
    2096             :     }
    2097         130 : }
    2098             : 
    2099             : /************************************************************************/
    2100             : /*                           IsOldDeegree()                             */
    2101             : /************************************************************************/
    2102             : 
    2103           5 : bool OGRWFSDataSource::IsOldDeegree(const char *pszErrorString)
    2104             : {
    2105           5 :     if (!bNeedNAMESPACE &&
    2106           5 :         strstr(pszErrorString, "Invalid \"TYPENAME\" parameter. "
    2107             :                                "No binding for prefix") != nullptr)
    2108             :     {
    2109           0 :         bNeedNAMESPACE = true;
    2110           0 :         return true;
    2111             :     }
    2112           5 :     return false;
    2113             : }
    2114             : 
    2115             : /************************************************************************/
    2116             : /*                         WFS_EscapeURL()                              */
    2117             : /************************************************************************/
    2118             : 
    2119         540 : CPLString WFS_EscapeURL(const char *pszURL)
    2120             : {
    2121         540 :     CPLString osEscapedURL;
    2122             : 
    2123             :     /* Difference with CPLEscapeString(, CPLES_URL) : we do not escape */
    2124             :     /* colon (:) or comma (,). Causes problems with servers such as
    2125             :      * http://www.mapinfo.com/miwfs? */
    2126             : 
    2127       36372 :     for (int i = 0; pszURL[i] != '\0'; i++)
    2128             :     {
    2129       35832 :         char ch = pszURL[i];
    2130       35832 :         if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
    2131        9724 :             (ch >= '0' && ch <= '9') || ch == '_' || ch == '.' || ch == ':' ||
    2132             :             ch == ',')
    2133             :         {
    2134       30024 :             osEscapedURL += ch;
    2135             :         }
    2136             :         else
    2137             :         {
    2138             :             char szPercentEncoded[10];
    2139        5808 :             snprintf(szPercentEncoded, sizeof(szPercentEncoded), "%%%02X",
    2140        5808 :                      ((unsigned char *)pszURL)[i]);
    2141        5808 :             osEscapedURL += szPercentEncoded;
    2142             :         }
    2143             :     }
    2144             : 
    2145         540 :     return osEscapedURL;
    2146             : }
    2147             : 
    2148             : /************************************************************************/
    2149             : /*                         WFS_DecodeURL()                              */
    2150             : /************************************************************************/
    2151             : 
    2152         138 : CPLString WFS_DecodeURL(const CPLString &osSrc)
    2153             : {
    2154         138 :     CPLString ret;
    2155         138 :     for (size_t i = 0; i < osSrc.length(); i++)
    2156             :     {
    2157           0 :         if (osSrc[i] == '%' && i + 2 < osSrc.length())
    2158             :         {
    2159           0 :             unsigned int ii = 0;
    2160           0 :             sscanf(osSrc.substr(i + 1, 2).c_str(), "%x", &ii);
    2161           0 :             char ch = static_cast<char>(ii);
    2162           0 :             ret += ch;
    2163           0 :             i = i + 2;
    2164             :         }
    2165             :         else
    2166             :         {
    2167           0 :             ret += osSrc[i];
    2168             :         }
    2169             :     }
    2170         138 :     return ret;
    2171             : }
    2172             : 
    2173             : /************************************************************************/
    2174             : /*                            HTTPFetch()                               */
    2175             : /************************************************************************/
    2176             : 
    2177         472 : CPLHTTPResult *OGRWFSDataSource::HTTPFetch(const char *pszURL,
    2178             :                                            char **papszOptions)
    2179             : {
    2180         472 :     char **papszNewOptions = CSLDuplicate(papszOptions);
    2181         472 :     if (bUseHttp10)
    2182             :         papszNewOptions =
    2183           0 :             CSLAddNameValue(papszNewOptions, "HTTP_VERSION", "1.0");
    2184         472 :     if (papszHttpOptions)
    2185           0 :         papszNewOptions = CSLMerge(papszNewOptions, papszHttpOptions);
    2186         472 :     CPLHTTPResult *psResult = CPLHTTPFetch(pszURL, papszNewOptions);
    2187         472 :     CSLDestroy(papszNewOptions);
    2188             : 
    2189         472 :     if (psResult == nullptr)
    2190             :     {
    2191           0 :         return nullptr;
    2192             :     }
    2193         472 :     if (psResult->nStatus != 0 || psResult->pszErrBuf != nullptr)
    2194             :     {
    2195             :         // A few buggy servers return chunked data with erroneous
    2196             :         // remaining bytes value curl does not like this. Retry with
    2197             :         // HTTP 1.0 protocol instead that does not support chunked
    2198             :         // data.
    2199          65 :         if (psResult->pszErrBuf &&
    2200          65 :             strstr(psResult->pszErrBuf,
    2201           0 :                    "transfer closed with outstanding read data remaining") &&
    2202           0 :             !bUseHttp10)
    2203             :         {
    2204           0 :             CPLDebug("WFS", "Probably buggy remote server. Retrying with HTTP "
    2205             :                             "1.0 protocol");
    2206           0 :             bUseHttp10 = true;
    2207           0 :             CPLHTTPDestroyResult(psResult);
    2208           0 :             return HTTPFetch(pszURL, papszOptions);
    2209             :         }
    2210             : 
    2211          65 :         CPLError(CE_Failure, CPLE_AppDefined,
    2212             :                  "Error returned by server : %s (%d)",
    2213          65 :                  (psResult->pszErrBuf) ? psResult->pszErrBuf : "unknown",
    2214             :                  psResult->nStatus);
    2215          65 :         CPLHTTPDestroyResult(psResult);
    2216          65 :         return nullptr;
    2217             :     }
    2218         407 :     if (psResult->pabyData == nullptr)
    2219             :     {
    2220          11 :         CPLError(CE_Failure, CPLE_AppDefined,
    2221             :                  "Empty content returned by server");
    2222          11 :         CPLHTTPDestroyResult(psResult);
    2223          11 :         return nullptr;
    2224             :     }
    2225         396 :     return psResult;
    2226             : }
    2227             : 
    2228             : /************************************************************************/
    2229             : /*                             ExecuteSQL()                             */
    2230             : /************************************************************************/
    2231             : 
    2232          60 : OGRLayer *OGRWFSDataSource::ExecuteSQL(const char *pszSQLCommand,
    2233             :                                        OGRGeometry *poSpatialFilter,
    2234             :                                        const char *pszDialect)
    2235             : 
    2236             : {
    2237          60 :     while (*pszSQLCommand &&
    2238          60 :            isspace(static_cast<unsigned char>(*pszSQLCommand)))
    2239           0 :         ++pszSQLCommand;
    2240             : 
    2241          60 :     swq_select_parse_options oParseOptions;
    2242          60 :     oParseOptions.poCustomFuncRegistrar = WFSGetCustomFuncRegistrar();
    2243             : 
    2244             :     /* -------------------------------------------------------------------- */
    2245             :     /*      Use generic implementation for recognized dialects              */
    2246             :     /* -------------------------------------------------------------------- */
    2247          60 :     if (IsGenericSQLDialect(pszDialect))
    2248             :     {
    2249           0 :         OGRLayer *poResLayer = GDALDataset::ExecuteSQL(
    2250           0 :             pszSQLCommand, poSpatialFilter, pszDialect, &oParseOptions);
    2251           0 :         oMap[poResLayer] = nullptr;
    2252           0 :         return poResLayer;
    2253             :     }
    2254             : 
    2255             :     /* -------------------------------------------------------------------- */
    2256             :     /*      Deal with "SELECT _LAST_INSERTED_FIDS_ FROM layername" statement */
    2257             :     /* -------------------------------------------------------------------- */
    2258          60 :     if (STARTS_WITH_CI(pszSQLCommand, "SELECT _LAST_INSERTED_FIDS_ FROM "))
    2259             :     {
    2260           6 :         const char *pszIter = pszSQLCommand + 33;
    2261          74 :         while (*pszIter && *pszIter != ' ')
    2262          68 :             pszIter++;
    2263             : 
    2264          12 :         CPLString osName = pszSQLCommand + 33;
    2265           6 :         osName.resize(pszIter - (pszSQLCommand + 33));
    2266           6 :         OGRWFSLayer *poLayer = (OGRWFSLayer *)GetLayerByName(osName);
    2267           6 :         if (poLayer == nullptr)
    2268             :         {
    2269           2 :             CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer : %s",
    2270             :                      osName.c_str());
    2271           2 :             return nullptr;
    2272             :         }
    2273             : 
    2274             :         GDALDriver *poMEMDrv =
    2275           4 :             OGRSFDriverRegistrar::GetRegistrar()->GetDriverByName("Memory");
    2276           4 :         if (poMEMDrv == nullptr)
    2277             :         {
    2278           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2279             :                      "Cannot load 'Memory' driver");
    2280           0 :             return nullptr;
    2281             :         }
    2282             : 
    2283             :         GDALDataset *poMEMDS =
    2284           4 :             poMEMDrv->Create("dummy_name", 0, 0, 0, GDT_Unknown, nullptr);
    2285             :         OGRLayer *poMEMLayer =
    2286           4 :             poMEMDS->CreateLayer("FID_LIST", nullptr, wkbNone, nullptr);
    2287           4 :         OGRFieldDefn oFDefn("gml_id", OFTString);
    2288           4 :         poMEMLayer->CreateField(&oFDefn);
    2289             : 
    2290             :         const std::vector<CPLString> &aosFIDList =
    2291           4 :             poLayer->GetLastInsertedFIDList();
    2292           4 :         std::vector<CPLString>::const_iterator oIter = aosFIDList.begin();
    2293           4 :         std::vector<CPLString>::const_iterator oEndIter = aosFIDList.end();
    2294           6 :         while (oIter != oEndIter)
    2295             :         {
    2296           2 :             const CPLString &osFID = *oIter;
    2297           2 :             OGRFeature *poFeature = new OGRFeature(poMEMLayer->GetLayerDefn());
    2298           2 :             poFeature->SetField(0, osFID);
    2299           2 :             CPL_IGNORE_RET_VAL(poMEMLayer->CreateFeature(poFeature));
    2300           2 :             delete poFeature;
    2301           2 :             ++oIter;
    2302             :         }
    2303             : 
    2304             :         OGRLayer *poResLayer =
    2305           4 :             new OGRWFSWrappedResultLayer(poMEMDS, poMEMLayer);
    2306           4 :         oMap[poResLayer] = nullptr;
    2307           4 :         return poResLayer;
    2308             :     }
    2309             : 
    2310             :     /* -------------------------------------------------------------------- */
    2311             :     /*      Deal with "DELETE FROM layer_name WHERE expression" statement   */
    2312             :     /* -------------------------------------------------------------------- */
    2313          54 :     if (STARTS_WITH_CI(pszSQLCommand, "DELETE FROM "))
    2314             :     {
    2315          12 :         const char *pszIter = pszSQLCommand + 12;
    2316         112 :         while (*pszIter && *pszIter != ' ')
    2317         100 :             pszIter++;
    2318          12 :         if (*pszIter == 0)
    2319             :         {
    2320           2 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid statement");
    2321           2 :             return nullptr;
    2322             :         }
    2323             : 
    2324          20 :         CPLString osName = pszSQLCommand + 12;
    2325          10 :         osName.resize(pszIter - (pszSQLCommand + 12));
    2326          10 :         OGRWFSLayer *poLayer = (OGRWFSLayer *)GetLayerByName(osName);
    2327          10 :         if (poLayer == nullptr)
    2328             :         {
    2329           2 :             CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer : %s",
    2330             :                      osName.c_str());
    2331           2 :             return nullptr;
    2332             :         }
    2333             : 
    2334          16 :         while (*pszIter == ' ')
    2335           8 :             pszIter++;
    2336           8 :         if (!STARTS_WITH_CI(pszIter, "WHERE "))
    2337             :         {
    2338           2 :             CPLError(CE_Failure, CPLE_AppDefined, "WHERE clause missing");
    2339           2 :             return nullptr;
    2340             :         }
    2341           6 :         pszIter += 5;
    2342             : 
    2343           6 :         const char *pszQuery = pszIter;
    2344             : 
    2345             :         /* Check with the generic SQL engine that this is a valid WHERE clause
    2346             :          */
    2347          12 :         OGRFeatureQuery oQuery;
    2348           6 :         OGRErr eErr = oQuery.Compile(poLayer->GetLayerDefn(), pszQuery);
    2349           6 :         if (eErr != OGRERR_NONE)
    2350             :         {
    2351           2 :             return nullptr;
    2352             :         }
    2353             : 
    2354             :         /* Now turn this into OGC Filter language if possible */
    2355           4 :         int bNeedsNullCheck = FALSE;
    2356           4 :         int nVersion = (strcmp(GetVersion(), "1.0.0") == 0) ? 100 : 110;
    2357           4 :         swq_expr_node *poNode = (swq_expr_node *)oQuery.GetSWQExpr();
    2358           4 :         poNode->ReplaceBetweenByGEAndLERecurse();
    2359             :         CPLString osOGCFilter = WFS_TurnSQLFilterToOGCFilter(
    2360             :             poNode, nullptr, poLayer->GetLayerDefn(), nVersion,
    2361           4 :             bPropertyIsNotEqualToSupported, bUseFeatureId,
    2362           8 :             bGmlObjectIdNeedsGMLPrefix, "", &bNeedsNullCheck);
    2363           4 :         if (bNeedsNullCheck && !HasNullCheck())
    2364           0 :             osOGCFilter = "";
    2365             : 
    2366           4 :         if (osOGCFilter.empty())
    2367             :         {
    2368           2 :             CPLError(CE_Failure, CPLE_AppDefined,
    2369             :                      "Cannot convert WHERE clause into a OGC filter");
    2370           2 :             return nullptr;
    2371             :         }
    2372             : 
    2373           2 :         poLayer->DeleteFromFilter(osOGCFilter);
    2374             : 
    2375           2 :         return nullptr;
    2376             :     }
    2377             : 
    2378             :     /* -------------------------------------------------------------------- */
    2379             :     /*      Deal with "SELECT xxxx ORDER BY" statement                      */
    2380             :     /* -------------------------------------------------------------------- */
    2381          42 :     if (STARTS_WITH_CI(pszSQLCommand, "SELECT"))
    2382             :     {
    2383          42 :         swq_select *psSelectInfo = new swq_select();
    2384          42 :         if (psSelectInfo->preparse(pszSQLCommand, TRUE) != CE_None)
    2385             :         {
    2386           0 :             delete psSelectInfo;
    2387           0 :             return nullptr;
    2388             :         }
    2389          42 :         int iLayer = 0;
    2390          42 :         if (strcmp(GetVersion(), "1.0.0") != 0 &&
    2391          42 :             psSelectInfo->table_count == 1 &&
    2392          16 :             psSelectInfo->table_defs[0].data_source == nullptr &&
    2393           8 :             (iLayer = GetLayerIndex(psSelectInfo->table_defs[0].table_name)) >=
    2394           8 :                 0 &&
    2395          84 :             psSelectInfo->join_count == 0 && psSelectInfo->order_specs > 0 &&
    2396           0 :             psSelectInfo->poOtherSelect == nullptr)
    2397             :         {
    2398           0 :             OGRWFSLayer *poSrcLayer = papoLayers[iLayer];
    2399           0 :             std::vector<OGRWFSSortDesc> aoSortColumns;
    2400           0 :             int i = 0;  // Used after for.
    2401           0 :             for (; i < psSelectInfo->order_specs; i++)
    2402             :             {
    2403           0 :                 int nFieldIndex = poSrcLayer->GetLayerDefn()->GetFieldIndex(
    2404           0 :                     psSelectInfo->order_defs[i].field_name);
    2405           0 :                 if (poSrcLayer->HasGotApproximateLayerDefn() || nFieldIndex < 0)
    2406           0 :                     break;
    2407             : 
    2408             :                 /* Make sure to have the right case */
    2409           0 :                 const char *pszFieldName = poSrcLayer->GetLayerDefn()
    2410           0 :                                                ->GetFieldDefn(nFieldIndex)
    2411           0 :                                                ->GetNameRef();
    2412             : 
    2413             :                 OGRWFSSortDesc oSortDesc(
    2414           0 :                     pszFieldName, psSelectInfo->order_defs[i].ascending_flag);
    2415           0 :                 aoSortColumns.push_back(oSortDesc);
    2416             :             }
    2417             : 
    2418           0 :             if (i == psSelectInfo->order_specs)
    2419             :             {
    2420           0 :                 OGRWFSLayer *poDupLayer = poSrcLayer->Clone();
    2421             : 
    2422           0 :                 poDupLayer->SetOrderBy(aoSortColumns);
    2423           0 :                 int nBackup = psSelectInfo->order_specs;
    2424           0 :                 psSelectInfo->order_specs = 0;
    2425           0 :                 char *pszSQLWithoutOrderBy = psSelectInfo->Unparse();
    2426           0 :                 CPLDebug("WFS", "SQL without ORDER BY: %s",
    2427             :                          pszSQLWithoutOrderBy);
    2428           0 :                 psSelectInfo->order_specs = nBackup;
    2429           0 :                 delete psSelectInfo;
    2430           0 :                 psSelectInfo = nullptr;
    2431             : 
    2432             :                 /* Just set poDupLayer in the papoLayers for the time of the */
    2433             :                 /* base ExecuteSQL(), so that the OGRGenSQLResultsLayer
    2434             :                  * references */
    2435             :                 /* that temporary layer */
    2436           0 :                 papoLayers[iLayer] = poDupLayer;
    2437             : 
    2438           0 :                 OGRLayer *poResLayer = GDALDataset::ExecuteSQL(
    2439             :                     pszSQLWithoutOrderBy, poSpatialFilter, pszDialect,
    2440           0 :                     &oParseOptions);
    2441           0 :                 papoLayers[iLayer] = poSrcLayer;
    2442             : 
    2443           0 :                 CPLFree(pszSQLWithoutOrderBy);
    2444             : 
    2445           0 :                 if (poResLayer != nullptr)
    2446           0 :                     oMap[poResLayer] = poDupLayer;
    2447             :                 else
    2448           0 :                     delete poDupLayer;
    2449           0 :                 return poResLayer;
    2450             :             }
    2451             :         }
    2452          42 :         else if (bStandardJoinsWFS2 && psSelectInfo->join_count > 0 &&
    2453          34 :                  psSelectInfo->poOtherSelect == nullptr)
    2454             :         {
    2455             :             // Just to make sure everything is valid, but we won't use
    2456             :             // that one as we want to run the join on server-side
    2457          34 :             oParseOptions.bAllowFieldsInSecondaryTablesInWhere = TRUE;
    2458          34 :             oParseOptions.bAddSecondaryTablesGeometryFields = TRUE;
    2459          34 :             oParseOptions.bAlwaysPrefixWithTableName = TRUE;
    2460          34 :             oParseOptions.bAllowDistinctOnGeometryField = TRUE;
    2461          34 :             oParseOptions.bAllowDistinctOnMultipleFields = TRUE;
    2462             :             GDALSQLParseInfo *psParseInfo =
    2463          34 :                 BuildParseInfo(psSelectInfo, &oParseOptions);
    2464          34 :             oParseOptions.bAllowFieldsInSecondaryTablesInWhere = FALSE;
    2465          34 :             oParseOptions.bAddSecondaryTablesGeometryFields = FALSE;
    2466          34 :             oParseOptions.bAlwaysPrefixWithTableName = FALSE;
    2467          34 :             oParseOptions.bAllowDistinctOnGeometryField = FALSE;
    2468          34 :             oParseOptions.bAllowDistinctOnMultipleFields = FALSE;
    2469          34 :             const bool bOK = psParseInfo != nullptr;
    2470          34 :             DestroyParseInfo(psParseInfo);
    2471             : 
    2472          34 :             OGRLayer *poResLayer = nullptr;
    2473          34 :             if (bOK)
    2474             :             {
    2475          34 :                 poResLayer = OGRWFSJoinLayer::Build(this, psSelectInfo);
    2476          34 :                 oMap[poResLayer] = nullptr;
    2477             :             }
    2478             : 
    2479          34 :             delete psSelectInfo;
    2480          34 :             return poResLayer;
    2481             :         }
    2482             : 
    2483           8 :         delete psSelectInfo;
    2484             :     }
    2485             : 
    2486           8 :     OGRLayer *poResLayer = OGRDataSource::ExecuteSQL(
    2487           8 :         pszSQLCommand, poSpatialFilter, pszDialect, &oParseOptions);
    2488           8 :     oMap[poResLayer] = nullptr;
    2489           8 :     return poResLayer;
    2490             : }
    2491             : 
    2492             : /************************************************************************/
    2493             : /*                          ReleaseResultSet()                          */
    2494             : /************************************************************************/
    2495             : 
    2496          40 : void OGRWFSDataSource::ReleaseResultSet(OGRLayer *poResultsSet)
    2497             : {
    2498          40 :     if (poResultsSet == nullptr)
    2499           0 :         return;
    2500             : 
    2501          40 :     std::map<OGRLayer *, OGRLayer *>::iterator oIter = oMap.find(poResultsSet);
    2502          40 :     if (oIter != oMap.end())
    2503             :     {
    2504             :         /* Destroy first the result layer, because it still references */
    2505             :         /* the poDupLayer (oIter->second) */
    2506          40 :         delete poResultsSet;
    2507             : 
    2508          40 :         delete oIter->second;
    2509          40 :         oMap.erase(oIter);
    2510             :     }
    2511             :     else
    2512             :     {
    2513           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2514             :                  "Trying to destroy an invalid result set !");
    2515             :     }
    2516             : }

Generated by: LCOV version 1.14