Coverage Report

Created: 2026-11-12 08:28

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