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