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