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

MapServer / MapServer / 18964964805

31 Oct 2025 06:44AM UTC coverage: 41.688% (+0.02%) from 41.673%
18964964805

push

github

web-flow
Remove legacy bootstrap4 files (#7363)

62724 of 150460 relevant lines covered (41.69%)

25214.53 hits per line

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

82.8
/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
#include "mapquery.h"
47

48
#include <cassert>
49
#include <stdarg.h>
50
#include <time.h>
51
#include <string.h>
52

53
#include <map>
54
#include <set>
55
#include <string>
56
#include <vector>
57

58
#ifdef _WIN32
59
#include <process.h>
60
#endif
61

62
/* ==================================================================
63
 * WMS Server stuff.
64
 * ================================================================== */
65
#ifdef USE_WMS_SVR
66

67
/*
68
** msWMSException()
69
**
70
** Report current MapServer error in requested format.
71
*/
72

73
static int msWMSException(mapObj *map, int nVersion, const char *exception_code,
78✔
74
                          const char *wms_exception_format) {
75
  char *schemalocation = NULL;
76

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

81
  /* get schema location */
82
  schemalocation = msEncodeHTMLEntities(msOWSGetSchemasLocation(map));
78✔
83

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

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

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

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

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

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

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

131
      msIO_printf(
32✔
132
          "<?xml version='1.0' encoding=\"UTF-8\" standalone=\"no\" ?>\n");
133

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

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

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

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

169
    if (exception_code)
73✔
170
      msIO_printf("<ServiceException code=\"%s\">\n", exception_code);
54✔
171
    else
172
      msIO_printf("<ServiceException>\n");
19✔
173
    msWriteErrorXML(stdout);
73✔
174
    msIO_printf("</ServiceException>\n");
73✔
175
    msIO_printf("</ServiceExceptionReport>\n");
73✔
176
  }
177
  free(schemalocation);
78✔
178

179
  return MS_FAILURE; /* so that we can call 'return msWMSException();' anywhere
78✔
180
                      */
181
}
182

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

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

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

227
  return true;
228
}
229

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

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

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

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

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

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

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

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

340
  return MS_SUCCESS;
341
}
342

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

354
  if (!map)
11✔
355
    return MS_FAILURE;
356

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

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

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

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

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

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

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

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

410
    const int curfilter = ows_request->layerwmsfilterindex[lp->index];
17✔
411

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

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

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

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

440
#ifdef do_we_need_this
441
    FLTProcessPropertyIsNull(psNode, map, lp->index);
442

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

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

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

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

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

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

487
#endif
488

489
    /* Apply filter to this layer */
490

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

494
    hashTableObj *tmpTable = msCreateHashTable();
15✔
495

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

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

509
    msInsertHashTable(&(lp->metadata), "gml_wmsfilter_flag", "true");
15✔
510

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

513
    msRemoveHashTable(&(lp->metadata), "gml_wmsfilter_flag");
15✔
514

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

522
    msFreeHashTable(tmpTable);
15✔
523

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

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

538
    FLTFreeFilterEncodingNode(psNode);
15✔
539

540
  } /* for */
15✔
541

542
  msFreeCharArray(paszFilters, numfilters);
10✔
543

544
  return MS_SUCCESS;
545
}
546

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

566
  for (int i = 0; i < map->numlayers; i++) {
7,269✔
567
    nestedGroups[i] = NULL;     /* default */
6,339✔
568
    numNestedGroups[i] = 0;     /* default */
6,339✔
569
    isUsedInNestedGroup[i] = 0; /* default */
6,339✔
570

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

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

625
  bool isextentavalue = false;
626
  bool isextentarange = false;
627
  bool ischaracter = false;
628

629
  if (forcecharacter)
630
    ischaracter = true;
631

632
  if (!value || !dimensionextent)
29✔
633
    return false;
634

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

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

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

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

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

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

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

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

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

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

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

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

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

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

844
  return uservaluevalid;
845
}
29✔
846

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

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

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

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

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

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

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

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

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

969
  const char *sldenabled = NULL;
970
  const char *sld_url = NULL;
971
  const char *sld_body = NULL;
972

973
  const char *filter = NULL;
974

975
  bool compliance_mode = false;
976

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

981
  msAdjustExtent(&(map->extent), map->width, map->height);
563✔
982

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

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

1000
      if (sldenabled == NULL) {
146✔
1001
        sldenabled = "true";
1002
      }
1003

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

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

1019
    if (strcasecmp(names[i], "REQUEST") == 0) {
6,797✔
1020
      request = values[i];
563✔
1021
    }
1022

1023
    if (strcasecmp(names[i], "LAYERS") == 0) {
6,797✔
1024
      std::vector<int> layerOrder(map->numlayers);
560✔
1025

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

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

1050
      for (int iLayer = 0; iLayer < map->numlayers; iLayer++) {
3,730✔
1051
        map->layerorder[iLayer] = iLayer;
3,172✔
1052
      }
1053

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

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

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

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

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

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

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

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

1152
        srsbuffer = "EPSG:";
1153
        srsbuffer += (values[i] + 5);
533✔
1154
        epsgbuf = srsbuffer;
1155

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

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

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

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

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

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

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

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

1340
  /*validate the exception format WMS 1.3.0 section 7.3.3.11*/
1341

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

1354
  int need_axis_swap = MS_FALSE;
1355
  if (bboxfound && nVersion >= OWS_1_3_0) {
559✔
1356
    rectObj rect;
1357
    projectionObj proj;
1358

1359
    /*we have already validated that the request format when reading
1360
     the request parameters*/
1361
    rect = map->extent;
213✔
1362

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

1390
    map->extent = rect;
213✔
1391

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

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

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

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

1427
      map->width += 2 * map_edge_buffer;
1✔
1428
      map->height += 2 * map_edge_buffer;
1✔
1429

1430
      if (map_edge_buffer > 0) {
1431
        char tilebufferstr[64];
1432

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

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

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

1462
      lp = (GET_LAYER(map, i));
1,707✔
1463
      if (lp->status != MS_ON && lp->status != MS_DEFAULT)
1,707✔
1464
        continue;
913✔
1465

1466
      if (msOWSLookupMetadata(&(lp->metadata), "MO", "timedefault"))
794✔
1467
        timerequest = true;
1468
    }
1469
  }
1470

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

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

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

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

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

1528
  if (format != NULL)
529✔
1529
    msApplyOutputFormat(&(map->outputformat), format, transparent);
496✔
1530

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

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

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

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

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

1640
      const double reqy = ((double)map->width) * dy / dx;
502✔
1641

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

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

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

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

1689
      if (nVersion >= OWS_1_3_0)
499✔
1690
        nTmp = msLoadProjectionStringEPSG(&newProj, srsbuffer.c_str());
208✔
1691
      else
1692
        nTmp = msLoadProjectionString(&newProj, srsbuffer.c_str());
291✔
1693
      if (nTmp != 0) {
499✔
1694
        msFreeProjection(&newProj);
×
1695
        return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1696
      }
1697
    }
1698

1699
    if (nonsquare_enabled ||
834✔
1700
        msProjectionsDiffer(&(map->projection), &newProj)) {
333✔
1701
      msMapSetLayerProjections(map);
267✔
1702
    }
1703
    msFreeProjection(&newProj);
501✔
1704
  }
1705

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

1716
    if (nTmp != 0)
499✔
1717
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1718

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

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

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

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

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

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

1802
    char **tokens = msStringSplitComplex(styles, ",", &n, MS_ALLOWEMPTYTOKENS);
23✔
1803
    for (int i = 0; i < n; i++) {
55✔
1804
      if (tokens[i] && strlen(tokens[i]) > 0 &&
35✔
1805
          strcasecmp(tokens[i], "default") != 0) {
25✔
1806
        if (!hasCheckedLayerUnicity) {
22✔
1807
          hasCheckedLayerUnicity = true;
1808
          bool bLayerInserted = false;
1809

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

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

1837
                layerCopyIndex = msInsertLayer(map, psTmpLayer, -1);
6✔
1838

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

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

1854
          if (bLayerInserted) {
17✔
1855
            msOWSRequestLayersEnabled(map, "M", "GetMap", ows_request);
4✔
1856
          }
1857
        }
1858

1859
        if (static_cast<int>(wmslayers.size()) == n) {
22✔
1860
          for (int j = 0; j < map->numlayers; j++) {
152✔
1861
            layerObj *lp = GET_LAYER(map, j);
133✔
1862
            if ((lp->name && strcasecmp(lp->name, wmslayers[i].c_str()) == 0) ||
133✔
1863
                (lp->group &&
113✔
1864
                 strcasecmp(lp->group, wmslayers[i].c_str()) == 0)) {
4✔
1865
              bool found = false;
1866

1867
#ifdef USE_WMS_LYR
1868
              if (lp->connectiontype == MS_WMS) {
20✔
1869
                const char *pszWmsStyle =
1870
                    msOWSLookupMetadata(&(lp->metadata), "MO", "style");
2✔
1871
                if (pszWmsStyle != NULL &&
2✔
1872
                    strcasecmp(pszWmsStyle, tokens[i]) == 0)
2✔
1873
                  found = true;
1874
              }
1875
#endif // USE_WMS_LYR
1876

1877
              if (!found) {
1878
                for (int k = 0; k < lp->numclasses; k++) {
32✔
1879
                  if (lp->_class[k]->group &&
30✔
1880
                      strcasecmp(lp->_class[k]->group, tokens[i]) == 0) {
30✔
1881
                    msFree(lp->classgroup);
17✔
1882
                    lp->classgroup = msStrdup(tokens[i]);
17✔
1883
                    found = true;
1884
                    break;
1885
                  }
1886
                }
1887
              }
1888

1889
              if (!found) {
1890
                msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
2✔
1891
                                     "Style (%s) not defined on layer.",
1892
                                     "msWMSLoadGetMapParams()", tokens[i]);
1893
                msFreeCharArray(tokens, n);
2✔
1894

1895
                return msWMSException(map, nVersion, "StyleNotDefined",
2✔
1896
                                      wms_exception_format);
3✔
1897
              }
1898
              /* Check the style of the root layer */
1899
            } else if (map->name &&
113✔
1900
                       strcasecmp(map->name, wmslayers[i].c_str()) == 0) {
113✔
1901
              const char *styleName =
1902
                  msOWSLookupMetadata(&(map->web.metadata), "MO", "style_name");
15✔
1903
              if (styleName == NULL)
15✔
1904
                styleName = "default";
1905
              char *pszEncodedStyleName = msEncodeHTMLEntities(styleName);
15✔
1906
              if (strcasecmp(pszEncodedStyleName, tokens[i]) != 0) {
15✔
1907
                msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
1908
                                     "Style (%s) not defined on root layer.",
1909
                                     "msWMSLoadGetMapParams()", tokens[i]);
1910
                msFreeCharArray(tokens, n);
×
1911
                msFree(pszEncodedStyleName);
×
1912

1913
                return msWMSException(map, nVersion, "StyleNotDefined",
×
1914
                                      wms_exception_format);
1915
              }
1916
              msFree(pszEncodedStyleName);
15✔
1917
            }
1918
          }
1919
        } else {
1920
          msSetErrorWithStatus(
1✔
1921
              MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1922
              "Invalid style (%s). Mapserver is expecting an empty "
1923
              "string for the STYLES : STYLES= or STYLES=,,, or using "
1924
              "keyword default  STYLES=default,default, ...",
1925
              "msWMSLoadGetMapParams()", styles);
1926
          msFreeCharArray(tokens, n);
1✔
1927
          return msWMSException(map, nVersion, "StyleNotDefined",
1✔
1928
                                wms_exception_format);
1929
        }
1930
      }
1931
    }
1932
    msFreeCharArray(tokens, n);
20✔
1933
  }
1934

1935
  /*
1936
  ** WMS extents are edge to edge while MapServer extents are center of
1937
  ** pixel to center of pixel.  Here we try to adjust the WMS extents
1938
  ** in by half a pixel.  We wait till here because we want to ensure we
1939
  ** are doing this in terms of the correct WIDTH and HEIGHT.
1940
  */
1941
  if (adjust_extent && map->width > 1 && map->height > 1 &&
511✔
1942
      !bbox_pixel_is_point) {
1943
    double dx, dy;
1944

1945
    dx = (map->extent.maxx - map->extent.minx) / map->width;
491✔
1946
    map->extent.minx += dx * 0.5;
491✔
1947
    map->extent.maxx -= dx * 0.5;
491✔
1948

1949
    dy = (map->extent.maxy - map->extent.miny) / map->height;
491✔
1950
    map->extent.miny += dy * 0.5;
491✔
1951
    map->extent.maxy -= dy * 0.5;
491✔
1952
  }
1953

1954
  if (request && strcasecmp(request, "DescribeLayer") != 0) {
511✔
1955
    if (!srsfound) {
492✔
1956
      if (nVersion >= OWS_1_3_0)
3✔
1957
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
2✔
1958
                             "Missing required parameter CRS",
1959
                             "msWMSLoadGetMapParams()");
1960
      else
1961
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
1962
                             "Missing required parameter SRS",
1963
                             "msWMSLoadGetMapParams()");
1964

1965
      return msWMSException(map, nVersion, "MissingParameterValue",
3✔
1966
                            wms_exception_format);
1967
    }
1968

1969
    if (!bboxfound) {
489✔
1970
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
1971
                           "Missing required parameter BBOX",
1972
                           "msWMSLoadGetMapParams()");
1973
      return msWMSException(map, nVersion, "MissingParameterValue",
1✔
1974
                            wms_exception_format);
1975
    }
1976

1977
    if (!formatfound && (strcasecmp(request, "GetMap") == 0 ||
488✔
1978
                         strcasecmp(request, "map") == 0)) {
8✔
1979
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
1980
                           "Missing required parameter FORMAT",
1981
                           "msWMSLoadGetMapParams()");
1982
      return msWMSException(map, nVersion, "MissingParameterValue",
1✔
1983
                            wms_exception_format);
1984
    }
1985

1986
    if (!widthfound) {
487✔
1987
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
1988
                           "Missing required parameter WIDTH",
1989
                           "msWMSLoadGetMapParams()");
1990
      return msWMSException(map, nVersion, "MissingParameterValue",
1✔
1991
                            wms_exception_format);
1992
    }
1993

1994
    if (!heightfound) {
486✔
1995
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
1996
                           "Missing required parameter HEIGHT",
1997
                           "msWMSLoadGetMapParams()");
1998
      return msWMSException(map, nVersion, "MissingParameterValue",
1✔
1999
                            wms_exception_format);
2000
    }
2001

2002
    if (styles == nullptr && sld_url == nullptr && sld_body == nullptr &&
82✔
2003
        (strcasecmp(request, "GetMap") == 0 ||
5✔
2004
         strcasecmp(request, "GetFeatureInfo") == 0) &&
490✔
2005
        msOWSLookupMetadata(&(map->web.metadata), "M",
1✔
2006
                            "allow_getmap_without_styles") == nullptr) {
2007
      msSetErrorWithStatus(
1✔
2008
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
2009
          "Missing required parameter STYLES. Note to service administrators: "
2010
          "defining the \"wms_allow_getmap_without_styles\" \"true\" "
2011
          "MAP.WEB.METADATA "
2012
          "item will disable this check (backward compatibility with behavior "
2013
          "of MapServer < 8.0)",
2014
          "msWMSLoadGetMapParams()");
2015
      return msWMSException(map, nVersion, "MissingParameterValue",
1✔
2016
                            wms_exception_format);
2017
    }
2018
  }
2019

2020
  /*
2021
  ** Apply vendor-specific filter if specified
2022
  */
2023
  if (filter) {
503✔
2024
    if (sld_url || sld_body) {
13✔
2025
      msSetErrorWithStatus(
×
2026
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
2027
          "Vendor-specific FILTER parameter cannot be used with SLD or "
2028
          "SLD_BODY.",
2029
          "msWMSLoadGetMapParams()");
2030
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
2031
    }
2032

2033
    if (msWMSApplyFilter(map, nVersion, filter, need_axis_swap,
13✔
2034
                         wms_exception_format, ows_request) == MS_FAILURE) {
2035
      return MS_FAILURE; /* msWMSException(map, nVersion,
2036
                            "InvalidFilterRequest"); */
2037
    }
2038
  }
2039

2040
  return MS_SUCCESS;
2041
}
563✔
2042

2043
/*
2044
**
2045
*/
2046
static void msWMSPrintRequestCap(int nVersion, const char *request,
275✔
2047
                                 const char *script_url, const char *formats,
2048
                                 ...) {
2049
  va_list argp;
2050

2051
  msIO_printf("    <%s>\n", request);
275✔
2052

2053
  /* We expect to receive a NULL-terminated args list of formats */
2054
  va_start(argp, formats);
275✔
2055
  const char *fmt = formats;
2056
  while (fmt != NULL) {
1,285✔
2057
    /* Special case for early WMS with subelements in Format (bug 908) */
2058
    char *encoded;
2059
    if (nVersion < OWS_1_1_0) {
1,010✔
2060
      encoded = msStrdup(fmt);
6✔
2061
    }
2062

2063
    /* otherwise we HTML code special characters */
2064
    else {
2065
      encoded = msEncodeHTMLEntities(fmt);
1,004✔
2066
    }
2067

2068
    msIO_printf("      <Format>%s</Format>\n", encoded);
1,010✔
2069
    msFree(encoded);
1,010✔
2070

2071
    fmt = va_arg(argp, const char *);
1,010✔
2072
  }
2073
  va_end(argp);
275✔
2074

2075
  msIO_printf("      <DCPType>\n");
275✔
2076
  msIO_printf("        <HTTP>\n");
275✔
2077
  /* The URL should already be HTML encoded. */
2078
  if (nVersion == OWS_1_0_0) {
275✔
2079
    msIO_printf("          <Get onlineResource=\"%s\" />\n", script_url);
6✔
2080
    msIO_printf("          <Post onlineResource=\"%s\" />\n", script_url);
6✔
2081
  } else {
2082
    msIO_printf("          <Get><OnlineResource "
269✔
2083
                "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
2084
                "xlink:href=\"%s\"/></Get>\n",
2085
                script_url);
2086
    msIO_printf("          <Post><OnlineResource "
269✔
2087
                "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
2088
                "xlink:href=\"%s\"/></Post>\n",
2089
                script_url);
2090
  }
2091

2092
  msIO_printf("        </HTTP>\n");
275✔
2093
  msIO_printf("      </DCPType>\n");
275✔
2094
  msIO_printf("    </%s>\n", request);
275✔
2095
}
275✔
2096

2097
void msWMSPrintAttribution(FILE *stream, const char *tabspace,
240✔
2098
                           hashTableObj *metadata,
2099
                           const char * /*namespaces*/) {
2100
  if (stream && metadata) {
240✔
2101
    const char *title =
2102
        msOWSLookupMetadata(metadata, "MO", "attribution_title");
240✔
2103
    const char *onlineres =
2104
        msOWSLookupMetadata(metadata, "MO", "attribution_onlineresource");
240✔
2105
    const char *logourl =
2106
        msOWSLookupMetadata(metadata, "MO", "attribution_logourl_width");
240✔
2107

2108
    if (title || onlineres || logourl) {
240✔
2109
      msIO_printf("%s<Attribution>\n", tabspace);
3✔
2110
      if (title) {
3✔
2111
        char *pszEncodedValue = msEncodeHTMLEntities(title);
3✔
2112
        msIO_fprintf(stream, "%s%s<Title>%s</Title>\n", tabspace, tabspace,
3✔
2113
                     pszEncodedValue);
2114
        free(pszEncodedValue);
3✔
2115
      }
2116

2117
      if (onlineres) {
3✔
2118
        char *pszEncodedValue = msEncodeHTMLEntities(onlineres);
1✔
2119
        msIO_fprintf(
1✔
2120
            stream,
2121
            "%s%s<OnlineResource xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
2122
            "xlink:href=\"%s\"/>\n",
2123
            tabspace, tabspace, pszEncodedValue);
2124
        free(pszEncodedValue);
1✔
2125
      }
2126

2127
      if (logourl) {
3✔
2128
        msOWSPrintURLType(stream, metadata, "MO", "attribution_logourl",
1✔
2129
                          OWS_NOERR, NULL, "LogoURL", NULL, " width=\"%s\"",
2130
                          " height=\"%s\"",
2131
                          ">\n             <Format>%s</Format",
2132
                          "\n             <OnlineResource "
2133
                          "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
2134
                          " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
2135
                          "          ",
2136
                          MS_FALSE, MS_TRUE, MS_TRUE, MS_TRUE, MS_TRUE, NULL,
2137
                          NULL, NULL, NULL, NULL, "        ");
2138
      }
2139
      msIO_printf("%s</Attribution>\n", tabspace);
3✔
2140
    }
2141
  }
2142
}
240✔
2143

2144
/*
2145
** msWMSPrintScaleHint()
2146
**
2147
** Print a Min/MaxScaleDenominator tag for the layer if applicable.
2148
** used for WMS >=1.3.0
2149
*/
2150
void msWMSPrintScaleDenominator(const char *tabspace, double minscaledenom,
118✔
2151
                                double maxscaledenom) {
2152
  if (minscaledenom > 0)
118✔
2153
    msIO_printf("%s<MinScaleDenominator>%g</MinScaleDenominator>\n", tabspace,
8✔
2154
                minscaledenom);
2155

2156
  if (maxscaledenom > 0)
118✔
2157
    msIO_printf("%s<MaxScaleDenominator>%g</MaxScaleDenominator>\n", tabspace,
2✔
2158
                maxscaledenom);
2159
}
118✔
2160

2161
/*
2162
** msWMSPrintScaleHint()
2163
**
2164
** Print a ScaleHint tag for this layer if applicable.
2165
**
2166
** (see WMS 1.1.0 sect. 7.1.5.4) The WMS defines the scalehint values as
2167
** the ground distance in meters of the southwest to northeast diagonal of
2168
** the central pixel of a map.  ScaleHint values are the min and max
2169
** recommended values of that diagonal.
2170
*/
2171
void msWMSPrintScaleHint(const char *tabspace, double minscaledenom,
124✔
2172
                         double maxscaledenom, double resolution) {
2173
  double scalehintmin = 0.0, scalehintmax = 0.0;
2174

2175
  const double diag = sqrt(2.0);
2176

2177
  if (minscaledenom > 0)
124✔
2178
    scalehintmin =
5✔
2179
        diag * (minscaledenom / resolution) / msInchesPerUnit(MS_METERS, 0);
5✔
2180
  if (maxscaledenom > 0)
124✔
2181
    scalehintmax =
2✔
2182
        diag * (maxscaledenom / resolution) / msInchesPerUnit(MS_METERS, 0);
2✔
2183

2184
  if (scalehintmin > 0.0 || scalehintmax > 0.0) {
124✔
2185
    msIO_printf("%s<ScaleHint min=\"%.15g\" max=\"%.15g\" />\n", tabspace,
5✔
2186
                scalehintmin, scalehintmax);
2187
    if (scalehintmax == 0.0)
5✔
2188
      msIO_printf("%s<!-- WARNING: Only MINSCALEDENOM and no MAXSCALEDENOM "
3✔
2189
                  "specified in "
2190
                  "the mapfile. A default value of 0 has been returned for the "
2191
                  "Max ScaleHint but this is probably not what you want. -->\n",
2192
                  tabspace);
2193
  }
2194
}
124✔
2195

2196
/*
2197
** msWMSPrintAuthorityURL()
2198
**
2199
** Print an AuthorityURL tag if applicable.
2200
*/
2201
void msWMSPrintAuthorityURL(FILE *stream, const char *tabspace,
240✔
2202
                            hashTableObj *metadata, const char *namespaces) {
2203
  if (stream && metadata) {
240✔
2204
    const char *pszWmsAuthorityName =
2205
        msOWSLookupMetadata(metadata, namespaces, "authorityurl_name");
240✔
2206
    const char *pszWMSAuthorityHref =
2207
        msOWSLookupMetadata(metadata, namespaces, "authorityurl_href");
240✔
2208

2209
    /* AuthorityURL only makes sense if you have *both* the name and url */
2210
    if (pszWmsAuthorityName && pszWMSAuthorityHref) {
240✔
2211
      msOWSPrintEncodeMetadata(
7✔
2212
          stream, metadata, namespaces, "authorityurl_name", OWS_NOERR,
2213
          (std::string(tabspace) + "<AuthorityURL name=\"%s\">\n").c_str(),
7✔
2214
          NULL);
2215
      msOWSPrintEncodeMetadata(
7✔
2216
          stream, metadata, namespaces, "authorityurl_href", OWS_NOERR,
2217
          (std::string(tabspace) +
7✔
2218
           "  <OnlineResource xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
2219
           "xlink:href=\"%s\"/>\n")
2220
              .c_str(),
2221
          NULL);
2222
      msIO_printf("%s</AuthorityURL>\n", tabspace);
7✔
2223
    } else if (pszWmsAuthorityName || pszWMSAuthorityHref) {
233✔
2224
      msIO_printf(
×
2225
          "%s<!-- WARNING: Both wms_authorityurl_name and "
2226
          "wms_authorityurl_href must be set to output an AuthorityURL -->\n",
2227
          tabspace);
2228
    }
2229
  }
2230
}
240✔
2231

