• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geographika / mapserver / 13697752106

06 Mar 2025 11:34AM UTC coverage: 41.364% (-0.03%) from 41.392%
13697752106

push

github

geographika
Mark zip output tests as TO_FIX as output is different on each run

61755 of 149297 relevant lines covered (41.36%)

24825.13 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.27
/src/mapwms.cpp
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✔
72
                          const char *wms_exception_format) {
73
  char *schemalocation = NULL;
74

75
  /* Default to WMS 1.3.0 exceptions if version not set yet */
76
  if (nVersion <= 0)
72✔
77
    nVersion = OWS_1_3_0;
78

79
  /* get scheam location */
80
  schemalocation = msEncodeHTMLEntities(msOWSGetSchemasLocation(map));
72✔
81

82
  /* Establish default exception format depending on VERSION */
83
  if (wms_exception_format == NULL) {
72✔
84
    if (nVersion <= OWS_1_0_0)
58✔
85
      wms_exception_format = "INIMAGE"; /* WMS 1.0.0 */
86
    else if (nVersion <= OWS_1_0_7)
58✔
87
      wms_exception_format = "SE_XML"; /* WMS 1.0.1 to 1.0.7 */
88
    else if (nVersion <= OWS_1_1_1)
58✔
89
      wms_exception_format =
90
          "application/vnd.ogc.se_xml"; /* WMS 1.1.0 and later */
91
    else
92
      wms_exception_format = "text/xml";
93
  }
94

95
  errorObj *ms_error = msGetErrorObj();
72✔
96
  if (ms_error && ms_error->http_status[0]) {
72✔
97
    msIO_setHeader("Status", "%s", ms_error->http_status);
1✔
98
  }
99

100
  if (strcasecmp(wms_exception_format, "INIMAGE") == 0 ||
72✔
101
      strcasecmp(wms_exception_format, "BLANK") == 0 ||
69✔
102
      strcasecmp(wms_exception_format, "application/vnd.ogc.se_inimage") == 0 ||
67✔
103
      strcasecmp(wms_exception_format, "application/vnd.ogc.se_blank") == 0) {
67✔
104
    int blank = 0;
105

106
    if (strcasecmp(wms_exception_format, "BLANK") == 0 ||
5✔
107
        strcasecmp(wms_exception_format, "application/vnd.ogc.se_blank") == 0) {
3✔
108
      blank = 1;
109
    }
110

111
    msWriteErrorImage(map, NULL, blank);
5✔
112

113
  } else if (strcasecmp(wms_exception_format, "WMS_XML") ==
72✔
114
             0) { /* Only in V1.0.0 */
115
    msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
×
116
    msIO_sendHeaders();
×
117

118
    msIO_printf("<WMTException version=\"1.0.0\">\n");
×
119
    msWriteErrorXML(stdout);
×
120
    msIO_printf("</WMTException>\n");
×
121
  } 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
  {
124
    if (nVersion <= OWS_1_0_7) {
67✔
125
      /* In V1.0.1 to 1.0.7, the MIME type was text/xml */
126
      msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
×
127
      msIO_sendHeaders();
×
128

129
      msIO_printf(
×
130
          "<?xml version='1.0' encoding=\"UTF-8\" standalone=\"no\" ?>\n");
131
      msIO_printf(
×
132
          "<!DOCTYPE ServiceExceptionReport SYSTEM "
133
          "\"http://www.digitalearth.gov/wmt/xml/exception_1_0_1.dtd\">\n");
134

135
      msIO_printf("<ServiceExceptionReport version=\"1.0.1\">\n");
×
136
    } else if (nVersion <= OWS_1_1_0) {
67✔
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
      msIO_setHeader("Content-Type",
29✔
140
                     "application/vnd.ogc.se_xml; charset=UTF-8");
141
      msIO_sendHeaders();
29✔
142

143
      msIO_printf(
29✔
144
          "<?xml version='1.0' encoding=\"UTF-8\" standalone=\"no\" ?>\n");
145

146
      msIO_printf("<!DOCTYPE ServiceExceptionReport SYSTEM "
29✔
147
                  "\"%s/wms/1.1.0/exception_1_1_0.dtd\">\n",
148
                  schemalocation);
149

150
      msIO_printf("<ServiceExceptionReport version=\"1.1.0\">\n");
29✔
151
    } else if (nVersion <= OWS_1_1_1) { /* 1.1.1 */
38✔
152
      msIO_setHeader("Content-Type",
22✔
153
                     "application/vnd.ogc.se_xml; charset=UTF-8");
154
      msIO_sendHeaders();
22✔
155

156
      msIO_printf(
22✔
157
          "<?xml version='1.0' encoding=\"UTF-8\" standalone=\"no\" ?>\n");
158
      msIO_printf("<!DOCTYPE ServiceExceptionReport SYSTEM "
22✔
159
                  "\"%s/wms/1.1.1/exception_1_1_1.dtd\">\n",
160
                  schemalocation);
161
      msIO_printf("<ServiceExceptionReport version=\"1.1.1\">\n");
22✔
162
    } else { /*1.3.0*/
163
      if (strcasecmp(wms_exception_format, "application/vnd.ogc.se_xml") == 0) {
16✔
164
        msIO_setHeader("Content-Type",
×
165
                       "application/vnd.ogc.se_xml; charset=UTF-8");
166
      } else {
167
        msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
16✔
168
      }
169
      msIO_sendHeaders();
16✔
170

171
      msIO_printf(
16✔
172
          "<?xml version='1.0' encoding=\"UTF-8\" standalone=\"no\" ?>\n");
173
      msIO_printf("<ServiceExceptionReport version=\"1.3.0\" "
16✔
174
                  "xmlns=\"http://www.opengis.net/ogc\" "
175
                  "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
176
                  "xsi:schemaLocation=\"http://www.opengis.net/ogc "
177
                  "%s/wms/1.3.0/exceptions_1_3_0.xsd\">\n",
178
                  schemalocation);
179
    }
180

181
    if (exception_code)
67✔
182
      msIO_printf("<ServiceException code=\"%s\">\n", exception_code);
50✔
183
    else
184
      msIO_printf("<ServiceException>\n");
17✔
185
    msWriteErrorXML(stdout);
67✔
186
    msIO_printf("</ServiceException>\n");
67✔
187
    msIO_printf("</ServiceExceptionReport>\n");
67✔
188
  }
189
  free(schemalocation);
72✔
190

191
  return MS_FAILURE; /* so that we can call 'return msWMSException();' anywhere
72✔
192
                      */
193
}
194

195
static bool msWMSSetTimePattern(const char *timepatternstring,
52✔
196
                                const char *timestring, bool checkonly) {
197
  if (timepatternstring && timestring) {
52✔
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
    const auto atimes = msStringSplit(timestring, ',');
52✔
203

204
    /* get the pattern to use */
205
    if (!atimes.empty()) {
52✔
206
      auto patterns = msStringSplit(timepatternstring, ',');
52✔
207
      for (auto &pattern : patterns) {
192✔
208
        msStringTrimBlanks(pattern);
140✔
209
        msStringTrimLeft(pattern);
140✔
210
      }
211

212
      for (const auto &atime : atimes) {
130✔
213
        const auto ranges = msStringSplit(atime.c_str(), '/');
82✔
214
        for (const auto &range : ranges) {
201✔
215
          bool match = false;
216
          for (const auto &pattern : patterns) {
135✔
217
            if (!pattern.empty()) {
131✔
218
              if (msTimeMatchPattern(range.c_str(), pattern.c_str()) ==
131✔
219
                  MS_TRUE) {
220
                if (!checkonly)
119✔
221
                  msSetLimitedPatternsToUse(pattern.c_str());
48✔
222
                match = true;
223
                break;
224
              }
225
            }
226
          }
227
          if (!match) {
228
            msSetErrorWithStatus(
4✔
229
                MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
230
                "Time value %s given does not match the time format pattern.",
231
                "msWMSSetTimePattern", range.c_str());
232
            return false;
233
          }
234
        }
235
      }
82✔
236
    }
52✔
237
  }
52✔
238

239
  return true;
240
}
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,
131✔
246
                          const char *wms_exception_format) {
247
  if (map) {
131✔
248

249
    const char *timepattern =
250
        msOWSLookupMetadata(&(map->web.metadata), "MO", "timeformat");
131✔
251

252
    for (int i = 0; i < map->numlayers; i++) {
1,582✔
253
      layerObj *lp = (GET_LAYER(map, i));
1,465✔
254
      if (lp->status != MS_ON && lp->status != MS_DEFAULT)
1,465✔
255
        continue;
1,334✔
256

257
      /* check if the layer is time aware */
258
      const char *timeextent =
259
          msOWSLookupMetadata(&(lp->metadata), "MO", "timeextent");
131✔
260
      const char *timefield =
261
          msOWSLookupMetadata(&(lp->metadata), "MO", "timeitem");
131✔
262
      const char *timedefault =
263
          msOWSLookupMetadata(&(lp->metadata), "MO", "timedefault");
131✔
264

265
      if (timeextent && timefield) {
131✔
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
        if (time == NULL || strlen(time) <= 0) {
131✔
270
          if (timedefault == NULL) {
5✔
271
            msSetErrorWithStatus(
2✔
272
                MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
273
                "No Time value was given, and no default time value defined.",
274
                "msWMSApplyTime");
275
            return msWMSException(map, version, "MissingDimensionValue",
2✔
276
                                  wms_exception_format);
2✔
277
          } else {
278
            if (msValidateTimeValue((char *)timedefault, timeextent) ==
3✔
279
                MS_FALSE) {
280
              msSetErrorWithStatus(
×
281
                  MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
282
                  "No Time value was given, and the default time value "
283
                  "%s is invalid or outside the time extent defined %s",
284
                  "msWMSApplyTime", timedefault, timeextent);
285
              /* return MS_FALSE; */
286
              return msWMSException(map, version, "InvalidDimensionValue",
×
287
                                    wms_exception_format);
×
288
            }
289
            msLayerSetTimeFilter(lp, timedefault, timefield);
3✔
290
          }
291
        } 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
          if (timepattern && time && strlen(time) > 0) {
126✔
303
            if (!msWMSSetTimePattern(timepattern, time, true))
32✔
304
              return msWMSException(map, version, "InvalidDimensionValue",
4✔
305
                                    wms_exception_format);
4✔
306
          }
307

308
          /* check if given time is in the range */
309
          if (msValidateTimeValue(time, timeextent) == MS_FALSE) {
122✔
310
            if (timedefault == NULL) {
8✔
311
              msSetErrorWithStatus(
8✔
312
                  MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
313
                  "Time value(s) %s given is invalid or outside the "
314
                  "time extent defined (%s).",
315
                  "msWMSApplyTime", time, timeextent);
316
              /* return MS_FALSE; */
317
              return msWMSException(map, version, "InvalidDimensionValue",
8✔
318
                                    wms_exception_format);
8✔
319
            } else {
320
              if (msValidateTimeValue((char *)timedefault, timeextent) ==
×
321
                  MS_FALSE) {
322
                msSetErrorWithStatus(
×
323
                    MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
324
                    "Time value(s) %s given is invalid or outside the time "
325
                    "extent defined (%s), and default time set is invalid (%s)",
326
                    "msWMSApplyTime", time, timeextent, timedefault);
327
                /* return MS_FALSE; */
328
                return msWMSException(map, version, "InvalidDimensionValue",
×
329
                                      wms_exception_format);
×
330
              } else
331
                msLayerSetTimeFilter(lp, timedefault, timefield);
×
332
            }
333

334
          } else {
335
            /* build the time string */
336
            msLayerSetTimeFilter(lp, time, timefield);
114✔
337
            timeextent = NULL;
338
          }
339
        }
340
      }
341
    }
342

343
    /* last argument is MS_FALSE to trigger a method call that set the patterns
344
       info. some drivers use it */
345
    if (timepattern && time && strlen(time) > 0) {
117✔
346
      if (!msWMSSetTimePattern(timepattern, time, false))
20✔
347
        return msWMSException(map, version, "InvalidDimensionValue",
×
348
                              wms_exception_format);
×
349
    }
350
  }
351

352
  return MS_SUCCESS;
353
}
354

355
/*
356
** Apply the FILTER parameter to layers (RFC118)
357
*/
358
static int msWMSApplyFilter(mapObj *map, int version, const char *filter,
13✔
359
                            int def_srs_needs_axis_swap,
360
                            const char *wms_exception_format,
361
                            owsRequestObj *ows_request) {
362
  // Empty filter should be ignored
363
  if (!filter || strlen(filter) == 0)
13✔
364
    return MS_SUCCESS;
365

366
  if (!map)
11✔
367
    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
  int numlayers = 0;
375
  for (int i = 0; i < map->numlayers; i++) {
43✔
376
    layerObj *lp = NULL;
377

378
    if (map->layerorder[i] != -1) {
32✔
379
      lp = (GET_LAYER(map, map->layerorder[i]));
32✔
380
      if (lp->status == MS_ON)
32✔
381
        numlayers++;
20✔
382
    }
383
  }
384

385
  /* -------------------------------------------------------------------- */
386
  /*      Parse the Filter parameter. If there are several Filter         */
387
  /*      parameters, each Filter is inside parentheses.                  */
388
  /* -------------------------------------------------------------------- */
389
  int numfilters = 0;
11✔
390
  char **paszFilters = NULL;
391
  if (filter[0] == '(') {
11✔
392
    paszFilters = FLTSplitFilters(filter, &numfilters);
8✔
393

394
  } else if (numlayers == 1) {
3✔
395
    numfilters = 1;
3✔
396
    paszFilters = (char **)msSmallMalloc(sizeof(char *) * numfilters);
3✔
397
    paszFilters[0] = msStrdup(filter);
3✔
398
  }
399

400
  if (numfilters != ows_request->numwmslayerargs) {
11✔
401
    msSetErrorWithStatus(
1✔
402
        MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
403
        "Wrong number of filter elements, one filter must be specified "
404
        "for each requested layer or groups.",
405
        "msWMSApplyFilter");
406
    msFreeCharArray(paszFilters, numfilters);
1✔
407
    return msWMSException(map, version, "InvalidParameterValue",
1✔
408
                          wms_exception_format);
409
  }
410

411
  /* We're good to go. Apply each filter to the corresponding layer */
412
  for (int i = 0; i < map->numlayers; i++) {
39✔
413
    layerObj *lp = NULL;
414

415
    if (map->layerorder[i] != -1)
29✔
416
      lp = (GET_LAYER(map, map->layerorder[i]));
29✔
417

418
    /* Only layers with STATUS ON were in the LAYERS request param.*/
419
    if (lp == NULL || lp->status != MS_ON)
29✔
420
      continue;
14✔
421

422
    const int curfilter = ows_request->layerwmsfilterindex[lp->index];
17✔
423

424
    /* Skip empty filters */
425
    assert(paszFilters);
426
    assert(curfilter >= 0 && curfilter < numfilters);
427
    if (paszFilters[curfilter][0] == '\0') {
17✔
428
      continue;
2✔
429
    }
430

431
    /* Force setting a template to enable query. */
432
    if (lp->_template == NULL)
15✔
433
      lp->_template = msStrdup("ttt.html");
14✔
434

435
    /* Parse filter */
436
    FilterEncodingNode *psNode = FLTParseFilterEncoding(paszFilters[curfilter]);
15✔
437
    if (!psNode) {
15✔
438
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
439
                           "Invalid or Unsupported FILTER : %s",
440
                           "msWMSApplyFilter()", paszFilters[curfilter]);
441
      msFreeCharArray(paszFilters, numfilters);
×
442
      return msWMSException(map, version, "InvalidParameterValue",
×
443
                            wms_exception_format);
×
444
    }
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
    if (version >= OWS_1_3_0)
15✔
450
      FLTDoAxisSwappingIfNecessary(map, psNode, def_srs_needs_axis_swap);
14✔
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
    std::string old_value_wfs_use_default_extent_for_getfeature;
507
    {
508
      const char *old_value_tmp = msLookupHashTable(
15✔
509
          &(lp->metadata), "wfs_use_default_extent_for_getfeature");
15✔
510
      if (old_value_tmp) {
15✔
511
        old_value_wfs_use_default_extent_for_getfeature = old_value_tmp;
512
        msRemoveHashTable(&(lp->metadata),
6✔
513
                          "wfs_use_default_extent_for_getfeature");
514
      }
515
    }
516

517
    msInsertHashTable(&(lp->metadata), "gml_wmsfilter_flag", "true");
15✔
518

519
    int ret = FLTApplyFilterToLayer(psNode, map, lp->index);
15✔
520

521
    msRemoveHashTable(&(lp->metadata), "gml_wmsfilter_flag");
15✔
522

523
    if (!old_value_wfs_use_default_extent_for_getfeature.empty()) {
15✔
524
      msInsertHashTable(
6✔
525
          &(lp->metadata), "wfs_use_default_extent_for_getfeature",
526
          old_value_wfs_use_default_extent_for_getfeature.c_str());
527
    }
528

529
    if (ret != MS_SUCCESS) {
15✔
530
      errorObj *ms_error = msGetErrorObj();
×
531

532
      if (ms_error->code != MS_NOTFOUND) {
×
533
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_500_INTERNAL_SERVER_ERROR,
×
534
                             "FLTApplyFilterToLayer() failed",
535
                             "msWMSApplyFilter()");
536
        FLTFreeFilterEncodingNode(psNode);
×
537
        msFreeCharArray(paszFilters, numfilters);
×
538
        return msWMSException(map, version, "InvalidParameterValue",
×
539
                              wms_exception_format);
540
      }
541
    }
542

543
    FLTFreeFilterEncodingNode(psNode);
15✔
544

545
  } /* for */
546

547
  msFreeCharArray(paszFilters, numfilters);
10✔
548

549
  return MS_SUCCESS;
550
}
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 */,
874✔
566
                                     char ***nestedGroups, int *numNestedGroups,
567
                                     int *isUsedInNestedGroup) {
568
  // Create set to hold unique groups
569
  std::set<std::string> uniqgroups;
570

571
  for (int i = 0; i < map->numlayers; i++) {
7,026✔
572
    nestedGroups[i] = NULL;     /* default */
6,152✔
573
    numNestedGroups[i] = 0;     /* default */
6,152✔
574
    isUsedInNestedGroup[i] = 0; /* default */
6,152✔
575

576
    const char *groups = msOWSLookupMetadata(&(GET_LAYER(map, i)->metadata),
6,152✔
577
                                             "MO", "layer_group");
578
    if ((groups != NULL) && (strlen(groups) != 0)) {
6,152✔
579
      if (GET_LAYER(map, i)->group != NULL &&
1,633✔
580
          strlen(GET_LAYER(map, i)->group) != 0) {
×
581
        const char *errorMsg = "It is not allowed to set both the GROUP and "
582
                               "WMS_LAYER_GROUP for a layer";
583
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_500_INTERNAL_SERVER_ERROR,
×
584
                             errorMsg, "msWMSPrepareNestedGroups()", NULL);
585
        msIO_fprintf(stdout, "<!-- ERROR: %s -->\n", errorMsg);
×
586
        /* cannot return exception at this point because we are already writing
587
         * to stdout */
588
      } else {
589
        if (groups[0] != '/') {
1,633✔
590
          const char *errorMsg =
591
              "The WMS_LAYER_GROUP metadata does not start with a '/'";
592
          msSetErrorWithStatus(MS_WMSERR, MS_HTTP_500_INTERNAL_SERVER_ERROR,
×
593
                               errorMsg, "msWMSPrepareNestedGroups()", NULL);
594
          msIO_fprintf(stdout, "<!-- ERROR: %s -->\n", errorMsg);
×
595
          /* cannot return exception at this point because we are already
596
           * writing to stdout */
597
        } else {
598
          /* split into subgroups. Start at address + 1 because the first '/'
599
           * would cause an extra empty group */
600
          nestedGroups[i] = msStringSplit(groups + 1, '/', &numNestedGroups[i]);
1,633✔
601
          /* Iterate through the groups and add them to the unique groups array
602
           */
603
          for (int k = 0; k < numNestedGroups[i]; k++) {
3,351✔
604
            uniqgroups.insert(msStringToLower(std::string(nestedGroups[i][k])));
3,436✔
605
          }
606
        }
607
      }
608
    }
609
  }
610
  /* Iterate through layers to find out whether they are in any of the nested
611
   * groups */
612
  for (int i = 0; i < map->numlayers; i++) {
7,026✔
613
    if (GET_LAYER(map, i)->name) {
6,152✔
614
      if (uniqgroups.find(msStringToLower(
6,152✔
615
              std::string(GET_LAYER(map, i)->name))) != uniqgroups.end()) {
12,304✔
616
        isUsedInNestedGroup[i] = 1;
422✔
617
      }
618
    }
619
  }
620
}
874✔
621

622
/*
623
** Validate that a given dimension is inside the extents defined
624
*/
625
static bool msWMSValidateDimensionValue(const char *value,
29✔
626
                                        const char *dimensionextent,
627
                                        bool forcecharacter) {
628
  std::vector<pointObj> aextentranges;
629

630
  bool isextentavalue = false;
631
  bool isextentarange = false;
632
  bool ischaracter = false;
633

634
  if (forcecharacter)
635
    ischaracter = true;
636

637
  if (!value || !dimensionextent)
29✔
638
    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
  auto extents = msStringSplit(dimensionextent, ',');
29✔
649
  for (auto &extent :
29✔
650
       extents) // Make sure to get by reference so that it is updated in place
88✔
651
    msStringTrim(extent);
59✔
652

653
  std::vector<std::string> aextentvalues;
654
  if (extents.size() == 1) {
29✔
655
    if (strstr(dimensionextent, "/") == NULL) {
10✔
656
      /*single value*/
657
      isextentavalue = true;
658
      aextentvalues.push_back(dimensionextent);
×
659
      if (!forcecharacter)
×
660
        ischaracter = FLTIsNumeric(dimensionextent) == MS_FALSE;
×
661

662
    } else {
663
      const auto ranges = msStringSplit(dimensionextent, '/');
10✔
664
      if (ranges.size() == 2 || ranges.size() == 3) {
10✔
665
        /*single range*/
666
        isextentarange = true;
667
        aextentranges.resize(1);
10✔
668
        aextentranges[0].x = atof(ranges[0].c_str());
10✔
669
        aextentranges[0].y = atof(ranges[1].c_str());
10✔
670
        /*ranges should be numeric*/
671
        ischaracter = false;
672
      }
673
    }
10✔
674
  } else if (extents.size() >
19✔
675
             1) { /*check if it is muliple values or multiple ranges*/
676
    if (strstr(dimensionextent, "/") == NULL) {
19✔
677
      /*multiple values*/
678
      isextentavalue = true;
679
      aextentvalues = std::move(extents);
11✔
680
      if (!forcecharacter)
11✔
681
        ischaracter = FLTIsNumeric(aextentvalues[0].c_str()) == MS_FALSE;
11✔
682
    } else { /*multiple range extent*/
683
      int isvalidextent = MS_TRUE;
684
      /*ranges should be numeric*/
685
      ischaracter = false;
686
      isextentarange = true;
687
      aextentranges.resize(extents.size());
8✔
688
      size_t nextentranges = 0;
689

690
      for (const auto &extent : extents) {
24✔
691
        const auto onerange = msStringSplit(extent.c_str(), '/');
16✔
692
        if (onerange.size() != 2 && onerange.size() != 3) {
16✔
693
          isvalidextent = MS_FALSE;
694
          break;
695
        }
696
        if (isvalidextent) {
697

698
          aextentranges[nextentranges].x = atof(onerange[0].c_str());
16✔
699
          aextentranges[nextentranges++].y = atof(onerange[1].c_str());
16✔
700
        }
701
      }
16✔
702
      if (!isvalidextent) {
703
        nextentranges = 0;
704
        isextentarange = false;
705
      }
706
      aextentranges.resize(nextentranges);
8✔
707
    }
708
  }
709

710
  /* make sure that we got a valid extent*/
711
  if (!isextentavalue && !isextentarange) {
29✔
712
    return false;
713
  }
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
  const auto uservalues = msStringSplit(value, ',');
29✔
720
  bool uservaluevalid = false;
721
  if (uservalues.size() == 1) {
29✔
722
    /*user input=single*/
723
    /*is it descret or range*/
724
    const auto ranges = msStringSplit(uservalues[0].c_str(), '/');
12✔
725
    if (ranges.size() == 1) { /*discrete*/
12✔
726
      if (isextentavalue) {
8✔
727
        /*single user value, single/multiple values extent*/
728
        for (const auto &extentvalue : aextentvalues) {
12✔
729
          if (ischaracter)
10✔
730
            uservaluevalid = (uservalues[0] == extentvalue);
4✔
731
          else {
732
            if (atof(uservalues[0].c_str()) == atof(extentvalue.c_str()))
6✔
733
              uservaluevalid = true;
734
          }
735
          if (uservaluevalid)
8✔
736
            break;
737
        }
738
      } else if (isextentarange) {
3✔
739
        /*single user value, single/multiple range extent*/
740
        const float currentval = atof(uservalues[0].c_str());
3✔
741

742
        for (const auto &extentrange : aextentranges) {
4✔
743
          const float minval = extentrange.x;
3✔
744
          const float maxval = extentrange.y;
3✔
745
          if (currentval >= minval && currentval <= maxval) {
3✔
746
            uservaluevalid = true;
747
            break;
748
          }
749
        }
750
      }
751
    } else if (ranges.size() == 2 || ranges.size() == 3) { /*range*/
4✔
752
      /*user input=single range. In this case the extents must
753
       be of a range type.*/
754
      const float mincurrentval = atof(ranges[0].c_str());
4✔
755
      const float maxcurrentval = atof(ranges[1].c_str());
4✔
756
      if (isextentarange) {
4✔
757
        for (const auto &extentrange : aextentranges) {
7✔
758
          const float minval = extentrange.x;
5✔
759
          const float maxval = extentrange.y;
5✔
760

761
          if (minval <= mincurrentval && maxval >= maxcurrentval &&
5✔
762
              minval <= maxval) {
763
            uservaluevalid = true;
764
            break;
765
          }
766
        }
767
      }
768
    }
769
  } else if (uservalues.size() > 1) { /*user input=multiple*/
29✔
770
    if (strstr(value, "/") == NULL) {
17✔
771
      /*user input=multiple value*/
772
      bool valueisvalid = false;
773
      for (const auto &uservalue : uservalues) {
21✔
774
        valueisvalid = false;
775
        if (isextentavalue) {
17✔
776
          /*user input is multiple values, extent is defined as one or multiple
777
           * values*/
778
          for (const auto &extentvalue : aextentvalues) {
24✔
779
            if (ischaracter) {
20✔
780
              if (uservalue == extentvalue) {
9✔
781
                valueisvalid = true;
782
                break;
783
              }
784
            } else {
785
              if (atof(uservalue.c_str()) == atof(extentvalue.c_str())) {
11✔
786
                valueisvalid = true;
787
                break;
788
              }
789
            }
790
          }
791
          /*every value should be valid*/
792
          if (!valueisvalid)
8✔
793
            break;
794
        } else if (isextentarange) {
9✔
795
          /*user input is multiple values, extent is defined as one or multiple
796
           * ranges*/
797
          for (const auto &extentrange : aextentranges) {
14✔
798
            const float minval = extentrange.x;
11✔
799
            const float maxval = extentrange.y;
11✔
800
            const float currentval = atof(uservalue.c_str());
11✔
801
            if (minval <= currentval && maxval >= currentval &&
11✔
802
                minval <= maxval) {
803
              valueisvalid = true;
804
              break;
805
            }
806
          }
807
          if (!valueisvalid)
9✔
808
            break;
809
        }
810
      }
811
      uservaluevalid = valueisvalid;
812
    } else { /*user input multiple ranges*/
813
      bool valueisvalid = true;
814

815
      for (const auto &uservalue : uservalues) {
12✔
816
        /*each ranges should be valid*/
817
        const auto onerange = msStringSplit(uservalue.c_str(), '/');
10✔
818
        if (onerange.size() == 2 || onerange.size() == 3) {
10✔
819
          const float mincurrentval = atof(onerange[0].c_str());
10✔
820
          const float maxcurrentval = atof(onerange[1].c_str());
10✔
821

822
          /*extent must be defined also as a rangle*/
823
          if (isextentarange) {
10✔
824
            bool found = false;
825
            for (const auto &extentrange : aextentranges) {
17✔
826
              const float mincurrentrange = extentrange.x;
13✔
827
              const float maxcurrentrange = extentrange.y;
13✔
828

829
              if (mincurrentval >= mincurrentrange &&
13✔
830
                  maxcurrentval <= maxcurrentrange &&
6✔
831
                  mincurrentval <= maxcurrentval) {
832
                found = true;
833
                break;
834
              }
835
            }
836
            if (!found) {
10✔
837
              valueisvalid = false;
838
              break;
839
            }
840
          }
841
        } else {
842
          valueisvalid = false;
843
        }
844
      }
10✔
845
      uservaluevalid = valueisvalid;
846
    }
847
  }
848

849
  return uservaluevalid;
850
}
29✔
851

852
static bool msWMSApplyDimensionLayer(layerObj *lp, const char *item,
13✔
853
                                     const char *value, bool forcecharacter) {
854
  bool result = false;
855

856
  if (lp && item && value) {
13✔
857
    /*for the value, we support descrete values (2005) */
858
    /* multiple values (abc, def, ...) */
859
    /* and range(s) (1000/2000, 3000/5000) */
860
    char *pszExpression = FLTGetExpressionForValuesRanges(
26✔
861
        lp, item, value, forcecharacter ? MS_TRUE : MS_FALSE);
862

863
    if (pszExpression) {
13✔
864
      // If tileindex is set, the filter is applied to tileindex too.
865
      int tlpindex = -1;
866
      if (lp->tileindex &&
13✔
867
          (tlpindex = msGetLayerIndex(lp->map, lp->tileindex)) != -1) {
×
868
        result = FLTApplyExpressionToLayer((GET_LAYER(lp->map, tlpindex)),
×
869
                                           pszExpression) != MS_FALSE;
870
      } else {
871
        result = true;
872
      }
873
      result &= FLTApplyExpressionToLayer(lp, pszExpression) != MS_FALSE;
13✔
874
      msFree(pszExpression);
13✔
875
    }
876
  }
877
  return result;
13✔
878
}
879

