Coverage Report

Created: 2025-06-13 06:29

/src/MapServer/src/mapwms.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 * $Id$
3
 *
4
 * Project:  MapServer
5
 * Purpose:  OpenGIS Web Mapping Service support implementation.
6
 * Author:   Steve Lime and the MapServer team.
7
 *
8
 ******************************************************************************
9
 * Copyright (c) 1996-2005 Regents of the University of Minnesota.
10
 *
11
 * Permission is hereby granted, free of charge, to any person obtaining a
12
 * copy of this software and associated documentation files (the "Software"),
13
 * to deal in the Software without restriction, including without limitation
14
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15
 * and/or sell copies of the Software, and to permit persons to whom the
16
 * Software is furnished to do so, subject to the following conditions:
17
 *
18
 * The above copyright notice and this permission notice shall be included in
19
 * all copies of this Software or works derived from this Software.
20
 *
21
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27
 * DEALINGS IN THE SOFTWARE.
28
 *****************************************************************************/
29
30
#define NEED_IGNORE_RET_VAL
31
32
#include "mapserver.h"
33
#include "maperror.h"
34
#include "mapthread.h"
35
#include "mapgml.h"
36
#include <ctype.h>
37
#include "maptemplate.h"
38
#include "mapows.h"
39
40
#include "mapogcsld.h"
41
#include "mapogcfilter.h"
42
#include "mapowscommon.h"
43
44
#include "maptime.h"
45
#include "mapproject.h"
46
47
#include <cassert>
48
#include <stdarg.h>
49
#include <time.h>
50
#include <string.h>
51
52
#include <set>
53
#include <string>
54
#include <vector>
55
56
#ifdef _WIN32
57
#include <process.h>
58
#endif
59
60
/* ==================================================================
61
 * WMS Server stuff.
62
 * ================================================================== */
63
#ifdef USE_WMS_SVR
64
65
/*
66
** msWMSException()
67
**
68
** Report current MapServer error in requested format.
69
*/
70
71
static int msWMSException(mapObj *map, int nVersion, const char *exception_code,
72
0
                          const char *wms_exception_format) {
73
0
  char *schemalocation = NULL;
74
75
  /* Default to WMS 1.3.0 exceptions if version not set yet */
76
0
  if (nVersion <= 0)
77
0
    nVersion = OWS_1_3_0;
78
79
  /* get scheam location */
80
0
  schemalocation = msEncodeHTMLEntities(msOWSGetSchemasLocation(map));
81
82
  /* Establish default exception format depending on VERSION */
83
0
  if (wms_exception_format == NULL) {
84
0
    if (nVersion <= OWS_1_0_0)
85
0
      wms_exception_format = "INIMAGE"; /* WMS 1.0.0 */
86
0
    else if (nVersion <= OWS_1_0_7)
87
0
      wms_exception_format = "SE_XML"; /* WMS 1.0.1 to 1.0.7 */
88
0
    else if (nVersion <= OWS_1_1_1)
89
0
      wms_exception_format =
90
0
          "application/vnd.ogc.se_xml"; /* WMS 1.1.0 and later */
91
0
    else
92
0
      wms_exception_format = "text/xml";
93
0
  }
94
95
0
  errorObj *ms_error = msGetErrorObj();
96
0
  if (ms_error && ms_error->http_status[0]) {
97
0
    msIO_setHeader("Status", "%s", ms_error->http_status);
98
0
  }
99
100
0
  if (strcasecmp(wms_exception_format, "INIMAGE") == 0 ||
101
0
      strcasecmp(wms_exception_format, "BLANK") == 0 ||
102
0
      strcasecmp(wms_exception_format, "application/vnd.ogc.se_inimage") == 0 ||
103
0
      strcasecmp(wms_exception_format, "application/vnd.ogc.se_blank") == 0) {
104
0
    int blank = 0;
105
106
0
    if (strcasecmp(wms_exception_format, "BLANK") == 0 ||
107
0
        strcasecmp(wms_exception_format, "application/vnd.ogc.se_blank") == 0) {
108
0
      blank = 1;
109
0
    }
110
111
0
    msWriteErrorImage(map, NULL, blank);
112
113
0
  } else if (strcasecmp(wms_exception_format, "WMS_XML") ==
114
0
             0) { /* Only in V1.0.0 */
115
0
    msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
116
0
    msIO_sendHeaders();
117
118
0
    msIO_printf("<WMTException version=\"1.0.0\">\n");
119
0
    msWriteErrorXML(stdout);
120
0
    msIO_printf("</WMTException>\n");
121
0
  } else /* XML error, the default: SE_XML (1.0.1 to 1.0.7) */
122
  /* or application/vnd.ogc.se_xml (1.1.0 and later) */
123
0
  {
124
0
    if (nVersion <= OWS_1_0_7) {
125
      /* In V1.0.1 to 1.0.7, the MIME type was text/xml */
126
0
      msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
127
0
      msIO_sendHeaders();
128
129
0
      msIO_printf(
130
0
          "<?xml version='1.0' encoding=\"UTF-8\" standalone=\"no\" ?>\n");
131
0
      msIO_printf(
132
0
          "<!DOCTYPE ServiceExceptionReport SYSTEM "
133
0
          "\"http://www.digitalearth.gov/wmt/xml/exception_1_0_1.dtd\">\n");
134
135
0
      msIO_printf("<ServiceExceptionReport version=\"1.0.1\">\n");
136
0
    } else if (nVersion <= OWS_1_1_0) {
137
      /* In V1.1.0 and later, we have OGC-specific MIME types */
138
      /* we cannot return anything else than application/vnd.ogc.se_xml here. */
139
0
      msIO_setHeader("Content-Type",
140
0
                     "application/vnd.ogc.se_xml; charset=UTF-8");
141
0
      msIO_sendHeaders();
142
143
0
      msIO_printf(
144
0
          "<?xml version='1.0' encoding=\"UTF-8\" standalone=\"no\" ?>\n");
145
146
0
      msIO_printf("<!DOCTYPE ServiceExceptionReport SYSTEM "
147
0
                  "\"%s/wms/1.1.0/exception_1_1_0.dtd\">\n",
148
0
                  schemalocation);
149
150
0
      msIO_printf("<ServiceExceptionReport version=\"1.1.0\">\n");
151
0
    } else if (nVersion <= OWS_1_1_1) { /* 1.1.1 */
152
0
      msIO_setHeader("Content-Type",
153
0
                     "application/vnd.ogc.se_xml; charset=UTF-8");
154
0
      msIO_sendHeaders();
155
156
0
      msIO_printf(
157
0
          "<?xml version='1.0' encoding=\"UTF-8\" standalone=\"no\" ?>\n");
158
0
      msIO_printf("<!DOCTYPE ServiceExceptionReport SYSTEM "
159
0
                  "\"%s/wms/1.1.1/exception_1_1_1.dtd\">\n",
160
0
                  schemalocation);
161
0
      msIO_printf("<ServiceExceptionReport version=\"1.1.1\">\n");
162
0
    } else { /*1.3.0*/
163
0
      if (strcasecmp(wms_exception_format, "application/vnd.ogc.se_xml") == 0) {
164
0
        msIO_setHeader("Content-Type",
165
0
                       "application/vnd.ogc.se_xml; charset=UTF-8");
166
0
      } else {
167
0
        msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
168
0
      }
169
0
      msIO_sendHeaders();
170
171
0
      msIO_printf(
172
0
          "<?xml version='1.0' encoding=\"UTF-8\" standalone=\"no\" ?>\n");
173
0
      msIO_printf("<ServiceExceptionReport version=\"1.3.0\" "
174
0
                  "xmlns=\"http://www.opengis.net/ogc\" "
175
0
                  "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
176
0
                  "xsi:schemaLocation=\"http://www.opengis.net/ogc "
177
0
                  "%s/wms/1.3.0/exceptions_1_3_0.xsd\">\n",
178
0
                  schemalocation);
179
0
    }
180
181
0
    if (exception_code)
182
0
      msIO_printf("<ServiceException code=\"%s\">\n", exception_code);
183
0
    else
184
0
      msIO_printf("<ServiceException>\n");
185
0
    msWriteErrorXML(stdout);
186
0
    msIO_printf("</ServiceException>\n");
187
0
    msIO_printf("</ServiceExceptionReport>\n");
188
0
  }
189
0
  free(schemalocation);
190
191
0
  return MS_FAILURE; /* so that we can call 'return msWMSException();' anywhere
192
                      */
193
0
}
194
195
static bool msWMSSetTimePattern(const char *timepatternstring,
196
0
                                const char *timestring, bool checkonly) {
197
0
  if (timepatternstring && timestring) {
198
    /* parse the time parameter to extract a distinct time. */
199
    /* time value can be discrete times (eg 2004-09-21), */
200
    /* multiple times (2004-09-21, 2004-09-22, ...) */
201
    /* and range(s) (2004-09-21/2004-09-25, 2004-09-27/2004-09-29) */
202
0
    const auto atimes = msStringSplit(timestring, ',');
203
204
    /* get the pattern to use */
205
0
    if (!atimes.empty()) {
206
0
      auto patterns = msStringSplit(timepatternstring, ',');
207
0
      for (auto &pattern : patterns) {
208
0
        msStringTrimBlanks(pattern);
209
0
        msStringTrimLeft(pattern);
210
0
      }
211
212
0
      for (const auto &atime : atimes) {
213
0
        const auto ranges = msStringSplit(atime.c_str(), '/');
214
0
        for (const auto &range : ranges) {
215
0
          bool match = false;
216
0
          for (const auto &pattern : patterns) {
217
0
            if (!pattern.empty()) {
218
0
              if (msTimeMatchPattern(range.c_str(), pattern.c_str()) ==
219
0
                  MS_TRUE) {
220
0
                if (!checkonly)
221
0
                  msSetLimitedPatternsToUse(pattern.c_str());
222
0
                match = true;
223
0
                break;
224
0
              }
225
0
            }
226
0
          }
227
0
          if (!match) {
228
0
            msSetErrorWithStatus(
229
0
                MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
230
0
                "Time value %s given does not match the time format pattern.",
231
0
                "msWMSSetTimePattern", range.c_str());
232
0
            return false;
233
0
          }
234
0
        }
235
0
      }
236
0
    }
237
0
  }
238
239
0
  return true;
240
0
}
241
242
/*
243
** Apply the TIME parameter to layers that are time aware
244
*/
245
static int msWMSApplyTime(mapObj *map, int version, const char *time,
246
0
                          const char *wms_exception_format) {
247
0
  if (map) {
248
249
0
    const char *timepattern =
250
0
        msOWSLookupMetadata(&(map->web.metadata), "MO", "timeformat");
251
252
0
    for (int i = 0; i < map->numlayers; i++) {
253
0
      layerObj *lp = (GET_LAYER(map, i));
254
0
      if (lp->status != MS_ON && lp->status != MS_DEFAULT)
255
0
        continue;
256
257
      /* check if the layer is time aware */
258
0
      const char *timeextent =
259
0
          msOWSLookupMetadata(&(lp->metadata), "MO", "timeextent");
260
0
      const char *timefield =
261
0
          msOWSLookupMetadata(&(lp->metadata), "MO", "timeitem");
262
0
      const char *timedefault =
263
0
          msOWSLookupMetadata(&(lp->metadata), "MO", "timedefault");
264
265
0
      if (timeextent && timefield) {
266
        /* check to see if the time value is given. If not */
267
        /* use default time. If default time is not available */
268
        /* send an exception */
269
0
        if (time == NULL || strlen(time) <= 0) {
270
0
          if (timedefault == NULL) {
271
0
            msSetErrorWithStatus(
272
0
                MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
273
0
                "No Time value was given, and no default time value defined.",
274
0
                "msWMSApplyTime");
275
0
            return msWMSException(map, version, "MissingDimensionValue",
276
0
                                  wms_exception_format);
277
0
          } else {
278
0
            if (msValidateTimeValue((char *)timedefault, timeextent) ==
279
0
                MS_FALSE) {
280
0
              msSetErrorWithStatus(
281
0
                  MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
282
0
                  "No Time value was given, and the default time value "
283
0
                  "%s is invalid or outside the time extent defined %s",
284
0
                  "msWMSApplyTime", timedefault, timeextent);
285
              /* return MS_FALSE; */
286
0
              return msWMSException(map, version, "InvalidDimensionValue",
287
0
                                    wms_exception_format);
288
0
            }
289
0
            msLayerSetTimeFilter(lp, timedefault, timefield);
290
0
          }
291
0
        } else {
292
          /*
293
          ** Check to see if there is a list of possible patterns defined. If it
294
          *is the case, use
295
          ** it to set the time pattern to use for the request.
296
          **
297
          ** Last argument is set to TRUE (checkonly) to not trigger the
298
          *patterns info setting, rather
299
          ** to only apply the wms_timeformats on the user request values, not
300
          *the mapfile values.
301
          */
302
0
          if (timepattern && time && strlen(time) > 0) {
303
0
            if (!msWMSSetTimePattern(timepattern, time, true))
304
0
              return msWMSException(map, version, "InvalidDimensionValue",
305
0
                                    wms_exception_format);
306
0
          }
307
308
          /* check if given time is in the range */
309
0
          if (msValidateTimeValue(time, timeextent) == MS_FALSE) {
310
0
            if (timedefault == NULL) {
311
0
              msSetErrorWithStatus(
312
0
                  MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
313
0
                  "Time value(s) %s given is invalid or outside the "
314
0
                  "time extent defined (%s).",
315
0
                  "msWMSApplyTime", time, timeextent);
316
              /* return MS_FALSE; */
317
0
              return msWMSException(map, version, "InvalidDimensionValue",
318
0
                                    wms_exception_format);
319
0
            } else {
320
0
              if (msValidateTimeValue((char *)timedefault, timeextent) ==
321
0
                  MS_FALSE) {
322
0
                msSetErrorWithStatus(
323
0
                    MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
324
0
                    "Time value(s) %s given is invalid or outside the time "
325
0
                    "extent defined (%s), and default time set is invalid (%s)",
326
0
                    "msWMSApplyTime", time, timeextent, timedefault);
327
                /* return MS_FALSE; */
328
0
                return msWMSException(map, version, "InvalidDimensionValue",
329
0
                                      wms_exception_format);
330
0
              } else
331
0
                msLayerSetTimeFilter(lp, timedefault, timefield);
332
0
            }
333
334
0
          } else {
335
            /* build the time string */
336
0
            msLayerSetTimeFilter(lp, time, timefield);
337
0
            timeextent = NULL;
338
0
          }
339
0
        }
340
0
      }
341
0
    }
342
343
    /* last argument is MS_FALSE to trigger a method call that set the patterns
344
       info. some drivers use it */
345
0
    if (timepattern && time && strlen(time) > 0) {
346
0
      if (!msWMSSetTimePattern(timepattern, time, false))
347
0
        return msWMSException(map, version, "InvalidDimensionValue",
348
0
                              wms_exception_format);
349
0
    }
350
0
  }
351
352
0
  return MS_SUCCESS;
353
0
}
354
355
/*
356
** Apply the FILTER parameter to layers (RFC118)
357
*/
358
static int msWMSApplyFilter(mapObj *map, int version, const char *filter,
359
                            int def_srs_needs_axis_swap,
360
                            const char *wms_exception_format,
361
0
                            owsRequestObj *ows_request) {
362
  // Empty filter should be ignored
363
0
  if (!filter || strlen(filter) == 0)
364
0
    return MS_SUCCESS;
365
366
0
  if (!map)
367
0
    return MS_FAILURE;
368
369
  /* Count number of requested layers / groups / etc.
370
   * Only layers with STATUS ON were in the LAYERS request param.
371
   * Layers with STATUS DEFAULT were set in the mapfile and are
372
   * not expected to have a corresponding filter in the request
373
   */
374
0
  int numlayers = 0;
375
0
  for (int i = 0; i < map->numlayers; i++) {
376
0
    layerObj *lp = NULL;
377
378
0
    if (map->layerorder[i] != -1) {
379
0
      lp = (GET_LAYER(map, map->layerorder[i]));
380
0
      if (lp->status == MS_ON)
381
0
        numlayers++;
382
0
    }
383
0
  }
384
385
  /* -------------------------------------------------------------------- */
386
  /*      Parse the Filter parameter. If there are several Filter         */
387
  /*      parameters, each Filter is inside parentheses.                  */
388
  /* -------------------------------------------------------------------- */
389
0
  int numfilters = 0;
390
0
  char **paszFilters = NULL;
391
0
  if (filter[0] == '(') {
392
0
    paszFilters = FLTSplitFilters(filter, &numfilters);
393
394
0
  } else if (numlayers == 1) {
395
0
    numfilters = 1;
396
0
    paszFilters = (char **)msSmallMalloc(sizeof(char *) * numfilters);
397
0
    paszFilters[0] = msStrdup(filter);
398
0
  }
399
400
0
  if (numfilters != ows_request->numwmslayerargs) {
401
0
    msSetErrorWithStatus(
402
0
        MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
403
0
        "Wrong number of filter elements, one filter must be specified "
404
0
        "for each requested layer or groups.",
405
0
        "msWMSApplyFilter");
406
0
    msFreeCharArray(paszFilters, numfilters);
407
0
    return msWMSException(map, version, "InvalidParameterValue",
408
0
                          wms_exception_format);
409
0
  }
410
411
  /* We're good to go. Apply each filter to the corresponding layer */
412
0
  for (int i = 0; i < map->numlayers; i++) {
413
0
    layerObj *lp = NULL;
414
415
0
    if (map->layerorder[i] != -1)
416
0
      lp = (GET_LAYER(map, map->layerorder[i]));
417
418
    /* Only layers with STATUS ON were in the LAYERS request param.*/
419
0
    if (lp == NULL || lp->status != MS_ON)
420
0
      continue;
421
422
0
    const int curfilter = ows_request->layerwmsfilterindex[lp->index];
423
424
    /* Skip empty filters */
425
0
    assert(paszFilters);
426
0
    assert(curfilter >= 0 && curfilter < numfilters);
427
0
    if (paszFilters[curfilter][0] == '\0') {
428
0
      continue;
429
0
    }
430
431
    /* Force setting a template to enable query. */
432
0
    if (lp->_template == NULL)
433
0
      lp->_template = msStrdup("ttt.html");
434
435
    /* Parse filter */
436
0
    FilterEncodingNode *psNode = FLTParseFilterEncoding(paszFilters[curfilter]);
437
0
    if (!psNode) {
438
0
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
439
0
                           "Invalid or Unsupported FILTER : %s",
440
0
                           "msWMSApplyFilter()", paszFilters[curfilter]);
441
0
      msFreeCharArray(paszFilters, numfilters);
442
0
      return msWMSException(map, version, "InvalidParameterValue",
443
0
                            wms_exception_format);
444
0
    }
445
446
    /* For WMS 1.3 and up, we may need to swap the axis of bbox and geometry
447
     * elements inside the filter(s)
448
     */
449
0
    if (version >= OWS_1_3_0)
450
0
      FLTDoAxisSwappingIfNecessary(map, psNode, def_srs_needs_axis_swap);
451
452
#ifdef do_we_need_this
453
    FLTProcessPropertyIsNull(psNode, map, lp->index);
454
455
    /*preparse the filter for gml aliases*/
456
    FLTPreParseFilterForAliasAndGroup(psNode, map, lp->index, "G");
457
458
    /* Check that FeatureId filters are consistent with the active layer */
459
    if (FLTCheckFeatureIdFilters(psNode, map, lp->index) == MS_FAILURE) {
460
      FLTFreeFilterEncodingNode(psNode);
461
      return msWFSException(map, "mapserv", MS_OWS_ERROR_NO_APPLICABLE_CODE,
462
                            paramsObj->pszVersion);
463
    }
464
465
    /* FIXME?: could probably apply to WFS 1.1 too */
466
    if (nWFSVersion >= OWS_2_0_0) {
467
      int nEvaluation;
468
469
      if (FLTCheckInvalidOperand(psNode) == MS_FAILURE) {
470
        FLTFreeFilterEncodingNode(psNode);
471
        msFreeCharArray(paszFilters, numfilters);
472
        return msWFSException(map, "filter",
473
                              MS_WFS_ERROR_OPERATION_PROCESSING_FAILED,
474
                              paramsObj->pszVersion);
475
      }
476
477
      if (FLTCheckInvalidProperty(psNode, map, lp->index) == MS_FAILURE) {
478
        FLTFreeFilterEncodingNode(psNode);
479
        msFreeCharArray(paszFilters, numfilters);
480
        return msWFSException(map, "filter",
481
                              MS_OWS_ERROR_INVALID_PARAMETER_VALUE,
482
                              paramsObj->pszVersion);
483
      }
484
485
      psNode = FLTSimplify(psNode, &nEvaluation);
486
      if (psNode == NULL) {
487
        FLTFreeFilterEncodingNode(psNode);
488
        msFreeCharArray(paszFilters, numfilters);
489
        if (nEvaluation == 1) {
490
          /* return full layer */
491
          return msWFSRunBasicGetFeature(map, lp, paramsObj, nWFSVersion);
492
        } else {
493
          /* return empty result set */
494
          return MS_SUCCESS;
495
        }
496
      }
497
    }
498
499
#endif
500
501
    /* Apply filter to this layer */
502
503
    /* But first, start by removing any wfs_use_default_extent_for_getfeature
504
     * metadata item */
505
    /* that could result in the BBOX to be removed */
506
0
    std::string old_value_wfs_use_default_extent_for_getfeature;
507
0
    {
508
0
      const char *old_value_tmp = msLookupHashTable(
509
0
          &(lp->metadata), "wfs_use_default_extent_for_getfeature");
510
0
      if (old_value_tmp) {
511
0
        old_value_wfs_use_default_extent_for_getfeature = old_value_tmp;
512
0
        msRemoveHashTable(&(lp->metadata),
513
0
                          "wfs_use_default_extent_for_getfeature");
514
0
      }
515
0
    }
516
517
0
    msInsertHashTable(&(lp->metadata), "gml_wmsfilter_flag", "true");
518
519
0
    int ret = FLTApplyFilterToLayer(psNode, map, lp->index);
520
521
0
    msRemoveHashTable(&(lp->metadata), "gml_wmsfilter_flag");
522
523
0
    if (!old_value_wfs_use_default_extent_for_getfeature.empty()) {
524
0
      msInsertHashTable(
525
0
          &(lp->metadata), "wfs_use_default_extent_for_getfeature",
526
0
          old_value_wfs_use_default_extent_for_getfeature.c_str());
527
0
    }
528
529
0
    if (ret != MS_SUCCESS) {
530
0
      errorObj *ms_error = msGetErrorObj();
531
532
0
      if (ms_error->code != MS_NOTFOUND) {
533
0
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_500_INTERNAL_SERVER_ERROR,
534
0
                             "FLTApplyFilterToLayer() failed",
535
0
                             "msWMSApplyFilter()");
536
0
        FLTFreeFilterEncodingNode(psNode);
537
0
        msFreeCharArray(paszFilters, numfilters);
538
0
        return msWMSException(map, version, "InvalidParameterValue",
539
0
                              wms_exception_format);
540
0
      }
541
0
    }
542
543
0
    FLTFreeFilterEncodingNode(psNode);
544
545
0
  } /* for */
546
547
0
  msFreeCharArray(paszFilters, numfilters);
548
549
0
  return MS_SUCCESS;
550
0
}
551
552
/*
553
** msWMSPrepareNestedGroups()
554
**
555
** purpose: Parse WMS_LAYER_GROUP settings into arrays
556
**
557
** params:
558
** - nestedGroups: This array holds the arrays of groups that have been set
559
**                 through the WMS_LAYER_GROUP metadata
560
** - numNestedGroups: This array holds the number of groups set in
561
**                    WMS_LAYER_GROUP for each layer
562
** - isUsedInNestedGroup: This array indicates if the layer is used as group
563
**                        as set through the WMS_LAYER_GROUP metadata
564
*/
565
static void msWMSPrepareNestedGroups(mapObj *map, int /* nVersion */,
566
                                     char ***nestedGroups, int *numNestedGroups,
567
0
                                     int *isUsedInNestedGroup) {
568
  // Create set to hold unique groups
569
0
  std::set<std::string> uniqgroups;
570
571
0
  for (int i = 0; i < map->numlayers; i++) {
572
0
    nestedGroups[i] = NULL;     /* default */
573
0
    numNestedGroups[i] = 0;     /* default */
574
0
    isUsedInNestedGroup[i] = 0; /* default */
575
576
0
    const char *groups = msOWSLookupMetadata(&(GET_LAYER(map, i)->metadata),
577
0
                                             "MO", "layer_group");
578
0
    if ((groups != NULL) && (strlen(groups) != 0)) {
579
0
      if (GET_LAYER(map, i)->group != NULL &&
580
0
          strlen(GET_LAYER(map, i)->group) != 0) {
581
0
        const char *errorMsg = "It is not allowed to set both the GROUP and "
582
0
                               "WMS_LAYER_GROUP for a layer";
583
0
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_500_INTERNAL_SERVER_ERROR,
584
0
                             errorMsg, "msWMSPrepareNestedGroups()", NULL);
585
0
        msIO_fprintf(stdout, "<!-- ERROR: %s -->\n", errorMsg);
586
        /* cannot return exception at this point because we are already writing
587
         * to stdout */
588
0
      } else {
589
0
        if (groups[0] != '/') {
590
0
          const char *errorMsg =
591
0
              "The WMS_LAYER_GROUP metadata does not start with a '/'";
592
0
          msSetErrorWithStatus(MS_WMSERR, MS_HTTP_500_INTERNAL_SERVER_ERROR,
593
0
                               errorMsg, "msWMSPrepareNestedGroups()", NULL);
594
0
          msIO_fprintf(stdout, "<!-- ERROR: %s -->\n", errorMsg);
595
          /* cannot return exception at this point because we are already
596
           * writing to stdout */
597
0
        } else {
598
          /* split into subgroups. Start at address + 1 because the first '/'
599
           * would cause an extra empty group */
600
0
          nestedGroups[i] = msStringSplit(groups + 1, '/', &numNestedGroups[i]);
601
          /* Iterate through the groups and add them to the unique groups array
602
           */
603
0
          for (int k = 0; k < numNestedGroups[i]; k++) {
604
0
            uniqgroups.insert(msStringToLower(std::string(nestedGroups[i][k])));
605
0
          }
606
0
        }
607
0
      }
608
0
    }
609
0
  }
610
  /* Iterate through layers to find out whether they are in any of the nested
611
   * groups */
612
0
  for (int i = 0; i < map->numlayers; i++) {
613
0
    if (GET_LAYER(map, i)->name) {
614
0
      if (uniqgroups.find(msStringToLower(
615
0
              std::string(GET_LAYER(map, i)->name))) != uniqgroups.end()) {
616
0
        isUsedInNestedGroup[i] = 1;
617
0
      }
618
0
    }
619
0
  }
