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

geographika / mapserver / 23004945429

12 Mar 2026 01:39PM UTC coverage: 42.423% (+0.003%) from 42.42%
23004945429

push

github

geographika
Use std::string and avoid strlcpy

8 of 9 new or added lines in 1 file covered. (88.89%)

955 existing lines in 4 files now uncovered.

64589 of 152250 relevant lines covered (42.42%)

27323.37 hits per line

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

82.54
/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 <memory>
55
#include <set>
56
#include <string>
57
#include <utility>
58
#include <vector>
59

60
#ifdef _WIN32
61
#include <process.h>
62
#endif
63

64
/* ==================================================================
65
 * WMS Server stuff.
66
 * ================================================================== */
67
#ifdef USE_WMS_SVR
68

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

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

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

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

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

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

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

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

113
    msWriteErrorImage(map, NULL, blank);
5✔
114

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

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

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

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

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

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

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

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

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

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

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

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

229
  return true;
230
}
231

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

239
    const char *timepattern =
240
        msOWSLookupMetadata(&(map->web.metadata), "MO", "timeformat");
133✔
241

242
    for (int i = 0; i < map->numlayers; i++) {
1,590✔
243
      layerObj *lp = (GET_LAYER(map, i));
1,471✔
244
      if (lp->status != MS_ON && lp->status != MS_DEFAULT)
1,471✔
245
        continue;
1,338✔
246

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

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

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

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

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

342
  return MS_SUCCESS;
343
}
344

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

356
  if (!map)
11✔
357
    return MS_FAILURE;
358

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

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

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

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

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

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

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

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

412
    const int curfilter = ows_request->layerwmsfilterindex[lp->index];
17✔
413

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

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

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

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

442
#ifdef do_we_need_this
443
    FLTProcessPropertyIsNull(psNode, map, lp->index);
444

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

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

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

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

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

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

489
#endif
490

491
    /* Apply filter to this layer */
492

493
    // add a flag to the layer metadata so we know the filter
494
    // is from a WMS request - we can avoid fetching all properties
495
    // and ensure any BBOXes aren't removed
496
    msInsertHashTable(&(lp->metadata), "gml_wmsfilter_flag", "true");
15✔
497

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

500
    msRemoveHashTable(&(lp->metadata), "gml_wmsfilter_flag");
15✔
501

502
    if (ret != MS_SUCCESS) {
15✔
UNCOV
503
      errorObj *ms_error = msGetErrorObj();
×
504

UNCOV
505
      if (ms_error->code != MS_NOTFOUND) {
×
UNCOV
506
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_500_INTERNAL_SERVER_ERROR,
×
507
                             "FLTApplyFilterToLayer() failed",
508
                             "msWMSApplyFilter()");
UNCOV
509
        FLTFreeFilterEncodingNode(psNode);
×
UNCOV
510
        msFreeCharArray(paszFilters, numfilters);
×
UNCOV
511
        return msWMSException(map, version, "InvalidParameterValue",
×
512
                              wms_exception_format);
513
      }
514
    }
515

516
    FLTFreeFilterEncodingNode(psNode);
15✔
517

518
  } /* for */
519

520
  msFreeCharArray(paszFilters, numfilters);
10✔
521

522
  return MS_SUCCESS;
523
}
524

525
/*
526
** Class representing a node in the hierarchy of <Layer> of the
527
* GetCapabilities response. All nodes don't necessarily correspond to an
528
* actual MapServer LAYER when groups are involved.
529
*/
530
class msWMSLayerNode {
531
public:
532
  std::string name{};
533
  std::string title{};
534
  std::string titleWarning{};
535
  std::string abstract{};
536
  int layerIdx = -1;
537
  msWMSLayerNode *parent = nullptr;
538
  std::vector<std::unique_ptr<msWMSLayerNode>> children{};
539

540
  msWMSLayerNode() = default;
6,190✔
541

542
  /*
543
  ** isQueryable()
544
  */
545
  bool isQueryable(const mapObj *map) const {
264✔
546
    if (layerIdx >= 0 && msIsLayerQueryable(GET_LAYER(map, layerIdx)))
264✔
547
      return true;
548
    for (const auto &child : children) {
168✔
549
      if (child->isQueryable(map)) {
54✔
550
        return true;
551
      }
552
    }
553
    return false;
554
  }
555

556
  /*
557
  ** collectLayerIndices()
558
  */
559
  std::vector<int> collectLayerIndices() const {
1,066✔
560
    std::vector<int> res;
561
    collectLayerIndices(res);
1,066✔
562
    return res;
1,066✔
UNCOV
563
  }
×
564

565
private:
566
  msWMSLayerNode(const msWMSLayerNode &) = delete;
567
  msWMSLayerNode &operator=(const msWMSLayerNode &) = delete;
568

569
  void collectLayerIndices(std::vector<int> &res) const {
1,561✔
570
    if (layerIdx >= 0)
1,561✔
571
      res.push_back(layerIdx);
1,437✔
572
    for (const auto &child : children) {
2,056✔
573
      child->collectLayerIndices(res);
495✔
574
    }
575
  }
1,561✔
576
};
577

578
/*
579
** msWMSCreateLayerTree()
580
*/
581
static std::pair<std::unique_ptr<msWMSLayerNode>,
582
                 std::map<std::string, msWMSLayerNode *>>
583
msWMSCreateLayerTree(mapObj *map, const char *validated_language) {
801✔
584
  auto root = std::make_unique<msWMSLayerNode>();
801✔
585
  std::map<std::vector<std::string>, msWMSLayerNode *> mapPathToNode;
586
  std::map<std::string, msWMSLayerNode *> mapNameToNode;
587

588
  if (map->name) {
801✔
589
    root->name = map->name;
801✔
590
    mapNameToNode[msStringToLower(root->name)] = root.get();
801✔
591
  }
592

593
  for (int i = 0; i < map->numlayers; i++) {
5,947✔
594
    layerObj *layer = GET_LAYER(map, i);
5,146✔
595
    if (!layer->name)
5,146✔
UNCOV
596
      continue;
×
597

598
    const char *layer_group =
599
        msOWSLookupMetadata(&(layer->metadata), "MO", "layer_group");
5,146✔
600
    const char *group = layer->group;
5,146✔
601
    const bool has_layer_group = layer_group && layer_group[0] != 0;
5,146✔
602
    const bool has_group = group && group[0] != 0;
5,146✔
603
    msWMSLayerNode *curNode = nullptr;
604

605
    std::vector<std::string> path;
606
    if (has_layer_group) {
5,146✔
607
      if (has_group) {
849✔
608
        const char *errorMsg = "It is not allowed to set both the GROUP and "
609
                               "WMS_LAYER_GROUP for a layer";
UNCOV
610
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_500_INTERNAL_SERVER_ERROR,
×
611
                             errorMsg, "msWMSCreateLayerTree()", NULL);
UNCOV
612
        msIO_fprintf(stdout, "<!-- ERROR: %s -->\n", errorMsg);
×
613
        /* cannot return exception at this point because we are already writing
614
         * to stdout */
615
      } else if (layer_group[0] != '/') {
849✔
616
        const char *errorMsg =
617
            "The WMS_LAYER_GROUP metadata does not start with a '/'";
UNCOV
618
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_500_INTERNAL_SERVER_ERROR,
×
619
                             errorMsg, "msWMSPrepareNestedGroups()", NULL);
620
        msIO_fprintf(stdout, "<!-- ERROR: %s -->\n", errorMsg);
×
621
        /* cannot return exception at this point because we are already
622
         * writing to stdout */
623
      } else {
624
        /* split into subgroups. Start at address + 1 because the first '/'
625
         * would cause an extra empty group */
626
        curNode = root.get();
627
        const auto splitPath = msStringSplit(layer_group + 1, '/');
849✔
628
        if (!splitPath.empty()) {
849✔
629
          bool lastComponentJustAdded = false;
630
          for (const std::string &subPath : splitPath) {
1,783✔
631
            path.push_back(msStringToLower(subPath));
1,868✔
632
            auto iter = mapPathToNode.find(path);
633
            if (iter == mapPathToNode.end()) {
934✔
634
              auto newNode = std::make_unique<msWMSLayerNode>();
137✔
635
              newNode->parent = curNode;
137✔
636
              newNode->name = subPath;
137✔
637
              newNode->title = subPath;
137✔
638
              mapPathToNode[path] = newNode.get();
137✔
639
              mapNameToNode[msStringToLower(newNode->name)] = newNode.get();
137✔
640
              curNode->children.push_back(std::move(newNode));
137✔
641
              curNode = curNode->children.back().get();
642
              lastComponentJustAdded = true;
643
            } else {
137✔
644
              curNode = iter->second;
797✔
645
              lastComponentJustAdded = false;
646
            }
647
          }
648

649
          if (lastComponentJustAdded) {
849✔
650
            const char *value;
651
            if ((value = msOWSLookupMetadataWithLanguage(&(layer->metadata),
103✔
652
                                                         "MO", "GROUP_TITLE",
653
                                                         validated_language))) {
654
              curNode->title = value;
34✔
655
            } else {
656
              curNode->title = curNode->name;
69✔
657

658
              char *pszExpandedName =
659
                  msStringConcatenate(nullptr, "GROUP_TITLE");
69✔
660
              if (validated_language && validated_language[0]) {
69✔
UNCOV
661
                pszExpandedName = msStringConcatenate(pszExpandedName, ".");
×
662
                pszExpandedName =
UNCOV
663
                    msStringConcatenate(pszExpandedName, validated_language);
×
664
              }
665
              char *pszExpandedMetadataKey =
666
                  msOWSGetExpandedMetadataKey("MO", pszExpandedName);
69✔
667
              curNode->titleWarning = "<!-- WARNING: Mandatory metadata ";
69✔
668
              curNode->titleWarning += pszExpandedMetadataKey;
669
              curNode->titleWarning += " was missing in this context. -->";
670
              msFree(pszExpandedName);
69✔
671
              msFree(pszExpandedMetadataKey);
69✔
672
            }
673
          }
674
          if (lastComponentJustAdded) {
675
            const char *value;
676
            if ((value = msOWSLookupMetadataWithLanguage(&(layer->metadata),
103✔
677
                                                         "MO", "GROUP_ABSTRACT",
678
                                                         validated_language))) {
679
              curNode->abstract = value;
34✔
680
            }
681
          }
682
        }
683
      }
849✔
684
    } else {
685
      curNode = root.get();
686
      if (has_group) {
4,297✔
687
        path.push_back(msStringToLower(group));
360✔
688
        auto iter = mapPathToNode.find(path);
689
        if (iter == mapPathToNode.end()) {
180✔
690
          auto newNode = std::make_unique<msWMSLayerNode>();
106✔
691
          newNode->parent = curNode;
106✔
692
          newNode->name = group;
106✔
693

694
          {
695
            const char *value;
696
            if ((value = msOWSLookupMetadataWithLanguage(&(layer->metadata),
106✔
697
                                                         "MO", "GROUP_TITLE",
698
                                                         validated_language))) {
699
              newNode->title = value;
41✔
700
            } else {
701
              newNode->title = newNode->name;
65✔
702

703
              char *pszExpandedName =
704
                  msStringConcatenate(nullptr, "GROUP_TITLE");
65✔
705
              if (validated_language && validated_language[0]) {
65✔
UNCOV
706
                pszExpandedName = msStringConcatenate(pszExpandedName, ".");
×
707
                pszExpandedName =
UNCOV
708
                    msStringConcatenate(pszExpandedName, validated_language);
×
709
              }
710
              char *pszExpandedMetadataKey =
711
                  msOWSGetExpandedMetadataKey("MO", pszExpandedName);
65✔
712
              newNode->titleWarning = "<!-- WARNING: Mandatory metadata ";
65✔
713
              newNode->titleWarning += pszExpandedMetadataKey;
714
              newNode->titleWarning += " was missing in this context. -->";
715
              msFree(pszExpandedName);
65✔
716
              msFree(pszExpandedMetadataKey);
65✔
717
            }
718
          }
719
          {
720
            const char *value;
721
            if ((value = msOWSLookupMetadataWithLanguage(&(layer->metadata),
106✔
722
                                                         "MO", "GROUP_ABSTRACT",
723
                                                         validated_language))) {
724
              newNode->abstract = value;
81✔
725
            }
726
          }
727

728
          mapPathToNode[path] = newNode.get();
106✔
729
          mapNameToNode[msStringToLower(newNode->name)] = newNode.get();
106✔
730
          curNode->children.push_back(std::move(newNode));
106✔
731
          curNode = root->children.back().get();
732
        } else {
106✔
733
          curNode = iter->second;
74✔
734
        }
735
      }
736
    }
737

738
    if (curNode) {
5,146✔
739
      auto newNode = std::make_unique<msWMSLayerNode>();
5,146✔
740
      newNode->parent = curNode;
5,146✔
741
      newNode->layerIdx = i;
5,146✔
742
      newNode->name = layer->name;
5,146✔
743
      path.push_back(msStringToLower(newNode->name));
5,146✔
744
      mapPathToNode[path] = newNode.get();
5,146✔
745
      mapNameToNode[msStringToLower(newNode->name)] = newNode.get();
5,146✔
746
      {
747
        const char *value;
748
        if ((value = msOWSLookupMetadataWithLanguage(
5,146✔
749
                 &(layer->metadata), "MO", "title", validated_language))) {
750
          newNode->title = value;
2,750✔
751
        } else {
752
          newNode->title = newNode->name;
2,396✔
753

754
          char *pszExpandedName = msStringConcatenate(nullptr, "TITLE");
2,396✔
755
          if (validated_language && validated_language[0]) {
2,396✔
756
            pszExpandedName = msStringConcatenate(pszExpandedName, ".");
6✔
757
            pszExpandedName =
758
                msStringConcatenate(pszExpandedName, validated_language);
6✔
759
          }
760
          char *pszExpandedMetadataKey =
761
              msOWSGetExpandedMetadataKey("MO", pszExpandedName);
2,396✔
762
          newNode->titleWarning = "<!-- WARNING: Mandatory metadata ";
2,396✔
763
          newNode->titleWarning += pszExpandedMetadataKey;
764
          newNode->titleWarning += " was missing in this context. -->";
765
          msFree(pszExpandedName);
2,396✔
766
          msFree(pszExpandedMetadataKey);
2,396✔
767
        }
768
      }
769
      {
770
        const char *value;
771
        if ((value = msOWSLookupMetadataWithLanguage(
5,146✔
772
                 &(layer->metadata), "MO", "abstract", validated_language))) {
773
          newNode->abstract = value;
293✔
774
        }
775
      }
776

777
      curNode->children.push_back(std::move(newNode));
5,146✔
778
    }
5,146✔
779
  }
5,146✔
780

781
  return std::make_pair(std::move(root), std::move(mapNameToNode));
801✔
782
}
801✔
783

784
/*
785
** Validate that a given dimension is inside the extents defined
786
*/
787
static bool msWMSValidateDimensionValue(const char *value,
29✔
788
                                        const char *dimensionextent,
789
                                        bool forcecharacter) {
790
  std::vector<pointObj> aextentranges;
791

792
  bool isextentavalue = false;
793
  bool isextentarange = false;
794
  bool ischaracter = false;
795

796
  if (forcecharacter)
797
    ischaracter = true;
798

799
  if (!value || !dimensionextent)
29✔
800
    return false;
801

802
  /*for the value, we support descrete values (2005) */
803
  /* multiple values (abc, def, ...) */
804
  /* and range(s) (1000/2000, 3000/5000) */
805
  /** we do not support resolution*/
806

807
  /* -------------------------------------------------------------------- */
808
  /*      parse the extent first.                                         */
809
  /* -------------------------------------------------------------------- */
810
  auto extents = msStringSplit(dimensionextent, ',');
29✔
811
  for (auto &extent :
29✔
812
       extents) // Make sure to get by reference so that it is updated in place
88✔
813
    msStringTrim(extent);
59✔
814

815
  std::vector<std::string> aextentvalues;
816
  if (extents.size() == 1) {
29✔
817
    if (strstr(dimensionextent, "/") == NULL) {
10✔
818
      /*single value*/
819
      isextentavalue = true;
UNCOV
820
      aextentvalues.push_back(dimensionextent);
×
UNCOV
821
      if (!forcecharacter)
×
UNCOV
822
        ischaracter = FLTIsNumeric(dimensionextent) == MS_FALSE;
×
823

824
    } else {
825
      const auto ranges = msStringSplit(dimensionextent, '/');
10✔
826
      if (ranges.size() == 2 || ranges.size() == 3) {
10✔
827
        /*single range*/
828
        isextentarange = true;
829
        aextentranges.resize(1);
10✔
830
        aextentranges[0].x = atof(ranges[0].c_str());
10✔
831
        aextentranges[0].y = atof(ranges[1].c_str());
10✔
832
        /*ranges should be numeric*/
833
        ischaracter = false;
834
      }
835
    }
10✔
836
  } else if (extents.size() >
19✔
837
             1) { /*check if it is muliple values or multiple ranges*/
838
    if (strstr(dimensionextent, "/") == NULL) {
19✔
839
      /*multiple values*/
840
      isextentavalue = true;
841
      aextentvalues = std::move(extents);
11✔
842
      if (!forcecharacter)
11✔
843
        ischaracter = FLTIsNumeric(aextentvalues[0].c_str()) == MS_FALSE;
11✔
844
    } else { /*multiple range extent*/
845
      int isvalidextent = MS_TRUE;
846
      /*ranges should be numeric*/
847
      ischaracter = false;
848
      isextentarange = true;
849
      aextentranges.resize(extents.size());
8✔
850
      size_t nextentranges = 0;
851

852
      for (const auto &extent : extents) {
24✔
853
        const auto onerange = msStringSplit(extent.c_str(), '/');
16✔
854
        if (onerange.size() != 2 && onerange.size() != 3) {
16✔
855
          isvalidextent = MS_FALSE;
856
          break;
857
        }
858
        if (isvalidextent) {
859

860
          aextentranges[nextentranges].x = atof(onerange[0].c_str());
16✔
861
          aextentranges[nextentranges++].y = atof(onerange[1].c_str());
16✔
862
        }
863
      }
16✔
864
      if (!isvalidextent) {
865
        nextentranges = 0;
866
        isextentarange = false;
867
      }
868
      aextentranges.resize(nextentranges);
8✔
869
    }
870
  }
871

872
  /* make sure that we got a valid extent*/
873
  if (!isextentavalue && !isextentarange) {
29✔
874
    return false;
875
  }
876

877
  /*for the extent of the dimesion, we support
878
  single value,  or list of mulitiple values comma separated,
879
  a single range or multiple ranges */
880

881
  const auto uservalues = msStringSplit(value, ',');
29✔
882
  bool uservaluevalid = false;
883
  if (uservalues.size() == 1) {
29✔
884
    /*user input=single*/
885
    /*is it descret or range*/
886
    const auto ranges = msStringSplit(uservalues[0].c_str(), '/');
12✔
887
    if (ranges.size() == 1) { /*discrete*/
12✔
888
      if (isextentavalue) {
8✔
889
        /*single user value, single/multiple values extent*/
890
        for (const auto &extentvalue : aextentvalues) {
12✔
891
          if (ischaracter)
10✔
892
            uservaluevalid = (uservalues[0] == extentvalue);
4✔
893
          else {
894
            if (atof(uservalues[0].c_str()) == atof(extentvalue.c_str()))
6✔
895
              uservaluevalid = true;
896
          }
897
          if (uservaluevalid)
8✔
898
            break;
899
        }
900
      } else if (isextentarange) {
3✔
901
        /*single user value, single/multiple range extent*/
902
        const double currentval = atof(uservalues[0].c_str());
903

904
        for (const auto &extentrange : aextentranges) {
4✔
905
          const double minval = extentrange.x;
3✔
906
          const double maxval = extentrange.y;
3✔
907
          if (currentval >= minval && currentval <= maxval) {
3✔
908
            uservaluevalid = true;
909
            break;
910
          }
911
        }
912
      }
913
    } else if (ranges.size() == 2 || ranges.size() == 3) { /*range*/
4✔
914
      /*user input=single range. In this case the extents must
915
       be of a range type.*/
916
      const double mincurrentval = atof(ranges[0].c_str());
917
      const double maxcurrentval = atof(ranges[1].c_str());
918
      if (isextentarange) {
4✔
919
        for (const auto &extentrange : aextentranges) {
7✔
920
          const double minval = extentrange.x;
5✔
921
          const double maxval = extentrange.y;
5✔
922

923
          if (minval <= mincurrentval && maxval >= maxcurrentval &&
5✔
924
              minval <= maxval) {
925
            uservaluevalid = true;
926
            break;
927
          }
928
        }
929
      }
930
    }
931
  } else if (uservalues.size() > 1) { /*user input=multiple*/
29✔
932
    if (strstr(value, "/") == NULL) {
17✔
933
      /*user input=multiple value*/
934
      bool valueisvalid = false;
935
      for (const auto &uservalue : uservalues) {
21✔
936
        valueisvalid = false;
937
        if (isextentavalue) {
17✔
938
          /*user input is multiple values, extent is defined as one or multiple
939
           * values*/
940
          for (const auto &extentvalue : aextentvalues) {
24✔
941
            if (ischaracter) {
20✔
942
              if (uservalue == extentvalue) {
9✔
943
                valueisvalid = true;
944
                break;
945
              }
946
            } else {
947
              if (atof(uservalue.c_str()) == atof(extentvalue.c_str())) {
11✔
948
                valueisvalid = true;
949
                break;
950
              }
951
            }
952
          }
953
          /*every value should be valid*/
954
          if (!valueisvalid)
8✔
955
            break;
956
        } else if (isextentarange) {
9✔
957
          /*user input is multiple values, extent is defined as one or multiple
958
           * ranges*/
959
          for (const auto &extentrange : aextentranges) {
14✔
960
            const double minval = extentrange.x;
11✔
961
            const double maxval = extentrange.y;
11✔
962
            const double currentval = atof(uservalue.c_str());
963
            if (minval <= currentval && maxval >= currentval &&
11✔
964
                minval <= maxval) {
965
              valueisvalid = true;
966
              break;
967
            }
968
          }
969
          if (!valueisvalid)
9✔
970
            break;
971
        }
972
      }
973
      uservaluevalid = valueisvalid;
974
    } else { /*user input multiple ranges*/
975
      bool valueisvalid = true;
976

977
      for (const auto &uservalue : uservalues) {
12✔
978
        /*each ranges should be valid*/
979
        const auto onerange = msStringSplit(uservalue.c_str(), '/');
10✔
980
        if (onerange.size() == 2 || onerange.size() == 3) {
10✔
981
          const double mincurrentval = atof(onerange[0].c_str());
982
          const double maxcurrentval = atof(onerange[1].c_str());
983

984
          /*extent must be defined also as a rangle*/
985
          if (isextentarange) {
10✔
986
            bool found = false;
987
            for (const auto &extentrange : aextentranges) {
17✔
988
              const double mincurrentrange = extentrange.x;
13✔
989
              const double maxcurrentrange = extentrange.y;
13✔
990

991
              if (mincurrentval >= mincurrentrange &&
13✔
992
                  maxcurrentval <= maxcurrentrange &&
6✔
993
                  mincurrentval <= maxcurrentval) {
994
                found = true;
995
                break;
996
              }
997
            }
998
            if (!found) {
10✔
999
              valueisvalid = false;
1000
              break;
1001
            }
1002
          }
1003
        } else {
1004
          valueisvalid = false;
1005
        }
1006
      }
10✔
1007
      uservaluevalid = valueisvalid;
1008
    }
1009
  }
1010

1011
  return uservaluevalid;
1012
}
29✔
1013

1014
static bool msWMSApplyDimensionLayer(layerObj *lp, const char *item,
13✔
1015
                                     const char *value, bool forcecharacter) {
1016
  bool result = false;
1017

1018
  if (lp && item && value) {
13✔
1019
    /*for the value, we support descrete values (2005) */
1020
    /* multiple values (abc, def, ...) */
1021
    /* and range(s) (1000/2000, 3000/5000) */
1022
    char *pszExpression = FLTGetExpressionForValuesRanges(
26✔
1023
        lp, item, value, forcecharacter ? MS_TRUE : MS_FALSE);
1024

1025
    if (pszExpression) {
13✔
1026
      // If tileindex is set, the filter is applied to tileindex too.
1027
      int tlpindex = -1;
1028
      if (lp->tileindex &&
13✔
UNCOV
1029
          (tlpindex = msGetLayerIndex(lp->map, lp->tileindex)) != -1) {
×
UNCOV
1030
        result = FLTApplyExpressionToLayer((GET_LAYER(lp->map, tlpindex)),
×
1031
                                           pszExpression) != MS_FALSE;
1032
      } else {
1033
        result = true;
1034
      }
1035
      result &= FLTApplyExpressionToLayer(lp, pszExpression) != MS_FALSE;
13✔
1036
      msFree(pszExpression);
13✔
1037
    }
1038
  }
1039
  return result;
13✔
1040
}
1041

