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

geographika / mapserver / 17709373190

14 Sep 2025 09:32AM UTC coverage: 41.466% (+0.09%) from 41.375%
17709373190

push

github

geographika
Add index templates

62086 of 149729 relevant lines covered (41.47%)

25036.08 hits per line

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

83.76
/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,
74✔
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)
74✔
77
    nVersion = OWS_1_3_0;
78

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

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

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

98
  if (strcasecmp(wms_exception_format, "INIMAGE") == 0 ||
74✔
99
      strcasecmp(wms_exception_format, "BLANK") == 0 ||
71✔
100
      strcasecmp(wms_exception_format, "application/vnd.ogc.se_inimage") == 0 ||
69✔
101
      strcasecmp(wms_exception_format, "application/vnd.ogc.se_blank") == 0) {
69✔
102
    int blank = 0;
103

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

109
    msWriteErrorImage(map, NULL, blank);
5✔
110

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

116
    msIO_printf("<WMTException version=\"1.0.0\">\n");
×
117
    msWriteErrorXML(stdout);
×
118
    msIO_printf("</WMTException>\n");
×
119
  } else /* XML error, the default:application/vnd.ogc.se_xml (1.1.0 and later)
120
          */
121
  {
122
    if (nVersion <= OWS_1_1_0) {
69✔
123
      /* In V1.1.0 and later, we have OGC-specific MIME types */
124
      /* we cannot return anything else than application/vnd.ogc.se_xml here. */
125
      msIO_setHeader("Content-Type",
29✔
126
                     "application/vnd.ogc.se_xml; charset=UTF-8");
127
      msIO_sendHeaders();
29✔
128

129
      msIO_printf(
29✔
130
          "<?xml version='1.0' encoding=\"UTF-8\" standalone=\"no\" ?>\n");
131

132
      msIO_printf("<!DOCTYPE ServiceExceptionReport SYSTEM "
29✔
133
                  "\"%s/wms/1.1.0/exception_1_1_0.dtd\">\n",
134
                  schemalocation);
135

136
      msIO_printf("<ServiceExceptionReport version=\"1.1.0\">\n");
29✔
137
    } else if (nVersion <= OWS_1_1_1) { /* 1.1.1 */
40✔
138
      msIO_setHeader("Content-Type",
22✔
139
                     "application/vnd.ogc.se_xml; charset=UTF-8");
140
      msIO_sendHeaders();
22✔
141

142
      msIO_printf(
22✔
143
          "<?xml version='1.0' encoding=\"UTF-8\" standalone=\"no\" ?>\n");
144
      msIO_printf("<!DOCTYPE ServiceExceptionReport SYSTEM "
22✔
145
                  "\"%s/wms/1.1.1/exception_1_1_1.dtd\">\n",
146
                  schemalocation);
147
      msIO_printf("<ServiceExceptionReport version=\"1.1.1\">\n");
22✔
148
    } else { /*1.3.0*/
149
      if (strcasecmp(wms_exception_format, "application/vnd.ogc.se_xml") == 0) {
18✔
150
        msIO_setHeader("Content-Type",
×
151
                       "application/vnd.ogc.se_xml; charset=UTF-8");
152
      } else {
153
        msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
18✔
154
      }
155
      msIO_sendHeaders();
18✔
156

157
      msIO_printf(
18✔
158
          "<?xml version='1.0' encoding=\"UTF-8\" standalone=\"no\" ?>\n");
159
      msIO_printf("<ServiceExceptionReport version=\"1.3.0\" "
18✔
160
                  "xmlns=\"http://www.opengis.net/ogc\" "
161
                  "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
162
                  "xsi:schemaLocation=\"http://www.opengis.net/ogc "
163
                  "%s/wms/1.3.0/exceptions_1_3_0.xsd\">\n",
164
                  schemalocation);
165
    }
166

167
    if (exception_code)
69✔
168
      msIO_printf("<ServiceException code=\"%s\">\n", exception_code);
50✔
169
    else
170
      msIO_printf("<ServiceException>\n");
19✔
171
    msWriteErrorXML(stdout);
69✔
172
    msIO_printf("</ServiceException>\n");
69✔
173
    msIO_printf("</ServiceExceptionReport>\n");
69✔
174
  }
175
  free(schemalocation);
74✔
176

177
  return MS_FAILURE; /* so that we can call 'return msWMSException();' anywhere
74✔
178
                      */
179
}
180

181
static bool msWMSSetTimePattern(const char *timepatternstring,
52✔
182
                                const char *timestring, bool checkonly) {
183
  if (timepatternstring && timestring) {
52✔
184
    /* parse the time parameter to extract a distinct time. */
185
    /* time value can be discrete times (eg 2004-09-21), */
186
    /* multiple times (2004-09-21, 2004-09-22, ...) */
187
    /* and range(s) (2004-09-21/2004-09-25, 2004-09-27/2004-09-29) */
188
    const auto atimes = msStringSplit(timestring, ',');
52✔
189

190
    /* get the pattern to use */
191
    if (!atimes.empty()) {
52✔
192
      auto patterns = msStringSplit(timepatternstring, ',');
52✔
193
      for (auto &pattern : patterns) {
192✔
194
        msStringTrimBlanks(pattern);
140✔
195
        msStringTrimLeft(pattern);
140✔
196
      }
197

198
      for (const auto &atime : atimes) {
130✔
199
        const auto ranges = msStringSplit(atime.c_str(), '/');
82✔
200
        for (const auto &range : ranges) {
201✔
201
          bool match = false;
202
          for (const auto &pattern : patterns) {
135✔
203
            if (!pattern.empty()) {
131✔
204
              if (msTimeMatchPattern(range.c_str(), pattern.c_str()) ==
131✔
205
                  MS_TRUE) {
206
                if (!checkonly)
119✔
207
                  msSetLimitedPatternsToUse(pattern.c_str());
48✔
208
                match = true;
209
                break;
210
              }
211
            }
212
          }
213
          if (!match) {
214
            msSetErrorWithStatus(
4✔
215
                MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
216
                "Time value %s given does not match the time format pattern.",
217
                "msWMSSetTimePattern", range.c_str());
218
            return false;
219
          }
220
        }
221
      }
82✔
222
    }
52✔
223
  }
52✔
224

225
  return true;
226
}
227

228
/*
229
** Apply the TIME parameter to layers that are time aware
230
*/
231
static int msWMSApplyTime(mapObj *map, int version, const char *time,
131✔
232
                          const char *wms_exception_format) {
233
  if (map) {
131✔
234

235
    const char *timepattern =
236
        msOWSLookupMetadata(&(map->web.metadata), "MO", "timeformat");
131✔
237

238
    for (int i = 0; i < map->numlayers; i++) {
1,582✔
239
      layerObj *lp = (GET_LAYER(map, i));
1,465✔
240
      if (lp->status != MS_ON && lp->status != MS_DEFAULT)
1,465✔
241
        continue;
1,334✔
242

243
      /* check if the layer is time aware */
244
      const char *timeextent =
245
          msOWSLookupMetadata(&(lp->metadata), "MO", "timeextent");
131✔
246
      const char *timefield =
247
          msOWSLookupMetadata(&(lp->metadata), "MO", "timeitem");
131✔
248
      const char *timedefault =
249
          msOWSLookupMetadata(&(lp->metadata), "MO", "timedefault");
131✔
250

251
      if (timeextent && timefield) {
131✔
252
        /* check to see if the time value is given. If not */
253
        /* use default time. If default time is not available */
254
        /* send an exception */
255
        if (time == NULL || strlen(time) <= 0) {
131✔
256
          if (timedefault == NULL) {
5✔
257
            msSetErrorWithStatus(
2✔
258
                MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
259
                "No Time value was given, and no default time value defined.",
260
                "msWMSApplyTime");
261
            return msWMSException(map, version, "MissingDimensionValue",
2✔
262
                                  wms_exception_format);
2✔
263
          } else {
264
            if (msValidateTimeValue((char *)timedefault, timeextent) ==
3✔
265
                MS_FALSE) {
266
              msSetErrorWithStatus(
×
267
                  MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
268
                  "No Time value was given, and the default time value "
269
                  "%s is invalid or outside the time extent defined %s",
270
                  "msWMSApplyTime", timedefault, timeextent);
271
              /* return MS_FALSE; */
272
              return msWMSException(map, version, "InvalidDimensionValue",
×
273
                                    wms_exception_format);
×
274
            }
275
            msLayerSetTimeFilter(lp, timedefault, timefield);
3✔
276
          }
277
        } else {
278
          /*
279
          ** Check to see if there is a list of possible patterns defined. If it
280
          *is the case, use
281
          ** it to set the time pattern to use for the request.
282
          **
283
          ** Last argument is set to TRUE (checkonly) to not trigger the
284
          *patterns info setting, rather
285
          ** to only apply the wms_timeformats on the user request values, not
286
          *the mapfile values.
287
          */
288
          if (timepattern && time && strlen(time) > 0) {
126✔
289
            if (!msWMSSetTimePattern(timepattern, time, true))
32✔
290
              return msWMSException(map, version, "InvalidDimensionValue",
4✔
291
                                    wms_exception_format);
4✔
292
          }
293

294
          /* check if given time is in the range */
295
          if (msValidateTimeValue(time, timeextent) == MS_FALSE) {
122✔
296
            if (timedefault == NULL) {
8✔
297
              msSetErrorWithStatus(
8✔
298
                  MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
299
                  "Time value(s) %s given is invalid or outside the "
300
                  "time extent defined (%s).",
301
                  "msWMSApplyTime", time, timeextent);
302
              /* return MS_FALSE; */
303
              return msWMSException(map, version, "InvalidDimensionValue",
8✔
304
                                    wms_exception_format);
8✔
305
            } else {
306
              if (msValidateTimeValue((char *)timedefault, timeextent) ==
×
307
                  MS_FALSE) {
308
                msSetErrorWithStatus(
×
309
                    MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
310
                    "Time value(s) %s given is invalid or outside the time "
311
                    "extent defined (%s), and default time set is invalid (%s)",
312
                    "msWMSApplyTime", time, timeextent, timedefault);
313
                /* return MS_FALSE; */
314
                return msWMSException(map, version, "InvalidDimensionValue",
×
315
                                      wms_exception_format);
×
316
              } else
317
                msLayerSetTimeFilter(lp, timedefault, timefield);
×
318
            }
319

320
          } else {
321
            /* build the time string */
322
            msLayerSetTimeFilter(lp, time, timefield);
114✔
323
            timeextent = NULL;
324
          }
325
        }
326
      }
327
    }
328

329
    /* last argument is MS_FALSE to trigger a method call that set the patterns
330
       info. some drivers use it */
331
    if (timepattern && time && strlen(time) > 0) {
117✔
332
      if (!msWMSSetTimePattern(timepattern, time, false))
20✔
333
        return msWMSException(map, version, "InvalidDimensionValue",
×
334
                              wms_exception_format);
×
335
    }
336
  }
337

338
  return MS_SUCCESS;
339
}
340

341
/*
342
** Apply the FILTER parameter to layers (RFC118)
343
*/
344
static int msWMSApplyFilter(mapObj *map, int version, const char *filter,
13✔
345
                            int def_srs_needs_axis_swap,
346
                            const char *wms_exception_format,
347
                            owsRequestObj *ows_request) {
348
  // Empty filter should be ignored
349
  if (!filter || strlen(filter) == 0)
13✔
350
    return MS_SUCCESS;
351

352
  if (!map)
11✔
353
    return MS_FAILURE;
354

355
  /* Count number of requested layers / groups / etc.
356
   * Only layers with STATUS ON were in the LAYERS request param.
357
   * Layers with STATUS DEFAULT were set in the mapfile and are
358
   * not expected to have a corresponding filter in the request
359
   */
360
  int numlayers = 0;
361
  for (int i = 0; i < map->numlayers; i++) {
43✔
362
    layerObj *lp = NULL;
363

364
    if (map->layerorder[i] != -1) {
32✔
365
      lp = (GET_LAYER(map, map->layerorder[i]));
32✔
366
      if (lp->status == MS_ON)
32✔
367
        numlayers++;
20✔
368
    }
369
  }
370

371
  /* -------------------------------------------------------------------- */
372
  /*      Parse the Filter parameter. If there are several Filter         */
373
  /*      parameters, each Filter is inside parentheses.                  */
374
  /* -------------------------------------------------------------------- */
375
  int numfilters = 0;
11✔
376
  char **paszFilters = NULL;
377
  if (filter[0] == '(') {
11✔
378
    paszFilters = FLTSplitFilters(filter, &numfilters);
8✔
379

380
  } else if (numlayers == 1) {
3✔
381
    numfilters = 1;
3✔
382
    paszFilters = (char **)msSmallMalloc(sizeof(char *) * numfilters);
3✔
383
    paszFilters[0] = msStrdup(filter);
3✔
384
  }
385

386
  if (numfilters != ows_request->numwmslayerargs) {
11✔
387
    msSetErrorWithStatus(
1✔
388
        MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
389
        "Wrong number of filter elements, one filter must be specified "
390
        "for each requested layer or groups.",
391
        "msWMSApplyFilter");
392
    msFreeCharArray(paszFilters, numfilters);
1✔
393
    return msWMSException(map, version, "InvalidParameterValue",
1✔
394
                          wms_exception_format);
395
  }
396

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

401
    if (map->layerorder[i] != -1)
29✔
402
      lp = (GET_LAYER(map, map->layerorder[i]));
29✔
403

404
    /* Only layers with STATUS ON were in the LAYERS request param.*/
405
    if (lp == NULL || lp->status != MS_ON)
29✔
406
      continue;
14✔
407

408
    const int curfilter = ows_request->layerwmsfilterindex[lp->index];
17✔
409

410
    /* Skip empty filters */
411
    assert(paszFilters);
412
    assert(curfilter >= 0 && curfilter < numfilters);
413
    if (paszFilters[curfilter][0] == '\0') {
17✔
414
      continue;
2✔
415
    }
416

417
    /* Force setting a template to enable query. */
418
    if (lp->_template == NULL)
15✔
419
      lp->_template = msStrdup("ttt.html");
14✔
420

421
    /* Parse filter */
422
    FilterEncodingNode *psNode = FLTParseFilterEncoding(paszFilters[curfilter]);
15✔
423
    if (!psNode) {
15✔
424
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
425
                           "Invalid or Unsupported FILTER : %s",
426
                           "msWMSApplyFilter()", paszFilters[curfilter]);
427
      msFreeCharArray(paszFilters, numfilters);
×
428
      return msWMSException(map, version, "InvalidParameterValue",
×
429
                            wms_exception_format);
×
430
    }
431

432
    /* For WMS 1.3 and up, we may need to swap the axis of bbox and geometry
433
     * elements inside the filter(s)
434
     */
435
    if (version >= OWS_1_3_0)
15✔
436
      FLTDoAxisSwappingIfNecessary(map, psNode, def_srs_needs_axis_swap);
14✔
437

438
#ifdef do_we_need_this
439
    FLTProcessPropertyIsNull(psNode, map, lp->index);
440

441
    /*preparse the filter for gml aliases*/
442
    FLTPreParseFilterForAliasAndGroup(psNode, map, lp->index, "G");
443

444
    /* Check that FeatureId filters are consistent with the active layer */
445
    if (FLTCheckFeatureIdFilters(psNode, map, lp->index) == MS_FAILURE) {
446
      FLTFreeFilterEncodingNode(psNode);
447
      return msWFSException(map, "mapserv", MS_OWS_ERROR_NO_APPLICABLE_CODE,
448
                            paramsObj->pszVersion);
449
    }
450

451
    /* FIXME?: could probably apply to WFS 1.1 too */
452
    if (nWFSVersion >= OWS_2_0_0) {
453
      int nEvaluation;
454

455
      if (FLTCheckInvalidOperand(psNode) == MS_FAILURE) {
456
        FLTFreeFilterEncodingNode(psNode);
457
        msFreeCharArray(paszFilters, numfilters);
458
        return msWFSException(map, "filter",
459
                              MS_WFS_ERROR_OPERATION_PROCESSING_FAILED,
460
                              paramsObj->pszVersion);
461
      }
462

463
      if (FLTCheckInvalidProperty(psNode, map, lp->index) == MS_FAILURE) {
464
        FLTFreeFilterEncodingNode(psNode);
465
        msFreeCharArray(paszFilters, numfilters);
466
        return msWFSException(map, "filter",
467
                              MS_OWS_ERROR_INVALID_PARAMETER_VALUE,
468
                              paramsObj->pszVersion);
469
      }
470

471
      psNode = FLTSimplify(psNode, &nEvaluation);
472
      if (psNode == NULL) {
473
        FLTFreeFilterEncodingNode(psNode);
474
        msFreeCharArray(paszFilters, numfilters);
475
        if (nEvaluation == 1) {
476
          /* return full layer */
477
          return msWFSRunBasicGetFeature(map, lp, paramsObj, nWFSVersion);
478
        } else {
479
          /* return empty result set */
480
          return MS_SUCCESS;
481
        }
482
      }
483
    }
484

485
#endif
486

487
    /* Apply filter to this layer */
488

489
    /* But first, start by removing any use_default_extent_for_getfeature
490
     * metadata items that could result in the BBOX to be removed */
491

492
    hashTableObj *tmpTable = msCreateHashTable();
15✔
493

494
    std::vector<std::string> keys_to_temporarily_remove = {
495
        "wfs_use_default_extent_for_getfeature",
496
        "ows_use_default_extent_for_getfeature",
497
        "oga_use_default_extent_for_getfeature"};
15✔
498

499
    for (const auto &key : keys_to_temporarily_remove) {
60✔
500
      const char *value = msLookupHashTable(&(lp->metadata), key.c_str());
45✔
501
      if (value) {
45✔
502
        msInsertHashTable(tmpTable, key.c_str(), value);
6✔
503
        msRemoveHashTable(&(lp->metadata), key.c_str());
6✔
504
      }
505
    }
506

507
    msInsertHashTable(&(lp->metadata), "gml_wmsfilter_flag", "true");
15✔
508

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

511
    msRemoveHashTable(&(lp->metadata), "gml_wmsfilter_flag");
15✔
512

513
    const char *pszKey;
514
    pszKey = msFirstKeyFromHashTable(tmpTable);
15✔
515
    for (; pszKey != NULL; pszKey = msNextKeyFromHashTable(tmpTable, pszKey)) {
21✔
516
      msInsertHashTable(&(lp->metadata), pszKey,
6✔
517
                        msLookupHashTable(tmpTable, pszKey));
518
    }
519

520
    msFreeHashTable(tmpTable);
15✔
521

522
    if (ret != MS_SUCCESS) {
15✔
523
      errorObj *ms_error = msGetErrorObj();
×
524

525
      if (ms_error->code != MS_NOTFOUND) {
×
526
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_500_INTERNAL_SERVER_ERROR,
×
527
                             "FLTApplyFilterToLayer() failed",
528
                             "msWMSApplyFilter()");
529
        FLTFreeFilterEncodingNode(psNode);
×
530
        msFreeCharArray(paszFilters, numfilters);
×
531
        return msWMSException(map, version, "InvalidParameterValue",
×
532
                              wms_exception_format);
533
      }
534
    }
535

536
    FLTFreeFilterEncodingNode(psNode);
15✔
537

538
  } /* for */
15✔
539

540
  msFreeCharArray(paszFilters, numfilters);
10✔
541

542
  return MS_SUCCESS;
543
}
544