620
0
}
621
622
/*
623
** Validate that a given dimension is inside the extents defined
624
*/
625
static bool msWMSValidateDimensionValue(const char *value,
626
                                        const char *dimensionextent,
627
0
                                        bool forcecharacter) {
628
0
  std::vector<pointObj> aextentranges;
629
630
0
  bool isextentavalue = false;
631
0
  bool isextentarange = false;
632
0
  bool ischaracter = false;
633
634
0
  if (forcecharacter)
635
0
    ischaracter = true;
636
637
0
  if (!value || !dimensionextent)
638
0
    return false;
639
640
  /*for the value, we support descrete values (2005) */
641
  /* multiple values (abc, def, ...) */
642
  /* and range(s) (1000/2000, 3000/5000) */
643
  /** we do not support resolution*/
644
645
  /* -------------------------------------------------------------------- */
646
  /*      parse the extent first.                                         */
647
  /* -------------------------------------------------------------------- */
648
0
  auto extents = msStringSplit(dimensionextent, ',');
649
0
  for (auto &extent :
650
0
       extents) // Make sure to get by reference so that it is updated in place
651
0
    msStringTrim(extent);
652
653
0
  std::vector<std::string> aextentvalues;
654
0
  if (extents.size() == 1) {
655
0
    if (strstr(dimensionextent, "/") == NULL) {
656
      /*single value*/
657
0
      isextentavalue = true;
658
0
      aextentvalues.push_back(dimensionextent);
659
0
      if (!forcecharacter)
660
0
        ischaracter = FLTIsNumeric(dimensionextent) == MS_FALSE;
661
662
0
    } else {
663
0
      const auto ranges = msStringSplit(dimensionextent, '/');
664
0
      if (ranges.size() == 2 || ranges.size() == 3) {
665
        /*single range*/
666
0
        isextentarange = true;
667
0
        aextentranges.resize(1);
668
0
        aextentranges[0].x = atof(ranges[0].c_str());
669
0
        aextentranges[0].y = atof(ranges[1].c_str());
670
        /*ranges should be numeric*/
671
0
        ischaracter = false;
672
0
      }
673
0
    }
674
0
  } else if (extents.size() >
675
0
             1) { /*check if it is muliple values or multiple ranges*/
676
0
    if (strstr(dimensionextent, "/") == NULL) {
677
      /*multiple values*/
678
0
      isextentavalue = true;
679
0
      aextentvalues = std::move(extents);
680
0
      if (!forcecharacter)
681
0
        ischaracter = FLTIsNumeric(aextentvalues[0].c_str()) == MS_FALSE;
682
0
    } else { /*multiple range extent*/
683
0
      int isvalidextent = MS_TRUE;
684
      /*ranges should be numeric*/
685
0
      ischaracter = false;
686
0
      isextentarange = true;
687
0
      aextentranges.resize(extents.size());
688
0
      size_t nextentranges = 0;
689
690
0
      for (const auto &extent : extents) {
691
0
        const auto onerange = msStringSplit(extent.c_str(), '/');
692
0
        if (onerange.size() != 2 && onerange.size() != 3) {
693
0
          isvalidextent = MS_FALSE;
694
0
          break;
695
0
        }
696
0
        if (isvalidextent) {
697
698
0
          aextentranges[nextentranges].x = atof(onerange[0].c_str());
699
0
          aextentranges[nextentranges++].y = atof(onerange[1].c_str());
700
0
        }
701
0
      }
702
0
      if (!isvalidextent) {
703
0
        nextentranges = 0;
704
0
        isextentarange = false;
705
0
      }
706
0
      aextentranges.resize(nextentranges);
707
0
    }
708
0
  }
709
710
  /* make sure that we got a valid extent*/
711
0
  if (!isextentavalue && !isextentarange) {
712
0
    return false;
713
0
  }
714
715
  /*for the extent of the dimesion, we support
716
  single value,  or list of mulitiple values comma separated,
717
  a single range or multiple ranges */
718
719
0
  const auto uservalues = msStringSplit(value, ',');
720
0
  bool uservaluevalid = false;
721
0
  if (uservalues.size() == 1) {
722
    /*user input=single*/
723
    /*is it descret or range*/
724
0
    const auto ranges = msStringSplit(uservalues[0].c_str(), '/');
725
0
    if (ranges.size() == 1) { /*discrete*/
726
0
      if (isextentavalue) {
727
        /*single user value, single/multiple values extent*/
728
0
        for (const auto &extentvalue : aextentvalues) {
729
0
          if (ischaracter)
730
0
            uservaluevalid = (uservalues[0] == extentvalue);
731
0
          else {
732
0
            if (atof(uservalues[0].c_str()) == atof(extentvalue.c_str()))
733
0
              uservaluevalid = true;
734
0
          }
735
0
          if (uservaluevalid)
736
0
            break;
737
0
        }
738
0
      } else if (isextentarange) {
739
        /*single user value, single/multiple range extent*/
740
0
        const float currentval = atof(uservalues[0].c_str());
741
742
0
        for (const auto &extentrange : aextentranges) {
743
0
          const float minval = extentrange.x;
744
0
          const float maxval = extentrange.y;
745
0
          if (currentval >= minval && currentval <= maxval) {
746
0
            uservaluevalid = true;
747
0
            break;
748
0
          }
749
0
        }
750
0
      }
751
0
    } else if (ranges.size() == 2 || ranges.size() == 3) { /*range*/
752
      /*user input=single range. In this case the extents must
753
       be of a range type.*/
754
0
      const float mincurrentval = atof(ranges[0].c_str());
755
0
      const float maxcurrentval = atof(ranges[1].c_str());
756
0
      if (isextentarange) {
757
0
        for (const auto &extentrange : aextentranges) {
758
0
          const float minval = extentrange.x;
759
0
          const float maxval = extentrange.y;
760
761
0
          if (minval <= mincurrentval && maxval >= maxcurrentval &&
762
0
              minval <= maxval) {
763
0
            uservaluevalid = true;
764
0
            break;
765
0
          }
766
0
        }
767
0
      }
768
0
    }
769
0
  } else if (uservalues.size() > 1) { /*user input=multiple*/
770
0
    if (strstr(value, "/") == NULL) {
771
      /*user input=multiple value*/
772
0
      bool valueisvalid = false;
773
0
      for (const auto &uservalue : uservalues) {
774
0
        valueisvalid = false;
775
0
        if (isextentavalue) {
776
          /*user input is multiple values, extent is defined as one or multiple
777
           * values*/
778
0
          for (const auto &extentvalue : aextentvalues) {
779
0
            if (ischaracter) {
780
0
              if (uservalue == extentvalue) {
781
0
                valueisvalid = true;
782
0
                break;
783
0
              }
784
0
            } else {
785
0
              if (atof(uservalue.c_str()) == atof(extentvalue.c_str())) {
786
0
                valueisvalid = true;
787
0
                break;
788
0
              }
789
0
            }
790
0
          }
791
          /*every value should be valid*/
792
0
          if (!valueisvalid)
793
0
            break;
794
0
        } else if (isextentarange) {
795
          /*user input is multiple values, extent is defined as one or multiple
796
           * ranges*/
797
0
          for (const auto &extentrange : aextentranges) {
798
0
            const float minval = extentrange.x;
799
0
            const float maxval = extentrange.y;
800
0
            const float currentval = atof(uservalue.c_str());
801
0
            if (minval <= currentval && maxval >= currentval &&
802
0
                minval <= maxval) {
803
0
              valueisvalid = true;
804
0
              break;
805
0
            }
806
0
          }
807
0
          if (!valueisvalid)
808
0
            break;
809
0
        }
810
0
      }
811
0
      uservaluevalid = valueisvalid;
812
0
    } else { /*user input multiple ranges*/
813
0
      bool valueisvalid = true;
814
815
0
      for (const auto &uservalue : uservalues) {
816
        /*each ranges should be valid*/
817
0
        const auto onerange = msStringSplit(uservalue.c_str(), '/');
818
0
        if (onerange.size() == 2 || onerange.size() == 3) {
819
0
          const float mincurrentval = atof(onerange[0].c_str());
820
0
          const float maxcurrentval = atof(onerange[1].c_str());
821
822
          /*extent must be defined also as a rangle*/
823
0
          if (isextentarange) {
824
0
            bool found = false;
825
0
            for (const auto &extentrange : aextentranges) {
826
0
              const float mincurrentrange = extentrange.x;
827
0
              const float maxcurrentrange = extentrange.y;
828
829
0
              if (mincurrentval >= mincurrentrange &&
830
0
                  maxcurrentval <= maxcurrentrange &&
831
0
                  mincurrentval <= maxcurrentval) {
832
0
                found = true;
833
0
                break;
834
0
              }
835
0
            }
836
0
            if (!found) {
837
0
              valueisvalid = false;
838
0
              break;
839
0
            }
840
0
          }
841
0
        } else {
842
0
          valueisvalid = false;
843
0
        }
844
0
      }
845
0
      uservaluevalid = valueisvalid;
846
0
    }
847
0
  }
848
849
0
  return uservaluevalid;
850
0
}
851
852
static bool msWMSApplyDimensionLayer(layerObj *lp, const char *item,
853
0
                                     const char *value, bool forcecharacter) {
854
0
  bool result = false;
855
856
0
  if (lp && item && value) {
857
    /*for the value, we support descrete values (2005) */
858
    /* multiple values (abc, def, ...) */
859
    /* and range(s) (1000/2000, 3000/5000) */
860
0
    char *pszExpression = FLTGetExpressionForValuesRanges(
861
0
        lp, item, value, forcecharacter ? MS_TRUE : MS_FALSE);
862
863
0
    if (pszExpression) {
864
      // If tileindex is set, the filter is applied to tileindex too.
865
0
      int tlpindex = -1;
866
0
      if (lp->tileindex &&
867
0
          (tlpindex = msGetLayerIndex(lp->map, lp->tileindex)) != -1) {
868
0
        result = FLTApplyExpressionToLayer((GET_LAYER(lp->map, tlpindex)),
869
0
                                           pszExpression) != MS_FALSE;
870
0
      } else {
871
0
        result = true;
872
0
      }
873
0
      result &= FLTApplyExpressionToLayer(lp, pszExpression) != MS_FALSE;
874
0
      msFree(pszExpression);
875
0
    }
876
0
  }
877
0
  return result;
878
0
}
879
880
static bool msWMSApplyDimension(layerObj *lp, int /* version */,
881
                                const char *dimensionname, const char *value,
882
0
                                const char * /* wms_exception_format */) {
883
0
  bool forcecharacter = false;
884
0
  bool result = false;
885
886
0
  if (lp && dimensionname && value) {
887
    /*check if the dimension name passes starts with dim_. All dimensions should
888
     * start with dim_, except elevation*/
889
0
    std::string dimension;
890
0
    if (strncasecmp(dimensionname, "dim_", 4) == 0)
891
0
      dimension = dimensionname + 4;
892
0
    else
893
0
      dimension = dimensionname;
894
895
    /*if value is empty and a default is defined, use it*/
896
0
    std::string currentvalue;
897
0
    if (strlen(value) > 0)
898
0
      currentvalue = value;
899
0
    else {
900
0
      const char *dimensiondefault = msOWSLookupMetadata(
901
0
          &(lp->metadata), "M", (dimension + "_default").c_str());
902
0
      if (dimensiondefault)
903
0
        currentvalue = dimensiondefault;
904
0
    }
905
906
    /*check if the manadatory metada related to the dimension are set*/
907
0
    const char *dimensionitem = msOWSLookupMetadata(
908
0
        &(lp->metadata), "M", (dimension + "_item").c_str());
909
0
    const char *dimensionextent = msOWSLookupMetadata(
910
0
        &(lp->metadata), "M", (dimension + "_extent").c_str());
911
0
    const char *dimensionunit = msOWSLookupMetadata(
912
0
        &(lp->metadata), "M", (dimension + "_units").c_str());
913
914
    /*if the server want to force the type to character*/
915
0
    const char *dimensiontype = msOWSLookupMetadata(
916
0
        &(lp->metadata), "M", (dimension + "_type").c_str());
917
0
    if (dimensiontype && strcasecmp(dimensiontype, "Character") == 0)
918
0
      forcecharacter = true;
919
920
0
    if (dimensionitem && dimensionextent && dimensionunit &&
921
0
        !currentvalue.empty()) {
922
0
      if (msWMSValidateDimensionValue(currentvalue.c_str(), dimensionextent,
923
0
                                      forcecharacter)) {
924
0
        result = msWMSApplyDimensionLayer(lp, dimensionitem,
925
0
                                          currentvalue.c_str(), forcecharacter);
926
0
      } else {
927
0
        msSetErrorWithStatus(
928
0
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
929
0
            "Dimension %s with a value of %s is invalid or outside the "
930
0
            "extents defined",
931
0
            "msWMSApplyDimension", dimension.c_str(), currentvalue.c_str());
932
0
        result = false;
933
0
      }
934
0
    } else
935
0
      msSetErrorWithStatus(
936
0
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
937
0
          "Dimension %s : invalid settings. Make sure that item, units "
938
0
          "and extent are set.",
939
0
          "msWMSApplyDimension", dimension.c_str());
940
0
  }
941
0
  return result;
942
0
}
943
/*
944
**
945
*/
946
int msWMSLoadGetMapParams(mapObj *map, int nVersion, char **names,
947
                          char **values, int numentries,
948
                          const char *wms_exception_format,
949
                          const char * /*wms_request*/,
950
0
                          owsRequestObj *ows_request) {
951
0
  bool adjust_extent = false;
952
0
  bool nonsquare_enabled = false;
953
0
  int transparent = MS_NOOVERRIDE;
954
0
  bool bbox_pixel_is_point = false;
955
0
  outputFormatObj *format = NULL;
956
0
  int validlayers = 0;
957
0
  const char *styles = NULL;
958
0
  int invalidlayers = 0;
959
0
  std::string epsgbuf;
960
0
  std::string srsbuffer;
961
0
  bool epsgvalid = false;
962
0
  bool timerequest = false;
963
0
  const char *stime = NULL;
964
0
  bool srsfound = false;
965
0
  bool bboxfound = false;
966
0
  bool formatfound = false;
967
0
  bool widthfound = false;
968
0
  bool heightfound = false;
969
0
  const char *request = NULL;
970
0
  int status = 0;
971
0
  const char *layerlimit = NULL;
972
0
  bool tiled = false;
973
974
0
  const char *sldenabled = NULL;
975
0
  const char *sld_url = NULL;
976
0
  const char *sld_body = NULL;
977
978
0
  const char *filter = NULL;
979
980
0
  const char *compliance_mode_str = NULL;
981
0
  bool compliance_mode = false;
982
983
  /* Some of the getMap parameters are actually required depending on the */
984
  /* request, but for now we assume all are optional and the map file */
985
  /* defaults will apply. */
986
987
0
  msAdjustExtent(&(map->extent), map->width, map->height);
988
989
  /*
990
    Check if we need strict checks for standard compliance.
991
    Defaults to false.
992
    So far only used for TRANSPARENT parameter value, but we will eventually
993
    need this more often.
994
  */
995
0
  compliance_mode_str =
996
0
      msOWSLookupMetadata(&(map->web.metadata), "MO", "compliance_mode");
997
0
  compliance_mode = (compliance_mode_str != NULL) &&
998
0
                    (strcasecmp(compliance_mode_str, "true") == 0);
999
1000
  /*
1001
    Check for SLDs first. If SLD is available LAYERS and STYLES parameters are
1002
    non mandatory
1003
   */
1004
0
  for (int i = 0; i < numentries; i++) {
1005
    /* check if SLD is passed.  If yes, check for OGR support */
1006
0
    if (strcasecmp(names[i], "SLD") == 0 ||
1007
0
        strcasecmp(names[i], "SLD_BODY") == 0) {
1008
0
      sldenabled =
1009
0
          msOWSLookupMetadata(&(map->web.metadata), "MO", "sld_enabled");
1010
1011
0
      if (sldenabled == NULL) {
1012
0
        sldenabled = "true";
1013
0
      }
1014
1015
0
      if (strcasecmp(sldenabled, "true") == 0) {
1016
0
        if (strcasecmp(names[i], "SLD") == 0) {
1017
0
          sld_url = values[i];
1018
0
        }
1019
0
        if (strcasecmp(names[i], "SLD_BODY") == 0) {
1020
0
          sld_body = values[i];
1021
0
        }
1022
0
      }
1023
0
    }
1024
0
  }
1025
1026
0
  std::vector<std::string> wmslayers;
1027
0
  for (int i = 0; i < numentries; i++) {
1028
    /* getMap parameters */
1029
1030
0
    if (strcasecmp(names[i], "REQUEST") == 0) {
1031
0
      request = values[i];
1032
0
    }
1033
1034
0
    if (strcasecmp(names[i], "LAYERS") == 0) {
1035
0
      std::vector<int> layerOrder(map->numlayers);
1036
1037
0
      wmslayers = msStringSplit(values[i], ',');
1038
0
      if (wmslayers.empty()) {
1039
0
        if (sld_url == NULL && sld_body == NULL) {
1040
0
          msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1041
0
                               "At least one layer name required in LAYERS.",
1042
0
                               "msWMSLoadGetMapParams()");
1043
0
          return msWMSException(map, nVersion, NULL, wms_exception_format);
1044
0
        }
1045
0
      }
1046
1047
0
      if (nVersion >= OWS_1_3_0) {
1048
0
        layerlimit =
1049
0
            msOWSLookupMetadata(&(map->web.metadata), "MO", "layerlimit");
1050
0
        if (layerlimit) {
1051
0
          if (static_cast<int>(wmslayers.size()) > atoi(layerlimit)) {
1052
0
            msSetErrorWithStatus(
1053
0
                MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1054
0
                "Number of layers requested exceeds LayerLimit.",
1055
0
                "msWMSLoadGetMapParams()");
1056
0
            return msWMSException(map, nVersion, NULL, wms_exception_format);
1057
0
          }
1058
0
        }
1059
0
      }
1060
1061
0
      for (int iLayer = 0; iLayer < map->numlayers; iLayer++) {
1062
0
        map->layerorder[iLayer] = iLayer;
1063
0
      }
1064
1065
0
      int nLayerOrder = 0;
1066
0
      for (int j = 0; j < map->numlayers; j++) {
1067
        /* Keep only layers with status=DEFAULT by default */
1068
        /* Layer with status DEFAULT is drawn first. */
1069
0
        if (GET_LAYER(map, j)->status != MS_DEFAULT)
1070
0
          GET_LAYER(map, j)->status = MS_OFF;
1071
0
        else {
1072
0
          map->layerorder[nLayerOrder++] = j;
1073
0
          layerOrder[j] = 1;
1074
0
        }
1075
0
      }
1076
1077
0
      char ***nestedGroups =
1078
0
          (char ***)msSmallCalloc(map->numlayers, sizeof(char **));
1079
0
      int *numNestedGroups = (int *)msSmallCalloc(map->numlayers, sizeof(int));
1080
0
      int *isUsedInNestedGroup =
1081
0
          (int *)msSmallCalloc(map->numlayers, sizeof(int));
1082
0
      msWMSPrepareNestedGroups(map, nVersion, nestedGroups, numNestedGroups,
1083
0
                               isUsedInNestedGroup);
1084
1085
0
      if (ows_request->layerwmsfilterindex != NULL)
1086
0
        msFree(ows_request->layerwmsfilterindex);
1087
0
      ows_request->layerwmsfilterindex =
1088
0
          (int *)msSmallMalloc(map->numlayers * sizeof(int));
1089
0
      for (int j = 0; j < map->numlayers; j++) {
1090
0
        ows_request->layerwmsfilterindex[j] = -1;
1091
0
      }
1092
0
      ows_request->numwmslayerargs = static_cast<int>(wmslayers.size());
1093
1094
0
      for (int k = 0; k < static_cast<int>(wmslayers.size()); k++) {
1095
0
        const auto &wmslayer = wmslayers[k];
1096
0
        bool layerfound = false;
1097
0
        for (int j = 0; j < map->numlayers; j++) {
1098
          /* Turn on selected layers only. */
1099
0
          if (((GET_LAYER(map, j)->name &&
1100
0
                strcasecmp(GET_LAYER(map, j)->name, wmslayer.c_str()) == 0) ||
1101
0
               (map->name && strcasecmp(map->name, wmslayer.c_str()) == 0) ||
1102
0
               (GET_LAYER(map, j)->group &&
1103
0
                strcasecmp(GET_LAYER(map, j)->group, wmslayer.c_str()) == 0) ||
1104
0
               ((numNestedGroups[j] > 0) &&
1105
0
                msStringInArray(wmslayer.c_str(), nestedGroups[j],
1106
0
                                numNestedGroups[j]))) &&
1107
0
              ((msIntegerInArray(GET_LAYER(map, j)->index,
1108
0
                                 ows_request->enabled_layers,
1109
0
                                 ows_request->numlayers)))) {
1110
0
            if (GET_LAYER(map, j)->status != MS_DEFAULT) {
1111
0
              if (layerOrder[j] == 0) {
1112
0
                map->layerorder[nLayerOrder++] = j;
1113
0
                layerOrder[j] = 1;
1114
0
                GET_LAYER(map, j)->status = MS_ON;
1115
0
              }
1116
0
            }
1117
            /* if a layer name is repeated assign the first matching filter */
1118
            /* duplicate names will be assigned filters later when layer copies
1119
             * are created */
1120
0
            if (ows_request->layerwmsfilterindex[j] == -1) {
1121
0
              ows_request->layerwmsfilterindex[j] = k;
1122
0
            }
1123
0
            validlayers++;
1124
0
            layerfound = true;
1125
0
          }
1126
0
        }
1127
0
        if (layerfound == false && !wmslayers.empty())
1128
0
          invalidlayers++;
1129
0
      }
1130
1131
      /* free the stuff used for nested layers */
1132
0
      for (int k = 0; k < map->numlayers; k++) {
1133
0
        if (numNestedGroups[k] > 0) {
1134
0
          msFreeCharArray(nestedGroups[k], numNestedGroups[k]);
1135
0
        }
1136
0
      }
1137
0
      free(nestedGroups);
1138
0
      free(numNestedGroups);
1139
0
      free(isUsedInNestedGroup);
1140
1141
      /* set all layers with status off at end of array */
1142
0
      for (int j = 0; j < map->numlayers; j++) {
1143
0
        if (GET_LAYER(map, j)->status == MS_OFF)
1144
0
          if (layerOrder[j] == 0)
1145
0
            map->layerorder[nLayerOrder++] = j;
1146
0
      }
1147
0
    } else if (strcasecmp(names[i], "STYLES") == 0) {
1148
0
      styles = values[i];
1149
1150
0
    } else if ((strcasecmp(names[i], "SRS") == 0 && nVersion < OWS_1_3_0) ||
1151
0
               (strcasecmp(names[i], "CRS") == 0 && nVersion >= OWS_1_3_0)) {
1152
0
      srsfound = true;
1153
0
      char *colon = strchr(values[i], ':');
1154
0
      bool srsOk = false;
1155
      /* SRS is in format "EPSG:epsg_id" or "AUTO:proj_id,unit_id,lon0,lat0" */
1156
0
      if (strncasecmp(values[i], "EPSG:", 5) == 0) {
1157
0
        srsOk = true;
1158
        /* SRS=EPSG:xxxx */
1159
1160
        /* don't need to copy init=xxx since the srsbuffer is only
1161
           used with msLoadProjection and that does already the job */
1162
1163
0
        srsbuffer = "EPSG:";
1164
0
        srsbuffer += (values[i] + 5);
1165
0
        epsgbuf = srsbuffer;
1166
1167
        /* This test was to correct a request by the OCG cite 1.3.0 test
1168
         sending CRS=ESPG:4326,  Bug:*/
1169
0
        if (nVersion >= OWS_1_3_0) {
1170
0
          if (srsbuffer.back() == ',') {
1171
0
            srsbuffer.resize(srsbuffer.size() - 1);
1172
0
            epsgbuf = srsbuffer;
1173
0
          }
1174
0
        }
1175
1176
        /* we need to wait until all params are read before */
1177
        /* loading the projection into the map. This will help */
1178
        /* insure that the passes srs is valid for all layers. */
1179
        /*
1180
        if (msLoadProjectionString(&(map->projection), buffer) != 0)
1181
          return msWMSException(map, nVersion, NULL);
1182
1183
        iUnits = GetMapserverUnitUsingProj(&(map->projection));
1184
        if (iUnits != -1)
1185
          map->units = iUnits;
1186
        */
1187
0
      } else if (strncasecmp(values[i], "AUTO:", 5) == 0 &&
1188
0
                 nVersion < OWS_1_3_0) {
1189
0
        if (nVersion < OWS_1_3_0) {
1190
0
          srsOk = true;
1191
0
          srsbuffer = values[i];
1192
          /* SRS=AUTO:proj_id,unit_id,lon0,lat0 */
1193
          /*
1194
          if (msLoadProjectionString(&(map->projection), values[i]) != 0)
1195
            return msWMSException(map, nVersion, NULL);
1196
1197
          iUnits = GetMapserverUnitUsingProj(&(map->projection));
1198
          if (iUnits != -1)
1199
            map->units = iUnits;
1200
          */
1201
0
        }
1202
0
      } else if (strncasecmp(values[i], "AUTO2:", 6) == 0 ||
1203
0
                 strncasecmp(values[i], "CRS:", 4) == 0) {
1204
0
        if (nVersion >= OWS_1_3_0) {
1205
0
          srsOk = true;
1206
0
          srsbuffer = values[i];
1207
0
        }
1208
0
      } else if (colon != NULL && strchr(colon + 1, ':') == NULL) {
1209
0
        srsOk = true;
1210
0
        srsbuffer = values[i];
1211
0
        epsgbuf = srsbuffer;
1212
0
      }
1213
0
      if (!srsOk) {
1214
0
        if (nVersion >= OWS_1_3_0) {
1215
0
          msSetErrorWithStatus(
1216
0
              MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1217
0
              "Unsupported CRS namespace (CRS must be in the format "
1218
0
              "AUTH:XXXX).",
1219
0
              "msWMSLoadGetMapParams()");
1220
0
          return msWMSException(map, nVersion, "InvalidCRS",
1221
0
                                wms_exception_format);
1222
0
        } else {
1223
0
          msSetErrorWithStatus(
1224
0
              MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1225
0
              "Unsupported SRS namespace (SRS must be in the format "
1226
0
              "AUTH:XXXX).",
1227
0
              "msWMSLoadGetMapParams()");
1228
0
          return msWMSException(map, nVersion, "InvalidSRS",
1229
0
                                wms_exception_format);
1230
0
        }
1231
0
      }
1232
0
    } else if (strcasecmp(names[i], "BBOX") == 0) {
1233
0
      bboxfound = true;
1234
0
      const auto tokens = msStringSplit(values[i], ',');
1235
0
      if (tokens.size() != 4) {
1236
0
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1237
0
                             "Wrong number of arguments for BBOX.",
1238
0
                             "msWMSLoadGetMapParams()");
1239
0
        return msWMSException(map, nVersion, NULL, wms_exception_format);
1240
0
      }
1241
0
      map->extent.minx = atof(tokens[0].c_str());
1242
0
      map->extent.miny = atof(tokens[1].c_str());
1243
0
      map->extent.maxx = atof(tokens[2].c_str());
1244
0
      map->extent.maxy = atof(tokens[3].c_str());
1245
1246
      /*for wms 1.3.0 we will do the validation of the bbox after all parameters
1247
       are read to account for the axes order*/
1248
0
      if (nVersion < OWS_1_3_0) {
1249
1250
        /* validate bbox values */
1251
0
        if (map->extent.minx >= map->extent.maxx ||
1252
0
            map->extent.miny >= map->extent.maxy) {
1253
0
          msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1254
0
                               "Invalid values for BBOX.",
1255
0
                               "msWMSLoadGetMapParams()");
1256
0
          return msWMSException(map, nVersion, NULL, wms_exception_format);
1257
0
        }
1258
0
        adjust_extent = true;
1259
0
      }
1260
0
    } else if (strcasecmp(names[i], "WIDTH") == 0) {
1261
0
      widthfound = true;
1262
0
      map->width = atoi(values[i]);
1263
0
    } else if (strcasecmp(names[i], "HEIGHT") == 0) {
1264
0
      heightfound = true;
1265
0
      map->height = atoi(values[i]);
1266
0
    } else if (strcasecmp(names[i], "FORMAT") == 0) {
1267
0
      formatfound = true;
1268
1269
0
      if (strcasecmp(values[i], "application/openlayers") != 0) {
1270
        /*check to see if a predefined list is given*/
1271
0
        const char *format_list =
1272
0
            msOWSLookupMetadata(&(map->web.metadata), "M", "getmap_formatlist");
1273
0
        if (format_list) {
1274
0
          format = msOwsIsOutputFormatValid(
1275
0
              map, values[i], &(map->web.metadata), "M", "getmap_formatlist");
1276
0
          if (format == NULL) {
1277
0
            msSetErrorWithStatus(MS_IMGERR, MS_HTTP_400_BAD_REQUEST,
1278
0
                                 "Unsupported output format (%s).",
1279
0
                                 "msWMSLoadGetMapParams()", values[i]);
1280
0
            return msWMSException(map, nVersion, "InvalidFormat",
1281
0
                                  wms_exception_format);
1282
0
          }
1283
0
        } else {
1284
0
          format = msSelectOutputFormat(map, values[i]);
1285
0
          if (format == NULL ||
1286
0
              (strncasecmp(format->driver, "MVT", 3) != 0 &&
1287
0
               strncasecmp(format->driver, "GDAL/", 5) != 0 &&
1288
0
               strncasecmp(format->driver, "AGG/", 4) != 0 &&
1289
0
               strncasecmp(format->driver, "UTFGRID", 7) != 0 &&
1290
0
               strncasecmp(format->driver, "CAIRO/", 6) != 0 &&
1291
0
               strncasecmp(format->driver, "OGL/", 4) != 0 &&
1292
0
               strncasecmp(format->driver, "KML", 3) != 0 &&
1293
0
               strncasecmp(format->driver, "KMZ", 3) != 0)) {
1294
0
            msSetErrorWithStatus(MS_IMGERR, MS_HTTP_400_BAD_REQUEST,
1295
0
                                 "Unsupported output format (%s).",
1296
0
                                 "msWMSLoadGetMapParams()", values[i]);
1297
0
            return msWMSException(map, nVersion, "InvalidFormat",
1298
0
                                  wms_exception_format);
1299
0
          }
1300
0
        }
1301
0
      }
1302
0
      msFree(map->imagetype);
1303
0
      map->imagetype = msStrdup(values[i]);
1304
0
    } else if (strcasecmp(names[i], "TRANSPARENT") == 0) {
1305
0
      if (compliance_mode) {
1306
0
        transparent = (strcmp(values[i], "TRUE") == 0);
1307
0
        if ((!transparent) && (strcmp(values[i], "FALSE") != 0)) {
1308
0
          msSetErrorWithStatus(
1309
0
              MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1310
0
              "Value for TRANSPARENT must be either TRUE or FALSE.",
1311
0
              "msWMSLoadGetMapParams()");
1312
0
          return msWMSException(map, nVersion, NULL, wms_exception_format);
1313
0
        }
1314
0
      } else {
1315
0
        transparent = (strcasecmp(values[i], "TRUE") == 0);
1316
0
      }
1317
0
    } else if (strcasecmp(names[i], "BGCOLOR") == 0) {
1318
0
      long c;
1319
0
      c = strtol(values[i], NULL, 16);
1320
0
      map->imagecolor.red = (c / 0x10000) & 0xff;
1321
0
      map->imagecolor.green = (c / 0x100) & 0xff;
1322
0
      map->imagecolor.blue = c & 0xff;
1323
0
    }
1324
1325
    /* value of time can be empty. We should look for a default value */
1326
    /* see function msWMSApplyTime */
1327
0
    else if (strcasecmp(names[i], "TIME") == 0) { /* &&  values[i]) */
1328
0
      stime = values[i];
1329
0
      timerequest = true;
1330
0
    }
1331
    /* Vendor-specific ANGLE param (for map rotation), added in ticket #3332,
1332
     * also supported by GeoServer
1333
     */
1334
0
    else if (strcasecmp(names[i], "ANGLE") == 0) {
1335
0
      msMapSetRotation(map, atof(values[i]));
1336
0
    }
1337
    /* Vendor-specific bbox_pixel_is_point, added in ticket #4652 */
1338
0
    else if (strcasecmp(names[i], "BBOX_PIXEL_IS_POINT") == 0) {
1339
0
      bbox_pixel_is_point = (strcasecmp(values[i], "TRUE") == 0);
1340
0
    }
1341
    /* Vendor specific TILED (WMS-C) */
1342
0
    else if (strcasecmp(names[i], "TILED") == 0) {
1343
0
      tiled = (strcasecmp(values[i], "TRUE") == 0);
1344
0
    }
1345
    /* Vendor-specific FILTER, added in RFC-118 */
1346
0
    else if (strcasecmp(names[i], "FILTER") == 0) {
1347
0
      filter = values[i];
1348
0
    }
1349
0
  }
1350
1351
  /*validate the exception format WMS 1.3.0 section 7.3.3.11*/
1352
1353
0
  if (nVersion >= OWS_1_3_0 && wms_exception_format != NULL) {
1354
0
    if (strcasecmp(wms_exception_format, "INIMAGE") != 0 &&
1355
0
        strcasecmp(wms_exception_format, "BLANK") != 0 &&
1356
0
        strcasecmp(wms_exception_format, "XML") != 0) {
1357
0
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1358
0
                           "Invalid format %s for the EXCEPTIONS parameter.",
1359
0
                           "msWMSLoadGetMapParams()", wms_exception_format);
1360
0
      return msWMSException(map, nVersion, "InvalidFormat",
1361
0
                            wms_exception_format);
1362
0
    }
1363
0
  }
1364
1365
0
  int need_axis_swap = MS_FALSE;
1366
0
  if (bboxfound && nVersion >= OWS_1_3_0) {
1367
0
    rectObj rect;
1368
0
    projectionObj proj;
1369
1370
    /*we have already validated that the request format when reading
1371
     the request parameters*/
1372
0
    rect = map->extent;
1373
1374
    /*try to adjust the axes if necessary*/
1375
0
    if (srsbuffer.size() > 1) {
1376
0
      msInitProjection(&proj);
1377
0
      msProjectionInheritContextFrom(&proj, &(map->projection));
1378
0
      if (msLoadProjectionStringEPSG(&proj, srsbuffer.c_str()) == 0 &&
1379
0
          (need_axis_swap = msIsAxisInvertedProj(&proj))) {
1380
0
        msAxisNormalizePoints(&proj, 1, &rect.minx, &rect.miny);
1381
0
        msAxisNormalizePoints(&proj, 1, &rect.maxx, &rect.maxy);
1382
0
      }
1383
0
      msFreeProjection(&proj);
1384
0
    }
1385
    /*if the CRS is AUTO2:auto_crs_id,factor,lon0,lat0,
1386
     we need to grab the factor parameter and use it with the bbox*/
1387
0
    if (srsbuffer.size() > 1 &&
1388
0
        strncasecmp(srsbuffer.c_str(), "AUTO2:", 6) == 0) {
1389
0
      const auto args = msStringSplit(srsbuffer.c_str(), ',');
1390
0
      if (args.size() == 4) {
1391
0
        const double factor = atof(args[1].c_str());
1392
0
        if (factor > 0 && factor != 1.0) {
1393
0
          rect.minx = rect.minx * factor;
1394
0
          rect.miny = rect.miny * factor;
1395
0
          rect.maxx = rect.maxx * factor;
1396
0
          rect.maxx = rect.maxy * factor;
1397
0
        }
1398
0
      }
1399
0
    }
1400
1401
0
    map->extent = rect;
1402
1403
    /* validate bbox values */
1404
0
    if (map->extent.minx >= map->extent.maxx ||
1405
0
        map->extent.miny >= map->extent.maxy) {
1406
0
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1407
0
                           "Invalid values for BBOX.",
1408
0
                           "msWMSLoadGetMapParams()");
1409
0
      return msWMSException(map, nVersion, NULL, wms_exception_format);
1410
0
    }
1411
0
    adjust_extent = true;
1412
0
  }
1413
1414
0
  if (tiled) {
1415
0
    const char *value;
1416
0
    hashTableObj *meta = &(map->web.metadata);
1417
0
    int map_edge_buffer = 0;
1418
1419
0
    if ((value = msLookupHashTable(meta, "tile_map_edge_buffer")) != NULL) {
1420
0
      map_edge_buffer = atoi(value);
1421
0
    }
1422
0
    if (map_edge_buffer > 0 && map->width > 0 && map->height > 0) {
1423
      /* adjust bbox and width and height to the buffer */
1424
0
      const double buffer_x = map_edge_buffer *
1425
0
                              (map->extent.maxx - map->extent.minx) /
1426
0
                              (double)map->width;
1427
0
      const double buffer_y = map_edge_buffer *
1428
0
                              (map->extent.maxy - map->extent.miny) /
1429
0
                              (double)map->height;
1430
1431
      // TODO: we should probably clamp the extent to avoid going outside of
1432
      // -180,-90,180,90 for geographic CRS for example
1433
0
      map->extent.minx -= buffer_x;
1434
0
      map->extent.maxx += buffer_x;
1435
0
      map->extent.miny -= buffer_y;
1436
0
      map->extent.maxy += buffer_y;
1437
1438
0
      map->width += 2 * map_edge_buffer;
1439
0
      map->height += 2 * map_edge_buffer;
1440
1441
0
      if (map_edge_buffer > 0) {
1442
0
        char tilebufferstr[64];
1443
1444
        /* Write the tile buffer to a string */
1445
0
        snprintf(tilebufferstr, sizeof(tilebufferstr), "-%d", map_edge_buffer);
1446
1447
        /* Hm, the labelcache buffer is set... */
1448
0
        if ((value = msLookupHashTable(meta, "labelcache_map_edge_buffer")) !=
1449
0
            NULL) {
1450
          /* If it's too small, replace with a bigger one */
1451
0
          if (map_edge_buffer > abs(atoi(value))) {
1452
0
            msRemoveHashTable(meta, "labelcache_map_edge_buffer");
1453
0
            msInsertHashTable(meta, "labelcache_map_edge_buffer",
1454
0
                              tilebufferstr);
1455
0
          }
1456
0
        }
1457
        /* No labelcache buffer value? Then we use the tile buffer. */
1458
0
        else {
1459
0
          msInsertHashTable(meta, "labelcache_map_edge_buffer", tilebufferstr);
1460
0
        }
1461
0
      }
1462
0
    }
1463
0
  }
1464
1465
  /*
1466
  ** If any select layers have a default time, we will apply the default
1467
  ** time value even if no TIME request was in the url.
1468
  */
1469
0
  if (!timerequest && map) {
1470
0
    for (int i = 0; i < map->numlayers && !timerequest; i++) {
1471
0
      layerObj *lp = NULL;
1472
1473
0
      lp = (GET_LAYER(map, i));
1474
0
      if (lp->status != MS_ON && lp->status != MS_DEFAULT)
1475
0
        continue;
1476
1477
0
      if (msOWSLookupMetadata(&(lp->metadata), "MO", "timedefault"))
1478
0
        timerequest = true;
1479
0
    }
1480
0
  }
1481
1482
  /*
1483
  ** Apply time filters if available in the request.
1484
  */
1485
0
  if (timerequest) {
1486
0
    if (msWMSApplyTime(map, nVersion, stime, wms_exception_format) ==
1487
0
        MS_FAILURE) {
1488
0
      return MS_FAILURE; /* msWMSException(map, nVersion, "InvalidTimeRequest");
1489
                          */
1490
0
    }
1491
0
  }
1492
1493
  /*
1494
  ** Check/apply wms dimensions
1495
  ** all dimension requests should start with dim_xxxx, except time and
1496
  *elevation.
1497
  */
1498
0
  for (int i = 0; i < map->numlayers; i++) {
1499
0
    layerObj *lp = (GET_LAYER(map, i));
1500
0
    if (lp->status != MS_ON && lp->status != MS_DEFAULT)
1501
0
      continue;
1502
1503
0
    const char *dimensionlist =
1504
0
        msOWSLookupMetadata(&(lp->metadata), "M", "dimensionlist");
1505
0
    if (dimensionlist) {
1506
0
      auto tokens = msStringSplit(dimensionlist, ',');
1507
0
      for (auto &token : tokens) {
1508
0
        msStringTrim(token);
1509
0
        for (int k = 0; k < numentries; k++) {
1510
0
          const std::string dimensionname(names[k]);
1511
1512
          /*the dim_ is supposed to be part of the dimension name in the
1513
           * request*/
1514
0
          std::string stmp;
1515
0
          if (strcasecmp(token.c_str(), "elevation") == 0)
1516
0
            stmp = token;
1517
0
          else {
1518
0
            stmp = "dim_";
1519
0
            stmp += token;
1520
0
          }
1521
0
          if (strcasecmp(dimensionname.c_str(), stmp.c_str()) == 0) {
1522
0
            if (!msWMSApplyDimension(lp, nVersion, dimensionname.c_str(),
1523
0
                                     values[k], wms_exception_format)) {
1524
0
              return msWMSException(lp->map, nVersion, "InvalidDimensionValue",
1525
0
                                    wms_exception_format);
1526
0
            }
1527
0
            break;
1528
0
          }
1529
0
        }
1530
0
      }
1531
0
    }
1532
0
  }
1533
1534
  /*
1535
  ** Apply the selected output format (if one was selected), and override
1536
  ** the transparency if needed.
1537
  */
1538
1539
0
  if (format != NULL)
1540
0
    msApplyOutputFormat(&(map->outputformat), format, transparent);
1541
1542
  /* Validate all layers given.
1543
  ** If an invalid layer is sent, return an exception.
1544
  */
1545
0
  if (validlayers == 0 || invalidlayers > 0) {
1546
0
    if (invalidlayers > 0) {
1547
0
      msSetErrorWithStatus(
1548
0
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1549
0
          "Invalid layer(s) given in the LAYERS parameter. A layer might be disabled for \
1550
0
this request. Check wms/ows_enable_request settings.",
1551
0
          "msWMSLoadGetMapParams()");
1552
0
      return msWMSException(map, nVersion, "LayerNotDefined",
1553
0
                            wms_exception_format);
1554
0
    }
1555
0
    if (validlayers == 0 && sld_url == NULL && sld_body == NULL) {
1556
0
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1557
0
                           "Missing required parameter LAYERS",
1558
0
                           "msWMSLoadGetMapParams()");
1559
0
      return msWMSException(map, nVersion, "MissingParameterValue",
1560
0
                            wms_exception_format);
1561
0
    }
1562
0
  }
1563
1564
  /* validate srs value: When the SRS parameter in a GetMap request contains a
1565
  ** SRS that is valid for some, but not all of the layers being requested,
1566
  ** then the server shall throw a Service Exception (code = "InvalidSRS").
1567
  ** Validate first against epsg in the map and if no matching srs is found
1568
  ** validate all layers requested.
1569
  */
1570
0
  if (epsgbuf.size() >= 2) { /*at least 2 chars*/
1571
0
    char *projstring;
1572
0
    epsgvalid = false;
1573
0
    msOWSGetEPSGProj(&(map->projection), &(map->web.metadata), "MO", MS_FALSE,
1574
0
                     &projstring);
1575
0
    if (projstring) {
1576
0
      const auto tokens = msStringSplit(projstring, ' ');
1577
0
      for (const auto &token : tokens) {
1578
0
        if (strcasecmp(token.c_str(), epsgbuf.c_str()) == 0) {
1579
0
          epsgvalid = true;
1580
0
          break;
1581
0
        }
1582
0
      }
1583
0
      msFree(projstring);
1584
0
    }
1585
0
    if (!epsgvalid) {
1586
0
      for (int i = 0; i < map->numlayers; i++) {
1587
0
        epsgvalid = false;
1588
0
        if (GET_LAYER(map, i)->status == MS_ON) {
1589
0
          msOWSGetEPSGProj(&(GET_LAYER(map, i)->projection),
1590
0
                           &(GET_LAYER(map, i)->metadata), "MO", MS_FALSE,
1591
0
                           &projstring);
1592
0
          if (projstring) {
1593
0
            const auto tokens = msStringSplit(projstring, ' ');
1594
0
            for (const auto &token : tokens) {
1595
0
              if (strcasecmp(token.c_str(), epsgbuf.c_str()) == 0) {
1596
0
                epsgvalid = true;
1597
0
                break;
1598
0
              }
1599
0
            }
1600
0
            msFree(projstring);
1601
0
          }
1602
0
          if (!epsgvalid) {
1603
0
            if (nVersion >= OWS_1_3_0) {
1604
0
              msSetErrorWithStatus(
1605
0
                  MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1606
0
                  "Invalid CRS given : CRS must be valid for all "
1607
0
                  "requested layers.",
1608
0
                  "msWMSLoadGetMapParams()");
1609
0
              return msWMSException(map, nVersion, "InvalidSRS",
1610
0
                                    wms_exception_format);
1611
0
            } else {
1612
0
              msSetErrorWithStatus(
1613
0
                  MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1614
0
                  "Invalid SRS given : SRS must be valid for all "
1615
0
                  "requested layers.",
1616
0
                  "msWMSLoadGetMapParams()");
1617
0
              return msWMSException(map, nVersion, "InvalidSRS",
1618
0
                                    wms_exception_format);
1619
0
            }
1620
0
          }
1621
0
        }
1622
0
      }
1623
0
    }
1624
0
  }
1625
1626
0
  if (request == NULL || strcasecmp(request, "DescribeLayer") != 0) {
1627
    /* Validate requested image size.
1628
     */
1629
0
    if (map->width > map->maxsize || map->height > map->maxsize ||
1630
0
        map->width < 1 || map->height < 1) {
1631
0
      msSetErrorWithStatus(
1632
0
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1633
0
          "Image size out of range, WIDTH and HEIGHT must be between 1 "
1634
0
          "and %d pixels.",
1635
0
          "msWMSLoadGetMapParams()", map->maxsize);
1636
1637
      /* Restore valid default values in case errors INIMAGE are used */
1638
0
      map->width = 400;
1639
0
      map->height = 300;
1640
0
      return msWMSException(map, nVersion, NULL, wms_exception_format);
1641
0
    }
1642
1643
    /* Check whether requested BBOX and width/height result in non-square pixels
1644
     */
1645
0
    nonsquare_enabled =
1646
0
        msTestConfigOption(map, "MS_NONSQUARE", MS_FALSE) != MS_FALSE;
1647
0
    if (!nonsquare_enabled) {
1648
0
      const double dx = MS_ABS(map->extent.maxx - map->extent.minx);
1649
0
      const double dy = MS_ABS(map->extent.maxy - map->extent.miny);
1650
1651
0
      const double reqy = ((double)map->width) * dy / dx;
1652
1653
      /* Allow up to 1 pixel of error on the width/height ratios. */
1654
      /* If more than 1 pixel then enable non-square pixels */
1655
0
      if (MS_ABS((reqy - (double)map->height)) > 1.0) {
1656
0
        if (map->debug)
1657
0
          msDebug("msWMSLoadGetMapParams(): enabling non-square pixels.\n");
1658
0
        msSetConfigOption(map, "MS_NONSQUARE", "YES");
1659
0
        nonsquare_enabled = true;
1660
0
      }
1661
0
    }
1662
0
  }
1663
1664
  /* If the requested SRS is different from the default mapfile projection, or
1665
  ** if a BBOX resulting in non-square pixels is requested then
1666
  ** copy the original mapfile's projection to any layer that doesn't already
1667
  ** have a projection. This will prevent problems when users forget to
1668
  ** explicitly set a projection on all layers in a WMS mapfile.
1669
  */