880
static bool msWMSApplyDimension(layerObj *lp, int /* version */,
29✔
881
                                const char *dimensionname, const char *value,
882
                                const char * /* wms_exception_format */) {
883
  bool forcecharacter = false;
884
  bool result = false;
885

886
  if (lp && dimensionname && value) {
29✔
887
    /*check if the dimension name passes starts with dim_. All dimensions should
888
     * start with dim_, except elevation*/
889
    std::string dimension;
890
    if (strncasecmp(dimensionname, "dim_", 4) == 0)
29✔
891
      dimension = dimensionname + 4;
5✔
892
    else
893
      dimension = dimensionname;
894

895
    /*if value is empty and a default is defined, use it*/
896
    std::string currentvalue;
897
    if (strlen(value) > 0)
29✔
898
      currentvalue = value;
899
    else {
900
      const char *dimensiondefault = msOWSLookupMetadata(
1✔
901
          &(lp->metadata), "M", (dimension + "_default").c_str());
1✔
902
      if (dimensiondefault)
1✔
903
        currentvalue = dimensiondefault;
904
    }
905

906
    /*check if the manadatory metada related to the dimension are set*/
907
    const char *dimensionitem = msOWSLookupMetadata(
29✔
908
        &(lp->metadata), "M", (dimension + "_item").c_str());
29✔
909
    const char *dimensionextent = msOWSLookupMetadata(
29✔
910
        &(lp->metadata), "M", (dimension + "_extent").c_str());
29✔
911
    const char *dimensionunit = msOWSLookupMetadata(
29✔
912
        &(lp->metadata), "M", (dimension + "_units").c_str());
29✔
913

914
    /*if the server want to force the type to character*/
915
    const char *dimensiontype = msOWSLookupMetadata(
29✔
916
        &(lp->metadata), "M", (dimension + "_type").c_str());
29✔
917
    if (dimensiontype && strcasecmp(dimensiontype, "Character") == 0)
29✔
918
      forcecharacter = true;
919

920
    if (dimensionitem && dimensionextent && dimensionunit &&
29✔
921
        !currentvalue.empty()) {
922
      if (msWMSValidateDimensionValue(currentvalue.c_str(), dimensionextent,
29✔
923
                                      forcecharacter)) {
924
        result = msWMSApplyDimensionLayer(lp, dimensionitem,
13✔
925
                                          currentvalue.c_str(), forcecharacter);
926
      } else {
927
        msSetErrorWithStatus(
16✔
928
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
929
            "Dimension %s with a value of %s is invalid or outside the "
930
            "extents defined",
931
            "msWMSApplyDimension", dimension.c_str(), currentvalue.c_str());
932
        result = false;
933
      }
934
    } else
935
      msSetErrorWithStatus(
×
936
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
937
          "Dimension %s : invalid settings. Make sure that item, units "
938
          "and extent are set.",
939
          "msWMSApplyDimension", dimension.c_str());
940
  }
941
  return result;
29✔
942
}
943
/*
944
**
945
*/
946
int msWMSLoadGetMapParams(mapObj *map, int nVersion, char **names,
529✔
947
                          char **values, int numentries,
948
                          const char *wms_exception_format,
949
                          const char * /*wms_request*/,
950
                          owsRequestObj *ows_request) {
951
  bool adjust_extent = false;
952
  bool nonsquare_enabled = false;
953
  int transparent = MS_NOOVERRIDE;
954
  bool bbox_pixel_is_point = false;
955
  outputFormatObj *format = NULL;
956
  int validlayers = 0;
957
  const char *styles = NULL;
958
  int invalidlayers = 0;
959
  std::string epsgbuf;
960
  std::string srsbuffer;
961
  bool epsgvalid = false;
962
  bool timerequest = false;
963
  const char *stime = NULL;
964
  bool srsfound = false;
965
  bool bboxfound = false;
966
  bool formatfound = false;
967
  bool widthfound = false;
968
  bool heightfound = false;
969
  const char *request = NULL;
970
  int status = 0;
971
  const char *layerlimit = NULL;
972
  bool tiled = false;
973

974
  const char *sldenabled = NULL;
975
  const char *sld_url = NULL;
976
  const char *sld_body = NULL;
977

978
  const char *filter = NULL;
979

980
  /* Some of the getMap parameters are actually required depending on the */
981
  /* request, but for now we assume all are optional and the map file */
982
  /* defaults will apply. */
983

984
  msAdjustExtent(&(map->extent), map->width, map->height);
529✔
985

986
  /*
987
    Check for SLDs first. If SLD is available LAYERS and STYLES parameters are
988
    non mandatory
989
   */
990
  for (int i = 0; i < numentries; i++) {
6,871✔
991
    /* check if SLD is passed.  If yes, check for OGR support */
992
    if (strcasecmp(names[i], "SLD") == 0 ||
6,342✔
993
        strcasecmp(names[i], "SLD_BODY") == 0) {
6,335✔
994
      sldenabled =
995
          msOWSLookupMetadata(&(map->web.metadata), "MO", "sld_enabled");
146✔
996

997
      if (sldenabled == NULL) {
146✔
998
        sldenabled = "true";
999
      }
1000

1001
      if (strcasecmp(sldenabled, "true") == 0) {
146✔
1002
        if (strcasecmp(names[i], "SLD") == 0) {
144✔
1003
          sld_url = values[i];
7✔
1004
        }
1005
        if (strcasecmp(names[i], "SLD_BODY") == 0) {
144✔
1006
          sld_body = values[i];
137✔
1007
        }
1008
      }
1009
    }
1010
  }
1011

1012
  std::vector<std::string> wmslayers;
1013
  for (int i = 0; i < numentries; i++) {
6,859✔
1014
    /* getMap parameters */
1015

1016
    if (strcasecmp(names[i], "REQUEST") == 0) {
6,332✔
1017
      request = values[i];
529✔
1018
    }
1019

1020
    if (strcasecmp(names[i], "LAYERS") == 0) {
6,332✔
1021
      std::vector<int> layerOrder(map->numlayers);
526✔
1022

1023
      wmslayers = msStringSplit(values[i], ',');
526✔
1024
      if (wmslayers.empty()) {
526✔
1025
        if (sld_url == NULL && sld_body == NULL) {
×
1026
          msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
1027
                               "At least one layer name required in LAYERS.",
1028
                               "msWMSLoadGetMapParams()");
1029
          return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1030
        }
1031
      }
1032

1033
      if (nVersion >= OWS_1_3_0) {
526✔
1034
        layerlimit =
1035
            msOWSLookupMetadata(&(map->web.metadata), "MO", "layerlimit");
214✔
1036
        if (layerlimit) {
214✔
1037
          if (static_cast<int>(wmslayers.size()) > atoi(layerlimit)) {
12✔
1038
            msSetErrorWithStatus(
2✔
1039
                MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1040
                "Number of layers requested exceeds LayerLimit.",
1041
                "msWMSLoadGetMapParams()");
1042
            return msWMSException(map, nVersion, NULL, wms_exception_format);
2✔
1043
          }
1044
        }
1045
      }
1046

1047
      for (int iLayer = 0; iLayer < map->numlayers; iLayer++) {
3,585✔
1048
        map->layerorder[iLayer] = iLayer;
3,061✔
1049
      }
1050

1051
      int nLayerOrder = 0;
1052
      for (int j = 0; j < map->numlayers; j++) {
3,585✔
1053
        /* Keep only layers with status=DEFAULT by default */
1054
        /* Layer with status DEFAULT is drawn first. */
1055
        if (GET_LAYER(map, j)->status != MS_DEFAULT)
3,061✔
1056
          GET_LAYER(map, j)->status = MS_OFF;
3,006✔
1057
        else {
1058
          map->layerorder[nLayerOrder++] = j;
55✔
1059
          layerOrder[j] = 1;
55✔
1060
        }
1061
      }
1062

1063
      char ***nestedGroups =
1064
          (char ***)msSmallCalloc(map->numlayers, sizeof(char **));
524✔
1065
      int *numNestedGroups = (int *)msSmallCalloc(map->numlayers, sizeof(int));
524✔
1066
      int *isUsedInNestedGroup =
1067
          (int *)msSmallCalloc(map->numlayers, sizeof(int));
524✔
1068
      msWMSPrepareNestedGroups(map, nVersion, nestedGroups, numNestedGroups,
524✔
1069
                               isUsedInNestedGroup);
1070

1071
      if (ows_request->layerwmsfilterindex != NULL)
524✔
1072
        msFree(ows_request->layerwmsfilterindex);
×
1073
      ows_request->layerwmsfilterindex =
524✔
1074
          (int *)msSmallMalloc(map->numlayers * sizeof(int));
524✔
1075
      for (int j = 0; j < map->numlayers; j++) {
3,585✔
1076
        ows_request->layerwmsfilterindex[j] = -1;
3,061✔
1077
      }
1078
      ows_request->numwmslayerargs = static_cast<int>(wmslayers.size());
524✔
1079

1080
      for (int k = 0; k < static_cast<int>(wmslayers.size()); k++) {
1,204✔
1081
        const auto &wmslayer = wmslayers[k];
680✔
1082
        bool layerfound = false;
1083
        for (int j = 0; j < map->numlayers; j++) {
4,229✔
1084
          /* Turn on selected layers only. */
1085
          if (((GET_LAYER(map, j)->name &&
7,098✔
1086
                strcasecmp(GET_LAYER(map, j)->name, wmslayer.c_str()) == 0) ||
3,549✔
1087
               (map->name && strcasecmp(map->name, wmslayer.c_str()) == 0) ||
2,902✔
1088
               (GET_LAYER(map, j)->group &&
2,734✔
1089
                strcasecmp(GET_LAYER(map, j)->group, wmslayer.c_str()) == 0) ||
79✔
1090
               ((numNestedGroups[j] > 0) &&
2,993✔
1091
                msStringInArray(wmslayer.c_str(), nestedGroups[j],
281✔
1092
                                numNestedGroups[j]))) &&
4,426✔
1093
              ((msIntegerInArray(GET_LAYER(map, j)->index,
877✔
1094
                                 ows_request->enabled_layers,
1095
                                 ows_request->numlayers)))) {
1096
            if (GET_LAYER(map, j)->status != MS_DEFAULT) {
872✔
1097
              if (layerOrder[j] == 0) {
843✔
1098
                map->layerorder[nLayerOrder++] = j;
833✔
1099
                layerOrder[j] = 1;
833✔
1100
                GET_LAYER(map, j)->status = MS_ON;
833✔
1101
              }
1102
            }
1103
            /* if a layer name is repeated assign the first matching filter */
1104
            /* duplicate names will be assigned filters later when layer copies
1105
             * are created */
1106
            if (ows_request->layerwmsfilterindex[j] == -1) {
872✔
1107
              ows_request->layerwmsfilterindex[j] = k;
862✔
1108
            }
1109
            validlayers++;
872✔
1110
            layerfound = true;
1111
          }
1112
        }
1113
        if (layerfound == false && !wmslayers.empty())
680✔
1114
          invalidlayers++;
3✔
1115
      }
1116

1117
      /* free the stuff used for nested layers */
1118
      for (int k = 0; k < map->numlayers; k++) {
3,585✔
1119
        if (numNestedGroups[k] > 0) {
3,061✔
1120
          msFreeCharArray(nestedGroups[k], numNestedGroups[k]);
423✔
1121
        }
1122
      }
1123
      free(nestedGroups);
524✔
1124
      free(numNestedGroups);
524✔
1125
      free(isUsedInNestedGroup);
524✔
1126

1127
      /* set all layers with status off at end of array */
1128
      for (int j = 0; j < map->numlayers; j++) {
3,585✔
1129
        if (GET_LAYER(map, j)->status == MS_OFF)
3,061✔
1130
          if (layerOrder[j] == 0)
2,173✔
1131
            map->layerorder[nLayerOrder++] = j;
2,173✔
1132
      }
1133
    } else if (strcasecmp(names[i], "STYLES") == 0) {
6,332✔
1134
      styles = values[i];
443✔
1135

1136
    } else if ((strcasecmp(names[i], "SRS") == 0 && nVersion < OWS_1_3_0) ||
5,363✔
1137
               (strcasecmp(names[i], "CRS") == 0 && nVersion >= OWS_1_3_0)) {
5,054✔
1138
      srsfound = true;
1139
      char *colon = strchr(values[i], ':');
503✔
1140
      bool srsOk = false;
1141
      /* SRS is in format "EPSG:epsg_id" or "AUTO:proj_id,unit_id,lon0,lat0" */
1142
      if (strncasecmp(values[i], "EPSG:", 5) == 0) {
503✔
1143
        srsOk = true;
1144
        /* SRS=EPSG:xxxx */
1145

1146
        /* don't need to copy init=xxx since the srsbuffer is only
1147
           used with msLoadProjection and that does already the job */
1148

1149
        srsbuffer = "EPSG:";
1150
        srsbuffer += (values[i] + 5);
499✔
1151
        epsgbuf = srsbuffer;
1152

1153
        /* This test was to correct a request by the OCG cite 1.3.0 test
1154
         sending CRS=ESPG:4326,  Bug:*/
1155
        if (nVersion >= OWS_1_3_0) {
499✔
1156
          if (srsbuffer.back() == ',') {
191✔
1157
            srsbuffer.resize(srsbuffer.size() - 1);
1158
            epsgbuf = srsbuffer;
1159
          }
1160
        }
1161

1162
        /* we need to wait until all params are read before */
1163
        /* loading the projection into the map. This will help */
1164
        /* insure that the passes srs is valid for all layers. */
1165
        /*
1166
        if (msLoadProjectionString(&(map->projection), buffer) != 0)
1167
          return msWMSException(map, nVersion, NULL);
1168

1169
        iUnits = GetMapserverUnitUsingProj(&(map->projection));
1170
        if (iUnits != -1)
1171
          map->units = iUnits;
1172
        */
1173
      } else if (strncasecmp(values[i], "AUTO:", 5) == 0 &&
4✔
1174
                 nVersion < OWS_1_3_0) {
1175
        if (nVersion < OWS_1_3_0) {
1176
          srsOk = true;
1177
          srsbuffer = values[i];
1178
          /* SRS=AUTO:proj_id,unit_id,lon0,lat0 */
1179
          /*
1180
          if (msLoadProjectionString(&(map->projection), values[i]) != 0)
1181
            return msWMSException(map, nVersion, NULL);
1182

1183
          iUnits = GetMapserverUnitUsingProj(&(map->projection));
1184
          if (iUnits != -1)
1185
            map->units = iUnits;
1186
          */
1187
        }
1188
      } else if (strncasecmp(values[i], "AUTO2:", 6) == 0 ||
4✔
1189
                 strncasecmp(values[i], "CRS:", 4) == 0) {
4✔
1190
        if (nVersion >= OWS_1_3_0) {
×
1191
          srsOk = true;
1192
          srsbuffer = values[i];
1193
        }
1194
      } else if (colon != NULL && strchr(colon + 1, ':') == NULL) {
4✔
1195
        srsOk = true;
1196
        srsbuffer = values[i];
1197
        epsgbuf = srsbuffer;
1198
      }
1199
      if (!srsOk) {
1200
        if (nVersion >= OWS_1_3_0) {
×
1201
          msSetErrorWithStatus(
×
1202
              MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1203
              "Unsupported CRS namespace (CRS must be in the format "
1204
              "AUTH:XXXX).",
1205
              "msWMSLoadGetMapParams()");
1206
          return msWMSException(map, nVersion, "InvalidCRS",
×
1207
                                wms_exception_format);
1208
        } else {
1209
          msSetErrorWithStatus(
×
1210
              MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1211
              "Unsupported SRS namespace (SRS must be in the format "
1212
              "AUTH:XXXX).",
1213
              "msWMSLoadGetMapParams()");
1214
          return msWMSException(map, nVersion, "InvalidSRS",
×
1215
                                wms_exception_format);
1216
        }
1217
      }
1218
    } else if (strcasecmp(names[i], "BBOX") == 0) {
4,860✔
1219
      bboxfound = true;
1220
      const auto tokens = msStringSplit(values[i], ',');
507✔
1221
      if (tokens.size() != 4) {
507✔
1222
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
1223
                             "Wrong number of arguments for BBOX.",
1224
                             "msWMSLoadGetMapParams()");
1225
        return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1226
      }
1227
      map->extent.minx = atof(tokens[0].c_str());
507✔
1228
      map->extent.miny = atof(tokens[1].c_str());
507✔
1229
      map->extent.maxx = atof(tokens[2].c_str());
507✔
1230
      map->extent.maxy = atof(tokens[3].c_str());
507✔
1231

1232
      /*for wms 1.3.0 we will do the validation of the bbox after all parameters
1233
       are read to account for the axes order*/
1234
      if (nVersion < OWS_1_3_0) {
507✔
1235

1236
        /* validate bbox values */
1237
        if (map->extent.minx >= map->extent.maxx ||
309✔
1238
            map->extent.miny >= map->extent.maxy) {
309✔
1239
          msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
1240
                               "Invalid values for BBOX.",
1241
                               "msWMSLoadGetMapParams()");
1242
          return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1243
        }
1244
        adjust_extent = true;
1245
      }
1246
    } else if (strcasecmp(names[i], "WIDTH") == 0) {
4,860✔
1247
      widthfound = true;
1248
      map->width = atoi(values[i]);
507✔
1249
    } else if (strcasecmp(names[i], "HEIGHT") == 0) {
3,846✔
1250
      heightfound = true;
1251
      map->height = atoi(values[i]);
507✔
1252
    } else if (strcasecmp(names[i], "FORMAT") == 0) {
3,339✔
1253
      formatfound = true;
1254

1255
      if (strcasecmp(values[i], "application/openlayers") != 0) {
499✔
1256
        /*check to see if a predefined list is given*/
1257
        const char *format_list =
1258
            msOWSLookupMetadata(&(map->web.metadata), "M", "getmap_formatlist");
497✔
1259
        if (format_list) {
497✔
1260
          format = msOwsIsOutputFormatValid(
57✔
1261
              map, values[i], &(map->web.metadata), "M", "getmap_formatlist");
1262
          if (format == NULL) {
57✔
1263
            msSetErrorWithStatus(MS_IMGERR, MS_HTTP_400_BAD_REQUEST,
×
1264
                                 "Unsupported output format (%s).",
1265
                                 "msWMSLoadGetMapParams()", values[i]);
1266
            return msWMSException(map, nVersion, "InvalidFormat",
×
1267
                                  wms_exception_format);
1268
          }
1269
        } else {
1270
          format = msSelectOutputFormat(map, values[i]);
440✔
1271
          if (format == NULL ||
440✔
1272
              (strncasecmp(format->driver, "MVT", 3) != 0 &&
440✔
1273
               strncasecmp(format->driver, "GDAL/", 5) != 0 &&
435✔
1274
               strncasecmp(format->driver, "AGG/", 4) != 0 &&
432✔
1275
               strncasecmp(format->driver, "UTFGRID", 7) != 0 &&
2✔
1276
               strncasecmp(format->driver, "CAIRO/", 6) != 0 &&
2✔
1277
               strncasecmp(format->driver, "OGL/", 4) != 0 &&
2✔
1278
               strncasecmp(format->driver, "KML", 3) != 0 &&
2✔
1279
               strncasecmp(format->driver, "KMZ", 3) != 0)) {
×
1280
            msSetErrorWithStatus(MS_IMGERR, MS_HTTP_400_BAD_REQUEST,
×
1281
                                 "Unsupported output format (%s).",
1282
                                 "msWMSLoadGetMapParams()", values[i]);
1283
            return msWMSException(map, nVersion, "InvalidFormat",
×
1284
                                  wms_exception_format);
1285
          }
1286
        }
1287
      }
1288
      msFree(map->imagetype);
499✔
1289
      map->imagetype = msStrdup(values[i]);
499✔
1290
    } else if (strcasecmp(names[i], "TRANSPARENT") == 0) {
2,840✔
1291
      transparent = (strcasecmp(values[i], "TRUE") == 0);
65✔
1292
    } else if (strcasecmp(names[i], "BGCOLOR") == 0) {
2,775✔
1293
      long c;
1294
      c = strtol(values[i], NULL, 16);
41✔
1295
      map->imagecolor.red = (c / 0x10000) & 0xff;
41✔
1296
      map->imagecolor.green = (c / 0x100) & 0xff;
41✔
1297
      map->imagecolor.blue = c & 0xff;
41✔
1298
    }
1299

1300
    /* value of time can be empty. We should look for a default value */
1301
    /* see function msWMSApplyTime */
1302
    else if (strcasecmp(names[i], "TIME") == 0) { /* &&  values[i]) */
2,734✔
1303
      stime = values[i];
130✔
1304
      timerequest = true;
1305
    }
1306
    /* Vendor-specific ANGLE param (for map rotation), added in ticket #3332,
1307
     * also supported by GeoServer
1308
     */
1309
    else if (strcasecmp(names[i], "ANGLE") == 0) {
2,604✔
1310
      msMapSetRotation(map, atof(values[i]));
×
1311
    }
1312
    /* Vendor-specific bbox_pixel_is_point, added in ticket #4652 */
1313
    else if (strcasecmp(names[i], "BBOX_PIXEL_IS_POINT") == 0) {
2,604✔
1314
      bbox_pixel_is_point = (strcasecmp(values[i], "TRUE") == 0);
×
1315
    }
1316
    /* Vendor specific TILED (WMS-C) */
1317
    else if (strcasecmp(names[i], "TILED") == 0) {
2,604✔
1318
      tiled = (strcasecmp(values[i], "TRUE") == 0);
1✔
1319
    }
1320
    /* Vendor-specific FILTER, added in RFC-118 */
1321
    else if (strcasecmp(names[i], "FILTER") == 0) {
2,603✔
1322
      filter = values[i];
14✔
1323
    }
1324
  }
1325

1326
  /*validate the exception format WMS 1.3.0 section 7.3.3.11*/
1327

1328
  if (nVersion >= OWS_1_3_0 && wms_exception_format != NULL) {
527✔
1329
    if (strcasecmp(wms_exception_format, "INIMAGE") != 0 &&
21✔
1330
        strcasecmp(wms_exception_format, "BLANK") != 0 &&
4✔
1331
        strcasecmp(wms_exception_format, "XML") != 0) {
4✔
1332
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
1333
                           "Invalid format %s for the EXCEPTIONS parameter.",
1334
                           "msWMSLoadGetMapParams()", wms_exception_format);
1335
      return msWMSException(map, nVersion, "InvalidFormat",
×
1336
                            wms_exception_format);
1337
    }
1338
  }
1339

1340
  int need_axis_swap = MS_FALSE;
1341
  if (bboxfound && nVersion >= OWS_1_3_0) {
527✔
1342
    rectObj rect;
1343
    projectionObj proj;
1344

1345
    /*we have already validated that the request format when reading
1346
     the request parameters*/
1347
    rect = map->extent;
196✔
1348

1349
    /*try to adjust the axes if necessary*/
1350
    if (srsbuffer.size() > 1) {
196✔
1351
      msInitProjection(&proj);
194✔
1352
      msProjectionInheritContextFrom(&proj, &(map->projection));
194✔
1353
      if (msLoadProjectionStringEPSG(&proj, srsbuffer.c_str()) == 0 &&
387✔
1354
          (need_axis_swap = msIsAxisInvertedProj(&proj))) {
193✔
1355
        msAxisNormalizePoints(&proj, 1, &rect.minx, &rect.miny);
184✔
1356
        msAxisNormalizePoints(&proj, 1, &rect.maxx, &rect.maxy);
184✔
1357
      }
1358
      msFreeProjection(&proj);
194✔
1359
    }
1360
    /*if the CRS is AUTO2:auto_crs_id,factor,lon0,lat0,
1361
     we need to grab the factor parameter and use it with the bbox*/
1362
    if (srsbuffer.size() > 1 &&
196✔
1363
        strncasecmp(srsbuffer.c_str(), "AUTO2:", 6) == 0) {
194✔
1364
      const auto args = msStringSplit(srsbuffer.c_str(), ',');
×
1365
      if (args.size() == 4) {
×
1366
        const double factor = atof(args[1].c_str());
1367
        if (factor > 0 && factor != 1.0) {
×
1368
          rect.minx = rect.minx * factor;
×
1369
          rect.miny = rect.miny * factor;
×
1370
          rect.maxx = rect.maxx * factor;
1371
          rect.maxx = rect.maxy * factor;
×
1372
        }
1373
      }
1374
    }
×
1375

1376
    map->extent = rect;
196✔
1377

1378
    /* validate bbox values */
1379
    if (map->extent.minx >= map->extent.maxx ||
196✔
1380
        map->extent.miny >= map->extent.maxy) {
196✔
1381
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
1382
                           "Invalid values for BBOX.",
1383
                           "msWMSLoadGetMapParams()");
1384
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1385
    }
1386
    adjust_extent = true;
1387
  }
1388

1389
  if (tiled) {
527✔
1390
    const char *value;
1391
    hashTableObj *meta = &(map->web.metadata);
1✔
1392
    int map_edge_buffer = 0;
1393

1394
    if ((value = msLookupHashTable(meta, "tile_map_edge_buffer")) != NULL) {
1✔
1395
      map_edge_buffer = atoi(value);
1396
    }
1397
    if (map_edge_buffer > 0 && map->width > 0 && map->height > 0) {
1✔
1398
      /* adjust bbox and width and height to the buffer */
1399
      const double buffer_x = map_edge_buffer *
1✔
1400
                              (map->extent.maxx - map->extent.minx) /
1✔
1401
                              (double)map->width;
1✔
1402
      const double buffer_y = map_edge_buffer *
1✔
1403
                              (map->extent.maxy - map->extent.miny) /
1✔
1404
                              (double)map->height;
1✔
1405

1406
      // TODO: we should probably clamp the extent to avoid going outside of
1407
      // -180,-90,180,90 for geographic CRS for example
1408
      map->extent.minx -= buffer_x;
1✔
1409
      map->extent.maxx += buffer_x;
1✔
1410
      map->extent.miny -= buffer_y;
1✔
1411
      map->extent.maxy += buffer_y;
1✔
1412

1413
      map->width += 2 * map_edge_buffer;
1✔
1414
      map->height += 2 * map_edge_buffer;
1✔
1415

1416
      if (map_edge_buffer > 0) {
1417
        char tilebufferstr[64];
1418

1419
        /* Write the tile buffer to a string */
1420
        snprintf(tilebufferstr, sizeof(tilebufferstr), "-%d", map_edge_buffer);
1421

1422
        /* Hm, the labelcache buffer is set... */
1423
        if ((value = msLookupHashTable(meta, "labelcache_map_edge_buffer")) !=
1✔
1424
            NULL) {
1425
          /* If it's too small, replace with a bigger one */
1426
          if (map_edge_buffer > abs(atoi(value))) {
×
1427
            msRemoveHashTable(meta, "labelcache_map_edge_buffer");
×
1428
            msInsertHashTable(meta, "labelcache_map_edge_buffer",
×
1429
                              tilebufferstr);
1430
          }
1431
        }
1432
        /* No labelcache buffer value? Then we use the tile buffer. */
1433
        else {
1434
          msInsertHashTable(meta, "labelcache_map_edge_buffer", tilebufferstr);
1✔
1435
        }
1436
      }
1437
    }
1438
  }
1439

1440
  /*
1441
  ** If any select layers have a default time, we will apply the default
1442
  ** time value even if no TIME request was in the url.
1443
  */
1444
  if (!timerequest && map) {
527✔
1445
    for (int i = 0; i < map->numlayers && !timerequest; i++) {
1,995✔
1446
      layerObj *lp = NULL;
1447

1448
      lp = (GET_LAYER(map, i));
1,598✔
1449
      if (lp->status != MS_ON && lp->status != MS_DEFAULT)
1,598✔
1450
        continue;
837✔
1451

1452
      if (msOWSLookupMetadata(&(lp->metadata), "MO", "timedefault"))
761✔
1453
        timerequest = true;
1454
    }
1455
  }
1456

1457
  /*
1458
  ** Apply time filters if available in the request.
1459
  */
1460
  if (timerequest) {
527✔
1461
    if (msWMSApplyTime(map, nVersion, stime, wms_exception_format) ==
131✔
1462
        MS_FAILURE) {
1463
      return MS_FAILURE; /* msWMSException(map, nVersion, "InvalidTimeRequest");
1464
                          */
1465
    }
1466
  }
1467

1468
  /*
1469
  ** Check/apply wms dimensions
1470
  ** all dimension requests should start with dim_xxxx, except time and
1471
  *elevation.
1472
  */
1473
  for (int i = 0; i < map->numlayers; i++) {
3,525✔
1474
    layerObj *lp = (GET_LAYER(map, i));
3,028✔
1475
    if (lp->status != MS_ON && lp->status != MS_DEFAULT)
3,028✔
1476
      continue;
2,151✔
1477

1478
    const char *dimensionlist =
1479
        msOWSLookupMetadata(&(lp->metadata), "M", "dimensionlist");
877✔
1480
    if (dimensionlist) {
877✔
1481
      auto tokens = msStringSplit(dimensionlist, ',');
29✔
1482
      for (auto &token : tokens) {
50✔
1483
        msStringTrim(token);
37✔
1484
        for (int k = 0; k < numentries; k++) {
470✔
1485
          const std::string dimensionname(names[k]);
462✔
1486

1487
          /*the dim_ is supposed to be part of the dimension name in the
1488
           * request*/
1489
          std::string stmp;
1490
          if (strcasecmp(token.c_str(), "elevation") == 0)
462✔
1491
            stmp = token;
1492
          else {
1493
            stmp = "dim_";
1494
            stmp += token;
1495
          }
1496
          if (strcasecmp(dimensionname.c_str(), stmp.c_str()) == 0) {
462✔
1497
            if (!msWMSApplyDimension(lp, nVersion, dimensionname.c_str(),
29✔
1498
                                     values[k], wms_exception_format)) {
29✔
1499
              return msWMSException(lp->map, nVersion, "InvalidDimensionValue",
16✔
1500
                                    wms_exception_format);
1501
            }
1502
            break;
1503
          }
1504
        }
1505
      }
1506
    }
29✔
1507
  }
1508

1509
  /*
1510
  ** Apply the selected output format (if one was selected), and override
1511
  ** the transparency if needed.
1512
  */
1513

1514
  if (format != NULL)
497✔
1515
    msApplyOutputFormat(&(map->outputformat), format, transparent);
467✔
1516

1517
  /* Validate all layers given.
1518
  ** If an invalid layer is sent, return an exception.
1519
  */
1520
  if (validlayers == 0 || invalidlayers > 0) {
497✔
1521
    if (invalidlayers > 0) {
6✔
1522
      msSetErrorWithStatus(
3✔
1523
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1524
          "Invalid layer(s) given in the LAYERS parameter. A layer might be disabled for \
1525
this request. Check wms/ows_enable_request settings.",
1526
          "msWMSLoadGetMapParams()");
1527
      return msWMSException(map, nVersion, "LayerNotDefined",
3✔
1528
                            wms_exception_format);
1529
    }
1530
    if (validlayers == 0 && sld_url == NULL && sld_body == NULL) {
3✔
1531
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
3✔
1532
                           "Missing required parameter LAYERS",
1533
                           "msWMSLoadGetMapParams()");
1534
      return msWMSException(map, nVersion, "MissingParameterValue",
3✔
1535
                            wms_exception_format);
1536
    }
1537
  }
1538

1539
  /* validate srs value: When the SRS parameter in a GetMap request contains a
1540
  ** SRS that is valid for some, but not all of the layers being requested,
1541
  ** then the server shall throw a Service Exception (code = "InvalidSRS").
1542
  ** Validate first against epsg in the map and if no matching srs is found
1543
  ** validate all layers requested.
1544
  */
1545
  if (epsgbuf.size() >= 2) { /*at least 2 chars*/
491✔
1546
    char *projstring;
1547
    epsgvalid = false;
1548
    msOWSGetEPSGProj(&(map->projection), &(map->web.metadata), "MO", MS_FALSE,
469✔
1549
                     &projstring);
1550
    if (projstring) {
469✔
1551
      const auto tokens = msStringSplit(projstring, ' ');
469✔
1552
      for (const auto &token : tokens) {
830✔
1553
        if (strcasecmp(token.c_str(), epsgbuf.c_str()) == 0) {
818✔
1554
          epsgvalid = true;
1555
          break;
1556
        }
1557
      }
1558
      msFree(projstring);
469✔
1559
    }
469✔
1560
    if (!epsgvalid) {
469✔
1561
      for (int i = 0; i < map->numlayers; i++) {
28✔
1562
        epsgvalid = false;
1563
        if (GET_LAYER(map, i)->status == MS_ON) {
17✔
1564
          msOWSGetEPSGProj(&(GET_LAYER(map, i)->projection),
11✔
1565
                           &(GET_LAYER(map, i)->metadata), "MO", MS_FALSE,
1566
                           &projstring);
1567
          if (projstring) {
11✔
1568
            const auto tokens = msStringSplit(projstring, ' ');
11✔
1569
            for (const auto &token : tokens) {
23✔
1570
              if (strcasecmp(token.c_str(), epsgbuf.c_str()) == 0) {
22✔
1571
                epsgvalid = true;
1572
                break;
1573
              }
1574
            }
1575
            msFree(projstring);
11✔
1576
          }
11✔
1577
          if (!epsgvalid) {
11✔
1578
            if (nVersion >= OWS_1_3_0) {
1✔
1579
              msSetErrorWithStatus(
1✔
1580
                  MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1581
                  "Invalid CRS given : CRS must be valid for all "
1582
                  "requested layers.",
1583
                  "msWMSLoadGetMapParams()");
1584
              return msWMSException(map, nVersion, "InvalidSRS",
1✔
1585
                                    wms_exception_format);
1✔
1586
            } else {
1587
              msSetErrorWithStatus(
×
1588
                  MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1589
                  "Invalid SRS given : SRS must be valid for all "
1590
                  "requested layers.",
1591
                  "msWMSLoadGetMapParams()");
1592
              return msWMSException(map, nVersion, "InvalidSRS",
×
1593
                                    wms_exception_format);
1594
            }
1595
          }
1596
        }
1597
      }
1598
    }
1599
  }
1600

1601
  if (request == NULL || strcasecmp(request, "DescribeLayer") != 0) {
490✔
1602
    /* Validate requested image size.
1603
     */
1604
    if (map->width > map->maxsize || map->height > map->maxsize ||
471✔
1605
        map->width < 1 || map->height < 1) {
471✔
1606
      msSetErrorWithStatus(
×
1607
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1608
          "Image size out of range, WIDTH and HEIGHT must be between 1 "
1609
          "and %d pixels.",
1610
          "msWMSLoadGetMapParams()", map->maxsize);
1611

1612
      /* Restore valid default values in case errors INIMAGE are used */
1613
      map->width = 400;
×
1614
      map->height = 300;
×
1615
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1616
    }
1617

1618
    /* Check whether requested BBOX and width/height result in non-square pixels
1619
     */
1620
    nonsquare_enabled =
1621
        msTestConfigOption(map, "MS_NONSQUARE", MS_FALSE) != MS_FALSE;
471✔
1622
    if (!nonsquare_enabled) {
471✔
1623
      const double dx = MS_ABS(map->extent.maxx - map->extent.minx);
471✔
1624
      const double dy = MS_ABS(map->extent.maxy - map->extent.miny);
471✔
1625

1626
      const double reqy = ((double)map->width) * dy / dx;
471✔
1627

1628
      /* Allow up to 1 pixel of error on the width/height ratios. */
1629
      /* If more than 1 pixel then enable non-square pixels */
1630
      if (MS_ABS((reqy - (double)map->height)) > 1.0) {
471✔
1631
        if (map->debug)
155✔
1632
          msDebug("msWMSLoadGetMapParams(): enabling non-square pixels.\n");
×
1633
        msSetConfigOption(map, "MS_NONSQUARE", "YES");
155✔
1634
        nonsquare_enabled = true;
1635
      }
1636
    }
1637
  }
1638

1639
  /* If the requested SRS is different from the default mapfile projection, or
1640
  ** if a BBOX resulting in non-square pixels is requested then
1641
  ** copy the original mapfile's projection to any layer that doesn't already
1642
  ** have a projection. This will prevent problems when users forget to
1643
  ** explicitly set a projection on all layers in a WMS mapfile.
1644
  */
1645
  if (srsbuffer.size() > 1 || nonsquare_enabled) {
490✔
1646
    projectionObj newProj;
1647

1648
    if (map->projection.numargs <= 0) {
470✔
1649
      if (nVersion >= OWS_1_3_0) {
×
1650
        msSetErrorWithStatus(
×
1651
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1652
            "Cannot set new CRS on a map that doesn't "
1653
            "have any projection set. Please make sure your mapfile "
1654
            "has a projection defined at the top level.",
1655
            "msWMSLoadGetMapParams()");
1656
        return msWMSException(map, nVersion, "InvalidCRS",
×
1657
                              wms_exception_format);
×
1658
      } else {
1659
        msSetErrorWithStatus(
×
1660
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1661
            "Cannot set new SRS on a map that doesn't "
1662
            "have any projection set. Please make sure your mapfile "
1663
            "has a projection defined at the top level.",
1664
            "msWMSLoadGetMapParams()");
1665
        return msWMSException(map, nVersion, "InvalidSRS",
×
1666
                              wms_exception_format);
1667
      }
1668
    }
1669

1670
    msInitProjection(&newProj);
470✔
1671
    msProjectionInheritContextFrom(&newProj, &map->projection);
470✔
1672
    if (srsbuffer.size() > 1) {
470✔
1673
      int nTmp;
1674

1675
      if (nVersion >= OWS_1_3_0)
468✔
1676
        nTmp = msLoadProjectionStringEPSG(&newProj, srsbuffer.c_str());
191✔
1677
      else
1678
        nTmp = msLoadProjectionString(&newProj, srsbuffer.c_str());
277✔
1679
      if (nTmp != 0) {
468✔
1680
        msFreeProjection(&newProj);
×
1681
        return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1682
      }
1683
    }
1684

1685
    if (nonsquare_enabled ||
785✔
1686
        msProjectionsDiffer(&(map->projection), &newProj)) {
315✔
1687
      msMapSetLayerProjections(map);
252✔
1688
    }
1689
    msFreeProjection(&newProj);
470✔
1690
  }
1691

1692
  /* apply the srs to the map file. This is only done after validating */
1693
  /* that the srs given as parameter is valid for all layers */
1694
  if (srsbuffer.size() > 1) {
490✔
1695
    int nTmp;
1696
    msFreeProjectionExceptContext(&map->projection);
468✔
1697
    if (nVersion >= OWS_1_3_0)
468✔
1698
      nTmp = msLoadProjectionStringEPSG(&(map->projection), srsbuffer.c_str());
191✔
1699
    else
1700
      nTmp = msLoadProjectionString(&(map->projection), srsbuffer.c_str());
277✔
1701

1702
    if (nTmp != 0)
468✔
1703
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1704

1705
    nTmp = GetMapserverUnitUsingProj(&(map->projection));
468✔
1706
    if (nTmp != -1) {
468✔
1707
      map->units = static_cast<MS_UNITS>(nTmp);
468✔
1708
    }
1709
  }
1710

1711
  if (sld_url || sld_body) {
490✔
1712
    char *pszLayerNames = NULL;
144✔
1713
    const int nLayersBefore = map->numlayers;
144✔
1714

1715
    /* -------------------------------------------------------------------- */
1716
    /*      if LAYERS parameter was not given, set all layers to off        */
1717
    /* -------------------------------------------------------------------- */
1718
    if (validlayers == 0) { /*no LAYERS parameter is give*/
144✔
1719
      for (int j = 0; j < map->numlayers; j++) {
×
1720
        if (GET_LAYER(map, j)->status != MS_DEFAULT)
×
1721
          GET_LAYER(map, j)->status = MS_OFF;
×
1722
      }
1723
    }
1724

1725
    /*apply sld if defined. This is done here so that bbox and srs are already
1726
     * applied*/
1727
    if (sld_url) {
144✔
1728
      if ((status = msSLDApplySLDURL(map, sld_url, -1, NULL, &pszLayerNames)) !=
7✔
1729
          MS_SUCCESS)
1730
        return msWMSException(map, nVersion, NULL, wms_exception_format);
7✔
1731
    } else if (sld_body) {
137✔
1732
      if ((status = msSLDApplySLD(map, sld_body, -1, NULL, &pszLayerNames)) !=
137✔
1733
          MS_SUCCESS)
1734
        return msWMSException(map, nVersion, NULL, wms_exception_format);
3✔
1735
    }
1736
    /* -------------------------------------------------------------------- */
1737
    /*      SLD and styles can use the same layer multiple times. If        */
1738
    /*      that is the case we duplicate the layer for drawing             */
1739
    /*      purpose. We need to reset the ows request enable settings (#1602)*/
1740
    /* -------------------------------------------------------------------- */
1741
    const int nLayerAfter = map->numlayers;
137✔
1742
    if (nLayersBefore != nLayerAfter) {
137✔
1743
      msOWSRequestLayersEnabled(map, "M", "GetMap", ows_request);
5✔
1744
    }
1745

1746
    /* -------------------------------------------------------------------- */
1747
    /*      We need to take into account where the LAYERS parameter was     */
1748
    /*      not given (the LAYERS is option when an SLD is given). In       */
1749
    /*      this particular case, we need to turn on the layers             */
1750
    /*      identified the SLD (#1166).                                     */
1751
    /*                                                                      */
1752
    /* -------------------------------------------------------------------- */
1753
    if (validlayers == 0) {
137✔
1754
      if (pszLayerNames) {
×
1755
        const auto tokens = msStringSplit(pszLayerNames, ',');
×
1756
        for (const auto &token : tokens) {
×
1757
          for (int j = 0; j < map->numlayers; j++) {
×
1758
            if (((GET_LAYER(map, j)->name &&
×
1759
                  strcasecmp(GET_LAYER(map, j)->name, token.c_str()) == 0) ||
×
1760
                 (map->name && strcasecmp(map->name, token.c_str()) == 0) ||
×
1761
                 (GET_LAYER(map, j)->group &&
×
1762
                  strcasecmp(GET_LAYER(map, j)->group, token.c_str()) == 0)) &&
×
1763
                ((msIntegerInArray(GET_LAYER(map, j)->index,
×
1764
                                   ows_request->enabled_layers,
1765
                                   ows_request->numlayers)))) {
1766
              if (GET_LAYER(map, j)->status != MS_DEFAULT)
×
1767
                GET_LAYER(map, j)->status = MS_ON;
×
1768
            }
1769
          }
1770
        }
1771
      }
×
1772
    }
1773
    msFree(pszLayerNames);
137✔
1774
  }