545
/*
546
** msWMSPrepareNestedGroups()
547
**
548
** purpose: Parse WMS_LAYER_GROUP settings into arrays
549
**
550
** params:
551
** - nestedGroups: This array holds the arrays of groups that have been set
552
**                 through the WMS_LAYER_GROUP metadata
553
** - numNestedGroups: This array holds the number of groups set in
554
**                    WMS_LAYER_GROUP for each layer
555
** - isUsedInNestedGroup: This array indicates if the layer is used as group
556
**                        as set through the WMS_LAYER_GROUP metadata
557
*/
558
static void msWMSPrepareNestedGroups(mapObj *map, int /* nVersion */,
899✔
559
                                     char ***nestedGroups, int *numNestedGroups,
560
                                     int *isUsedInNestedGroup) {
561
  // Create set to hold unique groups
562
  std::set<std::string> uniqgroups;
563

564
  for (int i = 0; i < map->numlayers; i++) {
7,095✔
565
    nestedGroups[i] = NULL;     /* default */
6,196✔
566
    numNestedGroups[i] = 0;     /* default */
6,196✔
567
    isUsedInNestedGroup[i] = 0; /* default */
6,196✔
568

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

615
/*
616
** Validate that a given dimension is inside the extents defined
617
*/
618
static bool msWMSValidateDimensionValue(const char *value,
29✔
619
                                        const char *dimensionextent,
620
                                        bool forcecharacter) {
621
  std::vector<pointObj> aextentranges;
622

623
  bool isextentavalue = false;
624
  bool isextentarange = false;
625
  bool ischaracter = false;
626

627
  if (forcecharacter)
628
    ischaracter = true;
629

630
  if (!value || !dimensionextent)
29✔
631
    return false;
632

633
  /*for the value, we support descrete values (2005) */
634
  /* multiple values (abc, def, ...) */
635
  /* and range(s) (1000/2000, 3000/5000) */
636
  /** we do not support resolution*/
637

638
  /* -------------------------------------------------------------------- */
639
  /*      parse the extent first.                                         */
640
  /* -------------------------------------------------------------------- */
641
  auto extents = msStringSplit(dimensionextent, ',');
29✔
642
  for (auto &extent :
29✔
643
       extents) // Make sure to get by reference so that it is updated in place
88✔
644
    msStringTrim(extent);
59✔
645

646
  std::vector<std::string> aextentvalues;
647
  if (extents.size() == 1) {
29✔
648
    if (strstr(dimensionextent, "/") == NULL) {
10✔
649
      /*single value*/
650
      isextentavalue = true;
651
      aextentvalues.push_back(dimensionextent);
×
652
      if (!forcecharacter)
×
653
        ischaracter = FLTIsNumeric(dimensionextent) == MS_FALSE;
×
654

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

683
      for (const auto &extent : extents) {
24✔
684
        const auto onerange = msStringSplit(extent.c_str(), '/');
16✔
685
        if (onerange.size() != 2 && onerange.size() != 3) {
16✔
686
          isvalidextent = MS_FALSE;
687
          break;
688
        }
689
        if (isvalidextent) {
690

691
          aextentranges[nextentranges].x = atof(onerange[0].c_str());
16✔
692
          aextentranges[nextentranges++].y = atof(onerange[1].c_str());
16✔
693
        }
694
      }
16✔
695
      if (!isvalidextent) {
696
        nextentranges = 0;
697
        isextentarange = false;
698
      }
699
      aextentranges.resize(nextentranges);
8✔
700
    }
701
  }
702

703
  /* make sure that we got a valid extent*/
704
  if (!isextentavalue && !isextentarange) {
29✔
705
    return false;
706
  }
707

708
  /*for the extent of the dimesion, we support
709
  single value,  or list of mulitiple values comma separated,
710
  a single range or multiple ranges */
711

712
  const auto uservalues = msStringSplit(value, ',');
29✔
713
  bool uservaluevalid = false;
714
  if (uservalues.size() == 1) {
29✔
715
    /*user input=single*/
716
    /*is it descret or range*/
717
    const auto ranges = msStringSplit(uservalues[0].c_str(), '/');
12✔
718
    if (ranges.size() == 1) { /*discrete*/
12✔
719
      if (isextentavalue) {
8✔
720
        /*single user value, single/multiple values extent*/
721
        for (const auto &extentvalue : aextentvalues) {
12✔
722
          if (ischaracter)
10✔
723
            uservaluevalid = (uservalues[0] == extentvalue);
4✔
724
          else {
725
            if (atof(uservalues[0].c_str()) == atof(extentvalue.c_str()))
6✔
726
              uservaluevalid = true;
727
          }
728
          if (uservaluevalid)
8✔
729
            break;
730
        }
731
      } else if (isextentarange) {
3✔
732
        /*single user value, single/multiple range extent*/
733
        const float currentval = atof(uservalues[0].c_str());
3✔
734

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

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

808
      for (const auto &uservalue : uservalues) {
12✔
809
        /*each ranges should be valid*/
810
        const auto onerange = msStringSplit(uservalue.c_str(), '/');
10✔
811
        if (onerange.size() == 2 || onerange.size() == 3) {
10✔
812
          const float mincurrentval = atof(onerange[0].c_str());
10✔
813
          const float maxcurrentval = atof(onerange[1].c_str());
10✔
814

815
          /*extent must be defined also as a rangle*/
816
          if (isextentarange) {
10✔
817
            bool found = false;
818
            for (const auto &extentrange : aextentranges) {
17✔
819
              const float mincurrentrange = extentrange.x;
13✔
820
              const float maxcurrentrange = extentrange.y;
13✔
821

822
              if (mincurrentval >= mincurrentrange &&
13✔
823
                  maxcurrentval <= maxcurrentrange &&
6✔
824
                  mincurrentval <= maxcurrentval) {
825
                found = true;
826
                break;
827
              }
828
            }
829
            if (!found) {
10✔
830
              valueisvalid = false;
831
              break;
832
            }
833
          }
834
        } else {
835
          valueisvalid = false;
836
        }
837
      }
10✔
838
      uservaluevalid = valueisvalid;
839
    }
840
  }
841

842
  return uservaluevalid;
843
}
29✔
844

845
static bool msWMSApplyDimensionLayer(layerObj *lp, const char *item,
13✔
846
                                     const char *value, bool forcecharacter) {
847
  bool result = false;
848

849
  if (lp && item && value) {
13✔
850
    /*for the value, we support descrete values (2005) */
851
    /* multiple values (abc, def, ...) */
852
    /* and range(s) (1000/2000, 3000/5000) */
853
    char *pszExpression = FLTGetExpressionForValuesRanges(
26✔
854
        lp, item, value, forcecharacter ? MS_TRUE : MS_FALSE);
855

856
    if (pszExpression) {
13✔
857
      // If tileindex is set, the filter is applied to tileindex too.
858
      int tlpindex = -1;
859
      if (lp->tileindex &&
13✔
860
          (tlpindex = msGetLayerIndex(lp->map, lp->tileindex)) != -1) {
×
861
        result = FLTApplyExpressionToLayer((GET_LAYER(lp->map, tlpindex)),
×
862
                                           pszExpression) != MS_FALSE;
863
      } else {
864
        result = true;
865
      }
866
      result &= FLTApplyExpressionToLayer(lp, pszExpression) != MS_FALSE;
13✔
867
      msFree(pszExpression);
13✔
868
    }
869
  }
870
  return result;
13✔
871
}
872

873
static bool msWMSApplyDimension(layerObj *lp, int /* version */,
29✔
874
                                const char *dimensionname, const char *value,
875
                                const char * /* wms_exception_format */) {
876
  bool forcecharacter = false;
877
  bool result = false;
878

879
  if (lp && dimensionname && value) {
29✔
880
    /*check if the dimension name passes starts with dim_. All dimensions should
881
     * start with dim_, except elevation*/
882
    std::string dimension;
883
    if (strncasecmp(dimensionname, "dim_", 4) == 0)
29✔
884
      dimension = dimensionname + 4;
5✔
885
    else
886
      dimension = dimensionname;
887

888
    /*if value is empty and a default is defined, use it*/
889
    std::string currentvalue;
890
    if (strlen(value) > 0)
29✔
891
      currentvalue = value;
892
    else {
893
      const char *dimensiondefault = msOWSLookupMetadata(
1✔
894
          &(lp->metadata), "M", (dimension + "_default").c_str());
1✔
895
      if (dimensiondefault)
1✔
896
        currentvalue = dimensiondefault;
897
    }
898

899
    /*check if the manadatory metada related to the dimension are set*/
900
    const char *dimensionitem = msOWSLookupMetadata(
29✔
901
        &(lp->metadata), "M", (dimension + "_item").c_str());
29✔
902
    const char *dimensionextent = msOWSLookupMetadata(
29✔
903
        &(lp->metadata), "M", (dimension + "_extent").c_str());
29✔
904
    const char *dimensionunit = msOWSLookupMetadata(
29✔
905
        &(lp->metadata), "M", (dimension + "_units").c_str());
29✔
906

907
    /*if the server want to force the type to character*/
908
    const char *dimensiontype = msOWSLookupMetadata(
29✔
909
        &(lp->metadata), "M", (dimension + "_type").c_str());
29✔
910
    if (dimensiontype && strcasecmp(dimensiontype, "Character") == 0)
29✔
911
      forcecharacter = true;
912

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

967
  const char *sldenabled = NULL;
968
  const char *sld_url = NULL;
969
  const char *sld_body = NULL;
970

971
  const char *filter = NULL;
972

973
  bool compliance_mode = false;
974

975
  /* Some of the getMap parameters are actually required depending on the */
976
  /* request, but for now we assume all are optional and the map file */
977
  /* defaults will apply. */
978

979
  msAdjustExtent(&(map->extent), map->width, map->height);
544✔
980

981
  /*
982
    Check if we need strict checks for standard compliance.
983
    Defaults to false.
984
  */
985
  compliance_mode = msOWSStrictCompliance(map);
544✔
986

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

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

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

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

1017
    if (strcasecmp(names[i], "REQUEST") == 0) {
6,528✔
1018
      request = values[i];
544✔
1019
    }
1020

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1256
      if (strcasecmp(values[i], "application/openlayers") != 0) {
514✔
1257
        /*check to see if a predefined list is given*/
1258
        const char *format_list =
1259
            msOWSLookupMetadata(&(map->web.metadata), "M", "getmap_formatlist");
511✔
1260
        if (format_list) {
511✔
1261
          format = msOwsIsOutputFormatValid(
70✔
1262
              map, values[i], &(map->web.metadata), "M", "getmap_formatlist");
1263
          if (format == NULL) {
70✔
1264
            msSetErrorWithStatus(MS_IMGERR, MS_HTTP_400_BAD_REQUEST,
×
1265
                                 "Unsupported output format (%s).",
1266
                                 "msWMSLoadGetMapParams()", values[i]);
1267
            return msWMSException(map, nVersion, "InvalidFormat",
×
1268
                                  wms_exception_format);
1269
          }
1270
        } else {
1271
          format = msSelectOutputFormat(map, values[i]);
441✔
1272
          if (format == NULL ||
441✔
1273
              (strncasecmp(format->driver, "MVT", 3) != 0 &&
441✔
1274
               strncasecmp(format->driver, "GDAL/", 5) != 0 &&
436✔
1275
               strncasecmp(format->driver, "AGG/", 4) != 0 &&
433✔
1276
               strncasecmp(format->driver, "UTFGRID", 7) != 0 &&
3✔
1277
               strncasecmp(format->driver, "CAIRO/", 6) != 0 &&
3✔
1278
               strncasecmp(format->driver, "OGL/", 4) != 0 &&
3✔
1279
               strncasecmp(format->driver, "KML", 3) != 0 &&
3✔
1280
               strncasecmp(format->driver, "KMZ", 3) != 0)) {
1✔
1281
            msSetErrorWithStatus(MS_IMGERR, MS_HTTP_400_BAD_REQUEST,
×
1282
                                 "Unsupported output format (%s).",
1283
                                 "msWMSLoadGetMapParams()", values[i]);
1284
            return msWMSException(map, nVersion, "InvalidFormat",
×
1285
                                  wms_exception_format);
1286
          }
1287
        }
1288
      }
1289
      msFree(map->imagetype);
514✔
1290
      map->imagetype = msStrdup(values[i]);
514✔
1291
    } else if (strcasecmp(names[i], "TRANSPARENT") == 0) {
2,931✔
1292
      if (compliance_mode) {
70✔
1293
        transparent = (strcmp(values[i], "TRUE") == 0);
3✔
1294
        if ((!transparent) && (strcmp(values[i], "FALSE") != 0)) {
3✔
1295
          msSetErrorWithStatus(
2✔
1296
              MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1297
              "Value for TRANSPARENT must be either TRUE or FALSE.",
1298
              "msWMSLoadGetMapParams()");
1299
          return msWMSException(map, nVersion, NULL, wms_exception_format);
2✔
1300
        }
1301
      } else {
1302
        transparent = (strcasecmp(values[i], "TRUE") == 0);
67✔
1303
      }
1304
    } else if (strcasecmp(names[i], "BGCOLOR") == 0) {
2,861✔
1305
      long c;
1306
      c = strtol(values[i], NULL, 16);
45✔
1307
      map->imagecolor.red = (c / 0x10000) & 0xff;
45✔
1308
      map->imagecolor.green = (c / 0x100) & 0xff;
45✔
1309
      map->imagecolor.blue = c & 0xff;
45✔
1310
    }
1311

1312
    /* value of time can be empty. We should look for a default value */
1313
    /* see function msWMSApplyTime */
1314
    else if (strcasecmp(names[i], "TIME") == 0) { /* &&  values[i]) */
2,816✔
1315
      stime = values[i];
130✔
1316
      timerequest = true;
1317
    }
1318
    /* Vendor-specific ANGLE param (for map rotation), added in ticket #3332,
1319
     * also supported by GeoServer
1320
     */
1321
    else if (strcasecmp(names[i], "ANGLE") == 0) {
2,686✔
1322
      msMapSetRotation(map, atof(values[i]));
×
1323
    }
1324
    /* Vendor-specific bbox_pixel_is_point, added in ticket #4652 */
1325
    else if (strcasecmp(names[i], "BBOX_PIXEL_IS_POINT") == 0) {
2,686✔
1326
      bbox_pixel_is_point = (strcasecmp(values[i], "TRUE") == 0);
×
1327
    }
1328
    /* Vendor specific TILED (WMS-C) */
1329
    else if (strcasecmp(names[i], "TILED") == 0) {
2,686✔
1330
      tiled = (strcasecmp(values[i], "TRUE") == 0);
1✔
1331
    }
1332
    /* Vendor-specific FILTER, added in RFC-118 */
1333
    else if (strcasecmp(names[i], "FILTER") == 0) {
2,685✔
1334
      filter = values[i];
14✔
1335
    }
1336
  }
1337

1338
  /*validate the exception format WMS 1.3.0 section 7.3.3.11*/
1339

1340
  if (nVersion >= OWS_1_3_0 && wms_exception_format != NULL) {
540✔
1341
    if (strcasecmp(wms_exception_format, "INIMAGE") != 0 &&
23✔
1342
        strcasecmp(wms_exception_format, "BLANK") != 0 &&
4✔
1343
        strcasecmp(wms_exception_format, "XML") != 0) {
4✔
1344
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
1345
                           "Invalid format %s for the EXCEPTIONS parameter.",
1346
                           "msWMSLoadGetMapParams()", wms_exception_format);
1347
      return msWMSException(map, nVersion, "InvalidFormat",
×
1348
                            wms_exception_format);
1349
    }
1350
  }
1351

1352
  int need_axis_swap = MS_FALSE;
1353
  if (bboxfound && nVersion >= OWS_1_3_0) {
540✔
1354
    rectObj rect;
1355
    projectionObj proj;
1356

1357
    /*we have already validated that the request format when reading
1358
     the request parameters*/
1359
    rect = map->extent;
208✔
1360

1361
    /*try to adjust the axes if necessary*/
1362
    if (srsbuffer.size() > 1) {
208✔
1363
      msInitProjection(&proj);
206✔
1364
      msProjectionInheritContextFrom(&proj, &(map->projection));
206✔
1365
      if (msLoadProjectionStringEPSG(&proj, srsbuffer.c_str()) == 0 &&
411✔
1366
          (need_axis_swap = msIsAxisInvertedProj(&proj))) {
205✔
1367
        msAxisNormalizePoints(&proj, 1, &rect.minx, &rect.miny);
194✔
1368
        msAxisNormalizePoints(&proj, 1, &rect.maxx, &rect.maxy);
194✔
1369
      }
1370
      msFreeProjection(&proj);
206✔
1371
    }
1372
    /*if the CRS is AUTO2:auto_crs_id,factor,lon0,lat0,
1373
     we need to grab the factor parameter and use it with the bbox*/
1374
    if (srsbuffer.size() > 1 &&
208✔
1375
        strncasecmp(srsbuffer.c_str(), "AUTO2:", 6) == 0) {
206✔
1376
      const auto args = msStringSplit(srsbuffer.c_str(), ',');
×
1377
      if (args.size() == 4) {
×
1378
        const double factor = atof(args[1].c_str());
1379
        if (factor > 0 && factor != 1.0) {
×
1380
          rect.minx = rect.minx * factor;
×
1381
          rect.miny = rect.miny * factor;
×
1382
          rect.maxx = rect.maxx * factor;
1383
          rect.maxx = rect.maxy * factor;
×
1384
        }
1385
      }
1386
    }
×
1387

1388
    map->extent = rect;
208✔
1389

1390
    /* validate bbox values */
1391
    if (map->extent.minx >= map->extent.maxx ||
208✔
1392
        map->extent.miny >= map->extent.maxy) {
208✔
1393
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
1394
                           "Invalid values for BBOX.",
1395
                           "msWMSLoadGetMapParams()");
1396
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1397
    }
1398
    adjust_extent = true;
1399
  }
1400

1401
  if (tiled) {
540✔
1402
    const char *value;
1403
    hashTableObj *meta = &(map->web.metadata);
1✔
1404
    int map_edge_buffer = 0;
1405

1406
    if ((value = msLookupHashTable(meta, "tile_map_edge_buffer")) != NULL) {
1✔
1407
      map_edge_buffer = atoi(value);
1408
    }
1409
    if (map_edge_buffer > 0 && map->width > 0 && map->height > 0) {
1✔
1410
      /* adjust bbox and width and height to the buffer */
1411
      const double buffer_x = map_edge_buffer *
1✔
1412
                              (map->extent.maxx - map->extent.minx) /
1✔
1413
                              (double)map->width;
1✔
1414
      const double buffer_y = map_edge_buffer *
1✔
1415
                              (map->extent.maxy - map->extent.miny) /
1✔
1416
                              (double)map->height;
1✔
1417

1418
      // TODO: we should probably clamp the extent to avoid going outside of
1419
      // -180,-90,180,90 for geographic CRS for example
1420
      map->extent.minx -= buffer_x;
1✔
1421
      map->extent.maxx += buffer_x;
1✔
1422
      map->extent.miny -= buffer_y;
1✔
1423
      map->extent.maxy += buffer_y;
1✔
1424

1425
      map->width += 2 * map_edge_buffer;
1✔
1426
      map->height += 2 * map_edge_buffer;
1✔
1427

1428
      if (map_edge_buffer > 0) {
1429
        char tilebufferstr[64];
1430

1431
        /* Write the tile buffer to a string */
1432
        snprintf(tilebufferstr, sizeof(tilebufferstr), "-%d", map_edge_buffer);
1433

1434
        /* Hm, the labelcache buffer is set... */
1435
        if ((value = msLookupHashTable(meta, "labelcache_map_edge_buffer")) !=
1✔
1436
            NULL) {
1437
          /* If it's too small, replace with a bigger one */
1438
          if (map_edge_buffer > abs(atoi(value))) {
×
1439
            msRemoveHashTable(meta, "labelcache_map_edge_buffer");
×
1440
            msInsertHashTable(meta, "labelcache_map_edge_buffer",
×
1441
                              tilebufferstr);
1442
          }
1443
        }
1444
        /* No labelcache buffer value? Then we use the tile buffer. */
1445
        else {
1446
          msInsertHashTable(meta, "labelcache_map_edge_buffer", tilebufferstr);
1✔
1447
        }
1448
      }
1449
    }
1450
  }
1451

1452
  /*
1453
  ** If any select layers have a default time, we will apply the default
1454
  ** time value even if no TIME request was in the url.
1455
  */
1456
  if (!timerequest && map) {
540✔
1457
    for (int i = 0; i < map->numlayers && !timerequest; i++) {
2,032✔
1458
      layerObj *lp = NULL;
1459

1460
      lp = (GET_LAYER(map, i));
1,622✔
1461
      if (lp->status != MS_ON && lp->status != MS_DEFAULT)
1,622✔
1462
        continue;
847✔
1463

1464
      if (msOWSLookupMetadata(&(lp->metadata), "MO", "timedefault"))
775✔
1465
        timerequest = true;
1466
    }
1467
  }
1468

1469
  /*
1470
  ** Apply time filters if available in the request.
1471
  */
1472
  if (timerequest) {
540✔
1473
    if (msWMSApplyTime(map, nVersion, stime, wms_exception_format) ==
131✔
1474
        MS_FAILURE) {
1475
      return MS_FAILURE; /* msWMSException(map, nVersion, "InvalidTimeRequest");
1476
                          */
1477
    }
1478
  }
1479

1480
  /*
1481
  ** Check/apply wms dimensions
1482
  ** all dimension requests should start with dim_xxxx, except time and
1483
  *elevation.
1484
  */
1485
  for (int i = 0; i < map->numlayers; i++) {
3,562✔
1486
    layerObj *lp = (GET_LAYER(map, i));
3,052✔
1487
    if (lp->status != MS_ON && lp->status != MS_DEFAULT)
3,052✔
1488
      continue;
2,161✔
1489

1490
    const char *dimensionlist =
1491
        msOWSLookupMetadata(&(lp->metadata), "M", "dimensionlist");
891✔
1492
    if (dimensionlist) {
891✔
1493
      auto tokens = msStringSplit(dimensionlist, ',');
29✔
1494
      for (auto &token : tokens) {
50✔
1495
        msStringTrim(token);
37✔
1496
        for (int k = 0; k < numentries; k++) {
470✔
1497
          const std::string dimensionname(names[k]);
462✔
1498

1499
          /*the dim_ is supposed to be part of the dimension name in the
1500
           * request*/
1501
          std::string stmp;
1502
          if (strcasecmp(token.c_str(), "elevation") == 0)
462✔
1503
            stmp = token;
1504
          else {
1505
            stmp = "dim_";
1506
            stmp += token;
1507
          }
1508
          if (strcasecmp(dimensionname.c_str(), stmp.c_str()) == 0) {
462✔
1509
            if (!msWMSApplyDimension(lp, nVersion, dimensionname.c_str(),
29✔
1510
                                     values[k], wms_exception_format)) {
29✔
1511
              return msWMSException(lp->map, nVersion, "InvalidDimensionValue",
16✔
1512
                                    wms_exception_format);
1513
            }
1514
            break;
1515
          }
1516
        }
1517
      }
1518
    }
29✔
1519
  }
1520

1521
  /*
1522
  ** Apply the selected output format (if one was selected), and override
1523
  ** the transparency if needed.
1524
  */
1525

1526
  if (format != NULL)
510✔
1527
    msApplyOutputFormat(&(map->outputformat), format, transparent);
479✔
1528

1529
  /* Validate all layers given.
1530
  ** If an invalid layer is sent, return an exception.
1531
  */
1532
  if (validlayers == 0 || invalidlayers > 0) {
510✔
1533
    if (invalidlayers > 0) {
6✔
1534
      msSetErrorWithStatus(
3✔
1535
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1536
          "Invalid layer(s) given in the LAYERS parameter. A layer might be disabled for \
1537
this request. Check wms/ows_enable_request settings.",
1538
          "msWMSLoadGetMapParams()");
1539
      return msWMSException(map, nVersion, "LayerNotDefined",
3✔
1540
                            wms_exception_format);
1541
    }
1542
    if (validlayers == 0 && sld_url == NULL && sld_body == NULL) {
3✔
1543
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
3✔
1544
                           "Missing required parameter LAYERS",
1545
                           "msWMSLoadGetMapParams()");
1546
      return msWMSException(map, nVersion, "MissingParameterValue",
3✔
1547
                            wms_exception_format);
1548
    }
1549
  }
