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