1775

1776
  /* Validate Styles :
1777
  ** MapServer advertise styles through the group setting in a class object.
1778
  ** If no styles are set MapServer expects to have empty values
1779
  ** for the styles parameter (...&STYLES=&...) Or for multiple Styles/Layers,
1780
  ** we could have ...&STYLES=,,,. If that is not the
1781
  ** case, we generate an exception.
1782
  */
1783
  if (styles && strlen(styles) > 0) {
483✔
1784
    bool hasCheckedLayerUnicity = false;
1785
    int n = 0;
17✔
1786
    int layerCopyIndex;
1787

1788
    char **tokens = msStringSplitComplex(styles, ",", &n, MS_ALLOWEMPTYTOKENS);
17✔
1789
    for (int i = 0; i < n; i++) {
45✔
1790
      if (tokens[i] && strlen(tokens[i]) > 0 &&
28✔
1791
          strcasecmp(tokens[i], "default") != 0) {
18✔
1792
        if (!hasCheckedLayerUnicity) {
15✔
1793
          hasCheckedLayerUnicity = true;
1794
          bool bLayerInserted = false;
1795

1796
          /* --------------------------------------------------------------------
1797
           */
1798
          /*      If the same layer is given more that once, we need to */
1799
          /*      duplicate it. */
1800
          /* --------------------------------------------------------------------
1801
           */
1802
          for (size_t m = 0; m < wmslayers.size(); m++) {
28✔
1803
            for (size_t l = m + 1; l < wmslayers.size(); l++) {
25✔
1804
              const int nIndex = msGetLayerIndex(map, wmslayers[m].c_str());
8✔
1805
              if (nIndex != -1 &&
8✔
1806
                  strcasecmp(wmslayers[m].c_str(), wmslayers[l].c_str()) == 0) {
8✔
1807
                layerObj *psTmpLayer = (layerObj *)malloc(sizeof(layerObj));
6✔
1808
                initLayer(psTmpLayer, map);
6✔
1809
                msCopyLayer(psTmpLayer, GET_LAYER(map, nIndex));
6✔
1810
                /* open the source layer */
1811
                if (!psTmpLayer->vtable)
6✔
1812
                  msInitializeVirtualTable(psTmpLayer);
6✔
1813

1814
                /*make the name unique*/
1815
                char tmpId[128];
1816
                snprintf(tmpId, sizeof(tmpId), "%lx_%x_%d", (long)time(NULL),
6✔
1817
                         (int)getpid(), map->numlayers);
6✔
1818
                if (psTmpLayer->name)
6✔
1819
                  msFree(psTmpLayer->name);
6✔
1820
                psTmpLayer->name = msStrdup(tmpId);
6✔
1821
                wmslayers[l] = tmpId;
1822

1823
                layerCopyIndex = msInsertLayer(map, psTmpLayer, -1);
6✔
1824

1825
                // expand the array mapping map layer index to filter indexes
1826
                ows_request->layerwmsfilterindex =
6✔
1827
                    (int *)msSmallRealloc(ows_request->layerwmsfilterindex,
6✔
1828
                                          map->numlayers * sizeof(int));
6✔
1829
                ows_request->layerwmsfilterindex[layerCopyIndex] =
6✔
1830
                    l; // the filter index matches the index of the layer name
1831
                       // in the WMS param
1832

1833
                bLayerInserted = true;
1834
                /* layer was copied, we need to decrement its refcount */
1835
                MS_REFCNT_DECR(psTmpLayer);
6✔
1836
              }
1837
            }
1838
          }
1839

1840
          if (bLayerInserted) {
11✔
1841
            msOWSRequestLayersEnabled(map, "M", "GetMap", ows_request);
4✔
1842
          }
1843
        }
1844

1845
        if (static_cast<int>(wmslayers.size()) == n) {
15✔
1846
          for (int j = 0; j < map->numlayers; j++) {
128✔
1847
            layerObj *lp = GET_LAYER(map, j);
113✔
1848
            if ((lp->name && strcasecmp(lp->name, wmslayers[i].c_str()) == 0) ||
113✔
1849
                (lp->group &&
99✔
1850
                 strcasecmp(lp->group, wmslayers[i].c_str()) == 0)) {
4✔
1851
              bool found = false;
1852
              for (int k = 0; k < lp->numclasses; k++) {
26✔
1853
                if (lp->_class[k]->group &&
26✔
1854
                    strcasecmp(lp->_class[k]->group, tokens[i]) == 0) {
26✔
1855
                  msFree(lp->classgroup);
14✔
1856
                  lp->classgroup = msStrdup(tokens[i]);
14✔
1857
                  found = true;
1858
                  break;
1859
                }
1860
              }
1861
              if (!found) {
1862
                msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
1863
                                     "Style (%s) not defined on layer.",
1864
                                     "msWMSLoadGetMapParams()", tokens[i]);
1865
                msFreeCharArray(tokens, n);
×
1866

1867
                return msWMSException(map, nVersion, "StyleNotDefined",
×
1868
                                      wms_exception_format);
×
1869
              }
1870
              /* Check the style of the root layer */
1871
            } else if (map->name &&
99✔
1872
                       strcasecmp(map->name, wmslayers[i].c_str()) == 0) {
99✔
1873
              const char *styleName =
1874
                  msOWSLookupMetadata(&(map->web.metadata), "MO", "style_name");
15✔
1875
              if (styleName == NULL)
15✔
1876
                styleName = "default";
1877
              char *pszEncodedStyleName = msEncodeHTMLEntities(styleName);
15✔
1878
              if (strcasecmp(pszEncodedStyleName, tokens[i]) != 0) {
15✔
1879
                msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
1880
                                     "Style (%s) not defined on root layer.",
1881
                                     "msWMSLoadGetMapParams()", tokens[i]);
1882
                msFreeCharArray(tokens, n);
×
1883
                msFree(pszEncodedStyleName);
×
1884

1885
                return msWMSException(map, nVersion, "StyleNotDefined",
×
1886
                                      wms_exception_format);
1887
              }
1888
              msFree(pszEncodedStyleName);
15✔
1889
            }
1890
          }
1891
        } else {
1892
          msSetErrorWithStatus(
×
1893
              MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1894
              "Invalid style (%s). Mapserver is expecting an empty "
1895
              "string for the STYLES : STYLES= or STYLES=,,, or using "
1896
              "keyword default  STYLES=default,default, ...",
1897
              "msWMSLoadGetMapParams()", styles);
1898
          msFreeCharArray(tokens, n);
×
1899
          return msWMSException(map, nVersion, "StyleNotDefined",
×
1900
                                wms_exception_format);
1901
        }
1902
      }
1903
    }
1904
    msFreeCharArray(tokens, n);
17✔
1905
  }
1906

1907
  /*
1908
  ** WMS extents are edge to edge while MapServer extents are center of
1909
  ** pixel to center of pixel.  Here we try to adjust the WMS extents
1910
  ** in by half a pixel.  We wait till here because we want to ensure we
1911
  ** are doing this in terms of the correct WIDTH and HEIGHT.
1912
  */
1913
  if (adjust_extent && map->width > 1 && map->height > 1 &&
483✔
1914
      !bbox_pixel_is_point) {
1915
    double dx, dy;
1916

1917
    dx = (map->extent.maxx - map->extent.minx) / map->width;
463✔
1918
    map->extent.minx += dx * 0.5;
463✔
1919
    map->extent.maxx -= dx * 0.5;
463✔
1920

1921
    dy = (map->extent.maxy - map->extent.miny) / map->height;
463✔
1922
    map->extent.miny += dy * 0.5;
463✔
1923
    map->extent.maxy -= dy * 0.5;
463✔
1924
  }
1925

1926
  if (request && strcasecmp(request, "DescribeLayer") != 0) {
483✔
1927
    if (!srsfound) {
464✔
1928
      if (nVersion >= OWS_1_3_0)
3✔
1929
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
2✔
1930
                             "Missing required parameter CRS",
1931
                             "msWMSLoadGetMapParams()");
1932
      else
1933
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
1934
                             "Missing required parameter SRS",
1935
                             "msWMSLoadGetMapParams()");
1936

1937
      return msWMSException(map, nVersion, "MissingParameterValue",
3✔
1938
                            wms_exception_format);
1939
    }
1940

1941
    if (!bboxfound) {
461✔
1942
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
1943
                           "Missing required parameter BBOX",
1944
                           "msWMSLoadGetMapParams()");
1945
      return msWMSException(map, nVersion, "MissingParameterValue",
1✔
1946
                            wms_exception_format);
1947
    }
1948

1949
    if (!formatfound && (strcasecmp(request, "GetMap") == 0 ||
460✔
1950
                         strcasecmp(request, "map") == 0)) {
6✔
1951
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
1952
                           "Missing required parameter FORMAT",
1953
                           "msWMSLoadGetMapParams()");
1954
      return msWMSException(map, nVersion, "MissingParameterValue",
1✔
1955
                            wms_exception_format);
1956
    }
1957

1958
    if (!widthfound) {
459✔
1959
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
1960
                           "Missing required parameter WIDTH",
1961
                           "msWMSLoadGetMapParams()");
1962
      return msWMSException(map, nVersion, "MissingParameterValue",
1✔
1963
                            wms_exception_format);
1964
    }
1965

1966
    if (!heightfound) {
458✔
1967
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
1968
                           "Missing required parameter HEIGHT",
1969
                           "msWMSLoadGetMapParams()");
1970
      return msWMSException(map, nVersion, "MissingParameterValue",
1✔
1971
                            wms_exception_format);
1972
    }
1973

1974
    if (styles == nullptr && sld_url == nullptr && sld_body == nullptr &&
82✔
1975
        (strcasecmp(request, "GetMap") == 0 ||
5✔
1976
         strcasecmp(request, "GetFeatureInfo") == 0) &&
462✔
1977
        msOWSLookupMetadata(&(map->web.metadata), "M",
1✔
1978
                            "allow_getmap_without_styles") == nullptr) {
1979
      msSetErrorWithStatus(
1✔
1980
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1981
          "Missing required parameter STYLES. Note to service administrators: "
1982
          "defining the \"wms_allow_getmap_without_styles\" \"true\" "
1983
          "MAP.WEB.METADATA "
1984
          "item will disable this check (backward compatibility with behavior "
1985
          "of MapServer < 8.0)",
1986
          "msWMSLoadGetMapParams()");
1987
      return msWMSException(map, nVersion, "MissingParameterValue",
1✔
1988
                            wms_exception_format);
1989
    }
1990
  }
1991

1992
  /*
1993
  ** Apply vendor-specific filter if specified
1994
  */
1995
  if (filter) {
475✔
1996
    if (sld_url || sld_body) {
13✔
1997
      msSetErrorWithStatus(
×
1998
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1999
          "Vendor-specific FILTER parameter cannot be used with SLD or "
2000
          "SLD_BODY.",
2001
          "msWMSLoadGetMapParams()");
2002
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
2003
    }
2004

2005
    if (msWMSApplyFilter(map, nVersion, filter, need_axis_swap,
13✔
2006
                         wms_exception_format, ows_request) == MS_FAILURE) {
2007
      return MS_FAILURE; /* msWMSException(map, nVersion,
2008
                            "InvalidFilterRequest"); */
2009
    }
2010
  }
2011

2012
  return MS_SUCCESS;
2013
}
529✔
2014

2015
/*
2016
**
2017
*/
2018
static void msWMSPrintRequestCap(int nVersion, const char *request,
264✔
2019
                                 const char *script_url, const char *formats,
2020
                                 ...) {
2021
  va_list argp;
2022

2023
  msIO_printf("    <%s>\n", request);
264✔
2024

2025
  /* We expect to receive a NULL-terminated args list of formats */
2026
  va_start(argp, formats);
264✔
2027
  const char *fmt = formats;
2028
  while (fmt != NULL) {
1,244✔
2029
    /* Special case for early WMS with subelements in Format (bug 908) */
2030
    char *encoded;
2031
    if (nVersion <= OWS_1_0_7) {
980✔
2032
      encoded = msStrdup(fmt);
3✔
2033
    }
2034

2035
    /* otherwise we HTML code special characters */
2036
    else {
2037
      encoded = msEncodeHTMLEntities(fmt);
977✔
2038
    }
2039

2040
    msIO_printf("      <Format>%s</Format>\n", encoded);
980✔
2041
    msFree(encoded);
980✔
2042

2043
    fmt = va_arg(argp, const char *);
980✔
2044
  }
2045
  va_end(argp);
264✔
2046

2047
  msIO_printf("      <DCPType>\n");
264✔
2048
  msIO_printf("        <HTTP>\n");
264✔
2049
  /* The URL should already be HTML encoded. */
2050
  if (nVersion == OWS_1_0_0) {
264✔
2051
    msIO_printf("          <Get onlineResource=\"%s\" />\n", script_url);
3✔
2052
    msIO_printf("          <Post onlineResource=\"%s\" />\n", script_url);
3✔
2053
  } else {
2054
    msIO_printf("          <Get><OnlineResource "
261✔
2055
                "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
2056
                "xlink:href=\"%s\"/></Get>\n",
2057
                script_url);
2058
    msIO_printf("          <Post><OnlineResource "
261✔
2059
                "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
2060
                "xlink:href=\"%s\"/></Post>\n",
2061
                script_url);
2062
  }
2063

2064
  msIO_printf("        </HTTP>\n");
264✔
2065
  msIO_printf("      </DCPType>\n");
264✔
2066
  msIO_printf("    </%s>\n", request);
264✔
2067
}
264✔
2068

2069
void msWMSPrintAttribution(FILE *stream, const char *tabspace,
238✔
2070
                           hashTableObj *metadata,
2071
                           const char * /*namespaces*/) {
2072
  if (stream && metadata) {
238✔
2073
    const char *title =
2074
        msOWSLookupMetadata(metadata, "MO", "attribution_title");
238✔
2075
    const char *onlineres =
2076
        msOWSLookupMetadata(metadata, "MO", "attribution_onlineresource");
238✔
2077
    const char *logourl =
2078
        msOWSLookupMetadata(metadata, "MO", "attribution_logourl_width");
238✔
2079

2080
    if (title || onlineres || logourl) {
238✔
2081
      msIO_printf("%s<Attribution>\n", tabspace);
3✔
2082
      if (title) {
3✔
2083
        char *pszEncodedValue = msEncodeHTMLEntities(title);
3✔
2084
        msIO_fprintf(stream, "%s%s<Title>%s</Title>\n", tabspace, tabspace,
3✔
2085
                     pszEncodedValue);
2086
        free(pszEncodedValue);
3✔
2087
      }
2088

2089
      if (onlineres) {
3✔
2090
        char *pszEncodedValue = msEncodeHTMLEntities(onlineres);
1✔
2091
        msIO_fprintf(
1✔
2092
            stream,
2093
            "%s%s<OnlineResource xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
2094
            "xlink:href=\"%s\"/>\n",
2095
            tabspace, tabspace, pszEncodedValue);
2096
        free(pszEncodedValue);
1✔
2097
      }
2098

2099
      if (logourl) {
3✔
2100
        msOWSPrintURLType(stream, metadata, "MO", "attribution_logourl",
1✔
2101
                          OWS_NOERR, NULL, "LogoURL", NULL, " width=\"%s\"",
2102
                          " height=\"%s\"",
2103
                          ">\n             <Format>%s</Format",
2104
                          "\n             <OnlineResource "
2105
                          "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
2106
                          " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
2107
                          "          ",
2108
                          MS_FALSE, MS_TRUE, MS_TRUE, MS_TRUE, MS_TRUE, NULL,
2109
                          NULL, NULL, NULL, NULL, "        ");
2110
      }
2111
      msIO_printf("%s</Attribution>\n", tabspace);
3✔
2112
    }
2113
  }
2114
}
238✔
2115

2116
/*
2117
** msWMSPrintScaleHint()
2118
**
2119
** Print a Min/MaxScaleDenominator tag for the layer if applicable.
2120
** used for WMS >=1.3.0
2121
*/
2122
void msWMSPrintScaleDenominator(const char *tabspace, double minscaledenom,
118✔
2123
                                double maxscaledenom) {
2124
  if (minscaledenom > 0)
118✔
2125
    msIO_printf("%s<MinScaleDenominator>%g</MinScaleDenominator>\n", tabspace,
8✔
2126
                minscaledenom);
2127

2128
  if (maxscaledenom > 0)
118✔
2129
    msIO_printf("%s<MaxScaleDenominator>%g</MaxScaleDenominator>\n", tabspace,
2✔
2130
                maxscaledenom);
2131
}
118✔
2132

2133
/*
2134
** msWMSPrintScaleHint()
2135
**
2136
** Print a ScaleHint tag for this layer if applicable.
2137
**
2138
** (see WMS 1.1.0 sect. 7.1.5.4) The WMS defines the scalehint values as
2139
** the ground distance in meters of the southwest to northeast diagonal of
2140
** the central pixel of a map.  ScaleHint values are the min and max
2141
** recommended values of that diagonal.
2142
*/
2143
void msWMSPrintScaleHint(const char *tabspace, double minscaledenom,
120✔
2144
                         double maxscaledenom, double resolution) {
2145
  double scalehintmin = 0.0, scalehintmax = 0.0;
2146

2147
  const double diag = sqrt(2.0);
2148

2149
  if (minscaledenom > 0)
120✔
2150
    scalehintmin =
5✔
2151
        diag * (minscaledenom / resolution) / msInchesPerUnit(MS_METERS, 0);
5✔
2152
  if (maxscaledenom > 0)
120✔
2153
    scalehintmax =
2✔
2154
        diag * (maxscaledenom / resolution) / msInchesPerUnit(MS_METERS, 0);
2✔
2155

2156
  if (scalehintmin > 0.0 || scalehintmax > 0.0) {
120✔
2157
    msIO_printf("%s<ScaleHint min=\"%.15g\" max=\"%.15g\" />\n", tabspace,
5✔
2158
                scalehintmin, scalehintmax);
2159
    if (scalehintmax == 0.0)
5✔
2160
      msIO_printf("%s<!-- WARNING: Only MINSCALEDENOM and no MAXSCALEDENOM "
3✔
2161
                  "specified in "
2162
                  "the mapfile. A default value of 0 has been returned for the "
2163
                  "Max ScaleHint but this is probably not what you want. -->\n",
2164
                  tabspace);
2165
  }
2166
}
120✔
2167

2168
/*
2169
** msWMSPrintAuthorityURL()
2170
**
2171
** Print an AuthorityURL tag if applicable.
2172
*/
2173
void msWMSPrintAuthorityURL(FILE *stream, const char *tabspace,
238✔
2174
                            hashTableObj *metadata, const char *namespaces) {
2175
  if (stream && metadata) {
238✔
2176
    const char *pszWmsAuthorityName =
2177
        msOWSLookupMetadata(metadata, namespaces, "authorityurl_name");
238✔
2178
    const char *pszWMSAuthorityHref =
2179
        msOWSLookupMetadata(metadata, namespaces, "authorityurl_href");
238✔
2180

2181
    /* AuthorityURL only makes sense if you have *both* the name and url */
2182
    if (pszWmsAuthorityName && pszWMSAuthorityHref) {
238✔
2183
      msOWSPrintEncodeMetadata(
7✔
2184
          stream, metadata, namespaces, "authorityurl_name", OWS_NOERR,
2185
          (std::string(tabspace) + "<AuthorityURL name=\"%s\">\n").c_str(),
7✔
2186
          NULL);
2187
      msOWSPrintEncodeMetadata(
7✔
2188
          stream, metadata, namespaces, "authorityurl_href", OWS_NOERR,
2189
          (std::string(tabspace) +
7✔
2190
           "  <OnlineResource xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
2191
           "xlink:href=\"%s\"/>\n")
2192
              .c_str(),
2193
          NULL);
2194
      msIO_printf("%s</AuthorityURL>\n", tabspace);
7✔
2195
    } else if (pszWmsAuthorityName || pszWMSAuthorityHref) {
231✔
2196
      msIO_printf(
×
2197
          "%s<!-- WARNING: Both wms_authorityurl_name and "
2198
          "wms_authorityurl_href must be set to output an AuthorityURL -->\n",
2199
          tabspace);
2200
    }
2201
  }
2202
}
238✔
2203

2204
/*
2205
** msWMSPrintIdentifier()
2206
**
2207
** Print an Identifier tag if applicable.
2208
*/
2209
void msWMSPrintIdentifier(FILE *stream, const char *tabspace,
238✔
2210
                          hashTableObj *metadata, const char *namespaces) {
2211
  if (stream && metadata) {
238✔
2212
    const char *pszWMSIdentifierAuthority =
2213
        msOWSLookupMetadata(metadata, namespaces, "identifier_authority");
238✔
2214
    const char *pszWMSIdentifierValue =
2215
        msOWSLookupMetadata(metadata, namespaces, "identifier_value");
238✔
2216

2217
    /* Identifier only makes sense if you have *both* the authority and value */
2218
    if (pszWMSIdentifierAuthority && pszWMSIdentifierValue) {
238✔
2219
      msOWSPrintEncodeMetadata(
73✔
2220
          stream, metadata, namespaces, "identifier_authority", OWS_NOERR,
2221
          (std::string(tabspace) + "<Identifier authority=\"%s\">").c_str(),
73✔
2222
          NULL);
2223
      msOWSPrintEncodeMetadata(stream, metadata, namespaces, "identifier_value",
73✔
2224
                               OWS_NOERR, "%s</Identifier>\n", NULL);
2225
    } else if (pszWMSIdentifierAuthority || pszWMSIdentifierValue) {
165✔
2226
      msIO_printf(
×
2227
          "%s<!-- WARNING: Both wms_identifier_authority and "
2228
          "wms_identifier_value must be set to output an Identifier -->\n",
2229
          tabspace);
2230
    }
2231
  }
2232
}
238✔
2233

2234
/*
2235
** msWMSPrintKeywordlist()
2236
**
2237
** Print a Keywordlist tag if applicable.
2238
*/
2239
void msWMSPrintKeywordlist(FILE *stream, const char *tabspace, const char *name,
295✔
2240
                           hashTableObj *metadata, const char *namespaces,
2241
                           int nVersion) {
2242
  std::string newname(name); /* max. rootlayer_keywordlist_items          */
295✔
2243
  newname += "_items";
2244

2245
  std::string vocname(name); /* max. rootlayer_keywordlist_vocabulary     */
295✔
2246
  vocname += "_vocabulary";
2247

2248
  if (nVersion == OWS_1_0_0) {
295✔
2249
    /* <Keywords> in V 1.0.0 */
2250
    /* The 1.0.0 spec doesn't specify which delimiter to use so let's use spaces
2251
     */
2252
    msOWSPrintEncodeMetadataList(
1✔
2253
        stream, metadata, namespaces, name,
2254
        (std::string(tabspace) + "<Keywords>").c_str(),
2✔
2255
        (std::string(tabspace) + "</Keywords>\n").c_str(), "%s ", NULL);
2✔
2256
  } else if (msOWSLookupMetadata(metadata, namespaces, name) ||
417✔
2257
             msOWSLookupMetadata(metadata, namespaces, newname.c_str()) ||
417✔
2258
             msOWSLookupMetadata(metadata, namespaces, vocname.c_str())) {
123✔
2259
    /* <KeywordList><Keyword> ... in V1.0.6+ */
2260
    msIO_printf("%s<KeywordList>\n", tabspace);
180✔
2261
    std::string template1(tabspace);
180✔
2262
    template1 += "    <Keyword>%s</Keyword>\n";
2263
    /* print old styled ..._keywordlist */
2264
    msOWSPrintEncodeMetadataList(stream, metadata, namespaces, name, NULL, NULL,
180✔
2265
                                 template1.c_str(), NULL);
2266
    /* print new styled ..._keywordlist_items */
2267
    msOWSPrintEncodeMetadataList(stream, metadata, namespaces, newname.c_str(),
180✔
2268
                                 NULL, NULL, template1.c_str(), NULL);
2269

2270
    /* find out if there's a vocabulary list set */
2271
    const char *vocabularylist =
2272
        msOWSLookupMetadata(metadata, namespaces, vocname.c_str());
180✔
2273
    if (vocabularylist && nVersion >= OWS_1_3_0) {
180✔
2274
      const auto tokens = msStringSplit(vocabularylist, ',');
9✔
2275
      for (const auto &token : tokens) {
18✔
2276
        msOWSPrintEncodeMetadataList(
9✔
2277
            stream, metadata, namespaces,
2278
            (std::string(name) + '_' + token + "_items").c_str(), NULL, NULL,
18✔
2279
            (std::string(tabspace) + "    <Keyword vocabulary=\"" + token +
18✔
2280
             "\">%s</Keyword>\n")
2281
                .c_str(),
2282
            NULL);
2283
      }
2284
    }
9✔
2285
    msIO_printf("%s</KeywordList>\n", tabspace);
180✔
2286
  }
2287
}
295✔
2288