1670
0
  if (srsbuffer.size() > 1 || nonsquare_enabled) {
1671
0
    projectionObj newProj;
1672
1673
0
    if (map->projection.numargs <= 0) {
1674
0
      if (nVersion >= OWS_1_3_0) {
1675
0
        msSetErrorWithStatus(
1676
0
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1677
0
            "Cannot set new CRS on a map that doesn't "
1678
0
            "have any projection set. Please make sure your mapfile "
1679
0
            "has a projection defined at the top level.",
1680
0
            "msWMSLoadGetMapParams()");
1681
0
        return msWMSException(map, nVersion, "InvalidCRS",
1682
0
                              wms_exception_format);
1683
0
      } else {
1684
0
        msSetErrorWithStatus(
1685
0
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1686
0
            "Cannot set new SRS on a map that doesn't "
1687
0
            "have any projection set. Please make sure your mapfile "
1688
0
            "has a projection defined at the top level.",
1689
0
            "msWMSLoadGetMapParams()");
1690
0
        return msWMSException(map, nVersion, "InvalidSRS",
1691
0
                              wms_exception_format);
1692
0
      }
1693
0
    }
1694
1695
0
    msInitProjection(&newProj);
1696
0
    msProjectionInheritContextFrom(&newProj, &map->projection);
1697
0
    if (srsbuffer.size() > 1) {
1698
0
      int nTmp;
1699
1700
0
      if (nVersion >= OWS_1_3_0)
1701
0
        nTmp = msLoadProjectionStringEPSG(&newProj, srsbuffer.c_str());
1702
0
      else
1703
0
        nTmp = msLoadProjectionString(&newProj, srsbuffer.c_str());
1704
0
      if (nTmp != 0) {
1705
0
        msFreeProjection(&newProj);
1706
0
        return msWMSException(map, nVersion, NULL, wms_exception_format);
1707
0
      }
1708
0
    }
1709
1710
0
    if (nonsquare_enabled ||
1711
0
        msProjectionsDiffer(&(map->projection), &newProj)) {
1712
0
      msMapSetLayerProjections(map);
1713
0
    }
1714
0
    msFreeProjection(&newProj);
1715
0
  }
1716
1717
  /* apply the srs to the map file. This is only done after validating */
1718
  /* that the srs given as parameter is valid for all layers */
1719
0
  if (srsbuffer.size() > 1) {
1720
0
    int nTmp;
1721
0
    msFreeProjectionExceptContext(&map->projection);
1722
0
    if (nVersion >= OWS_1_3_0)
1723
0
      nTmp = msLoadProjectionStringEPSG(&(map->projection), srsbuffer.c_str());
1724
0
    else
1725
0
      nTmp = msLoadProjectionString(&(map->projection), srsbuffer.c_str());
1726
1727
0
    if (nTmp != 0)
1728
0
      return msWMSException(map, nVersion, NULL, wms_exception_format);
1729
1730
0
    nTmp = GetMapserverUnitUsingProj(&(map->projection));
1731
0
    if (nTmp != -1) {
1732
0
      map->units = static_cast<MS_UNITS>(nTmp);
1733
0
    }
1734
0
  }
1735
1736
0
  if (sld_url || sld_body) {
1737
0
    char *pszLayerNames = NULL;
1738
0
    const int nLayersBefore = map->numlayers;
1739
1740
    /* -------------------------------------------------------------------- */
1741
    /*      if LAYERS parameter was not given, set all layers to off        */
1742
    /* -------------------------------------------------------------------- */
1743
0
    if (validlayers == 0) { /*no LAYERS parameter is give*/
1744
0
      for (int j = 0; j < map->numlayers; j++) {
1745
0
        if (GET_LAYER(map, j)->status != MS_DEFAULT)
1746
0
          GET_LAYER(map, j)->status = MS_OFF;
1747
0
      }
1748
0
    }
1749
1750
    /*apply sld if defined. This is done here so that bbox and srs are already
1751
     * applied*/
1752
0
    if (sld_url) {
1753
0
      if ((status = msSLDApplySLDURL(map, sld_url, -1, NULL, &pszLayerNames)) !=
1754
0
          MS_SUCCESS)
1755
0
        return msWMSException(map, nVersion, NULL, wms_exception_format);
1756
0
    } else if (sld_body) {
1757
0
      if ((status = msSLDApplySLD(map, sld_body, -1, NULL, &pszLayerNames)) !=
1758
0
          MS_SUCCESS)
1759
0
        return msWMSException(map, nVersion, NULL, wms_exception_format);
1760
0
    }
1761
    /* -------------------------------------------------------------------- */
1762
    /*      SLD and styles can use the same layer multiple times. If        */
1763
    /*      that is the case we duplicate the layer for drawing             */
1764
    /*      purpose. We need to reset the ows request enable settings (#1602)*/
1765
    /* -------------------------------------------------------------------- */
1766
0
    const int nLayerAfter = map->numlayers;
1767
0
    if (nLayersBefore != nLayerAfter) {
1768
0
      msOWSRequestLayersEnabled(map, "M", "GetMap", ows_request);
1769
0
    }
1770
1771
    /* -------------------------------------------------------------------- */
1772
    /*      We need to take into account where the LAYERS parameter was     */
1773
    /*      not given (the LAYERS is option when an SLD is given). In       */
1774
    /*      this particular case, we need to turn on the layers             */
1775
    /*      identified the SLD (#1166).                                     */
1776
    /*                                                                      */
1777
    /* -------------------------------------------------------------------- */
1778
0
    if (validlayers == 0) {
1779
0
      if (pszLayerNames) {
1780
0
        const auto tokens = msStringSplit(pszLayerNames, ',');
1781
0
        for (const auto &token : tokens) {
1782
0
          for (int j = 0; j < map->numlayers; j++) {
1783
0
            if (((GET_LAYER(map, j)->name &&
1784
0
                  strcasecmp(GET_LAYER(map, j)->name, token.c_str()) == 0) ||
1785
0
                 (map->name && strcasecmp(map->name, token.c_str()) == 0) ||
1786
0
                 (GET_LAYER(map, j)->group &&
1787
0
                  strcasecmp(GET_LAYER(map, j)->group, token.c_str()) == 0)) &&
1788
0
                ((msIntegerInArray(GET_LAYER(map, j)->index,
1789
0
                                   ows_request->enabled_layers,
1790
0
                                   ows_request->numlayers)))) {
1791
0
              if (GET_LAYER(map, j)->status != MS_DEFAULT)
1792
0
                GET_LAYER(map, j)->status = MS_ON;
1793
0
            }
1794
0
          }
1795
0
        }
1796
0
      }
1797
0
    }
1798
0
    msFree(pszLayerNames);
1799
0
  }
1800
1801
  /* Validate Styles :
1802
  ** MapServer advertise styles through the group setting in a class object.
1803
  ** If no styles are set MapServer expects to have empty values
1804
  ** for the styles parameter (...&STYLES=&...) Or for multiple Styles/Layers,
1805
  ** we could have ...&STYLES=,,,. If that is not the
1806
  ** case, we generate an exception.
1807
  */
1808
0
  if (styles && strlen(styles) > 0) {
1809
0
    bool hasCheckedLayerUnicity = false;
1810
0
    int n = 0;
1811
0
    int layerCopyIndex;
1812
1813
0
    char **tokens = msStringSplitComplex(styles, ",", &n, MS_ALLOWEMPTYTOKENS);
1814
0
    for (int i = 0; i < n; i++) {
1815
0
      if (tokens[i] && strlen(tokens[i]) > 0 &&
1816
0
          strcasecmp(tokens[i], "default") != 0) {
1817
0
        if (!hasCheckedLayerUnicity) {
1818
0
          hasCheckedLayerUnicity = true;
1819
0
          bool bLayerInserted = false;
1820
1821
          /* --------------------------------------------------------------------
1822
           */
1823
          /*      If the same layer is given more that once, we need to */
1824
          /*      duplicate it. */
1825
          /* --------------------------------------------------------------------
1826
           */
1827
0
          for (size_t m = 0; m < wmslayers.size(); m++) {
1828
0
            for (size_t l = m + 1; l < wmslayers.size(); l++) {
1829
0
              const int nIndex = msGetLayerIndex(map, wmslayers[m].c_str());
1830
0
              if (nIndex != -1 &&
1831
0
                  strcasecmp(wmslayers[m].c_str(), wmslayers[l].c_str()) == 0) {
1832
0
                layerObj *psTmpLayer = (layerObj *)malloc(sizeof(layerObj));
1833
0
                initLayer(psTmpLayer, map);
1834
0
                msCopyLayer(psTmpLayer, GET_LAYER(map, nIndex));
1835
                /* open the source layer */
1836
0
                if (!psTmpLayer->vtable)
1837
0
                  msInitializeVirtualTable(psTmpLayer);
1838
1839
                /*make the name unique*/
1840
0
                char tmpId[128];
1841
0
                snprintf(tmpId, sizeof(tmpId), "%lx_%x_%d", (long)time(NULL),
1842
0
                         (int)getpid(), map->numlayers);
1843
0
                if (psTmpLayer->name)
1844
0
                  msFree(psTmpLayer->name);
1845
0
                psTmpLayer->name = msStrdup(tmpId);
1846
0
                wmslayers[l] = tmpId;
1847
1848
0
                layerCopyIndex = msInsertLayer(map, psTmpLayer, -1);
1849
1850
                // expand the array mapping map layer index to filter indexes
1851
0
                ows_request->layerwmsfilterindex =
1852
0
                    (int *)msSmallRealloc(ows_request->layerwmsfilterindex,
1853
0
                                          map->numlayers * sizeof(int));
1854
0
                ows_request->layerwmsfilterindex[layerCopyIndex] =
1855
0
                    l; // the filter index matches the index of the layer name
1856
                       // in the WMS param
1857
1858
0
                bLayerInserted = true;
1859
                /* layer was copied, we need to decrement its refcount */
1860
0
                MS_REFCNT_DECR(psTmpLayer);
1861
0
              }
1862
0
            }
1863
0
          }
1864
1865
0
          if (bLayerInserted) {
1866
0
            msOWSRequestLayersEnabled(map, "M", "GetMap", ows_request);
1867
0
          }
1868
0
        }
1869
1870
0
        if (static_cast<int>(wmslayers.size()) == n) {
1871
0
          for (int j = 0; j < map->numlayers; j++) {
1872
0
            layerObj *lp = GET_LAYER(map, j);
1873
0
            if ((lp->name && strcasecmp(lp->name, wmslayers[i].c_str()) == 0) ||
1874
0
                (lp->group &&
1875
0
                 strcasecmp(lp->group, wmslayers[i].c_str()) == 0)) {
1876
0
              bool found = false;
1877
0
              for (int k = 0; k < lp->numclasses; k++) {
1878
0
                if (lp->_class[k]->group &&
1879
0
                    strcasecmp(lp->_class[k]->group, tokens[i]) == 0) {
1880
0
                  msFree(lp->classgroup);
1881
0
                  lp->classgroup = msStrdup(tokens[i]);
1882
0
                  found = true;
1883
0
                  break;
1884
0
                }
1885
0
              }
1886
0
              if (!found) {
1887
0
                msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1888
0
                                     "Style (%s) not defined on layer.",
1889
0
                                     "msWMSLoadGetMapParams()", tokens[i]);
1890
0
                msFreeCharArray(tokens, n);
1891
1892
0
                return msWMSException(map, nVersion, "StyleNotDefined",
1893
0
                                      wms_exception_format);
1894
0
              }
1895
              /* Check the style of the root layer */
1896
0
            } else if (map->name &&
1897
0
                       strcasecmp(map->name, wmslayers[i].c_str()) == 0) {
1898
0
              const char *styleName =
1899
0
                  msOWSLookupMetadata(&(map->web.metadata), "MO", "style_name");
1900
0
              if (styleName == NULL)
1901
0
                styleName = "default";
1902
0
              char *pszEncodedStyleName = msEncodeHTMLEntities(styleName);
1903
0
              if (strcasecmp(pszEncodedStyleName, tokens[i]) != 0) {
1904
0
                msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1905
0
                                     "Style (%s) not defined on root layer.",
1906
0
                                     "msWMSLoadGetMapParams()", tokens[i]);
1907
0
                msFreeCharArray(tokens, n);
1908
0
                msFree(pszEncodedStyleName);
1909
1910
0
                return msWMSException(map, nVersion, "StyleNotDefined",
1911
0
                                      wms_exception_format);
1912
0
              }
1913
0
              msFree(pszEncodedStyleName);
1914
0
            }
1915
0
          }
1916
0
        } else {
1917
0
          msSetErrorWithStatus(
1918
0
              MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1919
0
              "Invalid style (%s). Mapserver is expecting an empty "
1920
0
              "string for the STYLES : STYLES= or STYLES=,,, or using "
1921
0
              "keyword default  STYLES=default,default, ...",
1922
0
              "msWMSLoadGetMapParams()", styles);
1923
0
          msFreeCharArray(tokens, n);
1924
0
          return msWMSException(map, nVersion, "StyleNotDefined",
1925
0
                                wms_exception_format);
1926
0
        }
1927
0
      }
1928
0
    }
1929
0
    msFreeCharArray(tokens, n);
1930
0
  }
1931
1932
  /*
1933
  ** WMS extents are edge to edge while MapServer extents are center of
1934
  ** pixel to center of pixel.  Here we try to adjust the WMS extents
1935
  ** in by half a pixel.  We wait till here because we want to ensure we
1936
  ** are doing this in terms of the correct WIDTH and HEIGHT.
1937
  */
1938
0
  if (adjust_extent && map->width > 1 && map->height > 1 &&
1939
0
      !bbox_pixel_is_point) {
1940
0
    double dx, dy;
1941
1942
0
    dx = (map->extent.maxx - map->extent.minx) / map->width;
1943
0
    map->extent.minx += dx * 0.5;
1944
0
    map->extent.maxx -= dx * 0.5;
1945
1946
0
    dy = (map->extent.maxy - map->extent.miny) / map->height;
1947
0
    map->extent.miny += dy * 0.5;
1948
0
    map->extent.maxy -= dy * 0.5;
1949
0
  }
1950
1951
0
  if (request && strcasecmp(request, "DescribeLayer") != 0) {
1952
0
    if (!srsfound) {
1953
0
      if (nVersion >= OWS_1_3_0)
1954
0
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1955
0
                             "Missing required parameter CRS",
1956
0
                             "msWMSLoadGetMapParams()");
1957
0
      else
1958
0
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1959
0
                             "Missing required parameter SRS",
1960
0
                             "msWMSLoadGetMapParams()");
1961
1962
0
      return msWMSException(map, nVersion, "MissingParameterValue",
1963
0
                            wms_exception_format);
1964
0
    }
1965
1966
0
    if (!bboxfound) {
1967
0
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1968
0
                           "Missing required parameter BBOX",
1969
0
                           "msWMSLoadGetMapParams()");
1970
0
      return msWMSException(map, nVersion, "MissingParameterValue",
1971
0
                            wms_exception_format);
1972
0
    }
1973
1974
0
    if (!formatfound && (strcasecmp(request, "GetMap") == 0 ||
1975
0
                         strcasecmp(request, "map") == 0)) {
1976
0
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1977
0
                           "Missing required parameter FORMAT",
1978
0
                           "msWMSLoadGetMapParams()");
1979
0
      return msWMSException(map, nVersion, "MissingParameterValue",
1980
0
                            wms_exception_format);
1981
0
    }
1982
1983
0
    if (!widthfound) {
1984
0
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1985
0
                           "Missing required parameter WIDTH",
1986
0
                           "msWMSLoadGetMapParams()");
1987
0
      return msWMSException(map, nVersion, "MissingParameterValue",
1988
0
                            wms_exception_format);
1989
0
    }
1990
1991
0
    if (!heightfound) {
1992
0
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1993
0
                           "Missing required parameter HEIGHT",
1994
0
                           "msWMSLoadGetMapParams()");
1995
0
      return msWMSException(map, nVersion, "MissingParameterValue",
1996
0
                            wms_exception_format);
1997
0
    }
1998
1999
0
    if (styles == nullptr && sld_url == nullptr && sld_body == nullptr &&
2000
0
        (strcasecmp(request, "GetMap") == 0 ||
2001
0
         strcasecmp(request, "GetFeatureInfo") == 0) &&
2002
0
        msOWSLookupMetadata(&(map->web.metadata), "M",
2003
0
                            "allow_getmap_without_styles") == nullptr) {
2004
0
      msSetErrorWithStatus(
2005
0
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
2006
0
          "Missing required parameter STYLES. Note to service administrators: "
2007
0
          "defining the \"wms_allow_getmap_without_styles\" \"true\" "
2008
0
          "MAP.WEB.METADATA "
2009
0
          "item will disable this check (backward compatibility with behavior "
2010
0
          "of MapServer < 8.0)",
2011
0
          "msWMSLoadGetMapParams()");
2012
0
      return msWMSException(map, nVersion, "MissingParameterValue",
2013
0
                            wms_exception_format);
2014
0
    }
2015
0
  }
2016
2017
  /*
2018
  ** Apply vendor-specific filter if specified
2019
  */
2020
0
  if (filter) {
2021
0
    if (sld_url || sld_body) {
2022
0
      msSetErrorWithStatus(
2023
0
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
2024
0
          "Vendor-specific FILTER parameter cannot be used with SLD or "
2025
0
          "SLD_BODY.",
2026
0
          "msWMSLoadGetMapParams()");
2027
0
      return msWMSException(map, nVersion, NULL, wms_exception_format);
2028
0
    }
2029
2030
0
    if (msWMSApplyFilter(map, nVersion, filter, need_axis_swap,
2031
0
                         wms_exception_format, ows_request) == MS_FAILURE) {
2032
0
      return MS_FAILURE; /* msWMSException(map, nVersion,
2033
                            "InvalidFilterRequest"); */
2034
0
    }
2035
0
  }
2036
2037
0
  return MS_SUCCESS;