2232
/*
2233
** msWMSPrintIdentifier()
2234
**
2235
** Print an Identifier tag if applicable.
2236
*/
2237
void msWMSPrintIdentifier(FILE *stream, const char *tabspace,
240✔
2238
                          hashTableObj *metadata, const char *namespaces) {
2239
  if (stream && metadata) {
240✔
2240
    const char *pszWMSIdentifierAuthority =
2241
        msOWSLookupMetadata(metadata, namespaces, "identifier_authority");
240✔
2242
    const char *pszWMSIdentifierValue =
2243
        msOWSLookupMetadata(metadata, namespaces, "identifier_value");
240✔
2244

2245
    /* Identifier only makes sense if you have *both* the authority and value */
2246
    if (pszWMSIdentifierAuthority && pszWMSIdentifierValue) {
240✔
2247
      msOWSPrintEncodeMetadata(
73✔
2248
          stream, metadata, namespaces, "identifier_authority", OWS_NOERR,
2249
          (std::string(tabspace) + "<Identifier authority=\"%s\">").c_str(),
73✔
2250
          NULL);
2251
      msOWSPrintEncodeMetadata(stream, metadata, namespaces, "identifier_value",
73✔
2252
                               OWS_NOERR, "%s</Identifier>\n", NULL);
2253
    } else if (pszWMSIdentifierAuthority || pszWMSIdentifierValue) {
167✔
2254
      msIO_printf(
×
2255
          "%s<!-- WARNING: Both wms_identifier_authority and "
2256
          "wms_identifier_value must be set to output an Identifier -->\n",
2257
          tabspace);
2258
    }
2259
  }
2260
}
240✔
2261

2262
/*
2263
** msWMSPrintKeywordlist()
2264
**
2265
** Print a Keywordlist tag if applicable.
2266
*/
2267
void msWMSPrintKeywordlist(FILE *stream, const char *tabspace, const char *name,
302✔
2268
                           hashTableObj *metadata, const char *namespaces,
2269
                           int nVersion) {
2270
  std::string newname(name); /* max. rootlayer_keywordlist_items          */
302✔
2271
  newname += "_items";
2272

2273
  std::string vocname(name); /* max. rootlayer_keywordlist_vocabulary     */
302✔
2274
  vocname += "_vocabulary";
2275

2276
  if (nVersion == OWS_1_0_0) {
302✔
2277
    /* <Keywords> in V 1.0.0 */
2278
    /* The 1.0.0 spec doesn't specify which delimiter to use so let's use spaces
2279
     */
2280
    msOWSPrintEncodeMetadataList(
4✔
2281
        stream, metadata, namespaces, name,
2282
        (std::string(tabspace) + "<Keywords>").c_str(),
8✔
2283
        (std::string(tabspace) + "</Keywords>\n").c_str(), "%s ", NULL);
8✔
2284
  } else if (msOWSLookupMetadata(metadata, namespaces, name) ||
423✔
2285
             msOWSLookupMetadata(metadata, namespaces, newname.c_str()) ||
423✔
2286
             msOWSLookupMetadata(metadata, namespaces, vocname.c_str())) {
125✔
2287
    /* <KeywordList><Keyword> ... in V1.0.6+ */
2288
    msIO_printf("%s<KeywordList>\n", tabspace);
182✔
2289
    std::string template1(tabspace);
182✔
2290
    template1 += "    <Keyword>%s</Keyword>\n";
2291
    /* print old styled ..._keywordlist */
2292
    msOWSPrintEncodeMetadataList(stream, metadata, namespaces, name, NULL, NULL,
182✔
2293
                                 template1.c_str(), NULL);
2294
    /* print new styled ..._keywordlist_items */
2295
    msOWSPrintEncodeMetadataList(stream, metadata, namespaces, newname.c_str(),
182✔
2296
                                 NULL, NULL, template1.c_str(), NULL);
2297

2298
    /* find out if there's a vocabulary list set */
2299
    const char *vocabularylist =
2300
        msOWSLookupMetadata(metadata, namespaces, vocname.c_str());
182✔
2301
    if (vocabularylist && nVersion >= OWS_1_3_0) {
182✔
2302
      const auto tokens = msStringSplit(vocabularylist, ',');
9✔
2303
      for (const auto &token : tokens) {
18✔
2304
        msOWSPrintEncodeMetadataList(
9✔
2305
            stream, metadata, namespaces,
2306
            (std::string(name) + '_' + token + "_items").c_str(), NULL, NULL,
18✔
2307
            (std::string(tabspace) + "    <Keyword vocabulary=\"" + token +
18✔
2308
             "\">%s</Keyword>\n")
2309
                .c_str(),
2310
            NULL);
2311
      }
2312
    }
9✔
2313
    msIO_printf("%s</KeywordList>\n", tabspace);
182✔
2314
  }
2315
}
302✔
2316