2289
/*
2290
** msDumpLayer()
2291
*/
2292
static int msDumpLayer(mapObj *map, layerObj *lp, int nVersion,
185✔
2293
                       const char *script_url_encoded, const char *indent,
2294
                       const char *validated_language, int grouplayer,
2295
                       int hasQueryableSubLayers) {
2296
  rectObj ext;
2297
  char **classgroups = NULL;
2298
  int iclassgroups = 0;
2299
  char *pszMapEPSG, *pszLayerEPSG;
2300

2301
  /* if the layer status is set to MS_DEFAULT, output a warning */
2302
  if (lp->status == MS_DEFAULT)
185✔
2303
    msIO_fprintf(stdout,
4✔
2304
                 "<!-- WARNING: This layer has its status set to DEFAULT and "
2305
                 "will always be displayed when doing a GetMap request even if "
2306
                 "it is not requested by the client. This is not in line with "
2307
                 "the expected behavior of a WMS server. Using status ON or "
2308
                 "OFF is recommended. -->\n");
2309

2310
  if (nVersion <= OWS_1_0_7) {
185✔
2311
    msIO_printf("%s    <Layer queryable=\"%d\">\n", indent,
×
2312
                hasQueryableSubLayers || msIsLayerQueryable(lp));
×
2313
  } else {
2314
    /* 1.1.0 and later: opaque and cascaded are new. */
2315
    int cascaded = 0, opaque = 0;
2316
    const char *value = msOWSLookupMetadata(&(lp->metadata), "MO", "opaque");
185✔
2317
    if (value != NULL)
185✔
2318
      opaque = atoi(value);
2319
    if (lp->connectiontype == MS_WMS)
185✔
2320
      cascaded = 1;
2321

2322
    msIO_printf(
185✔
2323
        "%s    <Layer queryable=\"%d\" opaque=\"%d\" cascaded=\"%d\">\n",
2324
        indent, hasQueryableSubLayers || msIsLayerQueryable(lp), opaque,
185✔
2325
        cascaded);
2326
  }
2327

2328
  if (lp->name && strlen(lp->name) > 0 &&
370✔
2329
      (msIsXMLTagValid(lp->name) == MS_FALSE || isdigit(lp->name[0])))
370✔
2330
    msIO_fprintf(stdout,
×
2331
                 "<!-- WARNING: The layer name '%s' might contain spaces or "
2332
                 "invalid characters or may start with a number. This could "
2333
                 "lead to potential problems. -->\n",
2334
                 lp->name);
2335
  msOWSPrintEncodeParam(stdout, "LAYER.NAME", lp->name, OWS_NOERR,
185✔
2336
                        "        <Name>%s</Name>\n", NULL);
2337

2338
  /* the majority of this section is dependent on appropriately named metadata
2339
   * in the LAYER object */
2340
  msOWSPrintEncodeMetadata2(stdout, &(lp->metadata), "MO", "title", OWS_WARN,
185✔
2341
                            "        <Title>%s</Title>\n", lp->name,
185✔
2342
                            validated_language);
2343

2344
  msOWSPrintEncodeMetadata2(stdout, &(lp->metadata), "MO", "abstract",
185✔
2345
                            OWS_NOERR, "        <Abstract>%s</Abstract>\n",
2346
                            NULL, validated_language);
2347

2348
  msWMSPrintKeywordlist(stdout, "        ", "keywordlist", &(lp->metadata),
185✔
2349
                        "MO", nVersion);
2350

2351
  msOWSGetEPSGProj(&(map->projection), &(map->web.metadata), "MO", MS_FALSE,
185✔
2352
                   &pszMapEPSG);
2353
  msOWSGetEPSGProj(&(lp->projection), &(lp->metadata), "MO", MS_FALSE,
185✔
2354
                   &pszLayerEPSG);
2355
  if (pszMapEPSG == NULL) {
185✔
2356
    /* If map has no proj then every layer MUST have one or produce a warning */
2357
    if (nVersion > OWS_1_1_0) {
×
2358
      /* starting 1.1.1 SRS are given in individual tags */
2359
      if (nVersion >= OWS_1_3_0) {
×
2360
        msOWSPrintEncodeParamList(stdout,
×
2361
                                  "(at least one of) "
2362
                                  "MAP.PROJECTION, LAYER.PROJECTION "
2363
                                  "or wms_srs metadata",
2364
                                  pszLayerEPSG, OWS_WARN, ' ', NULL, NULL,
2365
                                  "        <CRS>%s</CRS>\n", NULL);
2366
      } else {
2367
        msOWSPrintEncodeParamList(stdout,
×
2368
                                  "(at least one of) "
2369
                                  "MAP.PROJECTION, LAYER.PROJECTION "
2370
                                  "or wms_srs metadata",
2371
                                  pszLayerEPSG, OWS_WARN, ' ', NULL, NULL,
2372
                                  "        <SRS>%s</SRS>\n", NULL);
2373
      }
2374
    } else {
2375
      msOWSPrintEncodeParam(stdout,
×
2376
                            "(at least one of) MAP.PROJECTION, "
2377
                            "LAYER.PROJECTION or wms_srs metadata",
2378
                            pszLayerEPSG, OWS_WARN, "        <SRS>%s</SRS>\n",
2379
                            NULL);
2380
    }
2381
  } else {
2382
    /* No warning required in this case since there's at least a map proj. */
2383
    if (nVersion > OWS_1_1_0) {
185✔
2384
      /* starting 1.1.1 SRS are given in individual tags */
2385
      if (nVersion >= OWS_1_3_0) {
172✔
2386
        msOWSPrintEncodeParamList(stdout,
91✔
2387
                                  "(at least one of) "
2388
                                  "MAP.PROJECTION, LAYER.PROJECTION "
2389
                                  "or wms_srs metadata",
2390
                                  pszLayerEPSG, OWS_NOERR, ' ', NULL, NULL,
2391
                                  "        <CRS>%s</CRS>\n", NULL);
2392
      } else {
2393
        msOWSPrintEncodeParamList(stdout,
81✔
2394
                                  "(at least one of) "
2395
                                  "MAP.PROJECTION, LAYER.PROJECTION "
2396
                                  "or wms_srs metadata",
2397
                                  pszLayerEPSG, OWS_NOERR, ' ', NULL, NULL,
2398
                                  "        <SRS>%s</SRS>\n", NULL);
2399
      }
2400
    } else {
2401
      msOWSPrintEncodeParam(stdout, " LAYER.PROJECTION (or wms_srs metadata)",
13✔
2402
                            pszLayerEPSG, OWS_NOERR, "        <SRS>%s</SRS>\n",
2403
                            NULL);
2404
    }
2405
  }
2406
  msFree(pszLayerEPSG);
185✔
2407
  msFree(pszMapEPSG);
185✔
2408

2409
  /* If layer has no proj set then use map->proj for bounding box. */
2410
  if (msOWSGetLayerExtent(map, lp, "MO", &ext) == MS_SUCCESS) {
185✔
2411
    if (lp->projection.numargs > 0) {
172✔
2412
      if (nVersion >= OWS_1_3_0)
165✔
2413
        msOWSPrintEX_GeographicBoundingBox(stdout, "        ", &(ext),
82✔
2414
                                           &(lp->projection));
2415
      else
2416
        msOWSPrintLatLonBoundingBox(stdout, "        ", &(ext),
83✔
2417
                                    &(lp->projection), NULL, OWS_WMS);
2418

2419
      msOWSPrintBoundingBox(stdout, "        ", &(ext), &(lp->projection),
165✔
2420
                            &(lp->metadata), &(map->web.metadata), "MO",
2421
                            nVersion);
2422
    } else {
2423
      if (nVersion >= OWS_1_3_0)
7✔
2424
        msOWSPrintEX_GeographicBoundingBox(stdout, "        ", &(ext),
6✔
2425
                                           &(map->projection));
2426
      else
2427
        msOWSPrintLatLonBoundingBox(stdout, "        ", &(ext),
1✔
2428
                                    &(map->projection), NULL, OWS_WMS);
2429
      msOWSPrintBoundingBox(stdout, "        ", &(ext), &(map->projection),
7✔
2430
                            &(lp->metadata), &(map->web.metadata), "MO",
2431
                            nVersion);
2432
    }
2433
  } else {
2434
    if (nVersion >= OWS_1_3_0)
13✔
2435
      msIO_printf(
3✔
2436
          "        <!-- WARNING: Optional Ex_GeographicBoundingBox could not "
2437
          "be established for this layer.  Consider setting the EXTENT in the "
2438
          "LAYER object, or wms_extent metadata. Also check that your data "
2439
          "exists in the DATA statement -->\n");
2440
    else
2441
      msIO_printf("        <!-- WARNING: Optional LatLonBoundingBox could not "
10✔
2442
                  "be established for this layer.  Consider setting the EXTENT "
2443
                  "in the LAYER object, or wms_extent metadata. Also check "
2444
                  "that your data exists in the DATA statement -->\n");
2445
  }
2446

2447
  /* time support */
2448
  const char *pszWmsTimeExtent =
2449
      msOWSLookupMetadata(&(lp->metadata), "MO", "timeextent");
185✔
2450
  if (pszWmsTimeExtent) {
185✔
2451
    const char *pszWmsTimeDefault =
2452
        msOWSLookupMetadata(&(lp->metadata), "MO", "timedefault");
4✔
2453

2454
    if (nVersion >= OWS_1_3_0) {
4✔
2455
      if (pszWmsTimeDefault)
2✔
2456
        msIO_fprintf(stdout,
×
2457
                     "        <Dimension name=\"time\" units=\"ISO8601\" "
2458
                     "default=\"%s\" nearestValue=\"0\">%s</Dimension>\n",
2459
                     pszWmsTimeDefault, pszWmsTimeExtent);
2460
      else
2461
        msIO_fprintf(stdout,
2✔
2462
                     "        <Dimension name=\"time\" units=\"ISO8601\" "
2463
                     "nearestValue=\"0\">%s</Dimension>\n",
2464
                     pszWmsTimeExtent);
2465
    }
2466

2467
    else {
2468
      msIO_fprintf(stdout,
2✔
2469
                   "        <Dimension name=\"time\" units=\"ISO8601\"/>\n");
2470
      if (pszWmsTimeDefault)
2✔
2471
        msIO_fprintf(stdout,
×
2472
                     "        <Extent name=\"time\" default=\"%s\" "
2473
                     "nearestValue=\"0\">%s</Extent>\n",
2474
                     pszWmsTimeDefault, pszWmsTimeExtent);
2475
      else
2476
        msIO_fprintf(
2✔
2477
            stdout,
2478
            "        <Extent name=\"time\" nearestValue=\"0\">%s</Extent>\n",
2479
            pszWmsTimeExtent);
2480
    }
2481
  }
2482

2483
  /*dimensions support: elevation + other user defined dimensions*/
2484
  const char *pszDimensionlist =
2485
      msOWSLookupMetadata(&(lp->metadata), "M", "dimensionlist");
185✔
2486
  if (pszDimensionlist) {
185✔
2487
    auto tokens = msStringSplit(pszDimensionlist, ',');
6✔
2488
    for (auto &dimension : tokens) {
14✔
2489
      /*check if manadatory unit and extent are set. Item should also be set.
2490
       * default value is optional*/
2491
      msStringTrim(dimension);
8✔
2492

2493
      const char *pszDimensionItem = msOWSLookupMetadata(
8✔
2494
          &(lp->metadata), "M", (dimension + "_item").c_str());
8✔
2495
      const char *pszDimensionExtent = msOWSLookupMetadata(
8✔
2496
          &(lp->metadata), "M", (dimension + "_extent").c_str());
8✔
2497
      const char *pszDimensionUnit = msOWSLookupMetadata(
8✔
2498
          &(lp->metadata), "M", (dimension + "_units").c_str());
8✔
2499
      const char *pszDimensionDefault = msOWSLookupMetadata(
8✔
2500
          &(lp->metadata), "M", (dimension + "_default").c_str());
8✔
2501

2502
      if (pszDimensionItem && pszDimensionExtent && pszDimensionUnit) {
8✔
2503
        if (nVersion >= OWS_1_3_0) {
8✔
2504
          if (pszDimensionDefault && strlen(pszDimensionDefault) > 0)
4✔
2505
            msIO_fprintf(
1✔
2506
                stdout,
2507
                "        <Dimension name=\"%s\" units=\"%s\" default=\"%s\" "
2508
                "multipleValues=\"1\" nearestValue=\"0\">%s</Dimension>\n",
2509
                dimension.c_str(), pszDimensionUnit, pszDimensionDefault,
2510
                pszDimensionExtent);
2511
          else
2512
            msIO_fprintf(
3✔
2513
                stdout,
2514
                "        <Dimension name=\"%s\" units=\"%s\"  "
2515
                "multipleValues=\"1\"  nearestValue=\"0\">%s</Dimension>\n",
2516
                dimension.c_str(), pszDimensionUnit, pszDimensionExtent);
2517
        } else {
2518
          msIO_fprintf(stdout,
4✔
2519
                       "        <Dimension name=\"%s\" units=\"%s\"/>\n",
2520
                       dimension.c_str(), pszDimensionUnit);
2521
          if (pszDimensionDefault && strlen(pszDimensionDefault) > 0)
4✔
2522
            msIO_fprintf(stdout,
1✔
2523
                         "        <Extent name=\"%s\" default=\"%s\" "
2524
                         "nearestValue=\"0\">%s</Extent>\n",
2525
                         dimension.c_str(), pszDimensionDefault,
2526
                         pszDimensionExtent);
2527
          else
2528
            msIO_fprintf(
3✔
2529
                stdout,
2530
                "        <Extent name=\"%s\" nearestValue=\"0\">%s</Extent>\n",
2531
                dimension.c_str(), pszDimensionExtent);
2532
        }
2533
      }
2534
    }
2535
  }
6✔
2536

2537
  if (nVersion >= OWS_1_0_7) {
185✔
2538
    msWMSPrintAttribution(stdout, "    ", &(lp->metadata), "MO");
185✔
2539
  }
2540

2541
  /* AuthorityURL support and Identifier support, only available >= WMS 1.1.0 */
2542
  if (nVersion >= OWS_1_1_0) {
185✔
2543
    msWMSPrintAuthorityURL(stdout, "        ", &(lp->metadata), "MO");
185✔
2544
    msWMSPrintIdentifier(stdout, "        ", &(lp->metadata), "MO");
185✔
2545
  }
2546

2547
  if (nVersion >= OWS_1_1_0) {
2548
    const char *metadataurl_list =
2549
        msOWSLookupMetadata(&(lp->metadata), "MO", "metadataurl_list");
185✔
2550
    if (metadataurl_list) {
185✔
2551
      const auto tokens = msStringSplit(metadataurl_list, ' ');
1✔
2552
      for (const auto &token : tokens) {
3✔
2553
        std::string key("metadataurl_");
2✔
2554
        key += token;
2555
        msOWSPrintURLType(stdout, &(lp->metadata), "MO", key.c_str(), OWS_NOERR,
2✔
2556
                          NULL, "MetadataURL", " type=\"%s\"", NULL, NULL,
2557
                          ">\n          <Format>%s</Format",
2558
                          "\n          <OnlineResource xmlns:xlink=\""
2559
                          "http://www.w3.org/1999/xlink\" "
2560
                          "xlink:type=\"simple\" xlink:href=\"%s\"/>\n        ",
2561
                          MS_TRUE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
2562
                          NULL, NULL, NULL, NULL, "        ");
2563
      }
2564
    } else {
1✔
2565
      if (!msOWSLookupMetadata(&(lp->metadata), "MO", "metadataurl_href"))
184✔
2566
        msMetadataSetGetMetadataURL(lp, script_url_encoded);
95✔
2567

2568
      msOWSPrintURLType(stdout, &(lp->metadata), "MO", "metadataurl", OWS_NOERR,
184✔
2569
                        NULL, "MetadataURL", " type=\"%s\"", NULL, NULL,
2570
                        ">\n          <Format>%s</Format",
2571
                        "\n          <OnlineResource xmlns:xlink=\""
2572
                        "http://www.w3.org/1999/xlink\" "
2573
                        "xlink:type=\"simple\" xlink:href=\"%s\"/>\n        ",
2574
                        MS_TRUE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
2575
                        NULL, NULL, NULL, NULL, "        ");
2576
    }
2577
  }
2578

2579
  if (nVersion < OWS_1_1_0)
2580
    msOWSPrintEncodeMetadata(stdout, &(lp->metadata), "MO", "dataurl_href",
×
2581
                             OWS_NOERR, "        <DataURL>%s</DataURL>\n",
2582
                             NULL);
2583
  else {
2584
    const char *dataurl_list =
2585
        msOWSLookupMetadata(&(lp->metadata), "MO", "dataurl_list");
185✔
2586
    if (dataurl_list) {
185✔
2587
      const auto tokens = msStringSplit(dataurl_list, ' ');
1✔
2588
      for (const auto &token : tokens) {
3✔
2589
        std::string key("dataurl_");
2✔
2590
        key += token;
2591
        msOWSPrintURLType(stdout, &(lp->metadata), "MO", key.c_str(), OWS_NOERR,
2✔
2592
                          NULL, "DataURL", NULL, NULL, NULL,
2593
                          ">\n          <Format>%s</Format",
2594
                          "\n          <OnlineResource xmlns:xlink=\""
2595
                          "http://www.w3.org/1999/xlink\" "
2596
                          "xlink:type=\"simple\" xlink:href=\"%s\"/>\n        ",
2597
                          MS_FALSE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
2598
                          NULL, NULL, NULL, NULL, "        ");
2599
      }
2600
    } else {
1✔
2601
      msOWSPrintURLType(stdout, &(lp->metadata), "MO", "dataurl", OWS_NOERR,
184✔
2602
                        NULL, "DataURL", NULL, NULL, NULL,
2603
                        ">\n          <Format>%s</Format",
2604
                        "\n          <OnlineResource xmlns:xlink=\""
2605
                        "http://www.w3.org/1999/xlink\" "
2606
                        "xlink:type=\"simple\" xlink:href=\"%s\"/>\n        ",
2607
                        MS_FALSE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
2608
                        NULL, NULL, NULL, NULL, "        ");
2609
    }
2610
  }
2611

2612
  /* The LegendURL reside in a style. The Web Map Context spec already  */
2613
  /* included the support on this in mapserver. However, it is not in the  */
2614
  /* wms_legendurl_... metadatas it's in the styles metadata, */
2615
  /* In wms_style_<style_name>_lengendurl_... metadata. So we have to detect */
2616
  /* the current style before reading it. Also in the Style block, we need */
2617
  /* a Title and a name. Title is derived from wms_style_<style>_title, */
2618
  /* which allows multiple style definitions, e.g. by using classgroups. */
2619
  const char *pszStyle = msOWSLookupMetadata(&(lp->metadata), "MO", "style");
185✔
2620
  const char *pszLegendURL = NULL;
2621
  if (pszStyle) {
185✔
2622
    pszLegendURL = msOWSLookupMetadata(
×
2623
        &(lp->metadata), "MO",
2624
        (std::string("style_") + pszStyle + "_legendurl_href").c_str());
×
2625
  } else
2626
    pszStyle = "default";
2627

2628
  if (nVersion <= OWS_1_0_0 && pszLegendURL) {
185✔
2629
    /* First, print the style block */
2630
    msIO_fprintf(stdout, "        <Style>\n");
×
2631
    msIO_fprintf(stdout, "          <Name>%s</Name>\n", pszStyle);
×
2632
    /* Print the real Title or Style name otherwise */
2633
    msOWSPrintEncodeMetadata2(
×
2634
        stdout, &(lp->metadata), "MO",
2635
        (std::string("style_") + pszStyle + "_title").c_str(), OWS_NOERR,
×
2636
        "          <Title>%s</Title>\n", pszStyle, validated_language);
2637

2638
    /* Inside, print the legend url block */
2639
    msOWSPrintEncodeMetadata(
×
2640
        stdout, &(lp->metadata), "MO",
2641
        (std::string("style_") + pszStyle + "_legendurl_href").c_str(),
×
2642
        OWS_NOERR, "          <StyleURL>%s</StyleURL>\n", NULL);
2643

2644
    /* close the style block */
2645
    msIO_fprintf(stdout, "        </Style>\n");
×
2646

2647
  } else if (nVersion >= OWS_1_1_0) {
185✔
2648
    if (pszLegendURL) {
185✔
2649
      /* First, print the style block */
2650
      msIO_fprintf(stdout, "        <Style>\n");
×
2651
      msIO_fprintf(stdout, "          <Name>%s</Name>\n", pszStyle);
×
2652
      /* Print the real Title or Style name otherwise */
2653
      msOWSPrintEncodeMetadata2(
×
2654
          stdout, &(lp->metadata), "MO",
2655
          (std::string("style_") + pszStyle + "_title").c_str(), OWS_NOERR,
×
2656
          "          <Title>%s</Title>\n", pszStyle, validated_language);
2657

2658
      /* Inside, print the legend url block */
2659
      msOWSPrintURLType(
×
2660
          stdout, &(lp->metadata), "MO",
2661
          (std::string("style_") + pszStyle + "_legendurl").c_str(), OWS_NOERR,
×
2662
          NULL, "LegendURL", NULL, " width=\"%s\"", " height=\"%s\"",
2663
          ">\n             <Format>%s</Format",
2664
          "\n             <OnlineResource "
2665
          "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
2666
          " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
2667
          "          ",
2668
          MS_FALSE, MS_TRUE, MS_TRUE, MS_TRUE, MS_TRUE, NULL, NULL, NULL, NULL,
2669
          NULL, "          ");
2670
      msIO_fprintf(stdout, "        </Style>\n");
×
2671

2672
    } else {
2673
      if (script_url_encoded) {
185✔
2674
        if (lp->connectiontype != MS_WMS && lp->connectiontype != MS_WFS &&
185✔
2675
            lp->connectiontype != MS_UNUSED_1 && lp->numclasses > 0) {
184✔
2676
          bool classnameset = false;
2677
          for (int i = 0; i < lp->numclasses; i++) {
184✔
2678
            if (lp->_class[i]->name && strlen(lp->_class[i]->name) > 0) {
166✔
2679
              classnameset = true;
2680
              break;
2681
            }
2682
          }
2683
          if (classnameset) {
163✔
2684
            int size_x = 0, size_y = 0;
145✔
2685
            std::vector<int> group_layers;
2686
            group_layers.reserve(map->numlayers);
145✔
2687

2688
            char ***nestedGroups =
2689
                (char ***)msSmallCalloc(map->numlayers, sizeof(char **));
145✔
2690
            int *numNestedGroups =
2691
                (int *)msSmallCalloc(map->numlayers, sizeof(int));
145✔
2692
            int *isUsedInNestedGroup =
2693
                (int *)msSmallCalloc(map->numlayers, sizeof(int));
145✔
2694
            msWMSPrepareNestedGroups(map, nVersion, nestedGroups,
145✔
2695
                                     numNestedGroups, isUsedInNestedGroup);
2696

2697
            group_layers.push_back(lp->index);
145✔
2698
            if (isUsedInNestedGroup[lp->index]) {
145✔
2699
              for (int j = 0; j < map->numlayers; j++) {
96✔
2700
                if (j == lp->index)
90✔
2701
                  continue;
6✔
2702
                for (int k = 0; k < numNestedGroups[j]; k++) {
132✔
2703
                  if (strcasecmp(lp->name, nestedGroups[j][k]) == 0) {
66✔
2704
                    group_layers.push_back(j);
18✔
2705
                    break;
2706
                  }
2707
                }
2708
              }
2709
            }
2710

2711
            if (msLegendCalcSize(map, 1, &size_x, &size_y, group_layers.data(),
145✔
2712
                                 static_cast<int>(group_layers.size()), NULL,
2713
                                 1) == MS_SUCCESS) {
2714
              const std::string width(std::to_string(size_x));
145✔
2715
              const std::string height(std::to_string(size_y));
145✔
2716

2717
              char *mimetype = NULL;
2718
#if defined USE_PNG
2719
              mimetype = msEncodeHTMLEntities("image/png");
145✔
2720
#endif
2721

2722
#if defined USE_JPEG
2723
              if (!mimetype)
145✔
2724
                mimetype = msEncodeHTMLEntities("image/jpeg");
×
2725
#endif
2726
              if (!mimetype)
×
2727
                mimetype =
2728
                    msEncodeHTMLEntities(MS_IMAGE_MIME_TYPE(map->outputformat));
×
2729

2730
              /* --------------------------------------------------------------------
2731
               */
2732
              /*      check if the group parameters for the classes are set. We
2733
               */
2734
              /*      should then publish the different class groups as
2735
               * different styles.*/
2736
              /* --------------------------------------------------------------------
2737
               */
2738
              iclassgroups = 0;
2739
              classgroups = NULL;
2740

2741
              const char *styleName =
2742
                  msOWSLookupMetadata(&(map->web.metadata), "MO", "style_name");
145✔
2743
              if (styleName == NULL)
145✔
2744
                styleName = "default";
2745
              char *pszEncodedStyleName = msEncodeHTMLEntities(styleName);
145✔
2746

2747
              for (int i = 0; i < lp->numclasses; i++) {
376✔
2748
                if (lp->_class[i]->name && lp->_class[i]->group) {
231✔
2749
                  /* Check that style is not inherited from root layer (#4442).
2750
                   */
2751
                  if (strcasecmp(pszEncodedStyleName, lp->_class[i]->group) ==
180✔
2752
                      0)
2753
                    continue;
102✔
2754
                  /* Check that style is not inherited from group layer(s)
2755
                   * (#4442). */
2756
                  if (numNestedGroups[lp->index] > 0) {
78✔
2757
                    int j = 0;
2758
                    layerObj *lp2 = NULL;
2759
                    for (; j < numNestedGroups[lp->index]; j++) {
138✔
2760
                      int l = 0;
2761
                      for (int k = 0; k < map->numlayers; k++) {
384✔
2762
                        if (GET_LAYER(map, k)->name &&
384✔
2763
                            strcasecmp(GET_LAYER(map, k)->name,
384✔
2764
                                       nestedGroups[lp->index][j]) == 0) {
384✔
2765
                          lp2 = (GET_LAYER(map, k));
2766
                          for (l = 0; l < lp2->numclasses; l++) {
90✔
2767
                            if (strcasecmp(lp2->_class[l]->group,
24✔
2768
                                           lp->_class[i]->group) == 0)
2769
                              break;
2770
                          }
2771
                          break;
2772
                        }
2773
                      }
2774
                      if (lp2 && l < lp2->numclasses)
72✔
2775
                        break;
2776
                    }
2777
                    if (j < numNestedGroups[lp->index])
72✔
2778
                      continue;
6✔
2779
                  }
2780
                  if (!classgroups) {
72✔
2781
                    classgroups = (char **)msSmallMalloc(sizeof(char *));
72✔
2782
                    classgroups[iclassgroups++] =
72✔
2783
                        msStrdup(lp->_class[i]->group);
72✔
2784
                  } else {
2785
                    /* Output style only once. */
2786
                    bool found = false;
2787
                    for (int j = 0; j < iclassgroups; j++) {
×
2788
                      if (strcasecmp(classgroups[j], lp->_class[i]->group) ==
×
2789
                          0) {
2790
                        found = true;
2791
                        break;
2792
                      }
2793
                    }
2794
                    if (!found) {
×
2795
                      iclassgroups++;
×
2796
                      classgroups = (char **)msSmallRealloc(
×
2797
                          classgroups, sizeof(char *) * iclassgroups);
×
2798
                      classgroups[iclassgroups - 1] =
×
2799
                          msStrdup(lp->_class[i]->group);
×
2800
                    }
2801
                  }
2802
                }
2803
              }
2804
              msFree(pszEncodedStyleName);
145✔
2805
              if (classgroups == NULL) {
145✔
2806
                classgroups = (char **)msSmallMalloc(sizeof(char *));
73✔
2807
                classgroups[0] = msStrdup("default");
73✔
2808
                iclassgroups = 1;
2809
              }
2810

2811
              for (int i = 0; i < iclassgroups; i++) {
290✔
2812
                char *name_encoded = msEncodeHTMLEntities(lp->name);
145✔
2813
                char *classgroup_encoded = msEncodeHTMLEntities(classgroups[i]);
145✔
2814
                std::string legendurl(script_url_encoded);
145✔
2815
                legendurl += "version=";
2816
                char szVersionBuf[OWS_VERSION_MAXLEN];
2817
                legendurl += msOWSGetVersionString(nVersion, szVersionBuf);
145✔
2818
                legendurl +=
2819
                    "&amp;service=WMS&amp;request=GetLegendGraphic&amp;";
2820
                if (nVersion >= OWS_1_3_0) {
145✔
2821
                  legendurl += "sld_version=1.1.0&amp;layer=";
2822
                } else {
2823
                  legendurl += "layer=";
2824
                }
2825
                legendurl += name_encoded;
2826
                legendurl += "&amp;format=";
2827
                legendurl += mimetype;
2828
                legendurl += "&amp;STYLE=";
2829
                legendurl += classgroup_encoded;
2830

2831
                msFree(name_encoded);
145✔
2832
                msFree(classgroup_encoded);
145✔
2833

2834
                msIO_fprintf(stdout, "        <Style>\n");
145✔
2835
                msIO_fprintf(stdout, "          <Name>%s</Name>\n",
145✔
2836
                             classgroups[i]);
2837
                msOWSPrintEncodeMetadata2(
145✔
2838
                    stdout, &(lp->metadata), "MO",
2839
                    (std::string("style_") + classgroups[i] + "_title").c_str(),
145✔
2840
                    OWS_NOERR, "          <Title>%s</Title>\n", classgroups[i],
2841
                    validated_language);
2842

2843
                /* A legendurl from wms_style_<style>_legendurl_href will
2844
                 * override a self generated legend graphic */
2845
                pszLegendURL = msOWSLookupMetadata(
145✔
2846
                    &(lp->metadata), "MO",
2847
                    (std::string("style_") + classgroups[i] + "_legendurl_href")
145✔
2848
                        .c_str());
2849
                if (pszLegendURL) {
145✔
2850
                  msOWSPrintURLType(
×
2851
                      stdout, &(lp->metadata), "MO",
2852
                      (std::string("style_") + classgroups[i] + "_legendurl")
×
2853
                          .c_str(),
2854
                      OWS_NOERR, NULL, "LegendURL", NULL, " width=\"%s\"",
2855
                      " height=\"%s\"", ">\n             <Format>%s</Format",
2856
                      "\n             <OnlineResource "
2857
                      "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
2858
                      " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
2859
                      "          ",
2860
                      MS_FALSE, MS_TRUE, MS_TRUE, MS_TRUE, MS_TRUE, NULL, NULL,
2861
                      NULL, NULL, NULL, "          ");
2862
                } else {
2863
                  msOWSPrintURLType(
145✔
2864
                      stdout, NULL, "O", "ttt", OWS_NOERR, NULL, "LegendURL",
2865
                      NULL, " width=\"%s\"", " height=\"%s\"",
2866
                      ">\n             <Format>%s</Format",
2867
                      "\n             <OnlineResource "
2868
                      "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
2869
                      " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
2870
                      "          ",
2871
                      MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE, NULL,
2872
                      width.c_str(), height.c_str(), mimetype,
2873
                      legendurl.c_str(), "          ");
2874
                }
2875
                msIO_fprintf(stdout, "        </Style>\n");
145✔
2876
              }
2877
              msFreeCharArray(classgroups, iclassgroups);
145✔
2878
              msFree(mimetype);
145✔
2879
            }
2880
            /* free the stuff used for nested layers */
2881
            for (int i = 0; i < map->numlayers; i++) {
1,433✔
2882
              if (numNestedGroups[i] > 0) {
1,288✔
2883
                msFreeCharArray(nestedGroups[i], numNestedGroups[i]);
801✔
2884
              }
2885
            }
2886
            free(nestedGroups);
145✔
2887
            free(numNestedGroups);
145✔
2888
            free(isUsedInNestedGroup);
145✔
2889
          }
145✔
2890
        }
2891
      }
2892
    }
2893
  }
2894

2895
  /* print Min/Max ScaleDenominator */
2896
  if (nVersion < OWS_1_3_0)
185✔
2897
    msWMSPrintScaleHint("        ", lp->minscaledenom, lp->maxscaledenom,
94✔
2898
                        map->resolution);
2899
  else
2900
    msWMSPrintScaleDenominator("        ", lp->minscaledenom,
91✔
2901
                               lp->maxscaledenom);
2902

2903
  if (grouplayer == MS_FALSE)
185✔
2904
    msIO_printf("%s    </Layer>\n", indent);
166✔
2905

2906
  return MS_SUCCESS;
185✔
2907
}
2908

2909
/*
2910
 * msWMSIsSubGroup
2911
 */
2912
static bool msWMSIsSubGroup(char **currentGroups, int currentLevel,
217✔
2913
                            char **otherGroups, int numOtherGroups) {
2914
  /* no match if otherGroups[] has less levels than currentLevel */
2915
  if (numOtherGroups <= currentLevel) {
217✔
2916
    return false;
2917
  }
2918
  /* compare all groups below the current level */
2919
  for (int i = 0; i <= currentLevel; i++) {
266✔
2920
    if (strcmp(currentGroups[i], otherGroups[i]) != 0) {
181✔
2921
      return false; /* if one of these is not equal it is not a sub group */
2922
    }
2923
  }
2924
  return true;
2925
}
2926

2927
/*
2928
 * msWMSHasQueryableSubLayers
2929
 */
2930
static int msWMSHasQueryableSubLayers(mapObj *map, int index, int level,
28✔
2931
                                      char ***nestedGroups,
2932
                                      int *numNestedGroups) {
2933
  for (int j = index; j < map->numlayers; j++) {
44✔
2934
    if (msWMSIsSubGroup(nestedGroups[index], level, nestedGroups[j],
41✔
2935
                        numNestedGroups[j])) {
41✔
2936
      if (msIsLayerQueryable(GET_LAYER(map, j)))
31✔
2937
        return MS_TRUE;
2938
    }
2939
  }
2940
  return MS_FALSE;
2941
}
2942

2943
/***********************************************************************************
2944
 * msWMSPrintNestedGroups() *
2945
 *                                                                                 *
2946
 * purpose: Writes the layers to the capabilities that have the *
2947
 * "WMS_LAYER_GROUP" metadata set. *
2948
 *                                                                                 *
2949
 * params: * -map: The main map object * -nVersion: OGC WMS version *
2950
 * -pabLayerProcessed: boolean array indicating which layers have been dealt
2951
 *with. * -index: the index of the current layer. * -level: the level of depth
2952
 *in the group tree (root = 0)                         * -nestedGroups: This
2953
 *array holds the arrays of groups that have                  * been set through
2954
 *the WMS_LAYER_GROUP metadata                                 *
2955
 * -numNestedGroups: This array holds the number of nested groups for each layer
2956
 **
2957
 ***********************************************************************************/
2958
void msWMSPrintNestedGroups(mapObj *map, int nVersion, char *pabLayerProcessed,
102✔
2959
                            int index, int level, char ***nestedGroups,
2960
                            int *numNestedGroups, int *isUsedInNestedGroup,
2961
                            const char *script_url_encoded,
2962
                            const char *validated_language) {
2963
  bool groupAdded = false;
2964
  std::string indent;
2965
  for (int i = 0; i < level; i++) {
185✔
2966
    indent += "  ";
2967
  }
2968

2969
  if (numNestedGroups[index] <= level) { /* no more subgroups */
102✔
2970
    if ((!pabLayerProcessed[index]) && (!isUsedInNestedGroup[index])) {
74✔
2971
      /* we are at the deepest level of the group branchings, so add layer now.
2972
       */
2973
      msDumpLayer(map, GET_LAYER(map, index), nVersion, script_url_encoded,
74✔
2974
                  indent.c_str(), validated_language, MS_FALSE, MS_FALSE);
2975
      pabLayerProcessed[index] = 1; /* done */
74✔
2976
    }
2977
  } else { /* not yet there, we have to deal with this group and possible
2978
              subgroups and layers. */
2979
    int j;
2980
    for (j = 0; j < map->numlayers; j++) {
155✔
2981
      if (GET_LAYER(map, j)->name &&
146✔
2982
          strcasecmp(GET_LAYER(map, j)->name, nestedGroups[index][level]) ==
146✔
2983
              0) {
2984
        break;
2985
      }
2986
    }
2987

2988
    /* Beginning of a new group... enclose the group in a layer block */
2989
    if (j < map->numlayers) {
28✔
2990
      if (!pabLayerProcessed[j]) {
19✔
2991
        msDumpLayer(map, GET_LAYER(map, j), nVersion, script_url_encoded,
19✔
2992
                    indent.c_str(), validated_language, MS_TRUE,
2993
                    msWMSHasQueryableSubLayers(map, index, level, nestedGroups,
2994
                                               numNestedGroups));
2995
        pabLayerProcessed[j] = 1; /* done */
19✔
2996
        groupAdded = true;
2997
      }
2998
    } else {
2999
      msIO_printf("%s    <Layer%s>\n", indent.c_str(),
12✔
3000
                  msWMSHasQueryableSubLayers(map, index, level, nestedGroups,
9✔
3001
                                             numNestedGroups)
3002
                      ? " queryable=\"1\""
3003
                      : "");
3004
      msIO_printf("%s      <Name>%s</Name>\n", indent.c_str(),
9✔
3005
                  nestedGroups[index][level]);
9✔
3006
      msIO_printf("%s      <Title>%s</Title>\n", indent.c_str(),
9✔
3007
                  nestedGroups[index][level]);
9✔
3008
      groupAdded = true;
3009
    }
3010

3011
    /* Look for one group deeper in the current layer */
3012
    if (!pabLayerProcessed[index]) {
28✔
3013
      msWMSPrintNestedGroups(map, nVersion, pabLayerProcessed, index, level + 1,
28✔
3014
                             nestedGroups, numNestedGroups, isUsedInNestedGroup,
3015
                             script_url_encoded, validated_language);
3016
    }
3017

3018
    /* look for subgroups in other layers. */
3019
    for (j = index + 1; j < map->numlayers; j++) {
204✔
3020
      if (msWMSIsSubGroup(nestedGroups[index], level, nestedGroups[j],
176✔
3021
                          numNestedGroups[j])) {
176✔
3022
        if (!pabLayerProcessed[j]) {
54✔
3023
          msWMSPrintNestedGroups(map, nVersion, pabLayerProcessed, j, level + 1,
50✔
3024
                                 nestedGroups, numNestedGroups,
3025
                                 isUsedInNestedGroup, script_url_encoded,
3026
                                 validated_language);
3027
        }
3028
      } else {
3029
        /* TODO: if we would sort all layers on "WMS_LAYER_GROUP" beforehand */
3030
        /* we could break out of this loop at this point, which would increase
3031
         */
3032
        /* performance.  */
3033
      }
3034
    }
3035
    /* Close group layer block */
3036
    if (groupAdded)
28✔
3037
      msIO_printf("%s    </Layer>\n", indent.c_str());
28✔
3038
  }
3039
} /* msWMSPrintNestedGroups */
102✔
3040