2038
0
}
2039
2040
/*
2041
**
2042
*/
2043
static void msWMSPrintRequestCap(int nVersion, const char *request,
2044
                                 const char *script_url, const char *formats,
2045
0
                                 ...) {
2046
0
  va_list argp;
2047
2048
0
  msIO_printf("    <%s>\n", request);
2049
2050
  /* We expect to receive a NULL-terminated args list of formats */
2051
0
  va_start(argp, formats);
2052
0
  const char *fmt = formats;
2053
0
  while (fmt != NULL) {
2054
    /* Special case for early WMS with subelements in Format (bug 908) */
2055
0
    char *encoded;
2056
0
    if (nVersion <= OWS_1_0_7) {
2057
0
      encoded = msStrdup(fmt);
2058
0
    }
2059
2060
    /* otherwise we HTML code special characters */
2061
0
    else {
2062
0
      encoded = msEncodeHTMLEntities(fmt);
2063
0
    }
2064
2065
0
    msIO_printf("      <Format>%s</Format>\n", encoded);
2066
0
    msFree(encoded);
2067
2068
0
    fmt = va_arg(argp, const char *);
2069
0
  }
2070
0
  va_end(argp);
2071
2072
0
  msIO_printf("      <DCPType>\n");
2073
0
  msIO_printf("        <HTTP>\n");
2074
  /* The URL should already be HTML encoded. */
2075
0
  if (nVersion == OWS_1_0_0) {
2076
0
    msIO_printf("          <Get onlineResource=\"%s\" />\n", script_url);
2077
0
    msIO_printf("          <Post onlineResource=\"%s\" />\n", script_url);
2078
0
  } else {
2079
0
    msIO_printf("          <Get><OnlineResource "
2080
0
                "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
2081
0
                "xlink:href=\"%s\"/></Get>\n",
2082
0
                script_url);
2083
0
    msIO_printf("          <Post><OnlineResource "
2084
0
                "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
2085
0
                "xlink:href=\"%s\"/></Post>\n",
2086
0
                script_url);
2087
0
  }
2088
2089
0
  msIO_printf("        </HTTP>\n");
2090
0
  msIO_printf("      </DCPType>\n");
2091
0
  msIO_printf("    </%s>\n", request);
2092
0
}
2093
2094
void msWMSPrintAttribution(FILE *stream, const char *tabspace,
2095
                           hashTableObj *metadata,
2096
0
                           const char * /*namespaces*/) {
2097
0
  if (stream && metadata) {
2098
0
    const char *title =
2099
0
        msOWSLookupMetadata(metadata, "MO", "attribution_title");
2100
0
    const char *onlineres =
2101
0
        msOWSLookupMetadata(metadata, "MO", "attribution_onlineresource");
2102
0
    const char *logourl =
2103
0
        msOWSLookupMetadata(metadata, "MO", "attribution_logourl_width");
2104
2105
0
    if (title || onlineres || logourl) {
2106
0
      msIO_printf("%s<Attribution>\n", tabspace);
2107
0
      if (title) {
2108
0
        char *pszEncodedValue = msEncodeHTMLEntities(title);
2109
0
        msIO_fprintf(stream, "%s%s<Title>%s</Title>\n", tabspace, tabspace,
2110
0
                     pszEncodedValue);
2111
0
        free(pszEncodedValue);
2112
0
      }
2113
2114
0
      if (onlineres) {
2115
0
        char *pszEncodedValue = msEncodeHTMLEntities(onlineres);
2116
0
        msIO_fprintf(
2117
0
            stream,
2118
0
            "%s%s<OnlineResource xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
2119
0
            "xlink:href=\"%s\"/>\n",
2120
0
            tabspace, tabspace, pszEncodedValue);
2121
0
        free(pszEncodedValue);
2122
0
      }
2123
2124
0
      if (logourl) {
2125
0
        msOWSPrintURLType(stream, metadata, "MO", "attribution_logourl",
2126
0
                          OWS_NOERR, NULL, "LogoURL", NULL, " width=\"%s\"",
2127
0
                          " height=\"%s\"",
2128
0
                          ">\n             <Format>%s</Format",
2129
0
                          "\n             <OnlineResource "
2130
0
                          "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
2131
0
                          " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
2132
0
                          "          ",
2133
0
                          MS_FALSE, MS_TRUE, MS_TRUE, MS_TRUE, MS_TRUE, NULL,
2134
0
                          NULL, NULL, NULL, NULL, "        ");
2135
0
      }
2136
0
      msIO_printf("%s</Attribution>\n", tabspace);
2137
0
    }
2138
0
  }
2139
0
}
2140
2141
/*
2142
** msWMSPrintScaleHint()
2143
**
2144
** Print a Min/MaxScaleDenominator tag for the layer if applicable.
2145
** used for WMS >=1.3.0
2146
*/
2147
void msWMSPrintScaleDenominator(const char *tabspace, double minscaledenom,
2148
0
                                double maxscaledenom) {
2149
0
  if (minscaledenom > 0)
2150
0
    msIO_printf("%s<MinScaleDenominator>%g</MinScaleDenominator>\n", tabspace,
2151
0
                minscaledenom);
2152
2153
0
  if (maxscaledenom > 0)
2154
0
    msIO_printf("%s<MaxScaleDenominator>%g</MaxScaleDenominator>\n", tabspace,
2155
0
                maxscaledenom);
2156
0
}
2157
2158
/*
2159
** msWMSPrintScaleHint()
2160
**
2161
** Print a ScaleHint tag for this layer if applicable.
2162
**
2163
** (see WMS 1.1.0 sect. 7.1.5.4) The WMS defines the scalehint values as
2164
** the ground distance in meters of the southwest to northeast diagonal of
2165
** the central pixel of a map.  ScaleHint values are the min and max
2166
** recommended values of that diagonal.
2167
*/
2168
void msWMSPrintScaleHint(const char *tabspace, double minscaledenom,
2169
0
                         double maxscaledenom, double resolution) {
2170
0
  double scalehintmin = 0.0, scalehintmax = 0.0;
2171
2172
0
  const double diag = sqrt(2.0);
2173
2174
0
  if (minscaledenom > 0)
2175
0
    scalehintmin =
2176
0
        diag * (minscaledenom / resolution) / msInchesPerUnit(MS_METERS, 0);
2177
0
  if (maxscaledenom > 0)
2178
0
    scalehintmax =
2179
0
        diag * (maxscaledenom / resolution) / msInchesPerUnit(MS_METERS, 0);
2180
2181
0
  if (scalehintmin > 0.0 || scalehintmax > 0.0) {
2182
0
    msIO_printf("%s<ScaleHint min=\"%.15g\" max=\"%.15g\" />\n", tabspace,
2183
0
                scalehintmin, scalehintmax);
2184
0
    if (scalehintmax == 0.0)
2185
0
      msIO_printf("%s<!-- WARNING: Only MINSCALEDENOM and no MAXSCALEDENOM "
2186
0
                  "specified in "
2187
0
                  "the mapfile. A default value of 0 has been returned for the "
2188
0
                  "Max ScaleHint but this is probably not what you want. -->\n",
2189
0
                  tabspace);
2190
0
  }
2191
0
}
2192
2193
/*
2194
** msWMSPrintAuthorityURL()
2195
**
2196
** Print an AuthorityURL tag if applicable.
2197
*/
2198
void msWMSPrintAuthorityURL(FILE *stream, const char *tabspace,
2199
0
                            hashTableObj *metadata, const char *namespaces) {
2200
0
  if (stream && metadata) {
2201
0
    const char *pszWmsAuthorityName =
2202
0
        msOWSLookupMetadata(metadata, namespaces, "authorityurl_name");
2203
0
    const char *pszWMSAuthorityHref =
2204
0
        msOWSLookupMetadata(metadata, namespaces, "authorityurl_href");
2205
2206
    /* AuthorityURL only makes sense if you have *both* the name and url */
2207
0
    if (pszWmsAuthorityName && pszWMSAuthorityHref) {
2208
0
      msOWSPrintEncodeMetadata(
2209
0
          stream, metadata, namespaces, "authorityurl_name", OWS_NOERR,
2210
0
          (std::string(tabspace) + "<AuthorityURL name=\"%s\">\n").c_str(),
2211
0
          NULL);
2212
0
      msOWSPrintEncodeMetadata(
2213
0
          stream, metadata, namespaces, "authorityurl_href", OWS_NOERR,
2214
0
          (std::string(tabspace) +
2215
0
           "  <OnlineResource xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
2216
0
           "xlink:href=\"%s\"/>\n")
2217
0
              .c_str(),
2218
0
          NULL);
2219
0
      msIO_printf("%s</AuthorityURL>\n", tabspace);
2220
0
    } else if (pszWmsAuthorityName || pszWMSAuthorityHref) {
2221
0
      msIO_printf(
2222
0
          "%s<!-- WARNING: Both wms_authorityurl_name and "
2223
0
          "wms_authorityurl_href must be set to output an AuthorityURL -->\n",
2224
0
          tabspace);
2225
0
    }
2226
0
  }
2227
0
}
2228
2229
/*
2230
** msWMSPrintIdentifier()
2231
**
2232
** Print an Identifier tag if applicable.
2233
*/
2234
void msWMSPrintIdentifier(FILE *stream, const char *tabspace,
2235
0
                          hashTableObj *metadata, const char *namespaces) {
2236
0
  if (stream && metadata) {
2237
0
    const char *pszWMSIdentifierAuthority =
2238
0
        msOWSLookupMetadata(metadata, namespaces, "identifier_authority");
2239
0
    const char *pszWMSIdentifierValue =
2240
0
        msOWSLookupMetadata(metadata, namespaces, "identifier_value");
2241
2242
    /* Identifier only makes sense if you have *both* the authority and value */
2243
0
    if (pszWMSIdentifierAuthority && pszWMSIdentifierValue) {
2244
0
      msOWSPrintEncodeMetadata(
2245
0
          stream, metadata, namespaces, "identifier_authority", OWS_NOERR,
2246
0
          (std::string(tabspace) + "<Identifier authority=\"%s\">").c_str(),
2247
0
          NULL);
2248
0
      msOWSPrintEncodeMetadata(stream, metadata, namespaces, "identifier_value",
2249
0
                               OWS_NOERR, "%s</Identifier>\n", NULL);
2250
0
    } else if (pszWMSIdentifierAuthority || pszWMSIdentifierValue) {
2251
0
      msIO_printf(
2252
0
          "%s<!-- WARNING: Both wms_identifier_authority and "
2253
0
          "wms_identifier_value must be set to output an Identifier -->\n",
2254
0
          tabspace);
2255
0
    }
2256
0
  }
2257
0
}
2258
2259
/*
2260
** msWMSPrintKeywordlist()
2261
**
2262
** Print a Keywordlist tag if applicable.
2263
*/
2264
void msWMSPrintKeywordlist(FILE *stream, const char *tabspace, const char *name,
2265
                           hashTableObj *metadata, const char *namespaces,
2266
0
                           int nVersion) {
2267
0
  std::string newname(name); /* max. rootlayer_keywordlist_items          */
2268
0
  newname += "_items";
2269
2270
0
  std::string vocname(name); /* max. rootlayer_keywordlist_vocabulary     */
2271
0
  vocname += "_vocabulary";
2272
2273
0
  if (nVersion == OWS_1_0_0) {
2274
    /* <Keywords> in V 1.0.0 */
2275
    /* The 1.0.0 spec doesn't specify which delimiter to use so let's use spaces
2276
     */
2277
0
    msOWSPrintEncodeMetadataList(
2278
0
        stream, metadata, namespaces, name,
2279
0
        (std::string(tabspace) + "<Keywords>").c_str(),
2280
0
        (std::string(tabspace) + "</Keywords>\n").c_str(), "%s ", NULL);
2281
0
  } else if (msOWSLookupMetadata(metadata, namespaces, name) ||
2282
0
             msOWSLookupMetadata(metadata, namespaces, newname.c_str()) ||
2283
0
             msOWSLookupMetadata(metadata, namespaces, vocname.c_str())) {
2284
    /* <KeywordList><Keyword> ... in V1.0.6+ */
2285
0
    msIO_printf("%s<KeywordList>\n", tabspace);
2286
0
    std::string template1(tabspace);
2287
0
    template1 += "    <Keyword>%s</Keyword>\n";
2288
    /* print old styled ..._keywordlist */
2289
0
    msOWSPrintEncodeMetadataList(stream, metadata, namespaces, name, NULL, NULL,
2290
0
                                 template1.c_str(), NULL);
2291
    /* print new styled ..._keywordlist_items */
2292
0
    msOWSPrintEncodeMetadataList(stream, metadata, namespaces, newname.c_str(),
2293
0
                                 NULL, NULL, template1.c_str(), NULL);
2294
2295
    /* find out if there's a vocabulary list set */
2296
0
    const char *vocabularylist =
2297
0
        msOWSLookupMetadata(metadata, namespaces, vocname.c_str());
2298
0
    if (vocabularylist && nVersion >= OWS_1_3_0) {
2299
0
      const auto tokens = msStringSplit(vocabularylist, ',');
2300
0
      for (const auto &token : tokens) {
2301
0
        msOWSPrintEncodeMetadataList(
2302
0
            stream, metadata, namespaces,
2303
0
            (std::string(name) + '_' + token + "_items").c_str(), NULL, NULL,
2304
0
            (std::string(tabspace) + "    <Keyword vocabulary=\"" + token +
2305
0
             "\">%s</Keyword>\n")
2306
0
                .c_str(),
2307
0
            NULL);
2308
0
      }
2309
0
    }
2310
0
    msIO_printf("%s</KeywordList>\n", tabspace);
2311
0
  }
2312
0
}
2313
2314
/*
2315
** msDumpLayer()
2316
*/
2317
static int msDumpLayer(mapObj *map, layerObj *lp, int nVersion,
2318
                       const char *script_url_encoded, const char *indent,
2319
                       const char *validated_language, int grouplayer,
2320
0
                       int hasQueryableSubLayers) {
2321
0
  rectObj ext;
2322
0
  char **classgroups = NULL;
2323
0
  int iclassgroups = 0;
2324
0
  char *pszMapEPSG, *pszLayerEPSG;
2325
2326
  /* if the layer status is set to MS_DEFAULT, output a warning */
2327
0
  if (lp->status == MS_DEFAULT)
2328
0
    msIO_fprintf(stdout,
2329
0
                 "<!-- WARNING: This layer has its status set to DEFAULT and "
2330
0
                 "will always be displayed when doing a GetMap request even if "
2331
0
                 "it is not requested by the client. This is not in line with "
2332
0
                 "the expected behavior of a WMS server. Using status ON or "
2333
0
                 "OFF is recommended. -->\n");
2334
2335
0
  if (nVersion <= OWS_1_0_7) {
2336
0
    msIO_printf("%s    <Layer queryable=\"%d\">\n", indent,
2337
0
                hasQueryableSubLayers || msIsLayerQueryable(lp));
2338
0
  } else {
2339
    /* 1.1.0 and later: opaque and cascaded are new. */
2340
0
    int cascaded = 0, opaque = 0;
2341
0
    const char *value = msOWSLookupMetadata(&(lp->metadata), "MO", "opaque");
2342
0
    if (value != NULL)
2343
0
      opaque = atoi(value);
2344
0
    if (lp->connectiontype == MS_WMS)
2345
0
      cascaded = 1;
2346
2347
0
    msIO_printf(
2348
0
        "%s    <Layer queryable=\"%d\" opaque=\"%d\" cascaded=\"%d\">\n",
2349
0
        indent, hasQueryableSubLayers || msIsLayerQueryable(lp), opaque,
2350
0
        cascaded);
2351
0
  }
2352
2353
0
  if (lp->name && strlen(lp->name) > 0 &&
2354
0
      (msIsXMLTagValid(lp->name) == MS_FALSE || isdigit(lp->name[0])))
2355
0
    msIO_fprintf(stdout,
2356
0
                 "<!-- WARNING: The layer name '%s' might contain spaces or "
2357
0
                 "invalid characters or may start with a number. This could "
2358
0
                 "lead to potential problems. -->\n",
2359
0
                 lp->name);
2360
0
  msOWSPrintEncodeParam(stdout, "LAYER.NAME", lp->name, OWS_NOERR,
2361
0
                        "        <Name>%s</Name>\n", NULL);
2362
2363
  /* the majority of this section is dependent on appropriately named metadata
2364
   * in the LAYER object */
2365
0
  msOWSPrintEncodeMetadata2(stdout, &(lp->metadata), "MO", "title", OWS_WARN,
2366
0
                            "        <Title>%s</Title>\n", lp->name,
2367
0
                            validated_language);
2368
2369
0
  msOWSPrintEncodeMetadata2(stdout, &(lp->metadata), "MO", "abstract",
2370
0
                            OWS_NOERR, "        <Abstract>%s</Abstract>\n",
2371
0
                            NULL, validated_language);
2372
2373
0
  msWMSPrintKeywordlist(stdout, "        ", "keywordlist", &(lp->metadata),
2374
0
                        "MO", nVersion);
2375
2376
0
  msOWSGetEPSGProj(&(map->projection), &(map->web.metadata), "MO", MS_FALSE,
2377
0
                   &pszMapEPSG);
2378
0
  msOWSGetEPSGProj(&(lp->projection), &(lp->metadata), "MO", MS_FALSE,
2379
0
                   &pszLayerEPSG);
2380
0
  if (pszMapEPSG == NULL) {
2381
    /* If map has no proj then every layer MUST have one or produce a warning */
2382
0
    if (nVersion > OWS_1_1_0) {
2383
      /* starting 1.1.1 SRS are given in individual tags */
2384
0
      if (nVersion >= OWS_1_3_0) {
2385
0
        msOWSPrintEncodeParamList(stdout,
2386
0
                                  "(at least one of) "
2387
0
                                  "MAP.PROJECTION, LAYER.PROJECTION "
2388
0
                                  "or wms_srs metadata",
2389
0
                                  pszLayerEPSG, OWS_WARN, ' ', NULL, NULL,
2390
0
                                  "        <CRS>%s</CRS>\n", NULL);
2391
0
      } else {
2392
0
        msOWSPrintEncodeParamList(stdout,
2393
0
                                  "(at least one of) "
2394
0
                                  "MAP.PROJECTION, LAYER.PROJECTION "
2395
0
                                  "or wms_srs metadata",
2396
0
                                  pszLayerEPSG, OWS_WARN, ' ', NULL, NULL,
2397
0
                                  "        <SRS>%s</SRS>\n", NULL);
2398
0
      }
2399
0
    } else {
2400
0
      msOWSPrintEncodeParam(stdout,
2401
0
                            "(at least one of) MAP.PROJECTION, "
2402
0
                            "LAYER.PROJECTION or wms_srs metadata",
2403
0
                            pszLayerEPSG, OWS_WARN, "        <SRS>%s</SRS>\n",
2404
0
                            NULL);
2405
0
    }
2406
0
  } else {
2407
    /* No warning required in this case since there's at least a map proj. */
2408
0
    if (nVersion > OWS_1_1_0) {
2409
      /* starting 1.1.1 SRS are given in individual tags */
2410
0
      if (nVersion >= OWS_1_3_0) {
2411
0
        msOWSPrintEncodeParamList(stdout,
2412
0
                                  "(at least one of) "
2413
0
                                  "MAP.PROJECTION, LAYER.PROJECTION "
2414
0
                                  "or wms_srs metadata",
2415
0
                                  pszLayerEPSG, OWS_NOERR, ' ', NULL, NULL,
2416
0
                                  "        <CRS>%s</CRS>\n", NULL);
2417
0
      } else {
2418
0
        msOWSPrintEncodeParamList(stdout,
2419
0
                                  "(at least one of) "
2420
0
                                  "MAP.PROJECTION, LAYER.PROJECTION "
2421
0
                                  "or wms_srs metadata",
2422
0
                                  pszLayerEPSG, OWS_NOERR, ' ', NULL, NULL,
2423
0
                                  "        <SRS>%s</SRS>\n", NULL);
2424
0
      }
2425
0
    } else {
2426
0
      msOWSPrintEncodeParam(stdout, " LAYER.PROJECTION (or wms_srs metadata)",
2427
0
                            pszLayerEPSG, OWS_NOERR, "        <SRS>%s</SRS>\n",
2428
0
                            NULL);
2429
0
    }
2430
0
  }
2431
0
  msFree(pszLayerEPSG);
2432
0
  msFree(pszMapEPSG);
2433
2434
  /* If layer has no proj set then use map->proj for bounding box. */
2435
0
  if (msOWSGetLayerExtent(map, lp, "MO", &ext) == MS_SUCCESS) {
2436
0
    if (lp->projection.numargs > 0) {
2437
0
      if (nVersion >= OWS_1_3_0)
2438
0
        msOWSPrintEX_GeographicBoundingBox(stdout, "        ", &(ext),
2439
0
                                           &(lp->projection));
2440
0
      else
2441
0
        msOWSPrintLatLonBoundingBox(stdout, "        ", &(ext),
2442
0
                                    &(lp->projection), NULL, OWS_WMS);
2443
2444
0
      msOWSPrintBoundingBox(stdout, "        ", &(ext), &(lp->projection),
2445
0
                            &(lp->metadata), &(map->web.metadata), "MO",
2446
0
                            nVersion);
2447
0
    } else {
2448
0
      if (nVersion >= OWS_1_3_0)
2449
0
        msOWSPrintEX_GeographicBoundingBox(stdout, "        ", &(ext),
2450
0
                                           &(map->projection));
2451
0
      else
2452
0
        msOWSPrintLatLonBoundingBox(stdout, "        ", &(ext),
2453
0
                                    &(map->projection), NULL, OWS_WMS);
2454
0
      msOWSPrintBoundingBox(stdout, "        ", &(ext), &(map->projection),
2455
0
                            &(lp->metadata), &(map->web.metadata), "MO",
2456
0
                            nVersion);
2457
0
    }
2458
0
  } else {
2459
0
    if (nVersion >= OWS_1_3_0)
2460
0
      msIO_printf(
2461
0
          "        <!-- WARNING: Optional Ex_GeographicBoundingBox could not "
2462
0
          "be established for this layer.  Consider setting the EXTENT in the "
2463
0
          "LAYER object, or wms_extent metadata. Also check that your data "
2464
0
          "exists in the DATA statement -->\n");
2465
0
    else
2466
0
      msIO_printf("        <!-- WARNING: Optional LatLonBoundingBox could not "
2467
0
                  "be established for this layer.  Consider setting the EXTENT "
2468
0
                  "in the LAYER object, or wms_extent metadata. Also check "
2469
0
                  "that your data exists in the DATA statement -->\n");
2470
0
  }
2471
2472
  /* time support */
2473
0
  const char *pszWmsTimeExtent =
2474
0
      msOWSLookupMetadata(&(lp->metadata), "MO", "timeextent");
2475
0
  if (pszWmsTimeExtent) {
2476
0
    const char *pszWmsTimeDefault =
2477
0
        msOWSLookupMetadata(&(lp->metadata), "MO", "timedefault");
2478
2479
0
    if (nVersion >= OWS_1_3_0) {
2480
0
      if (pszWmsTimeDefault)
2481
0
        msIO_fprintf(stdout,
2482
0
                     "        <Dimension name=\"time\" units=\"ISO8601\" "
2483
0
                     "default=\"%s\" nearestValue=\"0\">%s</Dimension>\n",
2484
0
                     pszWmsTimeDefault, pszWmsTimeExtent);
2485
0
      else
2486
0
        msIO_fprintf(stdout,
2487
0
                     "        <Dimension name=\"time\" units=\"ISO8601\" "
2488
0
                     "nearestValue=\"0\">%s</Dimension>\n",
2489
0
                     pszWmsTimeExtent);
2490
0
    }
2491
2492
0
    else {
2493
0
      msIO_fprintf(stdout,
2494
0
                   "        <Dimension name=\"time\" units=\"ISO8601\"/>\n");
2495
0
      if (pszWmsTimeDefault)
2496
0
        msIO_fprintf(stdout,
2497
0
                     "        <Extent name=\"time\" default=\"%s\" "
2498
0
                     "nearestValue=\"0\">%s</Extent>\n",
2499
0
                     pszWmsTimeDefault, pszWmsTimeExtent);
2500
0
      else
2501
0
        msIO_fprintf(
2502
0
            stdout,
2503
0
            "        <Extent name=\"time\" nearestValue=\"0\">%s</Extent>\n",
2504
0
            pszWmsTimeExtent);
2505
0
    }
2506
0
  }
2507
2508
  /*dimensions support: elevation + other user defined dimensions*/
2509
0
  const char *pszDimensionlist =
2510
0
      msOWSLookupMetadata(&(lp->metadata), "M", "dimensionlist");
2511
0
  if (pszDimensionlist) {
2512
0
    auto tokens = msStringSplit(pszDimensionlist, ',');
2513
0
    for (auto &dimension : tokens) {
2514
      /*check if manadatory unit and extent are set. Item should also be set.
2515
       * default value is optional*/
2516
0
      msStringTrim(dimension);
2517
2518
0
      const char *pszDimensionItem = msOWSLookupMetadata(
2519
0
          &(lp->metadata), "M", (dimension + "_item").c_str());
2520
0
      const char *pszDimensionExtent = msOWSLookupMetadata(
2521
0
          &(lp->metadata), "M", (dimension + "_extent").c_str());
2522
0
      const char *pszDimensionUnit = msOWSLookupMetadata(
2523
0
          &(lp->metadata), "M", (dimension + "_units").c_str());
2524
0
      const char *pszDimensionDefault = msOWSLookupMetadata(
2525
0
          &(lp->metadata), "M", (dimension + "_default").c_str());
2526
2527
0
      if (pszDimensionItem && pszDimensionExtent && pszDimensionUnit) {
2528
0
        if (nVersion >= OWS_1_3_0) {
2529
0
          if (pszDimensionDefault && strlen(pszDimensionDefault) > 0)
2530
0
            msIO_fprintf(
2531
0
                stdout,
2532
0
                "        <Dimension name=\"%s\" units=\"%s\" default=\"%s\" "
2533
0
                "multipleValues=\"1\" nearestValue=\"0\">%s</Dimension>\n",
2534
0
                dimension.c_str(), pszDimensionUnit, pszDimensionDefault,
2535
0
                pszDimensionExtent);
2536
0
          else
2537
0
            msIO_fprintf(
2538
0
                stdout,
2539
0
                "        <Dimension name=\"%s\" units=\"%s\"  "
2540
0
                "multipleValues=\"1\"  nearestValue=\"0\">%s</Dimension>\n",
2541
0
                dimension.c_str(), pszDimensionUnit, pszDimensionExtent);
2542
0
        } else {
2543
0
          msIO_fprintf(stdout,
2544
0
                       "        <Dimension name=\"%s\" units=\"%s\"/>\n",
2545
0
                       dimension.c_str(), pszDimensionUnit);
2546
0
          if (pszDimensionDefault && strlen(pszDimensionDefault) > 0)
2547
0
            msIO_fprintf(stdout,
2548
0
                         "        <Extent name=\"%s\" default=\"%s\" "
2549
0
                         "nearestValue=\"0\">%s</Extent>\n",
2550
0
                         dimension.c_str(), pszDimensionDefault,
2551
0
                         pszDimensionExtent);
2552
0
          else
2553
0
            msIO_fprintf(
2554
0
                stdout,
2555
0
                "        <Extent name=\"%s\" nearestValue=\"0\">%s</Extent>\n",
2556
0
                dimension.c_str(), pszDimensionExtent);
2557
0
        }
2558
0
      }
2559
0
    }
2560
0
  }
2561
2562
0
  if (nVersion >= OWS_1_0_7) {
2563
0
    msWMSPrintAttribution(stdout, "    ", &(lp->metadata), "MO");
2564
0
  }
2565
2566
  /* AuthorityURL support and Identifier support, only available >= WMS 1.1.0 */
2567
0
  if (nVersion >= OWS_1_1_0) {
2568
0
    msWMSPrintAuthorityURL(stdout, "        ", &(lp->metadata), "MO");
2569
0
    msWMSPrintIdentifier(stdout, "        ", &(lp->metadata), "MO");
2570
0
  }
2571
2572
0
  if (nVersion >= OWS_1_1_0) {
2573
0
    const char *metadataurl_list =
2574
0
        msOWSLookupMetadata(&(lp->metadata), "MO", "metadataurl_list");
2575
0
    if (metadataurl_list) {
2576
0
      const auto tokens = msStringSplit(metadataurl_list, ' ');
2577
0
      for (const auto &token : tokens) {
2578
0
        std::string key("metadataurl_");
2579
0
        key += token;
2580
0
        msOWSPrintURLType(stdout, &(lp->metadata), "MO", key.c_str(), OWS_NOERR,
2581
0
                          NULL, "MetadataURL", " type=\"%s\"", NULL, NULL,
2582
0
                          ">\n          <Format>%s</Format",
2583
0
                          "\n          <OnlineResource xmlns:xlink=\""
2584
0
                          "http://www.w3.org/1999/xlink\" "
2585
0
                          "xlink:type=\"simple\" xlink:href=\"%s\"/>\n        ",
2586
0
                          MS_TRUE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
2587
0
                          NULL, NULL, NULL, NULL, "        ");
2588
0
      }
2589
0
    } else {
2590
0
      if (!msOWSLookupMetadata(&(lp->metadata), "MO", "metadataurl_href"))
2591
0
        msMetadataSetGetMetadataURL(lp, script_url_encoded);
2592
2593
0
      msOWSPrintURLType(stdout, &(lp->metadata), "MO", "metadataurl", OWS_NOERR,
2594
0
                        NULL, "MetadataURL", " type=\"%s\"", NULL, NULL,
2595
0
                        ">\n          <Format>%s</Format",
2596
0
                        "\n          <OnlineResource xmlns:xlink=\""
2597
0
                        "http://www.w3.org/1999/xlink\" "
2598
0
                        "xlink:type=\"simple\" xlink:href=\"%s\"/>\n        ",
2599
0
                        MS_TRUE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
2600
0
                        NULL, NULL, NULL, NULL, "        ");
2601
0
    }
2602
0
  }
2603
2604
0
  if (nVersion < OWS_1_1_0)
2605
0
    msOWSPrintEncodeMetadata(stdout, &(lp->metadata), "MO", "dataurl_href",
2606
0
                             OWS_NOERR, "        <DataURL>%s</DataURL>\n",
2607
0
                             NULL);
2608
0
  else {
2609
0
    const char *dataurl_list =
2610
0
        msOWSLookupMetadata(&(lp->metadata), "MO", "dataurl_list");
2611
0
    if (dataurl_list) {
2612
0
      const auto tokens = msStringSplit(dataurl_list, ' ');
2613
0
      for (const auto &token : tokens) {
2614
0
        std::string key("dataurl_");
2615
0
        key += token;
2616
0
        msOWSPrintURLType(stdout, &(lp->metadata), "MO", key.c_str(), OWS_NOERR,
2617
0
                          NULL, "DataURL", NULL, NULL, NULL,
2618
0
                          ">\n          <Format>%s</Format",
2619
0
                          "\n          <OnlineResource xmlns:xlink=\""
2620
0
                          "http://www.w3.org/1999/xlink\" "
2621
0
                          "xlink:type=\"simple\" xlink:href=\"%s\"/>\n        ",
2622
0
                          MS_FALSE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
2623
0
                          NULL, NULL, NULL, NULL, "        ");
2624
0
      }
2625
0
    } else {
2626
0
      msOWSPrintURLType(stdout, &(lp->metadata), "MO", "dataurl", OWS_NOERR,
2627
0
                        NULL, "DataURL", NULL, NULL, NULL,
2628
0
                        ">\n          <Format>%s</Format",
2629
0
                        "\n          <OnlineResource xmlns:xlink=\""
2630
0
                        "http://www.w3.org/1999/xlink\" "
2631
0
                        "xlink:type=\"simple\" xlink:href=\"%s\"/>\n        ",
2632
0
                        MS_FALSE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
2633
0
                        NULL, NULL, NULL, NULL, "        ");
2634
0
    }
2635
0
  }
2636
2637
  /* The LegendURL reside in a style. The Web Map Context spec already  */
2638
  /* included the support on this in mapserver. However, it is not in the  */
2639
  /* wms_legendurl_... metadatas it's in the styles metadata, */
2640
  /* In wms_style_<style_name>_lengendurl_... metadata. So we have to detect */
2641
  /* the current style before reading it. Also in the Style block, we need */
2642
  /* a Title and a name. Title is derived from wms_style_<style>_title, */
2643
  /* which allows multiple style definitions, e.g. by using classgroups. */
2644
0
  const char *pszStyle = msOWSLookupMetadata(&(lp->metadata), "MO", "style");
2645
0
  const char *pszLegendURL = NULL;
2646
0
  if (pszStyle) {
2647
0
    pszLegendURL = msOWSLookupMetadata(
2648
0
        &(lp->metadata), "MO",
2649
0
        (std::string("style_") + pszStyle + "_legendurl_href").c_str());
2650
0
  } else
2651
0
    pszStyle = "default";
2652
2653
0
  if (nVersion <= OWS_1_0_0 && pszLegendURL) {
2654
    /* First, print the style block */
2655
0
    msIO_fprintf(stdout, "        <Style>\n");
2656
0
    msIO_fprintf(stdout, "          <Name>%s</Name>\n", pszStyle);
2657
    /* Print the real Title or Style name otherwise */
2658
0
    msOWSPrintEncodeMetadata2(
2659
0
        stdout, &(lp->metadata), "MO",
2660
0
        (std::string("style_") + pszStyle + "_title").c_str(), OWS_NOERR,
2661
0
        "          <Title>%s</Title>\n", pszStyle, validated_language);
2662
2663
    /* Inside, print the legend url block */
2664
0
    msOWSPrintEncodeMetadata(
2665
0
        stdout, &(lp->metadata), "MO",
2666
0
        (std::string("style_") + pszStyle + "_legendurl_href").c_str(),
2667
0
        OWS_NOERR, "          <StyleURL>%s</StyleURL>\n", NULL);
2668
2669
    /* close the style block */
2670
0
    msIO_fprintf(stdout, "        </Style>\n");
2671
2672
0
  } else if (nVersion >= OWS_1_1_0) {
2673
0
    if (pszLegendURL) {
2674
      /* First, print the style block */
2675
0
      msIO_fprintf(stdout, "        <Style>\n");
2676
0
      msIO_fprintf(stdout, "          <Name>%s</Name>\n", pszStyle);
2677
      /* Print the real Title or Style name otherwise */
2678
0
      msOWSPrintEncodeMetadata2(
2679
0
          stdout, &(lp->metadata), "MO",
2680
0
          (std::string("style_") + pszStyle + "_title").c_str(), OWS_NOERR,
2681
0
          "          <Title>%s</Title>\n", pszStyle, validated_language);
2682
2683
      /* Inside, print the legend url block */
2684
0
      msOWSPrintURLType(
2685
0
          stdout, &(lp->metadata), "MO",
2686
0
          (std::string("style_") + pszStyle + "_legendurl").c_str(), OWS_NOERR,
2687
0
          NULL, "LegendURL", NULL, " width=\"%s\"", " height=\"%s\"",
2688
0
          ">\n             <Format>%s</Format",
2689
0
          "\n             <OnlineResource "
2690
0
          "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
2691
0
          " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
2692
0
          "          ",
2693
0
          MS_FALSE, MS_TRUE, MS_TRUE, MS_TRUE, MS_TRUE, NULL, NULL, NULL, NULL,
2694
0
          NULL, "          ");
2695
0
      msIO_fprintf(stdout, "        </Style>\n");
2696
2697
0
    } else {
2698
0
      if (script_url_encoded) {
2699
0
        if (lp->connectiontype != MS_WMS && lp->connectiontype != MS_WFS &&
2700
0
            lp->connectiontype != MS_UNUSED_1 && lp->numclasses > 0) {
2701
0
          bool classnameset = false;
2702
0
          for (int i = 0; i < lp->numclasses; i++) {
2703
0
            if (lp->_class[i]->name && strlen(lp->_class[i]->name) > 0) {
2704
0
              classnameset = true;
2705
0
              break;
2706
0
            }
2707
0
          }
2708
0
          if (classnameset) {
2709
0
            int size_x = 0, size_y = 0;
2710
0
            std::vector<int> group_layers;
2711
0
            group_layers.reserve(map->numlayers);
2712
2713
0
            char ***nestedGroups =
2714
0
                (char ***)msSmallCalloc(map->numlayers, sizeof(char **));
2715
0
            int *numNestedGroups =
2716
0
                (int *)msSmallCalloc(map->numlayers, sizeof(int));
2717
0
            int *isUsedInNestedGroup =
2718
0
                (int *)msSmallCalloc(map->numlayers, sizeof(int));
2719
0
            msWMSPrepareNestedGroups(map, nVersion, nestedGroups,
2720
0
                                     numNestedGroups, isUsedInNestedGroup);
2721
2722
0
            group_layers.push_back(lp->index);
2723
0
            if (isUsedInNestedGroup[lp->index]) {
2724
0
              for (int j = 0; j < map->numlayers; j++) {
2725
0
                if (j == lp->index)
2726
0
                  continue;
2727
0
                for (int k = 0; k < numNestedGroups[j]; k++) {
2728
0
                  if (strcasecmp(lp->name, nestedGroups[j][k]) == 0) {
2729
0
                    group_layers.push_back(j);
2730
0
                    break;
2731
0
                  }
2732
0
                }
2733
0
              }
2734
0
            }
2735
2736
0
            if (msLegendCalcSize(map, 1, &size_x, &size_y, group_layers.data(),
2737
0
                                 static_cast<int>(group_layers.size()), NULL,
2738
0
                                 1) == MS_SUCCESS) {
2739
0
              const std::string width(std::to_string(size_x));
2740
0
              const std::string height(std::to_string(size_y));
2741
2742
0
              char *mimetype = NULL;
2743
0
#if defined USE_PNG
2744
0
              mimetype = msEncodeHTMLEntities("image/png");
2745
0
#endif
2746
2747
0
#if defined USE_JPEG
2748
0
              if (!mimetype)
2749
0
                mimetype = msEncodeHTMLEntities("image/jpeg");
2750
0
#endif
2751
0
              if (!mimetype)
2752
0
                mimetype =
2753
0
                    msEncodeHTMLEntities(MS_IMAGE_MIME_TYPE(map->outputformat));
2754
2755
              /* --------------------------------------------------------------------
2756
               */
2757
              /*      check if the group parameters for the classes are set. We
2758
               */
2759
              /*      should then publish the different class groups as
2760
               * different styles.*/
2761
              /* --------------------------------------------------------------------
2762
               */
2763
0
              iclassgroups = 0;
2764
0
              classgroups = NULL;
2765
2766
0
              const char *styleName =
2767
0
                  msOWSLookupMetadata(&(map->web.metadata), "MO", "style_name");
2768
0
              if (styleName == NULL)
2769
0
                styleName = "default";
2770
0
              char *pszEncodedStyleName = msEncodeHTMLEntities(styleName);
2771
2772
0
              for (int i = 0; i < lp->numclasses; i++) {
2773
0
                if (lp->_class[i]->name && lp->_class[i]->group) {
2774
                  /* Check that style is not inherited from root layer (#4442).
2775
                   */
2776
0
                  if (strcasecmp(pszEncodedStyleName, lp->_class[i]->group) ==
2777
0
                      0)
2778
0
                    continue;
2779
                  /* Check that style is not inherited from group layer(s)
2780
                   * (#4442). */
2781
0
                  if (numNestedGroups[lp->index] > 0) {
2782
0
                    int j = 0;
2783
0
                    layerObj *lp2 = NULL;
2784
0
                    for (; j < numNestedGroups[lp->index]; j++) {
2785
0
                      int l = 0;
2786
0
                      for (int k = 0; k < map->numlayers; k++) {
2787
0
                        if (GET_LAYER(map, k)->name &&
2788
0
                            strcasecmp(GET_LAYER(map, k)->name,
2789
0
                                       nestedGroups[lp->index][j]) == 0) {
2790
0
                          lp2 = (GET_LAYER(map, k));
2791
0
                          for (l = 0; l < lp2->numclasses; l++) {
2792
0
                            if (strcasecmp(lp2->_class[l]->group,
2793
0
                                           lp->_class[i]->group) == 0)
2794
0
                              break;
2795
0
                          }
2796
0
                          break;
2797
0
                        }
2798
0
                      }
2799
0
                      if (lp2 && l < lp2->numclasses)
2800
0
                        break;
2801
0
                    }
2802
0
                    if (j < numNestedGroups[lp->index])
2803
0
                      continue;
2804
0
                  }
2805
0
                  if (!classgroups) {
2806
0
                    classgroups = (char **)msSmallMalloc(sizeof(char *));
2807
0
                    classgroups[iclassgroups++] =
2808
0
                        msStrdup(lp->_class[i]->group);
2809
0
                  } else {
2810
                    /* Output style only once. */
2811
0
                    bool found = false;
2812
0
                    for (int j = 0; j < iclassgroups; j++) {
2813
0
                      if (strcasecmp(classgroups[j], lp->_class[i]->group) ==
2814
0
                          0) {
2815
0
                        found = true;
2816
0
                        break;
2817
0
                      }
2818
0
                    }
2819
0
                    if (!found) {
2820
0
                      iclassgroups++;
2821
0
                      classgroups = (char **)msSmallRealloc(
2822
0
                          classgroups, sizeof(char *) * iclassgroups);
2823
0
                      classgroups[iclassgroups - 1] =
2824
0
                          msStrdup(lp->_class[i]->group);
2825
0
                    }
2826
0
                  }
2827
0
                }
2828
0
              }
2829
0
              msFree(pszEncodedStyleName);
2830
0
              if (classgroups == NULL) {
2831
0
                classgroups = (char **)msSmallMalloc(sizeof(char *));
2832
0
                classgroups[0] = msStrdup("default");
2833
0
                iclassgroups = 1;
2834
0
              }
2835
2836
0
              for (int i = 0; i < iclassgroups; i++) {
2837
0
                char *name_encoded = msEncodeHTMLEntities(lp->name);
2838
0
                char *classgroup_encoded = msEncodeHTMLEntities(classgroups[i]);
2839
0
                std::string legendurl(script_url_encoded);
2840
0
                legendurl += "version=";
2841
0
                char szVersionBuf[OWS_VERSION_MAXLEN];
2842
0
                legendurl += msOWSGetVersionString(nVersion, szVersionBuf);
2843
0
                legendurl +=
2844
0
                    "&amp;service=WMS&amp;request=GetLegendGraphic&amp;";
2845
0
                if (nVersion >= OWS_1_3_0) {
2846
0
                  legendurl += "sld_version=1.1.0&amp;layer=";
2847
0
                } else {
2848
0
                  legendurl += "layer=";
2849
0
                }
2850
0
                legendurl += name_encoded;
2851
0
                legendurl += "&amp;format=";
2852
0
                legendurl += mimetype;
2853
0
                legendurl += "&amp;STYLE=";
2854
0
                legendurl += classgroup_encoded;
2855
2856
0
                msFree(name_encoded);
2857
0
                msFree(classgroup_encoded);
2858
2859
0
                msIO_fprintf(stdout, "        <Style>\n");
2860
0
                msIO_fprintf(stdout, "          <Name>%s</Name>\n",
2861
0
                             classgroups[i]);
2862
0
                msOWSPrintEncodeMetadata2(
2863
0
                    stdout, &(lp->metadata), "MO",
2864
0
                    (std::string("style_") + classgroups[i] + "_title").c_str(),
2865
0
                    OWS_NOERR, "          <Title>%s</Title>\n", classgroups[i],
2866
0
                    validated_language);
2867
2868
                /* A legendurl from wms_style_<style>_legendurl_href will
2869
                 * override a self generated legend graphic */
2870
0
                pszLegendURL = msOWSLookupMetadata(
2871
0
                    &(lp->metadata), "MO",
2872
0
                    (std::string("style_") + classgroups[i] + "_legendurl_href")
2873
0
                        .c_str());
2874
0
                if (pszLegendURL) {
2875
0
                  msOWSPrintURLType(
2876
0
                      stdout, &(lp->metadata), "MO",
2877
0
                      (std::string("style_") + classgroups[i] + "_legendurl")
2878
0
                          .c_str(),
2879
0
                      OWS_NOERR, NULL, "LegendURL", NULL, " width=\"%s\"",
2880
0
                      " height=\"%s\"", ">\n             <Format>%s</Format",
2881
0
                      "\n             <OnlineResource "
2882
0
                      "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
2883
0
                      " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
2884
0
                      "          ",
2885
0
                      MS_FALSE, MS_TRUE, MS_TRUE, MS_TRUE, MS_TRUE, NULL, NULL,
2886
0
                      NULL, NULL, NULL, "          ");
2887
0
                } else {
2888
0
                  msOWSPrintURLType(
2889
0
                      stdout, NULL, "O", "ttt", OWS_NOERR, NULL, "LegendURL",
2890
0
                      NULL, " width=\"%s\"", " height=\"%s\"",
2891
0
                      ">\n             <Format>%s</Format",
2892
0
                      "\n             <OnlineResource "
2893
0
                      "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
2894
0
                      " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
2895
0
                      "          ",
2896
0
                      MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE, NULL,
2897
0
                      width.c_str(), height.c_str(), mimetype,
2898
0
                      legendurl.c_str(), "          ");
2899
0
                }
2900
0
                msIO_fprintf(stdout, "        </Style>\n");
2901
0
              }
2902
0
              msFreeCharArray(classgroups, iclassgroups);
2903
0
              msFree(mimetype);
2904
0
            }
2905
            /* free the stuff used for nested layers */
2906
0
            for (int i = 0; i < map->numlayers; i++) {
2907
0
              if (numNestedGroups[i] > 0) {
2908
0
                msFreeCharArray(nestedGroups[i], numNestedGroups[i]);
2909
0
              }
2910
0
            }
2911
0
            free(nestedGroups);
2912
0
            free(numNestedGroups);
2913
0
            free(isUsedInNestedGroup);
2914
0
          }
2915
0
        }
2916
0
      }
2917
0
    }
2918
0
  }
2919
2920
  /* print Min/Max ScaleDenominator */
2921
0
  if (nVersion < OWS_1_3_0)
2922
0
    msWMSPrintScaleHint("        ", lp->minscaledenom, lp->maxscaledenom,
2923
0
                        map->resolution);
2924
0
  else
2925
0
    msWMSPrintScaleDenominator("        ", lp->minscaledenom,
2926
0
                               lp->maxscaledenom);
2927
2928
0
  if (grouplayer == MS_FALSE)
2929
0
    msIO_printf("%s    </Layer>\n", indent);
2930
2931
0
  return MS_SUCCESS;
2932
0
}
2933
2934
/*
2935
 * msWMSIsSubGroup
2936
 */
2937
static bool msWMSIsSubGroup(char **currentGroups, int currentLevel,
2938
0
                            char **otherGroups, int numOtherGroups) {
2939
  /* no match if otherGroups[] has less levels than currentLevel */
2940
0
  if (numOtherGroups <= currentLevel) {
2941
0
    return false;
2942
0
  }
2943
  /* compare all groups below the current level */
2944
0
  for (int i = 0; i <= currentLevel; i++) {
2945
0
    if (strcmp(currentGroups[i], otherGroups[i]) != 0) {
2946
0
      return false; /* if one of these is not equal it is not a sub group */
2947
0
    }
2948
0
  }
2949
0
  return true;
2950
0
}
2951
2952
/*
2953
 * msWMSHasQueryableSubLayers
2954
 */
2955
static int msWMSHasQueryableSubLayers(mapObj *map, int index, int level,
2956
                                      char ***nestedGroups,
2957
0
                                      int *numNestedGroups) {
2958
0
  for (int j = index; j < map->numlayers; j++) {
2959
0
    if (msWMSIsSubGroup(nestedGroups[index], level, nestedGroups[j],
2960
0
                        numNestedGroups[j])) {
2961
0
      if (msIsLayerQueryable(GET_LAYER(map, j)))
2962
0
        return MS_TRUE;
2963
0
    }
2964
0
  }
2965
0
  return MS_FALSE;
2966
0
}
2967
2968
/***********************************************************************************
2969
 * msWMSPrintNestedGroups() *
2970
 *                                                                                 *
2971
 * purpose: Writes the layers to the capabilities that have the *
2972
 * "WMS_LAYER_GROUP" metadata set. *
2973
 *                                                                                 *
2974
 * params: * -map: The main map object * -nVersion: OGC WMS version *
2975
 * -pabLayerProcessed: boolean array indicating which layers have been dealt
2976
 *with. * -index: the index of the current layer. * -level: the level of depth
2977
 *in the group tree (root = 0)                         * -nestedGroups: This
2978
 *array holds the arrays of groups that have                  * been set through
2979
 *the WMS_LAYER_GROUP metadata                                 *
2980
 * -numNestedGroups: This array holds the number of nested groups for each layer
2981
 **
2982
 ***********************************************************************************/