2317
/*
2318
** msDumpLayer()
2319
*/
2320
static int msDumpLayer(mapObj *map, layerObj *lp, int nVersion,
187✔
2321
                       const char *script_url_encoded, const char *indent,
2322
                       const char *validated_language, int grouplayer,
2323
                       int hasQueryableSubLayers) {
2324
  rectObj ext;
2325
  char **classgroups = NULL;
2326
  int iclassgroups = 0;
2327
  char *pszMapEPSG, *pszLayerEPSG;
2328

2329
  /* if the layer status is set to MS_DEFAULT, output a warning */
2330
  if (lp->status == MS_DEFAULT)
187✔
2331
    msIO_fprintf(stdout,
4✔
2332
                 "<!-- WARNING: This layer has its status set to DEFAULT and "
2333
                 "will always be displayed when doing a GetMap request even if "
2334
                 "it is not requested by the client. This is not in line with "
2335
                 "the expected behavior of a WMS server. Using status ON or "
2336
                 "OFF is recommended. -->\n");
2337

2338
  if (nVersion < OWS_1_1_0) {
187✔
2339
    msIO_printf("%s    <Layer queryable=\"%d\">\n", indent,
1✔
2340
                hasQueryableSubLayers || msIsLayerQueryable(lp));
1✔
2341
  } else {
2342
    /* 1.1.0 and later: opaque and cascaded are new. */
2343
    int cascaded = 0, opaque = 0;
2344
    const char *value = msOWSLookupMetadata(&(lp->metadata), "MO", "opaque");
186✔
2345
    if (value != NULL)
186✔
2346
      opaque = atoi(value);
2347
    if (lp->connectiontype == MS_WMS)
186✔
2348
      cascaded = 1;
2349

2350
    msIO_printf(
186✔
2351
        "%s    <Layer queryable=\"%d\" opaque=\"%d\" cascaded=\"%d\">\n",
2352
        indent, hasQueryableSubLayers || msIsLayerQueryable(lp), opaque,
186✔
2353
        cascaded);
2354
  }
2355

2356
  if (lp->name && strlen(lp->name) > 0 &&
374✔
2357
      (msIsXMLTagValid(lp->name) == MS_FALSE || isdigit(lp->name[0])))
374✔
2358
    msIO_fprintf(stdout,
×
2359
                 "<!-- WARNING: The layer name '%s' might contain spaces or "
2360
                 "invalid characters or may start with a number. This could "
2361
                 "lead to potential problems. -->\n",
2362
                 lp->name);
2363
  msOWSPrintEncodeParam(stdout, "LAYER.NAME", lp->name, OWS_NOERR,
187✔
2364
                        "        <Name>%s</Name>\n", NULL);
2365

2366
  /* the majority of this section is dependent on appropriately named metadata
2367
   * in the LAYER object */
2368
  msOWSPrintEncodeMetadata2(stdout, &(lp->metadata), "MO", "title", OWS_WARN,
187✔
2369
                            "        <Title>%s</Title>\n", lp->name,
187✔
2370
                            validated_language);
2371

2372
  msOWSPrintEncodeMetadata2(stdout, &(lp->metadata), "MO", "abstract",
187✔
2373
                            OWS_NOERR, "        <Abstract>%s</Abstract>\n",
2374
                            NULL, validated_language);
2375

2376
  msWMSPrintKeywordlist(stdout, "        ", "keywordlist", &(lp->metadata),
187✔
2377
                        "MO", nVersion);
2378

2379
  msOWSGetEPSGProj(&(map->projection), &(map->web.metadata), "MO", MS_FALSE,
187✔
2380
                   &pszMapEPSG);
2381
  msOWSGetEPSGProj(&(lp->projection), &(lp->metadata), "MO", MS_FALSE,
187✔
2382
                   &pszLayerEPSG);
2383
  if (pszMapEPSG == NULL) {
187✔
2384
    /* If map has no proj then every layer MUST have one or produce a warning */
2385
    if (nVersion > OWS_1_1_0) {
×
2386
      /* starting 1.1.1 SRS are given in individual tags */
2387
      if (nVersion >= OWS_1_3_0) {
×
2388
        msOWSPrintEncodeParamList(stdout,
×
2389
                                  "(at least one of) "
2390
                                  "MAP.PROJECTION, LAYER.PROJECTION "
2391
                                  "or wms_srs metadata",
2392
                                  pszLayerEPSG, OWS_WARN, ' ', NULL, NULL,
2393
                                  "        <CRS>%s</CRS>\n", NULL);
2394
      } else {
2395
        msOWSPrintEncodeParamList(stdout,
×
2396
                                  "(at least one of) "
2397
                                  "MAP.PROJECTION, LAYER.PROJECTION "
2398
                                  "or wms_srs metadata",
2399
                                  pszLayerEPSG, OWS_WARN, ' ', NULL, NULL,
2400
                                  "        <SRS>%s</SRS>\n", NULL);
2401
      }
2402
    } else {
2403
      msOWSPrintEncodeParam(stdout,
×
2404
                            "(at least one of) MAP.PROJECTION, "
2405
                            "LAYER.PROJECTION or wms_srs metadata",
2406
                            pszLayerEPSG, OWS_WARN, "        <SRS>%s</SRS>\n",
2407
                            NULL);
2408
    }
2409
  } else {
2410
    /* No warning required in this case since there's at least a map proj. */
2411
    if (nVersion > OWS_1_1_0) {
187✔
2412
      /* starting 1.1.1 SRS are given in individual tags */
2413
      if (nVersion >= OWS_1_3_0) {
172✔
2414
        msOWSPrintEncodeParamList(stdout,
91✔
2415
                                  "(at least one of) "
2416
                                  "MAP.PROJECTION, LAYER.PROJECTION "
2417
                                  "or wms_srs metadata",
2418
                                  pszLayerEPSG, OWS_NOERR, ' ', NULL, NULL,
2419
                                  "        <CRS>%s</CRS>\n", NULL);
2420
      } else {
2421
        msOWSPrintEncodeParamList(stdout,
81✔
2422
                                  "(at least one of) "
2423
                                  "MAP.PROJECTION, LAYER.PROJECTION "
2424
                                  "or wms_srs metadata",
2425
                                  pszLayerEPSG, OWS_NOERR, ' ', NULL, NULL,
2426
                                  "        <SRS>%s</SRS>\n", NULL);
2427
      }
2428
    } else {
2429
      msOWSPrintEncodeParam(stdout, " LAYER.PROJECTION (or wms_srs metadata)",
15✔
2430
                            pszLayerEPSG, OWS_NOERR, "        <SRS>%s</SRS>\n",
2431
                            NULL);
2432
    }
2433
  }
2434
  msFree(pszLayerEPSG);
187✔
2435
  msFree(pszMapEPSG);
187✔
2436

2437
  /* If layer has no proj set then use map->proj for bounding box. */
2438
  if (msOWSGetLayerExtent(map, lp, "MO", &ext) == MS_SUCCESS) {
187✔
2439
    if (lp->projection.numargs > 0) {
174✔
2440
      if (nVersion >= OWS_1_3_0)
167✔
2441
        msOWSPrintEX_GeographicBoundingBox(stdout, "        ", &(ext),
82✔
2442
                                           &(lp->projection));
2443
      else
2444
        msOWSPrintLatLonBoundingBox(stdout, "        ", &(ext),
85✔
2445
                                    &(lp->projection), NULL, OWS_WMS);
2446

2447
      msOWSPrintBoundingBox(stdout, "        ", &(ext), &(lp->projection),
167✔
2448
                            &(lp->metadata), &(map->web.metadata), "MO",
2449
                            nVersion);
2450
    } else {
2451
      if (nVersion >= OWS_1_3_0)
7✔
2452
        msOWSPrintEX_GeographicBoundingBox(stdout, "        ", &(ext),
6✔
2453
                                           &(map->projection));
2454
      else
2455
        msOWSPrintLatLonBoundingBox(stdout, "        ", &(ext),
1✔
2456
                                    &(map->projection), NULL, OWS_WMS);
2457
      msOWSPrintBoundingBox(stdout, "        ", &(ext), &(map->projection),
7✔
2458
                            &(lp->metadata), &(map->web.metadata), "MO",
2459
                            nVersion);
2460
    }
2461
  } else {
2462
    if (nVersion >= OWS_1_3_0)
13✔
2463
      msIO_printf(
3✔
2464
          "        <!-- WARNING: Optional Ex_GeographicBoundingBox could not "
2465
          "be established for this layer.  Consider setting the EXTENT in the "
2466
          "LAYER object, or wms_extent metadata. Also check that your data "
2467
          "exists in the DATA statement -->\n");
2468
    else
2469
      msIO_printf("        <!-- WARNING: Optional LatLonBoundingBox could not "
10✔
2470
                  "be established for this layer.  Consider setting the EXTENT "
2471
                  "in the LAYER object, or wms_extent metadata. Also check "
2472
                  "that your data exists in the DATA statement -->\n");
2473
  }
2474

2475
  /* time support */
2476
  const char *pszWmsTimeExtent =
2477
      msOWSLookupMetadata(&(lp->metadata), "MO", "timeextent");
187✔
2478
  if (pszWmsTimeExtent) {
187✔
2479
    const char *pszWmsTimeDefault =
2480
        msOWSLookupMetadata(&(lp->metadata), "MO", "timedefault");
4✔
2481

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

2495
    else {
2496
      msIO_fprintf(stdout,
2✔
2497
                   "        <Dimension name=\"time\" units=\"ISO8601\"/>\n");
2498
      if (pszWmsTimeDefault)
2✔
2499
        msIO_fprintf(stdout,
×
2500
                     "        <Extent name=\"time\" default=\"%s\" "
2501
                     "nearestValue=\"0\">%s</Extent>\n",
2502
                     pszWmsTimeDefault, pszWmsTimeExtent);
2503
      else
2504
        msIO_fprintf(
2✔
2505
            stdout,
2506
            "        <Extent name=\"time\" nearestValue=\"0\">%s</Extent>\n",
2507
            pszWmsTimeExtent);
2508
    }
2509
  }
2510

2511
  /*dimensions support: elevation + other user defined dimensions*/
2512
  const char *pszDimensionlist =
2513
      msOWSLookupMetadata(&(lp->metadata), "M", "dimensionlist");
187✔
2514
  if (pszDimensionlist) {
187✔
2515
    auto tokens = msStringSplit(pszDimensionlist, ',');
6✔
2516
    for (auto &dimension : tokens) {
14✔
2517
      /*check if manadatory unit and extent are set. Item should also be set.
2518
       * default value is optional*/
2519
      msStringTrim(dimension);
8✔
2520

2521
      const char *pszDimensionItem = msOWSLookupMetadata(
8✔
2522
          &(lp->metadata), "M", (dimension + "_item").c_str());
8✔
2523
      const char *pszDimensionExtent = msOWSLookupMetadata(
8✔
2524
          &(lp->metadata), "M", (dimension + "_extent").c_str());
8✔
2525
      const char *pszDimensionUnit = msOWSLookupMetadata(
8✔
2526
          &(lp->metadata), "M", (dimension + "_units").c_str());
8✔
2527
      const char *pszDimensionDefault = msOWSLookupMetadata(
8✔
2528
          &(lp->metadata), "M", (dimension + "_default").c_str());
8✔
2529

2530
      if (pszDimensionItem && pszDimensionExtent && pszDimensionUnit) {
8✔
2531
        if (nVersion >= OWS_1_3_0) {
8✔
2532
          if (pszDimensionDefault && strlen(pszDimensionDefault) > 0)
4✔
2533
            msIO_fprintf(
1✔
2534
                stdout,
2535
                "        <Dimension name=\"%s\" units=\"%s\" default=\"%s\" "
2536
                "multipleValues=\"1\" nearestValue=\"0\">%s</Dimension>\n",
2537
                dimension.c_str(), pszDimensionUnit, pszDimensionDefault,
2538
                pszDimensionExtent);
2539
          else
2540
            msIO_fprintf(
3✔
2541
                stdout,
2542
                "        <Dimension name=\"%s\" units=\"%s\"  "
2543
                "multipleValues=\"1\"  nearestValue=\"0\">%s</Dimension>\n",
2544
                dimension.c_str(), pszDimensionUnit, pszDimensionExtent);
2545
        } else {
2546
          msIO_fprintf(stdout,
4✔
2547
                       "        <Dimension name=\"%s\" units=\"%s\"/>\n",
2548
                       dimension.c_str(), pszDimensionUnit);
2549
          if (pszDimensionDefault && strlen(pszDimensionDefault) > 0)
4✔
2550
            msIO_fprintf(stdout,
1✔
2551
                         "        <Extent name=\"%s\" default=\"%s\" "
2552
                         "nearestValue=\"0\">%s</Extent>\n",
2553
                         dimension.c_str(), pszDimensionDefault,
2554
                         pszDimensionExtent);
2555
          else
2556
            msIO_fprintf(
3✔
2557
                stdout,
2558
                "        <Extent name=\"%s\" nearestValue=\"0\">%s</Extent>\n",
2559
                dimension.c_str(), pszDimensionExtent);
2560
        }
2561
      }
2562
    }
2563
  }
6✔
2564

2565
  /* AuthorityURL support and Identifier support, only available >= WMS 1.1.0 */
2566
  if (nVersion >= OWS_1_1_0) {
187✔
2567
    msWMSPrintAttribution(stdout, "    ", &(lp->metadata), "MO");
186✔
2568
    msWMSPrintAuthorityURL(stdout, "        ", &(lp->metadata), "MO");
186✔
2569
    msWMSPrintIdentifier(stdout, "        ", &(lp->metadata), "MO");
186✔
2570
  }
2571

2572
  if (nVersion >= OWS_1_1_0) {
2573
    const char *metadataurl_list =
2574
        msOWSLookupMetadata(&(lp->metadata), "MO", "metadataurl_list");
186✔
2575
    if (metadataurl_list) {
186✔
2576
      const auto tokens = msStringSplit(metadataurl_list, ' ');
1✔
2577
      for (const auto &token : tokens) {
3✔
2578
        std::string key("metadataurl_");
2✔
2579
        key += token;
2580
        msOWSPrintURLType(stdout, &(lp->metadata), "MO", key.c_str(), OWS_NOERR,
2✔
2581
                          NULL, "MetadataURL", " type=\"%s\"", NULL, NULL,
2582
                          ">\n          <Format>%s</Format",
2583
                          "\n          <OnlineResource xmlns:xlink=\""
2584
                          "http://www.w3.org/1999/xlink\" "
2585
                          "xlink:type=\"simple\" xlink:href=\"%s\"/>\n        ",
2586
                          MS_TRUE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
2587
                          NULL, NULL, NULL, NULL, "        ");
2588
      }
2589
    } else {
1✔
2590
      if (!msOWSLookupMetadata(&(lp->metadata), "MO", "metadataurl_href"))
185✔
2591
        msMetadataSetGetMetadataURL(lp, script_url_encoded);
96✔
2592

2593
      msOWSPrintURLType(stdout, &(lp->metadata), "MO", "metadataurl", OWS_NOERR,
185✔
2594
                        NULL, "MetadataURL", " type=\"%s\"", NULL, NULL,
2595
                        ">\n          <Format>%s</Format",
2596
                        "\n          <OnlineResource xmlns:xlink=\""
2597
                        "http://www.w3.org/1999/xlink\" "
2598
                        "xlink:type=\"simple\" xlink:href=\"%s\"/>\n        ",
2599
                        MS_TRUE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
2600
                        NULL, NULL, NULL, NULL, "        ");
2601
    }
2602
  }
2603

2604
  if (nVersion < OWS_1_1_0)
2605
    msOWSPrintEncodeMetadata(stdout, &(lp->metadata), "MO", "dataurl_href",
1✔
2606
                             OWS_NOERR, "        <DataURL>%s</DataURL>\n",
2607
                             NULL);
2608
  else {
2609
    const char *dataurl_list =
2610
        msOWSLookupMetadata(&(lp->metadata), "MO", "dataurl_list");
186✔
2611
    if (dataurl_list) {
186✔
2612
      const auto tokens = msStringSplit(dataurl_list, ' ');
1✔
2613
      for (const auto &token : tokens) {
3✔
2614
        std::string key("dataurl_");
2✔
2615
        key += token;
2616
        msOWSPrintURLType(stdout, &(lp->metadata), "MO", key.c_str(), OWS_NOERR,
2✔
2617
                          NULL, "DataURL", NULL, NULL, NULL,
2618
                          ">\n          <Format>%s</Format",
2619
                          "\n          <OnlineResource xmlns:xlink=\""
2620
                          "http://www.w3.org/1999/xlink\" "
2621
                          "xlink:type=\"simple\" xlink:href=\"%s\"/>\n        ",
2622
                          MS_FALSE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
2623
                          NULL, NULL, NULL, NULL, "        ");
2624
      }
2625
    } else {
1✔
2626
      msOWSPrintURLType(stdout, &(lp->metadata), "MO", "dataurl", OWS_NOERR,
185✔
2627
                        NULL, "DataURL", NULL, NULL, NULL,
2628
                        ">\n          <Format>%s</Format",
2629
                        "\n          <OnlineResource xmlns:xlink=\""
2630
                        "http://www.w3.org/1999/xlink\" "
2631
                        "xlink:type=\"simple\" xlink:href=\"%s\"/>\n        ",
2632
                        MS_FALSE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
2633
                        NULL, NULL, NULL, NULL, "        ");
2634
    }
2635
  }
2636

2637
  /* The LegendURL reside in a style. The Web Map Context spec already  */
2638
  /* included the support on this in mapserver. However, it is not in the  */
2639
  /* wms_legendurl_... metadatas it's in the styles metadata, */
2640
  /* In wms_style_<style_name>_lengendurl_... metadata. So we have to detect */
2641
  /* the current style before reading it. Also in the Style block, we need */
2642
  /* a Title and a name. Title is derived from wms_style_<style>_title, */
2643
  /* which allows multiple style definitions, e.g. by using classgroups. */
2644
  const char *pszStyle = msOWSLookupMetadata(&(lp->metadata), "MO", "style");
187✔
2645
  const char *pszLegendURL = NULL;
2646
  if (pszStyle) {
187✔
2647
    pszLegendURL = msOWSLookupMetadata(
×
2648
        &(lp->metadata), "MO",
2649
        (std::string("style_") + pszStyle + "_legendurl_href").c_str());
×
2650
  } else
2651
    pszStyle = "default";
2652

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

2663
    /* Inside, print the legend url block */
2664
    msOWSPrintEncodeMetadata(
×
2665
        stdout, &(lp->metadata), "MO",
2666
        (std::string("style_") + pszStyle + "_legendurl_href").c_str(),
×
2667
        OWS_NOERR, "          <StyleURL>%s</StyleURL>\n", NULL);
2668

2669
    /* close the style block */
2670
    msIO_fprintf(stdout, "        </Style>\n");
×
2671

2672
  } else if (nVersion >= OWS_1_1_0) {
187✔
2673
    if (pszLegendURL) {
186✔
2674
      /* First, print the style block */
2675
      msIO_fprintf(stdout, "        <Style>\n");
×
2676
      msIO_fprintf(stdout, "          <Name>%s</Name>\n", pszStyle);
×
2677
      /* Print the real Title or Style name otherwise */
2678
      msOWSPrintEncodeMetadata2(
×
2679
          stdout, &(lp->metadata), "MO",
2680
          (std::string("style_") + pszStyle + "_title").c_str(), OWS_NOERR,
×
2681
          "          <Title>%s</Title>\n", pszStyle, validated_language);
2682

2683
      /* Inside, print the legend url block */
2684
      msOWSPrintURLType(
×
2685
          stdout, &(lp->metadata), "MO",
2686
          (std::string("style_") + pszStyle + "_legendurl").c_str(), OWS_NOERR,
×
2687
          NULL, "LegendURL", NULL, " width=\"%s\"", " height=\"%s\"",
2688
          ">\n             <Format>%s</Format",
2689
          "\n             <OnlineResource "
2690
          "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
2691
          " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
2692
          "          ",
2693
          MS_FALSE, MS_TRUE, MS_TRUE, MS_TRUE, MS_TRUE, NULL, NULL, NULL, NULL,
2694
          NULL, "          ");
2695
      msIO_fprintf(stdout, "        </Style>\n");
×
2696

2697
    } else {
2698
      if (script_url_encoded) {
186✔
2699
        if (lp->connectiontype != MS_WMS && lp->connectiontype != MS_WFS &&
186✔
2700
            lp->connectiontype != MS_UNUSED_1 && lp->numclasses > 0) {
185✔
2701
          bool classnameset = false;
2702
          for (int i = 0; i < lp->numclasses; i++) {
185✔
2703
            if (lp->_class[i]->name && strlen(lp->_class[i]->name) > 0) {
167✔
2704
              classnameset = true;
2705
              break;
2706
            }
2707
          }
2708
          if (classnameset) {
164✔
2709
            int size_x = 0, size_y = 0;
146✔
2710
            std::vector<int> group_layers;
2711
            group_layers.reserve(map->numlayers);
146✔
2712

2713
            char ***nestedGroups =
2714
                (char ***)msSmallCalloc(map->numlayers, sizeof(char **));
146✔
2715
            int *numNestedGroups =
2716
                (int *)msSmallCalloc(map->numlayers, sizeof(int));
146✔
2717
            int *isUsedInNestedGroup =
2718
                (int *)msSmallCalloc(map->numlayers, sizeof(int));
146✔
2719
            msWMSPrepareNestedGroups(map, nVersion, nestedGroups,
146✔
2720
                                     numNestedGroups, isUsedInNestedGroup);
2721

2722
            group_layers.push_back(lp->index);
146✔
2723
            if (isUsedInNestedGroup[lp->index]) {
146✔
2724
              for (int j = 0; j < map->numlayers; j++) {
96✔
2725
                if (j == lp->index)
90✔
2726
                  continue;
6✔
2727
                for (int k = 0; k < numNestedGroups[j]; k++) {
132✔
2728
                  if (strcasecmp(lp->name, nestedGroups[j][k]) == 0) {
66✔
2729
                    group_layers.push_back(j);
18✔
2730
                    break;
2731
                  }
2732
                }
2733
              }
2734
            }
2735

2736
            if (msLegendCalcSize(map, 1, &size_x, &size_y, group_layers.data(),
146✔
2737
                                 static_cast<int>(group_layers.size()), NULL,
2738
                                 1) == MS_SUCCESS) {
2739
              const std::string width(std::to_string(size_x));
146✔
2740
              const std::string height(std::to_string(size_y));
146✔
2741

2742
              char *mimetype = NULL;
2743
#if defined USE_PNG
2744
              mimetype = msEncodeHTMLEntities("image/png");
146✔
2745
#endif
2746

2747
#if defined USE_JPEG
2748
              if (!mimetype)
146✔
2749
                mimetype = msEncodeHTMLEntities("image/jpeg");
×
2750
#endif
2751
              if (!mimetype)
×
2752
                mimetype =
2753
                    msEncodeHTMLEntities(MS_IMAGE_MIME_TYPE(map->outputformat));
×
2754

2755
              /* --------------------------------------------------------------------
2756
               */
2757
              /*      check if the group parameters for the classes are set. We
2758
               */
2759
              /*      should then publish the different class groups as
2760
               * different styles.*/
2761
              /* --------------------------------------------------------------------
2762
               */
2763
              iclassgroups = 0;
2764
              classgroups = NULL;
2765

2766
              const char *styleName =
2767
                  msOWSLookupMetadata(&(map->web.metadata), "MO", "style_name");
146✔
2768
              if (styleName == NULL)
146✔
2769
                styleName = "default";
2770
              char *pszEncodedStyleName = msEncodeHTMLEntities(styleName);
146✔
2771

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

2836
              for (int i = 0; i < iclassgroups; i++) {
292✔
2837
                char *name_encoded = msEncodeHTMLEntities(lp->name);
146✔
2838
                char *classgroup_encoded = msEncodeHTMLEntities(classgroups[i]);
146✔
2839
                std::string legendurl(script_url_encoded);
146✔
2840
                legendurl += "version=";
2841
                char szVersionBuf[OWS_VERSION_MAXLEN];
2842
                legendurl += msOWSGetVersionString(nVersion, szVersionBuf);
146✔
2843
                legendurl +=
2844
                    "&amp;service=WMS&amp;request=GetLegendGraphic&amp;";
2845
                if (nVersion >= OWS_1_3_0) {
146✔
2846
                  legendurl += "sld_version=1.1.0&amp;layer=";
2847
                } else {
2848
                  legendurl += "layer=";
2849
                }
2850
                legendurl += name_encoded;
2851
                legendurl += "&amp;format=";
2852
                legendurl += mimetype;
2853
                legendurl += "&amp;STYLE=";
2854
                legendurl += classgroup_encoded;
2855

2856
                msFree(name_encoded);
146✔
2857
                msFree(classgroup_encoded);
146✔
2858

2859
                msIO_fprintf(stdout, "        <Style>\n");
146✔
2860
                msIO_fprintf(stdout, "          <Name>%s</Name>\n",
146✔
2861
                             classgroups[i]);
2862
                msOWSPrintEncodeMetadata2(
146✔
2863
                    stdout, &(lp->metadata), "MO",
2864
                    (std::string("style_") + classgroups[i] + "_title").c_str(),
146✔
2865
                    OWS_NOERR, "          <Title>%s</Title>\n", classgroups[i],
2866
                    validated_language);
2867

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

2920
  /* print Min/Max ScaleDenominator */
2921
  if (nVersion < OWS_1_3_0)
186✔
2922
    msWMSPrintScaleHint("        ", lp->minscaledenom, lp->maxscaledenom,
96✔
2923
                        map->resolution);
2924
  else
2925
    msWMSPrintScaleDenominator("        ", lp->minscaledenom,
91✔
2926
                               lp->maxscaledenom);
2927

2928
  if (grouplayer == MS_FALSE)
187✔
2929
    msIO_printf("%s    </Layer>\n", indent);
168✔
2930

2931
  return MS_SUCCESS;
187✔
2932
}
2933

2934
/*
2935
 * msWMSIsSubGroup
2936
 */
2937
static bool msWMSIsSubGroup(char **currentGroups, int currentLevel,
217✔
2938
                            char **otherGroups, int numOtherGroups) {
2939
  /* no match if otherGroups[] has less levels than currentLevel */
2940
  if (numOtherGroups <= currentLevel) {
217✔
2941
    return false;
2942
  }
2943
  /* compare all groups below the current level */
2944
  for (int i = 0; i <= currentLevel; i++) {
266✔
2945
    if (strcmp(currentGroups[i], otherGroups[i]) != 0) {
181✔
2946
      return false; /* if one of these is not equal it is not a sub group */
2947
    }
2948
  }
2949
  return true;
2950
}
2951

2952
/*
2953
 * msWMSHasQueryableSubLayers
2954
 */
2955
static int msWMSHasQueryableSubLayers(mapObj *map, int index, int level,
28✔
2956
                                      char ***nestedGroups,
2957
                                      int *numNestedGroups) {
2958
  for (int j = index; j < map->numlayers; j++) {
44✔
2959
    if (msWMSIsSubGroup(nestedGroups[index], level, nestedGroups[j],
41✔
2960
                        numNestedGroups[j])) {
41✔
2961
      if (msIsLayerQueryable(GET_LAYER(map, j)))
31✔
2962
        return MS_TRUE;
2963
    }
2964
  }
2965
  return MS_FALSE;
2966
}
2967

2968
/***********************************************************************************
2969
 * msWMSPrintNestedGroups() *
2970
 *                                                                                 *
2971
 * purpose: Writes the layers to the capabilities that have the *
2972
 * "WMS_LAYER_GROUP" metadata set. *
2973
 *                                                                                 *
2974
 * params: * -map: The main map object * -nVersion: OGC WMS version *
2975
 * -pabLayerProcessed: boolean array indicating which layers have been dealt
2976
 *with. * -index: the index of the current layer. * -level: the level of depth
2977
 *in the group tree (root = 0)                         * -nestedGroups: This
2978
 *array holds the arrays of groups that have                  * been set through
2979
 *the WMS_LAYER_GROUP metadata                                 *
2980
 * -numNestedGroups: This array holds the number of nested groups for each layer
2981
 **
2982
 ***********************************************************************************/
2983
void msWMSPrintNestedGroups(mapObj *map, int nVersion, char *pabLayerProcessed,
102✔
2984
                            int index, int level, char ***nestedGroups,
2985
                            int *numNestedGroups, int *isUsedInNestedGroup,
2986
                            const char *script_url_encoded,
2987
                            const char *validated_language) {
2988
  bool groupAdded = false;
2989
  std::string indent;
2990
  for (int i = 0; i < level; i++) {
185✔
2991
    indent += "  ";
2992
  }
2993

2994
  if (numNestedGroups[index] <= level) { /* no more subgroups */
102✔
2995
    if ((!pabLayerProcessed[index]) && (!isUsedInNestedGroup[index])) {
74✔
2996
      /* we are at the deepest level of the group branchings, so add layer now.
2997
       */
2998
      msDumpLayer(map, GET_LAYER(map, index), nVersion, script_url_encoded,
74✔
2999
                  indent.c_str(), validated_language, MS_FALSE, MS_FALSE);
3000
      pabLayerProcessed[index] = 1; /* done */
74✔
3001
    }
3002
  } else { /* not yet there, we have to deal with this group and possible
3003
              subgroups and layers. */
3004
    int j;
3005
    for (j = 0; j < map->numlayers; j++) {
155✔
3006
      if (GET_LAYER(map, j)->name &&
146✔
3007
          strcasecmp(GET_LAYER(map, j)->name, nestedGroups[index][level]) ==
146✔
3008
              0) {
3009
        break;
3010
      }
3011
    }
3012

3013
    /* Beginning of a new group... enclose the group in a layer block */
3014
    if (j < map->numlayers) {
28✔
3015
      if (!pabLayerProcessed[j]) {
19✔
3016
        msDumpLayer(map, GET_LAYER(map, j), nVersion, script_url_encoded,
19✔
3017
                    indent.c_str(), validated_language, MS_TRUE,
3018
                    msWMSHasQueryableSubLayers(map, index, level, nestedGroups,
3019
                                               numNestedGroups));
3020
        pabLayerProcessed[j] = 1; /* done */
19✔
3021
        groupAdded = true;
3022
      }
3023
    } else {
3024
      msIO_printf("%s    <Layer%s>\n", indent.c_str(),
12✔
3025
                  msWMSHasQueryableSubLayers(map, index, level, nestedGroups,
9✔
3026
                                             numNestedGroups)
3027
                      ? " queryable=\"1\""
3028
                      : "");
3029
      msIO_printf("%s      <Name>%s</Name>\n", indent.c_str(),
9✔
3030
                  nestedGroups[index][level]);
9✔
3031
      msIO_printf("%s      <Title>%s</Title>\n", indent.c_str(),
9✔
3032
                  nestedGroups[index][level]);
9✔
3033
      groupAdded = true;
3034
    }
3035

3036
    /* Look for one group deeper in the current layer */
3037
    if (!pabLayerProcessed[index]) {
28✔
3038
      msWMSPrintNestedGroups(map, nVersion, pabLayerProcessed, index, level + 1,
28✔
3039
                             nestedGroups, numNestedGroups, isUsedInNestedGroup,
3040
                             script_url_encoded, validated_language);
3041
    }
3042

3043
    /* look for subgroups in other layers. */
3044
    for (j = index + 1; j < map->numlayers; j++) {
204✔
3045
      if (msWMSIsSubGroup(nestedGroups[index], level, nestedGroups[j],
176✔
3046
                          numNestedGroups[j])) {
176✔
3047
        if (!pabLayerProcessed[j]) {
54✔
3048
          msWMSPrintNestedGroups(map, nVersion, pabLayerProcessed, j, level + 1,
50✔
3049
                                 nestedGroups, numNestedGroups,
3050
                                 isUsedInNestedGroup, script_url_encoded,
3051
                                 validated_language);
3052
        }
3053
      } else {
3054
        /* TODO: if we would sort all layers on "WMS_LAYER_GROUP" beforehand */
3055
        /* we could break out of this loop at this point, which would increase
3056
         */
3057
        /* performance.  */
3058
      }
3059
    }
3060
    /* Close group layer block */
3061
    if (groupAdded)
28✔
3062
      msIO_printf("%s    </Layer>\n", indent.c_str());
28✔
3063
  }
3064
} /* msWMSPrintNestedGroups */
102✔
3065

3066
/*
3067
** msWMSGetCapabilities()
3068
*/
3069
static int msWMSGetCapabilities(mapObj *map, int nVersion, cgiRequestObj *req,
64✔
3070
                                owsRequestObj *ows_request,
3071
                                const char *requested_updatesequence,
3072
                                const char *wms_exception_format,
3073
                                const char *requested_language) {
3074
  const char *updatesequence =
3075
      msOWSLookupMetadata(&(map->web.metadata), "MO", "updatesequence");
64✔
3076

3077
  const char *sldenabled =
3078
      msOWSLookupMetadata(&(map->web.metadata), "MO", "sld_enabled");
64✔
3079

3080
  if (sldenabled == NULL)
64✔
3081
    sldenabled = "true";
3082

3083
  if (requested_updatesequence != NULL) {
64✔
3084
    int i =
3085
        msOWSNegotiateUpdateSequence(requested_updatesequence, updatesequence);
6✔
3086
    if (i == 0) { /* current */
6✔
3087
      msSetErrorWithStatus(
2✔
3088
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
3089
          "UPDATESEQUENCE parameter (%s) is equal to server (%s)",
3090
          "msWMSGetCapabilities()", requested_updatesequence, updatesequence);
3091
      return msWMSException(map, nVersion, "CurrentUpdateSequence",
2✔
3092
                            wms_exception_format);
3093
    }
3094
    if (i > 0) { /* invalid */
4✔
3095
      msSetErrorWithStatus(
2✔
3096
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
3097
          "UPDATESEQUENCE parameter (%s) is higher than server (%s)",
3098
          "msWMSGetCapabilities()", requested_updatesequence, updatesequence);
3099
      return msWMSException(map, nVersion, "InvalidUpdateSequence",
2✔
3100
                            wms_exception_format);
3101
    }
3102
  }
3103

3104
  std::string schemalocation;
3105
  {
3106
    char *pszSchemalocation =
3107
        msEncodeHTMLEntities(msOWSGetSchemasLocation(map));
60✔
3108
    schemalocation = pszSchemalocation;
3109
    msFree(pszSchemalocation);
60✔
3110
  }
3111

3112
  if (nVersion < 0)
60✔
3113
    nVersion = OWS_1_3_0; /* Default to 1.3.0 */
3114

3115
  /* Decide which version we're going to return. */
3116
  std::string dtd_url;
3117
  if (nVersion == OWS_1_0_0) {
60✔
3118
    dtd_url = std::move(schemalocation);
2✔
3119
    dtd_url += "/wms/1.0.0/capabilities_1_0_0.dtd";
3120
  } else if (nVersion < OWS_1_1_1) {
58✔
3121
    nVersion = OWS_1_1_0;
3122
    dtd_url = std::move(schemalocation);
7✔
3123
    dtd_url += "/wms/1.1.0/capabilities_1_1_0.dtd";
3124
  } else if (nVersion < OWS_1_3_0) {
51✔
3125
    nVersion = OWS_1_1_1;
3126
    dtd_url = std::move(schemalocation);
22✔
3127
    /* this exception was added to accommodate the OGC test suite (Bug 1576)*/
3128
    if (strcasecmp(dtd_url.c_str(), OWS_DEFAULT_SCHEMAS_LOCATION) == 0)
22✔
3129
      dtd_url += "/wms/1.1.1/WMS_MS_Capabilities.dtd";
3130
    else
3131
      dtd_url += "/wms/1.1.1/capabilities_1_1_1.dtd";
3132
  } else
3133
    nVersion = OWS_1_3_0;
3134

3135
  /* This function owns validated_language, so remember to free it later*/
3136
  std::string validated_language;
3137
  {
3138
    char *pszValidated_language =
3139
        msOWSGetLanguageFromList(map, "MO", requested_language);
60✔
3140
    if (pszValidated_language) {
60✔
3141
      validated_language = pszValidated_language;
3142
      msMapSetLanguageSpecificConnection(map, pszValidated_language);
15✔
3143
    }
3144
    msFree(pszValidated_language);
60✔
3145
  }
3146

3147
  /* We need this server's onlineresource. */
3148
  /* Default to use the value of the "onlineresource" metadata, and if not */
3149
  /* set then build it: "http://$(SERVER_NAME):$(SERVER_PORT)$(SCRIPT_NAME)?" */
3150
  /* the returned string should be freed once we're done with it. */
3151
  char *script_url_encoded = NULL;
3152
  {
3153
    char *script_url = msOWSGetOnlineResource2(map, "MO", "onlineresource", req,
60✔
3154
                                               validated_language.c_str());
3155
    if (script_url == NULL ||
120✔
3156
        (script_url_encoded = msEncodeHTMLEntities(script_url)) == NULL) {
60✔
3157
      free(script_url);
×
3158
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
3159
    }
3160
    free(script_url);
60✔
3161
  }
3162

3163
  if (nVersion == OWS_1_0_0 || nVersion >= OWS_1_3_0) /* 1.0.0 and >=1.3.0*/
60✔
3164
    msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
31✔
3165
  else /* 1.1.0 and later */
3166
    msIO_setHeader("Content-Type",
29✔
3167
                   "application/vnd.ogc.wms_xml; charset=UTF-8");
3168
  msIO_sendHeaders();
60✔
3169

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

3172
  /*TODO review wms1.3.0*/
3173
  if (nVersion < OWS_1_3_0) {
60✔
3174
    msIO_printf("<!DOCTYPE WMT_MS_Capabilities SYSTEM \"%s\"\n",
31✔
3175
                dtd_url.c_str());
3176
    msIO_printf(" [\n");
31✔
3177

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

3335
    msIO_printf(" ]>  <!-- end of DOCTYPE declaration -->\n\n");
31✔
3336
  }
3337

3338
  char szVersionBuf[OWS_VERSION_MAXLEN];
3339
  const char *pszVersion = msOWSGetVersionString(nVersion, szVersionBuf);
60✔
3340
  if (nVersion >= OWS_1_3_0)
60✔
3341
    msIO_printf("<WMS_Capabilities version=\"%s\"", pszVersion);
29✔
3342
  else
3343
    msIO_printf("<WMT_MS_Capabilities version=\"%s\"", pszVersion);
31✔
3344
  if (updatesequence)
60✔
3345
    msIO_printf(" updateSequence=\"%s\"", updatesequence);
32✔
3346

3347
  if (nVersion == OWS_1_3_0) {
60✔
3348
    msIO_printf("  xmlns=\"http://www.opengis.net/wms\""
29✔
3349
                "   xmlns:sld=\"http://www.opengis.net/sld\""
3350
                "   xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
3351
                "   xmlns:ms=\"http://mapserver.gis.umn.edu/mapserver\"");
3352

3353
    if (msOWSLookupMetadata(&(map->web.metadata), "MO",
29✔
3354
                            "inspire_capabilities")) {
3355
      msIO_printf("   xmlns:" MS_INSPIRE_COMMON_NAMESPACE_PREFIX
9✔
3356
                  "=\"" MS_INSPIRE_COMMON_NAMESPACE_URI "\""
3357
                  "   xmlns:" MS_INSPIRE_VS_NAMESPACE_PREFIX
3358
                  "=\"" MS_INSPIRE_VS_NAMESPACE_URI "\"");
3359
    }
3360

3361
    msIO_printf(
29✔
3362
        "   xsi:schemaLocation=\"http://www.opengis.net/wms "
3363
        "%s/wms/%s/capabilities_1_3_0.xsd "
3364
        " http://www.opengis.net/sld %s/sld/1.1.0/sld_capabilities.xsd ",
3365
        msOWSGetSchemasLocation(map), pszVersion, msOWSGetSchemasLocation(map));
3366

3367
    if (msOWSLookupMetadata(&(map->web.metadata), "MO",
29✔
3368
                            "inspire_capabilities")) {
3369
      char *inspireschemalocation =
3370
          msEncodeHTMLEntities(msOWSGetInspireSchemasLocation(map));
9✔
3371
      msIO_printf(" " MS_INSPIRE_VS_NAMESPACE_URI " "
9✔
3372
                  " %s%s",
3373
                  inspireschemalocation, MS_INSPIRE_VS_SCHEMA_LOCATION);
3374
      free(inspireschemalocation);
9✔
3375
    }
3376

3377
    msIO_printf(
29✔
3378
        " http://mapserver.gis.umn.edu/mapserver "
3379
        "%sservice=WMS&amp;version=1.3.0&amp;request=GetSchemaExtension\"",
3380
        script_url_encoded);
3381
  }
3382

3383
  msIO_printf(">\n");
60✔
3384

3385
  /* WMS definition */
3386
  msIO_printf("<Service>\n");
60✔
3387

3388
  /* Service name is defined by the spec and changed at v1.0.0 */
3389
  if (nVersion == OWS_1_0_0)
60✔
3390
    msIO_printf("  <Name>GetMap</Name>\n"); /* v 1.0.0 */
2✔
3391
  else if (nVersion >= OWS_1_1_0 && nVersion < OWS_1_3_0)
58✔
3392
    msIO_printf("  <Name>OGC:WMS</Name>\n"); /* v 1.1.0 to 1.1.1*/
29✔
3393
  else
3394
    msIO_printf("  <Name>WMS</Name>\n"); /* v 1.3.0+ */
29✔
3395

3396
  /* the majority of this section is dependent on appropriately named metadata
3397
   * in the WEB object */
3398
  msOWSPrintEncodeMetadata2(stdout, &(map->web.metadata), "MO", "title",
60✔
3399
                            OWS_WARN, "  <Title>%s</Title>\n", map->name,
60✔
3400
                            validated_language.c_str());
3401
  msOWSPrintEncodeMetadata2(stdout, &(map->web.metadata), "MO", "abstract",
60✔
3402
                            OWS_NOERR, "  <Abstract>%s</Abstract>\n", NULL,
3403
                            validated_language.c_str());
3404

3405
  msWMSPrintKeywordlist(stdout, "  ", "keywordlist", &(map->web.metadata), "MO",
60✔
3406
                        nVersion);
3407

3408
  /* Service/onlineresource */
3409
  /* Defaults to same as request onlineresource if wms_service_onlineresource */
3410
  /* is not set. */
3411
  if (nVersion == OWS_1_0_0)
60✔
3412
    msOWSPrintEncodeMetadata(
2✔
3413
        stdout, &(map->web.metadata), "MO", "service_onlineresource", OWS_NOERR,
3414
        "  <OnlineResource>%s</OnlineResource>\n", script_url_encoded);
3415
  else
3416
    msOWSPrintEncodeMetadata(
58✔
3417
        stdout, &(map->web.metadata), "MO", "service_onlineresource", OWS_NOERR,
3418
        "  <OnlineResource xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
3419
        "xlink:href=\"%s\"/>\n",
3420
        script_url_encoded);
3421

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

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

3428
  msOWSPrintEncodeMetadata(
60✔
3429
      stdout, &(map->web.metadata), "MO", "accessconstraints", OWS_NOERR,
3430
      "  <AccessConstraints>%s</AccessConstraints>\n", NULL);
3431

3432
  if (nVersion >= OWS_1_3_0) {
60✔
3433
    const char *layerlimit =
3434
        msOWSLookupMetadata(&(map->web.metadata), "MO", "layerlimit");
29✔
3435
    if (layerlimit) {
29✔
3436
      msIO_printf("  <LayerLimit>%s</LayerLimit>\n", layerlimit);
4✔
3437
    }
3438
    msIO_printf("  <MaxWidth>%d</MaxWidth>\n", map->maxsize);
29✔
3439
    msIO_printf("  <MaxHeight>%d</MaxHeight>\n", map->maxsize);
29✔
3440
  }
3441

3442
  msIO_printf("</Service>\n\n");
60✔
3443

3444
  /* WMS capabilities definitions */
3445
  msIO_printf("<Capability>\n");
60✔
3446
  msIO_printf("  <Request>\n");
60✔
3447

3448
  if (nVersion == OWS_1_0_0) {
60✔
3449
    /* WMS 1.0.0 - we don't try to use outputformats list here for now
3450
     */
3451
    if (msOWSRequestIsEnabled(map, NULL, "M", "GetMap", MS_FALSE))
2✔
3452
      msWMSPrintRequestCap(nVersion, "Map", script_url_encoded,
2✔
3453
                           ""
3454

3455
#if defined USE_PNG
3456
                           "<PNG />"
3457
#endif
3458
#if defined USE_JPEG
3459
                           "<JPEG />"
3460
#endif
3461
                           "<SVG />",
3462
                           NULL);
3463
    if (msOWSRequestIsEnabled(map, NULL, "M", "GetCapabilities", MS_TRUE))
2✔
3464
      msWMSPrintRequestCap(nVersion, "Capabilities", script_url_encoded,
2✔
3465
                           "<WMS_XML />", NULL);
3466
    if (msOWSRequestIsEnabled(map, NULL, "M", "GetFeatureInfo", MS_FALSE))
2✔
3467
      msWMSPrintRequestCap(nVersion, "FeatureInfo", script_url_encoded,
2✔
3468
                           "<MIME /><GML.1 />", NULL);
3469
  } else {
3470
    const char *mime_list[20];
3471
    int mime_count = 0;
3472
    int max_mime = 20;
3473
    /* WMS 1.1.0 and later */
3474
    /* Note changes to the request names, their ordering, and to the formats */
3475

3476
    if (msOWSRequestIsEnabled(map, NULL, "M", "GetCapabilities", MS_TRUE)) {
58✔
3477
      if (nVersion >= OWS_1_3_0)
58✔
3478
        msWMSPrintRequestCap(nVersion, "GetCapabilities", script_url_encoded,
29✔
3479
                             "text/xml", NULL);
3480
      else
3481
        msWMSPrintRequestCap(nVersion, "GetCapabilities", script_url_encoded,
29✔
3482
                             "application/vnd.ogc.wms_xml", NULL);
3483
    }
3484

3485
    msGetOutputFormatMimeListWMS(map, mime_list,
58✔
3486
                                 sizeof(mime_list) / sizeof(char *));
3487
    if (msOWSRequestIsEnabled(map, NULL, "M", "GetMap", MS_FALSE))
58✔
3488
      msWMSPrintRequestCap(
57✔
3489
          nVersion, "GetMap", script_url_encoded, mime_list[0], mime_list[1],
3490
          mime_list[2], mime_list[3], mime_list[4], mime_list[5], mime_list[6],
3491
          mime_list[7], mime_list[8], mime_list[9], mime_list[10],
3492
          mime_list[11], mime_list[12], mime_list[13], mime_list[14],
3493
          mime_list[15], mime_list[16], mime_list[17], mime_list[18],
3494
          mime_list[19], NULL);
3495

3496
    const char *format_list = msOWSLookupMetadata(&(map->web.metadata), "M",
58✔
3497
                                                  "getfeatureinfo_formatlist");
3498
    /*feature_info_mime_type deprecated for MapServer 6.0*/
3499
    if (!format_list)
58✔
3500
      format_list = msOWSLookupMetadata(&(map->web.metadata), "MO",
53✔
3501
                                        "feature_info_mime_type");
3502

3503
    if (format_list && strlen(format_list) > 0) {
58✔
3504
      auto tokens = msStringSplit(format_list, ',');
6✔
3505
      for (auto &token : tokens) {
17✔
3506
        msStringTrim(token);
11✔
3507
        /*text plain and gml do not need to be a format and accepted by
3508
         * default*/
3509
        /*can not really validate since the old way of using template
3510
          with wei->header, layer->template ... should be kept*/
3511
        if (!token.empty() && mime_count < max_mime)
11✔
3512
          mime_list[mime_count++] = token.c_str();
11✔
3513
      }
3514
      /*add text/plain and gml */
3515
      if (strcasestr(format_list, "GML") == 0 &&
6✔
3516
          strcasestr(format_list, "application/vnd.ogc.gml") == 0) {
1✔
3517
        if (mime_count < max_mime)
1✔
3518
          mime_list[mime_count++] = "application/vnd.ogc.gml";
1✔
3519
      }
3520
      if (strcasestr(format_list, "text/plain") == 0 &&
6✔
3521
          strcasestr(format_list, "MIME") == 0) {
6✔
3522
        if (mime_count < max_mime)
6✔
3523
          mime_list[mime_count++] = "text/plain";
6✔
3524
        else /*force always this format*/
3525
          mime_list[max_mime - 1] = "text/plain";
×
3526
      }
3527

3528
      if (msOWSRequestIsEnabled(map, NULL, "M", "GetFeatureInfo", MS_FALSE)) {
6✔
3529
        if (mime_count > 0) {
6✔
3530
          if (mime_count < max_mime)
6✔
3531
            mime_list[mime_count] = NULL;
6✔
3532
          msWMSPrintRequestCap(
6✔
3533
              nVersion, "GetFeatureInfo", script_url_encoded, mime_list[0],
3534
              mime_list[1], mime_list[2], mime_list[3], mime_list[4],
3535
              mime_list[5], mime_list[6], mime_list[7], mime_list[8],
3536
              mime_list[9], mime_list[10], mime_list[11], mime_list[12],
3537
              mime_list[13], mime_list[14], mime_list[15], mime_list[16],
3538
              mime_list[17], mime_list[18], mime_list[19], NULL);
3539
        }
3540
        /*if all formats given are invalid go to default*/
3541
        else
3542
          msWMSPrintRequestCap(nVersion, "GetFeatureInfo", script_url_encoded,
×
3543
                               "text/plain", "application/vnd.ogc.gml", NULL);
3544
      }
3545
    } else {
6✔
3546
      if (msOWSRequestIsEnabled(map, NULL, "M", "GetFeatureInfo", MS_FALSE))
52✔
3547
        msWMSPrintRequestCap(nVersion, "GetFeatureInfo", script_url_encoded,
48✔
3548
                             "text/plain", "application/vnd.ogc.gml", NULL);
3549
    }
3550

3551
    if (strcasecmp(sldenabled, "true") == 0) {
58✔
3552
      if (msOWSRequestIsEnabled(map, NULL, "M", "DescribeLayer", MS_FALSE)) {
41✔
3553
        if (nVersion == OWS_1_3_0)
40✔
3554
          msWMSPrintRequestCap(nVersion, "sld:DescribeLayer",
19✔
3555
                               script_url_encoded, "text/xml", NULL);
3556
        else
3557
          msWMSPrintRequestCap(nVersion, "DescribeLayer", script_url_encoded,
21✔
3558
                               "text/xml", NULL);
3559
      }
3560

3561
      msGetOutputFormatMimeListImg(map, mime_list,
41✔
3562
                                   sizeof(mime_list) / sizeof(char *));
3563

3564
      if (nVersion >= OWS_1_1_1) {
41✔
3565
        const auto isGetLegendGraphicEnabled =
3566
            msOWSRequestIsEnabled(map, NULL, "M", "GetLegendGraphic", MS_FALSE);
34✔
3567
        if (nVersion == OWS_1_3_0) {
34✔
3568
          if (isGetLegendGraphicEnabled)
20✔
3569
            msWMSPrintRequestCap(
16✔
3570
                nVersion, "sld:GetLegendGraphic", script_url_encoded,
3571
                mime_list[0], mime_list[1], mime_list[2], mime_list[3],
3572
                mime_list[4], mime_list[5], mime_list[6], mime_list[7],
3573
                mime_list[8], mime_list[9], mime_list[10], mime_list[11],
3574
                mime_list[12], mime_list[13], mime_list[14], mime_list[15],
3575
                mime_list[16], mime_list[17], mime_list[18], mime_list[19],
3576
                NULL);
3577
          if (msOWSRequestIsEnabled(map, NULL, "M", "GetStyles", MS_FALSE))
20✔
3578
            msWMSPrintRequestCap(nVersion, "ms:GetStyles", script_url_encoded,
16✔
3579
                                 "text/xml", NULL);
3580
        } else {
3581
          if (isGetLegendGraphicEnabled)
14✔
3582
            msWMSPrintRequestCap(
14✔
3583
                nVersion, "GetLegendGraphic", script_url_encoded, mime_list[0],
3584
                mime_list[1], mime_list[2], mime_list[3], mime_list[4],
3585
                mime_list[5], mime_list[6], mime_list[7], mime_list[8],
3586
                mime_list[9], mime_list[10], mime_list[11], mime_list[12],
3587
                mime_list[13], mime_list[14], mime_list[15], mime_list[16],
3588
                mime_list[17], mime_list[18], mime_list[19], NULL);
3589
          if (msOWSRequestIsEnabled(map, NULL, "M", "GetStyles", MS_FALSE))
14✔
3590
            msWMSPrintRequestCap(nVersion, "GetStyles", script_url_encoded,
14✔
3591
                                 "text/xml", NULL);
3592
        }
3593
      }
3594
    }
3595
  }
3596

3597
  msIO_printf("  </Request>\n");
60✔
3598

3599
  msIO_printf("  <Exception>\n");
60✔
3600
  if (nVersion < OWS_1_1_0)
60✔
3601
    msIO_printf("    <Format><BLANK /><INIMAGE /><WMS_XML /></Format>\n");
2✔
3602
  else if (nVersion <= OWS_1_1_1) {
58✔
3603
    msIO_printf("    <Format>application/vnd.ogc.se_xml</Format>\n");
29✔
3604
    msIO_printf("    <Format>application/vnd.ogc.se_inimage</Format>\n");
29✔
3605
    msIO_printf("    <Format>application/vnd.ogc.se_blank</Format>\n");
29✔
3606
  } else { /*>=1.3.0*/
3607
    msIO_printf("    <Format>XML</Format>\n");
29✔
3608
    msIO_printf("    <Format>INIMAGE</Format>\n");
29✔
3609
    msIO_printf("    <Format>BLANK</Format>\n");
29✔
3610
  }
3611
  msIO_printf("  </Exception>\n");
60✔
3612

3613
  if (nVersion != OWS_1_3_0) {
60✔
3614
    /* INSPIRE extended capabilities for WMS 1.1.1 */
3615
    if (nVersion == OWS_1_1_1 && msOWSLookupMetadata(&(map->web.metadata), "MO",
31✔
3616
                                                     "inspire_capabilities")) {
3617
      msIO_printf("  <VendorSpecificCapabilities>\n");
6✔
3618
      msOWSPrintInspireCommonExtendedCapabilities(
6✔
3619
          stdout, map, "MO", OWS_WARN, "inspire_vs:ExtendedCapabilities", NULL,
3620
          validated_language.c_str(), OWS_WMS);
3621
      msIO_printf("  </VendorSpecificCapabilities>\n");
6✔
3622
    } else {
3623
      msIO_printf("  <VendorSpecificCapabilities />\n"); /* nothing yet */
25✔
3624
    }
3625
  }
3626

3627
  /* SLD support */
3628
  if (strcasecmp(sldenabled, "true") == 0) {
60✔
3629
    if (nVersion >= OWS_1_3_0)
43✔
3630
      msIO_printf("  <sld:UserDefinedSymbolization SupportSLD=\"1\" "
20✔
3631
                  "UserLayer=\"0\" UserStyle=\"1\" RemoteWFS=\"0\" "
3632
                  "InlineFeature=\"0\" RemoteWCS=\"0\"/>\n");
3633
    else
3634
      msIO_printf("  <UserDefinedSymbolization SupportSLD=\"1\" "
23✔
3635
                  "UserLayer=\"0\" UserStyle=\"1\" RemoteWFS=\"0\"/>\n");
3636
  }
3637

3638
  /* INSPIRE extended capabilities for WMS 1.3.0 */
3639
  if (nVersion >= OWS_1_3_0 &&
89✔
3640
      msOWSLookupMetadata(&(map->web.metadata), "MO", "inspire_capabilities")) {
29✔
3641
    msOWSPrintInspireCommonExtendedCapabilities(
9✔
3642
        stdout, map, "MO", OWS_WARN, "inspire_vs:ExtendedCapabilities", NULL,
3643
        validated_language.c_str(), OWS_WMS);
3644
  }
3645

3646
  /* Top-level layer with map extents and SRS, encloses all map layers */
3647
  /* Output only if at least one layers is enabled. */
3648
  if (ows_request->numlayers == 0) {
60✔
3649
    msIO_fprintf(stdout, "  <!-- WARNING: No WMS layers are enabled. Check "
5✔
3650
                         "wms/ows_enable_request settings. -->\n");
3651
  } else {
3652
    int root_is_queryable = MS_FALSE;
3653

3654
    const char *rootlayer_name =
3655
        msOWSLookupMetadata(&(map->web.metadata), "MO", "rootlayer_name");
55✔
3656

3657
    /* Root layer is queryable if it has a valid name and at list one layer */
3658
    /* is queryable */
3659
    if (!rootlayer_name || strlen(rootlayer_name) > 0) {
55✔
3660
      int j;
3661
      for (j = 0; j < map->numlayers; j++) {
128✔
3662
        layerObj *layer = GET_LAYER(map, j);
103✔
3663
        if (msIsLayerQueryable(layer) &&
134✔
3664
            msIntegerInArray(layer->index, ows_request->enabled_layers,
31✔
3665
                             ows_request->numlayers)) {
3666
          root_is_queryable = MS_TRUE;
3667
          break;
3668
        }
3669
      }
3670
    }
3671
    msIO_printf("  <Layer%s>\n", root_is_queryable ? " queryable=\"1\"" : "");
80✔
3672

3673
    /* Layer Name is optional but title is mandatory. */
3674
    if (map->name && strlen(map->name) > 0 &&
110✔
3675
        (msIsXMLTagValid(map->name) == MS_FALSE || isdigit(map->name[0])))
110✔
3676
      msIO_fprintf(stdout,
×
3677
                   "<!-- WARNING: The layer name '%s' might contain spaces or "
3678
                   "invalid characters or may start with a number. This could "
3679
                   "lead to potential problems. -->\n",
3680
                   map->name);
3681

3682
    if (rootlayer_name) {
55✔
3683
      if (strlen(rootlayer_name) > 0) {
2✔
3684
        msOWSPrintEncodeMetadata(stdout, &(map->web.metadata), "MO",
1✔
3685
                                 "rootlayer_name", OWS_NOERR,
3686
                                 "    <Name>%s</Name>\n", NULL);
3687
      }
3688
    } else {
3689
      msOWSPrintEncodeParam(stdout, "MAP.NAME", map->name, OWS_NOERR,
53✔
3690
                            "    <Name>%s</Name>\n", NULL);
3691
    }
3692

3693
    if (msOWSLookupMetadataWithLanguage(&(map->web.metadata), "MO",
55✔
3694
                                        "rootlayer_title",
3695
                                        validated_language.c_str()))
3696
      msOWSPrintEncodeMetadata2(
26✔
3697
          stdout, &(map->web.metadata), "MO", "rootlayer_title", OWS_WARN,
3698
          "    <Title>%s</Title>\n", map->name, validated_language.c_str());
26✔
3699

3700
    else
3701
      msOWSPrintEncodeMetadata2(stdout, &(map->web.metadata), "MO", "title",
29✔
3702
                                OWS_WARN, "    <Title>%s</Title>\n", map->name,
29✔
3703
                                validated_language.c_str());
3704

3705
    if (msOWSLookupMetadataWithLanguage(&(map->web.metadata), "MO",
55✔
3706
                                        "rootlayer_abstract",
3707
                                        validated_language.c_str()))
3708
      msOWSPrintEncodeMetadata2(stdout, &(map->web.metadata), "MO",
26✔
3709
                                "rootlayer_abstract", OWS_NOERR,
3710
                                "    <Abstract>%s</Abstract>\n", map->name,
26✔
3711
                                validated_language.c_str());
3712
    else
3713
      msOWSPrintEncodeMetadata2(stdout, &(map->web.metadata), "MO", "abstract",
29✔
3714
                                OWS_NOERR, "    <Abstract>%s</Abstract>\n",
3715
                                map->name, validated_language.c_str());
29✔
3716

3717
    const char *pszTmp;
3718
    if (msOWSLookupMetadata(&(map->web.metadata), "MO",
55✔
3719
                            "rootlayer_keywordlist") ||
84✔
3720
        msOWSLookupMetadata(&(map->web.metadata), "MO",
29✔
3721
                            "rootlayer_keywordlist_vocabulary"))
3722
      pszTmp = "rootlayer_keywordlist";
3723
    else
3724
      pszTmp = "keywordlist";
3725
    msWMSPrintKeywordlist(stdout, "    ", pszTmp, &(map->web.metadata), "MO",
55✔
3726
                          nVersion);
3727

3728
    /* According to normative comments in the 1.0.7 DTD, the root layer's SRS
3729
     * tag */
3730
    /* is REQUIRED.  It also suggests that we use an empty SRS element if there
3731
     */
3732
    /* is no common SRS. */
3733
    char *pszMapEPSG;
3734
    msOWSGetEPSGProj(&(map->projection), &(map->web.metadata), "MO", MS_FALSE,
55✔
3735
                     &pszMapEPSG);
3736
    if (nVersion > OWS_1_1_0) {
55✔
3737
      /* starting 1.1.1 SRS are given in individual tags */
3738
      if (nVersion >= OWS_1_3_0) {
48✔
3739
        msOWSPrintEncodeParamList(stdout,
27✔
3740
                                  "(at least one of) "
3741
                                  "MAP.PROJECTION, LAYER.PROJECTION "
3742
                                  "or wms_srs metadata",
3743
                                  pszMapEPSG, OWS_WARN, ' ', NULL, NULL,
3744
                                  "    <CRS>%s</CRS>\n", "");
3745
      } else {
3746
        msOWSPrintEncodeParamList(stdout,
21✔
3747
                                  "(at least one of) "
3748
                                  "MAP.PROJECTION, LAYER.PROJECTION "
3749
                                  "or wms_srs metadata",
3750
                                  pszMapEPSG, OWS_WARN, ' ', NULL, NULL,
3751
                                  "    <SRS>%s</SRS>\n", "");
3752
      }
3753
    } else {
3754
      /* If map has no proj then every layer MUST have one or produce a warning
3755
       */
3756
      msOWSPrintEncodeParam(stdout, "MAP.PROJECTION (or wms_srs metadata)",
7✔
3757
                            pszMapEPSG, OWS_WARN, "    <SRS>%s</SRS>\n", "");
3758
    }
3759
    msFree(pszMapEPSG);
55✔
3760

3761
    if (nVersion >= OWS_1_3_0)
55✔
3762
      msOWSPrintEX_GeographicBoundingBox(stdout, "    ", &(map->extent),
27✔
3763
                                         &(map->projection));
3764
    else
3765
      msOWSPrintLatLonBoundingBox(stdout, "    ", &(map->extent),
28✔
3766
                                  &(map->projection), NULL, OWS_WMS);
3767

3768
    msOWSPrintBoundingBox(stdout, "    ", &(map->extent), &(map->projection),
55✔
3769
                          NULL, &(map->web.metadata), "MO", nVersion);
3770

3771
    /* AuthorityURL support and Identifier support, only available >= WMS 1.1.0
3772
     */
3773
    if (nVersion >= OWS_1_1_0) {
55✔
3774
      msWMSPrintAttribution(stdout, "    ", &(map->web.metadata), "MO");
54✔
3775
      msWMSPrintAuthorityURL(stdout, "    ", &(map->web.metadata), "MO");
54✔
3776
      msWMSPrintIdentifier(stdout, "    ", &(map->web.metadata), "MO");
54✔
3777
    }
3778

3779
    /* MetadataURL */
3780
    if (nVersion >= OWS_1_1_0)
3781
      msOWSPrintURLType(stdout, &(map->web.metadata), "MO", "metadataurl",
54✔
3782
                        OWS_NOERR, NULL, "MetadataURL", " type=\"%s\"", NULL,
3783
                        NULL, ">\n      <Format>%s</Format",
3784
                        "\n      <OnlineResource xmlns:xlink=\""
3785
                        "http://www.w3.org/1999/xlink\" "
3786
                        "xlink:type=\"simple\" xlink:href=\"%s\"/>\n    ",
3787
                        MS_TRUE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
3788
                        NULL, NULL, NULL, NULL, "    ");
3789

3790
    /* DataURL */
3791
    if (nVersion < OWS_1_1_0)
3792
      msOWSPrintEncodeMetadata(stdout, &(map->web.metadata), "MO",
1✔
3793
                               "dataurl_href", OWS_NOERR,
3794
                               "    <DataURL>%s</DataURL>\n", NULL);
3795
    else
3796
      msOWSPrintURLType(stdout, &(map->web.metadata), "MO", "dataurl",
54✔
3797
                        OWS_NOERR, NULL, "DataURL", NULL, NULL, NULL,
3798
                        ">\n      <Format>%s</Format",
3799
                        "\n      <OnlineResource xmlns:xlink=\""
3800
                        "http://www.w3.org/1999/xlink\" "
3801
                        "xlink:type=\"simple\" xlink:href=\"%s\"/>\n    ",
3802
                        MS_FALSE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
3803
                        NULL, NULL, NULL, NULL, "    ");
3804

3805
    if (map->name && strlen(map->name) > 0 &&
110✔
3806
        msOWSLookupMetadata(&(map->web.metadata), "MO",
55✔
3807
                            "inspire_capabilities")) {
3808
      char *pszEncodedName = NULL;
3809
      const char *styleName = NULL;
3810
      char *pszEncodedStyleName = NULL;
3811
      const char *legendURL = NULL;
3812

3813
      pszEncodedName = msEncodeHTMLEntities(map->name);
15✔
3814

3815
      styleName = msOWSLookupMetadata(&(map->web.metadata), "MO", "style_name");
15✔
3816
      if (styleName == NULL)
15✔
3817
        styleName = "default";
3818

3819
      pszEncodedStyleName = msEncodeHTMLEntities(styleName);
15✔
3820

3821
      msIO_fprintf(stdout, "    <Style>\n");
15✔
3822
      msIO_fprintf(stdout, "       <Name>%s</Name>\n", pszEncodedStyleName);
15✔
3823
      msOWSPrintEncodeMetadata2(
15✔
3824
          stdout, &(map->web.metadata), "MO", "style_title", OWS_NOERR,
3825
          "       <Title>%s</Title>\n", styleName, validated_language.c_str());
3826

3827
      legendURL = msOWSLookupMetadata(&(map->web.metadata), "MO",
15✔
3828
                                      "style_legendurl_href");
3829
      if (legendURL) {
15✔
3830
        msOWSPrintURLType(stdout, &(map->web.metadata), "MO", "style_legendurl",
9✔
3831
                          OWS_NOERR, NULL, "LegendURL", NULL, " width=\"%s\"",
3832
                          " height=\"%s\"", ">\n          <Format>%s</Format",
3833
                          "\n          <OnlineResource "
3834
                          "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
3835
                          " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
3836
                          "       ",
3837
                          MS_FALSE, MS_TRUE, MS_TRUE, MS_TRUE, MS_TRUE, NULL,
3838
                          NULL, NULL, NULL, NULL, "       ");
3839
      } else {
3840
        std::vector<int> group_layers;
3841
        group_layers.reserve(map->numlayers);
6✔
3842

3843
        for (int i = 0; i < map->numlayers; i++)
96✔
3844
          if (msIntegerInArray(GET_LAYER(map, i)->index,
90✔
3845
                               ows_request->enabled_layers,
3846
                               ows_request->numlayers))
3847
            group_layers.push_back(i);
84✔
3848

3849
        if (!group_layers.empty()) {
6✔
3850
          int size_x = 0, size_y = 0;
6✔
3851
          if (msLegendCalcSize(map, 1, &size_x, &size_y, group_layers.data(),
6✔
3852
                               static_cast<int>(group_layers.size()), NULL,
3853
                               1) == MS_SUCCESS) {
3854
            const std::string width(std::to_string(size_x));
6✔
3855
            const std::string height(std::to_string(size_y));
6✔
3856

3857
            const char *format_list = msOWSLookupMetadata(
6✔
3858
                &(map->web.metadata), "M", "getlegendgraphic_formatlist");
3859
            char *pszMimetype = NULL;
3860
            if (format_list && strlen(format_list) > 0) {
6✔
3861
              const auto tokens = msStringSplit(format_list, ',');
×
3862
              if (!tokens.empty()) {
×
3863
                /*just grab the first mime type*/
3864
                pszMimetype = msEncodeHTMLEntities(tokens[0].c_str());
×
3865
              }
3866
            } else
×
3867
              pszMimetype = msEncodeHTMLEntities("image/png");
6✔
3868

3869
            std::string legendurl(script_url_encoded);
6✔
3870
            legendurl += "version=";
3871
            char szVersionBuf[OWS_VERSION_MAXLEN];
3872
            legendurl += msOWSGetVersionString(nVersion, szVersionBuf);
6✔
3873
            legendurl += "&amp;service=WMS&amp;request=GetLegendGraphic&amp;";
3874
            if (nVersion >= OWS_1_3_0) {
6✔
3875
              legendurl += "sld_version=1.1.0&amp;layer=";
3876
            } else {
3877
              legendurl += "layer=";
3878
            }
3879
            legendurl += pszEncodedName;
3880
            legendurl += "&amp;format=";
3881
            legendurl += pszMimetype;
3882
            legendurl += "&amp;STYLE=";
3883
            legendurl += pszEncodedStyleName;
3884

3885
            msOWSPrintURLType(stdout, NULL, "O", "ttt", OWS_NOERR, NULL,
6✔
3886
                              "LegendURL", NULL, " width=\"%s\"",
3887
                              " height=\"%s\"",
3888
                              ">\n          <Format>%s</Format",
3889
                              "\n          <OnlineResource "
3890
                              "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
3891
                              " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
3892
                              "       ",
3893
                              MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE,
3894
                              NULL, width.c_str(), height.c_str(), pszMimetype,
3895
                              legendurl.c_str(), "       ");
3896
            msFree(pszMimetype);
6✔
3897
          }
3898
        }
3899
      }
6✔
3900
      msIO_fprintf(stdout, "    </Style>\n");
15✔
3901
      msFree(pszEncodedName);
15✔
3902
      msFree(pszEncodedStyleName);
15✔
3903
    }
3904

3905
    if (nVersion < OWS_1_3_0)
55✔
3906
      msWMSPrintScaleHint("    ", map->web.minscaledenom,
28✔
3907
                          map->web.maxscaledenom, map->resolution);
3908
    else
3909
      msWMSPrintScaleDenominator("    ", map->web.minscaledenom,
27✔
3910
                                 map->web.maxscaledenom);
3911

3912
    /*  */
3913
    /* Dump list of layers organized by groups.  Layers with no group are listed
3914
     */
3915
    /* individually, at the same level as the groups in the layer hierarchy */
3916
    /*  */
3917
    if (map->numlayers) {
55✔
3918
      char ***nestedGroups = NULL;
3919
      int *numNestedGroups = NULL;
3920
      int *isUsedInNestedGroup = NULL;
3921

3922
      /* We'll use this array of booleans to track which layer/group have been
3923
       */
3924
      /* processed already */
3925
      std::vector<char> pabLayerProcessed(map->numlayers);
55✔
3926

3927
      /* Mark disabled layers as processed to prevent from being displayed in
3928
       * nested groups (#4533)*/
3929
      for (int i = 0; i < map->numlayers; i++) {
255✔
3930
        if (!msIntegerInArray(GET_LAYER(map, i)->index,
200✔
3931
                              ows_request->enabled_layers,
3932
                              ows_request->numlayers))
3933
          pabLayerProcessed[i] = 1;
13✔
3934
      }
3935

3936
      nestedGroups = (char ***)msSmallCalloc(map->numlayers, sizeof(char **));
55✔
3937
      numNestedGroups = (int *)msSmallCalloc(map->numlayers, sizeof(int));
55✔
3938
      isUsedInNestedGroup = (int *)msSmallCalloc(map->numlayers, sizeof(int));
55✔
3939
      msWMSPrepareNestedGroups(map, nVersion, nestedGroups, numNestedGroups,
55✔
3940
                               isUsedInNestedGroup);
3941

3942
      for (int i = 0; i < map->numlayers; i++) {
255✔
3943
        layerObj *lp = (GET_LAYER(map, i));
200✔
3944

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

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

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

3999
          /*build a getlegendgraphicurl*/
4000
          if (script_url_encoded) {
4001
            if (lp->group && strlen(lp->group) > 0) {
11✔
4002
              char *pszEncodedName = NULL;
4003
              const char *styleName = NULL;
4004
              char *pszEncodedStyleName = NULL;
4005
              const char *legendURL = NULL;
4006

4007
              pszEncodedName = msEncodeHTMLEntities(lp->group);
11✔
4008

4009
              styleName = msOWSLookupMetadata(&(lp->metadata), "MO",
11✔
4010
                                              "group_style_name");
4011
              if (styleName == NULL)
11✔
4012
                styleName = "default";
4013

4014
              pszEncodedStyleName = msEncodeHTMLEntities(styleName);
11✔
4015

4016
              msIO_fprintf(stdout, "    <Style>\n");
11✔
4017
              msIO_fprintf(stdout, "       <Name>%s</Name>\n",
11✔
4018
                           pszEncodedStyleName);
4019
              msOWSPrintEncodeMetadata(stdout, &(lp->metadata), "MO",
11✔
4020
                                       "group_style_title", OWS_NOERR,
4021
                                       "       <Title>%s</Title>\n", styleName);
4022

4023
              legendURL = msOWSLookupMetadata(&(lp->metadata), "MO",
11✔
4024
                                              "group_style_legendurl_href");
4025
              if (legendURL) {
11✔
4026
                msOWSPrintURLType(
9✔
4027
                    stdout, &(lp->metadata), "MO", "group_style_legendurl",
4028
                    OWS_NOERR, NULL, "LegendURL", NULL, " width=\"%s\"",
4029
                    " height=\"%s\"", ">\n          <Format>%s</Format",
4030
                    "\n          <OnlineResource "
4031
                    "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
4032
                    " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
4033
                    "       ",
4034
                    MS_FALSE, MS_TRUE, MS_TRUE, MS_TRUE, MS_TRUE, NULL, NULL,
4035
                    NULL, NULL, NULL, "       ");
4036
              } else {
4037
                std::vector<int> group_layers;
4038
                group_layers.reserve(map->numlayers);
2✔
4039

4040
                for (int j = i; j < map->numlayers; j++)
7✔
4041
                  if (!pabLayerProcessed[j] && GET_LAYER(map, j)->group &&
5✔
4042
                      strcmp(lp->group, GET_LAYER(map, j)->group) == 0 &&
10✔
4043
                      msIntegerInArray(GET_LAYER(map, j)->index,
2✔
4044
                                       ows_request->enabled_layers,
4045
                                       ows_request->numlayers))
4046
                    group_layers.push_back(j);
2✔
4047

4048
                if (!group_layers.empty()) {
2✔
4049
                  int size_x = 0, size_y = 0;
2✔
4050
                  char *pszMimetype = NULL;
4051

4052
                  if (msLegendCalcSize(map, 1, &size_x, &size_y,
2✔
4053
                                       group_layers.data(),
4054
                                       static_cast<int>(group_layers.size()),
4055
                                       NULL, 1) == MS_SUCCESS) {
4056
                    const std::string width(std::to_string(size_x));
2✔
4057
                    const std::string height(std::to_string(size_y));
2✔
4058

4059
                    const char *format_list =
4060
                        msOWSLookupMetadata(&(map->web.metadata), "M",
2✔
4061
                                            "getlegendgraphic_formatlist");
4062
                    if (format_list && strlen(format_list) > 0) {
2✔
4063
                      const auto tokens = msStringSplit(format_list, ',');
×
4064
                      if (!tokens.empty()) {
×
4065
                        /*just grab the first mime type*/
4066
                        pszMimetype = msEncodeHTMLEntities(tokens[0].c_str());
×
4067
                      }
4068
                    } else
×
4069
                      pszMimetype = msEncodeHTMLEntities("image/png");
2✔
4070

4071
                    std::string legendurl(script_url_encoded);
2✔
4072
                    legendurl += "version=";
4073
                    char szVersionBuf[OWS_VERSION_MAXLEN];
4074
                    legendurl += msOWSGetVersionString(nVersion, szVersionBuf);
2✔
4075
                    legendurl +=
4076
                        "&amp;service=WMS&amp;request=GetLegendGraphic&amp;";
4077
                    if (nVersion >= OWS_1_3_0) {
2✔
4078
                      legendurl += "sld_version=1.1.0&amp;layer=";
4079
                    } else {
4080
                      legendurl += "layer=";
4081
                    }
4082
                    legendurl += pszEncodedName;
4083
                    legendurl += "&amp;format=";
4084
                    legendurl += pszMimetype;
4085
                    legendurl += "&amp;STYLE=";
4086
                    legendurl += pszEncodedStyleName;
4087

4088
                    msOWSPrintURLType(
2✔
4089
                        stdout, NULL, "O", "ttt", OWS_NOERR, NULL, "LegendURL",
4090
                        NULL, " width=\"%s\"", " height=\"%s\"",
4091
                        ">\n          <Format>%s</Format",
4092
                        "\n          <OnlineResource "
4093
                        "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
4094
                        " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
4095
                        "       ",
4096
                        MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE, NULL,
4097
                        width.c_str(), height.c_str(), pszMimetype,
4098
                        legendurl.c_str(), "       ");
4099

4100
                    msFree(pszMimetype);
2✔
4101
                  }
4102
                }
4103
              }
2✔
4104
              msIO_fprintf(stdout, "    </Style>\n");
11✔
4105
              msFree(pszEncodedName);
11✔
4106
              msFree(pszEncodedStyleName);
11✔
4107
            }
4108
          }
4109

4110
          /* Dump all layers for this group */
4111
          for (int j = i; j < map->numlayers; j++) {
34✔
4112
            if (!pabLayerProcessed[j] && GET_LAYER(map, j)->group &&
23✔
4113
                strcmp(lp->group, GET_LAYER(map, j)->group) == 0 &&
64✔
4114
                msIntegerInArray(GET_LAYER(map, j)->index,
20✔
4115
                                 ows_request->enabled_layers,
4116
                                 ows_request->numlayers)) {
4117
              msDumpLayer(map, (GET_LAYER(map, j)), nVersion,
20✔
4118
                          script_url_encoded, "  ", validated_language.c_str(),
4119
                          MS_FALSE, MS_FALSE);
4120
              pabLayerProcessed[j] = 1;
20✔
4121
            }
4122
          }
4123
          /* Close group layer block */
4124
          msIO_printf("    </Layer>\n");
11✔
4125
        }
4126
      }
4127

4128
      /* free the stuff used for nested layers */
4129
      for (int i = 0; i < map->numlayers; i++) {
255✔
4130
        if (numNestedGroups[i] > 0) {
200✔
4131
          msFreeCharArray(nestedGroups[i], numNestedGroups[i]);
80✔
4132
        }
4133
      }
4134
      free(nestedGroups);
55✔
4135
      free(numNestedGroups);
55✔
4136
      free(isUsedInNestedGroup);
55✔
4137
    }
55✔
4138

4139
    msIO_printf("  </Layer>\n");
55✔
4140
  }
4141

4142
  msIO_printf("</Capability>\n");
60✔
4143
  if (nVersion >= OWS_1_3_0)
60✔
4144
    msIO_printf("</WMS_Capabilities>\n");
29✔
4145
  else
4146
    msIO_printf("</WMT_MS_Capabilities>\n");
31✔
4147

4148
  free(script_url_encoded);
60✔
4149

4150
  return (MS_SUCCESS);
60✔
4151
}
4152

4153
/*
4154
 * This function look for params that can be used
4155
 * by mapserv when generating template.
4156
 */
4157
int msTranslateWMS2Mapserv(const char **names, const char **values,
9✔
4158
                           int numentries, char ***translated_names,
4159
                           char ***translated_values,
4160
                           int *translated_numentries) {
4161
  int num_allocated = numentries;
4162
  *translated_names = (char **)msSmallMalloc(num_allocated * sizeof(char *));
9✔
4163
  *translated_values = (char **)msSmallMalloc(num_allocated * sizeof(char *));
9✔
4164
  *translated_numentries = 0;
9✔
4165
  for (int i = 0; i < numentries; i++) {
158✔
4166
    (*translated_values)[*translated_numentries] = msStrdup(values[i]);
149✔
4167
    (*translated_names)[*translated_numentries] = msStrdup(names[i]);
149✔
4168
    (*translated_numentries)++;
149✔
4169
    if (strcasecmp("X", names[i]) == 0) {
149✔
4170
      num_allocated++;
3✔
4171
      *translated_names = (char **)msSmallRealloc(
6✔
4172
          *translated_names, num_allocated * sizeof(char *));
3✔
4173
      *translated_values = (char **)msSmallRealloc(
3✔
4174
          *translated_values, num_allocated * sizeof(char *));
4175
      (*translated_values)[*translated_numentries] = msStrdup(values[i]);
3✔
4176
      (*translated_names)[*translated_numentries] = msStrdup("img.x");
3✔
4177
      (*translated_numentries)++;
3✔
4178
    } else if (strcasecmp("Y", names[i]) == 0) {
146✔
4179
      num_allocated++;
3✔
4180
      *translated_names = (char **)msSmallRealloc(
6✔
4181
          *translated_names, num_allocated * sizeof(char *));
3✔
4182
      *translated_values = (char **)msSmallRealloc(
3✔
4183
          *translated_values, num_allocated * sizeof(char *));
4184
      (*translated_values)[*translated_numentries] = msStrdup(values[i]);
3✔
4185
      (*translated_names)[*translated_numentries] = msStrdup("img.y");
3✔
4186
      (*translated_numentries)++;
3✔
4187
    } else if (strcasecmp("LAYERS", names[i]) == 0) {
143✔
4188
      char **layers;
4189
      int tok;
4190
      int j;
4191
      layers = msStringSplit(values[i], ',', &tok);
9✔
4192
      num_allocated += tok;
9✔
4193
      *translated_names = (char **)msSmallRealloc(
18✔
4194
          *translated_names, num_allocated * sizeof(char *));
9✔
4195
      *translated_values = (char **)msSmallRealloc(
9✔
4196
          *translated_values, num_allocated * sizeof(char *));
4197
      for (j = 0; j < tok; j++) {
20✔
4198
        (*translated_values)[*translated_numentries] = layers[j];
11✔
4199
        (*translated_names)[*translated_numentries] = msStrdup("layer");
11✔
4200
        (*translated_numentries)++;
11✔
4201
        layers[j] = NULL;
11✔
4202
      }
4203
      free(layers);
9✔
4204
    } else if (strcasecmp("QUERY_LAYERS", names[i]) == 0) {
134✔
4205
      char **layers;
4206
      int tok;
4207
      int j;
4208
      layers = msStringSplit(values[i], ',', &tok);
9✔
4209
      num_allocated += tok;
9✔
4210
      *translated_names = (char **)msSmallRealloc(
18✔
4211
          *translated_names, num_allocated * sizeof(char *));
9✔
4212
      *translated_values = (char **)msSmallRealloc(
9✔
4213
          *translated_values, num_allocated * sizeof(char *));
4214
      for (j = 0; j < tok; j++) {
18✔
4215
        (*translated_values)[*translated_numentries] = layers[j];
9✔
4216
        (*translated_names)[*translated_numentries] = msStrdup("qlayer");
9✔
4217
        (*translated_numentries)++;
9✔
4218
        layers[j] = NULL;
9✔
4219
      }
4220
      free(layers);
9✔
4221
    } else if (strcasecmp("BBOX", names[i]) == 0) {
125✔
4222
      char *imgext;
4223
      num_allocated++;
9✔
4224
      *translated_names = (char **)msSmallRealloc(
18✔
4225
          *translated_names, num_allocated * sizeof(char *));
9✔
4226
      *translated_values = (char **)msSmallRealloc(
9✔
4227
          *translated_values, num_allocated * sizeof(char *));
4228

4229
      /* Note msReplaceSubstring() works on the string itself, so we need to
4230
       * make a copy */
4231
      imgext = msStrdup(values[i]);
9✔
4232
      imgext = msReplaceSubstring(imgext, ",", " ");
9✔
4233
      (*translated_values)[*translated_numentries] = imgext;
9✔
4234
      (*translated_names)[*translated_numentries] = msStrdup("imgext");
9✔
4235
      (*translated_numentries)++;
9✔
4236
    }
4237
  }
4238

4239
  return MS_SUCCESS;
9✔
4240
}
4241

4242
/*
4243
** msWMSGetMap()
4244
*/
4245
static int msWMSGetMap(mapObj *map, int nVersion, char **names, char **values,
412✔
4246
                       int numentries, const char *wms_exception_format,
4247
                       owsRequestObj *ows_request) {
4248

4249
  // If we are returning an OpenLayers map there is no need to first generate an
4250
  // image. We can't call msReturnOpenLayersPage directly here as it requires
4251
  // the mapservObj
4252
  if (strcasecmp(map->imagetype, "application/openlayers") == 0) {
412✔
4253
    return MS_SUCCESS;
4254
  }
4255

4256
  imageObj *img;
4257
  int sldrequested = MS_FALSE, sldspatialfilter = MS_FALSE;
4258
  int drawquerymap = MS_FALSE;
4259

4260
  /* __TODO__ msDrawMap() will try to adjust the extent of the map */
4261
  /* to match the width/height image ratio. */
4262
  /* The spec states that this should not happen so that we can deliver */
4263
  /* maps to devices with non-square pixels. */
4264

4265
  /* If there was an SLD in the request, we need to treat it */
4266
  /* differently : some SLD may contain spatial filters requiring */
4267
  /* to do a query. While parsing the SLD and applying it to the */
4268
  /* layer, we added a temporary metadata on the layer */
4269
  /* (tmp_wms_sld_query) for layers with a spatial filter. */
4270

4271
  for (int i = 0; i < numentries; i++) {
5,092✔
4272
    if ((strcasecmp(names[i], "SLD") == 0 && values[i] &&
4,817✔
4273
         strlen(values[i]) > 0) ||
134✔
4274
        (strcasecmp(names[i], "SLD_BODY") == 0 && values[i] &&
4,814✔
4275
         strlen(values[i]) > 0)) {
4276
      sldrequested = MS_TRUE;
4277
      break;
4278
    }
4279
  }
4280
  if (sldrequested) {
409✔
4281
    for (int i = 0; i < map->numlayers; i++) {
609✔
4282
      if (msLookupHashTable(&(GET_LAYER(map, i)->metadata),
475✔
4283
                            "tmp_wms_sld_query")) {
4284
        sldspatialfilter = MS_TRUE;
4285
        break;
4286
      }
4287
    }
4288
  }
4289
  /* If FILTER is passed then we'll render layers as querymap */
4290
  for (int i = 0; i < numentries; i++) {
5,215✔
4291
    if ((strcasecmp(names[i], "FILTER") == 0 && values[i] &&
4,816✔
4292
         strlen(values[i]) > 0)) {
10✔
4293
      drawquerymap = MS_TRUE;
4294
      map->querymap.status = MS_ON;
10✔
4295
      map->querymap.style = MS_SELECTED;
10✔
4296
      break;
10✔
4297
    }
4298
  }
4299

4300
  /* turn off layer if WMS GetMap is not enabled */
4301
  for (int i = 0; i < map->numlayers; i++)
3,034✔
4302
    if (!msIntegerInArray(GET_LAYER(map, i)->index, ows_request->enabled_layers,
2,625✔
4303
                          ows_request->numlayers))
4304
      GET_LAYER(map, i)->status = MS_OFF;
2✔
4305

4306
  if (sldrequested && sldspatialfilter) {
409✔
4307
    /* set the quermap style so that only selected features will be returned */
4308
    map->querymap.status = MS_ON;
×
4309
    map->querymap.style = MS_SELECTED;
×
4310

4311
    img = msPrepareImage(map, MS_TRUE);
×
4312

4313
    /* compute layer scale factors now */
4314
    for (int i = 0; i < map->numlayers; i++) {
×
4315
      if (GET_LAYER(map, i)->sizeunits != MS_PIXELS)
×
4316
        GET_LAYER(map, i)->scalefactor =
×
4317
            (msInchesPerUnit(GET_LAYER(map, i)->sizeunits, 0) /
×
4318
             msInchesPerUnit(map->units, 0)) /
×
4319
            map->cellsize;
×
4320
      else if (GET_LAYER(map, i)->symbolscaledenom > 0 && map->scaledenom > 0)
×
4321
        GET_LAYER(map, i)->scalefactor =
×
4322
            GET_LAYER(map, i)->symbolscaledenom / map->scaledenom;
×
4323
      else
4324
        GET_LAYER(map, i)->scalefactor = 1;
×
4325
    }
4326
    for (int i = 0; i < map->numlayers; i++) {
×
4327
      if (msLookupHashTable(&(GET_LAYER(map, i)->metadata),
×
4328
                            "tmp_wms_sld_query") &&
×
4329
          (GET_LAYER(map, i)->type == MS_LAYER_POINT ||
×
4330
           GET_LAYER(map, i)->type == MS_LAYER_LINE ||
4331
           GET_LAYER(map, i)->type == MS_LAYER_POLYGON ||
×
4332
           GET_LAYER(map, i)->type == MS_LAYER_TILEINDEX))
4333

4334
      {
4335
        /* make sure that there is a resultcache. If not just ignore */
4336
        /* the layer */
4337
        if (GET_LAYER(map, i)->resultcache)
×
4338
          msDrawQueryLayer(map, GET_LAYER(map, i), img);
×
4339
      }
4340

4341
      else
4342
        IGNORE_RET_VAL(msDrawLayer(map, GET_LAYER(map, i), img));
×
4343
    }
4344

4345
  } else {
4346

4347
    /* intercept requests for Mapbox vector tiles */
4348
    if (!strcmp(MS_IMAGE_MIME_TYPE(map->outputformat),
818✔
4349
                "application/vnd.mapbox-vector-tile") ||
405✔
4350
        !strcmp(MS_IMAGE_MIME_TYPE(map->outputformat),
405✔
4351
                "application/x-protobuf")) {
4352
      int status = 0;
4353
      if ((status = msMVTWriteTile(map, MS_TRUE)) != MS_SUCCESS)
5✔
4354
        return MS_FAILURE;
4355
      return MS_SUCCESS;
4356
    }
4357

4358
    img = msDrawMap(map, drawquerymap);
404✔
4359
  }
4360

4361
  /* see if we have tiled = true and a buffer */
4362
  /* if so, clip the image */
4363
  for (int i = 0; i < numentries; i++) {
5,164✔
4364
    if (strcasecmp(names[i], "TILED") == 0 &&
4,761✔
4365
        strcasecmp(values[i], "TRUE") == 0) {
1✔
4366
      hashTableObj *meta = &(map->web.metadata);
1✔
4367
      const char *value;
4368

4369
      if ((value = msLookupHashTable(meta, "tile_map_edge_buffer")) != NULL) {
1✔
4370
        const int map_edge_buffer = atoi(value);
4371
        if (map_edge_buffer > 0) {
1✔
4372
          /* we have to clip the image */
4373

4374
          // TODO: we could probably avoid the use of an intermediate image
4375
          // by playing with the rasterBufferObj's data->rgb.pixels and
4376
          // data->rgb.row_stride values.
4377
          rendererVTableObj *renderer = MS_MAP_RENDERER(map);
1✔
4378
          rasterBufferObj imgBuffer;
4379
          if (renderer->getRasterBufferHandle((imageObj *)img, &imgBuffer) !=
1✔
4380
              MS_SUCCESS) {
4381
            msFreeImage(img);
×
4382
            return MS_FAILURE;
×
4383
          }
4384

4385
          int width = map->width - map_edge_buffer - map_edge_buffer;
1✔
4386
          int height = map->height - map_edge_buffer - map_edge_buffer;
1✔
4387
          imageObj *tmp =
4388
              msImageCreate(width, height, map->outputformat, NULL, NULL,
1✔
4389
                            map->resolution, map->defresolution, NULL);
4390

4391
          if ((MS_FAILURE == renderer->mergeRasterBuffer(
1✔
4392
                                 tmp, &imgBuffer, 1.0, map_edge_buffer,
4393
                                 map_edge_buffer, 0, 0, width, height))) {
4394
            msFreeImage(tmp);
×
4395
            msFreeImage(img);
×
4396
            img = NULL;
4397
          } else {
4398
            msFreeImage(img);
1✔
4399
            img = tmp;
4400
          }
4401
        }
4402
      }
4403
      break;
4404
    }
4405
  }
4406

4407
  if (img == NULL)
404✔
4408
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4409

4410
  /* Set the HTTP Cache-control headers if they are defined
4411
     in the map object */
4412

4413
  const char *http_max_age =
4414
      msOWSLookupMetadata(&(map->web.metadata), "MO", "http_max_age");
404✔
4415
  if (http_max_age) {
404✔
4416
    msIO_setHeader("Cache-Control", "max-age=%s", http_max_age);
2✔
4417
  }
4418

4419
  if (strcasecmp(map->imagetype, "application/openlayers") != 0) {
404✔
4420
    if (!strcmp(MS_IMAGE_MIME_TYPE(map->outputformat), "application/json")) {
808✔
4421
      msIO_setHeader("Content-Type", "application/json; charset=utf-8");
×
4422
    } else {
4423
      msOutputFormatResolveFromImage(map, img);
404✔
4424
      msIO_setHeader("Content-Type", "%s",
404✔
4425
                     MS_IMAGE_MIME_TYPE(map->outputformat));
404✔
4426
    }
4427
    msIO_sendHeaders();
404✔
4428
    if (msSaveImage(map, img, NULL) != MS_SUCCESS) {
404✔
4429
      msFreeImage(img);
×
4430
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4431
    }
4432
  }
4433
  msFreeImage(img);
404✔
4434

4435
  return (MS_SUCCESS);
404✔
4436
}
4437

4438
static int msDumpResult(mapObj *map, int nVersion,
7✔
4439
                        const char *wms_exception_format) {
4440
  int numresults = 0;
4441

4442
  for (int i = 0; i < map->numlayers; i++) {
35✔
4443
    layerObj *lp = (GET_LAYER(map, i));
28✔
4444

4445
    if (lp->status != MS_ON || lp->resultcache == NULL ||
28✔
4446
        lp->resultcache->numresults == 0)
16✔
4447
      continue;
18✔
4448

4449
    /* if(msLayerOpen(lp) != MS_SUCCESS || msLayerGetItems(lp) != MS_SUCCESS)
4450
     return msWMSException(map, nVersion, NULL); */
4451

4452
    /* Use metadata to control which fields to output. We use the same
4453
     * metadata names as for GML:
4454
     * wms/ows_include_items: comma delimited list or keyword 'all'
4455
     * wms/ows_exclude_items: comma delimited list (all items are excluded by
4456
     * default)
4457
     */
4458
    /* get a list of items that should be excluded in output */
4459
    std::vector<std::string> incitems;
4460
    const char *value;
4461
    if ((value = msOWSLookupMetadata(&(lp->metadata), "MO", "include_items")) !=
10✔
4462
        NULL)
4463
      incitems = msStringSplit(value, ',');
10✔
4464

4465
    /* get a list of items that should be excluded in output */
4466
    std::vector<std::string> excitems;
4467
    if ((value = msOWSLookupMetadata(&(lp->metadata), "MO", "exclude_items")) !=
10✔
4468
        NULL)
4469
      excitems = msStringSplit(value, ',');
×
4470

4471
    std::vector<bool> itemvisible(lp->numitems);
10✔
4472
    for (int k = 0; k < lp->numitems; k++) {
175✔
4473
      /* check visibility, included items first... */
4474
      if (incitems.size() == 1 && strcasecmp("all", incitems[0].c_str()) == 0) {
165✔
4475
        itemvisible[k] = true;
4476
      } else {
4477
        for (const auto &incitem : incitems) {
×
4478
          if (strcasecmp(lp->items[k], incitem.c_str()) == 0)
×
4479
            itemvisible[k] = true;
4480
        }
4481
      }
4482

4483
      /* ...and now excluded items */
4484
      for (const auto &excitem : excitems) {
165✔
4485
        if (strcasecmp(lp->items[k], excitem.c_str()) == 0)
×
4486
          itemvisible[k] = false;
4487
      }
4488
    }
4489

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

4493
    for (int j = 0; j < lp->resultcache->numresults; j++) {
32✔
4494
      shapeObj shape;
22✔
4495

4496
      msInitShape(&shape);
22✔
4497
      if (msLayerGetShape(lp, &shape, &(lp->resultcache->results[j])) !=
22✔
4498
          MS_SUCCESS) {
4499
        return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4500
      }
4501

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

4504
      for (int k = 0; k < lp->numitems; k++) {
307✔
4505
        if (itemvisible[k]) {
285✔
4506
          value = msOWSLookupMetadata(
285✔
4507
              &(lp->metadata), "MO",
4508
              (std::string(lp->items[k]) + "_alias").c_str());
285✔
4509
          const char *lineTemplate = "    %s = '%s'\n";
4510
          msIO_printf(lineTemplate, value != NULL ? value : lp->items[k],
285✔
4511
                      shape.values[k]);
285✔
4512
        }
4513
      }
4514

4515
      msFreeShape(&shape);
22✔
4516
      numresults++;
22✔
4517
    }
22✔
4518

4519
    /* msLayerClose(lp); */
4520
  }
10✔
4521

4522
  return numresults;
4523
}
4524

4525
/*
4526
** msWMSFeatureInfo()
4527
*/
4528
static int msWMSFeatureInfo(mapObj *map, int nVersion, char **names,
58✔
4529
                            char **values, int numentries,
4530
                            const char *wms_exception_format,
4531
                            owsRequestObj *ows_request) {
4532
  int feature_count = 1, numlayers_found = 0;
4533
  pointObj point = {-1.0, -1.0, -1.0, -1.0};
4534
  const char *info_format = "MIME";
4535
  int query_layer = 0;
4536
  const char *format_list = NULL;
4537
  int valid_format = MS_FALSE;
4538
  int format_found = MS_FALSE;
4539
  int use_bbox = MS_FALSE;
4540
  int wms_layer = MS_FALSE;
4541
  const char *wms_connection = NULL;
4542
  int numOWSLayers = 0;
4543

4544
  char ***nestedGroups =
4545
      (char ***)msSmallCalloc(map->numlayers, sizeof(char **));
58✔
4546
  int *numNestedGroups = (int *)msSmallCalloc(map->numlayers, sizeof(int));
58✔
4547
  int *isUsedInNestedGroup = (int *)msSmallCalloc(map->numlayers, sizeof(int));
58✔
4548
  msWMSPrepareNestedGroups(map, nVersion, nestedGroups, numNestedGroups,
58✔
4549
                           isUsedInNestedGroup);
4550

4551
  const char *styles = nullptr;
4552
  std::vector<std::string> wmslayers;
4553
  for (int i = 0; i < numentries; i++) {
985✔
4554
    if (strcasecmp(names[i], "LAYERS") == 0) {
927✔
4555

4556
      wmslayers = msStringSplit(values[i], ',');
58✔
4557
      if (wmslayers.empty()) {
58✔
4558
        return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4559
      }
4560

4561
      if (nVersion >= OWS_1_3_0) {
58✔
4562
        const char *layerlimit =
4563
            msOWSLookupMetadata(&(map->web.metadata), "MO", "layerlimit");
33✔
4564
        if (layerlimit) {
33✔
4565
          if (static_cast<int>(wmslayers.size()) > atoi(layerlimit)) {
3✔
4566
            msSetErrorWithStatus(
×
4567
                MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4568
                "Number of layers requested exceeds LayerLimit.",
4569
                "msWMSLoadGetMapParams()");
4570
            return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4571
          }
4572
        }
4573
      }
4574
    } else if (strcasecmp(names[i], "STYLES=") == 0) {
869✔
4575
      styles = values[i];
×
4576
    }
4577
  }
4578

4579
  std::map<std::string, std::vector<std::string>> mapLayerNameToStyleNames;
4580

4581
  if (styles && strlen(styles) > 0) {
58✔
4582
    int n = 0;
×
4583
    char **tokens = msStringSplitComplex(styles, ",", &n, MS_ALLOWEMPTYTOKENS);
×
4584

4585
    if (static_cast<int>(wmslayers.size()) != n) {
×
4586
      msSetErrorWithStatus(
×
4587
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4588
          "Invalid style (%s). Mapserver is expecting an empty "
4589
          "string for the STYLES : STYLES= or STYLES=,,, or using "
4590
          "keyword default  STYLES=default,default, ...",
4591
          "msWMSLoadGetMapParams()", styles);
4592
      msFreeCharArray(tokens, n);
×
4593
      return msWMSException(map, nVersion, "StyleNotDefined",
×
4594
                            wms_exception_format);
×
4595
    }
4596

4597
    for (int i = 0; i < n; i++) {
×
4598
      if (tokens[i] && strlen(tokens[i]) > 0 &&
×
4599
          strcasecmp(tokens[i], "default") != 0) {
×
4600
        const std::string &correspondingLayer = wmslayers[i];
4601
        for (int j = 0; j < map->numlayers; j++) {
×
4602
          layerObj *lp = GET_LAYER(map, j);
×
4603
          if ((lp->name &&
×
4604
               strcasecmp(lp->name, correspondingLayer.c_str()) == 0) ||
×
4605
              (lp->group &&
×
4606
               strcasecmp(lp->group, correspondingLayer.c_str()) == 0)) {
×
4607
            bool found = false;
4608
            for (int k = 0; k < lp->numclasses; k++) {
×
4609
              if (lp->_class[k]->group &&
×
4610
                  strcasecmp(lp->_class[k]->group, tokens[i]) == 0) {
×
4611
                mapLayerNameToStyleNames[correspondingLayer].push_back(
×
4612
                    tokens[i]);
4613
                found = true;
4614
                break;
4615
              }
4616
            }
4617
            if (!found) {
4618
              msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
4619
                                   "Style (%s) not defined on layer.",
4620
                                   "msWMSLoadGetMapParams()", tokens[i]);
4621
              msFreeCharArray(tokens, n);
×
4622

4623
              return msWMSException(map, nVersion, "StyleNotDefined",
×
4624
                                    wms_exception_format);
4625
            }
4626
            /* Check the style of the root layer */
4627
          } else if (map->name &&
×
4628
                     strcasecmp(map->name, correspondingLayer.c_str()) == 0) {
×
4629
            const char *styleName =
4630
                msOWSLookupMetadata(&(map->web.metadata), "MO", "style_name");
×
4631
            if (styleName == NULL)
×
4632
              styleName = "default";
4633
            char *pszEncodedStyleName = msEncodeHTMLEntities(styleName);
×
4634
            if (strcasecmp(pszEncodedStyleName, tokens[i]) != 0) {
×
4635
              msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
4636
                                   "Style (%s) not defined on root layer.",
4637
                                   "msWMSLoadGetMapParams()", tokens[i]);
4638
              msFreeCharArray(tokens, n);
×
4639
              msFree(pszEncodedStyleName);
×
4640

4641
              return msWMSException(map, nVersion, "StyleNotDefined",
×
4642
                                    wms_exception_format);
4643
            }
4644
            msFree(pszEncodedStyleName);
×
4645

4646
            mapLayerNameToStyleNames[map->name].push_back(styleName);
×
4647
          }
4648
        }
4649
      }
4650
    }
4651
    msFreeCharArray(tokens, n);
×
4652
  }
4653

4654
  std::map<int, std::vector<std::string>> mapLayerIndexToStyleNames;
4655

4656
  for (int i = 0; i < numentries; i++) {
985✔
4657
    if (strcasecmp(names[i], "QUERY_LAYERS") == 0) {
927✔
4658
      query_layer = 1; /* flag set if QUERY_LAYERS is the request */
4659

4660
      const auto querylayers = msStringSplit(values[i], ',');
58✔
4661
      if (querylayers.empty()) {
58✔
4662
        msSetErrorWithStatus(
×
4663
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4664
            "At least one layer name required in QUERY_LAYERS.",
4665
            "msWMSFeatureInfo()");
4666
        return msWMSException(map, nVersion, "LayerNotDefined",
×
4667
                              wms_exception_format);
4668
      }
4669

4670
      for (int j = 0; j < map->numlayers; j++) {
302✔
4671
        /* Force all layers OFF by default */
4672
        GET_LAYER(map, j)->status = MS_OFF;
244✔
4673
      }
4674

4675
      /* Special case for root layer */
4676
      const char *rootlayer_name =
4677
          msOWSLookupMetadata(&(map->web.metadata), "MO", "rootlayer_name");
58✔
4678
      if (!rootlayer_name)
58✔
4679
        rootlayer_name = map->name;
58✔
4680
      if (rootlayer_name && msStringInArray(rootlayer_name, querylayers)) {
58✔
4681
        for (int j = 0; j < map->numlayers; j++) {
61✔
4682
          layerObj *layer = GET_LAYER(map, j);
54✔
4683
          if (msIsLayerQueryable(layer) &&
92✔
4684
              msIntegerInArray(layer->index, ows_request->enabled_layers,
38✔
4685
                               ows_request->numlayers)) {
4686
            if (layer->connectiontype == MS_WMS) {
38✔
4687
              wms_layer = MS_TRUE;
4688
              wms_connection = layer->connection;
×
4689
            }
4690

4691
            mapLayerIndexToStyleNames[j] =
38✔
4692
                mapLayerNameToStyleNames[rootlayer_name];
76✔
4693

4694
            numlayers_found++;
38✔
4695
            layer->status = MS_ON;
38✔
4696
          }
4697
        }
4698
      }
4699

4700
      for (int j = 0; j < map->numlayers; j++) {
302✔
4701
        layerObj *layer = GET_LAYER(map, j);
244✔
4702
        if (!msIsLayerQueryable(layer))
244✔
4703
          continue;
60✔
4704
        for (const auto &wmslayer : querylayers) {
373✔
4705
          if (((layer->name &&
378✔
4706
                strcasecmp(layer->name, wmslayer.c_str()) == 0) ||
189✔
4707
               (layer->group &&
140✔
4708
                strcasecmp(layer->group, wmslayer.c_str()) == 0) ||
5✔
4709
               ((numNestedGroups[j] > 0) &&
225✔
4710
                msStringInArray(wmslayer.c_str(), nestedGroups[j],
85✔
4711
                                numNestedGroups[j]))) &&
251✔
4712
              (msIntegerInArray(layer->index, ows_request->enabled_layers,
62✔
4713
                                ows_request->numlayers))) {
4714

4715
            if (layer->connectiontype == MS_WMS) {
62✔
4716
              wms_layer = MS_TRUE;
4717
              wms_connection = layer->connection;
8✔
4718
            }
4719

4720
            mapLayerIndexToStyleNames[j] = mapLayerNameToStyleNames[wmslayer];
62✔
4721

4722
            numlayers_found++;
62✔
4723
            layer->status = MS_ON;
62✔
4724
          }
4725
        }
4726
      }
4727
    } else if (strcasecmp(names[i], "INFO_FORMAT") == 0) {
927✔
4728
      if (values[i] && strlen(values[i]) > 0) {
54✔
4729
        info_format = values[i];
4730
        format_found = MS_TRUE;
4731
      }
4732
    } else if (strcasecmp(names[i], "FEATURE_COUNT") == 0)
815✔
4733
      feature_count = atoi(values[i]);
20✔
4734
    else if (strcasecmp(names[i], "X") == 0 || strcasecmp(names[i], "I") == 0)
795✔
4735
      point.x = atof(values[i]);
58✔
4736
    else if (strcasecmp(names[i], "Y") == 0 || strcasecmp(names[i], "J") == 0)
737✔
4737
      point.y = atof(values[i]);
58✔
4738
    else if (strcasecmp(names[i], "RADIUS") == 0) {
679✔
4739
      /* RADIUS in pixels. */
4740
      /* This is not part of the spec, but some servers such as cubeserv */
4741
      /* support it as a vendor-specific feature. */
4742
      /* It's easy for MapServer to handle this so let's do it! */
4743

4744
      /* Special RADIUS value that changes the query into a bbox query */
4745
      /* based on the bbox in the request parameters. */
4746
      if (strcasecmp(values[i], "BBOX") == 0) {
11✔
4747
        use_bbox = MS_TRUE;
4748
      } else {
4749
        int j;
4750
        for (j = 0; j < map->numlayers; j++) {
62✔
4751
          GET_LAYER(map, j)->tolerance = atoi(values[i]);
51✔
4752
          GET_LAYER(map, j)->toleranceunits = MS_PIXELS;
51✔
4753
        }
4754
      }
4755
    }
4756
  }
4757

4758
  /* free the stuff used for nested layers */
4759
  for (int i = 0; i < map->numlayers; i++) {
302✔
4760
    if (numNestedGroups[i] > 0) {
244✔
4761
      msFreeCharArray(nestedGroups[i], numNestedGroups[i]);
106✔
4762
    }
4763
  }
4764
  free(nestedGroups);
58✔
4765
  free(numNestedGroups);
58✔
4766
  free(isUsedInNestedGroup);
58✔
4767

4768
  if (numlayers_found == 0) {
58✔
4769
    if (query_layer) {
1✔
4770
      msSetErrorWithStatus(
1✔
4771
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4772
          "Layer(s) specified in QUERY_LAYERS parameter is not offered "
4773
          "by the service instance.",
4774
          "msWMSFeatureInfo()");
4775
      return msWMSException(map, nVersion, "LayerNotDefined",
1✔
4776
                            wms_exception_format);
4777
    } else {
4778
      msSetErrorWithStatus(
×
4779
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4780
          "Required QUERY_LAYERS parameter missing for getFeatureInfo.",
4781
          "msWMSFeatureInfo()");
4782
      return msWMSException(map, nVersion, "LayerNotDefined",
×
4783
                            wms_exception_format);
4784
    }
4785
  }
4786

4787
  /*make sure to initialize the map scale so that layers that are scale
4788
    dependent are respected for the query*/
4789
  msCalculateScale(map->extent, map->units, map->width, map->height,
57✔
4790
                   map->resolution, &map->scaledenom);
4791

4792
  /* -------------------------------------------------------------------- */
4793
  /*      check if all layers selected are queryable. If not send an      */
4794
  /*      exception.                                                      */
4795
  /* -------------------------------------------------------------------- */
4796

4797
  /* If a layer of type WMS was found... all layers have to be of that type and
4798
   * with the same connection */
4799
  for (int i = 0; i < map->numlayers; i++) {
295✔
4800
    if (GET_LAYER(map, i)->status == MS_ON) {
238✔
4801
      if (wms_layer == MS_TRUE) {
97✔
4802
        if ((GET_LAYER(map, i)->connectiontype != MS_WMS) ||
8✔
4803
            (strcasecmp(wms_connection, GET_LAYER(map, i)->connection) != 0)) {
8✔
4804
          msSetErrorWithStatus(
×
4805
              MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4806
              "Requested WMS layer(s) are not queryable: type or "
4807
              "connection differ",
4808
              "msWMSFeatureInfo()");
4809
          return msWMSException(map, nVersion, "LayerNotQueryable",
×
4810
                                wms_exception_format);
4811
        }
4812
        ++numOWSLayers;
8✔
4813
      }
4814
    }
4815
  }
4816

4817
  /* It's a valid Cascading WMS GetFeatureInfo request */
4818
  if (wms_layer)
57✔
4819
    return msWMSLayerExecuteRequest(map, numOWSLayers, point.x, point.y,
8✔
4820
                                    feature_count, info_format,
4821
                                    WMS_GETFEATUREINFO);
4822
  if (use_bbox == MS_FALSE) {
49✔
4823

4824
    if (point.x == -1.0 || point.y == -1.0) {
49✔
4825
      if (nVersion >= OWS_1_3_0)
×
4826
        msSetErrorWithStatus(
×
4827
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4828
            "Required I/J parameters missing for getFeatureInfo.",
4829
            "msWMSFeatureInfo()");
4830
      else
4831
        msSetErrorWithStatus(
×
4832
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4833
            "Required X/Y parameters missing for getFeatureInfo.",
4834
            "msWMSFeatureInfo()");
4835
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4836
    }
4837

4838
    /*wms1.3.0: check if the points are valid*/
4839
    if (nVersion >= OWS_1_3_0) {
49✔
4840
      if (point.x > map->width || point.y > map->height) {
28✔
4841
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
4842
                             "Invalid I/J values", "msWMSFeatureInfo()");
4843
        return msWMSException(map, nVersion, "InvalidPoint",
×
4844
                              wms_exception_format);
4845
      }
4846
    }
4847
  }
4848

4849
  /*validate the INFO_FORMAT*/
4850
  valid_format = MS_FALSE;
4851
  format_list = msOWSLookupMetadata(&(map->web.metadata), "M",
49✔
4852
                                    "getfeatureinfo_formatlist");
4853
  /*feature_info_mime_type deprecated for MapServer 6.0*/
4854
  if (!format_list)
49✔
4855
    format_list = msOWSLookupMetadata(&(map->web.metadata), "MO",
41✔
4856
                                      "feature_info_mime_type");
4857
  if (format_list) {
41✔
4858
    /*can not really validate if it is a valid output format
4859
      since old way of using template with web->header/footer and
4860
      layer templates need to still be supported.
4861
      We can only validate if it was part of the format list*/
4862
    if (strcasestr(format_list, info_format))
14✔
4863
      valid_format = MS_TRUE;
4864
  }
4865
  /*check to see if the format passed is text/plain or GML and if is
4866
    defined in the formatlist. If that is the case, It is a valid format*/
4867
  bool bMimeResponse = strcasecmp(info_format, "MIME") == 0 ||
49✔
4868
                       strcasecmp(info_format, "text/plain") == 0;
45✔
4869
  const bool bGMLResponse =
4870
      strncasecmp(info_format, "GML", 3) == 0 ||
49✔
4871
      strcasecmp(info_format, "application/vnd.ogc.gml") == 0;
48✔
4872
  if (bMimeResponse || bGMLResponse)
49✔
4873
    valid_format = MS_TRUE;
4874

4875
  /*last case: if the info_format is not part of the request, it defaults to
4876
   * MIME*/
4877
  if (!valid_format && format_found == MS_FALSE) {
49✔
4878
    bMimeResponse = true, valid_format = MS_TRUE;
4879
  }
4880

4881
  if (!valid_format) {
49✔
4882
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
4883
                         "Unsupported INFO_FORMAT value (%s).",
4884
                         "msWMSFeatureInfo()", info_format);
4885
    if (nVersion >= OWS_1_3_0)
×
4886
      return msWMSException(map, nVersion, "InvalidFormat",
×
4887
                            wms_exception_format);
4888
    else
4889
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4890
  }
4891

4892
  if (use_bbox == MS_FALSE) {
49✔
4893

4894
    /* Perform the actual query */
4895
    const double cellx =
49✔
4896
        MS_CELLSIZE(map->extent.minx, map->extent.maxx,
49✔
4897
                    map->width); /* note: don't adjust extent, WMS assumes
4898
                                    incoming extent is correct */
4899
    const double celly =
49✔
4900
        MS_CELLSIZE(map->extent.miny, map->extent.maxy, map->height);
49✔
4901

4902
    point.x = MS_IMAGE2MAP_X(point.x, map->extent.minx, cellx);
49✔
4903
    point.y = MS_IMAGE2MAP_Y(point.y, map->extent.maxy, celly);
49✔
4904

4905
    /* WMS 1.3.0 states that feature_count is *per layer*.
4906
     * Its value is a positive integer, if omitted then the default is 1
4907
     */
4908
    if (feature_count < 1)
49✔
4909
      feature_count = 1;
4910

4911
    map->query.type = MS_QUERY_BY_POINT;
49✔
4912
    map->query.mode =
49✔
4913
        (feature_count == 1 ? MS_QUERY_SINGLE : MS_QUERY_MULTIPLE);
49✔
4914
    map->query.layer = -1;
49✔
4915
    map->query.point = point;
49✔
4916
    map->query.buffer = 0;
49✔
4917
    map->query.maxresults = feature_count;
49✔
4918
    map->query.getFeatureInfo->mapLayerIndexToStyleNames =
49✔
4919
        std::move(mapLayerIndexToStyleNames);
4920

4921
    if (msQueryByPoint(map) != MS_SUCCESS)
49✔
4922
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4923

4924
  } else { /* use_bbox == MS_TRUE */
4925
    map->query.type = MS_QUERY_BY_RECT;
×
4926
    map->query.mode = MS_QUERY_MULTIPLE;
×
4927
    map->query.layer = -1;
×
4928
    map->query.rect = map->extent;
×
4929
    map->query.buffer = 0;
×
4930
    map->query.maxresults = feature_count;
×
4931
    if (msQueryByRect(map) != MS_SUCCESS)
×
4932
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4933
  }
4934

4935
  /* Generate response */
4936
  if (bMimeResponse) {
49✔
4937

4938
    /* MIME response... we're free to use any valid MIME type */
4939
    int numresults = 0;
4940

4941
    msIO_setHeader("Content-Type", "text/plain; charset=UTF-8");
7✔
4942
    msIO_sendHeaders();
7✔
4943
    msIO_printf("GetFeatureInfo results:\n");
7✔
4944

4945
    numresults = msDumpResult(map, nVersion, wms_exception_format);
7✔
4946

4947
    if (numresults == 0)
7✔
4948
      msIO_printf("\n  Search returned no results.\n");
1✔
4949

4950
  } else if (bGMLResponse) {
42✔
4951

4952
    if (nVersion == OWS_1_0_0)
33✔
4953
      msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
×
4954
    else /* 1.1.0 and later */
4955
      msIO_setHeader("Content-Type", "application/vnd.ogc.gml; charset=UTF-8");
33✔
4956
    msIO_sendHeaders();
33✔
4957
    msGMLWriteQuery(map, NULL, "MGO"); /* default is stdout */
33✔
4958

4959
  } else {
4960
    mapservObj *msObj;
4961

4962
    char **translated_names, **translated_values;
4963
    int translated_numentries;
4964
    msObj = msAllocMapServObj();
9✔
4965

4966
    /* Translate some vars from WMS to mapserv */
4967
    msTranslateWMS2Mapserv((const char **)names, (const char **)values,
9✔
4968
                           numentries, &translated_names, &translated_values,
4969
                           &translated_numentries);
4970

4971
    msObj->map = map;
9✔
4972
    msFreeCharArray(msObj->request->ParamNames, msObj->request->NumParams);
9✔
4973
    msFreeCharArray(msObj->request->ParamValues, msObj->request->NumParams);
9✔
4974
    msObj->request->ParamNames = translated_names;
9✔
4975
    msObj->request->ParamValues = translated_values;
9✔
4976
    msObj->Mode = QUERY;
9✔
4977
    msObj->request->NumParams = translated_numentries;
9✔
4978
    msObj->mappnt.x = point.x;
9✔
4979
    msObj->mappnt.y = point.y;
9✔
4980

4981
    bool hasResults = false;
4982
    for (int i = 0; i < map->numlayers; i++) {
13✔
4983
      layerObj *lp = (GET_LAYER(map, i));
11✔
4984

4985
      if (lp->status == MS_ON && lp->resultcache &&
11✔
4986
          lp->resultcache->numresults != 0) {
8✔
4987
        hasResults = true;
4988
        break;
4989
      }
4990
    }
4991

4992
    if (!hasResults && msObj->map->web.empty) {
9✔
4993
      if (msReturnURL(msObj, msObj->map->web.empty, BROWSE) != MS_SUCCESS)
1✔
4994
        return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4995
    } else if (msReturnTemplateQuery(msObj, (char *)info_format, NULL) !=
8✔
4996
               MS_SUCCESS)
4997
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4998

4999
    /* We don't want to free the map since it */
5000
    /* belongs to the caller, set it to NULL before freeing the mapservObj */
5001
    msObj->map = NULL;
9✔
5002

5003
    msFreeMapServObj(msObj);
9✔
5004
  }
5005

5006
  return (MS_SUCCESS);
5007
}
58✔
5008

5009
/*
5010
** msWMSDescribeLayer()
5011
*/
5012
static int msWMSDescribeLayer(mapObj *map, int nVersion, char **names,
19✔
5013
                              char **values, int numentries,
5014
                              const char *wms_exception_format) {
5015
  std::vector<std::string> wmslayers;
5016
  const char *version = NULL;
5017
  const char *sld_version = NULL;
5018

5019
  for (int i = 0; i < numentries; i++) {
130✔
5020
    if (strcasecmp(names[i], "LAYERS") == 0) {
111✔
5021
      wmslayers = msStringSplit(values[i], ',');
19✔
5022
    }
5023
    if (strcasecmp(names[i], "VERSION") == 0) {
111✔
5024
      version = values[i];
19✔
5025
    }
5026
    if (strcasecmp(names[i], "SLD_VERSION") == 0) {
111✔
5027
      sld_version = values[i];
16✔
5028
    }
5029
  }
5030

5031
  if (nVersion >= OWS_1_3_0 && sld_version == NULL) {
19✔
5032
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5033
                         "Missing required parameter SLD_VERSION",
5034
                         "DescribeLayer()");
5035
    return msWMSException(map, nVersion, "MissingParameterValue",
×
5036
                          wms_exception_format);
5037
  }
5038
  if (nVersion >= OWS_1_3_0 && strcasecmp(sld_version, "1.1.0") != 0) {
19✔
5039
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5040
                         "SLD_VERSION must be 1.1.0", "DescribeLayer()");
5041
    return msWMSException(map, nVersion, "InvalidParameterValue",
×
5042
                          wms_exception_format);
5043
  }