1042
static bool msWMSApplyDimension(layerObj *lp, int /* version */,
29✔
1043
                                const char *dimensionname, const char *value,
1044
                                const char * /* wms_exception_format */) {
1045
  bool forcecharacter = false;
1046
  bool result = false;
1047

1048
  if (lp && dimensionname && value) {
29✔
1049
    /*check if the dimension name passes starts with dim_. All dimensions should
1050
     * start with dim_, except elevation*/
1051
    std::string dimension;
1052
    if (strncasecmp(dimensionname, "dim_", 4) == 0)
29✔
1053
      dimension = dimensionname + 4;
5✔
1054
    else
1055
      dimension = dimensionname;
1056

1057
    /*if value is empty and a default is defined, use it*/
1058
    std::string currentvalue;
1059
    if (strlen(value) > 0)
29✔
1060
      currentvalue = value;
1061
    else {
1062
      const char *dimensiondefault = msOWSLookupMetadata(
1✔
1063
          &(lp->metadata), "M", (dimension + "_default").c_str());
1✔
1064
      if (dimensiondefault)
1✔
1065
        currentvalue = dimensiondefault;
1066
    }
1067

1068
    /*check if the manadatory metada related to the dimension are set*/
1069
    const char *dimensionitem = msOWSLookupMetadata(
29✔
1070
        &(lp->metadata), "M", (dimension + "_item").c_str());
29✔
1071
    const char *dimensionextent = msOWSLookupMetadata(
29✔
1072
        &(lp->metadata), "M", (dimension + "_extent").c_str());
29✔
1073
    const char *dimensionunit = msOWSLookupMetadata(
29✔
1074
        &(lp->metadata), "M", (dimension + "_units").c_str());
29✔
1075

1076
    /*if the server want to force the type to character*/
1077
    const char *dimensiontype = msOWSLookupMetadata(
29✔
1078
        &(lp->metadata), "M", (dimension + "_type").c_str());
29✔
1079
    if (dimensiontype && strcasecmp(dimensiontype, "Character") == 0)
29✔
1080
      forcecharacter = true;
1081

1082
    if (dimensionitem && dimensionextent && dimensionunit &&
29✔
1083
        !currentvalue.empty()) {
1084
      if (msWMSValidateDimensionValue(currentvalue.c_str(), dimensionextent,
29✔
1085
                                      forcecharacter)) {
1086
        result = msWMSApplyDimensionLayer(lp, dimensionitem,
13✔
1087
                                          currentvalue.c_str(), forcecharacter);
1088
      } else {
1089
        msSetErrorWithStatus(
16✔
1090
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1091
            "Dimension %s with a value of %s is invalid or outside the "
1092
            "extents defined",
1093
            "msWMSApplyDimension", dimension.c_str(), currentvalue.c_str());
1094
        result = false;
1095
      }
1096
    } else
UNCOV
1097
      msSetErrorWithStatus(
×
1098
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1099
          "Dimension %s : invalid settings. Make sure that item, units "
1100
          "and extent are set.",
1101
          "msWMSApplyDimension", dimension.c_str());
1102
  }
1103
  return result;
29✔
1104
}
1105
/*
1106
**
1107
*/
1108
int msWMSLoadGetMapParams(mapObj *map, int nVersion, char **names,
574✔
1109
                          char **values, int numentries,
1110
                          const char *wms_exception_format,
1111
                          const char * /*wms_request*/,
1112
                          owsRequestObj *ows_request) {
1113
  bool adjust_extent = false;
1114
  bool nonsquare_enabled = false;
1115
  int transparent = MS_NOOVERRIDE;
1116
  bool bbox_pixel_is_point = false;
1117
  outputFormatObj *format = NULL;
1118
  int validlayers = 0;
1119
  const char *styles = NULL;
1120
  int invalidlayers = 0;
1121
  std::string epsgbuf;
1122
  std::string srsbuffer;
1123
  bool epsgvalid = false;
1124
  bool timerequest = false;
1125
  const char *stime = NULL;
1126
  bool srsfound = false;
1127
  bool bboxfound = false;
1128
  bool formatfound = false;
1129
  bool widthfound = false;
1130
  bool heightfound = false;
1131
  const char *request = NULL;
1132
  int status = 0;
1133
  const char *layerlimit = NULL;
1134
  bool tiled = false;
1135

1136
  const char *sldenabled = NULL;
1137
  const char *sld_url = NULL;
1138
  const char *sld_body = NULL;
1139

1140
  const char *filter = NULL;
1141

1142
  bool compliance_mode = false;
1143

1144
  /* Some of the getMap parameters are actually required depending on the */
1145
  /* request, but for now we assume all are optional and the map file */
1146
  /* defaults will apply. */
1147

1148
  msAdjustExtent(&(map->extent), map->width, map->height);
574✔
1149

1150
  /*
1151
    Check if we need strict checks for standard compliance.
1152
    Defaults to false.
1153
  */
1154
  compliance_mode = msOWSStrictCompliance(map);
574✔
1155

1156
  /*
1157
    Check for SLDs first. If SLD is available LAYERS and STYLES parameters are
1158
    non mandatory
1159
   */
1160
  for (int i = 0; i < numentries; i++) {
7,517✔
1161
    /* check if SLD is passed.  If yes, check for OGR support */
1162
    if (strcasecmp(names[i], "SLD") == 0 ||
6,943✔
1163
        strcasecmp(names[i], "SLD_BODY") == 0) {
6,936✔
1164
      sldenabled =
1165
          msOWSLookupMetadata(&(map->web.metadata), "MO", "sld_enabled");
146✔
1166

1167
      if (sldenabled == NULL) {
146✔
1168
        sldenabled = "true";
1169
      }
1170

1171
      if (strcasecmp(sldenabled, "true") == 0) {
146✔
1172
        if (strcasecmp(names[i], "SLD") == 0) {
144✔
1173
          sld_url = values[i];
7✔
1174
        }
1175
        if (strcasecmp(names[i], "SLD_BODY") == 0) {
144✔
1176
          sld_body = values[i];
137✔
1177
        }
1178
      }
1179
    }
1180
  }
1181

1182
  std::vector<std::string> wmslayers;
1183
  for (int i = 0; i < numentries; i++) {
7,501✔
1184
    /* getMap parameters */
1185

1186
    if (strcasecmp(names[i], "REQUEST") == 0) {
6,931✔
1187
      request = values[i];
574✔
1188
    }
1189

1190
    if (strcasecmp(names[i], "LAYERS") == 0) {
6,931✔
1191
      std::vector<int> layerOrder(map->numlayers);
571✔
1192

1193
      wmslayers = msStringSplit(values[i], ',');
571✔
1194
      if (wmslayers.empty()) {
571✔
UNCOV
1195
        if (sld_url == NULL && sld_body == NULL) {
×
UNCOV
1196
          msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
1197
                               "At least one layer name required in LAYERS.",
1198
                               "msWMSLoadGetMapParams()");
UNCOV
1199
          return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1200
        }
1201
      }
1202

1203
      if (nVersion >= OWS_1_3_0) {
571✔
1204
        layerlimit =
1205
            msOWSLookupMetadata(&(map->web.metadata), "MO", "layerlimit");
239✔
1206
        if (layerlimit) {
239✔
1207
          if (static_cast<int>(wmslayers.size()) > atoi(layerlimit)) {
16✔
1208
            msSetErrorWithStatus(
2✔
1209
                MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1210
                "Number of layers requested exceeds LayerLimit.",
1211
                "msWMSLoadGetMapParams()");
1212
            return msWMSException(map, nVersion, NULL, wms_exception_format);
2✔
1213
          }
1214
        }
1215
      }
1216

1217
      for (int iLayer = 0; iLayer < map->numlayers; iLayer++) {
3,800✔
1218
        map->layerorder[iLayer] = iLayer;
3,231✔
1219
      }
1220

1221
      int nLayerOrder = 0;
1222
      for (int j = 0; j < map->numlayers; j++) {
3,800✔
1223
        /* Keep only layers with status=DEFAULT by default */
1224
        /* Layer with status DEFAULT is drawn first. */
1225
        if (GET_LAYER(map, j)->status != MS_DEFAULT)
3,231✔
1226
          GET_LAYER(map, j)->status = MS_OFF;
3,176✔
1227
        else {
1228
          map->layerorder[nLayerOrder++] = j;
55✔
1229
          layerOrder[j] = 1;
55✔
1230
        }
1231
      }
1232

1233
      if (ows_request->layerwmsfilterindex != NULL)
569✔
UNCOV
1234
        msFree(ows_request->layerwmsfilterindex);
×
1235
      ows_request->layerwmsfilterindex =
569✔
1236
          (int *)msSmallMalloc(map->numlayers * sizeof(int));
569✔
1237
      for (int j = 0; j < map->numlayers; j++) {
3,800✔
1238
        ows_request->layerwmsfilterindex[j] = -1;
3,231✔
1239
      }
1240
      ows_request->numwmslayerargs = static_cast<int>(wmslayers.size());
569✔
1241

1242
      auto [layerTree, mapNameToNode] = msWMSCreateLayerTree(map, nullptr);
569✔
1243
      (void)layerTree;
1244

1245
      for (int k = 0; k < static_cast<int>(wmslayers.size()); k++) {
1,296✔
1246
        const auto &wmslayer = wmslayers[k];
727✔
1247
        bool layerfound = false;
1248
        auto iter = mapNameToNode.find(msStringToLower(wmslayer));
1,454✔
1249
        std::vector<int> layerIndices;
1250
        if (iter != mapNameToNode.end()) {
727✔
1251
          layerIndices = iter->second->collectLayerIndices();
725✔
1252
        }
1253

1254
        for (int j : layerIndices) {
1,650✔
1255
          layerObj *layer = GET_LAYER(map, j);
923✔
1256
          if (msIntegerInArray(layer->index, ows_request->enabled_layers,
923✔
1257
                               ows_request->numlayers)) {
1258
            if (layer->status != MS_DEFAULT) {
918✔
1259
              if (layerOrder[j] == 0) {
889✔
1260
                map->layerorder[nLayerOrder++] = j;
879✔
1261
                layerOrder[j] = 1;
879✔
1262
                layer->status = MS_ON;
879✔
1263
              }
1264
            }
1265
            /* if a layer name is repeated assign the first matching filter */
1266
            /* duplicate names will be assigned filters later when layer copies
1267
             * are created */
1268
            if (ows_request->layerwmsfilterindex[j] == -1) {
918✔
1269
              ows_request->layerwmsfilterindex[j] = k;
908✔
1270
            }
1271
            validlayers++;
918✔
1272
            layerfound = true;
1273
          }
1274
        }
1275

1276
        if (!layerfound)
727✔
1277
          invalidlayers++;
4✔
1278
      }
727✔
1279

1280
      /* set all layers with status off at end of array */
1281
      for (int j = 0; j < map->numlayers; j++) {
3,800✔
1282
        if (GET_LAYER(map, j)->status == MS_OFF)
3,231✔
1283
          if (layerOrder[j] == 0)
2,297✔
1284
            map->layerorder[nLayerOrder++] = j;
2,297✔
1285
      }
1286
    } else if (strcasecmp(names[i], "STYLES") == 0) {
6,931✔
1287
      styles = values[i];
488✔
1288

1289
    } else if ((strcasecmp(names[i], "SRS") == 0 && nVersion < OWS_1_3_0) ||
5,872✔
1290
               (strcasecmp(names[i], "CRS") == 0 && nVersion >= OWS_1_3_0)) {
5,543✔
1291
      srsfound = true;
1292
      char *colon = strchr(values[i], ':');
548✔
1293
      bool srsOk = false;
1294
      /* SRS is in format "EPSG:epsg_id" or "AUTO:proj_id,unit_id,lon0,lat0" */
1295
      if (strncasecmp(values[i], "EPSG:", 5) == 0) {
548✔
1296
        srsOk = true;
1297
        /* SRS=EPSG:xxxx */
1298

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

1302
        srsbuffer = "EPSG:";
1303
        srsbuffer += (values[i] + 5);
544✔
1304
        epsgbuf = srsbuffer;
1305

1306
        /* This test was to correct a request by the OCG cite 1.3.0 test
1307
         sending CRS=ESPG:4326,  Bug:*/
1308
        if (nVersion >= OWS_1_3_0) {
544✔
1309
          if (srsbuffer.back() == ',') {
216✔
1310
            srsbuffer.resize(srsbuffer.size() - 1);
1311
            epsgbuf = srsbuffer;
1312
          }
1313
        }
1314

1315
        /* we need to wait until all params are read before */
1316
        /* loading the projection into the map. This will help */
1317
        /* insure that the passes srs is valid for all layers. */
1318
        /*
1319
        if (msLoadProjectionString(&(map->projection), buffer) != 0)
1320
          return msWMSException(map, nVersion, NULL);
1321

1322
        iUnits = GetMapserverUnitUsingProj(&(map->projection));
1323
        if (iUnits != -1)
1324
          map->units = iUnits;
1325
        */
1326
      } else if (strncasecmp(values[i], "AUTO:", 5) == 0 &&
4✔
1327
                 nVersion < OWS_1_3_0) {
1328
        if (nVersion < OWS_1_3_0) {
1329
          srsOk = true;
1330
          srsbuffer = values[i];
1331
          /* SRS=AUTO:proj_id,unit_id,lon0,lat0 */
1332
          /*
1333
          if (msLoadProjectionString(&(map->projection), values[i]) != 0)
1334
            return msWMSException(map, nVersion, NULL);
1335

1336
          iUnits = GetMapserverUnitUsingProj(&(map->projection));
1337
          if (iUnits != -1)
1338
            map->units = iUnits;
1339
          */
1340
        }
1341
      } else if (strncasecmp(values[i], "AUTO2:", 6) == 0 ||
4✔
1342
                 strncasecmp(values[i], "CRS:", 4) == 0) {
4✔
UNCOV
1343
        if (nVersion >= OWS_1_3_0) {
×
1344
          srsOk = true;
1345
          srsbuffer = values[i];
1346
        }
1347
      } else if (colon != NULL && strchr(colon + 1, ':') == NULL) {
4✔
1348
        srsOk = true;
1349
        srsbuffer = values[i];
1350
        epsgbuf = srsbuffer;
1351
      }
1352
      if (!srsOk) {
UNCOV
1353
        if (nVersion >= OWS_1_3_0) {
×
UNCOV
1354
          msSetErrorWithStatus(
×
1355
              MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1356
              "Unsupported CRS namespace (CRS must be in the format "
1357
              "AUTH:XXXX).",
1358
              "msWMSLoadGetMapParams()");
UNCOV
1359
          return msWMSException(map, nVersion, "InvalidCRS",
×
1360
                                wms_exception_format);
1361
        } else {
UNCOV
1362
          msSetErrorWithStatus(
×
1363
              MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1364
              "Unsupported SRS namespace (SRS must be in the format "
1365
              "AUTH:XXXX).",
1366
              "msWMSLoadGetMapParams()");
1367
          return msWMSException(map, nVersion, "InvalidSRS",
×
1368
                                wms_exception_format);
1369
        }
1370
      }
1371
    } else if (strcasecmp(names[i], "BBOX") == 0) {
5,324✔
1372
      bboxfound = true;
1373
      const auto tokens = msStringSplit(values[i], ',');
552✔
1374
      if (tokens.size() != 4) {
552✔
UNCOV
1375
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
1376
                             "Wrong number of arguments for BBOX.",
1377
                             "msWMSLoadGetMapParams()");
1378
        return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1379
      }
1380
      map->extent.minx = atof(tokens[0].c_str());
552✔
1381
      map->extent.miny = atof(tokens[1].c_str());
552✔
1382
      map->extent.maxx = atof(tokens[2].c_str());
552✔
1383
      map->extent.maxy = atof(tokens[3].c_str());
552✔
1384

1385
      /*for wms 1.3.0 we will do the validation of the bbox after all parameters
1386
       are read to account for the axes order*/
1387
      if (nVersion < OWS_1_3_0) {
552✔
1388

1389
        /* validate bbox values */
1390
        if (map->extent.minx >= map->extent.maxx ||
329✔
1391
            map->extent.miny >= map->extent.maxy) {
329✔
UNCOV
1392
          msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
1393
                               "Invalid values for BBOX.",
1394
                               "msWMSLoadGetMapParams()");
UNCOV
1395
          return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1396
        }
1397
        adjust_extent = true;
1398
      }
1399
    } else if (strcasecmp(names[i], "WIDTH") == 0) {
5,324✔
1400
      widthfound = true;
1401
      map->width = atoi(values[i]);
552✔
1402
    } else if (strcasecmp(names[i], "HEIGHT") == 0) {
4,220✔
1403
      heightfound = true;
1404
      map->height = atoi(values[i]);
552✔
1405
    } else if (strcasecmp(names[i], "FORMAT") == 0) {
3,668✔
1406
      formatfound = true;
1407

1408
      if (strcasecmp(values[i], "application/openlayers") != 0) {
538✔
1409
        /*check to see if a predefined list is given*/
1410
        const char *format_list =
1411
            msOWSLookupMetadata(&(map->web.metadata), "M", "getmap_formatlist");
535✔
1412
        if (format_list) {
535✔
1413
          format = msOwsIsOutputFormatValid(
73✔
1414
              map, values[i], &(map->web.metadata), "M", "getmap_formatlist");
1415
          if (format == NULL) {
73✔
1416
            msSetErrorWithStatus(MS_IMGERR, MS_HTTP_400_BAD_REQUEST,
×
1417
                                 "Unsupported output format (%s).",
1418
                                 "msWMSLoadGetMapParams()", values[i]);
1419
            return msWMSException(map, nVersion, "InvalidFormat",
×
1420
                                  wms_exception_format);
1421
          }
1422
        } else {
1423
          format = msSelectOutputFormat(map, values[i]);
462✔
1424
          if (format == NULL ||
462✔
1425
              (strncasecmp(format->driver, "MVT", 3) != 0 &&
462✔
1426
               strncasecmp(format->driver, "GDAL/", 5) != 0 &&
457✔
1427
               strncasecmp(format->driver, "AGG/", 4) != 0 &&
454✔
1428
               strncasecmp(format->driver, "UTFGRID", 7) != 0 &&
3✔
1429
               strncasecmp(format->driver, "CAIRO/", 6) != 0 &&
3✔
1430
               strncasecmp(format->driver, "OGL/", 4) != 0 &&
3✔
1431
               strncasecmp(format->driver, "KML", 3) != 0 &&
3✔
1432
               strncasecmp(format->driver, "KMZ", 3) != 0)) {
1✔
UNCOV
1433
            msSetErrorWithStatus(MS_IMGERR, MS_HTTP_400_BAD_REQUEST,
×
1434
                                 "Unsupported output format (%s).",
1435
                                 "msWMSLoadGetMapParams()", values[i]);
UNCOV
1436
            return msWMSException(map, nVersion, "InvalidFormat",
×
1437
                                  wms_exception_format);
1438
          }
1439
        }
1440
      }
1441
      msFree(map->imagetype);
538✔
1442
      map->imagetype = msStrdup(values[i]);
538✔
1443
    } else if (strcasecmp(names[i], "TRANSPARENT") == 0) {
3,130✔
1444
      if (compliance_mode) {
71✔
1445
        transparent = (strcmp(values[i], "TRUE") == 0);
3✔
1446
        if ((!transparent) && (strcmp(values[i], "FALSE") != 0)) {
3✔
1447
          msSetErrorWithStatus(
2✔
1448
              MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1449
              "Value for TRANSPARENT must be either TRUE or FALSE.",
1450
              "msWMSLoadGetMapParams()");
1451
          return msWMSException(map, nVersion, NULL, wms_exception_format);
2✔
1452
        }
1453
      } else {
1454
        transparent = (strcasecmp(values[i], "TRUE") == 0);
68✔
1455
      }
1456
    } else if (strcasecmp(names[i], "BGCOLOR") == 0) {
3,059✔
1457
      long c;
1458
      c = strtol(values[i], NULL, 16);
45✔
1459
      map->imagecolor.red = (c / 0x10000) & 0xff;
45✔
1460
      map->imagecolor.green = (c / 0x100) & 0xff;
45✔
1461
      map->imagecolor.blue = c & 0xff;
45✔
1462
    }
1463

1464
    /* value of time can be empty. We should look for a default value */
1465
    /* see function msWMSApplyTime */
1466
    else if (strcasecmp(names[i], "TIME") == 0) { /* &&  values[i]) */
3,014✔
1467
      stime = values[i];
130✔
1468
      timerequest = true;
1469
    }
1470
    /* Vendor-specific ANGLE param (for map rotation), added in ticket #3332,
1471
     * also supported by GeoServer
1472
     */
1473
    else if (strcasecmp(names[i], "ANGLE") == 0) {
2,884✔
UNCOV
1474
      msMapSetRotation(map, atof(values[i]));
×
1475
    }
1476
    /* Vendor-specific bbox_pixel_is_point, added in ticket #4652 */
1477
    else if (strcasecmp(names[i], "BBOX_PIXEL_IS_POINT") == 0) {
2,884✔
UNCOV
1478
      bbox_pixel_is_point = (strcasecmp(values[i], "TRUE") == 0);
×
1479
    }
1480
    /* Vendor specific TILED (WMS-C) */
1481
    else if (strcasecmp(names[i], "TILED") == 0) {
2,884✔
1482
      tiled = (strcasecmp(values[i], "TRUE") == 0);
1✔
1483
    }
1484
    /* Vendor-specific FILTER, added in RFC-118 */
1485
    else if (strcasecmp(names[i], "FILTER") == 0) {
2,883✔
1486
      filter = values[i];
14✔
1487
    }
1488
  }
1489

1490
  /*validate the exception format WMS 1.3.0 section 7.3.3.11*/
1491

1492
  if (nVersion >= OWS_1_3_0 && wms_exception_format != NULL) {
570✔
1493
    if (strcasecmp(wms_exception_format, "INIMAGE") != 0 &&
23✔
1494
        strcasecmp(wms_exception_format, "BLANK") != 0 &&
4✔
1495
        strcasecmp(wms_exception_format, "XML") != 0) {
4✔
UNCOV
1496
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
1497
                           "Invalid format %s for the EXCEPTIONS parameter.",
1498
                           "msWMSLoadGetMapParams()", wms_exception_format);
UNCOV
1499
      return msWMSException(map, nVersion, "InvalidFormat",
×
1500
                            wms_exception_format);
1501
    }
1502
  }
1503

1504
  int need_axis_swap = MS_FALSE;
1505
  if (bboxfound && nVersion >= OWS_1_3_0) {
570✔
1506
    rectObj rect;
1507
    projectionObj proj;
1508

1509
    /*we have already validated that the request format when reading
1510
     the request parameters*/
1511
    rect = map->extent;
219✔
1512

1513
    /*try to adjust the axes if necessary*/
1514
    if (srsbuffer.size() > 1) {
219✔
1515
      msInitProjection(&proj);
217✔
1516
      msProjectionInheritContextFrom(&proj, &(map->projection));
217✔
1517
      if (msLoadProjectionStringEPSG(&proj, srsbuffer.c_str()) == 0 &&
434✔
1518
          (need_axis_swap = msIsAxisInvertedProj(&proj))) {
217✔
1519
        msAxisNormalizePoints(&proj, 1, &rect.minx, &rect.miny);
199✔
1520
        msAxisNormalizePoints(&proj, 1, &rect.maxx, &rect.maxy);
199✔
1521
      }
1522
      msFreeProjection(&proj);
217✔
1523
    }
1524
    /*if the CRS is AUTO2:auto_crs_id,factor,lon0,lat0,
1525
     we need to grab the factor parameter and use it with the bbox*/
1526
    if (srsbuffer.size() > 1 &&
219✔
1527
        strncasecmp(srsbuffer.c_str(), "AUTO2:", 6) == 0) {
217✔
UNCOV
1528
      const auto args = msStringSplit(srsbuffer.c_str(), ',');
×
UNCOV
1529
      if (args.size() == 4) {
×
1530
        const double factor = atof(args[1].c_str());
UNCOV
1531
        if (factor > 0 && factor != 1.0) {
×
UNCOV
1532
          rect.minx = rect.minx * factor;
×
UNCOV
1533
          rect.miny = rect.miny * factor;
×
1534
          rect.maxx = rect.maxx * factor;
UNCOV
1535
          rect.maxx = rect.maxy * factor;
×
1536
        }
1537
      }
UNCOV
1538
    }
×
1539

1540
    map->extent = rect;
219✔
1541

1542
    /* validate bbox values */
1543
    if (map->extent.minx >= map->extent.maxx ||
219✔
1544
        map->extent.miny >= map->extent.maxy) {
219✔
UNCOV
1545
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
1546
                           "Invalid values for BBOX.",
1547
                           "msWMSLoadGetMapParams()");
UNCOV
1548
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1549
    }
1550
    adjust_extent = true;
1551
  }
1552

1553
  if (tiled) {
570✔
1554
    const char *value;
1555
    hashTableObj *meta = &(map->web.metadata);
1✔
1556
    int map_edge_buffer = 0;
1557

1558
    if ((value = msLookupHashTable(meta, "tile_map_edge_buffer")) != NULL) {
1✔
1559
      map_edge_buffer = atoi(value);
1560
    }
1561
    if (map_edge_buffer > 0 && map->width > 0 && map->height > 0) {
1✔
1562
      /* adjust bbox and width and height to the buffer */
1563
      const double buffer_x = map_edge_buffer *
1✔
1564
                              (map->extent.maxx - map->extent.minx) /
1✔
1565
                              (double)map->width;
1✔
1566
      const double buffer_y = map_edge_buffer *
1✔
1567
                              (map->extent.maxy - map->extent.miny) /
1✔
1568
                              (double)map->height;
1✔
1569

1570
      // TODO: we should probably clamp the extent to avoid going outside of
1571
      // -180,-90,180,90 for geographic CRS for example
1572
      map->extent.minx -= buffer_x;
1✔
1573
      map->extent.maxx += buffer_x;
1✔
1574
      map->extent.miny -= buffer_y;
1✔
1575
      map->extent.maxy += buffer_y;
1✔
1576

1577
      map->width += 2 * map_edge_buffer;
1✔
1578
      map->height += 2 * map_edge_buffer;
1✔
1579

1580
      if (map_edge_buffer > 0) {
1581
        char tilebufferstr[64];
1582

1583
        /* Write the tile buffer to a string */
1584
        snprintf(tilebufferstr, sizeof(tilebufferstr), "-%d", map_edge_buffer);
1585

1586
        /* Hm, the labelcache buffer is set... */
1587
        if ((value = msLookupHashTable(meta, "labelcache_map_edge_buffer")) !=
1✔
1588
            NULL) {
1589
          /* If it's too small, replace with a bigger one */
UNCOV
1590
          if (map_edge_buffer > abs(atoi(value))) {
×
UNCOV
1591
            msRemoveHashTable(meta, "labelcache_map_edge_buffer");
×
UNCOV
1592
            msInsertHashTable(meta, "labelcache_map_edge_buffer",
×
1593
                              tilebufferstr);
1594
          }
1595
        }
1596
        /* No labelcache buffer value? Then we use the tile buffer. */
1597
        else {
1598
          msInsertHashTable(meta, "labelcache_map_edge_buffer", tilebufferstr);
1✔
1599
        }
1600
      }
1601
    }
1602
  }
1603

1604
  /*
1605
  ** If any select layers have a default time, we will apply the default
1606
  ** time value even if no TIME request was in the url.
1607
  */
1608
  if (!timerequest && map) {
570✔
1609
    for (int i = 0; i < map->numlayers && !timerequest; i++) {
2,204✔
1610
      layerObj *lp = NULL;
1611

1612
      lp = (GET_LAYER(map, i));
1,764✔
1613
      if (lp->status != MS_ON && lp->status != MS_DEFAULT)
1,764✔
1614
        continue;
959✔
1615

1616
      if (msOWSLookupMetadata(&(lp->metadata), "MO", "timedefault"))
805✔
1617
        timerequest = true;
1618
    }
1619
  }
1620

1621
  /*
1622
  ** Apply time filters if available in the request.
1623
  */
1624
  if (timerequest) {
570✔
1625
    if (msWMSApplyTime(map, nVersion, stime, wms_exception_format) ==
133✔
1626
        MS_FAILURE) {
1627
      return MS_FAILURE; /* msWMSException(map, nVersion, "InvalidTimeRequest");
1628
                          */
1629
    }
1630
  }
1631

1632
  /*
1633
  ** Check/apply wms dimensions
1634
  ** all dimension requests should start with dim_xxxx, except time and
1635
  *elevation.
1636
  */
1637
  for (int i = 0; i < map->numlayers; i++) {
3,736✔
1638
    layerObj *lp = (GET_LAYER(map, i));
3,196✔
1639
    if (lp->status != MS_ON && lp->status != MS_DEFAULT)
3,196✔
1640
      continue;
2,275✔
1641

1642
    const char *dimensionlist =
1643
        msOWSLookupMetadata(&(lp->metadata), "M", "dimensionlist");
921✔
1644
    if (dimensionlist) {
921✔
1645
      auto tokens = msStringSplit(dimensionlist, ',');
29✔
1646
      for (auto &token : tokens) {
50✔
1647
        msStringTrim(token);
37✔
1648
        for (int k = 0; k < numentries; k++) {
470✔
1649
          const std::string dimensionname(names[k]);
462✔
1650

1651
          /*the dim_ is supposed to be part of the dimension name in the
1652
           * request*/
1653
          std::string stmp;
1654
          if (strcasecmp(token.c_str(), "elevation") == 0)
462✔
1655
            stmp = token;
1656
          else {
1657
            stmp = "dim_";
1658
            stmp += token;
1659
          }
1660
          if (strcasecmp(dimensionname.c_str(), stmp.c_str()) == 0) {
462✔
1661
            if (!msWMSApplyDimension(lp, nVersion, dimensionname.c_str(),
29✔
1662
                                     values[k], wms_exception_format)) {
29✔
1663
              return msWMSException(lp->map, nVersion, "InvalidDimensionValue",
16✔
1664
                                    wms_exception_format);
1665
            }
1666
            break;
1667
          }
1668
        }
1669
      }
1670
    }
29✔
1671
  }
1672

1673
  /*
1674
  ** Apply the selected output format (if one was selected), and override
1675
  ** the transparency if needed.
1676
  */
1677

1678
  if (format != NULL)
540✔
1679
    msApplyOutputFormat(&(map->outputformat), format, transparent);
503✔
1680

1681
  /* Validate all layers given.
1682
  ** If an invalid layer is sent, return an exception.
1683
  */
1684
  if (validlayers == 0 || invalidlayers > 0) {
540✔
1685
    if (invalidlayers > 0) {
7✔
1686
      msSetErrorWithStatus(
4✔
1687
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1688
          "Invalid layer(s) given in the LAYERS parameter. A layer might be disabled for \
1689
this request. Check wms/ows_enable_request settings.",
1690
          "msWMSLoadGetMapParams()");
1691
      return msWMSException(map, nVersion, "LayerNotDefined",
4✔
1692
                            wms_exception_format);
1693
    }
1694
    if (validlayers == 0 && sld_url == NULL && sld_body == NULL) {
3✔
1695
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
3✔
1696
                           "Missing required parameter LAYERS",
1697
                           "msWMSLoadGetMapParams()");
1698
      return msWMSException(map, nVersion, "MissingParameterValue",
3✔
1699
                            wms_exception_format);
1700
    }
1701
  }
1702

1703
  /* validate srs value: When the SRS parameter in a GetMap request contains a
1704
  ** SRS that is valid for some, but not all of the layers being requested,
1705
  ** then the server shall throw a Service Exception (code = "InvalidSRS").
1706
  ** Validate first against epsg in the map and if no matching srs is found
1707
  ** validate all layers requested.
1708
  */
1709
  if (epsgbuf.size() >= 2) { /*at least 2 chars*/
533✔
1710
    char *projstring;
1711
    epsgvalid = false;
1712
    msOWSGetEPSGProj(&(map->projection), &(map->web.metadata), "MO", MS_FALSE,
511✔
1713
                     &projstring);
1714
    if (projstring) {
511✔
1715
      const auto tokens = msStringSplit(projstring, ' ');
511✔
1716
      for (const auto &token : tokens) {
888✔
1717
        if (strcasecmp(token.c_str(), epsgbuf.c_str()) == 0) {
874✔
1718
          epsgvalid = true;
1719
          break;
1720
        }
1721
      }
1722
      msFree(projstring);
511✔
1723
    }
511✔
1724
    if (!epsgvalid) {
511✔
1725
      for (int i = 0; i < map->numlayers; i++) {
34✔
1726
        epsgvalid = false;
1727
        if (GET_LAYER(map, i)->status == MS_ON) {
21✔
1728
          msOWSGetEPSGProj(&(GET_LAYER(map, i)->projection),
13✔
1729
                           &(GET_LAYER(map, i)->metadata), "MO", MS_FALSE,
1730
                           &projstring);
1731
          if (projstring) {
13✔
1732
            const auto tokens = msStringSplit(projstring, ' ');
13✔
1733
            for (const auto &token : tokens) {
27✔
1734
              if (strcasecmp(token.c_str(), epsgbuf.c_str()) == 0) {
26✔
1735
                epsgvalid = true;
1736
                break;
1737
              }
1738
            }
1739
            msFree(projstring);
13✔
1740
          }
13✔
1741
          if (!epsgvalid) {
13✔
1742
            if (nVersion >= OWS_1_3_0) {
1✔
1743
              msSetErrorWithStatus(
1✔
1744
                  MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1745
                  "Invalid CRS given : CRS must be valid for all "
1746
                  "requested layers.",
1747
                  "msWMSLoadGetMapParams()");
1748
              return msWMSException(map, nVersion, "InvalidSRS",
1✔
1749
                                    wms_exception_format);
1✔
1750
            } else {
UNCOV
1751
              msSetErrorWithStatus(
×
1752
                  MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1753
                  "Invalid SRS given : SRS must be valid for all "
1754
                  "requested layers.",
1755
                  "msWMSLoadGetMapParams()");
UNCOV
1756
              return msWMSException(map, nVersion, "InvalidSRS",
×
1757
                                    wms_exception_format);
1758
            }
1759
          }
1760
        }
1761
      }
1762
    }
1763
  }
1764

1765
  if (request == NULL || strcasecmp(request, "DescribeLayer") != 0) {
532✔
1766
    /* Validate requested image size.
1767
     */
1768
    if (map->width > map->maxsize || map->height > map->maxsize ||
513✔
1769
        map->width < 1 || map->height < 1) {
513✔
UNCOV
1770
      msSetErrorWithStatus(
×
1771
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1772
          "Image size out of range, WIDTH and HEIGHT must be between 1 "
1773
          "and %d pixels.",
1774
          "msWMSLoadGetMapParams()", map->maxsize);
1775

1776
      /* Restore valid default values in case errors INIMAGE are used */
UNCOV
1777
      map->width = 400;
×
UNCOV
1778
      map->height = 300;
×
UNCOV
1779
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1780
    }
1781

1782
    /* Check whether requested BBOX and width/height result in non-square pixels
1783
     */
1784
    nonsquare_enabled =
1785
        msTestConfigOption(map, "MS_NONSQUARE", MS_FALSE) != MS_FALSE;
513✔
1786
    if (!nonsquare_enabled) {
513✔
1787
      const double dx = MS_ABS(map->extent.maxx - map->extent.minx);
513✔
1788
      const double dy = MS_ABS(map->extent.maxy - map->extent.miny);
513✔
1789

1790
      const double reqy = ((double)map->width) * dy / dx;
513✔
1791

1792
      /* Allow up to 1 pixel of error on the width/height ratios. */
1793
      /* If more than 1 pixel then enable non-square pixels */
1794
      if (MS_ABS((reqy - (double)map->height)) > 1.0) {
513✔
1795
        if (map->debug)
170✔
1796
          msDebug("msWMSLoadGetMapParams(): enabling non-square pixels.\n");
1✔
1797
        msSetConfigOption(map, "MS_NONSQUARE", "YES");
170✔
1798
        nonsquare_enabled = true;
1799
      }
1800
    }
1801
  }
1802

1803
  /* If the requested SRS is different from the default mapfile projection, or
1804
  ** if a BBOX resulting in non-square pixels is requested then
1805
  ** copy the original mapfile's projection to any layer that doesn't already
1806
  ** have a projection. This will prevent problems when users forget to
1807
  ** explicitly set a projection on all layers in a WMS mapfile.
1808
  */
1809
  if (srsbuffer.size() > 1 || nonsquare_enabled) {
532✔
1810
    projectionObj newProj;
1811

1812
    if (map->projection.numargs <= 0) {
512✔
UNCOV
1813
      if (nVersion >= OWS_1_3_0) {
×
UNCOV
1814
        msSetErrorWithStatus(
×
1815
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1816
            "Cannot set new CRS on a map that doesn't "
1817
            "have any projection set. Please make sure your mapfile "
1818
            "has a projection defined at the top level.",
1819
            "msWMSLoadGetMapParams()");
UNCOV
1820
        return msWMSException(map, nVersion, "InvalidCRS",
×
UNCOV
1821
                              wms_exception_format);
×
1822
      } else {
UNCOV
1823
        msSetErrorWithStatus(
×
1824
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1825
            "Cannot set new SRS on a map that doesn't "
1826
            "have any projection set. Please make sure your mapfile "
1827
            "has a projection defined at the top level.",
1828
            "msWMSLoadGetMapParams()");
UNCOV
1829
        return msWMSException(map, nVersion, "InvalidSRS",
×
1830
                              wms_exception_format);
1831
      }
1832
    }
1833

1834
    msInitProjection(&newProj);
512✔
1835
    msProjectionInheritContextFrom(&newProj, &map->projection);
512✔
1836
    if (srsbuffer.size() > 1) {
512✔
1837
      int nTmp;
1838

1839
      if (nVersion >= OWS_1_3_0)
510✔
1840
        nTmp = msLoadProjectionStringEPSG(&newProj, srsbuffer.c_str());
214✔
1841
      else
1842
        nTmp = msLoadProjectionString(&newProj, srsbuffer.c_str());
296✔
1843
      if (nTmp != 0) {
510✔
1844
        msFreeProjection(&newProj);
×
1845
        return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1846
      }
1847
    }
1848

1849
    if (nonsquare_enabled ||
854✔
1850
        msProjectionsDiffer(&(map->projection), &newProj)) {
342✔
1851
      msMapSetLayerProjections(map);
273✔
1852
    }
1853
    msFreeProjection(&newProj);
512✔
1854
  }
1855

1856
  /* apply the srs to the map file. This is only done after validating */
1857
  /* that the srs given as parameter is valid for all layers */
1858
  if (srsbuffer.size() > 1) {
532✔
1859
    int nTmp;
1860
    msFreeProjectionExceptContext(&map->projection);
510✔
1861
    if (nVersion >= OWS_1_3_0)
510✔
1862
      nTmp = msLoadProjectionStringEPSG(&(map->projection), srsbuffer.c_str());
214✔
1863
    else
1864
      nTmp = msLoadProjectionString(&(map->projection), srsbuffer.c_str());
296✔
1865

1866
    if (nTmp != 0)
510✔
UNCOV
1867
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1868

1869
    nTmp = GetMapserverUnitUsingProj(&(map->projection));
510✔
1870
    if (nTmp != -1) {
510✔
1871
      map->units = static_cast<MS_UNITS>(nTmp);
510✔
1872
    }
1873
  }
1874