1550

1551
  /* validate srs value: When the SRS parameter in a GetMap request contains a
1552
  ** SRS that is valid for some, but not all of the layers being requested,
1553
  ** then the server shall throw a Service Exception (code = "InvalidSRS").
1554
  ** Validate first against epsg in the map and if no matching srs is found
1555
  ** validate all layers requested.
1556
  */
1557
  if (epsgbuf.size() >= 2) { /*at least 2 chars*/
504✔
1558
    char *projstring;
1559
    epsgvalid = false;
1560
    msOWSGetEPSGProj(&(map->projection), &(map->web.metadata), "MO", MS_FALSE,
482✔
1561
                     &projstring);
1562
    if (projstring) {
482✔
1563
      const auto tokens = msStringSplit(projstring, ' ');
482✔
1564
      for (const auto &token : tokens) {
855✔
1565
        if (strcasecmp(token.c_str(), epsgbuf.c_str()) == 0) {
842✔
1566
          epsgvalid = true;
1567
          break;
1568
        }
1569
      }
1570
      msFree(projstring);
482✔
1571
    }
482✔
1572
    if (!epsgvalid) {
482✔
1573
      for (int i = 0; i < map->numlayers; i++) {
30✔
1574
        epsgvalid = false;
1575
        if (GET_LAYER(map, i)->status == MS_ON) {
18✔
1576
          msOWSGetEPSGProj(&(GET_LAYER(map, i)->projection),
12✔
1577
                           &(GET_LAYER(map, i)->metadata), "MO", MS_FALSE,
1578
                           &projstring);
1579
          if (projstring) {
12✔
1580
            const auto tokens = msStringSplit(projstring, ' ');
12✔
1581
            for (const auto &token : tokens) {
25✔
1582
              if (strcasecmp(token.c_str(), epsgbuf.c_str()) == 0) {
24✔
1583
                epsgvalid = true;
1584
                break;
1585
              }
1586
            }
1587
            msFree(projstring);
12✔
1588
          }
12✔
1589
          if (!epsgvalid) {
12✔
1590
            if (nVersion >= OWS_1_3_0) {
1✔
1591
              msSetErrorWithStatus(
1✔
1592
                  MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1593
                  "Invalid CRS given : CRS must be valid for all "
1594
                  "requested layers.",
1595
                  "msWMSLoadGetMapParams()");
1596
              return msWMSException(map, nVersion, "InvalidSRS",
1✔
1597
                                    wms_exception_format);
1✔
1598
            } else {
1599
              msSetErrorWithStatus(
×
1600
                  MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1601
                  "Invalid SRS given : SRS must be valid for all "
1602
                  "requested layers.",
1603
                  "msWMSLoadGetMapParams()");
1604
              return msWMSException(map, nVersion, "InvalidSRS",
×
1605
                                    wms_exception_format);
1606
            }
1607
          }
1608
        }
1609
      }
1610
    }
1611
  }
1612

1613
  if (request == NULL || strcasecmp(request, "DescribeLayer") != 0) {
503✔
1614
    /* Validate requested image size.
1615
     */
1616
    if (map->width > map->maxsize || map->height > map->maxsize ||
484✔
1617
        map->width < 1 || map->height < 1) {
484✔
1618
      msSetErrorWithStatus(
×
1619
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1620
          "Image size out of range, WIDTH and HEIGHT must be between 1 "
1621
          "and %d pixels.",
1622
          "msWMSLoadGetMapParams()", map->maxsize);
1623

1624
      /* Restore valid default values in case errors INIMAGE are used */
1625
      map->width = 400;
×
1626
      map->height = 300;
×
1627
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1628
    }
1629

1630
    /* Check whether requested BBOX and width/height result in non-square pixels
1631
     */
1632
    nonsquare_enabled =
1633
        msTestConfigOption(map, "MS_NONSQUARE", MS_FALSE) != MS_FALSE;
484✔
1634
    if (!nonsquare_enabled) {
484✔
1635
      const double dx = MS_ABS(map->extent.maxx - map->extent.minx);
484✔
1636
      const double dy = MS_ABS(map->extent.maxy - map->extent.miny);
484✔
1637

1638
      const double reqy = ((double)map->width) * dy / dx;
484✔
1639

1640
      /* Allow up to 1 pixel of error on the width/height ratios. */
1641
      /* If more than 1 pixel then enable non-square pixels */
1642
      if (MS_ABS((reqy - (double)map->height)) > 1.0) {
484✔
1643
        if (map->debug)
165✔
1644
          msDebug("msWMSLoadGetMapParams(): enabling non-square pixels.\n");
×
1645
        msSetConfigOption(map, "MS_NONSQUARE", "YES");
165✔
1646
        nonsquare_enabled = true;
1647
      }
1648
    }
1649
  }
1650

1651
  /* If the requested SRS is different from the default mapfile projection, or
1652
  ** if a BBOX resulting in non-square pixels is requested then
1653
  ** copy the original mapfile's projection to any layer that doesn't already
1654
  ** have a projection. This will prevent problems when users forget to
1655
  ** explicitly set a projection on all layers in a WMS mapfile.
1656
  */
1657
  if (srsbuffer.size() > 1 || nonsquare_enabled) {
503✔
1658
    projectionObj newProj;
1659

1660
    if (map->projection.numargs <= 0) {
483✔
1661
      if (nVersion >= OWS_1_3_0) {
×
1662
        msSetErrorWithStatus(
×
1663
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1664
            "Cannot set new CRS on a map that doesn't "
1665
            "have any projection set. Please make sure your mapfile "
1666
            "has a projection defined at the top level.",
1667
            "msWMSLoadGetMapParams()");
1668
        return msWMSException(map, nVersion, "InvalidCRS",
×
1669
                              wms_exception_format);
×
1670
      } else {
1671
        msSetErrorWithStatus(
×
1672
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1673
            "Cannot set new SRS on a map that doesn't "
1674
            "have any projection set. Please make sure your mapfile "
1675
            "has a projection defined at the top level.",
1676
            "msWMSLoadGetMapParams()");
1677
        return msWMSException(map, nVersion, "InvalidSRS",
×
1678
                              wms_exception_format);
1679
      }
1680
    }
1681

1682
    msInitProjection(&newProj);
483✔
1683
    msProjectionInheritContextFrom(&newProj, &map->projection);
483✔
1684
    if (srsbuffer.size() > 1) {
483✔
1685
      int nTmp;
1686

1687
      if (nVersion >= OWS_1_3_0)
481✔
1688
        nTmp = msLoadProjectionStringEPSG(&newProj, srsbuffer.c_str());
203✔
1689
      else
1690
        nTmp = msLoadProjectionString(&newProj, srsbuffer.c_str());
278✔
1691
      if (nTmp != 0) {
481✔
1692
        msFreeProjection(&newProj);
×
1693
        return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1694
      }
1695
    }
1696

1697
    if (nonsquare_enabled ||
801✔
1698
        msProjectionsDiffer(&(map->projection), &newProj)) {
318✔
1699
      msMapSetLayerProjections(map);
264✔
1700
    }
1701
    msFreeProjection(&newProj);
483✔
1702
  }
1703

1704
  /* apply the srs to the map file. This is only done after validating */
1705
  /* that the srs given as parameter is valid for all layers */
1706
  if (srsbuffer.size() > 1) {
503✔
1707
    int nTmp;
1708
    msFreeProjectionExceptContext(&map->projection);
481✔
1709
    if (nVersion >= OWS_1_3_0)
481✔
1710
      nTmp = msLoadProjectionStringEPSG(&(map->projection), srsbuffer.c_str());
203✔
1711
    else
1712
      nTmp = msLoadProjectionString(&(map->projection), srsbuffer.c_str());
278✔
1713

1714
    if (nTmp != 0)
481✔
1715
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1716

1717
    nTmp = GetMapserverUnitUsingProj(&(map->projection));
481✔
1718
    if (nTmp != -1) {
481✔
1719
      map->units = static_cast<MS_UNITS>(nTmp);
481✔
1720
    }
1721
  }
1722

1723
  if (sld_url || sld_body) {
503✔
1724
    char *pszLayerNames = NULL;
144✔
1725
    const int nLayersBefore = map->numlayers;
144✔
1726

1727
    /* -------------------------------------------------------------------- */
1728
    /*      if LAYERS parameter was not given, set all layers to off        */
1729
    /* -------------------------------------------------------------------- */
1730
    if (validlayers == 0) { /*no LAYERS parameter is give*/
144✔
1731
      for (int j = 0; j < map->numlayers; j++) {
×
1732
        if (GET_LAYER(map, j)->status != MS_DEFAULT)
×
1733
          GET_LAYER(map, j)->status = MS_OFF;
×
1734
      }
1735
    }
1736

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

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

1788
  /* Validate Styles :
1789
  ** MapServer advertise styles through the group setting in a class object.
1790
  ** If no styles are set MapServer expects to have empty values
1791
  ** for the styles parameter (...&STYLES=&...) Or for multiple Styles/Layers,
1792
  ** we could have ...&STYLES=,,,. If that is not the
1793
  ** case, we generate an exception.
1794
  */
1795
  if (styles && strlen(styles) > 0) {
496✔
1796
    bool hasCheckedLayerUnicity = false;
1797
    int n = 0;
17✔
1798
    int layerCopyIndex;
1799

1800
    char **tokens = msStringSplitComplex(styles, ",", &n, MS_ALLOWEMPTYTOKENS);
17✔
1801
    for (int i = 0; i < n; i++) {
45✔
1802
      if (tokens[i] && strlen(tokens[i]) > 0 &&
28✔
1803
          strcasecmp(tokens[i], "default") != 0) {
18✔
1804
        if (!hasCheckedLayerUnicity) {
15✔
1805
          hasCheckedLayerUnicity = true;
1806
          bool bLayerInserted = false;
1807

1808
          /* --------------------------------------------------------------------
1809
           */
1810
          /*      If the same layer is given more that once, we need to */
1811
          /*      duplicate it. */
1812
          /* --------------------------------------------------------------------
1813
           */
1814
          for (size_t m = 0; m < wmslayers.size(); m++) {
28✔
1815
            for (size_t l = m + 1; l < wmslayers.size(); l++) {
25✔
1816
              const int nIndex = msGetLayerIndex(map, wmslayers[m].c_str());
8✔
1817
              if (nIndex != -1 &&
8✔
1818
                  strcasecmp(wmslayers[m].c_str(), wmslayers[l].c_str()) == 0) {
8✔
1819
                layerObj *psTmpLayer = (layerObj *)malloc(sizeof(layerObj));
6✔
1820
                initLayer(psTmpLayer, map);
6✔
1821
                msCopyLayer(psTmpLayer, GET_LAYER(map, nIndex));
6✔
1822
                /* open the source layer */
1823
                if (!psTmpLayer->vtable)
6✔
1824
                  msInitializeVirtualTable(psTmpLayer);
6✔
1825

1826
                /*make the name unique*/
1827
                char tmpId[128];
1828
                snprintf(tmpId, sizeof(tmpId), "%lx_%x_%d", (long)time(NULL),
6✔
1829
                         (int)getpid(), map->numlayers);
6✔
1830
                if (psTmpLayer->name)
6✔
1831
                  msFree(psTmpLayer->name);
6✔
1832
                psTmpLayer->name = msStrdup(tmpId);
6✔
1833
                wmslayers[l] = tmpId;
1834

1835
                layerCopyIndex = msInsertLayer(map, psTmpLayer, -1);
6✔
1836

1837
                // expand the array mapping map layer index to filter indexes
1838
                ows_request->layerwmsfilterindex =
6✔
1839
                    (int *)msSmallRealloc(ows_request->layerwmsfilterindex,
6✔
1840
                                          map->numlayers * sizeof(int));
6✔
1841
                ows_request->layerwmsfilterindex[layerCopyIndex] =
6✔
1842
                    l; // the filter index matches the index of the layer name
1843
                       // in the WMS param
1844

1845
                bLayerInserted = true;
1846
                /* layer was copied, we need to decrement its refcount */
1847
                MS_REFCNT_DECR(psTmpLayer);
6✔
1848
              }
1849
            }
1850
          }
1851

1852
          if (bLayerInserted) {
11✔
1853
            msOWSRequestLayersEnabled(map, "M", "GetMap", ows_request);
4✔
1854
          }
1855
        }
1856

1857
        if (static_cast<int>(wmslayers.size()) == n) {
15✔
1858
          for (int j = 0; j < map->numlayers; j++) {
128✔
1859
            layerObj *lp = GET_LAYER(map, j);
113✔
1860
            if ((lp->name && strcasecmp(lp->name, wmslayers[i].c_str()) == 0) ||
113✔
1861
                (lp->group &&
99✔
1862
                 strcasecmp(lp->group, wmslayers[i].c_str()) == 0)) {
4✔
1863
              bool found = false;
1864
              for (int k = 0; k < lp->numclasses; k++) {
26✔
1865
                if (lp->_class[k]->group &&
26✔
1866
                    strcasecmp(lp->_class[k]->group, tokens[i]) == 0) {
26✔
1867
                  msFree(lp->classgroup);
14✔
1868
                  lp->classgroup = msStrdup(tokens[i]);
14✔
1869
                  found = true;
1870
                  break;
1871
                }
1872
              }
1873
              if (!found) {
1874
                msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
1875
                                     "Style (%s) not defined on layer.",
1876
                                     "msWMSLoadGetMapParams()", tokens[i]);
1877
                msFreeCharArray(tokens, n);
×
1878

1879
                return msWMSException(map, nVersion, "StyleNotDefined",
×
1880
                                      wms_exception_format);
×
1881
              }
1882
              /* Check the style of the root layer */
1883
            } else if (map->name &&
99✔
1884
                       strcasecmp(map->name, wmslayers[i].c_str()) == 0) {
99✔
1885
              const char *styleName =
1886
                  msOWSLookupMetadata(&(map->web.metadata), "MO", "style_name");
15✔
1887
              if (styleName == NULL)
15✔
1888
                styleName = "default";
1889
              char *pszEncodedStyleName = msEncodeHTMLEntities(styleName);
15✔
1890
              if (strcasecmp(pszEncodedStyleName, tokens[i]) != 0) {
15✔
1891
                msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
1892
                                     "Style (%s) not defined on root layer.",
1893
                                     "msWMSLoadGetMapParams()", tokens[i]);
1894
                msFreeCharArray(tokens, n);
×
1895
                msFree(pszEncodedStyleName);
×
1896

1897
                return msWMSException(map, nVersion, "StyleNotDefined",
×
1898
                                      wms_exception_format);
1899
              }
1900
              msFree(pszEncodedStyleName);
15✔
1901
            }
1902
          }
1903
        } else {
1904
          msSetErrorWithStatus(
×
1905
              MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1906
              "Invalid style (%s). Mapserver is expecting an empty "
1907
              "string for the STYLES : STYLES= or STYLES=,,, or using "
1908
              "keyword default  STYLES=default,default, ...",
1909
              "msWMSLoadGetMapParams()", styles);
1910
          msFreeCharArray(tokens, n);
×
1911
          return msWMSException(map, nVersion, "StyleNotDefined",
×
1912
                                wms_exception_format);
1913
        }
1914
      }
1915
    }
1916
    msFreeCharArray(tokens, n);
17✔
1917
  }
1918

1919
  /*
1920
  ** WMS extents are edge to edge while MapServer extents are center of
1921
  ** pixel to center of pixel.  Here we try to adjust the WMS extents
1922
  ** in by half a pixel.  We wait till here because we want to ensure we
1923
  ** are doing this in terms of the correct WIDTH and HEIGHT.
1924
  */
1925
  if (adjust_extent && map->width > 1 && map->height > 1 &&
496✔
1926
      !bbox_pixel_is_point) {
1927
    double dx, dy;
1928

1929
    dx = (map->extent.maxx - map->extent.minx) / map->width;
476✔
1930
    map->extent.minx += dx * 0.5;
476✔
1931
    map->extent.maxx -= dx * 0.5;
476✔
1932

1933
    dy = (map->extent.maxy - map->extent.miny) / map->height;
476✔
1934
    map->extent.miny += dy * 0.5;
476✔
1935
    map->extent.maxy -= dy * 0.5;
476✔
1936
  }
1937

1938
  if (request && strcasecmp(request, "DescribeLayer") != 0) {
496✔
1939
    if (!srsfound) {
477✔
1940
      if (nVersion >= OWS_1_3_0)
3✔
1941
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
2✔
1942
                             "Missing required parameter CRS",
1943
                             "msWMSLoadGetMapParams()");
1944
      else
1945
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
1946
                             "Missing required parameter SRS",
1947
                             "msWMSLoadGetMapParams()");
1948

1949
      return msWMSException(map, nVersion, "MissingParameterValue",
3✔
1950
                            wms_exception_format);
1951
    }
1952

1953
    if (!bboxfound) {
474✔
1954
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
1955
                           "Missing required parameter BBOX",
1956
                           "msWMSLoadGetMapParams()");
1957
      return msWMSException(map, nVersion, "MissingParameterValue",
1✔
1958
                            wms_exception_format);
1959
    }
1960

1961
    if (!formatfound && (strcasecmp(request, "GetMap") == 0 ||
473✔
1962
                         strcasecmp(request, "map") == 0)) {
6✔
1963
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
1964
                           "Missing required parameter FORMAT",
1965
                           "msWMSLoadGetMapParams()");
1966
      return msWMSException(map, nVersion, "MissingParameterValue",
1✔
1967
                            wms_exception_format);
1968
    }
1969

1970
    if (!widthfound) {
472✔
1971
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
1972
                           "Missing required parameter WIDTH",
1973
                           "msWMSLoadGetMapParams()");
1974
      return msWMSException(map, nVersion, "MissingParameterValue",
1✔
1975
                            wms_exception_format);
1976
    }
1977

1978
    if (!heightfound) {
471✔
1979
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
1980
                           "Missing required parameter HEIGHT",
1981
                           "msWMSLoadGetMapParams()");
1982
      return msWMSException(map, nVersion, "MissingParameterValue",
1✔
1983
                            wms_exception_format);
1984
    }
1985

1986
    if (styles == nullptr && sld_url == nullptr && sld_body == nullptr &&
82✔
1987
        (strcasecmp(request, "GetMap") == 0 ||
5✔
1988
         strcasecmp(request, "GetFeatureInfo") == 0) &&
475✔
1989
        msOWSLookupMetadata(&(map->web.metadata), "M",
1✔
1990
                            "allow_getmap_without_styles") == nullptr) {
1991
      msSetErrorWithStatus(
1✔
1992
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1993
          "Missing required parameter STYLES. Note to service administrators: "
1994
          "defining the \"wms_allow_getmap_without_styles\" \"true\" "
1995
          "MAP.WEB.METADATA "
1996
          "item will disable this check (backward compatibility with behavior "
1997
          "of MapServer < 8.0)",
1998
          "msWMSLoadGetMapParams()");
1999
      return msWMSException(map, nVersion, "MissingParameterValue",
1✔
2000
                            wms_exception_format);
2001
    }
2002
  }
2003