5044

5045
  msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
19✔
5046
  msIO_sendHeaders();
19✔
5047

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

5050
  {
5051
    char *schemalocation = msEncodeHTMLEntities(msOWSGetSchemasLocation(map));
19✔
5052
    if (nVersion < OWS_1_3_0) {
19✔
5053

5054
      msIO_printf("<!DOCTYPE WMS_DescribeLayerResponse SYSTEM "
3✔
5055
                  "\"%s/wms/1.1.1/WMS_DescribeLayerResponse.dtd\">\n",
5056
                  schemalocation);
5057

5058
      msIO_printf("<WMS_DescribeLayerResponse version=\"%s\" >\n", version);
3✔
5059
    } else {
5060
      msIO_printf("<DescribeLayerResponse xmlns=\"http://www.opengis.net/sld\" "
16✔
5061
                  "xmlns:ows=\"http://www.opengis.net/ows\" "
5062
                  "xmlns:se=\"http://www.opengis.net/se\" "
5063
                  "xmlns:wfs=\"http://www.opengis.net/wfs\" "
5064
                  "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
5065
                  "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
5066
                  "xsi:schemaLocation=\"http://www.opengis.net/sld "
5067
                  "%s/sld/1.1.0/DescribeLayer.xsd\">\n",
5068
                  schemalocation);
5069
      msIO_printf("<Version>%s</Version>\n", sld_version);
16✔
5070
    }
5071
    free(schemalocation);
19✔
5072
  }