2983
void msWMSPrintNestedGroups(mapObj *map, int nVersion, char *pabLayerProcessed,
2984
                            int index, int level, char ***nestedGroups,
2985
                            int *numNestedGroups, int *isUsedInNestedGroup,
2986
                            const char *script_url_encoded,
2987
0
                            const char *validated_language) {
2988
0
  bool groupAdded = false;
2989
0
  std::string indent;
2990
0
  for (int i = 0; i < level; i++) {
2991
0
    indent += "  ";
2992
0
  }
2993
2994
0
  if (numNestedGroups[index] <= level) { /* no more subgroups */
2995
0
    if ((!pabLayerProcessed[index]) && (!isUsedInNestedGroup[index])) {
2996
      /* we are at the deepest level of the group branchings, so add layer now.
2997
       */
2998
0
      msDumpLayer(map, GET_LAYER(map, index), nVersion, script_url_encoded,
2999
0
                  indent.c_str(), validated_language, MS_FALSE, MS_FALSE);
3000
0
      pabLayerProcessed[index] = 1; /* done */
3001
0
    }
3002
0
  } else { /* not yet there, we have to deal with this group and possible
3003
              subgroups and layers. */
3004
0
    int j;
3005
0
    for (j = 0; j < map->numlayers; j++) {
3006
0
      if (GET_LAYER(map, j)->name &&
3007
0
          strcasecmp(GET_LAYER(map, j)->name, nestedGroups[index][level]) ==
3008
0
              0) {
3009
0
        break;
3010
0
      }
3011
0
    }
3012
3013
    /* Beginning of a new group... enclose the group in a layer block */
3014
0
    if (j < map->numlayers) {
3015
0
      if (!pabLayerProcessed[j]) {
3016
0
        msDumpLayer(map, GET_LAYER(map, j), nVersion, script_url_encoded,
3017
0
                    indent.c_str(), validated_language, MS_TRUE,
3018
0
                    msWMSHasQueryableSubLayers(map, index, level, nestedGroups,
3019
0
                                               numNestedGroups));
3020
0
        pabLayerProcessed[j] = 1; /* done */
3021
0
        groupAdded = true;
3022
0
      }
3023
0
    } else {
3024
0
      msIO_printf("%s    <Layer%s>\n", indent.c_str(),
3025
0
                  msWMSHasQueryableSubLayers(map, index, level, nestedGroups,
3026
0
                                             numNestedGroups)
3027
0
                      ? " queryable=\"1\""
3028
0
                      : "");
3029
0
      msIO_printf("%s      <Name>%s</Name>\n", indent.c_str(),
3030
0
                  nestedGroups[index][level]);
3031
0
      msIO_printf("%s      <Title>%s</Title>\n", indent.c_str(),
3032
0
                  nestedGroups[index][level]);
3033
0
      groupAdded = true;
3034
0
    }
3035
3036
    /* Look for one group deeper in the current layer */
3037
0
    if (!pabLayerProcessed[index]) {
3038
0
      msWMSPrintNestedGroups(map, nVersion, pabLayerProcessed, index, level + 1,
3039
0
                             nestedGroups, numNestedGroups, isUsedInNestedGroup,
3040
0
                             script_url_encoded, validated_language);
3041
0
    }
3042
3043
    /* look for subgroups in other layers. */
3044
0
    for (j = index + 1; j < map->numlayers; j++) {
3045
0
      if (msWMSIsSubGroup(nestedGroups[index], level, nestedGroups[j],
3046
0
                          numNestedGroups[j])) {
3047
0
        if (!pabLayerProcessed[j]) {
3048
0
          msWMSPrintNestedGroups(map, nVersion, pabLayerProcessed, j, level + 1,
3049
0
                                 nestedGroups, numNestedGroups,
3050
0
                                 isUsedInNestedGroup, script_url_encoded,
3051
0
                                 validated_language);
3052
0
        }
3053
0
      } else {
3054
        /* TODO: if we would sort all layers on "WMS_LAYER_GROUP" beforehand */
3055
        /* we could break out of this loop at this point, which would increase
3056
         */
3057
        /* performance.  */
3058
0
      }
3059
0
    }
3060
    /* Close group layer block */
3061
0
    if (groupAdded)
3062
0
      msIO_printf("%s    </Layer>\n", indent.c_str());
3063
0
  }
3064
0
} /* msWMSPrintNestedGroups */
3065
3066
/*
3067
** msWMSGetCapabilities()
3068
*/
3069
static int msWMSGetCapabilities(mapObj *map, int nVersion, cgiRequestObj *req,
3070
                                owsRequestObj *ows_request,
3071
                                const char *requested_updatesequence,
3072
                                const char *wms_exception_format,
3073
0
                                const char *requested_language) {
3074
0
  const char *updatesequence =
3075
0
      msOWSLookupMetadata(&(map->web.metadata), "MO", "updatesequence");
3076
3077
0
  const char *sldenabled =
3078
0
      msOWSLookupMetadata(&(map->web.metadata), "MO", "sld_enabled");
3079
3080
0
  if (sldenabled == NULL)
3081
0
    sldenabled = "true";
3082
3083
0
  if (requested_updatesequence != NULL) {
3084
0
    int i =
3085
0
        msOWSNegotiateUpdateSequence(requested_updatesequence, updatesequence);
3086
0
    if (i == 0) { /* current */
3087
0
      msSetErrorWithStatus(
3088
0
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
3089
0
          "UPDATESEQUENCE parameter (%s) is equal to server (%s)",
3090
0
          "msWMSGetCapabilities()", requested_updatesequence, updatesequence);
3091
0
      return msWMSException(map, nVersion, "CurrentUpdateSequence",
3092
0
                            wms_exception_format);
3093
0
    }
3094
0
    if (i > 0) { /* invalid */
3095
0
      msSetErrorWithStatus(
3096
0
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
3097
0
          "UPDATESEQUENCE parameter (%s) is higher than server (%s)",
3098
0
          "msWMSGetCapabilities()", requested_updatesequence, updatesequence);
3099
0
      return msWMSException(map, nVersion, "InvalidUpdateSequence",
3100
0
                            wms_exception_format);
3101
0
    }
3102
0
  }
3103
3104
0
  std::string schemalocation;
3105
0
  {
3106
0
    char *pszSchemalocation =
3107
0
        msEncodeHTMLEntities(msOWSGetSchemasLocation(map));
3108
0
    schemalocation = pszSchemalocation;
3109
0
    msFree(pszSchemalocation);
3110
0
  }
3111
3112
0
  if (nVersion < 0)
3113
0
    nVersion = OWS_1_3_0; /* Default to 1.3.0 */
3114
3115
  /* Decide which version we're going to return. */
3116
0
  std::string dtd_url;
3117
0
  if (nVersion < OWS_1_0_7) {
3118
0
    nVersion = OWS_1_0_0;
3119
0
    dtd_url = std::move(schemalocation);
3120
0
    dtd_url += "/wms/1.0.0/capabilities_1_0_0.dtd";
3121
0
  }
3122
3123
0
  else if (nVersion < OWS_1_1_0) {
3124
0
    nVersion = OWS_1_0_7;
3125
0
    dtd_url = std::move(schemalocation);
3126
0
    dtd_url += "/wms/1.0.7/capabilities_1_0_7.dtd";
3127
0
  } else if (nVersion < OWS_1_1_1) {
3128
0
    nVersion = OWS_1_1_0;
3129
0
    dtd_url = std::move(schemalocation);
3130
0
    dtd_url += "/wms/1.1.0/capabilities_1_1_0.dtd";
3131
0
  } else if (nVersion < OWS_1_3_0) {
3132
0
    nVersion = OWS_1_1_1;
3133
0
    dtd_url = std::move(schemalocation);
3134
    /* this exception was added to accomadote the OGC test suite (Bug 1576)*/
3135
0
    if (strcasecmp(dtd_url.c_str(), OWS_DEFAULT_SCHEMAS_LOCATION) == 0)
3136
0
      dtd_url += "/wms/1.1.1/WMS_MS_Capabilities.dtd";
3137
0
    else
3138
0
      dtd_url += "/wms/1.1.1/capabilities_1_1_1.dtd";
3139
0
  } else
3140
0
    nVersion = OWS_1_3_0;
3141
3142
  /* This function owns validated_language, so remember to free it later*/
3143
0
  std::string validated_language;
3144
0
  {
3145
0
    char *pszValidated_language =
3146
0
        msOWSGetLanguageFromList(map, "MO", requested_language);
3147
0
    if (pszValidated_language) {
3148
0
      validated_language = pszValidated_language;
3149
0
      msMapSetLanguageSpecificConnection(map, pszValidated_language);
3150
0
    }
3151
0
    msFree(pszValidated_language);
3152
0
  }
3153
3154
  /* We need this server's onlineresource. */
3155
  /* Default to use the value of the "onlineresource" metadata, and if not */
3156
  /* set then build it: "http://$(SERVER_NAME):$(SERVER_PORT)$(SCRIPT_NAME)?" */
3157
  /* the returned string should be freed once we're done with it. */
3158
0
  char *script_url_encoded = NULL;
3159
0
  {
3160
0
    char *script_url = msOWSGetOnlineResource2(map, "MO", "onlineresource", req,
3161
0
                                               validated_language.c_str());
3162
0
    if (script_url == NULL ||
3163
0
        (script_url_encoded = msEncodeHTMLEntities(script_url)) == NULL) {
3164
0
      free(script_url);
3165
0
      return msWMSException(map, nVersion, NULL, wms_exception_format);
3166
0
    }
3167
0
    free(script_url);
3168
0
  }
3169
3170
0
  if (nVersion <= OWS_1_0_7 ||
3171
0
      nVersion >= OWS_1_3_0) /* 1.0.0 to 1.0.7 and >=1.3.0*/
3172
0
    msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
3173
0
  else /* 1.1.0 and later */
3174
0
    msIO_setHeader("Content-Type",
3175
0
                   "application/vnd.ogc.wms_xml; charset=UTF-8");
3176
0
  msIO_sendHeaders();
3177
3178
0
  msIO_printf("<?xml version='1.0' encoding=\"UTF-8\" standalone=\"no\" ?>\n");
3179
3180
  /*TODO review wms1.3.0*/
3181
0
  if (nVersion < OWS_1_3_0) {
3182
0
    msIO_printf("<!DOCTYPE WMT_MS_Capabilities SYSTEM \"%s\"\n",
3183
0
                dtd_url.c_str());
3184
0
    msIO_printf(" [\n");
3185
3186
0
    if (nVersion == OWS_1_1_1 && msOWSLookupMetadata(&(map->web.metadata), "MO",
3187
0
                                                     "inspire_capabilities")) {
3188
0
      msIO_printf(
3189
0
          "<!ELEMENT VendorSpecificCapabilities "
3190
0
          "(inspire_vs:ExtendedCapabilities)><!ELEMENT "
3191
0
          "inspire_vs:ExtendedCapabilities ((inspire_common:MetadataUrl, "
3192
0
          "inspire_common:SupportedLanguages, inspire_common:ResponseLanguage) "
3193
0
          "| (inspire_common:ResourceLocator+, inspire_common:ResourceType, "
3194
0
          "inspire_common:TemporalReference+, inspire_common:Conformity+, "
3195
0
          "inspire_common:MetadataPointOfContact+, "
3196
0
          "inspire_common:MetadataDate, inspire_common:SpatialDataServiceType, "
3197
0
          "inspire_common:MandatoryKeyword+, inspire_common:Keyword*, "
3198
0
          "inspire_common:SupportedLanguages, inspire_common:ResponseLanguage, "
3199
0
          "inspire_common:MetadataUrl?))><!ATTLIST "
3200
0
          "inspire_vs:ExtendedCapabilities xmlns:inspire_vs CDATA #FIXED "
3201
0
          "\"http://inspire.ec.europa.eu/schemas/inspire_vs/1.0\" ><!ELEMENT "
3202
0
          "inspire_common:MetadataUrl (inspire_common:URL, "
3203
0
          "inspire_common:MediaType*)><!ATTLIST inspire_common:MetadataUrl "
3204
0
          "xmlns:inspire_common CDATA #FIXED "
3205
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" xmlns:xsi CDATA "
3206
0
          "#FIXED \"http://www.w3.org/2001/XMLSchema-instance\" xsi:type CDATA "
3207
0
          "#FIXED \"inspire_common:resourceLocatorType\" ><!ELEMENT "
3208
0
          "inspire_common:URL (#PCDATA)><!ATTLIST inspire_common:URL "
3209
0
          "xmlns:inspire_common CDATA #FIXED "
3210
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\"><!ELEMENT "
3211
0
          "inspire_common:MediaType (#PCDATA)><!ATTLIST "
3212
0
          "inspire_common:MediaType xmlns:inspire_common CDATA #FIXED "
3213
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\"><!ELEMENT "
3214
0
          "inspire_common:SupportedLanguages (inspire_common:DefaultLanguage, "
3215
0
          "inspire_common:SupportedLanguage*)><!ATTLIST "
3216
0
          "inspire_common:SupportedLanguages xmlns:inspire_common CDATA #FIXED "
3217
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3218
0
          "inspire_common:DefaultLanguage (inspire_common:Language)><!ATTLIST "
3219
0
          "inspire_common:DefaultLanguage xmlns:inspire_common CDATA #FIXED "
3220
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3221
0
          "inspire_common:SupportedLanguage "
3222
0
          "(inspire_common:Language)><!ATTLIST "
3223
0
          "inspire_common:SupportedLanguage xmlns:inspire_common CDATA #FIXED "
3224
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" >"
3225
0
          "<!ELEMENT inspire_common:ResponseLanguage "
3226
0
          "(inspire_common:Language)><!ATTLIST inspire_common:ResponseLanguage "
3227
0
          "xmlns:inspire_common CDATA #FIXED "
3228
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3229
0
          "inspire_common:Language (#PCDATA)><!ATTLIST inspire_common:Language "
3230
0
          "xmlns:inspire_common CDATA #FIXED "
3231
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3232
0
          "inspire_common:ResourceLocator (inspire_common:URL, "
3233
0
          "inspire_common:MediaType*)><!ATTLIST inspire_common:ResourceLocator "
3234
0
          "xmlns:inspire_common CDATA #FIXED "
3235
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\"><!ELEMENT "
3236
0
          "inspire_common:ResourceType (#PCDATA)> <!ATTLIST "
3237
0
          "inspire_common:ResourceType xmlns:inspire_common CDATA #FIXED "
3238
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3239
0
          "inspire_common:TemporalReference (inspire_common:DateOfCreation?, "
3240
0
          "inspire_common:DateOfLastRevision?, "
3241
0
          "inspire_common:DateOfPublication*, "
3242
0
          "inspire_common:TemporalExtent*)><!ATTLIST "
3243
0
          "inspire_common:TemporalReference xmlns:inspire_common CDATA #FIXED "
3244
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3245
0
          "inspire_common:DateOfCreation (#PCDATA)> <!ATTLIST "
3246
0
          "inspire_common:DateOfCreation xmlns:inspire_common CDATA #FIXED "
3247
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\"><!ELEMENT "
3248
0
          "inspire_common:DateOfLastRevision (#PCDATA)><!ATTLIST "
3249
0
          "inspire_common:DateOfLastRevision xmlns:inspire_common CDATA #FIXED "
3250
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\"><!ELEMENT "
3251
0
          "inspire_common:DateOfPublication (#PCDATA)><!ATTLIST "
3252
0
          "inspire_common:DateOfPublication xmlns:inspire_common CDATA #FIXED "
3253
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\"><!ELEMENT "
3254
0
          "inspire_common:TemporalExtent (inspire_common:IndividualDate | "
3255
0
          "inspire_common:IntervalOfDates)><!ATTLIST "
3256
0
          "inspire_common:TemporalExtent xmlns:inspire_common CDATA #FIXED "
3257
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3258
0
          "inspire_common:IndividualDate (#PCDATA)> <!ATTLIST "
3259
0
          "inspire_common:IndividualDate xmlns:inspire_common CDATA #FIXED "
3260
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\">"
3261
0
          "<!ELEMENT inspire_common:IntervalOfDates "
3262
0
          "(inspire_common:StartingDate, inspire_common:EndDate)><!ATTLIST "
3263
0
          "inspire_common:IntervalOfDates xmlns:inspire_common CDATA #FIXED "
3264
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3265
0
          "inspire_common:StartingDate (#PCDATA)><!ATTLIST "
3266
0
          "inspire_common:StartingDate xmlns:inspire_common CDATA #FIXED "
3267
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3268
0
          "inspire_common:EndDate (#PCDATA)><!ATTLIST inspire_common:EndDate "
3269
0
          "xmlns:inspire_common CDATA #FIXED "
3270
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3271
0
          "inspire_common:Conformity (inspire_common:Specification, "
3272
0
          "inspire_common:Degree)><!ATTLIST inspire_common:Conformity "
3273
0
          "xmlns:inspire_common CDATA #FIXED "
3274
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3275
0
          "inspire_common:Specification (inspire_common:Title, "
3276
0
          "(inspire_common:DateOfPublication | inspire_common:DateOfCreation | "
3277
0
          "inspire_common:DateOfLastRevision), inspire_common:URI*, "
3278
0
          "inspire_common:ResourceLocator*)><!ATTLIST "
3279
0
          "inspire_common:Specification xmlns:inspire_common CDATA #FIXED "
3280
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3281
0
          "inspire_common:Title (#PCDATA)><!ATTLIST inspire_common:Title "
3282
0
          "xmlns:inspire_common CDATA #FIXED "
3283
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3284
0
          "inspire_common:URI (#PCDATA)><!ATTLIST inspire_common:URI "
3285
0
          "xmlns:inspire_common CDATA #FIXED "
3286
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3287
0
          "inspire_common:Degree (#PCDATA)><!ATTLIST inspire_common:Degree "
3288
0
          "xmlns:inspire_common CDATA #FIXED "
3289
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3290
0
          "inspire_common:MetadataPointOfContact "
3291
0
          "(inspire_common:OrganisationName, "
3292
0
          "inspire_common:EmailAddress)><!ATTLIST "
3293
0
          "inspire_common:MetadataPointOfContact xmlns:inspire_common CDATA "
3294
0
          "#FIXED \"http://inspire.ec.europa.eu/schemas/common/1.0\" >"
3295
0
          "<!ELEMENT inspire_common:OrganisationName (#PCDATA)><!ATTLIST "
3296
0
          "inspire_common:OrganisationName  xmlns:inspire_common CDATA #FIXED "
3297
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3298
0
          "inspire_common:EmailAddress (#PCDATA)><!ATTLIST "
3299
0
          "inspire_common:EmailAddress xmlns:inspire_common CDATA #FIXED "
3300
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3301
0
          "inspire_common:MetadataDate (#PCDATA)><!ATTLIST "
3302
0
          "inspire_common:MetadataDate xmlns:inspire_common CDATA #FIXED "
3303
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3304
0
          "inspire_common:SpatialDataServiceType (#PCDATA)><!ATTLIST "
3305
0
          "inspire_common:SpatialDataServiceType xmlns:inspire_common CDATA "
3306
0
          "#FIXED \"http://inspire.ec.europa.eu/schemas/common/1.0\" "
3307
0
          "><!ELEMENT inspire_common:MandatoryKeyword "
3308
0
          "(inspire_common:KeywordValue)><!ATTLIST "
3309
0
          "inspire_common:MandatoryKeyword xmlns:inspire_common CDATA #FIXED "
3310
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3311
0
          "inspire_common:KeywordValue (#PCDATA)><!ATTLIST "
3312
0
          "inspire_common:KeywordValue xmlns:inspire_common CDATA #FIXED "
3313
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" >"
3314
0
          "<!ELEMENT inspire_common:Keyword "
3315
0
          "(inspire_common:OriginatingControlledVocabulary?, "
3316
0
          "inspire_common:KeywordValue)><!ATTLIST inspire_common:Keyword "
3317
0
          "xmlns:inspire_common CDATA #FIXED "
3318
0
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" xmlns:xsi CDATA "
3319
0
          "#FIXED \"http://www.w3.org/2001/XMLSchemainstance\" xsi:type "
3320
0
          "(inspire_common:inspireTheme_bul | inspire_common:inspireTheme_cze "
3321
0
          "| inspire_common:inspireTheme_dan | inspire_common:inspireTheme_dut "
3322
0
          "| inspire_common:inspireTheme_eng | inspire_common:inspireTheme_est "
3323
0
          "| inspire_common:inspireTheme_fin | inspire_common:inspireTheme_fre "
3324
0
          "| inspire_common:inspireTheme_ger | inspire_common:inspireTheme_gre "
3325
0
          "| inspire_common:inspireTheme_hun | inspire_common:inspireTheme_gle "
3326
0
          "| inspire_common:inspireTheme_ita | inspire_common:inspireTheme_lav "
3327
0
          "| inspire_common:inspireTheme_lit | inspire_common:inspireTheme_mlt "
3328
0
          "| inspire_common:inspireTheme_pol | inspire_common:inspireTheme_por "
3329
0
          "| inspire_common:inspireTheme_rum | inspire_common:inspireTheme_slo "
3330
0
          "| inspire_common:inspireTheme_slv | inspire_common:inspireTheme_spa "
3331
0
          "| inspire_common:inspireTheme_swe) #IMPLIED ><!ELEMENT "
3332
0
          "inspire_common:OriginatingControlledVocabulary "
3333
0
          "(inspire_common:Title, (inspire_common:DateOfPublication | "
3334
0
          "inspire_common:DateOfCreation | inspire_common:DateOfLastRevision), "
3335
0
          "inspire_common:URI*, inspire_common:ResourceLocator*)><!ATTLIST "
3336
0
          "inspire_common:OriginatingControlledVocabulary xmlns:inspire_common "
3337
0
          "CDATA #FIXED \"http://inspire.ec.europa.eu/schemas/common/1.0\">\n");
3338
0
    } else {
3339
      /* some mapserver specific declarations will go here */
3340
0
      msIO_printf(" <!ELEMENT VendorSpecificCapabilities EMPTY>\n");
3341
0
    }
3342
3343
0
    msIO_printf(" ]>  <!-- end of DOCTYPE declaration -->\n\n");
3344
0
  }
3345
3346
0
  char szVersionBuf[OWS_VERSION_MAXLEN];
3347
0
  const char *pszVersion = msOWSGetVersionString(nVersion, szVersionBuf);
3348
0
  if (nVersion >= OWS_1_3_0)
3349
0
    msIO_printf("<WMS_Capabilities version=\"%s\"", pszVersion);
3350
0
  else
3351
0
    msIO_printf("<WMT_MS_Capabilities version=\"%s\"", pszVersion);
3352
0
  if (updatesequence)
3353
0
    msIO_printf(" updateSequence=\"%s\"", updatesequence);
3354
3355
0
  if (nVersion == OWS_1_3_0) {
3356
0
    msIO_printf("  xmlns=\"http://www.opengis.net/wms\""
3357
0
                "   xmlns:sld=\"http://www.opengis.net/sld\""
3358
0
                "   xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
3359
0
                "   xmlns:ms=\"http://mapserver.gis.umn.edu/mapserver\"");
3360
3361
0
    if (msOWSLookupMetadata(&(map->web.metadata), "MO",
3362
0
                            "inspire_capabilities")) {
3363
0
      msIO_printf("   xmlns:" MS_INSPIRE_COMMON_NAMESPACE_PREFIX
3364
0
                  "=\"" MS_INSPIRE_COMMON_NAMESPACE_URI "\""
3365
0
                  "   xmlns:" MS_INSPIRE_VS_NAMESPACE_PREFIX
3366
0
                  "=\"" MS_INSPIRE_VS_NAMESPACE_URI "\"");
3367
0
    }
3368
3369
0
    msIO_printf(
3370
0
        "   xsi:schemaLocation=\"http://www.opengis.net/wms "
3371
0
        "%s/wms/%s/capabilities_1_3_0.xsd "
3372
0
        " http://www.opengis.net/sld %s/sld/1.1.0/sld_capabilities.xsd ",
3373
0
        msOWSGetSchemasLocation(map), pszVersion, msOWSGetSchemasLocation(map));
3374
3375
0
    if (msOWSLookupMetadata(&(map->web.metadata), "MO",
3376
0
                            "inspire_capabilities")) {
3377
0
      char *inspireschemalocation =
3378
0
          msEncodeHTMLEntities(msOWSGetInspireSchemasLocation(map));
3379
0
      msIO_printf(" " MS_INSPIRE_VS_NAMESPACE_URI " "
3380
0
                  " %s%s",
3381
0
                  inspireschemalocation, MS_INSPIRE_VS_SCHEMA_LOCATION);
3382
0
      free(inspireschemalocation);
3383
0
    }
3384
3385
0
    msIO_printf(
3386
0
        " http://mapserver.gis.umn.edu/mapserver "
3387
0
        "%sservice=WMS&amp;version=1.3.0&amp;request=GetSchemaExtension\"",
3388
0
        script_url_encoded);
3389
0
  }
3390
3391
0
  msIO_printf(">\n");
3392
3393
  /* WMS definition */
3394
0
  msIO_printf("<Service>\n");
3395
3396
  /* Service name is defined by the spec and changed at v1.0.0 */
3397
0
  if (nVersion <= OWS_1_0_7)
3398
0
    msIO_printf("  <Name>GetMap</Name>\n"); /* v 1.0.0 to 1.0.7 */
3399
0
  else if (nVersion > OWS_1_0_7 && nVersion < OWS_1_3_0)
3400
0
    msIO_printf("  <Name>OGC:WMS</Name>\n"); /* v 1.1.0 to 1.1.1*/
3401
0
  else
3402
0
    msIO_printf("  <Name>WMS</Name>\n"); /* v 1.3.0+ */
3403
3404
  /* the majority of this section is dependent on appropriately named metadata
3405
   * in the WEB object */
3406
0
  msOWSPrintEncodeMetadata2(stdout, &(map->web.metadata), "MO", "title",
3407
0
                            OWS_WARN, "  <Title>%s</Title>\n", map->name,
3408
0
                            validated_language.c_str());
3409
0
  msOWSPrintEncodeMetadata2(stdout, &(map->web.metadata), "MO", "abstract",
3410
0
                            OWS_NOERR, "  <Abstract>%s</Abstract>\n", NULL,
3411
0
                            validated_language.c_str());
3412
3413
0
  msWMSPrintKeywordlist(stdout, "  ", "keywordlist", &(map->web.metadata), "MO",
3414
0
                        nVersion);
3415
3416
  /* Service/onlineresource */
3417
  /* Defaults to same as request onlineresource if wms_service_onlineresource */
3418
  /* is not set. */
3419
0
  if (nVersion == OWS_1_0_0)
3420
0
    msOWSPrintEncodeMetadata(
3421
0
        stdout, &(map->web.metadata), "MO", "service_onlineresource", OWS_NOERR,
3422
0
        "  <OnlineResource>%s</OnlineResource>\n", script_url_encoded);
3423
0
  else
3424
0
    msOWSPrintEncodeMetadata(
3425
0
        stdout, &(map->web.metadata), "MO", "service_onlineresource", OWS_NOERR,
3426
0
        "  <OnlineResource xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
3427
0
        "xlink:href=\"%s\"/>\n",
3428
0
        script_url_encoded);
3429
3430
  /* contact information is a required element in 1.0.7 but the */
3431
  /* sub-elements such as ContactPersonPrimary, etc. are not! */
3432
  /* In 1.1.0, ContactInformation becomes optional. */
3433
0
  msOWSPrintContactInfo(stdout, "  ", nVersion, &(map->web.metadata), "MO");
3434
3435
0
  msOWSPrintEncodeMetadata(stdout, &(map->web.metadata), "MO", "fees",
3436
0
                           OWS_NOERR, "  <Fees>%s</Fees>\n", NULL);
3437
3438
0
  msOWSPrintEncodeMetadata(
3439
0
      stdout, &(map->web.metadata), "MO", "accessconstraints", OWS_NOERR,
3440
0
      "  <AccessConstraints>%s</AccessConstraints>\n", NULL);
3441
3442
0
  if (nVersion >= OWS_1_3_0) {
3443
0
    const char *layerlimit =
3444
0
        msOWSLookupMetadata(&(map->web.metadata), "MO", "layerlimit");
3445
0
    if (layerlimit) {
3446
0
      msIO_printf("  <LayerLimit>%s</LayerLimit>\n", layerlimit);
3447
0
    }
3448
0
    msIO_printf("  <MaxWidth>%d</MaxWidth>\n", map->maxsize);
3449
0
    msIO_printf("  <MaxHeight>%d</MaxHeight>\n", map->maxsize);
3450
0
  }
3451
3452
0
  msIO_printf("</Service>\n\n");
3453
3454
  /* WMS capabilities definitions */
3455
0
  msIO_printf("<Capability>\n");
3456
0
  msIO_printf("  <Request>\n");
3457
3458
0
  if (nVersion <= OWS_1_0_7) {
3459
    /* WMS 1.0.0 to 1.0.7 - We don't try to use outputformats list here for now
3460
     */
3461
0
    if (msOWSRequestIsEnabled(map, NULL, "M", "GetMap", MS_FALSE))
3462
0
      msWMSPrintRequestCap(nVersion, "Map", script_url_encoded,
3463
0
                           ""
3464
3465
0
#if defined USE_PNG
3466
0
                           "<PNG />"
3467
0
#endif
3468
0
#if defined USE_JPEG
3469
0
                           "<JPEG />"
3470
0
#endif
3471
0
                           "<SVG />",
3472
0
                           NULL);
3473
0
    if (msOWSRequestIsEnabled(map, NULL, "M", "GetCapabilities", MS_TRUE))
3474
0
      msWMSPrintRequestCap(nVersion, "Capabilities", script_url_encoded,
3475
0
                           "<WMS_XML />", NULL);
3476
0
    if (msOWSRequestIsEnabled(map, NULL, "M", "GetFeatureInfo", MS_FALSE))
3477
0
      msWMSPrintRequestCap(nVersion, "FeatureInfo", script_url_encoded,
3478
0
                           "<MIME /><GML.1 />", NULL);
3479
0
  } else {
3480
0
    const char *mime_list[20];
3481
0
    int mime_count = 0;
3482
0
    int max_mime = 20;
3483
    /* WMS 1.1.0 and later */
3484
    /* Note changes to the request names, their ordering, and to the formats */
3485
3486
0
    if (msOWSRequestIsEnabled(map, NULL, "M", "GetCapabilities", MS_TRUE)) {
3487
0
      if (nVersion >= OWS_1_3_0)
3488
0
        msWMSPrintRequestCap(nVersion, "GetCapabilities", script_url_encoded,
3489
0
                             "text/xml", NULL);
3490
0
      else
3491
0
        msWMSPrintRequestCap(nVersion, "GetCapabilities", script_url_encoded,
3492
0
                             "application/vnd.ogc.wms_xml", NULL);
3493
0
    }
3494
3495
0
    msGetOutputFormatMimeListWMS(map, mime_list,
3496
0
                                 sizeof(mime_list) / sizeof(char *));
3497
0
    if (msOWSRequestIsEnabled(map, NULL, "M", "GetMap", MS_FALSE))
3498
0
      msWMSPrintRequestCap(
3499
0
          nVersion, "GetMap", script_url_encoded, mime_list[0], mime_list[1],
3500
0
          mime_list[2], mime_list[3], mime_list[4], mime_list[5], mime_list[6],
3501
0
          mime_list[7], mime_list[8], mime_list[9], mime_list[10],
3502
0
          mime_list[11], mime_list[12], mime_list[13], mime_list[14],
3503
0
          mime_list[15], mime_list[16], mime_list[17], mime_list[18],
3504
0
          mime_list[19], NULL);
3505
3506
0
    const char *format_list = msOWSLookupMetadata(&(map->web.metadata), "M",
3507
0
                                                  "getfeatureinfo_formatlist");
3508
    /*feature_info_mime_type deprecated for MapServer 6.0*/
3509
0
    if (!format_list)
3510
0
      format_list = msOWSLookupMetadata(&(map->web.metadata), "MO",
3511
0
                                        "feature_info_mime_type");
3512
3513
0
    if (format_list && strlen(format_list) > 0) {
3514
0
      auto tokens = msStringSplit(format_list, ',');
3515
0
      for (auto &token : tokens) {
3516
0
        msStringTrim(token);
3517
        /*text plain and gml do not need to be a format and accepted by
3518
         * default*/
3519
        /*can not really validate since the old way of using template
3520
          with wei->header, layer->template ... should be kept*/
3521
0
        if (!token.empty() && mime_count < max_mime)
3522
0
          mime_list[mime_count++] = token.c_str();
3523
0
      }
3524
      /*add text/plain and gml */
3525
0
      if (strcasestr(format_list, "GML") == 0 &&
3526
0
          strcasestr(format_list, "application/vnd.ogc.gml") == 0) {
3527
0
        if (mime_count < max_mime)
3528
0
          mime_list[mime_count++] = "application/vnd.ogc.gml";
3529
0
      }
3530
0
      if (strcasestr(format_list, "text/plain") == 0 &&
3531
0
          strcasestr(format_list, "MIME") == 0) {
3532
0
        if (mime_count < max_mime)
3533
0
          mime_list[mime_count++] = "text/plain";
3534
0
        else /*force always this format*/
3535
0
          mime_list[max_mime - 1] = "text/plain";
3536
0
      }
3537
3538
0
      if (msOWSRequestIsEnabled(map, NULL, "M", "GetFeatureInfo", MS_FALSE)) {
3539
0
        if (mime_count > 0) {
3540
0
          if (mime_count < max_mime)
3541
0
            mime_list[mime_count] = NULL;
3542
0
          msWMSPrintRequestCap(
3543
0
              nVersion, "GetFeatureInfo", script_url_encoded, mime_list[0],
3544
0
              mime_list[1], mime_list[2], mime_list[3], mime_list[4],
3545
0
              mime_list[5], mime_list[6], mime_list[7], mime_list[8],
3546
0
              mime_list[9], mime_list[10], mime_list[11], mime_list[12],
3547
0
              mime_list[13], mime_list[14], mime_list[15], mime_list[16],
3548
0
              mime_list[17], mime_list[18], mime_list[19], NULL);
3549
0
        }
3550
        /*if all formats given are invalid go to default*/
3551
0
        else
3552
0
          msWMSPrintRequestCap(nVersion, "GetFeatureInfo", script_url_encoded,
3553
0
                               "text/plain", "application/vnd.ogc.gml", NULL);
3554
0
      }
3555
0
    } else {
3556
0
      if (msOWSRequestIsEnabled(map, NULL, "M", "GetFeatureInfo", MS_FALSE))
3557
0
        msWMSPrintRequestCap(nVersion, "GetFeatureInfo", script_url_encoded,
3558
0
                             "text/plain", "application/vnd.ogc.gml", NULL);
3559
0
    }
3560
3561
0
    if (strcasecmp(sldenabled, "true") == 0) {
3562
0
      if (msOWSRequestIsEnabled(map, NULL, "M", "DescribeLayer", MS_FALSE)) {
3563
0
        if (nVersion == OWS_1_3_0)
3564
0
          msWMSPrintRequestCap(nVersion, "sld:DescribeLayer",
3565
0
                               script_url_encoded, "text/xml", NULL);
3566
0
        else
3567
0
          msWMSPrintRequestCap(nVersion, "DescribeLayer", script_url_encoded,
3568
0
                               "text/xml", NULL);
3569
0
      }
3570
3571
0
      msGetOutputFormatMimeListImg(map, mime_list,
3572
0
                                   sizeof(mime_list) / sizeof(char *));
3573
3574
0
      if (nVersion >= OWS_1_1_1) {
3575
0
        const auto isGetLegendGraphicEnabled =
3576
0
            msOWSRequestIsEnabled(map, NULL, "M", "GetLegendGraphic", MS_FALSE);
3577
0
        if (nVersion == OWS_1_3_0) {
3578
0
          if (isGetLegendGraphicEnabled)
3579
0
            msWMSPrintRequestCap(
3580
0
                nVersion, "sld:GetLegendGraphic", script_url_encoded,
3581
0
                mime_list[0], mime_list[1], mime_list[2], mime_list[3],
3582
0
                mime_list[4], mime_list[5], mime_list[6], mime_list[7],
3583
0
                mime_list[8], mime_list[9], mime_list[10], mime_list[11],
3584
0
                mime_list[12], mime_list[13], mime_list[14], mime_list[15],
3585
0
                mime_list[16], mime_list[17], mime_list[18], mime_list[19],
3586
0
                NULL);
3587
0
          if (msOWSRequestIsEnabled(map, NULL, "M", "GetStyles", MS_FALSE))
3588
0
            msWMSPrintRequestCap(nVersion, "ms:GetStyles", script_url_encoded,
3589
0
                                 "text/xml", NULL);
3590
0
        } else {
3591
0
          if (isGetLegendGraphicEnabled)
3592
0
            msWMSPrintRequestCap(
3593
0
                nVersion, "GetLegendGraphic", script_url_encoded, mime_list[0],
3594
0
                mime_list[1], mime_list[2], mime_list[3], mime_list[4],
3595
0
                mime_list[5], mime_list[6], mime_list[7], mime_list[8],
3596
0
                mime_list[9], mime_list[10], mime_list[11], mime_list[12],
3597
0
                mime_list[13], mime_list[14], mime_list[15], mime_list[16],
3598
0
                mime_list[17], mime_list[18], mime_list[19], NULL);
3599
0
          if (msOWSRequestIsEnabled(map, NULL, "M", "GetStyles", MS_FALSE))
3600
0
            msWMSPrintRequestCap(nVersion, "GetStyles", script_url_encoded,
3601
0
                                 "text/xml", NULL);
3602
0
        }
3603
0
      }
3604
0
    }
3605
0
  }
3606
3607
0
  msIO_printf("  </Request>\n");
3608
3609
0
  msIO_printf("  <Exception>\n");
3610
0
  if (nVersion <= OWS_1_0_7)
3611
0
    msIO_printf("    <Format><BLANK /><INIMAGE /><WMS_XML /></Format>\n");
3612
0
  else if (nVersion <= OWS_1_1_1) {
3613
0
    msIO_printf("    <Format>application/vnd.ogc.se_xml</Format>\n");
3614
0
    msIO_printf("    <Format>application/vnd.ogc.se_inimage</Format>\n");
3615
0
    msIO_printf("    <Format>application/vnd.ogc.se_blank</Format>\n");
3616
0
  } else { /*>=1.3.0*/
3617
0
    msIO_printf("    <Format>XML</Format>\n");
3618
0
    msIO_printf("    <Format>INIMAGE</Format>\n");
3619
0
    msIO_printf("    <Format>BLANK</Format>\n");
3620
0
  }
3621
0
  msIO_printf("  </Exception>\n");
3622
3623
0
  if (nVersion != OWS_1_3_0) {
3624
    /* INSPIRE extended capabilities for WMS 1.1.1 */
3625
0
    if (nVersion == OWS_1_1_1 && msOWSLookupMetadata(&(map->web.metadata), "MO",
3626
0
                                                     "inspire_capabilities")) {
3627
0
      msIO_printf("  <VendorSpecificCapabilities>\n");
3628
0
      msOWSPrintInspireCommonExtendedCapabilities(
3629
0
          stdout, map, "MO", OWS_WARN, "inspire_vs:ExtendedCapabilities", NULL,
3630
0
          validated_language.c_str(), OWS_WMS);
3631
0
      msIO_printf("  </VendorSpecificCapabilities>\n");
3632
0
    } else {
3633
0
      msIO_printf("  <VendorSpecificCapabilities />\n"); /* nothing yet */
3634
0
    }
3635
0
  }
3636
3637
  /* SLD support */
3638
0
  if (strcasecmp(sldenabled, "true") == 0) {
3639
0
    if (nVersion >= OWS_1_0_7) {
3640
0
      if (nVersion >= OWS_1_3_0)
3641
0
        msIO_printf("  <sld:UserDefinedSymbolization SupportSLD=\"1\" "
3642
0
                    "UserLayer=\"0\" UserStyle=\"1\" RemoteWFS=\"0\" "
3643
0
                    "InlineFeature=\"0\" RemoteWCS=\"0\"/>\n");
3644
0
      else
3645
0
        msIO_printf("  <UserDefinedSymbolization SupportSLD=\"1\" "
3646
0
                    "UserLayer=\"0\" UserStyle=\"1\" RemoteWFS=\"0\"/>\n");
3647
0
    }
3648
0
  }
3649
3650
  /* INSPIRE extended capabilities for WMS 1.3.0 */
3651
0
  if (nVersion >= OWS_1_3_0 &&
3652
0
      msOWSLookupMetadata(&(map->web.metadata), "MO", "inspire_capabilities")) {
3653
0
    msOWSPrintInspireCommonExtendedCapabilities(
3654
0
        stdout, map, "MO", OWS_WARN, "inspire_vs:ExtendedCapabilities", NULL,
3655
0
        validated_language.c_str(), OWS_WMS);
3656
0
  }
3657
3658
  /* Top-level layer with map extents and SRS, encloses all map layers */
3659
  /* Output only if at least one layers is enabled. */
3660
0
  if (ows_request->numlayers == 0) {
3661
0
    msIO_fprintf(stdout, "  <!-- WARNING: No WMS layers are enabled. Check "
3662
0
                         "wms/ows_enable_request settings. -->\n");
3663
0
  } else {
3664
0
    int root_is_queryable = MS_FALSE;
3665
3666
0
    const char *rootlayer_name =
3667
0
        msOWSLookupMetadata(&(map->web.metadata), "MO", "rootlayer_name");
3668
3669
    /* Root layer is queryable if it has a valid name and at list one layer */
3670
    /* is queryable */
3671
0
    if (!rootlayer_name || strlen(rootlayer_name) > 0) {
3672
0
      int j;
3673
0
      for (j = 0; j < map->numlayers; j++) {
3674
0
        layerObj *layer = GET_LAYER(map, j);
3675
0
        if (msIsLayerQueryable(layer) &&
3676
0
            msIntegerInArray(layer->index, ows_request->enabled_layers,
3677
0
                             ows_request->numlayers)) {
3678
0
          root_is_queryable = MS_TRUE;
3679
0
          break;
3680
0
        }
3681
0
      }
3682
0
    }
3683
0
    msIO_printf("  <Layer%s>\n", root_is_queryable ? " queryable=\"1\"" : "");
3684
3685
    /* Layer Name is optional but title is mandatory. */
3686
0
    if (map->name && strlen(map->name) > 0 &&
3687
0
        (msIsXMLTagValid(map->name) == MS_FALSE || isdigit(map->name[0])))
3688
0
      msIO_fprintf(stdout,
3689
0
                   "<!-- WARNING: The layer name '%s' might contain spaces or "
3690
0
                   "invalid characters or may start with a number. This could "
3691
0
                   "lead to potential problems. -->\n",
3692
0
                   map->name);
3693
3694
0
    if (rootlayer_name) {
3695
0
      if (strlen(rootlayer_name) > 0) {
3696
0
        msOWSPrintEncodeMetadata(stdout, &(map->web.metadata), "MO",
3697
0
                                 "rootlayer_name", OWS_NOERR,
3698
0
                                 "    <Name>%s</Name>\n", NULL);
3699
0
      }
3700
0
    } else {
3701
0
      msOWSPrintEncodeParam(stdout, "MAP.NAME", map->name, OWS_NOERR,
3702
0
                            "    <Name>%s</Name>\n", NULL);
3703
0
    }
3704
3705
0
    if (msOWSLookupMetadataWithLanguage(&(map->web.metadata), "MO",
3706
0
                                        "rootlayer_title",
3707
0
                                        validated_language.c_str()))
3708
0
      msOWSPrintEncodeMetadata2(
3709
0
          stdout, &(map->web.metadata), "MO", "rootlayer_title", OWS_WARN,
3710
0
          "    <Title>%s</Title>\n", map->name, validated_language.c_str());
3711
3712
0
    else
3713
0
      msOWSPrintEncodeMetadata2(stdout, &(map->web.metadata), "MO", "title",
3714
0
                                OWS_WARN, "    <Title>%s</Title>\n", map->name,
3715
0
                                validated_language.c_str());
3716
3717
0
    if (msOWSLookupMetadataWithLanguage(&(map->web.metadata), "MO",
3718
0
                                        "rootlayer_abstract",
3719
0
                                        validated_language.c_str()))
3720
0
      msOWSPrintEncodeMetadata2(stdout, &(map->web.metadata), "MO",
3721
0
                                "rootlayer_abstract", OWS_NOERR,
3722
0
                                "    <Abstract>%s</Abstract>\n", map->name,
3723
0
                                validated_language.c_str());
3724
0
    else
3725
0
      msOWSPrintEncodeMetadata2(stdout, &(map->web.metadata), "MO", "abstract",
3726
0
                                OWS_NOERR, "    <Abstract>%s</Abstract>\n",
3727
0
                                map->name, validated_language.c_str());
3728
3729
0
    const char *pszTmp;
3730
0
    if (msOWSLookupMetadata(&(map->web.metadata), "MO",
3731
0
                            "rootlayer_keywordlist") ||
3732
0
        msOWSLookupMetadata(&(map->web.metadata), "MO",
3733
0
                            "rootlayer_keywordlist_vocabulary"))
3734
0
      pszTmp = "rootlayer_keywordlist";
3735
0
    else
3736
0
      pszTmp = "keywordlist";
3737
0
    msWMSPrintKeywordlist(stdout, "    ", pszTmp, &(map->web.metadata), "MO",
3738
0
                          nVersion);
3739
3740
    /* According to normative comments in the 1.0.7 DTD, the root layer's SRS
3741
     * tag */
3742
    /* is REQUIRED.  It also suggests that we use an empty SRS element if there
3743
     */
3744
    /* is no common SRS. */
3745
0
    char *pszMapEPSG;
3746
0
    msOWSGetEPSGProj(&(map->projection), &(map->web.metadata), "MO", MS_FALSE,
3747
0
                     &pszMapEPSG);
3748
0
    if (nVersion > OWS_1_1_0) {
3749
      /* starting 1.1.1 SRS are given in individual tags */
3750
0
      if (nVersion >= OWS_1_3_0) {
3751
0
        msOWSPrintEncodeParamList(stdout,
3752
0
                                  "(at least one of) "
3753
0
                                  "MAP.PROJECTION, LAYER.PROJECTION "
3754
0
                                  "or wms_srs metadata",
3755
0
                                  pszMapEPSG, OWS_WARN, ' ', NULL, NULL,
3756
0
                                  "    <CRS>%s</CRS>\n", "");
3757
0
      } else {
3758
0
        msOWSPrintEncodeParamList(stdout,
3759
0
                                  "(at least one of) "
3760
0
                                  "MAP.PROJECTION, LAYER.PROJECTION "
3761
0
                                  "or wms_srs metadata",
3762
0
                                  pszMapEPSG, OWS_WARN, ' ', NULL, NULL,
3763
0
                                  "    <SRS>%s</SRS>\n", "");
3764
0
      }
3765
0
    } else {
3766
      /* If map has no proj then every layer MUST have one or produce a warning
3767
       */
3768
0
      msOWSPrintEncodeParam(stdout, "MAP.PROJECTION (or wms_srs metadata)",
3769
0
                            pszMapEPSG, OWS_WARN, "    <SRS>%s</SRS>\n", "");
3770
0
    }
3771
0
    msFree(pszMapEPSG);
3772
3773
0
    if (nVersion >= OWS_1_3_0)
3774
0
      msOWSPrintEX_GeographicBoundingBox(stdout, "    ", &(map->extent),
3775
0
                                         &(map->projection));
3776
0
    else
3777
0
      msOWSPrintLatLonBoundingBox(stdout, "    ", &(map->extent),
3778
0
                                  &(map->projection), NULL, OWS_WMS);
3779
3780
0
    msOWSPrintBoundingBox(stdout, "    ", &(map->extent), &(map->projection),
3781
0
                          NULL, &(map->web.metadata), "MO", nVersion);
3782
3783
0
    if (nVersion >= OWS_1_0_7) {
3784
0
      msWMSPrintAttribution(stdout, "    ", &(map->web.metadata), "MO");
3785
0
    }
3786
3787
    /* AuthorityURL support and Identifier support, only available >= WMS 1.1.0
3788
     */
3789
0
    if (nVersion >= OWS_1_1_0) {
3790
0
      msWMSPrintAuthorityURL(stdout, "    ", &(map->web.metadata), "MO");
3791
0
      msWMSPrintIdentifier(stdout, "    ", &(map->web.metadata), "MO");
3792
0
    }
3793
3794
    /* MetadataURL */
3795
0
    if (nVersion >= OWS_1_1_0)
3796
0
      msOWSPrintURLType(stdout, &(map->web.metadata), "MO", "metadataurl",
3797
0
                        OWS_NOERR, NULL, "MetadataURL", " type=\"%s\"", NULL,
3798
0
                        NULL, ">\n      <Format>%s</Format",
3799
0
                        "\n      <OnlineResource xmlns:xlink=\""
3800
0
                        "http://www.w3.org/1999/xlink\" "
3801
0
                        "xlink:type=\"simple\" xlink:href=\"%s\"/>\n    ",
3802
0
                        MS_TRUE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
3803
0
                        NULL, NULL, NULL, NULL, "    ");
3804
3805
    /* DataURL */
3806
0
    if (nVersion < OWS_1_1_0)
3807
0
      msOWSPrintEncodeMetadata(stdout, &(map->web.metadata), "MO",
3808
0
                               "dataurl_href", OWS_NOERR,
3809
0
                               "    <DataURL>%s</DataURL>\n", NULL);
3810
0
    else
3811
0
      msOWSPrintURLType(stdout, &(map->web.metadata), "MO", "dataurl",
3812
0
                        OWS_NOERR, NULL, "DataURL", NULL, NULL, NULL,
3813
0
                        ">\n      <Format>%s</Format",
3814
0
                        "\n      <OnlineResource xmlns:xlink=\""
3815
0
                        "http://www.w3.org/1999/xlink\" "
3816
0
                        "xlink:type=\"simple\" xlink:href=\"%s\"/>\n    ",
3817
0
                        MS_FALSE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
3818
0
                        NULL, NULL, NULL, NULL, "    ");
3819
3820
0
    if (map->name && strlen(map->name) > 0 &&
3821
0
        msOWSLookupMetadata(&(map->web.metadata), "MO",
3822
0
                            "inspire_capabilities")) {
3823
0
      char *pszEncodedName = NULL;
3824
0
      const char *styleName = NULL;
3825
0
      char *pszEncodedStyleName = NULL;
3826
0
      const char *legendURL = NULL;
3827
3828
0
      pszEncodedName = msEncodeHTMLEntities(map->name);
3829
3830
0
      styleName = msOWSLookupMetadata(&(map->web.metadata), "MO", "style_name");
3831
0
      if (styleName == NULL)
3832
0
        styleName = "default";
3833
3834
0
      pszEncodedStyleName = msEncodeHTMLEntities(styleName);
3835
3836
0
      msIO_fprintf(stdout, "    <Style>\n");
3837
0
      msIO_fprintf(stdout, "       <Name>%s</Name>\n", pszEncodedStyleName);
3838
0
      msOWSPrintEncodeMetadata2(
3839
0
          stdout, &(map->web.metadata), "MO", "style_title", OWS_NOERR,
3840
0
          "       <Title>%s</Title>\n", styleName, validated_language.c_str());
3841
3842
0
      legendURL = msOWSLookupMetadata(&(map->web.metadata), "MO",
3843
0
                                      "style_legendurl_href");
3844
0
      if (legendURL) {
3845
0
        msOWSPrintURLType(stdout, &(map->web.metadata), "MO", "style_legendurl",
3846
0
                          OWS_NOERR, NULL, "LegendURL", NULL, " width=\"%s\"",
3847
0
                          " height=\"%s\"", ">\n          <Format>%s</Format",
3848
0
                          "\n          <OnlineResource "
3849
0
                          "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
3850
0
                          " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
3851
0
                          "       ",
3852
0
                          MS_FALSE, MS_TRUE, MS_TRUE, MS_TRUE, MS_TRUE, NULL,
3853
0
                          NULL, NULL, NULL, NULL, "       ");
3854
0
      } else {
3855
0
        std::vector<int> group_layers;
3856
0
        group_layers.reserve(map->numlayers);
3857
3858
0
        for (int i = 0; i < map->numlayers; i++)
3859
0
          if (msIntegerInArray(GET_LAYER(map, i)->index,
3860
0
                               ows_request->enabled_layers,
3861
0
                               ows_request->numlayers))
3862
0
            group_layers.push_back(i);
3863
3864
0
        if (!group_layers.empty()) {
3865
0
          int size_x = 0, size_y = 0;
3866
0
          if (msLegendCalcSize(map, 1, &size_x, &size_y, group_layers.data(),
3867
0
                               static_cast<int>(group_layers.size()), NULL,
3868
0
                               1) == MS_SUCCESS) {
3869
0
            const std::string width(std::to_string(size_x));
3870
0
            const std::string height(std::to_string(size_y));
3871
3872
0
            const char *format_list = msOWSLookupMetadata(
3873
0
                &(map->web.metadata), "M", "getlegendgraphic_formatlist");
3874
0
            char *pszMimetype = NULL;
3875
0
            if (format_list && strlen(format_list) > 0) {
3876
0
              const auto tokens = msStringSplit(format_list, ',');
3877
0
              if (!tokens.empty()) {
3878
                /*just grab the first mime type*/
3879
0
                pszMimetype = msEncodeHTMLEntities(tokens[0].c_str());
3880
0
              }
3881
0
            } else
3882
0
              pszMimetype = msEncodeHTMLEntities("image/png");
3883
3884
0
            std::string legendurl(script_url_encoded);
3885
0
            legendurl += "version=";
3886
0
            char szVersionBuf[OWS_VERSION_MAXLEN];
3887
0
            legendurl += msOWSGetVersionString(nVersion, szVersionBuf);
3888
0
            legendurl += "&amp;service=WMS&amp;request=GetLegendGraphic&amp;";
3889
0
            if (nVersion >= OWS_1_3_0) {
3890
0
              legendurl += "sld_version=1.1.0&amp;layer=";
3891
0
            } else {
3892
0
              legendurl += "layer=";
3893
0
            }
3894
0
            legendurl += pszEncodedName;
3895
0
            legendurl += "&amp;format=";
3896
0
            legendurl += pszMimetype;
3897
0
            legendurl += "&amp;STYLE=";
3898
0
            legendurl += pszEncodedStyleName;
3899
3900
0
            msOWSPrintURLType(stdout, NULL, "O", "ttt", OWS_NOERR, NULL,
3901
0
                              "LegendURL", NULL, " width=\"%s\"",
3902
0
                              " height=\"%s\"",
3903
0
                              ">\n          <Format>%s</Format",
3904
0
                              "\n          <OnlineResource "
3905
0
                              "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
3906
0
                              " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
3907
0
                              "       ",
3908
0
                              MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE,
3909
0
                              NULL, width.c_str(), height.c_str(), pszMimetype,
3910
0
                              legendurl.c_str(), "       ");
3911
0
            msFree(pszMimetype);
3912
0
          }
3913
0
        }
3914
0
      }
3915
0
      msIO_fprintf(stdout, "    </Style>\n");
3916
0
      msFree(pszEncodedName);
3917
0
      msFree(pszEncodedStyleName);
3918
0
    }
3919
3920
0
    if (nVersion < OWS_1_3_0)
3921
0
      msWMSPrintScaleHint("    ", map->web.minscaledenom,
3922
0
                          map->web.maxscaledenom, map->resolution);
3923
0
    else
3924
0
      msWMSPrintScaleDenominator("    ", map->web.minscaledenom,
3925
0
                                 map->web.maxscaledenom);
3926
3927
    /*  */
3928
    /* Dump list of layers organized by groups.  Layers with no group are listed
3929
     */
3930
    /* individually, at the same level as the groups in the layer hierarchy */
3931
    /*  */
3932
0
    if (map->numlayers) {
3933
0
      char ***nestedGroups = NULL;
3934
0
      int *numNestedGroups = NULL;
3935
0
      int *isUsedInNestedGroup = NULL;
3936
3937
      /* We'll use this array of booleans to track which layer/group have been
3938
       */
3939
      /* processed already */
3940
0
      std::vector<char> pabLayerProcessed(map->numlayers);
3941
3942
      /* Mark disabled layers as processed to prevent from being displayed in
3943
       * nested groups (#4533)*/
3944
0
      for (int i = 0; i < map->numlayers; i++) {
3945
0
        if (!msIntegerInArray(GET_LAYER(map, i)->index,
3946
0
                              ows_request->enabled_layers,
3947
0
                              ows_request->numlayers))
3948
0
          pabLayerProcessed[i] = 1;
3949
0
      }
3950
3951
0
      nestedGroups = (char ***)msSmallCalloc(map->numlayers, sizeof(char **));
3952
0
      numNestedGroups = (int *)msSmallCalloc(map->numlayers, sizeof(int));
3953
0
      isUsedInNestedGroup = (int *)msSmallCalloc(map->numlayers, sizeof(int));
3954
0
      msWMSPrepareNestedGroups(map, nVersion, nestedGroups, numNestedGroups,
3955
0
                               isUsedInNestedGroup);
3956
3957
0
      for (int i = 0; i < map->numlayers; i++) {
3958
0
        layerObj *lp = (GET_LAYER(map, i));
3959
3960
0
        if (pabLayerProcessed[i] || (lp->status == MS_DELETE))
3961
0
          continue; /* Layer is hidden or has already been handled */
3962
3963
0
        if (numNestedGroups[i] > 0) {
3964
          /* Has nested groups.  */
3965
0
          msWMSPrintNestedGroups(map, nVersion, pabLayerProcessed.data(), i, 0,
3966
0
                                 nestedGroups, numNestedGroups,
3967
0
                                 isUsedInNestedGroup, script_url_encoded,
3968
0
                                 validated_language.c_str());
3969
0
        } else if (lp->group == NULL || strlen(lp->group) == 0) {
3970
          /* Don't dump layer if it is used in wms_group_layer. */
3971
0
          if (!isUsedInNestedGroup[i]) {
3972
            /* This layer is not part of a group... dump it directly */
3973
0
            msDumpLayer(map, lp, nVersion, script_url_encoded, "",
3974
0
                        validated_language.c_str(), MS_FALSE, MS_FALSE);
3975
0
            pabLayerProcessed[i] = 1;
3976
0
          }
3977
0
        } else {
3978
0
          bool group_is_queryable = false;
3979
          /* Group is queryable as soon as its member layers is. */
3980
0
          for (int j = i; j < map->numlayers; j++) {
3981
0
            if (GET_LAYER(map, j)->group &&
3982
0
                strcmp(lp->group, GET_LAYER(map, j)->group) == 0 &&
3983
0
                msIntegerInArray(GET_LAYER(map, j)->index,
3984
0
                                 ows_request->enabled_layers,
3985
0
                                 ows_request->numlayers) &&
3986
0
                msIsLayerQueryable(GET_LAYER(map, j))) {
3987
0
              group_is_queryable = true;
3988
0
              break;
3989
0
            }
3990
0
          }
3991
          /* Beginning of a new group... enclose the group in a layer block */
3992
0
          msIO_printf("    <Layer%s>\n",
3993
0
                      group_is_queryable ? " queryable=\"1\"" : "");
3994
3995
          /* Layer Name is optional but title is mandatory. */
3996
0
          if (lp->group && strlen(lp->group) > 0 &&
3997
0
              (msIsXMLTagValid(lp->group) == MS_FALSE || isdigit(lp->group[0])))
3998
0
            msIO_fprintf(
3999
0
                stdout,
4000
0
                "<!-- WARNING: The layer name '%s' might contain spaces or "
4001
0
                "invalid characters or may start with a number. This could "
4002
0
                "lead to potential problems. -->\n",
4003
0
                lp->group);
4004
0
          msOWSPrintEncodeParam(stdout, "GROUP.NAME", lp->group, OWS_NOERR,
4005
0
                                "      <Name>%s</Name>\n", NULL);
4006
0
          msOWSPrintGroupMetadata2(stdout, map, lp->group, "MO", "GROUP_TITLE",
4007
0
                                   OWS_WARN, "      <Title>%s</Title>\n",
4008
0
                                   lp->group, validated_language.c_str());
4009
0
          msOWSPrintGroupMetadata2(stdout, map, lp->group, "MO",
4010
0
                                   "GROUP_ABSTRACT", OWS_NOERR,
4011
0
                                   "      <Abstract>%s</Abstract>\n", lp->group,
4012
0
                                   validated_language.c_str());
4013
4014
          /*build a getlegendgraphicurl*/
4015
0
          if (script_url_encoded) {
4016
0
            if (lp->group && strlen(lp->group) > 0) {
4017
0
              char *pszEncodedName = NULL;
4018
0
              const char *styleName = NULL;
4019
0
              char *pszEncodedStyleName = NULL;
4020
0
              const char *legendURL = NULL;
4021
4022
0
              pszEncodedName = msEncodeHTMLEntities(lp->group);
4023
4024
0
              styleName = msOWSLookupMetadata(&(lp->metadata), "MO",
4025
0
                                              "group_style_name");
4026
0
              if (styleName == NULL)
4027
0
                styleName = "default";
4028
4029
0
              pszEncodedStyleName = msEncodeHTMLEntities(styleName);
4030
4031
0
              msIO_fprintf(stdout, "    <Style>\n");
4032
0
              msIO_fprintf(stdout, "       <Name>%s</Name>\n",
4033
0
                           pszEncodedStyleName);
4034
0
              msOWSPrintEncodeMetadata(stdout, &(lp->metadata), "MO",
4035
0
                                       "group_style_title", OWS_NOERR,
4036
0
                                       "       <Title>%s</Title>\n", styleName);
4037
4038
0
              legendURL = msOWSLookupMetadata(&(lp->metadata), "MO",
4039
0
                                              "group_style_legendurl_href");
4040
0
              if (legendURL) {
4041
0
                msOWSPrintURLType(
4042
0
                    stdout, &(lp->metadata), "MO", "group_style_legendurl",
4043
0
                    OWS_NOERR, NULL, "LegendURL", NULL, " width=\"%s\"",
4044
0
                    " height=\"%s\"", ">\n          <Format>%s</Format",
4045
0
                    "\n          <OnlineResource "
4046
0
                    "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
4047
0
                    " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
4048
0
                    "       ",
4049
0
                    MS_FALSE, MS_TRUE, MS_TRUE, MS_TRUE, MS_TRUE, NULL, NULL,
4050
0
                    NULL, NULL, NULL, "       ");
4051
0
              } else {
4052
0
                std::vector<int> group_layers;
4053
0
                group_layers.reserve(map->numlayers);
4054
4055
0
                for (int j = i; j < map->numlayers; j++)
4056
0
                  if (!pabLayerProcessed[j] && GET_LAYER(map, j)->group &&
4057
0
                      strcmp(lp->group, GET_LAYER(map, j)->group) == 0 &&
4058
0
                      msIntegerInArray(GET_LAYER(map, j)->index,
4059
0
                                       ows_request->enabled_layers,
4060
0
                                       ows_request->numlayers))
4061
0
                    group_layers.push_back(j);
4062
4063
0
                if (!group_layers.empty()) {
4064
0
                  int size_x = 0, size_y = 0;
4065
0
                  char *pszMimetype = NULL;
4066
4067
0
                  if (msLegendCalcSize(map, 1, &size_x, &size_y,
4068
0
                                       group_layers.data(),
4069
0
                                       static_cast<int>(group_layers.size()),
4070
0
                                       NULL, 1) == MS_SUCCESS) {
4071
0
                    const std::string width(std::to_string(size_x));
4072
0
                    const std::string height(std::to_string(size_y));
4073
4074
0
                    const char *format_list =
4075
0
                        msOWSLookupMetadata(&(map->web.metadata), "M",
4076
0
                                            "getlegendgraphic_formatlist");
4077
0
                    if (format_list && strlen(format_list) > 0) {
4078
0
                      const auto tokens = msStringSplit(format_list, ',');
4079
0
                      if (!tokens.empty()) {
4080
                        /*just grab the first mime type*/
4081
0
                        pszMimetype = msEncodeHTMLEntities(tokens[0].c_str());
4082
0
                      }
4083
0
                    } else
4084
0
                      pszMimetype = msEncodeHTMLEntities("image/png");
4085
4086
0
                    std::string legendurl(script_url_encoded);
4087
0
                    legendurl += "version=";
4088
0
                    char szVersionBuf[OWS_VERSION_MAXLEN];
4089
0
                    legendurl += msOWSGetVersionString(nVersion, szVersionBuf);
4090
0
                    legendurl +=
4091
0
                        "&amp;service=WMS&amp;request=GetLegendGraphic&amp;";
4092
0
                    if (nVersion >= OWS_1_3_0) {
4093
0
                      legendurl += "sld_version=1.1.0&amp;layer=";
4094
0
                    } else {
4095
0
                      legendurl += "layer=";
4096
0
                    }
4097
0
                    legendurl += pszEncodedName;
4098
0
                    legendurl += "&amp;format=";
4099
0
                    legendurl += pszMimetype;
4100
0
                    legendurl += "&amp;STYLE=";
4101
0
                    legendurl += pszEncodedStyleName;
4102
4103
0
                    msOWSPrintURLType(
4104
0
                        stdout, NULL, "O", "ttt", OWS_NOERR, NULL, "LegendURL",
4105
0
                        NULL, " width=\"%s\"", " height=\"%s\"",
4106
0
                        ">\n          <Format>%s</Format",
4107
0
                        "\n          <OnlineResource "
4108
0
                        "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
4109
0
                        " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
4110
0
                        "       ",
4111
0
                        MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE, NULL,
4112
0
                        width.c_str(), height.c_str(), pszMimetype,
4113
0
                        legendurl.c_str(), "       ");
4114
4115
0
                    msFree(pszMimetype);
4116
0
                  }
4117
0
                }
4118
0
              }
4119
0
              msIO_fprintf(stdout, "    </Style>\n");
4120
0
              msFree(pszEncodedName);
4121
0
              msFree(pszEncodedStyleName);
4122
0
            }
4123
0
          }
4124
4125
          /* Dump all layers for this group */
4126
0
          for (int j = i; j < map->numlayers; j++) {
4127
0
            if (!pabLayerProcessed[j] && GET_LAYER(map, j)->group &&
4128
0
                strcmp(lp->group, GET_LAYER(map, j)->group) == 0 &&
4129
0
                msIntegerInArray(GET_LAYER(map, j)->index,
4130
0
                                 ows_request->enabled_layers,
4131
0
                                 ows_request->numlayers)) {
4132
0
              msDumpLayer(map, (GET_LAYER(map, j)), nVersion,
4133
0
                          script_url_encoded, "  ", validated_language.c_str(),
4134
0
                          MS_FALSE, MS_FALSE);
4135
0
              pabLayerProcessed[j] = 1;
4136
0
            }
4137
0
          }
4138
          /* Close group layer block */
4139
0
          msIO_printf("    </Layer>\n");
4140
0
        }
4141
0
      }
4142
4143
      /* free the stuff used for nested layers */
4144
0
      for (int i = 0; i < map->numlayers; i++) {
4145
0
        if (numNestedGroups[i] > 0) {
4146
0
          msFreeCharArray(nestedGroups[i], numNestedGroups[i]);
4147
0
        }
4148
0
      }
4149
0
      free(nestedGroups);
4150
0
      free(numNestedGroups);
4151
0
      free(isUsedInNestedGroup);
4152
0
    }
4153
4154
0
    msIO_printf("  </Layer>\n");
4155
0
  }
4156
4157
0
  msIO_printf("</Capability>\n");
4158
0
  if (nVersion >= OWS_1_3_0)
4159
0
    msIO_printf("</WMS_Capabilities>\n");
4160
0
  else
4161
0
    msIO_printf("</WMT_MS_Capabilities>\n");
4162
4163
0
  free(script_url_encoded);
4164
4165
0
  return (MS_SUCCESS);
4166
0
}
4167
4168
/*
4169
 * This function look for params that can be used
4170
 * by mapserv when generating template.
4171
 */