1875
  if (sld_url || sld_body) {
532✔
1876
    char *pszLayerNames = NULL;
144✔
1877
    const int nLayersBefore = map->numlayers;
144✔
1878

1879
    /* -------------------------------------------------------------------- */
1880
    /*      if LAYERS parameter was not given, set all layers to off        */
1881
    /* -------------------------------------------------------------------- */
1882
    if (validlayers == 0) { /*no LAYERS parameter is give*/
144✔
UNCOV
1883
      for (int j = 0; j < map->numlayers; j++) {
×
UNCOV
1884
        if (GET_LAYER(map, j)->status != MS_DEFAULT)
×
UNCOV
1885
          GET_LAYER(map, j)->status = MS_OFF;
×
1886
      }
1887
    }
1888

1889
    /*apply sld if defined. This is done here so that bbox and srs are already
1890
     * applied*/
1891
    if (sld_url) {
144✔
1892
      if ((status = msSLDApplySLDURL(map, sld_url, -1, NULL, &pszLayerNames)) !=
7✔
1893
          MS_SUCCESS)
1894
        return msWMSException(map, nVersion, NULL, wms_exception_format);
7✔
1895
    } else if (sld_body) {
137✔
1896
      if ((status = msSLDApplySLD(map, sld_body, -1, NULL, &pszLayerNames)) !=
137✔
1897
          MS_SUCCESS)
1898
        return msWMSException(map, nVersion, NULL, wms_exception_format);
3✔
1899
    }
1900
    /* -------------------------------------------------------------------- */
1901
    /*      SLD and styles can use the same layer multiple times. If        */
1902
    /*      that is the case we duplicate the layer for drawing             */
1903
    /*      purpose. We need to reset the ows request enable settings (#1602)*/
1904
    /* -------------------------------------------------------------------- */
1905
    const int nLayerAfter = map->numlayers;
137✔
1906
    if (nLayersBefore != nLayerAfter) {
137✔
1907
      msOWSRequestLayersEnabled(map, "M", "GetMap", ows_request);
5✔
1908
    }
1909

1910
    /* -------------------------------------------------------------------- */
1911
    /*      We need to take into account where the LAYERS parameter was     */
1912
    /*      not given (the LAYERS is option when an SLD is given). In       */
1913
    /*      this particular case, we need to turn on the layers             */
1914
    /*      identified the SLD (#1166).                                     */
1915
    /*                                                                      */
1916
    /* -------------------------------------------------------------------- */
1917
    if (validlayers == 0) {
137✔
UNCOV
1918
      if (pszLayerNames) {
×
UNCOV
1919
        const auto tokens = msStringSplit(pszLayerNames, ',');
×
UNCOV
1920
        for (const auto &token : tokens) {
×
UNCOV
1921
          for (int j = 0; j < map->numlayers; j++) {
×
UNCOV
1922
            if (((GET_LAYER(map, j)->name &&
×
UNCOV
1923
                  strcasecmp(GET_LAYER(map, j)->name, token.c_str()) == 0) ||
×
UNCOV
1924
                 (map->name && strcasecmp(map->name, token.c_str()) == 0) ||
×
UNCOV
1925
                 (GET_LAYER(map, j)->group &&
×
UNCOV
1926
                  strcasecmp(GET_LAYER(map, j)->group, token.c_str()) == 0)) &&
×
UNCOV
1927
                ((msIntegerInArray(GET_LAYER(map, j)->index,
×
1928
                                   ows_request->enabled_layers,
1929
                                   ows_request->numlayers)))) {
UNCOV
1930
              if (GET_LAYER(map, j)->status != MS_DEFAULT)
×
UNCOV
1931
                GET_LAYER(map, j)->status = MS_ON;
×
1932
            }
1933
          }
1934
        }
UNCOV
1935
      }
×
1936
    }
1937
    msFree(pszLayerNames);
137✔
1938
  }
1939

1940
  /* Validate Styles :
1941
  ** MapServer advertise styles through the group setting in a class object.
1942
  ** If no styles are set MapServer expects to have empty values
1943
  ** for the styles parameter (...&STYLES=&...) Or for multiple Styles/Layers,
1944
  ** we could have ...&STYLES=,,,. If that is not the
1945
  ** case, we generate an exception.
1946
  */
1947
  if (styles && strlen(styles) > 0) {
525✔
1948
    bool hasCheckedLayerUnicity = false;
1949
    int n = 0;
23✔
1950
    int layerCopyIndex;
1951

1952
    char **tokens = msStringSplitComplex(styles, ",", &n, MS_ALLOWEMPTYTOKENS);
23✔
1953
    for (int i = 0; i < n; i++) {
55✔
1954
      if (tokens[i] && strlen(tokens[i]) > 0 &&
35✔
1955
          strcasecmp(tokens[i], "default") != 0) {
25✔
1956
        if (!hasCheckedLayerUnicity) {
22✔
1957
          hasCheckedLayerUnicity = true;
1958
          bool bLayerInserted = false;
1959

1960
          /* --------------------------------------------------------------------
1961
           */
1962
          /*      If the same layer is given more that once, we need to */
1963
          /*      duplicate it. */
1964
          /* --------------------------------------------------------------------
1965
           */
1966
          for (size_t m = 0; m < wmslayers.size(); m++) {
41✔
1967
            for (size_t l = m + 1; l < wmslayers.size(); l++) {
33✔
1968
              const int nIndex = msGetLayerIndex(map, wmslayers[m].c_str());
9✔
1969
              if (nIndex != -1 &&
9✔
1970
                  strcasecmp(wmslayers[m].c_str(), wmslayers[l].c_str()) == 0) {
9✔
1971
                layerObj *psTmpLayer = (layerObj *)malloc(sizeof(layerObj));
6✔
1972
                initLayer(psTmpLayer, map);
6✔
1973
                msCopyLayer(psTmpLayer, GET_LAYER(map, nIndex));
6✔
1974
                /* open the source layer */
1975
                if (!psTmpLayer->vtable)
6✔
1976
                  msInitializeVirtualTable(psTmpLayer);
6✔
1977

1978
                /*make the name unique*/
1979
                char tmpId[128];
1980
                snprintf(tmpId, sizeof(tmpId), "%lx_%x_%d", (long)time(NULL),
6✔
1981
                         (int)getpid(), map->numlayers);
6✔
1982
                if (psTmpLayer->name)
6✔
1983
                  msFree(psTmpLayer->name);
6✔
1984
                psTmpLayer->name = msStrdup(tmpId);
6✔
1985
                wmslayers[l] = tmpId;
1986

1987
                layerCopyIndex = msInsertLayer(map, psTmpLayer, -1);
6✔
1988

1989
                // expand the array mapping map layer index to filter indexes
1990
                ows_request->layerwmsfilterindex =
6✔
1991
                    (int *)msSmallRealloc(ows_request->layerwmsfilterindex,
6✔
1992
                                          map->numlayers * sizeof(int));
6✔
1993
                ows_request->layerwmsfilterindex[layerCopyIndex] =
6✔
1994
                    l; // the filter index matches the index of the layer name
1995
                       // in the WMS param
1996

1997
                bLayerInserted = true;
1998
                /* layer was copied, we need to decrement its refcount */
1999
                MS_REFCNT_DECR(psTmpLayer);
6✔
2000
              }
2001
            }
2002
          }
2003

2004
          if (bLayerInserted) {
17✔
2005
            msOWSRequestLayersEnabled(map, "M", "GetMap", ows_request);
4✔
2006
          }
2007
        }
2008

2009
        if (static_cast<int>(wmslayers.size()) == n) {
22✔
2010
          for (int j = 0; j < map->numlayers; j++) {
155✔
2011
            layerObj *lp = GET_LAYER(map, j);
136✔
2012
            if ((lp->name && strcasecmp(lp->name, wmslayers[i].c_str()) == 0) ||
136✔
2013
                (lp->group &&
116✔
2014
                 strcasecmp(lp->group, wmslayers[i].c_str()) == 0)) {
4✔
2015
              bool found = false;
2016

2017
#ifdef USE_WMS_LYR
2018
              if (lp->connectiontype == MS_WMS) {
20✔
2019
                const char *pszWmsStyle =
2020
                    msOWSLookupMetadata(&(lp->metadata), "MO", "style");
2✔
2021
                if (pszWmsStyle != NULL &&
2✔
2022
                    strcasecmp(pszWmsStyle, tokens[i]) == 0)
2✔
2023
                  found = true;
2024
              }
2025
#endif // USE_WMS_LYR
2026

2027
              if (!found) {
2028
                for (int k = 0; k < lp->numclasses; k++) {
32✔
2029
                  if (lp->_class[k]->group &&
30✔
2030
                      strcasecmp(lp->_class[k]->group, tokens[i]) == 0) {
30✔
2031
                    msFree(lp->classgroup);
17✔
2032
                    lp->classgroup = msStrdup(tokens[i]);
17✔
2033
                    found = true;
2034
                    break;
2035
                  }
2036
                }
2037
              }
2038

2039
              if (!found) {
2040
                msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
2✔
2041
                                     "Style (%s) not defined on layer.",
2042
                                     "msWMSLoadGetMapParams()", tokens[i]);
2043
                msFreeCharArray(tokens, n);
2✔
2044

2045
                return msWMSException(map, nVersion, "StyleNotDefined",
2✔
2046
                                      wms_exception_format);
3✔
2047
              }
2048
              /* Check the style of the root layer */
2049
            } else if (map->name &&
116✔
2050
                       strcasecmp(map->name, wmslayers[i].c_str()) == 0) {
116✔
2051
              const char *styleName =
2052
                  msOWSLookupMetadata(&(map->web.metadata), "MO", "style_name");
15✔
2053
              if (styleName == NULL)
15✔
2054
                styleName = "default";
2055
              char *pszEncodedStyleName = msEncodeHTMLEntities(styleName);
15✔
2056
              if (strcasecmp(pszEncodedStyleName, tokens[i]) != 0) {
15✔
UNCOV
2057
                msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
2058
                                     "Style (%s) not defined on root layer.",
2059
                                     "msWMSLoadGetMapParams()", tokens[i]);
UNCOV
2060
                msFreeCharArray(tokens, n);
×
UNCOV
2061
                msFree(pszEncodedStyleName);
×
2062

UNCOV
2063
                return msWMSException(map, nVersion, "StyleNotDefined",
×
2064
                                      wms_exception_format);
2065
              }
2066
              msFree(pszEncodedStyleName);
15✔
2067
            }
2068
          }
2069
        } else {
2070
          msSetErrorWithStatus(
1✔
2071
              MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
2072
              "Invalid style (%s). Mapserver is expecting an empty "
2073
              "string for the STYLES : STYLES= or STYLES=,,, or using "
2074
              "keyword default  STYLES=default,default, ...",
2075
              "msWMSLoadGetMapParams()", styles);
2076
          msFreeCharArray(tokens, n);
1✔
2077
          return msWMSException(map, nVersion, "StyleNotDefined",
1✔
2078
                                wms_exception_format);
2079
        }
2080
      }
2081
    }
2082
    msFreeCharArray(tokens, n);
20✔
2083
  }
2084

2085
  /*
2086
  ** WMS extents are edge to edge while MapServer extents are center of
2087
  ** pixel to center of pixel.  Here we try to adjust the WMS extents
2088
  ** in by half a pixel.  We wait till here because we want to ensure we
2089
  ** are doing this in terms of the correct WIDTH and HEIGHT.
2090
  */
2091
  if (adjust_extent && map->width > 1 && map->height > 1 &&
522✔
2092
      !bbox_pixel_is_point) {
2093
    double dx, dy;
2094

2095
    dx = (map->extent.maxx - map->extent.minx) / map->width;
502✔
2096
    map->extent.minx += dx * 0.5;
502✔
2097
    map->extent.maxx -= dx * 0.5;
502✔
2098

2099
    dy = (map->extent.maxy - map->extent.miny) / map->height;
502✔
2100
    map->extent.miny += dy * 0.5;
502✔
2101
    map->extent.maxy -= dy * 0.5;
502✔
2102
  }
2103

2104
  if (request && strcasecmp(request, "DescribeLayer") != 0) {
522✔
2105
    if (!srsfound) {
503✔
2106
      if (nVersion >= OWS_1_3_0)
3✔
2107
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
2✔
2108
                             "Missing required parameter CRS",
2109
                             "msWMSLoadGetMapParams()");
2110
      else
2111
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
2112
                             "Missing required parameter SRS",
2113
                             "msWMSLoadGetMapParams()");
2114

2115
      return msWMSException(map, nVersion, "MissingParameterValue",
3✔
2116
                            wms_exception_format);
2117
    }
2118

2119
    if (!bboxfound) {
500✔
2120
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
2121
                           "Missing required parameter BBOX",
2122
                           "msWMSLoadGetMapParams()");
2123
      return msWMSException(map, nVersion, "MissingParameterValue",
1✔
2124
                            wms_exception_format);
2125
    }
2126

2127
    if (!formatfound && (strcasecmp(request, "GetMap") == 0 ||
499✔
2128
                         strcasecmp(request, "map") == 0)) {
12✔
2129
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
2130
                           "Missing required parameter FORMAT",
2131
                           "msWMSLoadGetMapParams()");
2132
      return msWMSException(map, nVersion, "MissingParameterValue",
1✔
2133
                            wms_exception_format);
2134
    }
2135

2136
    if (!widthfound) {
498✔
2137
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
2138
                           "Missing required parameter WIDTH",
2139
                           "msWMSLoadGetMapParams()");
2140
      return msWMSException(map, nVersion, "MissingParameterValue",
1✔
2141
                            wms_exception_format);
2142
    }
2143

2144
    if (!heightfound) {
497✔
2145
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
2146
                           "Missing required parameter HEIGHT",
2147
                           "msWMSLoadGetMapParams()");
2148
      return msWMSException(map, nVersion, "MissingParameterValue",
1✔
2149
                            wms_exception_format);
2150
    }
2151

2152
    if (styles == nullptr && sld_url == nullptr && sld_body == nullptr &&
82✔
2153
        (strcasecmp(request, "GetMap") == 0 ||
5✔
2154
         strcasecmp(request, "GetFeatureInfo") == 0) &&
501✔
2155
        msOWSLookupMetadata(&(map->web.metadata), "M",
1✔
2156
                            "allow_getmap_without_styles") == nullptr) {
2157
      msSetErrorWithStatus(
1✔
2158
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
2159
          "Missing required parameter STYLES. Note to service administrators: "
2160
          "defining the \"wms_allow_getmap_without_styles\" \"true\" "
2161
          "MAP.WEB.METADATA "
2162
          "item will disable this check (backward compatibility with behavior "
2163
          "of MapServer < 8.0)",
2164
          "msWMSLoadGetMapParams()");
2165
      return msWMSException(map, nVersion, "MissingParameterValue",
1✔
2166
                            wms_exception_format);
2167
    }
2168
  }
2169

2170
  /*
2171
  ** Apply vendor-specific filter if specified
2172
  */
2173
  if (filter) {
514✔
2174
    if (sld_url || sld_body) {
13✔
UNCOV
2175
      msSetErrorWithStatus(
×
2176
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
2177
          "Vendor-specific FILTER parameter cannot be used with SLD or "
2178
          "SLD_BODY.",
2179
          "msWMSLoadGetMapParams()");
UNCOV
2180
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
2181
    }
2182

2183
    if (msWMSApplyFilter(map, nVersion, filter, need_axis_swap,
13✔
2184
                         wms_exception_format, ows_request) == MS_FAILURE) {
2185
      return MS_FAILURE; /* msWMSException(map, nVersion,
2186
                            "InvalidFilterRequest"); */
2187
    }
2188
  }
2189

2190
  return MS_SUCCESS;
2191
}
574✔
2192

2193
/*
2194
**
2195
*/
2196
static void msWMSPrintRequestCap(int nVersion, const char *request,
287✔
2197
                                 const char *script_url, const char *formats,
2198
                                 ...) {
2199
  va_list argp;
2200

2201
  msIO_printf("    <%s>\n", request);
287✔
2202

2203
  /* We expect to receive a NULL-terminated args list of formats */
2204
  va_start(argp, formats);
287✔
2205
  const char *fmt = formats;
2206
  while (fmt != NULL) {
1,343✔
2207
    /* Special case for early WMS with subelements in Format (bug 908) */
2208
    char *encoded;
2209
    if (nVersion < OWS_1_1_0) {
1,056✔
2210
      encoded = msStrdup(fmt);
6✔
2211
    }
2212

2213
    /* otherwise we HTML code special characters */
2214
    else {
2215
      encoded = msEncodeHTMLEntities(fmt);
1,050✔
2216
    }
2217

2218
    msIO_printf("      <Format>%s</Format>\n", encoded);
1,056✔
2219
    msFree(encoded);
1,056✔
2220

2221
    fmt = va_arg(argp, const char *);
1,056✔
2222
  }
2223
  va_end(argp);
287✔
2224

2225
  msIO_printf("      <DCPType>\n");
287✔
2226
  msIO_printf("        <HTTP>\n");
287✔
2227
  /* The URL should already be HTML encoded. */
2228
  if (nVersion == OWS_1_0_0) {
287✔
2229
    msIO_printf("          <Get onlineResource=\"%s\" />\n", script_url);
6✔
2230
    msIO_printf("          <Post onlineResource=\"%s\" />\n", script_url);
6✔
2231
  } else {
2232
    msIO_printf("          <Get><OnlineResource "
281✔
2233
                "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
2234
                "xlink:href=\"%s\"/></Get>\n",
2235
                script_url);
2236
    msIO_printf("          <Post><OnlineResource "
281✔
2237
                "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
2238
                "xlink:href=\"%s\"/></Post>\n",
2239
                script_url);
2240
  }
2241

2242
  msIO_printf("        </HTTP>\n");
287✔
2243
  msIO_printf("      </DCPType>\n");
287✔
2244
  msIO_printf("    </%s>\n", request);
287✔
2245
}
287✔
2246

2247
void msWMSPrintAttribution(FILE *stream, const char *tabspace,
244✔
2248
                           hashTableObj *metadata,
2249
                           const char * /*namespaces*/) {
2250
  if (stream && metadata) {
244✔
2251
    const char *title =
2252
        msOWSLookupMetadata(metadata, "MO", "attribution_title");
244✔
2253
    const char *onlineres =
2254
        msOWSLookupMetadata(metadata, "MO", "attribution_onlineresource");
244✔
2255
    const char *logourl =
2256
        msOWSLookupMetadata(metadata, "MO", "attribution_logourl_width");
244✔
2257

2258
    if (title || onlineres || logourl) {
244✔
2259
      msIO_printf("%s<Attribution>\n", tabspace);
3✔
2260
      if (title) {
3✔
2261
        char *pszEncodedValue = msEncodeHTMLEntities(title);
3✔
2262
        msIO_fprintf(stream, "%s%s<Title>%s</Title>\n", tabspace, tabspace,
3✔
2263
                     pszEncodedValue);
2264
        free(pszEncodedValue);
3✔
2265
      }
2266

2267
      if (onlineres) {
3✔
2268
        char *pszEncodedValue = msEncodeHTMLEntities(onlineres);
1✔
2269
        msIO_fprintf(
1✔
2270
            stream,
2271
            "%s%s<OnlineResource xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
2272
            "xlink:href=\"%s\"/>\n",
2273
            tabspace, tabspace, pszEncodedValue);
2274
        free(pszEncodedValue);
1✔
2275
      }
2276

2277
      if (logourl) {
3✔
2278
        msOWSPrintURLType(stream, metadata, "MO", "attribution_logourl",
1✔
2279
                          OWS_NOERR, NULL, "LogoURL", NULL, " width=\"%s\"",
2280
                          " height=\"%s\"",
2281
                          ">\n             <Format>%s</Format",
2282
                          "\n             <OnlineResource "
2283
                          "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
2284
                          " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
2285
                          "          ",
2286
                          MS_FALSE, MS_TRUE, MS_TRUE, MS_TRUE, MS_TRUE, NULL,
2287
                          NULL, NULL, NULL, NULL, "        ");
2288
      }
2289
      msIO_printf("%s</Attribution>\n", tabspace);
3✔
2290
    }
2291
  }
2292
}
244✔
2293

2294
/*
2295
** msWMSPrintScaleHint()
2296
**
2297
** Print a Min/MaxScaleDenominator tag for the layer if applicable.
2298
** used for WMS >=1.3.0
2299
*/
2300
void msWMSPrintScaleDenominator(const char *tabspace, double minscaledenom,
122✔
2301
                                double maxscaledenom) {
2302
  if (minscaledenom > 0)
122✔
2303
    msIO_printf("%s<MinScaleDenominator>%g</MinScaleDenominator>\n", tabspace,
8✔
2304
                minscaledenom);
2305

2306
  if (maxscaledenom > 0)
122✔
2307
    msIO_printf("%s<MaxScaleDenominator>%g</MaxScaleDenominator>\n", tabspace,
2✔
2308
                maxscaledenom);
2309
}
122✔
2310

2311
/*
2312
** msWMSPrintScaleHint()
2313
**
2314
** Print a ScaleHint tag for this layer if applicable.
2315
**
2316
** (see WMS 1.1.0 sect. 7.1.5.4) The WMS defines the scalehint values as
2317
** the ground distance in meters of the southwest to northeast diagonal of
2318
** the central pixel of a map.  ScaleHint values are the min and max
2319
** recommended values of that diagonal.
2320
*/
2321
void msWMSPrintScaleHint(const char *tabspace, double minscaledenom,
124✔
2322
                         double maxscaledenom, double resolution) {
2323
  double scalehintmin = 0.0, scalehintmax = 0.0;
2324

2325
  const double diag = sqrt(2.0);
2326

2327
  if (minscaledenom > 0)
124✔
2328
    scalehintmin =
5✔
2329
        diag * (minscaledenom / resolution) / msInchesPerUnit(MS_METERS, 0);
5✔
2330
  if (maxscaledenom > 0)
124✔
2331
    scalehintmax =
2✔
2332
        diag * (maxscaledenom / resolution) / msInchesPerUnit(MS_METERS, 0);
2✔
2333

2334
  if (scalehintmin > 0.0 || scalehintmax > 0.0) {
124✔
2335
    msIO_printf("%s<ScaleHint min=\"%.15g\" max=\"%.15g\" />\n", tabspace,
5✔
2336
                scalehintmin, scalehintmax);
2337
    if (scalehintmax == 0.0)
5✔
2338
      msIO_printf("%s<!-- WARNING: Only MINSCALEDENOM and no MAXSCALEDENOM "
3✔
2339
                  "specified in "
2340
                  "the mapfile. A default value of 0 has been returned for the "
2341
                  "Max ScaleHint but this is probably not what you want. -->\n",
2342
                  tabspace);
2343
  }
2344
}
124✔
2345

2346
/*
2347
** msWMSPrintAuthorityURL()
2348
**
2349
** Print an AuthorityURL tag if applicable.
2350
*/
2351
void msWMSPrintAuthorityURL(FILE *stream, const char *tabspace,
244✔
2352
                            hashTableObj *metadata, const char *namespaces) {
2353
  if (stream && metadata) {
244✔
2354
    const char *pszWmsAuthorityName =
2355
        msOWSLookupMetadata(metadata, namespaces, "authorityurl_name");
244✔
2356
    const char *pszWMSAuthorityHref =
2357
        msOWSLookupMetadata(metadata, namespaces, "authorityurl_href");
244✔
2358

2359
    /* AuthorityURL only makes sense if you have *both* the name and url */
2360
    if (pszWmsAuthorityName && pszWMSAuthorityHref) {
244✔
2361
      msOWSPrintEncodeMetadata(
7✔
2362
          stream, metadata, namespaces, "authorityurl_name", OWS_NOERR,
2363
          (std::string(tabspace) + "<AuthorityURL name=\"%s\">\n").c_str(),
7✔
2364
          NULL);
2365
      msOWSPrintEncodeMetadata(
7✔
2366
          stream, metadata, namespaces, "authorityurl_href", OWS_NOERR,
2367
          (std::string(tabspace) +
7✔
2368
           "  <OnlineResource xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
2369
           "xlink:href=\"%s\"/>\n")
2370
              .c_str(),
2371
          NULL);
2372
      msIO_printf("%s</AuthorityURL>\n", tabspace);
7✔
2373
    } else if (pszWmsAuthorityName || pszWMSAuthorityHref) {
237✔
UNCOV
2374
      msIO_printf(
×
2375
          "%s<!-- WARNING: Both wms_authorityurl_name and "
2376
          "wms_authorityurl_href must be set to output an AuthorityURL -->\n",
2377
          tabspace);
2378
    }
2379
  }
2380
}
244✔
2381

2382
/*
2383
** msWMSPrintIdentifier()
2384
**
2385
** Print an Identifier tag if applicable.
2386
*/
2387
void msWMSPrintIdentifier(FILE *stream, const char *tabspace,
244✔
2388
                          hashTableObj *metadata, const char *namespaces) {
2389
  if (stream && metadata) {
244✔
2390
    const char *pszWMSIdentifierAuthority =
2391
        msOWSLookupMetadata(metadata, namespaces, "identifier_authority");
244✔
2392
    const char *pszWMSIdentifierValue =
2393
        msOWSLookupMetadata(metadata, namespaces, "identifier_value");
244✔
2394

2395
    /* Identifier only makes sense if you have *both* the authority and value */
2396
    if (pszWMSIdentifierAuthority && pszWMSIdentifierValue) {
244✔
2397
      msOWSPrintEncodeMetadata(
73✔
2398
          stream, metadata, namespaces, "identifier_authority", OWS_NOERR,
2399
          (std::string(tabspace) + "<Identifier authority=\"%s\">").c_str(),
73✔
2400
          NULL);
2401
      msOWSPrintEncodeMetadata(stream, metadata, namespaces, "identifier_value",
73✔
2402
                               OWS_NOERR, "%s</Identifier>\n", NULL);
2403
    } else if (pszWMSIdentifierAuthority || pszWMSIdentifierValue) {
171✔
UNCOV
2404
      msIO_printf(
×
2405
          "%s<!-- WARNING: Both wms_identifier_authority and "
2406
          "wms_identifier_value must be set to output an Identifier -->\n",
2407
          tabspace);
2408
    }
2409
  }
2410
}
244✔
2411

2412
/*
2413
** msWMSPrintKeywordlist()
2414
**
2415
** Print a Keywordlist tag if applicable.
2416
*/
2417
void msWMSPrintKeywordlist(FILE *stream, const char *tabspace, const char *name,
308✔
2418
                           hashTableObj *metadata, const char *namespaces,
2419
                           int nVersion) {
2420
  std::string newname(name); /* max. rootlayer_keywordlist_items          */
308✔
2421
  newname += "_items";
2422

2423
  std::string vocname(name); /* max. rootlayer_keywordlist_vocabulary     */
308✔
2424
  vocname += "_vocabulary";
2425

2426
  if (nVersion == OWS_1_0_0) {
308✔
2427
    /* <Keywords> in V 1.0.0 */
2428
    /* The 1.0.0 spec doesn't specify which delimiter to use so let's use spaces
2429
     */
2430
    msOWSPrintEncodeMetadataList(
4✔
2431
        stream, metadata, namespaces, name,
2432
        (std::string(tabspace) + "<Keywords>").c_str(),
8✔
2433
        (std::string(tabspace) + "</Keywords>\n").c_str(), "%s ", NULL);
8✔
2434
  } else if (msOWSLookupMetadata(metadata, namespaces, name) ||
435✔
2435
             msOWSLookupMetadata(metadata, namespaces, newname.c_str()) ||
435✔
2436
             msOWSLookupMetadata(metadata, namespaces, vocname.c_str())) {
131✔
2437
    /* <KeywordList><Keyword> ... in V1.0.6+ */
2438
    msIO_printf("%s<KeywordList>\n", tabspace);
182✔
2439
    std::string template1(tabspace);
182✔
2440
    template1 += "    <Keyword>%s</Keyword>\n";
2441
    /* print old styled ..._keywordlist */
2442
    msOWSPrintEncodeMetadataList(stream, metadata, namespaces, name, NULL, NULL,
182✔
2443
                                 template1.c_str(), NULL);
2444
    /* print new styled ..._keywordlist_items */
2445
    msOWSPrintEncodeMetadataList(stream, metadata, namespaces, newname.c_str(),
182✔
2446
                                 NULL, NULL, template1.c_str(), NULL);
2447

2448
    /* find out if there's a vocabulary list set */
2449
    const char *vocabularylist =
2450
        msOWSLookupMetadata(metadata, namespaces, vocname.c_str());
182✔
2451
    if (vocabularylist && nVersion >= OWS_1_3_0) {
182✔
2452
      const auto tokens = msStringSplit(vocabularylist, ',');
9✔
2453
      for (const auto &token : tokens) {
18✔
2454
        msOWSPrintEncodeMetadataList(
9✔
2455
            stream, metadata, namespaces,
2456
            (std::string(name) + '_' + token + "_items").c_str(), NULL, NULL,
18✔
2457
            (std::string(tabspace) + "    <Keyword vocabulary=\"" + token +
18✔
2458
             "\">%s</Keyword>\n")
2459
                .c_str(),
2460
            NULL);
2461
      }
2462
    }
9✔
2463
    msIO_printf("%s</KeywordList>\n", tabspace);
182✔
2464
  }
2465
}
308✔
2466