5073

5074
  /* check if map-level metadata wfs(wcs)_onlineresource is available */
5075
  const char *pszOnlineResMapWFS =
5076
      msOWSLookupMetadata(&(map->web.metadata), "FO", "onlineresource");
19✔
5077
  if (pszOnlineResMapWFS && strlen(pszOnlineResMapWFS) == 0)
19✔
5078
    pszOnlineResMapWFS = NULL;
5079

5080
  const char *pszOnlineResMapWCS =
5081
      msOWSLookupMetadata(&(map->web.metadata), "CO", "onlineresource");
19✔
5082
  if (pszOnlineResMapWCS && strlen(pszOnlineResMapWCS) == 0)
19✔
5083
    pszOnlineResMapWCS = NULL;
5084

5085
  char ***nestedGroups =
5086
      (char ***)msSmallCalloc(map->numlayers, sizeof(char **));
19✔
5087
  int *numNestedGroups = (int *)msSmallCalloc(map->numlayers, sizeof(int));
19✔
5088
  int *isUsedInNestedGroup = (int *)msSmallCalloc(map->numlayers, sizeof(int));
19✔
5089
  msWMSPrepareNestedGroups(map, nVersion, nestedGroups, numNestedGroups,
19✔
5090
                           isUsedInNestedGroup);