3041
/*
3042
** msWMSGetCapabilities()
3043
*/
3044
static int msWMSGetCapabilities(mapObj *map, int nVersion, cgiRequestObj *req,
61✔
3045
                                owsRequestObj *ows_request,
3046
                                const char *requested_updatesequence,
3047
                                const char *wms_exception_format,
3048
                                const char *requested_language) {
3049
  const char *updatesequence =
3050
      msOWSLookupMetadata(&(map->web.metadata), "MO", "updatesequence");
61✔
3051

3052
  const char *sldenabled =
3053
      msOWSLookupMetadata(&(map->web.metadata), "MO", "sld_enabled");
61✔
3054

3055
  if (sldenabled == NULL)
61✔
3056
    sldenabled = "true";
3057

3058
  if (requested_updatesequence != NULL) {
61✔
3059
    int i =
3060
        msOWSNegotiateUpdateSequence(requested_updatesequence, updatesequence);
6✔
3061
    if (i == 0) { /* current */
6✔
3062
      msSetErrorWithStatus(
2✔
3063
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
3064
          "UPDATESEQUENCE parameter (%s) is equal to server (%s)",
3065
          "msWMSGetCapabilities()", requested_updatesequence, updatesequence);
3066
      return msWMSException(map, nVersion, "CurrentUpdateSequence",
2✔
3067
                            wms_exception_format);
3068
    }
3069
    if (i > 0) { /* invalid */
4✔
3070
      msSetErrorWithStatus(
2✔
3071
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
3072
          "UPDATESEQUENCE parameter (%s) is higher than server (%s)",
3073
          "msWMSGetCapabilities()", requested_updatesequence, updatesequence);
3074
      return msWMSException(map, nVersion, "InvalidUpdateSequence",
2✔
3075
                            wms_exception_format);
3076
    }
3077
  }
3078

3079
  std::string schemalocation;
3080
  {
3081
    char *pszSchemalocation =
3082
        msEncodeHTMLEntities(msOWSGetSchemasLocation(map));
57✔
3083
    schemalocation = pszSchemalocation;
3084
    msFree(pszSchemalocation);
57✔
3085
  }
3086

3087
  if (nVersion < 0)
57✔
3088
    nVersion = OWS_1_3_0; /* Default to 1.3.0 */
3089

3090
  /* Decide which version we're going to return. */
3091
  std::string dtd_url;
3092
  if (nVersion < OWS_1_0_7) {
57✔
3093
    nVersion = OWS_1_0_0;
3094
    dtd_url = std::move(schemalocation);
1✔
3095
    dtd_url += "/wms/1.0.0/capabilities_1_0_0.dtd";
3096
  }
3097

3098
  else if (nVersion < OWS_1_1_0) {
56✔
3099
    nVersion = OWS_1_0_7;
3100
    dtd_url = std::move(schemalocation);
×
3101
    dtd_url += "/wms/1.0.7/capabilities_1_0_7.dtd";
3102
  } else if (nVersion < OWS_1_1_1) {
56✔
3103
    nVersion = OWS_1_1_0;
3104
    dtd_url = std::move(schemalocation);
5✔
3105
    dtd_url += "/wms/1.1.0/capabilities_1_1_0.dtd";
3106
  } else if (nVersion < OWS_1_3_0) {
51✔
3107
    nVersion = OWS_1_1_1;
3108
    dtd_url = std::move(schemalocation);
22✔
3109
    /* this exception was added to accomadote the OGC test suite (Bug 1576)*/
3110
    if (strcasecmp(dtd_url.c_str(), OWS_DEFAULT_SCHEMAS_LOCATION) == 0)
22✔
3111
      dtd_url += "/wms/1.1.1/WMS_MS_Capabilities.dtd";
3112
    else
3113
      dtd_url += "/wms/1.1.1/capabilities_1_1_1.dtd";
3114
  } else
3115
    nVersion = OWS_1_3_0;
3116

3117
  /* This function owns validated_language, so remember to free it later*/
3118
  std::string validated_language;
3119
  {
3120
    char *pszValidated_language =
3121
        msOWSGetLanguageFromList(map, "MO", requested_language);
57✔
3122
    if (pszValidated_language) {
57✔
3123
      validated_language = pszValidated_language;
3124
      msMapSetLanguageSpecificConnection(map, pszValidated_language);
15✔
3125
    }
3126
    msFree(pszValidated_language);
57✔
3127
  }
3128

3129
  /* We need this server's onlineresource. */
3130
  /* Default to use the value of the "onlineresource" metadata, and if not */
3131
  /* set then build it: "http://$(SERVER_NAME):$(SERVER_PORT)$(SCRIPT_NAME)?" */
3132
  /* the returned string should be freed once we're done with it. */
3133
  char *script_url_encoded = NULL;
3134
  {
3135
    char *script_url = msOWSGetOnlineResource2(map, "MO", "onlineresource", req,
57✔
3136
                                               validated_language.c_str());
3137
    if (script_url == NULL ||
114✔
3138
        (script_url_encoded = msEncodeHTMLEntities(script_url)) == NULL) {
57✔
3139
      free(script_url);
×
3140
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
3141
    }
3142
    free(script_url);
57✔
3143
  }
3144

3145
  if (nVersion <= OWS_1_0_7 ||
57✔
3146
      nVersion >= OWS_1_3_0) /* 1.0.0 to 1.0.7 and >=1.3.0*/
3147
    msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
30✔
3148
  else /* 1.1.0 and later */
3149
    msIO_setHeader("Content-Type",
27✔
3150
                   "application/vnd.ogc.wms_xml; charset=UTF-8");
3151
  msIO_sendHeaders();
57✔
3152

3153
  msIO_printf("<?xml version='1.0' encoding=\"UTF-8\" standalone=\"no\" ?>\n");
57✔
3154

3155
  /*TODO review wms1.3.0*/
3156
  if (nVersion < OWS_1_3_0) {
57✔
3157
    msIO_printf("<!DOCTYPE WMT_MS_Capabilities SYSTEM \"%s\"\n",
28✔
3158
                dtd_url.c_str());
3159
    msIO_printf(" [\n");
28✔
3160

3161
    if (nVersion == OWS_1_1_1 && msOWSLookupMetadata(&(map->web.metadata), "MO",
28✔
3162
                                                     "inspire_capabilities")) {
3163
      msIO_printf(
6✔
3164
          "<!ELEMENT VendorSpecificCapabilities "
3165
          "(inspire_vs:ExtendedCapabilities)><!ELEMENT "
3166
          "inspire_vs:ExtendedCapabilities ((inspire_common:MetadataUrl, "
3167
          "inspire_common:SupportedLanguages, inspire_common:ResponseLanguage) "
3168
          "| (inspire_common:ResourceLocator+, inspire_common:ResourceType, "
3169
          "inspire_common:TemporalReference+, inspire_common:Conformity+, "
3170
          "inspire_common:MetadataPointOfContact+, "
3171
          "inspire_common:MetadataDate, inspire_common:SpatialDataServiceType, "
3172
          "inspire_common:MandatoryKeyword+, inspire_common:Keyword*, "
3173
          "inspire_common:SupportedLanguages, inspire_common:ResponseLanguage, "
3174
          "inspire_common:MetadataUrl?))><!ATTLIST "
3175
          "inspire_vs:ExtendedCapabilities xmlns:inspire_vs CDATA #FIXED "
3176
          "\"http://inspire.ec.europa.eu/schemas/inspire_vs/1.0\" ><!ELEMENT "
3177
          "inspire_common:MetadataUrl (inspire_common:URL, "
3178
          "inspire_common:MediaType*)><!ATTLIST inspire_common:MetadataUrl "
3179
          "xmlns:inspire_common CDATA #FIXED "
3180
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" xmlns:xsi CDATA "
3181
          "#FIXED \"http://www.w3.org/2001/XMLSchema-instance\" xsi:type CDATA "
3182
          "#FIXED \"inspire_common:resourceLocatorType\" ><!ELEMENT "
3183
          "inspire_common:URL (#PCDATA)><!ATTLIST inspire_common:URL "
3184
          "xmlns:inspire_common CDATA #FIXED "
3185
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\"><!ELEMENT "
3186
          "inspire_common:MediaType (#PCDATA)><!ATTLIST "
3187
          "inspire_common:MediaType xmlns:inspire_common CDATA #FIXED "
3188
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\"><!ELEMENT "
3189
          "inspire_common:SupportedLanguages (inspire_common:DefaultLanguage, "
3190
          "inspire_common:SupportedLanguage*)><!ATTLIST "
3191
          "inspire_common:SupportedLanguages xmlns:inspire_common CDATA #FIXED "
3192
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3193
          "inspire_common:DefaultLanguage (inspire_common:Language)><!ATTLIST "
3194
          "inspire_common:DefaultLanguage xmlns:inspire_common CDATA #FIXED "
3195
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3196
          "inspire_common:SupportedLanguage "
3197
          "(inspire_common:Language)><!ATTLIST "
3198
          "inspire_common:SupportedLanguage xmlns:inspire_common CDATA #FIXED "
3199
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" >"
3200
          "<!ELEMENT inspire_common:ResponseLanguage "
3201
          "(inspire_common:Language)><!ATTLIST inspire_common:ResponseLanguage "
3202
          "xmlns:inspire_common CDATA #FIXED "
3203
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3204
          "inspire_common:Language (#PCDATA)><!ATTLIST inspire_common:Language "
3205
          "xmlns:inspire_common CDATA #FIXED "
3206
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3207
          "inspire_common:ResourceLocator (inspire_common:URL, "
3208
          "inspire_common:MediaType*)><!ATTLIST inspire_common:ResourceLocator "
3209
          "xmlns:inspire_common CDATA #FIXED "
3210
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\"><!ELEMENT "
3211
          "inspire_common:ResourceType (#PCDATA)> <!ATTLIST "
3212
          "inspire_common:ResourceType xmlns:inspire_common CDATA #FIXED "
3213
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3214
          "inspire_common:TemporalReference (inspire_common:DateOfCreation?, "
3215
          "inspire_common:DateOfLastRevision?, "
3216
          "inspire_common:DateOfPublication*, "
3217
          "inspire_common:TemporalExtent*)><!ATTLIST "
3218
          "inspire_common:TemporalReference xmlns:inspire_common CDATA #FIXED "
3219
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3220
          "inspire_common:DateOfCreation (#PCDATA)> <!ATTLIST "
3221
          "inspire_common:DateOfCreation xmlns:inspire_common CDATA #FIXED "
3222
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\"><!ELEMENT "
3223
          "inspire_common:DateOfLastRevision (#PCDATA)><!ATTLIST "
3224
          "inspire_common:DateOfLastRevision xmlns:inspire_common CDATA #FIXED "
3225
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\"><!ELEMENT "
3226
          "inspire_common:DateOfPublication (#PCDATA)><!ATTLIST "
3227
          "inspire_common:DateOfPublication xmlns:inspire_common CDATA #FIXED "
3228
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\"><!ELEMENT "
3229
          "inspire_common:TemporalExtent (inspire_common:IndividualDate | "
3230
          "inspire_common:IntervalOfDates)><!ATTLIST "
3231
          "inspire_common:TemporalExtent xmlns:inspire_common CDATA #FIXED "
3232
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3233
          "inspire_common:IndividualDate (#PCDATA)> <!ATTLIST "
3234
          "inspire_common:IndividualDate xmlns:inspire_common CDATA #FIXED "
3235
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\">"
3236
          "<!ELEMENT inspire_common:IntervalOfDates "
3237
          "(inspire_common:StartingDate, inspire_common:EndDate)><!ATTLIST "
3238
          "inspire_common:IntervalOfDates xmlns:inspire_common CDATA #FIXED "
3239
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3240
          "inspire_common:StartingDate (#PCDATA)><!ATTLIST "
3241
          "inspire_common:StartingDate xmlns:inspire_common CDATA #FIXED "
3242
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3243
          "inspire_common:EndDate (#PCDATA)><!ATTLIST inspire_common:EndDate "
3244
          "xmlns:inspire_common CDATA #FIXED "
3245
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3246
          "inspire_common:Conformity (inspire_common:Specification, "
3247
          "inspire_common:Degree)><!ATTLIST inspire_common:Conformity "
3248
          "xmlns:inspire_common CDATA #FIXED "
3249
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3250
          "inspire_common:Specification (inspire_common:Title, "
3251
          "(inspire_common:DateOfPublication | inspire_common:DateOfCreation | "
3252
          "inspire_common:DateOfLastRevision), inspire_common:URI*, "
3253
          "inspire_common:ResourceLocator*)><!ATTLIST "
3254
          "inspire_common:Specification xmlns:inspire_common CDATA #FIXED "
3255
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3256
          "inspire_common:Title (#PCDATA)><!ATTLIST inspire_common:Title "
3257
          "xmlns:inspire_common CDATA #FIXED "
3258
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3259
          "inspire_common:URI (#PCDATA)><!ATTLIST inspire_common:URI "
3260
          "xmlns:inspire_common CDATA #FIXED "
3261
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3262
          "inspire_common:Degree (#PCDATA)><!ATTLIST inspire_common:Degree "
3263
          "xmlns:inspire_common CDATA #FIXED "
3264
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3265
          "inspire_common:MetadataPointOfContact "
3266
          "(inspire_common:OrganisationName, "
3267
          "inspire_common:EmailAddress)><!ATTLIST "
3268
          "inspire_common:MetadataPointOfContact xmlns:inspire_common CDATA "
3269
          "#FIXED \"http://inspire.ec.europa.eu/schemas/common/1.0\" >"
3270
          "<!ELEMENT inspire_common:OrganisationName (#PCDATA)><!ATTLIST "
3271
          "inspire_common:OrganisationName  xmlns:inspire_common CDATA #FIXED "
3272
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3273
          "inspire_common:EmailAddress (#PCDATA)><!ATTLIST "
3274
          "inspire_common:EmailAddress xmlns:inspire_common CDATA #FIXED "
3275
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3276
          "inspire_common:MetadataDate (#PCDATA)><!ATTLIST "
3277
          "inspire_common:MetadataDate xmlns:inspire_common CDATA #FIXED "
3278
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3279
          "inspire_common:SpatialDataServiceType (#PCDATA)><!ATTLIST "
3280
          "inspire_common:SpatialDataServiceType xmlns:inspire_common CDATA "
3281
          "#FIXED \"http://inspire.ec.europa.eu/schemas/common/1.0\" "
3282
          "><!ELEMENT inspire_common:MandatoryKeyword "
3283
          "(inspire_common:KeywordValue)><!ATTLIST "
3284
          "inspire_common:MandatoryKeyword xmlns:inspire_common CDATA #FIXED "
3285
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3286
          "inspire_common:KeywordValue (#PCDATA)><!ATTLIST "
3287
          "inspire_common:KeywordValue xmlns:inspire_common CDATA #FIXED "
3288
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" >"
3289
          "<!ELEMENT inspire_common:Keyword "
3290
          "(inspire_common:OriginatingControlledVocabulary?, "
3291
          "inspire_common:KeywordValue)><!ATTLIST inspire_common:Keyword "
3292
          "xmlns:inspire_common CDATA #FIXED "
3293
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" xmlns:xsi CDATA "
3294
          "#FIXED \"http://www.w3.org/2001/XMLSchemainstance\" xsi:type "
3295
          "(inspire_common:inspireTheme_bul | inspire_common:inspireTheme_cze "
3296
          "| inspire_common:inspireTheme_dan | inspire_common:inspireTheme_dut "
3297
          "| inspire_common:inspireTheme_eng | inspire_common:inspireTheme_est "
3298
          "| inspire_common:inspireTheme_fin | inspire_common:inspireTheme_fre "
3299
          "| inspire_common:inspireTheme_ger | inspire_common:inspireTheme_gre "
3300
          "| inspire_common:inspireTheme_hun | inspire_common:inspireTheme_gle "
3301
          "| inspire_common:inspireTheme_ita | inspire_common:inspireTheme_lav "
3302
          "| inspire_common:inspireTheme_lit | inspire_common:inspireTheme_mlt "
3303
          "| inspire_common:inspireTheme_pol | inspire_common:inspireTheme_por "
3304
          "| inspire_common:inspireTheme_rum | inspire_common:inspireTheme_slo "
3305
          "| inspire_common:inspireTheme_slv | inspire_common:inspireTheme_spa "
3306
          "| inspire_common:inspireTheme_swe) #IMPLIED ><!ELEMENT "
3307
          "inspire_common:OriginatingControlledVocabulary "
3308
          "(inspire_common:Title, (inspire_common:DateOfPublication | "
3309
          "inspire_common:DateOfCreation | inspire_common:DateOfLastRevision), "
3310
          "inspire_common:URI*, inspire_common:ResourceLocator*)><!ATTLIST "
3311
          "inspire_common:OriginatingControlledVocabulary xmlns:inspire_common "
3312
          "CDATA #FIXED \"http://inspire.ec.europa.eu/schemas/common/1.0\">\n");
3313
    } else {
3314
      /* some mapserver specific declarations will go here */
3315
      msIO_printf(" <!ELEMENT VendorSpecificCapabilities EMPTY>\n");
22✔
3316
    }
3317

3318
    msIO_printf(" ]>  <!-- end of DOCTYPE declaration -->\n\n");
28✔
3319
  }
3320

3321
  char szVersionBuf[OWS_VERSION_MAXLEN];
3322
  const char *pszVersion = msOWSGetVersionString(nVersion, szVersionBuf);
57✔
3323
  if (nVersion >= OWS_1_3_0)
57✔
3324
    msIO_printf("<WMS_Capabilities version=\"%s\"", pszVersion);
29✔
3325
  else
3326
    msIO_printf("<WMT_MS_Capabilities version=\"%s\"", pszVersion);
28✔
3327
  if (updatesequence)
57✔
3328
    msIO_printf(" updateSequence=\"%s\"", updatesequence);
30✔
3329

3330
  if (nVersion == OWS_1_3_0) {
57✔
3331
    msIO_printf("  xmlns=\"http://www.opengis.net/wms\""
29✔
3332
                "   xmlns:sld=\"http://www.opengis.net/sld\""
3333
                "   xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
3334
                "   xmlns:ms=\"http://mapserver.gis.umn.edu/mapserver\"");
3335

3336
    if (msOWSLookupMetadata(&(map->web.metadata), "MO",
29✔
3337
                            "inspire_capabilities")) {
3338
      msIO_printf("   xmlns:" MS_INSPIRE_COMMON_NAMESPACE_PREFIX
9✔
3339
                  "=\"" MS_INSPIRE_COMMON_NAMESPACE_URI "\""
3340
                  "   xmlns:" MS_INSPIRE_VS_NAMESPACE_PREFIX
3341
                  "=\"" MS_INSPIRE_VS_NAMESPACE_URI "\"");
3342
    }
3343

3344
    msIO_printf(
29✔
3345
        "   xsi:schemaLocation=\"http://www.opengis.net/wms "
3346
        "%s/wms/%s/capabilities_1_3_0.xsd "
3347
        " http://www.opengis.net/sld %s/sld/1.1.0/sld_capabilities.xsd ",
3348
        msOWSGetSchemasLocation(map), pszVersion, msOWSGetSchemasLocation(map));
3349

3350
    if (msOWSLookupMetadata(&(map->web.metadata), "MO",
29✔
3351
                            "inspire_capabilities")) {
3352
      char *inspireschemalocation =
3353
          msEncodeHTMLEntities(msOWSGetInspireSchemasLocation(map));
9✔
3354
      msIO_printf(" " MS_INSPIRE_VS_NAMESPACE_URI " "
9✔
3355
                  " %s%s",
3356
                  inspireschemalocation, MS_INSPIRE_VS_SCHEMA_LOCATION);
3357
      free(inspireschemalocation);
9✔
3358
    }
3359

3360
    msIO_printf(
29✔
3361
        " http://mapserver.gis.umn.edu/mapserver "
3362
        "%sservice=WMS&amp;version=1.3.0&amp;request=GetSchemaExtension\"",
3363
        script_url_encoded);
3364
  }
3365

3366
  msIO_printf(">\n");
57✔
3367

3368
  /* WMS definition */
3369
  msIO_printf("<Service>\n");
57✔
3370

3371
  /* Service name is defined by the spec and changed at v1.0.0 */
3372
  if (nVersion <= OWS_1_0_7)
57✔
3373
    msIO_printf("  <Name>GetMap</Name>\n"); /* v 1.0.0 to 1.0.7 */
1✔
3374
  else if (nVersion > OWS_1_0_7 && nVersion < OWS_1_3_0)
56✔
3375
    msIO_printf("  <Name>OGC:WMS</Name>\n"); /* v 1.1.0 to 1.1.1*/
27✔
3376
  else
3377
    msIO_printf("  <Name>WMS</Name>\n"); /* v 1.3.0+ */
29✔
3378

3379
  /* the majority of this section is dependent on appropriately named metadata
3380
   * in the WEB object */
3381
  msOWSPrintEncodeMetadata2(stdout, &(map->web.metadata), "MO", "title",
57✔
3382
                            OWS_WARN, "  <Title>%s</Title>\n", map->name,
57✔
3383
                            validated_language.c_str());
3384
  msOWSPrintEncodeMetadata2(stdout, &(map->web.metadata), "MO", "abstract",
57✔
3385
                            OWS_NOERR, "  <Abstract>%s</Abstract>\n", NULL,
3386
                            validated_language.c_str());
3387

3388
  msWMSPrintKeywordlist(stdout, "  ", "keywordlist", &(map->web.metadata), "MO",
57✔
3389
                        nVersion);
3390

3391
  /* Service/onlineresource */
3392
  /* Defaults to same as request onlineresource if wms_service_onlineresource */
3393
  /* is not set. */
3394
  if (nVersion == OWS_1_0_0)
57✔
3395
    msOWSPrintEncodeMetadata(
1✔
3396
        stdout, &(map->web.metadata), "MO", "service_onlineresource", OWS_NOERR,
3397
        "  <OnlineResource>%s</OnlineResource>\n", script_url_encoded);
3398
  else
3399
    msOWSPrintEncodeMetadata(
56✔
3400
        stdout, &(map->web.metadata), "MO", "service_onlineresource", OWS_NOERR,
3401
        "  <OnlineResource xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
3402
        "xlink:href=\"%s\"/>\n",
3403
        script_url_encoded);
3404

3405
  /* contact information is a required element in 1.0.7 but the */
3406
  /* sub-elements such as ContactPersonPrimary, etc. are not! */
3407
  /* In 1.1.0, ContactInformation becomes optional. */
3408
  msOWSPrintContactInfo(stdout, "  ", nVersion, &(map->web.metadata), "MO");
57✔
3409

3410
  msOWSPrintEncodeMetadata(stdout, &(map->web.metadata), "MO", "fees",
57✔
3411
                           OWS_NOERR, "  <Fees>%s</Fees>\n", NULL);
3412

3413
  msOWSPrintEncodeMetadata(
57✔
3414
      stdout, &(map->web.metadata), "MO", "accessconstraints", OWS_NOERR,
3415
      "  <AccessConstraints>%s</AccessConstraints>\n", NULL);
3416

3417
  if (nVersion >= OWS_1_3_0) {
57✔
3418
    const char *layerlimit =
3419
        msOWSLookupMetadata(&(map->web.metadata), "MO", "layerlimit");
29✔
3420
    if (layerlimit) {
29✔
3421
      msIO_printf("  <LayerLimit>%s</LayerLimit>\n", layerlimit);
4✔
3422
    }
3423
    msIO_printf("  <MaxWidth>%d</MaxWidth>\n", map->maxsize);
29✔
3424
    msIO_printf("  <MaxHeight>%d</MaxHeight>\n", map->maxsize);
29✔
3425
  }
3426

3427
  msIO_printf("</Service>\n\n");
57✔
3428

3429
  /* WMS capabilities definitions */
3430
  msIO_printf("<Capability>\n");
57✔
3431
  msIO_printf("  <Request>\n");
57✔
3432

3433
  if (nVersion <= OWS_1_0_7) {
57✔
3434
    /* WMS 1.0.0 to 1.0.7 - We don't try to use outputformats list here for now
3435
     */
3436
    if (msOWSRequestIsEnabled(map, NULL, "M", "GetMap", MS_FALSE))
1✔
3437
      msWMSPrintRequestCap(nVersion, "Map", script_url_encoded,
1✔
3438
                           ""
3439

3440
#if defined USE_PNG
3441
                           "<PNG />"
3442
#endif
3443
#if defined USE_JPEG
3444
                           "<JPEG />"
3445
#endif
3446
                           "<SVG />",
3447
                           NULL);
3448
    if (msOWSRequestIsEnabled(map, NULL, "M", "GetCapabilities", MS_TRUE))
1✔
3449
      msWMSPrintRequestCap(nVersion, "Capabilities", script_url_encoded,
1✔
3450
                           "<WMS_XML />", NULL);
3451
    if (msOWSRequestIsEnabled(map, NULL, "M", "GetFeatureInfo", MS_FALSE))
1✔
3452
      msWMSPrintRequestCap(nVersion, "FeatureInfo", script_url_encoded,
1✔
3453
                           "<MIME /><GML.1 />", NULL);
3454
  } else {
3455
    const char *mime_list[20];
3456
    int mime_count = 0;
3457
    int max_mime = 20;
3458
    /* WMS 1.1.0 and later */
3459
    /* Note changes to the request names, their ordering, and to the formats */
3460

3461
    if (msOWSRequestIsEnabled(map, NULL, "M", "GetCapabilities", MS_TRUE)) {
56✔
3462
      if (nVersion >= OWS_1_3_0)
56✔
3463
        msWMSPrintRequestCap(nVersion, "GetCapabilities", script_url_encoded,
29✔
3464
                             "text/xml", NULL);
3465
      else
3466
        msWMSPrintRequestCap(nVersion, "GetCapabilities", script_url_encoded,
27✔
3467
                             "application/vnd.ogc.wms_xml", NULL);
3468
    }
3469

3470
    msGetOutputFormatMimeListWMS(map, mime_list,
56✔
3471
                                 sizeof(mime_list) / sizeof(char *));
3472
    if (msOWSRequestIsEnabled(map, NULL, "M", "GetMap", MS_FALSE))
56✔
3473
      msWMSPrintRequestCap(
55✔
3474
          nVersion, "GetMap", script_url_encoded, mime_list[0], mime_list[1],
3475
          mime_list[2], mime_list[3], mime_list[4], mime_list[5], mime_list[6],
3476
          mime_list[7], mime_list[8], mime_list[9], mime_list[10],
3477
          mime_list[11], mime_list[12], mime_list[13], mime_list[14],
3478
          mime_list[15], mime_list[16], mime_list[17], mime_list[18],
3479
          mime_list[19], NULL);
3480

3481
    const char *format_list = msOWSLookupMetadata(&(map->web.metadata), "M",
56✔
3482
                                                  "getfeatureinfo_formatlist");
3483
    /*feature_info_mime_type deprecated for MapServer 6.0*/
3484
    if (!format_list)
56✔
3485
      format_list = msOWSLookupMetadata(&(map->web.metadata), "MO",
52✔
3486
                                        "feature_info_mime_type");
3487

3488
    if (format_list && strlen(format_list) > 0) {
56✔
3489
      auto tokens = msStringSplit(format_list, ',');
5✔
3490
      for (auto &token : tokens) {
14✔
3491
        msStringTrim(token);
9✔
3492
        /*text plain and gml do not need to be a format and accepted by
3493
         * default*/
3494
        /*can not really validate since the old way of using template
3495
          with wei->header, layer->template ... should be kept*/
3496
        if (!token.empty() && mime_count < max_mime)
9✔
3497
          mime_list[mime_count++] = token.c_str();
9✔
3498
      }
3499
      /*add text/plain and gml */
3500
      if (strcasestr(format_list, "GML") == 0 &&
5✔
3501
          strcasestr(format_list, "application/vnd.ogc.gml") == 0) {
1✔
3502
        if (mime_count < max_mime)
1✔
3503
          mime_list[mime_count++] = "application/vnd.ogc.gml";
1✔
3504
      }
3505
      if (strcasestr(format_list, "text/plain") == 0 &&
5✔
3506
          strcasestr(format_list, "MIME") == 0) {
5✔
3507
        if (mime_count < max_mime)
5✔
3508
          mime_list[mime_count++] = "text/plain";
5✔
3509
        else /*force always this format*/
3510
          mime_list[max_mime - 1] = "text/plain";
×
3511
      }
3512

3513
      if (msOWSRequestIsEnabled(map, NULL, "M", "GetFeatureInfo", MS_FALSE)) {
5✔
3514
        if (mime_count > 0) {
5✔
3515
          if (mime_count < max_mime)
5✔
3516
            mime_list[mime_count] = NULL;
5✔
3517
          msWMSPrintRequestCap(
5✔
3518
              nVersion, "GetFeatureInfo", script_url_encoded, mime_list[0],
3519
              mime_list[1], mime_list[2], mime_list[3], mime_list[4],
3520
              mime_list[5], mime_list[6], mime_list[7], mime_list[8],
3521
              mime_list[9], mime_list[10], mime_list[11], mime_list[12],
3522
              mime_list[13], mime_list[14], mime_list[15], mime_list[16],
3523
              mime_list[17], mime_list[18], mime_list[19], NULL);
3524
        }
3525
        /*if all formats given are invalid go to default*/
3526
        else
3527
          msWMSPrintRequestCap(nVersion, "GetFeatureInfo", script_url_encoded,
×
3528
                               "text/plain", "application/vnd.ogc.gml", NULL);
3529
      }
3530
    } else {
5✔
3531
      if (msOWSRequestIsEnabled(map, NULL, "M", "GetFeatureInfo", MS_FALSE))
51✔
3532
        msWMSPrintRequestCap(nVersion, "GetFeatureInfo", script_url_encoded,
47✔
3533
                             "text/plain", "application/vnd.ogc.gml", NULL);
3534
    }
3535

3536
    if (strcasecmp(sldenabled, "true") == 0) {
56✔
3537
      if (msOWSRequestIsEnabled(map, NULL, "M", "DescribeLayer", MS_FALSE)) {
39✔
3538
        if (nVersion == OWS_1_3_0)
38✔
3539
          msWMSPrintRequestCap(nVersion, "sld:DescribeLayer",
19✔
3540
                               script_url_encoded, "text/xml", NULL);
3541
        else
3542
          msWMSPrintRequestCap(nVersion, "DescribeLayer", script_url_encoded,
19✔
3543
                               "text/xml", NULL);
3544
      }
3545

3546
      msGetOutputFormatMimeListImg(map, mime_list,
39✔
3547
                                   sizeof(mime_list) / sizeof(char *));
3548

3549
      if (nVersion >= OWS_1_1_1) {
39✔
3550
        const auto isGetLegendGraphicEnabled =
3551
            msOWSRequestIsEnabled(map, NULL, "M", "GetLegendGraphic", MS_FALSE);
34✔
3552
        if (nVersion == OWS_1_3_0) {
34✔
3553
          if (isGetLegendGraphicEnabled)
20✔
3554
            msWMSPrintRequestCap(
16✔
3555
                nVersion, "sld:GetLegendGraphic", script_url_encoded,
3556
                mime_list[0], mime_list[1], mime_list[2], mime_list[3],
3557
                mime_list[4], mime_list[5], mime_list[6], mime_list[7],
3558
                mime_list[8], mime_list[9], mime_list[10], mime_list[11],
3559
                mime_list[12], mime_list[13], mime_list[14], mime_list[15],
3560
                mime_list[16], mime_list[17], mime_list[18], mime_list[19],
3561
                NULL);
3562
          if (msOWSRequestIsEnabled(map, NULL, "M", "GetStyles", MS_FALSE))
20✔
3563
            msWMSPrintRequestCap(nVersion, "ms:GetStyles", script_url_encoded,
16✔
3564
                                 "text/xml", NULL);
3565
        } else {
3566
          if (isGetLegendGraphicEnabled)
14✔
3567
            msWMSPrintRequestCap(
14✔
3568
                nVersion, "GetLegendGraphic", script_url_encoded, mime_list[0],
3569
                mime_list[1], mime_list[2], mime_list[3], mime_list[4],
3570
                mime_list[5], mime_list[6], mime_list[7], mime_list[8],
3571
                mime_list[9], mime_list[10], mime_list[11], mime_list[12],
3572
                mime_list[13], mime_list[14], mime_list[15], mime_list[16],
3573
                mime_list[17], mime_list[18], mime_list[19], NULL);
3574
          if (msOWSRequestIsEnabled(map, NULL, "M", "GetStyles", MS_FALSE))
14✔
3575
            msWMSPrintRequestCap(nVersion, "GetStyles", script_url_encoded,
14✔
3576
                                 "text/xml", NULL);
3577
        }
3578
      }
3579
    }
3580
  }
3581

3582
  msIO_printf("  </Request>\n");
57✔
3583

3584
  msIO_printf("  <Exception>\n");
57✔
3585
  if (nVersion <= OWS_1_0_7)
57✔
3586
    msIO_printf("    <Format><BLANK /><INIMAGE /><WMS_XML /></Format>\n");
1✔
3587
  else if (nVersion <= OWS_1_1_1) {
56✔
3588
    msIO_printf("    <Format>application/vnd.ogc.se_xml</Format>\n");
27✔
3589
    msIO_printf("    <Format>application/vnd.ogc.se_inimage</Format>\n");
27✔
3590
    msIO_printf("    <Format>application/vnd.ogc.se_blank</Format>\n");
27✔
3591
  } else { /*>=1.3.0*/
3592
    msIO_printf("    <Format>XML</Format>\n");
29✔
3593
    msIO_printf("    <Format>INIMAGE</Format>\n");
29✔
3594
    msIO_printf("    <Format>BLANK</Format>\n");
29✔
3595
  }
3596
  msIO_printf("  </Exception>\n");
57✔
3597

3598
  if (nVersion != OWS_1_3_0) {
57✔
3599
    /* INSPIRE extended capabilities for WMS 1.1.1 */
3600
    if (nVersion == OWS_1_1_1 && msOWSLookupMetadata(&(map->web.metadata), "MO",
28✔
3601
                                                     "inspire_capabilities")) {
3602
      msIO_printf("  <VendorSpecificCapabilities>\n");
6✔
3603
      msOWSPrintInspireCommonExtendedCapabilities(
6✔
3604
          stdout, map, "MO", OWS_WARN, "inspire_vs:ExtendedCapabilities", NULL,
3605
          validated_language.c_str(), OWS_WMS);
3606
      msIO_printf("  </VendorSpecificCapabilities>\n");
6✔
3607
    } else {
3608
      msIO_printf("  <VendorSpecificCapabilities />\n"); /* nothing yet */
22✔
3609
    }
3610
  }
3611

3612
  /* SLD support */
3613
  if (strcasecmp(sldenabled, "true") == 0) {
57✔
3614
    if (nVersion >= OWS_1_0_7) {
40✔
3615
      if (nVersion >= OWS_1_3_0)
39✔
3616
        msIO_printf("  <sld:UserDefinedSymbolization SupportSLD=\"1\" "
20✔
3617
                    "UserLayer=\"0\" UserStyle=\"1\" RemoteWFS=\"0\" "
3618
                    "InlineFeature=\"0\" RemoteWCS=\"0\"/>\n");
3619
      else
3620
        msIO_printf("  <UserDefinedSymbolization SupportSLD=\"1\" "
19✔
3621
                    "UserLayer=\"0\" UserStyle=\"1\" RemoteWFS=\"0\"/>\n");
3622
    }
3623
  }
3624

3625
  /* INSPIRE extended capabilities for WMS 1.3.0 */
3626
  if (nVersion >= OWS_1_3_0 &&
85✔
3627
      msOWSLookupMetadata(&(map->web.metadata), "MO", "inspire_capabilities")) {
29✔
3628
    msOWSPrintInspireCommonExtendedCapabilities(
9✔
3629
        stdout, map, "MO", OWS_WARN, "inspire_vs:ExtendedCapabilities", NULL,
3630
        validated_language.c_str(), OWS_WMS);
3631
  }
3632

3633
  /* Top-level layer with map extents and SRS, encloses all map layers */
3634
  /* Output only if at least one layers is enabled. */
3635
  if (ows_request->numlayers == 0) {
57✔
3636
    msIO_fprintf(stdout, "  <!-- WARNING: No WMS layers are enabled. Check "
4✔
3637
                         "wms/ows_enable_request settings. -->\n");
3638
  } else {
3639
    int root_is_queryable = MS_FALSE;
3640

3641
    const char *rootlayer_name =
3642
        msOWSLookupMetadata(&(map->web.metadata), "MO", "rootlayer_name");
53✔
3643

3644
    /* Root layer is queryable if it has a valid name and at list one layer */
3645
    /* is queryable */
3646
    if (!rootlayer_name || strlen(rootlayer_name) > 0) {
53✔
3647
      int j;
3648
      for (j = 0; j < map->numlayers; j++) {
126✔
3649
        layerObj *layer = GET_LAYER(map, j);
101✔
3650
        if (msIsLayerQueryable(layer) &&
130✔
3651
            msIntegerInArray(layer->index, ows_request->enabled_layers,
29✔
3652
                             ows_request->numlayers)) {
3653
          root_is_queryable = MS_TRUE;
3654
          break;
3655
        }
3656
      }
3657
    }
3658
    msIO_printf("  <Layer%s>\n", root_is_queryable ? " queryable=\"1\"" : "");
78✔
3659

3660
    /* Layer Name is optional but title is mandatory. */
3661
    if (map->name && strlen(map->name) > 0 &&
106✔
3662
        (msIsXMLTagValid(map->name) == MS_FALSE || isdigit(map->name[0])))
106✔
3663
      msIO_fprintf(stdout,
×
3664
                   "<!-- WARNING: The layer name '%s' might contain spaces or "
3665
                   "invalid characters or may start with a number. This could "
3666
                   "lead to potential problems. -->\n",
3667
                   map->name);
3668

3669
    if (rootlayer_name) {
53✔
3670
      if (strlen(rootlayer_name) > 0) {
2✔
3671
        msOWSPrintEncodeMetadata(stdout, &(map->web.metadata), "MO",
1✔
3672
                                 "rootlayer_name", OWS_NOERR,
3673
                                 "    <Name>%s</Name>\n", NULL);
3674
      }
3675
    } else {
3676
      msOWSPrintEncodeParam(stdout, "MAP.NAME", map->name, OWS_NOERR,
51✔
3677
                            "    <Name>%s</Name>\n", NULL);
3678
    }
3679

3680
    if (msOWSLookupMetadataWithLanguage(&(map->web.metadata), "MO",
53✔
3681
                                        "rootlayer_title",
3682
                                        validated_language.c_str()))
3683
      msOWSPrintEncodeMetadata2(
24✔
3684
          stdout, &(map->web.metadata), "MO", "rootlayer_title", OWS_WARN,
3685
          "    <Title>%s</Title>\n", map->name, validated_language.c_str());
24✔
3686

3687
    else
3688
      msOWSPrintEncodeMetadata2(stdout, &(map->web.metadata), "MO", "title",
29✔
3689
                                OWS_WARN, "    <Title>%s</Title>\n", map->name,
29✔
3690
                                validated_language.c_str());
3691

3692
    if (msOWSLookupMetadataWithLanguage(&(map->web.metadata), "MO",
53✔
3693
                                        "rootlayer_abstract",
3694
                                        validated_language.c_str()))
3695
      msOWSPrintEncodeMetadata2(stdout, &(map->web.metadata), "MO",
24✔
3696
                                "rootlayer_abstract", OWS_NOERR,
3697
                                "    <Abstract>%s</Abstract>\n", map->name,
24✔
3698
                                validated_language.c_str());
3699
    else
3700
      msOWSPrintEncodeMetadata2(stdout, &(map->web.metadata), "MO", "abstract",
29✔
3701
                                OWS_NOERR, "    <Abstract>%s</Abstract>\n",
3702
                                map->name, validated_language.c_str());
29✔
3703

3704
    const char *pszTmp;
3705
    if (msOWSLookupMetadata(&(map->web.metadata), "MO",
53✔
3706
                            "rootlayer_keywordlist") ||
82✔
3707
        msOWSLookupMetadata(&(map->web.metadata), "MO",
29✔
3708
                            "rootlayer_keywordlist_vocabulary"))
3709
      pszTmp = "rootlayer_keywordlist";
3710
    else
3711
      pszTmp = "keywordlist";
3712
    msWMSPrintKeywordlist(stdout, "    ", pszTmp, &(map->web.metadata), "MO",
53✔
3713
                          nVersion);
3714

3715
    /* According to normative comments in the 1.0.7 DTD, the root layer's SRS
3716
     * tag */
3717
    /* is REQUIRED.  It also suggests that we use an empty SRS element if there
3718
     */
3719
    /* is no common SRS. */
3720
    char *pszMapEPSG;
3721
    msOWSGetEPSGProj(&(map->projection), &(map->web.metadata), "MO", MS_FALSE,
53✔
3722
                     &pszMapEPSG);
3723
    if (nVersion > OWS_1_1_0) {
53✔
3724
      /* starting 1.1.1 SRS are given in individual tags */
3725
      if (nVersion >= OWS_1_3_0) {
48✔
3726
        msOWSPrintEncodeParamList(stdout,
27✔
3727
                                  "(at least one of) "
3728
                                  "MAP.PROJECTION, LAYER.PROJECTION "
3729
                                  "or wms_srs metadata",
3730
                                  pszMapEPSG, OWS_WARN, ' ', NULL, NULL,
3731
                                  "    <CRS>%s</CRS>\n", "");
3732
      } else {
3733
        msOWSPrintEncodeParamList(stdout,
21✔
3734
                                  "(at least one of) "
3735
                                  "MAP.PROJECTION, LAYER.PROJECTION "
3736
                                  "or wms_srs metadata",
3737
                                  pszMapEPSG, OWS_WARN, ' ', NULL, NULL,
3738
                                  "    <SRS>%s</SRS>\n", "");
3739
      }
3740
    } else {
3741
      /* If map has no proj then every layer MUST have one or produce a warning
3742
       */
3743
      msOWSPrintEncodeParam(stdout, "MAP.PROJECTION (or wms_srs metadata)",
5✔
3744
                            pszMapEPSG, OWS_WARN, "    <SRS>%s</SRS>\n", "");
3745
    }
3746
    msFree(pszMapEPSG);
53✔
3747

3748
    if (nVersion >= OWS_1_3_0)
53✔
3749
      msOWSPrintEX_GeographicBoundingBox(stdout, "    ", &(map->extent),
27✔
3750
                                         &(map->projection));
3751
    else
3752
      msOWSPrintLatLonBoundingBox(stdout, "    ", &(map->extent),
26✔
3753
                                  &(map->projection), NULL, OWS_WMS);
3754

3755
    msOWSPrintBoundingBox(stdout, "    ", &(map->extent), &(map->projection),
53✔
3756
                          NULL, &(map->web.metadata), "MO", nVersion);
3757

3758
    if (nVersion >= OWS_1_0_7) {
53✔
3759
      msWMSPrintAttribution(stdout, "    ", &(map->web.metadata), "MO");
53✔
3760
    }
3761

3762
    /* AuthorityURL support and Identifier support, only available >= WMS 1.1.0
3763
     */
3764
    if (nVersion >= OWS_1_1_0) {
53✔
3765
      msWMSPrintAuthorityURL(stdout, "    ", &(map->web.metadata), "MO");
53✔
3766
      msWMSPrintIdentifier(stdout, "    ", &(map->web.metadata), "MO");
53✔
3767
    }
3768

3769
    /* MetadataURL */
3770
    if (nVersion >= OWS_1_1_0)
3771
      msOWSPrintURLType(stdout, &(map->web.metadata), "MO", "metadataurl",
53✔
3772
                        OWS_NOERR, NULL, "MetadataURL", " type=\"%s\"", NULL,
3773
                        NULL, ">\n      <Format>%s</Format",
3774
                        "\n      <OnlineResource xmlns:xlink=\""
3775
                        "http://www.w3.org/1999/xlink\" "
3776
                        "xlink:type=\"simple\" xlink:href=\"%s\"/>\n    ",
3777
                        MS_TRUE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
3778
                        NULL, NULL, NULL, NULL, "    ");
3779

3780
    /* DataURL */
3781
    if (nVersion < OWS_1_1_0)
3782
      msOWSPrintEncodeMetadata(stdout, &(map->web.metadata), "MO",
×
3783
                               "dataurl_href", OWS_NOERR,
3784
                               "    <DataURL>%s</DataURL>\n", NULL);
3785
    else
3786
      msOWSPrintURLType(stdout, &(map->web.metadata), "MO", "dataurl",
53✔
3787
                        OWS_NOERR, NULL, "DataURL", NULL, NULL, NULL,
3788
                        ">\n      <Format>%s</Format",
3789
                        "\n      <OnlineResource xmlns:xlink=\""
3790
                        "http://www.w3.org/1999/xlink\" "
3791
                        "xlink:type=\"simple\" xlink:href=\"%s\"/>\n    ",
3792
                        MS_FALSE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
3793
                        NULL, NULL, NULL, NULL, "    ");
3794

3795
    if (map->name && strlen(map->name) > 0 &&
106✔
3796
        msOWSLookupMetadata(&(map->web.metadata), "MO",
53✔
3797
                            "inspire_capabilities")) {
3798
      char *pszEncodedName = NULL;
3799
      const char *styleName = NULL;
3800
      char *pszEncodedStyleName = NULL;
3801
      const char *legendURL = NULL;
3802

3803
      pszEncodedName = msEncodeHTMLEntities(map->name);
15✔
3804

3805
      styleName = msOWSLookupMetadata(&(map->web.metadata), "MO", "style_name");
15✔
3806
      if (styleName == NULL)
15✔
3807
        styleName = "default";
3808

3809
      pszEncodedStyleName = msEncodeHTMLEntities(styleName);
15✔
3810

3811
      msIO_fprintf(stdout, "    <Style>\n");
15✔
3812
      msIO_fprintf(stdout, "       <Name>%s</Name>\n", pszEncodedStyleName);
15✔
3813
      msOWSPrintEncodeMetadata2(
15✔
3814
          stdout, &(map->web.metadata), "MO", "style_title", OWS_NOERR,
3815
          "       <Title>%s</Title>\n", styleName, validated_language.c_str());
3816

3817
      legendURL = msOWSLookupMetadata(&(map->web.metadata), "MO",
15✔
3818
                                      "style_legendurl_href");
3819
      if (legendURL) {
15✔
3820
        msOWSPrintURLType(stdout, &(map->web.metadata), "MO", "style_legendurl",
9✔
3821
                          OWS_NOERR, NULL, "LegendURL", NULL, " width=\"%s\"",
3822
                          " height=\"%s\"", ">\n          <Format>%s</Format",
3823
                          "\n          <OnlineResource "
3824
                          "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
3825
                          " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
3826
                          "       ",
3827
                          MS_FALSE, MS_TRUE, MS_TRUE, MS_TRUE, MS_TRUE, NULL,
3828
                          NULL, NULL, NULL, NULL, "       ");
3829
      } else {
3830
        std::vector<int> group_layers;
3831
        group_layers.reserve(map->numlayers);
6✔
3832

3833
        for (int i = 0; i < map->numlayers; i++)
96✔
3834
          if (msIntegerInArray(GET_LAYER(map, i)->index,
90✔
3835
                               ows_request->enabled_layers,
3836
                               ows_request->numlayers))
3837
            group_layers.push_back(i);
84✔
3838

3839
        if (!group_layers.empty()) {
6✔
3840
          int size_x = 0, size_y = 0;
6✔
3841
          if (msLegendCalcSize(map, 1, &size_x, &size_y, group_layers.data(),
6✔
3842
                               static_cast<int>(group_layers.size()), NULL,
3843
                               1) == MS_SUCCESS) {
3844
            const std::string width(std::to_string(size_x));
6✔
3845
            const std::string height(std::to_string(size_y));
6✔
3846

3847
            const char *format_list = msOWSLookupMetadata(
6✔
3848
                &(map->web.metadata), "M", "getlegendgraphic_formatlist");
3849
            char *pszMimetype = NULL;
3850
            if (format_list && strlen(format_list) > 0) {
6✔
3851
              const auto tokens = msStringSplit(format_list, ',');
×
3852
              if (!tokens.empty()) {
×
3853
                /*just grab the first mime type*/
3854
                pszMimetype = msEncodeHTMLEntities(tokens[0].c_str());
×
3855
              }
3856
            } else
×
3857
              pszMimetype = msEncodeHTMLEntities("image/png");
6✔
3858

3859
            std::string legendurl(script_url_encoded);
6✔
3860
            legendurl += "version=";
3861
            char szVersionBuf[OWS_VERSION_MAXLEN];
3862
            legendurl += msOWSGetVersionString(nVersion, szVersionBuf);
6✔
3863
            legendurl += "&amp;service=WMS&amp;request=GetLegendGraphic&amp;";
3864
            if (nVersion >= OWS_1_3_0) {
6✔
3865
              legendurl += "sld_version=1.1.0&amp;layer=";
3866
            } else {
3867
              legendurl += "layer=";
3868
            }
3869
            legendurl += pszEncodedName;
3870
            legendurl += "&amp;format=";
3871
            legendurl += pszMimetype;
3872
            legendurl += "&amp;STYLE=";
3873
            legendurl += pszEncodedStyleName;
3874

3875
            msOWSPrintURLType(stdout, NULL, "O", "ttt", OWS_NOERR, NULL,
6✔
3876
                              "LegendURL", NULL, " width=\"%s\"",
3877
                              " height=\"%s\"",
3878
                              ">\n          <Format>%s</Format",
3879
                              "\n          <OnlineResource "
3880
                              "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
3881
                              " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
3882
                              "       ",
3883
                              MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE,
3884
                              NULL, width.c_str(), height.c_str(), pszMimetype,
3885
                              legendurl.c_str(), "       ");
3886
            msFree(pszMimetype);
6✔
3887
          }
3888
        }
3889
      }
6✔
3890
      msIO_fprintf(stdout, "    </Style>\n");
15✔
3891
      msFree(pszEncodedName);
15✔
3892
      msFree(pszEncodedStyleName);
15✔
3893
    }
3894

3895
    if (nVersion < OWS_1_3_0)
53✔
3896
      msWMSPrintScaleHint("    ", map->web.minscaledenom,
26✔
3897
                          map->web.maxscaledenom, map->resolution);
3898
    else
3899
      msWMSPrintScaleDenominator("    ", map->web.minscaledenom,
27✔
3900
                                 map->web.maxscaledenom);
3901

3902
    /*  */
3903
    /* Dump list of layers organized by groups.  Layers with no group are listed
3904
     */
3905
    /* individually, at the same level as the groups in the layer hierarchy */
3906
    /*  */
3907
    if (map->numlayers) {
53✔
3908
      char ***nestedGroups = NULL;
3909
      int *numNestedGroups = NULL;
3910
      int *isUsedInNestedGroup = NULL;
3911

3912
      /* We'll use this array of booleans to track which layer/group have been
3913
       */
3914
      /* processed already */
3915
      std::vector<char> pabLayerProcessed(map->numlayers);
53✔
3916

3917
      /* Mark disabled layers as processed to prevent from being displayed in
3918
       * nested groups (#4533)*/
3919
      for (int i = 0; i < map->numlayers; i++) {
251✔
3920
        if (!msIntegerInArray(GET_LAYER(map, i)->index,
198✔
3921
                              ows_request->enabled_layers,
3922
                              ows_request->numlayers))
3923
          pabLayerProcessed[i] = 1;
13✔
3924
      }
3925

3926
      nestedGroups = (char ***)msSmallCalloc(map->numlayers, sizeof(char **));
53✔
3927
      numNestedGroups = (int *)msSmallCalloc(map->numlayers, sizeof(int));
53✔
3928
      isUsedInNestedGroup = (int *)msSmallCalloc(map->numlayers, sizeof(int));
53✔
3929
      msWMSPrepareNestedGroups(map, nVersion, nestedGroups, numNestedGroups,
53✔
3930
                               isUsedInNestedGroup);
3931

3932
      for (int i = 0; i < map->numlayers; i++) {
251✔
3933
        layerObj *lp = (GET_LAYER(map, i));
198✔
3934

3935
        if (pabLayerProcessed[i] || (lp->status == MS_DELETE))
198✔
3936
          continue; /* Layer is hidden or has already been handled */
72✔
3937

3938
        if (numNestedGroups[i] > 0) {
126✔
3939
          /* Has nested groups.  */
3940
          msWMSPrintNestedGroups(map, nVersion, pabLayerProcessed.data(), i, 0,
24✔
3941
                                 nestedGroups, numNestedGroups,
3942
                                 isUsedInNestedGroup, script_url_encoded,
3943
                                 validated_language.c_str());
3944
        } else if (lp->group == NULL || strlen(lp->group) == 0) {
102✔
3945
          /* Don't dump layer if it is used in wms_group_layer. */
3946
          if (!isUsedInNestedGroup[i]) {
91✔
3947
            /* This layer is not part of a group... dump it directly */
3948
            msDumpLayer(map, lp, nVersion, script_url_encoded, "",
72✔
3949
                        validated_language.c_str(), MS_FALSE, MS_FALSE);
3950
            pabLayerProcessed[i] = 1;
72✔
3951
          }
3952
        } else {
3953
          bool group_is_queryable = false;
3954
          /* Group is queryable as soon as its member layers is. */
3955
          for (int j = i; j < map->numlayers; j++) {
29✔
3956
            if (GET_LAYER(map, j)->group &&
40✔
3957
                strcmp(lp->group, GET_LAYER(map, j)->group) == 0 &&
40✔
3958
                msIntegerInArray(GET_LAYER(map, j)->index,
20✔
3959
                                 ows_request->enabled_layers,
3960
                                 ows_request->numlayers) &&
40✔
3961
                msIsLayerQueryable(GET_LAYER(map, j))) {
20✔
3962
              group_is_queryable = true;
3963
              break;
3964
            }
3965
          }
3966
          /* Beginning of a new group... enclose the group in a layer block */
3967
          msIO_printf("    <Layer%s>\n",
20✔
3968
                      group_is_queryable ? " queryable=\"1\"" : "");
3969

3970
          /* Layer Name is optional but title is mandatory. */
3971
          if (lp->group && strlen(lp->group) > 0 &&
22✔
3972
              (msIsXMLTagValid(lp->group) == MS_FALSE || isdigit(lp->group[0])))
22✔
3973
            msIO_fprintf(
×
3974
                stdout,
3975
                "<!-- WARNING: The layer name '%s' might contain spaces or "
3976
                "invalid characters or may start with a number. This could "
3977
                "lead to potential problems. -->\n",
3978
                lp->group);
3979
          msOWSPrintEncodeParam(stdout, "GROUP.NAME", lp->group, OWS_NOERR,
11✔
3980
                                "      <Name>%s</Name>\n", NULL);
3981
          msOWSPrintGroupMetadata2(stdout, map, lp->group, "MO", "GROUP_TITLE",
11✔
3982
                                   OWS_WARN, "      <Title>%s</Title>\n",
3983
                                   lp->group, validated_language.c_str());
11✔
3984
          msOWSPrintGroupMetadata2(stdout, map, lp->group, "MO",
11✔
3985
                                   "GROUP_ABSTRACT", OWS_NOERR,
3986
                                   "      <Abstract>%s</Abstract>\n", lp->group,
11✔
3987
                                   validated_language.c_str());
3988

3989
          /*build a getlegendgraphicurl*/
3990
          if (script_url_encoded) {
3991
            if (lp->group && strlen(lp->group) > 0) {
11✔
3992
              char *pszEncodedName = NULL;
3993
              const char *styleName = NULL;
3994
              char *pszEncodedStyleName = NULL;
3995
              const char *legendURL = NULL;
3996

3997
              pszEncodedName = msEncodeHTMLEntities(lp->group);
11✔
3998

3999
              styleName = msOWSLookupMetadata(&(lp->metadata), "MO",
11✔
4000
                                              "group_style_name");
4001
              if (styleName == NULL)
11✔
4002
                styleName = "default";
4003

4004
              pszEncodedStyleName = msEncodeHTMLEntities(styleName);
11✔
4005

4006
              msIO_fprintf(stdout, "    <Style>\n");
11✔
4007
              msIO_fprintf(stdout, "       <Name>%s</Name>\n",
11✔
4008
                           pszEncodedStyleName);
4009
              msOWSPrintEncodeMetadata(stdout, &(lp->metadata), "MO",
11✔
4010
                                       "group_style_title", OWS_NOERR,
4011
                                       "       <Title>%s</Title>\n", styleName);
4012

4013
              legendURL = msOWSLookupMetadata(&(lp->metadata), "MO",
11✔
4014
                                              "group_style_legendurl_href");
4015
              if (legendURL) {
11✔
4016
                msOWSPrintURLType(
9✔
4017
                    stdout, &(lp->metadata), "MO", "group_style_legendurl",
4018
                    OWS_NOERR, NULL, "LegendURL", NULL, " width=\"%s\"",
4019
                    " height=\"%s\"", ">\n          <Format>%s</Format",
4020
                    "\n          <OnlineResource "
4021
                    "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
4022
                    " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
4023
                    "       ",
4024
                    MS_FALSE, MS_TRUE, MS_TRUE, MS_TRUE, MS_TRUE, NULL, NULL,
4025
                    NULL, NULL, NULL, "       ");
4026
              } else {
4027
                std::vector<int> group_layers;
4028
                group_layers.reserve(map->numlayers);
2✔
4029

4030
                for (int j = i; j < map->numlayers; j++)
7✔
4031
                  if (!pabLayerProcessed[j] && GET_LAYER(map, j)->group &&
5✔
4032
                      strcmp(lp->group, GET_LAYER(map, j)->group) == 0 &&
10✔
4033
                      msIntegerInArray(GET_LAYER(map, j)->index,
2✔
4034
                                       ows_request->enabled_layers,
4035
                                       ows_request->numlayers))
4036
                    group_layers.push_back(j);
2✔
4037

4038
                if (!group_layers.empty()) {
2✔
4039
                  int size_x = 0, size_y = 0;
2✔
4040
                  char *pszMimetype = NULL;
4041

4042
                  if (msLegendCalcSize(map, 1, &size_x, &size_y,
2✔
4043
                                       group_layers.data(),
4044
                                       static_cast<int>(group_layers.size()),
4045
                                       NULL, 1) == MS_SUCCESS) {
4046
                    const std::string width(std::to_string(size_x));
2✔
4047
                    const std::string height(std::to_string(size_y));
2✔
4048

4049
                    const char *format_list =
4050
                        msOWSLookupMetadata(&(map->web.metadata), "M",
2✔
4051
                                            "getlegendgraphic_formatlist");
4052
                    if (format_list && strlen(format_list) > 0) {
2✔
4053
                      const auto tokens = msStringSplit(format_list, ',');
×
4054
                      if (!tokens.empty()) {
×
4055
                        /*just grab the first mime type*/
4056
                        pszMimetype = msEncodeHTMLEntities(tokens[0].c_str());
×
4057
                      }
4058
                    } else
×
4059
                      pszMimetype = msEncodeHTMLEntities("image/png");
2✔
4060

4061
                    std::string legendurl(script_url_encoded);
2✔
4062
                    legendurl += "version=";
4063
                    char szVersionBuf[OWS_VERSION_MAXLEN];
4064
                    legendurl += msOWSGetVersionString(nVersion, szVersionBuf);
2✔
4065
                    legendurl +=
4066
                        "&amp;service=WMS&amp;request=GetLegendGraphic&amp;";
4067
                    if (nVersion >= OWS_1_3_0) {
2✔
4068
                      legendurl += "sld_version=1.1.0&amp;layer=";
4069
                    } else {
4070
                      legendurl += "layer=";
4071
                    }
4072
                    legendurl += pszEncodedName;
4073
                    legendurl += "&amp;format=";
4074
                    legendurl += pszMimetype;
4075
                    legendurl += "&amp;STYLE=";
4076
                    legendurl += pszEncodedStyleName;
4077

4078
                    msOWSPrintURLType(
2✔
4079
                        stdout, NULL, "O", "ttt", OWS_NOERR, NULL, "LegendURL",
4080
                        NULL, " width=\"%s\"", " height=\"%s\"",
4081
                        ">\n          <Format>%s</Format",
4082
                        "\n          <OnlineResource "
4083
                        "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
4084
                        " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
4085
                        "       ",
4086
                        MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE, NULL,
4087
                        width.c_str(), height.c_str(), pszMimetype,
4088
                        legendurl.c_str(), "       ");
4089

4090
                    msFree(pszMimetype);
2✔
4091
                  }
4092
                }
4093
              }
2✔
4094
              msIO_fprintf(stdout, "    </Style>\n");
11✔
4095
              msFree(pszEncodedName);
11✔
4096
              msFree(pszEncodedStyleName);
11✔
4097
            }
4098
          }
4099

4100
          /* Dump all layers for this group */
4101
          for (int j = i; j < map->numlayers; j++) {
34✔
4102
            if (!pabLayerProcessed[j] && GET_LAYER(map, j)->group &&
23✔
4103
                strcmp(lp->group, GET_LAYER(map, j)->group) == 0 &&
64✔
4104
                msIntegerInArray(GET_LAYER(map, j)->index,
20✔
4105
                                 ows_request->enabled_layers,
4106
                                 ows_request->numlayers)) {
4107
              msDumpLayer(map, (GET_LAYER(map, j)), nVersion,
20✔
4108
                          script_url_encoded, "  ", validated_language.c_str(),
4109
                          MS_FALSE, MS_FALSE);
4110
              pabLayerProcessed[j] = 1;
20✔
4111
            }
4112
          }
4113
          /* Close group layer block */
4114
          msIO_printf("    </Layer>\n");
11✔
4115
        }
4116
      }
4117

4118
      /* free the stuff used for nested layers */
4119
      for (int i = 0; i < map->numlayers; i++) {
251✔
4120
        if (numNestedGroups[i] > 0) {
198✔
4121
          msFreeCharArray(nestedGroups[i], numNestedGroups[i]);
80✔
4122
        }
4123
      }
4124
      free(nestedGroups);
53✔
4125
      free(numNestedGroups);
53✔
4126
      free(isUsedInNestedGroup);
53✔
4127
    }
53✔
4128

4129
    msIO_printf("  </Layer>\n");
53✔
4130
  }
4131

4132
  msIO_printf("</Capability>\n");
57✔
4133
  if (nVersion >= OWS_1_3_0)
57✔
4134
    msIO_printf("</WMS_Capabilities>\n");
29✔
4135
  else
4136
    msIO_printf("</WMT_MS_Capabilities>\n");
28✔
4137

4138
  free(script_url_encoded);
57✔
4139

4140
  return (MS_SUCCESS);
57✔
4141
}
4142