2467
/*
2468
** msWMSDumpLayer()
2469
*/
2470
static void msWMSDumpLayer(mapObj *map, const msWMSLayerNode *node,
189✔
2471
                           int nVersion, const char *script_url_encoded,
2472
                           const char *validated_language,
2473
                           const std::string &indent) {
2474
  rectObj ext;
2475
  char **classgroups = NULL;
2476
  int iclassgroups = 0;
2477
  char *pszMapEPSG, *pszLayerEPSG;
2478

2479
  layerObj *lp = GET_LAYER(map, node->layerIdx);
189✔
2480

2481
  /* if the layer status is set to MS_DEFAULT, output a warning */
2482
  if (lp->status == MS_DEFAULT)
189✔
2483
    msIO_fprintf(stdout,
4✔
2484
                 "<!-- WARNING: This layer has its status set to DEFAULT and "
2485
                 "will always be displayed when doing a GetMap request even if "
2486
                 "it is not requested by the client. This is not in line with "
2487
                 "the expected behavior of a WMS server. Using status ON or "
2488
                 "OFF is recommended. -->\n");
2489

2490
  if (nVersion < OWS_1_1_0) {
189✔
2491
    msIO_printf("%s<Layer queryable=\"%d\">\n", indent.c_str(),
1✔
2492
                node->isQueryable(map));
1✔
2493
  } else {
2494
    /* 1.1.0 and later: opaque and cascaded are new. */
2495
    int cascaded = 0, opaque = 0;
2496
    const char *value = msOWSLookupMetadata(&(lp->metadata), "MO", "opaque");
188✔
2497
    if (value != NULL)
188✔
2498
      opaque = atoi(value);
2499
    if (lp->connectiontype == MS_WMS)
188✔
2500
      cascaded = 1;
2501

2502
    msIO_printf("%s<Layer queryable=\"%d\" opaque=\"%d\" cascaded=\"%d\">\n",
188✔
2503
                indent.c_str(), node->isQueryable(map), opaque, cascaded);
188✔
2504
  }
2505

2506
  if (lp->name && strlen(lp->name) > 0 &&
378✔
2507
      (msIsXMLTagValid(lp->name) == MS_FALSE || isdigit(lp->name[0])))
378✔
UNCOV
2508
    msIO_fprintf(stdout,
×
2509
                 "<!-- WARNING: The layer name '%s' might contain spaces or "
2510
                 "invalid characters or may start with a number. This could "
2511
                 "lead to potential problems. -->\n",
2512
                 lp->name);
2513
  msOWSPrintEncodeParam(stdout, "LAYER.NAME", lp->name, OWS_NOERR,
189✔
2514
                        (indent + "  <Name>%s</Name>\n").c_str(), NULL);
189✔
2515

2516
  /* the majority of this section is dependent on appropriately named metadata
2517
   * in the LAYER object */
2518
  msOWSPrintEncodeMetadata2(stdout, &(lp->metadata), "MO", "title", OWS_WARN,
189✔
2519
                            (indent + "  <Title>%s</Title>\n").c_str(),
378✔
2520
                            lp->name, validated_language);
189✔
2521

2522
  msOWSPrintEncodeMetadata2(stdout, &(lp->metadata), "MO", "abstract",
189✔
2523
                            OWS_NOERR,
2524
                            (indent + "  <Abstract>%s</Abstract>\n").c_str(),
189✔
2525
                            NULL, validated_language);
2526

2527
  msWMSPrintKeywordlist(stdout, (indent + "  ").c_str(), "keywordlist",
189✔
2528
                        &(lp->metadata), "MO", nVersion);
2529

2530
  msOWSGetEPSGProj(&(map->projection), &(map->web.metadata), "MO", MS_FALSE,
189✔
2531
                   &pszMapEPSG);
2532
  msOWSGetEPSGProj(&(lp->projection), &(lp->metadata), "MO", MS_FALSE,
189✔
2533
                   &pszLayerEPSG);
2534
  if (pszMapEPSG == NULL) {
189✔
2535
    /* If map has no proj then every layer MUST have one or produce a warning */
UNCOV
2536
    if (nVersion > OWS_1_1_0) {
×
2537
      /* starting 1.1.1 SRS are given in individual tags */
UNCOV
2538
      if (nVersion >= OWS_1_3_0) {
×
UNCOV
2539
        msOWSPrintEncodeParamList(stdout,
×
2540
                                  "(at least one of) "
2541
                                  "MAP.PROJECTION, LAYER.PROJECTION "
2542
                                  "or wms_srs metadata",
2543
                                  pszLayerEPSG, OWS_WARN, ' ', NULL, NULL,
UNCOV
2544
                                  (indent + "  <CRS>%s</CRS>\n").c_str(), NULL);
×
2545
      } else {
UNCOV
2546
        msOWSPrintEncodeParamList(stdout,
×
2547
                                  "(at least one of) "
2548
                                  "MAP.PROJECTION, LAYER.PROJECTION "
2549
                                  "or wms_srs metadata",
2550
                                  pszLayerEPSG, OWS_WARN, ' ', NULL, NULL,
UNCOV
2551
                                  (indent + "  <SRS>%s</SRS>\n").c_str(), NULL);
×
2552
      }
2553
    } else {
UNCOV
2554
      msOWSPrintEncodeParam(stdout,
×
2555
                            "(at least one of) MAP.PROJECTION, "
2556
                            "LAYER.PROJECTION or wms_srs metadata",
2557
                            pszLayerEPSG, OWS_WARN,
UNCOV
2558
                            (indent + "  <SRS>%s</SRS>\n").c_str(), NULL);
×
2559
    }
2560
  } else {
2561
    /* No warning required in this case since there's at least a map proj. */
2562
    if (nVersion > OWS_1_1_0) {
189✔
2563
      /* starting 1.1.1 SRS are given in individual tags */
2564
      if (nVersion >= OWS_1_3_0) {
174✔
2565
        msOWSPrintEncodeParamList(stdout,
93✔
2566
                                  "(at least one of) "
2567
                                  "MAP.PROJECTION, LAYER.PROJECTION "
2568
                                  "or wms_srs metadata",
2569
                                  pszLayerEPSG, OWS_NOERR, ' ', NULL, NULL,
2570
                                  (indent + "  <CRS>%s</CRS>\n").c_str(), NULL);
186✔
2571
      } else {
2572
        msOWSPrintEncodeParamList(stdout,
81✔
2573
                                  "(at least one of) "
2574
                                  "MAP.PROJECTION, LAYER.PROJECTION "
2575
                                  "or wms_srs metadata",
2576
                                  pszLayerEPSG, OWS_NOERR, ' ', NULL, NULL,
2577
                                  (indent + "  <SRS>%s</SRS>\n").c_str(), NULL);
162✔
2578
      }
2579
    } else {
2580
      msOWSPrintEncodeParam(stdout, " LAYER.PROJECTION (or wms_srs metadata)",
15✔
2581
                            pszLayerEPSG, OWS_NOERR,
2582
                            (indent + "  <SRS>%s</SRS>\n").c_str(), NULL);
30✔
2583
    }
2584
  }
2585
  msFree(pszLayerEPSG);
189✔
2586
  msFree(pszMapEPSG);
189✔
2587

2588
  /* If layer has no proj set then use map->proj for bounding box. */
2589
  if (msOWSGetLayerExtent(map, lp, "MO", &ext) == MS_SUCCESS) {
189✔
2590
    if (lp->projection.numargs > 0) {
176✔
2591
      if (nVersion >= OWS_1_3_0)
167✔
2592
        msOWSPrintEX_GeographicBoundingBox(stdout, (indent + "  ").c_str(),
164✔
2593
                                           &(ext), &(lp->projection));
2594
      else
2595
        msOWSPrintLatLonBoundingBox(stdout, (indent + "  ").c_str(), &(ext),
170✔
2596
                                    &(lp->projection), NULL, OWS_WMS);
2597

2598
      msOWSPrintBoundingBox(stdout, (indent + "  ").c_str(), &(ext),
334✔
2599
                            &(lp->projection), &(lp->metadata),
2600
                            &(map->web.metadata), "MO", nVersion);
2601
    } else {
2602
      if (nVersion >= OWS_1_3_0)
9✔
2603
        msOWSPrintEX_GeographicBoundingBox(stdout, (indent + "  ").c_str(),
16✔
2604
                                           &(ext), &(map->projection));
2605
      else
2606
        msOWSPrintLatLonBoundingBox(stdout, (indent + "  ").c_str(), &(ext),
2✔
2607
                                    &(map->projection), NULL, OWS_WMS);
2608
      msOWSPrintBoundingBox(stdout, (indent + "  ").c_str(), &(ext),
18✔
2609
                            &(map->projection), &(lp->metadata),
2610
                            &(map->web.metadata), "MO", nVersion);
2611
    }
2612
  } else {
2613
    if (nVersion >= OWS_1_3_0)
13✔
2614
      msIO_printf(
3✔
2615
          (indent + "  %s").c_str(),
6✔
2616
          "<!-- WARNING: Optional Ex_GeographicBoundingBox could not "
2617
          "be established for this layer.  Consider setting the EXTENT in the "
2618
          "LAYER object, or wms_extent metadata. Also check that your data "
2619
          "exists in the DATA statement -->\n");
2620
    else
2621
      msIO_printf((indent + "  %s").c_str(),
20✔
2622
                  "<!-- WARNING: Optional LatLonBoundingBox could not "
2623
                  "be established for this layer.  Consider setting the EXTENT "
2624
                  "in the LAYER object, or wms_extent metadata. Also check "
2625
                  "that your data exists in the DATA statement -->\n");
2626
  }
2627

2628
  /* time support */
2629
  const char *pszWmsTimeExtent =
2630
      msOWSLookupMetadata(&(lp->metadata), "MO", "timeextent");
189✔
2631
  if (pszWmsTimeExtent) {
189✔
2632
    const char *pszWmsTimeDefault =
2633
        msOWSLookupMetadata(&(lp->metadata), "MO", "timedefault");
4✔
2634

2635
    if (nVersion >= OWS_1_3_0) {
4✔
2636
      if (pszWmsTimeDefault)
2✔
UNCOV
2637
        msIO_fprintf(stdout,
×
UNCOV
2638
                     (indent +
×
2639
                      "  <Dimension name=\"time\" units=\"ISO8601\" "
2640
                      "default=\"%s\" nearestValue=\"0\">%s</Dimension>\n")
2641
                         .c_str(),
2642
                     pszWmsTimeDefault, pszWmsTimeExtent);
2643
      else
2644
        msIO_fprintf(stdout,
2✔
2645
                     (indent + "  <Dimension name=\"time\" units=\"ISO8601\" "
4✔
2646
                               "nearestValue=\"0\">%s</Dimension>\n")
2647
                         .c_str(),
2648
                     pszWmsTimeExtent);
2649
    }
2650

2651
    else {
2652
      msIO_fprintf(stdout, "%s",
2✔
2653
                   (indent + "  <Dimension name=\"time\" units=\"ISO8601\"/>\n")
2✔
2654
                       .c_str());
2655
      if (pszWmsTimeDefault)
2✔
UNCOV
2656
        msIO_fprintf(stdout,
×
UNCOV
2657
                     (indent + "  <Extent name=\"time\" default=\"%s\" "
×
2658
                               "nearestValue=\"0\">%s</Extent>\n")
2659
                         .c_str(),
2660
                     pszWmsTimeDefault, pszWmsTimeExtent);
2661
      else
2662
        msIO_fprintf(
2✔
2663
            stdout,
2664
            (indent +
4✔
2665
             "  <Extent name=\"time\" nearestValue=\"0\">%s</Extent>\n")
2666
                .c_str(),
2667
            pszWmsTimeExtent);
2668
    }
2669
  }
2670

2671
  /*dimensions support: elevation + other user defined dimensions*/
2672
  const char *pszDimensionlist =
2673
      msOWSLookupMetadata(&(lp->metadata), "M", "dimensionlist");
189✔
2674
  if (pszDimensionlist) {
189✔
2675
    auto tokens = msStringSplit(pszDimensionlist, ',');
6✔
2676
    for (auto &dimension : tokens) {
14✔
2677
      /*check if manadatory unit and extent are set. Item should also be set.
2678
       * default value is optional*/
2679
      msStringTrim(dimension);
8✔
2680

2681
      const char *pszDimensionItem = msOWSLookupMetadata(
8✔
2682
          &(lp->metadata), "M", (dimension + "_item").c_str());
8✔
2683
      const char *pszDimensionExtent = msOWSLookupMetadata(
8✔
2684
          &(lp->metadata), "M", (dimension + "_extent").c_str());
8✔
2685
      const char *pszDimensionUnit = msOWSLookupMetadata(
8✔
2686
          &(lp->metadata), "M", (dimension + "_units").c_str());
8✔
2687
      const char *pszDimensionDefault = msOWSLookupMetadata(
8✔
2688
          &(lp->metadata), "M", (dimension + "_default").c_str());
8✔
2689

2690
      if (pszDimensionItem && pszDimensionExtent && pszDimensionUnit) {
8✔
2691
        if (nVersion >= OWS_1_3_0) {
8✔
2692
          if (pszDimensionDefault && strlen(pszDimensionDefault) > 0)
4✔
2693
            msIO_fprintf(
1✔
2694
                stdout,
2695
                (indent +
2✔
2696
                 "  <Dimension name=\"%s\" units=\"%s\" default=\"%s\" "
2697
                 "multipleValues=\"1\" nearestValue=\"0\">%s</Dimension>\n")
2698
                    .c_str(),
2699
                dimension.c_str(), pszDimensionUnit, pszDimensionDefault,
2700
                pszDimensionExtent);
2701
          else
2702
            msIO_fprintf(
3✔
2703
                stdout,
2704
                (indent +
6✔
2705
                 "  <Dimension name=\"%s\" units=\"%s\"  "
2706
                 "multipleValues=\"1\"  nearestValue=\"0\">%s</Dimension>\n")
2707
                    .c_str(),
2708
                dimension.c_str(), pszDimensionUnit, pszDimensionExtent);
2709
        } else {
2710
          msIO_fprintf(
4✔
2711
              stdout,
2712
              (indent + "  <Dimension name=\"%s\" units=\"%s\"/>\n").c_str(),
4✔
2713
              dimension.c_str(), pszDimensionUnit);
2714
          if (pszDimensionDefault && strlen(pszDimensionDefault) > 0)
4✔
2715
            msIO_fprintf(stdout,
1✔
2716
                         (indent + "  <Extent name=\"%s\" default=\"%s\" "
2✔
2717
                                   "nearestValue=\"0\">%s</Extent>\n")
2718
                             .c_str(),
2719
                         dimension.c_str(), pszDimensionDefault,
2720
                         pszDimensionExtent);
2721
          else
2722
            msIO_fprintf(
3✔
2723
                stdout,
2724
                (indent +
6✔
2725
                 "  <Extent name=\"%s\" nearestValue=\"0\">%s</Extent>\n")
2726
                    .c_str(),
2727
                dimension.c_str(), pszDimensionExtent);
2728
        }
2729
      }
2730
    }
2731
  }
6✔
2732

2733
  /* AuthorityURL support and Identifier support, only available >= WMS 1.1.0 */
2734
  if (nVersion >= OWS_1_1_0) {
189✔
2735
    msWMSPrintAttribution(stdout, (indent + "  ").c_str(), &(lp->metadata),
188✔
2736
                          "MO");
2737
    msWMSPrintAuthorityURL(stdout, (indent + "  ").c_str(), &(lp->metadata),
188✔
2738
                           "MO");
2739
    msWMSPrintIdentifier(stdout, (indent + "  ").c_str(), &(lp->metadata),
188✔
2740
                         "MO");
2741
  }
2742

2743
  if (nVersion >= OWS_1_1_0) {
2744
    const char *metadataurl_list =
2745
        msOWSLookupMetadata(&(lp->metadata), "MO", "metadataurl_list");
188✔
2746
    if (metadataurl_list) {
188✔
2747
      const auto tokens = msStringSplit(metadataurl_list, ' ');
1✔
2748
      for (const auto &token : tokens) {
3✔
2749
        std::string key("metadataurl_");
2✔
2750
        key += token;
2751
        msOWSPrintURLType(stdout, &(lp->metadata), "MO", key.c_str(), OWS_NOERR,
2✔
2752
                          NULL, "MetadataURL", " type=\"%s\"", NULL, NULL,
2753
                          ">\n          <Format>%s</Format",
2754
                          "\n        <OnlineResource xmlns:xlink=\""
2755
                          "http://www.w3.org/1999/xlink\" "
2756
                          "xlink:type=\"simple\" xlink:href=\"%s\"/>\n        ",
2757
                          MS_TRUE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
2758
                          NULL, NULL, NULL, NULL, (indent + "  ").c_str());
4✔
2759
      }
2760
    } else {
1✔
2761
      if (!msOWSLookupMetadata(&(lp->metadata), "MO", "metadataurl_href"))
187✔
2762
        msMetadataSetGetMetadataURL(lp, script_url_encoded);
98✔
2763

2764
      msOWSPrintURLType(stdout, &(lp->metadata), "MO", "metadataurl", OWS_NOERR,
187✔
2765
                        NULL, "MetadataURL", " type=\"%s\"", NULL, NULL,
2766
                        (">\n" + indent + "    <Format>%s</Format").c_str(),
374✔
2767
                        ("\n" + indent +
187✔
2768
                         "    <OnlineResource xmlns:xlink=\""
2769
                         "http://www.w3.org/1999/xlink\" "
2770
                         "xlink:type=\"simple\" xlink:href=\"%s\"/>\n" +
187✔
2771
                         indent + "  ")
187✔
2772
                            .c_str(),
2773
                        MS_TRUE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
2774
                        NULL, NULL, NULL, NULL, (indent + "  ").c_str());
374✔
2775
    }
2776
  }
2777

2778
  if (nVersion < OWS_1_1_0)
2779
    msOWSPrintEncodeMetadata(
1✔
2780
        stdout, &(lp->metadata), "MO", "dataurl_href", OWS_NOERR,
2781
        (indent + "  <DataURL>%s</DataURL>\n").c_str(), NULL);
2✔
2782
  else {
2783
    const char *dataurl_list =
2784
        msOWSLookupMetadata(&(lp->metadata), "MO", "dataurl_list");
188✔
2785
    if (dataurl_list) {
188✔
2786
      const auto tokens = msStringSplit(dataurl_list, ' ');
1✔
2787
      for (const auto &token : tokens) {
3✔
2788
        std::string key("dataurl_");
2✔
2789
        key += token;
2790
        msOWSPrintURLType(stdout, &(lp->metadata), "MO", key.c_str(), OWS_NOERR,
2✔
2791
                          NULL, "DataURL", NULL, NULL, NULL,
2792
                          (">\n" + indent + "    <Format>%s</Format").c_str(),
4✔
2793
                          ("\n" + indent +
2✔
2794
                           "    <OnlineResource xmlns:xlink=\""
2795
                           "http://www.w3.org/1999/xlink\" "
2796
                           "xlink:type=\"simple\" xlink:href=\"%s\"/>\n" +
2✔
2797
                           indent + "  ")
2✔
2798
                              .c_str(),
2799
                          MS_FALSE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
2800
                          NULL, NULL, NULL, NULL, (indent + "  ").c_str());
4✔
2801
      }
2802
    } else {
1✔
2803
      msOWSPrintURLType(stdout, &(lp->metadata), "MO", "dataurl", OWS_NOERR,
187✔
2804
                        NULL, "DataURL", NULL, NULL, NULL,
2805
                        (">\n" + indent + "    <Format>%s</Format").c_str(),
374✔
2806
                        ("\n" + indent +
187✔
2807
                         "    <OnlineResource xmlns:xlink=\""
2808
                         "http://www.w3.org/1999/xlink\" "
2809
                         "xlink:type=\"simple\" xlink:href=\"%s\"/>\n" +
187✔
2810
                         indent + "  ")
187✔
2811
                            .c_str(),
2812
                        MS_FALSE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
2813
                        NULL, NULL, NULL, NULL, (indent + "  ").c_str());
374✔
2814
    }
2815
  }
2816

2817
  /* The LegendURL reside in a style. The Web Map Context spec already  */
2818
  /* included the support on this in mapserver. However, it is not in the  */
2819
  /* wms_legendurl_... metadatas it's in the styles metadata, */
2820
  /* In wms_style_<style_name>_lengendurl_... metadata. So we have to detect */
2821
  /* the current style before reading it. Also in the Style block, we need */
2822
  /* a Title and a name. Title is derived from wms_style_<style>_title, */
2823
  /* which allows multiple style definitions, e.g. by using classgroups. */
2824
  const char *pszStyle = msOWSLookupMetadata(&(lp->metadata), "MO", "style");
189✔
2825
  const char *pszLegendURL = NULL;
2826
  if (pszStyle) {
189✔
UNCOV
2827
    pszLegendURL = msOWSLookupMetadata(
×
2828
        &(lp->metadata), "MO",
UNCOV
2829
        (std::string("style_") + pszStyle + "_legendurl_href").c_str());
×
2830
  } else
2831
    pszStyle = "default";
2832

2833
  if (nVersion <= OWS_1_0_0 && pszLegendURL) {
189✔
2834
    /* First, print the style block */
UNCOV
2835
    msIO_fprintf(stdout, "%s", (indent + "  <Style>\n").c_str());
×
UNCOV
2836
    msIO_fprintf(stdout, (indent + "    <Name>%s</Name>\n").c_str(), pszStyle);
×
2837
    /* Print the real Title or Style name otherwise */
UNCOV
2838
    msOWSPrintEncodeMetadata2(
×
2839
        stdout, &(lp->metadata), "MO",
UNCOV
2840
        (std::string("style_") + pszStyle + "_title").c_str(), OWS_NOERR,
×
UNCOV
2841
        (indent + "    <Title>%s</Title>\n").c_str(), pszStyle,
×
2842
        validated_language);
2843

2844
    /* Inside, print the legend url block */
UNCOV
2845
    msOWSPrintEncodeMetadata(
×
2846
        stdout, &(lp->metadata), "MO",
UNCOV
2847
        (std::string("style_") + pszStyle + "_legendurl_href").c_str(),
×
UNCOV
2848
        OWS_NOERR, (indent + "    <StyleURL>%s</StyleURL>\n").c_str(), NULL);
×
2849

2850
    /* close the style block */
2851
    msIO_fprintf(stdout, "%s", (indent + "  </Style>\n").c_str());
×
2852

2853
  } else if (nVersion >= OWS_1_1_0) {
189✔
2854
    if (pszLegendURL) {
188✔
2855
      /* First, print the style block */
UNCOV
2856
      msIO_fprintf(stdout, "%s", (indent + "  <Style>\n").c_str());
×
UNCOV
2857
      msIO_fprintf(stdout, (indent + "    <Name>%s</Name>\n").c_str(),
×
2858
                   pszStyle);
2859
      /* Print the real Title or Style name otherwise */
2860
      msOWSPrintEncodeMetadata2(
×
2861
          stdout, &(lp->metadata), "MO",
2862
          (std::string("style_") + pszStyle + "_title").c_str(), OWS_NOERR,
×
UNCOV
2863
          (indent + "    <Title>%s</Title>\n").c_str(), pszStyle,
×
2864
          validated_language);
2865

2866
      /* Inside, print the legend url block */
UNCOV
2867
      msOWSPrintURLType(
×
2868
          stdout, &(lp->metadata), "MO",
2869
          (std::string("style_") + pszStyle + "_legendurl").c_str(), OWS_NOERR,
×
2870
          NULL, "LegendURL", NULL, " width=\"%s\"", " height=\"%s\"",
2871
          ">\n             <Format>%s</Format",
2872
          "\n             <OnlineResource "
2873
          "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
2874
          " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
2875
          "          ",
2876
          MS_FALSE, MS_TRUE, MS_TRUE, MS_TRUE, MS_TRUE, NULL, NULL, NULL, NULL,
2877
          NULL, "          ");
UNCOV
2878
      msIO_fprintf(stdout, "%s", (indent + "  </Style>\n").c_str());
×
2879

2880
    } else {
2881
      if (script_url_encoded) {
188✔
2882
        if (lp->connectiontype != MS_WMS && lp->connectiontype != MS_WFS &&
188✔
2883
            lp->connectiontype != MS_UNUSED_1 && lp->numclasses > 0) {
187✔
2884
          bool classnameset = false;
2885
          for (int i = 0; i < lp->numclasses; i++) {
187✔
2886
            if (lp->_class[i]->name && strlen(lp->_class[i]->name) > 0) {
169✔
2887
              classnameset = true;
2888
              break;
2889
            }
2890
          }
2891
          if (classnameset) {
166✔
2892
            int size_x = 0, size_y = 0;
148✔
2893
            std::vector<int> group_layers;
2894
            group_layers.reserve(map->numlayers);
148✔
2895

2896
            group_layers.push_back(lp->index);
148✔
2897
            for (int j : node->collectLayerIndices()) {
314✔
2898
              if (j != lp->index) {
166✔
2899
                group_layers.push_back(j);
18✔
2900
              }
2901
            }
148✔
2902

2903
            if (msLegendCalcSize(map, 1, &size_x, &size_y, group_layers.data(),
148✔
2904
                                 static_cast<int>(group_layers.size()), NULL,
2905
                                 1) == MS_SUCCESS) {
2906
              const std::string width(std::to_string(size_x));
148✔
2907
              const std::string height(std::to_string(size_y));
148✔
2908

2909
              char *mimetype = NULL;
2910
#if defined USE_PNG
2911
              mimetype = msEncodeHTMLEntities("image/png");
148✔
2912
#endif
2913

2914
#if defined USE_JPEG
2915
              if (!mimetype)
148✔
UNCOV
2916
                mimetype = msEncodeHTMLEntities("image/jpeg");
×
2917
#endif
UNCOV
2918
              if (!mimetype)
×
2919
                mimetype =
UNCOV
2920
                    msEncodeHTMLEntities(MS_IMAGE_MIME_TYPE(map->outputformat));
×
2921

2922
              /* --------------------------------------------------------------------
2923
               */
2924
              /*      check if the group parameters for the classes are set. We
2925
               */
2926
              /*      should then publish the different class groups as
2927
               * different styles.*/
2928
              /* --------------------------------------------------------------------
2929
               */
2930
              iclassgroups = 0;
2931
              classgroups = NULL;
2932

2933
              const char *styleName =
2934
                  msOWSLookupMetadata(&(map->web.metadata), "MO", "style_name");
148✔
2935
              if (styleName == NULL)
148✔
2936
                styleName = "default";
2937
              char *pszEncodedStyleName = msEncodeHTMLEntities(styleName);
148✔
2938

2939
              for (int i = 0; i < lp->numclasses; i++) {
382✔
2940
                if (lp->_class[i]->name && lp->_class[i]->group) {
234✔
2941
                  /* Check that style is not inherited from root layer (#4442).
2942
                   */
2943
                  if (strcasecmp(pszEncodedStyleName, lp->_class[i]->group) ==
180✔
2944
                      0)
2945
                    continue;
102✔
2946
                  /* Check that style is not inherited from group layer(s)
2947
                   * (#4442). */
2948
                  bool styleInheritedFromParent = false;
2949
                  for (const msWMSLayerNode *cur = node->parent;
78✔
2950
                       cur && !styleInheritedFromParent; cur = cur->parent) {
222✔
2951
                    if (cur->layerIdx >= 0) {
144✔
2952
                      layerObj *lp2 = GET_LAYER(map, cur->layerIdx);
72✔
2953
                      for (int l = 0; l < lp2->numclasses; l++) {
90✔
2954
                        if (strcasecmp(lp2->_class[l]->group,
24✔
2955
                                       lp->_class[i]->group) == 0) {
2956
                          styleInheritedFromParent = true;
2957
                          break;
2958
                        }
2959
                      }
2960
                    }
2961
                  }
2962
                  if (styleInheritedFromParent) {
78✔
2963
                    continue;
6✔
2964
                  }
2965
                  if (!classgroups) {
72✔
2966
                    classgroups = (char **)msSmallMalloc(sizeof(char *));
72✔
2967
                    classgroups[iclassgroups++] =
72✔
2968
                        msStrdup(lp->_class[i]->group);
72✔
2969
                  } else {
2970
                    /* Output style only once. */
2971
                    bool found = false;
UNCOV
2972
                    for (int j = 0; j < iclassgroups; j++) {
×
UNCOV
2973
                      if (strcasecmp(classgroups[j], lp->_class[i]->group) ==
×
2974
                          0) {
2975
                        found = true;
2976
                        break;
2977
                      }
2978
                    }
UNCOV
2979
                    if (!found) {
×
UNCOV
2980
                      iclassgroups++;
×
UNCOV
2981
                      classgroups = (char **)msSmallRealloc(
×
UNCOV
2982
                          classgroups, sizeof(char *) * iclassgroups);
×
UNCOV
2983
                      classgroups[iclassgroups - 1] =
×
UNCOV
2984
                          msStrdup(lp->_class[i]->group);
×
2985
                    }
2986
                  }
2987
                }
2988
              }
2989
              msFree(pszEncodedStyleName);
148✔
2990
              if (classgroups == NULL) {
148✔
2991
                classgroups = (char **)msSmallMalloc(sizeof(char *));
76✔
2992
                classgroups[0] = msStrdup("default");
76✔
2993
                iclassgroups = 1;
2994
              }
2995

2996
              for (int i = 0; i < iclassgroups; i++) {
296✔
2997
                char *name_encoded = msEncodeHTMLEntities(lp->name);
148✔
2998
                char *classgroup_encoded = msEncodeHTMLEntities(classgroups[i]);
148✔
2999
                std::string legendurl(script_url_encoded);
148✔
3000
                legendurl += "version=";
3001
                char szVersionBuf[OWS_VERSION_MAXLEN];
3002
                legendurl += msOWSGetVersionString(nVersion, szVersionBuf);
148✔
3003
                legendurl +=
3004
                    "&amp;service=WMS&amp;request=GetLegendGraphic&amp;";
3005
                if (nVersion >= OWS_1_3_0) {
148✔
3006
                  legendurl += "sld_version=1.1.0&amp;layer=";
3007
                } else {
3008
                  legendurl += "layer=";
3009
                }
3010
                legendurl += name_encoded;
3011
                legendurl += "&amp;format=";
3012
                legendurl += mimetype;
3013
                legendurl += "&amp;STYLE=";
3014
                legendurl += classgroup_encoded;
3015

3016
                msFree(name_encoded);
148✔
3017
                msFree(classgroup_encoded);
148✔
3018

3019
                msIO_fprintf(stdout, "        <Style>\n");
148✔
3020
                msIO_fprintf(stdout, "          <Name>%s</Name>\n",
148✔
3021
                             classgroups[i]);
3022
                msOWSPrintEncodeMetadata2(
148✔
3023
                    stdout, &(lp->metadata), "MO",
3024
                    (std::string("style_") + classgroups[i] + "_title").c_str(),
148✔
3025
                    OWS_NOERR, "          <Title>%s</Title>\n", classgroups[i],
3026
                    validated_language);
3027

3028
                /* A legendurl from wms_style_<style>_legendurl_href will
3029
                 * override a self generated legend graphic */
3030
                pszLegendURL = msOWSLookupMetadata(
148✔
3031
                    &(lp->metadata), "MO",
3032
                    (std::string("style_") + classgroups[i] + "_legendurl_href")
148✔
3033
                        .c_str());
3034
                if (pszLegendURL) {
148✔
UNCOV
3035
                  msOWSPrintURLType(
×
3036
                      stdout, &(lp->metadata), "MO",
UNCOV
3037
                      (std::string("style_") + classgroups[i] + "_legendurl")
×
3038
                          .c_str(),
3039
                      OWS_NOERR, NULL, "LegendURL", NULL, " width=\"%s\"",
3040
                      " height=\"%s\"", ">\n             <Format>%s</Format",
3041
                      "\n             <OnlineResource "
3042
                      "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
3043
                      " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
3044
                      "          ",
3045
                      MS_FALSE, MS_TRUE, MS_TRUE, MS_TRUE, MS_TRUE, NULL, NULL,
3046
                      NULL, NULL, NULL, "          ");
3047
                } else {
3048
                  msOWSPrintURLType(
148✔
3049
                      stdout, NULL, "O", "ttt", OWS_NOERR, NULL, "LegendURL",
3050
                      NULL, " width=\"%s\"", " height=\"%s\"",
3051
                      ">\n             <Format>%s</Format",
3052
                      "\n             <OnlineResource "
3053
                      "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
3054
                      " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
3055
                      "          ",
3056
                      MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE, NULL,
3057
                      width.c_str(), height.c_str(), mimetype,
3058
                      legendurl.c_str(), "          ");
3059
                }
3060
                msIO_fprintf(stdout, "        </Style>\n");
148✔
3061
              }
3062
              msFreeCharArray(classgroups, iclassgroups);
148✔
3063
              msFree(mimetype);
148✔
3064
            }
3065
          }
148✔
3066
        }
3067
      }
3068
    }
3069
  }
3070

3071
  /* print Min/Max ScaleDenominator */
3072
  if (nVersion < OWS_1_3_0)
188✔
3073
    msWMSPrintScaleHint((indent + "  ").c_str(), lp->minscaledenom,
192✔
3074
                        lp->maxscaledenom, map->resolution);
3075
  else
3076
    msWMSPrintScaleDenominator((indent + "  ").c_str(), lp->minscaledenom,
186✔
3077
                               lp->maxscaledenom);
3078
}
189✔
3079

3080
/*
3081
** msWMSPrintGroupStyle()
3082
*/
3083
static void msWMSPrintGroupStyle(mapObj *map, int nVersion,
11✔
3084
                                 owsRequestObj *ows_request,
3085
                                 const msWMSLayerNode *node, layerObj *lp,
3086
                                 const char *script_url_encoded,
3087
                                 const std::string &indent) {
3088
  char *pszEncodedName = NULL;
3089
  const char *styleName = NULL;
3090
  char *pszEncodedStyleName = NULL;
3091
  const char *legendURL = NULL;
3092

3093
  pszEncodedName = msEncodeHTMLEntities(lp->group);
11✔
3094

3095
  styleName = msOWSLookupMetadata(&(lp->metadata), "MO", "group_style_name");
11✔
3096
  if (styleName == NULL)
11✔
3097
    styleName = "default";
3098

3099
  pszEncodedStyleName = msEncodeHTMLEntities(styleName);
11✔
3100

3101
  msIO_fprintf(stdout, "%s", (indent + "  <Style>\n").c_str());
11✔
3102
  msIO_fprintf(stdout, (indent + "    <Name>%s</Name>\n").c_str(),
11✔
3103
               pszEncodedStyleName);
3104
  msOWSPrintEncodeMetadata(
11✔
3105
      stdout, &(lp->metadata), "MO", "group_style_title", OWS_NOERR,
3106
      (indent + "    <Title>%s</Title>\n").c_str(), styleName);
11✔
3107

3108
  legendURL =
3109
      msOWSLookupMetadata(&(lp->metadata), "MO", "group_style_legendurl_href");
11✔
3110
  if (legendURL) {
11✔
3111
    msOWSPrintURLType(stdout, &(lp->metadata), "MO", "group_style_legendurl",
9✔
3112
                      OWS_NOERR, NULL, "LegendURL", NULL, " width=\"%s\"",
3113
                      " height=\"%s\"",
3114
                      (">\n" + indent + "    <Format>%s</Format").c_str(),
18✔
3115
                      ("\n" + indent +
9✔
3116
                       "    <OnlineResource "
3117
                       "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
3118
                       " xlink:type=\"simple\" xlink:href=\"%s\"/>\n" +
9✔
3119
                       indent + "  ")
9✔
3120
                          .c_str(),
3121
                      MS_FALSE, MS_TRUE, MS_TRUE, MS_TRUE, MS_TRUE, NULL, NULL,
3122
                      NULL, NULL, NULL, (indent + "  ").c_str());
18✔
3123
  } else {
3124
    std::vector<int> group_layers;
3125
    group_layers.reserve(map->numlayers);
2✔
3126
    for (int j : node->collectLayerIndices()) {
4✔
3127
      if (msIntegerInArray(GET_LAYER(map, j)->index,
2✔
3128
                           ows_request->enabled_layers,
3129
                           ows_request->numlayers)) {
3130
        group_layers.push_back(j);
2✔
3131
      }
3132
    }
2✔
3133

3134
    if (!group_layers.empty()) {
2✔
3135
      int size_x = 0, size_y = 0;
2✔
3136
      char *pszMimetype = NULL;
3137

3138
      if (msLegendCalcSize(map, 1, &size_x, &size_y, group_layers.data(),
2✔
3139
                           static_cast<int>(group_layers.size()), NULL,
3140
                           1) == MS_SUCCESS) {
3141
        const std::string width(std::to_string(size_x));
2✔
3142
        const std::string height(std::to_string(size_y));
2✔
3143

3144
        const char *format_list = msOWSLookupMetadata(
2✔
3145
            &(map->web.metadata), "M", "getlegendgraphic_formatlist");
2✔
3146
        if (format_list && strlen(format_list) > 0) {
2✔
UNCOV
3147
          const auto tokens = msStringSplit(format_list, ',');
×
UNCOV
3148
          if (!tokens.empty()) {
×
3149
            /*just grab the first mime type*/
UNCOV
3150
            pszMimetype = msEncodeHTMLEntities(tokens[0].c_str());
×
3151
          }
UNCOV
3152
        } else
×
3153
          pszMimetype = msEncodeHTMLEntities("image/png");
2✔
3154

3155
        std::string legendurl(script_url_encoded);
2✔
3156
        legendurl += "version=";
3157
        char szVersionBuf[OWS_VERSION_MAXLEN];
3158
        legendurl += msOWSGetVersionString(nVersion, szVersionBuf);
2✔
3159
        legendurl += "&amp;service=WMS&amp;request=GetLegendGraphic&amp;";
3160
        if (nVersion >= OWS_1_3_0) {
2✔
3161
          legendurl += "sld_version=1.1.0&amp;layer=";
3162
        } else {
3163
          legendurl += "layer=";
3164
        }
3165
        legendurl += pszEncodedName;
3166
        legendurl += "&amp;format=";
3167
        legendurl += pszMimetype;
3168
        legendurl += "&amp;STYLE=";
3169
        legendurl += pszEncodedStyleName;
3170

3171
        msOWSPrintURLType(stdout, NULL, "O", "ttt", OWS_NOERR, NULL,
2✔
3172
                          "LegendURL", NULL, " width=\"%s\"", " height=\"%s\"",
3173
                          ">\n          <Format>%s</Format",
3174
                          "\n          <OnlineResource "
3175
                          "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
3176
                          " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
3177
                          "       ",
3178
                          MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE,
3179
                          NULL, width.c_str(), height.c_str(), pszMimetype,
3180
                          legendurl.c_str(), "       ");
3181

3182
        msFree(pszMimetype);
2✔
3183
      }
3184
    }
3185
  }
2✔
3186
  msIO_fprintf(stdout, "%s", (indent + "  </Style>\n").c_str());
11✔
3187
  msFree(pszEncodedName);
11✔
3188
  msFree(pszEncodedStyleName);
11✔
3189
}
11✔
3190

