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