2004
  /*
2005
  ** Apply vendor-specific filter if specified
2006
  */
2007
  if (filter) {
488✔
2008
    if (sld_url || sld_body) {
13✔
2009
      msSetErrorWithStatus(
×
2010
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
2011
          "Vendor-specific FILTER parameter cannot be used with SLD or "
2012
          "SLD_BODY.",
2013
          "msWMSLoadGetMapParams()");
2014
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
2015
    }
2016

2017
    if (msWMSApplyFilter(map, nVersion, filter, need_axis_swap,
13✔
2018
                         wms_exception_format, ows_request) == MS_FAILURE) {
2019
      return MS_FAILURE; /* msWMSException(map, nVersion,
2020
                            "InvalidFilterRequest"); */
2021
    }
2022
  }
2023

2024
  return MS_SUCCESS;
2025
}
544✔
2026

2027
/*
2028
**
2029
*/
2030
static void msWMSPrintRequestCap(int nVersion, const char *request,
275✔
2031
                                 const char *script_url, const char *formats,
2032
                                 ...) {
2033
  va_list argp;
2034

2035
  msIO_printf("    <%s>\n", request);
275✔
2036

2037
  /* We expect to receive a NULL-terminated args list of formats */
2038
  va_start(argp, formats);
275✔
2039
  const char *fmt = formats;
2040
  while (fmt != NULL) {
1,285✔
2041
    /* Special case for early WMS with subelements in Format (bug 908) */
2042
    char *encoded;
2043
    if (nVersion < OWS_1_1_0) {
1,010✔
2044
      encoded = msStrdup(fmt);
6✔
2045
    }
2046

2047
    /* otherwise we HTML code special characters */
2048
    else {
2049
      encoded = msEncodeHTMLEntities(fmt);
1,004✔
2050
    }
2051

2052
    msIO_printf("      <Format>%s</Format>\n", encoded);
1,010✔
2053
    msFree(encoded);
1,010✔
2054

2055
    fmt = va_arg(argp, const char *);
1,010✔
2056
  }
2057
  va_end(argp);
275✔
2058

2059
  msIO_printf("      <DCPType>\n");
275✔
2060
  msIO_printf("        <HTTP>\n");
275✔
2061
  /* The URL should already be HTML encoded. */
2062
  if (nVersion == OWS_1_0_0) {
275✔
2063
    msIO_printf("          <Get onlineResource=\"%s\" />\n", script_url);
6✔
2064
    msIO_printf("          <Post onlineResource=\"%s\" />\n", script_url);
6✔
2065
  } else {
2066
    msIO_printf("          <Get><OnlineResource "
269✔
2067
                "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
2068
                "xlink:href=\"%s\"/></Get>\n",
2069
                script_url);
2070
    msIO_printf("          <Post><OnlineResource "
269✔
2071
                "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
2072
                "xlink:href=\"%s\"/></Post>\n",
2073
                script_url);
2074
  }
2075

2076
  msIO_printf("        </HTTP>\n");
275✔
2077
  msIO_printf("      </DCPType>\n");
275✔
2078
  msIO_printf("    </%s>\n", request);
275✔
2079
}
275✔
2080

2081
void msWMSPrintAttribution(FILE *stream, const char *tabspace,
240✔
2082
                           hashTableObj *metadata,
2083
                           const char * /*namespaces*/) {
2084
  if (stream && metadata) {
240✔
2085
    const char *title =
2086
        msOWSLookupMetadata(metadata, "MO", "attribution_title");
240✔
2087
    const char *onlineres =
2088
        msOWSLookupMetadata(metadata, "MO", "attribution_onlineresource");
240✔
2089
    const char *logourl =
2090
        msOWSLookupMetadata(metadata, "MO", "attribution_logourl_width");
240✔
2091

2092
    if (title || onlineres || logourl) {
240✔
2093
      msIO_printf("%s<Attribution>\n", tabspace);
3✔
2094
      if (title) {
3✔
2095
        char *pszEncodedValue = msEncodeHTMLEntities(title);
3✔
2096
        msIO_fprintf(stream, "%s%s<Title>%s</Title>\n", tabspace, tabspace,
3✔
2097
                     pszEncodedValue);
2098
        free(pszEncodedValue);
3✔
2099
      }
2100

2101
      if (onlineres) {
3✔
2102
        char *pszEncodedValue = msEncodeHTMLEntities(onlineres);
1✔
2103
        msIO_fprintf(
1✔
2104
            stream,
2105
            "%s%s<OnlineResource xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
2106
            "xlink:href=\"%s\"/>\n",
2107
            tabspace, tabspace, pszEncodedValue);
2108
        free(pszEncodedValue);
1✔
2109
      }
2110

2111
      if (logourl) {
3✔
2112
        msOWSPrintURLType(stream, metadata, "MO", "attribution_logourl",
1✔
2113
                          OWS_NOERR, NULL, "LogoURL", NULL, " width=\"%s\"",
2114
                          " height=\"%s\"",
2115
                          ">\n             <Format>%s</Format",
2116
                          "\n             <OnlineResource "
2117
                          "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
2118
                          " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
2119
                          "          ",
2120
                          MS_FALSE, MS_TRUE, MS_TRUE, MS_TRUE, MS_TRUE, NULL,
2121
                          NULL, NULL, NULL, NULL, "        ");
2122
      }
2123
      msIO_printf("%s</Attribution>\n", tabspace);
3✔
2124
    }
2125
  }
2126
}
240✔
2127

2128
/*
2129
** msWMSPrintScaleHint()
2130
**
2131
** Print a Min/MaxScaleDenominator tag for the layer if applicable.
2132
** used for WMS >=1.3.0
2133
*/
2134
void msWMSPrintScaleDenominator(const char *tabspace, double minscaledenom,
118✔
2135
                                double maxscaledenom) {
2136
  if (minscaledenom > 0)
118✔
2137
    msIO_printf("%s<MinScaleDenominator>%g</MinScaleDenominator>\n", tabspace,
8✔
2138
                minscaledenom);
2139

2140
  if (maxscaledenom > 0)
118✔
2141
    msIO_printf("%s<MaxScaleDenominator>%g</MaxScaleDenominator>\n", tabspace,
2✔
2142
                maxscaledenom);
2143
}
118✔
2144

2145
/*
2146
** msWMSPrintScaleHint()
2147
**
2148
** Print a ScaleHint tag for this layer if applicable.
2149
**
2150
** (see WMS 1.1.0 sect. 7.1.5.4) The WMS defines the scalehint values as
2151
** the ground distance in meters of the southwest to northeast diagonal of
2152
** the central pixel of a map.  ScaleHint values are the min and max
2153
** recommended values of that diagonal.
2154
*/
2155
void msWMSPrintScaleHint(const char *tabspace, double minscaledenom,
124✔
2156
                         double maxscaledenom, double resolution) {
2157
  double scalehintmin = 0.0, scalehintmax = 0.0;
2158

2159
  const double diag = sqrt(2.0);
2160

2161
  if (minscaledenom > 0)
124✔
2162
    scalehintmin =
5✔
2163
        diag * (minscaledenom / resolution) / msInchesPerUnit(MS_METERS, 0);
5✔
2164
  if (maxscaledenom > 0)
124✔
2165
    scalehintmax =
2✔
2166
        diag * (maxscaledenom / resolution) / msInchesPerUnit(MS_METERS, 0);
2✔
2167

2168
  if (scalehintmin > 0.0 || scalehintmax > 0.0) {
124✔
2169
    msIO_printf("%s<ScaleHint min=\"%.15g\" max=\"%.15g\" />\n", tabspace,
5✔
2170
                scalehintmin, scalehintmax);
2171
    if (scalehintmax == 0.0)
5✔
2172
      msIO_printf("%s<!-- WARNING: Only MINSCALEDENOM and no MAXSCALEDENOM "
3✔
2173
                  "specified in "
2174
                  "the mapfile. A default value of 0 has been returned for the "
2175
                  "Max ScaleHint but this is probably not what you want. -->\n",
2176
                  tabspace);
2177
  }
2178
}
124✔
2179

2180
/*
2181
** msWMSPrintAuthorityURL()
2182
**
2183
** Print an AuthorityURL tag if applicable.
2184
*/
2185
void msWMSPrintAuthorityURL(FILE *stream, const char *tabspace,
240✔
2186
                            hashTableObj *metadata, const char *namespaces) {
2187
  if (stream && metadata) {
240✔
2188
    const char *pszWmsAuthorityName =
2189
        msOWSLookupMetadata(metadata, namespaces, "authorityurl_name");
240✔
2190
    const char *pszWMSAuthorityHref =
2191
        msOWSLookupMetadata(metadata, namespaces, "authorityurl_href");
240✔
2192

2193
    /* AuthorityURL only makes sense if you have *both* the name and url */
2194
    if (pszWmsAuthorityName && pszWMSAuthorityHref) {
240✔
2195
      msOWSPrintEncodeMetadata(
7✔
2196
          stream, metadata, namespaces, "authorityurl_name", OWS_NOERR,
2197
          (std::string(tabspace) + "<AuthorityURL name=\"%s\">\n").c_str(),
7✔
2198
          NULL);
2199
      msOWSPrintEncodeMetadata(
7✔
2200
          stream, metadata, namespaces, "authorityurl_href", OWS_NOERR,
2201
          (std::string(tabspace) +
7✔
2202
           "  <OnlineResource xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
2203
           "xlink:href=\"%s\"/>\n")
2204
              .c_str(),
2205
          NULL);
2206
      msIO_printf("%s</AuthorityURL>\n", tabspace);
7✔
2207
    } else if (pszWmsAuthorityName || pszWMSAuthorityHref) {
233✔
2208
      msIO_printf(
×
2209
          "%s<!-- WARNING: Both wms_authorityurl_name and "
2210
          "wms_authorityurl_href must be set to output an AuthorityURL -->\n",
2211
          tabspace);
2212
    }
2213
  }
2214
}
240✔
2215

2216
/*
2217
** msWMSPrintIdentifier()
2218
**
2219
** Print an Identifier tag if applicable.
2220
*/
2221
void msWMSPrintIdentifier(FILE *stream, const char *tabspace,
240✔
2222
                          hashTableObj *metadata, const char *namespaces) {
2223
  if (stream && metadata) {
240✔
2224
    const char *pszWMSIdentifierAuthority =
2225
        msOWSLookupMetadata(metadata, namespaces, "identifier_authority");
240✔
2226
    const char *pszWMSIdentifierValue =
2227
        msOWSLookupMetadata(metadata, namespaces, "identifier_value");
240✔
2228

2229
    /* Identifier only makes sense if you have *both* the authority and value */
2230
    if (pszWMSIdentifierAuthority && pszWMSIdentifierValue) {
240✔
2231
      msOWSPrintEncodeMetadata(
73✔
2232
          stream, metadata, namespaces, "identifier_authority", OWS_NOERR,
2233
          (std::string(tabspace) + "<Identifier authority=\"%s\">").c_str(),
73✔
2234
          NULL);
2235
      msOWSPrintEncodeMetadata(stream, metadata, namespaces, "identifier_value",
73✔
2236
                               OWS_NOERR, "%s</Identifier>\n", NULL);
2237
    } else if (pszWMSIdentifierAuthority || pszWMSIdentifierValue) {
167✔
2238
      msIO_printf(
×
2239
          "%s<!-- WARNING: Both wms_identifier_authority and "
2240
          "wms_identifier_value must be set to output an Identifier -->\n",
2241
          tabspace);
2242
    }
2243
  }
2244
}
240✔
2245

2246
/*
2247
** msWMSPrintKeywordlist()
2248
**
2249
** Print a Keywordlist tag if applicable.
2250
*/
2251
void msWMSPrintKeywordlist(FILE *stream, const char *tabspace, const char *name,
302✔
2252
                           hashTableObj *metadata, const char *namespaces,
2253
                           int nVersion) {
2254
  std::string newname(name); /* max. rootlayer_keywordlist_items          */
302✔
2255
  newname += "_items";
2256

2257
  std::string vocname(name); /* max. rootlayer_keywordlist_vocabulary     */
302✔
2258
  vocname += "_vocabulary";
2259

2260
  if (nVersion == OWS_1_0_0) {
302✔
2261
    /* <Keywords> in V 1.0.0 */
2262
    /* The 1.0.0 spec doesn't specify which delimiter to use so let's use spaces
2263
     */
2264
    msOWSPrintEncodeMetadataList(
4✔
2265
        stream, metadata, namespaces, name,
2266
        (std::string(tabspace) + "<Keywords>").c_str(),
8✔
2267
        (std::string(tabspace) + "</Keywords>\n").c_str(), "%s ", NULL);
8✔
2268
  } else if (msOWSLookupMetadata(metadata, namespaces, name) ||
423✔
2269
             msOWSLookupMetadata(metadata, namespaces, newname.c_str()) ||
423✔
2270
             msOWSLookupMetadata(metadata, namespaces, vocname.c_str())) {
125✔
2271
    /* <KeywordList><Keyword> ... in V1.0.6+ */
2272
    msIO_printf("%s<KeywordList>\n", tabspace);
182✔
2273
    std::string template1(tabspace);
182✔
2274
    template1 += "    <Keyword>%s</Keyword>\n";
2275
    /* print old styled ..._keywordlist */
2276
    msOWSPrintEncodeMetadataList(stream, metadata, namespaces, name, NULL, NULL,
182✔
2277
                                 template1.c_str(), NULL);
2278
    /* print new styled ..._keywordlist_items */
2279
    msOWSPrintEncodeMetadataList(stream, metadata, namespaces, newname.c_str(),
182✔
2280
                                 NULL, NULL, template1.c_str(), NULL);
2281

2282
    /* find out if there's a vocabulary list set */
2283
    const char *vocabularylist =
2284
        msOWSLookupMetadata(metadata, namespaces, vocname.c_str());
182✔
2285
    if (vocabularylist && nVersion >= OWS_1_3_0) {
182✔
2286
      const auto tokens = msStringSplit(vocabularylist, ',');
9✔
2287
      for (const auto &token : tokens) {
18✔
2288
        msOWSPrintEncodeMetadataList(
9✔
2289
            stream, metadata, namespaces,
2290
            (std::string(name) + '_' + token + "_items").c_str(), NULL, NULL,
18✔
2291
            (std::string(tabspace) + "    <Keyword vocabulary=\"" + token +
18✔
2292
             "\">%s</Keyword>\n")
2293
                .c_str(),
2294
            NULL);
2295
      }
2296
    }
9✔
2297
    msIO_printf("%s</KeywordList>\n", tabspace);
182✔
2298
  }
2299
}
302✔
2300