5091

5092
  for (const auto &wmslayer : wmslayers) {
38✔
5093
    for (int k = 0; k < map->numlayers; k++) {
128✔
5094
      layerObj *lp = GET_LAYER(map, k);
109✔
5095

5096
      if ((map->name && strcasecmp(map->name, wmslayer.c_str()) == 0) ||
109✔
5097
          (lp->name && strcasecmp(lp->name, wmslayer.c_str()) == 0) ||
88✔
5098
          (lp->group && strcasecmp(lp->group, wmslayer.c_str()) == 0) ||
183✔
5099
          ((numNestedGroups[k] > 0) &&
115✔
5100
           msStringInArray(wmslayer.c_str(), nestedGroups[k],
45✔
5101
                           numNestedGroups[k]))) {
5102
        /* Look for a WFS onlineresouce at the layer level and then at
5103
         * the map level.
5104
         */
5105
        const char *pszOnlineResLyrWFS =
5106
            msOWSLookupMetadata(&(lp->metadata), "FO", "onlineresource");
50✔
5107
        const char *pszOnlineResLyrWCS =
5108
            msOWSLookupMetadata(&(lp->metadata), "CO", "onlineresource");
50✔
5109
        if (pszOnlineResLyrWFS == NULL || strlen(pszOnlineResLyrWFS) == 0)
50✔
5110
          pszOnlineResLyrWFS = pszOnlineResMapWFS;
5111

5112
        if (pszOnlineResLyrWCS == NULL || strlen(pszOnlineResLyrWCS) == 0)
50✔
5113
          pszOnlineResLyrWCS = pszOnlineResMapWCS;
5114

5115
        if (pszOnlineResLyrWFS &&
50✔
5116
            (lp->type == MS_LAYER_POINT || lp->type == MS_LAYER_LINE ||
1✔
5117
             lp->type == MS_LAYER_POLYGON)) {
5118
          char *pszOnlineResEncoded = msEncodeHTMLEntities(pszOnlineResLyrWFS);
1✔
5119
          char *pszLayerName = msEncodeHTMLEntities(lp->name);
1✔
5120

5121
          if (nVersion < OWS_1_3_0) {
1✔
5122
            msIO_printf("<LayerDescription name=\"%s\" wfs=\"%s\" "
1✔
5123
                        "owsType=\"WFS\" owsURL=\"%s\">\n",
5124
                        pszLayerName, pszOnlineResEncoded, pszOnlineResEncoded);
5125
            msIO_printf("<Query typeName=\"%s\" />\n", pszLayerName);
1✔
5126
            msIO_printf("</LayerDescription>\n");
1✔
5127
          } else { /*wms 1.3.0*/
5128
            msIO_printf("  <LayerDescription>\n");
×
5129
            msIO_printf("    <owsType>wfs</owsType>\n");
×
5130
            msIO_printf("    <se:OnlineResource xlink:type=\"simple\" "
×
5131
                        "xlink:href=\"%s\"/>\n",
5132
                        pszOnlineResEncoded);
5133
            msIO_printf("    <TypeName>\n");
×
5134
            msIO_printf("      <se:FeatureTypeName>%s</se:FeatureTypeName>\n",
×
5135
                        pszLayerName);
5136
            msIO_printf("    </TypeName>\n");
×
5137
            msIO_printf("  </LayerDescription>\n");
×
5138
          }
5139

5140
          msFree(pszOnlineResEncoded);
1✔
5141
          msFree(pszLayerName);
1✔
5142
        } else if (pszOnlineResLyrWCS && lp->type == MS_LAYER_RASTER &&
50✔
5143
                   lp->connectiontype != MS_WMS) {
×
5144
          char *pszOnlineResEncoded = msEncodeHTMLEntities(pszOnlineResLyrWCS);
×
5145
          char *pszLayerName = msEncodeHTMLEntities(lp->name);
×
5146

5147
          if (nVersion < OWS_1_3_0) {
×
5148
            msIO_printf("<LayerDescription name=\"%s\"  owsType=\"WCS\" "
×
5149
                        "owsURL=\"%s\">\n",
5150
                        pszLayerName, pszOnlineResEncoded);
5151
            msIO_printf("<Query typeName=\"%s\" />\n", pszLayerName);
×
5152
            msIO_printf("</LayerDescription>\n");
×
5153
          } else {
5154
            msIO_printf("  <LayerDescription>\n");
×
5155
            msIO_printf("    <owsType>wcs</owsType>\n");
×
5156
            msIO_printf("    <se:OnlineResource xlink:type=\"simple\" "
×
5157
                        "xlink:href=\"%s\"/>\n",
5158
                        pszOnlineResEncoded);
5159
            msIO_printf("    <TypeName>\n");
×
5160
            msIO_printf("      <se:CoverageTypeName>%s</se:CoverageTypeName>\n",
×
5161
                        pszLayerName);
5162
            msIO_printf("    </TypeName>\n");
×
5163
            msIO_printf("  </LayerDescription>\n");
×
5164
          }
5165
          msFree(pszOnlineResEncoded);
×
5166
          msFree(pszLayerName);
×
5167
        } else {
×
5168
          char *pszLayerName = msEncodeHTMLEntities(lp->name);
49✔
5169

5170
          if (nVersion < OWS_1_3_0)
49✔
5171
            msIO_printf("<LayerDescription name=\"%s\"></LayerDescription>\n",
2✔
5172
                        pszLayerName);
5173
          else { /*wms 1.3.0*/
5174
            msIO_printf("  <LayerDescription>\n");
47✔
5175
            /*need to have a owstype for the DescribeLayer to be valid*/
5176
            if (lp->type == MS_LAYER_RASTER && lp->connectiontype != MS_WMS)
47✔
5177
              msIO_printf("    <owsType>wcs</owsType>\n");
×
5178
            else
5179
              msIO_printf("    <owsType>wfs</owsType>\n");
47✔
5180

5181
            msIO_printf("    <se:OnlineResource xlink:type=\"simple\"/>\n");
47✔
5182
            msIO_printf("    <TypeName>\n");
47✔
5183
            if (lp->type == MS_LAYER_RASTER && lp->connectiontype != MS_WMS)
47✔
5184
              msIO_printf(
×
5185
                  "      <se:CoverageTypeName>%s</se:CoverageTypeName>\n",
5186
                  pszLayerName);
5187
            else
5188
              msIO_printf("      <se:FeatureTypeName>%s</se:FeatureTypeName>\n",
47✔
5189
                          pszLayerName);
5190
            msIO_printf("    </TypeName>\n");
47✔
5191
            msIO_printf("  </LayerDescription>\n");
47✔
5192
          }
5193

5194
          msFree(pszLayerName);
49✔
5195
        }
5196
        /* break; */
5197
      }
5198
    }
5199
  }
