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 : std::unique_ptr<OGRSpatialReference,
1405 : OGRSpatialReferenceReleaser>(
1406 2 : new OGRSpatialReference());
1407 1 : if (poSRS->SetFromUserInput(
1408 : pszDefaultSRS,
1409 : OGRSpatialReference::
1410 1 : SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
1411 : OGRERR_NONE)
1412 : {
1413 1 : aosSupportedCRSList.emplace_back(pszDefaultSRS);
1414 1 : apoSupportedCRSList.emplace_back(std::move(poSRS));
1415 : }
1416 : }
1417 :
1418 : CPLErrorStateBackuper oErrorStateBackuper(
1419 4 : CPLQuietErrorHandler);
1420 8 : for (const CPLXMLNode *psIter = psOtherSRS; psIter;
1421 6 : psIter = psIter->psNext)
1422 : {
1423 6 : if (psIter->eType == CXT_Element)
1424 : {
1425 : const char *pszSRS =
1426 6 : CPLGetXMLValue(psIter, "", nullptr);
1427 6 : if (pszSRS && IsValidCRSName(pszSRS))
1428 : {
1429 : auto poSRS = std::unique_ptr<
1430 : OGRSpatialReference,
1431 : OGRSpatialReferenceReleaser>(
1432 6 : new OGRSpatialReference());
1433 6 : if (poSRS->SetFromUserInput(
1434 3 : EQUAL(pszSRS, "CRS:84") ? "OGC:CRS84"
1435 : : pszSRS,
1436 : OGRSpatialReference::
1437 3 : SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
1438 : OGRERR_NONE)
1439 : {
1440 3 : aosSupportedCRSList.emplace_back(pszSRS);
1441 : apoSupportedCRSList.emplace_back(
1442 3 : std::move(poSRS));
1443 : }
1444 : else
1445 : {
1446 0 : CPLDebug("WFS", "Invalid CRS %s", pszSRS);
1447 : }
1448 : }
1449 : }
1450 : }
1451 : }
1452 :
1453 : CPLXMLNode *psOutputFormats =
1454 213 : CPLGetXMLNode(psChildIter, "OutputFormats");
1455 426 : CPLString osOutputFormat;
1456 213 : if (psOutputFormats)
1457 : {
1458 16 : std::vector<CPLString> osFormats;
1459 8 : CPLXMLNode *psOutputFormatIter = psOutputFormats->psChild;
1460 16 : while (psOutputFormatIter)
1461 : {
1462 8 : if (psOutputFormatIter->eType == CXT_Element &&
1463 8 : EQUAL(psOutputFormatIter->pszValue, "Format") &&
1464 8 : psOutputFormatIter->psChild != nullptr &&
1465 8 : psOutputFormatIter->psChild->eType == CXT_Text)
1466 : {
1467 8 : osFormats.push_back(
1468 8 : psOutputFormatIter->psChild->pszValue);
1469 : }
1470 8 : psOutputFormatIter = psOutputFormatIter->psNext;
1471 : }
1472 :
1473 16 : if (strcmp(osVersion.c_str(), "1.1.0") == 0 &&
1474 8 : !osFormats.empty())
1475 : {
1476 8 : bool bFoundGML31 = false;
1477 8 : for (size_t i = 0; i < osFormats.size(); i++)
1478 : {
1479 8 : if (strstr(osFormats[i].c_str(), "3.1") != nullptr)
1480 : {
1481 8 : bFoundGML31 = true;
1482 8 : break;
1483 : }
1484 : }
1485 :
1486 : /* If we didn't find any mention to GML 3.1, then
1487 : * arbitrarily */
1488 : /* use the first output format */
1489 8 : if (!bFoundGML31)
1490 0 : osOutputFormat = osFormats[0].c_str();
1491 : }
1492 : }
1493 213 : if (osOutputFormat.empty() && bRequestJSON)
1494 0 : osOutputFormat = "json";
1495 :
1496 213 : OGRSpatialReference *poSRS = nullptr;
1497 213 : bool bAxisOrderAlreadyInverted = false;
1498 :
1499 : /* If a SRSNAME parameter has been encoded in the URL, use it as
1500 : * the SRS */
1501 426 : CPLString osSRSName = CPLURLGetValue(osBaseURL, "SRSNAME");
1502 213 : if (!osSRSName.empty())
1503 : {
1504 0 : pszDefaultSRS = osSRSName.c_str();
1505 : }
1506 :
1507 213 : if (pszDefaultSRS)
1508 : {
1509 388 : OGRSpatialReference oSRS;
1510 194 : if (oSRS.SetFromUserInput(
1511 : pszDefaultSRS,
1512 : OGRSpatialReference::
1513 194 : SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
1514 : OGRERR_NONE)
1515 : {
1516 194 : poSRS = oSRS.Clone();
1517 194 : poSRS->SetAxisMappingStrategy(
1518 194 : bInvertAxisOrderIfLatLong
1519 : ? OAMS_TRADITIONAL_GIS_ORDER
1520 : : OAMS_AUTHORITY_COMPLIANT);
1521 361 : if (GML_IsSRSLatLongOrder(pszDefaultSRS) &&
1522 167 : bInvertAxisOrderIfLatLong)
1523 : {
1524 167 : bAxisOrderAlreadyInverted = true;
1525 : }
1526 : }
1527 : }
1528 :
1529 213 : CPLXMLNode *psBBox = nullptr;
1530 213 : CPLXMLNode *psLatLongBBox = nullptr;
1531 : /* bool bFoundBBox = false; */
1532 213 : double dfMinX = 0.0;
1533 213 : double dfMinY = 0.0;
1534 213 : double dfMaxX = 0.0;
1535 213 : double dfMaxY = 0.0;
1536 213 : if ((psBBox = CPLGetXMLNode(psChildIter, "WGS84BoundingBox")) !=
1537 : nullptr)
1538 : {
1539 : const char *pszLC =
1540 195 : CPLGetXMLValue(psBBox, "LowerCorner", nullptr);
1541 : const char *pszUC =
1542 195 : CPLGetXMLValue(psBBox, "UpperCorner", nullptr);
1543 195 : if (pszLC != nullptr && pszUC != nullptr)
1544 : {
1545 390 : CPLString osConcat(pszLC);
1546 195 : osConcat += " ";
1547 195 : osConcat += pszUC;
1548 195 : char **papszTokens = CSLTokenizeStringComplex(
1549 : osConcat, " ,", FALSE, FALSE);
1550 195 : if (CSLCount(papszTokens) == 4)
1551 : {
1552 : // bFoundBBox = true;
1553 195 : dfMinX = CPLAtof(papszTokens[0]);
1554 195 : dfMinY = CPLAtof(papszTokens[1]);
1555 195 : dfMaxX = CPLAtof(papszTokens[2]);
1556 195 : dfMaxY = CPLAtof(papszTokens[3]);
1557 : }
1558 195 : CSLDestroy(papszTokens);
1559 : }
1560 : }
1561 18 : else if ((psLatLongBBox = CPLGetXMLNode(
1562 18 : psChildIter, "LatLongBoundingBox")) != nullptr)
1563 : {
1564 : const char *pszMinX =
1565 0 : CPLGetXMLValue(psLatLongBBox, "minx", nullptr);
1566 : const char *pszMinY =
1567 0 : CPLGetXMLValue(psLatLongBBox, "miny", nullptr);
1568 : const char *pszMaxX =
1569 0 : CPLGetXMLValue(psLatLongBBox, "maxx", nullptr);
1570 : const char *pszMaxY =
1571 0 : CPLGetXMLValue(psLatLongBBox, "maxy", nullptr);
1572 0 : if (pszMinX != nullptr && pszMinY != nullptr &&
1573 0 : pszMaxX != nullptr && pszMaxY != nullptr)
1574 : {
1575 : // bFoundBBox = true;
1576 0 : dfMinX = CPLAtof(pszMinX);
1577 0 : dfMinY = CPLAtof(pszMinY);
1578 0 : dfMaxX = CPLAtof(pszMaxX);
1579 0 : dfMaxY = CPLAtof(pszMaxY);
1580 : }
1581 : }
1582 :
1583 213 : char *pszCSVEscaped = CPLEscapeString(l_pszName, -1, CPLES_CSV);
1584 213 : osLayerMetadataCSV += pszCSVEscaped;
1585 213 : CPLFree(pszCSVEscaped);
1586 :
1587 213 : osLayerMetadataCSV += ",";
1588 213 : if (pszTitle)
1589 : {
1590 34 : pszCSVEscaped = CPLEscapeString(pszTitle, -1, CPLES_CSV);
1591 34 : osLayerMetadataCSV += pszCSVEscaped;
1592 34 : CPLFree(pszCSVEscaped);
1593 : }
1594 213 : osLayerMetadataCSV += ",";
1595 213 : if (pszAbstract)
1596 : {
1597 28 : pszCSVEscaped = CPLEscapeString(pszAbstract, -1, CPLES_CSV);
1598 28 : osLayerMetadataCSV += pszCSVEscaped;
1599 28 : CPLFree(pszCSVEscaped);
1600 : }
1601 213 : osLayerMetadataCSV += "\n";
1602 :
1603 : OGRWFSLayer *poLayer =
1604 : new OGRWFSLayer(this, poSRS, bAxisOrderAlreadyInverted,
1605 213 : osBaseURL, l_pszName, pszNS, pszNSVal);
1606 213 : if (!osOutputFormat.empty())
1607 0 : poLayer->SetRequiredOutputFormat(osOutputFormat);
1608 :
1609 213 : if (pszTitle)
1610 34 : poLayer->SetMetadataItem("TITLE", pszTitle);
1611 213 : if (pszAbstract)
1612 28 : poLayer->SetMetadataItem("ABSTRACT", pszAbstract);
1613 213 : CPLXMLNode *psKeywords = CPLGetXMLNode(psChildIter, "Keywords");
1614 213 : if (psKeywords)
1615 : {
1616 20 : int nKeywordCounter = 1;
1617 20 : for (CPLXMLNode *psKeyword = psKeywords->psChild;
1618 75 : psKeyword != nullptr; psKeyword = psKeyword->psNext)
1619 : {
1620 55 : if (psKeyword->eType == CXT_Element &&
1621 55 : psKeyword->psChild != nullptr)
1622 : {
1623 55 : poLayer->SetMetadataItem(
1624 : CPLSPrintf("KEYWORD_%d", nKeywordCounter),
1625 55 : psKeyword->psChild->pszValue);
1626 55 : nKeywordCounter++;
1627 : }
1628 0 : else if (psKeyword->eType == CXT_Text)
1629 : {
1630 0 : poLayer->SetMetadataItem("KEYWORDS",
1631 0 : psKeyword->pszValue);
1632 : }
1633 : }
1634 : }
1635 :
1636 213 : if (poSRS)
1637 : {
1638 194 : char *pszProj4 = nullptr;
1639 194 : if (poSRS->exportToProj4(&pszProj4) == OGRERR_NONE)
1640 : {
1641 : /* See http://trac.osgeo.org/gdal/ticket/4041 */
1642 388 : const bool bTrustBounds = CPLFetchBool(
1643 : papszOpenOptionsIn, "TRUST_CAPABILITIES_BOUNDS",
1644 194 : CPLTestBool(CPLGetConfigOption(
1645 : "OGR_WFS_TRUST_CAPABILITIES_BOUNDS", "FALSE")));
1646 :
1647 194 : if (((bTrustBounds ||
1648 177 : (dfMinX == -180 && dfMinY == -90 &&
1649 137 : dfMaxX == 180 && dfMaxY == 90)) &&
1650 154 : strcmp(pszProj4,
1651 : "+proj=longlat +datum=WGS84 +no_defs") ==
1652 50 : 0) ||
1653 50 : strcmp(pszDefaultSRS,
1654 : "urn:ogc:def:crs:OGC:1.3:CRS84") == 0)
1655 : {
1656 144 : poLayer->SetWGS84Extents(dfMinX, dfMinY, dfMaxX,
1657 : dfMaxY);
1658 144 : poLayer->SetExtents(dfMinX, dfMinY, dfMaxX, dfMaxY);
1659 : }
1660 :
1661 50 : else if (bTrustBounds)
1662 : {
1663 16 : OGRSpatialReference oWGS84;
1664 8 : oWGS84.SetWellKnownGeogCS("WGS84");
1665 8 : oWGS84.SetAxisMappingStrategy(
1666 : OAMS_TRADITIONAL_GIS_ORDER);
1667 8 : CPLPushErrorHandler(CPLQuietErrorHandler);
1668 : auto poCT =
1669 : std::unique_ptr<OGRCoordinateTransformation>(
1670 : OGRCreateCoordinateTransformation(&oWGS84,
1671 16 : poSRS));
1672 8 : if (poCT)
1673 : {
1674 8 : poLayer->SetWGS84Extents(dfMinX, dfMinY, dfMaxX,
1675 : dfMaxY);
1676 8 : poCT->TransformBounds(dfMinX, dfMinY, dfMaxX,
1677 : dfMaxY, &dfMinX, &dfMinY,
1678 8 : &dfMaxX, &dfMaxY, 20);
1679 8 : poLayer->SetExtents(dfMinX, dfMinY, dfMaxX,
1680 : dfMaxY);
1681 : }
1682 8 : CPLPopErrorHandler();
1683 8 : CPLErrorReset();
1684 : }
1685 : }
1686 194 : CPLFree(pszProj4);
1687 : }
1688 213 : poLayer->SetSupportedSRSList(std::move(aosSupportedCRSList),
1689 213 : std::move(apoSupportedCRSList));
1690 :
1691 426 : papoLayers = static_cast<OGRWFSLayer **>(CPLRealloc(
1692 213 : papoLayers, sizeof(OGRWFSLayer *) * (nLayers + 1)));
1693 213 : papoLayers[nLayers++] = poLayer;
1694 :
1695 213 : if (psFileXML != nullptr)
1696 : {
1697 10 : CPLXMLNode *psIter = psXML->psChild;
1698 36 : while (psIter)
1699 : {
1700 34 : if (psIter->eType == CXT_Element && psIter->psChild &&
1701 82 : EQUAL(psIter->pszValue, "OGRWFSLayer") &&
1702 14 : strcmp(CPLGetXMLValue(psIter, "name", ""),
1703 : l_pszName) == 0)
1704 : {
1705 : const CPLXMLNode *psSchema =
1706 8 : WFSFindNode(psIter->psChild, "schema");
1707 8 : if (psSchema)
1708 : {
1709 : OGRFeatureDefn *poSrcFDefn =
1710 8 : poLayer->ParseSchema(psSchema);
1711 8 : if (poSrcFDefn)
1712 8 : poLayer->BuildLayerDefn(poSrcFDefn);
1713 : }
1714 8 : break;
1715 : }
1716 26 : psIter = psIter->psNext;
1717 : }
1718 : }
1719 : }
1720 : }
1721 : }
1722 :
1723 135 : CSLDestroy(papszTypenames);
1724 :
1725 135 : if (!psFileXML)
1726 129 : CPLDestroyXMLNode(psXML);
1727 135 : CPLDestroyXMLNode(psStrippedXML);
1728 :
1729 135 : return TRUE;
1730 : }
1731 :
1732 : /************************************************************************/
1733 : /* LoadMultipleLayerDefn() */
1734 : /************************************************************************/
1735 :
1736 : /* TinyOWS doesn't support POST, but MapServer, GeoServer and Deegree do */
1737 : #define USE_GET_FOR_DESCRIBE_FEATURE_TYPE 1
1738 :
1739 53 : void OGRWFSDataSource::LoadMultipleLayerDefn(const char *pszLayerName,
1740 : char *pszNS, char *pszNSVal)
1741 : {
1742 53 : if (!bLoadMultipleLayerDefn)
1743 23 : return;
1744 :
1745 44 : if (aoSetAlreadyTriedLayers.find(pszLayerName) !=
1746 88 : aoSetAlreadyTriedLayers.end())
1747 0 : return;
1748 :
1749 44 : std::string osPrefix(pszLayerName);
1750 44 : const auto nColumnPos = osPrefix.find(':');
1751 44 : if (nColumnPos == std::string::npos)
1752 38 : osPrefix.clear();
1753 : else
1754 6 : osPrefix.resize(nColumnPos);
1755 :
1756 : OGRWFSLayer *poRefLayer =
1757 44 : dynamic_cast<OGRWFSLayer *>(GetLayerByName(pszLayerName));
1758 44 : if (poRefLayer == nullptr)
1759 0 : return;
1760 :
1761 44 : const char *pszRequiredOutputFormat = poRefLayer->GetRequiredOutputFormat();
1762 :
1763 : #if USE_GET_FOR_DESCRIBE_FEATURE_TYPE == 1
1764 44 : CPLString osLayerToFetch(pszLayerName);
1765 : #else
1766 : CPLString osTypeNameToPost;
1767 : osTypeNameToPost += " <TypeName>";
1768 : osTypeNameToPost += pszLayerName;
1769 : osTypeNameToPost += "</TypeName>\n";
1770 : #endif
1771 :
1772 44 : int nLayersToFetch = 1;
1773 44 : aoSetAlreadyTriedLayers.insert(pszLayerName);
1774 :
1775 144 : for (int i = 0; i < nLayers; i++)
1776 : {
1777 100 : if (!papoLayers[i]->HasLayerDefn())
1778 : {
1779 : /* We must be careful to requests only layers with the same
1780 : * prefix/namespace */
1781 100 : const char *l_pszName = papoLayers[i]->GetName();
1782 188 : if (((osPrefix.empty() && strchr(l_pszName, ':') == nullptr) ||
1783 12 : (!osPrefix.empty() &&
1784 12 : strncmp(l_pszName, osPrefix.c_str(), osPrefix.size()) == 0 &&
1785 298 : l_pszName[osPrefix.size()] == ':')) &&
1786 98 : ((pszRequiredOutputFormat == nullptr &&
1787 98 : papoLayers[i]->GetRequiredOutputFormat() == nullptr) ||
1788 0 : (pszRequiredOutputFormat != nullptr &&
1789 0 : papoLayers[i]->GetRequiredOutputFormat() != nullptr &&
1790 0 : strcmp(pszRequiredOutputFormat,
1791 0 : papoLayers[i]->GetRequiredOutputFormat()) == 0)))
1792 : {
1793 98 : if (aoSetAlreadyTriedLayers.find(l_pszName) !=
1794 196 : aoSetAlreadyTriedLayers.end())
1795 44 : continue;
1796 54 : aoSetAlreadyTriedLayers.insert(l_pszName);
1797 :
1798 : #if USE_GET_FOR_DESCRIBE_FEATURE_TYPE == 1
1799 54 : if (nLayersToFetch > 0)
1800 54 : osLayerToFetch += ",";
1801 54 : osLayerToFetch += papoLayers[i]->GetName();
1802 : #else
1803 : osTypeNameToPost += " <TypeName>";
1804 : osTypeNameToPost += l_pszName;
1805 : osTypeNameToPost += "</TypeName>\n";
1806 : #endif
1807 54 : nLayersToFetch++;
1808 :
1809 : /* Avoid fetching to many layer definition at a time */
1810 54 : if (nLayersToFetch >= 50)
1811 0 : break;
1812 : }
1813 : }
1814 : }
1815 :
1816 : #if USE_GET_FOR_DESCRIBE_FEATURE_TYPE == 1
1817 44 : CPLString osURL(osBaseURL);
1818 44 : osURL = CPLURLAddKVP(osURL, "SERVICE", "WFS");
1819 44 : osURL = CPLURLAddKVP(osURL, "VERSION", GetVersion());
1820 44 : osURL = CPLURLAddKVP(osURL, "REQUEST", "DescribeFeatureType");
1821 44 : osURL = CPLURLAddKVP(osURL, "TYPENAME", WFS_EscapeURL(osLayerToFetch));
1822 44 : osURL = CPLURLAddKVP(osURL, "PROPERTYNAME", nullptr);
1823 44 : osURL = CPLURLAddKVP(osURL, "MAXFEATURES", nullptr);
1824 44 : osURL = CPLURLAddKVP(osURL, "FILTER", nullptr);
1825 88 : osURL = CPLURLAddKVP(osURL, "OUTPUTFORMAT",
1826 : pszRequiredOutputFormat
1827 44 : ? WFS_EscapeURL(pszRequiredOutputFormat).c_str()
1828 44 : : nullptr);
1829 :
1830 44 : if (pszNS && GetNeedNAMESPACE())
1831 : {
1832 : /* Older Deegree version require NAMESPACE */
1833 : /* This has been now corrected */
1834 0 : CPLString osValue("xmlns(");
1835 0 : osValue += pszNS;
1836 0 : osValue += "=";
1837 0 : osValue += pszNSVal;
1838 0 : osValue += ")";
1839 0 : osURL = CPLURLAddKVP(osURL, "NAMESPACE", WFS_EscapeURL(osValue));
1840 : }
1841 :
1842 44 : CPLHTTPResult *psResult = HTTPFetch(osURL, nullptr);
1843 : #else
1844 : CPLString osPost;
1845 : osPost += "<?xml version=\"1.0\"?>\n";
1846 : osPost +=
1847 : "<wfs:DescribeFeatureType xmlns:wfs=\"http://www.opengis.net/wfs\"\n";
1848 : osPost += " "
1849 : "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n";
1850 : osPost += " service=\"WFS\" version=\"";
1851 : osPost += GetVersion();
1852 : osPost += "\"\n";
1853 : osPost += " xmlns:gml=\"http://www.opengis.net/gml\"\n";
1854 : osPost += " xmlns:ogc=\"http://www.opengis.net/ogc\"\n";
1855 : if (pszNS && pszNSVal)
1856 : {
1857 : osPost += " xmlns:";
1858 : osPost += pszNS;
1859 : osPost += "=\"";
1860 : osPost += pszNSVal;
1861 : osPost += "\"\n";
1862 : }
1863 : osPost +=
1864 : " xsi:schemaLocation=\"http://www.opengis.net/wfs "
1865 : "http://schemas.opengis.net/wfs/";
1866 : osPost += GetVersion();
1867 : osPost += "/wfs.xsd\"";
1868 : const char *pszRequiredOutputFormat = poRefLayer->GetRequiredOutputFormat();
1869 : if (pszRequiredOutputFormat)
1870 : {
1871 : osPost += "\n";
1872 : osPost += " outputFormat=\"";
1873 : osPost += pszRequiredOutputFormat;
1874 : osPost += "\"";
1875 : }
1876 : osPost += ">\n";
1877 : osPost += osTypeNameToPost;
1878 : osPost += "</wfs:DescribeFeatureType>\n";
1879 :
1880 : // CPLDebug("WFS", "%s", osPost.c_str());
1881 :
1882 : CSLConstList papszOptions = NULL;
1883 : papszOptions = CSLAddNameValue(papszOptions, "POSTFIELDS", osPost.c_str());
1884 : papszOptions =
1885 : CSLAddNameValue(papszOptions, "HEADERS",
1886 : "Content-Type: application/xml; charset=UTF-8");
1887 :
1888 : CPLHTTPResult *psResult = HTTPFetch(GetPostTransactionURL(), papszOptions);
1889 : CSLDestroy(papszOptions);
1890 : #endif
1891 :
1892 44 : if (psResult == nullptr)
1893 : {
1894 8 : bLoadMultipleLayerDefn = false;
1895 8 : return;
1896 : }
1897 :
1898 36 : if (strstr(reinterpret_cast<const char *>(psResult->pabyData),
1899 : "<ServiceExceptionReport") != nullptr)
1900 : {
1901 2 : if (IsOldDeegree(reinterpret_cast<const char *>(psResult->pabyData)))
1902 : {
1903 : /* just silently forgive */
1904 : }
1905 : else
1906 : {
1907 2 : CPLError(CE_Failure, CPLE_AppDefined,
1908 : "Error returned by server : %s", psResult->pabyData);
1909 : }
1910 2 : CPLHTTPDestroyResult(psResult);
1911 2 : bLoadMultipleLayerDefn = false;
1912 2 : return;
1913 : }
1914 :
1915 : CPLXMLNode *psXML =
1916 34 : CPLParseXMLString(reinterpret_cast<const char *>(psResult->pabyData));
1917 34 : if (psXML == nullptr)
1918 : {
1919 2 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
1920 : psResult->pabyData);
1921 2 : CPLHTTPDestroyResult(psResult);
1922 2 : bLoadMultipleLayerDefn = false;
1923 2 : return;
1924 : }
1925 32 : CPLHTTPDestroyResult(psResult);
1926 :
1927 32 : const CPLXMLNode *psSchema = WFSFindNode(psXML, "schema");
1928 32 : if (psSchema == nullptr)
1929 : {
1930 2 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find <Schema>");
1931 2 : CPLDestroyXMLNode(psXML);
1932 2 : bLoadMultipleLayerDefn = false;
1933 2 : return;
1934 : }
1935 :
1936 60 : const CPLString osTmpFileName = VSIMemGenerateHiddenFilename("file.xsd");
1937 30 : CPLSerializeXMLTreeToFile(psSchema, osTmpFileName);
1938 :
1939 60 : std::vector<GMLFeatureClass *> aosClasses;
1940 30 : bool bFullyUnderstood = false;
1941 30 : bool bUseSchemaImports = false;
1942 30 : GMLParseXSD(osTmpFileName, bUseSchemaImports, aosClasses, bFullyUnderstood);
1943 :
1944 30 : int nLayersFound = 0;
1945 30 : if (!aosClasses.empty())
1946 : {
1947 : std::vector<GMLFeatureClass *>::const_iterator oIter =
1948 30 : aosClasses.begin();
1949 : std::vector<GMLFeatureClass *>::const_iterator oEndIter =
1950 30 : aosClasses.end();
1951 86 : while (oIter != oEndIter)
1952 : {
1953 56 : GMLFeatureClass *poClass = *oIter;
1954 56 : ++oIter;
1955 :
1956 56 : OGRWFSLayer *poLayer = nullptr;
1957 :
1958 56 : if (bKeepLayerNamePrefix && pszNS != nullptr &&
1959 0 : strchr(poClass->GetName(), ':') == nullptr)
1960 : {
1961 0 : CPLString osWithPrefix(pszNS);
1962 0 : osWithPrefix += ":";
1963 0 : osWithPrefix += poClass->GetName();
1964 0 : poLayer =
1965 0 : dynamic_cast<OGRWFSLayer *>(GetLayerByName(osWithPrefix));
1966 : }
1967 : else
1968 56 : poLayer = dynamic_cast<OGRWFSLayer *>(
1969 56 : GetLayerByName(poClass->GetName()));
1970 :
1971 56 : if (poLayer)
1972 : {
1973 54 : if (!poLayer->HasLayerDefn())
1974 : {
1975 54 : nLayersFound++;
1976 :
1977 54 : CPLXMLNode *psSchemaForLayer = CPLCloneXMLTree(psSchema);
1978 54 : CPLStripXMLNamespace(psSchemaForLayer, nullptr, TRUE);
1979 54 : CPLXMLNode *psIter = psSchemaForLayer->psChild;
1980 54 : bool bHasAlreadyImportedGML = false;
1981 54 : bool bFoundComplexType = false;
1982 54 : bool bFoundElement = false;
1983 590 : while (psIter != nullptr)
1984 : {
1985 536 : CPLXMLNode *psIterNext = psIter->psNext;
1986 536 : if (psIter->eType == CXT_Element &&
1987 266 : strcmp(psIter->pszValue, "complexType") == 0)
1988 : {
1989 : const char *l_pszName =
1990 106 : CPLGetXMLValue(psIter, "name", "");
1991 212 : CPLString osExpectedName(poLayer->GetShortName());
1992 106 : osExpectedName += "Type";
1993 212 : CPLString osExpectedName2(poLayer->GetShortName());
1994 106 : osExpectedName2 += "_Type";
1995 106 : if (strcmp(l_pszName, osExpectedName) == 0 ||
1996 158 : strcmp(l_pszName, osExpectedName2) == 0 ||
1997 52 : strcmp(l_pszName, poLayer->GetShortName()) == 0)
1998 : {
1999 54 : bFoundComplexType = true;
2000 : }
2001 : else
2002 : {
2003 52 : CPLRemoveXMLChild(psSchemaForLayer, psIter);
2004 52 : CPLDestroyXMLNode(psIter);
2005 106 : }
2006 : }
2007 430 : else if (psIter->eType == CXT_Element &&
2008 160 : strcmp(psIter->pszValue, "element") == 0)
2009 : {
2010 : const char *l_pszName =
2011 106 : CPLGetXMLValue(psIter, "name", "");
2012 212 : CPLString osExpectedName(poLayer->GetShortName());
2013 106 : osExpectedName += "Type";
2014 212 : CPLString osExpectedName2(poLayer->GetShortName());
2015 106 : osExpectedName2 += "_Type";
2016 :
2017 : const char *pszType =
2018 106 : CPLGetXMLValue(psIter, "type", "");
2019 212 : CPLString osExpectedType(poLayer->GetName());
2020 106 : osExpectedType += "Type";
2021 212 : CPLString osExpectedType2(poLayer->GetName());
2022 106 : osExpectedType2 += "_Type";
2023 106 : if (strcmp(pszType, osExpectedType) == 0 ||
2024 98 : strcmp(pszType, osExpectedType2) == 0 ||
2025 302 : strcmp(pszType, poLayer->GetName()) == 0 ||
2026 98 : (strchr(pszType, ':') &&
2027 98 : (strcmp(strchr(pszType, ':') + 1,
2028 52 : osExpectedType) == 0 ||
2029 52 : strcmp(strchr(pszType, ':') + 1,
2030 : osExpectedType2) == 0)))
2031 : {
2032 54 : bFoundElement = true;
2033 : }
2034 104 : else if (*pszType == '\0' &&
2035 0 : CPLGetXMLNode(psIter, "complexType") !=
2036 52 : nullptr &&
2037 0 : (strcmp(l_pszName, osExpectedName) == 0 ||
2038 0 : strcmp(l_pszName, osExpectedName2) == 0 ||
2039 0 : strcmp(l_pszName,
2040 : poLayer->GetShortName()) == 0))
2041 : {
2042 0 : bFoundElement = true;
2043 0 : bFoundComplexType = true;
2044 : }
2045 : else
2046 : {
2047 52 : CPLRemoveXMLChild(psSchemaForLayer, psIter);
2048 52 : CPLDestroyXMLNode(psIter);
2049 106 : }
2050 : }
2051 702 : else if (psIter->eType == CXT_Element &&
2052 378 : strcmp(psIter->pszValue, "import") == 0 &&
2053 54 : strcmp(CPLGetXMLValue(psIter, "namespace", ""),
2054 : "http://www.opengis.net/gml") == 0)
2055 : {
2056 54 : if (bHasAlreadyImportedGML)
2057 : {
2058 0 : CPLRemoveXMLChild(psSchemaForLayer, psIter);
2059 0 : CPLDestroyXMLNode(psIter);
2060 : }
2061 : else
2062 : {
2063 54 : bHasAlreadyImportedGML = true;
2064 : }
2065 : }
2066 536 : psIter = psIterNext;
2067 : }
2068 :
2069 54 : if (bFoundComplexType && bFoundElement)
2070 : {
2071 : OGRFeatureDefn *poSrcFDefn =
2072 54 : poLayer->ParseSchema(psSchemaForLayer);
2073 54 : if (poSrcFDefn)
2074 : {
2075 54 : poLayer->BuildLayerDefn(poSrcFDefn);
2076 54 : SaveLayerSchema(poLayer->GetName(),
2077 : psSchemaForLayer);
2078 : }
2079 : }
2080 :
2081 54 : CPLDestroyXMLNode(psSchemaForLayer);
2082 : }
2083 : else
2084 : {
2085 0 : CPLDebug("WFS",
2086 : "Found several time schema for layer %s in "
2087 : "server response. Should not happen",
2088 : poClass->GetName());
2089 : }
2090 : }
2091 56 : delete poClass;
2092 : }
2093 : }
2094 :
2095 30 : if (nLayersFound != nLayersToFetch)
2096 : {
2097 4 : CPLDebug("WFS", "Turn off loading of multiple layer definitions at a "
2098 : "single time");
2099 4 : bLoadMultipleLayerDefn = false;
2100 : }
2101 :
2102 30 : VSIUnlink(osTmpFileName);
2103 :
2104 30 : CPLDestroyXMLNode(psXML);
2105 : }
2106 :
2107 : /************************************************************************/
2108 : /* SaveLayerSchema() */
2109 : /************************************************************************/
2110 :
2111 135 : void OGRWFSDataSource::SaveLayerSchema(const char *pszLayerName,
2112 : const CPLXMLNode *psSchema)
2113 : {
2114 135 : if (psFileXML != nullptr)
2115 : {
2116 1 : bRewriteFile = true;
2117 : CPLXMLNode *psLayerNode =
2118 1 : CPLCreateXMLNode(nullptr, CXT_Element, "OGRWFSLayer");
2119 1 : CPLSetXMLValue(psLayerNode, "#name", pszLayerName);
2120 1 : CPLAddXMLChild(psLayerNode, CPLCloneXMLTree(psSchema));
2121 1 : CPLAddXMLChild(psFileXML, psLayerNode);
2122 : }
2123 135 : }
2124 :
2125 : /************************************************************************/
2126 : /* IsOldDeegree() */
2127 : /************************************************************************/
2128 :
2129 5 : bool OGRWFSDataSource::IsOldDeegree(const char *pszErrorString)
2130 : {
2131 5 : if (!bNeedNAMESPACE &&
2132 5 : strstr(pszErrorString, "Invalid \"TYPENAME\" parameter. "
2133 : "No binding for prefix") != nullptr)
2134 : {
2135 0 : bNeedNAMESPACE = true;
2136 0 : return true;
2137 : }
2138 5 : return false;
2139 : }
2140 :
2141 : /************************************************************************/
2142 : /* WFS_EscapeURL() */
2143 : /************************************************************************/
2144 :
2145 567 : CPLString WFS_EscapeURL(const char *pszURL)
2146 : {
2147 567 : CPLString osEscapedURL;
2148 :
2149 : /* Difference with CPLEscapeString(, CPLES_URL) : we do not escape */
2150 : /* colon (:) or comma (,). Causes problems with servers such as
2151 : * http://www.mapinfo.com/miwfs? */
2152 :
2153 37446 : for (int i = 0; pszURL[i] != '\0'; i++)
2154 : {
2155 36879 : char ch = pszURL[i];
2156 36879 : if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
2157 10004 : (ch >= '0' && ch <= '9') || ch == '_' || ch == '.' || ch == ':' ||
2158 : ch == ',')
2159 : {
2160 30943 : osEscapedURL += ch;
2161 : }
2162 : else
2163 : {
2164 : char szPercentEncoded[10];
2165 5936 : snprintf(szPercentEncoded, sizeof(szPercentEncoded), "%%%02X",
2166 5936 : reinterpret_cast<const unsigned char *>(pszURL)[i]);
2167 5936 : osEscapedURL += szPercentEncoded;
2168 : }
2169 : }
2170 :
2171 567 : return osEscapedURL;
2172 : }
2173 :
2174 : /************************************************************************/
2175 : /* WFS_DecodeURL() */
2176 : /************************************************************************/
2177 :
2178 143 : CPLString WFS_DecodeURL(const CPLString &osSrc)
2179 : {
2180 143 : CPLString ret;
2181 143 : for (size_t i = 0; i < osSrc.length(); i++)
2182 : {
2183 0 : if (osSrc[i] == '%' && i + 2 < osSrc.length())
2184 : {
2185 0 : unsigned int ii = 0;
2186 0 : sscanf(osSrc.substr(i + 1, 2).c_str(), "%x", &ii);
2187 0 : char ch = static_cast<char>(ii);
2188 0 : ret += ch;
2189 0 : i = i + 2;
2190 : }
2191 : else
2192 : {
2193 0 : ret += osSrc[i];
2194 : }
2195 : }
2196 143 : return ret;
2197 : }
2198 :
2199 : /************************************************************************/
2200 : /* HTTPFetch() */
2201 : /************************************************************************/
2202 :
2203 500 : CPLHTTPResult *OGRWFSDataSource::HTTPFetch(const char *pszURL,
2204 : CSLConstList papszOptions)
2205 : {
2206 500 : char **papszNewOptions = CSLDuplicate(papszOptions);
2207 500 : if (bUseHttp10)
2208 : papszNewOptions =
2209 0 : CSLAddNameValue(papszNewOptions, "HTTP_VERSION", "1.0");
2210 500 : if (papszHttpOptions)
2211 0 : papszNewOptions = CSLMerge(papszNewOptions, papszHttpOptions);
2212 500 : CPLHTTPResult *psResult = CPLHTTPFetch(pszURL, papszNewOptions);
2213 500 : CSLDestroy(papszNewOptions);
2214 :
2215 500 : if (psResult == nullptr)
2216 : {
2217 0 : return nullptr;
2218 : }
2219 500 : if (psResult->nStatus != 0 || psResult->pszErrBuf != nullptr)
2220 : {
2221 : // A few buggy servers return chunked data with erroneous
2222 : // remaining bytes value curl does not like this. Retry with
2223 : // HTTP 1.0 protocol instead that does not support chunked
2224 : // data.
2225 78 : if (psResult->pszErrBuf &&
2226 78 : strstr(psResult->pszErrBuf,
2227 0 : "transfer closed with outstanding read data remaining") &&
2228 0 : !bUseHttp10)
2229 : {
2230 0 : CPLDebug("WFS", "Probably buggy remote server. Retrying with HTTP "
2231 : "1.0 protocol");
2232 0 : bUseHttp10 = true;
2233 0 : CPLHTTPDestroyResult(psResult);
2234 0 : return HTTPFetch(pszURL, papszOptions);
2235 : }
2236 :
2237 78 : CPLError(CE_Failure, CPLE_AppDefined,
2238 : "Error returned by server : %s (%d)",
2239 78 : (psResult->pszErrBuf) ? psResult->pszErrBuf : "unknown",
2240 : psResult->nStatus);
2241 78 : CPLHTTPDestroyResult(psResult);
2242 78 : return nullptr;
2243 : }
2244 422 : if (psResult->pabyData == nullptr)
2245 : {
2246 11 : CPLError(CE_Failure, CPLE_AppDefined,
2247 : "Empty content returned by server");
2248 11 : CPLHTTPDestroyResult(psResult);
2249 11 : return nullptr;
2250 : }
2251 411 : return psResult;
2252 : }
2253 :
2254 : /************************************************************************/
2255 : /* ExecuteSQL() */
2256 : /************************************************************************/
2257 :
2258 60 : OGRLayer *OGRWFSDataSource::ExecuteSQL(const char *pszSQLCommand,
2259 : OGRGeometry *poSpatialFilter,
2260 : const char *pszDialect)
2261 :
2262 : {
2263 60 : while (*pszSQLCommand &&
2264 60 : isspace(static_cast<unsigned char>(*pszSQLCommand)))
2265 0 : ++pszSQLCommand;
2266 :
2267 60 : swq_select_parse_options oParseOptions;
2268 60 : oParseOptions.poCustomFuncRegistrar = WFSGetCustomFuncRegistrar();
2269 :
2270 : /* -------------------------------------------------------------------- */
2271 : /* Use generic implementation for recognized dialects */
2272 : /* -------------------------------------------------------------------- */
2273 60 : if (IsGenericSQLDialect(pszDialect))
2274 : {
2275 0 : OGRLayer *poResLayer = GDALDataset::ExecuteSQL(
2276 0 : pszSQLCommand, poSpatialFilter, pszDialect, &oParseOptions);
2277 0 : oMap[poResLayer] = nullptr;
2278 0 : return poResLayer;
2279 : }
2280 :
2281 : /* -------------------------------------------------------------------- */
2282 : /* Deal with "SELECT _LAST_INSERTED_FIDS_ FROM layername" statement */
2283 : /* -------------------------------------------------------------------- */
2284 60 : if (STARTS_WITH_CI(pszSQLCommand, "SELECT _LAST_INSERTED_FIDS_ FROM "))
2285 : {
2286 6 : const char *pszIter = pszSQLCommand + 33;
2287 74 : while (*pszIter && *pszIter != ' ')
2288 68 : pszIter++;
2289 :
2290 12 : CPLString osName = pszSQLCommand + 33;
2291 6 : osName.resize(pszIter - (pszSQLCommand + 33));
2292 : OGRWFSLayer *poLayer =
2293 6 : dynamic_cast<OGRWFSLayer *>(GetLayerByName(osName));
2294 6 : if (poLayer == nullptr)
2295 : {
2296 2 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer : %s",
2297 : osName.c_str());
2298 2 : return nullptr;
2299 : }
2300 :
2301 : auto poMEMDS = std::unique_ptr<MEMDataset>(
2302 8 : MEMDataset::Create("dummy_name", 0, 0, 0, GDT_Unknown, nullptr));
2303 : OGRLayer *poMEMLayer =
2304 4 : poMEMDS->CreateLayer("FID_LIST", nullptr, wkbNone, nullptr);
2305 4 : OGRFieldDefn oFDefn("gml_id", OFTString);
2306 4 : CPL_IGNORE_RET_VAL(poMEMLayer->CreateField(&oFDefn));
2307 :
2308 6 : for (const auto &osFID : poLayer->GetLastInsertedFIDList())
2309 : {
2310 2 : OGRFeature oFeature(poMEMLayer->GetLayerDefn());
2311 2 : oFeature.SetField(0, osFID);
2312 2 : CPL_IGNORE_RET_VAL(poMEMLayer->CreateFeature(&oFeature));
2313 : }
2314 :
2315 : OGRLayer *poResLayer =
2316 4 : new OGRWFSWrappedResultLayer(poMEMDS.release(), poMEMLayer);
2317 4 : oMap[poResLayer] = nullptr;
2318 4 : return poResLayer;
2319 : }
2320 :
2321 : /* -------------------------------------------------------------------- */
2322 : /* Deal with "DELETE FROM layer_name WHERE expression" statement */
2323 : /* -------------------------------------------------------------------- */
2324 54 : if (STARTS_WITH_CI(pszSQLCommand, "DELETE FROM "))
2325 : {
2326 12 : const char *pszIter = pszSQLCommand + 12;
2327 112 : while (*pszIter && *pszIter != ' ')
2328 100 : pszIter++;
2329 12 : if (*pszIter == 0)
2330 : {
2331 2 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid statement");
2332 2 : return nullptr;
2333 : }
2334 :
2335 20 : CPLString osName = pszSQLCommand + 12;
2336 10 : osName.resize(pszIter - (pszSQLCommand + 12));
2337 : OGRWFSLayer *poLayer =
2338 10 : dynamic_cast<OGRWFSLayer *>(GetLayerByName(osName));
2339 10 : if (poLayer == nullptr)
2340 : {
2341 2 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer : %s",
2342 : osName.c_str());
2343 2 : return nullptr;
2344 : }
2345 :
2346 16 : while (*pszIter == ' ')
2347 8 : pszIter++;
2348 8 : if (!STARTS_WITH_CI(pszIter, "WHERE "))
2349 : {
2350 2 : CPLError(CE_Failure, CPLE_AppDefined, "WHERE clause missing");
2351 2 : return nullptr;
2352 : }
2353 6 : pszIter += 5;
2354 :
2355 6 : const char *pszQuery = pszIter;
2356 :
2357 : /* Check with the generic SQL engine that this is a valid WHERE clause
2358 : */
2359 12 : OGRFeatureQuery oQuery;
2360 6 : OGRErr eErr = oQuery.Compile(poLayer->GetLayerDefn(), pszQuery);
2361 6 : if (eErr != OGRERR_NONE)
2362 : {
2363 2 : return nullptr;
2364 : }
2365 :
2366 : /* Now turn this into OGC Filter language if possible */
2367 4 : int bNeedsNullCheck = FALSE;
2368 4 : int nVersion = (strcmp(GetVersion(), "1.0.0") == 0) ? 100 : 110;
2369 : swq_expr_node *poNode =
2370 4 : static_cast<swq_expr_node *>(oQuery.GetSWQExpr());
2371 4 : poNode->ReplaceBetweenByGEAndLERecurse();
2372 4 : poNode->ReplaceInByOrRecurse();
2373 : CPLString osOGCFilter = WFS_TurnSQLFilterToOGCFilter(
2374 : poNode, nullptr, poLayer->GetLayerDefn(), nVersion,
2375 4 : bPropertyIsNotEqualToSupported, bUseFeatureId,
2376 8 : bGmlObjectIdNeedsGMLPrefix, "", &bNeedsNullCheck);
2377 4 : if (bNeedsNullCheck && !HasNullCheck())
2378 0 : osOGCFilter = "";
2379 :
2380 4 : if (osOGCFilter.empty())
2381 : {
2382 2 : CPLError(CE_Failure, CPLE_AppDefined,
2383 : "Cannot convert WHERE clause into a OGC filter");
2384 2 : return nullptr;
2385 : }
2386 :
2387 2 : poLayer->DeleteFromFilter(osOGCFilter);
2388 :
2389 2 : return nullptr;
2390 : }
2391 :
2392 : /* -------------------------------------------------------------------- */
2393 : /* Deal with "SELECT xxxx ORDER BY" statement */
2394 : /* -------------------------------------------------------------------- */
2395 42 : if (STARTS_WITH_CI(pszSQLCommand, "SELECT"))
2396 : {
2397 42 : swq_select *psSelectInfo = new swq_select();
2398 42 : if (psSelectInfo->preparse(pszSQLCommand, TRUE) != CE_None)
2399 : {
2400 0 : delete psSelectInfo;
2401 0 : return nullptr;
2402 : }
2403 42 : int iLayer = 0;
2404 42 : if (strcmp(GetVersion(), "1.0.0") != 0 &&
2405 42 : psSelectInfo->table_count == 1 &&
2406 16 : psSelectInfo->table_defs[0].data_source == nullptr &&
2407 8 : (iLayer = GetLayerIndex(psSelectInfo->table_defs[0].table_name)) >=
2408 8 : 0 &&
2409 84 : psSelectInfo->join_count == 0 && psSelectInfo->order_specs > 0 &&
2410 0 : psSelectInfo->poOtherSelect == nullptr)
2411 : {
2412 0 : OGRWFSLayer *poSrcLayer = papoLayers[iLayer];
2413 0 : std::vector<OGRWFSSortDesc> aoSortColumns;
2414 0 : int i = 0; // Used after for.
2415 0 : for (; i < psSelectInfo->order_specs; i++)
2416 : {
2417 0 : int nFieldIndex = poSrcLayer->GetLayerDefn()->GetFieldIndex(
2418 0 : psSelectInfo->order_defs[i].field_name);
2419 0 : if (poSrcLayer->HasGotApproximateLayerDefn() || nFieldIndex < 0)
2420 0 : break;
2421 :
2422 : /* Make sure to have the right case */
2423 0 : const char *pszFieldName = poSrcLayer->GetLayerDefn()
2424 0 : ->GetFieldDefn(nFieldIndex)
2425 0 : ->GetNameRef();
2426 :
2427 : aoSortColumns.emplace_back(
2428 0 : pszFieldName, psSelectInfo->order_defs[i].ascending_flag);
2429 : }
2430 :
2431 0 : if (i == psSelectInfo->order_specs)
2432 : {
2433 0 : OGRWFSLayer *poDupLayer = poSrcLayer->Clone();
2434 :
2435 0 : poDupLayer->SetOrderBy(aoSortColumns);
2436 0 : int nBackup = psSelectInfo->order_specs;
2437 0 : psSelectInfo->order_specs = 0;
2438 0 : char *pszSQLWithoutOrderBy = psSelectInfo->Unparse();
2439 0 : CPLDebug("WFS", "SQL without ORDER BY: %s",
2440 : pszSQLWithoutOrderBy);
2441 0 : psSelectInfo->order_specs = nBackup;
2442 0 : delete psSelectInfo;
2443 0 : psSelectInfo = nullptr;
2444 :
2445 : /* Just set poDupLayer in the papoLayers for the time of the */
2446 : /* base ExecuteSQL(), so that the OGRGenSQLResultsLayer
2447 : * references */
2448 : /* that temporary layer */
2449 0 : papoLayers[iLayer] = poDupLayer;
2450 :
2451 0 : OGRLayer *poResLayer = GDALDataset::ExecuteSQL(
2452 : pszSQLWithoutOrderBy, poSpatialFilter, pszDialect,
2453 0 : &oParseOptions);
2454 0 : papoLayers[iLayer] = poSrcLayer;
2455 :
2456 0 : CPLFree(pszSQLWithoutOrderBy);
2457 :
2458 0 : if (poResLayer != nullptr)
2459 0 : oMap[poResLayer] = poDupLayer;
2460 : else
2461 0 : delete poDupLayer;
2462 0 : return poResLayer;
2463 : }
2464 : }
2465 42 : else if (bStandardJoinsWFS2 && psSelectInfo->join_count > 0 &&
2466 34 : psSelectInfo->poOtherSelect == nullptr)
2467 : {
2468 : // Just to make sure everything is valid, but we won't use
2469 : // that one as we want to run the join on server-side
2470 34 : oParseOptions.bAllowFieldsInSecondaryTablesInWhere = TRUE;
2471 34 : oParseOptions.bAddSecondaryTablesGeometryFields = TRUE;
2472 34 : oParseOptions.bAlwaysPrefixWithTableName = TRUE;
2473 34 : oParseOptions.bAllowDistinctOnGeometryField = TRUE;
2474 34 : oParseOptions.bAllowDistinctOnMultipleFields = TRUE;
2475 : GDALSQLParseInfo *psParseInfo =
2476 34 : BuildParseInfo(psSelectInfo, &oParseOptions);
2477 34 : oParseOptions.bAllowFieldsInSecondaryTablesInWhere = FALSE;
2478 34 : oParseOptions.bAddSecondaryTablesGeometryFields = FALSE;
2479 34 : oParseOptions.bAlwaysPrefixWithTableName = FALSE;
2480 34 : oParseOptions.bAllowDistinctOnGeometryField = FALSE;
2481 34 : oParseOptions.bAllowDistinctOnMultipleFields = FALSE;
2482 34 : const bool bOK = psParseInfo != nullptr;
2483 34 : DestroyParseInfo(psParseInfo);
2484 :
2485 34 : OGRLayer *poResLayer = nullptr;
2486 34 : if (bOK)
2487 : {
2488 34 : poResLayer = OGRWFSJoinLayer::Build(this, psSelectInfo);
2489 34 : oMap[poResLayer] = nullptr;
2490 : }
2491 :
2492 34 : delete psSelectInfo;
2493 34 : return poResLayer;
2494 : }
2495 :
2496 8 : delete psSelectInfo;
2497 : }
2498 :
2499 8 : OGRLayer *poResLayer = GDALDataset::ExecuteSQL(
2500 8 : pszSQLCommand, poSpatialFilter, pszDialect, &oParseOptions);
2501 8 : oMap[poResLayer] = nullptr;
2502 8 : return poResLayer;
2503 : }
2504 :
2505 : /************************************************************************/
2506 : /* ReleaseResultSet() */
2507 : /************************************************************************/
2508 :
2509 40 : void OGRWFSDataSource::ReleaseResultSet(OGRLayer *poResultsSet)
2510 : {
2511 40 : if (poResultsSet == nullptr)
2512 0 : return;
2513 :
2514 40 : std::map<OGRLayer *, OGRLayer *>::iterator oIter = oMap.find(poResultsSet);
2515 40 : if (oIter != oMap.end())
2516 : {
2517 : /* Destroy first the result layer, because it still references */
2518 : /* the poDupLayer (oIter->second) */
2519 40 : delete poResultsSet;
2520 :
2521 40 : delete oIter->second;
2522 40 : oMap.erase(oIter);
2523 : }
2524 : else
2525 : {
2526 0 : CPLError(CE_Failure, CPLE_AppDefined,
2527 : "Trying to destroy an invalid result set !");
2528 : }
2529 : }
|