2301
/*
2302
** msDumpLayer()
2303
*/
2304
static int msDumpLayer(mapObj *map, layerObj *lp, int nVersion,
187✔
2305
                       const char *script_url_encoded, const char *indent,
2306
                       const char *validated_language, int grouplayer,
2307
                       int hasQueryableSubLayers) {
2308
  rectObj ext;
2309
  char **classgroups = NULL;
2310
  int iclassgroups = 0;
2311
  char *pszMapEPSG, *pszLayerEPSG;
2312

2313
  /* if the layer status is set to MS_DEFAULT, output a warning */
2314
  if (lp->status == MS_DEFAULT)
187✔
2315
    msIO_fprintf(stdout,
4✔
2316
                 "<!-- WARNING: This layer has its status set to DEFAULT and "
2317
                 "will always be displayed when doing a GetMap request even if "
2318
                 "it is not requested by the client. This is not in line with "
2319
                 "the expected behavior of a WMS server. Using status ON or "
2320
                 "OFF is recommended. -->\n");
2321

2322
  if (nVersion < OWS_1_1_0) {
187✔
2323
    msIO_printf("%s    <Layer queryable=\"%d\">\n", indent,
1✔
2324
                hasQueryableSubLayers || msIsLayerQueryable(lp));
1✔
2325
  } else {
2326
    /* 1.1.0 and later: opaque and cascaded are new. */
2327
    int cascaded = 0, opaque = 0;
2328
    const char *value = msOWSLookupMetadata(&(lp->metadata), "MO", "opaque");
186✔
2329
    if (value != NULL)
186✔
2330
      opaque = atoi(value);
2331
    if (lp->connectiontype == MS_WMS)
186✔
2332
      cascaded = 1;
2333

2334
    msIO_printf(
186✔
2335
        "%s    <Layer queryable=\"%d\" opaque=\"%d\" cascaded=\"%d\">\n",
2336
        indent, hasQueryableSubLayers || msIsLayerQueryable(lp), opaque,
186✔
2337
        cascaded);
2338
  }
2339

2340
  if (lp->name && strlen(lp->name) > 0 &&
374✔
2341
      (msIsXMLTagValid(lp->name) == MS_FALSE || isdigit(lp->name[0])))
374✔
2342
    msIO_fprintf(stdout,
×
2343
                 "<!-- WARNING: The layer name '%s' might contain spaces or "
2344
                 "invalid characters or may start with a number. This could "
2345
                 "lead to potential problems. -->\n",
2346
                 lp->name);
2347
  msOWSPrintEncodeParam(stdout, "LAYER.NAME", lp->name, OWS_NOERR,
187✔
2348
                        "        <Name>%s</Name>\n", NULL);
2349

2350
  /* the majority of this section is dependent on appropriately named metadata
2351
   * in the LAYER object */
2352
  msOWSPrintEncodeMetadata2(stdout, &(lp->metadata), "MO", "title", OWS_WARN,
187✔
2353
                            "        <Title>%s</Title>\n", lp->name,
187✔
2354
                            validated_language);
2355

2356
  msOWSPrintEncodeMetadata2(stdout, &(lp->metadata), "MO", "abstract",
187✔
2357
                            OWS_NOERR, "        <Abstract>%s</Abstract>\n",
2358
                            NULL, validated_language);
2359

2360
  msWMSPrintKeywordlist(stdout, "        ", "keywordlist", &(lp->metadata),
187✔
2361
                        "MO", nVersion);
2362

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

2421
  /* If layer has no proj set then use map->proj for bounding box. */
2422
  if (msOWSGetLayerExtent(map, lp, "MO", &ext) == MS_SUCCESS) {
187✔
2423
    if (lp->projection.numargs > 0) {
174✔
2424
      if (nVersion >= OWS_1_3_0)
167✔
2425
        msOWSPrintEX_GeographicBoundingBox(stdout, "        ", &(ext),
82✔
2426
                                           &(lp->projection));
2427
      else
2428
        msOWSPrintLatLonBoundingBox(stdout, "        ", &(ext),
85✔
2429
                                    &(lp->projection), NULL, OWS_WMS);
2430

2431
      msOWSPrintBoundingBox(stdout, "        ", &(ext), &(lp->projection),
167✔
2432
                            &(lp->metadata), &(map->web.metadata), "MO",
2433
                            nVersion);
2434
    } else {
2435
      if (nVersion >= OWS_1_3_0)
7✔
2436
        msOWSPrintEX_GeographicBoundingBox(stdout, "        ", &(ext),
6✔
2437
                                           &(map->projection));
2438
      else
2439
        msOWSPrintLatLonBoundingBox(stdout, "        ", &(ext),
1✔
2440
                                    &(map->projection), NULL, OWS_WMS);
2441
      msOWSPrintBoundingBox(stdout, "        ", &(ext), &(map->projection),
7✔
2442
                            &(lp->metadata), &(map->web.metadata), "MO",
2443
                            nVersion);
2444
    }
2445
  } else {
2446
    if (nVersion >= OWS_1_3_0)
13✔
2447
      msIO_printf(
3✔
2448
          "        <!-- WARNING: Optional Ex_GeographicBoundingBox could not "
2449
          "be established for this layer.  Consider setting the EXTENT in the "
2450
          "LAYER object, or wms_extent metadata. Also check that your data "
2451
          "exists in the DATA statement -->\n");
2452
    else
2453
      msIO_printf("        <!-- WARNING: Optional LatLonBoundingBox could not "
10✔
2454
                  "be established for this layer.  Consider setting the EXTENT "
2455
                  "in the LAYER object, or wms_extent metadata. Also check "
2456
                  "that your data exists in the DATA statement -->\n");
2457
  }
2458

2459
  /* time support */
2460
  const char *pszWmsTimeExtent =
2461
      msOWSLookupMetadata(&(lp->metadata), "MO", "timeextent");
187✔
2462
  if (pszWmsTimeExtent) {
187✔
2463
    const char *pszWmsTimeDefault =
2464
        msOWSLookupMetadata(&(lp->metadata), "MO", "timedefault");
4✔
2465

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

2479
    else {
2480
      msIO_fprintf(stdout,
2✔
2481
                   "        <Dimension name=\"time\" units=\"ISO8601\"/>\n");
2482
      if (pszWmsTimeDefault)
2✔
2483
        msIO_fprintf(stdout,
×
2484
                     "        <Extent name=\"time\" default=\"%s\" "
2485
                     "nearestValue=\"0\">%s</Extent>\n",
2486
                     pszWmsTimeDefault, pszWmsTimeExtent);
2487
      else
2488
        msIO_fprintf(
2✔
2489
            stdout,
2490
            "        <Extent name=\"time\" nearestValue=\"0\">%s</Extent>\n",
2491
            pszWmsTimeExtent);
2492
    }
2493
  }
2494

2495
  /*dimensions support: elevation + other user defined dimensions*/
2496
  const char *pszDimensionlist =
2497
      msOWSLookupMetadata(&(lp->metadata), "M", "dimensionlist");
187✔
2498
  if (pszDimensionlist) {
187✔
2499
    auto tokens = msStringSplit(pszDimensionlist, ',');
6✔
2500
    for (auto &dimension : tokens) {
14✔
2501
      /*check if manadatory unit and extent are set. Item should also be set.
2502
       * default value is optional*/
2503
      msStringTrim(dimension);
8✔
2504

2505
      const char *pszDimensionItem = msOWSLookupMetadata(
8✔
2506
          &(lp->metadata), "M", (dimension + "_item").c_str());
8✔
2507
      const char *pszDimensionExtent = msOWSLookupMetadata(
8✔
2508
          &(lp->metadata), "M", (dimension + "_extent").c_str());
8✔
2509
      const char *pszDimensionUnit = msOWSLookupMetadata(
8✔
2510
          &(lp->metadata), "M", (dimension + "_units").c_str());
8✔
2511
      const char *pszDimensionDefault = msOWSLookupMetadata(
8✔
2512
          &(lp->metadata), "M", (dimension + "_default").c_str());
8✔
2513

2514
      if (pszDimensionItem && pszDimensionExtent && pszDimensionUnit) {
8✔
2515
        if (nVersion >= OWS_1_3_0) {
8✔
2516
          if (pszDimensionDefault && strlen(pszDimensionDefault) > 0)
4✔
2517
            msIO_fprintf(
1✔
2518
                stdout,
2519
                "        <Dimension name=\"%s\" units=\"%s\" default=\"%s\" "
2520
                "multipleValues=\"1\" nearestValue=\"0\">%s</Dimension>\n",
2521
                dimension.c_str(), pszDimensionUnit, pszDimensionDefault,
2522
                pszDimensionExtent);
2523
          else
2524
            msIO_fprintf(
3✔
2525
                stdout,
2526
                "        <Dimension name=\"%s\" units=\"%s\"  "
2527
                "multipleValues=\"1\"  nearestValue=\"0\">%s</Dimension>\n",
2528
                dimension.c_str(), pszDimensionUnit, pszDimensionExtent);
2529
        } else {
2530
          msIO_fprintf(stdout,
4✔
2531
                       "        <Dimension name=\"%s\" units=\"%s\"/>\n",
2532
                       dimension.c_str(), pszDimensionUnit);
2533
          if (pszDimensionDefault && strlen(pszDimensionDefault) > 0)
4✔
2534
            msIO_fprintf(stdout,
1✔
2535
                         "        <Extent name=\"%s\" default=\"%s\" "
2536
                         "nearestValue=\"0\">%s</Extent>\n",
2537
                         dimension.c_str(), pszDimensionDefault,
2538
                         pszDimensionExtent);
2539
          else
2540
            msIO_fprintf(
3✔
2541
                stdout,
2542
                "        <Extent name=\"%s\" nearestValue=\"0\">%s</Extent>\n",
2543
                dimension.c_str(), pszDimensionExtent);
2544
        }
2545
      }
2546
    }
2547
  }
6✔
2548

2549
  /* AuthorityURL support and Identifier support, only available >= WMS 1.1.0 */
2550
  if (nVersion >= OWS_1_1_0) {
187✔
2551
    msWMSPrintAttribution(stdout, "    ", &(lp->metadata), "MO");
186✔
2552
    msWMSPrintAuthorityURL(stdout, "        ", &(lp->metadata), "MO");
186✔
2553
    msWMSPrintIdentifier(stdout, "        ", &(lp->metadata), "MO");
186✔
2554
  }
2555

2556
  if (nVersion >= OWS_1_1_0) {
2557
    const char *metadataurl_list =
2558
        msOWSLookupMetadata(&(lp->metadata), "MO", "metadataurl_list");
186✔
2559
    if (metadataurl_list) {
186✔
2560
      const auto tokens = msStringSplit(metadataurl_list, ' ');
1✔
2561
      for (const auto &token : tokens) {
3✔
2562
        std::string key("metadataurl_");
2✔
2563
        key += token;
2564
        msOWSPrintURLType(stdout, &(lp->metadata), "MO", key.c_str(), OWS_NOERR,
2✔
2565
                          NULL, "MetadataURL", " type=\"%s\"", NULL, NULL,
2566
                          ">\n          <Format>%s</Format",
2567
                          "\n          <OnlineResource xmlns:xlink=\""
2568
                          "http://www.w3.org/1999/xlink\" "
2569
                          "xlink:type=\"simple\" xlink:href=\"%s\"/>\n        ",
2570
                          MS_TRUE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
2571
                          NULL, NULL, NULL, NULL, "        ");
2572
      }
2573
    } else {
1✔
2574
      if (!msOWSLookupMetadata(&(lp->metadata), "MO", "metadataurl_href"))
185✔
2575
        msMetadataSetGetMetadataURL(lp, script_url_encoded);
96✔
2576

2577
      msOWSPrintURLType(stdout, &(lp->metadata), "MO", "metadataurl", OWS_NOERR,
185✔
2578
                        NULL, "MetadataURL", " type=\"%s\"", NULL, NULL,
2579
                        ">\n          <Format>%s</Format",
2580
                        "\n          <OnlineResource xmlns:xlink=\""
2581
                        "http://www.w3.org/1999/xlink\" "
2582
                        "xlink:type=\"simple\" xlink:href=\"%s\"/>\n        ",
2583
                        MS_TRUE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
2584
                        NULL, NULL, NULL, NULL, "        ");
2585
    }
2586
  }
2587

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

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

2637
  if (nVersion <= OWS_1_0_0 && pszLegendURL) {
187✔
2638
    /* First, print the style block */
2639
    msIO_fprintf(stdout, "        <Style>\n");
×
2640
    msIO_fprintf(stdout, "          <Name>%s</Name>\n", pszStyle);
×
2641
    /* Print the real Title or Style name otherwise */
2642
    msOWSPrintEncodeMetadata2(
×
2643
        stdout, &(lp->metadata), "MO",
2644
        (std::string("style_") + pszStyle + "_title").c_str(), OWS_NOERR,
×
2645
        "          <Title>%s</Title>\n", pszStyle, validated_language);
2646

2647
    /* Inside, print the legend url block */
2648
    msOWSPrintEncodeMetadata(
×
2649
        stdout, &(lp->metadata), "MO",
2650
        (std::string("style_") + pszStyle + "_legendurl_href").c_str(),
×
2651
        OWS_NOERR, "          <StyleURL>%s</StyleURL>\n", NULL);
2652

2653
    /* close the style block */
2654
    msIO_fprintf(stdout, "        </Style>\n");
×
2655

2656
  } else if (nVersion >= OWS_1_1_0) {
187✔
2657
    if (pszLegendURL) {
186✔
2658
      /* First, print the style block */
2659
      msIO_fprintf(stdout, "        <Style>\n");
×
2660
      msIO_fprintf(stdout, "          <Name>%s</Name>\n", pszStyle);
×
2661
      /* Print the real Title or Style name otherwise */
2662
      msOWSPrintEncodeMetadata2(
×
2663
          stdout, &(lp->metadata), "MO",
2664
          (std::string("style_") + pszStyle + "_title").c_str(), OWS_NOERR,
×
2665
          "          <Title>%s</Title>\n", pszStyle, validated_language);
2666

2667
      /* Inside, print the legend url block */
2668
      msOWSPrintURLType(
×
2669
          stdout, &(lp->metadata), "MO",
2670
          (std::string("style_") + pszStyle + "_legendurl").c_str(), OWS_NOERR,
×
2671
          NULL, "LegendURL", NULL, " width=\"%s\"", " height=\"%s\"",
2672
          ">\n             <Format>%s</Format",
2673
          "\n             <OnlineResource "
2674
          "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
2675
          " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
2676
          "          ",
2677
          MS_FALSE, MS_TRUE, MS_TRUE, MS_TRUE, MS_TRUE, NULL, NULL, NULL, NULL,
2678
          NULL, "          ");
2679
      msIO_fprintf(stdout, "        </Style>\n");
×
2680

2681
    } else {
2682
      if (script_url_encoded) {
186✔
2683
        if (lp->connectiontype != MS_WMS && lp->connectiontype != MS_WFS &&
186✔
2684
            lp->connectiontype != MS_UNUSED_1 && lp->numclasses > 0) {
185✔
2685
          bool classnameset = false;
2686
          for (int i = 0; i < lp->numclasses; i++) {
185✔
2687
            if (lp->_class[i]->name && strlen(lp->_class[i]->name) > 0) {
167✔
2688
              classnameset = true;
2689
              break;
2690
            }
2691
          }
2692
          if (classnameset) {
164✔
2693
            int size_x = 0, size_y = 0;
146✔
2694
            std::vector<int> group_layers;
2695
            group_layers.reserve(map->numlayers);
146✔
2696

2697
            char ***nestedGroups =
2698
                (char ***)msSmallCalloc(map->numlayers, sizeof(char **));
146✔
2699
            int *numNestedGroups =
2700
                (int *)msSmallCalloc(map->numlayers, sizeof(int));
146✔
2701
            int *isUsedInNestedGroup =
2702
                (int *)msSmallCalloc(map->numlayers, sizeof(int));
146✔
2703
            msWMSPrepareNestedGroups(map, nVersion, nestedGroups,
146✔
2704
                                     numNestedGroups, isUsedInNestedGroup);
2705

2706
            group_layers.push_back(lp->index);
146✔
2707
            if (isUsedInNestedGroup[lp->index]) {
146✔
2708
              for (int j = 0; j < map->numlayers; j++) {
96✔
2709
                if (j == lp->index)
90✔
2710
                  continue;
6✔
2711
                for (int k = 0; k < numNestedGroups[j]; k++) {
132✔
2712
                  if (strcasecmp(lp->name, nestedGroups[j][k]) == 0) {
66✔
2713
                    group_layers.push_back(j);
18✔
2714
                    break;
2715
                  }
2716
                }
2717
              }
2718
            }
2719

2720
            if (msLegendCalcSize(map, 1, &size_x, &size_y, group_layers.data(),
146✔
2721
                                 static_cast<int>(group_layers.size()), NULL,
2722
                                 1) == MS_SUCCESS) {
2723
              const std::string width(std::to_string(size_x));
146✔
2724
              const std::string height(std::to_string(size_y));
146✔
2725

2726
              char *mimetype = NULL;
2727
#if defined USE_PNG
2728
              mimetype = msEncodeHTMLEntities("image/png");
146✔
2729
#endif
2730

2731
#if defined USE_JPEG
2732
              if (!mimetype)
146✔
2733
                mimetype = msEncodeHTMLEntities("image/jpeg");
×
2734
#endif
2735
              if (!mimetype)
×
2736
                mimetype =
2737
                    msEncodeHTMLEntities(MS_IMAGE_MIME_TYPE(map->outputformat));
×
2738

2739
              /* --------------------------------------------------------------------
2740
               */
2741
              /*      check if the group parameters for the classes are set. We
2742
               */
2743
              /*      should then publish the different class groups as
2744
               * different styles.*/
2745
              /* --------------------------------------------------------------------
2746
               */
2747
              iclassgroups = 0;
2748
              classgroups = NULL;
2749

2750
              const char *styleName =
2751
                  msOWSLookupMetadata(&(map->web.metadata), "MO", "style_name");
146✔
2752
              if (styleName == NULL)
146✔
2753
                styleName = "default";
2754
              char *pszEncodedStyleName = msEncodeHTMLEntities(styleName);
146✔
2755

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

2820
              for (int i = 0; i < iclassgroups; i++) {
292✔
2821
                char *name_encoded = msEncodeHTMLEntities(lp->name);
146✔
2822
                char *classgroup_encoded = msEncodeHTMLEntities(classgroups[i]);
146✔
2823
                std::string legendurl(script_url_encoded);
146✔
2824
                legendurl += "version=";
2825
                char szVersionBuf[OWS_VERSION_MAXLEN];
2826
                legendurl += msOWSGetVersionString(nVersion, szVersionBuf);
146✔
2827
                legendurl +=
2828
                    "&amp;service=WMS&amp;request=GetLegendGraphic&amp;";
2829
                if (nVersion >= OWS_1_3_0) {
146✔
2830
                  legendurl += "sld_version=1.1.0&amp;layer=";
2831
                } else {
2832
                  legendurl += "layer=";
2833
                }
2834
                legendurl += name_encoded;
2835
                legendurl += "&amp;format=";
2836
                legendurl += mimetype;
2837
                legendurl += "&amp;STYLE=";
2838
                legendurl += classgroup_encoded;
2839

2840
                msFree(name_encoded);
146✔
2841
                msFree(classgroup_encoded);
146✔
2842

2843
                msIO_fprintf(stdout, "        <Style>\n");
146✔
2844
                msIO_fprintf(stdout, "          <Name>%s</Name>\n",
146✔
2845
                             classgroups[i]);
2846
                msOWSPrintEncodeMetadata2(
146✔
2847
                    stdout, &(lp->metadata), "MO",
2848
                    (std::string("style_") + classgroups[i] + "_title").c_str(),
146✔
2849
                    OWS_NOERR, "          <Title>%s</Title>\n", classgroups[i],
2850
                    validated_language);
2851

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

2904
  /* print Min/Max ScaleDenominator */
2905
  if (nVersion < OWS_1_3_0)
186✔
2906
    msWMSPrintScaleHint("        ", lp->minscaledenom, lp->maxscaledenom,
96✔
2907
                        map->resolution);
2908
  else
2909
    msWMSPrintScaleDenominator("        ", lp->minscaledenom,
91✔
2910
                               lp->maxscaledenom);
2911

2912
  if (grouplayer == MS_FALSE)
187✔
2913
    msIO_printf("%s    </Layer>\n", indent);
168✔
2914

2915
  return MS_SUCCESS;
187✔
2916
}
2917

2918
/*
2919
 * msWMSIsSubGroup
2920
 */
2921
static bool msWMSIsSubGroup(char **currentGroups, int currentLevel,
217✔
2922
                            char **otherGroups, int numOtherGroups) {
2923
  /* no match if otherGroups[] has less levels than currentLevel */
2924
  if (numOtherGroups <= currentLevel) {
217✔
2925
    return false;
2926
  }
2927
  /* compare all groups below the current level */
2928
  for (int i = 0; i <= currentLevel; i++) {
266✔
2929
    if (strcmp(currentGroups[i], otherGroups[i]) != 0) {
181✔
2930
      return false; /* if one of these is not equal it is not a sub group */
2931
    }
2932
  }
2933
  return true;
2934
}
2935

2936
/*
2937
 * msWMSHasQueryableSubLayers
2938
 */
2939
static int msWMSHasQueryableSubLayers(mapObj *map, int index, int level,
28✔
2940
                                      char ***nestedGroups,
2941
                                      int *numNestedGroups) {
2942
  for (int j = index; j < map->numlayers; j++) {
44✔
2943
    if (msWMSIsSubGroup(nestedGroups[index], level, nestedGroups[j],
41✔
2944
                        numNestedGroups[j])) {
41✔
2945
      if (msIsLayerQueryable(GET_LAYER(map, j)))
31✔
2946
        return MS_TRUE;
2947
    }
2948
  }
2949
  return MS_FALSE;
2950
}
2951

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

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

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

3020
    /* Look for one group deeper in the current layer */
3021
    if (!pabLayerProcessed[index]) {
28✔
3022
      msWMSPrintNestedGroups(map, nVersion, pabLayerProcessed, index, level + 1,
28✔
3023
                             nestedGroups, numNestedGroups, isUsedInNestedGroup,
3024
                             script_url_encoded, validated_language);
3025
    }
3026

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

3050
/*
3051
** msWMSGetCapabilities()
3052
*/
3053
static int msWMSGetCapabilities(mapObj *map, int nVersion, cgiRequestObj *req,
64✔
3054
                                owsRequestObj *ows_request,
3055
                                const char *requested_updatesequence,
3056
                                const char *wms_exception_format,
3057
                                const char *requested_language) {
3058
  const char *updatesequence =
3059
      msOWSLookupMetadata(&(map->web.metadata), "MO", "updatesequence");
64✔
3060

3061
  const char *sldenabled =
3062
      msOWSLookupMetadata(&(map->web.metadata), "MO", "sld_enabled");
64✔
3063

3064
  if (sldenabled == NULL)
64✔
3065
    sldenabled = "true";
3066

3067
  if (requested_updatesequence != NULL) {
64✔
3068
    int i =
3069
        msOWSNegotiateUpdateSequence(requested_updatesequence, updatesequence);
6✔
3070
    if (i == 0) { /* current */
6✔
3071
      msSetErrorWithStatus(
2✔
3072
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
3073
          "UPDATESEQUENCE parameter (%s) is equal to server (%s)",
3074
          "msWMSGetCapabilities()", requested_updatesequence, updatesequence);
3075
      return msWMSException(map, nVersion, "CurrentUpdateSequence",
2✔
3076
                            wms_exception_format);
3077
    }
3078
    if (i > 0) { /* invalid */
4✔
3079
      msSetErrorWithStatus(
2✔
3080
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
3081
          "UPDATESEQUENCE parameter (%s) is higher than server (%s)",
3082
          "msWMSGetCapabilities()", requested_updatesequence, updatesequence);
3083
      return msWMSException(map, nVersion, "InvalidUpdateSequence",
2✔
3084
                            wms_exception_format);
3085
    }
3086
  }
3087

3088
  std::string schemalocation;
3089
  {
3090
    char *pszSchemalocation =
3091
        msEncodeHTMLEntities(msOWSGetSchemasLocation(map));
60✔
3092
    schemalocation = pszSchemalocation;
3093
    msFree(pszSchemalocation);
60✔
3094
  }
3095

3096
  if (nVersion < 0)
60✔
3097
    nVersion = OWS_1_3_0; /* Default to 1.3.0 */
3098

3099
  /* Decide which version we're going to return. */
3100
  std::string dtd_url;
3101
  if (nVersion == OWS_1_0_0) {
60✔
3102
    dtd_url = std::move(schemalocation);
2✔
3103
    dtd_url += "/wms/1.0.0/capabilities_1_0_0.dtd";
3104
  } else if (nVersion < OWS_1_1_1) {
58✔
3105
    nVersion = OWS_1_1_0;
3106
    dtd_url = std::move(schemalocation);
7✔
3107
    dtd_url += "/wms/1.1.0/capabilities_1_1_0.dtd";
3108
  } else if (nVersion < OWS_1_3_0) {
51✔
3109
    nVersion = OWS_1_1_1;
3110
    dtd_url = std::move(schemalocation);
22✔
3111
    /* this exception was added to accommodate the OGC test suite (Bug 1576)*/
3112
    if (strcasecmp(dtd_url.c_str(), OWS_DEFAULT_SCHEMAS_LOCATION) == 0)
22✔
3113
      dtd_url += "/wms/1.1.1/WMS_MS_Capabilities.dtd";
3114
    else
3115
      dtd_url += "/wms/1.1.1/capabilities_1_1_1.dtd";
3116
  } else
3117
    nVersion = OWS_1_3_0;
3118

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

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

3147
  if (nVersion == OWS_1_0_0 || nVersion >= OWS_1_3_0) /* 1.0.0 and >=1.3.0*/
60✔
3148
    msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
31✔
3149
  else /* 1.1.0 and later */
3150
    msIO_setHeader("Content-Type",
29✔
3151
                   "application/vnd.ogc.wms_xml; charset=UTF-8");
3152
  msIO_sendHeaders();
60✔
3153

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

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

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

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

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

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

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

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

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

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

3367
  msIO_printf(">\n");
60✔
3368

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

3372
  /* Service name is defined by the spec and changed at v1.0.0 */
3373
  if (nVersion == OWS_1_0_0)
60✔
3374
    msIO_printf("  <Name>GetMap</Name>\n"); /* v 1.0.0 */
2✔
3375
  else if (nVersion >= OWS_1_1_0 && nVersion < OWS_1_3_0)
58✔
3376
    msIO_printf("  <Name>OGC:WMS</Name>\n"); /* v 1.1.0 to 1.1.1*/
29✔
3377
  else
3378
    msIO_printf("  <Name>WMS</Name>\n"); /* v 1.3.0+ */
29✔
3379

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

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

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

3406
  /* In 1.1.0, ContactInformation becomes optional. */
3407
  msOWSPrintContactInfo(stdout, "  ", nVersion, &(map->web.metadata), "MO");
60✔
3408

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

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

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

3426
  msIO_printf("</Service>\n\n");
60✔
3427

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

3432
  if (nVersion == OWS_1_0_0) {
60✔
3433
    /* WMS 1.0.0 - we don't try to use outputformats list here for now
3434
     */
3435
    if (msOWSRequestIsEnabled(map, NULL, "M", "GetMap", MS_FALSE))
2✔
3436
      msWMSPrintRequestCap(nVersion, "Map", script_url_encoded,
2✔
3437
                           ""
3438

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

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

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

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

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

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

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

3545
      msGetOutputFormatMimeListImg(map, mime_list,
41✔
3546
                                   sizeof(mime_list) / sizeof(char *));
3547

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

3581
  msIO_printf("  </Request>\n");
60✔
3582

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

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

3611
  /* SLD support */
3612
  if (strcasecmp(sldenabled, "true") == 0) {
60✔
3613
    if (nVersion >= OWS_1_3_0)
43✔
3614
      msIO_printf("  <sld:UserDefinedSymbolization SupportSLD=\"1\" "
20✔
3615
                  "UserLayer=\"0\" UserStyle=\"1\" RemoteWFS=\"0\" "
3616
                  "InlineFeature=\"0\" RemoteWCS=\"0\"/>\n");
3617
    else
3618
      msIO_printf("  <UserDefinedSymbolization SupportSLD=\"1\" "
23✔
3619
                  "UserLayer=\"0\" UserStyle=\"1\" RemoteWFS=\"0\"/>\n");
3620
  }
3621

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

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

3638
    const char *rootlayer_name =
3639
        msOWSLookupMetadata(&(map->web.metadata), "MO", "rootlayer_name");
55✔
3640

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

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

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

3677
    if (msOWSLookupMetadataWithLanguage(&(map->web.metadata), "MO",
55✔
3678
                                        "rootlayer_title",
3679
                                        validated_language.c_str()))
3680
      msOWSPrintEncodeMetadata2(
26✔
3681
          stdout, &(map->web.metadata), "MO", "rootlayer_title", OWS_WARN,
3682
          "    <Title>%s</Title>\n", map->name, validated_language.c_str());
26✔
3683

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

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

3701
    const char *pszTmp;
3702
    if (msOWSLookupMetadata(&(map->web.metadata), "MO",
55✔
3703
                            "rootlayer_keywordlist") ||
84✔
3704
        msOWSLookupMetadata(&(map->web.metadata), "MO",
29✔
3705
                            "rootlayer_keywordlist_vocabulary"))
3706
      pszTmp = "rootlayer_keywordlist";
3707
    else
3708
      pszTmp = "keywordlist";
3709
    msWMSPrintKeywordlist(stdout, "    ", pszTmp, &(map->web.metadata), "MO",
55✔
3710
                          nVersion);
3711

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

3745
    if (nVersion >= OWS_1_3_0)
55✔
3746
      msOWSPrintEX_GeographicBoundingBox(stdout, "    ", &(map->extent),
27✔
3747
                                         &(map->projection));
3748
    else
3749
      msOWSPrintLatLonBoundingBox(stdout, "    ", &(map->extent),
28✔
3750
                                  &(map->projection), NULL, OWS_WMS);
3751

3752
    msOWSPrintBoundingBox(stdout, "    ", &(map->extent), &(map->projection),
55✔
3753
                          NULL, &(map->web.metadata), "MO", nVersion);
3754

3755
    /* AuthorityURL support and Identifier support, only available >= WMS 1.1.0
3756
     */
3757
    if (nVersion >= OWS_1_1_0) {
55✔
3758
      msWMSPrintAttribution(stdout, "    ", &(map->web.metadata), "MO");
54✔
3759
      msWMSPrintAuthorityURL(stdout, "    ", &(map->web.metadata), "MO");
54✔
3760
      msWMSPrintIdentifier(stdout, "    ", &(map->web.metadata), "MO");
54✔
3761
    }
3762

3763
    /* MetadataURL */
3764
    if (nVersion >= OWS_1_1_0)
3765
      msOWSPrintURLType(stdout, &(map->web.metadata), "MO", "metadataurl",
54✔
3766
                        OWS_NOERR, NULL, "MetadataURL", " type=\"%s\"", NULL,
3767
                        NULL, ">\n      <Format>%s</Format",
3768
                        "\n      <OnlineResource xmlns:xlink=\""
3769
                        "http://www.w3.org/1999/xlink\" "
3770
                        "xlink:type=\"simple\" xlink:href=\"%s\"/>\n    ",
3771
                        MS_TRUE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
3772
                        NULL, NULL, NULL, NULL, "    ");
3773

3774
    /* DataURL */
3775
    if (nVersion < OWS_1_1_0)
3776
      msOWSPrintEncodeMetadata(stdout, &(map->web.metadata), "MO",
1✔
3777
                               "dataurl_href", OWS_NOERR,
3778
                               "    <DataURL>%s</DataURL>\n", NULL);
3779
    else
3780
      msOWSPrintURLType(stdout, &(map->web.metadata), "MO", "dataurl",
54✔
3781
                        OWS_NOERR, NULL, "DataURL", NULL, NULL, NULL,
3782
                        ">\n      <Format>%s</Format",
3783
                        "\n      <OnlineResource xmlns:xlink=\""
3784
                        "http://www.w3.org/1999/xlink\" "
3785
                        "xlink:type=\"simple\" xlink:href=\"%s\"/>\n    ",
3786
                        MS_FALSE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
3787
                        NULL, NULL, NULL, NULL, "    ");
3788

3789
    if (map->name && strlen(map->name) > 0 &&
110✔
3790
        msOWSLookupMetadata(&(map->web.metadata), "MO",
55✔
3791
                            "inspire_capabilities")) {
3792
      char *pszEncodedName = NULL;
3793
      const char *styleName = NULL;
3794
      char *pszEncodedStyleName = NULL;
3795
      const char *legendURL = NULL;
3796

3797
      pszEncodedName = msEncodeHTMLEntities(map->name);
15✔
3798

3799
      styleName = msOWSLookupMetadata(&(map->web.metadata), "MO", "style_name");
15✔
3800
      if (styleName == NULL)
15✔
3801
        styleName = "default";
3802

3803
      pszEncodedStyleName = msEncodeHTMLEntities(styleName);
15✔
3804

3805
      msIO_fprintf(stdout, "    <Style>\n");
15✔
3806
      msIO_fprintf(stdout, "       <Name>%s</Name>\n", pszEncodedStyleName);
15✔
3807
      msOWSPrintEncodeMetadata2(
15✔
3808
          stdout, &(map->web.metadata), "MO", "style_title", OWS_NOERR,
3809
          "       <Title>%s</Title>\n", styleName, validated_language.c_str());
3810

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

3827
        for (int i = 0; i < map->numlayers; i++)
96✔
3828
          if (msIntegerInArray(GET_LAYER(map, i)->index,
90✔
3829
                               ows_request->enabled_layers,
3830
                               ows_request->numlayers))
3831
            group_layers.push_back(i);
84✔
3832

3833
        if (!group_layers.empty()) {
6✔
3834
          int size_x = 0, size_y = 0;
6✔
3835
          if (msLegendCalcSize(map, 1, &size_x, &size_y, group_layers.data(),
6✔
3836
                               static_cast<int>(group_layers.size()), NULL,
3837
                               1) == MS_SUCCESS) {
3838
            const std::string width(std::to_string(size_x));
6✔
3839
            const std::string height(std::to_string(size_y));
6✔
3840

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

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

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

3889
    if (nVersion < OWS_1_3_0)
55✔
3890
      msWMSPrintScaleHint("    ", map->web.minscaledenom,
28✔
3891
                          map->web.maxscaledenom, map->resolution);
3892
    else
3893
      msWMSPrintScaleDenominator("    ", map->web.minscaledenom,
27✔
3894
                                 map->web.maxscaledenom);
3895

3896
    /*  */
3897
    /* Dump list of layers organized by groups.  Layers with no group are listed
3898
     */
3899
    /* individually, at the same level as the groups in the layer hierarchy */
3900
    /*  */
3901
    if (map->numlayers) {
55✔
3902
      char ***nestedGroups = NULL;
3903
      int *numNestedGroups = NULL;
3904
      int *isUsedInNestedGroup = NULL;
3905

3906
      /* We'll use this array of booleans to track which layer/group have been
3907
       */
3908
      /* processed already */
3909
      std::vector<char> pabLayerProcessed(map->numlayers);
55✔
3910

3911
      /* Mark disabled layers as processed to prevent from being displayed in
3912
       * nested groups (#4533)*/
3913
      for (int i = 0; i < map->numlayers; i++) {
255✔
3914
        if (!msIntegerInArray(GET_LAYER(map, i)->index,
200✔
3915
                              ows_request->enabled_layers,
3916
                              ows_request->numlayers))
3917
          pabLayerProcessed[i] = 1;
13✔
3918
      }
3919

3920
      nestedGroups = (char ***)msSmallCalloc(map->numlayers, sizeof(char **));
55✔
3921
      numNestedGroups = (int *)msSmallCalloc(map->numlayers, sizeof(int));
55✔
3922
      isUsedInNestedGroup = (int *)msSmallCalloc(map->numlayers, sizeof(int));
55✔
3923
      msWMSPrepareNestedGroups(map, nVersion, nestedGroups, numNestedGroups,
55✔
3924
                               isUsedInNestedGroup);
3925

3926
      for (int i = 0; i < map->numlayers; i++) {
255✔
3927
        layerObj *lp = (GET_LAYER(map, i));
200✔
3928

3929
        if (pabLayerProcessed[i] || (lp->status == MS_DELETE))
200✔
3930
          continue; /* Layer is hidden or has already been handled */
72✔
3931

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

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

3983
          /*build a getlegendgraphicurl*/
3984
          if (script_url_encoded) {
3985
            if (lp->group && strlen(lp->group) > 0) {
11✔
3986
              char *pszEncodedName = NULL;
3987
              const char *styleName = NULL;
3988
              char *pszEncodedStyleName = NULL;
3989
              const char *legendURL = NULL;
3990

3991
              pszEncodedName = msEncodeHTMLEntities(lp->group);
11✔
3992

3993
              styleName = msOWSLookupMetadata(&(lp->metadata), "MO",
11✔
3994
                                              "group_style_name");
3995
              if (styleName == NULL)
11✔
3996
                styleName = "default";
3997

3998
              pszEncodedStyleName = msEncodeHTMLEntities(styleName);
11✔
3999

4000
              msIO_fprintf(stdout, "    <Style>\n");
11✔
4001
              msIO_fprintf(stdout, "       <Name>%s</Name>\n",
11✔
4002
                           pszEncodedStyleName);
4003
              msOWSPrintEncodeMetadata(stdout, &(lp->metadata), "MO",
11✔
4004
                                       "group_style_title", OWS_NOERR,
4005
                                       "       <Title>%s</Title>\n", styleName);
4006

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

4024
                for (int j = i; j < map->numlayers; j++)
7✔
4025
                  if (!pabLayerProcessed[j] && GET_LAYER(map, j)->group &&
5✔
4026
                      strcmp(lp->group, GET_LAYER(map, j)->group) == 0 &&
10✔
4027
                      msIntegerInArray(GET_LAYER(map, j)->index,
2✔
4028
                                       ows_request->enabled_layers,
4029
                                       ows_request->numlayers))
4030
                    group_layers.push_back(j);
2✔
4031

4032
                if (!group_layers.empty()) {
2✔
4033
                  int size_x = 0, size_y = 0;
2✔
4034
                  char *pszMimetype = NULL;
4035

4036
                  if (msLegendCalcSize(map, 1, &size_x, &size_y,
2✔
4037
                                       group_layers.data(),
4038
                                       static_cast<int>(group_layers.size()),
4039
                                       NULL, 1) == MS_SUCCESS) {
4040
                    const std::string width(std::to_string(size_x));
2✔
4041
                    const std::string height(std::to_string(size_y));
2✔
4042

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

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

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

4084
                    msFree(pszMimetype);
2✔
4085
                  }
4086
                }
4087
              }
2✔
4088
              msIO_fprintf(stdout, "    </Style>\n");
11✔
4089
              msFree(pszEncodedName);
11✔
4090
              msFree(pszEncodedStyleName);
11✔
4091
            }
4092
          }
4093

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

4112
      /* free the stuff used for nested layers */
4113
      for (int i = 0; i < map->numlayers; i++) {
255✔
4114
        if (numNestedGroups[i] > 0) {
200✔
4115
          msFreeCharArray(nestedGroups[i], numNestedGroups[i]);
80✔
4116
        }
4117
      }
4118
      free(nestedGroups);
55✔
4119
      free(numNestedGroups);
55✔
4120
      free(isUsedInNestedGroup);
55✔
4121
    }
55✔
4122

4123
    msIO_printf("  </Layer>\n");
55✔
4124
  }
4125

4126
  msIO_printf("</Capability>\n");
60✔
4127
  if (nVersion >= OWS_1_3_0)
60✔
4128
    msIO_printf("</WMS_Capabilities>\n");
29✔
4129
  else
4130
    msIO_printf("</WMT_MS_Capabilities>\n");
31✔
4131

4132
  free(script_url_encoded);
60✔
4133

4134
  return (MS_SUCCESS);
60✔
4135
}
4136

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

4213
      /* Note msReplaceSubstring() works on the string itself, so we need to
4214
       * make a copy */
4215
      imgext = msStrdup(values[i]);
7✔
4216
      imgext = msReplaceSubstring(imgext, ",", " ");
7✔
4217
      (*translated_values)[*translated_numentries] = imgext;
7✔
4218
      (*translated_names)[*translated_numentries] = msStrdup("imgext");
7✔
4219
      (*translated_numentries)++;
7✔
4220
    }
4221
  }
4222

4223
  return MS_SUCCESS;
7✔
4224
}
4225

4226
/*
4227
** msWMSGetMap()
4228
*/
4229
static int msWMSGetMap(mapObj *map, int nVersion, char **names, char **values,
409✔
4230
                       int numentries, const char *wms_exception_format,
4231
                       owsRequestObj *ows_request) {
4232

4233
  // If we are returning an OpenLayers map there is no need to first generate an
4234
  // image. We can't call msReturnOpenLayersPage directly here as it requires
4235
  // the mapservObj
4236
  if (strcasecmp(map->imagetype, "application/openlayers") == 0) {
409✔
4237
    return MS_SUCCESS;
4238
  }
4239

4240
  imageObj *img;
4241
  int sldrequested = MS_FALSE, sldspatialfilter = MS_FALSE;
4242
  int drawquerymap = MS_FALSE;
4243

4244
  /* __TODO__ msDrawMap() will try to adjust the extent of the map */
4245
  /* to match the width/height image ratio. */
4246
  /* The spec states that this should not happen so that we can deliver */
4247
  /* maps to devices with non-square pixels. */
4248

4249
  /* If there was an SLD in the request, we need to treat it */
4250
  /* differently : some SLD may contain spatial filters requiring */
4251
  /* to do a query. While parsing the SLD and applying it to the */
4252
  /* layer, we added a temporary metadata on the layer */
4253
  /* (tmp_wms_sld_query) for layers with a spatial filter. */
4254

4255
  for (int i = 0; i < numentries; i++) {
5,056✔
4256
    if ((strcasecmp(names[i], "SLD") == 0 && values[i] &&
4,784✔
4257
         strlen(values[i]) > 0) ||
134✔
4258
        (strcasecmp(names[i], "SLD_BODY") == 0 && values[i] &&
4,781✔
4259
         strlen(values[i]) > 0)) {
4260
      sldrequested = MS_TRUE;
4261
      break;
4262
    }
4263
  }
4264
  if (sldrequested) {
406✔
4265
    for (int i = 0; i < map->numlayers; i++) {
609✔
4266
      if (msLookupHashTable(&(GET_LAYER(map, i)->metadata),
475✔
4267
                            "tmp_wms_sld_query")) {
4268
        sldspatialfilter = MS_TRUE;
4269
        break;
4270
      }
4271
    }
4272
  }
4273
  /* If FILTER is passed then we'll render layers as querymap */
4274
  for (int i = 0; i < numentries; i++) {
5,179✔
4275
    if ((strcasecmp(names[i], "FILTER") == 0 && values[i] &&
4,783✔
4276
         strlen(values[i]) > 0)) {
10✔
4277
      drawquerymap = MS_TRUE;
4278
      map->querymap.status = MS_ON;
10✔
4279
      map->querymap.style = MS_SELECTED;
10✔
4280
      break;
10✔
4281
    }
4282
  }
4283

4284
  /* turn off layer if WMS GetMap is not enabled */
4285
  for (int i = 0; i < map->numlayers; i++)
3,019✔
4286
    if (!msIntegerInArray(GET_LAYER(map, i)->index, ows_request->enabled_layers,
2,613✔
4287
                          ows_request->numlayers))
4288
      GET_LAYER(map, i)->status = MS_OFF;
2✔
4289

4290
  if (sldrequested && sldspatialfilter) {
406✔
4291
    /* set the quermap style so that only selected features will be returned */
4292
    map->querymap.status = MS_ON;
×
4293
    map->querymap.style = MS_SELECTED;
×
4294

4295
    img = msPrepareImage(map, MS_TRUE);
×
4296

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

4318
      {
4319
        /* make sure that there is a resultcache. If not just ignore */
4320
        /* the layer */
4321
        if (GET_LAYER(map, i)->resultcache)
×
4322
          msDrawQueryLayer(map, GET_LAYER(map, i), img);
×
4323
      }
4324

4325
      else
4326
        IGNORE_RET_VAL(msDrawLayer(map, GET_LAYER(map, i), img));
×
4327
    }
4328

4329
  } else {
4330

4331
    /* intercept requests for Mapbox vector tiles */
4332
    if (!strcmp(MS_IMAGE_MIME_TYPE(map->outputformat),
812✔
4333
                "application/vnd.mapbox-vector-tile") ||
402✔
4334
        !strcmp(MS_IMAGE_MIME_TYPE(map->outputformat),
402✔
4335
                "application/x-protobuf")) {
4336
      int status = 0;
4337
      if ((status = msMVTWriteTile(map, MS_TRUE)) != MS_SUCCESS)
5✔
4338
        return MS_FAILURE;
4339
      return MS_SUCCESS;
4340
    }
4341

4342
    img = msDrawMap(map, drawquerymap);
401✔
4343
  }
4344

4345
  /* see if we have tiled = true and a buffer */
4346
  /* if so, clip the image */
4347
  for (int i = 0; i < numentries; i++) {
5,128✔
4348
    if (strcasecmp(names[i], "TILED") == 0 &&
4,728✔
4349
        strcasecmp(values[i], "TRUE") == 0) {
1✔
4350
      hashTableObj *meta = &(map->web.metadata);
1✔
4351
      const char *value;
4352

4353
      if ((value = msLookupHashTable(meta, "tile_map_edge_buffer")) != NULL) {
1✔
4354
        const int map_edge_buffer = atoi(value);
4355
        if (map_edge_buffer > 0) {
1✔
4356
          /* we have to clip the image */
4357

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

4369
          int width = map->width - map_edge_buffer - map_edge_buffer;
1✔
4370
          int height = map->height - map_edge_buffer - map_edge_buffer;
1✔
4371
          imageObj *tmp =
4372
              msImageCreate(width, height, map->outputformat, NULL, NULL,
1✔
4373
                            map->resolution, map->defresolution, NULL);
4374

4375
          if ((MS_FAILURE == renderer->mergeRasterBuffer(
1✔
4376
                                 tmp, &imgBuffer, 1.0, map_edge_buffer,
4377
                                 map_edge_buffer, 0, 0, width, height))) {
4378
            msFreeImage(tmp);
×
4379
            msFreeImage(img);
×
4380
            img = NULL;
4381
          } else {
4382
            msFreeImage(img);
1✔
4383
            img = tmp;
4384
          }
4385
        }
4386
      }
4387
      break;
4388
    }
4389
  }
4390

4391
  if (img == NULL)
401✔
4392
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4393

4394
  /* Set the HTTP Cache-control headers if they are defined
4395
     in the map object */
4396

4397
  const char *http_max_age =
4398
      msOWSLookupMetadata(&(map->web.metadata), "MO", "http_max_age");
401✔
4399
  if (http_max_age) {
401✔
4400
    msIO_setHeader("Cache-Control", "max-age=%s", http_max_age);
2✔
4401
  }
4402

4403
  if (strcasecmp(map->imagetype, "application/openlayers") != 0) {
401✔
4404
    if (!strcmp(MS_IMAGE_MIME_TYPE(map->outputformat), "application/json")) {
802✔
4405
      msIO_setHeader("Content-Type", "application/json; charset=utf-8");
×
4406
    } else {
4407
      msOutputFormatResolveFromImage(map, img);
401✔
4408
      msIO_setHeader("Content-Type", "%s",
401✔
4409
                     MS_IMAGE_MIME_TYPE(map->outputformat));
401✔
4410
    }
4411
    msIO_sendHeaders();
401✔
4412
    if (msSaveImage(map, img, NULL) != MS_SUCCESS) {
401✔
4413
      msFreeImage(img);
×
4414
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4415
    }
4416
  }
4417
  msFreeImage(img);
401✔
4418

4419
  return (MS_SUCCESS);
401✔
4420
}
4421

4422
static int msDumpResult(mapObj *map, int nVersion,
7✔
4423
                        const char *wms_exception_format) {
4424
  int numresults = 0;
4425

4426
  for (int i = 0; i < map->numlayers; i++) {
35✔
4427
    layerObj *lp = (GET_LAYER(map, i));
28✔
4428

4429
    if (lp->status != MS_ON || lp->resultcache == NULL ||
28✔
4430
        lp->resultcache->numresults == 0)
16✔
4431
      continue;
18✔
4432

4433
    /* if(msLayerOpen(lp) != MS_SUCCESS || msLayerGetItems(lp) != MS_SUCCESS)
4434
     return msWMSException(map, nVersion, NULL); */
4435

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

4449
    /* get a list of items that should be excluded in output */
4450
    std::vector<std::string> excitems;
4451
    if ((value = msOWSLookupMetadata(&(lp->metadata), "MO", "exclude_items")) !=
10✔
4452
        NULL)
4453
      excitems = msStringSplit(value, ',');
×
4454

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

4467
      /* ...and now excluded items */
4468
      for (const auto &excitem : excitems) {
165✔
4469
        if (strcasecmp(lp->items[k], excitem.c_str()) == 0)
×
4470
          itemvisible[k] = false;
4471
      }
4472
    }
4473

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

4477
    for (int j = 0; j < lp->resultcache->numresults; j++) {
32✔
4478
      shapeObj shape;
4479

4480
      msInitShape(&shape);
22✔
4481
      if (msLayerGetShape(lp, &shape, &(lp->resultcache->results[j])) !=
22✔
4482
          MS_SUCCESS) {
4483
        return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4484
      }
4485

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

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

4499
      msFreeShape(&shape);
22✔
4500
      numresults++;
22✔
4501
    }
4502

4503
    /* msLayerClose(lp); */
4504
  }
10✔
4505

4506
  return numresults;
4507
}
4508

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

4528
  char ***nestedGroups =
4529
      (char ***)msSmallCalloc(map->numlayers, sizeof(char **));
46✔
4530
  int *numNestedGroups = (int *)msSmallCalloc(map->numlayers, sizeof(int));
46✔
4531
  int *isUsedInNestedGroup = (int *)msSmallCalloc(map->numlayers, sizeof(int));
46✔
4532
  msWMSPrepareNestedGroups(map, nVersion, nestedGroups, numNestedGroups,
46✔
4533
                           isUsedInNestedGroup);
4534

4535
  for (int i = 0; i < numentries; i++) {
793✔
4536
    if (strcasecmp(names[i], "QUERY_LAYERS") == 0) {
747✔
4537
      query_layer = 1; /* flag set if QUERY_LAYERS is the request */
4538

4539
      const auto wmslayers = msStringSplit(values[i], ',');
46✔
4540
      if (wmslayers.empty()) {
46✔
4541
        msSetErrorWithStatus(
×
4542
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4543
            "At least one layer name required in QUERY_LAYERS.",
4544
            "msWMSFeatureInfo()");
4545
        return msWMSException(map, nVersion, "LayerNotDefined",
×
4546
                              wms_exception_format);
4547
      }
4548

4549
      for (int j = 0; j < map->numlayers; j++) {
234✔
4550
        /* Force all layers OFF by default */
4551
        GET_LAYER(map, j)->status = MS_OFF;
188✔
4552
      }
4553

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

4570
            numlayers_found++;
38✔
4571
            layer->status = MS_ON;
38✔
4572
          }
4573
        }
4574
      }
4575

4576
      for (int j = 0; j < map->numlayers; j++) {
234✔
4577
        layerObj *layer = GET_LAYER(map, j);
188✔
4578
        if (!msIsLayerQueryable(layer))
188✔
4579
          continue;
60✔
4580
        for (const auto &wmslayer : wmslayers) {
256✔
4581
          if (((layer->name &&
256✔
4582
                strcasecmp(layer->name, wmslayer.c_str()) == 0) ||
128✔
4583
               (layer->group &&
92✔
4584
                strcasecmp(layer->group, wmslayer.c_str()) == 0) ||
5✔
4585
               ((numNestedGroups[j] > 0) &&
177✔
4586
                msStringInArray(wmslayer.c_str(), nestedGroups[j],
85✔
4587
                                numNestedGroups[j]))) &&
177✔
4588
              (msIntegerInArray(layer->index, ows_request->enabled_layers,
49✔
4589
                                ows_request->numlayers))) {
4590

4591
            if (layer->connectiontype == MS_WMS) {
49✔
4592
              wms_layer = MS_TRUE;
4593
              wms_connection = layer->connection;
8✔
4594
            }
4595

4596
            numlayers_found++;
49✔
4597
            layer->status = MS_ON;
49✔
4598
          }
4599
        }
4600
      }
4601
    } else if (strcasecmp(names[i], "INFO_FORMAT") == 0) {
747✔
4602
      if (values[i] && strlen(values[i]) > 0) {
42✔
4603
        info_format = values[i];
4604
        format_found = MS_TRUE;
4605
      }
4606
    } else if (strcasecmp(names[i], "FEATURE_COUNT") == 0)
659✔
4607
      feature_count = atoi(values[i]);
19✔
4608
    else if (strcasecmp(names[i], "X") == 0 || strcasecmp(names[i], "I") == 0)
640✔
4609
      point.x = atof(values[i]);
46✔
4610
    else if (strcasecmp(names[i], "Y") == 0 || strcasecmp(names[i], "J") == 0)
594✔
4611
      point.y = atof(values[i]);
46✔
4612
    else if (strcasecmp(names[i], "RADIUS") == 0) {
548✔
4613
      /* RADIUS in pixels. */
4614
      /* This is not part of the spec, but some servers such as cubeserv */
4615
      /* support it as a vendor-specific feature. */
4616
      /* It's easy for MapServer to handle this so let's do it! */
4617

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

4632
  /* free the stuff used for nested layers */
4633
  for (int i = 0; i < map->numlayers; i++) {
234✔
4634
    if (numNestedGroups[i] > 0) {
188✔
4635
      msFreeCharArray(nestedGroups[i], numNestedGroups[i]);
106✔
4636
    }
4637
  }
4638
  free(nestedGroups);
46✔
4639
  free(numNestedGroups);
46✔
4640
  free(isUsedInNestedGroup);
46✔
4641

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

4661
  /*make sure to initialize the map scale so that layers that are scale
4662
    dependent are respected for the query*/
4663
  msCalculateScale(map->extent, map->units, map->width, map->height,
45✔
4664
                   map->resolution, &map->scaledenom);
4665

4666
  /* -------------------------------------------------------------------- */
4667
  /*      check if all layers selected are queryable. If not send an      */
4668
  /*      exception.                                                      */
4669
  /* -------------------------------------------------------------------- */
4670

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

4691
  /* It's a valid Cascading WMS GetFeatureInfo request */
4692
  if (wms_layer)
45✔
4693
    return msWMSLayerExecuteRequest(map, numOWSLayers, point.x, point.y,
8✔
4694
                                    feature_count, info_format,
4695
                                    WMS_GETFEATUREINFO);
4696

4697
  if (use_bbox == MS_FALSE) {
37✔
4698

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

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

4732
    /* WMS 1.3.0 states that feature_count is *per layer*.
4733
     * Its value is a positive integer, if omitted then the default is 1
4734
     */
4735
    if (feature_count < 1)
37✔
4736
      feature_count = 1;
4737

4738
    map->query.type = MS_QUERY_BY_POINT;
37✔
4739
    map->query.mode =
37✔
4740
        (feature_count == 1 ? MS_QUERY_SINGLE : MS_QUERY_MULTIPLE);
37✔
4741
    map->query.layer = -1;
37✔
4742
    map->query.point = point;
37✔
4743
    map->query.buffer = 0;
37✔
4744
    map->query.maxresults = feature_count;
37✔
4745

4746
    if (msQueryByPoint(map) != MS_SUCCESS)
37✔
4747
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4748

4749
  } else { /* use_bbox == MS_TRUE */
4750
    map->query.type = MS_QUERY_BY_RECT;
×
4751
    map->query.mode = MS_QUERY_MULTIPLE;
×
4752
    map->query.layer = -1;
×
4753
    map->query.rect = map->extent;
×
4754
    map->query.buffer = 0;
×
4755
    map->query.maxresults = feature_count;
×
4756
    if (msQueryByRect(map) != MS_SUCCESS)
×
4757
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4758
  }
4759

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

4784
  /*last case: if the info_format is not part of the request, it defaults to
4785
   * MIME*/
4786
  if (!valid_format && format_found == MS_FALSE)
37✔
4787
    valid_format = MS_TRUE;
4788

4789
  if (!valid_format) {
37✔
4790
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
4791
                         "Unsupported INFO_FORMAT value (%s).",
4792
                         "msWMSFeatureInfo()", info_format);
4793
    if (nVersion >= OWS_1_3_0)
×
4794
      return msWMSException(map, nVersion, "InvalidFormat",
×
4795
                            wms_exception_format);
4796
    else
4797
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4798
  }
4799

4800
  /* Generate response */
4801
  if (strcasecmp(info_format, "MIME") == 0 ||
37✔
4802
      strcasecmp(info_format, "text/plain") == 0) {
33✔
4803

4804
    /* MIME response... we're free to use any valid MIME type */
4805
    int numresults = 0;
4806

4807
    msIO_setHeader("Content-Type", "text/plain; charset=UTF-8");
7✔
4808
    msIO_sendHeaders();
7✔
4809
    msIO_printf("GetFeatureInfo results:\n");
7✔
4810

4811
    numresults = msDumpResult(map, nVersion, wms_exception_format);
7✔
4812

4813
    if (numresults == 0)
7✔
4814
      msIO_printf("\n  Search returned no results.\n");
1✔
4815

4816
  } else if (strncasecmp(info_format, "GML", 3) ==
30✔
4817
                 0 || /* accept GML.1 or GML */
29✔
4818
             strcasecmp(info_format, "application/vnd.ogc.gml") == 0) {
29✔
4819

4820
    if (nVersion == OWS_1_0_0)
23✔
4821
      msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
×
4822
    else /* 1.1.0 and later */
4823
      msIO_setHeader("Content-Type", "application/vnd.ogc.gml; charset=UTF-8");
23✔
4824
    msIO_sendHeaders();
23✔
4825
    msGMLWriteQuery(map, NULL, "MGO"); /* default is stdout */
23✔
4826

4827
  } else {
4828
    mapservObj *msObj;
4829

4830
    char **translated_names, **translated_values;
4831
    int translated_numentries;
4832
    msObj = msAllocMapServObj();
7✔
4833

4834
    /* Translate some vars from WMS to mapserv */
4835
    msTranslateWMS2Mapserv((const char **)names, (const char **)values,
7✔
4836
                           numentries, &translated_names, &translated_values,
4837
                           &translated_numentries);
4838

4839
    msObj->map = map;
7✔
4840
    msFreeCharArray(msObj->request->ParamNames, msObj->request->NumParams);
7✔
4841
    msFreeCharArray(msObj->request->ParamValues, msObj->request->NumParams);
7✔
4842
    msObj->request->ParamNames = translated_names;
7✔
4843
    msObj->request->ParamValues = translated_values;
7✔
4844
    msObj->Mode = QUERY;
7✔
4845
    msObj->request->NumParams = translated_numentries;
7✔
4846
    msObj->mappnt.x = point.x;
7✔
4847
    msObj->mappnt.y = point.y;
7✔
4848

4849
    bool hasResults = false;
4850
    for (int i = 0; i < map->numlayers; i++) {
10✔
4851
      layerObj *lp = (GET_LAYER(map, i));
8✔
4852

4853
      if (lp->status == MS_ON && lp->resultcache &&
8✔
4854
          lp->resultcache->numresults != 0) {
6✔
4855
        hasResults = true;
4856
        break;
4857
      }
4858
    }
4859

4860
    if (!hasResults && msObj->map->web.empty) {
7✔
4861
      if (msReturnURL(msObj, msObj->map->web.empty, BROWSE) != MS_SUCCESS)
1✔
4862
        return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4863
    } else if (msReturnTemplateQuery(msObj, (char *)info_format, NULL) !=
6✔
4864
               MS_SUCCESS)
4865
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4866

4867
    /* We don't want to free the map since it */
4868
    /* belongs to the caller, set it to NULL before freeing the mapservObj */
4869
    msObj->map = NULL;
7✔
4870

4871
    msFreeMapServObj(msObj);
7✔
4872
  }
4873

4874
  return (MS_SUCCESS);
4875
}
4876

4877
/*
4878
** msWMSDescribeLayer()
4879
*/
4880
static int msWMSDescribeLayer(mapObj *map, int nVersion, char **names,
19✔
4881
                              char **values, int numentries,
4882
                              const char *wms_exception_format) {
4883
  std::vector<std::string> wmslayers;
4884
  const char *version = NULL;
4885
  const char *sld_version = NULL;
4886

4887
  for (int i = 0; i < numentries; i++) {
130✔
4888
    if (strcasecmp(names[i], "LAYERS") == 0) {
111✔
4889
      wmslayers = msStringSplit(values[i], ',');
19✔
4890
    }
4891
    if (strcasecmp(names[i], "VERSION") == 0) {
111✔
4892
      version = values[i];
19✔
4893
    }
4894
    if (strcasecmp(names[i], "SLD_VERSION") == 0) {
111✔
4895
      sld_version = values[i];
16✔
4896
    }
4897
  }
4898

4899
  if (nVersion >= OWS_1_3_0 && sld_version == NULL) {
19✔
4900
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
4901
                         "Missing required parameter SLD_VERSION",
4902
                         "DescribeLayer()");
4903
    return msWMSException(map, nVersion, "MissingParameterValue",
×
4904
                          wms_exception_format);
4905
  }
4906
  if (nVersion >= OWS_1_3_0 && strcasecmp(sld_version, "1.1.0") != 0) {
19✔
4907
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
4908
                         "SLD_VERSION must be 1.1.0", "DescribeLayer()");
4909
    return msWMSException(map, nVersion, "InvalidParameterValue",
×
4910
                          wms_exception_format);
4911
  }
4912

4913
  msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
19✔
4914
  msIO_sendHeaders();
19✔
4915

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

4918
  {
4919
    char *schemalocation = msEncodeHTMLEntities(msOWSGetSchemasLocation(map));
19✔
4920
    if (nVersion < OWS_1_3_0) {
19✔
4921

4922
      msIO_printf("<!DOCTYPE WMS_DescribeLayerResponse SYSTEM "
3✔
4923
                  "\"%s/wms/1.1.1/WMS_DescribeLayerResponse.dtd\">\n",
4924
                  schemalocation);
4925

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

4942
  /* check if map-level metadata wfs(wcs)_onlineresource is available */
4943
  const char *pszOnlineResMapWFS =
4944
      msOWSLookupMetadata(&(map->web.metadata), "FO", "onlineresource");
19✔
4945
  if (pszOnlineResMapWFS && strlen(pszOnlineResMapWFS) == 0)
19✔
4946
    pszOnlineResMapWFS = NULL;
4947

4948
  const char *pszOnlineResMapWCS =
4949
      msOWSLookupMetadata(&(map->web.metadata), "CO", "onlineresource");
19✔
4950
  if (pszOnlineResMapWCS && strlen(pszOnlineResMapWCS) == 0)
19✔
4951
    pszOnlineResMapWCS = NULL;
4952

4953
  char ***nestedGroups =
4954
      (char ***)msSmallCalloc(map->numlayers, sizeof(char **));
19✔
4955
  int *numNestedGroups = (int *)msSmallCalloc(map->numlayers, sizeof(int));
19✔
4956
  int *isUsedInNestedGroup = (int *)msSmallCalloc(map->numlayers, sizeof(int));
19✔
4957
  msWMSPrepareNestedGroups(map, nVersion, nestedGroups, numNestedGroups,
19✔
4958
                           isUsedInNestedGroup);
4959

4960
  for (const auto &wmslayer : wmslayers) {
38✔
4961
    for (int k = 0; k < map->numlayers; k++) {
128✔
4962
      layerObj *lp = GET_LAYER(map, k);
109✔
4963

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

4980
        if (pszOnlineResLyrWCS == NULL || strlen(pszOnlineResLyrWCS) == 0)
50✔
4981
          pszOnlineResLyrWCS = pszOnlineResMapWCS;
4982

4983
        if (pszOnlineResLyrWFS &&
50✔
4984
            (lp->type == MS_LAYER_POINT || lp->type == MS_LAYER_LINE ||
1✔
4985
             lp->type == MS_LAYER_POLYGON)) {
4986
          char *pszOnlineResEncoded = msEncodeHTMLEntities(pszOnlineResLyrWFS);
1✔
4987
          char *pszLayerName = msEncodeHTMLEntities(lp->name);
1✔
4988

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

5008
          msFree(pszOnlineResEncoded);
1✔
5009
          msFree(pszLayerName);
1✔
5010
        } else if (pszOnlineResLyrWCS && lp->type == MS_LAYER_RASTER &&
50✔
5011
                   lp->connectiontype != MS_WMS) {
×
5012
          char *pszOnlineResEncoded = msEncodeHTMLEntities(pszOnlineResLyrWCS);
×
5013
          char *pszLayerName = msEncodeHTMLEntities(lp->name);
×
5014

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

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

5049
            msIO_printf("    <se:OnlineResource xlink:type=\"simple\"/>\n");
47✔
5050
            msIO_printf("    <TypeName>\n");
47✔
5051
            if (lp->type == MS_LAYER_RASTER && lp->connectiontype != MS_WMS)
47✔
5052
              msIO_printf(
×
5053
                  "      <se:CoverageTypeName>%s</se:CoverageTypeName>\n",
5054
                  pszLayerName);
5055
            else
5056
              msIO_printf("      <se:FeatureTypeName>%s</se:FeatureTypeName>\n",
47✔
5057
                          pszLayerName);
5058
            msIO_printf("    </TypeName>\n");
47✔
5059
            msIO_printf("  </LayerDescription>\n");
47✔
5060
          }
5061

5062
          msFree(pszLayerName);
49✔
5063
        }
5064
        /* break; */
5065
      }
5066
    }
5067
  }
5068

5069
  if (nVersion < OWS_1_3_0)
19✔
5070
    msIO_printf("</WMS_DescribeLayerResponse>\n");
3✔
5071
  else
5072
    msIO_printf("</DescribeLayerResponse>\n");
16✔
5073

5074
  /* free the stuff used for nested layers */
5075
  for (int i = 0; i < map->numlayers; i++) {
128✔
5076
    if (numNestedGroups[i] > 0) {
109✔
5077
      msFreeCharArray(nestedGroups[i], numNestedGroups[i]);
58✔
5078
    }
5079
  }
5080
  free(nestedGroups);
19✔
5081
  free(numNestedGroups);
19✔
5082
  free(isUsedInNestedGroup);
19✔
5083

5084
  return (MS_SUCCESS);
19✔
5085
}
19✔
5086

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

5110
  if (!hittest) {
46✔
5111
    /* we can skip a lot of testing if we already have a hittest, as it has
5112
     * already been done in the hittesting phase */
5113

5114
    sldenabled = msOWSLookupMetadata(&(map->web.metadata), "MO", "sld_enabled");
34✔
5115

5116
    if (sldenabled == NULL)
34✔
5117
      sldenabled = "true";
5118

5119
    for (int i = 0; i < numentries; i++) {
283✔
5120
      if (strcasecmp(names[i], "LAYER") == 0) {
249✔
5121
        pszLayer = values[i];
34✔
5122
      } else if (strcasecmp(names[i], "WIDTH") == 0)
215✔
5123
        nWidth = atoi(values[i]);
×
5124
      else if (strcasecmp(names[i], "HEIGHT") == 0)
215✔
5125
        nHeight = atoi(values[i]);
×
5126
      else if (strcasecmp(names[i], "FORMAT") == 0)
215✔
5127
        pszFormat = values[i];
34✔
5128
      else if (strcasecmp(names[i], "SCALE") == 0)
181✔
5129
        psScale = values[i];
×
5130

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

5149
      /* -------------------------------------------------------------------- */
5150
      /*      SLD support:                                                    */
5151
      /*        - because the request parameter "sld_version" is required in  */
5152
      /*          in WMS 1.3.0, it will be set regardless of OGR support.     */
5153
      /* -------------------------------------------------------------------- */
5154
      else if (strcasecmp(names[i], "SLD_VERSION") == 0)
169✔
5155
        sld_version = values[i];
17✔
5156
    }
5157

5158
    if (!pszLayer) {
34✔
5159
      msSetErrorWithStatus(
×
5160
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5161
          "Mandatory LAYER parameter missing in GetLegendGraphic request.",
5162
          "msWMSGetLegendGraphic()");
5163
      return msWMSException(map, nVersion, "LayerNotDefined",
×
5164
                            wms_exception_format);
×
5165
    }
5166
    if (!pszFormat) {
34✔
5167
      msSetErrorWithStatus(
×
5168
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5169
          "Mandatory FORMAT parameter missing in GetLegendGraphic request.",
5170
          "msWMSGetLegendGraphic()");
5171
      return msWMSException(map, nVersion, "InvalidFormat",
×
5172
                            wms_exception_format);
×
5173
    }
5174

5175
    if (nVersion >= OWS_1_3_0 && sld_version == NULL) {
34✔
5176
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5177
                           "Missing required parameter SLD_VERSION",
5178
                           "GetLegendGraphic()");
5179
      return msWMSException(map, nVersion, "MissingParameterValue",
×
5180
                            wms_exception_format);
×
5181
    }
5182
    if (nVersion >= OWS_1_3_0 && strcasecmp(sld_version, "1.1.0") != 0) {
34✔
5183
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5184
                           "SLD_VERSION must be 1.1.0", "GetLegendGraphic()");
5185
      return msWMSException(map, nVersion, "InvalidParameterValue",
×
5186
                            wms_exception_format);
×
5187
    }