4143
/*
4144
 * This function look for params that can be used
4145
 * by mapserv when generating template.
4146
 */
4147
int msTranslateWMS2Mapserv(const char **names, const char **values,
7✔
4148
                           int numentries, char ***translated_names,
4149
                           char ***translated_values,
4150
                           int *translated_numentries) {
4151
  int num_allocated = numentries;
4152
  *translated_names = (char **)msSmallMalloc(num_allocated * sizeof(char *));
7✔
4153
  *translated_values = (char **)msSmallMalloc(num_allocated * sizeof(char *));
7✔
4154
  *translated_numentries = 0;
7✔
4155
  for (int i = 0; i < numentries; i++) {
128✔
4156
    (*translated_values)[*translated_numentries] = msStrdup(values[i]);
121✔
4157
    (*translated_names)[*translated_numentries] = msStrdup(names[i]);
121✔
4158
    (*translated_numentries)++;
121✔
4159
    if (strcasecmp("X", names[i]) == 0) {
121✔
4160
      num_allocated++;
3✔
4161
      *translated_names = (char **)msSmallRealloc(
6✔
4162
          *translated_names, num_allocated * sizeof(char *));
3✔
4163
      *translated_values = (char **)msSmallRealloc(
3✔
4164
          *translated_values, num_allocated * sizeof(char *));
4165
      (*translated_values)[*translated_numentries] = msStrdup(values[i]);
3✔
4166
      (*translated_names)[*translated_numentries] = msStrdup("img.x");
3✔
4167
      (*translated_numentries)++;
3✔
4168
    } else if (strcasecmp("Y", names[i]) == 0) {
118✔
4169
      num_allocated++;
3✔
4170
      *translated_names = (char **)msSmallRealloc(
6✔
4171
          *translated_names, num_allocated * sizeof(char *));
3✔
4172
      *translated_values = (char **)msSmallRealloc(
3✔
4173
          *translated_values, num_allocated * sizeof(char *));
4174
      (*translated_values)[*translated_numentries] = msStrdup(values[i]);
3✔
4175
      (*translated_names)[*translated_numentries] = msStrdup("img.y");
3✔
4176
      (*translated_numentries)++;
3✔
4177
    } else if (strcasecmp("LAYERS", names[i]) == 0) {
115✔
4178
      char **layers;
4179
      int tok;
4180
      int j;
4181
      layers = msStringSplit(values[i], ',', &tok);
7✔
4182
      num_allocated += tok;
7✔
4183
      *translated_names = (char **)msSmallRealloc(
14✔
4184
          *translated_names, num_allocated * sizeof(char *));
7✔
4185
      *translated_values = (char **)msSmallRealloc(
7✔
4186
          *translated_values, num_allocated * sizeof(char *));
4187
      for (j = 0; j < tok; j++) {
16✔
4188
        (*translated_values)[*translated_numentries] = layers[j];
9✔
4189
        (*translated_names)[*translated_numentries] = msStrdup("layer");
9✔
4190
        (*translated_numentries)++;
9✔
4191
        layers[j] = NULL;
9✔
4192
      }
4193
      free(layers);
7✔
4194
    } else if (strcasecmp("QUERY_LAYERS", names[i]) == 0) {
108✔
4195
      char **layers;
4196
      int tok;
4197
      int j;
4198
      layers = msStringSplit(values[i], ',', &tok);
7✔
4199
      num_allocated += tok;
7✔
4200
      *translated_names = (char **)msSmallRealloc(
14✔
4201
          *translated_names, num_allocated * sizeof(char *));
7✔
4202
      *translated_values = (char **)msSmallRealloc(
7✔
4203
          *translated_values, num_allocated * sizeof(char *));
4204
      for (j = 0; j < tok; j++) {
14✔
4205
        (*translated_values)[*translated_numentries] = layers[j];
7✔
4206
        (*translated_names)[*translated_numentries] = msStrdup("qlayer");
7✔
4207
        (*translated_numentries)++;
7✔
4208
        layers[j] = NULL;
7✔
4209
      }
4210
      free(layers);
7✔
4211
    } else if (strcasecmp("BBOX", names[i]) == 0) {
101✔
4212
      char *imgext;
4213
      num_allocated++;
7✔
4214
      *translated_names = (char **)msSmallRealloc(
14✔
4215
          *translated_names, num_allocated * sizeof(char *));
7✔
4216
      *translated_values = (char **)msSmallRealloc(
7✔
4217
          *translated_values, num_allocated * sizeof(char *));
4218

4219
      /* Note msReplaceSubstring() works on the string itself, so we need to
4220
       * make a copy */
4221
      imgext = msStrdup(values[i]);
7✔
4222
      imgext = msReplaceSubstring(imgext, ",", " ");
7✔
4223
      (*translated_values)[*translated_numentries] = imgext;
7✔
4224
      (*translated_names)[*translated_numentries] = msStrdup("imgext");
7✔
4225
      (*translated_numentries)++;
7✔
4226
    }
4227
  }
4228

4229
  return MS_SUCCESS;
7✔
4230
}
4231

4232
/*
4233
** msWMSGetMap()
4234
*/
4235
static int msWMSGetMap(mapObj *map, int nVersion, char **names, char **values,
400✔
4236
                       int numentries, const char *wms_exception_format,
4237
                       owsRequestObj *ows_request) {
4238

4239
  // If we are returning an OpenLayers map there is no need to first generate an
4240
  // image. We can't call msReturnOpenLayersPage directly here as it requires
4241
  // the mapservObj
4242
  if (strcasecmp(map->imagetype, "application/openlayers") == 0) {
400✔
4243
    return MS_SUCCESS;
4244
  }
4245

4246
  imageObj *img;
4247
  int sldrequested = MS_FALSE, sldspatialfilter = MS_FALSE;
4248
  int drawquerymap = MS_FALSE;
4249

4250
  /* __TODO__ msDrawMap() will try to adjust the extent of the map */
4251
  /* to match the width/height image ratio. */
4252
  /* The spec states that this should not happen so that we can deliver */
4253
  /* maps to devices with non-square pixels. */
4254

4255
  /* If there was an SLD in the request, we need to treat it */
4256
  /* differently : some SLD may contain spatial filters requiring */
4257
  /* to do a query. While parsing the SLD and applying it to the */
4258
  /* layer, we added a temporary metadata on the layer */
4259
  /* (tmp_wms_sld_query) for layers with a spatial filter. */
4260

4261
  for (int i = 0; i < numentries; i++) {
4,954✔
4262
    if ((strcasecmp(names[i], "SLD") == 0 && values[i] &&
4,690✔
4263
         strlen(values[i]) > 0) ||
134✔
4264
        (strcasecmp(names[i], "SLD_BODY") == 0 && values[i] &&
4,687✔
4265
         strlen(values[i]) > 0)) {
4266
      sldrequested = MS_TRUE;
4267
      break;
4268
    }
4269
  }
4270
  if (sldrequested) {
398✔
4271
    for (int i = 0; i < map->numlayers; i++) {
609✔
4272
      if (msLookupHashTable(&(GET_LAYER(map, i)->metadata),
475✔
4273
                            "tmp_wms_sld_query")) {
4274
        sldspatialfilter = MS_TRUE;
4275
        break;
4276
      }
4277
    }
4278
  }
4279
  /* If FILTER is passed then we'll render layers as querymap */
4280
  for (int i = 0; i < numentries; i++) {
5,077✔
4281
    if ((strcasecmp(names[i], "FILTER") == 0 && values[i] &&
4,689✔
4282
         strlen(values[i]) > 0)) {
10✔
4283
      drawquerymap = MS_TRUE;
4284
      map->querymap.status = MS_ON;
10✔
4285
      map->querymap.style = MS_SELECTED;
10✔
4286
      break;
10✔
4287
    }
4288
  }
4289

4290
  /* turn off layer if WMS GetMap is not enabled */
4291
  for (int i = 0; i < map->numlayers; i++)
3,002✔
4292
    if (!msIntegerInArray(GET_LAYER(map, i)->index, ows_request->enabled_layers,
2,604✔
4293
                          ows_request->numlayers))
4294
      GET_LAYER(map, i)->status = MS_OFF;
2✔
4295

4296
  if (sldrequested && sldspatialfilter) {
398✔
4297
    /* set the quermap style so that only selected features will be returned */
4298
    map->querymap.status = MS_ON;
×
4299
    map->querymap.style = MS_SELECTED;
×
4300

4301
    img = msPrepareImage(map, MS_TRUE);
×
4302

4303
    /* compute layer scale factors now */
4304
    for (int i = 0; i < map->numlayers; i++) {
×
4305
      if (GET_LAYER(map, i)->sizeunits != MS_PIXELS)
×
4306
        GET_LAYER(map, i)->scalefactor =
×
4307
            (msInchesPerUnit(GET_LAYER(map, i)->sizeunits, 0) /
×
4308
             msInchesPerUnit(map->units, 0)) /
×
4309
            map->cellsize;
×
4310
      else if (GET_LAYER(map, i)->symbolscaledenom > 0 && map->scaledenom > 0)
×
4311
        GET_LAYER(map, i)->scalefactor =
×
4312
            GET_LAYER(map, i)->symbolscaledenom / map->scaledenom;
×
4313
      else
4314
        GET_LAYER(map, i)->scalefactor = 1;
×
4315
    }
4316
    for (int i = 0; i < map->numlayers; i++) {
×
4317
      if (msLookupHashTable(&(GET_LAYER(map, i)->metadata),
×
4318
                            "tmp_wms_sld_query") &&
×
4319
          (GET_LAYER(map, i)->type == MS_LAYER_POINT ||
×
4320
           GET_LAYER(map, i)->type == MS_LAYER_LINE ||
4321
           GET_LAYER(map, i)->type == MS_LAYER_POLYGON ||
×
4322
           GET_LAYER(map, i)->type == MS_LAYER_TILEINDEX))
4323

4324
      {
4325
        /* make sure that there is a resultcache. If not just ignore */
4326
        /* the layer */
4327
        if (GET_LAYER(map, i)->resultcache)
×
4328
          msDrawQueryLayer(map, GET_LAYER(map, i), img);
×
4329
      }
4330

4331
      else
4332
        IGNORE_RET_VAL(msDrawLayer(map, GET_LAYER(map, i), img));
×
4333
    }
4334

4335
  } else {
4336

4337
    /* intercept requests for Mapbox vector tiles */
4338
    if (!strcmp(MS_IMAGE_MIME_TYPE(map->outputformat),
796✔
4339
                "application/vnd.mapbox-vector-tile") ||
394✔
4340
        !strcmp(MS_IMAGE_MIME_TYPE(map->outputformat),
394✔
4341
                "application/x-protobuf")) {
4342
      int status = 0;
4343
      if ((status = msMVTWriteTile(map, MS_TRUE)) != MS_SUCCESS)
5✔
4344
        return MS_FAILURE;
4345
      return MS_SUCCESS;
4346
    }
4347

4348
    img = msDrawMap(map, drawquerymap);
393✔
4349
  }
4350

4351
  /* see if we have tiled = true and a buffer */
4352
  /* if so, clip the image */
4353
  for (int i = 0; i < numentries; i++) {
5,026✔
4354
    if (strcasecmp(names[i], "TILED") == 0 &&
4,634✔
4355
        strcasecmp(values[i], "TRUE") == 0) {
1✔
4356
      hashTableObj *meta = &(map->web.metadata);
1✔
4357
      const char *value;
4358

4359
      if ((value = msLookupHashTable(meta, "tile_map_edge_buffer")) != NULL) {
1✔
4360
        const int map_edge_buffer = atoi(value);
4361
        if (map_edge_buffer > 0) {
1✔
4362
          /* we have to clip the image */
4363

4364
          // TODO: we could probably avoid the use of an intermediate image
4365
          // by playing with the rasterBufferObj's data->rgb.pixels and
4366
          // data->rgb.row_stride values.
4367
          rendererVTableObj *renderer = MS_MAP_RENDERER(map);
1✔
4368
          rasterBufferObj imgBuffer;
4369
          if (renderer->getRasterBufferHandle((imageObj *)img, &imgBuffer) !=
1✔
4370
              MS_SUCCESS) {
4371
            msFreeImage(img);
×
4372
            return MS_FAILURE;
×
4373
          }
4374

4375
          int width = map->width - map_edge_buffer - map_edge_buffer;
1✔
4376
          int height = map->height - map_edge_buffer - map_edge_buffer;
1✔
4377
          imageObj *tmp =
4378
              msImageCreate(width, height, map->outputformat, NULL, NULL,
1✔
4379
                            map->resolution, map->defresolution, NULL);
4380

4381
          if ((MS_FAILURE == renderer->mergeRasterBuffer(
1✔
4382
                                 tmp, &imgBuffer, 1.0, map_edge_buffer,
4383
                                 map_edge_buffer, 0, 0, width, height))) {
4384
            msFreeImage(tmp);
×
4385
            msFreeImage(img);
×
4386
            img = NULL;
4387
          } else {
4388
            msFreeImage(img);
1✔
4389
            img = tmp;
4390
          }
4391
        }
4392
      }
4393
      break;
4394
    }
4395
  }
4396

4397
  if (img == NULL)
393✔
4398
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4399

4400
  /* Set the HTTP Cache-control headers if they are defined
4401
     in the map object */
4402

4403
  const char *http_max_age =
4404
      msOWSLookupMetadata(&(map->web.metadata), "MO", "http_max_age");
393✔
4405
  if (http_max_age) {
393✔
4406
    msIO_setHeader("Cache-Control", "max-age=%s", http_max_age);
2✔
4407
  }
4408

4409
  if (strcasecmp(map->imagetype, "application/openlayers") != 0) {
393✔
4410
    if (!strcmp(MS_IMAGE_MIME_TYPE(map->outputformat), "application/json")) {
786✔
4411
      msIO_setHeader("Content-Type", "application/json; charset=utf-8");
×
4412
    } else {
4413
      msOutputFormatResolveFromImage(map, img);
393✔
4414
      msIO_setHeader("Content-Type", "%s",
393✔
4415
                     MS_IMAGE_MIME_TYPE(map->outputformat));
393✔
4416
    }
4417
    msIO_sendHeaders();
393✔
4418
    if (msSaveImage(map, img, NULL) != MS_SUCCESS) {
393✔
4419
      msFreeImage(img);
×
4420
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4421
    }
4422
  }
4423
  msFreeImage(img);
393✔
4424

4425
  return (MS_SUCCESS);
393✔
4426
}
4427

4428
static int msDumpResult(mapObj *map, int nVersion,
7✔
4429
                        const char *wms_exception_format) {
4430
  int numresults = 0;
4431

4432
  for (int i = 0; i < map->numlayers; i++) {
35✔
4433
    layerObj *lp = (GET_LAYER(map, i));
28✔
4434

4435
    if (lp->status != MS_ON || lp->resultcache == NULL ||
28✔
4436
        lp->resultcache->numresults == 0)
16✔
4437
      continue;
18✔
4438

4439
    /* if(msLayerOpen(lp) != MS_SUCCESS || msLayerGetItems(lp) != MS_SUCCESS)
4440
     return msWMSException(map, nVersion, NULL); */
4441

4442
    /* Use metadata to control which fields to output. We use the same
4443
     * metadata names as for GML:
4444
     * wms/ows_include_items: comma delimited list or keyword 'all'
4445
     * wms/ows_exclude_items: comma delimited list (all items are excluded by
4446
     * default)
4447
     */
4448
    /* get a list of items that should be excluded in output */
4449
    std::vector<std::string> incitems;
4450
    const char *value;
4451
    if ((value = msOWSLookupMetadata(&(lp->metadata), "MO", "include_items")) !=
10✔
4452
        NULL)
4453
      incitems = msStringSplit(value, ',');
10✔
4454

4455
    /* get a list of items that should be excluded in output */
4456
    std::vector<std::string> excitems;
4457
    if ((value = msOWSLookupMetadata(&(lp->metadata), "MO", "exclude_items")) !=
10✔
4458
        NULL)
4459
      excitems = msStringSplit(value, ',');
×
4460

4461
    std::vector<bool> itemvisible(lp->numitems);
10✔
4462
    for (int k = 0; k < lp->numitems; k++) {
175✔
4463
      /* check visibility, included items first... */
4464
      if (incitems.size() == 1 && strcasecmp("all", incitems[0].c_str()) == 0) {
165✔
4465
        itemvisible[k] = true;
4466
      } else {
4467
        for (const auto &incitem : incitems) {
×
4468
          if (strcasecmp(lp->items[k], incitem.c_str()) == 0)
×
4469
            itemvisible[k] = true;
4470
        }
4471
      }
4472

4473
      /* ...and now excluded items */
4474
      for (const auto &excitem : excitems) {
165✔
4475
        if (strcasecmp(lp->items[k], excitem.c_str()) == 0)
×
4476
          itemvisible[k] = false;
4477
      }
4478
    }
4479

4480
    /* Output selected shapes for this layer */
4481
    msIO_printf("\nLayer '%s'\n", lp->name);
10✔
4482

4483
    for (int j = 0; j < lp->resultcache->numresults; j++) {
32✔
4484
      shapeObj shape;
4485

4486
      msInitShape(&shape);
22✔
4487
      if (msLayerGetShape(lp, &shape, &(lp->resultcache->results[j])) !=
22✔
4488
          MS_SUCCESS) {
4489
        return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4490
      }
4491

4492
      msIO_printf("  Feature %ld: \n", lp->resultcache->results[j].shapeindex);
22✔
4493

4494
      for (int k = 0; k < lp->numitems; k++) {
307✔
4495
        if (itemvisible[k]) {
285✔
4496
          value = msOWSLookupMetadata(
285✔
4497
              &(lp->metadata), "MO",
4498
              (std::string(lp->items[k]) + "_alias").c_str());
285✔
4499
          const char *lineTemplate = "    %s = '%s'\n";
4500
          msIO_printf(lineTemplate, value != NULL ? value : lp->items[k],
285✔
4501
                      shape.values[k]);
285✔
4502
        }
4503
      }
4504

4505
      msFreeShape(&shape);
22✔
4506
      numresults++;
22✔
4507
    }
4508

4509
    /* msLayerClose(lp); */
4510
  }
10✔
4511

4512
  return numresults;
4513
}
4514