3191
/*
3192
** msWMSPrintLayerNode()
3193
*/
3194
static void
3195
msWMSPrintLayerNode(mapObj *map, int nVersion, owsRequestObj *ows_request,
223✔
3196
                    const msWMSLayerNode *node, const char *script_url_encoded,
3197
                    const char *validated_language, int indentLevel) {
3198
  std::string indent;
3199
  for (int i = 0; i < indentLevel; i++) {
779✔
3200
    indent += "  ";
3201
  }
3202

3203
  bool closeLayerTag = false;
3204
  if (node->layerIdx >= 0) {
223✔
3205
    layerObj *lp = GET_LAYER(map, node->layerIdx);
202✔
3206
    if (lp->status != MS_DELETE &&
404✔
3207
        msIntegerInArray(lp->index, ows_request->enabled_layers,
202✔
3208
                         ows_request->numlayers)) {
3209
      closeLayerTag = true;
3210
      msWMSDumpLayer(map, node, nVersion, script_url_encoded,
189✔
3211
                     validated_language, indent);
3212
    }
3213
  } else {
3214
    closeLayerTag = true;
3215
    msIO_printf("%s<Layer%s>\n", indent.c_str(),
33✔
3216
                node->isQueryable(map) ? " queryable=\"1\"" : "");
21✔
3217
    msIO_printf("%s  <Name>%s</Name>\n", indent.c_str(), node->name.c_str());
21✔
3218
    if (!node->titleWarning.empty()) {
21✔
3219
      msIO_printf("%s  %s\n", indent.c_str(), node->titleWarning.c_str());
6✔
3220
    }
3221
    {
3222
      char *encoded = msEncodeHTMLEntities(node->title.c_str());
21✔
3223
      msIO_printf("%s  <Title>%s</Title>\n", indent.c_str(), encoded);
21✔
3224
      msFree(encoded);
21✔
3225
    }
3226
    if (!node->abstract.empty()) {
21✔
3227
      char *encoded = msEncodeHTMLEntities(node->abstract.c_str());
13✔
3228
      msIO_printf("%s  <Abstract>%s</Abstract>\n", indent.c_str(), encoded);
13✔
3229
      msFree(encoded);
13✔
3230
    }
3231

3232
    if (script_url_encoded && indentLevel == 2 && !node->children.empty() &&
21✔
3233
        node->children[0]->layerIdx >= 0) {
17✔
3234
      layerObj *lp = GET_LAYER(map, node->children[0]->layerIdx);
15✔
3235
      if (lp->group && strlen(lp->group) > 0 &&
26✔
3236
          msIntegerInArray(lp->index, ows_request->enabled_layers,
11✔
3237
                           ows_request->numlayers)) {
3238
        msWMSPrintGroupStyle(map, nVersion, ows_request, node, lp,
11✔
3239
                             script_url_encoded, indent);
3240
      }
3241
    }
3242
  }
3243

3244
  indent += "  ";
3245

3246
  for (const auto &child : node->children) {
328✔
3247
    msWMSPrintLayerNode(map, nVersion, ows_request, child.get(),
105✔
3248
                        script_url_encoded, validated_language,
3249
                        indentLevel + 1);
3250
  }
3251

3252
  indent.pop_back();
3253
  indent.pop_back();
3254

3255
  if (closeLayerTag)
223✔
3256
    msIO_printf("%s</Layer>\n", indent.c_str());
210✔
3257
}
223✔
3258

3259
/*
3260
** msWMSGetCapabilities()
3261
*/
3262
static int msWMSGetCapabilities(mapObj *map, int nVersion, cgiRequestObj *req,
66✔
3263
                                owsRequestObj *ows_request,
3264
                                const char *requested_updatesequence,
3265
                                const char *wms_exception_format,
3266
                                const char *requested_language) {
3267
  const char *updatesequence =
3268
      msOWSLookupMetadata(&(map->web.metadata), "MO", "updatesequence");
66✔
3269

3270
  const char *sldenabled =
3271
      msOWSLookupMetadata(&(map->web.metadata), "MO", "sld_enabled");
66✔
3272

3273
  if (sldenabled == NULL)
66✔
3274
    sldenabled = "true";
3275

3276
  if (requested_updatesequence != NULL) {
66✔
3277
    int i =
3278
        msOWSNegotiateUpdateSequence(requested_updatesequence, updatesequence);
6✔
3279
    if (i == 0) { /* current */
6✔
3280
      msSetErrorWithStatus(
2✔
3281
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
3282
          "UPDATESEQUENCE parameter (%s) is equal to server (%s)",
3283
          "msWMSGetCapabilities()", requested_updatesequence, updatesequence);
3284
      return msWMSException(map, nVersion, "CurrentUpdateSequence",
2✔
3285
                            wms_exception_format);
3286
    }
3287
    if (i > 0) { /* invalid */
4✔
3288
      msSetErrorWithStatus(
2✔
3289
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
3290
          "UPDATESEQUENCE parameter (%s) is higher than server (%s)",
3291
          "msWMSGetCapabilities()", requested_updatesequence, updatesequence);
3292
      return msWMSException(map, nVersion, "InvalidUpdateSequence",
2✔
3293
                            wms_exception_format);
3294
    }
3295
  }
3296

3297
  std::string schemalocation;
3298
  {
3299
    char *pszSchemalocation =
3300
        msEncodeHTMLEntities(msOWSGetSchemasLocation(map));
62✔
3301
    schemalocation = pszSchemalocation;
3302
    msFree(pszSchemalocation);
62✔
3303
  }
3304

3305
  if (nVersion < 0)
62✔
3306
    nVersion = OWS_1_3_0; /* Default to 1.3.0 */
3307

3308
  /* Decide which version we're going to return. */
3309
  std::string dtd_url;
3310
  if (nVersion == OWS_1_0_0) {
62✔
3311
    dtd_url = std::move(schemalocation);
2✔
3312
    dtd_url += "/wms/1.0.0/capabilities_1_0_0.dtd";
3313
  } else if (nVersion < OWS_1_1_1) {
60✔
3314
    nVersion = OWS_1_1_0;
3315
    dtd_url = std::move(schemalocation);
7✔
3316
    dtd_url += "/wms/1.1.0/capabilities_1_1_0.dtd";
3317
  } else if (nVersion < OWS_1_3_0) {
53✔
3318
    nVersion = OWS_1_1_1;
3319
    dtd_url = std::move(schemalocation);
22✔
3320
    /* this exception was added to accommodate the OGC test suite (Bug 1576)*/
3321
    if (strcasecmp(dtd_url.c_str(), OWS_DEFAULT_SCHEMAS_LOCATION) == 0)
22✔
3322
      dtd_url += "/wms/1.1.1/WMS_MS_Capabilities.dtd";
3323
    else
3324
      dtd_url += "/wms/1.1.1/capabilities_1_1_1.dtd";
3325
  } else
3326
    nVersion = OWS_1_3_0;
3327

3328
  /* This function owns validated_language, so remember to free it later*/
3329
  std::string validated_language;
3330
  {
3331
    char *pszValidated_language =
3332
        msOWSGetLanguageFromList(map, "MO", requested_language);
62✔
3333
    if (pszValidated_language) {
62✔
3334
      validated_language = pszValidated_language;
3335
      msMapSetLanguageSpecificConnection(map, pszValidated_language);
15✔
3336
    }
3337
    msFree(pszValidated_language);
62✔
3338
  }
3339

3340
  /* We need this server's onlineresource. */
3341
  /* Default to use the value of the "onlineresource" metadata, and if not */
3342
  /* set then build it: "http://$(SERVER_NAME):$(SERVER_PORT)$(SCRIPT_NAME)?" */
3343
  /* the returned string should be freed once we're done with it. */
3344
  char *script_url_encoded = NULL;
3345
  {
3346
    char *script_url = msOWSGetOnlineResource2(map, "MO", "onlineresource", req,
62✔
3347
                                               validated_language.c_str());
3348
    if (script_url == NULL ||
124✔
3349
        (script_url_encoded = msEncodeHTMLEntities(script_url)) == NULL) {
62✔
UNCOV
3350
      free(script_url);
×
UNCOV
3351
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
3352
    }
3353
    free(script_url);
62✔
3354
  }
3355

3356
  if (nVersion == OWS_1_0_0 || nVersion >= OWS_1_3_0) /* 1.0.0 and >=1.3.0*/
62✔
3357
    msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
33✔
3358
  else /* 1.1.0 and later */
3359
    msIO_setHeader("Content-Type",
29✔
3360
                   "application/vnd.ogc.wms_xml; charset=UTF-8");
3361
  msIO_sendHeaders();
62✔
3362

3363
  msIO_printf("<?xml version='1.0' encoding=\"UTF-8\" standalone=\"no\" ?>\n");
62✔
3364

3365
  /*TODO review wms1.3.0*/
3366
  if (nVersion < OWS_1_3_0) {
62✔
3367
    msIO_printf("<!DOCTYPE WMT_MS_Capabilities SYSTEM \"%s\"\n",
31✔
3368
                dtd_url.c_str());
3369
    msIO_printf(" [\n");
31✔
3370

3371
    if (nVersion == OWS_1_1_1 && msOWSLookupMetadata(&(map->web.metadata), "MO",
31✔
3372
                                                     "inspire_capabilities")) {
3373
      msIO_printf(
6✔
3374
          "<!ELEMENT VendorSpecificCapabilities "
3375
          "(inspire_vs:ExtendedCapabilities)><!ELEMENT "
3376
          "inspire_vs:ExtendedCapabilities ((inspire_common:MetadataUrl, "
3377
          "inspire_common:SupportedLanguages, inspire_common:ResponseLanguage) "
3378
          "| (inspire_common:ResourceLocator+, inspire_common:ResourceType, "
3379
          "inspire_common:TemporalReference+, inspire_common:Conformity+, "
3380
          "inspire_common:MetadataPointOfContact+, "
3381
          "inspire_common:MetadataDate, inspire_common:SpatialDataServiceType, "
3382
          "inspire_common:MandatoryKeyword+, inspire_common:Keyword*, "
3383
          "inspire_common:SupportedLanguages, inspire_common:ResponseLanguage, "
3384
          "inspire_common:MetadataUrl?))><!ATTLIST "
3385
          "inspire_vs:ExtendedCapabilities xmlns:inspire_vs CDATA #FIXED "
3386
          "\"http://inspire.ec.europa.eu/schemas/inspire_vs/1.0\" ><!ELEMENT "
3387
          "inspire_common:MetadataUrl (inspire_common:URL, "
3388
          "inspire_common:MediaType*)><!ATTLIST inspire_common:MetadataUrl "
3389
          "xmlns:inspire_common CDATA #FIXED "
3390
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" xmlns:xsi CDATA "
3391
          "#FIXED \"http://www.w3.org/2001/XMLSchema-instance\" xsi:type CDATA "
3392
          "#FIXED \"inspire_common:resourceLocatorType\" ><!ELEMENT "
3393
          "inspire_common:URL (#PCDATA)><!ATTLIST inspire_common:URL "
3394
          "xmlns:inspire_common CDATA #FIXED "
3395
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\"><!ELEMENT "
3396
          "inspire_common:MediaType (#PCDATA)><!ATTLIST "
3397
          "inspire_common:MediaType xmlns:inspire_common CDATA #FIXED "
3398
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\"><!ELEMENT "
3399
          "inspire_common:SupportedLanguages (inspire_common:DefaultLanguage, "
3400
          "inspire_common:SupportedLanguage*)><!ATTLIST "
3401
          "inspire_common:SupportedLanguages xmlns:inspire_common CDATA #FIXED "
3402
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3403
          "inspire_common:DefaultLanguage (inspire_common:Language)><!ATTLIST "
3404
          "inspire_common:DefaultLanguage xmlns:inspire_common CDATA #FIXED "
3405
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3406
          "inspire_common:SupportedLanguage "
3407
          "(inspire_common:Language)><!ATTLIST "
3408
          "inspire_common:SupportedLanguage xmlns:inspire_common CDATA #FIXED "
3409
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" >"
3410
          "<!ELEMENT inspire_common:ResponseLanguage "
3411
          "(inspire_common:Language)><!ATTLIST inspire_common:ResponseLanguage "
3412
          "xmlns:inspire_common CDATA #FIXED "
3413
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3414
          "inspire_common:Language (#PCDATA)><!ATTLIST inspire_common:Language "
3415
          "xmlns:inspire_common CDATA #FIXED "
3416
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3417
          "inspire_common:ResourceLocator (inspire_common:URL, "
3418
          "inspire_common:MediaType*)><!ATTLIST inspire_common:ResourceLocator "
3419
          "xmlns:inspire_common CDATA #FIXED "
3420
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\"><!ELEMENT "
3421
          "inspire_common:ResourceType (#PCDATA)> <!ATTLIST "
3422
          "inspire_common:ResourceType xmlns:inspire_common CDATA #FIXED "
3423
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3424
          "inspire_common:TemporalReference (inspire_common:DateOfCreation?, "
3425
          "inspire_common:DateOfLastRevision?, "
3426
          "inspire_common:DateOfPublication*, "
3427
          "inspire_common:TemporalExtent*)><!ATTLIST "
3428
          "inspire_common:TemporalReference xmlns:inspire_common CDATA #FIXED "
3429
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3430
          "inspire_common:DateOfCreation (#PCDATA)> <!ATTLIST "
3431
          "inspire_common:DateOfCreation xmlns:inspire_common CDATA #FIXED "
3432
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\"><!ELEMENT "
3433
          "inspire_common:DateOfLastRevision (#PCDATA)><!ATTLIST "
3434
          "inspire_common:DateOfLastRevision xmlns:inspire_common CDATA #FIXED "
3435
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\"><!ELEMENT "
3436
          "inspire_common:DateOfPublication (#PCDATA)><!ATTLIST "
3437
          "inspire_common:DateOfPublication xmlns:inspire_common CDATA #FIXED "
3438
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\"><!ELEMENT "
3439
          "inspire_common:TemporalExtent (inspire_common:IndividualDate | "
3440
          "inspire_common:IntervalOfDates)><!ATTLIST "
3441
          "inspire_common:TemporalExtent xmlns:inspire_common CDATA #FIXED "
3442
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3443
          "inspire_common:IndividualDate (#PCDATA)> <!ATTLIST "
3444
          "inspire_common:IndividualDate xmlns:inspire_common CDATA #FIXED "
3445
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\">"
3446
          "<!ELEMENT inspire_common:IntervalOfDates "
3447
          "(inspire_common:StartingDate, inspire_common:EndDate)><!ATTLIST "
3448
          "inspire_common:IntervalOfDates xmlns:inspire_common CDATA #FIXED "
3449
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3450
          "inspire_common:StartingDate (#PCDATA)><!ATTLIST "
3451
          "inspire_common:StartingDate xmlns:inspire_common CDATA #FIXED "
3452
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3453
          "inspire_common:EndDate (#PCDATA)><!ATTLIST inspire_common:EndDate "
3454
          "xmlns:inspire_common CDATA #FIXED "
3455
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3456
          "inspire_common:Conformity (inspire_common:Specification, "
3457
          "inspire_common:Degree)><!ATTLIST inspire_common:Conformity "
3458
          "xmlns:inspire_common CDATA #FIXED "
3459
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3460
          "inspire_common:Specification (inspire_common:Title, "
3461
          "(inspire_common:DateOfPublication | inspire_common:DateOfCreation | "
3462
          "inspire_common:DateOfLastRevision), inspire_common:URI*, "
3463
          "inspire_common:ResourceLocator*)><!ATTLIST "
3464
          "inspire_common:Specification xmlns:inspire_common CDATA #FIXED "
3465
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3466
          "inspire_common:Title (#PCDATA)><!ATTLIST inspire_common:Title "
3467
          "xmlns:inspire_common CDATA #FIXED "
3468
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3469
          "inspire_common:URI (#PCDATA)><!ATTLIST inspire_common:URI "
3470
          "xmlns:inspire_common CDATA #FIXED "
3471
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3472
          "inspire_common:Degree (#PCDATA)><!ATTLIST inspire_common:Degree "
3473
          "xmlns:inspire_common CDATA #FIXED "
3474
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3475
          "inspire_common:MetadataPointOfContact "
3476
          "(inspire_common:OrganisationName, "
3477
          "inspire_common:EmailAddress)><!ATTLIST "
3478
          "inspire_common:MetadataPointOfContact xmlns:inspire_common CDATA "
3479
          "#FIXED \"http://inspire.ec.europa.eu/schemas/common/1.0\" >"
3480
          "<!ELEMENT inspire_common:OrganisationName (#PCDATA)><!ATTLIST "
3481
          "inspire_common:OrganisationName  xmlns:inspire_common CDATA #FIXED "
3482
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3483
          "inspire_common:EmailAddress (#PCDATA)><!ATTLIST "
3484
          "inspire_common:EmailAddress xmlns:inspire_common CDATA #FIXED "
3485
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3486
          "inspire_common:MetadataDate (#PCDATA)><!ATTLIST "
3487
          "inspire_common:MetadataDate xmlns:inspire_common CDATA #FIXED "
3488
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3489
          "inspire_common:SpatialDataServiceType (#PCDATA)><!ATTLIST "
3490
          "inspire_common:SpatialDataServiceType xmlns:inspire_common CDATA "
3491
          "#FIXED \"http://inspire.ec.europa.eu/schemas/common/1.0\" "
3492
          "><!ELEMENT inspire_common:MandatoryKeyword "
3493
          "(inspire_common:KeywordValue)><!ATTLIST "
3494
          "inspire_common:MandatoryKeyword xmlns:inspire_common CDATA #FIXED "
3495
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" ><!ELEMENT "
3496
          "inspire_common:KeywordValue (#PCDATA)><!ATTLIST "
3497
          "inspire_common:KeywordValue xmlns:inspire_common CDATA #FIXED "
3498
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" >"
3499
          "<!ELEMENT inspire_common:Keyword "
3500
          "(inspire_common:OriginatingControlledVocabulary?, "
3501
          "inspire_common:KeywordValue)><!ATTLIST inspire_common:Keyword "
3502
          "xmlns:inspire_common CDATA #FIXED "
3503
          "\"http://inspire.ec.europa.eu/schemas/common/1.0\" xmlns:xsi CDATA "
3504
          "#FIXED \"http://www.w3.org/2001/XMLSchemainstance\" xsi:type "
3505
          "(inspire_common:inspireTheme_bul | inspire_common:inspireTheme_cze "
3506
          "| inspire_common:inspireTheme_dan | inspire_common:inspireTheme_dut "
3507
          "| inspire_common:inspireTheme_eng | inspire_common:inspireTheme_est "
3508
          "| inspire_common:inspireTheme_fin | inspire_common:inspireTheme_fre "
3509
          "| inspire_common:inspireTheme_ger | inspire_common:inspireTheme_gre "
3510
          "| inspire_common:inspireTheme_hun | inspire_common:inspireTheme_gle "
3511
          "| inspire_common:inspireTheme_ita | inspire_common:inspireTheme_lav "
3512
          "| inspire_common:inspireTheme_lit | inspire_common:inspireTheme_mlt "
3513
          "| inspire_common:inspireTheme_pol | inspire_common:inspireTheme_por "
3514
          "| inspire_common:inspireTheme_rum | inspire_common:inspireTheme_slo "
3515
          "| inspire_common:inspireTheme_slv | inspire_common:inspireTheme_spa "
3516
          "| inspire_common:inspireTheme_swe) #IMPLIED ><!ELEMENT "
3517
          "inspire_common:OriginatingControlledVocabulary "
3518
          "(inspire_common:Title, (inspire_common:DateOfPublication | "
3519
          "inspire_common:DateOfCreation | inspire_common:DateOfLastRevision), "
3520
          "inspire_common:URI*, inspire_common:ResourceLocator*)><!ATTLIST "
3521
          "inspire_common:OriginatingControlledVocabulary xmlns:inspire_common "
3522
          "CDATA #FIXED \"http://inspire.ec.europa.eu/schemas/common/1.0\">\n");
3523
    } else {
3524
      /* some mapserver specific declarations will go here */
3525
      msIO_printf(" <!ELEMENT VendorSpecificCapabilities EMPTY>\n");
25✔
3526
    }
3527

3528
    msIO_printf(" ]>  <!-- end of DOCTYPE declaration -->\n\n");
31✔
3529
  }
3530

3531
  char szVersionBuf[OWS_VERSION_MAXLEN];
3532
  const char *pszVersion = msOWSGetVersionString(nVersion, szVersionBuf);
62✔
3533
  if (nVersion >= OWS_1_3_0)
62✔
3534
    msIO_printf("<WMS_Capabilities version=\"%s\"", pszVersion);
31✔
3535
  else
3536
    msIO_printf("<WMT_MS_Capabilities version=\"%s\"", pszVersion);
31✔
3537
  if (updatesequence)
62✔
3538
    msIO_printf(" updateSequence=\"%s\"", updatesequence);
32✔
3539

3540
  if (nVersion == OWS_1_3_0) {
62✔
3541
    msIO_printf("  xmlns=\"http://www.opengis.net/wms\""
31✔
3542
                "   xmlns:sld=\"http://www.opengis.net/sld\""
3543
                "   xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
3544
                "   xmlns:ms=\"http://mapserver.gis.umn.edu/mapserver\"");
3545

3546
    if (msOWSLookupMetadata(&(map->web.metadata), "MO",
31✔
3547
                            "inspire_capabilities")) {
3548
      msIO_printf("   xmlns:" MS_INSPIRE_COMMON_NAMESPACE_PREFIX
9✔
3549
                  "=\"" MS_INSPIRE_COMMON_NAMESPACE_URI "\""
3550
                  "   xmlns:" MS_INSPIRE_VS_NAMESPACE_PREFIX
3551
                  "=\"" MS_INSPIRE_VS_NAMESPACE_URI "\"");
3552
    }
3553

3554
    msIO_printf(
31✔
3555
        "   xsi:schemaLocation=\"http://www.opengis.net/wms "
3556
        "%s/wms/%s/capabilities_1_3_0.xsd "
3557
        " http://www.opengis.net/sld %s/sld/1.1.0/sld_capabilities.xsd ",
3558
        msOWSGetSchemasLocation(map), pszVersion, msOWSGetSchemasLocation(map));
3559

3560
    if (msOWSLookupMetadata(&(map->web.metadata), "MO",
31✔
3561
                            "inspire_capabilities")) {
3562
      char *inspireschemalocation =
3563
          msEncodeHTMLEntities(msOWSGetInspireSchemasLocation(map));
9✔
3564
      msIO_printf(" " MS_INSPIRE_VS_NAMESPACE_URI " "
9✔
3565
                  " %s%s",
3566
                  inspireschemalocation, MS_INSPIRE_VS_SCHEMA_LOCATION);
3567
      free(inspireschemalocation);
9✔
3568
    }
3569

3570
    msIO_printf(
31✔
3571
        " http://mapserver.gis.umn.edu/mapserver "
3572
        "%sservice=WMS&amp;version=1.3.0&amp;request=GetSchemaExtension\"",
3573
        script_url_encoded);
3574
  }
3575

3576
  msIO_printf(">\n");
62✔
3577

3578
  /* WMS definition */
3579
  msIO_printf("<Service>\n");
62✔
3580

3581
  /* Service name is defined by the spec and changed at v1.0.0 */
3582
  if (nVersion == OWS_1_0_0)
62✔
3583
    msIO_printf("  <Name>GetMap</Name>\n"); /* v 1.0.0 */
2✔
3584
  else if (nVersion >= OWS_1_1_0 && nVersion < OWS_1_3_0)
60✔
3585
    msIO_printf("  <Name>OGC:WMS</Name>\n"); /* v 1.1.0 to 1.1.1*/
29✔
3586
  else
3587
    msIO_printf("  <Name>WMS</Name>\n"); /* v 1.3.0+ */
31✔
3588

3589
  /* the majority of this section is dependent on appropriately named metadata
3590
   * in the WEB object */
3591
  msOWSPrintEncodeMetadata2(stdout, &(map->web.metadata), "MO", "title",
62✔
3592
                            OWS_WARN, "  <Title>%s</Title>\n", map->name,
62✔
3593
                            validated_language.c_str());
3594
  msOWSPrintEncodeMetadata2(stdout, &(map->web.metadata), "MO", "abstract",
62✔
3595
                            OWS_NOERR, "  <Abstract>%s</Abstract>\n", NULL,
3596
                            validated_language.c_str());
3597

3598
  msWMSPrintKeywordlist(stdout, "  ", "keywordlist", &(map->web.metadata), "MO",
62✔
3599
                        nVersion);
3600

3601
  /* Service/onlineresource */
3602
  /* Defaults to same as request onlineresource if wms_service_onlineresource */
3603
  /* is not set. */
3604
  if (nVersion == OWS_1_0_0)
62✔
3605
    msOWSPrintEncodeMetadata(
2✔
3606
        stdout, &(map->web.metadata), "MO", "service_onlineresource", OWS_NOERR,
3607
        "  <OnlineResource>%s</OnlineResource>\n", script_url_encoded);
3608
  else
3609
    msOWSPrintEncodeMetadata(
60✔
3610
        stdout, &(map->web.metadata), "MO", "service_onlineresource", OWS_NOERR,
3611
        "  <OnlineResource xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
3612
        "xlink:href=\"%s\"/>\n",
3613
        script_url_encoded);
3614

3615
  /* In 1.1.0, ContactInformation becomes optional. */
3616
  msOWSPrintContactInfo(stdout, "  ", nVersion, &(map->web.metadata), "MO");
62✔
3617

3618
  msOWSPrintEncodeMetadata(stdout, &(map->web.metadata), "MO", "fees",
62✔
3619
                           OWS_NOERR, "  <Fees>%s</Fees>\n", NULL);
3620

3621
  msOWSPrintEncodeMetadata(
62✔
3622
      stdout, &(map->web.metadata), "MO", "accessconstraints", OWS_NOERR,
3623
      "  <AccessConstraints>%s</AccessConstraints>\n", NULL);
3624

3625
  if (nVersion >= OWS_1_3_0) {
62✔
3626
    const char *layerlimit =
3627
        msOWSLookupMetadata(&(map->web.metadata), "MO", "layerlimit");
31✔
3628
    if (layerlimit) {
31✔
3629
      msIO_printf("  <LayerLimit>%s</LayerLimit>\n", layerlimit);
4✔
3630
    }
3631
    msIO_printf("  <MaxWidth>%d</MaxWidth>\n", map->maxsize);
31✔
3632
    msIO_printf("  <MaxHeight>%d</MaxHeight>\n", map->maxsize);
31✔
3633
  }
3634

3635
  msIO_printf("</Service>\n\n");
62✔
3636

3637
  /* WMS capabilities definitions */
3638
  msIO_printf("<Capability>\n");
62✔
3639
  msIO_printf("  <Request>\n");
62✔
3640

3641
  if (nVersion == OWS_1_0_0) {
62✔
3642
    /* WMS 1.0.0 - we don't try to use outputformats list here for now
3643
     */
3644
    if (msOWSRequestIsEnabled(map, NULL, "M", "GetMap", MS_FALSE))
2✔
3645
      msWMSPrintRequestCap(nVersion, "Map", script_url_encoded,
2✔
3646
                           ""
3647

3648
#if defined USE_PNG
3649
                           "<PNG />"
3650
#endif
3651
#if defined USE_JPEG
3652
                           "<JPEG />"
3653
#endif
3654
                           "<SVG />",
3655
                           NULL);
3656
    if (msOWSRequestIsEnabled(map, NULL, "M", "GetCapabilities", MS_TRUE))
2✔
3657
      msWMSPrintRequestCap(nVersion, "Capabilities", script_url_encoded,
2✔
3658
                           "<WMS_XML />", NULL);
3659
    if (msOWSRequestIsEnabled(map, NULL, "M", "GetFeatureInfo", MS_FALSE))
2✔
3660
      msWMSPrintRequestCap(nVersion, "FeatureInfo", script_url_encoded,
2✔
3661
                           "<MIME /><GML.1 />", NULL);
3662
  } else {
3663
    const char *mime_list[20];
3664
    int mime_count = 0;
3665
    int max_mime = 20;
3666
    /* WMS 1.1.0 and later */
3667
    /* Note changes to the request names, their ordering, and to the formats */
3668

3669
    if (msOWSRequestIsEnabled(map, NULL, "M", "GetCapabilities", MS_TRUE)) {
60✔
3670
      if (nVersion >= OWS_1_3_0)
60✔
3671
        msWMSPrintRequestCap(nVersion, "GetCapabilities", script_url_encoded,
31✔
3672
                             "text/xml", NULL);
3673
      else
3674
        msWMSPrintRequestCap(nVersion, "GetCapabilities", script_url_encoded,
29✔
3675
                             "application/vnd.ogc.wms_xml", NULL);
3676
    }
3677

3678
    msGetOutputFormatMimeListWMS(map, mime_list,
60✔
3679
                                 sizeof(mime_list) / sizeof(char *));
3680
    if (msOWSRequestIsEnabled(map, NULL, "M", "GetMap", MS_FALSE))
60✔
3681
      msWMSPrintRequestCap(
59✔
3682
          nVersion, "GetMap", script_url_encoded, mime_list[0], mime_list[1],
3683
          mime_list[2], mime_list[3], mime_list[4], mime_list[5], mime_list[6],
3684
          mime_list[7], mime_list[8], mime_list[9], mime_list[10],
3685
          mime_list[11], mime_list[12], mime_list[13], mime_list[14],
3686
          mime_list[15], mime_list[16], mime_list[17], mime_list[18],
3687
          mime_list[19], NULL);
3688

3689
    const char *format_list = msOWSLookupMetadata(&(map->web.metadata), "M",
60✔
3690
                                                  "getfeatureinfo_formatlist");
3691
    /*feature_info_mime_type deprecated for MapServer 6.0*/
3692
    if (!format_list)
60✔
3693
      format_list = msOWSLookupMetadata(&(map->web.metadata), "MO",
55✔
3694
                                        "feature_info_mime_type");
3695

3696
    if (format_list && strlen(format_list) > 0) {
60✔
3697
      auto tokens = msStringSplit(format_list, ',');
6✔
3698
      for (auto &token : tokens) {
17✔
3699
        msStringTrim(token);
11✔
3700
        /*text plain and gml do not need to be a format and accepted by
3701
         * default*/
3702
        /*can not really validate since the old way of using template
3703
          with wei->header, layer->template ... should be kept*/
3704
        if (!token.empty() && mime_count < max_mime)
11✔
3705
          mime_list[mime_count++] = token.c_str();
11✔
3706
      }
3707
      /*add text/plain and gml */
3708
      if (strcasestr(format_list, "GML") == 0 &&
6✔
3709
          strcasestr(format_list, "application/vnd.ogc.gml") == 0) {
1✔
3710
        if (mime_count < max_mime)
1✔
3711
          mime_list[mime_count++] = "application/vnd.ogc.gml";
1✔
3712
      }
3713
      if (strcasestr(format_list, "text/plain") == 0 &&
6✔
3714
          strcasestr(format_list, "MIME") == 0) {
6✔
3715
        if (mime_count < max_mime)
6✔
3716
          mime_list[mime_count++] = "text/plain";
6✔
3717
        else /*force always this format*/
UNCOV
3718
          mime_list[max_mime - 1] = "text/plain";
×
3719
      }
3720

3721
      if (msOWSRequestIsEnabled(map, NULL, "M", "GetFeatureInfo", MS_FALSE)) {
6✔
3722
        if (mime_count > 0) {
6✔
3723
          if (mime_count < max_mime)
6✔
3724
            mime_list[mime_count] = NULL;
6✔
3725
          msWMSPrintRequestCap(
6✔
3726
              nVersion, "GetFeatureInfo", script_url_encoded, mime_list[0],
3727
              mime_list[1], mime_list[2], mime_list[3], mime_list[4],
3728
              mime_list[5], mime_list[6], mime_list[7], mime_list[8],
3729
              mime_list[9], mime_list[10], mime_list[11], mime_list[12],
3730
              mime_list[13], mime_list[14], mime_list[15], mime_list[16],
3731
              mime_list[17], mime_list[18], mime_list[19], NULL);
3732
        }
3733
        /*if all formats given are invalid go to default*/
3734
        else
UNCOV
3735
          msWMSPrintRequestCap(nVersion, "GetFeatureInfo", script_url_encoded,
×
3736
                               "text/plain", "application/vnd.ogc.gml", NULL);
3737
      }
3738
    } else {
6✔
3739
      if (msOWSRequestIsEnabled(map, NULL, "M", "GetFeatureInfo", MS_FALSE))
54✔
3740
        msWMSPrintRequestCap(nVersion, "GetFeatureInfo", script_url_encoded,
50✔
3741
                             "text/plain", "application/vnd.ogc.gml", NULL);
3742
    }
3743

3744
    if (strcasecmp(sldenabled, "true") == 0) {
60✔
3745
      if (msOWSRequestIsEnabled(map, NULL, "M", "DescribeLayer", MS_FALSE)) {
43✔
3746
        if (nVersion == OWS_1_3_0)
42✔
3747
          msWMSPrintRequestCap(nVersion, "sld:DescribeLayer",
21✔
3748
                               script_url_encoded, "text/xml", NULL);
3749
        else
3750
          msWMSPrintRequestCap(nVersion, "DescribeLayer", script_url_encoded,
21✔
3751
                               "text/xml", NULL);
3752
      }
3753

3754
      msGetOutputFormatMimeListImg(map, mime_list,
43✔
3755
                                   sizeof(mime_list) / sizeof(char *));
3756

3757
      if (nVersion >= OWS_1_1_1) {
43✔
3758
        const auto isGetLegendGraphicEnabled =
3759
            msOWSRequestIsEnabled(map, NULL, "M", "GetLegendGraphic", MS_FALSE);
36✔
3760
        if (nVersion == OWS_1_3_0) {
36✔
3761
          if (isGetLegendGraphicEnabled)
22✔
3762
            msWMSPrintRequestCap(
18✔
3763
                nVersion, "sld:GetLegendGraphic", script_url_encoded,
3764
                mime_list[0], mime_list[1], mime_list[2], mime_list[3],
3765
                mime_list[4], mime_list[5], mime_list[6], mime_list[7],
3766
                mime_list[8], mime_list[9], mime_list[10], mime_list[11],
3767
                mime_list[12], mime_list[13], mime_list[14], mime_list[15],
3768
                mime_list[16], mime_list[17], mime_list[18], mime_list[19],
3769
                NULL);
3770
          if (msOWSRequestIsEnabled(map, NULL, "M", "GetStyles", MS_FALSE))
22✔
3771
            msWMSPrintRequestCap(nVersion, "ms:GetStyles", script_url_encoded,
18✔
3772
                                 "text/xml", NULL);
3773
        } else {
3774
          if (isGetLegendGraphicEnabled)
14✔
3775
            msWMSPrintRequestCap(
14✔
3776
                nVersion, "GetLegendGraphic", script_url_encoded, mime_list[0],
3777
                mime_list[1], mime_list[2], mime_list[3], mime_list[4],
3778
                mime_list[5], mime_list[6], mime_list[7], mime_list[8],
3779
                mime_list[9], mime_list[10], mime_list[11], mime_list[12],
3780
                mime_list[13], mime_list[14], mime_list[15], mime_list[16],
3781
                mime_list[17], mime_list[18], mime_list[19], NULL);
3782
          if (msOWSRequestIsEnabled(map, NULL, "M", "GetStyles", MS_FALSE))
14✔
3783
            msWMSPrintRequestCap(nVersion, "GetStyles", script_url_encoded,
14✔
3784
                                 "text/xml", NULL);
3785
        }
3786
      }
3787
    }
3788
  }
3789

3790
  msIO_printf("  </Request>\n");
62✔
3791

3792
  msIO_printf("  <Exception>\n");
62✔
3793
  if (nVersion < OWS_1_1_0)
62✔
3794
    msIO_printf("    <Format><BLANK /><INIMAGE /><WMS_XML /></Format>\n");
2✔
3795
  else if (nVersion <= OWS_1_1_1) {
60✔
3796
    msIO_printf("    <Format>application/vnd.ogc.se_xml</Format>\n");
29✔
3797
    msIO_printf("    <Format>application/vnd.ogc.se_inimage</Format>\n");
29✔
3798
    msIO_printf("    <Format>application/vnd.ogc.se_blank</Format>\n");
29✔
3799
  } else { /*>=1.3.0*/
3800
    msIO_printf("    <Format>XML</Format>\n");
31✔
3801
    msIO_printf("    <Format>INIMAGE</Format>\n");
31✔
3802
    msIO_printf("    <Format>BLANK</Format>\n");
31✔
3803
  }
3804
  msIO_printf("  </Exception>\n");
62✔
3805

3806
  if (nVersion != OWS_1_3_0) {
62✔
3807
    /* INSPIRE extended capabilities for WMS 1.1.1 */
3808
    if (nVersion == OWS_1_1_1 && msOWSLookupMetadata(&(map->web.metadata), "MO",
31✔
3809
                                                     "inspire_capabilities")) {
3810
      msIO_printf("  <VendorSpecificCapabilities>\n");
6✔
3811
      msOWSPrintInspireCommonExtendedCapabilities(
6✔
3812
          stdout, map, "MO", OWS_WARN, "inspire_vs:ExtendedCapabilities", NULL,
3813
          validated_language.c_str(), OWS_WMS);
3814
      msIO_printf("  </VendorSpecificCapabilities>\n");
6✔
3815
    } else {
3816
      msIO_printf("  <VendorSpecificCapabilities />\n"); /* nothing yet */
25✔
3817
    }
3818
  }
3819

3820
  /* SLD support */
3821
  if (strcasecmp(sldenabled, "true") == 0) {
62✔
3822
    if (nVersion >= OWS_1_3_0)
45✔
3823
      msIO_printf("  <sld:UserDefinedSymbolization SupportSLD=\"1\" "
22✔
3824
                  "UserLayer=\"0\" UserStyle=\"1\" RemoteWFS=\"0\" "
3825
                  "InlineFeature=\"0\" RemoteWCS=\"0\"/>\n");
3826
    else
3827
      msIO_printf("  <UserDefinedSymbolization SupportSLD=\"1\" "
23✔
3828
                  "UserLayer=\"0\" UserStyle=\"1\" RemoteWFS=\"0\"/>\n");
3829
  }
3830

3831
  /* INSPIRE extended capabilities for WMS 1.3.0 */
3832
  if (nVersion >= OWS_1_3_0 &&
93✔
3833
      msOWSLookupMetadata(&(map->web.metadata), "MO", "inspire_capabilities")) {
31✔
3834
    msOWSPrintInspireCommonExtendedCapabilities(
9✔
3835
        stdout, map, "MO", OWS_WARN, "inspire_vs:ExtendedCapabilities", NULL,
3836
        validated_language.c_str(), OWS_WMS);
3837
  }
3838

3839
  /* Top-level layer with map extents and SRS, encloses all map layers */
3840
  /* Output only if at least one layers is enabled. */
3841
  if (ows_request->numlayers == 0) {
62✔
3842
    msIO_fprintf(stdout, "  <!-- WARNING: No WMS layers are enabled. Check "
5✔
3843
                         "wms/ows_enable_request settings. -->\n");
3844
  } else {
3845
    int root_is_queryable = MS_FALSE;
3846

3847
    const char *rootlayer_name =
3848
        msOWSLookupMetadata(&(map->web.metadata), "MO", "rootlayer_name");
57✔
3849

3850
    /* Root layer is queryable if it has a valid name and at list one layer */
3851
    /* is queryable */
3852
    if (!rootlayer_name || strlen(rootlayer_name) > 0) {
57✔
3853
      int j;
3854
      for (j = 0; j < map->numlayers; j++) {
130✔
3855
        layerObj *layer = GET_LAYER(map, j);
105✔
3856
        if (msIsLayerQueryable(layer) &&
138✔
3857
            msIntegerInArray(layer->index, ows_request->enabled_layers,
33✔
3858
                             ows_request->numlayers)) {
3859
          root_is_queryable = MS_TRUE;
3860
          break;
3861
        }
3862
      }
3863
    }
3864
    msIO_printf("  <Layer%s>\n", root_is_queryable ? " queryable=\"1\"" : "");
82✔
3865

3866
    /* Layer Name is optional but title is mandatory. */
3867
    if (map->name && strlen(map->name) > 0 &&
114✔
3868
        (msIsXMLTagValid(map->name) == MS_FALSE || isdigit(map->name[0])))
114✔
UNCOV
3869
      msIO_fprintf(stdout,
×
3870
                   "<!-- WARNING: The layer name '%s' might contain spaces or "
3871
                   "invalid characters or may start with a number. This could "
3872
                   "lead to potential problems. -->\n",
3873
                   map->name);
3874

3875
    if (rootlayer_name) {
57✔
3876
      if (strlen(rootlayer_name) > 0) {
2✔
3877
        msOWSPrintEncodeMetadata(stdout, &(map->web.metadata), "MO",
1✔
3878
                                 "rootlayer_name", OWS_NOERR,
3879
                                 "    <Name>%s</Name>\n", NULL);
3880
      }
3881
    } else {
3882
      msOWSPrintEncodeParam(stdout, "MAP.NAME", map->name, OWS_NOERR,
55✔
3883
                            "    <Name>%s</Name>\n", NULL);
3884
    }
3885

3886
    if (msOWSLookupMetadataWithLanguage(&(map->web.metadata), "MO",
57✔
3887
                                        "rootlayer_title",
3888
                                        validated_language.c_str()))
3889
      msOWSPrintEncodeMetadata2(
26✔
3890
          stdout, &(map->web.metadata), "MO", "rootlayer_title", OWS_WARN,
3891
          "    <Title>%s</Title>\n", map->name, validated_language.c_str());
26✔
3892

3893
    else
3894
      msOWSPrintEncodeMetadata2(stdout, &(map->web.metadata), "MO", "title",
31✔
3895
                                OWS_WARN, "    <Title>%s</Title>\n", map->name,
31✔
3896
                                validated_language.c_str());
3897

3898
    if (msOWSLookupMetadataWithLanguage(&(map->web.metadata), "MO",
57✔
3899
                                        "rootlayer_abstract",
3900
                                        validated_language.c_str()))
3901
      msOWSPrintEncodeMetadata2(stdout, &(map->web.metadata), "MO",
26✔
3902
                                "rootlayer_abstract", OWS_NOERR,
3903
                                "    <Abstract>%s</Abstract>\n", map->name,
26✔
3904
                                validated_language.c_str());
3905
    else
3906
      msOWSPrintEncodeMetadata2(stdout, &(map->web.metadata), "MO", "abstract",
31✔
3907
                                OWS_NOERR, "    <Abstract>%s</Abstract>\n",
3908
                                map->name, validated_language.c_str());
31✔
3909

3910
    const char *pszTmp;
3911
    if (msOWSLookupMetadata(&(map->web.metadata), "MO",
57✔
3912
                            "rootlayer_keywordlist") ||
88✔
3913
        msOWSLookupMetadata(&(map->web.metadata), "MO",
31✔
3914
                            "rootlayer_keywordlist_vocabulary"))
3915
      pszTmp = "rootlayer_keywordlist";
3916
    else
3917
      pszTmp = "keywordlist";
3918
    msWMSPrintKeywordlist(stdout, "    ", pszTmp, &(map->web.metadata), "MO",
57✔
3919
                          nVersion);
3920

3921
    /* According to normative comments in the 1.0.7 DTD, the root layer's SRS
3922
     * tag */
3923
    /* is REQUIRED.  It also suggests that we use an empty SRS element if there
3924
     */
3925
    /* is no common SRS. */
3926
    char *pszMapEPSG;
3927
    msOWSGetEPSGProj(&(map->projection), &(map->web.metadata), "MO", MS_FALSE,
57✔
3928
                     &pszMapEPSG);
3929
    if (nVersion > OWS_1_1_0) {
57✔
3930
      /* starting 1.1.1 SRS are given in individual tags */
3931
      if (nVersion >= OWS_1_3_0) {
50✔
3932
        msOWSPrintEncodeParamList(stdout,
29✔
3933
                                  "(at least one of) "
3934
                                  "MAP.PROJECTION, LAYER.PROJECTION "
3935
                                  "or wms_srs metadata",
3936
                                  pszMapEPSG, OWS_WARN, ' ', NULL, NULL,
3937
                                  "    <CRS>%s</CRS>\n", "");
3938
      } else {
3939
        msOWSPrintEncodeParamList(stdout,
21✔
3940
                                  "(at least one of) "
3941
                                  "MAP.PROJECTION, LAYER.PROJECTION "
3942
                                  "or wms_srs metadata",
3943
                                  pszMapEPSG, OWS_WARN, ' ', NULL, NULL,
3944
                                  "    <SRS>%s</SRS>\n", "");
3945
      }
3946
    } else {
3947
      /* If map has no proj then every layer MUST have one or produce a warning
3948
       */
3949
      msOWSPrintEncodeParam(stdout, "MAP.PROJECTION (or wms_srs metadata)",
7✔
3950
                            pszMapEPSG, OWS_WARN, "    <SRS>%s</SRS>\n", "");
3951
    }
3952
    msFree(pszMapEPSG);
57✔
3953

3954
    if (nVersion >= OWS_1_3_0)
57✔
3955
      msOWSPrintEX_GeographicBoundingBox(stdout, "    ", &(map->extent),
29✔
3956
                                         &(map->projection));
3957
    else
3958
      msOWSPrintLatLonBoundingBox(stdout, "    ", &(map->extent),
28✔
3959
                                  &(map->projection), NULL, OWS_WMS);
3960

3961
    msOWSPrintBoundingBox(stdout, "    ", &(map->extent), &(map->projection),
57✔
3962
                          NULL, &(map->web.metadata), "MO", nVersion);
3963

3964
    /* AuthorityURL support and Identifier support, only available >= WMS 1.1.0
3965
     */
3966
    if (nVersion >= OWS_1_1_0) {
57✔
3967
      msWMSPrintAttribution(stdout, "    ", &(map->web.metadata), "MO");
56✔
3968
      msWMSPrintAuthorityURL(stdout, "    ", &(map->web.metadata), "MO");
56✔
3969
      msWMSPrintIdentifier(stdout, "    ", &(map->web.metadata), "MO");
56✔
3970
    }
3971

3972
    /* MetadataURL */
3973
    if (nVersion >= OWS_1_1_0)
3974
      msOWSPrintURLType(stdout, &(map->web.metadata), "MO", "metadataurl",
56✔
3975
                        OWS_NOERR, NULL, "MetadataURL", " type=\"%s\"", NULL,
3976
                        NULL, ">\n      <Format>%s</Format",
3977
                        "\n      <OnlineResource xmlns:xlink=\""
3978
                        "http://www.w3.org/1999/xlink\" "
3979
                        "xlink:type=\"simple\" xlink:href=\"%s\"/>\n    ",
3980
                        MS_TRUE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
3981
                        NULL, NULL, NULL, NULL, "    ");
3982

3983
    /* DataURL */
3984
    if (nVersion < OWS_1_1_0)
3985
      msOWSPrintEncodeMetadata(stdout, &(map->web.metadata), "MO",
1✔
3986
                               "dataurl_href", OWS_NOERR,
3987
                               "    <DataURL>%s</DataURL>\n", NULL);
3988
    else
3989
      msOWSPrintURLType(stdout, &(map->web.metadata), "MO", "dataurl",
56✔
3990
                        OWS_NOERR, NULL, "DataURL", NULL, NULL, NULL,
3991
                        ">\n      <Format>%s</Format",
3992
                        "\n      <OnlineResource xmlns:xlink=\""
3993
                        "http://www.w3.org/1999/xlink\" "
3994
                        "xlink:type=\"simple\" xlink:href=\"%s\"/>\n    ",
3995
                        MS_FALSE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
3996
                        NULL, NULL, NULL, NULL, "    ");
3997

3998
    if (map->name && strlen(map->name) > 0 &&
114✔
3999
        msOWSLookupMetadata(&(map->web.metadata), "MO",
57✔
4000
                            "inspire_capabilities")) {
4001
      char *pszEncodedName = NULL;
4002
      const char *styleName = NULL;
4003
      char *pszEncodedStyleName = NULL;
4004
      const char *legendURL = NULL;
4005

4006
      pszEncodedName = msEncodeHTMLEntities(map->name);
15✔
4007

4008
      styleName = msOWSLookupMetadata(&(map->web.metadata), "MO", "style_name");
15✔
4009
      if (styleName == NULL)
15✔
4010
        styleName = "default";
4011

4012
      pszEncodedStyleName = msEncodeHTMLEntities(styleName);
15✔
4013

4014
      msIO_fprintf(stdout, "    <Style>\n");
15✔
4015
      msIO_fprintf(stdout, "       <Name>%s</Name>\n", pszEncodedStyleName);
15✔
4016
      msOWSPrintEncodeMetadata2(
15✔
4017
          stdout, &(map->web.metadata), "MO", "style_title", OWS_NOERR,
4018
          "       <Title>%s</Title>\n", styleName, validated_language.c_str());
4019

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

4036
        for (int i = 0; i < map->numlayers; i++) {
96✔
4037
          if (msIntegerInArray(GET_LAYER(map, i)->index,
90✔
4038
                               ows_request->enabled_layers,
4039
                               ows_request->numlayers)) {
4040
            group_layers.push_back(i);
84✔
4041
          }
4042
        }
4043

4044
        if (!group_layers.empty()) {
6✔
4045
          int size_x = 0, size_y = 0;
6✔
4046
          if (msLegendCalcSize(map, 1, &size_x, &size_y, group_layers.data(),
6✔
4047
                               static_cast<int>(group_layers.size()), NULL,
4048
                               1) == MS_SUCCESS) {
4049
            const std::string width(std::to_string(size_x));
6✔
4050
            const std::string height(std::to_string(size_y));
6✔
4051

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

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

4079
            msOWSPrintURLType(stdout, NULL, "O", "ttt", OWS_NOERR, NULL,
6✔
4080
                              "LegendURL", NULL, " width=\"%s\"",
4081
                              " height=\"%s\"",
4082
                              ">\n          <Format>%s</Format",
4083
                              "\n          <OnlineResource "
4084
                              "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
4085
                              " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
4086
                              "       ",
4087
                              MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE,
4088
                              NULL, width.c_str(), height.c_str(), pszMimetype,
4089
                              legendurl.c_str(), "       ");
4090
            msFree(pszMimetype);
6✔
4091
          }
4092
        }
4093
      }
6✔
4094
      msIO_fprintf(stdout, "    </Style>\n");
15✔
4095
      msFree(pszEncodedName);
15✔
4096
      msFree(pszEncodedStyleName);
15✔
4097
    }
4098

4099
    if (nVersion < OWS_1_3_0)
57✔
4100
      msWMSPrintScaleHint("    ", map->web.minscaledenom,
28✔
4101
                          map->web.maxscaledenom, map->resolution);
4102
    else
4103
      msWMSPrintScaleDenominator("    ", map->web.minscaledenom,
29✔
4104
                                 map->web.maxscaledenom);
4105

4106
    /*  */
4107
    /* Dump list of layers organized by groups.  Layers with no group are listed
4108
     */
4109
    /* individually, at the same level as the groups in the layer hierarchy */
4110
    /*  */
4111
    if (map->numlayers) {
57✔
4112

4113
      auto [layerTree, mapNameToNode] =
4114
          msWMSCreateLayerTree(map, validated_language.c_str());
57✔
4115
      (void)mapNameToNode;
4116
      for (const auto &child : layerTree->children) {
175✔
4117
        msWMSPrintLayerNode(map, nVersion, ows_request, child.get(),
118✔
4118
                            script_url_encoded, validated_language.c_str(), 2);
4119
      }
4120
    }
4121

4122
    msIO_printf("  </Layer>\n");
57✔
4123
  }