5188

5189
    char ***nestedGroups =
5190
        (char ***)msSmallCalloc(map->numlayers, sizeof(char **));
34✔
5191
    int *numNestedGroups = (int *)msSmallCalloc(map->numlayers, sizeof(int));
34✔
5192
    int *isUsedInNestedGroup =
5193
        (int *)msSmallCalloc(map->numlayers, sizeof(int));
34✔
5194
    msWMSPrepareNestedGroups(map, nVersion, nestedGroups, numNestedGroups,
34✔
5195
                             isUsedInNestedGroup);
5196

5197
    /* check if layer name is valid. we check for layer's and group's name */
5198
    /* as well as wms_layer_group names */
5199
    for (int i = 0; i < map->numlayers; i++) {
277✔
5200
      layerObj *lp = GET_LAYER(map, i);
243✔
5201
      if (((map->name && strcasecmp(map->name, pszLayer) == 0) ||
243✔
5202
           (lp->name && strcasecmp(lp->name, pszLayer) == 0) ||
222✔
5203
           (lp->group && strcasecmp(lp->group, pszLayer) == 0) ||
194✔
5204
           ((numNestedGroups[i] > 0) &&
281✔
5205
            (msStringInArray(pszLayer, nestedGroups[i],
93✔
5206
                             numNestedGroups[i])))) &&
309✔
5207
          (msIntegerInArray(lp->index, ows_request->enabled_layers,
66✔
5208
                            ows_request->numlayers))) {
5209
        nLayers++;
66✔
5210
        lp->status = MS_ON;
66✔
5211
        iLayerIndex = i;
5212
        if (GET_LAYER(map, i)->connectiontype == MS_WMS) {
66✔
5213
          /* we do not cascade a wms layer if it contains at least
5214
           * one class with the property name set */
5215
          wms_layer = MS_TRUE;
5216
          for (int j = 0; j < lp->numclasses; j++) {
4✔
5217
            if (lp->_class[j]->name != NULL &&
×
5218
                strlen(lp->_class[j]->name) > 0) {
×
5219
              wms_layer = MS_FALSE;
5220
              break;
5221
            }
5222
          }
5223
        }
5224
      } else
5225
        lp->status = MS_OFF;
177✔
5226
    }
