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