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