5227

5228
    /* free the stuff used for nested layers */
5229
    for (int i = 0; i < map->numlayers; i++) {
277✔
5230
      if (numNestedGroups[i] > 0) {
243✔
5231
        msFreeCharArray(nestedGroups[i], numNestedGroups[i]);
110✔
5232
      }
5233
    }
5234
    free(nestedGroups);
34✔
5235
    free(numNestedGroups);
34✔
5236
    free(isUsedInNestedGroup);
34✔
5237

5238
    if (nLayers == 0) {
34✔
5239
      msSetErrorWithStatus(
×
5240
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5241
          "Invalid layer given in the LAYER parameter. A layer might be disabled for \
5242
this request. Check wms/ows_enable_request settings.",
5243
          "msWMSGetLegendGraphic()");
5244
      return msWMSException(map, nVersion, "LayerNotDefined",
×
5245
                            wms_exception_format);
×
5246
    }
5247

5248
    /* if SCALE was provided in request, calculate an extent and use a default
5249
     * width and height */
5250
    if (psScale != NULL) {
34✔
5251
      double scale, cellsize;
5252

5253
      scale = atof(psScale);
5254
      map->width = 600;
×
5255
      map->height = 600;
×
5256

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

5259
      map->extent.maxx = cellsize * map->width / 2.0;
×
5260
      map->extent.maxy = cellsize * map->height / 2.0;
×
5261
      map->extent.minx = -map->extent.maxx;
×
5262
      map->extent.miny = -map->extent.maxy;
×
5263
    }