4515
/*
4516
** msWMSFeatureInfo()
4517
*/
4518
static int msWMSFeatureInfo(mapObj *map, int nVersion, char **names,
42✔
4519
                            char **values, int numentries,
4520
                            const char *wms_exception_format,
4521
                            owsRequestObj *ows_request) {
4522
  int feature_count = 1, numlayers_found = 0;
4523
  pointObj point = {-1.0, -1.0, -1.0, -1.0};
4524
  const char *info_format = "MIME";
4525
  int query_layer = 0;
4526
  const char *format_list = NULL;
4527
  int valid_format = MS_FALSE;
4528
  int format_found = MS_FALSE;
4529
  int use_bbox = MS_FALSE;
4530
  int wms_layer = MS_FALSE;
4531
  const char *wms_connection = NULL;
4532
  int numOWSLayers = 0;
4533

4534
  char ***nestedGroups =
4535
      (char ***)msSmallCalloc(map->numlayers, sizeof(char **));
42✔
4536
  int *numNestedGroups = (int *)msSmallCalloc(map->numlayers, sizeof(int));
42✔
4537
  int *isUsedInNestedGroup = (int *)msSmallCalloc(map->numlayers, sizeof(int));
42✔
4538
  msWMSPrepareNestedGroups(map, nVersion, nestedGroups, numNestedGroups,
42✔
4539
                           isUsedInNestedGroup);
4540

4541
  for (int i = 0; i < numentries; i++) {
725✔
4542
    if (strcasecmp(names[i], "QUERY_LAYERS") == 0) {
683✔
4543
      query_layer = 1; /* flag set if QUERY_LAYERS is the request */
4544

4545
      const auto wmslayers = msStringSplit(values[i], ',');
42✔
4546
      if (wmslayers.empty()) {
42✔
4547
        msSetErrorWithStatus(
×
4548
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4549
            "At least one layer name required in QUERY_LAYERS.",
4550
            "msWMSFeatureInfo()");
4551
        return msWMSException(map, nVersion, "LayerNotDefined",
×
4552
                              wms_exception_format);
4553
      }
4554

4555
      for (int j = 0; j < map->numlayers; j++) {
226✔
4556
        /* Force all layers OFF by default */
4557
        GET_LAYER(map, j)->status = MS_OFF;
184✔
4558
      }
4559

4560
      /* Special case for root layer */
4561
      const char *rootlayer_name =
4562
          msOWSLookupMetadata(&(map->web.metadata), "MO", "rootlayer_name");
42✔
4563
      if (!rootlayer_name)
42✔
4564
        rootlayer_name = map->name;
42✔
4565
      if (rootlayer_name && msStringInArray(rootlayer_name, wmslayers)) {
42✔
4566
        for (int j = 0; j < map->numlayers; j++) {
61✔
4567
          layerObj *layer = GET_LAYER(map, j);
54✔
4568
          if (msIsLayerQueryable(layer) &&
92✔
4569
              msIntegerInArray(layer->index, ows_request->enabled_layers,
38✔
4570
                               ows_request->numlayers)) {
4571
            if (layer->connectiontype == MS_WMS) {
38✔
4572
              wms_layer = MS_TRUE;
4573
              wms_connection = layer->connection;
×
4574
            }
4575

4576
            numlayers_found++;
38✔
4577
            layer->status = MS_ON;
38✔
4578
          }
4579
        }
4580
      }
4581

4582
      for (int j = 0; j < map->numlayers; j++) {
226✔
4583
        layerObj *layer = GET_LAYER(map, j);
184✔
4584
        if (!msIsLayerQueryable(layer))
184✔
4585
          continue;
60✔
4586
        for (const auto &wmslayer : wmslayers) {
248✔
4587
          if (((layer->name &&
248✔
4588
                strcasecmp(layer->name, wmslayer.c_str()) == 0) ||
124✔
4589
               (layer->group &&
92✔
4590
                strcasecmp(layer->group, wmslayer.c_str()) == 0) ||
5✔
4591
               ((numNestedGroups[j] > 0) &&
177✔
4592
                msStringInArray(wmslayer.c_str(), nestedGroups[j],
85✔
4593
                                numNestedGroups[j]))) &&
169✔
4594
              (msIntegerInArray(layer->index, ows_request->enabled_layers,
45✔
4595
                                ows_request->numlayers))) {
4596

4597
            if (layer->connectiontype == MS_WMS) {
45✔
4598
              wms_layer = MS_TRUE;
4599
              wms_connection = layer->connection;
4✔
4600
            }
4601

4602
            numlayers_found++;
45✔
4603
            layer->status = MS_ON;
45✔
4604
          }
4605
        }
4606
      }
4607
    } else if (strcasecmp(names[i], "INFO_FORMAT") == 0) {
683✔
4608
      if (values[i] && strlen(values[i]) > 0) {
38✔
4609
        info_format = values[i];
4610
        format_found = MS_TRUE;
4611
      }
4612
    } else if (strcasecmp(names[i], "FEATURE_COUNT") == 0)
603✔
4613
      feature_count = atoi(values[i]);
15✔
4614
    else if (strcasecmp(names[i], "X") == 0 || strcasecmp(names[i], "I") == 0)
588✔
4615
      point.x = atof(values[i]);
42✔
4616
    else if (strcasecmp(names[i], "Y") == 0 || strcasecmp(names[i], "J") == 0)
546✔
4617
      point.y = atof(values[i]);
42✔
4618
    else if (strcasecmp(names[i], "RADIUS") == 0) {
504✔
4619
      /* RADIUS in pixels. */
4620
      /* This is not part of the spec, but some servers such as cubeserv */
4621
      /* support it as a vendor-specific feature. */
4622
      /* It's easy for MapServer to handle this so let's do it! */
4623

4624
      /* Special RADIUS value that changes the query into a bbox query */
4625
      /* based on the bbox in the request parameters. */
4626
      if (strcasecmp(values[i], "BBOX") == 0) {
11✔
4627
        use_bbox = MS_TRUE;
4628
      } else {
4629
        int j;
4630
        for (j = 0; j < map->numlayers; j++) {
62✔
4631
          GET_LAYER(map, j)->tolerance = atoi(values[i]);
51✔
4632
          GET_LAYER(map, j)->toleranceunits = MS_PIXELS;
51✔
4633
        }
4634
      }
4635
    }
4636
  }
4637

4638
  /* free the stuff used for nested layers */
4639
  for (int i = 0; i < map->numlayers; i++) {
226✔
4640
    if (numNestedGroups[i] > 0) {
184✔
4641
      msFreeCharArray(nestedGroups[i], numNestedGroups[i]);
106✔
4642
    }
4643
  }
4644
  free(nestedGroups);
42✔
4645
  free(numNestedGroups);
42✔
4646
  free(isUsedInNestedGroup);
42✔
4647

4648
  if (numlayers_found == 0) {
42✔
4649
    if (query_layer) {
1✔
4650
      msSetErrorWithStatus(
1✔
4651
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4652
          "Layer(s) specified in QUERY_LAYERS parameter is not offered "
4653
          "by the service instance.",
4654
          "msWMSFeatureInfo()");
4655
      return msWMSException(map, nVersion, "LayerNotDefined",
1✔
4656
                            wms_exception_format);
4657
    } else {
4658
      msSetErrorWithStatus(
×
4659
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4660
          "Required QUERY_LAYERS parameter missing for getFeatureInfo.",
4661
          "msWMSFeatureInfo()");
4662
      return msWMSException(map, nVersion, "LayerNotDefined",
×
4663
                            wms_exception_format);
4664
    }
4665
  }
4666

4667
  /*make sure to initialize the map scale so that layers that are scale
4668
    dependent are respected for the query*/
4669
  msCalculateScale(map->extent, map->units, map->width, map->height,
41✔
4670
                   map->resolution, &map->scaledenom);
4671

4672
  /* -------------------------------------------------------------------- */
4673
  /*      check if all layers selected are queryable. If not send an      */
4674
  /*      exception.                                                      */
4675
  /* -------------------------------------------------------------------- */
4676

4677
  /* If a layer of type WMS was found... all layers have to be of that type and
4678
   * with the same connection */
4679
  for (int i = 0; i < map->numlayers; i++) {
219✔
4680
    if (GET_LAYER(map, i)->status == MS_ON) {
178✔
4681
      if (wms_layer == MS_TRUE) {
80✔
4682
        if ((GET_LAYER(map, i)->connectiontype != MS_WMS) ||
4✔
4683
            (strcasecmp(wms_connection, GET_LAYER(map, i)->connection) != 0)) {
4✔
4684
          msSetErrorWithStatus(
×
4685
              MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4686
              "Requested WMS layer(s) are not queryable: type or "
4687
              "connection differ",
4688
              "msWMSFeatureInfo()");
4689
          return msWMSException(map, nVersion, "LayerNotQueryable",
×
4690
                                wms_exception_format);
4691
        }
4692
        ++numOWSLayers;
4✔
4693
      }
4694
    }
4695
  }
4696

4697
  /* It's a valid Cascading WMS GetFeatureInfo request */
4698
  if (wms_layer)
41✔
4699
    return msWMSLayerExecuteRequest(map, numOWSLayers, point.x, point.y,
4✔
4700
                                    feature_count, info_format,
4701
                                    WMS_GETFEATUREINFO);
4702

4703
  if (use_bbox == MS_FALSE) {
37✔
4704

4705
    if (point.x == -1.0 || point.y == -1.0) {
37✔
4706
      if (nVersion >= OWS_1_3_0)
×
4707
        msSetErrorWithStatus(
×
4708
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4709
            "Required I/J parameters missing for getFeatureInfo.",
4710
            "msWMSFeatureInfo()");
4711
      else
4712
        msSetErrorWithStatus(
×
4713
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4714
            "Required X/Y parameters missing for getFeatureInfo.",
4715
            "msWMSFeatureInfo()");
4716
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4717
    }
4718

4719
    /*wms1.3.0: check if the points are valid*/
4720
    if (nVersion >= OWS_1_3_0) {
37✔
4721
      if (point.x > map->width || point.y > map->height) {
26✔
4722
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
4723
                             "Invalid I/J values", "msWMSFeatureInfo()");
4724
        return msWMSException(map, nVersion, "InvalidPoint",
×
4725
                              wms_exception_format);
4726
      }
4727
    }
4728
    /* Perform the actual query */
4729
    const double cellx =
37✔
4730
        MS_CELLSIZE(map->extent.minx, map->extent.maxx,
37✔
4731
                    map->width); /* note: don't adjust extent, WMS assumes
4732
                                    incoming extent is correct */
4733
    const double celly =
37✔
4734
        MS_CELLSIZE(map->extent.miny, map->extent.maxy, map->height);
37✔
4735
    point.x = MS_IMAGE2MAP_X(point.x, map->extent.minx, cellx);
37✔
4736
    point.y = MS_IMAGE2MAP_Y(point.y, map->extent.maxy, celly);
37✔
4737

4738
    /* WMS 1.3.0 states that feature_count is *per layer*.
4739
     * Its value is a positive integer, if omitted then the default is 1
4740
     */
4741
    if (feature_count < 1)
37✔
4742
      feature_count = 1;
4743

4744
    map->query.type = MS_QUERY_BY_POINT;
37✔
4745
    map->query.mode =
37✔
4746
        (feature_count == 1 ? MS_QUERY_SINGLE : MS_QUERY_MULTIPLE);
37✔
4747
    map->query.layer = -1;
37✔
4748
    map->query.point = point;
37✔
4749
    map->query.buffer = 0;
37✔
4750
    map->query.maxresults = feature_count;
37✔
4751

4752
    if (msQueryByPoint(map) != MS_SUCCESS)
37✔
4753
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4754

4755
  } else { /* use_bbox == MS_TRUE */
4756
    map->query.type = MS_QUERY_BY_RECT;
×
4757
    map->query.mode = MS_QUERY_MULTIPLE;
×
4758
    map->query.layer = -1;
×
4759
    map->query.rect = map->extent;
×
4760
    map->query.buffer = 0;
×
4761
    map->query.maxresults = feature_count;
×
4762
    if (msQueryByRect(map) != MS_SUCCESS)
×
4763
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4764
  }
4765

4766
  /*validate the INFO_FORMAT*/
4767
  valid_format = MS_FALSE;
4768
  format_list = msOWSLookupMetadata(&(map->web.metadata), "M",
37✔
4769
                                    "getfeatureinfo_formatlist");
4770
  /*feature_info_mime_type deprecated for MapServer 6.0*/
4771
  if (!format_list)
37✔
4772
    format_list = msOWSLookupMetadata(&(map->web.metadata), "MO",
31✔
4773
                                      "feature_info_mime_type");
4774
  if (format_list) {
31✔
4775
    /*can not really validate if it is a valid output format
4776
      since old way of using template with web->header/footer and
4777
      layer templates need to still be supported.
4778
      We can only validate if it was part of the format list*/
4779
    if (strcasestr(format_list, info_format))
12✔
4780
      valid_format = MS_TRUE;
4781
  }
4782
  /*check to see if the format passed is text/plain or GML and if is
4783
    defined in the formatlist. If that is the case, It is a valid format*/
4784
  if (strcasecmp(info_format, "MIME") == 0 ||
37✔
4785
      strcasecmp(info_format, "text/plain") == 0 ||
33✔
4786
      strncasecmp(info_format, "GML", 3) == 0 ||
30✔
4787
      strcasecmp(info_format, "application/vnd.ogc.gml") == 0)
29✔
4788
    valid_format = MS_TRUE;
4789

4790
  /*last case: if the info_format is not part of the request, it defaults to
4791
   * MIME*/
4792
  if (!valid_format && format_found == MS_FALSE)
37✔
4793
    valid_format = MS_TRUE;
4794

4795
  if (!valid_format) {
37✔
4796
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
4797
                         "Unsupported INFO_FORMAT value (%s).",
4798
                         "msWMSFeatureInfo()", info_format);
4799
    if (nVersion >= OWS_1_3_0)
×
4800
      return msWMSException(map, nVersion, "InvalidFormat",
×
4801
                            wms_exception_format);
4802
    else
4803
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4804
  }
4805

4806
  /* Generate response */
4807
  if (strcasecmp(info_format, "MIME") == 0 ||
37✔
4808
      strcasecmp(info_format, "text/plain") == 0) {
33✔
4809

4810
    /* MIME response... we're free to use any valid MIME type */
4811
    int numresults = 0;
4812

4813
    msIO_setHeader("Content-Type", "text/plain; charset=UTF-8");
7✔
4814
    msIO_sendHeaders();
7✔
4815
    msIO_printf("GetFeatureInfo results:\n");
7✔
4816

4817
    numresults = msDumpResult(map, nVersion, wms_exception_format);
7✔
4818

4819
    if (numresults == 0)
7✔
4820
      msIO_printf("\n  Search returned no results.\n");
1✔
4821

4822
  } else if (strncasecmp(info_format, "GML", 3) ==
30✔
4823
                 0 || /* accept GML.1 or GML */
29✔
4824
             strcasecmp(info_format, "application/vnd.ogc.gml") == 0) {
29✔
4825

4826
    if (nVersion <= OWS_1_0_7) /* 1.0.0 to 1.0.7 */
23✔
4827
      msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
×
4828
    else /* 1.1.0 and later */
4829
      msIO_setHeader("Content-Type", "application/vnd.ogc.gml; charset=UTF-8");
23✔
4830
    msIO_sendHeaders();
23✔
4831
    msGMLWriteQuery(map, NULL, "MGO"); /* default is stdout */
23✔
4832

4833
  } else {
4834
    mapservObj *msObj;
4835

4836
    char **translated_names, **translated_values;
4837
    int translated_numentries;
4838
    msObj = msAllocMapServObj();
7✔
4839

4840
    /* Translate some vars from WMS to mapserv */
4841
    msTranslateWMS2Mapserv((const char **)names, (const char **)values,
7✔
4842
                           numentries, &translated_names, &translated_values,
4843
                           &translated_numentries);
4844

4845
    msObj->map = map;
7✔
4846
    msFreeCharArray(msObj->request->ParamNames, msObj->request->NumParams);
7✔
4847
    msFreeCharArray(msObj->request->ParamValues, msObj->request->NumParams);
7✔
4848
    msObj->request->ParamNames = translated_names;
7✔
4849
    msObj->request->ParamValues = translated_values;
7✔
4850
    msObj->Mode = QUERY;
7✔
4851
    msObj->request->NumParams = translated_numentries;
7✔
4852
    msObj->mappnt.x = point.x;
7✔
4853
    msObj->mappnt.y = point.y;
7✔
4854

4855
    bool hasResults = false;
4856
    for (int i = 0; i < map->numlayers; i++) {
10✔
4857
      layerObj *lp = (GET_LAYER(map, i));
8✔
4858

4859
      if (lp->status == MS_ON && lp->resultcache &&
8✔
4860
          lp->resultcache->numresults != 0) {
6✔
4861
        hasResults = true;
4862
        break;
4863
      }
4864
    }
4865

4866
    if (!hasResults && msObj->map->web.empty) {
7✔
4867
      if (msReturnURL(msObj, msObj->map->web.empty, BROWSE) != MS_SUCCESS)
1✔
4868
        return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4869
    } else if (msReturnTemplateQuery(msObj, (char *)info_format, NULL) !=
6✔
4870
               MS_SUCCESS)
4871
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4872

4873
    /* We don't want to free the map since it */
4874
    /* belongs to the caller, set it to NULL before freeing the mapservObj */
4875
    msObj->map = NULL;
7✔
4876

4877
    msFreeMapServObj(msObj);
7✔
4878
  }
4879

4880
  return (MS_SUCCESS);
4881
}
4882

4883
/*
4884
** msWMSDescribeLayer()
4885
*/
4886
static int msWMSDescribeLayer(mapObj *map, int nVersion, char **names,
19✔
4887
                              char **values, int numentries,
4888
                              const char *wms_exception_format) {
4889
  std::vector<std::string> wmslayers;
4890
  const char *version = NULL;
4891
  const char *sld_version = NULL;
4892

4893
  for (int i = 0; i < numentries; i++) {
130✔
4894
    if (strcasecmp(names[i], "LAYERS") == 0) {
111✔
4895
      wmslayers = msStringSplit(values[i], ',');
19✔
4896
    }
4897
    if (strcasecmp(names[i], "VERSION") == 0) {
111✔
4898
      version = values[i];
19✔
4899
    }
4900
    if (strcasecmp(names[i], "SLD_VERSION") == 0) {
111✔
4901
      sld_version = values[i];
16✔
4902
    }
4903
  }
4904

4905
  if (nVersion >= OWS_1_3_0 && sld_version == NULL) {
19✔
4906
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
4907
                         "Missing required parameter SLD_VERSION",
4908
                         "DescribeLayer()");
4909
    return msWMSException(map, nVersion, "MissingParameterValue",
×
4910
                          wms_exception_format);
4911
  }
4912
  if (nVersion >= OWS_1_3_0 && strcasecmp(sld_version, "1.1.0") != 0) {
19✔
4913
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
4914
                         "SLD_VERSION must be 1.1.0", "DescribeLayer()");
4915
    return msWMSException(map, nVersion, "InvalidParameterValue",
×
4916
                          wms_exception_format);
4917
  }
4918

4919
  msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
19✔
4920
  msIO_sendHeaders();
19✔
4921

4922
  msIO_printf("<?xml version='1.0' encoding=\"UTF-8\"?>\n");
19✔
4923

4924
  {
4925
    char *schemalocation = msEncodeHTMLEntities(msOWSGetSchemasLocation(map));
19✔
4926
    if (nVersion < OWS_1_3_0) {
19✔
4927

4928
      msIO_printf("<!DOCTYPE WMS_DescribeLayerResponse SYSTEM "
3✔
4929
                  "\"%s/wms/1.1.1/WMS_DescribeLayerResponse.dtd\">\n",
4930
                  schemalocation);
4931

4932
      msIO_printf("<WMS_DescribeLayerResponse version=\"%s\" >\n", version);
3✔
4933
    } else {
4934
      msIO_printf("<DescribeLayerResponse xmlns=\"http://www.opengis.net/sld\" "
16✔
4935
                  "xmlns:ows=\"http://www.opengis.net/ows\" "
4936
                  "xmlns:se=\"http://www.opengis.net/se\" "
4937
                  "xmlns:wfs=\"http://www.opengis.net/wfs\" "
4938
                  "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
4939
                  "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
4940
                  "xsi:schemaLocation=\"http://www.opengis.net/sld "
4941
                  "%s/sld/1.1.0/DescribeLayer.xsd\">\n",
4942
                  schemalocation);
4943
      msIO_printf("<Version>%s</Version>\n", sld_version);
16✔
4944
    }
4945
    free(schemalocation);
19✔
4946
  }
4947

4948
  /* check if map-level metadata wfs(wcs)_onlineresource is available */
4949
  const char *pszOnlineResMapWFS =
4950
      msOWSLookupMetadata(&(map->web.metadata), "FO", "onlineresource");
19✔
4951
  if (pszOnlineResMapWFS && strlen(pszOnlineResMapWFS) == 0)
19✔
4952
    pszOnlineResMapWFS = NULL;
4953

4954
  const char *pszOnlineResMapWCS =
4955
      msOWSLookupMetadata(&(map->web.metadata), "CO", "onlineresource");
19✔
4956
  if (pszOnlineResMapWCS && strlen(pszOnlineResMapWCS) == 0)
19✔
4957
    pszOnlineResMapWCS = NULL;
4958

4959
  char ***nestedGroups =
4960
      (char ***)msSmallCalloc(map->numlayers, sizeof(char **));
19✔
4961
  int *numNestedGroups = (int *)msSmallCalloc(map->numlayers, sizeof(int));
19✔
4962
  int *isUsedInNestedGroup = (int *)msSmallCalloc(map->numlayers, sizeof(int));
19✔
4963
  msWMSPrepareNestedGroups(map, nVersion, nestedGroups, numNestedGroups,
19✔
4964
                           isUsedInNestedGroup);
4965

4966
  for (const auto &wmslayer : wmslayers) {
38✔
4967
    for (int k = 0; k < map->numlayers; k++) {
128✔
4968
      layerObj *lp = GET_LAYER(map, k);
109✔
4969

4970
      if ((map->name && strcasecmp(map->name, wmslayer.c_str()) == 0) ||
109✔
4971
          (lp->name && strcasecmp(lp->name, wmslayer.c_str()) == 0) ||
88✔
4972
          (lp->group && strcasecmp(lp->group, wmslayer.c_str()) == 0) ||
183✔
4973
          ((numNestedGroups[k] > 0) &&
115✔
4974
           msStringInArray(wmslayer.c_str(), nestedGroups[k],
45✔
4975
                           numNestedGroups[k]))) {
4976
        /* Look for a WFS onlineresouce at the layer level and then at
4977
         * the map level.
4978
         */
4979
        const char *pszOnlineResLyrWFS =
4980
            msOWSLookupMetadata(&(lp->metadata), "FO", "onlineresource");
50✔
4981
        const char *pszOnlineResLyrWCS =
4982
            msOWSLookupMetadata(&(lp->metadata), "CO", "onlineresource");
50✔
4983
        if (pszOnlineResLyrWFS == NULL || strlen(pszOnlineResLyrWFS) == 0)
50✔
4984
          pszOnlineResLyrWFS = pszOnlineResMapWFS;
4985

4986
        if (pszOnlineResLyrWCS == NULL || strlen(pszOnlineResLyrWCS) == 0)
50✔
4987
          pszOnlineResLyrWCS = pszOnlineResMapWCS;
4988

4989
        if (pszOnlineResLyrWFS &&
50✔
4990
            (lp->type == MS_LAYER_POINT || lp->type == MS_LAYER_LINE ||
1✔
4991
             lp->type == MS_LAYER_POLYGON)) {
4992
          char *pszOnlineResEncoded = msEncodeHTMLEntities(pszOnlineResLyrWFS);
1✔
4993
          char *pszLayerName = msEncodeHTMLEntities(lp->name);
1✔
4994

4995
          if (nVersion < OWS_1_3_0) {
1✔
4996
            msIO_printf("<LayerDescription name=\"%s\" wfs=\"%s\" "
1✔
4997
                        "owsType=\"WFS\" owsURL=\"%s\">\n",
4998
                        pszLayerName, pszOnlineResEncoded, pszOnlineResEncoded);
4999
            msIO_printf("<Query typeName=\"%s\" />\n", pszLayerName);
1✔
5000
            msIO_printf("</LayerDescription>\n");
1✔
5001
          } else { /*wms 1.3.0*/
5002
            msIO_printf("  <LayerDescription>\n");
×
5003
            msIO_printf("    <owsType>wfs</owsType>\n");
×
5004
            msIO_printf("    <se:OnlineResource xlink:type=\"simple\" "
×
5005
                        "xlink:href=\"%s\"/>\n",
5006
                        pszOnlineResEncoded);
5007
            msIO_printf("    <TypeName>\n");
×
5008
            msIO_printf("      <se:FeatureTypeName>%s</se:FeatureTypeName>\n",
×
5009
                        pszLayerName);
5010
            msIO_printf("    </TypeName>\n");
×
5011
            msIO_printf("  </LayerDescription>\n");
×
5012
          }
5013

5014
          msFree(pszOnlineResEncoded);
1✔
5015
          msFree(pszLayerName);
1✔
5016
        } else if (pszOnlineResLyrWCS && lp->type == MS_LAYER_RASTER &&
50✔
5017
                   lp->connectiontype != MS_WMS) {
×
5018
          char *pszOnlineResEncoded = msEncodeHTMLEntities(pszOnlineResLyrWCS);
×
5019
          char *pszLayerName = msEncodeHTMLEntities(lp->name);
×
5020

5021
          if (nVersion < OWS_1_3_0) {
×
5022
            msIO_printf("<LayerDescription name=\"%s\"  owsType=\"WCS\" "
×
5023
                        "owsURL=\"%s\">\n",
5024
                        pszLayerName, pszOnlineResEncoded);
5025
            msIO_printf("<Query typeName=\"%s\" />\n", pszLayerName);
×
5026
            msIO_printf("</LayerDescription>\n");
×
5027
          } else {
5028
            msIO_printf("  <LayerDescription>\n");
×
5029
            msIO_printf("    <owsType>wcs</owsType>\n");
×
5030
            msIO_printf("    <se:OnlineResource xlink:type=\"simple\" "
×
5031
                        "xlink:href=\"%s\"/>\n",
5032
                        pszOnlineResEncoded);
5033
            msIO_printf("    <TypeName>\n");
×
5034
            msIO_printf("      <se:CoverageTypeName>%s</se:CoverageTypeName>\n",
×
5035
                        pszLayerName);
5036
            msIO_printf("    </TypeName>\n");
×
5037
            msIO_printf("  </LayerDescription>\n");
×
5038
          }
5039
          msFree(pszOnlineResEncoded);
×
5040
          msFree(pszLayerName);
×
5041
        } else {
×
5042
          char *pszLayerName = msEncodeHTMLEntities(lp->name);
49✔
5043

5044
          if (nVersion < OWS_1_3_0)
49✔
5045
            msIO_printf("<LayerDescription name=\"%s\"></LayerDescription>\n",
2✔
5046
                        pszLayerName);
5047
          else { /*wms 1.3.0*/
5048
            msIO_printf("  <LayerDescription>\n");
47✔
5049
            /*need to have a owstype for the DescribeLayer to be valid*/
5050
            if (lp->type == MS_LAYER_RASTER && lp->connectiontype != MS_WMS)
47✔
5051
              msIO_printf("    <owsType>wcs</owsType>\n");
×
5052
            else
5053
              msIO_printf("    <owsType>wfs</owsType>\n");
47✔
5054

5055
            msIO_printf("    <se:OnlineResource xlink:type=\"simple\"/>\n");
47✔
5056
            msIO_printf("    <TypeName>\n");
47✔
5057
            if (lp->type == MS_LAYER_RASTER && lp->connectiontype != MS_WMS)
47✔
5058
              msIO_printf(
×
5059
                  "      <se:CoverageTypeName>%s</se:CoverageTypeName>\n",
5060
                  pszLayerName);
5061
            else
5062
              msIO_printf("      <se:FeatureTypeName>%s</se:FeatureTypeName>\n",
47✔
5063
                          pszLayerName);
5064
            msIO_printf("    </TypeName>\n");
47✔
5065
            msIO_printf("  </LayerDescription>\n");
47✔
5066
          }
5067

5068
          msFree(pszLayerName);
49✔
5069
        }
5070
        /* break; */
5071
      }
5072
    }
5073
  }
5074

5075
  if (nVersion < OWS_1_3_0)
19✔
5076
    msIO_printf("</WMS_DescribeLayerResponse>\n");
3✔
5077
  else
5078
    msIO_printf("</DescribeLayerResponse>\n");
16✔
5079

5080
  /* free the stuff used for nested layers */
5081
  for (int i = 0; i < map->numlayers; i++) {
128✔
5082
    if (numNestedGroups[i] > 0) {
109✔
5083
      msFreeCharArray(nestedGroups[i], numNestedGroups[i]);
58✔
5084
    }
5085
  }
5086
  free(nestedGroups);
19✔
5087
  free(numNestedGroups);
19✔
5088
  free(isUsedInNestedGroup);
19✔
5089

5090
  return (MS_SUCCESS);
19✔
5091
}
19✔
5092

5093
/*
5094
** msWMSGetLegendGraphic()
5095
*/
5096
static int msWMSLegendGraphic(mapObj *map, int nVersion, char **names,
43✔
5097
                              char **values, int numentries,
5098
                              const char *wms_exception_format,
5099
                              owsRequestObj *ows_request,
5100
                              map_hittest *hittest) {
5101
  const char *pszLayer = NULL;
5102
  const char *pszFormat = NULL;
5103
  const char *psRule = NULL;
5104
  const char *psScale = NULL;
5105
  int iLayerIndex = -1;
5106
  outputFormatObj *psFormat = NULL;
5107
  imageObj *img = NULL;
5108
  int nWidth = -1, nHeight = -1;
5109
  const char *pszStyle = NULL;
5110
  const char *sld_version = NULL;
5111
  int wms_layer = MS_FALSE;
5112
  const char *sldenabled = NULL;
5113
  const char *format_list = NULL;
5114
  int nLayers = 0;
5115

5116
  if (!hittest) {
43✔
5117
    /* we can skip a lot of testing if we already have a hittest, as it has
5118
     * already been done in the hittesting phase */
5119

5120
    sldenabled = msOWSLookupMetadata(&(map->web.metadata), "MO", "sld_enabled");
31✔
5121

5122
    if (sldenabled == NULL)
31✔
5123
      sldenabled = "true";
5124

5125
    for (int i = 0; i < numentries; i++) {
255✔
5126
      if (strcasecmp(names[i], "LAYER") == 0) {
224✔
5127
        pszLayer = values[i];
31✔
5128
      } else if (strcasecmp(names[i], "WIDTH") == 0)
193✔
5129
        nWidth = atoi(values[i]);
×
5130
      else if (strcasecmp(names[i], "HEIGHT") == 0)
193✔
5131
        nHeight = atoi(values[i]);
×
5132
      else if (strcasecmp(names[i], "FORMAT") == 0)
193✔
5133
        pszFormat = values[i];
31✔
5134
      else if (strcasecmp(names[i], "SCALE") == 0)
162✔
5135
        psScale = values[i];
×
5136

5137
      /* -------------------------------------------------------------------- */
5138
      /*      SLD support :                                                   */
5139
      /*        - check if the SLD parameter is there. it is supposed to      */
5140
      /*      refer a valid URL containing an SLD document.                   */
5141
      /*        - check the SLD_BODY parameter that should contain the SLD    */
5142
      /*      xml string.                                                     */
5143
      /* -------------------------------------------------------------------- */
5144
      else if (strcasecmp(names[i], "SLD") == 0 && values[i] &&
162✔
5145
               strlen(values[i]) > 0 && strcasecmp(sldenabled, "true") == 0)
×
5146
        msSLDApplySLDURL(map, values[i], -1, NULL, NULL);
×
5147
      else if (strcasecmp(names[i], "SLD_BODY") == 0 && values[i] &&
162✔
5148
               strlen(values[i]) > 0 && strcasecmp(sldenabled, "true") == 0)
3✔
5149
        msSLDApplySLD(map, values[i], -1, NULL, NULL);
1✔
5150
      else if (strcasecmp(names[i], "RULE") == 0)
161✔
5151
        psRule = values[i];
×
5152
      else if (strcasecmp(names[i], "STYLE") == 0)
161✔
5153
        pszStyle = values[i];
11✔
5154

5155
      /* -------------------------------------------------------------------- */
5156
      /*      SLD support:                                                    */
5157
      /*        - because the request parameter "sld_version" is required in  */
5158
      /*          in WMS 1.3.0, it will be set regardless of OGR support.     */
5159
      /* -------------------------------------------------------------------- */
5160
      else if (strcasecmp(names[i], "SLD_VERSION") == 0)
150✔
5161
        sld_version = values[i];
14✔
5162
    }
5163

5164
    if (!pszLayer) {
31✔
5165
      msSetErrorWithStatus(
×
5166
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5167
          "Mandatory LAYER parameter missing in GetLegendGraphic request.",
5168
          "msWMSGetLegendGraphic()");
5169
      return msWMSException(map, nVersion, "LayerNotDefined",
×
5170
                            wms_exception_format);
×
5171
    }
5172
    if (!pszFormat) {
31✔
5173
      msSetErrorWithStatus(
×
5174
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5175
          "Mandatory FORMAT parameter missing in GetLegendGraphic request.",
5176
          "msWMSGetLegendGraphic()");
5177
      return msWMSException(map, nVersion, "InvalidFormat",
×
5178
                            wms_exception_format);
×
5179
    }
5180

5181
    if (nVersion >= OWS_1_3_0 && sld_version == NULL) {
31✔
5182
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5183
                           "Missing required parameter SLD_VERSION",
5184
                           "GetLegendGraphic()");
5185
      return msWMSException(map, nVersion, "MissingParameterValue",
×
5186
                            wms_exception_format);
×
5187
    }
5188
    if (nVersion >= OWS_1_3_0 && strcasecmp(sld_version, "1.1.0") != 0) {
31✔
5189
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5190
                           "SLD_VERSION must be 1.1.0", "GetLegendGraphic()");
5191
      return msWMSException(map, nVersion, "InvalidParameterValue",
×
5192
                            wms_exception_format);
×
5193
    }
5194

5195
    char ***nestedGroups =
5196
        (char ***)msSmallCalloc(map->numlayers, sizeof(char **));
31✔
5197
    int *numNestedGroups = (int *)msSmallCalloc(map->numlayers, sizeof(int));
31✔
5198
    int *isUsedInNestedGroup =
5199
        (int *)msSmallCalloc(map->numlayers, sizeof(int));
31✔
5200
    msWMSPrepareNestedGroups(map, nVersion, nestedGroups, numNestedGroups,
31✔
5201
                             isUsedInNestedGroup);
5202

5203
    /* check if layer name is valid. we check for layer's and group's name */
5204
    /* as well as wms_layer_group names */