5200

5201
  if (nVersion < OWS_1_3_0)
19✔
5202
    msIO_printf("</WMS_DescribeLayerResponse>\n");
3✔
5203
  else
5204
    msIO_printf("</DescribeLayerResponse>\n");
16✔
5205

5206
  /* free the stuff used for nested layers */
5207
  for (int i = 0; i < map->numlayers; i++) {
128✔
5208
    if (numNestedGroups[i] > 0) {
109✔
5209
      msFreeCharArray(nestedGroups[i], numNestedGroups[i]);
58✔
5210
    }
5211
  }
5212
  free(nestedGroups);
19✔
5213
  free(numNestedGroups);
19✔
5214
  free(isUsedInNestedGroup);
19✔
5215

5216
  return (MS_SUCCESS);
19✔
5217
}
19✔
5218

5219
/*
5220
** msWMSGetLegendGraphic()
5221
*/
5222
static int msWMSLegendGraphic(mapObj *map, int nVersion, char **names,
46✔
5223
                              char **values, int numentries,
5224
                              const char *wms_exception_format,
5225
                              owsRequestObj *ows_request,
5226
                              map_hittest *hittest) {
5227
  const char *pszLayer = NULL;
5228
  const char *pszFormat = NULL;
5229
  const char *psRule = NULL;
5230
  const char *psScale = NULL;
5231
  int iLayerIndex = -1;
5232
  outputFormatObj *psFormat = NULL;
5233
  imageObj *img = NULL;
5234
  int nWidth = -1, nHeight = -1;
5235
  const char *pszStyle = NULL;
5236
  const char *sld_version = NULL;
5237
  int wms_layer = MS_FALSE;
5238
  const char *sldenabled = NULL;
5239
  const char *format_list = NULL;
5240
  int nLayers = 0;
5241

5242
  if (!hittest) {
46✔
5243
    /* we can skip a lot of testing if we already have a hittest, as it has
5244
     * already been done in the hittesting phase */
5245

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

5248
    if (sldenabled == NULL)
34✔
5249
      sldenabled = "true";
5250

5251
    for (int i = 0; i < numentries; i++) {
283✔
5252
      if (strcasecmp(names[i], "LAYER") == 0) {
249✔
5253
        pszLayer = values[i];
34✔
5254
      } else if (strcasecmp(names[i], "WIDTH") == 0)
215✔
5255
        nWidth = atoi(values[i]);
×
5256
      else if (strcasecmp(names[i], "HEIGHT") == 0)
215✔
5257
        nHeight = atoi(values[i]);
×
5258
      else if (strcasecmp(names[i], "FORMAT") == 0)
215✔
5259
        pszFormat = values[i];
34✔
5260
      else if (strcasecmp(names[i], "SCALE") == 0)
181✔
5261
        psScale = values[i];
×
5262

5263
      /* -------------------------------------------------------------------- */
5264
      /*      SLD support :                                                   */
5265
      /*        - check if the SLD parameter is there. it is supposed to      */
5266
      /*      refer a valid URL containing an SLD document.                   */
5267
      /*        - check the SLD_BODY parameter that should contain the SLD    */
5268
      /*      xml string.                                                     */
5269
      /* -------------------------------------------------------------------- */
5270
      else if (strcasecmp(names[i], "SLD") == 0 && values[i] &&
181✔
5271
               strlen(values[i]) > 0 && strcasecmp(sldenabled, "true") == 0)
×
5272
        msSLDApplySLDURL(map, values[i], -1, NULL, NULL);
×
5273
      else if (strcasecmp(names[i], "SLD_BODY") == 0 && values[i] &&
181✔
5274
               strlen(values[i]) > 0 && strcasecmp(sldenabled, "true") == 0)
3✔
5275
        msSLDApplySLD(map, values[i], -1, NULL, NULL);
1✔
5276
      else if (strcasecmp(names[i], "RULE") == 0)
180✔
5277
        psRule = values[i];
×
5278
      else if (strcasecmp(names[i], "STYLE") == 0)
180✔
5279
        pszStyle = values[i];
11✔
5280

5281
      /* -------------------------------------------------------------------- */
5282
      /*      SLD support:                                                    */
5283
      /*        - because the request parameter "sld_version" is required in  */
5284
      /*          in WMS 1.3.0, it will be set regardless of OGR support.     */
5285
      /* -------------------------------------------------------------------- */
5286
      else if (strcasecmp(names[i], "SLD_VERSION") == 0)
169✔
5287
        sld_version = values[i];
17✔
5288
    }
5289

5290
    if (!pszLayer) {
34✔
5291
      msSetErrorWithStatus(
×
5292
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5293
          "Mandatory LAYER parameter missing in GetLegendGraphic request.",
5294
          "msWMSGetLegendGraphic()");
5295
      return msWMSException(map, nVersion, "LayerNotDefined",
×
5296
                            wms_exception_format);
×
5297
    }
5298
    if (!pszFormat) {
34✔
5299
      msSetErrorWithStatus(
×
5300
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5301
          "Mandatory FORMAT parameter missing in GetLegendGraphic request.",
5302
          "msWMSGetLegendGraphic()");
5303
      return msWMSException(map, nVersion, "InvalidFormat",
×
5304
                            wms_exception_format);
×
5305
    }
5306

5307
    if (nVersion >= OWS_1_3_0 && sld_version == NULL) {
34✔
5308
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5309
                           "Missing required parameter SLD_VERSION",
5310
                           "GetLegendGraphic()");
5311
      return msWMSException(map, nVersion, "MissingParameterValue",
×
5312
                            wms_exception_format);
×
5313
    }
5314
    if (nVersion >= OWS_1_3_0 && strcasecmp(sld_version, "1.1.0") != 0) {
34✔
5315
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5316
                           "SLD_VERSION must be 1.1.0", "GetLegendGraphic()");
5317
      return msWMSException(map, nVersion, "InvalidParameterValue",
×
5318
                            wms_exception_format);
×
5319
    }
5320

5321
    char ***nestedGroups =
5322
        (char ***)msSmallCalloc(map->numlayers, sizeof(char **));
34✔
5323
    int *numNestedGroups = (int *)msSmallCalloc(map->numlayers, sizeof(int));
34✔
5324
    int *isUsedInNestedGroup =
5325
        (int *)msSmallCalloc(map->numlayers, sizeof(int));
34✔
5326
    msWMSPrepareNestedGroups(map, nVersion, nestedGroups, numNestedGroups,
34✔
5327
                             isUsedInNestedGroup);
5328

5329
    /* check if layer name is valid. we check for layer's and group's name */
5330
    /* as well as wms_layer_group names */
5331
    for (int i = 0; i < map->numlayers; i++) {
279✔
5332
      layerObj *lp = GET_LAYER(map, i);
245✔
5333
      if (((map->name && strcasecmp(map->name, pszLayer) == 0) ||
245✔
5334
           (lp->name && strcasecmp(lp->name, pszLayer) == 0) ||
224✔
5335
           (lp->group && strcasecmp(lp->group, pszLayer) == 0) ||
196✔
5336
           ((numNestedGroups[i] > 0) &&
283✔
5337
            (msStringInArray(pszLayer, nestedGroups[i],
93✔
5338
                             numNestedGroups[i])))) &&
311✔
5339
          (msIntegerInArray(lp->index, ows_request->enabled_layers,
66✔
5340
                            ows_request->numlayers))) {
5341
        nLayers++;
66✔
5342
        lp->status = MS_ON;
66✔
5343
        iLayerIndex = i;
5344
        if (GET_LAYER(map, i)->connectiontype == MS_WMS) {
66✔
5345
          /* we do not cascade a wms layer if it contains at least
5346
           * one class with the property name set */
5347
          wms_layer = MS_TRUE;
5348
          for (int j = 0; j < lp->numclasses; j++) {
4✔
5349
            if (lp->_class[j]->name != NULL &&
×
5350
                strlen(lp->_class[j]->name) > 0) {
×
5351
              wms_layer = MS_FALSE;
5352
              break;
5353
            }
5354
          }
5355
        }
5356
      } else
5357
        lp->status = MS_OFF;
179✔
5358
    }
5359

5360
    /* free the stuff used for nested layers */
5361
    for (int i = 0; i < map->numlayers; i++) {
279✔
5362
      if (numNestedGroups[i] > 0) {
245✔
5363
        msFreeCharArray(nestedGroups[i], numNestedGroups[i]);
110✔
5364
      }
5365
    }
5366
    free(nestedGroups);
34✔
5367
    free(numNestedGroups);
34✔
5368
    free(isUsedInNestedGroup);
34✔
5369

5370
    if (nLayers == 0) {
34✔
5371
      msSetErrorWithStatus(
×
5372
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5373
          "Invalid layer given in the LAYER parameter. A layer might be disabled for \
5374
this request. Check wms/ows_enable_request settings.",
5375
          "msWMSGetLegendGraphic()");
5376
      return msWMSException(map, nVersion, "LayerNotDefined",
×
5377
                            wms_exception_format);
×
5378
    }
5379

5380
    /* if SCALE was provided in request, calculate an extent and use a default
5381
     * width and height */
5382
    if (psScale != NULL) {
34✔
5383
      double scale, cellsize;
5384

5385
      scale = atof(psScale);
5386
      map->width = 600;
×
5387
      map->height = 600;
×
5388

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

5391
      map->extent.maxx = cellsize * map->width / 2.0;
×
5392
      map->extent.maxy = cellsize * map->height / 2.0;
×
5393
      map->extent.minx = -map->extent.maxx;
×
5394
      map->extent.miny = -map->extent.maxy;
×
5395
    }
5396

5397
    /* It's a valid Cascading WMS GetLegendGraphic request */
5398
    if (wms_layer)
34✔
5399
      return msWMSLayerExecuteRequest(map, 1, 0, 0, 0, NULL,
4✔
5400
                                      WMS_GETLEGENDGRAPHIC);
4✔
5401

5402
    /*if STYLE is set, check if it is a valid style (valid = at least one
5403
    of the classes have a the group value equals to the style */
5404
    /*style is only validated when there is only one layer #3411*/
5405
    if (nLayers == 1 && pszStyle && strlen(pszStyle) > 0 &&
30✔
5406
        strcasecmp(pszStyle, "default") != 0) {
7✔
5407
      bool found = false;
5408
      for (int i = 0; i < GET_LAYER(map, iLayerIndex)->numclasses; i++) {
11✔
5409
        if (GET_LAYER(map, iLayerIndex)->_class[i]->group &&
11✔
5410
            strcasecmp(GET_LAYER(map, iLayerIndex)->_class[i]->group,
11✔
5411
                       pszStyle) == 0) {
5412
          found = true;
5413
          break;
5414
        }
5415
      }
5416

5417
      if (!found) {
7✔
5418
        msSetErrorWithStatus(
×
5419
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5420
            "style used in the STYLE parameter is not defined on the layer.",
5421
            "msWMSGetLegendGraphic()");
5422
        return msWMSException(map, nVersion, "StyleNotDefined",
×
5423
                              wms_exception_format);
×
5424
      } else {
5425
        msFree(GET_LAYER(map, iLayerIndex)->classgroup);
7✔
5426
        GET_LAYER(map, iLayerIndex)->classgroup = msStrdup(pszStyle);
7✔
5427
      }
5428
    }
5429
  } else {
5430
    /* extract the parameters we need */
5431
    for (int i = 0; i < numentries; i++) {
182✔
5432
      if (strcasecmp(names[i], "FORMAT") == 0)
170✔
5433
        pszFormat = values[i];
12✔
5434
      else if (strcasecmp(names[i], "RULE") == 0)
158✔
5435
        psRule = values[i];
×
5436
    }
5437
  }
5438
  /* validate format */
5439

5440
  /*check to see if a predefined list is given*/
5441
  format_list = msOWSLookupMetadata(&(map->web.metadata), "M",
42✔
5442
                                    "getlegendgraphic_formatlist");
5443
  if (format_list) {
42✔
5444
    psFormat = msOwsIsOutputFormatValid(map, pszFormat, &(map->web.metadata),
×
5445
                                        "M", "getlegendgraphic_formatlist");
5446
    if (psFormat == NULL) {
×
5447
      msSetErrorWithStatus(MS_IMGERR, MS_HTTP_400_BAD_REQUEST,
×
5448
                           "Unsupported output format (%s).",
5449
                           "msWMSGetLegendGraphic()", pszFormat);
5450
      return msWMSException(map, nVersion, "InvalidFormat",
×
5451
                            wms_exception_format);
×
5452
    }
5453
  } else {
5454
    psFormat = msSelectOutputFormat(map, pszFormat);
42✔
5455
    if (psFormat == NULL || !MS_RENDERER_PLUGIN(psFormat))
42✔
5456
    /* msDrawLegend and msCreateLegendIcon both switch the alpha channel to gd
5457
     ** after creation, so they can be called here without going through
5458
     ** the msAlphaGD2AGG functions */
5459
    {
5460
      msSetErrorWithStatus(MS_IMGERR, MS_HTTP_400_BAD_REQUEST,
×
5461
                           "Unsupported output format (%s).",
5462
                           "msWMSGetLegendGraphic()", pszFormat);
5463
      return msWMSException(map, nVersion, "InvalidFormat",
×
5464
                            wms_exception_format);
×
5465
    }
5466
  }
5467
  msApplyOutputFormat(&(map->outputformat), psFormat, MS_NOOVERRIDE);
42✔
5468

5469
  if (psRule == NULL || nLayers > 1) {
42✔
5470
    if (psScale != NULL) {
42✔
5471
      /* Scale-dependent legend. map->scaledenom will be calculated in
5472
       * msDrawLegend */
5473
      img = msDrawLegend(map, MS_FALSE, NULL);
×
5474
    } else {
5475
      /* Scale-independent legend */
5476
      img = msDrawLegend(map, MS_TRUE, hittest);
42✔
5477
    }
5478
  } else {
5479
    /* RULE was specified. Get the class corresponding to the RULE */
5480
    /* (RULE = class->name) */
5481
    /* TBT FIXME? also check the map->scaledenom if multiple scale-dependant
5482
     * classes with same name */
5483

5484
    layerObj *lp = GET_LAYER(map, iLayerIndex);
×
5485
    int i;
5486
    for (i = 0; i < lp->numclasses; i++) {
×
5487
      if (lp->classgroup &&
×
5488
          (lp->_class[i]->group == NULL ||
×
5489
           strcasecmp(lp->_class[i]->group, lp->classgroup) != 0))
×
5490
        continue;
×
5491

5492
      if (lp->_class[i]->name && strlen(lp->_class[i]->name) > 0 &&
×
5493
          strcasecmp(lp->_class[i]->name, psRule) == 0)
×
5494
        break;
5495
    }
5496
    if (i < lp->numclasses) {
×
5497
      /* set the map legend parameters */
5498
      if (nWidth < 0) {
×
5499
        if (map->legend.keysizex > 0)
×
5500
          nWidth = map->legend.keysizex;
5501
        else
5502
          nWidth = 20; /* default values : this in not defined in the specs */
5503
      }
5504
      if (nHeight < 0) {
×
5505
        if (map->legend.keysizey > 0)
×
5506
          nHeight = map->legend.keysizey;
5507
        else
5508
          nHeight = 20;
5509
      }
5510

5511
      if (psScale != NULL) {
×
5512
        /* Scale-dependent legend. calculate map->scaledenom */
5513
        map->cellsize = msAdjustExtent(&(map->extent), map->width, map->height);
×
5514
        msCalculateScale(map->extent, map->units, map->width, map->height,
×
5515
                         map->resolution, &map->scaledenom);
5516
        img = msCreateLegendIcon(map, lp, lp->_class[i], nWidth, nHeight,
×
5517
                                 MS_FALSE);
5518
      } else {
5519
        /* Scale-independent legend */
5520
        img = msCreateLegendIcon(map, lp, lp->_class[i], nWidth, nHeight,
×
5521
                                 MS_TRUE);
5522
      }
5523
    }
5524
    if (img == NULL) {
×
5525
      msSetErrorWithStatus(MS_IMGERR, MS_HTTP_400_BAD_REQUEST,
×
5526
                           "Unavailable RULE (%s).", "msWMSGetLegendGraphic()",
5527
                           psRule);
5528
      return msWMSException(map, nVersion, "InvalidRule", wms_exception_format);
×
5529
    }
5530
  }
5531

5532
  if (img == NULL)
42✔
5533
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5534

5535
  msIO_setHeader("Content-Type", "%s", MS_IMAGE_MIME_TYPE(map->outputformat));
84✔
5536
  msIO_sendHeaders();
42✔
5537
  if (msSaveImage(map, img, NULL) != MS_SUCCESS)
42✔
5538
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5539

5540
  msFreeImage(img);
42✔
5541

5542
  return (MS_SUCCESS);
42✔
5543
}
5544