5264

5265
    /* It's a valid Cascading WMS GetLegendGraphic request */
5266
    if (wms_layer)
34✔
5267
      return msWMSLayerExecuteRequest(map, 1, 0, 0, 0, NULL,
4✔
5268
                                      WMS_GETLEGENDGRAPHIC);
4✔
5269

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

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

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

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

5352
    layerObj *lp = GET_LAYER(map, iLayerIndex);
×
5353
    int i;
5354
    for (i = 0; i < lp->numclasses; i++) {
×
5355
      if (lp->classgroup &&
×
5356
          (lp->_class[i]->group == NULL ||
×
5357
           strcasecmp(lp->_class[i]->group, lp->classgroup) != 0))
×
5358
        continue;
×
5359

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

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

5400
  if (img == NULL)
42✔
5401
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5402

5403
  msIO_setHeader("Content-Type", "%s", MS_IMAGE_MIME_TYPE(map->outputformat));
84✔
5404
  msIO_sendHeaders();
42✔
5405
  if (msSaveImage(map, img, NULL) != MS_SUCCESS)
42✔
5406
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5407

5408
  msFreeImage(img);
42✔
5409

5410
  return (MS_SUCCESS);
42✔
5411
}
5412

5413
/*
5414
** msWMSGetContentDependentLegend()
5415
*/
5416
static int msWMSGetContentDependentLegend(mapObj *map, int nVersion,
12✔
5417
                                          char **names, char **values,
5418
                                          int numentries,
5419
                                          const char *wms_exception_format,
5420
                                          owsRequestObj *ows_request) {
5421

5422
  /* turn off layer if WMS GetMap is not enabled */
5423
  for (int i = 0; i < map->numlayers; i++)
69✔
5424
    if (!msIntegerInArray(GET_LAYER(map, i)->index, ows_request->enabled_layers,
57✔
5425
                          ows_request->numlayers))
5426
      GET_LAYER(map, i)->status = MS_OFF;
×
5427

5428
  map_hittest hittest;
5429
  initMapHitTests(map, &hittest);
12✔
5430
  int status = msHitTestMap(map, &hittest);
12✔
5431
  if (status == MS_SUCCESS) {
12✔
5432
    status = msWMSLegendGraphic(map, nVersion, names, values, numentries,
12✔
5433
                                wms_exception_format, ows_request, &hittest);
5434
  }
5435
  freeMapHitTests(map, &hittest);
12✔
5436
  if (status != MS_SUCCESS) {
12✔
5437
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5438
  } else {
5439
    return MS_SUCCESS;
5440
  }
5441
}
5442

5443
/*
5444
** msWMSGetStyles() : return an SLD document for all layers that
5445
** have a status set to on or default.
5446
*/
5447
static int msWMSGetStyles(mapObj *map, int nVersion, char **names,
60✔
5448
                          char **values, int numentries,
5449
                          const char *wms_exception_format)
5450

5451
{
5452
  bool validlayer = false;
5453

5454
  char ***nestedGroups =
5455
      (char ***)msSmallCalloc(map->numlayers, sizeof(char **));
60✔
5456
  int *numNestedGroups = (int *)msSmallCalloc(map->numlayers, sizeof(int));
60✔
5457
  int *isUsedInNestedGroup = (int *)msSmallCalloc(map->numlayers, sizeof(int));
60✔
5458
  msWMSPrepareNestedGroups(map, nVersion, nestedGroups, numNestedGroups,
60✔
5459
                           isUsedInNestedGroup);
5460

5461
  const char *sldenabled =
5462
      msOWSLookupMetadata(&(map->web.metadata), "MO", "sld_enabled");
60✔
5463
  if (sldenabled == NULL)
60✔
5464
    sldenabled = "true";
5465

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

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

5497
    else if (strcasecmp(names[i], "SLD") == 0 && values[i] &&
247✔
5498
             strlen(values[i]) > 0 && strcasecmp(sldenabled, "true") == 0) {
×
5499
      msSLDApplySLDURL(map, values[i], -1, NULL, NULL);
×
5500
    }
5501

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

5508
  /* free the stuff used for nested layers */
5509
  for (int i = 0; i < map->numlayers; i++) {
1,140✔
5510
    if (numNestedGroups[i] > 0) {
1,080✔
5511
      msFreeCharArray(nestedGroups[i], numNestedGroups[i]);
55✔
5512
    }
5513
  }
5514
  free(nestedGroups);
60✔
5515
  free(numNestedGroups);
60✔
5516
  free(isUsedInNestedGroup);
60✔
5517

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

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

5547
  return (MS_SUCCESS);
5548
}
5549

5550
int msWMSGetSchemaExtension(mapObj *map) {
2✔
5551
  char *schemalocation = msEncodeHTMLEntities(msOWSGetSchemasLocation(map));
2✔
5552

5553
  msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
2✔
5554
  msIO_sendHeaders();
2✔
5555

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

5569
  free(schemalocation);
2✔
5570

5571
  return (MS_SUCCESS);
2✔
5572
}
5573

5574
#endif /* USE_WMS_SVR */
5575

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

5592
  /*
5593
  ** Process Params common to all requests
5594
  */
5595
  /* VERSION (WMTVER in 1.0.0) and REQUEST must be present in a valid request */
5596
  for (int i = 0; i < req->NumParams; i++) {
8,119✔
5597
    if (strcasecmp(req->ParamNames[i], "VERSION") == 0)
7,404✔
5598
      version = req->ParamValues[i];
704✔
5599
    else if (strcasecmp(req->ParamNames[i], "WMTVER") == 0 && version == NULL)
6,700✔
5600
      version = req->ParamValues[i];
×
5601
    else if (strcasecmp(req->ParamNames[i], "UPDATESEQUENCE") == 0)
6,700✔
5602
      updatesequence = req->ParamValues[i];
6✔
5603
    else if (strcasecmp(req->ParamNames[i], "REQUEST") == 0)
6,694✔
5604
      request = req->ParamValues[i];
706✔
5605
    else if (strcasecmp(req->ParamNames[i], "EXCEPTIONS") == 0)
5,988✔
5606
      wms_exception_format = req->ParamValues[i];
57✔
5607
    else if (strcasecmp(req->ParamNames[i], "SERVICE") == 0)
5,931✔
5608
      service = req->ParamValues[i];
713✔
5609
    else if (strcasecmp(req->ParamNames[i], "FORMAT") == 0)
5,218✔
5610
      format = req->ParamValues[i];
550✔
5611
    else if (strcasecmp(req->ParamNames[i], "LANGUAGE") == 0 &&
4,678✔
5612
             msOWSLookupMetadata(&(map->web.metadata), "MO",
10✔
5613
                                 "inspire_capabilities"))
5614
      language = req->ParamValues[i];
10✔
5615
  }
5616

5617
  /* If SERVICE is specified then it MUST be "WMS" */
5618
  if (service != NULL && strcasecmp(service, "WMS") != 0)
715✔
5619
    return MS_DONE; /* Not a WMS request */
5620

5621
  nVersion = msOWSParseVersionString(version);
715✔
5622
  if (nVersion == OWS_VERSION_BADFORMAT) {
715✔
5623
    /* Invalid version format. msSetError() has been called by
5624
     * msOWSParseVersionString() and we return the error as an exception
5625
     */
5626
    return msWMSException(map, OWS_VERSION_NOTSET, NULL, wms_exception_format);
8✔
5627
  }
5628

5629
  /*
5630
  ** GetCapbilities request needs the service parameter defined as WMS:
5631
  see section 7.1.3.2 wms 1.1.1 specs for description.
5632
  */
5633
  if (request && service == NULL &&
707✔
5634
      (strcasecmp(request, "capabilities") == 0 ||
3✔
5635
       strcasecmp(request, "GetCapabilities") == 0) &&
3✔
5636
      (nVersion >= OWS_1_1_0 || nVersion == OWS_VERSION_NOTSET)) {
×
5637
    if (force_wms_mode) {
×
5638
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5639
                           "Required SERVICE parameter missing.",
5640
                           "msWMSDispatch");
5641
      return msWMSException(map, nVersion, "ServiceNotDefined",
×
5642
                            wms_exception_format);
×
5643
    } else
5644
      return MS_DONE;
5645
  }
5646

5647
  /*
5648
  ** Dispatch request... we should probably do some validation on VERSION here
5649
  ** vs the versions we actually support.
5650
  */
5651
  if (request && (strcasecmp(request, "capabilities") == 0 ||
707✔
5652
                  strcasecmp(request, "GetCapabilities") == 0)) {
706✔
5653
    const char *enable_request;
5654
    int globally_enabled, disabled = MS_FALSE;
66✔
5655

5656
    if (nVersion == OWS_VERSION_NOTSET) {
66✔
5657
      version = msOWSLookupMetadata(&(map->web.metadata), "M",
10✔
5658
                                    "getcapabilities_version");
5659
      if (version)
10✔
5660
        nVersion = msOWSParseVersionString(version);
×
5661
      else
5662
        nVersion =
5663
            OWS_1_3_0; /* VERSION is optional with getCapabilities only */
5664
    }
5665

5666
    if (msOWSMakeAllLayersUnique(map) != MS_SUCCESS)
66✔
5667
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5668

5669
    msOWSRequestLayersEnabled(map, "M", "GetCapabilities", ows_request);
66✔
5670

5671
    enable_request =
5672
        msOWSLookupMetadata(&map->web.metadata, "OM", "enable_request");
66✔
5673
    globally_enabled =
5674
        msOWSParseRequestMetadata(enable_request, "GetCapabilities", &disabled);
66✔
5675

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

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

5710
    if (getcontext_enabled == NULL || atoi(getcontext_enabled) == 0) {
×
5711
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5712
                           "GetContext not enabled on this server.",
5713
                           "msWMSDispatch()");
5714
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5715
    }
5716

5717
    if (msOWSMakeAllLayersUnique(map) != MS_SUCCESS)
×
5718
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5719

5720
    msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
×
5721
    msIO_sendHeaders();
×
5722

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

5753
  /* If SERVICE, VERSION and REQUEST not included than this isn't a WMS req*/
5754
  if (service == NULL && nVersion == OWS_VERSION_NOTSET && request == NULL)
641✔
5755
    return MS_DONE; /* Not a WMS request */
5756

5757
  /* VERSION *and* REQUEST required by both getMap and getFeatureInfo */
5758
  if (nVersion == OWS_VERSION_NOTSET) {
641✔
5759
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
5760
                         "Incomplete WMS request: VERSION parameter missing",
5761
                         "msWMSDispatch()");
5762
    return msWMSException(map, OWS_VERSION_NOTSET, NULL, wms_exception_format);
1✔
5763
  }
5764

5765
  /*check if the version is one of the supported versions*/
5766
  if (nVersion != OWS_1_0_0 && nVersion != OWS_1_1_0 && nVersion != OWS_1_1_1 &&
640✔
5767
      nVersion != OWS_1_3_0) {
381✔
5768
    msSetErrorWithStatus(
×
5769
        MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5770
        "Invalid WMS version: VERSION %s is not supported. Supported "
5771
        "versions are 1.0.0, 1.1.0, 1.1.1, 1.3.0",
5772
        "msWMSDispatch()", version);
5773
    return msWMSException(map, OWS_VERSION_NOTSET, NULL, wms_exception_format);
×
5774
  }
5775

5776
  if (request == NULL) {
640✔
5777
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5778
                         "Incomplete WMS request: REQUEST parameter missing",
5779
                         "msWMSDispatch()");
5780
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5781
  }
5782

5783
  /* hack !? The function can return MS_DONE ... be sure it's a wms request
5784
   * before checking the enabled layers */
5785
  if ((strcasecmp(request, "GetStyles") == 0) ||
640✔
5786
      (strcasecmp(request, "GetLegendGraphic") == 0) ||
580✔
5787
      (strcasecmp(request, "GetSchemaExtension") == 0) ||
533✔
5788
      (strcasecmp(request, "map") == 0 || strcasecmp(request, "GetMap") == 0) ||
531✔
5789
      (strcasecmp(request, "feature_info") == 0 ||
67✔
5790
       strcasecmp(request, "GetFeatureInfo") == 0) ||
67✔
5791
      (strcasecmp(request, "DescribeLayer") == 0)) {
21✔
5792
    const char *request_tmp;
5793
    if (strcasecmp(request, "map") == 0)
640✔
5794
      request_tmp = "GetMap";
5795
    else if (strcasecmp(request, "feature_info") == 0)
640✔
5796
      request_tmp = "GetFeatureInfo";
5797
    else
5798
      request_tmp = request;
5799

5800
    msOWSRequestLayersEnabled(map, "M", request_tmp, ows_request);
640✔
5801
    if (ows_request->numlayers == 0) {
640✔
5802
      msSetErrorWithStatus(
1✔
5803
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5804
          "WMS request not enabled. Check wms/ows_enable_request settings.",
5805
          "msWMSDispatch()");
5806
      return msWMSException(map, nVersion, NULL, wms_exception_format);
1✔
5807
    }
5808
  }
5809

5810
  if (msOWSMakeAllLayersUnique(map) != MS_SUCCESS)
639✔
5811
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5812

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

5847
  if (strcasecmp(request, "GetStyles") == 0)
605✔
5848
    return msWMSGetStyles(map, nVersion, req->ParamNames, req->ParamValues,
60✔
5849
                          req->NumParams, wms_exception_format);
60✔
5850

5851
  else if (request && strcasecmp(request, "GetSchemaExtension") == 0)
545✔
5852
    return msWMSGetSchemaExtension(map);
2✔
5853

5854
  /* getMap parameters are used by both getMap, getFeatureInfo, and content
5855
   * dependent legendgraphics */
5856
  if (strcasecmp(request, "map") == 0 || strcasecmp(request, "GetMap") == 0 ||
543✔
5857
      strcasecmp(request, "feature_info") == 0 ||
79✔
5858
      strcasecmp(request, "GetFeatureInfo") == 0 ||
79✔
5859
      strcasecmp(request, "DescribeLayer") == 0 || isContentDependentLegend) {
33✔
5860

5861
    const int status = msWMSLoadGetMapParams(
543✔
5862
        map, nVersion, req->ParamNames, req->ParamValues, req->NumParams,
5863
        wms_exception_format, request, ows_request);
5864
    if (status != MS_SUCCESS)
543✔
5865
      return status;
5866
  }
5867

5868
  /* This function owns validated_language, so remember to free it later*/
5869
  char *validated_language = msOWSGetLanguageFromList(map, "MO", language);
486✔
5870
  if (validated_language != NULL) {
486✔
5871
    msMapSetLanguageSpecificConnection(map, validated_language);
47✔
5872
  }
5873
  msFree(validated_language);
486✔
5874

5875
  if (strcasecmp(request, "map") == 0 || strcasecmp(request, "GetMap") == 0)
486✔
5876
    return msWMSGetMap(map, nVersion, req->ParamNames, req->ParamValues,
409✔
5877
                       req->NumParams, wms_exception_format, ows_request);
409✔
5878
  else if (strcasecmp(request, "feature_info") == 0 ||
77✔
5879
           strcasecmp(request, "GetFeatureInfo") == 0)
77✔
5880
    return msWMSFeatureInfo(map, nVersion, req->ParamNames, req->ParamValues,
46✔
5881
                            req->NumParams, wms_exception_format, ows_request);
46✔
5882
  else if (strcasecmp(request, "DescribeLayer") == 0) {
31✔
5883
    return msWMSDescribeLayer(map, nVersion, req->ParamNames, req->ParamValues,
19✔
5884
                              req->NumParams, wms_exception_format);
19✔
5885
  } else if (isContentDependentLegend) {
12✔
5886
    return msWMSGetContentDependentLegend(map, nVersion, req->ParamNames,
12✔
5887
                                          req->ParamValues, req->NumParams,
5888
                                          wms_exception_format, ows_request);
12✔
5889
  }
5890

5891
  /* Hummmm... incomplete or unsupported WMS request */
5892
  if (service != NULL && strcasecmp(service, "WMS") == 0) {
×
5893
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5894
                         "Incomplete or unsupported WMS request",
5895
                         "msWMSDispatch()");
5896
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5897
  } else
5898
    return MS_DONE; /* Not a WMS request */
5899
#else
5900
  msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5901
                       "WMS server support is not available.",
5902
                       "msWMSDispatch()");
5903
  return (MS_FAILURE);
5904
#endif
5905
}
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