4124

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

4131
  free(script_url_encoded);
62✔
4132

4133
  return (MS_SUCCESS);
62✔
4134
}
4135

4136
/*
4137
 * This function look for params that can be used
4138
 * by mapserv when generating template.
4139
 */
4140
int msTranslateWMS2Mapserv(const char **names, const char **values,
9✔
4141
                           int numentries, char ***translated_names,
4142
                           char ***translated_values,
4143
                           int *translated_numentries) {
4144
  int num_allocated = numentries;
4145
  *translated_names = (char **)msSmallMalloc(num_allocated * sizeof(char *));
9✔
4146
  *translated_values = (char **)msSmallMalloc(num_allocated * sizeof(char *));
9✔
4147
  *translated_numentries = 0;
9✔
4148
  for (int i = 0; i < numentries; i++) {
158✔
4149
    (*translated_values)[*translated_numentries] = msStrdup(values[i]);
149✔
4150
    (*translated_names)[*translated_numentries] = msStrdup(names[i]);
149✔
4151
    (*translated_numentries)++;
149✔
4152
    if (strcasecmp("X", names[i]) == 0) {
149✔
4153
      num_allocated++;
3✔
4154
      *translated_names = (char **)msSmallRealloc(
6✔
4155
          *translated_names, num_allocated * sizeof(char *));
3✔
4156
      *translated_values = (char **)msSmallRealloc(
3✔
4157
          *translated_values, num_allocated * sizeof(char *));
4158
      (*translated_values)[*translated_numentries] = msStrdup(values[i]);
3✔
4159
      (*translated_names)[*translated_numentries] = msStrdup("img.x");
3✔
4160
      (*translated_numentries)++;
3✔
4161
    } else if (strcasecmp("Y", names[i]) == 0) {
146✔
4162
      num_allocated++;
3✔
4163
      *translated_names = (char **)msSmallRealloc(
6✔
4164
          *translated_names, num_allocated * sizeof(char *));
3✔
4165
      *translated_values = (char **)msSmallRealloc(
3✔
4166
          *translated_values, num_allocated * sizeof(char *));
4167
      (*translated_values)[*translated_numentries] = msStrdup(values[i]);
3✔
4168
      (*translated_names)[*translated_numentries] = msStrdup("img.y");
3✔
4169
      (*translated_numentries)++;
3✔
4170
    } else if (strcasecmp("LAYERS", names[i]) == 0) {
143✔
4171
      char **layers;
4172
      int tok;
4173
      int j;
4174
      layers = msStringSplit(values[i], ',', &tok);
9✔
4175
      num_allocated += tok;
9✔
4176
      *translated_names = (char **)msSmallRealloc(
18✔
4177
          *translated_names, num_allocated * sizeof(char *));
9✔
4178
      *translated_values = (char **)msSmallRealloc(
9✔
4179
          *translated_values, num_allocated * sizeof(char *));
4180
      for (j = 0; j < tok; j++) {
20✔
4181
        (*translated_values)[*translated_numentries] = layers[j];
11✔
4182
        (*translated_names)[*translated_numentries] = msStrdup("layer");
11✔
4183
        (*translated_numentries)++;
11✔
4184
        layers[j] = NULL;
11✔
4185
      }
4186
      free(layers);
9✔
4187
    } else if (strcasecmp("QUERY_LAYERS", names[i]) == 0) {
134✔
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++) {
18✔
4198
        (*translated_values)[*translated_numentries] = layers[j];
9✔
4199
        (*translated_names)[*translated_numentries] = msStrdup("qlayer");
9✔
4200
        (*translated_numentries)++;
9✔
4201
        layers[j] = NULL;
9✔
4202
      }
4203
      free(layers);
9✔
4204
    } else if (strcasecmp("BBOX", names[i]) == 0) {
125✔
4205
      char *imgext;
4206
      num_allocated++;
9✔
4207
      *translated_names = (char **)msSmallRealloc(
18✔
4208
          *translated_names, num_allocated * sizeof(char *));
9✔
4209
      *translated_values = (char **)msSmallRealloc(
9✔
4210
          *translated_values, num_allocated * sizeof(char *));
4211

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

4222
  return MS_SUCCESS;
9✔
4223
}
4224

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

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

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

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

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

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

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

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

UNCOV
4294
    img = msPrepareImage(map, MS_TRUE);
×
4295

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

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

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

4328
  } else {
4329

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

4341
    img = msDrawMap(map, drawquerymap);
411✔
4342
  }
4343

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

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

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

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

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

4390
  if (img == NULL)
411✔
UNCOV
4391
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4392

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

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

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

4418
  return (MS_SUCCESS);
411✔
4419
}
4420

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4505
  return numresults;
4506
}
4507

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

4527
  auto [layerTree, mapNameToNode] = msWMSCreateLayerTree(map, nullptr);
62✔
4528
  (void)layerTree;
4529

4530
  const char *styles = nullptr;
4531
  std::vector<std::string> wmslayers;
4532
  for (int i = 0; i < numentries; i++) {
1,045✔
4533
    if (strcasecmp(names[i], "LAYERS") == 0) {
983✔
4534

4535
      wmslayers = msStringSplit(values[i], ',');
62✔
4536
      if (wmslayers.empty()) {
62✔
UNCOV
4537
        return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4538
      }
4539

4540
      if (nVersion >= OWS_1_3_0) {
62✔
4541
        const char *layerlimit =
4542
            msOWSLookupMetadata(&(map->web.metadata), "MO", "layerlimit");
33✔
4543
        if (layerlimit) {
33✔
4544
          if (static_cast<int>(wmslayers.size()) > atoi(layerlimit)) {
3✔
UNCOV
4545
            msSetErrorWithStatus(
×
4546
                MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4547
                "Number of layers requested exceeds LayerLimit.",
4548
                "msWMSLoadGetMapParams()");
UNCOV
4549
            return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4550
          }
4551
        }
4552
      }
4553
    } else if (strcasecmp(names[i], "STYLES=") == 0) {
921✔
UNCOV
4554
      styles = values[i];
×
4555
    }
4556
  }
4557

4558
  std::map<std::string, std::vector<std::string>> mapLayerNameToStyleNames;
4559

4560
  if (styles && strlen(styles) > 0) {
62✔
4561
    int n = 0;
×
UNCOV
4562
    char **tokens = msStringSplitComplex(styles, ",", &n, MS_ALLOWEMPTYTOKENS);
×
4563

UNCOV
4564
    if (static_cast<int>(wmslayers.size()) != n) {
×
UNCOV
4565
      msSetErrorWithStatus(
×
4566
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4567
          "Invalid style (%s). Mapserver is expecting an empty "
4568
          "string for the STYLES : STYLES= or STYLES=,,, or using "
4569
          "keyword default  STYLES=default,default, ...",
4570
          "msWMSLoadGetMapParams()", styles);
UNCOV
4571
      msFreeCharArray(tokens, n);
×
UNCOV
4572
      return msWMSException(map, nVersion, "StyleNotDefined",
×
4573
                            wms_exception_format);
×
4574
    }
4575

UNCOV
4576
    for (int i = 0; i < n; i++) {
×
UNCOV
4577
      if (tokens[i] && strlen(tokens[i]) > 0 &&
×
4578
          strcasecmp(tokens[i], "default") != 0) {
×
4579
        const std::string &correspondingLayer = wmslayers[i];
UNCOV
4580
        for (int j = 0; j < map->numlayers; j++) {
×
UNCOV
4581
          layerObj *lp = GET_LAYER(map, j);
×
UNCOV
4582
          if ((lp->name &&
×
UNCOV
4583
               strcasecmp(lp->name, correspondingLayer.c_str()) == 0) ||
×
UNCOV
4584
              (lp->group &&
×
4585
               strcasecmp(lp->group, correspondingLayer.c_str()) == 0)) {
×
4586
            bool found = false;
UNCOV
4587
            for (int k = 0; k < lp->numclasses; k++) {
×
4588
              if (lp->_class[k]->group &&
×
4589
                  strcasecmp(lp->_class[k]->group, tokens[i]) == 0) {
×
UNCOV
4590
                mapLayerNameToStyleNames[correspondingLayer].push_back(
×
4591
                    tokens[i]);
4592
                found = true;
4593
                break;
4594
              }
4595
            }
4596
            if (!found) {
4597
              msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
4598
                                   "Style (%s) not defined on layer.",
4599
                                   "msWMSLoadGetMapParams()", tokens[i]);
4600
              msFreeCharArray(tokens, n);
×
4601

4602
              return msWMSException(map, nVersion, "StyleNotDefined",
×
4603
                                    wms_exception_format);
4604
            }
4605
            /* Check the style of the root layer */
4606
          } else if (map->name &&
×
4607
                     strcasecmp(map->name, correspondingLayer.c_str()) == 0) {
×
4608
            const char *styleName =
4609
                msOWSLookupMetadata(&(map->web.metadata), "MO", "style_name");
×
UNCOV
4610
            if (styleName == NULL)
×
4611
              styleName = "default";
4612
            char *pszEncodedStyleName = msEncodeHTMLEntities(styleName);
×
4613
            if (strcasecmp(pszEncodedStyleName, tokens[i]) != 0) {
×
4614
              msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
4615
                                   "Style (%s) not defined on root layer.",
4616
                                   "msWMSLoadGetMapParams()", tokens[i]);
UNCOV
4617
              msFreeCharArray(tokens, n);
×
UNCOV
4618
              msFree(pszEncodedStyleName);
×
4619

UNCOV
4620
              return msWMSException(map, nVersion, "StyleNotDefined",
×
4621
                                    wms_exception_format);
4622
            }
UNCOV
4623
            msFree(pszEncodedStyleName);
×
4624

UNCOV
4625
            mapLayerNameToStyleNames[map->name].push_back(styleName);
×
4626
          }
4627
        }
4628
      }
4629
    }
4630
    msFreeCharArray(tokens, n);
×
4631
  }
4632

4633
  std::map<int, std::vector<std::string>> mapLayerIndexToStyleNames;
4634