5545
/*
5546
** msWMSGetContentDependentLegend()
5547
*/
5548
static int msWMSGetContentDependentLegend(mapObj *map, int nVersion,
12✔
5549
                                          char **names, char **values,
5550
                                          int numentries,
5551
                                          const char *wms_exception_format,
5552
                                          owsRequestObj *ows_request) {
5553

5554
  /* turn off layer if WMS GetMap is not enabled */
5555
  for (int i = 0; i < map->numlayers; i++)
69✔
5556
    if (!msIntegerInArray(GET_LAYER(map, i)->index, ows_request->enabled_layers,
57✔
5557
                          ows_request->numlayers))
5558
      GET_LAYER(map, i)->status = MS_OFF;
×
5559

5560
  map_hittest hittest;
5561
  initMapHitTests(map, &hittest);
12✔
5562
  int status = msHitTestMap(map, &hittest);
12✔
5563
  if (status == MS_SUCCESS) {
12✔
5564
    status = msWMSLegendGraphic(map, nVersion, names, values, numentries,
12✔
5565
                                wms_exception_format, ows_request, &hittest);
5566
  }
5567
  freeMapHitTests(map, &hittest);
12✔
5568
  if (status != MS_SUCCESS) {
12✔
5569
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5570
  } else {
5571
    return MS_SUCCESS;
5572
  }
5573
}
5574

5575
/*
5576
** msWMSGetStyles() : return an SLD document for all layers that
5577
** have a status set to on or default.
5578
*/
5579
static int msWMSGetStyles(mapObj *map, int nVersion, char **names,
60✔
5580
                          char **values, int numentries,
5581
                          const char *wms_exception_format)
5582

5583
{
5584
  bool validlayer = false;
5585

5586
  char ***nestedGroups =
5587
      (char ***)msSmallCalloc(map->numlayers, sizeof(char **));
60✔
5588
  int *numNestedGroups = (int *)msSmallCalloc(map->numlayers, sizeof(int));
60✔
5589
  int *isUsedInNestedGroup = (int *)msSmallCalloc(map->numlayers, sizeof(int));
60✔
5590
  msWMSPrepareNestedGroups(map, nVersion, nestedGroups, numNestedGroups,
60✔
5591
                           isUsedInNestedGroup);
5592

5593
  const char *sldenabled =
5594
      msOWSLookupMetadata(&(map->web.metadata), "MO", "sld_enabled");
60✔
5595
  if (sldenabled == NULL)
60✔
5596
    sldenabled = "true";
5597

5598
  for (int i = 0; i < numentries; i++) {
367✔
5599
    /* getMap parameters */
5600
    if (strcasecmp(names[i], "LAYERS") == 0) {
307✔
5601
      const auto wmslayers = msStringSplit(values[i], ',');
60✔
5602
      if (wmslayers.empty()) {
60✔
5603
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5604
                             "At least one layer name required in LAYERS.",
5605
                             "msWMSGetStyles()");
5606
        return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5607
      }
5608
      for (int j = 0; j < map->numlayers; j++)
1,140✔
5609
        GET_LAYER(map, j)->status = MS_OFF;
1,080✔
5610

5611
      for (int k = 0; k < static_cast<int>(wmslayers.size()); k++) {
135✔
5612
        const auto &wmslayer = wmslayers[k];
75✔
5613
        for (int j = 0; j < map->numlayers; j++) {
1,199✔
5614
          if ((map->name && strcasecmp(map->name, wmslayer.c_str()) == 0) ||
1,124✔
5615
              (GET_LAYER(map, j)->name &&
1,103✔
5616
               strcasecmp(GET_LAYER(map, j)->name, wmslayer.c_str()) == 0) ||
1,103✔
5617
              (GET_LAYER(map, j)->group &&
1,033✔
5618
               strcasecmp(GET_LAYER(map, j)->group, wmslayer.c_str()) == 0) ||
1,135✔
5619
              ((numNestedGroups[j] > 0) &&
1,072✔
5620
               msStringInArray(wmslayer.c_str(), nestedGroups[j],
43✔
5621
                               numNestedGroups[j]))) {
5622
            GET_LAYER(map, j)->status = MS_ON;
106✔
5623
            validlayer = true;
5624
          }
5625
        }
5626
      }
5627
    }
60✔
5628

5629
    else if (strcasecmp(names[i], "SLD") == 0 && values[i] &&
247✔
5630
             strlen(values[i]) > 0 && strcasecmp(sldenabled, "true") == 0) {
×
5631
      msSLDApplySLDURL(map, values[i], -1, NULL, NULL);
×
5632
    }
5633

5634
    else if (strcasecmp(names[i], "SLD_BODY") == 0 && values[i] &&
247✔
5635
             strlen(values[i]) > 0 && strcasecmp(sldenabled, "true") == 0) {
6✔
5636
      msSLDApplySLD(map, values[i], -1, NULL, NULL);
6✔
5637
    }
5638
  }
5639

5640
  /* free the stuff used for nested layers */
5641
  for (int i = 0; i < map->numlayers; i++) {
1,140✔
5642
    if (numNestedGroups[i] > 0) {
1,080✔
5643
      msFreeCharArray(nestedGroups[i], numNestedGroups[i]);
55✔
5644
    }
5645
  }
5646
  free(nestedGroups);
60✔
5647
  free(numNestedGroups);
60✔
5648
  free(isUsedInNestedGroup);
60✔
5649

5650
  /* validate all layers given. If an invalid layer is sent, return an
5651
   * exception. */
5652
  if (!validlayer) {
60✔
5653
    msSetErrorWithStatus(
×
5654
        MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5655
        "Invalid layer(s) given in the LAYERS parameter. A layer might be disabled for \
5656
this request. Check wms/ows_enable_request settings.",
5657
        "msWMSGetStyles()");
5658
    return msWMSException(map, nVersion, "LayerNotDefined",
×
5659
                          wms_exception_format);
×
5660
  }
5661

5662
  char *sld = NULL;
5663
  if (nVersion <= OWS_1_1_1) {
60✔
5664
    msIO_setHeader("Content-Type",
4✔
5665
                   "application/vnd.ogc.sld+xml; charset=UTF-8");
5666
    msIO_sendHeaders();
4✔
5667
    sld = msSLDGenerateSLD(map, -1, "1.0.0");
4✔
5668
  } else {
5669
    /*for wms 1.3.0 generate a 1.1 sld*/
5670
    msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
56✔
5671
    msIO_sendHeaders();
56✔
5672
    sld = msSLDGenerateSLD(map, -1, "1.1.0");
56✔
5673
  }
5674
  if (sld) {
60✔
5675
    msIO_printf("%s\n", sld);
60✔
5676
    free(sld);
60✔
5677
  }
5678

5679
  return (MS_SUCCESS);
5680
}
5681

5682
int msWMSGetSchemaExtension(mapObj *map) {
2✔
5683
  char *schemalocation = msEncodeHTMLEntities(msOWSGetSchemasLocation(map));
2✔
5684

5685
  msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
2✔
5686
  msIO_sendHeaders();
2✔
5687

5688
  msIO_printf("<?xml version='1.0' encoding=\"UTF-8\"?>\n");
2✔
5689
  msIO_printf("<schema xmlns=\"http://www.w3.org/2001/XMLSchema\" "
2✔
5690
              "xmlns:wms=\"http://www.opengis.net/wms\" "
5691
              "xmlns:ms=\"http://mapserver.gis.umn.edu/mapserver\" "
5692
              "targetNamespace=\"http://mapserver.gis.umn.edu/mapserver\" "
5693
              "elementFormDefault=\"qualified\" version=\"1.0.0\">\n");
5694
  msIO_printf("  <import namespace=\"http://www.opengis.net/wms\" "
2✔
5695
              "schemaLocation=\"%s/wms/1.3.0/capabilities_1_3_0.xsd\"/>\n",
5696
              schemalocation);
5697
  msIO_printf("  <element name=\"GetStyles\" type=\"wms:OperationType\" "
2✔
5698
              "substitutionGroup=\"wms:_ExtendedOperation\"/>\n");
5699
  msIO_printf("</schema>");
2✔
5700

5701
  free(schemalocation);
2✔
5702

5703
  return (MS_SUCCESS);
2✔
5704
}
5705

5706
#endif /* USE_WMS_SVR */
5707

5708
/*
5709
** msWMSDispatch() is the entry point for WMS requests.
5710
** - If this is a valid request then it is processed and MS_SUCCESS is returned
5711
**   on success, or MS_FAILURE on failure.
5712
** - If this does not appear to be a valid WMS request then MS_DONE
5713
**   is returned and MapServer is expected to process this as a regular
5714
**   MapServer request.
5715
*/
5716
int msWMSDispatch(mapObj *map, cgiRequestObj *req, owsRequestObj *ows_request,
734✔
5717
                  int force_wms_mode) {
5718
#ifdef USE_WMS_SVR
5719
  int nVersion = OWS_VERSION_NOTSET;
5720
  const char *version = NULL, *request = NULL, *service = NULL, *format = NULL,
5721
             *updatesequence = NULL, *language = NULL;
5722
  const char *wms_exception_format = NULL;
5723

5724
  /*
5725
  ** Process Params common to all requests
5726
  */
5727
  /* VERSION (WMTVER in 1.0.0) and REQUEST must be present in a valid request */
5728
  for (int i = 0; i < req->NumParams; i++) {
8,407✔
5729
    if (strcasecmp(req->ParamNames[i], "VERSION") == 0)
7,673✔
5730
      version = req->ParamValues[i];
723✔
5731
    else if (strcasecmp(req->ParamNames[i], "WMTVER") == 0 && version == NULL)
6,950✔
5732
      version = req->ParamValues[i];
×
5733
    else if (strcasecmp(req->ParamNames[i], "UPDATESEQUENCE") == 0)
6,950✔
5734
      updatesequence = req->ParamValues[i];
6✔
5735
    else if (strcasecmp(req->ParamNames[i], "REQUEST") == 0)
6,944✔
5736
      request = req->ParamValues[i];
725✔
5737
    else if (strcasecmp(req->ParamNames[i], "EXCEPTIONS") == 0)
6,219✔
5738
      wms_exception_format = req->ParamValues[i];
57✔
5739
    else if (strcasecmp(req->ParamNames[i], "SERVICE") == 0)
6,162✔
5740
      service = req->ParamValues[i];
732✔
5741
    else if (strcasecmp(req->ParamNames[i], "FORMAT") == 0)
5,430✔
5742
      format = req->ParamValues[i];
567✔
5743
    else if (strcasecmp(req->ParamNames[i], "LANGUAGE") == 0 &&
4,873✔
5744
             msOWSLookupMetadata(&(map->web.metadata), "MO",
10✔
5745
                                 "inspire_capabilities"))
5746
      language = req->ParamValues[i];
10✔
5747
  }
5748

5749
  /* If SERVICE is specified then it MUST be "WMS" */
5750
  if (service != NULL && strcasecmp(service, "WMS") != 0)
734✔
5751
    return MS_DONE; /* Not a WMS request */
5752

5753
  nVersion = msOWSParseVersionString(version);
734✔
5754
  if (nVersion == OWS_VERSION_BADFORMAT) {
734✔
5755
    /* Invalid version format. msSetError() has been called by
5756
     * msOWSParseVersionString() and we return the error as an exception
5757
     */
5758
    return msWMSException(map, OWS_VERSION_NOTSET, NULL, wms_exception_format);
8✔
5759
  }
5760

5761
  /*
5762
  ** GetCapbilities request needs the service parameter defined as WMS:
5763
  see section 7.1.3.2 wms 1.1.1 specs for description.
5764
  */
5765
  if (request && service == NULL &&
726✔
5766
      (strcasecmp(request, "capabilities") == 0 ||
3✔
5767
       strcasecmp(request, "GetCapabilities") == 0) &&
3✔
5768
      (nVersion >= OWS_1_1_0 || nVersion == OWS_VERSION_NOTSET)) {
×
5769
    if (force_wms_mode) {
×
5770
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5771
                           "Required SERVICE parameter missing.",
5772
                           "msWMSDispatch");
5773
      return msWMSException(map, nVersion, "ServiceNotDefined",
×
5774
                            wms_exception_format);
×
5775
    } else
5776
      return MS_DONE;
5777
  }
5778

5779
  /*
5780
  ** Dispatch request... we should probably do some validation on VERSION here
5781
  ** vs the versions we actually support.
5782
  */
5783
  if (request && (strcasecmp(request, "capabilities") == 0 ||
726✔
5784
                  strcasecmp(request, "GetCapabilities") == 0)) {
725✔
5785
    const char *enable_request;
5786
    int globally_enabled, disabled = MS_FALSE;
66✔
5787

5788
    if (nVersion == OWS_VERSION_NOTSET) {
66✔
5789
      version = msOWSLookupMetadata(&(map->web.metadata), "M",
10✔
5790
                                    "getcapabilities_version");
5791
      if (version)
10✔
5792
        nVersion = msOWSParseVersionString(version);
×
5793
      else
5794
        nVersion =
5795
            OWS_1_3_0; /* VERSION is optional with getCapabilities only */
5796
    }
5797

5798
    if (msOWSMakeAllLayersUnique(map) != MS_SUCCESS)
66✔
5799
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5800

5801
    msOWSRequestLayersEnabled(map, "M", "GetCapabilities", ows_request);
66✔
5802

5803
    enable_request =
5804
        msOWSLookupMetadata(&map->web.metadata, "OM", "enable_request");
66✔
5805
    globally_enabled =
5806
        msOWSParseRequestMetadata(enable_request, "GetCapabilities", &disabled);
66✔
5807

5808
    if (ows_request->numlayers == 0 && !globally_enabled) {
66✔
5809
      msSetErrorWithStatus(
2✔
5810
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5811
          "WMS request not enabled. Check wms/ows_enable_request settings.",
5812
          "msWMSGetCapabilities()");
5813
      return msWMSException(map, nVersion, NULL, wms_exception_format);
2✔
5814
    }
5815
    msAcquireLock(TLOCK_WxS);
64✔
5816
    const int status =
5817
        msWMSGetCapabilities(map, nVersion, req, ows_request, updatesequence,
64✔
5818
                             wms_exception_format, language);
5819
    msReleaseLock(TLOCK_WxS);
64✔
5820
    return status;
64✔
5821
  } else if (request && (strcasecmp(request, "context") == 0 ||
659✔
5822
                         strcasecmp(request, "GetContext") == 0)) {
659✔
5823
    /* Return a context document with all layers in this mapfile
5824
     * This is not a standard WMS request.
5825
     * __TODO__ The real implementation should actually return only context
5826
     * info for selected layers in the LAYERS parameter.
5827
     */
5828
    const char *getcontext_enabled;
5829
    getcontext_enabled =
5830
        msOWSLookupMetadata(&(map->web.metadata), "MO", "getcontext_enabled");
×
5831

5832
    if (nVersion != OWS_VERSION_NOTSET) {
×
5833
      /* VERSION, if specified, is Map Context version, not WMS version */
5834
      /* Pass it via wms_context_version metadata */
5835
      char szVersion[OWS_VERSION_MAXLEN];
5836
      msInsertHashTable(&(map->web.metadata), "wms_context_version",
×
5837
                        msOWSGetVersionString(nVersion, szVersion));
5838
    }
5839
    /* Now set version to 1.1.1 for error handling purposes */
5840
    nVersion = OWS_1_1_1;
5841

5842
    if (getcontext_enabled == NULL || atoi(getcontext_enabled) == 0) {
×
5843
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5844
                           "GetContext not enabled on this server.",
5845
                           "msWMSDispatch()");
5846
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5847
    }
5848

5849
    if (msOWSMakeAllLayersUnique(map) != MS_SUCCESS)
×
5850
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5851

5852
    msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
×
5853
    msIO_sendHeaders();
×
5854

5855
    if (msWriteMapContext(map, stdout) != MS_SUCCESS)
×
5856
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5857
    /* Request completed */
5858
    return MS_SUCCESS;
5859
  } else if (request && strcasecmp(request, "GetMap") == 0 && format &&
659✔
5860
             strcasecmp(format, "image/txt") == 0) {
467✔
5861
    /* Until someone adds full support for ASCII graphics this should do. ;) */
5862
    msIO_setHeader("Content-Type", "text/plain; charset=UTF-8");
×
5863
    msIO_sendHeaders();
×
5864
    msIO_printf(".\n               ,,ggddY\"\"\"Ybbgg,,\n          ,agd888b,_ "
×
5865
                "\"Y8, ___'\"\"Ybga,\n       ,gdP\"\"88888888baa,.\"\"8b    \""
5866
                "888g,\n     ,dP\"     ]888888888P'  \"Y     '888Yb,\n   ,dP\""
5867
                "      ,88888888P\"  db,       \"8P\"\"Yb,\n  ,8\"       ,8888"
5868
                "88888b, d8888a           \"8,\n ,8'        d88888888888,88P\""
5869
                "' a,          '8,\n,8'         88888888888888PP\"  \"\"      "
5870
                "     '8,\nd'          I88888888888P\"                   'b\n8"
5871
                "           '8\"88P\"\"Y8P'                      8\n8         "
5872
                "   Y 8[  _ \"                        8\n8              \"Y8d8"
5873
                "b  \"Y a                   8\n8                 '\"\"8d,   __"
5874
                "                 8\nY,                    '\"8bd888b,        "
5875
                "     ,P\n'8,                     ,d8888888baaa       ,8'\n '8"
5876
                ",                    888888888888'      ,8'\n  '8a           "
5877
                "        \"8888888888I      a8'\n   'Yba                  'Y88"
5878
                "88888P'    adP'\n     \"Yba                 '888888P'   adY\""
5879
                "\n       '\"Yba,             d8888P\" ,adP\"' \n          '\""
5880
                "Y8baa,      ,d888P,ad8P\"' \n               ''\"\"YYba8888P\""
5881
                "\"''\n");
5882
    return MS_SUCCESS;
×
5883
  }
5884

5885
  /* If SERVICE, VERSION and REQUEST not included than this isn't a WMS req*/
5886
  if (service == NULL && nVersion == OWS_VERSION_NOTSET && request == NULL)
660✔
5887
    return MS_DONE; /* Not a WMS request */
5888

5889
  /* VERSION *and* REQUEST required by both getMap and getFeatureInfo */
5890
  if (nVersion == OWS_VERSION_NOTSET) {
660✔
5891
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
5892
                         "Incomplete WMS request: VERSION parameter missing",
5893
                         "msWMSDispatch()");
5894
    return msWMSException(map, OWS_VERSION_NOTSET, NULL, wms_exception_format);
1✔
5895
  }
5896

5897
  /*check if the version is one of the supported versions*/
5898
  if (nVersion != OWS_1_0_0 && nVersion != OWS_1_1_0 && nVersion != OWS_1_1_1 &&
659✔
5899
      nVersion != OWS_1_3_0) {
386✔
5900
    msSetErrorWithStatus(
×
5901
        MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5902
        "Invalid WMS version: VERSION %s is not supported. Supported "
5903
        "versions are 1.0.0, 1.1.0, 1.1.1, 1.3.0",
5904
        "msWMSDispatch()", version);
5905
    return msWMSException(map, OWS_VERSION_NOTSET, NULL, wms_exception_format);
×
5906
  }
5907

5908
  if (request == NULL) {
659✔
5909
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5910
                         "Incomplete WMS request: REQUEST parameter missing",
5911
                         "msWMSDispatch()");
5912
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5913
  }
5914

5915
  /* hack !? The function can return MS_DONE ... be sure it's a wms request
5916
   * before checking the enabled layers */
5917
  if ((strcasecmp(request, "GetStyles") == 0) ||
659✔
5918
      (strcasecmp(request, "GetLegendGraphic") == 0) ||
599✔
5919
      (strcasecmp(request, "GetSchemaExtension") == 0) ||
552✔
5920
      (strcasecmp(request, "map") == 0 || strcasecmp(request, "GetMap") == 0) ||
550✔
5921
      (strcasecmp(request, "feature_info") == 0 ||
82✔
5922
       strcasecmp(request, "GetFeatureInfo") == 0) ||
82✔
5923
      (strcasecmp(request, "DescribeLayer") == 0)) {
21✔
5924
    const char *request_tmp;
5925
    if (strcasecmp(request, "map") == 0)
659✔
5926
      request_tmp = "GetMap";
5927
    else if (strcasecmp(request, "feature_info") == 0)
659✔
5928
      request_tmp = "GetFeatureInfo";
5929
    else
5930
      request_tmp = request;
5931

5932
    msOWSRequestLayersEnabled(map, "M", request_tmp, ows_request);
659✔
5933
    if (ows_request->numlayers == 0) {
659✔
5934
      msSetErrorWithStatus(
1✔
5935
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5936
          "WMS request not enabled. Check wms/ows_enable_request settings.",
5937
          "msWMSDispatch()");
5938
      return msWMSException(map, nVersion, NULL, wms_exception_format);
1✔
5939
    }
5940
  }
5941

5942
  if (msOWSMakeAllLayersUnique(map) != MS_SUCCESS)
658✔
5943
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5944

5945
  bool isContentDependentLegend = false;
5946
  if (strcasecmp(request, "GetLegendGraphic") == 0) {
658✔
5947
    /*
5948
     * check for a BBOX in the request, in that case we have a content-dependant
5949
     * legend request, and should be following the GetMap path a bit more
5950
     */
5951
    bool found = false;
5952
    for (int i = 0; i < req->NumParams; i++) {
430✔
5953
      if (strcasecmp(req->ParamNames[i], "BBOX") == 0) {
396✔
5954
        if (req->ParamValues[i] && *req->ParamValues[i]) {
12✔
5955
          found = true;
5956
          break;
5957
        }
5958
      }
5959
    }
5960
    if (found) {
46✔
5961
      isContentDependentLegend = true;
5962
      /* getLegendGraphic uses LAYER= , we need to create a LAYERS= value that
5963
       * is identical we'll suppose that the client is conformat and hasn't
5964
       * included a LAYERS= parameter in its request */
5965
      for (int i = 0; i < req->NumParams; i++) {
182✔
5966
        if (strcasecmp(req->ParamNames[i], "LAYER") == 0) {
170✔
5967
          req->ParamNames[req->NumParams] = msStrdup("LAYERS");
9✔
5968
          req->ParamValues[req->NumParams] = msStrdup(req->ParamValues[i]);
9✔
5969
          req->NumParams++;
9✔
5970
        }
5971
      }
5972
    } else {
5973
      return msWMSLegendGraphic(map, nVersion, req->ParamNames,
34✔
5974
                                req->ParamValues, req->NumParams,
5975
                                wms_exception_format, ows_request, NULL);
34✔
5976
    }
5977
  }
5978

5979
  if (strcasecmp(request, "GetStyles") == 0)
624✔
5980
    return msWMSGetStyles(map, nVersion, req->ParamNames, req->ParamValues,
60✔
5981
                          req->NumParams, wms_exception_format);
60✔
5982

5983
  else if (request && strcasecmp(request, "GetSchemaExtension") == 0)
564✔
5984
    return msWMSGetSchemaExtension(map);
2✔
5985

5986
  /* getMap parameters are used by both getMap, getFeatureInfo, and content
5987
   * dependent legendgraphics */
5988
  if (strcasecmp(request, "map") == 0 || strcasecmp(request, "GetMap") == 0 ||
562✔
5989
      strcasecmp(request, "feature_info") == 0 ||
94✔
5990
      strcasecmp(request, "GetFeatureInfo") == 0 ||
94✔
5991
      strcasecmp(request, "DescribeLayer") == 0 || isContentDependentLegend) {
33✔
5992

5993
    const int status = msWMSLoadGetMapParams(
562✔
5994
        map, nVersion, req->ParamNames, req->ParamValues, req->NumParams,
5995
        wms_exception_format, request, ows_request);
5996
    if (status != MS_SUCCESS)
562✔
5997
      return status;
5998
  }
5999

6000
  /* This function owns validated_language, so remember to free it later*/
6001
  char *validated_language = msOWSGetLanguageFromList(map, "MO", language);
501✔
6002
  if (validated_language != NULL) {
501✔
6003
    msMapSetLanguageSpecificConnection(map, validated_language);
47✔
6004
  }
6005
  msFree(validated_language);
501✔
6006

6007
  if (strcasecmp(request, "map") == 0 || strcasecmp(request, "GetMap") == 0)
501✔
6008
    return msWMSGetMap(map, nVersion, req->ParamNames, req->ParamValues,
412✔
6009
                       req->NumParams, wms_exception_format, ows_request);
412✔
6010
  else if (strcasecmp(request, "feature_info") == 0 ||
89✔
6011
           strcasecmp(request, "GetFeatureInfo") == 0)
89✔
6012
    return msWMSFeatureInfo(map, nVersion, req->ParamNames, req->ParamValues,
58✔
6013
                            req->NumParams, wms_exception_format, ows_request);
58✔
6014
  else if (strcasecmp(request, "DescribeLayer") == 0) {
31✔
6015
    return msWMSDescribeLayer(map, nVersion, req->ParamNames, req->ParamValues,
19✔
6016
                              req->NumParams, wms_exception_format);
19✔
6017
  } else if (isContentDependentLegend) {
12✔
6018
    return msWMSGetContentDependentLegend(map, nVersion, req->ParamNames,
12✔
6019
                                          req->ParamValues, req->NumParams,
6020
                                          wms_exception_format, ows_request);
12✔
6021
  }
6022

6023
  /* Hummmm... incomplete or unsupported WMS request */
6024
  if (service != NULL && strcasecmp(service, "WMS") == 0) {
×
6025
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
6026
                         "Incomplete or unsupported WMS request",
6027
                         "msWMSDispatch()");
6028
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
6029
  } else
6030
    return MS_DONE; /* Not a WMS request */
6031
#else
6032
  msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
6033
                       "WMS server support is not available.",
6034
                       "msWMSDispatch()");
6035
  return (MS_FAILURE);
6036
#endif
6037
}
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