4172
int msTranslateWMS2Mapserv(const char **names, const char **values,
4173
                           int numentries, char ***translated_names,
4174
                           char ***translated_values,
4175
0
                           int *translated_numentries) {
4176
0
  int num_allocated = numentries;
4177
0
  *translated_names = (char **)msSmallMalloc(num_allocated * sizeof(char *));
4178
0
  *translated_values = (char **)msSmallMalloc(num_allocated * sizeof(char *));
4179
0
  *translated_numentries = 0;
4180
0
  for (int i = 0; i < numentries; i++) {
4181
0
    (*translated_values)[*translated_numentries] = msStrdup(values[i]);
4182
0
    (*translated_names)[*translated_numentries] = msStrdup(names[i]);
4183
0
    (*translated_numentries)++;
4184
0
    if (strcasecmp("X", names[i]) == 0) {
4185
0
      num_allocated++;
4186
0
      *translated_names = (char **)msSmallRealloc(
4187
0
          *translated_names, num_allocated * sizeof(char *));
4188
0
      *translated_values = (char **)msSmallRealloc(
4189
0
          *translated_values, num_allocated * sizeof(char *));
4190
0
      (*translated_values)[*translated_numentries] = msStrdup(values[i]);
4191
0
      (*translated_names)[*translated_numentries] = msStrdup("img.x");
4192
0
      (*translated_numentries)++;
4193
0
    } else if (strcasecmp("Y", names[i]) == 0) {
4194
0
      num_allocated++;
4195
0
      *translated_names = (char **)msSmallRealloc(
4196
0
          *translated_names, num_allocated * sizeof(char *));
4197
0
      *translated_values = (char **)msSmallRealloc(
4198
0
          *translated_values, num_allocated * sizeof(char *));
4199
0
      (*translated_values)[*translated_numentries] = msStrdup(values[i]);
4200
0
      (*translated_names)[*translated_numentries] = msStrdup("img.y");
4201
0
      (*translated_numentries)++;
4202
0
    } else if (strcasecmp("LAYERS", names[i]) == 0) {
4203
0
      char **layers;
4204
0
      int tok;
4205
0
      int j;
4206
0
      layers = msStringSplit(values[i], ',', &tok);
4207
0
      num_allocated += tok;
4208
0
      *translated_names = (char **)msSmallRealloc(
4209
0
          *translated_names, num_allocated * sizeof(char *));
4210
0
      *translated_values = (char **)msSmallRealloc(
4211
0
          *translated_values, num_allocated * sizeof(char *));
4212
0
      for (j = 0; j < tok; j++) {
4213
0
        (*translated_values)[*translated_numentries] = layers[j];
4214
0
        (*translated_names)[*translated_numentries] = msStrdup("layer");
4215
0
        (*translated_numentries)++;
4216
0
        layers[j] = NULL;
4217
0
      }
4218
0
      free(layers);
4219
0
    } else if (strcasecmp("QUERY_LAYERS", names[i]) == 0) {
4220
0
      char **layers;
4221
0
      int tok;
4222
0
      int j;
4223
0
      layers = msStringSplit(values[i], ',', &tok);
4224
0
      num_allocated += tok;
4225
0
      *translated_names = (char **)msSmallRealloc(
4226
0
          *translated_names, num_allocated * sizeof(char *));
4227
0
      *translated_values = (char **)msSmallRealloc(
4228
0
          *translated_values, num_allocated * sizeof(char *));
4229
0
      for (j = 0; j < tok; j++) {
4230
0
        (*translated_values)[*translated_numentries] = layers[j];
4231
0
        (*translated_names)[*translated_numentries] = msStrdup("qlayer");
4232
0
        (*translated_numentries)++;
4233
0
        layers[j] = NULL;
4234
0
      }
4235
0
      free(layers);
4236
0
    } else if (strcasecmp("BBOX", names[i]) == 0) {
4237
0
      char *imgext;
4238
0
      num_allocated++;
4239
0
      *translated_names = (char **)msSmallRealloc(
4240
0
          *translated_names, num_allocated * sizeof(char *));
4241
0
      *translated_values = (char **)msSmallRealloc(
4242
0
          *translated_values, num_allocated * sizeof(char *));
4243
4244
      /* Note msReplaceSubstring() works on the string itself, so we need to
4245
       * make a copy */
4246
0
      imgext = msStrdup(values[i]);
4247
0
      imgext = msReplaceSubstring(imgext, ",", " ");
4248
0
      (*translated_values)[*translated_numentries] = imgext;
4249
0
      (*translated_names)[*translated_numentries] = msStrdup("imgext");
4250
0
      (*translated_numentries)++;
4251
0
    }
4252
0
  }
4253
4254
0
  return MS_SUCCESS;
4255
0
}
4256
4257
/*
4258
** msWMSGetMap()
4259
*/
4260
static int msWMSGetMap(mapObj *map, int nVersion, char **names, char **values,
4261
                       int numentries, const char *wms_exception_format,
4262
0
                       owsRequestObj *ows_request) {
4263
4264
  // If we are returning an OpenLayers map there is no need to first generate an
4265
  // image. We can't call msReturnOpenLayersPage directly here as it requires
4266
  // the mapservObj
4267
0
  if (strcasecmp(map->imagetype, "application/openlayers") == 0) {
4268
0
    return MS_SUCCESS;
4269
0
  }
4270
4271
0
  imageObj *img;
4272
0
  int sldrequested = MS_FALSE, sldspatialfilter = MS_FALSE;
4273
0
  int drawquerymap = MS_FALSE;
4274
4275
  /* __TODO__ msDrawMap() will try to adjust the extent of the map */
4276
  /* to match the width/height image ratio. */
4277
  /* The spec states that this should not happen so that we can deliver */
4278
  /* maps to devices with non-square pixels. */
4279
4280
  /* If there was an SLD in the request, we need to treat it */
4281
  /* differently : some SLD may contain spatial filters requiring */
4282
  /* to do a query. While parsing the SLD and applying it to the */
4283
  /* layer, we added a temporary metadata on the layer */
4284
  /* (tmp_wms_sld_query) for layers with a spatial filter. */
4285
4286
0
  for (int i = 0; i < numentries; i++) {
4287
0
    if ((strcasecmp(names[i], "SLD") == 0 && values[i] &&
4288
0
         strlen(values[i]) > 0) ||
4289
0
        (strcasecmp(names[i], "SLD_BODY") == 0 && values[i] &&
4290
0
         strlen(values[i]) > 0)) {
4291
0
      sldrequested = MS_TRUE;
4292
0
      break;
4293
0
    }
4294
0
  }
4295
0
  if (sldrequested) {
4296
0
    for (int i = 0; i < map->numlayers; i++) {
4297
0
      if (msLookupHashTable(&(GET_LAYER(map, i)->metadata),
4298
0
                            "tmp_wms_sld_query")) {
4299
0
        sldspatialfilter = MS_TRUE;
4300
0
        break;
4301
0
      }
4302
0
    }
4303
0
  }
4304
  /* If FILTER is passed then we'll render layers as querymap */
4305
0
  for (int i = 0; i < numentries; i++) {
4306
0
    if ((strcasecmp(names[i], "FILTER") == 0 && values[i] &&
4307
0
         strlen(values[i]) > 0)) {
4308
0
      drawquerymap = MS_TRUE;
4309
0
      map->querymap.status = MS_ON;
4310
0
      map->querymap.style = MS_SELECTED;
4311
0
      break;
4312
0
    }
4313
0
  }
4314
4315
  /* turn off layer if WMS GetMap is not enabled */
4316
0
  for (int i = 0; i < map->numlayers; i++)
4317
0
    if (!msIntegerInArray(GET_LAYER(map, i)->index, ows_request->enabled_layers,
4318
0
                          ows_request->numlayers))
4319
0
      GET_LAYER(map, i)->status = MS_OFF;
4320
4321
0
  if (sldrequested && sldspatialfilter) {
4322
    /* set the quermap style so that only selected features will be returned */
4323
0
    map->querymap.status = MS_ON;
4324
0
    map->querymap.style = MS_SELECTED;
4325
4326
0
    img = msPrepareImage(map, MS_TRUE);
4327
4328
    /* compute layer scale factors now */
4329
0
    for (int i = 0; i < map->numlayers; i++) {
4330
0
      if (GET_LAYER(map, i)->sizeunits != MS_PIXELS)
4331
0
        GET_LAYER(map, i)->scalefactor =
4332
0
            (msInchesPerUnit(GET_LAYER(map, i)->sizeunits, 0) /
4333
0
             msInchesPerUnit(map->units, 0)) /
4334
0
            map->cellsize;
4335
0
      else if (GET_LAYER(map, i)->symbolscaledenom > 0 && map->scaledenom > 0)
4336
0
        GET_LAYER(map, i)->scalefactor =
4337
0
            GET_LAYER(map, i)->symbolscaledenom / map->scaledenom;
4338
0
      else
4339
0
        GET_LAYER(map, i)->scalefactor = 1;
4340
0
    }
4341
0
    for (int i = 0; i < map->numlayers; i++) {
4342
0
      if (msLookupHashTable(&(GET_LAYER(map, i)->metadata),
4343
0
                            "tmp_wms_sld_query") &&
4344
0
          (GET_LAYER(map, i)->type == MS_LAYER_POINT ||
4345
0
           GET_LAYER(map, i)->type == MS_LAYER_LINE ||
4346
0
           GET_LAYER(map, i)->type == MS_LAYER_POLYGON ||
4347
0
           GET_LAYER(map, i)->type == MS_LAYER_TILEINDEX))
4348
4349
0
      {
4350
        /* make sure that there is a resultcache. If not just ignore */
4351
        /* the layer */
4352
0
        if (GET_LAYER(map, i)->resultcache)
4353
0
          msDrawQueryLayer(map, GET_LAYER(map, i), img);
4354
0
      }
4355
4356
0
      else
4357
0
        IGNORE_RET_VAL(msDrawLayer(map, GET_LAYER(map, i), img));
4358
0
    }
4359
4360
0
  } else {
4361
4362
    /* intercept requests for Mapbox vector tiles */
4363
0
    if (!strcmp(MS_IMAGE_MIME_TYPE(map->outputformat),
4364
0
                "application/vnd.mapbox-vector-tile") ||
4365
0
        !strcmp(MS_IMAGE_MIME_TYPE(map->outputformat),
4366
0
                "application/x-protobuf")) {
4367
0
      int status = 0;
4368
0
      if ((status = msMVTWriteTile(map, MS_TRUE)) != MS_SUCCESS)
4369
0
        return MS_FAILURE;
4370
0
      return MS_SUCCESS;
4371
0
    }
4372
4373
0
    img = msDrawMap(map, drawquerymap);
4374
0
  }
4375
4376
  /* see if we have tiled = true and a buffer */
4377
  /* if so, clip the image */
4378
0
  for (int i = 0; i < numentries; i++) {
4379
0
    if (strcasecmp(names[i], "TILED") == 0 &&
4380
0
        strcasecmp(values[i], "TRUE") == 0) {
4381
0
      hashTableObj *meta = &(map->web.metadata);
4382
0
      const char *value;
4383
4384
0
      if ((value = msLookupHashTable(meta, "tile_map_edge_buffer")) != NULL) {
4385
0
        const int map_edge_buffer = atoi(value);
4386
0
        if (map_edge_buffer > 0) {
4387
          /* we have to clip the image */
4388
4389
          // TODO: we could probably avoid the use of an intermediate image
4390
          // by playing with the rasterBufferObj's data->rgb.pixels and
4391
          // data->rgb.row_stride values.
4392
0
          rendererVTableObj *renderer = MS_MAP_RENDERER(map);
4393
0
          rasterBufferObj imgBuffer;
4394
0
          if (renderer->getRasterBufferHandle((imageObj *)img, &imgBuffer) !=
4395
0
              MS_SUCCESS) {
4396
0
            msFreeImage(img);
4397
0
            return MS_FAILURE;
4398
0
          }
4399
4400
0
          int width = map->width - map_edge_buffer - map_edge_buffer;
4401
0
          int height = map->height - map_edge_buffer - map_edge_buffer;
4402
0
          imageObj *tmp =
4403
0
              msImageCreate(width, height, map->outputformat, NULL, NULL,
4404
0
                            map->resolution, map->defresolution, NULL);
4405
4406
0
          if ((MS_FAILURE == renderer->mergeRasterBuffer(
4407
0
                                 tmp, &imgBuffer, 1.0, map_edge_buffer,
4408
0
                                 map_edge_buffer, 0, 0, width, height))) {
4409
0
            msFreeImage(tmp);
4410
0
            msFreeImage(img);
4411
0
            img = NULL;
4412
0
          } else {
4413
0
            msFreeImage(img);
4414
0
            img = tmp;
4415
0
          }
4416
0
        }
4417
0
      }
4418
0
      break;
4419
0
    }
4420
0
  }
4421
4422
0
  if (img == NULL)
4423
0
    return msWMSException(map, nVersion, NULL, wms_exception_format);
4424
4425
  /* Set the HTTP Cache-control headers if they are defined
4426
     in the map object */
4427
4428
0
  const char *http_max_age =
4429
0
      msOWSLookupMetadata(&(map->web.metadata), "MO", "http_max_age");
4430
0
  if (http_max_age) {
4431
0
    msIO_setHeader("Cache-Control", "max-age=%s", http_max_age);
4432
0
  }
4433
4434
0
  if (strcasecmp(map->imagetype, "application/openlayers") != 0) {
4435
0
    if (!strcmp(MS_IMAGE_MIME_TYPE(map->outputformat), "application/json")) {
4436
0
      msIO_setHeader("Content-Type", "application/json; charset=utf-8");
4437
0
    } else {
4438
0
      msOutputFormatResolveFromImage(map, img);
4439
0
      msIO_setHeader("Content-Type", "%s",
4440
0
                     MS_IMAGE_MIME_TYPE(map->outputformat));
4441
0
    }
4442
0
    msIO_sendHeaders();
4443
0
    if (msSaveImage(map, img, NULL) != MS_SUCCESS) {
4444
0
      msFreeImage(img);
4445
0
      return msWMSException(map, nVersion, NULL, wms_exception_format);
4446
0
    }
4447
0
  }
4448
0
  msFreeImage(img);
4449
4450
0
  return (MS_SUCCESS);
4451
0
}
4452
4453
static int msDumpResult(mapObj *map, int nVersion,
4454
0
                        const char *wms_exception_format) {
4455
0
  int numresults = 0;
4456
4457
0
  for (int i = 0; i < map->numlayers; i++) {
4458
0
    layerObj *lp = (GET_LAYER(map, i));
4459
4460
0
    if (lp->status != MS_ON || lp->resultcache == NULL ||
4461
0
        lp->resultcache->numresults == 0)
4462
0
      continue;
4463
4464
    /* if(msLayerOpen(lp) != MS_SUCCESS || msLayerGetItems(lp) != MS_SUCCESS)
4465
     return msWMSException(map, nVersion, NULL); */
4466
4467
    /* Use metadata to control which fields to output. We use the same
4468
     * metadata names as for GML:
4469
     * wms/ows_include_items: comma delimited list or keyword 'all'
4470
     * wms/ows_exclude_items: comma delimited list (all items are excluded by
4471
     * default)
4472
     */
4473
    /* get a list of items that should be excluded in output */
4474
0
    std::vector<std::string> incitems;
4475
0
    const char *value;
4476
0
    if ((value = msOWSLookupMetadata(&(lp->metadata), "MO", "include_items")) !=
4477
0
        NULL)
4478
0
      incitems = msStringSplit(value, ',');
4479
4480
    /* get a list of items that should be excluded in output */
4481
0
    std::vector<std::string> excitems;
4482
0
    if ((value = msOWSLookupMetadata(&(lp->metadata), "MO", "exclude_items")) !=
4483
0
        NULL)
4484
0
      excitems = msStringSplit(value, ',');
4485
4486
0
    std::vector<bool> itemvisible(lp->numitems);
4487
0
    for (int k = 0; k < lp->numitems; k++) {
4488
      /* check visibility, included items first... */
4489
0
      if (incitems.size() == 1 && strcasecmp("all", incitems[0].c_str()) == 0) {
4490
0
        itemvisible[k] = true;
4491
0
      } else {
4492
0
        for (const auto &incitem : incitems) {
4493
0
          if (strcasecmp(lp->items[k], incitem.c_str()) == 0)
4494
0
            itemvisible[k] = true;
4495
0
        }
4496
0
      }
4497
4498
      /* ...and now excluded items */
4499
0
      for (const auto &excitem : excitems) {
4500
0
        if (strcasecmp(lp->items[k], excitem.c_str()) == 0)
4501
0
          itemvisible[k] = false;
4502
0
      }
4503
0
    }
4504
4505
    /* Output selected shapes for this layer */
4506
0
    msIO_printf("\nLayer '%s'\n", lp->name);
4507
4508
0
    for (int j = 0; j < lp->resultcache->numresults; j++) {
4509
0
      shapeObj shape;
4510
4511
0
      msInitShape(&shape);
4512
0
      if (msLayerGetShape(lp, &shape, &(lp->resultcache->results[j])) !=
4513
0
          MS_SUCCESS) {
4514
0
        return msWMSException(map, nVersion, NULL, wms_exception_format);
4515
0
      }
4516
4517
0
      msIO_printf("  Feature %ld: \n", lp->resultcache->results[j].shapeindex);
4518
4519
0
      for (int k = 0; k < lp->numitems; k++) {
4520
0
        if (itemvisible[k]) {
4521
0
          value = msOWSLookupMetadata(
4522
0
              &(lp->metadata), "MO",
4523
0
              (std::string(lp->items[k]) + "_alias").c_str());
4524
0
          const char *lineTemplate = "    %s = '%s'\n";
4525
0
          msIO_printf(lineTemplate, value != NULL ? value : lp->items[k],
4526
0
                      shape.values[k]);
4527
0
        }
4528
0
      }
4529
4530
0
      msFreeShape(&shape);
4531
0
      numresults++;
4532
0
    }
4533
4534
    /* msLayerClose(lp); */
4535
0
  }
4536
4537
0
  return numresults;
4538
0
}
4539
4540
/*
4541
** msWMSFeatureInfo()
4542
*/
4543
static int msWMSFeatureInfo(mapObj *map, int nVersion, char **names,
4544
                            char **values, int numentries,
4545
                            const char *wms_exception_format,
4546
0
                            owsRequestObj *ows_request) {
4547
0
  int feature_count = 1, numlayers_found = 0;
4548
0
  pointObj point = {-1.0, -1.0, -1.0, -1.0};
4549
0
  const char *info_format = "MIME";
4550
0
  int query_layer = 0;
4551
0
  const char *format_list = NULL;
4552
0
  int valid_format = MS_FALSE;
4553
0
  int format_found = MS_FALSE;
4554
0
  int use_bbox = MS_FALSE;
4555
0
  int wms_layer = MS_FALSE;
4556
0
  const char *wms_connection = NULL;
4557
0
  int numOWSLayers = 0;
4558
4559
0
  char ***nestedGroups =
4560
0
      (char ***)msSmallCalloc(map->numlayers, sizeof(char **));
4561
0
  int *numNestedGroups = (int *)msSmallCalloc(map->numlayers, sizeof(int));
4562
0
  int *isUsedInNestedGroup = (int *)msSmallCalloc(map->numlayers, sizeof(int));
4563
0
  msWMSPrepareNestedGroups(map, nVersion, nestedGroups, numNestedGroups,
4564
0
                           isUsedInNestedGroup);
4565
4566
0
  for (int i = 0; i < numentries; i++) {
4567
0
    if (strcasecmp(names[i], "QUERY_LAYERS") == 0) {
4568
0
      query_layer = 1; /* flag set if QUERY_LAYERS is the request */
4569
4570
0
      const auto wmslayers = msStringSplit(values[i], ',');
4571
0
      if (wmslayers.empty()) {
4572
0
        msSetErrorWithStatus(
4573
0
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4574
0
            "At least one layer name required in QUERY_LAYERS.",
4575
0
            "msWMSFeatureInfo()");
4576
0
        return msWMSException(map, nVersion, "LayerNotDefined",
4577
0
                              wms_exception_format);
4578
0
      }
4579
4580
0
      for (int j = 0; j < map->numlayers; j++) {
4581
        /* Force all layers OFF by default */
4582
0
        GET_LAYER(map, j)->status = MS_OFF;
4583
0
      }
4584
4585
      /* Special case for root layer */
4586
0
      const char *rootlayer_name =
4587
0
          msOWSLookupMetadata(&(map->web.metadata), "MO", "rootlayer_name");
4588
0
      if (!rootlayer_name)
4589
0
        rootlayer_name = map->name;
4590
0
      if (rootlayer_name && msStringInArray(rootlayer_name, wmslayers)) {
4591
0
        for (int j = 0; j < map->numlayers; j++) {
4592
0
          layerObj *layer = GET_LAYER(map, j);
4593
0
          if (msIsLayerQueryable(layer) &&
4594
0
              msIntegerInArray(layer->index, ows_request->enabled_layers,
4595
0
                               ows_request->numlayers)) {
4596
0
            if (layer->connectiontype == MS_WMS) {
4597
0
              wms_layer = MS_TRUE;
4598
0
              wms_connection = layer->connection;
4599
0
            }
4600
4601
0
            numlayers_found++;
4602
0
            layer->status = MS_ON;
4603
0
          }
4604
0
        }
4605
0
      }
4606
4607
0
      for (int j = 0; j < map->numlayers; j++) {
4608
0
        layerObj *layer = GET_LAYER(map, j);
4609
0
        if (!msIsLayerQueryable(layer))
4610
0
          continue;
4611
0
        for (const auto &wmslayer : wmslayers) {
4612
0
          if (((layer->name &&
4613
0
                strcasecmp(layer->name, wmslayer.c_str()) == 0) ||
4614
0
               (layer->group &&
4615
0
                strcasecmp(layer->group, wmslayer.c_str()) == 0) ||
4616
0
               ((numNestedGroups[j] > 0) &&
4617
0
                msStringInArray(wmslayer.c_str(), nestedGroups[j],
4618
0
                                numNestedGroups[j]))) &&
4619
0
              (msIntegerInArray(layer->index, ows_request->enabled_layers,
4620
0
                                ows_request->numlayers))) {
4621
4622
0
            if (layer->connectiontype == MS_WMS) {
4623
0
              wms_layer = MS_TRUE;
4624
0
              wms_connection = layer->connection;
4625
0
            }
4626
4627
0
            numlayers_found++;
4628
0
            layer->status = MS_ON;
4629
0
          }
4630
0
        }
4631
0
      }
4632
0
    } else if (strcasecmp(names[i], "INFO_FORMAT") == 0) {
4633
0
      if (values[i] && strlen(values[i]) > 0) {
4634
0
        info_format = values[i];
4635
0
        format_found = MS_TRUE;
4636
0
      }
4637
0
    } else if (strcasecmp(names[i], "FEATURE_COUNT") == 0)
4638
0
      feature_count = atoi(values[i]);
4639
0
    else if (strcasecmp(names[i], "X") == 0 || strcasecmp(names[i], "I") == 0)
4640
0
      point.x = atof(values[i]);
4641
0
    else if (strcasecmp(names[i], "Y") == 0 || strcasecmp(names[i], "J") == 0)
4642
0
      point.y = atof(values[i]);
4643
0
    else if (strcasecmp(names[i], "RADIUS") == 0) {
4644
      /* RADIUS in pixels. */
4645
      /* This is not part of the spec, but some servers such as cubeserv */
4646
      /* support it as a vendor-specific feature. */
4647
      /* It's easy for MapServer to handle this so let's do it! */
4648
4649
      /* Special RADIUS value that changes the query into a bbox query */
4650
      /* based on the bbox in the request parameters. */
4651
0
      if (strcasecmp(values[i], "BBOX") == 0) {
4652
0
        use_bbox = MS_TRUE;
4653
0
      } else {
4654
0
        int j;
4655
0
        for (j = 0; j < map->numlayers; j++) {
4656
0
          GET_LAYER(map, j)->tolerance = atoi(values[i]);
4657
0
          GET_LAYER(map, j)->toleranceunits = MS_PIXELS;
4658
0
        }
4659
0
      }
4660
0
    }
4661
0
  }
4662
4663
  /* free the stuff used for nested layers */
4664
0
  for (int i = 0; i < map->numlayers; i++) {
4665
0
    if (numNestedGroups[i] > 0) {
4666
0
      msFreeCharArray(nestedGroups[i], numNestedGroups[i]);
4667
0
    }
4668
0
  }
4669
0
  free(nestedGroups);
4670
0
  free(numNestedGroups);
4671
0
  free(isUsedInNestedGroup);
4672
4673
0
  if (numlayers_found == 0) {
4674
0
    if (query_layer) {
4675
0
      msSetErrorWithStatus(
4676
0
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4677
0
          "Layer(s) specified in QUERY_LAYERS parameter is not offered "
4678
0
          "by the service instance.",
4679
0
          "msWMSFeatureInfo()");
4680
0
      return msWMSException(map, nVersion, "LayerNotDefined",
4681
0
                            wms_exception_format);
4682
0
    } else {
4683
0
      msSetErrorWithStatus(
4684
0
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4685
0
          "Required QUERY_LAYERS parameter missing for getFeatureInfo.",
4686
0
          "msWMSFeatureInfo()");
4687
0
      return msWMSException(map, nVersion, "LayerNotDefined",
4688
0
                            wms_exception_format);
4689
0
    }
4690
0
  }
4691
4692
  /*make sure to initialize the map scale so that layers that are scale
4693
    dependent are respected for the query*/
4694
0
  msCalculateScale(map->extent, map->units, map->width, map->height,
4695
0
                   map->resolution, &map->scaledenom);
4696
4697
  /* -------------------------------------------------------------------- */
4698
  /*      check if all layers selected are queryable. If not send an      */
4699
  /*      exception.                                                      */
4700
  /* -------------------------------------------------------------------- */
4701
4702
  /* If a layer of type WMS was found... all layers have to be of that type and
4703
   * with the same connection */
4704
0
  for (int i = 0; i < map->numlayers; i++) {
4705
0
    if (GET_LAYER(map, i)->status == MS_ON) {
4706
0
      if (wms_layer == MS_TRUE) {
4707
0
        if ((GET_LAYER(map, i)->connectiontype != MS_WMS) ||
4708
0
            (strcasecmp(wms_connection, GET_LAYER(map, i)->connection) != 0)) {
4709
0
          msSetErrorWithStatus(
4710
0
              MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4711
0
              "Requested WMS layer(s) are not queryable: type or "
4712
0
              "connection differ",
4713
0
              "msWMSFeatureInfo()");
4714
0
          return msWMSException(map, nVersion, "LayerNotQueryable",
4715
0
                                wms_exception_format);
4716
0
        }
4717
0
        ++numOWSLayers;
4718
0
      }
4719
0
    }
4720
0
  }
4721
4722
  /* It's a valid Cascading WMS GetFeatureInfo request */
4723
0
  if (wms_layer)
4724
0
    return msWMSLayerExecuteRequest(map, numOWSLayers, point.x, point.y,
4725
0
                                    feature_count, info_format,
4726
0
                                    WMS_GETFEATUREINFO);
4727
4728
0
  if (use_bbox == MS_FALSE) {
4729
4730
0
    if (point.x == -1.0 || point.y == -1.0) {
4731
0
      if (nVersion >= OWS_1_3_0)
4732
0
        msSetErrorWithStatus(
4733
0
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4734
0
            "Required I/J parameters missing for getFeatureInfo.",
4735
0
            "msWMSFeatureInfo()");
4736
0
      else
4737
0
        msSetErrorWithStatus(
4738
0
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4739
0
            "Required X/Y parameters missing for getFeatureInfo.",
4740
0
            "msWMSFeatureInfo()");
4741
0
      return msWMSException(map, nVersion, NULL, wms_exception_format);
4742
0
    }
4743
4744
    /*wms1.3.0: check if the points are valid*/
4745
0
    if (nVersion >= OWS_1_3_0) {
4746
0
      if (point.x > map->width || point.y > map->height) {
4747
0
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4748
0
                             "Invalid I/J values", "msWMSFeatureInfo()");
4749
0
        return msWMSException(map, nVersion, "InvalidPoint",
4750
0
                              wms_exception_format);
4751
0
      }
4752
0
    }
4753
    /* Perform the actual query */
4754
0
    const double cellx =
4755
0
        MS_CELLSIZE(map->extent.minx, map->extent.maxx,
4756
0
                    map->width); /* note: don't adjust extent, WMS assumes
4757
                                    incoming extent is correct */
4758
0
    const double celly =
4759
0
        MS_CELLSIZE(map->extent.miny, map->extent.maxy, map->height);
4760
0
    point.x = MS_IMAGE2MAP_X(point.x, map->extent.minx, cellx);
4761
0
    point.y = MS_IMAGE2MAP_Y(point.y, map->extent.maxy, celly);
4762
4763
    /* WMS 1.3.0 states that feature_count is *per layer*.
4764
     * Its value is a positive integer, if omitted then the default is 1
4765
     */
4766
0
    if (feature_count < 1)
4767
0
      feature_count = 1;
4768
4769
0
    map->query.type = MS_QUERY_BY_POINT;
4770
0
    map->query.mode =
4771
0
        (feature_count == 1 ? MS_QUERY_SINGLE : MS_QUERY_MULTIPLE);
4772
0
    map->query.layer = -1;
4773
0
    map->query.point = point;
4774
0
    map->query.buffer = 0;
4775
0
    map->query.maxresults = feature_count;
4776
4777
0
    if (msQueryByPoint(map) != MS_SUCCESS)
4778
0
      return msWMSException(map, nVersion, NULL, wms_exception_format);
4779
4780
0
  } else { /* use_bbox == MS_TRUE */
4781
0
    map->query.type = MS_QUERY_BY_RECT;
4782
0
    map->query.mode = MS_QUERY_MULTIPLE;
4783
0
    map->query.layer = -1;
4784
0
    map->query.rect = map->extent;
4785
0
    map->query.buffer = 0;
4786
0
    map->query.maxresults = feature_count;
4787
0
    if (msQueryByRect(map) != MS_SUCCESS)
4788
0
      return msWMSException(map, nVersion, NULL, wms_exception_format);
4789
0
  }