5205
    for (int i = 0; i < map->numlayers; i++) {
263✔
5206
      layerObj *lp = GET_LAYER(map, i);
232✔
5207
      if (((map->name && strcasecmp(map->name, pszLayer) == 0) ||
232✔
5208
           (lp->name && strcasecmp(lp->name, pszLayer) == 0) ||
211✔
5209
           (lp->group && strcasecmp(lp->group, pszLayer) == 0) ||
186✔
5210
           ((numNestedGroups[i] > 0) &&
273✔
5211
            (msStringInArray(pszLayer, nestedGroups[i],
93✔
5212
                             numNestedGroups[i])))) &&
295✔
5213
          (msIntegerInArray(lp->index, ows_request->enabled_layers,
63✔
5214
                            ows_request->numlayers))) {
5215
        nLayers++;
63✔
5216
        lp->status = MS_ON;
63✔
5217
        iLayerIndex = i;
5218
        if (GET_LAYER(map, i)->connectiontype == MS_WMS) {
63✔
5219
          /* we do not cascade a wms layer if it contains at least
5220
           * one class with the property name set */
5221
          wms_layer = MS_TRUE;
5222
          for (int j = 0; j < lp->numclasses; j++) {
2✔
5223
            if (lp->_class[j]->name != NULL &&
×
5224
                strlen(lp->_class[j]->name) > 0) {
×
5225
              wms_layer = MS_FALSE;
5226
              break;
5227
            }
5228
          }
5229
        }
5230
      } else
5231
        lp->status = MS_OFF;
169✔
5232
    }
5233

5234
    /* free the stuff used for nested layers */
5235
    for (int i = 0; i < map->numlayers; i++) {
263✔
5236
      if (numNestedGroups[i] > 0) {
232✔
5237
        msFreeCharArray(nestedGroups[i], numNestedGroups[i]);
110✔
5238
      }
5239
    }
5240
    free(nestedGroups);
31✔
5241
    free(numNestedGroups);
31✔
5242
    free(isUsedInNestedGroup);
31✔
5243

5244
    if (nLayers == 0) {
31✔
5245
      msSetErrorWithStatus(
×
5246
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5247
          "Invalid layer given in the LAYER parameter. A layer might be disabled for \
5248
this request. Check wms/ows_enable_request settings.",
5249
          "msWMSGetLegendGraphic()");
5250
      return msWMSException(map, nVersion, "LayerNotDefined",
×
5251
                            wms_exception_format);
×
5252
    }
5253

5254
    /* if SCALE was provided in request, calculate an extent and use a default
5255
     * width and height */
5256
    if (psScale != NULL) {
31✔
5257
      double scale, cellsize;
5258

5259
      scale = atof(psScale);
5260
      map->width = 600;
×
5261
      map->height = 600;
×
5262

5263
      cellsize = (scale / map->resolution) / msInchesPerUnit(map->units, 0.0);
×
5264

5265
      map->extent.maxx = cellsize * map->width / 2.0;
×
5266
      map->extent.maxy = cellsize * map->height / 2.0;
×
5267
      map->extent.minx = -map->extent.maxx;
×
5268
      map->extent.miny = -map->extent.maxy;
×
5269
    }
5270

5271
    /* It's a valid Cascading WMS GetLegendGraphic request */
5272
    if (wms_layer)
31✔
5273
      return msWMSLayerExecuteRequest(map, 1, 0, 0, 0, NULL,
2✔
5274
                                      WMS_GETLEGENDGRAPHIC);
2✔
5275

5276
    /*if STYLE is set, check if it is a valid style (valid = at least one
5277
    of the classes have a the group value equals to the style */
5278
    /*style is only validated when there is only one layer #3411*/
5279
    if (nLayers == 1 && pszStyle && strlen(pszStyle) > 0 &&
29✔
5280
        strcasecmp(pszStyle, "default") != 0) {
7✔
5281
      bool found = false;
5282
      for (int i = 0; i < GET_LAYER(map, iLayerIndex)->numclasses; i++) {
11✔
5283
        if (GET_LAYER(map, iLayerIndex)->_class[i]->group &&
11✔
5284
            strcasecmp(GET_LAYER(map, iLayerIndex)->_class[i]->group,
11✔
5285
                       pszStyle) == 0) {
5286
          found = true;
5287
          break;
5288
        }
5289
      }
5290

5291
      if (!found) {
7✔
5292
        msSetErrorWithStatus(
×
5293
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5294
            "style used in the STYLE parameter is not defined on the layer.",
5295
            "msWMSGetLegendGraphic()");
5296
        return msWMSException(map, nVersion, "StyleNotDefined",
×
5297
                              wms_exception_format);
×
5298
      } else {
5299
        msFree(GET_LAYER(map, iLayerIndex)->classgroup);
7✔
5300
        GET_LAYER(map, iLayerIndex)->classgroup = msStrdup(pszStyle);
7✔
5301
      }
5302
    }
5303
  } else {
5304
    /* extract the parameters we need */
5305
    for (int i = 0; i < numentries; i++) {
182✔
5306
      if (strcasecmp(names[i], "FORMAT") == 0)
170✔
5307
        pszFormat = values[i];
12✔
5308
      else if (strcasecmp(names[i], "RULE") == 0)
158✔
5309
        psRule = values[i];
×
5310
    }
5311
  }
5312
  /* validate format */
5313

5314
  /*check to see if a predefined list is given*/
5315
  format_list = msOWSLookupMetadata(&(map->web.metadata), "M",
41✔
5316
                                    "getlegendgraphic_formatlist");
5317
  if (format_list) {
41✔
5318
    psFormat = msOwsIsOutputFormatValid(map, pszFormat, &(map->web.metadata),
×
5319
                                        "M", "getlegendgraphic_formatlist");
5320
    if (psFormat == NULL) {
×
5321
      msSetErrorWithStatus(MS_IMGERR, MS_HTTP_400_BAD_REQUEST,
×
5322
                           "Unsupported output format (%s).",
5323
                           "msWMSGetLegendGraphic()", pszFormat);
5324
      return msWMSException(map, nVersion, "InvalidFormat",
×
5325
                            wms_exception_format);
×
5326
    }
5327
  } else {
5328
    psFormat = msSelectOutputFormat(map, pszFormat);
41✔
5329
    if (psFormat == NULL || !MS_RENDERER_PLUGIN(psFormat))
41✔
5330
    /* msDrawLegend and msCreateLegendIcon both switch the alpha channel to gd
5331
     ** after creation, so they can be called here without going through
5332
     ** the msAlphaGD2AGG functions */
5333
    {
5334
      msSetErrorWithStatus(MS_IMGERR, MS_HTTP_400_BAD_REQUEST,
×
5335
                           "Unsupported output format (%s).",
5336
                           "msWMSGetLegendGraphic()", pszFormat);
5337
      return msWMSException(map, nVersion, "InvalidFormat",
×
5338
                            wms_exception_format);
×
5339
    }
5340
  }
5341
  msApplyOutputFormat(&(map->outputformat), psFormat, MS_NOOVERRIDE);
41✔
5342

5343
  if (psRule == NULL || nLayers > 1) {
41✔
5344
    if (psScale != NULL) {
41✔
5345
      /* Scale-dependent legend. map->scaledenom will be calculated in
5346
       * msDrawLegend */
5347
      img = msDrawLegend(map, MS_FALSE, NULL);
×
5348
    } else {
5349
      /* Scale-independent legend */
5350
      img = msDrawLegend(map, MS_TRUE, hittest);
41✔
5351
    }
5352
  } else {
5353
    /* RULE was specified. Get the class corresponding to the RULE */
5354
    /* (RULE = class->name) */
5355
    /* TBT FIXME? also check the map->scaledenom if multiple scale-dependant
5356
     * classes with same name */
5357

5358
    layerObj *lp = GET_LAYER(map, iLayerIndex);
×
5359
    int i;
5360
    for (i = 0; i < lp->numclasses; i++) {
×
5361
      if (lp->classgroup &&
×
5362
          (lp->_class[i]->group == NULL ||
×
5363
           strcasecmp(lp->_class[i]->group, lp->classgroup) != 0))
×
5364
        continue;
×
5365

5366
      if (lp->_class[i]->name && strlen(lp->_class[i]->name) > 0 &&
×
5367
          strcasecmp(lp->_class[i]->name, psRule) == 0)
×
5368
        break;
5369
    }
5370
    if (i < lp->numclasses) {
×
5371
      /* set the map legend parameters */
5372
      if (nWidth < 0) {
×
5373
        if (map->legend.keysizex > 0)
×
5374
          nWidth = map->legend.keysizex;
5375
        else
5376
          nWidth = 20; /* default values : this in not defined in the specs */
5377
      }
5378
      if (nHeight < 0) {
×
5379
        if (map->legend.keysizey > 0)
×
5380
          nHeight = map->legend.keysizey;
5381
        else
5382
          nHeight = 20;
5383
      }
5384

5385
      if (psScale != NULL) {
×
5386
        /* Scale-dependent legend. calculate map->scaledenom */
5387
        map->cellsize = msAdjustExtent(&(map->extent), map->width, map->height);
×
5388
        msCalculateScale(map->extent, map->units, map->width, map->height,
×
5389
                         map->resolution, &map->scaledenom);
5390
        img = msCreateLegendIcon(map, lp, lp->_class[i], nWidth, nHeight,
×
5391
                                 MS_FALSE);
5392
      } else {
5393
        /* Scale-independent legend */
5394
        img = msCreateLegendIcon(map, lp, lp->_class[i], nWidth, nHeight,
×
5395
                                 MS_TRUE);
5396
      }
5397
    }
5398
    if (img == NULL) {
×
5399
      msSetErrorWithStatus(MS_IMGERR, MS_HTTP_400_BAD_REQUEST,
×
5400
                           "Unavailable RULE (%s).", "msWMSGetLegendGraphic()",
5401
                           psRule);
5402
      return msWMSException(map, nVersion, "InvalidRule", wms_exception_format);
×
5403
    }
5404
  }
5405

5406
  if (img == NULL)
41✔
5407
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5408

5409
  msIO_setHeader("Content-Type", "%s", MS_IMAGE_MIME_TYPE(map->outputformat));
82✔
5410
  msIO_sendHeaders();
41✔
5411
  if (msSaveImage(map, img, NULL) != MS_SUCCESS)
41✔
5412
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5413

5414
  msFreeImage(img);
41✔
5415

5416
  return (MS_SUCCESS);
41✔
5417
}
5418

5419
/*
5420
** msWMSGetContentDependentLegend()
5421
*/
5422
static int msWMSGetContentDependentLegend(mapObj *map, int nVersion,
12✔
5423
                                          char **names, char **values,
5424
                                          int numentries,
5425
                                          const char *wms_exception_format,
5426
                                          owsRequestObj *ows_request) {
5427

5428
  /* turn off layer if WMS GetMap is not enabled */
5429
  for (int i = 0; i < map->numlayers; i++)
60✔
5430
    if (!msIntegerInArray(GET_LAYER(map, i)->index, ows_request->enabled_layers,
48✔
5431
                          ows_request->numlayers))
5432
      GET_LAYER(map, i)->status = MS_OFF;
×
5433

5434
  map_hittest hittest;
5435
  initMapHitTests(map, &hittest);
12✔
5436
  int status = msHitTestMap(map, &hittest);
12✔
5437
  if (status == MS_SUCCESS) {
12✔
5438
    status = msWMSLegendGraphic(map, nVersion, names, values, numentries,
12✔
5439
                                wms_exception_format, ows_request, &hittest);
5440
  }
5441
  freeMapHitTests(map, &hittest);
12✔
5442
  if (status != MS_SUCCESS) {
12✔
5443
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5444
  } else {
5445
    return MS_SUCCESS;
5446
  }
5447
}
5448

5449
/*
5450
** msWMSGetStyles() : return an SLD document for all layers that
5451
** have a status set to on or default.
5452
*/
5453
static int msWMSGetStyles(mapObj *map, int nVersion, char **names,
60✔
5454
                          char **values, int numentries,
5455
                          const char *wms_exception_format)
5456

5457
{
5458
  bool validlayer = false;
5459

5460
  char ***nestedGroups =
5461
      (char ***)msSmallCalloc(map->numlayers, sizeof(char **));
60✔
5462
  int *numNestedGroups = (int *)msSmallCalloc(map->numlayers, sizeof(int));
60✔
5463
  int *isUsedInNestedGroup = (int *)msSmallCalloc(map->numlayers, sizeof(int));
60✔
5464
  msWMSPrepareNestedGroups(map, nVersion, nestedGroups, numNestedGroups,
60✔
5465
                           isUsedInNestedGroup);
5466

5467
  const char *sldenabled =
5468
      msOWSLookupMetadata(&(map->web.metadata), "MO", "sld_enabled");
60✔
5469
  if (sldenabled == NULL)
60✔
5470
    sldenabled = "true";
5471

5472
  for (int i = 0; i < numentries; i++) {
367✔
5473
    /* getMap parameters */
5474
    if (strcasecmp(names[i], "LAYERS") == 0) {
307✔
5475
      const auto wmslayers = msStringSplit(values[i], ',');
60✔
5476
      if (wmslayers.empty()) {
60✔
5477
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5478
                             "At least one layer name required in LAYERS.",
5479
                             "msWMSGetStyles()");
5480
        return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5481
      }
5482
      for (int j = 0; j < map->numlayers; j++)
1,140✔
5483
        GET_LAYER(map, j)->status = MS_OFF;
1,080✔
5484

5485
      for (int k = 0; k < static_cast<int>(wmslayers.size()); k++) {
135✔
5486
        const auto &wmslayer = wmslayers[k];
75✔
5487
        for (int j = 0; j < map->numlayers; j++) {
1,199✔
5488
          if ((map->name && strcasecmp(map->name, wmslayer.c_str()) == 0) ||
1,124✔
5489
              (GET_LAYER(map, j)->name &&
1,103✔
5490
               strcasecmp(GET_LAYER(map, j)->name, wmslayer.c_str()) == 0) ||
1,103✔
5491
              (GET_LAYER(map, j)->group &&
1,033✔
5492
               strcasecmp(GET_LAYER(map, j)->group, wmslayer.c_str()) == 0) ||
1,135✔
5493
              ((numNestedGroups[j] > 0) &&
1,072✔
5494
               msStringInArray(wmslayer.c_str(), nestedGroups[j],
43✔
5495
                               numNestedGroups[j]))) {
5496
            GET_LAYER(map, j)->status = MS_ON;
106✔
5497
            validlayer = true;
5498
          }
5499
        }
5500
      }
5501
    }
60✔
5502

5503
    else if (strcasecmp(names[i], "SLD") == 0 && values[i] &&
247✔
5504
             strlen(values[i]) > 0 && strcasecmp(sldenabled, "true") == 0) {
×
5505
      msSLDApplySLDURL(map, values[i], -1, NULL, NULL);
×
5506
    }
5507

5508
    else if (strcasecmp(names[i], "SLD_BODY") == 0 && values[i] &&
247✔
5509
             strlen(values[i]) > 0 && strcasecmp(sldenabled, "true") == 0) {
6✔
5510
      msSLDApplySLD(map, values[i], -1, NULL, NULL);
6✔
5511
    }
5512
  }
5513

5514
  /* free the stuff used for nested layers */
5515
  for (int i = 0; i < map->numlayers; i++) {
1,140✔
5516
    if (numNestedGroups[i] > 0) {
1,080✔
5517
      msFreeCharArray(nestedGroups[i], numNestedGroups[i]);
55✔
5518
    }
5519
  }
5520
  free(nestedGroups);
60✔
5521
  free(numNestedGroups);
60✔
5522
  free(isUsedInNestedGroup);
60✔
5523

5524
  /* validate all layers given. If an invalid layer is sent, return an
5525
   * exception. */
5526
  if (!validlayer) {
60✔
5527
    msSetErrorWithStatus(
×
5528
        MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5529
        "Invalid layer(s) given in the LAYERS parameter. A layer might be disabled for \
5530
this request. Check wms/ows_enable_request settings.",
5531
        "msWMSGetStyles()");
5532
    return msWMSException(map, nVersion, "LayerNotDefined",
×
5533
                          wms_exception_format);
×
5534
  }
5535

5536
  char *sld = NULL;
5537
  if (nVersion <= OWS_1_1_1) {
60✔
5538
    msIO_setHeader("Content-Type",
4✔
5539
                   "application/vnd.ogc.sld+xml; charset=UTF-8");
5540
    msIO_sendHeaders();
4✔
5541
    sld = msSLDGenerateSLD(map, -1, "1.0.0");
4✔
5542
  } else {
5543
    /*for wms 1.3.0 generate a 1.1 sld*/
5544
    msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
56✔
5545
    msIO_sendHeaders();
56✔
5546
    sld = msSLDGenerateSLD(map, -1, "1.1.0");
56✔
5547
  }
5548
  if (sld) {
60✔
5549
    msIO_printf("%s\n", sld);
60✔
5550
    free(sld);
60✔
5551
  }
5552

5553
  return (MS_SUCCESS);
5554
}
5555

5556
int msWMSGetSchemaExtension(mapObj *map) {
2✔
5557
  char *schemalocation = msEncodeHTMLEntities(msOWSGetSchemasLocation(map));
2✔
5558

5559
  msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
2✔
5560
  msIO_sendHeaders();
2✔
5561

5562
  msIO_printf("<?xml version='1.0' encoding=\"UTF-8\"?>\n");
2✔
5563
  msIO_printf("<schema xmlns=\"http://www.w3.org/2001/XMLSchema\" "
2✔
5564
              "xmlns:wms=\"http://www.opengis.net/wms\" "
5565
              "xmlns:ms=\"http://mapserver.gis.umn.edu/mapserver\" "
5566
              "targetNamespace=\"http://mapserver.gis.umn.edu/mapserver\" "
5567
              "elementFormDefault=\"qualified\" version=\"1.0.0\">\n");
5568
  msIO_printf("  <import namespace=\"http://www.opengis.net/wms\" "
2✔
5569
              "schemaLocation=\"%s/wms/1.3.0/capabilities_1_3_0.xsd\"/>\n",
5570
              schemalocation);
5571
  msIO_printf("  <element name=\"GetStyles\" type=\"wms:OperationType\" "
2✔
5572
              "substitutionGroup=\"wms:_ExtendedOperation\"/>\n");
5573
  msIO_printf("</schema>");
2✔
5574

5575
  free(schemalocation);
2✔
5576

5577
  return (MS_SUCCESS);
2✔
5578
}
5579

5580
#endif /* USE_WMS_SVR */
5581

5582
/*
5583
** msWMSDispatch() is the entry point for WMS requests.
5584
** - If this is a valid request then it is processed and MS_SUCCESS is returned
5585
**   on success, or MS_FAILURE on failure.
5586
** - If this does not appear to be a valid WMS request then MS_DONE
5587
**   is returned and MapServer is expected to process this as a regular
5588
**   MapServer request.
5589
*/
5590
int msWMSDispatch(mapObj *map, cgiRequestObj *req, owsRequestObj *ows_request,
694✔
5591
                  int force_wms_mode) {
5592
#ifdef USE_WMS_SVR
5593
  int nVersion = OWS_VERSION_NOTSET;
5594
  const char *version = NULL, *request = NULL, *service = NULL, *format = NULL,
5595
             *updatesequence = NULL, *language = NULL;
5596
  const char *wms_exception_format = NULL;
5597

5598
  /*
5599
  ** Process Params common to all requests
5600
  */
5601
  /* VERSION (WMTVER in 1.0.0) and REQUEST must be present in a valid request */
5602
  for (int i = 0; i < req->NumParams; i++) {
7,863✔
5603
    if (strcasecmp(req->ParamNames[i], "VERSION") == 0)
7,169✔
5604
      version = req->ParamValues[i];
683✔
5605
    else if (strcasecmp(req->ParamNames[i], "WMTVER") == 0 && version == NULL)
6,486✔
5606
      version = req->ParamValues[i];
×
5607
    else if (strcasecmp(req->ParamNames[i], "UPDATESEQUENCE") == 0)
6,486✔
5608
      updatesequence = req->ParamValues[i];
6✔
5609
    else if (strcasecmp(req->ParamNames[i], "REQUEST") == 0)
6,480✔
5610
      request = req->ParamValues[i];
685✔
5611
    else if (strcasecmp(req->ParamNames[i], "EXCEPTIONS") == 0)
5,795✔
5612
      wms_exception_format = req->ParamValues[i];
52✔
5613
    else if (strcasecmp(req->ParamNames[i], "SERVICE") == 0)
5,743✔
5614
      service = req->ParamValues[i];
692✔
5615
    else if (strcasecmp(req->ParamNames[i], "FORMAT") == 0)
5,051✔
5616
      format = req->ParamValues[i];
532✔
5617
    else if (strcasecmp(req->ParamNames[i], "LANGUAGE") == 0 &&
4,529✔
5618
             msOWSLookupMetadata(&(map->web.metadata), "MO",
10✔
5619
                                 "inspire_capabilities"))
5620
      language = req->ParamValues[i];
10✔
5621
  }
5622

5623
  /* If SERVICE is specified then it MUST be "WMS" */
5624
  if (service != NULL && strcasecmp(service, "WMS") != 0)
694✔
5625
    return MS_DONE; /* Not a WMS request */
5626

5627
  nVersion = msOWSParseVersionString(version);
694✔
5628
  if (nVersion == OWS_VERSION_BADFORMAT) {
694✔
5629
    /* Invalid version format. msSetError() has been called by
5630
     * msOWSParseVersionString() and we return the error as an exception
5631
     */
5632
    return msWMSException(map, OWS_VERSION_NOTSET, NULL, wms_exception_format);
8✔
5633
  }
5634

5635
  /*
5636
  ** GetCapbilities request needs the service parameter defined as WMS:
5637
  see section 7.1.3.2 wms 1.1.1 specs for decsription.
5638
  */
5639
  if (request && service == NULL &&
686✔
5640
      (strcasecmp(request, "capabilities") == 0 ||
3✔
5641
       strcasecmp(request, "GetCapabilities") == 0) &&
3✔
5642
      (nVersion >= OWS_1_0_7 || nVersion == OWS_VERSION_NOTSET)) {
×
5643
    if (force_wms_mode) {
×
5644
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5645
                           "Required SERVICE parameter missing.",
5646
                           "msWMSDispatch");
5647
      return msWMSException(map, nVersion, "ServiceNotDefined",
×
5648
                            wms_exception_format);
×
5649
    } else
5650
      return MS_DONE;
5651
  }
5652

5653
  /*
5654
  ** Dispatch request... we should probably do some validation on VERSION here
5655
  ** vs the versions we actually support.
5656
  */
5657
  if (request && (strcasecmp(request, "capabilities") == 0 ||
686✔
5658
                  strcasecmp(request, "GetCapabilities") == 0)) {
685✔
5659
    const char *enable_request;
5660
    int globally_enabled, disabled = MS_FALSE;
63✔
5661

5662
    if (nVersion == OWS_VERSION_NOTSET) {
63✔
5663
      version = msOWSLookupMetadata(&(map->web.metadata), "M",
10✔
5664
                                    "getcapabilities_version");
5665
      if (version)
10✔
5666
        nVersion = msOWSParseVersionString(version);
×
5667
      else
5668
        nVersion =
5669
            OWS_1_3_0; /* VERSION is optional with getCapabilities only */
5670
    }
5671

5672
    if (msOWSMakeAllLayersUnique(map) != MS_SUCCESS)
63✔
5673
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5674

5675
    msOWSRequestLayersEnabled(map, "M", "GetCapabilities", ows_request);
63✔
5676

5677
    enable_request =
5678
        msOWSLookupMetadata(&map->web.metadata, "OM", "enable_request");
63✔
5679
    globally_enabled =
5680
        msOWSParseRequestMetadata(enable_request, "GetCapabilities", &disabled);
63✔
5681

5682
    if (ows_request->numlayers == 0 && !globally_enabled) {
63✔
5683
      msSetErrorWithStatus(
2✔
5684
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5685
          "WMS request not enabled. Check wms/ows_enable_request settings.",
5686
          "msWMSGetCapabilities()");
5687
      return msWMSException(map, nVersion, NULL, wms_exception_format);
2✔
5688
    }
5689
    msAcquireLock(TLOCK_WxS);
61✔
5690
    const int status =
5691
        msWMSGetCapabilities(map, nVersion, req, ows_request, updatesequence,
61✔
5692
                             wms_exception_format, language);
5693
    msReleaseLock(TLOCK_WxS);
61✔
5694
    return status;
61✔
5695
  } else if (request && (strcasecmp(request, "context") == 0 ||
622✔
5696
                         strcasecmp(request, "GetContext") == 0)) {
622✔
5697
    /* Return a context document with all layers in this mapfile
5698
     * This is not a standard WMS request.
5699
     * __TODO__ The real implementation should actually return only context
5700
     * info for selected layers in the LAYERS parameter.
5701
     */
5702
    const char *getcontext_enabled;
5703
    getcontext_enabled =
5704
        msOWSLookupMetadata(&(map->web.metadata), "MO", "getcontext_enabled");
×
5705

5706
    if (nVersion != OWS_VERSION_NOTSET) {
×
5707
      /* VERSION, if specified, is Map Context version, not WMS version */
5708
      /* Pass it via wms_context_version metadata */
5709
      char szVersion[OWS_VERSION_MAXLEN];
5710
      msInsertHashTable(&(map->web.metadata), "wms_context_version",
×
5711
                        msOWSGetVersionString(nVersion, szVersion));
5712
    }
5713
    /* Now set version to 1.1.1 for error handling purposes */
5714
    nVersion = OWS_1_1_1;
5715

5716
    if (getcontext_enabled == NULL || atoi(getcontext_enabled) == 0) {
×
5717
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5718
                           "GetContext not enabled on this server.",
5719
                           "msWMSDispatch()");
5720
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5721
    }
5722

5723
    if (msOWSMakeAllLayersUnique(map) != MS_SUCCESS)
×
5724
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5725

5726
    msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
×
5727
    msIO_sendHeaders();
×
5728

5729
    if (msWriteMapContext(map, stdout) != MS_SUCCESS)
×
5730
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5731
    /* Request completed */
5732
    return MS_SUCCESS;
5733
  } else if (request && strcasecmp(request, "GetMap") == 0 && format &&
622✔
5734
             strcasecmp(format, "image/txt") == 0) {
452✔
5735
    /* Until someone adds full support for ASCII graphics this should do. ;) */
5736
    msIO_setHeader("Content-Type", "text/plain; charset=UTF-8");
×
5737
    msIO_sendHeaders();
×
5738
    msIO_printf(".\n               ,,ggddY\"\"\"Ybbgg,,\n          ,agd888b,_ "
×
5739
                "\"Y8, ___'\"\"Ybga,\n       ,gdP\"\"88888888baa,.\"\"8b    \""
5740
                "888g,\n     ,dP\"     ]888888888P'  \"Y     '888Yb,\n   ,dP\""
5741
                "      ,88888888P\"  db,       \"8P\"\"Yb,\n  ,8\"       ,8888"
5742
                "88888b, d8888a           \"8,\n ,8'        d88888888888,88P\""
5743
                "' a,          '8,\n,8'         88888888888888PP\"  \"\"      "
5744
                "     '8,\nd'          I88888888888P\"                   'b\n8"
5745
                "           '8\"88P\"\"Y8P'                      8\n8         "
5746
                "   Y 8[  _ \"                        8\n8              \"Y8d8"
5747
                "b  \"Y a                   8\n8                 '\"\"8d,   __"
5748
                "                 8\nY,                    '\"8bd888b,        "
5749
                "     ,P\n'8,                     ,d8888888baaa       ,8'\n '8"
5750
                ",                    888888888888'      ,8'\n  '8a           "
5751
                "        \"8888888888I      a8'\n   'Yba                  'Y88"
5752
                "88888P'    adP'\n     \"Yba                 '888888P'   adY\""
5753
                "\n       '\"Yba,             d8888P\" ,adP\"' \n          '\""
5754
                "Y8baa,      ,d888P,ad8P\"' \n               ''\"\"YYba8888P\""
5755
                "\"''\n");
5756
    return MS_SUCCESS;
×
5757
  }
5758

5759
  /* If SERVICE, VERSION and REQUEST not included than this isn't a WMS req*/
5760
  if (service == NULL && nVersion == OWS_VERSION_NOTSET && request == NULL)
623✔
5761
    return MS_DONE; /* Not a WMS request */
5762

5763
  /* VERSION *and* REQUEST required by both getMap and getFeatureInfo */
5764
  if (nVersion == OWS_VERSION_NOTSET) {
623✔
5765
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
5766
                         "Incomplete WMS request: VERSION parameter missing",
5767
                         "msWMSDispatch()");
5768
    return msWMSException(map, OWS_VERSION_NOTSET, NULL, wms_exception_format);
1✔
5769
  }
5770

5771
  /*check if the version is one of the supported vcersions*/
5772
  if (nVersion != OWS_1_0_0 && nVersion != OWS_1_0_6 && nVersion != OWS_1_0_7 &&
622✔
5773
      nVersion != OWS_1_1_0 && nVersion != OWS_1_1_1 && nVersion != OWS_1_3_0) {
622✔
5774
    msSetErrorWithStatus(
×
5775
        MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5776
        "Invalid WMS version: VERSION %s is not supported. Supported "
5777
        "versions are 1.0.0, 1.0.6, 1.0.7, 1.1.0, 1.1.1, 1.3.0",
5778
        "msWMSDispatch()", version);
5779
    return msWMSException(map, OWS_VERSION_NOTSET, NULL, wms_exception_format);
×
5780
  }
5781

5782
  if (request == NULL) {
622✔
5783
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5784
                         "Incomplete WMS request: REQUEST parameter missing",
5785
                         "msWMSDispatch()");
5786
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5787
  }
5788

5789
  /* hack !? The function can return MS_DONE ... be sure it's a wms request
5790
   * before checking the enabled layers */
5791
  if ((strcasecmp(request, "GetStyles") == 0) ||
622✔
5792
      (strcasecmp(request, "GetLegendGraphic") == 0) ||
562✔
5793
      (strcasecmp(request, "GetSchemaExtension") == 0) ||
518✔
5794
      (strcasecmp(request, "map") == 0 || strcasecmp(request, "GetMap") == 0) ||
516✔
5795
      (strcasecmp(request, "feature_info") == 0 ||
63✔
5796
       strcasecmp(request, "GetFeatureInfo") == 0) ||
63✔
5797
      (strcasecmp(request, "DescribeLayer") == 0)) {
21✔
5798
    const char *request_tmp;
5799
    if (strcasecmp(request, "map") == 0)
622✔
5800
      request_tmp = "GetMap";
5801
    else if (strcasecmp(request, "feature_info") == 0)
622✔
5802
      request_tmp = "GetFeatureInfo";
5803
    else
5804
      request_tmp = request;
5805

5806
    msOWSRequestLayersEnabled(map, "M", request_tmp, ows_request);
622✔
5807
    if (ows_request->numlayers == 0) {
622✔
5808
      msSetErrorWithStatus(
1✔
5809
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5810
          "WMS request not enabled. Check wms/ows_enable_request settings.",
5811
          "msWMSDispatch()");
5812
      return msWMSException(map, nVersion, NULL, wms_exception_format);
1✔
5813
    }
5814
  }
5815

5816
  if (msOWSMakeAllLayersUnique(map) != MS_SUCCESS)
621✔
5817
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5818

5819
  bool isContentDependentLegend = false;
5820
  if (strcasecmp(request, "GetLegendGraphic") == 0) {
621✔
5821
    /*
5822
     * check for a BBOX in the request, in that case we have a content-dependant
5823
     * legend request, and should be following the GetMap path a bit more
5824
     */
5825
    bool found = false;
5826
    for (int i = 0; i < req->NumParams; i++) {
402✔
5827
      if (strcasecmp(req->ParamNames[i], "BBOX") == 0) {
371✔
5828
        if (req->ParamValues[i] && *req->ParamValues[i]) {
12✔
5829
          found = true;
5830
          break;
5831
        }
5832
      }
5833
    }
5834
    if (found) {
43✔
5835
      isContentDependentLegend = true;
5836
      /* getLegendGraphic uses LAYER= , we need to create a LAYERS= value that
5837
       * is identical we'll suppose that the client is conformat and hasn't
5838
       * included a LAYERS= parameter in its request */
5839
      for (int i = 0; i < req->NumParams; i++) {
182✔
5840
        if (strcasecmp(req->ParamNames[i], "LAYER") == 0) {
170✔
5841
          req->ParamNames[req->NumParams] = msStrdup("LAYERS");
9✔
5842
          req->ParamValues[req->NumParams] = msStrdup(req->ParamValues[i]);
9✔
5843
          req->NumParams++;
9✔
5844
        }
5845
      }
5846
    } else {
5847
      return msWMSLegendGraphic(map, nVersion, req->ParamNames,
31✔
5848
                                req->ParamValues, req->NumParams,
5849
                                wms_exception_format, ows_request, NULL);
31✔
5850
    }
5851
  }
5852

5853
  if (strcasecmp(request, "GetStyles") == 0)
590✔
5854
    return msWMSGetStyles(map, nVersion, req->ParamNames, req->ParamValues,
60✔
5855
                          req->NumParams, wms_exception_format);
60✔
5856

5857
  else if (request && strcasecmp(request, "GetSchemaExtension") == 0)
530✔
5858
    return msWMSGetSchemaExtension(map);
2✔
5859

5860
  /* getMap parameters are used by both getMap, getFeatureInfo, and content
5861
   * dependent legendgraphics */
5862
  if (strcasecmp(request, "map") == 0 || strcasecmp(request, "GetMap") == 0 ||
528✔
5863
      strcasecmp(request, "feature_info") == 0 ||
75✔
5864
      strcasecmp(request, "GetFeatureInfo") == 0 ||
75✔
5865
      strcasecmp(request, "DescribeLayer") == 0 || isContentDependentLegend) {
33✔
5866

5867
    const int status = msWMSLoadGetMapParams(
528✔
5868
        map, nVersion, req->ParamNames, req->ParamValues, req->NumParams,
5869
        wms_exception_format, request, ows_request);
5870
    if (status != MS_SUCCESS)
528✔
5871
      return status;
5872
  }
5873

5874
  /* This function owns validated_language, so remember to free it later*/
5875
  char *validated_language = msOWSGetLanguageFromList(map, "MO", language);
473✔
5876
  if (validated_language != NULL) {
473✔
5877
    msMapSetLanguageSpecificConnection(map, validated_language);
47✔
5878
  }
5879
  msFree(validated_language);
473✔
5880

5881
  if (strcasecmp(request, "map") == 0 || strcasecmp(request, "GetMap") == 0)
473✔
5882
    return msWMSGetMap(map, nVersion, req->ParamNames, req->ParamValues,
400✔
5883
                       req->NumParams, wms_exception_format, ows_request);
400✔
5884
  else if (strcasecmp(request, "feature_info") == 0 ||
73✔
5885
           strcasecmp(request, "GetFeatureInfo") == 0)
73✔
5886
    return msWMSFeatureInfo(map, nVersion, req->ParamNames, req->ParamValues,
42✔
5887
                            req->NumParams, wms_exception_format, ows_request);
42✔
5888
  else if (strcasecmp(request, "DescribeLayer") == 0) {
31✔
5889
    return msWMSDescribeLayer(map, nVersion, req->ParamNames, req->ParamValues,
19✔
5890
                              req->NumParams, wms_exception_format);
19✔
5891
  } else if (isContentDependentLegend) {
12✔
5892
    return msWMSGetContentDependentLegend(map, nVersion, req->ParamNames,
12✔
5893
                                          req->ParamValues, req->NumParams,
5894
                                          wms_exception_format, ows_request);
12✔
5895
  }
5896

5897
  /* Hummmm... incomplete or unsupported WMS request */
5898
  if (service != NULL && strcasecmp(service, "WMS") == 0) {
×
5899
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5900
                         "Incomplete or unsupported WMS request",
5901
                         "msWMSDispatch()");
5902
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5903
  } else
5904
    return MS_DONE; /* Not a WMS request */
5905
#else
5906
  msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5907
                       "WMS server support is not available.",
5908
                       "msWMSDispatch()");
5909
  return (MS_FAILURE);
5910
#endif
5911
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc