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 158 : const CPLXMLNode *WFSFindNode(const CPLXMLNode *psXML, const char *pszRootName)
46 : {
47 158 : const CPLXMLNode *psIter = psXML;
48 11 : do
49 : {
50 169 : if (psIter->eType == CXT_Element)
51 : {
52 161 : const char *pszNodeName = psIter->pszValue;
53 161 : const char *pszSep = strchr(pszNodeName, ':');
54 161 : if (pszSep)
55 121 : pszNodeName = pszSep + 1;
56 161 : if (EQUAL(pszNodeName, pszRootName))
57 : {
58 143 : 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 144 : OGRWFSDataSource::OGRWFSDataSource()
143 : : bPagingAllowed(
144 144 : CPLTestBool(CPLGetConfigOption("OGR_WFS_PAGING_ALLOWED", "OFF"))),
145 144 : bLoadMultipleLayerDefn(CPLTestBool(
146 432 : CPLGetConfigOption("OGR_WFS_LOAD_MULTIPLE_LAYER_DEFN", "TRUE")))
147 : {
148 144 : 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 144 : }
164 :
165 : /************************************************************************/
166 : /* ~OGRWFSDataSource() */
167 : /************************************************************************/
168 :
169 288 : OGRWFSDataSource::~OGRWFSDataSource()
170 :
171 : {
172 144 : if (psFileXML)
173 : {
174 6 : if (bRewriteFile)
175 : {
176 1 : CPLSerializeXMLTreeToFile(psFileXML, GetDescription());
177 : }
178 :
179 6 : CPLDestroyXMLNode(psFileXML);
180 : }
181 :
182 355 : for (int i = 0; i < nLayers; i++)
183 211 : delete papoLayers[i];
184 144 : CPLFree(papoLayers);
185 :
186 144 : if (!osLayerMetadataTmpFileName.empty())
187 1 : VSIUnlink(osLayerMetadataTmpFileName);
188 144 : delete poLayerMetadataDS;
189 144 : delete poLayerGetCapabilitiesDS;
190 :
191 144 : CSLDestroy(papszIdGenMethods);
192 144 : CSLDestroy(papszHttpOptions);
193 288 : }
194 :
195 : /************************************************************************/
196 : /* GetLayer() */
197 : /************************************************************************/
198 :
199 123 : const OGRLayer *OGRWFSDataSource::GetLayer(int iLayer) const
200 :
201 : {
202 123 : if (iLayer < 0 || iLayer >= nLayers)
203 0 : return nullptr;
204 : else
205 123 : 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 : 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 : char **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 449 : const char *FindSubStringInsensitive(const char *pszStr, const char *pszSubStr)
340 : {
341 449 : size_t nSubStrPos = CPLString(pszStr).ifind(pszSubStr);
342 449 : if (nSubStrPos == std::string::npos)
343 445 : return nullptr;
344 4 : return pszStr + nSubStrPos;
345 : }
346 :
347 : /************************************************************************/
348 : /* DetectIfGetFeatureSupportHits() */
349 : /************************************************************************/
350 :
351 94 : static bool DetectIfGetFeatureSupportHits(const CPLXMLNode *psRoot)
352 : {
353 : const CPLXMLNode *psOperationsMetadata =
354 94 : CPLGetXMLNode(psRoot, "OperationsMetadata");
355 94 : if (!psOperationsMetadata)
356 : {
357 60 : CPLDebug("WFS", "Could not find <OperationsMetadata>");
358 60 : 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 134 : 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 134 : const CPLXMLNode *psGeometryOperands = CPLGetXMLNode(
433 : psRoot, "Filter_Capabilities.Spatial_Capabilities.GeometryOperands");
434 134 : if (!psGeometryOperands)
435 : {
436 93 : 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 135 : bool OGRWFSDataSource::DetectTransactionSupport(const CPLXMLNode *psRoot)
473 : {
474 : const CPLXMLNode *psTransactionWFS100 =
475 135 : CPLGetXMLNode(psRoot, "Capability.Request.Transaction");
476 135 : 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 135 : CPLGetXMLNode(psRoot, "OperationsMetadata");
496 135 : if (!psOperationsMetadata)
497 : {
498 67 : 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 40 : bool OGRWFSDataSource::DetectSupportPagingWFS2(
574 : const CPLXMLNode *psGetCapabilitiesResponse,
575 : const CPLXMLNode *psConfigurationRoot)
576 : {
577 40 : const char *pszPagingAllowed = CPLGetConfigOption(
578 : "OGR_WFS_PAGING_ALLOWED",
579 : CPLGetXMLValue(psConfigurationRoot, "PagingAllowed", nullptr));
580 40 : if (pszPagingAllowed != nullptr && !CPLTestBool(pszPagingAllowed))
581 0 : return false;
582 :
583 : const CPLXMLNode *psOperationsMetadata =
584 40 : CPLGetXMLNode(psGetCapabilitiesResponse, "OperationsMetadata");
585 40 : if (!psOperationsMetadata)
586 : {
587 6 : 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 40 : bool OGRWFSDataSource::DetectSupportStandardJoinsWFS2(const CPLXMLNode *psRoot)
673 : {
674 : const CPLXMLNode *psOperationsMetadata =
675 40 : CPLGetXMLNode(psRoot, "OperationsMetadata");
676 40 : if (!psOperationsMetadata)
677 : {
678 6 : 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 144 : CPLXMLNode *OGRWFSDataSource::LoadFromFile(const char *pszFilename)
738 : {
739 : VSIStatBufL sStatBuf;
740 144 : if (VSIStatExL(pszFilename, &sStatBuf,
741 155 : VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) != 0 ||
742 11 : VSI_ISDIR(sStatBuf.st_mode))
743 133 : 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 135 : CPLHTTPResult *OGRWFSDataSource::SendGetCapabilities(const char *pszBaseURL,
807 : CPLString &osTypeName)
808 : {
809 270 : CPLString osURL(pszBaseURL);
810 :
811 135 : osURL = CPLURLAddKVP(osURL, "SERVICE", "WFS");
812 135 : osURL = CPLURLAddKVP(osURL, "REQUEST", "GetCapabilities");
813 135 : osTypeName = CPLURLGetValue(osURL, "TYPENAME");
814 135 : if (osTypeName.empty())
815 135 : osTypeName = CPLURLGetValue(osURL, "TYPENAMES");
816 135 : osURL = CPLURLAddKVP(osURL, "TYPENAME", nullptr);
817 135 : osURL = CPLURLAddKVP(osURL, "TYPENAMES", nullptr);
818 135 : osURL = CPLURLAddKVP(osURL, "FILTER", nullptr);
819 135 : osURL = CPLURLAddKVP(osURL, "PROPERTYNAME", nullptr);
820 135 : osURL = CPLURLAddKVP(osURL, "MAXFEATURES", nullptr);
821 135 : osURL = CPLURLAddKVP(osURL, "OUTPUTFORMAT", nullptr);
822 :
823 135 : CPLDebug("WFS", "%s", osURL.c_str());
824 :
825 135 : CPLHTTPResult *psResult = HTTPFetch(osURL, nullptr);
826 135 : if (psResult == nullptr)
827 : {
828 3 : return nullptr;
829 : }
830 :
831 132 : if (strstr(reinterpret_cast<const char *>(psResult->pabyData),
832 131 : "<ServiceExceptionReport") != nullptr ||
833 131 : strstr(reinterpret_cast<const char *>(psResult->pabyData),
834 131 : "<ows:ExceptionReport") != nullptr ||
835 131 : 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 131 : return psResult;
845 : }
846 :
847 : /************************************************************************/
848 : /* Open() */
849 : /************************************************************************/
850 :
851 145 : int OGRWFSDataSource::Open(const char *pszFilename, int bUpdateIn,
852 : CSLConstList papszOpenOptionsIn)
853 :
854 : {
855 145 : bUpdate = CPL_TO_BOOL(bUpdateIn);
856 :
857 145 : const CPLXMLNode *psWFSCapabilities = nullptr;
858 145 : CPLXMLNode *psXML = nullptr;
859 145 : if (!STARTS_WITH(pszFilename, "http://") &&
860 144 : !STARTS_WITH(pszFilename, "https://"))
861 : {
862 144 : psXML = LoadFromFile(pszFilename);
863 : }
864 290 : CPLString osTypeName;
865 145 : const char *pszBaseURL = nullptr;
866 :
867 145 : bEmptyAsNull = CPLFetchBool(papszOpenOptionsIn, "EMPTY_AS_NULL", true);
868 :
869 145 : const CPLXMLNode *psConfigurationRoot = nullptr;
870 :
871 145 : if (psXML == nullptr)
872 : {
873 272 : if (!STARTS_WITH_CI(pszFilename, "WFS:") &&
874 2 : !STARTS_WITH(pszFilename, "http://") &&
875 138 : !STARTS_WITH(pszFilename, "https://") &&
876 1 : FindSubStringInsensitive(pszFilename, "SERVICE=WFS") == nullptr)
877 : {
878 6 : return FALSE;
879 : }
880 :
881 134 : pszBaseURL = CSLFetchNameValue(papszOpenOptionsIn, "URL");
882 134 : if (pszBaseURL == nullptr)
883 : {
884 134 : pszBaseURL = pszFilename;
885 134 : if (STARTS_WITH_CI(pszFilename, "WFS:"))
886 133 : pszBaseURL += strlen("WFS:");
887 : }
888 :
889 134 : osBaseURL = pszBaseURL;
890 :
891 134 : if (!STARTS_WITH(pszBaseURL, "http://") &&
892 131 : !STARTS_WITH(pszBaseURL, "https://") &&
893 131 : !STARTS_WITH(pszBaseURL, "/vsimem/"))
894 0 : return FALSE;
895 :
896 134 : CPLString strOriginalTypeName = "";
897 : CPLHTTPResult *psResult =
898 134 : SendGetCapabilities(pszBaseURL, strOriginalTypeName);
899 134 : osTypeName = WFS_DecodeURL(strOriginalTypeName);
900 134 : if (psResult == nullptr)
901 : {
902 4 : return FALSE;
903 : }
904 :
905 130 : 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 130 : else if (strstr(reinterpret_cast<const char *>(psResult->pabyData),
912 : "deegree"))
913 : {
914 0 : bGmlObjectIdNeedsGMLPrefix = true;
915 : }
916 :
917 260 : psXML = CPLParseXMLString(
918 130 : reinterpret_cast<const char *>(psResult->pabyData));
919 130 : 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 129 : osGetCapabilities = reinterpret_cast<const char *>(psResult->pabyData);
927 :
928 129 : 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 138 : 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 138 : CPLGetConfigOption("GML_CONSIDER_EPSG_AS_URN", "AUTO"));
1068 138 : bExposeGMLId = CPLTestBool(
1069 : CSLFetchNameValueDef(papszOpenOptionsIn, "EXPOSE_GML_ID",
1070 : CPLGetConfigOption("GML_EXPOSE_GML_ID", "YES")));
1071 :
1072 138 : CPLXMLNode *psStrippedXML = CPLCloneXMLTree(psXML);
1073 138 : CPLStripXMLNamespace(psStrippedXML, nullptr, TRUE);
1074 138 : psWFSCapabilities = CPLGetXMLNode(psStrippedXML, "=WFS_Capabilities");
1075 138 : if (psWFSCapabilities == nullptr)
1076 : {
1077 : psWFSCapabilities =
1078 8 : CPLGetXMLNode(psStrippedXML, "=OGRWFSDataSource.WFS_Capabilities");
1079 : }
1080 138 : 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 136 : 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 135 : pszBaseURL = nullptr;
1114 :
1115 810 : for (const auto &sMetadata : asMetadata)
1116 : {
1117 : const char *pszVal =
1118 675 : CPLGetXMLValue(psWFSCapabilities, sMetadata.pszPath, nullptr);
1119 675 : if (pszVal)
1120 13 : SetMetadataItem(sMetadata.pszMDI, pszVal);
1121 : }
1122 :
1123 135 : if (osVersion.empty())
1124 135 : osVersion = CPLGetXMLValue(psWFSCapabilities, "version", "1.0.0");
1125 135 : 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 134 : if (atoi(osVersion) >= 2)
1134 40 : bGetFeatureSupportHits = true; /* WFS >= 2.0.0 supports hits */
1135 : else
1136 94 : bGetFeatureSupportHits =
1137 94 : DetectIfGetFeatureSupportHits(psWFSCapabilities);
1138 134 : bRequiresEnvelopeSpatialFilter =
1139 134 : DetectRequiresEnvelopeSpatialFilter(psWFSCapabilities);
1140 : }
1141 :
1142 135 : bool bRequestJSON = false;
1143 :
1144 135 : if (atoi(osVersion) >= 2)
1145 : {
1146 80 : 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 40 : if (osMaxFeatures.empty())
1150 : {
1151 40 : osMaxFeatures = CPLURLGetValue(osBaseURL, "MAXFEATURES");
1152 40 : 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 40 : DetectSupportPagingWFS2(psWFSCapabilities, psConfigurationRoot);
1164 40 : DetectSupportStandardJoinsWFS2(psWFSCapabilities);
1165 :
1166 : const CPLXMLNode *psOperationsMetadata =
1167 40 : CPLGetXMLNode(psWFSCapabilities, "OperationsMetadata");
1168 40 : auto poGMLDriver = GDALDriver::FromHandle(GDALGetDriverByName("GML"));
1169 40 : if (psOperationsMetadata && !(poGMLDriver && poGMLDriver->pfnOpen) &&
1170 40 : 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 135 : DetectTransactionSupport(psWFSCapabilities);
1229 :
1230 135 : 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 134 : const CPLXMLNode *psFilterCap = CPLGetXMLNode(
1241 : psWFSCapabilities, "Filter_Capabilities.Scalar_Capabilities");
1242 134 : 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 134 : CPLGetXMLNode(psWFSCapabilities, "FeatureTypeList");
1301 134 : 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 133 : std::set<CPLString> aosSetLayerNames;
1312 365 : for (CPLXMLNode *psChildIter = psChild->psChild; psChildIter != nullptr;
1313 232 : psChildIter = psChildIter->psNext)
1314 : {
1315 234 : if (psChildIter->eType == CXT_Element &&
1316 234 : strcmp(psChildIter->pszValue, "FeatureType") == 0)
1317 : {
1318 : const char *l_pszName =
1319 229 : CPLGetXMLValue(psChildIter, "Name", nullptr);
1320 229 : if (l_pszName != nullptr)
1321 : {
1322 211 : const char *pszShortName = strchr(l_pszName, ':');
1323 211 : if (pszShortName)
1324 34 : l_pszName = pszShortName + 1;
1325 211 : 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 209 : aosSetLayerNames.insert(l_pszName);
1334 : }
1335 : }
1336 : }
1337 :
1338 133 : char **papszTypenames = nullptr;
1339 133 : if (!osTypeName.empty())
1340 : papszTypenames =
1341 0 : CSLTokenizeStringComplex(osTypeName, ",", FALSE, FALSE);
1342 :
1343 367 : for (CPLXMLNode *psChildIter = psChild->psChild; psChildIter != nullptr;
1344 234 : psChildIter = psChildIter->psNext)
1345 : {
1346 234 : if (psChildIter->eType == CXT_Element &&
1347 234 : strcmp(psChildIter->pszValue, "FeatureType") == 0)
1348 : {
1349 229 : const char *pszNS = nullptr;
1350 229 : const char *pszNSVal = nullptr;
1351 229 : CPLXMLNode *psFeatureTypeIter = psChildIter->psChild;
1352 948 : while (psFeatureTypeIter != nullptr)
1353 : {
1354 719 : if (psFeatureTypeIter->eType == CXT_Attribute)
1355 : {
1356 28 : pszNS = psFeatureTypeIter->pszValue;
1357 28 : pszNSVal = psFeatureTypeIter->psChild->pszValue;
1358 : }
1359 719 : psFeatureTypeIter = psFeatureTypeIter->psNext;
1360 : }
1361 :
1362 : const char *l_pszName =
1363 229 : CPLGetXMLValue(psChildIter, "Name", nullptr);
1364 : const char *pszTitle =
1365 229 : CPLGetXMLValue(psChildIter, "Title", nullptr);
1366 : const char *pszAbstract =
1367 229 : CPLGetXMLValue(psChildIter, "Abstract", nullptr);
1368 229 : if (l_pszName != nullptr &&
1369 0 : (papszTypenames == nullptr ||
1370 0 : CSLFindString(papszTypenames, l_pszName) != -1))
1371 : {
1372 : const char *pszDefaultSRS =
1373 211 : CPLGetXMLValue(psChildIter, "DefaultSRS", nullptr);
1374 211 : if (pszDefaultSRS == nullptr)
1375 18 : pszDefaultSRS = CPLGetXMLValue(psChildIter, "SRS", nullptr);
1376 211 : if (pszDefaultSRS == nullptr)
1377 18 : pszDefaultSRS = CPLGetXMLValue(psChildIter, "DefaultCRS",
1378 : nullptr); /* WFS 2.0.0 */
1379 :
1380 : const CPLXMLNode *psOtherSRS =
1381 211 : CPLGetXMLNode(psChildIter, "OtherSRS"); // WFS 1.1
1382 211 : if (psOtherSRS == nullptr)
1383 : psOtherSRS =
1384 210 : CPLGetXMLNode(psChildIter, "OtherCRS"); // WFS 2.0
1385 :
1386 422 : std::vector<std::string> aosSupportedCRSList{};
1387 422 : OGRLayer::GetSupportedSRSListRetType apoSupportedCRSList;
1388 211 : if (psOtherSRS)
1389 : {
1390 1 : if (pszDefaultSRS)
1391 : {
1392 : auto poSRS =
1393 : std::unique_ptr<OGRSpatialReference,
1394 : OGRSpatialReferenceReleaser>(
1395 2 : new OGRSpatialReference());
1396 1 : if (poSRS->SetFromUserInput(
1397 : pszDefaultSRS,
1398 : OGRSpatialReference::
1399 1 : SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
1400 : OGRERR_NONE)
1401 : {
1402 1 : aosSupportedCRSList.emplace_back(pszDefaultSRS);
1403 1 : apoSupportedCRSList.emplace_back(std::move(poSRS));
1404 : }
1405 : }
1406 :
1407 : CPLErrorStateBackuper oErrorStateBackuper(
1408 2 : CPLQuietErrorHandler);
1409 4 : for (const CPLXMLNode *psIter = psOtherSRS; psIter;
1410 3 : psIter = psIter->psNext)
1411 : {
1412 3 : if (psIter->eType == CXT_Element)
1413 : {
1414 : const char *pszSRS =
1415 3 : CPLGetXMLValue(psIter, "", nullptr);
1416 3 : if (pszSRS)
1417 : {
1418 : auto poSRS = std::unique_ptr<
1419 : OGRSpatialReference,
1420 : OGRSpatialReferenceReleaser>(
1421 4 : new OGRSpatialReference());
1422 4 : if (poSRS->SetFromUserInput(
1423 2 : EQUAL(pszSRS, "CRS:84") ? "OGC:CRS84"
1424 : : pszSRS,
1425 : OGRSpatialReference::
1426 2 : SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
1427 : OGRERR_NONE)
1428 : {
1429 2 : aosSupportedCRSList.emplace_back(pszSRS);
1430 : apoSupportedCRSList.emplace_back(
1431 2 : std::move(poSRS));
1432 : }
1433 : else
1434 : {
1435 0 : CPLDebug("WFS", "Invalid CRS %s", pszSRS);
1436 : }
1437 : }
1438 : }
1439 : }
1440 : }
1441 :
1442 : CPLXMLNode *psOutputFormats =
1443 211 : CPLGetXMLNode(psChildIter, "OutputFormats");
1444 422 : CPLString osOutputFormat;
1445 211 : if (psOutputFormats)
1446 : {
1447 16 : std::vector<CPLString> osFormats;
1448 8 : CPLXMLNode *psOutputFormatIter = psOutputFormats->psChild;
1449 16 : while (psOutputFormatIter)
1450 : {
1451 8 : if (psOutputFormatIter->eType == CXT_Element &&
1452 8 : EQUAL(psOutputFormatIter->pszValue, "Format") &&
1453 8 : psOutputFormatIter->psChild != nullptr &&
1454 8 : psOutputFormatIter->psChild->eType == CXT_Text)
1455 : {
1456 8 : osFormats.push_back(
1457 8 : psOutputFormatIter->psChild->pszValue);
1458 : }
1459 8 : psOutputFormatIter = psOutputFormatIter->psNext;
1460 : }
1461 :
1462 16 : if (strcmp(osVersion.c_str(), "1.1.0") == 0 &&
1463 8 : !osFormats.empty())
1464 : {
1465 8 : bool bFoundGML31 = false;
1466 8 : for (size_t i = 0; i < osFormats.size(); i++)
1467 : {
1468 8 : if (strstr(osFormats[i].c_str(), "3.1") != nullptr)
1469 : {
1470 8 : bFoundGML31 = true;
1471 8 : break;
1472 : }
1473 : }
1474 :
1475 : /* If we didn't find any mention to GML 3.1, then
1476 : * arbitrarily */
1477 : /* use the first output format */
1478 8 : if (!bFoundGML31)
1479 0 : osOutputFormat = osFormats[0].c_str();
1480 : }
1481 : }
1482 211 : if (osOutputFormat.empty() && bRequestJSON)
1483 0 : osOutputFormat = "json";
1484 :
1485 211 : OGRSpatialReference *poSRS = nullptr;
1486 211 : bool bAxisOrderAlreadyInverted = false;
1487 :
1488 : /* If a SRSNAME parameter has been encoded in the URL, use it as
1489 : * the SRS */
1490 422 : CPLString osSRSName = CPLURLGetValue(osBaseURL, "SRSNAME");
1491 211 : if (!osSRSName.empty())
1492 : {
1493 0 : pszDefaultSRS = osSRSName.c_str();
1494 : }
1495 :
1496 : // EPSG:404000 is a GeoServer joke to indicate a unknown SRS
1497 : // https://osgeo-org.atlassian.net/browse/GEOS-8993
1498 211 : if (pszDefaultSRS && !EQUAL(pszDefaultSRS, "EPSG:404000") &&
1499 193 : !EQUAL(pszDefaultSRS, "urn:ogc:def:crs:EPSG::404000"))
1500 : {
1501 386 : OGRSpatialReference oSRS;
1502 193 : if (oSRS.SetFromUserInput(
1503 : pszDefaultSRS,
1504 : OGRSpatialReference::
1505 193 : SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
1506 : OGRERR_NONE)
1507 : {
1508 193 : poSRS = oSRS.Clone();
1509 193 : poSRS->SetAxisMappingStrategy(
1510 193 : bInvertAxisOrderIfLatLong
1511 : ? OAMS_TRADITIONAL_GIS_ORDER
1512 : : OAMS_AUTHORITY_COMPLIANT);
1513 359 : if (GML_IsSRSLatLongOrder(pszDefaultSRS) &&
1514 166 : bInvertAxisOrderIfLatLong)
1515 : {
1516 166 : bAxisOrderAlreadyInverted = true;
1517 : }
1518 : }
1519 : }
1520 :
1521 211 : CPLXMLNode *psBBox = nullptr;
1522 211 : CPLXMLNode *psLatLongBBox = nullptr;
1523 : /* bool bFoundBBox = false; */
1524 211 : double dfMinX = 0.0;
1525 211 : double dfMinY = 0.0;
1526 211 : double dfMaxX = 0.0;
1527 211 : double dfMaxY = 0.0;
1528 211 : if ((psBBox = CPLGetXMLNode(psChildIter, "WGS84BoundingBox")) !=
1529 : nullptr)
1530 : {
1531 : const char *pszLC =
1532 193 : CPLGetXMLValue(psBBox, "LowerCorner", nullptr);
1533 : const char *pszUC =
1534 193 : CPLGetXMLValue(psBBox, "UpperCorner", nullptr);
1535 193 : if (pszLC != nullptr && pszUC != nullptr)
1536 : {
1537 386 : CPLString osConcat(pszLC);
1538 193 : osConcat += " ";
1539 193 : osConcat += pszUC;
1540 193 : char **papszTokens = CSLTokenizeStringComplex(
1541 : osConcat, " ,", FALSE, FALSE);
1542 193 : if (CSLCount(papszTokens) == 4)
1543 : {
1544 : // bFoundBBox = true;
1545 193 : dfMinX = CPLAtof(papszTokens[0]);
1546 193 : dfMinY = CPLAtof(papszTokens[1]);
1547 193 : dfMaxX = CPLAtof(papszTokens[2]);
1548 193 : dfMaxY = CPLAtof(papszTokens[3]);
1549 : }
1550 193 : CSLDestroy(papszTokens);
1551 : }
1552 : }
1553 18 : else if ((psLatLongBBox = CPLGetXMLNode(
1554 18 : psChildIter, "LatLongBoundingBox")) != nullptr)
1555 : {
1556 : const char *pszMinX =
1557 0 : CPLGetXMLValue(psLatLongBBox, "minx", nullptr);
1558 : const char *pszMinY =
1559 0 : CPLGetXMLValue(psLatLongBBox, "miny", nullptr);
1560 : const char *pszMaxX =
1561 0 : CPLGetXMLValue(psLatLongBBox, "maxx", nullptr);
1562 : const char *pszMaxY =
1563 0 : CPLGetXMLValue(psLatLongBBox, "maxy", nullptr);
1564 0 : if (pszMinX != nullptr && pszMinY != nullptr &&
1565 0 : pszMaxX != nullptr && pszMaxY != nullptr)
1566 : {
1567 : // bFoundBBox = true;
1568 0 : dfMinX = CPLAtof(pszMinX);
1569 0 : dfMinY = CPLAtof(pszMinY);
1570 0 : dfMaxX = CPLAtof(pszMaxX);
1571 0 : dfMaxY = CPLAtof(pszMaxY);
1572 : }
1573 : }
1574 :
1575 211 : char *pszCSVEscaped = CPLEscapeString(l_pszName, -1, CPLES_CSV);
1576 211 : osLayerMetadataCSV += pszCSVEscaped;
1577 211 : CPLFree(pszCSVEscaped);
1578 :
1579 211 : osLayerMetadataCSV += ",";
1580 211 : if (pszTitle)
1581 : {
1582 34 : pszCSVEscaped = CPLEscapeString(pszTitle, -1, CPLES_CSV);
1583 34 : osLayerMetadataCSV += pszCSVEscaped;
1584 34 : CPLFree(pszCSVEscaped);
1585 : }
1586 211 : osLayerMetadataCSV += ",";
1587 211 : if (pszAbstract)
1588 : {
1589 28 : pszCSVEscaped = CPLEscapeString(pszAbstract, -1, CPLES_CSV);
1590 28 : osLayerMetadataCSV += pszCSVEscaped;
1591 28 : CPLFree(pszCSVEscaped);
1592 : }
1593 211 : osLayerMetadataCSV += "\n";
1594 :
1595 : OGRWFSLayer *poLayer =
1596 : new OGRWFSLayer(this, poSRS, bAxisOrderAlreadyInverted,
1597 211 : osBaseURL, l_pszName, pszNS, pszNSVal);
1598 211 : if (!osOutputFormat.empty())
1599 0 : poLayer->SetRequiredOutputFormat(osOutputFormat);
1600 :
1601 211 : if (pszTitle)
1602 34 : poLayer->SetMetadataItem("TITLE", pszTitle);
1603 211 : if (pszAbstract)
1604 28 : poLayer->SetMetadataItem("ABSTRACT", pszAbstract);
1605 211 : CPLXMLNode *psKeywords = CPLGetXMLNode(psChildIter, "Keywords");
1606 211 : if (psKeywords)
1607 : {
1608 20 : int nKeywordCounter = 1;
1609 20 : for (CPLXMLNode *psKeyword = psKeywords->psChild;
1610 75 : psKeyword != nullptr; psKeyword = psKeyword->psNext)
1611 : {
1612 55 : if (psKeyword->eType == CXT_Element &&
1613 55 : psKeyword->psChild != nullptr)
1614 : {
1615 55 : poLayer->SetMetadataItem(
1616 : CPLSPrintf("KEYWORD_%d", nKeywordCounter),
1617 55 : psKeyword->psChild->pszValue);
1618 55 : nKeywordCounter++;
1619 : }
1620 0 : else if (psKeyword->eType == CXT_Text)
1621 : {
1622 0 : poLayer->SetMetadataItem("KEYWORDS",
1623 0 : psKeyword->pszValue);
1624 : }
1625 : }
1626 : }
1627 :
1628 211 : if (poSRS)
1629 : {
1630 193 : char *pszProj4 = nullptr;
1631 193 : if (poSRS->exportToProj4(&pszProj4) == OGRERR_NONE)
1632 : {
1633 : /* See http://trac.osgeo.org/gdal/ticket/4041 */
1634 386 : const bool bTrustBounds = CPLFetchBool(
1635 : papszOpenOptionsIn, "TRUST_CAPABILITIES_BOUNDS",
1636 193 : CPLTestBool(CPLGetConfigOption(
1637 : "OGR_WFS_TRUST_CAPABILITIES_BOUNDS", "FALSE")));
1638 :
1639 193 : if (((bTrustBounds ||
1640 176 : (dfMinX == -180 && dfMinY == -90 &&
1641 137 : dfMaxX == 180 && dfMaxY == 90)) &&
1642 154 : strcmp(pszProj4,
1643 : "+proj=longlat +datum=WGS84 +no_defs") ==
1644 49 : 0) ||
1645 49 : strcmp(pszDefaultSRS,
1646 : "urn:ogc:def:crs:OGC:1.3:CRS84") == 0)
1647 : {
1648 144 : poLayer->SetWGS84Extents(dfMinX, dfMinY, dfMaxX,
1649 : dfMaxY);
1650 144 : poLayer->SetExtents(dfMinX, dfMinY, dfMaxX, dfMaxY);
1651 : }
1652 :
1653 49 : else if (bTrustBounds)
1654 : {
1655 16 : OGRSpatialReference oWGS84;
1656 8 : oWGS84.SetWellKnownGeogCS("WGS84");
1657 8 : oWGS84.SetAxisMappingStrategy(
1658 : OAMS_TRADITIONAL_GIS_ORDER);
1659 8 : CPLPushErrorHandler(CPLQuietErrorHandler);
1660 : auto poCT =
1661 : std::unique_ptr<OGRCoordinateTransformation>(
1662 : OGRCreateCoordinateTransformation(&oWGS84,
1663 16 : poSRS));
1664 8 : if (poCT)
1665 : {
1666 8 : poLayer->SetWGS84Extents(dfMinX, dfMinY, dfMaxX,
1667 : dfMaxY);
1668 8 : poCT->TransformBounds(dfMinX, dfMinY, dfMaxX,
1669 : dfMaxY, &dfMinX, &dfMinY,
1670 8 : &dfMaxX, &dfMaxY, 20);
1671 8 : poLayer->SetExtents(dfMinX, dfMinY, dfMaxX,
1672 : dfMaxY);
1673 : }
1674 8 : CPLPopErrorHandler();
1675 8 : CPLErrorReset();
1676 : }
1677 : }
1678 193 : CPLFree(pszProj4);
1679 : }
1680 211 : poLayer->SetSupportedSRSList(std::move(aosSupportedCRSList),
1681 211 : std::move(apoSupportedCRSList));
1682 :
1683 422 : papoLayers = static_cast<OGRWFSLayer **>(CPLRealloc(
1684 211 : papoLayers, sizeof(OGRWFSLayer *) * (nLayers + 1)));
1685 211 : papoLayers[nLayers++] = poLayer;
1686 :
1687 211 : if (psFileXML != nullptr)
1688 : {
1689 10 : CPLXMLNode *psIter = psXML->psChild;
1690 36 : while (psIter)
1691 : {
1692 34 : if (psIter->eType == CXT_Element && psIter->psChild &&
1693 82 : EQUAL(psIter->pszValue, "OGRWFSLayer") &&
1694 14 : strcmp(CPLGetXMLValue(psIter, "name", ""),
1695 : l_pszName) == 0)
1696 : {
1697 : const CPLXMLNode *psSchema =
1698 8 : WFSFindNode(psIter->psChild, "schema");
1699 8 : if (psSchema)
1700 : {
1701 : OGRFeatureDefn *poSrcFDefn =
1702 8 : poLayer->ParseSchema(psSchema);
1703 8 : if (poSrcFDefn)
1704 8 : poLayer->BuildLayerDefn(poSrcFDefn);
1705 : }
1706 8 : break;
1707 : }
1708 26 : psIter = psIter->psNext;
1709 : }
1710 : }
1711 : }
1712 : }
1713 : }
1714 :
1715 133 : CSLDestroy(papszTypenames);
1716 :
1717 133 : if (!psFileXML)
1718 127 : CPLDestroyXMLNode(psXML);
1719 133 : CPLDestroyXMLNode(psStrippedXML);
1720 :
1721 133 : return TRUE;
1722 : }
1723 :
1724 : /************************************************************************/
1725 : /* LoadMultipleLayerDefn() */
1726 : /************************************************************************/
1727 :
1728 : /* TinyOWS doesn't support POST, but MapServer, GeoServer and Deegree do */
1729 : #define USE_GET_FOR_DESCRIBE_FEATURE_TYPE 1
1730 :
1731 53 : void OGRWFSDataSource::LoadMultipleLayerDefn(const char *pszLayerName,
1732 : char *pszNS, char *pszNSVal)
1733 : {
1734 53 : if (!bLoadMultipleLayerDefn)
1735 23 : return;
1736 :
1737 44 : if (aoSetAlreadyTriedLayers.find(pszLayerName) !=
1738 88 : aoSetAlreadyTriedLayers.end())
1739 0 : return;
1740 :
1741 44 : std::string osPrefix(pszLayerName);
1742 44 : const auto nColumnPos = osPrefix.find(':');
1743 44 : if (nColumnPos == std::string::npos)
1744 38 : osPrefix.clear();
1745 : else
1746 6 : osPrefix.resize(nColumnPos);
1747 :
1748 : OGRWFSLayer *poRefLayer =
1749 44 : dynamic_cast<OGRWFSLayer *>(GetLayerByName(pszLayerName));
1750 44 : if (poRefLayer == nullptr)
1751 0 : return;
1752 :
1753 44 : const char *pszRequiredOutputFormat = poRefLayer->GetRequiredOutputFormat();
1754 :
1755 : #if USE_GET_FOR_DESCRIBE_FEATURE_TYPE == 1
1756 44 : CPLString osLayerToFetch(pszLayerName);
1757 : #else
1758 : CPLString osTypeNameToPost;
1759 : osTypeNameToPost += " <TypeName>";
1760 : osTypeNameToPost += pszLayerName;
1761 : osTypeNameToPost += "</TypeName>\n";
1762 : #endif
1763 :
1764 44 : int nLayersToFetch = 1;
1765 44 : aoSetAlreadyTriedLayers.insert(pszLayerName);
1766 :
1767 144 : for (int i = 0; i < nLayers; i++)
1768 : {
1769 100 : if (!papoLayers[i]->HasLayerDefn())
1770 : {
1771 : /* We must be careful to requests only layers with the same
1772 : * prefix/namespace */
1773 100 : const char *l_pszName = papoLayers[i]->GetName();
1774 188 : if (((osPrefix.empty() && strchr(l_pszName, ':') == nullptr) ||
1775 12 : (!osPrefix.empty() &&
1776 12 : strncmp(l_pszName, osPrefix.c_str(), osPrefix.size()) == 0 &&
1777 298 : l_pszName[osPrefix.size()] == ':')) &&
1778 98 : ((pszRequiredOutputFormat == nullptr &&
1779 98 : papoLayers[i]->GetRequiredOutputFormat() == nullptr) ||
1780 0 : (pszRequiredOutputFormat != nullptr &&
1781 0 : papoLayers[i]->GetRequiredOutputFormat() != nullptr &&
1782 0 : strcmp(pszRequiredOutputFormat,
1783 0 : papoLayers[i]->GetRequiredOutputFormat()) == 0)))
1784 : {
1785 98 : if (aoSetAlreadyTriedLayers.find(l_pszName) !=
1786 196 : aoSetAlreadyTriedLayers.end())
1787 44 : continue;
1788 54 : aoSetAlreadyTriedLayers.insert(l_pszName);
1789 :
1790 : #if USE_GET_FOR_DESCRIBE_FEATURE_TYPE == 1
1791 54 : if (nLayersToFetch > 0)
1792 54 : osLayerToFetch += ",";
1793 54 : osLayerToFetch += papoLayers[i]->GetName();
1794 : #else
1795 : osTypeNameToPost += " <TypeName>";
1796 : osTypeNameToPost += l_pszName;
1797 : osTypeNameToPost += "</TypeName>\n";
1798 : #endif
1799 54 : nLayersToFetch++;
1800 :
1801 : /* Avoid fetching to many layer definition at a time */
1802 54 : if (nLayersToFetch >= 50)
1803 0 : break;
1804 : }
1805 : }
1806 : }
1807 :
1808 : #if USE_GET_FOR_DESCRIBE_FEATURE_TYPE == 1
1809 44 : CPLString osURL(osBaseURL);
1810 44 : osURL = CPLURLAddKVP(osURL, "SERVICE", "WFS");
1811 44 : osURL = CPLURLAddKVP(osURL, "VERSION", GetVersion());
1812 44 : osURL = CPLURLAddKVP(osURL, "REQUEST", "DescribeFeatureType");
1813 44 : osURL = CPLURLAddKVP(osURL, "TYPENAME", WFS_EscapeURL(osLayerToFetch));
1814 44 : osURL = CPLURLAddKVP(osURL, "PROPERTYNAME", nullptr);
1815 44 : osURL = CPLURLAddKVP(osURL, "MAXFEATURES", nullptr);
1816 44 : osURL = CPLURLAddKVP(osURL, "FILTER", nullptr);
1817 88 : osURL = CPLURLAddKVP(osURL, "OUTPUTFORMAT",
1818 : pszRequiredOutputFormat
1819 44 : ? WFS_EscapeURL(pszRequiredOutputFormat).c_str()
1820 44 : : nullptr);
1821 :
1822 44 : if (pszNS && GetNeedNAMESPACE())
1823 : {
1824 : /* Older Deegree version require NAMESPACE */
1825 : /* This has been now corrected */
1826 0 : CPLString osValue("xmlns(");
1827 0 : osValue += pszNS;
1828 0 : osValue += "=";
1829 0 : osValue += pszNSVal;
1830 0 : osValue += ")";
1831 0 : osURL = CPLURLAddKVP(osURL, "NAMESPACE", WFS_EscapeURL(osValue));
1832 : }
1833 :
1834 44 : CPLHTTPResult *psResult = HTTPFetch(osURL, nullptr);
1835 : #else
1836 : CPLString osPost;
1837 : osPost += "<?xml version=\"1.0\"?>\n";
1838 : osPost +=
1839 : "<wfs:DescribeFeatureType xmlns:wfs=\"http://www.opengis.net/wfs\"\n";
1840 : osPost += " "
1841 : "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n";
1842 : osPost += " service=\"WFS\" version=\"";
1843 : osPost += GetVersion();
1844 : osPost += "\"\n";
1845 : osPost += " xmlns:gml=\"http://www.opengis.net/gml\"\n";
1846 : osPost += " xmlns:ogc=\"http://www.opengis.net/ogc\"\n";
1847 : if (pszNS && pszNSVal)
1848 : {
1849 : osPost += " xmlns:";
1850 : osPost += pszNS;
1851 : osPost += "=\"";
1852 : osPost += pszNSVal;
1853 : osPost += "\"\n";
1854 : }
1855 : osPost +=
1856 : " xsi:schemaLocation=\"http://www.opengis.net/wfs "
1857 : "http://schemas.opengis.net/wfs/";
1858 : osPost += GetVersion();
1859 : osPost += "/wfs.xsd\"";
1860 : const char *pszRequiredOutputFormat = poRefLayer->GetRequiredOutputFormat();
1861 : if (pszRequiredOutputFormat)
1862 : {
1863 : osPost += "\n";
1864 : osPost += " outputFormat=\"";
1865 : osPost += pszRequiredOutputFormat;
1866 : osPost += "\"";
1867 : }
1868 : osPost += ">\n";
1869 : osPost += osTypeNameToPost;
1870 : osPost += "</wfs:DescribeFeatureType>\n";
1871 :
1872 : // CPLDebug("WFS", "%s", osPost.c_str());
1873 :
1874 : char **papszOptions = NULL;
1875 : papszOptions = CSLAddNameValue(papszOptions, "POSTFIELDS", osPost.c_str());
1876 : papszOptions =
1877 : CSLAddNameValue(papszOptions, "HEADERS",
1878 : "Content-Type: application/xml; charset=UTF-8");
1879 :
1880 : CPLHTTPResult *psResult = HTTPFetch(GetPostTransactionURL(), papszOptions);
1881 : CSLDestroy(papszOptions);
1882 : #endif
1883 :
1884 44 : if (psResult == nullptr)
1885 : {
1886 8 : bLoadMultipleLayerDefn = false;
1887 8 : return;
1888 : }
1889 :
1890 36 : if (strstr(reinterpret_cast<const char *>(psResult->pabyData),
1891 : "<ServiceExceptionReport") != nullptr)
1892 : {
1893 2 : if (IsOldDeegree(reinterpret_cast<const char *>(psResult->pabyData)))
1894 : {
1895 : /* just silently forgive */
1896 : }
1897 : else
1898 : {
1899 2 : CPLError(CE_Failure, CPLE_AppDefined,
1900 : "Error returned by server : %s", psResult->pabyData);
1901 : }
1902 2 : CPLHTTPDestroyResult(psResult);
1903 2 : bLoadMultipleLayerDefn = false;
1904 2 : return;
1905 : }
1906 :
1907 : CPLXMLNode *psXML =
1908 34 : CPLParseXMLString(reinterpret_cast<const char *>(psResult->pabyData));
1909 34 : if (psXML == nullptr)
1910 : {
1911 2 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
1912 : psResult->pabyData);
1913 2 : CPLHTTPDestroyResult(psResult);
1914 2 : bLoadMultipleLayerDefn = false;
1915 2 : return;
1916 : }
1917 32 : CPLHTTPDestroyResult(psResult);
1918 :
1919 32 : const CPLXMLNode *psSchema = WFSFindNode(psXML, "schema");
1920 32 : if (psSchema == nullptr)
1921 : {
1922 2 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find <Schema>");
1923 2 : CPLDestroyXMLNode(psXML);
1924 2 : bLoadMultipleLayerDefn = false;
1925 2 : return;
1926 : }
1927 :
1928 60 : const CPLString osTmpFileName = VSIMemGenerateHiddenFilename("file.xsd");
1929 30 : CPLSerializeXMLTreeToFile(psSchema, osTmpFileName);
1930 :
1931 60 : std::vector<GMLFeatureClass *> aosClasses;
1932 30 : bool bFullyUnderstood = false;
1933 30 : bool bUseSchemaImports = false;
1934 30 : GMLParseXSD(osTmpFileName, bUseSchemaImports, aosClasses, bFullyUnderstood);
1935 :
1936 30 : int nLayersFound = 0;
1937 30 : if (!aosClasses.empty())
1938 : {
1939 : std::vector<GMLFeatureClass *>::const_iterator oIter =
1940 30 : aosClasses.begin();
1941 : std::vector<GMLFeatureClass *>::const_iterator oEndIter =
1942 30 : aosClasses.end();
1943 86 : while (oIter != oEndIter)
1944 : {
1945 56 : GMLFeatureClass *poClass = *oIter;
1946 56 : ++oIter;
1947 :
1948 56 : OGRWFSLayer *poLayer = nullptr;
1949 :
1950 56 : if (bKeepLayerNamePrefix && pszNS != nullptr &&
1951 0 : strchr(poClass->GetName(), ':') == nullptr)
1952 : {
1953 0 : CPLString osWithPrefix(pszNS);
1954 0 : osWithPrefix += ":";
1955 0 : osWithPrefix += poClass->GetName();
1956 0 : poLayer =
1957 0 : dynamic_cast<OGRWFSLayer *>(GetLayerByName(osWithPrefix));
1958 : }
1959 : else
1960 56 : poLayer = dynamic_cast<OGRWFSLayer *>(
1961 56 : GetLayerByName(poClass->GetName()));
1962 :
1963 56 : if (poLayer)
1964 : {
1965 54 : if (!poLayer->HasLayerDefn())
1966 : {
1967 54 : nLayersFound++;
1968 :
1969 54 : CPLXMLNode *psSchemaForLayer = CPLCloneXMLTree(psSchema);
1970 54 : CPLStripXMLNamespace(psSchemaForLayer, nullptr, TRUE);
1971 54 : CPLXMLNode *psIter = psSchemaForLayer->psChild;
1972 54 : bool bHasAlreadyImportedGML = false;
1973 54 : bool bFoundComplexType = false;
1974 54 : bool bFoundElement = false;
1975 590 : while (psIter != nullptr)
1976 : {
1977 536 : CPLXMLNode *psIterNext = psIter->psNext;
1978 536 : if (psIter->eType == CXT_Element &&
1979 266 : strcmp(psIter->pszValue, "complexType") == 0)
1980 : {
1981 : const char *l_pszName =
1982 106 : CPLGetXMLValue(psIter, "name", "");
1983 212 : CPLString osExpectedName(poLayer->GetShortName());
1984 106 : osExpectedName += "Type";
1985 212 : CPLString osExpectedName2(poLayer->GetShortName());
1986 106 : osExpectedName2 += "_Type";
1987 106 : if (strcmp(l_pszName, osExpectedName) == 0 ||
1988 158 : strcmp(l_pszName, osExpectedName2) == 0 ||
1989 52 : strcmp(l_pszName, poLayer->GetShortName()) == 0)
1990 : {
1991 54 : bFoundComplexType = true;
1992 : }
1993 : else
1994 : {
1995 52 : CPLRemoveXMLChild(psSchemaForLayer, psIter);
1996 52 : CPLDestroyXMLNode(psIter);
1997 106 : }
1998 : }
1999 430 : else if (psIter->eType == CXT_Element &&
2000 160 : strcmp(psIter->pszValue, "element") == 0)
2001 : {
2002 : const char *l_pszName =
2003 106 : CPLGetXMLValue(psIter, "name", "");
2004 212 : CPLString osExpectedName(poLayer->GetShortName());
2005 106 : osExpectedName += "Type";
2006 212 : CPLString osExpectedName2(poLayer->GetShortName());
2007 106 : osExpectedName2 += "_Type";
2008 :
2009 : const char *pszType =
2010 106 : CPLGetXMLValue(psIter, "type", "");
2011 212 : CPLString osExpectedType(poLayer->GetName());
2012 106 : osExpectedType += "Type";
2013 212 : CPLString osExpectedType2(poLayer->GetName());
2014 106 : osExpectedType2 += "_Type";
2015 106 : if (strcmp(pszType, osExpectedType) == 0 ||
2016 98 : strcmp(pszType, osExpectedType2) == 0 ||
2017 302 : strcmp(pszType, poLayer->GetName()) == 0 ||
2018 98 : (strchr(pszType, ':') &&
2019 98 : (strcmp(strchr(pszType, ':') + 1,
2020 52 : osExpectedType) == 0 ||
2021 52 : strcmp(strchr(pszType, ':') + 1,
2022 : osExpectedType2) == 0)))
2023 : {
2024 54 : bFoundElement = true;
2025 : }
2026 104 : else if (*pszType == '\0' &&
2027 0 : CPLGetXMLNode(psIter, "complexType") !=
2028 52 : nullptr &&
2029 0 : (strcmp(l_pszName, osExpectedName) == 0 ||
2030 0 : strcmp(l_pszName, osExpectedName2) == 0 ||
2031 0 : strcmp(l_pszName,
2032 : poLayer->GetShortName()) == 0))
2033 : {
2034 0 : bFoundElement = true;
2035 0 : bFoundComplexType = true;
2036 : }
2037 : else
2038 : {
2039 52 : CPLRemoveXMLChild(psSchemaForLayer, psIter);
2040 52 : CPLDestroyXMLNode(psIter);
2041 106 : }
2042 : }
2043 702 : else if (psIter->eType == CXT_Element &&
2044 378 : strcmp(psIter->pszValue, "import") == 0 &&
2045 54 : strcmp(CPLGetXMLValue(psIter, "namespace", ""),
2046 : "http://www.opengis.net/gml") == 0)
2047 : {
2048 54 : if (bHasAlreadyImportedGML)
2049 : {
2050 0 : CPLRemoveXMLChild(psSchemaForLayer, psIter);
2051 0 : CPLDestroyXMLNode(psIter);
2052 : }
2053 : else
2054 : {
2055 54 : bHasAlreadyImportedGML = true;
2056 : }
2057 : }
2058 536 : psIter = psIterNext;
2059 : }
2060 :
2061 54 : if (bFoundComplexType && bFoundElement)
2062 : {
2063 : OGRFeatureDefn *poSrcFDefn =
2064 54 : poLayer->ParseSchema(psSchemaForLayer);
2065 54 : if (poSrcFDefn)
2066 : {
2067 54 : poLayer->BuildLayerDefn(poSrcFDefn);
2068 54 : SaveLayerSchema(poLayer->GetName(),
2069 : psSchemaForLayer);
2070 : }
2071 : }
2072 :
2073 54 : CPLDestroyXMLNode(psSchemaForLayer);
2074 : }
2075 : else
2076 : {
2077 0 : CPLDebug("WFS",
2078 : "Found several time schema for layer %s in "
2079 : "server response. Should not happen",
2080 : poClass->GetName());
2081 : }
2082 : }
2083 56 : delete poClass;
2084 : }
2085 : }
2086 :
2087 30 : if (nLayersFound != nLayersToFetch)
2088 : {
2089 4 : CPLDebug("WFS", "Turn off loading of multiple layer definitions at a "
2090 : "single time");
2091 4 : bLoadMultipleLayerDefn = false;
2092 : }
2093 :
2094 30 : VSIUnlink(osTmpFileName);
2095 :
2096 30 : CPLDestroyXMLNode(psXML);
2097 : }
2098 :
2099 : /************************************************************************/
2100 : /* SaveLayerSchema() */
2101 : /************************************************************************/
2102 :
2103 134 : void OGRWFSDataSource::SaveLayerSchema(const char *pszLayerName,
2104 : const CPLXMLNode *psSchema)
2105 : {
2106 134 : if (psFileXML != nullptr)
2107 : {
2108 1 : bRewriteFile = true;
2109 : CPLXMLNode *psLayerNode =
2110 1 : CPLCreateXMLNode(nullptr, CXT_Element, "OGRWFSLayer");
2111 1 : CPLSetXMLValue(psLayerNode, "#name", pszLayerName);
2112 1 : CPLAddXMLChild(psLayerNode, CPLCloneXMLTree(psSchema));
2113 1 : CPLAddXMLChild(psFileXML, psLayerNode);
2114 : }
2115 134 : }
2116 :
2117 : /************************************************************************/
2118 : /* IsOldDeegree() */
2119 : /************************************************************************/
2120 :
2121 5 : bool OGRWFSDataSource::IsOldDeegree(const char *pszErrorString)
2122 : {
2123 5 : if (!bNeedNAMESPACE &&
2124 5 : strstr(pszErrorString, "Invalid \"TYPENAME\" parameter. "
2125 : "No binding for prefix") != nullptr)
2126 : {
2127 0 : bNeedNAMESPACE = true;
2128 0 : return true;
2129 : }
2130 5 : return false;
2131 : }
2132 :
2133 : /************************************************************************/
2134 : /* WFS_EscapeURL() */
2135 : /************************************************************************/
2136 :
2137 562 : CPLString WFS_EscapeURL(const char *pszURL)
2138 : {
2139 562 : CPLString osEscapedURL;
2140 :
2141 : /* Difference with CPLEscapeString(, CPLES_URL) : we do not escape */
2142 : /* colon (:) or comma (,). Causes problems with servers such as
2143 : * http://www.mapinfo.com/miwfs? */
2144 :
2145 37087 : for (int i = 0; pszURL[i] != '\0'; i++)
2146 : {
2147 36525 : char ch = pszURL[i];
2148 36525 : if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
2149 9859 : (ch >= '0' && ch <= '9') || ch == '_' || ch == '.' || ch == ':' ||
2150 : ch == ',')
2151 : {
2152 30637 : osEscapedURL += ch;
2153 : }
2154 : else
2155 : {
2156 : char szPercentEncoded[10];
2157 5888 : snprintf(szPercentEncoded, sizeof(szPercentEncoded), "%%%02X",
2158 5888 : reinterpret_cast<const unsigned char *>(pszURL)[i]);
2159 5888 : osEscapedURL += szPercentEncoded;
2160 : }
2161 : }
2162 :
2163 562 : return osEscapedURL;
2164 : }
2165 :
2166 : /************************************************************************/
2167 : /* WFS_DecodeURL() */
2168 : /************************************************************************/
2169 :
2170 141 : CPLString WFS_DecodeURL(const CPLString &osSrc)
2171 : {
2172 141 : CPLString ret;
2173 141 : for (size_t i = 0; i < osSrc.length(); i++)
2174 : {
2175 0 : if (osSrc[i] == '%' && i + 2 < osSrc.length())
2176 : {
2177 0 : unsigned int ii = 0;
2178 0 : sscanf(osSrc.substr(i + 1, 2).c_str(), "%x", &ii);
2179 0 : char ch = static_cast<char>(ii);
2180 0 : ret += ch;
2181 0 : i = i + 2;
2182 : }
2183 : else
2184 : {
2185 0 : ret += osSrc[i];
2186 : }
2187 : }
2188 141 : return ret;
2189 : }
2190 :
2191 : /************************************************************************/
2192 : /* HTTPFetch() */
2193 : /************************************************************************/
2194 :
2195 494 : CPLHTTPResult *OGRWFSDataSource::HTTPFetch(const char *pszURL,
2196 : char **papszOptions)
2197 : {
2198 494 : char **papszNewOptions = CSLDuplicate(papszOptions);
2199 494 : if (bUseHttp10)
2200 : papszNewOptions =
2201 0 : CSLAddNameValue(papszNewOptions, "HTTP_VERSION", "1.0");
2202 494 : if (papszHttpOptions)
2203 0 : papszNewOptions = CSLMerge(papszNewOptions, papszHttpOptions);
2204 494 : CPLHTTPResult *psResult = CPLHTTPFetch(pszURL, papszNewOptions);
2205 494 : CSLDestroy(papszNewOptions);
2206 :
2207 494 : if (psResult == nullptr)
2208 : {
2209 0 : return nullptr;
2210 : }
2211 494 : if (psResult->nStatus != 0 || psResult->pszErrBuf != nullptr)
2212 : {
2213 : // A few buggy servers return chunked data with erroneous
2214 : // remaining bytes value curl does not like this. Retry with
2215 : // HTTP 1.0 protocol instead that does not support chunked
2216 : // data.
2217 78 : if (psResult->pszErrBuf &&
2218 78 : strstr(psResult->pszErrBuf,
2219 0 : "transfer closed with outstanding read data remaining") &&
2220 0 : !bUseHttp10)
2221 : {
2222 0 : CPLDebug("WFS", "Probably buggy remote server. Retrying with HTTP "
2223 : "1.0 protocol");
2224 0 : bUseHttp10 = true;
2225 0 : CPLHTTPDestroyResult(psResult);
2226 0 : return HTTPFetch(pszURL, papszOptions);
2227 : }
2228 :
2229 78 : CPLError(CE_Failure, CPLE_AppDefined,
2230 : "Error returned by server : %s (%d)",
2231 78 : (psResult->pszErrBuf) ? psResult->pszErrBuf : "unknown",
2232 : psResult->nStatus);
2233 78 : CPLHTTPDestroyResult(psResult);
2234 78 : return nullptr;
2235 : }
2236 416 : if (psResult->pabyData == nullptr)
2237 : {
2238 11 : CPLError(CE_Failure, CPLE_AppDefined,
2239 : "Empty content returned by server");
2240 11 : CPLHTTPDestroyResult(psResult);
2241 11 : return nullptr;
2242 : }
2243 405 : return psResult;
2244 : }
2245 :
2246 : /************************************************************************/
2247 : /* ExecuteSQL() */
2248 : /************************************************************************/
2249 :
2250 60 : OGRLayer *OGRWFSDataSource::ExecuteSQL(const char *pszSQLCommand,
2251 : OGRGeometry *poSpatialFilter,
2252 : const char *pszDialect)
2253 :
2254 : {
2255 60 : while (*pszSQLCommand &&
2256 60 : isspace(static_cast<unsigned char>(*pszSQLCommand)))
2257 0 : ++pszSQLCommand;
2258 :
2259 60 : swq_select_parse_options oParseOptions;
2260 60 : oParseOptions.poCustomFuncRegistrar = WFSGetCustomFuncRegistrar();
2261 :
2262 : /* -------------------------------------------------------------------- */
2263 : /* Use generic implementation for recognized dialects */
2264 : /* -------------------------------------------------------------------- */
2265 60 : if (IsGenericSQLDialect(pszDialect))
2266 : {
2267 0 : OGRLayer *poResLayer = GDALDataset::ExecuteSQL(
2268 0 : pszSQLCommand, poSpatialFilter, pszDialect, &oParseOptions);
2269 0 : oMap[poResLayer] = nullptr;
2270 0 : return poResLayer;
2271 : }
2272 :
2273 : /* -------------------------------------------------------------------- */
2274 : /* Deal with "SELECT _LAST_INSERTED_FIDS_ FROM layername" statement */
2275 : /* -------------------------------------------------------------------- */
2276 60 : if (STARTS_WITH_CI(pszSQLCommand, "SELECT _LAST_INSERTED_FIDS_ FROM "))
2277 : {
2278 6 : const char *pszIter = pszSQLCommand + 33;
2279 74 : while (*pszIter && *pszIter != ' ')
2280 68 : pszIter++;
2281 :
2282 12 : CPLString osName = pszSQLCommand + 33;
2283 6 : osName.resize(pszIter - (pszSQLCommand + 33));
2284 : OGRWFSLayer *poLayer =
2285 6 : dynamic_cast<OGRWFSLayer *>(GetLayerByName(osName));
2286 6 : if (poLayer == nullptr)
2287 : {
2288 2 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer : %s",
2289 : osName.c_str());
2290 2 : return nullptr;
2291 : }
2292 :
2293 : auto poMEMDS = std::unique_ptr<MEMDataset>(
2294 8 : MEMDataset::Create("dummy_name", 0, 0, 0, GDT_Unknown, nullptr));
2295 : OGRLayer *poMEMLayer =
2296 4 : poMEMDS->CreateLayer("FID_LIST", nullptr, wkbNone, nullptr);
2297 4 : OGRFieldDefn oFDefn("gml_id", OFTString);
2298 4 : poMEMLayer->CreateField(&oFDefn);
2299 :
2300 : const std::vector<CPLString> &aosFIDList =
2301 4 : poLayer->GetLastInsertedFIDList();
2302 4 : std::vector<CPLString>::const_iterator oIter = aosFIDList.begin();
2303 4 : std::vector<CPLString>::const_iterator oEndIter = aosFIDList.end();
2304 6 : while (oIter != oEndIter)
2305 : {
2306 2 : const CPLString &osFID = *oIter;
2307 2 : OGRFeature *poFeature = new OGRFeature(poMEMLayer->GetLayerDefn());
2308 2 : poFeature->SetField(0, osFID);
2309 2 : CPL_IGNORE_RET_VAL(poMEMLayer->CreateFeature(poFeature));
2310 2 : delete poFeature;
2311 2 : ++oIter;
2312 : }
2313 :
2314 : OGRLayer *poResLayer =
2315 4 : new OGRWFSWrappedResultLayer(poMEMDS.release(), poMEMLayer);
2316 4 : oMap[poResLayer] = nullptr;
2317 4 : return poResLayer;
2318 : }
2319 :
2320 : /* -------------------------------------------------------------------- */
2321 : /* Deal with "DELETE FROM layer_name WHERE expression" statement */
2322 : /* -------------------------------------------------------------------- */
2323 54 : if (STARTS_WITH_CI(pszSQLCommand, "DELETE FROM "))
2324 : {
2325 12 : const char *pszIter = pszSQLCommand + 12;
2326 112 : while (*pszIter && *pszIter != ' ')
2327 100 : pszIter++;
2328 12 : if (*pszIter == 0)
2329 : {
2330 2 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid statement");
2331 2 : return nullptr;
2332 : }
2333 :
2334 20 : CPLString osName = pszSQLCommand + 12;
2335 10 : osName.resize(pszIter - (pszSQLCommand + 12));
2336 : OGRWFSLayer *poLayer =
2337 10 : dynamic_cast<OGRWFSLayer *>(GetLayerByName(osName));
2338 10 : if (poLayer == nullptr)
2339 : {
2340 2 : CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer : %s",
2341 : osName.c_str());
2342 2 : return nullptr;
2343 : }
2344 :
2345 16 : while (*pszIter == ' ')
2346 8 : pszIter++;
2347 8 : if (!STARTS_WITH_CI(pszIter, "WHERE "))
2348 : {
2349 2 : CPLError(CE_Failure, CPLE_AppDefined, "WHERE clause missing");
2350 2 : return nullptr;
2351 : }
2352 6 : pszIter += 5;
2353 :
2354 6 : const char *pszQuery = pszIter;
2355 :
2356 : /* Check with the generic SQL engine that this is a valid WHERE clause
2357 : */
2358 12 : OGRFeatureQuery oQuery;
2359 6 : OGRErr eErr = oQuery.Compile(poLayer->GetLayerDefn(), pszQuery);
2360 6 : if (eErr != OGRERR_NONE)
2361 : {
2362 2 : return nullptr;
2363 : }
2364 :
2365 : /* Now turn this into OGC Filter language if possible */
2366 4 : int bNeedsNullCheck = FALSE;
2367 4 : int nVersion = (strcmp(GetVersion(), "1.0.0") == 0) ? 100 : 110;
2368 : swq_expr_node *poNode =
2369 4 : static_cast<swq_expr_node *>(oQuery.GetSWQExpr());
2370 4 : poNode->ReplaceBetweenByGEAndLERecurse();
2371 4 : poNode->ReplaceInByOrRecurse();
2372 : CPLString osOGCFilter = WFS_TurnSQLFilterToOGCFilter(
2373 : poNode, nullptr, poLayer->GetLayerDefn(), nVersion,
2374 4 : bPropertyIsNotEqualToSupported, bUseFeatureId,
2375 8 : bGmlObjectIdNeedsGMLPrefix, "", &bNeedsNullCheck);
2376 4 : if (bNeedsNullCheck && !HasNullCheck())
2377 0 : osOGCFilter = "";
2378 :
2379 4 : if (osOGCFilter.empty())
2380 : {
2381 2 : CPLError(CE_Failure, CPLE_AppDefined,
2382 : "Cannot convert WHERE clause into a OGC filter");
2383 2 : return nullptr;
2384 : }
2385 :
2386 2 : poLayer->DeleteFromFilter(osOGCFilter);
2387 :
2388 2 : return nullptr;
2389 : }
2390 :
2391 : /* -------------------------------------------------------------------- */
2392 : /* Deal with "SELECT xxxx ORDER BY" statement */
2393 : /* -------------------------------------------------------------------- */
2394 42 : if (STARTS_WITH_CI(pszSQLCommand, "SELECT"))
2395 : {
2396 42 : swq_select *psSelectInfo = new swq_select();
2397 42 : if (psSelectInfo->preparse(pszSQLCommand, TRUE) != CE_None)
2398 : {
2399 0 : delete psSelectInfo;
2400 0 : return nullptr;
2401 : }
2402 42 : int iLayer = 0;
2403 42 : if (strcmp(GetVersion(), "1.0.0") != 0 &&
2404 42 : psSelectInfo->table_count == 1 &&
2405 16 : psSelectInfo->table_defs[0].data_source == nullptr &&
2406 8 : (iLayer = GetLayerIndex(psSelectInfo->table_defs[0].table_name)) >=
2407 8 : 0 &&
2408 84 : psSelectInfo->join_count == 0 && psSelectInfo->order_specs > 0 &&
2409 0 : psSelectInfo->poOtherSelect == nullptr)
2410 : {
2411 0 : OGRWFSLayer *poSrcLayer = papoLayers[iLayer];
2412 0 : std::vector<OGRWFSSortDesc> aoSortColumns;
2413 0 : int i = 0; // Used after for.
2414 0 : for (; i < psSelectInfo->order_specs; i++)
2415 : {
2416 0 : int nFieldIndex = poSrcLayer->GetLayerDefn()->GetFieldIndex(
2417 0 : psSelectInfo->order_defs[i].field_name);
2418 0 : if (poSrcLayer->HasGotApproximateLayerDefn() || nFieldIndex < 0)
2419 0 : break;
2420 :
2421 : /* Make sure to have the right case */
2422 0 : const char *pszFieldName = poSrcLayer->GetLayerDefn()
2423 0 : ->GetFieldDefn(nFieldIndex)
2424 0 : ->GetNameRef();
2425 :
2426 : aoSortColumns.emplace_back(
2427 0 : pszFieldName, psSelectInfo->order_defs[i].ascending_flag);
2428 : }
2429 :
2430 0 : if (i == psSelectInfo->order_specs)
2431 : {
2432 0 : OGRWFSLayer *poDupLayer = poSrcLayer->Clone();
2433 :
2434 0 : poDupLayer->SetOrderBy(aoSortColumns);
2435 0 : int nBackup = psSelectInfo->order_specs;
2436 0 : psSelectInfo->order_specs = 0;
2437 0 : char *pszSQLWithoutOrderBy = psSelectInfo->Unparse();
2438 0 : CPLDebug("WFS", "SQL without ORDER BY: %s",
2439 : pszSQLWithoutOrderBy);
2440 0 : psSelectInfo->order_specs = nBackup;
2441 0 : delete psSelectInfo;
2442 0 : psSelectInfo = nullptr;
2443 :
2444 : /* Just set poDupLayer in the papoLayers for the time of the */
2445 : /* base ExecuteSQL(), so that the OGRGenSQLResultsLayer
2446 : * references */
2447 : /* that temporary layer */
2448 0 : papoLayers[iLayer] = poDupLayer;
2449 :
2450 0 : OGRLayer *poResLayer = GDALDataset::ExecuteSQL(
2451 : pszSQLWithoutOrderBy, poSpatialFilter, pszDialect,
2452 0 : &oParseOptions);
2453 0 : papoLayers[iLayer] = poSrcLayer;
2454 :
2455 0 : CPLFree(pszSQLWithoutOrderBy);
2456 :
2457 0 : if (poResLayer != nullptr)
2458 0 : oMap[poResLayer] = poDupLayer;
2459 : else
2460 0 : delete poDupLayer;
2461 0 : return poResLayer;
2462 : }
2463 : }
2464 42 : else if (bStandardJoinsWFS2 && psSelectInfo->join_count > 0 &&
2465 34 : psSelectInfo->poOtherSelect == nullptr)
2466 : {
2467 : // Just to make sure everything is valid, but we won't use
2468 : // that one as we want to run the join on server-side
2469 34 : oParseOptions.bAllowFieldsInSecondaryTablesInWhere = TRUE;
2470 34 : oParseOptions.bAddSecondaryTablesGeometryFields = TRUE;
2471 34 : oParseOptions.bAlwaysPrefixWithTableName = TRUE;
2472 34 : oParseOptions.bAllowDistinctOnGeometryField = TRUE;
2473 34 : oParseOptions.bAllowDistinctOnMultipleFields = TRUE;
2474 : GDALSQLParseInfo *psParseInfo =
2475 34 : BuildParseInfo(psSelectInfo, &oParseOptions);
2476 34 : oParseOptions.bAllowFieldsInSecondaryTablesInWhere = FALSE;
2477 34 : oParseOptions.bAddSecondaryTablesGeometryFields = FALSE;
2478 34 : oParseOptions.bAlwaysPrefixWithTableName = FALSE;
2479 34 : oParseOptions.bAllowDistinctOnGeometryField = FALSE;
2480 34 : oParseOptions.bAllowDistinctOnMultipleFields = FALSE;
2481 34 : const bool bOK = psParseInfo != nullptr;
2482 34 : DestroyParseInfo(psParseInfo);
2483 :
2484 34 : OGRLayer *poResLayer = nullptr;
2485 34 : if (bOK)
2486 : {
2487 34 : poResLayer = OGRWFSJoinLayer::Build(this, psSelectInfo);
2488 34 : oMap[poResLayer] = nullptr;
2489 : }
2490 :
2491 34 : delete psSelectInfo;
2492 34 : return poResLayer;
2493 : }
2494 :
2495 8 : delete psSelectInfo;
2496 : }
2497 :
2498 8 : OGRLayer *poResLayer = GDALDataset::ExecuteSQL(
2499 8 : pszSQLCommand, poSpatialFilter, pszDialect, &oParseOptions);
2500 8 : oMap[poResLayer] = nullptr;
2501 8 : return poResLayer;
2502 : }
2503 :
2504 : /************************************************************************/
2505 : /* ReleaseResultSet() */
2506 : /************************************************************************/
2507 :
2508 40 : void OGRWFSDataSource::ReleaseResultSet(OGRLayer *poResultsSet)
2509 : {
2510 40 : if (poResultsSet == nullptr)
2511 0 : return;
2512 :
2513 40 : std::map<OGRLayer *, OGRLayer *>::iterator oIter = oMap.find(poResultsSet);
2514 40 : if (oIter != oMap.end())
2515 : {
2516 : /* Destroy first the result layer, because it still references */
2517 : /* the poDupLayer (oIter->second) */
2518 40 : delete poResultsSet;
2519 :
2520 40 : delete oIter->second;
2521 40 : oMap.erase(oIter);
2522 : }
2523 : else
2524 : {
2525 0 : CPLError(CE_Failure, CPLE_AppDefined,
2526 : "Trying to destroy an invalid result set !");
2527 : }
2528 : }
|