4790
4791
  /*validate the INFO_FORMAT*/
4792
0
  valid_format = MS_FALSE;
4793
0
  format_list = msOWSLookupMetadata(&(map->web.metadata), "M",
4794
0
                                    "getfeatureinfo_formatlist");
4795
  /*feature_info_mime_type deprecated for MapServer 6.0*/
4796
0
  if (!format_list)
4797
0
    format_list = msOWSLookupMetadata(&(map->web.metadata), "MO",
4798
0
                                      "feature_info_mime_type");
4799
0
  if (format_list) {
4800
    /*can not really validate if it is a valid output format
4801
      since old way of using template with web->header/footer and
4802
      layer templates need to still be supported.
4803
      We can only validate if it was part of the format list*/
4804
0
    if (strcasestr(format_list, info_format))
4805
0
      valid_format = MS_TRUE;
4806
0
  }
4807
  /*check to see if the format passed is text/plain or GML and if is
4808
    defined in the formatlist. If that is the case, It is a valid format*/
4809
0
  if (strcasecmp(info_format, "MIME") == 0 ||
4810
0
      strcasecmp(info_format, "text/plain") == 0 ||
4811
0
      strncasecmp(info_format, "GML", 3) == 0 ||
4812
0
      strcasecmp(info_format, "application/vnd.ogc.gml") == 0)
4813
0
    valid_format = MS_TRUE;
4814
4815
  /*last case: if the info_format is not part of the request, it defaults to
4816
   * MIME*/
4817
0
  if (!valid_format && format_found == MS_FALSE)
4818
0
    valid_format = MS_TRUE;
4819
4820
0
  if (!valid_format) {
4821
0
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4822
0
                         "Unsupported INFO_FORMAT value (%s).",
4823
0
                         "msWMSFeatureInfo()", info_format);
4824
0
    if (nVersion >= OWS_1_3_0)
4825
0
      return msWMSException(map, nVersion, "InvalidFormat",
4826
0
                            wms_exception_format);
4827
0
    else
4828
0
      return msWMSException(map, nVersion, NULL, wms_exception_format);
4829
0
  }
4830
4831
  /* Generate response */
4832
0
  if (strcasecmp(info_format, "MIME") == 0 ||
4833
0
      strcasecmp(info_format, "text/plain") == 0) {
4834
4835
    /* MIME response... we're free to use any valid MIME type */
4836
0
    int numresults = 0;
4837
4838
0
    msIO_setHeader("Content-Type", "text/plain; charset=UTF-8");
4839
0
    msIO_sendHeaders();
4840
0
    msIO_printf("GetFeatureInfo results:\n");
4841
4842
0
    numresults = msDumpResult(map, nVersion, wms_exception_format);
4843
4844
0
    if (numresults == 0)
4845
0
      msIO_printf("\n  Search returned no results.\n");
4846
4847
0
  } else if (strncasecmp(info_format, "GML", 3) ==
4848
0
                 0 || /* accept GML.1 or GML */
4849
0
             strcasecmp(info_format, "application/vnd.ogc.gml") == 0) {
4850
4851
0
    if (nVersion <= OWS_1_0_7) /* 1.0.0 to 1.0.7 */
4852
0
      msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
4853
0
    else /* 1.1.0 and later */
4854
0
      msIO_setHeader("Content-Type", "application/vnd.ogc.gml; charset=UTF-8");
4855
0
    msIO_sendHeaders();
4856
0
    msGMLWriteQuery(map, NULL, "MGO"); /* default is stdout */
4857
4858
0
  } else {
4859
0
    mapservObj *msObj;
4860
4861
0
    char **translated_names, **translated_values;
4862
0
    int translated_numentries;
4863
0
    msObj = msAllocMapServObj();
4864
4865
    /* Translate some vars from WMS to mapserv */
4866
0
    msTranslateWMS2Mapserv((const char **)names, (const char **)values,
4867
0
                           numentries, &translated_names, &translated_values,
4868
0
                           &translated_numentries);
4869
4870
0
    msObj->map = map;
4871
0
    msFreeCharArray(msObj->request->ParamNames, msObj->request->NumParams);
4872
0
    msFreeCharArray(msObj->request->ParamValues, msObj->request->NumParams);
4873
0
    msObj->request->ParamNames = translated_names;
4874
0
    msObj->request->ParamValues = translated_values;
4875
0
    msObj->Mode = QUERY;
4876
0
    msObj->request->NumParams = translated_numentries;
4877
0
    msObj->mappnt.x = point.x;
4878
0
    msObj->mappnt.y = point.y;
4879
4880
0
    bool hasResults = false;
4881
0
    for (int i = 0; i < map->numlayers; i++) {
4882
0
      layerObj *lp = (GET_LAYER(map, i));
4883
4884
0
      if (lp->status == MS_ON && lp->resultcache &&
4885
0
          lp->resultcache->numresults != 0) {
4886
0
        hasResults = true;
4887
0
        break;
4888
0
      }
4889
0
    }
4890
4891
0
    if (!hasResults && msObj->map->web.empty) {
4892
0
      if (msReturnURL(msObj, msObj->map->web.empty, BROWSE) != MS_SUCCESS)
4893
0
        return msWMSException(map, nVersion, NULL, wms_exception_format);
4894
0
    } else if (msReturnTemplateQuery(msObj, (char *)info_format, NULL) !=
4895
0
               MS_SUCCESS)
4896
0
      return msWMSException(map, nVersion, NULL, wms_exception_format);
4897
4898
    /* We don't want to free the map since it */
4899
    /* belongs to the caller, set it to NULL before freeing the mapservObj */
4900
0
    msObj->map = NULL;
4901
4902
0
    msFreeMapServObj(msObj);
4903
0
  }
4904
4905
0
  return (MS_SUCCESS);
4906
0
}
4907
4908
/*
4909
** msWMSDescribeLayer()
4910
*/
4911
static int msWMSDescribeLayer(mapObj *map, int nVersion, char **names,
4912
                              char **values, int numentries,
4913
0
                              const char *wms_exception_format) {
4914
0
  std::vector<std::string> wmslayers;
4915
0
  const char *version = NULL;
4916
0
  const char *sld_version = NULL;
4917
4918
0
  for (int i = 0; i < numentries; i++) {
4919
0
    if (strcasecmp(names[i], "LAYERS") == 0) {
4920
0
      wmslayers = msStringSplit(values[i], ',');
4921
0
    }
4922
0
    if (strcasecmp(names[i], "VERSION") == 0) {
4923
0
      version = values[i];
4924
0
    }
4925
0
    if (strcasecmp(names[i], "SLD_VERSION") == 0) {
4926
0
      sld_version = values[i];
4927
0
    }
4928
0
  }
4929
4930
0
  if (nVersion >= OWS_1_3_0 && sld_version == NULL) {
4931
0
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4932
0
                         "Missing required parameter SLD_VERSION",
4933
0
                         "DescribeLayer()");
4934
0
    return msWMSException(map, nVersion, "MissingParameterValue",
4935
0
                          wms_exception_format);
4936
0
  }
4937
0
  if (nVersion >= OWS_1_3_0 && strcasecmp(sld_version, "1.1.0") != 0) {
4938
0
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4939
0
                         "SLD_VERSION must be 1.1.0", "DescribeLayer()");
4940
0
    return msWMSException(map, nVersion, "InvalidParameterValue",
4941
0
                          wms_exception_format);
4942
0
  }
4943
4944
0
  msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
4945
0
  msIO_sendHeaders();
4946
4947
0
  msIO_printf("<?xml version='1.0' encoding=\"UTF-8\"?>\n");
4948
4949
0
  {
4950
0
    char *schemalocation = msEncodeHTMLEntities(msOWSGetSchemasLocation(map));
4951
0
    if (nVersion < OWS_1_3_0) {
4952
4953
0
      msIO_printf("<!DOCTYPE WMS_DescribeLayerResponse SYSTEM "
4954
0
                  "\"%s/wms/1.1.1/WMS_DescribeLayerResponse.dtd\">\n",
4955
0
                  schemalocation);
4956
4957
0
      msIO_printf("<WMS_DescribeLayerResponse version=\"%s\" >\n", version);
4958
0
    } else {
4959
0
      msIO_printf("<DescribeLayerResponse xmlns=\"http://www.opengis.net/sld\" "
4960
0
                  "xmlns:ows=\"http://www.opengis.net/ows\" "
4961
0
                  "xmlns:se=\"http://www.opengis.net/se\" "
4962
0
                  "xmlns:wfs=\"http://www.opengis.net/wfs\" "
4963
0
                  "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
4964
0
                  "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
4965
0
                  "xsi:schemaLocation=\"http://www.opengis.net/sld "
4966
0
                  "%s/sld/1.1.0/DescribeLayer.xsd\">\n",
4967
0
                  schemalocation);
4968
0
      msIO_printf("<Version>%s</Version>\n", sld_version);
4969
0
    }
4970
0
    free(schemalocation);
4971
0
  }
4972
4973
  /* check if map-level metadata wfs(wcs)_onlineresource is available */
4974
0
  const char *pszOnlineResMapWFS =
4975
0
      msOWSLookupMetadata(&(map->web.metadata), "FO", "onlineresource");
4976
0
  if (pszOnlineResMapWFS && strlen(pszOnlineResMapWFS) == 0)
4977
0
    pszOnlineResMapWFS = NULL;
4978
4979
0
  const char *pszOnlineResMapWCS =
4980
0
      msOWSLookupMetadata(&(map->web.metadata), "CO", "onlineresource");
4981
0
  if (pszOnlineResMapWCS && strlen(pszOnlineResMapWCS) == 0)
4982
0
    pszOnlineResMapWCS = NULL;
4983
4984
0
  char ***nestedGroups =
4985
0
      (char ***)msSmallCalloc(map->numlayers, sizeof(char **));
4986
0
  int *numNestedGroups = (int *)msSmallCalloc(map->numlayers, sizeof(int));
4987
0
  int *isUsedInNestedGroup = (int *)msSmallCalloc(map->numlayers, sizeof(int));
4988
0
  msWMSPrepareNestedGroups(map, nVersion, nestedGroups, numNestedGroups,
4989
0
                           isUsedInNestedGroup);
4990
4991
0
  for (const auto &wmslayer : wmslayers) {
4992
0
    for (int k = 0; k < map->numlayers; k++) {
4993
0
      layerObj *lp = GET_LAYER(map, k);
4994
4995
0
      if ((map->name && strcasecmp(map->name, wmslayer.c_str()) == 0) ||
4996
0
          (lp->name && strcasecmp(lp->name, wmslayer.c_str()) == 0) ||
4997
0
          (lp->group && strcasecmp(lp->group, wmslayer.c_str()) == 0) ||
4998
0
          ((numNestedGroups[k] > 0) &&
4999
0
           msStringInArray(wmslayer.c_str(), nestedGroups[k],
5000
0
                           numNestedGroups[k]))) {
5001
        /* Look for a WFS onlineresouce at the layer level and then at
5002
         * the map level.
5003
         */
5004
0
        const char *pszOnlineResLyrWFS =
5005
0
            msOWSLookupMetadata(&(lp->metadata), "FO", "onlineresource");
5006
0
        const char *pszOnlineResLyrWCS =
5007
0
            msOWSLookupMetadata(&(lp->metadata), "CO", "onlineresource");
5008
0
        if (pszOnlineResLyrWFS == NULL || strlen(pszOnlineResLyrWFS) == 0)
5009
0
          pszOnlineResLyrWFS = pszOnlineResMapWFS;
5010
5011
0
        if (pszOnlineResLyrWCS == NULL || strlen(pszOnlineResLyrWCS) == 0)
5012
0
          pszOnlineResLyrWCS = pszOnlineResMapWCS;
5013
5014
0
        if (pszOnlineResLyrWFS &&
5015
0
            (lp->type == MS_LAYER_POINT || lp->type == MS_LAYER_LINE ||
5016
0
             lp->type == MS_LAYER_POLYGON)) {
5017
0
          char *pszOnlineResEncoded = msEncodeHTMLEntities(pszOnlineResLyrWFS);
5018
0
          char *pszLayerName = msEncodeHTMLEntities(lp->name);
5019
5020
0
          if (nVersion < OWS_1_3_0) {
5021
0
            msIO_printf("<LayerDescription name=\"%s\" wfs=\"%s\" "
5022
0
                        "owsType=\"WFS\" owsURL=\"%s\">\n",
5023
0
                        pszLayerName, pszOnlineResEncoded, pszOnlineResEncoded);
5024
0
            msIO_printf("<Query typeName=\"%s\" />\n", pszLayerName);
5025
0
            msIO_printf("</LayerDescription>\n");
5026
0
          } else { /*wms 1.3.0*/
5027
0
            msIO_printf("  <LayerDescription>\n");
5028
0
            msIO_printf("    <owsType>wfs</owsType>\n");
5029
0
            msIO_printf("    <se:OnlineResource xlink:type=\"simple\" "
5030
0
                        "xlink:href=\"%s\"/>\n",
5031
0
                        pszOnlineResEncoded);
5032
0
            msIO_printf("    <TypeName>\n");
5033
0
            msIO_printf("      <se:FeatureTypeName>%s</se:FeatureTypeName>\n",
5034
0
                        pszLayerName);
5035
0
            msIO_printf("    </TypeName>\n");
5036
0
            msIO_printf("  </LayerDescription>\n");
5037
0
          }
5038
5039
0
          msFree(pszOnlineResEncoded);
5040
0
          msFree(pszLayerName);
5041
0
        } else if (pszOnlineResLyrWCS && lp->type == MS_LAYER_RASTER &&
5042
0
                   lp->connectiontype != MS_WMS) {
5043
0
          char *pszOnlineResEncoded = msEncodeHTMLEntities(pszOnlineResLyrWCS);
5044
0
          char *pszLayerName = msEncodeHTMLEntities(lp->name);
5045
5046
0
          if (nVersion < OWS_1_3_0) {
5047
0
            msIO_printf("<LayerDescription name=\"%s\"  owsType=\"WCS\" "
5048
0
                        "owsURL=\"%s\">\n",
5049
0
                        pszLayerName, pszOnlineResEncoded);
5050
0
            msIO_printf("<Query typeName=\"%s\" />\n", pszLayerName);
5051
0
            msIO_printf("</LayerDescription>\n");
5052
0
          } else {
5053
0
            msIO_printf("  <LayerDescription>\n");
5054
0
            msIO_printf("    <owsType>wcs</owsType>\n");
5055
0
            msIO_printf("    <se:OnlineResource xlink:type=\"simple\" "
5056
0
                        "xlink:href=\"%s\"/>\n",
5057
0
                        pszOnlineResEncoded);
5058
0
            msIO_printf("    <TypeName>\n");
5059
0
            msIO_printf("      <se:CoverageTypeName>%s</se:CoverageTypeName>\n",
5060
0
                        pszLayerName);
5061
0
            msIO_printf("    </TypeName>\n");
5062
0
            msIO_printf("  </LayerDescription>\n");
5063
0
          }
5064
0
          msFree(pszOnlineResEncoded);
5065
0
          msFree(pszLayerName);
5066
0
        } else {
5067
0
          char *pszLayerName = msEncodeHTMLEntities(lp->name);
5068
5069
0
          if (nVersion < OWS_1_3_0)
5070
0
            msIO_printf("<LayerDescription name=\"%s\"></LayerDescription>\n",
5071
0
                        pszLayerName);
5072
0
          else { /*wms 1.3.0*/
5073
0
            msIO_printf("  <LayerDescription>\n");
5074
            /*need to have a owstype for the DescribeLayer to be valid*/
5075
0
            if (lp->type == MS_LAYER_RASTER && lp->connectiontype != MS_WMS)
5076
0
              msIO_printf("    <owsType>wcs</owsType>\n");
5077
0
            else
5078
0
              msIO_printf("    <owsType>wfs</owsType>\n");
5079
5080
0
            msIO_printf("    <se:OnlineResource xlink:type=\"simple\"/>\n");
5081
0
            msIO_printf("    <TypeName>\n");
5082
0
            if (lp->type == MS_LAYER_RASTER && lp->connectiontype != MS_WMS)
5083
0
              msIO_printf(
5084
0
                  "      <se:CoverageTypeName>%s</se:CoverageTypeName>\n",
5085
0
                  pszLayerName);
5086
0
            else
5087
0
              msIO_printf("      <se:FeatureTypeName>%s</se:FeatureTypeName>\n",
5088
0
                          pszLayerName);
5089
0
            msIO_printf("    </TypeName>\n");
5090
0
            msIO_printf("  </LayerDescription>\n");
5091
0
          }
5092
5093
0
          msFree(pszLayerName);
5094
0
        }
5095
        /* break; */
5096
0
      }
5097
0
    }
5098
0
  }
5099
5100
0
  if (nVersion < OWS_1_3_0)
5101
0
    msIO_printf("</WMS_DescribeLayerResponse>\n");
5102
0
  else
5103
0
    msIO_printf("</DescribeLayerResponse>\n");
5104
5105
  /* free the stuff used for nested layers */
5106
0
  for (int i = 0; i < map->numlayers; i++) {
5107
0
    if (numNestedGroups[i] > 0) {
5108
0
      msFreeCharArray(nestedGroups[i], numNestedGroups[i]);
5109
0
    }
5110
0
  }
5111
0
  free(nestedGroups);
5112
0
  free(numNestedGroups);
5113
0
  free(isUsedInNestedGroup);
5114
5115
0
  return (MS_SUCCESS);
5116
0
}
5117
5118
/*
5119
** msWMSGetLegendGraphic()
5120
*/
5121
static int msWMSLegendGraphic(mapObj *map, int nVersion, char **names,
5122
                              char **values, int numentries,
5123
                              const char *wms_exception_format,
5124
                              owsRequestObj *ows_request,
5125
0
                              map_hittest *hittest) {
5126
0
  const char *pszLayer = NULL;
5127
0
  const char *pszFormat = NULL;
5128
0
  const char *psRule = NULL;
5129
0
  const char *psScale = NULL;
5130
0
  int iLayerIndex = -1;
5131
0
  outputFormatObj *psFormat = NULL;
5132
0
  imageObj *img = NULL;
5133
0
  int nWidth = -1, nHeight = -1;
5134
0
  const char *pszStyle = NULL;
5135
0
  const char *sld_version = NULL;
5136
0
  int wms_layer = MS_FALSE;
5137
0
  const char *sldenabled = NULL;
5138
0
  const char *format_list = NULL;
5139
0
  int nLayers = 0;
5140
5141
0
  if (!hittest) {
5142
    /* we can skip a lot of testing if we already have a hittest, as it has
5143
     * already been done in the hittesting phase */
5144
5145
0
    sldenabled = msOWSLookupMetadata(&(map->web.metadata), "MO", "sld_enabled");
5146
5147
0
    if (sldenabled == NULL)
5148
0
      sldenabled = "true";
5149
5150
0
    for (int i = 0; i < numentries; i++) {
5151
0
      if (strcasecmp(names[i], "LAYER") == 0) {
5152
0
        pszLayer = values[i];
5153
0
      } else if (strcasecmp(names[i], "WIDTH") == 0)
5154
0
        nWidth = atoi(values[i]);
5155
0
      else if (strcasecmp(names[i], "HEIGHT") == 0)
5156
0
        nHeight = atoi(values[i]);
5157
0
      else if (strcasecmp(names[i], "FORMAT") == 0)
5158
0
        pszFormat = values[i];
5159
0
      else if (strcasecmp(names[i], "SCALE") == 0)
5160
0
        psScale = values[i];
5161
5162
      /* -------------------------------------------------------------------- */
5163
      /*      SLD support :                                                   */
5164
      /*        - check if the SLD parameter is there. it is supposed to      */
5165
      /*      refer a valid URL containing an SLD document.                   */
5166
      /*        - check the SLD_BODY parameter that should contain the SLD    */
5167
      /*      xml string.                                                     */
5168
      /* -------------------------------------------------------------------- */
5169
0
      else if (strcasecmp(names[i], "SLD") == 0 && values[i] &&
5170
0
               strlen(values[i]) > 0 && strcasecmp(sldenabled, "true") == 0)
5171
0
        msSLDApplySLDURL(map, values[i], -1, NULL, NULL);
5172
0
      else if (strcasecmp(names[i], "SLD_BODY") == 0 && values[i] &&
5173
0
               strlen(values[i]) > 0 && strcasecmp(sldenabled, "true") == 0)
5174
0
        msSLDApplySLD(map, values[i], -1, NULL, NULL);
5175
0
      else if (strcasecmp(names[i], "RULE") == 0)
5176
0
        psRule = values[i];
5177
0
      else if (strcasecmp(names[i], "STYLE") == 0)
5178
0
        pszStyle = values[i];
5179
5180
      /* -------------------------------------------------------------------- */
5181
      /*      SLD support:                                                    */
5182
      /*        - because the request parameter "sld_version" is required in  */
5183
      /*          in WMS 1.3.0, it will be set regardless of OGR support.     */
5184
      /* -------------------------------------------------------------------- */
5185
0
      else if (strcasecmp(names[i], "SLD_VERSION") == 0)
5186
0
        sld_version = values[i];
5187
0
    }
5188
5189
0
    if (!pszLayer) {
5190
0
      msSetErrorWithStatus(
5191
0
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5192
0
          "Mandatory LAYER parameter missing in GetLegendGraphic request.",
5193
0
          "msWMSGetLegendGraphic()");
5194
0
      return msWMSException(map, nVersion, "LayerNotDefined",
5195
0
                            wms_exception_format);
5196
0
    }
5197
0
    if (!pszFormat) {
5198
0
      msSetErrorWithStatus(
5199
0
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5200
0
          "Mandatory FORMAT parameter missing in GetLegendGraphic request.",
5201
0
          "msWMSGetLegendGraphic()");
5202
0
      return msWMSException(map, nVersion, "InvalidFormat",
5203
0
                            wms_exception_format);
5204
0
    }
5205
5206
0
    if (nVersion >= OWS_1_3_0 && sld_version == NULL) {
5207
0
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5208
0
                           "Missing required parameter SLD_VERSION",
5209
0
                           "GetLegendGraphic()");
5210
0
      return msWMSException(map, nVersion, "MissingParameterValue",
5211
0
                            wms_exception_format);
5212
0
    }
5213
0
    if (nVersion >= OWS_1_3_0 && strcasecmp(sld_version, "1.1.0") != 0) {
5214
0
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5215
0
                           "SLD_VERSION must be 1.1.0", "GetLegendGraphic()");
5216
0
      return msWMSException(map, nVersion, "InvalidParameterValue",
5217
0
                            wms_exception_format);
5218
0
    }
5219
5220
0
    char ***nestedGroups =