4635
  for (int i = 0; i < numentries; i++) {
1,045✔
4636
    if (strcasecmp(names[i], "QUERY_LAYERS") == 0) {
983✔
4637
      query_layer = 1; /* flag set if QUERY_LAYERS is the request */
4638

4639
      const auto querylayers = msStringSplit(values[i], ',');
62✔
4640
      if (querylayers.empty()) {
62✔
4641
        msSetErrorWithStatus(
×
4642
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4643
            "At least one layer name required in QUERY_LAYERS.",
4644
            "msWMSFeatureInfo()");
UNCOV
4645
        return msWMSException(map, nVersion, "LayerNotDefined",
×
4646
                              wms_exception_format);
4647
      }
4648

4649
      for (int j = 0; j < map->numlayers; j++) {
341✔
4650
        /* Force all layers OFF by default */
4651
        GET_LAYER(map, j)->status = MS_OFF;
279✔
4652
      }
4653

4654
      /* Special case for root layer */
4655
      const char *rootlayer_name =
4656
          msOWSLookupMetadata(&(map->web.metadata), "MO", "rootlayer_name");
62✔
4657
      if (!rootlayer_name)
62✔
4658
        rootlayer_name = map->name;
62✔
4659
      if (rootlayer_name && msStringInArray(rootlayer_name, querylayers)) {
62✔
4660
        for (int j = 0; j < map->numlayers; j++) {
59✔
4661
          layerObj *layer = GET_LAYER(map, j);
53✔
4662
          if (msIsLayerQueryable(layer) &&
91✔
4663
              msIntegerInArray(layer->index, ows_request->enabled_layers,
38✔
4664
                               ows_request->numlayers)) {
4665
            if (layer->connectiontype == MS_WMS) {
38✔
4666
              wms_layer = MS_TRUE;
UNCOV
4667
              wms_connection = layer->connection;
×
4668
            }
4669

4670
            mapLayerIndexToStyleNames[j] =
38✔
4671
                mapLayerNameToStyleNames[rootlayer_name];
76✔
4672

4673
            numlayers_found++;
38✔
4674
            layer->status = MS_ON;
38✔
4675
          }
4676
        }
4677
      }
4678

4679
      for (const auto &wmslayer : querylayers) {
125✔
4680
        auto iter = mapNameToNode.find(msStringToLower(wmslayer));
63✔
4681
        if (iter != mapNameToNode.end()) {
63✔
4682
          const auto layerIndices = iter->second->collectLayerIndices();
63✔
4683
          for (int j : layerIndices) {
187✔
4684
            layerObj *layer = GET_LAYER(map, j);
124✔
4685
            if (msIsLayerQueryable(layer) &&
227✔
4686
                msIntegerInArray(layer->index, ows_request->enabled_layers,
103✔
4687
                                 ows_request->numlayers)) {
4688
              if (layer->connectiontype == MS_WMS) {
103✔
4689
                wms_layer = MS_TRUE;
4690
                wms_connection = layer->connection;
8✔
4691
              }
4692

4693
              mapLayerIndexToStyleNames[j] = mapLayerNameToStyleNames[wmslayer];
103✔
4694

4695
              numlayers_found++;
103✔
4696
              layer->status = MS_ON;
103✔
4697
            }
4698
          }
4699
        }
63✔
4700
      }
4701

4702
    } else if (strcasecmp(names[i], "INFO_FORMAT") == 0) {
983✔
4703
      if (values[i] && strlen(values[i]) > 0) {
58✔
4704
        info_format = values[i];
4705
        format_found = MS_TRUE;
4706
      }
4707
    } else if (strcasecmp(names[i], "FEATURE_COUNT") == 0)
863✔
4708
      feature_count = atoi(values[i]);
20✔
4709
    else if (strcasecmp(names[i], "X") == 0 || strcasecmp(names[i], "I") == 0)
843✔
4710
      point.x = atof(values[i]);
62✔
4711
    else if (strcasecmp(names[i], "Y") == 0 || strcasecmp(names[i], "J") == 0)
781✔
4712
      point.y = atof(values[i]);
62✔
4713
    else if (strcasecmp(names[i], "RADIUS") == 0) {
719✔
4714
      /* RADIUS in pixels. */
4715
      /* This is not part of the spec, but some servers such as cubeserv */
4716
      /* support it as a vendor-specific feature. */
4717
      /* It's easy for MapServer to handle this so let's do it! */
4718

4719
      /* Special RADIUS value that changes the query into a bbox query */
4720
      /* based on the bbox in the request parameters. */
4721
      if (strcasecmp(values[i], "BBOX") == 0) {
11✔
4722
        use_bbox = MS_TRUE;
4723
      } else {
4724
        int j;
4725
        for (j = 0; j < map->numlayers; j++) {
62✔
4726
          GET_LAYER(map, j)->tolerance = atoi(values[i]);
51✔
4727
          GET_LAYER(map, j)->toleranceunits = MS_PIXELS;
51✔
4728
        }
4729
      }
4730
    }
4731
  }
4732

4733
  if (numlayers_found == 0) {
62✔
4734
    if (query_layer) {
1✔
4735
      msSetErrorWithStatus(
1✔
4736
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4737
          "Layer(s) specified in QUERY_LAYERS parameter is not offered "
4738
          "by the service instance.",
4739
          "msWMSFeatureInfo()");
4740
      return msWMSException(map, nVersion, "LayerNotDefined",
1✔
4741
                            wms_exception_format);
4742
    } else {
UNCOV
4743
      msSetErrorWithStatus(
×
4744
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4745
          "Required QUERY_LAYERS parameter missing for getFeatureInfo.",
4746
          "msWMSFeatureInfo()");
UNCOV
4747
      return msWMSException(map, nVersion, "LayerNotDefined",
×
4748
                            wms_exception_format);
4749
    }
4750
  }
4751

4752
  /*make sure to initialize the map scale so that layers that are scale
4753
    dependent are respected for the query*/
4754
  msCalculateScale(map->extent, map->units, map->width, map->height,
61✔
4755
                   map->resolution, &map->scaledenom);
4756

4757
  /* -------------------------------------------------------------------- */
4758
  /*      check if all layers selected are queryable. If not send an      */
4759
  /*      exception.                                                      */
4760
  /* -------------------------------------------------------------------- */
4761

4762
  /* If a layer of type WMS was found... all layers have to be of that type and
4763
   * with the same connection */
4764
  for (int i = 0; i < map->numlayers; i++) {
334✔
4765
    if (GET_LAYER(map, i)->status == MS_ON) {
273✔
4766
      if (wms_layer == MS_TRUE) {
103✔
4767
        if ((GET_LAYER(map, i)->connectiontype != MS_WMS) ||
8✔
4768
            (strcasecmp(wms_connection, GET_LAYER(map, i)->connection) != 0)) {
8✔
UNCOV
4769
          msSetErrorWithStatus(
×
4770
              MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4771
              "Requested WMS layer(s) are not queryable: type or "
4772
              "connection differ",
4773
              "msWMSFeatureInfo()");
UNCOV
4774
          return msWMSException(map, nVersion, "LayerNotQueryable",
×
4775
                                wms_exception_format);
4776
        }
4777
        ++numOWSLayers;
8✔
4778
      }
4779
    }
4780
  }
4781

4782
  /* It's a valid Cascading WMS GetFeatureInfo request */
4783
  if (wms_layer)
61✔
4784
    return msWMSLayerExecuteRequest(
8✔
4785
        map, numOWSLayers, static_cast<int>(point.x), static_cast<int>(point.y),
4786
        feature_count, info_format, WMS_GETFEATUREINFO);
4787
  if (use_bbox == MS_FALSE) {
53✔
4788

4789
    if (point.x == -1.0 || point.y == -1.0) {
53✔
UNCOV
4790
      if (nVersion >= OWS_1_3_0)
×
UNCOV
4791
        msSetErrorWithStatus(
×
4792
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4793
            "Required I/J parameters missing for getFeatureInfo.",
4794
            "msWMSFeatureInfo()");
4795
      else
UNCOV
4796
        msSetErrorWithStatus(
×
4797
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4798
            "Required X/Y parameters missing for getFeatureInfo.",
4799
            "msWMSFeatureInfo()");
UNCOV
4800
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4801
    }
4802

4803
    /*wms1.3.0: check if the points are valid*/
4804
    if (nVersion >= OWS_1_3_0) {
53✔
4805
      if (point.x > map->width || point.y > map->height) {
28✔
UNCOV
4806
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
4807
                             "Invalid I/J values", "msWMSFeatureInfo()");
UNCOV
4808
        return msWMSException(map, nVersion, "InvalidPoint",
×
4809
                              wms_exception_format);
4810
      }
4811
    }
4812
  }
4813

4814
  /*validate the INFO_FORMAT*/
4815
  valid_format = MS_FALSE;
4816
  format_list = msOWSLookupMetadata(&(map->web.metadata), "M",
53✔
4817
                                    "getfeatureinfo_formatlist");
4818
  /*feature_info_mime_type deprecated for MapServer 6.0*/
4819
  if (!format_list)
53✔
4820
    format_list = msOWSLookupMetadata(&(map->web.metadata), "MO",
45✔
4821
                                      "feature_info_mime_type");
4822
  if (format_list) {
45✔
4823
    /*can not really validate if it is a valid output format
4824
      since old way of using template with web->header/footer and
4825
      layer templates need to still be supported.
4826
      We can only validate if it was part of the format list*/
4827
    if (strcasestr(format_list, info_format))
14✔
4828
      valid_format = MS_TRUE;
4829
  }
4830
  /*check to see if the format passed is text/plain or GML and if is
4831
    defined in the formatlist. If that is the case, It is a valid format*/
4832
  bool bMimeResponse = strcasecmp(info_format, "MIME") == 0 ||
53✔
4833
                       strcasecmp(info_format, "text/plain") == 0;
49✔
4834
  const bool bGMLResponse =
4835
      strncasecmp(info_format, "GML", 3) == 0 ||
53✔
4836
      strcasecmp(info_format, "application/vnd.ogc.gml") == 0;
52✔
4837
  if (bMimeResponse || bGMLResponse)
53✔
4838
    valid_format = MS_TRUE;
4839

4840
  /*last case: if the info_format is not part of the request, it defaults to
4841
   * MIME*/
4842
  if (!valid_format && format_found == MS_FALSE) {
53✔
4843
    bMimeResponse = true, valid_format = MS_TRUE;
4844
  }
4845

4846
  if (!valid_format) {
53✔
UNCOV
4847
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
4848
                         "Unsupported INFO_FORMAT value (%s).",
4849
                         "msWMSFeatureInfo()", info_format);
UNCOV
4850
    if (nVersion >= OWS_1_3_0)
×
UNCOV
4851
      return msWMSException(map, nVersion, "InvalidFormat",
×
4852
                            wms_exception_format);
4853
    else
UNCOV
4854
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4855
  }
4856

4857
  if (use_bbox == MS_FALSE) {
53✔
4858

4859
    /* Perform the actual query */
4860
    const double cellx =
53✔
4861
        MS_CELLSIZE(map->extent.minx, map->extent.maxx,
53✔
4862
                    map->width); /* note: don't adjust extent, WMS assumes
4863
                                    incoming extent is correct */
4864
    const double celly =
53✔
4865
        MS_CELLSIZE(map->extent.miny, map->extent.maxy, map->height);
53✔
4866

4867
    point.x = MS_IMAGE2MAP_X(point.x, map->extent.minx, cellx);
53✔
4868
    point.y = MS_IMAGE2MAP_Y(point.y, map->extent.maxy, celly);
53✔
4869

4870
    /* WMS 1.3.0 states that feature_count is *per layer*.
4871
     * Its value is a positive integer, if omitted then the default is 1
4872
     */
4873
    if (feature_count < 1)
53✔
4874
      feature_count = 1;
4875

4876
    map->query.type = MS_QUERY_BY_POINT;
53✔
4877
    map->query.mode =
53✔
4878
        (feature_count == 1 ? MS_QUERY_SINGLE : MS_QUERY_MULTIPLE);
53✔
4879
    map->query.layer = -1;
53✔
4880
    map->query.point = point;
53✔
4881
    map->query.buffer = 0;
53✔
4882
    map->query.maxresults = feature_count;
53✔
4883
    map->query.getFeatureInfo->mapLayerIndexToStyleNames =
53✔
4884
        std::move(mapLayerIndexToStyleNames);
4885

4886
    if (msQueryByPoint(map) != MS_SUCCESS)
53✔
UNCOV
4887
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4888

4889
  } else { /* use_bbox == MS_TRUE */
UNCOV
4890
    map->query.type = MS_QUERY_BY_RECT;
×
UNCOV
4891
    map->query.mode = MS_QUERY_MULTIPLE;
×
UNCOV
4892
    map->query.layer = -1;
×
UNCOV
4893
    map->query.rect = map->extent;
×
UNCOV
4894
    map->query.buffer = 0;
×
UNCOV
4895
    map->query.maxresults = feature_count;
×
UNCOV
4896
    if (msQueryByRect(map) != MS_SUCCESS)
×
UNCOV
4897
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4898
  }
4899

4900
  /* Generate response */
4901
  if (bMimeResponse) {
53✔
4902

4903
    /* MIME response... we're free to use any valid MIME type */
4904
    int numresults = 0;
4905

4906
    msIO_setHeader("Content-Type", "text/plain; charset=UTF-8");
7✔
4907
    msIO_sendHeaders();
7✔
4908
    msIO_printf("GetFeatureInfo results:\n");
7✔
4909

4910
    numresults = msDumpResult(map, nVersion, wms_exception_format);
7✔
4911

4912
    if (numresults == 0)
7✔
4913
      msIO_printf("\n  Search returned no results.\n");
1✔
4914

4915
  } else if (bGMLResponse) {
46✔
4916

4917
    if (nVersion == OWS_1_0_0)
37✔
4918
      msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
×
4919
    else /* 1.1.0 and later */
4920
      msIO_setHeader("Content-Type", "application/vnd.ogc.gml; charset=UTF-8");
37✔
4921
    msIO_sendHeaders();
37✔
4922
    msGMLWriteQuery(map, NULL, "MGO"); /* default is stdout */
37✔
4923

4924
  } else {
4925
    mapservObj *msObj;
4926

4927
    char **translated_names, **translated_values;
4928
    int translated_numentries;
4929
    msObj = msAllocMapServObj();
9✔
4930

4931
    /* Translate some vars from WMS to mapserv */
4932
    msTranslateWMS2Mapserv((const char **)names, (const char **)values,
9✔
4933
                           numentries, &translated_names, &translated_values,
4934
                           &translated_numentries);
4935

4936
    msObj->map = map;
9✔
4937
    msFreeCharArray(msObj->request->ParamNames, msObj->request->NumParams);
9✔
4938
    msFreeCharArray(msObj->request->ParamValues, msObj->request->NumParams);
9✔
4939
    msObj->request->ParamNames = translated_names;
9✔
4940
    msObj->request->ParamValues = translated_values;
9✔
4941
    msObj->Mode = QUERY;
9✔
4942
    msObj->request->NumParams = translated_numentries;
9✔
4943
    msObj->mappnt.x = point.x;
9✔
4944
    msObj->mappnt.y = point.y;
9✔
4945

4946
    bool hasResults = false;
4947
    for (int i = 0; i < map->numlayers; i++) {
13✔
4948
      layerObj *lp = (GET_LAYER(map, i));
11✔
4949

4950
      if (lp->status == MS_ON && lp->resultcache &&
11✔
4951
          lp->resultcache->numresults != 0) {
8✔
4952
        hasResults = true;
4953
        break;
4954
      }
4955
    }
4956

4957
    if (!hasResults && msObj->map->web.empty) {
9✔
4958
      if (msReturnURL(msObj, msObj->map->web.empty, BROWSE) != MS_SUCCESS)
1✔
UNCOV
4959
        return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4960
    } else if (msReturnTemplateQuery(msObj, (char *)info_format, NULL) !=
8✔
4961
               MS_SUCCESS)
UNCOV
4962
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4963

4964
    /* We don't want to free the map since it */
4965
    /* belongs to the caller, set it to NULL before freeing the mapservObj */
4966
    msObj->map = NULL;
9✔
4967

4968
    msFreeMapServObj(msObj);
9✔
4969
  }
4970

4971
  return (MS_SUCCESS);
4972
}
62✔
4973

4974
/*
4975
** msWMSDescribeLayer()
4976
*/
4977
static int msWMSDescribeLayer(mapObj *map, int nVersion, char **names,
19✔
4978
                              char **values, int numentries,
4979
                              const char *wms_exception_format,
4980
                              owsRequestObj *ows_request) {
4981
  std::vector<std::string> wmslayers;
4982
  const char *version = NULL;
4983
  const char *sld_version = NULL;
4984

4985
  for (int i = 0; i < numentries; i++) {
130✔
4986
    if (strcasecmp(names[i], "LAYERS") == 0) {
111✔
4987
      wmslayers = msStringSplit(values[i], ',');
19✔
4988
    }
4989
    if (strcasecmp(names[i], "VERSION") == 0) {
111✔
4990
      version = values[i];
19✔
4991
    }
4992
    if (strcasecmp(names[i], "SLD_VERSION") == 0) {
111✔
4993
      sld_version = values[i];
16✔
4994
    }
4995
  }
4996

4997
  if (nVersion >= OWS_1_3_0 && sld_version == NULL) {
19✔
UNCOV
4998
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
4999
                         "Missing required parameter SLD_VERSION",
5000
                         "DescribeLayer()");
UNCOV
5001
    return msWMSException(map, nVersion, "MissingParameterValue",
×
5002
                          wms_exception_format);
5003
  }
5004
  if (nVersion >= OWS_1_3_0 && strcasecmp(sld_version, "1.1.0") != 0) {
19✔
UNCOV
5005
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5006
                         "SLD_VERSION must be 1.1.0", "DescribeLayer()");
UNCOV
5007
    return msWMSException(map, nVersion, "InvalidParameterValue",
×
5008
                          wms_exception_format);
5009
  }
5010

5011
  msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
19✔
5012
  msIO_sendHeaders();
19✔
5013

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

5016
  {
5017
    char *schemalocation = msEncodeHTMLEntities(msOWSGetSchemasLocation(map));
19✔
5018
    if (nVersion < OWS_1_3_0) {
19✔
5019

5020
      msIO_printf("<!DOCTYPE WMS_DescribeLayerResponse SYSTEM "
3✔
5021
                  "\"%s/wms/1.1.1/WMS_DescribeLayerResponse.dtd\">\n",
5022
                  schemalocation);
5023

5024
      msIO_printf("<WMS_DescribeLayerResponse version=\"%s\" >\n", version);
3✔
5025
    } else {
5026
      msIO_printf("<DescribeLayerResponse xmlns=\"http://www.opengis.net/sld\" "
16✔
5027
                  "xmlns:ows=\"http://www.opengis.net/ows\" "
5028
                  "xmlns:se=\"http://www.opengis.net/se\" "
5029
                  "xmlns:wfs=\"http://www.opengis.net/wfs\" "
5030
                  "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
5031
                  "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
5032
                  "xsi:schemaLocation=\"http://www.opengis.net/sld "
5033
                  "%s/sld/1.1.0/DescribeLayer.xsd\">\n",
5034
                  schemalocation);
5035
      msIO_printf("<Version>%s</Version>\n", sld_version);
16✔
5036
    }
5037
    free(schemalocation);
19✔
5038
  }
5039

5040
  /* check if map-level metadata wfs(wcs)_onlineresource is available */
5041
  const char *pszOnlineResMapWFS =
5042
      msOWSLookupMetadata(&(map->web.metadata), "FO", "onlineresource");
19✔
5043
  if (pszOnlineResMapWFS && strlen(pszOnlineResMapWFS) == 0)
19✔
5044
    pszOnlineResMapWFS = NULL;
5045

5046
  const char *pszOnlineResMapWCS =
5047
      msOWSLookupMetadata(&(map->web.metadata), "CO", "onlineresource");
19✔
5048
  if (pszOnlineResMapWCS && strlen(pszOnlineResMapWCS) == 0)
19✔
5049
    pszOnlineResMapWCS = NULL;
5050

5051
  auto [layerTree, mapNameToNode] = msWMSCreateLayerTree(map, nullptr);
19✔
5052
  (void)layerTree;
5053

5054
  for (const auto &wmslayer : wmslayers) {
38✔
5055
    auto iter = mapNameToNode.find(msStringToLower(wmslayer));
19✔
5056
    if (iter != mapNameToNode.end()) {
19✔
5057
      const auto layerIndices = iter->second->collectLayerIndices();
19✔
5058
      for (int j : layerIndices) {
69✔
5059
        layerObj *lp = GET_LAYER(map, j);
50✔
5060
        if (msIntegerInArray(lp->index, ows_request->enabled_layers,
50✔
5061
                             ows_request->numlayers)) {
5062
          /* Look for a WFS onlineresouce at the layer level and then at
5063
           * the map level.
5064
           */
5065
          const char *pszOnlineResLyrWFS =
5066
              msOWSLookupMetadata(&(lp->metadata), "FO", "onlineresource");
50✔
5067
          const char *pszOnlineResLyrWCS =
5068
              msOWSLookupMetadata(&(lp->metadata), "CO", "onlineresource");
50✔
5069
          if (pszOnlineResLyrWFS == NULL || strlen(pszOnlineResLyrWFS) == 0)
50✔
5070
            pszOnlineResLyrWFS = pszOnlineResMapWFS;
5071

5072
          if (pszOnlineResLyrWCS == NULL || strlen(pszOnlineResLyrWCS) == 0)
50✔
5073
            pszOnlineResLyrWCS = pszOnlineResMapWCS;
5074

5075
          if (pszOnlineResLyrWFS &&
50✔
5076
              (lp->type == MS_LAYER_POINT || lp->type == MS_LAYER_LINE ||
1✔
5077
               lp->type == MS_LAYER_POLYGON)) {
5078
            char *pszOnlineResEncoded =
5079
                msEncodeHTMLEntities(pszOnlineResLyrWFS);
1✔
5080
            char *pszLayerName = msEncodeHTMLEntities(lp->name);
1✔
5081

5082
            if (nVersion < OWS_1_3_0) {
1✔
5083
              msIO_printf("<LayerDescription name=\"%s\" wfs=\"%s\" "
1✔
5084
                          "owsType=\"WFS\" owsURL=\"%s\">\n",
5085
                          pszLayerName, pszOnlineResEncoded,
5086
                          pszOnlineResEncoded);
5087
              msIO_printf("<Query typeName=\"%s\" />\n", pszLayerName);
1✔
5088
              msIO_printf("</LayerDescription>\n");
1✔
5089
            } else { /*wms 1.3.0*/
UNCOV
5090
              msIO_printf("  <LayerDescription>\n");
×
UNCOV
5091
              msIO_printf("    <owsType>wfs</owsType>\n");
×
UNCOV
5092
              msIO_printf("    <se:OnlineResource xlink:type=\"simple\" "
×
5093
                          "xlink:href=\"%s\"/>\n",
5094
                          pszOnlineResEncoded);
UNCOV
5095
              msIO_printf("    <TypeName>\n");
×
UNCOV
5096
              msIO_printf("      <se:FeatureTypeName>%s</se:FeatureTypeName>\n",
×
5097
                          pszLayerName);
UNCOV
5098
              msIO_printf("    </TypeName>\n");
×
UNCOV
5099
              msIO_printf("  </LayerDescription>\n");
×
5100
            }
5101

5102
            msFree(pszOnlineResEncoded);
1✔
5103
            msFree(pszLayerName);
1✔
5104
          } else if (pszOnlineResLyrWCS && lp->type == MS_LAYER_RASTER &&
50✔
UNCOV
5105
                     lp->connectiontype != MS_WMS) {
×
5106
            char *pszOnlineResEncoded =
UNCOV
5107
                msEncodeHTMLEntities(pszOnlineResLyrWCS);
×
UNCOV
5108
            char *pszLayerName = msEncodeHTMLEntities(lp->name);
×
5109

UNCOV
5110
            if (nVersion < OWS_1_3_0) {
×
UNCOV
5111
              msIO_printf("<LayerDescription name=\"%s\"  owsType=\"WCS\" "
×
5112
                          "owsURL=\"%s\">\n",
5113
                          pszLayerName, pszOnlineResEncoded);
5114
              msIO_printf("<Query typeName=\"%s\" />\n", pszLayerName);
×
5115
              msIO_printf("</LayerDescription>\n");
×
5116
            } else {
UNCOV
5117
              msIO_printf("  <LayerDescription>\n");
×
UNCOV
5118
              msIO_printf("    <owsType>wcs</owsType>\n");
×
5119
              msIO_printf("    <se:OnlineResource xlink:type=\"simple\" "
×
5120
                          "xlink:href=\"%s\"/>\n",
5121
                          pszOnlineResEncoded);
5122
              msIO_printf("    <TypeName>\n");
×
5123
              msIO_printf(
×
5124
                  "      <se:CoverageTypeName>%s</se:CoverageTypeName>\n",
5125
                  pszLayerName);
UNCOV
5126
              msIO_printf("    </TypeName>\n");
×
UNCOV
5127
              msIO_printf("  </LayerDescription>\n");
×
5128
            }
5129
            msFree(pszOnlineResEncoded);
×
UNCOV
5130
            msFree(pszLayerName);
×
5131
          } else {
×
5132
            char *pszLayerName = msEncodeHTMLEntities(lp->name);
49✔
5133

5134
            if (nVersion < OWS_1_3_0)
49✔
5135
              msIO_printf("<LayerDescription name=\"%s\"></LayerDescription>\n",
2✔
5136
                          pszLayerName);
5137
            else { /*wms 1.3.0*/
5138
              msIO_printf("  <LayerDescription>\n");
47✔
5139
              /*need to have a owstype for the DescribeLayer to be valid*/
5140
              if (lp->type == MS_LAYER_RASTER && lp->connectiontype != MS_WMS)
47✔
5141
                msIO_printf("    <owsType>wcs</owsType>\n");
×
5142
              else
5143
                msIO_printf("    <owsType>wfs</owsType>\n");
47✔
5144

5145
              msIO_printf("    <se:OnlineResource xlink:type=\"simple\"/>\n");
47✔
5146
              msIO_printf("    <TypeName>\n");
47✔
5147
              if (lp->type == MS_LAYER_RASTER && lp->connectiontype != MS_WMS)
47✔
UNCOV
5148
                msIO_printf(
×
5149
                    "      <se:CoverageTypeName>%s</se:CoverageTypeName>\n",
5150
                    pszLayerName);
5151
              else
5152
                msIO_printf(
47✔
5153
                    "      <se:FeatureTypeName>%s</se:FeatureTypeName>\n",
5154
                    pszLayerName);
5155
              msIO_printf("    </TypeName>\n");
47✔
5156
              msIO_printf("  </LayerDescription>\n");
47✔
5157
            }
5158

5159
            msFree(pszLayerName);
49✔
5160
          }
5161
        }
5162
      }
5163
    }
19✔
5164
  }
5165

5166
  if (nVersion < OWS_1_3_0)
19✔
5167
    msIO_printf("</WMS_DescribeLayerResponse>\n");
3✔
5168
  else
5169
    msIO_printf("</DescribeLayerResponse>\n");
16✔
5170

5171
  return (MS_SUCCESS);
5172
}
19✔
5173

5174
/*
5175
** msWMSGetLegendGraphic()
5176
*/
5177
static int msWMSLegendGraphic(mapObj *map, int nVersion, char **names,
46✔
5178
                              char **values, int numentries,
5179
                              const char *wms_exception_format,
5180
                              owsRequestObj *ows_request,
5181
                              map_hittest *hittest) {
5182
  const char *pszLayer = NULL;
5183
  const char *pszFormat = NULL;
5184
  const char *psRule = NULL;
5185
  const char *psScale = NULL;
5186
  int iLayerIndex = -1;
5187
  outputFormatObj *psFormat = NULL;
5188
  imageObj *img = NULL;
5189
  int nWidth = -1, nHeight = -1;
5190
  const char *pszStyle = NULL;
5191
  const char *sld_version = NULL;
5192
  int wms_layer = MS_FALSE;
5193
  const char *sldenabled = NULL;
5194
  const char *format_list = NULL;
5195
  int nLayers = 0;
5196

5197
  if (!hittest) {
46✔
5198
    /* we can skip a lot of testing if we already have a hittest, as it has
5199
     * already been done in the hittesting phase */
5200

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

5203
    if (sldenabled == NULL)
34✔
5204
      sldenabled = "true";
5205

5206
    for (int i = 0; i < numentries; i++) {
283✔
5207
      if (strcasecmp(names[i], "LAYER") == 0) {
249✔
5208
        pszLayer = values[i];
34✔
5209
      } else if (strcasecmp(names[i], "WIDTH") == 0)
215✔
UNCOV
5210
        nWidth = atoi(values[i]);
×
5211
      else if (strcasecmp(names[i], "HEIGHT") == 0)
215✔
UNCOV
5212
        nHeight = atoi(values[i]);
×
5213
      else if (strcasecmp(names[i], "FORMAT") == 0)
215✔
5214
        pszFormat = values[i];
34✔
5215
      else if (strcasecmp(names[i], "SCALE") == 0)
181✔
UNCOV
5216
        psScale = values[i];
×
5217

5218
      /* -------------------------------------------------------------------- */
5219
      /*      SLD support :                                                   */
5220
      /*        - check if the SLD parameter is there. it is supposed to      */
5221
      /*      refer a valid URL containing an SLD document.                   */
5222
      /*        - check the SLD_BODY parameter that should contain the SLD    */
5223
      /*      xml string.                                                     */
5224
      /* -------------------------------------------------------------------- */
5225
      else if (strcasecmp(names[i], "SLD") == 0 && values[i] &&
181✔
UNCOV
5226
               strlen(values[i]) > 0 && strcasecmp(sldenabled, "true") == 0)
×
UNCOV
5227
        msSLDApplySLDURL(map, values[i], -1, NULL, NULL);
×
5228
      else if (strcasecmp(names[i], "SLD_BODY") == 0 && values[i] &&
181✔
5229
               strlen(values[i]) > 0 && strcasecmp(sldenabled, "true") == 0)
3✔
5230
        msSLDApplySLD(map, values[i], -1, NULL, NULL);
1✔
5231
      else if (strcasecmp(names[i], "RULE") == 0)
180✔
UNCOV
5232
        psRule = values[i];
×
5233
      else if (strcasecmp(names[i], "STYLE") == 0)
180✔
5234
        pszStyle = values[i];
11✔
5235

5236
      /* -------------------------------------------------------------------- */
5237
      /*      SLD support:                                                    */
5238
      /*        - because the request parameter "sld_version" is required in  */
5239
      /*          in WMS 1.3.0, it will be set regardless of OGR support.     */
5240
      /* -------------------------------------------------------------------- */
5241
      else if (strcasecmp(names[i], "SLD_VERSION") == 0)
169✔
5242
        sld_version = values[i];
17✔
5243
    }
5244

5245
    if (!pszLayer) {
34✔
UNCOV
5246
      msSetErrorWithStatus(
×
5247
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5248
          "Mandatory LAYER parameter missing in GetLegendGraphic request.",
5249
          "msWMSGetLegendGraphic()");
5250
      return msWMSException(map, nVersion, "LayerNotDefined",
×
5251
                            wms_exception_format);
4✔
5252
    }
5253
    if (!pszFormat) {
34✔
UNCOV
5254
      msSetErrorWithStatus(
×
5255
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5256
          "Mandatory FORMAT parameter missing in GetLegendGraphic request.",
5257
          "msWMSGetLegendGraphic()");
UNCOV
5258
      return msWMSException(map, nVersion, "InvalidFormat",
×
5259
                            wms_exception_format);
5260
    }
5261

5262
    if (nVersion >= OWS_1_3_0 && sld_version == NULL) {
34✔
UNCOV
5263
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5264
                           "Missing required parameter SLD_VERSION",
5265
                           "GetLegendGraphic()");
UNCOV
5266
      return msWMSException(map, nVersion, "MissingParameterValue",
×
5267
                            wms_exception_format);
5268
    }
5269
    if (nVersion >= OWS_1_3_0 && strcasecmp(sld_version, "1.1.0") != 0) {
34✔
5270
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5271
                           "SLD_VERSION must be 1.1.0", "GetLegendGraphic()");
UNCOV
5272
      return msWMSException(map, nVersion, "InvalidParameterValue",
×
5273
                            wms_exception_format);
5274
    }
5275

5276
    auto [layerTree, mapNameToNode] = msWMSCreateLayerTree(map, nullptr);
34✔
5277
    (void)layerTree;
5278

5279
    for (int i = 0; i < map->numlayers; ++i) {
279✔
5280
      layerObj *lp = GET_LAYER(map, i);
245✔
5281
      lp->status = MS_OFF;
245✔
5282
    }
5283

5284
    auto iter = mapNameToNode.find(msStringToLower(pszLayer));
34✔
5285
    if (iter != mapNameToNode.end()) {
34✔
5286
      const auto layerIndices = iter->second->collectLayerIndices();
34✔
5287
      for (int i : layerIndices) {
100✔
5288
        layerObj *lp = GET_LAYER(map, i);
66✔
5289
        if (msIntegerInArray(lp->index, ows_request->enabled_layers,
66✔
5290
                             ows_request->numlayers)) {
5291
          nLayers++;
66✔
5292
          lp->status = MS_ON;
66✔
5293
          iLayerIndex = i;
5294
          if (lp->connectiontype == MS_WMS) {
66✔
5295
            /* we do not cascade a wms layer if it contains at least
5296
             * one class with the property name set */
5297
            wms_layer = MS_TRUE;
5298
            for (int j = 0; j < lp->numclasses; j++) {
4✔
UNCOV
5299
              if (lp->_class[j]->name != NULL &&
×
UNCOV
5300
                  strlen(lp->_class[j]->name) > 0) {
×
5301
                wms_layer = MS_FALSE;
5302
                break;
5303
              }
5304
            }
5305
          }
5306
        }
5307
      }
5308
    }
34✔
5309

5310
    if (nLayers == 0) {
34✔
UNCOV
5311
      msSetErrorWithStatus(
×
5312
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5313
          "Invalid layer given in the LAYER parameter. A layer might be disabled for \
5314
this request. Check wms/ows_enable_request settings.",
5315
          "msWMSGetLegendGraphic()");
UNCOV
5316
      return msWMSException(map, nVersion, "LayerNotDefined",
×
5317
                            wms_exception_format);
5318
    }
5319

5320
    /* if SCALE was provided in request, calculate an extent and use a default
5321
     * width and height */
5322
    if (psScale != NULL) {
34✔
5323
      double scale, cellsize;
5324

5325
      scale = atof(psScale);
UNCOV
5326
      map->width = 600;
×
UNCOV
5327
      map->height = 600;
×
5328

UNCOV
5329
      cellsize = (scale / map->resolution) / msInchesPerUnit(map->units, 0.0);
×
5330

UNCOV
5331
      map->extent.maxx = cellsize * map->width / 2.0;
×
UNCOV
5332
      map->extent.maxy = cellsize * map->height / 2.0;
×
UNCOV
5333
      map->extent.minx = -map->extent.maxx;
×
UNCOV
5334
      map->extent.miny = -map->extent.maxy;
×
5335
    }
