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