5221
0
        (char ***)msSmallCalloc(map->numlayers, sizeof(char **));
5222
0
    int *numNestedGroups = (int *)msSmallCalloc(map->numlayers, sizeof(int));
5223
0
    int *isUsedInNestedGroup =
5224
0
        (int *)msSmallCalloc(map->numlayers, sizeof(int));
5225
0
    msWMSPrepareNestedGroups(map, nVersion, nestedGroups, numNestedGroups,
5226
0
                             isUsedInNestedGroup);
5227
5228
    /* check if layer name is valid. we check for layer's and group's name */
5229
    /* as well as wms_layer_group names */
5230
0
    for (int i = 0; i < map->numlayers; i++) {
5231
0
      layerObj *lp = GET_LAYER(map, i);
5232
0
      if (((map->name && strcasecmp(map->name, pszLayer) == 0) ||
5233
0
           (lp->name && strcasecmp(lp->name, pszLayer) == 0) ||
5234
0
           (lp->group && strcasecmp(lp->group, pszLayer) == 0) ||
5235
0
           ((numNestedGroups[i] > 0) &&
5236
0
            (msStringInArray(pszLayer, nestedGroups[i],
5237
0
                             numNestedGroups[i])))) &&
5238
0
          (msIntegerInArray(lp->index, ows_request->enabled_layers,
5239
0
                            ows_request->numlayers))) {
5240
0
        nLayers++;
5241
0
        lp->status = MS_ON;
5242
0
        iLayerIndex = i;
5243
0
        if (GET_LAYER(map, i)->connectiontype == MS_WMS) {
5244
          /* we do not cascade a wms layer if it contains at least
5245
           * one class with the property name set */
5246
0
          wms_layer = MS_TRUE;
5247
0
          for (int j = 0; j < lp->numclasses; j++) {
5248
0
            if (lp->_class[j]->name != NULL &&
5249
0
                strlen(lp->_class[j]->name) > 0) {
5250
0
              wms_layer = MS_FALSE;
5251
0
              break;
5252
0
            }
5253
0
          }
5254
0
        }
5255
0
      } else
5256
0
        lp->status = MS_OFF;
5257
0
    }
5258
5259
    /* free the stuff used for nested layers */
5260
0
    for (int i = 0; i < map->numlayers; i++) {
5261
0
      if (numNestedGroups[i] > 0) {
5262
0
        msFreeCharArray(nestedGroups[i], numNestedGroups[i]);
5263
0
      }
5264
0
    }
5265
0
    free(nestedGroups);
5266
0
    free(numNestedGroups);
5267
0
    free(isUsedInNestedGroup);
5268
5269
0
    if (nLayers == 0) {
5270
0
      msSetErrorWithStatus(
5271
0
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5272
0
          "Invalid layer given in the LAYER parameter. A layer might be disabled for \
5273
0
this request. Check wms/ows_enable_request settings.",
5274
0
          "msWMSGetLegendGraphic()");
5275
0
      return msWMSException(map, nVersion, "LayerNotDefined",
5276
0
                            wms_exception_format);
5277
0
    }
5278
5279
    /* if SCALE was provided in request, calculate an extent and use a default
5280
     * width and height */
5281
0
    if (psScale != NULL) {
5282
0
      double scale, cellsize;
5283
5284
0
      scale = atof(psScale);
5285
0
      map->width = 600;
5286
0
      map->height = 600;
5287
5288
0
      cellsize = (scale / map->resolution) / msInchesPerUnit(map->units, 0.0);
5289
5290
0
      map->extent.maxx = cellsize * map->width / 2.0;
5291
0
      map->extent.maxy = cellsize * map->height / 2.0;
5292
0
      map->extent.minx = -map->extent.maxx;
5293
0
      map->extent.miny = -map->extent.maxy;
5294
0
    }
5295
5296
    /* It's a valid Cascading WMS GetLegendGraphic request */
5297
0
    if (wms_layer)
5298
0
      return msWMSLayerExecuteRequest(map, 1, 0, 0, 0, NULL,
5299
0
                                      WMS_GETLEGENDGRAPHIC);
5300
5301
    /*if STYLE is set, check if it is a valid style (valid = at least one
5302
    of the classes have a the group value equals to the style */
5303
    /*style is only validated when there is only one layer #3411*/
5304
0
    if (nLayers == 1 && pszStyle && strlen(pszStyle) > 0 &&
5305
0
        strcasecmp(pszStyle, "default") != 0) {
5306
0
      bool found = false;
5307
0
      for (int i = 0; i < GET_LAYER(map, iLayerIndex)->numclasses; i++) {
5308
0
        if (GET_LAYER(map, iLayerIndex)->_class[i]->group &&
5309
0
            strcasecmp(GET_LAYER(map, iLayerIndex)->_class[i]->group,
5310
0
                       pszStyle) == 0) {
5311
0
          found = true;
5312
0
          break;
5313
0
        }
5314
0
      }
5315
5316
0
      if (!found) {
5317
0
        msSetErrorWithStatus(
5318
0
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5319
0
            "style used in the STYLE parameter is not defined on the layer.",
5320
0
            "msWMSGetLegendGraphic()");
5321
0
        return msWMSException(map, nVersion, "StyleNotDefined",
5322
0
                              wms_exception_format);
5323
0
      } else {
5324
0
        msFree(GET_LAYER(map, iLayerIndex)->classgroup);
5325
0
        GET_LAYER(map, iLayerIndex)->classgroup = msStrdup(pszStyle);
5326
0
      }
5327
0
    }
5328
0
  } else {
5329
    /* extract the parameters we need */
5330
0
    for (int i = 0; i < numentries; i++) {
5331
0
      if (strcasecmp(names[i], "FORMAT") == 0)
5332
0
        pszFormat = values[i];
5333
0
      else if (strcasecmp(names[i], "RULE") == 0)
5334
0
        psRule = values[i];
5335
0
    }
5336
0
  }
5337
  /* validate format */
5338
5339
  /*check to see if a predefined list is given*/
5340
0
  format_list = msOWSLookupMetadata(&(map->web.metadata), "M",
5341
0
                                    "getlegendgraphic_formatlist");
5342
0
  if (format_list) {
5343
0
    psFormat = msOwsIsOutputFormatValid(map, pszFormat, &(map->web.metadata),
5344
0
                                        "M", "getlegendgraphic_formatlist");
5345
0
    if (psFormat == NULL) {
5346
0
      msSetErrorWithStatus(MS_IMGERR, MS_HTTP_400_BAD_REQUEST,
5347
0
                           "Unsupported output format (%s).",
5348
0
                           "msWMSGetLegendGraphic()", pszFormat);
5349
0
      return msWMSException(map, nVersion, "InvalidFormat",
5350
0
                            wms_exception_format);
5351
0
    }
5352
0
  } else {
5353
0
    psFormat = msSelectOutputFormat(map, pszFormat);
5354
0
    if (psFormat == NULL || !MS_RENDERER_PLUGIN(psFormat))
5355
    /* msDrawLegend and msCreateLegendIcon both switch the alpha channel to gd
5356
     ** after creation, so they can be called here without going through
5357
     ** the msAlphaGD2AGG functions */
5358
0
    {
5359
0
      msSetErrorWithStatus(MS_IMGERR, MS_HTTP_400_BAD_REQUEST,
5360
0
                           "Unsupported output format (%s).",
5361
0
                           "msWMSGetLegendGraphic()", pszFormat);
5362
0
      return msWMSException(map, nVersion, "InvalidFormat",
5363
0
                            wms_exception_format);
5364
0
    }
5365
0
  }
5366
0
  msApplyOutputFormat(&(map->outputformat), psFormat, MS_NOOVERRIDE);
5367
5368
0
  if (psRule == NULL || nLayers > 1) {
5369
0
    if (psScale != NULL) {
5370
      /* Scale-dependent legend. map->scaledenom will be calculated in
5371
       * msDrawLegend */
5372
0
      img = msDrawLegend(map, MS_FALSE, NULL);
5373
0
    } else {
5374
      /* Scale-independent legend */
5375
0
      img = msDrawLegend(map, MS_TRUE, hittest);
5376
0
    }
5377
0
  } else {
5378
    /* RULE was specified. Get the class corresponding to the RULE */
5379
    /* (RULE = class->name) */
5380
    /* TBT FIXME? also check the map->scaledenom if multiple scale-dependant
5381
     * classes with same name */
5382
5383
0
    layerObj *lp = GET_LAYER(map, iLayerIndex);
5384
0
    int i;
5385
0
    for (i = 0; i < lp->numclasses; i++) {
5386
0
      if (lp->classgroup &&
5387
0
          (lp->_class[i]->group == NULL ||
5388
0
           strcasecmp(lp->_class[i]->group, lp->classgroup) != 0))
5389
0
        continue;
5390
5391
0
      if (lp->_class[i]->name && strlen(lp->_class[i]->name) > 0 &&
5392
0
          strcasecmp(lp->_class[i]->name, psRule) == 0)
5393
0
        break;
5394
0
    }
5395
0
    if (i < lp->numclasses) {
5396
      /* set the map legend parameters */
5397
0
      if (nWidth < 0) {
5398
0
        if (map->legend.keysizex > 0)
5399
0
          nWidth = map->legend.keysizex;
5400
0
        else
5401
0
          nWidth = 20; /* default values : this in not defined in the specs */
5402
0
      }
5403
0
      if (nHeight < 0) {
5404
0
        if (map->legend.keysizey > 0)
5405
0
          nHeight = map->legend.keysizey;
5406
0
        else
5407
0
          nHeight = 20;
5408
0
      }
5409
5410
0
      if (psScale != NULL) {
5411
        /* Scale-dependent legend. calculate map->scaledenom */
5412
0
        map->cellsize = msAdjustExtent(&(map->extent), map->width, map->height);
5413
0
        msCalculateScale(map->extent, map->units, map->width, map->height,
5414
0
                         map->resolution, &map->scaledenom);
5415
0
        img = msCreateLegendIcon(map, lp, lp->_class[i], nWidth, nHeight,
5416
0
                                 MS_FALSE);
5417
0
      } else {
5418
        /* Scale-independent legend */
5419
0
        img = msCreateLegendIcon(map, lp, lp->_class[i], nWidth, nHeight,
5420
0
                                 MS_TRUE);
5421
0
      }
5422
0
    }
5423
0
    if (img == NULL) {
5424
0
      msSetErrorWithStatus(MS_IMGERR, MS_HTTP_400_BAD_REQUEST,
5425
0
                           "Unavailable RULE (%s).", "msWMSGetLegendGraphic()",
5426
0
                           psRule);
5427
0
      return msWMSException(map, nVersion, "InvalidRule", wms_exception_format);
5428
0
    }
5429
0
  }
5430
5431
0
  if (img == NULL)
5432
0
    return msWMSException(map, nVersion, NULL, wms_exception_format);
5433
5434
0
  msIO_setHeader("Content-Type", "%s", MS_IMAGE_MIME_TYPE(map->outputformat));
5435
0
  msIO_sendHeaders();
5436
0
  if (msSaveImage(map, img, NULL) != MS_SUCCESS)
5437
0
    return msWMSException(map, nVersion, NULL, wms_exception_format);
5438
5439
0
  msFreeImage(img);
5440
5441
0
  return (MS_SUCCESS);
5442
0
}
5443
5444
/*
5445
** msWMSGetContentDependentLegend()
5446
*/
5447
static int msWMSGetContentDependentLegend(mapObj *map, int nVersion,
5448
                                          char **names, char **values,
5449
                                          int numentries,
5450
                                          const char *wms_exception_format,
5451
0
                                          owsRequestObj *ows_request) {
5452
5453
  /* turn off layer if WMS GetMap is not enabled */
5454
0
  for (int i = 0; i < map->numlayers; i++)
5455
0
    if (!msIntegerInArray(GET_LAYER(map, i)->index, ows_request->enabled_layers,
5456
0
                          ows_request->numlayers))
5457
0
      GET_LAYER(map, i)->status = MS_OFF;
5458
5459
0
  map_hittest hittest;
5460
0
  initMapHitTests(map, &hittest);
5461
0
  int status = msHitTestMap(map, &hittest);
5462
0
  if (status == MS_SUCCESS) {
5463
0
    status = msWMSLegendGraphic(map, nVersion, names, values, numentries,
5464
0
                                wms_exception_format, ows_request, &hittest);
5465
0
  }
5466
0
  freeMapHitTests(map, &hittest);
5467
0
  if (status != MS_SUCCESS) {
5468
0
    return msWMSException(map, nVersion, NULL, wms_exception_format);
5469
0
  } else {
5470
0
    return MS_SUCCESS;
5471
0
  }
5472
0
}
5473
5474
/*
5475
** msWMSGetStyles() : return an SLD document for all layers that
5476
** have a status set to on or default.
5477
*/
5478
static int msWMSGetStyles(mapObj *map, int nVersion, char **names,
5479
                          char **values, int numentries,
5480
                          const char *wms_exception_format)
5481
5482
0
{
5483
0
  bool validlayer = false;
5484
5485
0
  char ***nestedGroups =
5486
0
      (char ***)msSmallCalloc(map->numlayers, sizeof(char **));
5487
0
  int *numNestedGroups = (int *)msSmallCalloc(map->numlayers, sizeof(int));
5488
0
  int *isUsedInNestedGroup = (int *)msSmallCalloc(map->numlayers, sizeof(int));
5489
0
  msWMSPrepareNestedGroups(map, nVersion, nestedGroups, numNestedGroups,
5490
0
                           isUsedInNestedGroup);
5491
5492
0
  const char *sldenabled =
5493
0
      msOWSLookupMetadata(&(map->web.metadata), "MO", "sld_enabled");
5494
0
  if (sldenabled == NULL)
5495
0
    sldenabled = "true";
5496
5497
0
  for (int i = 0; i < numentries; i++) {
5498
    /* getMap parameters */
5499
0
    if (strcasecmp(names[i], "LAYERS") == 0) {
5500
0
      const auto wmslayers = msStringSplit(values[i], ',');
5501
0
      if (wmslayers.empty()) {
5502
0
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5503
0
                             "At least one layer name required in LAYERS.",
5504
0
                             "msWMSGetStyles()");
5505
0
        return msWMSException(map, nVersion, NULL, wms_exception_format);
5506
0
      }
5507
0
      for (int j = 0; j < map->numlayers; j++)
5508
0
        GET_LAYER(map, j)->status = MS_OFF;
5509
5510
0
      for (int k = 0; k < static_cast<int>(wmslayers.size()); k++) {
5511
0
        const auto &wmslayer = wmslayers[k];
5512
0
        for (int j = 0; j < map->numlayers; j++) {
5513
0
          if ((map->name && strcasecmp(map->name, wmslayer.c_str()) == 0) ||
5514
0
              (GET_LAYER(map, j)->name &&
5515
0
               strcasecmp(GET_LAYER(map, j)->name, wmslayer.c_str()) == 0) ||
5516
0
              (GET_LAYER(map, j)->group &&
5517
0
               strcasecmp(GET_LAYER(map, j)->group, wmslayer.c_str()) == 0) ||
5518
0
              ((numNestedGroups[j] > 0) &&
5519
0
               msStringInArray(wmslayer.c_str(), nestedGroups[j],
5520
0
                               numNestedGroups[j]))) {
5521
0
            GET_LAYER(map, j)->status = MS_ON;
5522
0
            validlayer = true;
5523
0
          }
5524
0
        }
5525
0
      }
5526
0
    }
5527
5528
0
    else if (strcasecmp(names[i], "SLD") == 0 && values[i] &&
5529
0
             strlen(values[i]) > 0 && strcasecmp(sldenabled, "true") == 0) {
5530
0
      msSLDApplySLDURL(map, values[i], -1, NULL, NULL);
5531
0
    }
5532
5533
0
    else if (strcasecmp(names[i], "SLD_BODY") == 0 && values[i] &&
5534
0
             strlen(values[i]) > 0 && strcasecmp(sldenabled, "true") == 0) {
5535
0
      msSLDApplySLD(map, values[i], -1, NULL, NULL);
5536
0
    }
5537
0
  }
5538
5539
  /* free the stuff used for nested layers */
5540
0
  for (int i = 0; i < map->numlayers; i++) {
5541
0
    if (numNestedGroups[i] > 0) {
5542
0
      msFreeCharArray(nestedGroups[i], numNestedGroups[i]);
5543
0
    }
5544
0
  }
5545
0
  free(nestedGroups);
5546
0
  free(numNestedGroups);
5547
0
  free(isUsedInNestedGroup);
5548
5549
  /* validate all layers given. If an invalid layer is sent, return an
5550
   * exception. */
5551
0
  if (!validlayer) {
5552
0
    msSetErrorWithStatus(
5553
0
        MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5554
0
        "Invalid layer(s) given in the LAYERS parameter. A layer might be disabled for \
5555
0
this request. Check wms/ows_enable_request settings.",
5556
0
        "msWMSGetStyles()");
5557
0
    return msWMSException(map, nVersion, "LayerNotDefined",
5558
0
                          wms_exception_format);
5559
0
  }
5560
5561
0
  char *sld = NULL;
5562
0
  if (nVersion <= OWS_1_1_1) {
5563
0
    msIO_setHeader("Content-Type",
5564
0
                   "application/vnd.ogc.sld+xml; charset=UTF-8");
5565
0
    msIO_sendHeaders();
5566
0
    sld = msSLDGenerateSLD(map, -1, "1.0.0");
5567
0
  } else {
5568
    /*for wms 1.3.0 generate a 1.1 sld*/
5569
0
    msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
5570
0
    msIO_sendHeaders();
5571
0
    sld = msSLDGenerateSLD(map, -1, "1.1.0");
5572
0
  }
5573
0
  if (sld) {
5574
0
    msIO_printf("%s\n", sld);
5575
0
    free(sld);
5576
0
  }
5577
5578
0
  return (MS_SUCCESS);
5579
0
}
5580
5581
0
int msWMSGetSchemaExtension(mapObj *map) {
5582
0
  char *schemalocation = msEncodeHTMLEntities(msOWSGetSchemasLocation(map));
5583
5584
0
  msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
5585
0
  msIO_sendHeaders();
5586
5587
0
  msIO_printf("<?xml version='1.0' encoding=\"UTF-8\"?>\n");
5588
0
  msIO_printf("<schema xmlns=\"http://www.w3.org/2001/XMLSchema\" "
5589
0
              "xmlns:wms=\"http://www.opengis.net/wms\" "
5590
0
              "xmlns:ms=\"http://mapserver.gis.umn.edu/mapserver\" "
5591
0
              "targetNamespace=\"http://mapserver.gis.umn.edu/mapserver\" "
5592
0
              "elementFormDefault=\"qualified\" version=\"1.0.0\">\n");
5593
0
  msIO_printf("  <import namespace=\"http://www.opengis.net/wms\" "
5594
0
              "schemaLocation=\"%s/wms/1.3.0/capabilities_1_3_0.xsd\"/>\n",
5595
0
              schemalocation);
5596
0
  msIO_printf("  <element name=\"GetStyles\" type=\"wms:OperationType\" "
5597
0
              "substitutionGroup=\"wms:_ExtendedOperation\"/>\n");
5598
0
  msIO_printf("</schema>");
5599
5600
0
  free(schemalocation);
5601
5602
0
  return (MS_SUCCESS);
5603
0
}
5604
5605
#endif /* USE_WMS_SVR */
5606
5607
/*
5608
** msWMSDispatch() is the entry point for WMS requests.
5609
** - If this is a valid request then it is processed and MS_SUCCESS is returned
5610
**   on success, or MS_FAILURE on failure.
5611
** - If this does not appear to be a valid WMS request then MS_DONE
5612
**   is returned and MapServer is expected to process this as a regular
5613
**   MapServer request.
5614
*/
5615
int msWMSDispatch(mapObj *map, cgiRequestObj *req, owsRequestObj *ows_request,
5616
0
                  int force_wms_mode) {
5617
0
#ifdef USE_WMS_SVR
5618
0
  int nVersion = OWS_VERSION_NOTSET;
5619
0
  const char *version = NULL, *request = NULL, *service = NULL, *format = NULL,
5620
0
             *updatesequence = NULL, *language = NULL;
5621
0
  const char *wms_exception_format = NULL;
5622
5623
  /*
5624
  ** Process Params common to all requests
5625
  */
5626
  /* VERSION (WMTVER in 1.0.0) and REQUEST must be present in a valid request */
5627
0
  for (int i = 0; i < req->NumParams; i++) {
5628
0
    if (strcasecmp(req->ParamNames[i], "VERSION") == 0)
5629
0
      version = req->ParamValues[i];
5630
0
    else if (strcasecmp(req->ParamNames[i], "WMTVER") == 0 && version == NULL)
5631
0
      version = req->ParamValues[i];
5632
0
    else if (strcasecmp(req->ParamNames[i], "UPDATESEQUENCE") == 0)
5633
0
      updatesequence = req->ParamValues[i];
5634
0
    else if (strcasecmp(req->ParamNames[i], "REQUEST") == 0)
5635
0
      request = req->ParamValues[i];
5636
0
    else if (strcasecmp(req->ParamNames[i], "EXCEPTIONS") == 0)
5637
0
      wms_exception_format = req->ParamValues[i];
5638
0
    else if (strcasecmp(req->ParamNames[i], "SERVICE") == 0)
5639
0
      service = req->ParamValues[i];
5640
0
    else if (strcasecmp(req->ParamNames[i], "FORMAT") == 0)
5641
0
      format = req->ParamValues[i];
5642
0
    else if (strcasecmp(req->ParamNames[i], "LANGUAGE") == 0 &&
5643
0
             msOWSLookupMetadata(&(map->web.metadata), "MO",
5644
0
                                 "inspire_capabilities"))
5645
0
      language = req->ParamValues[i];
5646
0
  }
5647
5648
  /* If SERVICE is specified then it MUST be "WMS" */
5649
0
  if (service != NULL && strcasecmp(service, "WMS") != 0)
5650
0
    return MS_DONE; /* Not a WMS request */
5651
5652
0
  nVersion = msOWSParseVersionString(version);
5653
0
  if (nVersion == OWS_VERSION_BADFORMAT) {
5654
    /* Invalid version format. msSetError() has been called by
5655
     * msOWSParseVersionString() and we return the error as an exception
5656
     */
5657
0
    return msWMSException(map, OWS_VERSION_NOTSET, NULL, wms_exception_format);
5658
0
  }
5659
5660
  /*
5661
  ** GetCapbilities request needs the service parameter defined as WMS:
5662
  see section 7.1.3.2 wms 1.1.1 specs for decsription.
5663
  */
5664
0
  if (request && service == NULL &&
5665
0
      (strcasecmp(request, "capabilities") == 0 ||
5666
0
       strcasecmp(request, "GetCapabilities") == 0) &&
5667
0
      (nVersion >= OWS_1_0_7 || nVersion == OWS_VERSION_NOTSET)) {
5668
0
    if (force_wms_mode) {
5669
0
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5670
0
                           "Required SERVICE parameter missing.",
5671
0
                           "msWMSDispatch");
5672
0
      return msWMSException(map, nVersion, "ServiceNotDefined",
5673
0
                            wms_exception_format);
5674
0
    } else
5675
0
      return MS_DONE;
5676
0
  }
5677
5678
  /*
5679
  ** Dispatch request... we should probably do some validation on VERSION here
5680
  ** vs the versions we actually support.
5681
  */
5682
0
  if (request && (strcasecmp(request, "capabilities") == 0 ||
5683
0
                  strcasecmp(request, "GetCapabilities") == 0)) {
5684
0
    const char *enable_request;
5685
0
    int globally_enabled, disabled = MS_FALSE;
5686
5687
0
    if (nVersion == OWS_VERSION_NOTSET) {
5688
0
      version = msOWSLookupMetadata(&(map->web.metadata), "M",
5689
0
                                    "getcapabilities_version");
5690
0
      if (version)
5691
0
        nVersion = msOWSParseVersionString(version);
5692
0
      else
5693
0
        nVersion =
5694
0
            OWS_1_3_0; /* VERSION is optional with getCapabilities only */
5695
0
    }
5696
5697
0
    if (msOWSMakeAllLayersUnique(map) != MS_SUCCESS)
5698
0
      return msWMSException(map, nVersion, NULL, wms_exception_format);
5699
5700
0
    msOWSRequestLayersEnabled(map, "M", "GetCapabilities", ows_request);
5701
5702
0
    enable_request =
5703
0
        msOWSLookupMetadata(&map->web.metadata, "OM", "enable_request");
5704
0
    globally_enabled =
5705
0
        msOWSParseRequestMetadata(enable_request, "GetCapabilities", &disabled);
5706
5707
0
    if (ows_request->numlayers == 0 && !globally_enabled) {
5708
0
      msSetErrorWithStatus(
5709
0
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5710
0
          "WMS request not enabled. Check wms/ows_enable_request settings.",
5711
0
          "msWMSGetCapabilities()");
5712
0
      return msWMSException(map, nVersion, NULL, wms_exception_format);
5713
0
    }
5714
0
    msAcquireLock(TLOCK_WxS);
5715
0
    const int status =
5716
0
        msWMSGetCapabilities(map, nVersion, req, ows_request, updatesequence,
5717
0
                             wms_exception_format, language);
5718
0
    msReleaseLock(TLOCK_WxS);
5719
0
    return status;
5720
0
  } else if (request && (strcasecmp(request, "context") == 0 ||
5721
0
                         strcasecmp(request, "GetContext") == 0)) {
5722
    /* Return a context document with all layers in this mapfile
5723
     * This is not a standard WMS request.
5724
     * __TODO__ The real implementation should actually return only context
5725
     * info for selected layers in the LAYERS parameter.
5726
     */
5727
0
    const char *getcontext_enabled;
5728
0
    getcontext_enabled =
5729
0
        msOWSLookupMetadata(&(map->web.metadata), "MO", "getcontext_enabled");
5730
5731
0
    if (nVersion != OWS_VERSION_NOTSET) {
5732
      /* VERSION, if specified, is Map Context version, not WMS version */
5733
      /* Pass it via wms_context_version metadata */
5734
0
      char szVersion[OWS_VERSION_MAXLEN];
5735
0
      msInsertHashTable(&(map->web.metadata), "wms_context_version",
5736
0
                        msOWSGetVersionString(nVersion, szVersion));
5737
0
    }
5738
    /* Now set version to 1.1.1 for error handling purposes */
5739
0
    nVersion = OWS_1_1_1;
5740
5741
0
    if (getcontext_enabled == NULL || atoi(getcontext_enabled) == 0) {
5742
0
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5743
0
                           "GetContext not enabled on this server.",
5744
0
                           "msWMSDispatch()");
5745
0
      return msWMSException(map, nVersion, NULL, wms_exception_format);
5746
0
    }
5747
5748
0
    if (msOWSMakeAllLayersUnique(map) != MS_SUCCESS)
5749
0
      return msWMSException(map, nVersion, NULL, wms_exception_format);
5750
5751
0
    msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
5752
0
    msIO_sendHeaders();
5753
5754
0
    if (msWriteMapContext(map, stdout) != MS_SUCCESS)
5755
0
      return msWMSException(map, nVersion, NULL, wms_exception_format);
5756
    /* Request completed */
5757
0
    return MS_SUCCESS;
5758
0
  } else if (request && strcasecmp(request, "GetMap") == 0 && format &&
5759
0
             strcasecmp(format, "image/txt") == 0) {
5760
    /* Until someone adds full support for ASCII graphics this should do. ;) */
5761
0
    msIO_setHeader("Content-Type", "text/plain; charset=UTF-8");
5762
0
    msIO_sendHeaders();
5763
0
    msIO_printf(".\n               ,,ggddY\"\"\"Ybbgg,,\n          ,agd888b,_ "
5764
0
                "\"Y8, ___'\"\"Ybga,\n       ,gdP\"\"88888888baa,.\"\"8b    \""
5765
0
                "888g,\n     ,dP\"     ]888888888P'  \"Y     '888Yb,\n   ,dP\""
5766
0
                "      ,88888888P\"  db,       \"8P\"\"Yb,\n  ,8\"       ,8888"
5767
0
                "88888b, d8888a           \"8,\n ,8'        d88888888888,88P\""
5768
0
                "' a,          '8,\n,8'         88888888888888PP\"  \"\"      "
5769
0
                "     '8,\nd'          I88888888888P\"                   'b\n8"
5770
0
                "           '8\"88P\"\"Y8P'                      8\n8         "
5771
0
                "   Y 8[  _ \"                        8\n8              \"Y8d8"
5772
0
                "b  \"Y a                   8\n8                 '\"\"8d,   __"
5773
0
                "                 8\nY,                    '\"8bd888b,        "
5774
0
                "     ,P\n'8,                     ,d8888888baaa       ,8'\n '8"
5775
0
                ",                    888888888888'      ,8'\n  '8a           "
5776
0
                "        \"8888888888I      a8'\n   'Yba                  'Y88"
5777
0
                "88888P'    adP'\n     \"Yba                 '888888P'   adY\""
5778
0
                "\n       '\"Yba,             d8888P\" ,adP\"' \n          '\""
5779
0
                "Y8baa,      ,d888P,ad8P\"' \n               ''\"\"YYba8888P\""
5780
0
                "\"''\n");
5781
0
    return MS_SUCCESS;
5782
0
  }
5783
5784
  /* If SERVICE, VERSION and REQUEST not included than this isn't a WMS req*/
5785
0
  if (service == NULL && nVersion == OWS_VERSION_NOTSET && request == NULL)
5786
0
    return MS_DONE; /* Not a WMS request */
5787
5788
  /* VERSION *and* REQUEST required by both getMap and getFeatureInfo */
5789
0
  if (nVersion == OWS_VERSION_NOTSET) {
5790
0
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5791
0
                         "Incomplete WMS request: VERSION parameter missing",
5792
0
                         "msWMSDispatch()");
5793
0
    return msWMSException(map, OWS_VERSION_NOTSET, NULL, wms_exception_format);
5794
0
  }
5795
5796
  /*check if the version is one of the supported vcersions*/
5797
0
  if (nVersion != OWS_1_0_0 && nVersion != OWS_1_0_6 && nVersion != OWS_1_0_7 &&
5798
0
      nVersion != OWS_1_1_0 && nVersion != OWS_1_1_1 && nVersion != OWS_1_3_0) {
5799
0
    msSetErrorWithStatus(
5800
0
        MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5801
0
        "Invalid WMS version: VERSION %s is not supported. Supported "
5802
0
        "versions are 1.0.0, 1.0.6, 1.0.7, 1.1.0, 1.1.1, 1.3.0",
5803
0
        "msWMSDispatch()", version);
5804
0
    return msWMSException(map, OWS_VERSION_NOTSET, NULL, wms_exception_format);
5805
0
  }
5806
5807
0
  if (request == NULL) {
5808
0
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5809
0
                         "Incomplete WMS request: REQUEST parameter missing",
5810
0
                         "msWMSDispatch()");
5811
0
    return msWMSException(map, nVersion, NULL, wms_exception_format);
5812
0
  }
5813
5814
  /* hack !? The function can return MS_DONE ... be sure it's a wms request
5815
   * before checking the enabled layers */
5816
0
  if ((strcasecmp(request, "GetStyles") == 0) ||
5817
0
      (strcasecmp(request, "GetLegendGraphic") == 0) ||
5818
0
      (strcasecmp(request, "GetSchemaExtension") == 0) ||
5819
0
      (strcasecmp(request, "map") == 0 || strcasecmp(request, "GetMap") == 0) ||
5820
0
      (strcasecmp(request, "feature_info") == 0 ||
5821
0
       strcasecmp(request, "GetFeatureInfo") == 0) ||
5822
0
      (strcasecmp(request, "DescribeLayer") == 0)) {
5823
0
    const char *request_tmp;
5824
0
    if (strcasecmp(request, "map") == 0)
5825
0
      request_tmp = "GetMap";
5826
0
    else if (strcasecmp(request, "feature_info") == 0)
5827
0
      request_tmp = "GetFeatureInfo";
5828
0
    else
5829
0
      request_tmp = request;
5830
5831
0
    msOWSRequestLayersEnabled(map, "M", request_tmp, ows_request);
5832
0
    if (ows_request->numlayers == 0) {
5833
0
      msSetErrorWithStatus(
5834
0
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5835
0
          "WMS request not enabled. Check wms/ows_enable_request settings.",
5836
0
          "msWMSDispatch()");
5837
0
      return msWMSException(map, nVersion, NULL, wms_exception_format);
5838
0
    }
5839
0
  }
5840
5841
0
  if (msOWSMakeAllLayersUnique(map) != MS_SUCCESS)
5842
0
    return msWMSException(map, nVersion, NULL, wms_exception_format);
5843
5844
0
  bool isContentDependentLegend = false;
5845
0
  if (strcasecmp(request, "GetLegendGraphic") == 0) {
5846
    /*
5847
     * check for a BBOX in the request, in that case we have a content-dependant
5848
     * legend request, and should be following the GetMap path a bit more
5849
     */
5850
0
    bool found = false;
5851
0
    for (int i = 0; i < req->NumParams; i++) {
5852
0
      if (strcasecmp(req->ParamNames[i], "BBOX") == 0) {
5853
0
        if (req->ParamValues[i] && *req->ParamValues[i]) {
5854
0
          found = true;
5855
0
          break;
5856
0
        }
5857
0
      }
5858
0
    }
5859
0
    if (found) {
5860
0
      isContentDependentLegend = true;
5861
      /* getLegendGraphic uses LAYER= , we need to create a LAYERS= value that
5862
       * is identical we'll suppose that the client is conformat and hasn't
5863
       * included a LAYERS= parameter in its request */
5864
0
      for (int i = 0; i < req->NumParams; i++) {
5865
0
        if (strcasecmp(req->ParamNames[i], "LAYER") == 0) {
5866
0
          req->ParamNames[req->NumParams] = msStrdup("LAYERS");
5867
0
          req->ParamValues[req->NumParams] = msStrdup(req->ParamValues[i]);
5868
0
          req->NumParams++;
5869
0
        }
5870
0
      }
5871
0
    } else {
5872
0
      return msWMSLegendGraphic(map, nVersion, req->ParamNames,
5873
0
                                req->ParamValues, req->NumParams,
5874
0
                                wms_exception_format, ows_request, NULL);
5875
0
    }
5876
0
  }
5877
5878
0
  if (strcasecmp(request, "GetStyles") == 0)
5879
0
    return msWMSGetStyles(map, nVersion, req->ParamNames, req->ParamValues,
5880
0
                          req->NumParams, wms_exception_format);
5881
5882
0
  else if (request && strcasecmp(request, "GetSchemaExtension") == 0)
5883
0
    return msWMSGetSchemaExtension(map);
5884
5885
  /* getMap parameters are used by both getMap, getFeatureInfo, and content
5886
   * dependent legendgraphics */
5887
0
  if (strcasecmp(request, "map") == 0 || strcasecmp(request, "GetMap") == 0 ||
5888
0
      strcasecmp(request, "feature_info") == 0 ||
5889
0
      strcasecmp(request, "GetFeatureInfo") == 0 ||
5890
0
      strcasecmp(request, "DescribeLayer") == 0 || isContentDependentLegend) {
5891
5892
0
    const int status = msWMSLoadGetMapParams(
5893
0
        map, nVersion, req->ParamNames, req->ParamValues, req->NumParams,
5894
0
        wms_exception_format, request, ows_request);
5895
0
    if (status != MS_SUCCESS)
5896
0
      return status;
5897
0
  }
5898
5899
  /* This function owns validated_language, so remember to free it later*/
5900
0
  char *validated_language = msOWSGetLanguageFromList(map, "MO", language);
5901
0
  if (validated_language != NULL) {
5902
0
    msMapSetLanguageSpecificConnection(map, validated_language);
5903
0
  }
5904
0
  msFree(validated_language);
5905
5906
0
  if (strcasecmp(request, "map") == 0 || strcasecmp(request, "GetMap") == 0)
5907
0
    return msWMSGetMap(map, nVersion, req->ParamNames, req->ParamValues,
5908
0
                       req->NumParams, wms_exception_format, ows_request);
5909
0
  else if (strcasecmp(request, "feature_info") == 0 ||
5910
0
           strcasecmp(request, "GetFeatureInfo") == 0)
5911
0
    return msWMSFeatureInfo(map, nVersion, req->ParamNames, req->ParamValues,
5912
0
                            req->NumParams, wms_exception_format, ows_request);
5913
0
  else if (strcasecmp(request, "DescribeLayer") == 0) {
5914
0
    return msWMSDescribeLayer(map, nVersion, req->ParamNames, req->ParamValues,
5915
0
                              req->NumParams, wms_exception_format);
5916
0
  } else if (isContentDependentLegend) {
5917
0
    return msWMSGetContentDependentLegend(map, nVersion, req->ParamNames,
5918
0
                                          req->ParamValues, req->NumParams,
5919
0
                                          wms_exception_format, ows_request);
5920
0
  }
5921
5922
  /* Hummmm... incomplete or unsupported WMS request */
5923
0
  if (service != NULL && strcasecmp(service, "WMS") == 0) {
5924
0
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5925
0
                         "Incomplete or unsupported WMS request",
5926
0
                         "msWMSDispatch()");
5927
0
    return msWMSException(map, nVersion, NULL, wms_exception_format);
5928
0
  } else
5929
0
    return MS_DONE; /* Not a WMS request */
5930
#else
5931
  msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5932
                       "WMS server support is not available.",
5933
                       "msWMSDispatch()");
5934
  return (MS_FAILURE);
5935
#endif
5936
0
}