5336

5337
    /* It's a valid Cascading WMS GetLegendGraphic request */
5338
    if (wms_layer)
34✔
5339
      return msWMSLayerExecuteRequest(map, 1, 0, 0, 0, NULL,
4✔
5340
                                      WMS_GETLEGENDGRAPHIC);
5341

5342
    /*if STYLE is set, check if it is a valid style (valid = at least one
5343
    of the classes have a the group value equals to the style */
5344
    /*style is only validated when there is only one layer #3411*/
5345
    if (nLayers == 1 && pszStyle && strlen(pszStyle) > 0 &&
30✔
5346
        strcasecmp(pszStyle, "default") != 0) {
7✔
5347
      bool found = false;
5348
      for (int i = 0; i < GET_LAYER(map, iLayerIndex)->numclasses; i++) {
11✔
5349
        if (GET_LAYER(map, iLayerIndex)->_class[i]->group &&
11✔
5350
            strcasecmp(GET_LAYER(map, iLayerIndex)->_class[i]->group,
11✔
5351
                       pszStyle) == 0) {
5352
          found = true;
5353
          break;
5354
        }
5355
      }
5356

5357
      if (!found) {
7✔
5358
        msSetErrorWithStatus(
×
5359
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5360
            "style used in the STYLE parameter is not defined on the layer.",
5361
            "msWMSGetLegendGraphic()");
UNCOV
5362
        return msWMSException(map, nVersion, "StyleNotDefined",
×
5363
                              wms_exception_format);
5364
      } else {
5365
        msFree(GET_LAYER(map, iLayerIndex)->classgroup);
7✔
5366
        GET_LAYER(map, iLayerIndex)->classgroup = msStrdup(pszStyle);
7✔
5367
      }
5368
    }
5369
  } else {
5370
    /* extract the parameters we need */
5371
    for (int i = 0; i < numentries; i++) {
182✔
5372
      if (strcasecmp(names[i], "FORMAT") == 0)
170✔
5373
        pszFormat = values[i];
12✔
5374
      else if (strcasecmp(names[i], "RULE") == 0)
158✔
UNCOV
5375
        psRule = values[i];
×
5376
    }
5377
  }
5378
  /* validate format */
5379

5380
  /*check to see if a predefined list is given*/
5381
  format_list = msOWSLookupMetadata(&(map->web.metadata), "M",
42✔
5382
                                    "getlegendgraphic_formatlist");
5383
  if (format_list) {
42✔
UNCOV
5384
    psFormat = msOwsIsOutputFormatValid(map, pszFormat, &(map->web.metadata),
×
5385
                                        "M", "getlegendgraphic_formatlist");
5386
    if (psFormat == NULL) {
×
UNCOV
5387
      msSetErrorWithStatus(MS_IMGERR, MS_HTTP_400_BAD_REQUEST,
×
5388
                           "Unsupported output format (%s).",
5389
                           "msWMSGetLegendGraphic()", pszFormat);
UNCOV
5390
      return msWMSException(map, nVersion, "InvalidFormat",
×
UNCOV
5391
                            wms_exception_format);
×
5392
    }
5393
  } else {
5394
    psFormat = msSelectOutputFormat(map, pszFormat);
42✔
5395
    if (psFormat == NULL || !MS_RENDERER_PLUGIN(psFormat))
42✔
5396
    /* msDrawLegend and msCreateLegendIcon both switch the alpha channel to gd
5397
     ** after creation, so they can be called here without going through
5398
     ** the msAlphaGD2AGG functions */
5399
    {
UNCOV
5400
      msSetErrorWithStatus(MS_IMGERR, MS_HTTP_400_BAD_REQUEST,
×
5401
                           "Unsupported output format (%s).",
5402
                           "msWMSGetLegendGraphic()", pszFormat);
UNCOV
5403
      return msWMSException(map, nVersion, "InvalidFormat",
×
UNCOV
5404
                            wms_exception_format);
×
5405
    }
5406
  }
5407
  msApplyOutputFormat(&(map->outputformat), psFormat, MS_NOOVERRIDE);
42✔
5408

5409
  if (psRule == NULL || nLayers > 1) {
42✔
5410
    if (psScale != NULL) {
42✔
5411
      /* Scale-dependent legend. map->scaledenom will be calculated in
5412
       * msDrawLegend */
UNCOV
5413
      img = msDrawLegend(map, MS_FALSE, NULL);
×
5414
    } else {
5415
      /* Scale-independent legend */
5416
      img = msDrawLegend(map, MS_TRUE, hittest);
42✔
5417
    }
5418
  } else {
5419
    /* RULE was specified. Get the class corresponding to the RULE */
5420
    /* (RULE = class->name) */
5421
    /* TBT FIXME? also check the map->scaledenom if multiple scale-dependant
5422
     * classes with same name */
5423

5424
    layerObj *lp = GET_LAYER(map, iLayerIndex);
×
5425
    int i;
UNCOV
5426
    for (i = 0; i < lp->numclasses; i++) {
×
5427
      if (lp->classgroup &&
×
5428
          (lp->_class[i]->group == NULL ||
×
UNCOV
5429
           strcasecmp(lp->_class[i]->group, lp->classgroup) != 0))
×
UNCOV
5430
        continue;
×
5431

UNCOV
5432
      if (lp->_class[i]->name && strlen(lp->_class[i]->name) > 0 &&
×
UNCOV
5433
          strcasecmp(lp->_class[i]->name, psRule) == 0)
×
5434
        break;
5435
    }
UNCOV
5436
    if (i < lp->numclasses) {
×
5437
      /* set the map legend parameters */
UNCOV
5438
      if (nWidth < 0) {
×
UNCOV
5439
        if (map->legend.keysizex > 0)
×
5440
          nWidth = map->legend.keysizex;
5441
        else
5442
          nWidth = 20; /* default values : this in not defined in the specs */
5443
      }
UNCOV
5444
      if (nHeight < 0) {
×
UNCOV
5445
        if (map->legend.keysizey > 0)
×
5446
          nHeight = map->legend.keysizey;
5447
        else
5448
          nHeight = 20;
5449
      }
5450

5451
      if (psScale != NULL) {
×
5452
        /* Scale-dependent legend. calculate map->scaledenom */
5453
        map->cellsize = msAdjustExtent(&(map->extent), map->width, map->height);
×
5454
        msCalculateScale(map->extent, map->units, map->width, map->height,
×
5455
                         map->resolution, &map->scaledenom);
5456
        img = msCreateLegendIcon(map, lp, lp->_class[i], nWidth, nHeight,
×
5457
                                 MS_FALSE);
5458
      } else {
5459
        /* Scale-independent legend */
5460
        img = msCreateLegendIcon(map, lp, lp->_class[i], nWidth, nHeight,
×
5461
                                 MS_TRUE);
5462
      }
5463
    }
UNCOV
5464
    if (img == NULL) {
×
UNCOV
5465
      msSetErrorWithStatus(MS_IMGERR, MS_HTTP_400_BAD_REQUEST,
×
5466
                           "Unavailable RULE (%s).", "msWMSGetLegendGraphic()",
5467
                           psRule);
5468
      return msWMSException(map, nVersion, "InvalidRule", wms_exception_format);
×
5469
    }
5470
  }
5471

5472
  if (img == NULL)
42✔
UNCOV
5473
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5474

5475
  msIO_setHeader("Content-Type", "%s", MS_IMAGE_MIME_TYPE(map->outputformat));
84✔
5476
  msIO_sendHeaders();
42✔
5477
  if (msSaveImage(map, img, NULL) != MS_SUCCESS)
42✔
5478
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5479

5480
  msFreeImage(img);
42✔
5481

5482
  return (MS_SUCCESS);
42✔
5483
}
5484

5485
/*
5486
** msWMSGetContentDependentLegend()
5487
*/
5488
static int msWMSGetContentDependentLegend(mapObj *map, int nVersion,
12✔
5489
                                          char **names, char **values,
5490
                                          int numentries,
5491
                                          const char *wms_exception_format,
5492
                                          owsRequestObj *ows_request) {
5493

5494
  /* turn off layer if WMS GetMap is not enabled */
5495
  for (int i = 0; i < map->numlayers; i++)
69✔
5496
    if (!msIntegerInArray(GET_LAYER(map, i)->index, ows_request->enabled_layers,
57✔
5497
                          ows_request->numlayers))
UNCOV
5498
      GET_LAYER(map, i)->status = MS_OFF;
×
5499

5500
  map_hittest hittest;
5501
  initMapHitTests(map, &hittest);
12✔
5502
  int status = msHitTestMap(map, &hittest);
12✔
5503
  if (status == MS_SUCCESS) {
12✔
5504
    status = msWMSLegendGraphic(map, nVersion, names, values, numentries,
12✔
5505
                                wms_exception_format, ows_request, &hittest);
5506
  }
5507
  freeMapHitTests(map, &hittest);
12✔
5508
  if (status != MS_SUCCESS) {
12✔
UNCOV
5509
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5510
  } else {
5511
    return MS_SUCCESS;
5512
  }
5513
}
5514

5515
/*
5516
** msWMSGetStyles() : return an SLD document for all layers that
5517
** have a status set to on or default.
5518
*/
5519
static int msWMSGetStyles(mapObj *map, int nVersion, const char *const *names,
60✔
5520
                          const char *const *values, int numentries,
5521
                          const char *wms_exception_format,
5522
                          owsRequestObj *ows_request)
5523

5524
{
5525
  bool validlayer = false;
5526

5527
  const char *sldenabled =
5528
      msOWSLookupMetadata(&(map->web.metadata), "MO", "sld_enabled");
60✔
5529
  if (sldenabled == NULL)
60✔
5530
    sldenabled = "true";
5531

5532
  auto [layerTree, mapNameToNode] = msWMSCreateLayerTree(map, nullptr);
60✔
5533
  (void)layerTree;
5534

5535
  for (int i = 0; i < numentries; i++) {
367✔
5536
    /* getMap parameters */
5537
    if (strcasecmp(names[i], "LAYERS") == 0) {
307✔
5538
      const auto wmslayers = msStringSplit(values[i], ',');
60✔
5539
      if (wmslayers.empty()) {
60✔
UNCOV
5540
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5541
                             "At least one layer name required in LAYERS.",
5542
                             "msWMSGetStyles()");
UNCOV
5543
        return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5544
      }
5545
      for (int j = 0; j < map->numlayers; j++)
1,140✔
5546
        GET_LAYER(map, j)->status = MS_OFF;
1,080✔
5547

5548
      for (const auto &wmslayer : wmslayers) {
135✔
5549
        auto iter = mapNameToNode.find(msStringToLower(wmslayer));
75✔
5550
        if (iter != mapNameToNode.end()) {
75✔
5551
          const auto layerIndices = iter->second->collectLayerIndices();
75✔
5552
          for (int layerIdx : layerIndices) {
181✔
5553
            layerObj *lp = GET_LAYER(map, layerIdx);
106✔
5554
            if (msIntegerInArray(lp->index, ows_request->enabled_layers,
106✔
5555
                                 ows_request->numlayers)) {
5556
              lp->status = MS_ON;
106✔
5557
              validlayer = true;
5558
            }
5559
          }
5560
        }
75✔
5561
      }
5562
    }
60✔
5563

5564
    else if (strcasecmp(names[i], "SLD") == 0 && values[i] &&
247✔
UNCOV
5565
             strlen(values[i]) > 0 && strcasecmp(sldenabled, "true") == 0) {
×
UNCOV
5566
      msSLDApplySLDURL(map, values[i], -1, NULL, NULL);
×
5567
    }
5568

5569
    else if (strcasecmp(names[i], "SLD_BODY") == 0 && values[i] &&
247✔
5570
             strlen(values[i]) > 0 && strcasecmp(sldenabled, "true") == 0) {
6✔
5571
      msSLDApplySLD(map, values[i], -1, NULL, NULL);
6✔
5572
    }
5573
  }
5574

5575
  /* validate all layers given. If an invalid layer is sent, return an
5576
   * exception. */
5577
  if (!validlayer) {
60✔
UNCOV
5578
    msSetErrorWithStatus(
×
5579
        MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5580
        "Invalid layer(s) given in the LAYERS parameter. A layer might be disabled for \
5581
this request. Check wms/ows_enable_request settings.",
5582
        "msWMSGetStyles()");
UNCOV
5583
    return msWMSException(map, nVersion, "LayerNotDefined",
×
5584
                          wms_exception_format);
5585
  }
5586

5587
  char *sld = NULL;
5588
  if (nVersion <= OWS_1_1_1) {
60✔
5589
    msIO_setHeader("Content-Type",
4✔
5590
                   "application/vnd.ogc.sld+xml; charset=UTF-8");
5591
    msIO_sendHeaders();
4✔
5592
    sld = msSLDGenerateSLD(map, -1, "1.0.0");
4✔
5593
  } else {
5594
    /*for wms 1.3.0 generate a 1.1 sld*/
5595
    msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
56✔
5596
    msIO_sendHeaders();
56✔
5597
    sld = msSLDGenerateSLD(map, -1, "1.1.0");
56✔
5598
  }
5599
  if (sld) {
60✔
5600
    msIO_printf("%s\n", sld);
60✔
5601
    free(sld);
60✔
5602
  }
5603

5604
  return (MS_SUCCESS);
5605
}
5606

5607
int msWMSGetSchemaExtension(mapObj *map) {
2✔
5608
  char *schemalocation = msEncodeHTMLEntities(msOWSGetSchemasLocation(map));
2✔
5609

5610
  msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
2✔
5611
  msIO_sendHeaders();
2✔
5612

5613
  msIO_printf("<?xml version='1.0' encoding=\"UTF-8\"?>\n");
2✔
5614
  msIO_printf("<schema xmlns=\"http://www.w3.org/2001/XMLSchema\" "
2✔
5615
              "xmlns:wms=\"http://www.opengis.net/wms\" "
5616
              "xmlns:ms=\"http://mapserver.gis.umn.edu/mapserver\" "
5617
              "targetNamespace=\"http://mapserver.gis.umn.edu/mapserver\" "
5618
              "elementFormDefault=\"qualified\" version=\"1.0.0\">\n");
5619
  msIO_printf("  <import namespace=\"http://www.opengis.net/wms\" "
2✔
5620
              "schemaLocation=\"%s/wms/1.3.0/capabilities_1_3_0.xsd\"/>\n",
5621
              schemalocation);
5622
  msIO_printf("  <element name=\"GetStyles\" type=\"wms:OperationType\" "
2✔
5623
              "substitutionGroup=\"wms:_ExtendedOperation\"/>\n");
5624
  msIO_printf("</schema>");
2✔
5625

5626
  free(schemalocation);
2✔
5627

5628
  return (MS_SUCCESS);
2✔
5629
}
5630

5631
#endif /* USE_WMS_SVR */
5632

5633
/*
5634
** msWMSDispatch() is the entry point for WMS requests.
5635
** - If this is a valid request then it is processed and MS_SUCCESS is returned
5636
**   on success, or MS_FAILURE on failure.
5637
** - If this does not appear to be a valid WMS request then MS_DONE
5638
**   is returned and MapServer is expected to process this as a regular
5639
**   MapServer request.
5640
*/
5641
int msWMSDispatch(mapObj *map, cgiRequestObj *req, owsRequestObj *ows_request,
747✔
5642
                  int force_wms_mode) {
5643
#ifdef USE_WMS_SVR
5644
  int nVersion = OWS_VERSION_NOTSET;
5645
  const char *version = NULL, *request = NULL, *service = NULL, *format = NULL,
5646
             *updatesequence = NULL, *language = NULL;
5647
  const char *wms_exception_format = NULL;
5648

5649
  /*
5650
  ** Process Params common to all requests
5651
  */
5652
  /* VERSION (WMTVER in 1.0.0) and REQUEST must be present in a valid request */
5653
  for (int i = 0; i < req->NumParams; i++) {
8,561✔
5654
    if (strcasecmp(req->ParamNames[i], "VERSION") == 0)
7,814✔
5655
      version = req->ParamValues[i];
736✔
5656
    else if (strcasecmp(req->ParamNames[i], "WMTVER") == 0 && version == NULL)
7,078✔
UNCOV
5657
      version = req->ParamValues[i];
×
5658
    else if (strcasecmp(req->ParamNames[i], "UPDATESEQUENCE") == 0)
7,078✔
5659
      updatesequence = req->ParamValues[i];
6✔
5660
    else if (strcasecmp(req->ParamNames[i], "REQUEST") == 0)
7,072✔
5661
      request = req->ParamValues[i];
738✔
5662
    else if (strcasecmp(req->ParamNames[i], "EXCEPTIONS") == 0)
6,334✔
5663
      wms_exception_format = req->ParamValues[i];
57✔
5664
    else if (strcasecmp(req->ParamNames[i], "SERVICE") == 0)
6,277✔
5665
      service = req->ParamValues[i];
745✔
5666
    else if (strcasecmp(req->ParamNames[i], "FORMAT") == 0)
5,532✔
5667
      format = req->ParamValues[i];
574✔
5668
    else if (strcasecmp(req->ParamNames[i], "LANGUAGE") == 0 &&
4,968✔
5669
             msOWSLookupMetadata(&(map->web.metadata), "MO",
10✔
5670
                                 "inspire_capabilities"))
5671
      language = req->ParamValues[i];
10✔
5672
  }
5673

5674
  /* If SERVICE is specified then it MUST be "WMS" */
5675
  if (service != NULL && strcasecmp(service, "WMS") != 0)
747✔
5676
    return MS_DONE; /* Not a WMS request */
5677

5678
  nVersion = msOWSParseVersionString(version);
747✔
5679
  if (nVersion == OWS_VERSION_BADFORMAT) {
747✔
5680
    /* Invalid version format. msSetError() has been called by
5681
     * msOWSParseVersionString() and we return the error as an exception
5682
     */
5683
    return msWMSException(map, OWS_VERSION_NOTSET, NULL, wms_exception_format);
8✔
5684
  }
5685

5686
  /*
5687
  ** GetCapbilities request needs the service parameter defined as WMS:
5688
  see section 7.1.3.2 wms 1.1.1 specs for description.
5689
  */
5690
  if (request && service == NULL &&
739✔
5691
      (strcasecmp(request, "capabilities") == 0 ||
3✔
5692
       strcasecmp(request, "GetCapabilities") == 0) &&
3✔
UNCOV
5693
      (nVersion >= OWS_1_1_0 || nVersion == OWS_VERSION_NOTSET)) {
×
UNCOV
5694
    if (force_wms_mode) {
×
UNCOV
5695
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5696
                           "Required SERVICE parameter missing.",
5697
                           "msWMSDispatch");
UNCOV
5698
      return msWMSException(map, nVersion, "ServiceNotDefined",
×
UNCOV
5699
                            wms_exception_format);
×
5700
    } else
5701
      return MS_DONE;
5702
  }
5703

5704
  /*
5705
  ** Dispatch request... we should probably do some validation on VERSION here
5706
  ** vs the versions we actually support.
5707
  */
5708
  if (request && (strcasecmp(request, "capabilities") == 0 ||
739✔
5709
                  strcasecmp(request, "GetCapabilities") == 0)) {
738✔
5710
    const char *enable_request;
5711
    int globally_enabled, disabled = MS_FALSE;
68✔
5712

5713
    if (nVersion == OWS_VERSION_NOTSET) {
68✔
5714
      version = msOWSLookupMetadata(&(map->web.metadata), "M",
10✔
5715
                                    "getcapabilities_version");
5716
      if (version)
10✔
5717
        nVersion = msOWSParseVersionString(version);
×
5718
      else
5719
        nVersion =
5720
            OWS_1_3_0; /* VERSION is optional with getCapabilities only */
5721
    }
5722

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

5726
    msOWSRequestLayersEnabled(map, "M", "GetCapabilities", ows_request);
68✔
5727

5728
    enable_request =
5729
        msOWSLookupMetadata(&map->web.metadata, "OM", "enable_request");
68✔
5730
    globally_enabled =
5731
        msOWSParseRequestMetadata(enable_request, "GetCapabilities", &disabled);
68✔
5732

5733
    if (ows_request->numlayers == 0 && !globally_enabled) {
68✔
5734
      msSetErrorWithStatus(
2✔
5735
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5736
          "WMS request not enabled. Check wms/ows_enable_request settings.",
5737
          "msWMSGetCapabilities()");
5738
      return msWMSException(map, nVersion, NULL, wms_exception_format);
2✔
5739
    }
5740
    msAcquireLock(TLOCK_WxS);
66✔
5741
    const int status =
5742
        msWMSGetCapabilities(map, nVersion, req, ows_request, updatesequence,
66✔
5743
                             wms_exception_format, language);
5744
    msReleaseLock(TLOCK_WxS);
66✔
5745
    return status;
66✔
5746
  } else if (request && (strcasecmp(request, "context") == 0 ||
670✔
5747
                         strcasecmp(request, "GetContext") == 0)) {
670✔
5748
    /* Return a context document with all layers in this mapfile
5749
     * This is not a standard WMS request.
5750
     * __TODO__ The real implementation should actually return only context
5751
     * info for selected layers in the LAYERS parameter.
5752
     */
5753
    const char *getcontext_enabled;
5754
    getcontext_enabled =
UNCOV
5755
        msOWSLookupMetadata(&(map->web.metadata), "MO", "getcontext_enabled");
×
5756

UNCOV
5757
    if (nVersion != OWS_VERSION_NOTSET) {
×
5758
      /* VERSION, if specified, is Map Context version, not WMS version */
5759
      /* Pass it via wms_context_version metadata */
5760
      char szVersion[OWS_VERSION_MAXLEN];
UNCOV
5761
      msInsertHashTable(&(map->web.metadata), "wms_context_version",
×
5762
                        msOWSGetVersionString(nVersion, szVersion));
5763
    }
5764
    /* Now set version to 1.1.1 for error handling purposes */
5765
    nVersion = OWS_1_1_1;
5766

UNCOV
5767
    if (getcontext_enabled == NULL || atoi(getcontext_enabled) == 0) {
×
UNCOV
5768
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5769
                           "GetContext not enabled on this server.",
5770
                           "msWMSDispatch()");
UNCOV
5771
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5772
    }
5773

UNCOV
5774
    if (msOWSMakeAllLayersUnique(map) != MS_SUCCESS)
×
UNCOV
5775
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5776

UNCOV
5777
    msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
×
UNCOV
5778
    msIO_sendHeaders();
×
5779

UNCOV
5780
    if (msWriteMapContext(map, stdout) != MS_SUCCESS)
×
5781
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5782
    /* Request completed */
5783
    return MS_SUCCESS;
5784
  } else if (request && strcasecmp(request, "GetMap") == 0 && format &&
670✔
5785
             strcasecmp(format, "image/txt") == 0) {
474✔
5786
    /* Until someone adds full support for ASCII graphics this should do. ;) */
UNCOV
5787
    msIO_setHeader("Content-Type", "text/plain; charset=UTF-8");
×
UNCOV
5788
    msIO_sendHeaders();
×
UNCOV
5789
    msIO_printf(".\n               ,,ggddY\"\"\"Ybbgg,,\n          ,agd888b,_ "
×
5790
                "\"Y8, ___'\"\"Ybga,\n       ,gdP\"\"88888888baa,.\"\"8b    \""
5791
                "888g,\n     ,dP\"     ]888888888P'  \"Y     '888Yb,\n   ,dP\""
5792
                "      ,88888888P\"  db,       \"8P\"\"Yb,\n  ,8\"       ,8888"
5793
                "88888b, d8888a           \"8,\n ,8'        d88888888888,88P\""
5794
                "' a,          '8,\n,8'         88888888888888PP\"  \"\"      "
5795
                "     '8,\nd'          I88888888888P\"                   'b\n8"
5796
                "           '8\"88P\"\"Y8P'                      8\n8         "
5797
                "   Y 8[  _ \"                        8\n8              \"Y8d8"
5798
                "b  \"Y a                   8\n8                 '\"\"8d,   __"
5799
                "                 8\nY,                    '\"8bd888b,        "
5800
                "     ,P\n'8,                     ,d8888888baaa       ,8'\n '8"
5801
                ",                    888888888888'      ,8'\n  '8a           "
5802
                "        \"8888888888I      a8'\n   'Yba                  'Y88"
5803
                "88888P'    adP'\n     \"Yba                 '888888P'   adY\""
5804
                "\n       '\"Yba,             d8888P\" ,adP\"' \n          '\""
5805
                "Y8baa,      ,d888P,ad8P\"' \n               ''\"\"YYba8888P\""
5806
                "\"''\n");
UNCOV
5807
    return MS_SUCCESS;
×
5808
  }
5809

5810
  /* If SERVICE, VERSION and REQUEST not included than this isn't a WMS req*/
5811
  if (service == NULL && nVersion == OWS_VERSION_NOTSET && request == NULL)
671✔
5812
    return MS_DONE; /* Not a WMS request */
5813

5814
  /* VERSION *and* REQUEST required by both getMap and getFeatureInfo */
5815
  if (nVersion == OWS_VERSION_NOTSET) {
671✔
5816
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
5817
                         "Incomplete WMS request: VERSION parameter missing",
5818
                         "msWMSDispatch()");
5819
    return msWMSException(map, OWS_VERSION_NOTSET, NULL, wms_exception_format);
1✔
5820
  }
5821

5822
  /*check if the version is one of the supported versions*/
5823
  if (nVersion != OWS_1_0_0 && nVersion != OWS_1_1_0 && nVersion != OWS_1_1_1 &&
670✔
5824
      nVersion != OWS_1_3_0) {
392✔
UNCOV
5825
    msSetErrorWithStatus(
×
5826
        MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5827
        "Invalid WMS version: VERSION %s is not supported. Supported "
5828
        "versions are 1.0.0, 1.1.0, 1.1.1, 1.3.0",
5829
        "msWMSDispatch()", version);
UNCOV
5830
    return msWMSException(map, OWS_VERSION_NOTSET, NULL, wms_exception_format);
×
5831
  }
5832

5833
  if (request == NULL) {
670✔
UNCOV
5834
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5835
                         "Incomplete WMS request: REQUEST parameter missing",
5836
                         "msWMSDispatch()");
UNCOV
5837
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5838
  }
5839

5840
  /* hack !? The function can return MS_DONE ... be sure it's a wms request
5841
   * before checking the enabled layers */
5842
  if ((strcasecmp(request, "GetStyles") == 0) ||
670✔
5843
      (strcasecmp(request, "GetLegendGraphic") == 0) ||
610✔
5844
      (strcasecmp(request, "GetSchemaExtension") == 0) ||
563✔
5845
      (strcasecmp(request, "map") == 0 || strcasecmp(request, "GetMap") == 0) ||
561✔
5846
      (strcasecmp(request, "feature_info") == 0 ||
86✔
5847
       strcasecmp(request, "GetFeatureInfo") == 0) ||
86✔
5848
      (strcasecmp(request, "DescribeLayer") == 0)) {
21✔
5849
    const char *request_tmp;
5850
    if (strcasecmp(request, "map") == 0)
670✔
5851
      request_tmp = "GetMap";
5852
    else if (strcasecmp(request, "feature_info") == 0)
670✔
5853
      request_tmp = "GetFeatureInfo";
5854
    else
5855
      request_tmp = request;
5856

5857
    msOWSRequestLayersEnabled(map, "M", request_tmp, ows_request);
670✔
5858
    if (ows_request->numlayers == 0) {
670✔
5859
      msSetErrorWithStatus(
1✔
5860
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5861
          "WMS request not enabled. Check wms/ows_enable_request settings.",
5862
          "msWMSDispatch()");
5863
      return msWMSException(map, nVersion, NULL, wms_exception_format);
1✔
5864
    }
5865
  }
5866

5867
  if (msOWSMakeAllLayersUnique(map) != MS_SUCCESS)
669✔
UNCOV
5868
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5869

5870
  bool isContentDependentLegend = false;
5871
  if (strcasecmp(request, "GetLegendGraphic") == 0) {
669✔
5872
    /*
5873
     * check for a BBOX in the request, in that case we have a content-dependant
5874
     * legend request, and should be following the GetMap path a bit more
5875
     */
5876
    bool found = false;
5877
    for (int i = 0; i < req->NumParams; i++) {
430✔
5878
      if (strcasecmp(req->ParamNames[i], "BBOX") == 0) {
396✔
5879
        if (req->ParamValues[i] && *req->ParamValues[i]) {
12✔
5880
          found = true;
5881
          break;
5882
        }
5883
      }
5884
    }
5885
    if (found) {
46✔
5886
      isContentDependentLegend = true;
5887
      /* getLegendGraphic uses LAYER= , we need to create a LAYERS= value that
5888
       * is identical we'll suppose that the client is conformat and hasn't
5889
       * included a LAYERS= parameter in its request */
5890
      for (int i = 0; i < req->NumParams; i++) {
182✔
5891
        if (strcasecmp(req->ParamNames[i], "LAYER") == 0) {
170✔
5892
          req->ParamNames[req->NumParams] = msStrdup("LAYERS");
9✔
5893
          req->ParamValues[req->NumParams] = msStrdup(req->ParamValues[i]);
9✔
5894
          req->NumParams++;
9✔
5895
        }
5896
      }
5897
    } else {
5898
      return msWMSLegendGraphic(map, nVersion, req->ParamNames,
34✔
5899
                                req->ParamValues, req->NumParams,
5900
                                wms_exception_format, ows_request, NULL);
34✔
5901
    }
5902
  }
5903

5904
  if (strcasecmp(request, "GetStyles") == 0)
635✔
5905
    return msWMSGetStyles(map, nVersion, req->ParamNames, req->ParamValues,
60✔
5906
                          req->NumParams, wms_exception_format, ows_request);
60✔
5907

5908
  else if (request && strcasecmp(request, "GetSchemaExtension") == 0)
575✔
5909
    return msWMSGetSchemaExtension(map);
2✔
5910

5911
  /* getMap parameters are used by both getMap, getFeatureInfo, and content
5912
   * dependent legendgraphics */
5913
  if (strcasecmp(request, "map") == 0 || strcasecmp(request, "GetMap") == 0 ||
573✔
5914
      strcasecmp(request, "feature_info") == 0 ||
98✔
5915
      strcasecmp(request, "GetFeatureInfo") == 0 ||
98✔
5916
      strcasecmp(request, "DescribeLayer") == 0 || isContentDependentLegend) {
33✔
5917

5918
    const int status = msWMSLoadGetMapParams(
573✔
5919
        map, nVersion, req->ParamNames, req->ParamValues, req->NumParams,
5920
        wms_exception_format, request, ows_request);
5921
    if (status != MS_SUCCESS)
573✔
5922
      return status;
5923
  }
5924

5925
  /* This function owns validated_language, so remember to free it later*/
5926
  char *validated_language = msOWSGetLanguageFromList(map, "MO", language);
512✔
5927
  if (validated_language != NULL) {
512✔
5928
    msMapSetLanguageSpecificConnection(map, validated_language);
47✔
5929
  }
5930
  msFree(validated_language);
512✔
5931

5932
  if (strcasecmp(request, "map") == 0 || strcasecmp(request, "GetMap") == 0)
512✔
5933
    return msWMSGetMap(map, nVersion, req->ParamNames, req->ParamValues,
419✔
5934
                       req->NumParams, wms_exception_format, ows_request);
419✔
5935
  else if (strcasecmp(request, "feature_info") == 0 ||
93✔
5936
           strcasecmp(request, "GetFeatureInfo") == 0)
93✔
5937
    return msWMSFeatureInfo(map, nVersion, req->ParamNames, req->ParamValues,
62✔
5938
                            req->NumParams, wms_exception_format, ows_request);
62✔
5939
  else if (strcasecmp(request, "DescribeLayer") == 0) {
31✔
5940
    return msWMSDescribeLayer(map, nVersion, req->ParamNames, req->ParamValues,
19✔
5941
                              req->NumParams, wms_exception_format,
5942
                              ows_request);
19✔
5943
  } else if (isContentDependentLegend) {
12✔
5944
    return msWMSGetContentDependentLegend(map, nVersion, req->ParamNames,
12✔
5945
                                          req->ParamValues, req->NumParams,
5946
                                          wms_exception_format, ows_request);
12✔
5947
  }
5948

5949
  /* Hummmm... incomplete or unsupported WMS request */
UNCOV
5950
  if (service != NULL && strcasecmp(service, "WMS") == 0) {
×
UNCOV
5951
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5952
                         "Incomplete or unsupported WMS request",
5953
                         "msWMSDispatch()");
UNCOV
5954
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5955
  } else
5956
    return MS_DONE; /* Not a WMS request */
5957
#else
5958
  msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5959
                       "WMS server support is not available.",
5960
                       "msWMSDispatch()");
5961
  return (MS_FAILURE);
5962
#endif
5963
}
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