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

MapServer / MapServer / 20718230642

05 Jan 2026 02:18PM UTC coverage: 41.754% (+0.03%) from 41.725%
20718230642

push

github

web-flow
WMS: rework building of the layer tree to make it faster, and understandable! (#7410)

* WMS: rework building of the layer tree to make it faster, and understandable!

Avoid quadratic performance in the number of layers.

On a 103 MB .map file with 6130 layers and 3 level of nesting, WMS
GetCapabilities response generation goes from 56 seconds to 5 seconds.

* msRenameLayer(): avoid potential overflow

* mapows.cpp: minimal conversion to C++

* msOWSMakeAllLayersUnique(): avoid quadratic performance in number of layers

* loadMap(): optimize CRS creation when they are all the same

360 of 422 new or added lines in 4 files covered. (85.31%)

76 existing lines in 4 files now uncovered.

62888 of 150616 relevant lines covered (41.75%)

25229.23 hits per line

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

82.63
/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;
14✔
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
    /* But first, start by removing any use_default_extent_for_getfeature
494
     * metadata items that could result in the BBOX to be removed */
495

496
    hashTableObj *tmpTable = msCreateHashTable();
15✔
497

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

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

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

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

515
    msRemoveHashTable(&(lp->metadata), "gml_wmsfilter_flag");
15✔
516

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

524
    msFreeHashTable(tmpTable);
15✔
525

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

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

540
    FLTFreeFilterEncodingNode(psNode);
15✔
541

542
  } /* for */
15✔
543

544
  msFreeCharArray(paszFilters, numfilters);
10✔
545

546
  return MS_SUCCESS;
547
}
548

549
/*
550
** Class representing a node in the hierarchy of <Layer> of the
551
* GetCapabilities response. All nodes don't necessarily correspond to an
552
* actual MapServer LAYER when groups are involved.
553
*/
554
class msWMSLayerNode {
555
public:
556
  std::string name{};
557
  std::string title{};
558
  std::string titleWarning{};
559
  std::string abstract{};
560
  int layerIdx = -1;
561
  msWMSLayerNode *parent = nullptr;
562
  std::vector<std::unique_ptr<msWMSLayerNode>> children{};
563

564
  msWMSLayerNode() = default;
6,093✔
565

566
  /*
567
  ** isQueryable()
568
  */
569
  bool isQueryable(const mapObj *map) const {
262✔
570
    if (layerIdx >= 0 && msIsLayerQueryable(GET_LAYER(map, layerIdx)))
262✔
571
      return true;
572
    for (const auto &child : children) {
168✔
573
      if (child->isQueryable(map)) {
54✔
574
        return true;
575
      }
576
    }
577
    return false;
578
  }
579

580
  /*
581
  ** collectLayerIndices()
582
  */
583
  std::vector<int> collectLayerIndices() const {
1,052✔
584
    std::vector<int> res;
585
    collectLayerIndices(res);
1,052✔
586
    return res;
1,052✔
NEW
587
  }
×
588

589
private:
590
  msWMSLayerNode(const msWMSLayerNode &) = delete;
591
  msWMSLayerNode &operator=(const msWMSLayerNode &) = delete;
592

593
  void collectLayerIndices(std::vector<int> &res) const {
1,547✔
594
    if (layerIdx >= 0)
1,547✔
595
      res.push_back(layerIdx);
1,423✔
596
    for (const auto &child : children) {
2,042✔
597
      child->collectLayerIndices(res);
495✔
598
    }
599
  }
1,547✔
600
};
601

602
/*
603
** msWMSCreateLayerTree()
604
*/
605
static std::pair<std::unique_ptr<msWMSLayerNode>,
606
                 std::map<std::string, msWMSLayerNode *>>
607
msWMSCreateLayerTree(mapObj *map, const char *validated_language) {
787✔
608
  auto root = std::make_unique<msWMSLayerNode>();
787✔
609
  std::map<std::vector<std::string>, msWMSLayerNode *> mapPathToNode;
610
  std::map<std::string, msWMSLayerNode *> mapNameToNode;
611

612
  if (map->name) {
787✔
613
    root->name = map->name;
787✔
614
    mapNameToNode[msStringToLower(root->name)] = root.get();
787✔
615
  }
616

617
  for (int i = 0; i < map->numlayers; i++) {
5,850✔
618
    layerObj *layer = GET_LAYER(map, i);
5,063✔
619
    if (!layer->name)
5,063✔
NEW
620
      continue;
×
621

622
    const char *layer_group =
623
        msOWSLookupMetadata(&(layer->metadata), "MO", "layer_group");
5,063✔
624
    const char *group = layer->group;
5,063✔
625
    const bool has_layer_group = layer_group && layer_group[0] != 0;
5,063✔
626
    const bool has_group = group && group[0] != 0;
5,063✔
627
    msWMSLayerNode *curNode = nullptr;
628

629
    std::vector<std::string> path;
630
    if (has_layer_group) {
5,063✔
631
      if (has_group) {
849✔
632
        const char *errorMsg = "It is not allowed to set both the GROUP and "
633
                               "WMS_LAYER_GROUP for a layer";
634
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_500_INTERNAL_SERVER_ERROR,
×
635
                             errorMsg, "msWMSCreateLayerTree()", NULL);
636
        msIO_fprintf(stdout, "<!-- ERROR: %s -->\n", errorMsg);
×
637
        /* cannot return exception at this point because we are already writing
638
         * to stdout */
639
      } else if (layer_group[0] != '/') {
849✔
640
        const char *errorMsg =
641
            "The WMS_LAYER_GROUP metadata does not start with a '/'";
NEW
642
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_500_INTERNAL_SERVER_ERROR,
×
643
                             errorMsg, "msWMSPrepareNestedGroups()", NULL);
NEW
644
        msIO_fprintf(stdout, "<!-- ERROR: %s -->\n", errorMsg);
×
645
        /* cannot return exception at this point because we are already
646
         * writing to stdout */
647
      } else {
648
        /* split into subgroups. Start at address + 1 because the first '/'
649
         * would cause an extra empty group */
650
        curNode = root.get();
651
        const auto splitPath = msStringSplit(layer_group + 1, '/');
849✔
652
        if (!splitPath.empty()) {
849✔
653
          bool lastComponentJustAdded = false;
654
          for (const std::string &subPath : splitPath) {
1,783✔
655
            path.push_back(msStringToLower(subPath));
1,868✔
656
            auto iter = mapPathToNode.find(path);
657
            if (iter == mapPathToNode.end()) {
934✔
658
              auto newNode = std::make_unique<msWMSLayerNode>();
137✔
659
              newNode->parent = curNode;
137✔
660
              newNode->name = subPath;
137✔
661
              newNode->title = subPath;
137✔
662
              mapPathToNode[path] = newNode.get();
137✔
663
              mapNameToNode[msStringToLower(newNode->name)] = newNode.get();
137✔
664
              curNode->children.push_back(std::move(newNode));
137✔
665
              curNode = curNode->children.back().get();
666
              lastComponentJustAdded = true;
667
            } else {
137✔
668
              curNode = iter->second;
797✔
669
              lastComponentJustAdded = false;
670
            }
671
          }
672

673
          if (lastComponentJustAdded) {
849✔
674
            const char *value;
675
            if ((value = msOWSLookupMetadataWithLanguage(&(layer->metadata),
103✔
676
                                                         "MO", "GROUP_TITLE",
677
                                                         validated_language))) {
678
              curNode->title = value;
34✔
679
            } else {
680
              curNode->title = curNode->name;
69✔
681

682
              char *pszExpandedName =
683
                  msStringConcatenate(nullptr, "GROUP_TITLE");
69✔
684
              if (validated_language && validated_language[0]) {
69✔
NEW
685
                pszExpandedName = msStringConcatenate(pszExpandedName, ".");
×
686
                pszExpandedName =
NEW
687
                    msStringConcatenate(pszExpandedName, validated_language);
×
688
              }
689
              char *pszExpandedMetadataKey =
690
                  msOWSGetExpandedMetadataKey("MO", pszExpandedName);
69✔
691
              curNode->titleWarning = "<!-- WARNING: Mandatory metadata ";
69✔
692
              curNode->titleWarning += pszExpandedMetadataKey;
693
              curNode->titleWarning += " was missing in this context. -->";
694
              msFree(pszExpandedName);
69✔
695
              msFree(pszExpandedMetadataKey);
69✔
696
            }
697
          }
698
          if (lastComponentJustAdded) {
699
            const char *value;
700
            if ((value = msOWSLookupMetadataWithLanguage(&(layer->metadata),
103✔
701
                                                         "MO", "GROUP_ABSTRACT",
702
                                                         validated_language))) {
703
              curNode->abstract = value;
34✔
704
            }
705
          }
706
        }
707
      }
849✔
708
    } else {
709
      curNode = root.get();
710
      if (has_group) {
4,214✔
711
        path.push_back(msStringToLower(group));
360✔
712
        auto iter = mapPathToNode.find(path);
713
        if (iter == mapPathToNode.end()) {
180✔
714
          auto newNode = std::make_unique<msWMSLayerNode>();
106✔
715
          newNode->parent = curNode;
106✔
716
          newNode->name = group;
106✔
717

718
          {
719
            const char *value;
720
            if ((value = msOWSLookupMetadataWithLanguage(&(layer->metadata),
106✔
721
                                                         "MO", "GROUP_TITLE",
722
                                                         validated_language))) {
723
              newNode->title = value;
41✔
724
            } else {
725
              newNode->title = newNode->name;
65✔
726

727
              char *pszExpandedName =
728
                  msStringConcatenate(nullptr, "GROUP_TITLE");
65✔
729
              if (validated_language && validated_language[0]) {
65✔
NEW
730
                pszExpandedName = msStringConcatenate(pszExpandedName, ".");
×
731
                pszExpandedName =
NEW
732
                    msStringConcatenate(pszExpandedName, validated_language);
×
733
              }
734
              char *pszExpandedMetadataKey =
735
                  msOWSGetExpandedMetadataKey("MO", pszExpandedName);
65✔
736
              newNode->titleWarning = "<!-- WARNING: Mandatory metadata ";
65✔
737
              newNode->titleWarning += pszExpandedMetadataKey;
738
              newNode->titleWarning += " was missing in this context. -->";
739
              msFree(pszExpandedName);
65✔
740
              msFree(pszExpandedMetadataKey);
65✔
741
            }
742
          }
743
          {
744
            const char *value;
745
            if ((value = msOWSLookupMetadataWithLanguage(&(layer->metadata),
106✔
746
                                                         "MO", "GROUP_ABSTRACT",
747
                                                         validated_language))) {
748
              newNode->abstract = value;
81✔
749
            }
750
          }
751

752
          mapPathToNode[path] = newNode.get();
106✔
753
          mapNameToNode[msStringToLower(newNode->name)] = newNode.get();
106✔
754
          curNode->children.push_back(std::move(newNode));
106✔
755
          curNode = root->children.back().get();
756
        } else {
106✔
757
          curNode = iter->second;
74✔
758
        }
759
      }
760
    }
761

762
    if (curNode) {
5,063✔
763
      auto newNode = std::make_unique<msWMSLayerNode>();
5,063✔
764
      newNode->parent = curNode;
5,063✔
765
      newNode->layerIdx = i;
5,063✔
766
      newNode->name = layer->name;
5,063✔
767
      path.push_back(msStringToLower(newNode->name));
5,063✔
768
      mapPathToNode[path] = newNode.get();
5,063✔
769
      mapNameToNode[msStringToLower(newNode->name)] = newNode.get();
5,063✔
770
      {
771
        const char *value;
772
        if ((value = msOWSLookupMetadataWithLanguage(
5,063✔
773
                 &(layer->metadata), "MO", "title", validated_language))) {
774
          newNode->title = value;
2,667✔
775
        } else {
776
          newNode->title = newNode->name;
2,396✔
777

778
          char *pszExpandedName = msStringConcatenate(nullptr, "TITLE");
2,396✔
779
          if (validated_language && validated_language[0]) {
2,396✔
780
            pszExpandedName = msStringConcatenate(pszExpandedName, ".");
6✔
781
            pszExpandedName =
782
                msStringConcatenate(pszExpandedName, validated_language);
6✔
783
          }
784
          char *pszExpandedMetadataKey =
785
              msOWSGetExpandedMetadataKey("MO", pszExpandedName);
2,396✔
786
          newNode->titleWarning = "<!-- WARNING: Mandatory metadata ";
2,396✔
787
          newNode->titleWarning += pszExpandedMetadataKey;
788
          newNode->titleWarning += " was missing in this context. -->";
789
          msFree(pszExpandedName);
2,396✔
790
          msFree(pszExpandedMetadataKey);
2,396✔
791
        }
792
      }
793
      {
794
        const char *value;
795
        if ((value = msOWSLookupMetadataWithLanguage(
5,063✔
796
                 &(layer->metadata), "MO", "abstract", validated_language))) {
797
          newNode->abstract = value;
293✔
798
        }
799
      }
800

801
      curNode->children.push_back(std::move(newNode));
5,063✔
802
    }
5,063✔
803
  }
5,063✔
804

805
  return std::make_pair(std::move(root), std::move(mapNameToNode));
787✔
806
}
787✔
807

808
/*
809
** Validate that a given dimension is inside the extents defined
810
*/
811
static bool msWMSValidateDimensionValue(const char *value,
29✔
812
                                        const char *dimensionextent,
813
                                        bool forcecharacter) {
814
  std::vector<pointObj> aextentranges;
815

816
  bool isextentavalue = false;
817
  bool isextentarange = false;
818
  bool ischaracter = false;
819

820
  if (forcecharacter)
821
    ischaracter = true;
822

823
  if (!value || !dimensionextent)
29✔
824
    return false;
825

826
  /*for the value, we support descrete values (2005) */
827
  /* multiple values (abc, def, ...) */
828
  /* and range(s) (1000/2000, 3000/5000) */
829
  /** we do not support resolution*/
830

831
  /* -------------------------------------------------------------------- */
832
  /*      parse the extent first.                                         */
833
  /* -------------------------------------------------------------------- */
834
  auto extents = msStringSplit(dimensionextent, ',');
29✔
835
  for (auto &extent :
29✔
836
       extents) // Make sure to get by reference so that it is updated in place
88✔
837
    msStringTrim(extent);
59✔
838

839
  std::vector<std::string> aextentvalues;
840
  if (extents.size() == 1) {
29✔
841
    if (strstr(dimensionextent, "/") == NULL) {
10✔
842
      /*single value*/
843
      isextentavalue = true;
844
      aextentvalues.push_back(dimensionextent);
×
845
      if (!forcecharacter)
×
846
        ischaracter = FLTIsNumeric(dimensionextent) == MS_FALSE;
×
847

848
    } else {
849
      const auto ranges = msStringSplit(dimensionextent, '/');
10✔
850
      if (ranges.size() == 2 || ranges.size() == 3) {
10✔
851
        /*single range*/
852
        isextentarange = true;
853
        aextentranges.resize(1);
10✔
854
        aextentranges[0].x = atof(ranges[0].c_str());
10✔
855
        aextentranges[0].y = atof(ranges[1].c_str());
10✔
856
        /*ranges should be numeric*/
857
        ischaracter = false;
858
      }
859
    }
10✔
860
  } else if (extents.size() >
19✔
861
             1) { /*check if it is muliple values or multiple ranges*/
862
    if (strstr(dimensionextent, "/") == NULL) {
19✔
863
      /*multiple values*/
864
      isextentavalue = true;
865
      aextentvalues = std::move(extents);
11✔
866
      if (!forcecharacter)
11✔
867
        ischaracter = FLTIsNumeric(aextentvalues[0].c_str()) == MS_FALSE;
11✔
868
    } else { /*multiple range extent*/
869
      int isvalidextent = MS_TRUE;
870
      /*ranges should be numeric*/
871
      ischaracter = false;
872
      isextentarange = true;
873
      aextentranges.resize(extents.size());
8✔
874
      size_t nextentranges = 0;
875

876
      for (const auto &extent : extents) {
24✔
877
        const auto onerange = msStringSplit(extent.c_str(), '/');
16✔
878
        if (onerange.size() != 2 && onerange.size() != 3) {
16✔
879
          isvalidextent = MS_FALSE;
880
          break;
881
        }
882
        if (isvalidextent) {
883

884
          aextentranges[nextentranges].x = atof(onerange[0].c_str());
16✔
885
          aextentranges[nextentranges++].y = atof(onerange[1].c_str());
16✔
886
        }
887
      }
16✔
888
      if (!isvalidextent) {
889
        nextentranges = 0;
890
        isextentarange = false;
891
      }
892
      aextentranges.resize(nextentranges);
8✔
893
    }
894
  }
895

896
  /* make sure that we got a valid extent*/
897
  if (!isextentavalue && !isextentarange) {
29✔
898
    return false;
899
  }
900

901
  /*for the extent of the dimesion, we support
902
  single value,  or list of mulitiple values comma separated,
903
  a single range or multiple ranges */
904

905
  const auto uservalues = msStringSplit(value, ',');
29✔
906
  bool uservaluevalid = false;
907
  if (uservalues.size() == 1) {
29✔
908
    /*user input=single*/
909
    /*is it descret or range*/
910
    const auto ranges = msStringSplit(uservalues[0].c_str(), '/');
12✔
911
    if (ranges.size() == 1) { /*discrete*/
12✔
912
      if (isextentavalue) {
8✔
913
        /*single user value, single/multiple values extent*/
914
        for (const auto &extentvalue : aextentvalues) {
12✔
915
          if (ischaracter)
10✔
916
            uservaluevalid = (uservalues[0] == extentvalue);
4✔
917
          else {
918
            if (atof(uservalues[0].c_str()) == atof(extentvalue.c_str()))
6✔
919
              uservaluevalid = true;
920
          }
921
          if (uservaluevalid)
8✔
922
            break;
923
        }
924
      } else if (isextentarange) {
3✔
925
        /*single user value, single/multiple range extent*/
926
        const float currentval = atof(uservalues[0].c_str());
3✔
927

928
        for (const auto &extentrange : aextentranges) {
4✔
929
          const float minval = extentrange.x;
3✔
930
          const float maxval = extentrange.y;
3✔
931
          if (currentval >= minval && currentval <= maxval) {
3✔
932
            uservaluevalid = true;
933
            break;
934
          }
935
        }
936
      }
937
    } else if (ranges.size() == 2 || ranges.size() == 3) { /*range*/
4✔
938
      /*user input=single range. In this case the extents must
939
       be of a range type.*/
940
      const float mincurrentval = atof(ranges[0].c_str());
4✔
941
      const float maxcurrentval = atof(ranges[1].c_str());
4✔
942
      if (isextentarange) {
4✔
943
        for (const auto &extentrange : aextentranges) {
7✔
944
          const float minval = extentrange.x;
5✔
945
          const float maxval = extentrange.y;
5✔
946

947
          if (minval <= mincurrentval && maxval >= maxcurrentval &&
5✔
948
              minval <= maxval) {
949
            uservaluevalid = true;
950
            break;
951
          }
952
        }
953
      }
954
    }
955
  } else if (uservalues.size() > 1) { /*user input=multiple*/
29✔
956
    if (strstr(value, "/") == NULL) {
17✔
957
      /*user input=multiple value*/
958
      bool valueisvalid = false;
959
      for (const auto &uservalue : uservalues) {
21✔
960
        valueisvalid = false;
961
        if (isextentavalue) {
17✔
962
          /*user input is multiple values, extent is defined as one or multiple
963
           * values*/
964
          for (const auto &extentvalue : aextentvalues) {
24✔
965
            if (ischaracter) {
20✔
966
              if (uservalue == extentvalue) {
9✔
967
                valueisvalid = true;
968
                break;
969
              }
970
            } else {
971
              if (atof(uservalue.c_str()) == atof(extentvalue.c_str())) {
11✔
972
                valueisvalid = true;
973
                break;
974
              }
975
            }
976
          }
977
          /*every value should be valid*/
978
          if (!valueisvalid)
8✔
979
            break;
980
        } else if (isextentarange) {
9✔
981
          /*user input is multiple values, extent is defined as one or multiple
982
           * ranges*/
983
          for (const auto &extentrange : aextentranges) {
14✔
984
            const float minval = extentrange.x;
11✔
985
            const float maxval = extentrange.y;
11✔
986
            const float currentval = atof(uservalue.c_str());
11✔
987
            if (minval <= currentval && maxval >= currentval &&
11✔
988
                minval <= maxval) {
989
              valueisvalid = true;
990
              break;
991
            }
992
          }
993
          if (!valueisvalid)
9✔
994
            break;
995
        }
996
      }
997
      uservaluevalid = valueisvalid;
998
    } else { /*user input multiple ranges*/
999
      bool valueisvalid = true;
1000

1001
      for (const auto &uservalue : uservalues) {
12✔
1002
        /*each ranges should be valid*/
1003
        const auto onerange = msStringSplit(uservalue.c_str(), '/');
10✔
1004
        if (onerange.size() == 2 || onerange.size() == 3) {
10✔
1005
          const float mincurrentval = atof(onerange[0].c_str());
10✔
1006
          const float maxcurrentval = atof(onerange[1].c_str());
10✔
1007

1008
          /*extent must be defined also as a rangle*/
1009
          if (isextentarange) {
10✔
1010
            bool found = false;
1011
            for (const auto &extentrange : aextentranges) {
17✔
1012
              const float mincurrentrange = extentrange.x;
13✔
1013
              const float maxcurrentrange = extentrange.y;
13✔
1014

1015
              if (mincurrentval >= mincurrentrange &&
13✔
1016
                  maxcurrentval <= maxcurrentrange &&
6✔
1017
                  mincurrentval <= maxcurrentval) {
1018
                found = true;
1019
                break;
1020
              }
1021
            }
1022
            if (!found) {
10✔
1023
              valueisvalid = false;
1024
              break;
1025
            }
1026
          }
1027
        } else {
1028
          valueisvalid = false;
1029
        }
1030
      }
10✔
1031
      uservaluevalid = valueisvalid;
1032
    }
1033
  }
1034

1035
  return uservaluevalid;
1036
}
29✔
1037

1038
static bool msWMSApplyDimensionLayer(layerObj *lp, const char *item,
13✔
1039
                                     const char *value, bool forcecharacter) {
1040
  bool result = false;
1041

1042
  if (lp && item && value) {
13✔
1043
    /*for the value, we support descrete values (2005) */
1044
    /* multiple values (abc, def, ...) */
1045
    /* and range(s) (1000/2000, 3000/5000) */
1046
    char *pszExpression = FLTGetExpressionForValuesRanges(
26✔
1047
        lp, item, value, forcecharacter ? MS_TRUE : MS_FALSE);
1048

1049
    if (pszExpression) {
13✔
1050
      // If tileindex is set, the filter is applied to tileindex too.
1051
      int tlpindex = -1;
1052
      if (lp->tileindex &&
13✔
1053
          (tlpindex = msGetLayerIndex(lp->map, lp->tileindex)) != -1) {
×
1054
        result = FLTApplyExpressionToLayer((GET_LAYER(lp->map, tlpindex)),
×
1055
                                           pszExpression) != MS_FALSE;
1056
      } else {
1057
        result = true;
1058
      }
1059
      result &= FLTApplyExpressionToLayer(lp, pszExpression) != MS_FALSE;
13✔
1060
      msFree(pszExpression);
13✔
1061
    }
1062
  }
1063
  return result;
13✔
1064
}
1065

1066
static bool msWMSApplyDimension(layerObj *lp, int /* version */,
29✔
1067
                                const char *dimensionname, const char *value,
1068
                                const char * /* wms_exception_format */) {
1069
  bool forcecharacter = false;
1070
  bool result = false;
1071

1072
  if (lp && dimensionname && value) {
29✔
1073
    /*check if the dimension name passes starts with dim_. All dimensions should
1074
     * start with dim_, except elevation*/
1075
    std::string dimension;
1076
    if (strncasecmp(dimensionname, "dim_", 4) == 0)
29✔
1077
      dimension = dimensionname + 4;
5✔
1078
    else
1079
      dimension = dimensionname;
1080

1081
    /*if value is empty and a default is defined, use it*/
1082
    std::string currentvalue;
1083
    if (strlen(value) > 0)
29✔
1084
      currentvalue = value;
1085
    else {
1086
      const char *dimensiondefault = msOWSLookupMetadata(
1✔
1087
          &(lp->metadata), "M", (dimension + "_default").c_str());
1✔
1088
      if (dimensiondefault)
1✔
1089
        currentvalue = dimensiondefault;
1090
    }
1091

1092
    /*check if the manadatory metada related to the dimension are set*/
1093
    const char *dimensionitem = msOWSLookupMetadata(
29✔
1094
        &(lp->metadata), "M", (dimension + "_item").c_str());
29✔
1095
    const char *dimensionextent = msOWSLookupMetadata(
29✔
1096
        &(lp->metadata), "M", (dimension + "_extent").c_str());
29✔
1097
    const char *dimensionunit = msOWSLookupMetadata(
29✔
1098
        &(lp->metadata), "M", (dimension + "_units").c_str());
29✔
1099

1100
    /*if the server want to force the type to character*/
1101
    const char *dimensiontype = msOWSLookupMetadata(
29✔
1102
        &(lp->metadata), "M", (dimension + "_type").c_str());
29✔
1103
    if (dimensiontype && strcasecmp(dimensiontype, "Character") == 0)
29✔
1104
      forcecharacter = true;
1105

1106
    if (dimensionitem && dimensionextent && dimensionunit &&
29✔
1107
        !currentvalue.empty()) {
1108
      if (msWMSValidateDimensionValue(currentvalue.c_str(), dimensionextent,
29✔
1109
                                      forcecharacter)) {
1110
        result = msWMSApplyDimensionLayer(lp, dimensionitem,
13✔
1111
                                          currentvalue.c_str(), forcecharacter);
1112
      } else {
1113
        msSetErrorWithStatus(
16✔
1114
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1115
            "Dimension %s with a value of %s is invalid or outside the "
1116
            "extents defined",
1117
            "msWMSApplyDimension", dimension.c_str(), currentvalue.c_str());
1118
        result = false;
1119
      }
1120
    } else
1121
      msSetErrorWithStatus(
×
1122
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1123
          "Dimension %s : invalid settings. Make sure that item, units "
1124
          "and extent are set.",
1125
          "msWMSApplyDimension", dimension.c_str());
1126
  }
1127
  return result;
29✔
1128
}
1129
/*
1130
**
1131
*/
1132
int msWMSLoadGetMapParams(mapObj *map, int nVersion, char **names,
566✔
1133
                          char **values, int numentries,
1134
                          const char *wms_exception_format,
1135
                          const char * /*wms_request*/,
1136
                          owsRequestObj *ows_request) {
1137
  bool adjust_extent = false;
1138
  bool nonsquare_enabled = false;
1139
  int transparent = MS_NOOVERRIDE;
1140
  bool bbox_pixel_is_point = false;
1141
  outputFormatObj *format = NULL;
1142
  int validlayers = 0;
1143
  const char *styles = NULL;
1144
  int invalidlayers = 0;
1145
  std::string epsgbuf;
1146
  std::string srsbuffer;
1147
  bool epsgvalid = false;
1148
  bool timerequest = false;
1149
  const char *stime = NULL;
1150
  bool srsfound = false;
1151
  bool bboxfound = false;
1152
  bool formatfound = false;
1153
  bool widthfound = false;
1154
  bool heightfound = false;
1155
  const char *request = NULL;
1156
  int status = 0;
1157
  const char *layerlimit = NULL;
1158
  bool tiled = false;
1159

1160
  const char *sldenabled = NULL;
1161
  const char *sld_url = NULL;
1162
  const char *sld_body = NULL;
1163

1164
  const char *filter = NULL;
1165

1166
  bool compliance_mode = false;
1167

1168
  /* Some of the getMap parameters are actually required depending on the */
1169
  /* request, but for now we assume all are optional and the map file */
1170
  /* defaults will apply. */
1171

1172
  msAdjustExtent(&(map->extent), map->width, map->height);
566✔
1173

1174
  /*
1175
    Check if we need strict checks for standard compliance.
1176
    Defaults to false.
1177
  */
1178
  compliance_mode = msOWSStrictCompliance(map);
566✔
1179

1180
  /*
1181
    Check for SLDs first. If SLD is available LAYERS and STYLES parameters are
1182
    non mandatory
1183
   */
1184
  for (int i = 0; i < numentries; i++) {
7,409✔
1185
    /* check if SLD is passed.  If yes, check for OGR support */
1186
    if (strcasecmp(names[i], "SLD") == 0 ||
6,843✔
1187
        strcasecmp(names[i], "SLD_BODY") == 0) {
6,836✔
1188
      sldenabled =
1189
          msOWSLookupMetadata(&(map->web.metadata), "MO", "sld_enabled");
146✔
1190

1191
      if (sldenabled == NULL) {
146✔
1192
        sldenabled = "true";
1193
      }
1194

1195
      if (strcasecmp(sldenabled, "true") == 0) {
146✔
1196
        if (strcasecmp(names[i], "SLD") == 0) {
144✔
1197
          sld_url = values[i];
7✔
1198
        }
1199
        if (strcasecmp(names[i], "SLD_BODY") == 0) {
144✔
1200
          sld_body = values[i];
137✔
1201
        }
1202
      }
1203
    }
1204
  }
1205

1206
  std::vector<std::string> wmslayers;
1207
  for (int i = 0; i < numentries; i++) {
7,393✔
1208
    /* getMap parameters */
1209

1210
    if (strcasecmp(names[i], "REQUEST") == 0) {
6,831✔
1211
      request = values[i];
566✔
1212
    }
1213

1214
    if (strcasecmp(names[i], "LAYERS") == 0) {
6,831✔
1215
      std::vector<int> layerOrder(map->numlayers);
563✔
1216

1217
      wmslayers = msStringSplit(values[i], ',');
563✔
1218
      if (wmslayers.empty()) {
563✔
1219
        if (sld_url == NULL && sld_body == NULL) {
×
1220
          msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
1221
                               "At least one layer name required in LAYERS.",
1222
                               "msWMSLoadGetMapParams()");
1223
          return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1224
        }
1225
      }
1226

1227
      if (nVersion >= OWS_1_3_0) {
563✔
1228
        layerlimit =
1229
            msOWSLookupMetadata(&(map->web.metadata), "MO", "layerlimit");
236✔
1230
        if (layerlimit) {
236✔
1231
          if (static_cast<int>(wmslayers.size()) > atoi(layerlimit)) {
16✔
1232
            msSetErrorWithStatus(
2✔
1233
                MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1234
                "Number of layers requested exceeds LayerLimit.",
1235
                "msWMSLoadGetMapParams()");
1236
            return msWMSException(map, nVersion, NULL, wms_exception_format);
2✔
1237
          }
1238
        }
1239
      }
1240

1241
      for (int iLayer = 0; iLayer < map->numlayers; iLayer++) {
3,745✔
1242
        map->layerorder[iLayer] = iLayer;
3,184✔
1243
      }
1244

1245
      int nLayerOrder = 0;
1246
      for (int j = 0; j < map->numlayers; j++) {
3,745✔
1247
        /* Keep only layers with status=DEFAULT by default */
1248
        /* Layer with status DEFAULT is drawn first. */
1249
        if (GET_LAYER(map, j)->status != MS_DEFAULT)
3,184✔
1250
          GET_LAYER(map, j)->status = MS_OFF;
3,129✔
1251
        else {
1252
          map->layerorder[nLayerOrder++] = j;
55✔
1253
          layerOrder[j] = 1;
55✔
1254
        }
1255
      }
1256

1257
      if (ows_request->layerwmsfilterindex != NULL)
561✔
UNCOV
1258
        msFree(ows_request->layerwmsfilterindex);
×
1259
      ows_request->layerwmsfilterindex =
561✔
1260
          (int *)msSmallMalloc(map->numlayers * sizeof(int));
561✔
1261
      for (int j = 0; j < map->numlayers; j++) {
3,745✔
1262
        ows_request->layerwmsfilterindex[j] = -1;
3,184✔
1263
      }
1264
      ows_request->numwmslayerargs = static_cast<int>(wmslayers.size());
561✔
1265

1266
      auto [layerTree, mapNameToNode] = msWMSCreateLayerTree(map, nullptr);
561✔
1267
      (void)layerTree;
1268

1269
      for (int k = 0; k < static_cast<int>(wmslayers.size()); k++) {
1,280✔
1270
        const auto &wmslayer = wmslayers[k];
719✔
1271
        bool layerfound = false;
1272
        auto iter = mapNameToNode.find(msStringToLower(wmslayer));
1,438✔
1273
        std::vector<int> layerIndices;
1274
        if (iter != mapNameToNode.end()) {
719✔
1275
          layerIndices = iter->second->collectLayerIndices();
717✔
1276
        }
1277

1278
        for (int j : layerIndices) {
1,634✔
1279
          layerObj *layer = GET_LAYER(map, j);
915✔
1280
          if (msIntegerInArray(layer->index, ows_request->enabled_layers,
915✔
1281
                               ows_request->numlayers)) {
1282
            if (layer->status != MS_DEFAULT) {
910✔
1283
              if (layerOrder[j] == 0) {
881✔
1284
                map->layerorder[nLayerOrder++] = j;
871✔
1285
                layerOrder[j] = 1;
871✔
1286
                layer->status = MS_ON;
871✔
1287
              }
1288
            }
1289
            /* if a layer name is repeated assign the first matching filter */
1290
            /* duplicate names will be assigned filters later when layer copies
1291
             * are created */
1292
            if (ows_request->layerwmsfilterindex[j] == -1) {
910✔
1293
              ows_request->layerwmsfilterindex[j] = k;
900✔
1294
            }
1295
            validlayers++;
910✔
1296
            layerfound = true;
1297
          }
1298
        }
1299

1300
        if (!layerfound)
719✔
1301
          invalidlayers++;
4✔
1302
      }
719✔
1303

1304
      /* set all layers with status off at end of array */
1305
      for (int j = 0; j < map->numlayers; j++) {
3,745✔
1306
        if (GET_LAYER(map, j)->status == MS_OFF)
3,184✔
1307
          if (layerOrder[j] == 0)
2,258✔
1308
            map->layerorder[nLayerOrder++] = j;
2,258✔
1309
      }
1310
    } else if (strcasecmp(names[i], "STYLES") == 0) {
6,831✔
1311
      styles = values[i];
480✔
1312

1313
    } else if ((strcasecmp(names[i], "SRS") == 0 && nVersion < OWS_1_3_0) ||
5,788✔
1314
               (strcasecmp(names[i], "CRS") == 0 && nVersion >= OWS_1_3_0)) {
5,464✔
1315
      srsfound = true;
1316
      char *colon = strchr(values[i], ':');
540✔
1317
      bool srsOk = false;
1318
      /* SRS is in format "EPSG:epsg_id" or "AUTO:proj_id,unit_id,lon0,lat0" */
1319
      if (strncasecmp(values[i], "EPSG:", 5) == 0) {
540✔
1320
        srsOk = true;
1321
        /* SRS=EPSG:xxxx */
1322

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

1326
        srsbuffer = "EPSG:";
1327
        srsbuffer += (values[i] + 5);
536✔
1328
        epsgbuf = srsbuffer;
1329

1330
        /* This test was to correct a request by the OCG cite 1.3.0 test
1331
         sending CRS=ESPG:4326,  Bug:*/
1332
        if (nVersion >= OWS_1_3_0) {
536✔
1333
          if (srsbuffer.back() == ',') {
213✔
1334
            srsbuffer.resize(srsbuffer.size() - 1);
1335
            epsgbuf = srsbuffer;
1336
          }
1337
        }
1338

1339
        /* we need to wait until all params are read before */
1340
        /* loading the projection into the map. This will help */
1341
        /* insure that the passes srs is valid for all layers. */
1342
        /*
1343
        if (msLoadProjectionString(&(map->projection), buffer) != 0)
1344
          return msWMSException(map, nVersion, NULL);
1345

1346
        iUnits = GetMapserverUnitUsingProj(&(map->projection));
1347
        if (iUnits != -1)
1348
          map->units = iUnits;
1349
        */
1350
      } else if (strncasecmp(values[i], "AUTO:", 5) == 0 &&
4✔
1351
                 nVersion < OWS_1_3_0) {
1352
        if (nVersion < OWS_1_3_0) {
1353
          srsOk = true;
1354
          srsbuffer = values[i];
1355
          /* SRS=AUTO:proj_id,unit_id,lon0,lat0 */
1356
          /*
1357
          if (msLoadProjectionString(&(map->projection), values[i]) != 0)
1358
            return msWMSException(map, nVersion, NULL);
1359

1360
          iUnits = GetMapserverUnitUsingProj(&(map->projection));
1361
          if (iUnits != -1)
1362
            map->units = iUnits;
1363
          */
1364
        }
1365
      } else if (strncasecmp(values[i], "AUTO2:", 6) == 0 ||
4✔
1366
                 strncasecmp(values[i], "CRS:", 4) == 0) {
4✔
1367
        if (nVersion >= OWS_1_3_0) {
×
1368
          srsOk = true;
1369
          srsbuffer = values[i];
1370
        }
1371
      } else if (colon != NULL && strchr(colon + 1, ':') == NULL) {
4✔
1372
        srsOk = true;
1373
        srsbuffer = values[i];
1374
        epsgbuf = srsbuffer;
1375
      }
1376
      if (!srsOk) {
1377
        if (nVersion >= OWS_1_3_0) {
×
1378
          msSetErrorWithStatus(
×
1379
              MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1380
              "Unsupported CRS namespace (CRS must be in the format "
1381
              "AUTH:XXXX).",
1382
              "msWMSLoadGetMapParams()");
1383
          return msWMSException(map, nVersion, "InvalidCRS",
×
1384
                                wms_exception_format);
1385
        } else {
1386
          msSetErrorWithStatus(
×
1387
              MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1388
              "Unsupported SRS namespace (SRS must be in the format "
1389
              "AUTH:XXXX).",
1390
              "msWMSLoadGetMapParams()");
1391
          return msWMSException(map, nVersion, "InvalidSRS",
×
1392
                                wms_exception_format);
1393
        }
1394
      }
1395
    } else if (strcasecmp(names[i], "BBOX") == 0) {
5,248✔
1396
      bboxfound = true;
1397
      const auto tokens = msStringSplit(values[i], ',');
544✔
1398
      if (tokens.size() != 4) {
544✔
1399
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
1400
                             "Wrong number of arguments for BBOX.",
1401
                             "msWMSLoadGetMapParams()");
1402
        return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1403
      }
1404
      map->extent.minx = atof(tokens[0].c_str());
544✔
1405
      map->extent.miny = atof(tokens[1].c_str());
544✔
1406
      map->extent.maxx = atof(tokens[2].c_str());
544✔
1407
      map->extent.maxy = atof(tokens[3].c_str());
544✔
1408

1409
      /*for wms 1.3.0 we will do the validation of the bbox after all parameters
1410
       are read to account for the axes order*/
1411
      if (nVersion < OWS_1_3_0) {
544✔
1412

1413
        /* validate bbox values */
1414
        if (map->extent.minx >= map->extent.maxx ||
324✔
1415
            map->extent.miny >= map->extent.maxy) {
324✔
1416
          msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
1417
                               "Invalid values for BBOX.",
1418
                               "msWMSLoadGetMapParams()");
1419
          return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1420
        }
1421
        adjust_extent = true;
1422
      }
1423
    } else if (strcasecmp(names[i], "WIDTH") == 0) {
5,248✔
1424
      widthfound = true;
1425
      map->width = atoi(values[i]);
544✔
1426
    } else if (strcasecmp(names[i], "HEIGHT") == 0) {
4,160✔
1427
      heightfound = true;
1428
      map->height = atoi(values[i]);
544✔
1429
    } else if (strcasecmp(names[i], "FORMAT") == 0) {
3,616✔
1430
      formatfound = true;
1431

1432
      if (strcasecmp(values[i], "application/openlayers") != 0) {
534✔
1433
        /*check to see if a predefined list is given*/
1434
        const char *format_list =
1435
            msOWSLookupMetadata(&(map->web.metadata), "M", "getmap_formatlist");
531✔
1436
        if (format_list) {
531✔
1437
          format = msOwsIsOutputFormatValid(
73✔
1438
              map, values[i], &(map->web.metadata), "M", "getmap_formatlist");
1439
          if (format == NULL) {
73✔
1440
            msSetErrorWithStatus(MS_IMGERR, MS_HTTP_400_BAD_REQUEST,
×
1441
                                 "Unsupported output format (%s).",
1442
                                 "msWMSLoadGetMapParams()", values[i]);
1443
            return msWMSException(map, nVersion, "InvalidFormat",
×
1444
                                  wms_exception_format);
1445
          }
1446
        } else {
1447
          format = msSelectOutputFormat(map, values[i]);
458✔
1448
          if (format == NULL ||
458✔
1449
              (strncasecmp(format->driver, "MVT", 3) != 0 &&
458✔
1450
               strncasecmp(format->driver, "GDAL/", 5) != 0 &&
453✔
1451
               strncasecmp(format->driver, "AGG/", 4) != 0 &&
450✔
1452
               strncasecmp(format->driver, "UTFGRID", 7) != 0 &&
3✔
1453
               strncasecmp(format->driver, "CAIRO/", 6) != 0 &&
3✔
1454
               strncasecmp(format->driver, "OGL/", 4) != 0 &&
3✔
1455
               strncasecmp(format->driver, "KML", 3) != 0 &&
3✔
1456
               strncasecmp(format->driver, "KMZ", 3) != 0)) {
1✔
1457
            msSetErrorWithStatus(MS_IMGERR, MS_HTTP_400_BAD_REQUEST,
×
1458
                                 "Unsupported output format (%s).",
1459
                                 "msWMSLoadGetMapParams()", values[i]);
1460
            return msWMSException(map, nVersion, "InvalidFormat",
×
1461
                                  wms_exception_format);
1462
          }
1463
        }
1464
      }
1465
      msFree(map->imagetype);
534✔
1466
      map->imagetype = msStrdup(values[i]);
534✔
1467
    } else if (strcasecmp(names[i], "TRANSPARENT") == 0) {
3,082✔
1468
      if (compliance_mode) {
71✔
1469
        transparent = (strcmp(values[i], "TRUE") == 0);
3✔
1470
        if ((!transparent) && (strcmp(values[i], "FALSE") != 0)) {
3✔
1471
          msSetErrorWithStatus(
2✔
1472
              MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1473
              "Value for TRANSPARENT must be either TRUE or FALSE.",
1474
              "msWMSLoadGetMapParams()");
1475
          return msWMSException(map, nVersion, NULL, wms_exception_format);
2✔
1476
        }
1477
      } else {
1478
        transparent = (strcasecmp(values[i], "TRUE") == 0);
68✔
1479
      }
1480
    } else if (strcasecmp(names[i], "BGCOLOR") == 0) {
3,011✔
1481
      long c;
1482
      c = strtol(values[i], NULL, 16);
45✔
1483
      map->imagecolor.red = (c / 0x10000) & 0xff;
45✔
1484
      map->imagecolor.green = (c / 0x100) & 0xff;
45✔
1485
      map->imagecolor.blue = c & 0xff;
45✔
1486
    }
1487

1488
    /* value of time can be empty. We should look for a default value */
1489
    /* see function msWMSApplyTime */
1490
    else if (strcasecmp(names[i], "TIME") == 0) { /* &&  values[i]) */
2,966✔
1491
      stime = values[i];
130✔
1492
      timerequest = true;
1493
    }
1494
    /* Vendor-specific ANGLE param (for map rotation), added in ticket #3332,
1495
     * also supported by GeoServer
1496
     */
1497
    else if (strcasecmp(names[i], "ANGLE") == 0) {
2,836✔
1498
      msMapSetRotation(map, atof(values[i]));
×
1499
    }
1500
    /* Vendor-specific bbox_pixel_is_point, added in ticket #4652 */
1501
    else if (strcasecmp(names[i], "BBOX_PIXEL_IS_POINT") == 0) {
2,836✔
1502
      bbox_pixel_is_point = (strcasecmp(values[i], "TRUE") == 0);
×
1503
    }
1504
    /* Vendor specific TILED (WMS-C) */
1505
    else if (strcasecmp(names[i], "TILED") == 0) {
2,836✔
1506
      tiled = (strcasecmp(values[i], "TRUE") == 0);
1✔
1507
    }
1508
    /* Vendor-specific FILTER, added in RFC-118 */
1509
    else if (strcasecmp(names[i], "FILTER") == 0) {
2,835✔
1510
      filter = values[i];
14✔
1511
    }
1512
  }
1513

1514
  /*validate the exception format WMS 1.3.0 section 7.3.3.11*/
1515

1516
  if (nVersion >= OWS_1_3_0 && wms_exception_format != NULL) {
562✔
1517
    if (strcasecmp(wms_exception_format, "INIMAGE") != 0 &&
23✔
1518
        strcasecmp(wms_exception_format, "BLANK") != 0 &&
4✔
1519
        strcasecmp(wms_exception_format, "XML") != 0) {
4✔
1520
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
1521
                           "Invalid format %s for the EXCEPTIONS parameter.",
1522
                           "msWMSLoadGetMapParams()", wms_exception_format);
1523
      return msWMSException(map, nVersion, "InvalidFormat",
×
1524
                            wms_exception_format);
1525
    }
1526
  }
1527

1528
  int need_axis_swap = MS_FALSE;
1529
  if (bboxfound && nVersion >= OWS_1_3_0) {
562✔
1530
    rectObj rect;
1531
    projectionObj proj;
1532

1533
    /*we have already validated that the request format when reading
1534
     the request parameters*/
1535
    rect = map->extent;
216✔
1536

1537
    /*try to adjust the axes if necessary*/
1538
    if (srsbuffer.size() > 1) {
216✔
1539
      msInitProjection(&proj);
214✔
1540
      msProjectionInheritContextFrom(&proj, &(map->projection));
214✔
1541
      if (msLoadProjectionStringEPSG(&proj, srsbuffer.c_str()) == 0 &&
427✔
1542
          (need_axis_swap = msIsAxisInvertedProj(&proj))) {
213✔
1543
        msAxisNormalizePoints(&proj, 1, &rect.minx, &rect.miny);
198✔
1544
        msAxisNormalizePoints(&proj, 1, &rect.maxx, &rect.maxy);
198✔
1545
      }
1546
      msFreeProjection(&proj);
214✔
1547
    }
1548
    /*if the CRS is AUTO2:auto_crs_id,factor,lon0,lat0,
1549
     we need to grab the factor parameter and use it with the bbox*/
1550
    if (srsbuffer.size() > 1 &&
216✔
1551
        strncasecmp(srsbuffer.c_str(), "AUTO2:", 6) == 0) {
214✔
1552
      const auto args = msStringSplit(srsbuffer.c_str(), ',');
×
1553
      if (args.size() == 4) {
×
1554
        const double factor = atof(args[1].c_str());
1555
        if (factor > 0 && factor != 1.0) {
×
1556
          rect.minx = rect.minx * factor;
×
1557
          rect.miny = rect.miny * factor;
×
1558
          rect.maxx = rect.maxx * factor;
1559
          rect.maxx = rect.maxy * factor;
×
1560
        }
1561
      }
1562
    }
×
1563

1564
    map->extent = rect;
216✔
1565

1566
    /* validate bbox values */
1567
    if (map->extent.minx >= map->extent.maxx ||
216✔
1568
        map->extent.miny >= map->extent.maxy) {
216✔
1569
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
1570
                           "Invalid values for BBOX.",
1571
                           "msWMSLoadGetMapParams()");
1572
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1573
    }
1574
    adjust_extent = true;
1575
  }
1576

1577
  if (tiled) {
562✔
1578
    const char *value;
1579
    hashTableObj *meta = &(map->web.metadata);
1✔
1580
    int map_edge_buffer = 0;
1581

1582
    if ((value = msLookupHashTable(meta, "tile_map_edge_buffer")) != NULL) {
1✔
1583
      map_edge_buffer = atoi(value);
1584
    }
1585
    if (map_edge_buffer > 0 && map->width > 0 && map->height > 0) {
1✔
1586
      /* adjust bbox and width and height to the buffer */
1587
      const double buffer_x = map_edge_buffer *
1✔
1588
                              (map->extent.maxx - map->extent.minx) /
1✔
1589
                              (double)map->width;
1✔
1590
      const double buffer_y = map_edge_buffer *
1✔
1591
                              (map->extent.maxy - map->extent.miny) /
1✔
1592
                              (double)map->height;
1✔
1593

1594
      // TODO: we should probably clamp the extent to avoid going outside of
1595
      // -180,-90,180,90 for geographic CRS for example
1596
      map->extent.minx -= buffer_x;
1✔
1597
      map->extent.maxx += buffer_x;
1✔
1598
      map->extent.miny -= buffer_y;
1✔
1599
      map->extent.maxy += buffer_y;
1✔
1600

1601
      map->width += 2 * map_edge_buffer;
1✔
1602
      map->height += 2 * map_edge_buffer;
1✔
1603

1604
      if (map_edge_buffer > 0) {
1605
        char tilebufferstr[64];
1606

1607
        /* Write the tile buffer to a string */
1608
        snprintf(tilebufferstr, sizeof(tilebufferstr), "-%d", map_edge_buffer);
1609

1610
        /* Hm, the labelcache buffer is set... */
1611
        if ((value = msLookupHashTable(meta, "labelcache_map_edge_buffer")) !=
1✔
1612
            NULL) {
1613
          /* If it's too small, replace with a bigger one */
1614
          if (map_edge_buffer > abs(atoi(value))) {
×
1615
            msRemoveHashTable(meta, "labelcache_map_edge_buffer");
×
1616
            msInsertHashTable(meta, "labelcache_map_edge_buffer",
×
1617
                              tilebufferstr);
1618
          }
1619
        }
1620
        /* No labelcache buffer value? Then we use the tile buffer. */
1621
        else {
1622
          msInsertHashTable(meta, "labelcache_map_edge_buffer", tilebufferstr);
1✔
1623
        }
1624
      }
1625
    }
1626
  }
1627

1628
  /*
1629
  ** If any select layers have a default time, we will apply the default
1630
  ** time value even if no TIME request was in the url.
1631
  */
1632
  if (!timerequest && map) {
562✔
1633
    for (int i = 0; i < map->numlayers && !timerequest; i++) {
2,149✔
1634
      layerObj *lp = NULL;
1635

1636
      lp = (GET_LAYER(map, i));
1,717✔
1637
      if (lp->status != MS_ON && lp->status != MS_DEFAULT)
1,717✔
1638
        continue;
920✔
1639

1640
      if (msOWSLookupMetadata(&(lp->metadata), "MO", "timedefault"))
797✔
1641
        timerequest = true;
1642
    }
1643
  }
1644

1645
  /*
1646
  ** Apply time filters if available in the request.
1647
  */
1648
  if (timerequest) {
562✔
1649
    if (msWMSApplyTime(map, nVersion, stime, wms_exception_format) ==
133✔
1650
        MS_FAILURE) {
1651
      return MS_FAILURE; /* msWMSException(map, nVersion, "InvalidTimeRequest");
1652
                          */
1653
    }
1654
  }
1655

1656
  /*
1657
  ** Check/apply wms dimensions
1658
  ** all dimension requests should start with dim_xxxx, except time and
1659
  *elevation.
1660
  */
1661
  for (int i = 0; i < map->numlayers; i++) {
3,681✔
1662
    layerObj *lp = (GET_LAYER(map, i));
3,149✔
1663
    if (lp->status != MS_ON && lp->status != MS_DEFAULT)
3,149✔
1664
      continue;
2,236✔
1665

1666
    const char *dimensionlist =
1667
        msOWSLookupMetadata(&(lp->metadata), "M", "dimensionlist");
913✔
1668
    if (dimensionlist) {
913✔
1669
      auto tokens = msStringSplit(dimensionlist, ',');
29✔
1670
      for (auto &token : tokens) {
50✔
1671
        msStringTrim(token);
37✔
1672
        for (int k = 0; k < numentries; k++) {
470✔
1673
          const std::string dimensionname(names[k]);
462✔
1674

1675
          /*the dim_ is supposed to be part of the dimension name in the
1676
           * request*/
1677
          std::string stmp;
1678
          if (strcasecmp(token.c_str(), "elevation") == 0)
462✔
1679
            stmp = token;
1680
          else {
1681
            stmp = "dim_";
1682
            stmp += token;
1683
          }
1684
          if (strcasecmp(dimensionname.c_str(), stmp.c_str()) == 0) {
462✔
1685
            if (!msWMSApplyDimension(lp, nVersion, dimensionname.c_str(),
29✔
1686
                                     values[k], wms_exception_format)) {
29✔
1687
              return msWMSException(lp->map, nVersion, "InvalidDimensionValue",
16✔
1688
                                    wms_exception_format);
1689
            }
1690
            break;
1691
          }
1692
        }
1693
      }
1694
    }
29✔
1695
  }
1696

1697
  /*
1698
  ** Apply the selected output format (if one was selected), and override
1699
  ** the transparency if needed.
1700
  */
1701

1702
  if (format != NULL)
532✔
1703
    msApplyOutputFormat(&(map->outputformat), format, transparent);
499✔
1704

1705
  /* Validate all layers given.
1706
  ** If an invalid layer is sent, return an exception.
1707
  */
1708
  if (validlayers == 0 || invalidlayers > 0) {
532✔
1709
    if (invalidlayers > 0) {
7✔
1710
      msSetErrorWithStatus(
4✔
1711
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1712
          "Invalid layer(s) given in the LAYERS parameter. A layer might be disabled for \
1713
this request. Check wms/ows_enable_request settings.",
1714
          "msWMSLoadGetMapParams()");
1715
      return msWMSException(map, nVersion, "LayerNotDefined",
4✔
1716
                            wms_exception_format);
1717
    }
1718
    if (validlayers == 0 && sld_url == NULL && sld_body == NULL) {
3✔
1719
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
3✔
1720
                           "Missing required parameter LAYERS",
1721
                           "msWMSLoadGetMapParams()");
1722
      return msWMSException(map, nVersion, "MissingParameterValue",
3✔
1723
                            wms_exception_format);
1724
    }
1725
  }
1726

1727
  /* validate srs value: When the SRS parameter in a GetMap request contains a
1728
  ** SRS that is valid for some, but not all of the layers being requested,
1729
  ** then the server shall throw a Service Exception (code = "InvalidSRS").
1730
  ** Validate first against epsg in the map and if no matching srs is found
1731
  ** validate all layers requested.
1732
  */
1733
  if (epsgbuf.size() >= 2) { /*at least 2 chars*/
525✔
1734
    char *projstring;
1735
    epsgvalid = false;
1736
    msOWSGetEPSGProj(&(map->projection), &(map->web.metadata), "MO", MS_FALSE,
503✔
1737
                     &projstring);
1738
    if (projstring) {
503✔
1739
      const auto tokens = msStringSplit(projstring, ' ');
503✔
1740
      for (const auto &token : tokens) {
878✔
1741
        if (strcasecmp(token.c_str(), epsgbuf.c_str()) == 0) {
864✔
1742
          epsgvalid = true;
1743
          break;
1744
        }
1745
      }
1746
      msFree(projstring);
503✔
1747
    }
503✔
1748
    if (!epsgvalid) {
503✔
1749
      for (int i = 0; i < map->numlayers; i++) {
34✔
1750
        epsgvalid = false;
1751
        if (GET_LAYER(map, i)->status == MS_ON) {
21✔
1752
          msOWSGetEPSGProj(&(GET_LAYER(map, i)->projection),
13✔
1753
                           &(GET_LAYER(map, i)->metadata), "MO", MS_FALSE,
1754
                           &projstring);
1755
          if (projstring) {
13✔
1756
            const auto tokens = msStringSplit(projstring, ' ');
13✔
1757
            for (const auto &token : tokens) {
27✔
1758
              if (strcasecmp(token.c_str(), epsgbuf.c_str()) == 0) {
26✔
1759
                epsgvalid = true;
1760
                break;
1761
              }
1762
            }
1763
            msFree(projstring);
13✔
1764
          }
13✔
1765
          if (!epsgvalid) {
13✔
1766
            if (nVersion >= OWS_1_3_0) {
1✔
1767
              msSetErrorWithStatus(
1✔
1768
                  MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1769
                  "Invalid CRS given : CRS must be valid for all "
1770
                  "requested layers.",
1771
                  "msWMSLoadGetMapParams()");
1772
              return msWMSException(map, nVersion, "InvalidSRS",
1✔
1773
                                    wms_exception_format);
1✔
1774
            } else {
1775
              msSetErrorWithStatus(
×
1776
                  MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1777
                  "Invalid SRS given : SRS must be valid for all "
1778
                  "requested layers.",
1779
                  "msWMSLoadGetMapParams()");
1780
              return msWMSException(map, nVersion, "InvalidSRS",
×
1781
                                    wms_exception_format);
1782
            }
1783
          }
1784
        }
1785
      }
1786
    }
1787
  }
1788

1789
  if (request == NULL || strcasecmp(request, "DescribeLayer") != 0) {
524✔
1790
    /* Validate requested image size.
1791
     */
1792
    if (map->width > map->maxsize || map->height > map->maxsize ||
505✔
1793
        map->width < 1 || map->height < 1) {
505✔
1794
      msSetErrorWithStatus(
×
1795
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1796
          "Image size out of range, WIDTH and HEIGHT must be between 1 "
1797
          "and %d pixels.",
1798
          "msWMSLoadGetMapParams()", map->maxsize);
1799

1800
      /* Restore valid default values in case errors INIMAGE are used */
1801
      map->width = 400;
×
1802
      map->height = 300;
×
1803
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1804
    }
1805

1806
    /* Check whether requested BBOX and width/height result in non-square pixels
1807
     */
1808
    nonsquare_enabled =
1809
        msTestConfigOption(map, "MS_NONSQUARE", MS_FALSE) != MS_FALSE;
505✔
1810
    if (!nonsquare_enabled) {
505✔
1811
      const double dx = MS_ABS(map->extent.maxx - map->extent.minx);
505✔
1812
      const double dy = MS_ABS(map->extent.maxy - map->extent.miny);
505✔
1813

1814
      const double reqy = ((double)map->width) * dy / dx;
505✔
1815

1816
      /* Allow up to 1 pixel of error on the width/height ratios. */
1817
      /* If more than 1 pixel then enable non-square pixels */
1818
      if (MS_ABS((reqy - (double)map->height)) > 1.0) {
505✔
1819
        if (map->debug)
168✔
1820
          msDebug("msWMSLoadGetMapParams(): enabling non-square pixels.\n");
1✔
1821
        msSetConfigOption(map, "MS_NONSQUARE", "YES");
168✔
1822
        nonsquare_enabled = true;
1823
      }
1824
    }
1825
  }
1826

1827
  /* If the requested SRS is different from the default mapfile projection, or
1828
  ** if a BBOX resulting in non-square pixels is requested then
1829
  ** copy the original mapfile's projection to any layer that doesn't already
1830
  ** have a projection. This will prevent problems when users forget to
1831
  ** explicitly set a projection on all layers in a WMS mapfile.
1832
  */
1833
  if (srsbuffer.size() > 1 || nonsquare_enabled) {
524✔
1834
    projectionObj newProj;
1835

1836
    if (map->projection.numargs <= 0) {
504✔
1837
      if (nVersion >= OWS_1_3_0) {
×
1838
        msSetErrorWithStatus(
×
1839
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1840
            "Cannot set new CRS on a map that doesn't "
1841
            "have any projection set. Please make sure your mapfile "
1842
            "has a projection defined at the top level.",
1843
            "msWMSLoadGetMapParams()");
1844
        return msWMSException(map, nVersion, "InvalidCRS",
×
1845
                              wms_exception_format);
×
1846
      } else {
1847
        msSetErrorWithStatus(
×
1848
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1849
            "Cannot set new SRS on a map that doesn't "
1850
            "have any projection set. Please make sure your mapfile "
1851
            "has a projection defined at the top level.",
1852
            "msWMSLoadGetMapParams()");
1853
        return msWMSException(map, nVersion, "InvalidSRS",
×
1854
                              wms_exception_format);
1855
      }
1856
    }
1857

1858
    msInitProjection(&newProj);
504✔
1859
    msProjectionInheritContextFrom(&newProj, &map->projection);
504✔
1860
    if (srsbuffer.size() > 1) {
504✔
1861
      int nTmp;
1862

1863
      if (nVersion >= OWS_1_3_0)
502✔
1864
        nTmp = msLoadProjectionStringEPSG(&newProj, srsbuffer.c_str());
211✔
1865
      else
1866
        nTmp = msLoadProjectionString(&newProj, srsbuffer.c_str());
291✔
1867
      if (nTmp != 0) {
502✔
1868
        msFreeProjection(&newProj);
×
1869
        return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1870
      }
1871
    }
1872

1873
    if (nonsquare_enabled ||
840✔
1874
        msProjectionsDiffer(&(map->projection), &newProj)) {
336✔
1875
      msMapSetLayerProjections(map);
270✔
1876
    }
1877
    msFreeProjection(&newProj);
504✔
1878
  }
1879

1880
  /* apply the srs to the map file. This is only done after validating */
1881
  /* that the srs given as parameter is valid for all layers */
1882
  if (srsbuffer.size() > 1) {
524✔
1883
    int nTmp;
1884
    msFreeProjectionExceptContext(&map->projection);
502✔
1885
    if (nVersion >= OWS_1_3_0)
502✔
1886
      nTmp = msLoadProjectionStringEPSG(&(map->projection), srsbuffer.c_str());
211✔
1887
    else
1888
      nTmp = msLoadProjectionString(&(map->projection), srsbuffer.c_str());
291✔
1889

1890
    if (nTmp != 0)
502✔
1891
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
1892

1893
    nTmp = GetMapserverUnitUsingProj(&(map->projection));
502✔
1894
    if (nTmp != -1) {
502✔
1895
      map->units = static_cast<MS_UNITS>(nTmp);
502✔
1896
    }
1897
  }
1898

1899
  if (sld_url || sld_body) {
524✔
1900
    char *pszLayerNames = NULL;
144✔
1901
    const int nLayersBefore = map->numlayers;
144✔
1902

1903
    /* -------------------------------------------------------------------- */
1904
    /*      if LAYERS parameter was not given, set all layers to off        */
1905
    /* -------------------------------------------------------------------- */
1906
    if (validlayers == 0) { /*no LAYERS parameter is give*/
144✔
1907
      for (int j = 0; j < map->numlayers; j++) {
×
1908
        if (GET_LAYER(map, j)->status != MS_DEFAULT)
×
1909
          GET_LAYER(map, j)->status = MS_OFF;
×
1910
      }
1911
    }
1912

1913
    /*apply sld if defined. This is done here so that bbox and srs are already
1914
     * applied*/
1915
    if (sld_url) {
144✔
1916
      if ((status = msSLDApplySLDURL(map, sld_url, -1, NULL, &pszLayerNames)) !=
7✔
1917
          MS_SUCCESS)
1918
        return msWMSException(map, nVersion, NULL, wms_exception_format);
7✔
1919
    } else if (sld_body) {
137✔
1920
      if ((status = msSLDApplySLD(map, sld_body, -1, NULL, &pszLayerNames)) !=
137✔
1921
          MS_SUCCESS)
1922
        return msWMSException(map, nVersion, NULL, wms_exception_format);
3✔
1923
    }
1924
    /* -------------------------------------------------------------------- */
1925
    /*      SLD and styles can use the same layer multiple times. If        */
1926
    /*      that is the case we duplicate the layer for drawing             */
1927
    /*      purpose. We need to reset the ows request enable settings (#1602)*/
1928
    /* -------------------------------------------------------------------- */
1929
    const int nLayerAfter = map->numlayers;
137✔
1930
    if (nLayersBefore != nLayerAfter) {
137✔
1931
      msOWSRequestLayersEnabled(map, "M", "GetMap", ows_request);
5✔
1932
    }
1933

1934
    /* -------------------------------------------------------------------- */
1935
    /*      We need to take into account where the LAYERS parameter was     */
1936
    /*      not given (the LAYERS is option when an SLD is given). In       */
1937
    /*      this particular case, we need to turn on the layers             */
1938
    /*      identified the SLD (#1166).                                     */
1939
    /*                                                                      */
1940
    /* -------------------------------------------------------------------- */
1941
    if (validlayers == 0) {
137✔
1942
      if (pszLayerNames) {
×
1943
        const auto tokens = msStringSplit(pszLayerNames, ',');
×
1944
        for (const auto &token : tokens) {
×
1945
          for (int j = 0; j < map->numlayers; j++) {
×
1946
            if (((GET_LAYER(map, j)->name &&
×
1947
                  strcasecmp(GET_LAYER(map, j)->name, token.c_str()) == 0) ||
×
1948
                 (map->name && strcasecmp(map->name, token.c_str()) == 0) ||
×
1949
                 (GET_LAYER(map, j)->group &&
×
1950
                  strcasecmp(GET_LAYER(map, j)->group, token.c_str()) == 0)) &&
×
1951
                ((msIntegerInArray(GET_LAYER(map, j)->index,
×
1952
                                   ows_request->enabled_layers,
1953
                                   ows_request->numlayers)))) {
1954
              if (GET_LAYER(map, j)->status != MS_DEFAULT)
×
1955
                GET_LAYER(map, j)->status = MS_ON;
×
1956
            }
1957
          }
1958
        }
1959
      }
×
1960
    }
1961
    msFree(pszLayerNames);
137✔
1962
  }
1963

1964
  /* Validate Styles :
1965
  ** MapServer advertise styles through the group setting in a class object.
1966
  ** If no styles are set MapServer expects to have empty values
1967
  ** for the styles parameter (...&STYLES=&...) Or for multiple Styles/Layers,
1968
  ** we could have ...&STYLES=,,,. If that is not the
1969
  ** case, we generate an exception.
1970
  */
1971
  if (styles && strlen(styles) > 0) {
517✔
1972
    bool hasCheckedLayerUnicity = false;
1973
    int n = 0;
23✔
1974
    int layerCopyIndex;
1975

1976
    char **tokens = msStringSplitComplex(styles, ",", &n, MS_ALLOWEMPTYTOKENS);
23✔
1977
    for (int i = 0; i < n; i++) {
55✔
1978
      if (tokens[i] && strlen(tokens[i]) > 0 &&
35✔
1979
          strcasecmp(tokens[i], "default") != 0) {
25✔
1980
        if (!hasCheckedLayerUnicity) {
22✔
1981
          hasCheckedLayerUnicity = true;
1982
          bool bLayerInserted = false;
1983

1984
          /* --------------------------------------------------------------------
1985
           */
1986
          /*      If the same layer is given more that once, we need to */
1987
          /*      duplicate it. */
1988
          /* --------------------------------------------------------------------
1989
           */
1990
          for (size_t m = 0; m < wmslayers.size(); m++) {
41✔
1991
            for (size_t l = m + 1; l < wmslayers.size(); l++) {
33✔
1992
              const int nIndex = msGetLayerIndex(map, wmslayers[m].c_str());
9✔
1993
              if (nIndex != -1 &&
9✔
1994
                  strcasecmp(wmslayers[m].c_str(), wmslayers[l].c_str()) == 0) {
9✔
1995
                layerObj *psTmpLayer = (layerObj *)malloc(sizeof(layerObj));
6✔
1996
                initLayer(psTmpLayer, map);
6✔
1997
                msCopyLayer(psTmpLayer, GET_LAYER(map, nIndex));
6✔
1998
                /* open the source layer */
1999
                if (!psTmpLayer->vtable)
6✔
2000
                  msInitializeVirtualTable(psTmpLayer);
6✔
2001

2002
                /*make the name unique*/
2003
                char tmpId[128];
2004
                snprintf(tmpId, sizeof(tmpId), "%lx_%x_%d", (long)time(NULL),
6✔
2005
                         (int)getpid(), map->numlayers);
6✔
2006
                if (psTmpLayer->name)
6✔
2007
                  msFree(psTmpLayer->name);
6✔
2008
                psTmpLayer->name = msStrdup(tmpId);
6✔
2009
                wmslayers[l] = tmpId;
2010

2011
                layerCopyIndex = msInsertLayer(map, psTmpLayer, -1);
6✔
2012

2013
                // expand the array mapping map layer index to filter indexes
2014
                ows_request->layerwmsfilterindex =
6✔
2015
                    (int *)msSmallRealloc(ows_request->layerwmsfilterindex,
6✔
2016
                                          map->numlayers * sizeof(int));
6✔
2017
                ows_request->layerwmsfilterindex[layerCopyIndex] =
6✔
2018
                    l; // the filter index matches the index of the layer name
2019
                       // in the WMS param
2020

2021
                bLayerInserted = true;
2022
                /* layer was copied, we need to decrement its refcount */
2023
                MS_REFCNT_DECR(psTmpLayer);
6✔
2024
              }
2025
            }
2026
          }
2027

2028
          if (bLayerInserted) {
17✔
2029
            msOWSRequestLayersEnabled(map, "M", "GetMap", ows_request);
4✔
2030
          }
2031
        }
2032

2033
        if (static_cast<int>(wmslayers.size()) == n) {
22✔
2034
          for (int j = 0; j < map->numlayers; j++) {
152✔
2035
            layerObj *lp = GET_LAYER(map, j);
133✔
2036
            if ((lp->name && strcasecmp(lp->name, wmslayers[i].c_str()) == 0) ||
133✔
2037
                (lp->group &&
113✔
2038
                 strcasecmp(lp->group, wmslayers[i].c_str()) == 0)) {
4✔
2039
              bool found = false;
2040

2041
#ifdef USE_WMS_LYR
2042
              if (lp->connectiontype == MS_WMS) {
20✔
2043
                const char *pszWmsStyle =
2044
                    msOWSLookupMetadata(&(lp->metadata), "MO", "style");
2✔
2045
                if (pszWmsStyle != NULL &&
2✔
2046
                    strcasecmp(pszWmsStyle, tokens[i]) == 0)
2✔
2047
                  found = true;
2048
              }
2049
#endif // USE_WMS_LYR
2050

2051
              if (!found) {
2052
                for (int k = 0; k < lp->numclasses; k++) {
32✔
2053
                  if (lp->_class[k]->group &&
30✔
2054
                      strcasecmp(lp->_class[k]->group, tokens[i]) == 0) {
30✔
2055
                    msFree(lp->classgroup);
17✔
2056
                    lp->classgroup = msStrdup(tokens[i]);
17✔
2057
                    found = true;
2058
                    break;
2059
                  }
2060
                }
2061
              }
2062

2063
              if (!found) {
2064
                msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
2✔
2065
                                     "Style (%s) not defined on layer.",
2066
                                     "msWMSLoadGetMapParams()", tokens[i]);
2067
                msFreeCharArray(tokens, n);
2✔
2068

2069
                return msWMSException(map, nVersion, "StyleNotDefined",
2✔
2070
                                      wms_exception_format);
3✔
2071
              }
2072
              /* Check the style of the root layer */
2073
            } else if (map->name &&
113✔
2074
                       strcasecmp(map->name, wmslayers[i].c_str()) == 0) {
113✔
2075
              const char *styleName =
2076
                  msOWSLookupMetadata(&(map->web.metadata), "MO", "style_name");
15✔
2077
              if (styleName == NULL)
15✔
2078
                styleName = "default";
2079
              char *pszEncodedStyleName = msEncodeHTMLEntities(styleName);
15✔
2080
              if (strcasecmp(pszEncodedStyleName, tokens[i]) != 0) {
15✔
2081
                msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
2082
                                     "Style (%s) not defined on root layer.",
2083
                                     "msWMSLoadGetMapParams()", tokens[i]);
2084
                msFreeCharArray(tokens, n);
×
2085
                msFree(pszEncodedStyleName);
×
2086

2087
                return msWMSException(map, nVersion, "StyleNotDefined",
×
2088
                                      wms_exception_format);
2089
              }
2090
              msFree(pszEncodedStyleName);
15✔
2091
            }
2092
          }
2093
        } else {
2094
          msSetErrorWithStatus(
1✔
2095
              MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
2096
              "Invalid style (%s). Mapserver is expecting an empty "
2097
              "string for the STYLES : STYLES= or STYLES=,,, or using "
2098
              "keyword default  STYLES=default,default, ...",
2099
              "msWMSLoadGetMapParams()", styles);
2100
          msFreeCharArray(tokens, n);
1✔
2101
          return msWMSException(map, nVersion, "StyleNotDefined",
1✔
2102
                                wms_exception_format);
2103
        }
2104
      }
2105
    }
2106
    msFreeCharArray(tokens, n);
20✔
2107
  }
2108

2109
  /*
2110
  ** WMS extents are edge to edge while MapServer extents are center of
2111
  ** pixel to center of pixel.  Here we try to adjust the WMS extents
2112
  ** in by half a pixel.  We wait till here because we want to ensure we
2113
  ** are doing this in terms of the correct WIDTH and HEIGHT.
2114
  */
2115
  if (adjust_extent && map->width > 1 && map->height > 1 &&
514✔
2116
      !bbox_pixel_is_point) {
2117
    double dx, dy;
2118

2119
    dx = (map->extent.maxx - map->extent.minx) / map->width;
494✔
2120
    map->extent.minx += dx * 0.5;
494✔
2121
    map->extent.maxx -= dx * 0.5;
494✔
2122

2123
    dy = (map->extent.maxy - map->extent.miny) / map->height;
494✔
2124
    map->extent.miny += dy * 0.5;
494✔
2125
    map->extent.maxy -= dy * 0.5;
494✔
2126
  }
2127

2128
  if (request && strcasecmp(request, "DescribeLayer") != 0) {
514✔
2129
    if (!srsfound) {
495✔
2130
      if (nVersion >= OWS_1_3_0)
3✔
2131
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
2✔
2132
                             "Missing required parameter CRS",
2133
                             "msWMSLoadGetMapParams()");
2134
      else
2135
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
2136
                             "Missing required parameter SRS",
2137
                             "msWMSLoadGetMapParams()");
2138

2139
      return msWMSException(map, nVersion, "MissingParameterValue",
3✔
2140
                            wms_exception_format);
2141
    }
2142

2143
    if (!bboxfound) {
492✔
2144
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
2145
                           "Missing required parameter BBOX",
2146
                           "msWMSLoadGetMapParams()");
2147
      return msWMSException(map, nVersion, "MissingParameterValue",
1✔
2148
                            wms_exception_format);
2149
    }
2150

2151
    if (!formatfound && (strcasecmp(request, "GetMap") == 0 ||
491✔
2152
                         strcasecmp(request, "map") == 0)) {
8✔
2153
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
2154
                           "Missing required parameter FORMAT",
2155
                           "msWMSLoadGetMapParams()");
2156
      return msWMSException(map, nVersion, "MissingParameterValue",
1✔
2157
                            wms_exception_format);
2158
    }
2159

2160
    if (!widthfound) {
490✔
2161
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
2162
                           "Missing required parameter WIDTH",
2163
                           "msWMSLoadGetMapParams()");
2164
      return msWMSException(map, nVersion, "MissingParameterValue",
1✔
2165
                            wms_exception_format);
2166
    }
2167

2168
    if (!heightfound) {
489✔
2169
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
2170
                           "Missing required parameter HEIGHT",
2171
                           "msWMSLoadGetMapParams()");
2172
      return msWMSException(map, nVersion, "MissingParameterValue",
1✔
2173
                            wms_exception_format);
2174
    }
2175

2176
    if (styles == nullptr && sld_url == nullptr && sld_body == nullptr &&
82✔
2177
        (strcasecmp(request, "GetMap") == 0 ||
5✔
2178
         strcasecmp(request, "GetFeatureInfo") == 0) &&
493✔
2179
        msOWSLookupMetadata(&(map->web.metadata), "M",
1✔
2180
                            "allow_getmap_without_styles") == nullptr) {
2181
      msSetErrorWithStatus(
1✔
2182
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
2183
          "Missing required parameter STYLES. Note to service administrators: "
2184
          "defining the \"wms_allow_getmap_without_styles\" \"true\" "
2185
          "MAP.WEB.METADATA "
2186
          "item will disable this check (backward compatibility with behavior "
2187
          "of MapServer < 8.0)",
2188
          "msWMSLoadGetMapParams()");
2189
      return msWMSException(map, nVersion, "MissingParameterValue",
1✔
2190
                            wms_exception_format);
2191
    }
2192
  }
2193

2194
  /*
2195
  ** Apply vendor-specific filter if specified
2196
  */
2197
  if (filter) {
506✔
2198
    if (sld_url || sld_body) {
13✔
2199
      msSetErrorWithStatus(
×
2200
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
2201
          "Vendor-specific FILTER parameter cannot be used with SLD or "
2202
          "SLD_BODY.",
2203
          "msWMSLoadGetMapParams()");
2204
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
2205
    }
2206

2207
    if (msWMSApplyFilter(map, nVersion, filter, need_axis_swap,
13✔
2208
                         wms_exception_format, ows_request) == MS_FAILURE) {
2209
      return MS_FAILURE; /* msWMSException(map, nVersion,
2210
                            "InvalidFilterRequest"); */
2211
    }
2212
  }
2213

2214
  return MS_SUCCESS;
2215
}
566✔
2216

2217
/*
2218
**
2219
*/
2220
static void msWMSPrintRequestCap(int nVersion, const char *request,
275✔
2221
                                 const char *script_url, const char *formats,
2222
                                 ...) {
2223
  va_list argp;
2224

2225
  msIO_printf("    <%s>\n", request);
275✔
2226

2227
  /* We expect to receive a NULL-terminated args list of formats */
2228
  va_start(argp, formats);
275✔
2229
  const char *fmt = formats;
2230
  while (fmt != NULL) {
1,285✔
2231
    /* Special case for early WMS with subelements in Format (bug 908) */
2232
    char *encoded;
2233
    if (nVersion < OWS_1_1_0) {
1,010✔
2234
      encoded = msStrdup(fmt);
6✔
2235
    }
2236

2237
    /* otherwise we HTML code special characters */
2238
    else {
2239
      encoded = msEncodeHTMLEntities(fmt);
1,004✔
2240
    }
2241

2242
    msIO_printf("      <Format>%s</Format>\n", encoded);
1,010✔
2243
    msFree(encoded);
1,010✔
2244

2245
    fmt = va_arg(argp, const char *);
1,010✔
2246
  }
2247
  va_end(argp);
275✔
2248

2249
  msIO_printf("      <DCPType>\n");
275✔
2250
  msIO_printf("        <HTTP>\n");
275✔
2251
  /* The URL should already be HTML encoded. */
2252
  if (nVersion == OWS_1_0_0) {
275✔
2253
    msIO_printf("          <Get onlineResource=\"%s\" />\n", script_url);
6✔
2254
    msIO_printf("          <Post onlineResource=\"%s\" />\n", script_url);
6✔
2255
  } else {
2256
    msIO_printf("          <Get><OnlineResource "
269✔
2257
                "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
2258
                "xlink:href=\"%s\"/></Get>\n",
2259
                script_url);
2260
    msIO_printf("          <Post><OnlineResource "
269✔
2261
                "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
2262
                "xlink:href=\"%s\"/></Post>\n",
2263
                script_url);
2264
  }
2265

2266
  msIO_printf("        </HTTP>\n");
275✔
2267
  msIO_printf("      </DCPType>\n");
275✔
2268
  msIO_printf("    </%s>\n", request);
275✔
2269
}
275✔
2270

2271
void msWMSPrintAttribution(FILE *stream, const char *tabspace,
240✔
2272
                           hashTableObj *metadata,
2273
                           const char * /*namespaces*/) {
2274
  if (stream && metadata) {
240✔
2275
    const char *title =
2276
        msOWSLookupMetadata(metadata, "MO", "attribution_title");
240✔
2277
    const char *onlineres =
2278
        msOWSLookupMetadata(metadata, "MO", "attribution_onlineresource");
240✔
2279
    const char *logourl =
2280
        msOWSLookupMetadata(metadata, "MO", "attribution_logourl_width");
240✔
2281

2282
    if (title || onlineres || logourl) {
240✔
2283
      msIO_printf("%s<Attribution>\n", tabspace);
3✔
2284
      if (title) {
3✔
2285
        char *pszEncodedValue = msEncodeHTMLEntities(title);
3✔
2286
        msIO_fprintf(stream, "%s%s<Title>%s</Title>\n", tabspace, tabspace,
3✔
2287
                     pszEncodedValue);
2288
        free(pszEncodedValue);
3✔
2289
      }
2290

2291
      if (onlineres) {
3✔
2292
        char *pszEncodedValue = msEncodeHTMLEntities(onlineres);
1✔
2293
        msIO_fprintf(
1✔
2294
            stream,
2295
            "%s%s<OnlineResource xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
2296
            "xlink:href=\"%s\"/>\n",
2297
            tabspace, tabspace, pszEncodedValue);
2298
        free(pszEncodedValue);
1✔
2299
      }
2300

2301
      if (logourl) {
3✔
2302
        msOWSPrintURLType(stream, metadata, "MO", "attribution_logourl",
1✔
2303
                          OWS_NOERR, NULL, "LogoURL", NULL, " width=\"%s\"",
2304
                          " height=\"%s\"",
2305
                          ">\n             <Format>%s</Format",
2306
                          "\n             <OnlineResource "
2307
                          "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
2308
                          " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
2309
                          "          ",
2310
                          MS_FALSE, MS_TRUE, MS_TRUE, MS_TRUE, MS_TRUE, NULL,
2311
                          NULL, NULL, NULL, NULL, "        ");
2312
      }
2313
      msIO_printf("%s</Attribution>\n", tabspace);
3✔
2314
    }
2315
  }
2316
}
240✔
2317

2318
/*
2319
** msWMSPrintScaleHint()
2320
**
2321
** Print a Min/MaxScaleDenominator tag for the layer if applicable.
2322
** used for WMS >=1.3.0
2323
*/
2324
void msWMSPrintScaleDenominator(const char *tabspace, double minscaledenom,
118✔
2325
                                double maxscaledenom) {
2326
  if (minscaledenom > 0)
118✔
2327
    msIO_printf("%s<MinScaleDenominator>%g</MinScaleDenominator>\n", tabspace,
8✔
2328
                minscaledenom);
2329

2330
  if (maxscaledenom > 0)
118✔
2331
    msIO_printf("%s<MaxScaleDenominator>%g</MaxScaleDenominator>\n", tabspace,
2✔
2332
                maxscaledenom);
2333
}
118✔
2334

2335
/*
2336
** msWMSPrintScaleHint()
2337
**
2338
** Print a ScaleHint tag for this layer if applicable.
2339
**
2340
** (see WMS 1.1.0 sect. 7.1.5.4) The WMS defines the scalehint values as
2341
** the ground distance in meters of the southwest to northeast diagonal of
2342
** the central pixel of a map.  ScaleHint values are the min and max
2343
** recommended values of that diagonal.
2344
*/
2345
void msWMSPrintScaleHint(const char *tabspace, double minscaledenom,
124✔
2346
                         double maxscaledenom, double resolution) {
2347
  double scalehintmin = 0.0, scalehintmax = 0.0;
2348

2349
  const double diag = sqrt(2.0);
2350

2351
  if (minscaledenom > 0)
124✔
2352
    scalehintmin =
5✔
2353
        diag * (minscaledenom / resolution) / msInchesPerUnit(MS_METERS, 0);
5✔
2354
  if (maxscaledenom > 0)
124✔
2355
    scalehintmax =
2✔
2356
        diag * (maxscaledenom / resolution) / msInchesPerUnit(MS_METERS, 0);
2✔
2357

2358
  if (scalehintmin > 0.0 || scalehintmax > 0.0) {
124✔
2359
    msIO_printf("%s<ScaleHint min=\"%.15g\" max=\"%.15g\" />\n", tabspace,
5✔
2360
                scalehintmin, scalehintmax);
2361
    if (scalehintmax == 0.0)
5✔
2362
      msIO_printf("%s<!-- WARNING: Only MINSCALEDENOM and no MAXSCALEDENOM "
3✔
2363
                  "specified in "
2364
                  "the mapfile. A default value of 0 has been returned for the "
2365
                  "Max ScaleHint but this is probably not what you want. -->\n",
2366
                  tabspace);
2367
  }
2368
}
124✔
2369

2370
/*
2371
** msWMSPrintAuthorityURL()
2372
**
2373
** Print an AuthorityURL tag if applicable.
2374
*/
2375
void msWMSPrintAuthorityURL(FILE *stream, const char *tabspace,
240✔
2376
                            hashTableObj *metadata, const char *namespaces) {
2377
  if (stream && metadata) {
240✔
2378
    const char *pszWmsAuthorityName =
2379
        msOWSLookupMetadata(metadata, namespaces, "authorityurl_name");
240✔
2380
    const char *pszWMSAuthorityHref =
2381
        msOWSLookupMetadata(metadata, namespaces, "authorityurl_href");
240✔
2382

2383
    /* AuthorityURL only makes sense if you have *both* the name and url */
2384
    if (pszWmsAuthorityName && pszWMSAuthorityHref) {
240✔
2385
      msOWSPrintEncodeMetadata(
7✔
2386
          stream, metadata, namespaces, "authorityurl_name", OWS_NOERR,
2387
          (std::string(tabspace) + "<AuthorityURL name=\"%s\">\n").c_str(),
7✔
2388
          NULL);
2389
      msOWSPrintEncodeMetadata(
7✔
2390
          stream, metadata, namespaces, "authorityurl_href", OWS_NOERR,
2391
          (std::string(tabspace) +
7✔
2392
           "  <OnlineResource xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
2393
           "xlink:href=\"%s\"/>\n")
2394
              .c_str(),
2395
          NULL);
2396
      msIO_printf("%s</AuthorityURL>\n", tabspace);
7✔
2397
    } else if (pszWmsAuthorityName || pszWMSAuthorityHref) {
233✔
2398
      msIO_printf(
×
2399
          "%s<!-- WARNING: Both wms_authorityurl_name and "
2400
          "wms_authorityurl_href must be set to output an AuthorityURL -->\n",
2401
          tabspace);
2402
    }
2403
  }
2404
}
240✔
2405

2406
/*
2407
** msWMSPrintIdentifier()
2408
**
2409
** Print an Identifier tag if applicable.
2410
*/
2411
void msWMSPrintIdentifier(FILE *stream, const char *tabspace,
240✔
2412
                          hashTableObj *metadata, const char *namespaces) {
2413
  if (stream && metadata) {
240✔
2414
    const char *pszWMSIdentifierAuthority =
2415
        msOWSLookupMetadata(metadata, namespaces, "identifier_authority");
240✔
2416
    const char *pszWMSIdentifierValue =
2417
        msOWSLookupMetadata(metadata, namespaces, "identifier_value");
240✔
2418

2419
    /* Identifier only makes sense if you have *both* the authority and value */
2420
    if (pszWMSIdentifierAuthority && pszWMSIdentifierValue) {
240✔
2421
      msOWSPrintEncodeMetadata(
73✔
2422
          stream, metadata, namespaces, "identifier_authority", OWS_NOERR,
2423
          (std::string(tabspace) + "<Identifier authority=\"%s\">").c_str(),
73✔
2424
          NULL);
2425
      msOWSPrintEncodeMetadata(stream, metadata, namespaces, "identifier_value",
73✔
2426
                               OWS_NOERR, "%s</Identifier>\n", NULL);
2427
    } else if (pszWMSIdentifierAuthority || pszWMSIdentifierValue) {
167✔
2428
      msIO_printf(
×
2429
          "%s<!-- WARNING: Both wms_identifier_authority and "
2430
          "wms_identifier_value must be set to output an Identifier -->\n",
2431
          tabspace);
2432
    }
2433
  }
2434
}
240✔
2435

2436
/*
2437
** msWMSPrintKeywordlist()
2438
**
2439
** Print a Keywordlist tag if applicable.
2440
*/
2441
void msWMSPrintKeywordlist(FILE *stream, const char *tabspace, const char *name,
302✔
2442
                           hashTableObj *metadata, const char *namespaces,
2443
                           int nVersion) {
2444
  std::string newname(name); /* max. rootlayer_keywordlist_items          */
302✔
2445
  newname += "_items";
2446

2447
  std::string vocname(name); /* max. rootlayer_keywordlist_vocabulary     */
302✔
2448
  vocname += "_vocabulary";
2449

2450
  if (nVersion == OWS_1_0_0) {
302✔
2451
    /* <Keywords> in V 1.0.0 */
2452
    /* The 1.0.0 spec doesn't specify which delimiter to use so let's use spaces
2453
     */
2454
    msOWSPrintEncodeMetadataList(
4✔
2455
        stream, metadata, namespaces, name,
2456
        (std::string(tabspace) + "<Keywords>").c_str(),
8✔
2457
        (std::string(tabspace) + "</Keywords>\n").c_str(), "%s ", NULL);
8✔
2458
  } else if (msOWSLookupMetadata(metadata, namespaces, name) ||
423✔
2459
             msOWSLookupMetadata(metadata, namespaces, newname.c_str()) ||
423✔
2460
             msOWSLookupMetadata(metadata, namespaces, vocname.c_str())) {
125✔
2461
    /* <KeywordList><Keyword> ... in V1.0.6+ */
2462
    msIO_printf("%s<KeywordList>\n", tabspace);
182✔
2463
    std::string template1(tabspace);
182✔
2464
    template1 += "    <Keyword>%s</Keyword>\n";
2465
    /* print old styled ..._keywordlist */
2466
    msOWSPrintEncodeMetadataList(stream, metadata, namespaces, name, NULL, NULL,
182✔
2467
                                 template1.c_str(), NULL);
2468
    /* print new styled ..._keywordlist_items */
2469
    msOWSPrintEncodeMetadataList(stream, metadata, namespaces, newname.c_str(),
182✔
2470
                                 NULL, NULL, template1.c_str(), NULL);
2471

2472
    /* find out if there's a vocabulary list set */
2473
    const char *vocabularylist =
2474
        msOWSLookupMetadata(metadata, namespaces, vocname.c_str());
182✔
2475
    if (vocabularylist && nVersion >= OWS_1_3_0) {
182✔
2476
      const auto tokens = msStringSplit(vocabularylist, ',');
9✔
2477
      for (const auto &token : tokens) {
18✔
2478
        msOWSPrintEncodeMetadataList(
9✔
2479
            stream, metadata, namespaces,
2480
            (std::string(name) + '_' + token + "_items").c_str(), NULL, NULL,
18✔
2481
            (std::string(tabspace) + "    <Keyword vocabulary=\"" + token +
18✔
2482
             "\">%s</Keyword>\n")
2483
                .c_str(),
2484
            NULL);
2485
      }
2486
    }
9✔
2487
    msIO_printf("%s</KeywordList>\n", tabspace);
182✔
2488
  }
2489
}
302✔
2490

2491
/*
2492
** msWMSDumpLayer()
2493
*/
2494
static void msWMSDumpLayer(mapObj *map, const msWMSLayerNode *node,
187✔
2495
                           int nVersion, const char *script_url_encoded,
2496
                           const char *validated_language,
2497
                           const std::string &indent) {
2498
  rectObj ext;
2499
  char **classgroups = NULL;
2500
  int iclassgroups = 0;
2501
  char *pszMapEPSG, *pszLayerEPSG;
2502

2503
  layerObj *lp = GET_LAYER(map, node->layerIdx);
187✔
2504

2505
  /* if the layer status is set to MS_DEFAULT, output a warning */
2506
  if (lp->status == MS_DEFAULT)
187✔
2507
    msIO_fprintf(stdout,
4✔
2508
                 "<!-- WARNING: This layer has its status set to DEFAULT and "
2509
                 "will always be displayed when doing a GetMap request even if "
2510
                 "it is not requested by the client. This is not in line with "
2511
                 "the expected behavior of a WMS server. Using status ON or "
2512
                 "OFF is recommended. -->\n");
2513

2514
  if (nVersion < OWS_1_1_0) {
187✔
2515
    msIO_printf("%s<Layer queryable=\"%d\">\n", indent.c_str(),
1✔
2516
                node->isQueryable(map));
1✔
2517
  } else {
2518
    /* 1.1.0 and later: opaque and cascaded are new. */
2519
    int cascaded = 0, opaque = 0;
2520
    const char *value = msOWSLookupMetadata(&(lp->metadata), "MO", "opaque");
186✔
2521
    if (value != NULL)
186✔
2522
      opaque = atoi(value);
2523
    if (lp->connectiontype == MS_WMS)
186✔
2524
      cascaded = 1;
2525

2526
    msIO_printf("%s<Layer queryable=\"%d\" opaque=\"%d\" cascaded=\"%d\">\n",
186✔
2527
                indent.c_str(), node->isQueryable(map), opaque, cascaded);
186✔
2528
  }
2529

2530
  if (lp->name && strlen(lp->name) > 0 &&
374✔
2531
      (msIsXMLTagValid(lp->name) == MS_FALSE || isdigit(lp->name[0])))
374✔
2532
    msIO_fprintf(stdout,
×
2533
                 "<!-- WARNING: The layer name '%s' might contain spaces or "
2534
                 "invalid characters or may start with a number. This could "
2535
                 "lead to potential problems. -->\n",
2536
                 lp->name);
2537
  msOWSPrintEncodeParam(stdout, "LAYER.NAME", lp->name, OWS_NOERR,
187✔
2538
                        (indent + "  <Name>%s</Name>\n").c_str(), NULL);
187✔
2539

2540
  /* the majority of this section is dependent on appropriately named metadata
2541
   * in the LAYER object */
2542
  msOWSPrintEncodeMetadata2(stdout, &(lp->metadata), "MO", "title", OWS_WARN,
187✔
2543
                            (indent + "  <Title>%s</Title>\n").c_str(),
374✔
2544
                            lp->name, validated_language);
187✔
2545

2546
  msOWSPrintEncodeMetadata2(stdout, &(lp->metadata), "MO", "abstract",
187✔
2547
                            OWS_NOERR,
2548
                            (indent + "  <Abstract>%s</Abstract>\n").c_str(),
187✔
2549
                            NULL, validated_language);
2550

2551
  msWMSPrintKeywordlist(stdout, (indent + "  ").c_str(), "keywordlist",
187✔
2552
                        &(lp->metadata), "MO", nVersion);
2553

2554
  msOWSGetEPSGProj(&(map->projection), &(map->web.metadata), "MO", MS_FALSE,
187✔
2555
                   &pszMapEPSG);
2556
  msOWSGetEPSGProj(&(lp->projection), &(lp->metadata), "MO", MS_FALSE,
187✔
2557
                   &pszLayerEPSG);
2558
  if (pszMapEPSG == NULL) {
187✔
2559
    /* If map has no proj then every layer MUST have one or produce a warning */
2560
    if (nVersion > OWS_1_1_0) {
×
2561
      /* starting 1.1.1 SRS are given in individual tags */
2562
      if (nVersion >= OWS_1_3_0) {
×
2563
        msOWSPrintEncodeParamList(stdout,
×
2564
                                  "(at least one of) "
2565
                                  "MAP.PROJECTION, LAYER.PROJECTION "
2566
                                  "or wms_srs metadata",
2567
                                  pszLayerEPSG, OWS_WARN, ' ', NULL, NULL,
NEW
2568
                                  (indent + "  <CRS>%s</CRS>\n").c_str(), NULL);
×
2569
      } else {
2570
        msOWSPrintEncodeParamList(stdout,
×
2571
                                  "(at least one of) "
2572
                                  "MAP.PROJECTION, LAYER.PROJECTION "
2573
                                  "or wms_srs metadata",
2574
                                  pszLayerEPSG, OWS_WARN, ' ', NULL, NULL,
NEW
2575
                                  (indent + "  <SRS>%s</SRS>\n").c_str(), NULL);
×
2576
      }
2577
    } else {
2578
      msOWSPrintEncodeParam(stdout,
×
2579
                            "(at least one of) MAP.PROJECTION, "
2580
                            "LAYER.PROJECTION or wms_srs metadata",
2581
                            pszLayerEPSG, OWS_WARN,
NEW
2582
                            (indent + "  <SRS>%s</SRS>\n").c_str(), NULL);
×
2583
    }
2584
  } else {
2585
    /* No warning required in this case since there's at least a map proj. */
2586
    if (nVersion > OWS_1_1_0) {
187✔
2587
      /* starting 1.1.1 SRS are given in individual tags */
2588
      if (nVersion >= OWS_1_3_0) {
172✔
2589
        msOWSPrintEncodeParamList(stdout,
91✔
2590
                                  "(at least one of) "
2591
                                  "MAP.PROJECTION, LAYER.PROJECTION "
2592
                                  "or wms_srs metadata",
2593
                                  pszLayerEPSG, OWS_NOERR, ' ', NULL, NULL,
2594
                                  (indent + "  <CRS>%s</CRS>\n").c_str(), NULL);
182✔
2595
      } else {
2596
        msOWSPrintEncodeParamList(stdout,
81✔
2597
                                  "(at least one of) "
2598
                                  "MAP.PROJECTION, LAYER.PROJECTION "
2599
                                  "or wms_srs metadata",
2600
                                  pszLayerEPSG, OWS_NOERR, ' ', NULL, NULL,
2601
                                  (indent + "  <SRS>%s</SRS>\n").c_str(), NULL);
162✔
2602
      }
2603
    } else {
2604
      msOWSPrintEncodeParam(stdout, " LAYER.PROJECTION (or wms_srs metadata)",
15✔
2605
                            pszLayerEPSG, OWS_NOERR,
2606
                            (indent + "  <SRS>%s</SRS>\n").c_str(), NULL);
30✔
2607
    }
2608
  }
2609
  msFree(pszLayerEPSG);
187✔
2610
  msFree(pszMapEPSG);
187✔
2611

2612
  /* If layer has no proj set then use map->proj for bounding box. */
2613
  if (msOWSGetLayerExtent(map, lp, "MO", &ext) == MS_SUCCESS) {
187✔
2614
    if (lp->projection.numargs > 0) {
174✔
2615
      if (nVersion >= OWS_1_3_0)
167✔
2616
        msOWSPrintEX_GeographicBoundingBox(stdout, (indent + "  ").c_str(),
164✔
2617
                                           &(ext), &(lp->projection));
2618
      else
2619
        msOWSPrintLatLonBoundingBox(stdout, (indent + "  ").c_str(), &(ext),
170✔
2620
                                    &(lp->projection), NULL, OWS_WMS);
2621

2622
      msOWSPrintBoundingBox(stdout, (indent + "  ").c_str(), &(ext),
334✔
2623
                            &(lp->projection), &(lp->metadata),
2624
                            &(map->web.metadata), "MO", nVersion);
2625
    } else {
2626
      if (nVersion >= OWS_1_3_0)
7✔
2627
        msOWSPrintEX_GeographicBoundingBox(stdout, (indent + "  ").c_str(),
12✔
2628
                                           &(ext), &(map->projection));
2629
      else
2630
        msOWSPrintLatLonBoundingBox(stdout, (indent + "  ").c_str(), &(ext),
2✔
2631
                                    &(map->projection), NULL, OWS_WMS);
2632
      msOWSPrintBoundingBox(stdout, (indent + "  ").c_str(), &(ext),
14✔
2633
                            &(map->projection), &(lp->metadata),
2634
                            &(map->web.metadata), "MO", nVersion);
2635
    }
2636
  } else {
2637
    if (nVersion >= OWS_1_3_0)
13✔
2638
      msIO_printf(
3✔
2639
          (indent + "  %s").c_str(),
6✔
2640
          "<!-- WARNING: Optional Ex_GeographicBoundingBox could not "
2641
          "be established for this layer.  Consider setting the EXTENT in the "
2642
          "LAYER object, or wms_extent metadata. Also check that your data "
2643
          "exists in the DATA statement -->\n");
2644
    else
2645
      msIO_printf((indent + "  %s").c_str(),
20✔
2646
                  "<!-- WARNING: Optional LatLonBoundingBox could not "
2647
                  "be established for this layer.  Consider setting the EXTENT "
2648
                  "in the LAYER object, or wms_extent metadata. Also check "
2649
                  "that your data exists in the DATA statement -->\n");
2650
  }
2651

2652
  /* time support */
2653
  const char *pszWmsTimeExtent =
2654
      msOWSLookupMetadata(&(lp->metadata), "MO", "timeextent");
187✔
2655
  if (pszWmsTimeExtent) {
187✔
2656
    const char *pszWmsTimeDefault =
2657
        msOWSLookupMetadata(&(lp->metadata), "MO", "timedefault");
4✔
2658

2659
    if (nVersion >= OWS_1_3_0) {
4✔
2660
      if (pszWmsTimeDefault)
2✔
2661
        msIO_fprintf(stdout,
×
NEW
2662
                     (indent +
×
2663
                      "  <Dimension name=\"time\" units=\"ISO8601\" "
2664
                      "default=\"%s\" nearestValue=\"0\">%s</Dimension>\n")
2665
                         .c_str(),
2666
                     pszWmsTimeDefault, pszWmsTimeExtent);
2667
      else
2668
        msIO_fprintf(stdout,
2✔
2669
                     (indent + "  <Dimension name=\"time\" units=\"ISO8601\" "
4✔
2670
                               "nearestValue=\"0\">%s</Dimension>\n")
2671
                         .c_str(),
2672
                     pszWmsTimeExtent);
2673
    }
2674

2675
    else {
2676
      msIO_fprintf(stdout, "%s",
2✔
2677
                   (indent + "  <Dimension name=\"time\" units=\"ISO8601\"/>\n")
2✔
2678
                       .c_str());
2679
      if (pszWmsTimeDefault)
2✔
2680
        msIO_fprintf(stdout,
×
NEW
2681
                     (indent + "  <Extent name=\"time\" default=\"%s\" "
×
2682
                               "nearestValue=\"0\">%s</Extent>\n")
2683
                         .c_str(),
2684
                     pszWmsTimeDefault, pszWmsTimeExtent);
2685
      else
2686
        msIO_fprintf(
2✔
2687
            stdout,
2688
            (indent +
4✔
2689
             "  <Extent name=\"time\" nearestValue=\"0\">%s</Extent>\n")
2690
                .c_str(),
2691
            pszWmsTimeExtent);
2692
    }
2693
  }
2694

2695
  /*dimensions support: elevation + other user defined dimensions*/
2696
  const char *pszDimensionlist =
2697
      msOWSLookupMetadata(&(lp->metadata), "M", "dimensionlist");
187✔
2698
  if (pszDimensionlist) {
187✔
2699
    auto tokens = msStringSplit(pszDimensionlist, ',');
6✔
2700
    for (auto &dimension : tokens) {
14✔
2701
      /*check if manadatory unit and extent are set. Item should also be set.
2702
       * default value is optional*/
2703
      msStringTrim(dimension);
8✔
2704

2705
      const char *pszDimensionItem = msOWSLookupMetadata(
8✔
2706
          &(lp->metadata), "M", (dimension + "_item").c_str());
8✔
2707
      const char *pszDimensionExtent = msOWSLookupMetadata(
8✔
2708
          &(lp->metadata), "M", (dimension + "_extent").c_str());
8✔
2709
      const char *pszDimensionUnit = msOWSLookupMetadata(
8✔
2710
          &(lp->metadata), "M", (dimension + "_units").c_str());
8✔
2711
      const char *pszDimensionDefault = msOWSLookupMetadata(
8✔
2712
          &(lp->metadata), "M", (dimension + "_default").c_str());
8✔
2713

2714
      if (pszDimensionItem && pszDimensionExtent && pszDimensionUnit) {
8✔
2715
        if (nVersion >= OWS_1_3_0) {
8✔
2716
          if (pszDimensionDefault && strlen(pszDimensionDefault) > 0)
4✔
2717
            msIO_fprintf(
1✔
2718
                stdout,
2719
                (indent +
2✔
2720
                 "  <Dimension name=\"%s\" units=\"%s\" default=\"%s\" "
2721
                 "multipleValues=\"1\" nearestValue=\"0\">%s</Dimension>\n")
2722
                    .c_str(),
2723
                dimension.c_str(), pszDimensionUnit, pszDimensionDefault,
2724
                pszDimensionExtent);
2725
          else
2726
            msIO_fprintf(
3✔
2727
                stdout,
2728
                (indent +
6✔
2729
                 "  <Dimension name=\"%s\" units=\"%s\"  "
2730
                 "multipleValues=\"1\"  nearestValue=\"0\">%s</Dimension>\n")
2731
                    .c_str(),
2732
                dimension.c_str(), pszDimensionUnit, pszDimensionExtent);
2733
        } else {
2734
          msIO_fprintf(
4✔
2735
              stdout,
2736
              (indent + "  <Dimension name=\"%s\" units=\"%s\"/>\n").c_str(),
4✔
2737
              dimension.c_str(), pszDimensionUnit);
2738
          if (pszDimensionDefault && strlen(pszDimensionDefault) > 0)
4✔
2739
            msIO_fprintf(stdout,
1✔
2740
                         (indent + "  <Extent name=\"%s\" default=\"%s\" "
2✔
2741
                                   "nearestValue=\"0\">%s</Extent>\n")
2742
                             .c_str(),
2743
                         dimension.c_str(), pszDimensionDefault,
2744
                         pszDimensionExtent);
2745
          else
2746
            msIO_fprintf(
3✔
2747
                stdout,
2748
                (indent +
6✔
2749
                 "  <Extent name=\"%s\" nearestValue=\"0\">%s</Extent>\n")
2750
                    .c_str(),
2751
                dimension.c_str(), pszDimensionExtent);
2752
        }
2753
      }
2754
    }
2755
  }
6✔
2756

2757
  /* AuthorityURL support and Identifier support, only available >= WMS 1.1.0 */
2758
  if (nVersion >= OWS_1_1_0) {
187✔
2759
    msWMSPrintAttribution(stdout, (indent + "  ").c_str(), &(lp->metadata),
186✔
2760
                          "MO");
2761
    msWMSPrintAuthorityURL(stdout, (indent + "  ").c_str(), &(lp->metadata),
186✔
2762
                           "MO");
2763
    msWMSPrintIdentifier(stdout, (indent + "  ").c_str(), &(lp->metadata),
186✔
2764
                         "MO");
2765
  }
2766

2767
  if (nVersion >= OWS_1_1_0) {
2768
    const char *metadataurl_list =
2769
        msOWSLookupMetadata(&(lp->metadata), "MO", "metadataurl_list");
186✔
2770
    if (metadataurl_list) {
186✔
2771
      const auto tokens = msStringSplit(metadataurl_list, ' ');
1✔
2772
      for (const auto &token : tokens) {
3✔
2773
        std::string key("metadataurl_");
2✔
2774
        key += token;
2775
        msOWSPrintURLType(stdout, &(lp->metadata), "MO", key.c_str(), OWS_NOERR,
2✔
2776
                          NULL, "MetadataURL", " type=\"%s\"", NULL, NULL,
2777
                          ">\n          <Format>%s</Format",
2778
                          "\n        <OnlineResource xmlns:xlink=\""
2779
                          "http://www.w3.org/1999/xlink\" "
2780
                          "xlink:type=\"simple\" xlink:href=\"%s\"/>\n        ",
2781
                          MS_TRUE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
2782
                          NULL, NULL, NULL, NULL, (indent + "  ").c_str());
4✔
2783
      }
2784
    } else {
1✔
2785
      if (!msOWSLookupMetadata(&(lp->metadata), "MO", "metadataurl_href"))
185✔
2786
        msMetadataSetGetMetadataURL(lp, script_url_encoded);
96✔
2787

2788
      msOWSPrintURLType(stdout, &(lp->metadata), "MO", "metadataurl", OWS_NOERR,
185✔
2789
                        NULL, "MetadataURL", " type=\"%s\"", NULL, NULL,
2790
                        (">\n" + indent + "    <Format>%s</Format").c_str(),
370✔
2791
                        ("\n" + indent +
185✔
2792
                         "    <OnlineResource xmlns:xlink=\""
2793
                         "http://www.w3.org/1999/xlink\" "
2794
                         "xlink:type=\"simple\" xlink:href=\"%s\"/>\n" +
185✔
2795
                         indent + "  ")
185✔
2796
                            .c_str(),
2797
                        MS_TRUE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
2798
                        NULL, NULL, NULL, NULL, (indent + "  ").c_str());
370✔
2799
    }
2800
  }
2801

2802
  if (nVersion < OWS_1_1_0)
2803
    msOWSPrintEncodeMetadata(
1✔
2804
        stdout, &(lp->metadata), "MO", "dataurl_href", OWS_NOERR,
2805
        (indent + "  <DataURL>%s</DataURL>\n").c_str(), NULL);
2✔
2806
  else {
2807
    const char *dataurl_list =
2808
        msOWSLookupMetadata(&(lp->metadata), "MO", "dataurl_list");
186✔
2809
    if (dataurl_list) {
186✔
2810
      const auto tokens = msStringSplit(dataurl_list, ' ');
1✔
2811
      for (const auto &token : tokens) {
3✔
2812
        std::string key("dataurl_");
2✔
2813
        key += token;
2814
        msOWSPrintURLType(stdout, &(lp->metadata), "MO", key.c_str(), OWS_NOERR,
2✔
2815
                          NULL, "DataURL", NULL, NULL, NULL,
2816
                          (">\n" + indent + "    <Format>%s</Format").c_str(),
4✔
2817
                          ("\n" + indent +
2✔
2818
                           "    <OnlineResource xmlns:xlink=\""
2819
                           "http://www.w3.org/1999/xlink\" "
2820
                           "xlink:type=\"simple\" xlink:href=\"%s\"/>\n" +
2✔
2821
                           indent + "  ")
2✔
2822
                              .c_str(),
2823
                          MS_FALSE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
2824
                          NULL, NULL, NULL, NULL, (indent + "  ").c_str());
4✔
2825
      }
2826
    } else {
1✔
2827
      msOWSPrintURLType(stdout, &(lp->metadata), "MO", "dataurl", OWS_NOERR,
185✔
2828
                        NULL, "DataURL", NULL, NULL, NULL,
2829
                        (">\n" + indent + "    <Format>%s</Format").c_str(),
370✔
2830
                        ("\n" + indent +
185✔
2831
                         "    <OnlineResource xmlns:xlink=\""
2832
                         "http://www.w3.org/1999/xlink\" "
2833
                         "xlink:type=\"simple\" xlink:href=\"%s\"/>\n" +
185✔
2834
                         indent + "  ")
185✔
2835
                            .c_str(),
2836
                        MS_FALSE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
2837
                        NULL, NULL, NULL, NULL, (indent + "  ").c_str());
370✔
2838
    }
2839
  }
2840

2841
  /* The LegendURL reside in a style. The Web Map Context spec already  */
2842
  /* included the support on this in mapserver. However, it is not in the  */
2843
  /* wms_legendurl_... metadatas it's in the styles metadata, */
2844
  /* In wms_style_<style_name>_lengendurl_... metadata. So we have to detect */
2845
  /* the current style before reading it. Also in the Style block, we need */
2846
  /* a Title and a name. Title is derived from wms_style_<style>_title, */
2847
  /* which allows multiple style definitions, e.g. by using classgroups. */
2848
  const char *pszStyle = msOWSLookupMetadata(&(lp->metadata), "MO", "style");
187✔
2849
  const char *pszLegendURL = NULL;
2850
  if (pszStyle) {
187✔
2851
    pszLegendURL = msOWSLookupMetadata(
×
2852
        &(lp->metadata), "MO",
2853
        (std::string("style_") + pszStyle + "_legendurl_href").c_str());
×
2854
  } else
2855
    pszStyle = "default";
2856

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

2868
    /* Inside, print the legend url block */
2869
    msOWSPrintEncodeMetadata(
×
2870
        stdout, &(lp->metadata), "MO",
2871
        (std::string("style_") + pszStyle + "_legendurl_href").c_str(),
×
NEW
2872
        OWS_NOERR, (indent + "    <StyleURL>%s</StyleURL>\n").c_str(), NULL);
×
2873

2874
    /* close the style block */
NEW
2875
    msIO_fprintf(stdout, "%s", (indent + "  </Style>\n").c_str());
×
2876

2877
  } else if (nVersion >= OWS_1_1_0) {
187✔
2878
    if (pszLegendURL) {
186✔
2879
      /* First, print the style block */
NEW
2880
      msIO_fprintf(stdout, "%s", (indent + "  <Style>\n").c_str());
×
NEW
2881
      msIO_fprintf(stdout, (indent + "    <Name>%s</Name>\n").c_str(),
×
2882
                   pszStyle);
2883
      /* Print the real Title or Style name otherwise */
2884
      msOWSPrintEncodeMetadata2(
×
2885
          stdout, &(lp->metadata), "MO",
2886
          (std::string("style_") + pszStyle + "_title").c_str(), OWS_NOERR,
×
NEW
2887
          (indent + "    <Title>%s</Title>\n").c_str(), pszStyle,
×
2888
          validated_language);
2889

2890
      /* Inside, print the legend url block */
2891
      msOWSPrintURLType(
×
2892
          stdout, &(lp->metadata), "MO",
2893
          (std::string("style_") + pszStyle + "_legendurl").c_str(), OWS_NOERR,
×
2894
          NULL, "LegendURL", NULL, " width=\"%s\"", " height=\"%s\"",
2895
          ">\n             <Format>%s</Format",
2896
          "\n             <OnlineResource "
2897
          "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
2898
          " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
2899
          "          ",
2900
          MS_FALSE, MS_TRUE, MS_TRUE, MS_TRUE, MS_TRUE, NULL, NULL, NULL, NULL,
2901
          NULL, "          ");
NEW
2902
      msIO_fprintf(stdout, "%s", (indent + "  </Style>\n").c_str());
×
2903

2904
    } else {
2905
      if (script_url_encoded) {
186✔
2906
        if (lp->connectiontype != MS_WMS && lp->connectiontype != MS_WFS &&
186✔
2907
            lp->connectiontype != MS_UNUSED_1 && lp->numclasses > 0) {
185✔
2908
          bool classnameset = false;
2909
          for (int i = 0; i < lp->numclasses; i++) {
185✔
2910
            if (lp->_class[i]->name && strlen(lp->_class[i]->name) > 0) {
167✔
2911
              classnameset = true;
2912
              break;
2913
            }
2914
          }
2915
          if (classnameset) {
164✔
2916
            int size_x = 0, size_y = 0;
146✔
2917
            std::vector<int> group_layers;
2918
            group_layers.reserve(map->numlayers);
146✔
2919

2920
            group_layers.push_back(lp->index);
146✔
2921
            for (int j : node->collectLayerIndices()) {
310✔
2922
              if (j != lp->index) {
164✔
2923
                group_layers.push_back(j);
18✔
2924
              }
2925
            }
146✔
2926

2927
            if (msLegendCalcSize(map, 1, &size_x, &size_y, group_layers.data(),
146✔
2928
                                 static_cast<int>(group_layers.size()), NULL,
2929
                                 1) == MS_SUCCESS) {
2930
              const std::string width(std::to_string(size_x));
146✔
2931
              const std::string height(std::to_string(size_y));
146✔
2932

2933
              char *mimetype = NULL;
2934
#if defined USE_PNG
2935
              mimetype = msEncodeHTMLEntities("image/png");
146✔
2936
#endif
2937

2938
#if defined USE_JPEG
2939
              if (!mimetype)
146✔
2940
                mimetype = msEncodeHTMLEntities("image/jpeg");
×
2941
#endif
2942
              if (!mimetype)
×
2943
                mimetype =
2944
                    msEncodeHTMLEntities(MS_IMAGE_MIME_TYPE(map->outputformat));
×
2945

2946
              /* --------------------------------------------------------------------
2947
               */
2948
              /*      check if the group parameters for the classes are set. We
2949
               */
2950
              /*      should then publish the different class groups as
2951
               * different styles.*/
2952
              /* --------------------------------------------------------------------
2953
               */
2954
              iclassgroups = 0;
2955
              classgroups = NULL;
2956

2957
              const char *styleName =
2958
                  msOWSLookupMetadata(&(map->web.metadata), "MO", "style_name");
146✔
2959
              if (styleName == NULL)
146✔
2960
                styleName = "default";
2961
              char *pszEncodedStyleName = msEncodeHTMLEntities(styleName);
146✔
2962

2963
              for (int i = 0; i < lp->numclasses; i++) {
378✔
2964
                if (lp->_class[i]->name && lp->_class[i]->group) {
232✔
2965
                  /* Check that style is not inherited from root layer (#4442).
2966
                   */
2967
                  if (strcasecmp(pszEncodedStyleName, lp->_class[i]->group) ==
180✔
2968
                      0)
2969
                    continue;
102✔
2970
                  /* Check that style is not inherited from group layer(s)
2971
                   * (#4442). */
2972
                  bool styleInheritedFromParent = false;
2973
                  for (const msWMSLayerNode *cur = node->parent;
78✔
2974
                       cur && !styleInheritedFromParent; cur = cur->parent) {
222✔
2975
                    if (cur->layerIdx >= 0) {
144✔
2976
                      layerObj *lp2 = GET_LAYER(map, cur->layerIdx);
72✔
2977
                      for (int l = 0; l < lp2->numclasses; l++) {
90✔
2978
                        if (strcasecmp(lp2->_class[l]->group,
24✔
2979
                                       lp->_class[i]->group) == 0) {
2980
                          styleInheritedFromParent = true;
2981
                          break;
2982
                        }
2983
                      }
2984
                    }
2985
                  }
2986
                  if (styleInheritedFromParent) {
78✔
2987
                    continue;
6✔
2988
                  }
2989
                  if (!classgroups) {
72✔
2990
                    classgroups = (char **)msSmallMalloc(sizeof(char *));
72✔
2991
                    classgroups[iclassgroups++] =
72✔
2992
                        msStrdup(lp->_class[i]->group);
72✔
2993
                  } else {
2994
                    /* Output style only once. */
2995
                    bool found = false;
2996
                    for (int j = 0; j < iclassgroups; j++) {
×
2997
                      if (strcasecmp(classgroups[j], lp->_class[i]->group) ==
×
2998
                          0) {
2999
                        found = true;
3000
                        break;
3001
                      }
3002
                    }
3003
                    if (!found) {
×
3004
                      iclassgroups++;
×
3005
                      classgroups = (char **)msSmallRealloc(
×
3006
                          classgroups, sizeof(char *) * iclassgroups);
×
3007
                      classgroups[iclassgroups - 1] =
×
3008
                          msStrdup(lp->_class[i]->group);
×
3009
                    }
3010
                  }
3011
                }
3012
              }
3013
              msFree(pszEncodedStyleName);
146✔
3014
              if (classgroups == NULL) {
146✔
3015
                classgroups = (char **)msSmallMalloc(sizeof(char *));
74✔
3016
                classgroups[0] = msStrdup("default");
74✔
3017
                iclassgroups = 1;
3018
              }
3019

3020
              for (int i = 0; i < iclassgroups; i++) {
292✔
3021
                char *name_encoded = msEncodeHTMLEntities(lp->name);
146✔
3022
                char *classgroup_encoded = msEncodeHTMLEntities(classgroups[i]);
146✔
3023
                std::string legendurl(script_url_encoded);
146✔
3024
                legendurl += "version=";
3025
                char szVersionBuf[OWS_VERSION_MAXLEN];
3026
                legendurl += msOWSGetVersionString(nVersion, szVersionBuf);
146✔
3027
                legendurl +=
3028
                    "&amp;service=WMS&amp;request=GetLegendGraphic&amp;";
3029
                if (nVersion >= OWS_1_3_0) {
146✔
3030
                  legendurl += "sld_version=1.1.0&amp;layer=";
3031
                } else {
3032
                  legendurl += "layer=";
3033
                }
3034
                legendurl += name_encoded;
3035
                legendurl += "&amp;format=";
3036
                legendurl += mimetype;
3037
                legendurl += "&amp;STYLE=";
3038
                legendurl += classgroup_encoded;
3039

3040
                msFree(name_encoded);
146✔
3041
                msFree(classgroup_encoded);
146✔
3042

3043
                msIO_fprintf(stdout, "        <Style>\n");
146✔
3044
                msIO_fprintf(stdout, "          <Name>%s</Name>\n",
146✔
3045
                             classgroups[i]);
3046
                msOWSPrintEncodeMetadata2(
146✔
3047
                    stdout, &(lp->metadata), "MO",
3048
                    (std::string("style_") + classgroups[i] + "_title").c_str(),
146✔
3049
                    OWS_NOERR, "          <Title>%s</Title>\n", classgroups[i],
3050
                    validated_language);
3051

3052
                /* A legendurl from wms_style_<style>_legendurl_href will
3053
                 * override a self generated legend graphic */
3054
                pszLegendURL = msOWSLookupMetadata(
146✔
3055
                    &(lp->metadata), "MO",
3056
                    (std::string("style_") + classgroups[i] + "_legendurl_href")
146✔
3057
                        .c_str());
3058
                if (pszLegendURL) {
146✔
3059
                  msOWSPrintURLType(
×
3060
                      stdout, &(lp->metadata), "MO",
3061
                      (std::string("style_") + classgroups[i] + "_legendurl")
×
3062
                          .c_str(),
3063
                      OWS_NOERR, NULL, "LegendURL", NULL, " width=\"%s\"",
3064
                      " height=\"%s\"", ">\n             <Format>%s</Format",
3065
                      "\n             <OnlineResource "
3066
                      "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
3067
                      " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
3068
                      "          ",
3069
                      MS_FALSE, MS_TRUE, MS_TRUE, MS_TRUE, MS_TRUE, NULL, NULL,
3070
                      NULL, NULL, NULL, "          ");
3071
                } else {
3072
                  msOWSPrintURLType(
146✔
3073
                      stdout, NULL, "O", "ttt", OWS_NOERR, NULL, "LegendURL",
3074
                      NULL, " width=\"%s\"", " height=\"%s\"",
3075
                      ">\n             <Format>%s</Format",
3076
                      "\n             <OnlineResource "
3077
                      "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
3078
                      " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
3079
                      "          ",
3080
                      MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE, NULL,
3081
                      width.c_str(), height.c_str(), mimetype,
3082
                      legendurl.c_str(), "          ");
3083
                }
3084
                msIO_fprintf(stdout, "        </Style>\n");
146✔
3085
              }
3086
              msFreeCharArray(classgroups, iclassgroups);
146✔
3087
              msFree(mimetype);
146✔
3088
            }
3089
          }
146✔
3090
        }
3091
      }
3092
    }
3093
  }
3094

3095
  /* print Min/Max ScaleDenominator */
3096
  if (nVersion < OWS_1_3_0)
186✔
3097
    msWMSPrintScaleHint((indent + "  ").c_str(), lp->minscaledenom,
192✔
3098
                        lp->maxscaledenom, map->resolution);
3099
  else
3100
    msWMSPrintScaleDenominator((indent + "  ").c_str(), lp->minscaledenom,
182✔
3101
                               lp->maxscaledenom);
3102
}
187✔
3103

3104
/*
3105
** msWMSPrintGroupStyle()
3106
*/
3107
static void msWMSPrintGroupStyle(mapObj *map, int nVersion,
11✔
3108
                                 owsRequestObj *ows_request,
3109
                                 const msWMSLayerNode *node, layerObj *lp,
3110
                                 const char *script_url_encoded,
3111
                                 const std::string &indent) {
3112
  char *pszEncodedName = NULL;
3113
  const char *styleName = NULL;
3114
  char *pszEncodedStyleName = NULL;
3115
  const char *legendURL = NULL;
3116

3117
  pszEncodedName = msEncodeHTMLEntities(lp->group);
11✔
3118

3119
  styleName = msOWSLookupMetadata(&(lp->metadata), "MO", "group_style_name");
11✔
3120
  if (styleName == NULL)
11✔
3121
    styleName = "default";
3122

3123
  pszEncodedStyleName = msEncodeHTMLEntities(styleName);
11✔
3124

3125
  msIO_fprintf(stdout, "%s", (indent + "  <Style>\n").c_str());
11✔
3126
  msIO_fprintf(stdout, (indent + "    <Name>%s</Name>\n").c_str(),
11✔
3127
               pszEncodedStyleName);
3128
  msOWSPrintEncodeMetadata(
11✔
3129
      stdout, &(lp->metadata), "MO", "group_style_title", OWS_NOERR,
3130
      (indent + "    <Title>%s</Title>\n").c_str(), styleName);
11✔
3131

3132
  legendURL =
3133
      msOWSLookupMetadata(&(lp->metadata), "MO", "group_style_legendurl_href");
11✔
3134
  if (legendURL) {
11✔
3135
    msOWSPrintURLType(stdout, &(lp->metadata), "MO", "group_style_legendurl",
9✔
3136
                      OWS_NOERR, NULL, "LegendURL", NULL, " width=\"%s\"",
3137
                      " height=\"%s\"",
3138
                      (">\n" + indent + "    <Format>%s</Format").c_str(),
18✔
3139
                      ("\n" + indent +
9✔
3140
                       "    <OnlineResource "
3141
                       "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
3142
                       " xlink:type=\"simple\" xlink:href=\"%s\"/>\n" +
9✔
3143
                       indent + "  ")
9✔
3144
                          .c_str(),
3145
                      MS_FALSE, MS_TRUE, MS_TRUE, MS_TRUE, MS_TRUE, NULL, NULL,
3146
                      NULL, NULL, NULL, (indent + "  ").c_str());
18✔
3147
  } else {
3148
    std::vector<int> group_layers;
3149
    group_layers.reserve(map->numlayers);
2✔
3150
    for (int j : node->collectLayerIndices()) {
4✔
3151
      if (msIntegerInArray(GET_LAYER(map, j)->index,
2✔
3152
                           ows_request->enabled_layers,
3153
                           ows_request->numlayers)) {
3154
        group_layers.push_back(j);
2✔
3155
      }
3156
    }
2✔
3157

3158
    if (!group_layers.empty()) {
2✔
3159
      int size_x = 0, size_y = 0;
2✔
3160
      char *pszMimetype = NULL;
3161

3162
      if (msLegendCalcSize(map, 1, &size_x, &size_y, group_layers.data(),
2✔
3163
                           static_cast<int>(group_layers.size()), NULL,
3164
                           1) == MS_SUCCESS) {
3165
        const std::string width(std::to_string(size_x));
2✔
3166
        const std::string height(std::to_string(size_y));
2✔
3167

3168
        const char *format_list = msOWSLookupMetadata(
2✔
3169
            &(map->web.metadata), "M", "getlegendgraphic_formatlist");
3170
        if (format_list && strlen(format_list) > 0) {
2✔
NEW
3171
          const auto tokens = msStringSplit(format_list, ',');
×
NEW
3172
          if (!tokens.empty()) {
×
3173
            /*just grab the first mime type*/
NEW
3174
            pszMimetype = msEncodeHTMLEntities(tokens[0].c_str());
×
3175
          }
NEW
3176
        } else
×
3177
          pszMimetype = msEncodeHTMLEntities("image/png");
2✔
3178

3179
        std::string legendurl(script_url_encoded);
2✔
3180
        legendurl += "version=";
3181
        char szVersionBuf[OWS_VERSION_MAXLEN];
3182
        legendurl += msOWSGetVersionString(nVersion, szVersionBuf);
2✔
3183
        legendurl += "&amp;service=WMS&amp;request=GetLegendGraphic&amp;";
3184
        if (nVersion >= OWS_1_3_0) {
2✔
3185
          legendurl += "sld_version=1.1.0&amp;layer=";
3186
        } else {
3187
          legendurl += "layer=";
3188
        }
3189
        legendurl += pszEncodedName;
3190
        legendurl += "&amp;format=";
3191
        legendurl += pszMimetype;
3192
        legendurl += "&amp;STYLE=";
3193
        legendurl += pszEncodedStyleName;
3194

3195
        msOWSPrintURLType(stdout, NULL, "O", "ttt", OWS_NOERR, NULL,
2✔
3196
                          "LegendURL", NULL, " width=\"%s\"", " height=\"%s\"",
3197
                          ">\n          <Format>%s</Format",
3198
                          "\n          <OnlineResource "
3199
                          "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
3200
                          " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
3201
                          "       ",
3202
                          MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE,
3203
                          NULL, width.c_str(), height.c_str(), pszMimetype,
3204
                          legendurl.c_str(), "       ");
3205

3206
        msFree(pszMimetype);
2✔
3207
      }
3208
    }
3209
  }
2✔
3210
  msIO_fprintf(stdout, "%s", (indent + "  </Style>\n").c_str());
11✔
3211
  msFree(pszEncodedName);
11✔
3212
  msFree(pszEncodedStyleName);
11✔
3213
}
11✔
3214

3215
/*
3216
** msWMSPrintLayerNode()
3217
*/
3218
static void
3219
msWMSPrintLayerNode(mapObj *map, int nVersion, owsRequestObj *ows_request,
221✔
3220
                    const msWMSLayerNode *node, const char *script_url_encoded,
3221
                    const char *validated_language, int indentLevel) {
3222
  std::string indent;
3223
  for (int i = 0; i < indentLevel; i++) {
773✔
3224
    indent += "  ";
3225
  }
3226

3227
  bool closeLayerTag = false;
3228
  if (node->layerIdx >= 0) {
221✔
3229
    layerObj *lp = GET_LAYER(map, node->layerIdx);
200✔
3230
    if (lp->status != MS_DELETE &&
400✔
3231
        msIntegerInArray(lp->index, ows_request->enabled_layers,
200✔
3232
                         ows_request->numlayers)) {
3233
      closeLayerTag = true;
3234
      msWMSDumpLayer(map, node, nVersion, script_url_encoded,
187✔
3235
                     validated_language, indent);
3236
    }
3237
  } else {
3238
    closeLayerTag = true;
3239
    msIO_printf("%s<Layer%s>\n", indent.c_str(),
33✔
3240
                node->isQueryable(map) ? " queryable=\"1\"" : "");
21✔
3241
    msIO_printf("%s  <Name>%s</Name>\n", indent.c_str(), node->name.c_str());
21✔
3242
    if (!node->titleWarning.empty()) {
21✔
3243
      msIO_printf("%s  %s\n", indent.c_str(), node->titleWarning.c_str());
6✔
3244
    }
3245
    {
3246
      char *encoded = msEncodeHTMLEntities(node->title.c_str());
21✔
3247
      msIO_printf("%s  <Title>%s</Title>\n", indent.c_str(), encoded);
21✔
3248
      msFree(encoded);
21✔
3249
    }
3250
    if (!node->abstract.empty()) {
21✔
3251
      char *encoded = msEncodeHTMLEntities(node->abstract.c_str());
13✔
3252
      msIO_printf("%s  <Abstract>%s</Abstract>\n", indent.c_str(), encoded);
13✔
3253
      msFree(encoded);
13✔
3254
    }
3255

3256
    if (script_url_encoded && indentLevel == 2 && !node->children.empty() &&
21✔
3257
        node->children[0]->layerIdx >= 0) {
17✔
3258
      layerObj *lp = GET_LAYER(map, node->children[0]->layerIdx);
15✔
3259
      if (lp->group && strlen(lp->group) > 0 &&
26✔
3260
          msIntegerInArray(lp->index, ows_request->enabled_layers,
11✔
3261
                           ows_request->numlayers)) {
3262
        msWMSPrintGroupStyle(map, nVersion, ows_request, node, lp,
11✔
3263
                             script_url_encoded, indent);
3264
      }
3265
    }
3266
  }
3267

3268
  indent += "  ";
3269

3270
  for (const auto &child : node->children) {
326✔
3271
    msWMSPrintLayerNode(map, nVersion, ows_request, child.get(),
105✔
3272
                        script_url_encoded, validated_language,
3273
                        indentLevel + 1);
3274
  }
3275

3276
  indent.pop_back();
3277
  indent.pop_back();
3278

3279
  if (closeLayerTag)
221✔
3280
    msIO_printf("%s</Layer>\n", indent.c_str());
208✔
3281
}
221✔
3282

3283
/*
3284
** msWMSGetCapabilities()
3285
*/
3286
static int msWMSGetCapabilities(mapObj *map, int nVersion, cgiRequestObj *req,
64✔
3287
                                owsRequestObj *ows_request,
3288
                                const char *requested_updatesequence,
3289
                                const char *wms_exception_format,
3290
                                const char *requested_language) {
3291
  const char *updatesequence =
3292
      msOWSLookupMetadata(&(map->web.metadata), "MO", "updatesequence");
64✔
3293

3294
  const char *sldenabled =
3295
      msOWSLookupMetadata(&(map->web.metadata), "MO", "sld_enabled");
64✔
3296

3297
  if (sldenabled == NULL)
64✔
3298
    sldenabled = "true";
3299

3300
  if (requested_updatesequence != NULL) {
64✔
3301
    int i =
3302
        msOWSNegotiateUpdateSequence(requested_updatesequence, updatesequence);
6✔
3303
    if (i == 0) { /* current */
6✔
3304
      msSetErrorWithStatus(
2✔
3305
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
3306
          "UPDATESEQUENCE parameter (%s) is equal to server (%s)",
3307
          "msWMSGetCapabilities()", requested_updatesequence, updatesequence);
3308
      return msWMSException(map, nVersion, "CurrentUpdateSequence",
2✔
3309
                            wms_exception_format);
3310
    }
3311
    if (i > 0) { /* invalid */
4✔
3312
      msSetErrorWithStatus(
2✔
3313
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
3314
          "UPDATESEQUENCE parameter (%s) is higher than server (%s)",
3315
          "msWMSGetCapabilities()", requested_updatesequence, updatesequence);
3316
      return msWMSException(map, nVersion, "InvalidUpdateSequence",
2✔
3317
                            wms_exception_format);
3318
    }
3319
  }
3320

3321
  std::string schemalocation;
3322
  {
3323
    char *pszSchemalocation =
3324
        msEncodeHTMLEntities(msOWSGetSchemasLocation(map));
60✔
3325
    schemalocation = pszSchemalocation;
3326
    msFree(pszSchemalocation);
60✔
3327
  }
3328

3329
  if (nVersion < 0)
60✔
3330
    nVersion = OWS_1_3_0; /* Default to 1.3.0 */
3331

3332
  /* Decide which version we're going to return. */
3333
  std::string dtd_url;
3334
  if (nVersion == OWS_1_0_0) {
60✔
3335
    dtd_url = std::move(schemalocation);
2✔
3336
    dtd_url += "/wms/1.0.0/capabilities_1_0_0.dtd";
3337
  } else if (nVersion < OWS_1_1_1) {
58✔
3338
    nVersion = OWS_1_1_0;
3339
    dtd_url = std::move(schemalocation);
7✔
3340
    dtd_url += "/wms/1.1.0/capabilities_1_1_0.dtd";
3341
  } else if (nVersion < OWS_1_3_0) {
51✔
3342
    nVersion = OWS_1_1_1;
3343
    dtd_url = std::move(schemalocation);
22✔
3344
    /* this exception was added to accommodate the OGC test suite (Bug 1576)*/
3345
    if (strcasecmp(dtd_url.c_str(), OWS_DEFAULT_SCHEMAS_LOCATION) == 0)
22✔
3346
      dtd_url += "/wms/1.1.1/WMS_MS_Capabilities.dtd";
3347
    else
3348
      dtd_url += "/wms/1.1.1/capabilities_1_1_1.dtd";
3349
  } else
3350
    nVersion = OWS_1_3_0;
3351

3352
  /* This function owns validated_language, so remember to free it later*/
3353
  std::string validated_language;
3354
  {
3355
    char *pszValidated_language =
3356
        msOWSGetLanguageFromList(map, "MO", requested_language);
60✔
3357
    if (pszValidated_language) {
60✔
3358
      validated_language = pszValidated_language;
3359
      msMapSetLanguageSpecificConnection(map, pszValidated_language);
15✔
3360
    }
3361
    msFree(pszValidated_language);
60✔
3362
  }
3363

3364
  /* We need this server's onlineresource. */
3365
  /* Default to use the value of the "onlineresource" metadata, and if not */
3366
  /* set then build it: "http://$(SERVER_NAME):$(SERVER_PORT)$(SCRIPT_NAME)?" */
3367
  /* the returned string should be freed once we're done with it. */
3368
  char *script_url_encoded = NULL;
3369
  {
3370
    char *script_url = msOWSGetOnlineResource2(map, "MO", "onlineresource", req,
60✔
3371
                                               validated_language.c_str());
3372
    if (script_url == NULL ||
120✔
3373
        (script_url_encoded = msEncodeHTMLEntities(script_url)) == NULL) {
60✔
3374
      free(script_url);
×
3375
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
3376
    }
3377
    free(script_url);
60✔
3378
  }
3379

3380
  if (nVersion == OWS_1_0_0 || nVersion >= OWS_1_3_0) /* 1.0.0 and >=1.3.0*/
60✔
3381
    msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
31✔
3382
  else /* 1.1.0 and later */
3383
    msIO_setHeader("Content-Type",
29✔
3384
                   "application/vnd.ogc.wms_xml; charset=UTF-8");
3385
  msIO_sendHeaders();
60✔
3386

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

3389
  /*TODO review wms1.3.0*/
3390
  if (nVersion < OWS_1_3_0) {
60✔
3391
    msIO_printf("<!DOCTYPE WMT_MS_Capabilities SYSTEM \"%s\"\n",
31✔
3392
                dtd_url.c_str());
3393
    msIO_printf(" [\n");
31✔
3394

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

3552
    msIO_printf(" ]>  <!-- end of DOCTYPE declaration -->\n\n");
31✔
3553
  }
3554

3555
  char szVersionBuf[OWS_VERSION_MAXLEN];
3556
  const char *pszVersion = msOWSGetVersionString(nVersion, szVersionBuf);
60✔
3557
  if (nVersion >= OWS_1_3_0)
60✔
3558
    msIO_printf("<WMS_Capabilities version=\"%s\"", pszVersion);
29✔
3559
  else
3560
    msIO_printf("<WMT_MS_Capabilities version=\"%s\"", pszVersion);
31✔
3561
  if (updatesequence)
60✔
3562
    msIO_printf(" updateSequence=\"%s\"", updatesequence);
32✔
3563

3564
  if (nVersion == OWS_1_3_0) {
60✔
3565
    msIO_printf("  xmlns=\"http://www.opengis.net/wms\""
29✔
3566
                "   xmlns:sld=\"http://www.opengis.net/sld\""
3567
                "   xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
3568
                "   xmlns:ms=\"http://mapserver.gis.umn.edu/mapserver\"");
3569

3570
    if (msOWSLookupMetadata(&(map->web.metadata), "MO",
29✔
3571
                            "inspire_capabilities")) {
3572
      msIO_printf("   xmlns:" MS_INSPIRE_COMMON_NAMESPACE_PREFIX
9✔
3573
                  "=\"" MS_INSPIRE_COMMON_NAMESPACE_URI "\""
3574
                  "   xmlns:" MS_INSPIRE_VS_NAMESPACE_PREFIX
3575
                  "=\"" MS_INSPIRE_VS_NAMESPACE_URI "\"");
3576
    }
3577

3578
    msIO_printf(
29✔
3579
        "   xsi:schemaLocation=\"http://www.opengis.net/wms "
3580
        "%s/wms/%s/capabilities_1_3_0.xsd "
3581
        " http://www.opengis.net/sld %s/sld/1.1.0/sld_capabilities.xsd ",
3582
        msOWSGetSchemasLocation(map), pszVersion, msOWSGetSchemasLocation(map));
3583

3584
    if (msOWSLookupMetadata(&(map->web.metadata), "MO",
29✔
3585
                            "inspire_capabilities")) {
3586
      char *inspireschemalocation =
3587
          msEncodeHTMLEntities(msOWSGetInspireSchemasLocation(map));
9✔
3588
      msIO_printf(" " MS_INSPIRE_VS_NAMESPACE_URI " "
9✔
3589
                  " %s%s",
3590
                  inspireschemalocation, MS_INSPIRE_VS_SCHEMA_LOCATION);
3591
      free(inspireschemalocation);
9✔
3592
    }
3593

3594
    msIO_printf(
29✔
3595
        " http://mapserver.gis.umn.edu/mapserver "
3596
        "%sservice=WMS&amp;version=1.3.0&amp;request=GetSchemaExtension\"",
3597
        script_url_encoded);
3598
  }
3599

3600
  msIO_printf(">\n");
60✔
3601

3602
  /* WMS definition */
3603
  msIO_printf("<Service>\n");
60✔
3604

3605
  /* Service name is defined by the spec and changed at v1.0.0 */
3606
  if (nVersion == OWS_1_0_0)
60✔
3607
    msIO_printf("  <Name>GetMap</Name>\n"); /* v 1.0.0 */
2✔
3608
  else if (nVersion >= OWS_1_1_0 && nVersion < OWS_1_3_0)
58✔
3609
    msIO_printf("  <Name>OGC:WMS</Name>\n"); /* v 1.1.0 to 1.1.1*/
29✔
3610
  else
3611
    msIO_printf("  <Name>WMS</Name>\n"); /* v 1.3.0+ */
29✔
3612

3613
  /* the majority of this section is dependent on appropriately named metadata
3614
   * in the WEB object */
3615
  msOWSPrintEncodeMetadata2(stdout, &(map->web.metadata), "MO", "title",
60✔
3616
                            OWS_WARN, "  <Title>%s</Title>\n", map->name,
60✔
3617
                            validated_language.c_str());
3618
  msOWSPrintEncodeMetadata2(stdout, &(map->web.metadata), "MO", "abstract",
60✔
3619
                            OWS_NOERR, "  <Abstract>%s</Abstract>\n", NULL,
3620
                            validated_language.c_str());
3621

3622
  msWMSPrintKeywordlist(stdout, "  ", "keywordlist", &(map->web.metadata), "MO",
60✔
3623
                        nVersion);
3624

3625
  /* Service/onlineresource */
3626
  /* Defaults to same as request onlineresource if wms_service_onlineresource */
3627
  /* is not set. */
3628
  if (nVersion == OWS_1_0_0)
60✔
3629
    msOWSPrintEncodeMetadata(
2✔
3630
        stdout, &(map->web.metadata), "MO", "service_onlineresource", OWS_NOERR,
3631
        "  <OnlineResource>%s</OnlineResource>\n", script_url_encoded);
3632
  else
3633
    msOWSPrintEncodeMetadata(
58✔
3634
        stdout, &(map->web.metadata), "MO", "service_onlineresource", OWS_NOERR,
3635
        "  <OnlineResource xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
3636
        "xlink:href=\"%s\"/>\n",
3637
        script_url_encoded);
3638

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

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

3645
  msOWSPrintEncodeMetadata(
60✔
3646
      stdout, &(map->web.metadata), "MO", "accessconstraints", OWS_NOERR,
3647
      "  <AccessConstraints>%s</AccessConstraints>\n", NULL);
3648

3649
  if (nVersion >= OWS_1_3_0) {
60✔
3650
    const char *layerlimit =
3651
        msOWSLookupMetadata(&(map->web.metadata), "MO", "layerlimit");
29✔
3652
    if (layerlimit) {
29✔
3653
      msIO_printf("  <LayerLimit>%s</LayerLimit>\n", layerlimit);
4✔
3654
    }
3655
    msIO_printf("  <MaxWidth>%d</MaxWidth>\n", map->maxsize);
29✔
3656
    msIO_printf("  <MaxHeight>%d</MaxHeight>\n", map->maxsize);
29✔
3657
  }
3658

3659
  msIO_printf("</Service>\n\n");
60✔
3660

3661
  /* WMS capabilities definitions */
3662
  msIO_printf("<Capability>\n");
60✔
3663
  msIO_printf("  <Request>\n");
60✔
3664

3665
  if (nVersion == OWS_1_0_0) {
60✔
3666
    /* WMS 1.0.0 - we don't try to use outputformats list here for now
3667
     */
3668
    if (msOWSRequestIsEnabled(map, NULL, "M", "GetMap", MS_FALSE))
2✔
3669
      msWMSPrintRequestCap(nVersion, "Map", script_url_encoded,
2✔
3670
                           ""
3671

3672
#if defined USE_PNG
3673
                           "<PNG />"
3674
#endif
3675
#if defined USE_JPEG
3676
                           "<JPEG />"
3677
#endif
3678
                           "<SVG />",
3679
                           NULL);
3680
    if (msOWSRequestIsEnabled(map, NULL, "M", "GetCapabilities", MS_TRUE))
2✔
3681
      msWMSPrintRequestCap(nVersion, "Capabilities", script_url_encoded,
2✔
3682
                           "<WMS_XML />", NULL);
3683
    if (msOWSRequestIsEnabled(map, NULL, "M", "GetFeatureInfo", MS_FALSE))
2✔
3684
      msWMSPrintRequestCap(nVersion, "FeatureInfo", script_url_encoded,
2✔
3685
                           "<MIME /><GML.1 />", NULL);
3686
  } else {
3687
    const char *mime_list[20];
3688
    int mime_count = 0;
3689
    int max_mime = 20;
3690
    /* WMS 1.1.0 and later */
3691
    /* Note changes to the request names, their ordering, and to the formats */
3692

3693
    if (msOWSRequestIsEnabled(map, NULL, "M", "GetCapabilities", MS_TRUE)) {
58✔
3694
      if (nVersion >= OWS_1_3_0)
58✔
3695
        msWMSPrintRequestCap(nVersion, "GetCapabilities", script_url_encoded,
29✔
3696
                             "text/xml", NULL);
3697
      else
3698
        msWMSPrintRequestCap(nVersion, "GetCapabilities", script_url_encoded,
29✔
3699
                             "application/vnd.ogc.wms_xml", NULL);
3700
    }
3701

3702
    msGetOutputFormatMimeListWMS(map, mime_list,
58✔
3703
                                 sizeof(mime_list) / sizeof(char *));
3704
    if (msOWSRequestIsEnabled(map, NULL, "M", "GetMap", MS_FALSE))
58✔
3705
      msWMSPrintRequestCap(
57✔
3706
          nVersion, "GetMap", script_url_encoded, mime_list[0], mime_list[1],
3707
          mime_list[2], mime_list[3], mime_list[4], mime_list[5], mime_list[6],
3708
          mime_list[7], mime_list[8], mime_list[9], mime_list[10],
3709
          mime_list[11], mime_list[12], mime_list[13], mime_list[14],
3710
          mime_list[15], mime_list[16], mime_list[17], mime_list[18],
3711
          mime_list[19], NULL);
3712

3713
    const char *format_list = msOWSLookupMetadata(&(map->web.metadata), "M",
58✔
3714
                                                  "getfeatureinfo_formatlist");
3715
    /*feature_info_mime_type deprecated for MapServer 6.0*/
3716
    if (!format_list)
58✔
3717
      format_list = msOWSLookupMetadata(&(map->web.metadata), "MO",
53✔
3718
                                        "feature_info_mime_type");
3719

3720
    if (format_list && strlen(format_list) > 0) {
58✔
3721
      auto tokens = msStringSplit(format_list, ',');
6✔
3722
      for (auto &token : tokens) {
17✔
3723
        msStringTrim(token);
11✔
3724
        /*text plain and gml do not need to be a format and accepted by
3725
         * default*/
3726
        /*can not really validate since the old way of using template
3727
          with wei->header, layer->template ... should be kept*/
3728
        if (!token.empty() && mime_count < max_mime)
11✔
3729
          mime_list[mime_count++] = token.c_str();
11✔
3730
      }
3731
      /*add text/plain and gml */
3732
      if (strcasestr(format_list, "GML") == 0 &&
6✔
3733
          strcasestr(format_list, "application/vnd.ogc.gml") == 0) {
1✔
3734
        if (mime_count < max_mime)
1✔
3735
          mime_list[mime_count++] = "application/vnd.ogc.gml";
1✔
3736
      }
3737
      if (strcasestr(format_list, "text/plain") == 0 &&
6✔
3738
          strcasestr(format_list, "MIME") == 0) {
6✔
3739
        if (mime_count < max_mime)
6✔
3740
          mime_list[mime_count++] = "text/plain";
6✔
3741
        else /*force always this format*/
3742
          mime_list[max_mime - 1] = "text/plain";
×
3743
      }
3744

3745
      if (msOWSRequestIsEnabled(map, NULL, "M", "GetFeatureInfo", MS_FALSE)) {
6✔
3746
        if (mime_count > 0) {
6✔
3747
          if (mime_count < max_mime)
6✔
3748
            mime_list[mime_count] = NULL;
6✔
3749
          msWMSPrintRequestCap(
6✔
3750
              nVersion, "GetFeatureInfo", script_url_encoded, mime_list[0],
3751
              mime_list[1], mime_list[2], mime_list[3], mime_list[4],
3752
              mime_list[5], mime_list[6], mime_list[7], mime_list[8],
3753
              mime_list[9], mime_list[10], mime_list[11], mime_list[12],
3754
              mime_list[13], mime_list[14], mime_list[15], mime_list[16],
3755
              mime_list[17], mime_list[18], mime_list[19], NULL);
3756
        }
3757
        /*if all formats given are invalid go to default*/
3758
        else
3759
          msWMSPrintRequestCap(nVersion, "GetFeatureInfo", script_url_encoded,
×
3760
                               "text/plain", "application/vnd.ogc.gml", NULL);
3761
      }
3762
    } else {
6✔
3763
      if (msOWSRequestIsEnabled(map, NULL, "M", "GetFeatureInfo", MS_FALSE))
52✔
3764
        msWMSPrintRequestCap(nVersion, "GetFeatureInfo", script_url_encoded,
48✔
3765
                             "text/plain", "application/vnd.ogc.gml", NULL);
3766
    }
3767

3768
    if (strcasecmp(sldenabled, "true") == 0) {
58✔
3769
      if (msOWSRequestIsEnabled(map, NULL, "M", "DescribeLayer", MS_FALSE)) {
41✔
3770
        if (nVersion == OWS_1_3_0)
40✔
3771
          msWMSPrintRequestCap(nVersion, "sld:DescribeLayer",
19✔
3772
                               script_url_encoded, "text/xml", NULL);
3773
        else
3774
          msWMSPrintRequestCap(nVersion, "DescribeLayer", script_url_encoded,
21✔
3775
                               "text/xml", NULL);
3776
      }
3777

3778
      msGetOutputFormatMimeListImg(map, mime_list,
41✔
3779
                                   sizeof(mime_list) / sizeof(char *));
3780

3781
      if (nVersion >= OWS_1_1_1) {
41✔
3782
        const auto isGetLegendGraphicEnabled =
3783
            msOWSRequestIsEnabled(map, NULL, "M", "GetLegendGraphic", MS_FALSE);
34✔
3784
        if (nVersion == OWS_1_3_0) {
34✔
3785
          if (isGetLegendGraphicEnabled)
20✔
3786
            msWMSPrintRequestCap(
16✔
3787
                nVersion, "sld:GetLegendGraphic", script_url_encoded,
3788
                mime_list[0], mime_list[1], mime_list[2], mime_list[3],
3789
                mime_list[4], mime_list[5], mime_list[6], mime_list[7],
3790
                mime_list[8], mime_list[9], mime_list[10], mime_list[11],
3791
                mime_list[12], mime_list[13], mime_list[14], mime_list[15],
3792
                mime_list[16], mime_list[17], mime_list[18], mime_list[19],
3793
                NULL);
3794
          if (msOWSRequestIsEnabled(map, NULL, "M", "GetStyles", MS_FALSE))
20✔
3795
            msWMSPrintRequestCap(nVersion, "ms:GetStyles", script_url_encoded,
16✔
3796
                                 "text/xml", NULL);
3797
        } else {
3798
          if (isGetLegendGraphicEnabled)
14✔
3799
            msWMSPrintRequestCap(
14✔
3800
                nVersion, "GetLegendGraphic", script_url_encoded, mime_list[0],
3801
                mime_list[1], mime_list[2], mime_list[3], mime_list[4],
3802
                mime_list[5], mime_list[6], mime_list[7], mime_list[8],
3803
                mime_list[9], mime_list[10], mime_list[11], mime_list[12],
3804
                mime_list[13], mime_list[14], mime_list[15], mime_list[16],
3805
                mime_list[17], mime_list[18], mime_list[19], NULL);
3806
          if (msOWSRequestIsEnabled(map, NULL, "M", "GetStyles", MS_FALSE))
14✔
3807
            msWMSPrintRequestCap(nVersion, "GetStyles", script_url_encoded,
14✔
3808
                                 "text/xml", NULL);
3809
        }
3810
      }
3811
    }
3812
  }
3813

3814
  msIO_printf("  </Request>\n");
60✔
3815

3816
  msIO_printf("  <Exception>\n");
60✔
3817
  if (nVersion < OWS_1_1_0)
60✔
3818
    msIO_printf("    <Format><BLANK /><INIMAGE /><WMS_XML /></Format>\n");
2✔
3819
  else if (nVersion <= OWS_1_1_1) {
58✔
3820
    msIO_printf("    <Format>application/vnd.ogc.se_xml</Format>\n");
29✔
3821
    msIO_printf("    <Format>application/vnd.ogc.se_inimage</Format>\n");
29✔
3822
    msIO_printf("    <Format>application/vnd.ogc.se_blank</Format>\n");
29✔
3823
  } else { /*>=1.3.0*/
3824
    msIO_printf("    <Format>XML</Format>\n");
29✔
3825
    msIO_printf("    <Format>INIMAGE</Format>\n");
29✔
3826
    msIO_printf("    <Format>BLANK</Format>\n");
29✔
3827
  }
3828
  msIO_printf("  </Exception>\n");
60✔
3829

3830
  if (nVersion != OWS_1_3_0) {
60✔
3831
    /* INSPIRE extended capabilities for WMS 1.1.1 */
3832
    if (nVersion == OWS_1_1_1 && msOWSLookupMetadata(&(map->web.metadata), "MO",
31✔
3833
                                                     "inspire_capabilities")) {
3834
      msIO_printf("  <VendorSpecificCapabilities>\n");
6✔
3835
      msOWSPrintInspireCommonExtendedCapabilities(
6✔
3836
          stdout, map, "MO", OWS_WARN, "inspire_vs:ExtendedCapabilities", NULL,
3837
          validated_language.c_str(), OWS_WMS);
3838
      msIO_printf("  </VendorSpecificCapabilities>\n");
6✔
3839
    } else {
3840
      msIO_printf("  <VendorSpecificCapabilities />\n"); /* nothing yet */
25✔
3841
    }
3842
  }
3843

3844
  /* SLD support */
3845
  if (strcasecmp(sldenabled, "true") == 0) {
60✔
3846
    if (nVersion >= OWS_1_3_0)
43✔
3847
      msIO_printf("  <sld:UserDefinedSymbolization SupportSLD=\"1\" "
20✔
3848
                  "UserLayer=\"0\" UserStyle=\"1\" RemoteWFS=\"0\" "
3849
                  "InlineFeature=\"0\" RemoteWCS=\"0\"/>\n");
3850
    else
3851
      msIO_printf("  <UserDefinedSymbolization SupportSLD=\"1\" "
23✔
3852
                  "UserLayer=\"0\" UserStyle=\"1\" RemoteWFS=\"0\"/>\n");
3853
  }
3854

3855
  /* INSPIRE extended capabilities for WMS 1.3.0 */
3856
  if (nVersion >= OWS_1_3_0 &&
89✔
3857
      msOWSLookupMetadata(&(map->web.metadata), "MO", "inspire_capabilities")) {
29✔
3858
    msOWSPrintInspireCommonExtendedCapabilities(
9✔
3859
        stdout, map, "MO", OWS_WARN, "inspire_vs:ExtendedCapabilities", NULL,
3860
        validated_language.c_str(), OWS_WMS);
3861
  }
3862

3863
  /* Top-level layer with map extents and SRS, encloses all map layers */
3864
  /* Output only if at least one layers is enabled. */
3865
  if (ows_request->numlayers == 0) {
60✔
3866
    msIO_fprintf(stdout, "  <!-- WARNING: No WMS layers are enabled. Check "
5✔
3867
                         "wms/ows_enable_request settings. -->\n");
3868
  } else {
3869
    int root_is_queryable = MS_FALSE;
3870

3871
    const char *rootlayer_name =
3872
        msOWSLookupMetadata(&(map->web.metadata), "MO", "rootlayer_name");
55✔
3873

3874
    /* Root layer is queryable if it has a valid name and at list one layer */
3875
    /* is queryable */
3876
    if (!rootlayer_name || strlen(rootlayer_name) > 0) {
55✔
3877
      int j;
3878
      for (j = 0; j < map->numlayers; j++) {
128✔
3879
        layerObj *layer = GET_LAYER(map, j);
103✔
3880
        if (msIsLayerQueryable(layer) &&
134✔
3881
            msIntegerInArray(layer->index, ows_request->enabled_layers,
31✔
3882
                             ows_request->numlayers)) {
3883
          root_is_queryable = MS_TRUE;
3884
          break;
3885
        }
3886
      }
3887
    }
3888
    msIO_printf("  <Layer%s>\n", root_is_queryable ? " queryable=\"1\"" : "");
80✔
3889

3890
    /* Layer Name is optional but title is mandatory. */
3891
    if (map->name && strlen(map->name) > 0 &&
110✔
3892
        (msIsXMLTagValid(map->name) == MS_FALSE || isdigit(map->name[0])))
110✔
3893
      msIO_fprintf(stdout,
×
3894
                   "<!-- WARNING: The layer name '%s' might contain spaces or "
3895
                   "invalid characters or may start with a number. This could "
3896
                   "lead to potential problems. -->\n",
3897
                   map->name);
3898

3899
    if (rootlayer_name) {
55✔
3900
      if (strlen(rootlayer_name) > 0) {
2✔
3901
        msOWSPrintEncodeMetadata(stdout, &(map->web.metadata), "MO",
1✔
3902
                                 "rootlayer_name", OWS_NOERR,
3903
                                 "    <Name>%s</Name>\n", NULL);
3904
      }
3905
    } else {
3906
      msOWSPrintEncodeParam(stdout, "MAP.NAME", map->name, OWS_NOERR,
53✔
3907
                            "    <Name>%s</Name>\n", NULL);
3908
    }
3909

3910
    if (msOWSLookupMetadataWithLanguage(&(map->web.metadata), "MO",
55✔
3911
                                        "rootlayer_title",
3912
                                        validated_language.c_str()))
3913
      msOWSPrintEncodeMetadata2(
26✔
3914
          stdout, &(map->web.metadata), "MO", "rootlayer_title", OWS_WARN,
3915
          "    <Title>%s</Title>\n", map->name, validated_language.c_str());
26✔
3916

3917
    else
3918
      msOWSPrintEncodeMetadata2(stdout, &(map->web.metadata), "MO", "title",
29✔
3919
                                OWS_WARN, "    <Title>%s</Title>\n", map->name,
29✔
3920
                                validated_language.c_str());
3921

3922
    if (msOWSLookupMetadataWithLanguage(&(map->web.metadata), "MO",
55✔
3923
                                        "rootlayer_abstract",
3924
                                        validated_language.c_str()))
3925
      msOWSPrintEncodeMetadata2(stdout, &(map->web.metadata), "MO",
26✔
3926
                                "rootlayer_abstract", OWS_NOERR,
3927
                                "    <Abstract>%s</Abstract>\n", map->name,
26✔
3928
                                validated_language.c_str());
3929
    else
3930
      msOWSPrintEncodeMetadata2(stdout, &(map->web.metadata), "MO", "abstract",
29✔
3931
                                OWS_NOERR, "    <Abstract>%s</Abstract>\n",
3932
                                map->name, validated_language.c_str());
29✔
3933

3934
    const char *pszTmp;
3935
    if (msOWSLookupMetadata(&(map->web.metadata), "MO",
55✔
3936
                            "rootlayer_keywordlist") ||
84✔
3937
        msOWSLookupMetadata(&(map->web.metadata), "MO",
29✔
3938
                            "rootlayer_keywordlist_vocabulary"))
3939
      pszTmp = "rootlayer_keywordlist";
3940
    else
3941
      pszTmp = "keywordlist";
3942
    msWMSPrintKeywordlist(stdout, "    ", pszTmp, &(map->web.metadata), "MO",
55✔
3943
                          nVersion);
3944

3945
    /* According to normative comments in the 1.0.7 DTD, the root layer's SRS
3946
     * tag */
3947
    /* is REQUIRED.  It also suggests that we use an empty SRS element if there
3948
     */
3949
    /* is no common SRS. */
3950
    char *pszMapEPSG;
3951
    msOWSGetEPSGProj(&(map->projection), &(map->web.metadata), "MO", MS_FALSE,
55✔
3952
                     &pszMapEPSG);
3953
    if (nVersion > OWS_1_1_0) {
55✔
3954
      /* starting 1.1.1 SRS are given in individual tags */
3955
      if (nVersion >= OWS_1_3_0) {
48✔
3956
        msOWSPrintEncodeParamList(stdout,
27✔
3957
                                  "(at least one of) "
3958
                                  "MAP.PROJECTION, LAYER.PROJECTION "
3959
                                  "or wms_srs metadata",
3960
                                  pszMapEPSG, OWS_WARN, ' ', NULL, NULL,
3961
                                  "    <CRS>%s</CRS>\n", "");
3962
      } else {
3963
        msOWSPrintEncodeParamList(stdout,
21✔
3964
                                  "(at least one of) "
3965
                                  "MAP.PROJECTION, LAYER.PROJECTION "
3966
                                  "or wms_srs metadata",
3967
                                  pszMapEPSG, OWS_WARN, ' ', NULL, NULL,
3968
                                  "    <SRS>%s</SRS>\n", "");
3969
      }
3970
    } else {
3971
      /* If map has no proj then every layer MUST have one or produce a warning
3972
       */
3973
      msOWSPrintEncodeParam(stdout, "MAP.PROJECTION (or wms_srs metadata)",
7✔
3974
                            pszMapEPSG, OWS_WARN, "    <SRS>%s</SRS>\n", "");
3975
    }
3976
    msFree(pszMapEPSG);
55✔
3977

3978
    if (nVersion >= OWS_1_3_0)
55✔
3979
      msOWSPrintEX_GeographicBoundingBox(stdout, "    ", &(map->extent),
27✔
3980
                                         &(map->projection));
3981
    else
3982
      msOWSPrintLatLonBoundingBox(stdout, "    ", &(map->extent),
28✔
3983
                                  &(map->projection), NULL, OWS_WMS);
3984

3985
    msOWSPrintBoundingBox(stdout, "    ", &(map->extent), &(map->projection),
55✔
3986
                          NULL, &(map->web.metadata), "MO", nVersion);
3987

3988
    /* AuthorityURL support and Identifier support, only available >= WMS 1.1.0
3989
     */
3990
    if (nVersion >= OWS_1_1_0) {
55✔
3991
      msWMSPrintAttribution(stdout, "    ", &(map->web.metadata), "MO");
54✔
3992
      msWMSPrintAuthorityURL(stdout, "    ", &(map->web.metadata), "MO");
54✔
3993
      msWMSPrintIdentifier(stdout, "    ", &(map->web.metadata), "MO");
54✔
3994
    }
3995

3996
    /* MetadataURL */
3997
    if (nVersion >= OWS_1_1_0)
3998
      msOWSPrintURLType(stdout, &(map->web.metadata), "MO", "metadataurl",
54✔
3999
                        OWS_NOERR, NULL, "MetadataURL", " type=\"%s\"", NULL,
4000
                        NULL, ">\n      <Format>%s</Format",
4001
                        "\n      <OnlineResource xmlns:xlink=\""
4002
                        "http://www.w3.org/1999/xlink\" "
4003
                        "xlink:type=\"simple\" xlink:href=\"%s\"/>\n    ",
4004
                        MS_TRUE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
4005
                        NULL, NULL, NULL, NULL, "    ");
4006

4007
    /* DataURL */
4008
    if (nVersion < OWS_1_1_0)
4009
      msOWSPrintEncodeMetadata(stdout, &(map->web.metadata), "MO",
1✔
4010
                               "dataurl_href", OWS_NOERR,
4011
                               "    <DataURL>%s</DataURL>\n", NULL);
4012
    else
4013
      msOWSPrintURLType(stdout, &(map->web.metadata), "MO", "dataurl",
54✔
4014
                        OWS_NOERR, NULL, "DataURL", NULL, NULL, NULL,
4015
                        ">\n      <Format>%s</Format",
4016
                        "\n      <OnlineResource xmlns:xlink=\""
4017
                        "http://www.w3.org/1999/xlink\" "
4018
                        "xlink:type=\"simple\" xlink:href=\"%s\"/>\n    ",
4019
                        MS_FALSE, MS_FALSE, MS_FALSE, MS_TRUE, MS_TRUE, NULL,
4020
                        NULL, NULL, NULL, NULL, "    ");
4021

4022
    if (map->name && strlen(map->name) > 0 &&
110✔
4023
        msOWSLookupMetadata(&(map->web.metadata), "MO",
55✔
4024
                            "inspire_capabilities")) {
4025
      char *pszEncodedName = NULL;
4026
      const char *styleName = NULL;
4027
      char *pszEncodedStyleName = NULL;
4028
      const char *legendURL = NULL;
4029

4030
      pszEncodedName = msEncodeHTMLEntities(map->name);
15✔
4031

4032
      styleName = msOWSLookupMetadata(&(map->web.metadata), "MO", "style_name");
15✔
4033
      if (styleName == NULL)
15✔
4034
        styleName = "default";
4035

4036
      pszEncodedStyleName = msEncodeHTMLEntities(styleName);
15✔
4037

4038
      msIO_fprintf(stdout, "    <Style>\n");
15✔
4039
      msIO_fprintf(stdout, "       <Name>%s</Name>\n", pszEncodedStyleName);
15✔
4040
      msOWSPrintEncodeMetadata2(
15✔
4041
          stdout, &(map->web.metadata), "MO", "style_title", OWS_NOERR,
4042
          "       <Title>%s</Title>\n", styleName, validated_language.c_str());
4043

4044
      legendURL = msOWSLookupMetadata(&(map->web.metadata), "MO",
15✔
4045
                                      "style_legendurl_href");
4046
      if (legendURL) {
15✔
4047
        msOWSPrintURLType(stdout, &(map->web.metadata), "MO", "style_legendurl",
9✔
4048
                          OWS_NOERR, NULL, "LegendURL", NULL, " width=\"%s\"",
4049
                          " height=\"%s\"", ">\n          <Format>%s</Format",
4050
                          "\n          <OnlineResource "
4051
                          "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
4052
                          " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
4053
                          "       ",
4054
                          MS_FALSE, MS_TRUE, MS_TRUE, MS_TRUE, MS_TRUE, NULL,
4055
                          NULL, NULL, NULL, NULL, "       ");
4056
      } else {
4057
        std::vector<int> group_layers;
4058
        group_layers.reserve(map->numlayers);
6✔
4059

4060
        for (int i = 0; i < map->numlayers; i++) {
96✔
4061
          if (msIntegerInArray(GET_LAYER(map, i)->index,
90✔
4062
                               ows_request->enabled_layers,
4063
                               ows_request->numlayers)) {
4064
            group_layers.push_back(i);
84✔
4065
          }
4066
        }
4067

4068
        if (!group_layers.empty()) {
6✔
4069
          int size_x = 0, size_y = 0;
6✔
4070
          if (msLegendCalcSize(map, 1, &size_x, &size_y, group_layers.data(),
6✔
4071
                               static_cast<int>(group_layers.size()), NULL,
4072
                               1) == MS_SUCCESS) {
4073
            const std::string width(std::to_string(size_x));
6✔
4074
            const std::string height(std::to_string(size_y));
6✔
4075

4076
            const char *format_list = msOWSLookupMetadata(
6✔
4077
                &(map->web.metadata), "M", "getlegendgraphic_formatlist");
4078
            char *pszMimetype = NULL;
4079
            if (format_list && strlen(format_list) > 0) {
6✔
4080
              const auto tokens = msStringSplit(format_list, ',');
×
4081
              if (!tokens.empty()) {
×
4082
                /*just grab the first mime type*/
4083
                pszMimetype = msEncodeHTMLEntities(tokens[0].c_str());
×
4084
              }
4085
            } else
×
4086
              pszMimetype = msEncodeHTMLEntities("image/png");
6✔
4087

4088
            std::string legendurl(script_url_encoded);
6✔
4089
            legendurl += "version=";
4090
            char szVersionBuf[OWS_VERSION_MAXLEN];
4091
            legendurl += msOWSGetVersionString(nVersion, szVersionBuf);
6✔
4092
            legendurl += "&amp;service=WMS&amp;request=GetLegendGraphic&amp;";
4093
            if (nVersion >= OWS_1_3_0) {
6✔
4094
              legendurl += "sld_version=1.1.0&amp;layer=";
4095
            } else {
4096
              legendurl += "layer=";
4097
            }
4098
            legendurl += pszEncodedName;
4099
            legendurl += "&amp;format=";
4100
            legendurl += pszMimetype;
4101
            legendurl += "&amp;STYLE=";
4102
            legendurl += pszEncodedStyleName;
4103

4104
            msOWSPrintURLType(stdout, NULL, "O", "ttt", OWS_NOERR, NULL,
6✔
4105
                              "LegendURL", NULL, " width=\"%s\"",
4106
                              " height=\"%s\"",
4107
                              ">\n          <Format>%s</Format",
4108
                              "\n          <OnlineResource "
4109
                              "xmlns:xlink=\"http://www.w3.org/1999/xlink\""
4110
                              " xlink:type=\"simple\" xlink:href=\"%s\"/>\n"
4111
                              "       ",
4112
                              MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE, MS_FALSE,
4113
                              NULL, width.c_str(), height.c_str(), pszMimetype,
4114
                              legendurl.c_str(), "       ");
4115
            msFree(pszMimetype);
6✔
4116
          }
4117
        }
4118
      }
6✔
4119
      msIO_fprintf(stdout, "    </Style>\n");
15✔
4120
      msFree(pszEncodedName);
15✔
4121
      msFree(pszEncodedStyleName);
15✔
4122
    }
4123

4124
    if (nVersion < OWS_1_3_0)
55✔
4125
      msWMSPrintScaleHint("    ", map->web.minscaledenom,
28✔
4126
                          map->web.maxscaledenom, map->resolution);
4127
    else
4128
      msWMSPrintScaleDenominator("    ", map->web.minscaledenom,
27✔
4129
                                 map->web.maxscaledenom);
4130

4131
    /*  */
4132
    /* Dump list of layers organized by groups.  Layers with no group are listed
4133
     */
4134
    /* individually, at the same level as the groups in the layer hierarchy */
4135
    /*  */
4136
    if (map->numlayers) {
55✔
4137

4138
      auto [layerTree, mapNameToNode] =
4139
          msWMSCreateLayerTree(map, validated_language.c_str());
55✔
4140
      (void)mapNameToNode;
4141
      for (const auto &child : layerTree->children) {
171✔
4142
        msWMSPrintLayerNode(map, nVersion, ows_request, child.get(),
116✔
4143
                            script_url_encoded, validated_language.c_str(), 2);
4144
      }
4145
    }
4146

4147
    msIO_printf("  </Layer>\n");
55✔
4148
  }
4149

4150
  msIO_printf("</Capability>\n");
60✔
4151
  if (nVersion >= OWS_1_3_0)
60✔
4152
    msIO_printf("</WMS_Capabilities>\n");
29✔
4153
  else
4154
    msIO_printf("</WMT_MS_Capabilities>\n");
31✔
4155

4156
  free(script_url_encoded);
60✔
4157

4158
  return (MS_SUCCESS);
60✔
4159
}
4160

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

4237
      /* Note msReplaceSubstring() works on the string itself, so we need to
4238
       * make a copy */
4239
      imgext = msStrdup(values[i]);
9✔
4240
      imgext = msReplaceSubstring(imgext, ",", " ");
9✔
4241
      (*translated_values)[*translated_numentries] = imgext;
9✔
4242
      (*translated_names)[*translated_numentries] = msStrdup("imgext");
9✔
4243
      (*translated_numentries)++;
9✔
4244
    }
4245
  }
4246

4247
  return MS_SUCCESS;
9✔
4248
}
4249

4250
/*
4251
** msWMSGetMap()
4252
*/
4253
static int msWMSGetMap(mapObj *map, int nVersion, char **names, char **values,
415✔
4254
                       int numentries, const char *wms_exception_format,
4255
                       owsRequestObj *ows_request) {
4256

4257
  // If we are returning an OpenLayers map there is no need to first generate an
4258
  // image. We can't call msReturnOpenLayersPage directly here as it requires
4259
  // the mapservObj
4260
  if (strcasecmp(map->imagetype, "application/openlayers") == 0) {
415✔
4261
    return MS_SUCCESS;
4262
  }
4263

4264
  imageObj *img;
4265
  int sldrequested = MS_FALSE, sldspatialfilter = MS_FALSE;
4266
  int drawquerymap = MS_FALSE;
4267

4268
  /* __TODO__ msDrawMap() will try to adjust the extent of the map */
4269
  /* to match the width/height image ratio. */
4270
  /* The spec states that this should not happen so that we can deliver */
4271
  /* maps to devices with non-square pixels. */
4272

4273
  /* If there was an SLD in the request, we need to treat it */
4274
  /* differently : some SLD may contain spatial filters requiring */
4275
  /* to do a query. While parsing the SLD and applying it to the */
4276
  /* layer, we added a temporary metadata on the layer */
4277
  /* (tmp_wms_sld_query) for layers with a spatial filter. */
4278

4279
  for (int i = 0; i < numentries; i++) {
5,129✔
4280
    if ((strcasecmp(names[i], "SLD") == 0 && values[i] &&
4,851✔
4281
         strlen(values[i]) > 0) ||
134✔
4282
        (strcasecmp(names[i], "SLD_BODY") == 0 && values[i] &&
4,848✔
4283
         strlen(values[i]) > 0)) {
4284
      sldrequested = MS_TRUE;
4285
      break;
4286
    }
4287
  }
4288
  if (sldrequested) {
412✔
4289
    for (int i = 0; i < map->numlayers; i++) {
609✔
4290
      if (msLookupHashTable(&(GET_LAYER(map, i)->metadata),
475✔
4291
                            "tmp_wms_sld_query")) {
4292
        sldspatialfilter = MS_TRUE;
4293
        break;
4294
      }
4295
    }
4296
  }
4297
  /* If FILTER is passed then we'll render layers as querymap */
4298
  for (int i = 0; i < numentries; i++) {
5,252✔
4299
    if ((strcasecmp(names[i], "FILTER") == 0 && values[i] &&
4,850✔
4300
         strlen(values[i]) > 0)) {
10✔
4301
      drawquerymap = MS_TRUE;
4302
      map->querymap.status = MS_ON;
10✔
4303
      map->querymap.style = MS_SELECTED;
10✔
4304
      break;
10✔
4305
    }
4306
  }
4307

4308
  /* turn off layer if WMS GetMap is not enabled */
4309
  for (int i = 0; i < map->numlayers; i++)
3,048✔
4310
    if (!msIntegerInArray(GET_LAYER(map, i)->index, ows_request->enabled_layers,
2,636✔
4311
                          ows_request->numlayers))
4312
      GET_LAYER(map, i)->status = MS_OFF;
2✔
4313

4314
  if (sldrequested && sldspatialfilter) {
412✔
4315
    /* set the quermap style so that only selected features will be returned */
4316
    map->querymap.status = MS_ON;
×
4317
    map->querymap.style = MS_SELECTED;
×
4318

4319
    img = msPrepareImage(map, MS_TRUE);
×
4320

4321
    /* compute layer scale factors now */
4322
    for (int i = 0; i < map->numlayers; i++) {
×
4323
      if (GET_LAYER(map, i)->sizeunits != MS_PIXELS)
×
4324
        GET_LAYER(map, i)->scalefactor =
×
4325
            (msInchesPerUnit(GET_LAYER(map, i)->sizeunits, 0) /
×
4326
             msInchesPerUnit(map->units, 0)) /
×
4327
            map->cellsize;
×
4328
      else if (GET_LAYER(map, i)->symbolscaledenom > 0 && map->scaledenom > 0)
×
4329
        GET_LAYER(map, i)->scalefactor =
×
4330
            GET_LAYER(map, i)->symbolscaledenom / map->scaledenom;
×
4331
      else
4332
        GET_LAYER(map, i)->scalefactor = 1;
×
4333
    }
4334
    for (int i = 0; i < map->numlayers; i++) {
×
4335
      if (msLookupHashTable(&(GET_LAYER(map, i)->metadata),
×
4336
                            "tmp_wms_sld_query") &&
×
4337
          (GET_LAYER(map, i)->type == MS_LAYER_POINT ||
×
4338
           GET_LAYER(map, i)->type == MS_LAYER_LINE ||
4339
           GET_LAYER(map, i)->type == MS_LAYER_POLYGON ||
×
4340
           GET_LAYER(map, i)->type == MS_LAYER_TILEINDEX))
4341

4342
      {
4343
        /* make sure that there is a resultcache. If not just ignore */
4344
        /* the layer */
4345
        if (GET_LAYER(map, i)->resultcache)
×
4346
          msDrawQueryLayer(map, GET_LAYER(map, i), img);
×
4347
      }
4348

4349
      else
4350
        IGNORE_RET_VAL(msDrawLayer(map, GET_LAYER(map, i), img));
×
4351
    }
4352

4353
  } else {
4354

4355
    /* intercept requests for Mapbox vector tiles */
4356
    if (!strcmp(MS_IMAGE_MIME_TYPE(map->outputformat),
824✔
4357
                "application/vnd.mapbox-vector-tile") ||
408✔
4358
        !strcmp(MS_IMAGE_MIME_TYPE(map->outputformat),
408✔
4359
                "application/x-protobuf")) {
4360
      int status = 0;
4361
      if ((status = msMVTWriteTile(map, MS_TRUE)) != MS_SUCCESS)
5✔
4362
        return MS_FAILURE;
4363
      return MS_SUCCESS;
4364
    }
4365

4366
    img = msDrawMap(map, drawquerymap);
407✔
4367
  }
4368

4369
  /* see if we have tiled = true and a buffer */
4370
  /* if so, clip the image */
4371
  for (int i = 0; i < numentries; i++) {
5,201✔
4372
    if (strcasecmp(names[i], "TILED") == 0 &&
4,795✔
4373
        strcasecmp(values[i], "TRUE") == 0) {
1✔
4374
      hashTableObj *meta = &(map->web.metadata);
1✔
4375
      const char *value;
4376

4377
      if ((value = msLookupHashTable(meta, "tile_map_edge_buffer")) != NULL) {
1✔
4378
        const int map_edge_buffer = atoi(value);
4379
        if (map_edge_buffer > 0) {
1✔
4380
          /* we have to clip the image */
4381

4382
          // TODO: we could probably avoid the use of an intermediate image
4383
          // by playing with the rasterBufferObj's data->rgb.pixels and
4384
          // data->rgb.row_stride values.
4385
          rendererVTableObj *renderer = MS_MAP_RENDERER(map);
1✔
4386
          rasterBufferObj imgBuffer;
4387
          if (renderer->getRasterBufferHandle((imageObj *)img, &imgBuffer) !=
1✔
4388
              MS_SUCCESS) {
4389
            msFreeImage(img);
×
4390
            return MS_FAILURE;
×
4391
          }
4392

4393
          int width = map->width - map_edge_buffer - map_edge_buffer;
1✔
4394
          int height = map->height - map_edge_buffer - map_edge_buffer;
1✔
4395
          imageObj *tmp =
4396
              msImageCreate(width, height, map->outputformat, NULL, NULL,
1✔
4397
                            map->resolution, map->defresolution, NULL);
4398

4399
          if ((MS_FAILURE == renderer->mergeRasterBuffer(
1✔
4400
                                 tmp, &imgBuffer, 1.0, map_edge_buffer,
4401
                                 map_edge_buffer, 0, 0, width, height))) {
4402
            msFreeImage(tmp);
×
4403
            msFreeImage(img);
×
4404
            img = NULL;
4405
          } else {
4406
            msFreeImage(img);
1✔
4407
            img = tmp;
4408
          }
4409
        }
4410
      }
4411
      break;
4412
    }
4413
  }
4414

4415
  if (img == NULL)
407✔
4416
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4417

4418
  /* Set the HTTP Cache-control headers if they are defined
4419
     in the map object */
4420

4421
  const char *http_max_age =
4422
      msOWSLookupMetadata(&(map->web.metadata), "MO", "http_max_age");
407✔
4423
  if (http_max_age) {
407✔
4424
    msIO_setHeader("Cache-Control", "max-age=%s", http_max_age);
2✔
4425
  }
4426

4427
  if (strcasecmp(map->imagetype, "application/openlayers") != 0) {
407✔
4428
    if (!strcmp(MS_IMAGE_MIME_TYPE(map->outputformat), "application/json")) {
814✔
4429
      msIO_setHeader("Content-Type", "application/json; charset=utf-8");
×
4430
    } else {
4431
      msOutputFormatResolveFromImage(map, img);
407✔
4432
      msIO_setHeader("Content-Type", "%s",
407✔
4433
                     MS_IMAGE_MIME_TYPE(map->outputformat));
407✔
4434
    }
4435
    msIO_sendHeaders();
407✔
4436
    if (msSaveImage(map, img, NULL) != MS_SUCCESS) {
407✔
4437
      msFreeImage(img);
×
4438
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4439
    }
4440
  }
4441
  msFreeImage(img);
407✔
4442

4443
  return (MS_SUCCESS);
407✔
4444
}
4445

4446
static int msDumpResult(mapObj *map, int nVersion,
7✔
4447
                        const char *wms_exception_format) {
4448
  int numresults = 0;
4449

4450
  for (int i = 0; i < map->numlayers; i++) {
35✔
4451
    layerObj *lp = (GET_LAYER(map, i));
28✔
4452

4453
    if (lp->status != MS_ON || lp->resultcache == NULL ||
28✔
4454
        lp->resultcache->numresults == 0)
16✔
4455
      continue;
18✔
4456

4457
    /* if(msLayerOpen(lp) != MS_SUCCESS || msLayerGetItems(lp) != MS_SUCCESS)
4458
     return msWMSException(map, nVersion, NULL); */
4459

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

4473
    /* get a list of items that should be excluded in output */
4474
    std::vector<std::string> excitems;
4475
    if ((value = msOWSLookupMetadata(&(lp->metadata), "MO", "exclude_items")) !=
10✔
4476
        NULL)
4477
      excitems = msStringSplit(value, ',');
×
4478

4479
    std::vector<bool> itemvisible(lp->numitems);
10✔
4480
    for (int k = 0; k < lp->numitems; k++) {
175✔
4481
      /* check visibility, included items first... */
4482
      if (incitems.size() == 1 && strcasecmp("all", incitems[0].c_str()) == 0) {
165✔
4483
        itemvisible[k] = true;
4484
      } else {
4485
        for (const auto &incitem : incitems) {
×
4486
          if (strcasecmp(lp->items[k], incitem.c_str()) == 0)
×
4487
            itemvisible[k] = true;
4488
        }
4489
      }
4490

4491
      /* ...and now excluded items */
4492
      for (const auto &excitem : excitems) {
165✔
4493
        if (strcasecmp(lp->items[k], excitem.c_str()) == 0)
×
4494
          itemvisible[k] = false;
4495
      }
4496
    }
4497

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

4501
    for (int j = 0; j < lp->resultcache->numresults; j++) {
32✔
4502
      shapeObj shape;
22✔
4503

4504
      msInitShape(&shape);
22✔
4505
      if (msLayerGetShape(lp, &shape, &(lp->resultcache->results[j])) !=
22✔
4506
          MS_SUCCESS) {
4507
        return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4508
      }
4509

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

4512
      for (int k = 0; k < lp->numitems; k++) {
307✔
4513
        if (itemvisible[k]) {
285✔
4514
          value = msOWSLookupMetadata(
285✔
4515
              &(lp->metadata), "MO",
4516
              (std::string(lp->items[k]) + "_alias").c_str());
285✔
4517
          const char *lineTemplate = "    %s = '%s'\n";
4518
          msIO_printf(lineTemplate, value != NULL ? value : lp->items[k],
285✔
4519
                      shape.values[k]);
285✔
4520
        }
4521
      }
4522

4523
      msFreeShape(&shape);
22✔
4524
      numresults++;
22✔
4525
    }
22✔
4526

4527
    /* msLayerClose(lp); */
4528
  }
10✔
4529

4530
  return numresults;
4531
}
4532

4533
/*
4534
** msWMSFeatureInfo()
4535
*/
4536
static int msWMSFeatureInfo(mapObj *map, int nVersion, char **names,
58✔
4537
                            char **values, int numentries,
4538
                            const char *wms_exception_format,
4539
                            owsRequestObj *ows_request) {
4540
  int feature_count = 1, numlayers_found = 0;
4541
  pointObj point = {-1.0, -1.0, -1.0, -1.0};
4542
  const char *info_format = "MIME";
4543
  int query_layer = 0;
4544
  const char *format_list = NULL;
4545
  int valid_format = MS_FALSE;
4546
  int format_found = MS_FALSE;
4547
  int use_bbox = MS_FALSE;
4548
  int wms_layer = MS_FALSE;
4549
  const char *wms_connection = NULL;
4550
  int numOWSLayers = 0;
4551

4552
  auto [layerTree, mapNameToNode] = msWMSCreateLayerTree(map, nullptr);
58✔
4553
  (void)layerTree;
4554

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

4560
      wmslayers = msStringSplit(values[i], ',');
58✔
4561
      if (wmslayers.empty()) {
58✔
4562
        return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4563
      }
4564

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

4583
  std::map<std::string, std::vector<std::string>> mapLayerNameToStyleNames;
4584

4585
  if (styles && strlen(styles) > 0) {
58✔
4586
    int n = 0;
×
4587
    char **tokens = msStringSplitComplex(styles, ",", &n, MS_ALLOWEMPTYTOKENS);
×
4588

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

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

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

4645
              return msWMSException(map, nVersion, "StyleNotDefined",
×
4646
                                    wms_exception_format);
4647
            }
4648
            msFree(pszEncodedStyleName);
×
4649

4650
            mapLayerNameToStyleNames[map->name].push_back(styleName);
×
4651
          }
4652
        }
4653
      }
4654
    }
4655
    msFreeCharArray(tokens, n);
×
4656
  }
4657

4658
  std::map<int, std::vector<std::string>> mapLayerIndexToStyleNames;
4659

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

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

4674
      for (int j = 0; j < map->numlayers; j++) {
303✔
4675
        /* Force all layers OFF by default */
4676
        GET_LAYER(map, j)->status = MS_OFF;
245✔
4677
      }
4678

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

4695
            mapLayerIndexToStyleNames[j] =
38✔
4696
                mapLayerNameToStyleNames[rootlayer_name];
76✔
4697

4698
            numlayers_found++;
38✔
4699
            layer->status = MS_ON;
38✔
4700
          }
4701
        }
4702
      }
4703

4704
      for (const auto &wmslayer : querylayers) {
117✔
4705
        auto iter = mapNameToNode.find(msStringToLower(wmslayer));
59✔
4706
        if (iter != mapNameToNode.end()) {
59✔
4707
          const auto layerIndices = iter->second->collectLayerIndices();
59✔
4708
          for (int j : layerIndices) {
179✔
4709
            layerObj *layer = GET_LAYER(map, j);
120✔
4710
            if (msIsLayerQueryable(layer) &&
219✔
4711
                msIntegerInArray(layer->index, ows_request->enabled_layers,
99✔
4712
                                 ows_request->numlayers)) {
4713
              if (layer->connectiontype == MS_WMS) {
99✔
4714
                wms_layer = MS_TRUE;
4715
                wms_connection = layer->connection;
8✔
4716
              }
4717

4718
              mapLayerIndexToStyleNames[j] = mapLayerNameToStyleNames[wmslayer];
99✔
4719

4720
              numlayers_found++;
99✔
4721
              layer->status = MS_ON;
99✔
4722
            }
4723
          }
4724
        }
59✔
4725
      }
4726

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

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

4758
  if (numlayers_found == 0) {
58✔
4759
    if (query_layer) {
1✔
4760
      msSetErrorWithStatus(
1✔
4761
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4762
          "Layer(s) specified in QUERY_LAYERS parameter is not offered "
4763
          "by the service instance.",
4764
          "msWMSFeatureInfo()");
4765
      return msWMSException(map, nVersion, "LayerNotDefined",
1✔
4766
                            wms_exception_format);
4767
    } else {
4768
      msSetErrorWithStatus(
×
4769
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4770
          "Required QUERY_LAYERS parameter missing for getFeatureInfo.",
4771
          "msWMSFeatureInfo()");
4772
      return msWMSException(map, nVersion, "LayerNotDefined",
×
4773
                            wms_exception_format);
4774
    }
4775
  }
4776

4777
  /*make sure to initialize the map scale so that layers that are scale
4778
    dependent are respected for the query*/
4779
  msCalculateScale(map->extent, map->units, map->width, map->height,
57✔
4780
                   map->resolution, &map->scaledenom);
4781

4782
  /* -------------------------------------------------------------------- */
4783
  /*      check if all layers selected are queryable. If not send an      */
4784
  /*      exception.                                                      */
4785
  /* -------------------------------------------------------------------- */
4786

4787
  /* If a layer of type WMS was found... all layers have to be of that type and
4788
   * with the same connection */
4789
  for (int i = 0; i < map->numlayers; i++) {
296✔
4790
    if (GET_LAYER(map, i)->status == MS_ON) {
239✔
4791
      if (wms_layer == MS_TRUE) {
99✔
4792
        if ((GET_LAYER(map, i)->connectiontype != MS_WMS) ||
8✔
4793
            (strcasecmp(wms_connection, GET_LAYER(map, i)->connection) != 0)) {
8✔
4794
          msSetErrorWithStatus(
×
4795
              MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4796
              "Requested WMS layer(s) are not queryable: type or "
4797
              "connection differ",
4798
              "msWMSFeatureInfo()");
4799
          return msWMSException(map, nVersion, "LayerNotQueryable",
×
4800
                                wms_exception_format);
4801
        }
4802
        ++numOWSLayers;
8✔
4803
      }
4804
    }
4805
  }
4806

4807
  /* It's a valid Cascading WMS GetFeatureInfo request */
4808
  if (wms_layer)
57✔
4809
    return msWMSLayerExecuteRequest(map, numOWSLayers, point.x, point.y,
8✔
4810
                                    feature_count, info_format,
4811
                                    WMS_GETFEATUREINFO);
4812
  if (use_bbox == MS_FALSE) {
49✔
4813

4814
    if (point.x == -1.0 || point.y == -1.0) {
49✔
4815
      if (nVersion >= OWS_1_3_0)
×
4816
        msSetErrorWithStatus(
×
4817
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4818
            "Required I/J parameters missing for getFeatureInfo.",
4819
            "msWMSFeatureInfo()");
4820
      else
4821
        msSetErrorWithStatus(
×
4822
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
4823
            "Required X/Y parameters missing for getFeatureInfo.",
4824
            "msWMSFeatureInfo()");
4825
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4826
    }
4827

4828
    /*wms1.3.0: check if the points are valid*/
4829
    if (nVersion >= OWS_1_3_0) {
49✔
4830
      if (point.x > map->width || point.y > map->height) {
28✔
4831
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
4832
                             "Invalid I/J values", "msWMSFeatureInfo()");
4833
        return msWMSException(map, nVersion, "InvalidPoint",
×
4834
                              wms_exception_format);
4835
      }
4836
    }
4837
  }
4838

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

4865
  /*last case: if the info_format is not part of the request, it defaults to
4866
   * MIME*/
4867
  if (!valid_format && format_found == MS_FALSE) {
49✔
4868
    bMimeResponse = true, valid_format = MS_TRUE;
4869
  }
4870

4871
  if (!valid_format) {
49✔
4872
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
4873
                         "Unsupported INFO_FORMAT value (%s).",
4874
                         "msWMSFeatureInfo()", info_format);
4875
    if (nVersion >= OWS_1_3_0)
×
4876
      return msWMSException(map, nVersion, "InvalidFormat",
×
4877
                            wms_exception_format);
4878
    else
4879
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4880
  }
4881

4882
  if (use_bbox == MS_FALSE) {
49✔
4883

4884
    /* Perform the actual query */
4885
    const double cellx =
49✔
4886
        MS_CELLSIZE(map->extent.minx, map->extent.maxx,
49✔
4887
                    map->width); /* note: don't adjust extent, WMS assumes
4888
                                    incoming extent is correct */
4889
    const double celly =
49✔
4890
        MS_CELLSIZE(map->extent.miny, map->extent.maxy, map->height);
49✔
4891

4892
    point.x = MS_IMAGE2MAP_X(point.x, map->extent.minx, cellx);
49✔
4893
    point.y = MS_IMAGE2MAP_Y(point.y, map->extent.maxy, celly);
49✔
4894

4895
    /* WMS 1.3.0 states that feature_count is *per layer*.
4896
     * Its value is a positive integer, if omitted then the default is 1
4897
     */
4898
    if (feature_count < 1)
49✔
4899
      feature_count = 1;
4900

4901
    map->query.type = MS_QUERY_BY_POINT;
49✔
4902
    map->query.mode =
49✔
4903
        (feature_count == 1 ? MS_QUERY_SINGLE : MS_QUERY_MULTIPLE);
49✔
4904
    map->query.layer = -1;
49✔
4905
    map->query.point = point;
49✔
4906
    map->query.buffer = 0;
49✔
4907
    map->query.maxresults = feature_count;
49✔
4908
    map->query.getFeatureInfo->mapLayerIndexToStyleNames =
49✔
4909
        std::move(mapLayerIndexToStyleNames);
4910

4911
    if (msQueryByPoint(map) != MS_SUCCESS)
49✔
4912
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4913

4914
  } else { /* use_bbox == MS_TRUE */
4915
    map->query.type = MS_QUERY_BY_RECT;
×
4916
    map->query.mode = MS_QUERY_MULTIPLE;
×
4917
    map->query.layer = -1;
×
4918
    map->query.rect = map->extent;
×
4919
    map->query.buffer = 0;
×
4920
    map->query.maxresults = feature_count;
×
4921
    if (msQueryByRect(map) != MS_SUCCESS)
×
4922
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4923
  }
4924

4925
  /* Generate response */
4926
  if (bMimeResponse) {
49✔
4927

4928
    /* MIME response... we're free to use any valid MIME type */
4929
    int numresults = 0;
4930

4931
    msIO_setHeader("Content-Type", "text/plain; charset=UTF-8");
7✔
4932
    msIO_sendHeaders();
7✔
4933
    msIO_printf("GetFeatureInfo results:\n");
7✔
4934

4935
    numresults = msDumpResult(map, nVersion, wms_exception_format);
7✔
4936

4937
    if (numresults == 0)
7✔
4938
      msIO_printf("\n  Search returned no results.\n");
1✔
4939

4940
  } else if (bGMLResponse) {
42✔
4941

4942
    if (nVersion == OWS_1_0_0)
33✔
4943
      msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
×
4944
    else /* 1.1.0 and later */
4945
      msIO_setHeader("Content-Type", "application/vnd.ogc.gml; charset=UTF-8");
33✔
4946
    msIO_sendHeaders();
33✔
4947
    msGMLWriteQuery(map, NULL, "MGO"); /* default is stdout */
33✔
4948

4949
  } else {
4950
    mapservObj *msObj;
4951

4952
    char **translated_names, **translated_values;
4953
    int translated_numentries;
4954
    msObj = msAllocMapServObj();
9✔
4955

4956
    /* Translate some vars from WMS to mapserv */
4957
    msTranslateWMS2Mapserv((const char **)names, (const char **)values,
9✔
4958
                           numentries, &translated_names, &translated_values,
4959
                           &translated_numentries);
4960

4961
    msObj->map = map;
9✔
4962
    msFreeCharArray(msObj->request->ParamNames, msObj->request->NumParams);
9✔
4963
    msFreeCharArray(msObj->request->ParamValues, msObj->request->NumParams);
9✔
4964
    msObj->request->ParamNames = translated_names;
9✔
4965
    msObj->request->ParamValues = translated_values;
9✔
4966
    msObj->Mode = QUERY;
9✔
4967
    msObj->request->NumParams = translated_numentries;
9✔
4968
    msObj->mappnt.x = point.x;
9✔
4969
    msObj->mappnt.y = point.y;
9✔
4970

4971
    bool hasResults = false;
4972
    for (int i = 0; i < map->numlayers; i++) {
13✔
4973
      layerObj *lp = (GET_LAYER(map, i));
11✔
4974

4975
      if (lp->status == MS_ON && lp->resultcache &&
11✔
4976
          lp->resultcache->numresults != 0) {
8✔
4977
        hasResults = true;
4978
        break;
4979
      }
4980
    }
4981

4982
    if (!hasResults && msObj->map->web.empty) {
9✔
4983
      if (msReturnURL(msObj, msObj->map->web.empty, BROWSE) != MS_SUCCESS)
1✔
4984
        return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4985
    } else if (msReturnTemplateQuery(msObj, (char *)info_format, NULL) !=
8✔
4986
               MS_SUCCESS)
4987
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
4988

4989
    /* We don't want to free the map since it */
4990
    /* belongs to the caller, set it to NULL before freeing the mapservObj */
4991
    msObj->map = NULL;
9✔
4992

4993
    msFreeMapServObj(msObj);
9✔
4994
  }
4995

4996
  return (MS_SUCCESS);
4997
}
58✔
4998

4999
/*
5000
** msWMSDescribeLayer()
5001
*/
5002
static int msWMSDescribeLayer(mapObj *map, int nVersion, char **names,
19✔
5003
                              char **values, int numentries,
5004
                              const char *wms_exception_format,
5005
                              owsRequestObj *ows_request) {
5006
  std::vector<std::string> wmslayers;
5007
  const char *version = NULL;
5008
  const char *sld_version = NULL;
5009

5010
  for (int i = 0; i < numentries; i++) {
130✔
5011
    if (strcasecmp(names[i], "LAYERS") == 0) {
111✔
5012
      wmslayers = msStringSplit(values[i], ',');
19✔
5013
    }
5014
    if (strcasecmp(names[i], "VERSION") == 0) {
111✔
5015
      version = values[i];
19✔
5016
    }
5017
    if (strcasecmp(names[i], "SLD_VERSION") == 0) {
111✔
5018
      sld_version = values[i];
16✔
5019
    }
5020
  }
5021

5022
  if (nVersion >= OWS_1_3_0 && sld_version == NULL) {
19✔
5023
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5024
                         "Missing required parameter SLD_VERSION",
5025
                         "DescribeLayer()");
5026
    return msWMSException(map, nVersion, "MissingParameterValue",
×
5027
                          wms_exception_format);
5028
  }
5029
  if (nVersion >= OWS_1_3_0 && strcasecmp(sld_version, "1.1.0") != 0) {
19✔
5030
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5031
                         "SLD_VERSION must be 1.1.0", "DescribeLayer()");
5032
    return msWMSException(map, nVersion, "InvalidParameterValue",
×
5033
                          wms_exception_format);
5034
  }
5035

5036
  msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
19✔
5037
  msIO_sendHeaders();
19✔
5038

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

5041
  {
5042
    char *schemalocation = msEncodeHTMLEntities(msOWSGetSchemasLocation(map));
19✔
5043
    if (nVersion < OWS_1_3_0) {
19✔
5044

5045
      msIO_printf("<!DOCTYPE WMS_DescribeLayerResponse SYSTEM "
3✔
5046
                  "\"%s/wms/1.1.1/WMS_DescribeLayerResponse.dtd\">\n",
5047
                  schemalocation);
5048

5049
      msIO_printf("<WMS_DescribeLayerResponse version=\"%s\" >\n", version);
3✔
5050
    } else {
5051
      msIO_printf("<DescribeLayerResponse xmlns=\"http://www.opengis.net/sld\" "
16✔
5052
                  "xmlns:ows=\"http://www.opengis.net/ows\" "
5053
                  "xmlns:se=\"http://www.opengis.net/se\" "
5054
                  "xmlns:wfs=\"http://www.opengis.net/wfs\" "
5055
                  "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
5056
                  "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
5057
                  "xsi:schemaLocation=\"http://www.opengis.net/sld "
5058
                  "%s/sld/1.1.0/DescribeLayer.xsd\">\n",
5059
                  schemalocation);
5060
      msIO_printf("<Version>%s</Version>\n", sld_version);
16✔
5061
    }
5062
    free(schemalocation);
19✔
5063
  }
5064

5065
  /* check if map-level metadata wfs(wcs)_onlineresource is available */
5066
  const char *pszOnlineResMapWFS =
5067
      msOWSLookupMetadata(&(map->web.metadata), "FO", "onlineresource");
19✔
5068
  if (pszOnlineResMapWFS && strlen(pszOnlineResMapWFS) == 0)
19✔
5069
    pszOnlineResMapWFS = NULL;
5070

5071
  const char *pszOnlineResMapWCS =
5072
      msOWSLookupMetadata(&(map->web.metadata), "CO", "onlineresource");
19✔
5073
  if (pszOnlineResMapWCS && strlen(pszOnlineResMapWCS) == 0)
19✔
5074
    pszOnlineResMapWCS = NULL;
5075

5076
  auto [layerTree, mapNameToNode] = msWMSCreateLayerTree(map, nullptr);
19✔
5077
  (void)layerTree;
5078

5079
  for (const auto &wmslayer : wmslayers) {
38✔
5080
    auto iter = mapNameToNode.find(msStringToLower(wmslayer));
19✔
5081
    if (iter != mapNameToNode.end()) {
19✔
5082
      const auto layerIndices = iter->second->collectLayerIndices();
19✔
5083
      for (int j : layerIndices) {
69✔
5084
        layerObj *lp = GET_LAYER(map, j);
50✔
5085
        if (msIntegerInArray(lp->index, ows_request->enabled_layers,
50✔
5086
                             ows_request->numlayers)) {
5087
          /* Look for a WFS onlineresouce at the layer level and then at
5088
           * the map level.
5089
           */
5090
          const char *pszOnlineResLyrWFS =
5091
              msOWSLookupMetadata(&(lp->metadata), "FO", "onlineresource");
50✔
5092
          const char *pszOnlineResLyrWCS =
5093
              msOWSLookupMetadata(&(lp->metadata), "CO", "onlineresource");
50✔
5094
          if (pszOnlineResLyrWFS == NULL || strlen(pszOnlineResLyrWFS) == 0)
50✔
5095
            pszOnlineResLyrWFS = pszOnlineResMapWFS;
5096

5097
          if (pszOnlineResLyrWCS == NULL || strlen(pszOnlineResLyrWCS) == 0)
50✔
5098
            pszOnlineResLyrWCS = pszOnlineResMapWCS;
5099

5100
          if (pszOnlineResLyrWFS &&
50✔
5101
              (lp->type == MS_LAYER_POINT || lp->type == MS_LAYER_LINE ||
1✔
5102
               lp->type == MS_LAYER_POLYGON)) {
5103
            char *pszOnlineResEncoded =
5104
                msEncodeHTMLEntities(pszOnlineResLyrWFS);
1✔
5105
            char *pszLayerName = msEncodeHTMLEntities(lp->name);
1✔
5106

5107
            if (nVersion < OWS_1_3_0) {
1✔
5108
              msIO_printf("<LayerDescription name=\"%s\" wfs=\"%s\" "
1✔
5109
                          "owsType=\"WFS\" owsURL=\"%s\">\n",
5110
                          pszLayerName, pszOnlineResEncoded,
5111
                          pszOnlineResEncoded);
5112
              msIO_printf("<Query typeName=\"%s\" />\n", pszLayerName);
1✔
5113
              msIO_printf("</LayerDescription>\n");
1✔
5114
            } else { /*wms 1.3.0*/
NEW
5115
              msIO_printf("  <LayerDescription>\n");
×
UNCOV
5116
              msIO_printf("    <owsType>wfs</owsType>\n");
×
NEW
5117
              msIO_printf("    <se:OnlineResource xlink:type=\"simple\" "
×
5118
                          "xlink:href=\"%s\"/>\n",
5119
                          pszOnlineResEncoded);
NEW
5120
              msIO_printf("    <TypeName>\n");
×
NEW
5121
              msIO_printf("      <se:FeatureTypeName>%s</se:FeatureTypeName>\n",
×
5122
                          pszLayerName);
NEW
5123
              msIO_printf("    </TypeName>\n");
×
NEW
5124
              msIO_printf("  </LayerDescription>\n");
×
5125
            }
5126

5127
            msFree(pszOnlineResEncoded);
1✔
5128
            msFree(pszLayerName);
1✔
5129
          } else if (pszOnlineResLyrWCS && lp->type == MS_LAYER_RASTER &&
50✔
NEW
5130
                     lp->connectiontype != MS_WMS) {
×
5131
            char *pszOnlineResEncoded =
NEW
5132
                msEncodeHTMLEntities(pszOnlineResLyrWCS);
×
NEW
5133
            char *pszLayerName = msEncodeHTMLEntities(lp->name);
×
5134

NEW
5135
            if (nVersion < OWS_1_3_0) {
×
NEW
5136
              msIO_printf("<LayerDescription name=\"%s\"  owsType=\"WCS\" "
×
5137
                          "owsURL=\"%s\">\n",
5138
                          pszLayerName, pszOnlineResEncoded);
NEW
5139
              msIO_printf("<Query typeName=\"%s\" />\n", pszLayerName);
×
NEW
5140
              msIO_printf("</LayerDescription>\n");
×
5141
            } else {
NEW
5142
              msIO_printf("  <LayerDescription>\n");
×
NEW
5143
              msIO_printf("    <owsType>wcs</owsType>\n");
×
NEW
5144
              msIO_printf("    <se:OnlineResource xlink:type=\"simple\" "
×
5145
                          "xlink:href=\"%s\"/>\n",
5146
                          pszOnlineResEncoded);
NEW
5147
              msIO_printf("    <TypeName>\n");
×
UNCOV
5148
              msIO_printf(
×
5149
                  "      <se:CoverageTypeName>%s</se:CoverageTypeName>\n",
5150
                  pszLayerName);
NEW
5151
              msIO_printf("    </TypeName>\n");
×
NEW
5152
              msIO_printf("  </LayerDescription>\n");
×
5153
            }
NEW
5154
            msFree(pszOnlineResEncoded);
×
NEW
5155
            msFree(pszLayerName);
×
NEW
5156
          } else {
×
5157
            char *pszLayerName = msEncodeHTMLEntities(lp->name);
49✔
5158

5159
            if (nVersion < OWS_1_3_0)
49✔
5160
              msIO_printf("<LayerDescription name=\"%s\"></LayerDescription>\n",
2✔
5161
                          pszLayerName);
5162
            else { /*wms 1.3.0*/
5163
              msIO_printf("  <LayerDescription>\n");
47✔
5164
              /*need to have a owstype for the DescribeLayer to be valid*/
5165
              if (lp->type == MS_LAYER_RASTER && lp->connectiontype != MS_WMS)
47✔
NEW
5166
                msIO_printf("    <owsType>wcs</owsType>\n");
×
5167
              else
5168
                msIO_printf("    <owsType>wfs</owsType>\n");
47✔
5169

5170
              msIO_printf("    <se:OnlineResource xlink:type=\"simple\"/>\n");
47✔
5171
              msIO_printf("    <TypeName>\n");
47✔
5172
              if (lp->type == MS_LAYER_RASTER && lp->connectiontype != MS_WMS)
47✔
NEW
5173
                msIO_printf(
×
5174
                    "      <se:CoverageTypeName>%s</se:CoverageTypeName>\n",
5175
                    pszLayerName);
5176
              else
5177
                msIO_printf(
47✔
5178
                    "      <se:FeatureTypeName>%s</se:FeatureTypeName>\n",
5179
                    pszLayerName);
5180
              msIO_printf("    </TypeName>\n");
47✔
5181
              msIO_printf("  </LayerDescription>\n");
47✔
5182
            }
5183

5184
            msFree(pszLayerName);
49✔
5185
          }
5186
        }
5187
      }
5188
    }
19✔
5189
  }
5190

5191
  if (nVersion < OWS_1_3_0)
19✔
5192
    msIO_printf("</WMS_DescribeLayerResponse>\n");
3✔
5193
  else
5194
    msIO_printf("</DescribeLayerResponse>\n");
16✔
5195

5196
  return (MS_SUCCESS);
5197
}
19✔
5198

5199
/*
5200
** msWMSGetLegendGraphic()
5201
*/
5202
static int msWMSLegendGraphic(mapObj *map, int nVersion, char **names,
46✔
5203
                              char **values, int numentries,
5204
                              const char *wms_exception_format,
5205
                              owsRequestObj *ows_request,
5206
                              map_hittest *hittest) {
5207
  const char *pszLayer = NULL;
5208
  const char *pszFormat = NULL;
5209
  const char *psRule = NULL;
5210
  const char *psScale = NULL;
5211
  int iLayerIndex = -1;
5212
  outputFormatObj *psFormat = NULL;
5213
  imageObj *img = NULL;
5214
  int nWidth = -1, nHeight = -1;
5215
  const char *pszStyle = NULL;
5216
  const char *sld_version = NULL;
5217
  int wms_layer = MS_FALSE;
5218
  const char *sldenabled = NULL;
5219
  const char *format_list = NULL;
5220
  int nLayers = 0;
5221

5222
  if (!hittest) {
46✔
5223
    /* we can skip a lot of testing if we already have a hittest, as it has
5224
     * already been done in the hittesting phase */
5225

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

5228
    if (sldenabled == NULL)
34✔
5229
      sldenabled = "true";
5230

5231
    for (int i = 0; i < numentries; i++) {
283✔
5232
      if (strcasecmp(names[i], "LAYER") == 0) {
249✔
5233
        pszLayer = values[i];
34✔
5234
      } else if (strcasecmp(names[i], "WIDTH") == 0)
215✔
5235
        nWidth = atoi(values[i]);
×
5236
      else if (strcasecmp(names[i], "HEIGHT") == 0)
215✔
5237
        nHeight = atoi(values[i]);
×
5238
      else if (strcasecmp(names[i], "FORMAT") == 0)
215✔
5239
        pszFormat = values[i];
34✔
5240
      else if (strcasecmp(names[i], "SCALE") == 0)
181✔
5241
        psScale = values[i];
×
5242

5243
      /* -------------------------------------------------------------------- */
5244
      /*      SLD support :                                                   */
5245
      /*        - check if the SLD parameter is there. it is supposed to      */
5246
      /*      refer a valid URL containing an SLD document.                   */
5247
      /*        - check the SLD_BODY parameter that should contain the SLD    */
5248
      /*      xml string.                                                     */
5249
      /* -------------------------------------------------------------------- */
5250
      else if (strcasecmp(names[i], "SLD") == 0 && values[i] &&
181✔
5251
               strlen(values[i]) > 0 && strcasecmp(sldenabled, "true") == 0)
×
5252
        msSLDApplySLDURL(map, values[i], -1, NULL, NULL);
×
5253
      else if (strcasecmp(names[i], "SLD_BODY") == 0 && values[i] &&
181✔
5254
               strlen(values[i]) > 0 && strcasecmp(sldenabled, "true") == 0)
3✔
5255
        msSLDApplySLD(map, values[i], -1, NULL, NULL);
1✔
5256
      else if (strcasecmp(names[i], "RULE") == 0)
180✔
5257
        psRule = values[i];
×
5258
      else if (strcasecmp(names[i], "STYLE") == 0)
180✔
5259
        pszStyle = values[i];
11✔
5260

5261
      /* -------------------------------------------------------------------- */
5262
      /*      SLD support:                                                    */
5263
      /*        - because the request parameter "sld_version" is required in  */
5264
      /*          in WMS 1.3.0, it will be set regardless of OGR support.     */
5265
      /* -------------------------------------------------------------------- */
5266
      else if (strcasecmp(names[i], "SLD_VERSION") == 0)
169✔
5267
        sld_version = values[i];
17✔
5268
    }
5269

5270
    if (!pszLayer) {
34✔
5271
      msSetErrorWithStatus(
×
5272
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5273
          "Mandatory LAYER parameter missing in GetLegendGraphic request.",
5274
          "msWMSGetLegendGraphic()");
5275
      return msWMSException(map, nVersion, "LayerNotDefined",
×
5276
                            wms_exception_format);
4✔
5277
    }
5278
    if (!pszFormat) {
34✔
5279
      msSetErrorWithStatus(
×
5280
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5281
          "Mandatory FORMAT parameter missing in GetLegendGraphic request.",
5282
          "msWMSGetLegendGraphic()");
5283
      return msWMSException(map, nVersion, "InvalidFormat",
×
5284
                            wms_exception_format);
5285
    }
5286

5287
    if (nVersion >= OWS_1_3_0 && sld_version == NULL) {
34✔
5288
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5289
                           "Missing required parameter SLD_VERSION",
5290
                           "GetLegendGraphic()");
5291
      return msWMSException(map, nVersion, "MissingParameterValue",
×
5292
                            wms_exception_format);
5293
    }
5294
    if (nVersion >= OWS_1_3_0 && strcasecmp(sld_version, "1.1.0") != 0) {
34✔
5295
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5296
                           "SLD_VERSION must be 1.1.0", "GetLegendGraphic()");
5297
      return msWMSException(map, nVersion, "InvalidParameterValue",
×
5298
                            wms_exception_format);
5299
    }
5300

5301
    auto [layerTree, mapNameToNode] = msWMSCreateLayerTree(map, nullptr);
34✔
5302
    (void)layerTree;
5303

5304
    for (int i = 0; i < map->numlayers; ++i) {
279✔
5305
      layerObj *lp = GET_LAYER(map, i);
245✔
5306
      lp->status = MS_OFF;
245✔
5307
    }
5308

5309
    auto iter = mapNameToNode.find(msStringToLower(pszLayer));
34✔
5310
    if (iter != mapNameToNode.end()) {
34✔
5311
      const auto layerIndices = iter->second->collectLayerIndices();
34✔
5312
      for (int i : layerIndices) {
100✔
5313
        layerObj *lp = GET_LAYER(map, i);
66✔
5314
        if (msIntegerInArray(lp->index, ows_request->enabled_layers,
66✔
5315
                             ows_request->numlayers)) {
5316
          nLayers++;
66✔
5317
          lp->status = MS_ON;
66✔
5318
          iLayerIndex = i;
5319
          if (lp->connectiontype == MS_WMS) {
66✔
5320
            /* we do not cascade a wms layer if it contains at least
5321
             * one class with the property name set */
5322
            wms_layer = MS_TRUE;
5323
            for (int j = 0; j < lp->numclasses; j++) {
4✔
NEW
5324
              if (lp->_class[j]->name != NULL &&
×
NEW
5325
                  strlen(lp->_class[j]->name) > 0) {
×
5326
                wms_layer = MS_FALSE;
5327
                break;
5328
              }
5329
            }
5330
          }
5331
        }
5332
      }
5333
    }
34✔
5334

5335
    if (nLayers == 0) {
34✔
5336
      msSetErrorWithStatus(
×
5337
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5338
          "Invalid layer given in the LAYER parameter. A layer might be disabled for \
5339
this request. Check wms/ows_enable_request settings.",
5340
          "msWMSGetLegendGraphic()");
5341
      return msWMSException(map, nVersion, "LayerNotDefined",
×
5342
                            wms_exception_format);
5343
    }
5344

5345
    /* if SCALE was provided in request, calculate an extent and use a default
5346
     * width and height */
5347
    if (psScale != NULL) {
34✔
5348
      double scale, cellsize;
5349

5350
      scale = atof(psScale);
5351
      map->width = 600;
×
5352
      map->height = 600;
×
5353

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

5356
      map->extent.maxx = cellsize * map->width / 2.0;
×
5357
      map->extent.maxy = cellsize * map->height / 2.0;
×
5358
      map->extent.minx = -map->extent.maxx;
×
5359
      map->extent.miny = -map->extent.maxy;
×
5360
    }
5361

5362
    /* It's a valid Cascading WMS GetLegendGraphic request */
5363
    if (wms_layer)
34✔
5364
      return msWMSLayerExecuteRequest(map, 1, 0, 0, 0, NULL,
4✔
5365
                                      WMS_GETLEGENDGRAPHIC);
5366

5367
    /*if STYLE is set, check if it is a valid style (valid = at least one
5368
    of the classes have a the group value equals to the style */
5369
    /*style is only validated when there is only one layer #3411*/
5370
    if (nLayers == 1 && pszStyle && strlen(pszStyle) > 0 &&
30✔
5371
        strcasecmp(pszStyle, "default") != 0) {
7✔
5372
      bool found = false;
5373
      for (int i = 0; i < GET_LAYER(map, iLayerIndex)->numclasses; i++) {
11✔
5374
        if (GET_LAYER(map, iLayerIndex)->_class[i]->group &&
11✔
5375
            strcasecmp(GET_LAYER(map, iLayerIndex)->_class[i]->group,
11✔
5376
                       pszStyle) == 0) {
5377
          found = true;
5378
          break;
5379
        }
5380
      }
5381

5382
      if (!found) {
7✔
5383
        msSetErrorWithStatus(
×
5384
            MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5385
            "style used in the STYLE parameter is not defined on the layer.",
5386
            "msWMSGetLegendGraphic()");
5387
        return msWMSException(map, nVersion, "StyleNotDefined",
×
5388
                              wms_exception_format);
5389
      } else {
5390
        msFree(GET_LAYER(map, iLayerIndex)->classgroup);
7✔
5391
        GET_LAYER(map, iLayerIndex)->classgroup = msStrdup(pszStyle);
7✔
5392
      }
5393
    }
5394
  } else {
5395
    /* extract the parameters we need */
5396
    for (int i = 0; i < numentries; i++) {
182✔
5397
      if (strcasecmp(names[i], "FORMAT") == 0)
170✔
5398
        pszFormat = values[i];
12✔
5399
      else if (strcasecmp(names[i], "RULE") == 0)
158✔
5400
        psRule = values[i];
×
5401
    }
5402
  }
5403
  /* validate format */
5404

5405
  /*check to see if a predefined list is given*/
5406
  format_list = msOWSLookupMetadata(&(map->web.metadata), "M",
42✔
5407
                                    "getlegendgraphic_formatlist");
5408
  if (format_list) {
42✔
5409
    psFormat = msOwsIsOutputFormatValid(map, pszFormat, &(map->web.metadata),
×
5410
                                        "M", "getlegendgraphic_formatlist");
5411
    if (psFormat == NULL) {
×
5412
      msSetErrorWithStatus(MS_IMGERR, MS_HTTP_400_BAD_REQUEST,
×
5413
                           "Unsupported output format (%s).",
5414
                           "msWMSGetLegendGraphic()", pszFormat);
5415
      return msWMSException(map, nVersion, "InvalidFormat",
×
5416
                            wms_exception_format);
×
5417
    }
5418
  } else {
5419
    psFormat = msSelectOutputFormat(map, pszFormat);
42✔
5420
    if (psFormat == NULL || !MS_RENDERER_PLUGIN(psFormat))
42✔
5421
    /* msDrawLegend and msCreateLegendIcon both switch the alpha channel to gd
5422
     ** after creation, so they can be called here without going through
5423
     ** the msAlphaGD2AGG functions */
5424
    {
5425
      msSetErrorWithStatus(MS_IMGERR, MS_HTTP_400_BAD_REQUEST,
×
5426
                           "Unsupported output format (%s).",
5427
                           "msWMSGetLegendGraphic()", pszFormat);
5428
      return msWMSException(map, nVersion, "InvalidFormat",
×
5429
                            wms_exception_format);
×
5430
    }
5431
  }
5432
  msApplyOutputFormat(&(map->outputformat), psFormat, MS_NOOVERRIDE);
42✔
5433

5434
  if (psRule == NULL || nLayers > 1) {
42✔
5435
    if (psScale != NULL) {
42✔
5436
      /* Scale-dependent legend. map->scaledenom will be calculated in
5437
       * msDrawLegend */
5438
      img = msDrawLegend(map, MS_FALSE, NULL);
×
5439
    } else {
5440
      /* Scale-independent legend */
5441
      img = msDrawLegend(map, MS_TRUE, hittest);
42✔
5442
    }
5443
  } else {
5444
    /* RULE was specified. Get the class corresponding to the RULE */
5445
    /* (RULE = class->name) */
5446
    /* TBT FIXME? also check the map->scaledenom if multiple scale-dependant
5447
     * classes with same name */
5448

5449
    layerObj *lp = GET_LAYER(map, iLayerIndex);
×
5450
    int i;
5451
    for (i = 0; i < lp->numclasses; i++) {
×
5452
      if (lp->classgroup &&
×
5453
          (lp->_class[i]->group == NULL ||
×
5454
           strcasecmp(lp->_class[i]->group, lp->classgroup) != 0))
×
5455
        continue;
×
5456

5457
      if (lp->_class[i]->name && strlen(lp->_class[i]->name) > 0 &&
×
5458
          strcasecmp(lp->_class[i]->name, psRule) == 0)
×
5459
        break;
5460
    }
5461
    if (i < lp->numclasses) {
×
5462
      /* set the map legend parameters */
5463
      if (nWidth < 0) {
×
5464
        if (map->legend.keysizex > 0)
×
5465
          nWidth = map->legend.keysizex;
5466
        else
5467
          nWidth = 20; /* default values : this in not defined in the specs */
5468
      }
5469
      if (nHeight < 0) {
×
5470
        if (map->legend.keysizey > 0)
×
5471
          nHeight = map->legend.keysizey;
5472
        else
5473
          nHeight = 20;
5474
      }
5475

5476
      if (psScale != NULL) {
×
5477
        /* Scale-dependent legend. calculate map->scaledenom */
5478
        map->cellsize = msAdjustExtent(&(map->extent), map->width, map->height);
×
5479
        msCalculateScale(map->extent, map->units, map->width, map->height,
×
5480
                         map->resolution, &map->scaledenom);
5481
        img = msCreateLegendIcon(map, lp, lp->_class[i], nWidth, nHeight,
×
5482
                                 MS_FALSE);
5483
      } else {
5484
        /* Scale-independent legend */
5485
        img = msCreateLegendIcon(map, lp, lp->_class[i], nWidth, nHeight,
×
5486
                                 MS_TRUE);
5487
      }
5488
    }
5489
    if (img == NULL) {
×
5490
      msSetErrorWithStatus(MS_IMGERR, MS_HTTP_400_BAD_REQUEST,
×
5491
                           "Unavailable RULE (%s).", "msWMSGetLegendGraphic()",
5492
                           psRule);
5493
      return msWMSException(map, nVersion, "InvalidRule", wms_exception_format);
×
5494
    }
5495
  }
5496

5497
  if (img == NULL)
42✔
5498
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5499

5500
  msIO_setHeader("Content-Type", "%s", MS_IMAGE_MIME_TYPE(map->outputformat));
84✔
5501
  msIO_sendHeaders();
42✔
5502
  if (msSaveImage(map, img, NULL) != MS_SUCCESS)
42✔
5503
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5504

5505
  msFreeImage(img);
42✔
5506

5507
  return (MS_SUCCESS);
42✔
5508
}
5509

5510
/*
5511
** msWMSGetContentDependentLegend()
5512
*/
5513
static int msWMSGetContentDependentLegend(mapObj *map, int nVersion,
12✔
5514
                                          char **names, char **values,
5515
                                          int numentries,
5516
                                          const char *wms_exception_format,
5517
                                          owsRequestObj *ows_request) {
5518

5519
  /* turn off layer if WMS GetMap is not enabled */
5520
  for (int i = 0; i < map->numlayers; i++)
69✔
5521
    if (!msIntegerInArray(GET_LAYER(map, i)->index, ows_request->enabled_layers,
57✔
5522
                          ows_request->numlayers))
5523
      GET_LAYER(map, i)->status = MS_OFF;
×
5524

5525
  map_hittest hittest;
5526
  initMapHitTests(map, &hittest);
12✔
5527
  int status = msHitTestMap(map, &hittest);
12✔
5528
  if (status == MS_SUCCESS) {
12✔
5529
    status = msWMSLegendGraphic(map, nVersion, names, values, numentries,
12✔
5530
                                wms_exception_format, ows_request, &hittest);
5531
  }
5532
  freeMapHitTests(map, &hittest);
12✔
5533
  if (status != MS_SUCCESS) {
12✔
5534
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5535
  } else {
5536
    return MS_SUCCESS;
5537
  }
5538
}
5539

5540
/*
5541
** msWMSGetStyles() : return an SLD document for all layers that
5542
** have a status set to on or default.
5543
*/
5544
static int msWMSGetStyles(mapObj *map, int nVersion, char **names,
60✔
5545
                          char **values, int numentries,
5546
                          const char *wms_exception_format,
5547
                          owsRequestObj *ows_request)
5548

5549
{
5550
  bool validlayer = false;
5551

5552
  const char *sldenabled =
5553
      msOWSLookupMetadata(&(map->web.metadata), "MO", "sld_enabled");
60✔
5554
  if (sldenabled == NULL)
60✔
5555
    sldenabled = "true";
5556

5557
  auto [layerTree, mapNameToNode] = msWMSCreateLayerTree(map, nullptr);
60✔
5558
  (void)layerTree;
5559

5560
  for (int i = 0; i < numentries; i++) {
367✔
5561
    /* getMap parameters */
5562
    if (strcasecmp(names[i], "LAYERS") == 0) {
307✔
5563
      const auto wmslayers = msStringSplit(values[i], ',');
60✔
5564
      if (wmslayers.empty()) {
60✔
5565
        msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5566
                             "At least one layer name required in LAYERS.",
5567
                             "msWMSGetStyles()");
5568
        return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5569
      }
5570
      for (int j = 0; j < map->numlayers; j++)
1,140✔
5571
        GET_LAYER(map, j)->status = MS_OFF;
1,080✔
5572

5573
      for (const auto &wmslayer : wmslayers) {
135✔
5574
        auto iter = mapNameToNode.find(msStringToLower(wmslayer));
75✔
5575
        if (iter != mapNameToNode.end()) {
75✔
5576
          const auto layerIndices = iter->second->collectLayerIndices();
75✔
5577
          for (int i : layerIndices) {
181✔
5578
            layerObj *lp = GET_LAYER(map, i);
106✔
5579
            if (msIntegerInArray(lp->index, ows_request->enabled_layers,
106✔
5580
                                 ows_request->numlayers)) {
5581
              lp->status = MS_ON;
106✔
5582
              validlayer = true;
5583
            }
5584
          }
5585
        }
75✔
5586
      }
5587
    }
60✔
5588

5589
    else if (strcasecmp(names[i], "SLD") == 0 && values[i] &&
247✔
5590
             strlen(values[i]) > 0 && strcasecmp(sldenabled, "true") == 0) {
×
5591
      msSLDApplySLDURL(map, values[i], -1, NULL, NULL);
×
5592
    }
5593

5594
    else if (strcasecmp(names[i], "SLD_BODY") == 0 && values[i] &&
247✔
5595
             strlen(values[i]) > 0 && strcasecmp(sldenabled, "true") == 0) {
6✔
5596
      msSLDApplySLD(map, values[i], -1, NULL, NULL);
6✔
5597
    }
5598
  }
5599

5600
  /* validate all layers given. If an invalid layer is sent, return an
5601
   * exception. */
5602
  if (!validlayer) {
60✔
5603
    msSetErrorWithStatus(
×
5604
        MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5605
        "Invalid layer(s) given in the LAYERS parameter. A layer might be disabled for \
5606
this request. Check wms/ows_enable_request settings.",
5607
        "msWMSGetStyles()");
5608
    return msWMSException(map, nVersion, "LayerNotDefined",
×
5609
                          wms_exception_format);
5610
  }
5611

5612
  char *sld = NULL;
5613
  if (nVersion <= OWS_1_1_1) {
60✔
5614
    msIO_setHeader("Content-Type",
4✔
5615
                   "application/vnd.ogc.sld+xml; charset=UTF-8");
5616
    msIO_sendHeaders();
4✔
5617
    sld = msSLDGenerateSLD(map, -1, "1.0.0");
4✔
5618
  } else {
5619
    /*for wms 1.3.0 generate a 1.1 sld*/
5620
    msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
56✔
5621
    msIO_sendHeaders();
56✔
5622
    sld = msSLDGenerateSLD(map, -1, "1.1.0");
56✔
5623
  }
5624
  if (sld) {
60✔
5625
    msIO_printf("%s\n", sld);
60✔
5626
    free(sld);
60✔
5627
  }
5628

5629
  return (MS_SUCCESS);
5630
}
5631

5632
int msWMSGetSchemaExtension(mapObj *map) {
2✔
5633
  char *schemalocation = msEncodeHTMLEntities(msOWSGetSchemasLocation(map));
2✔
5634

5635
  msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
2✔
5636
  msIO_sendHeaders();
2✔
5637

5638
  msIO_printf("<?xml version='1.0' encoding=\"UTF-8\"?>\n");
2✔
5639
  msIO_printf("<schema xmlns=\"http://www.w3.org/2001/XMLSchema\" "
2✔
5640
              "xmlns:wms=\"http://www.opengis.net/wms\" "
5641
              "xmlns:ms=\"http://mapserver.gis.umn.edu/mapserver\" "
5642
              "targetNamespace=\"http://mapserver.gis.umn.edu/mapserver\" "
5643
              "elementFormDefault=\"qualified\" version=\"1.0.0\">\n");
5644
  msIO_printf("  <import namespace=\"http://www.opengis.net/wms\" "
2✔
5645
              "schemaLocation=\"%s/wms/1.3.0/capabilities_1_3_0.xsd\"/>\n",
5646
              schemalocation);
5647
  msIO_printf("  <element name=\"GetStyles\" type=\"wms:OperationType\" "
2✔
5648
              "substitutionGroup=\"wms:_ExtendedOperation\"/>\n");
5649
  msIO_printf("</schema>");
2✔
5650

5651
  free(schemalocation);
2✔
5652

5653
  return (MS_SUCCESS);
2✔
5654
}
5655

5656
#endif /* USE_WMS_SVR */
5657

5658
/*
5659
** msWMSDispatch() is the entry point for WMS requests.
5660
** - If this is a valid request then it is processed and MS_SUCCESS is returned
5661
**   on success, or MS_FAILURE on failure.
5662
** - If this does not appear to be a valid WMS request then MS_DONE
5663
**   is returned and MapServer is expected to process this as a regular
5664
**   MapServer request.
5665
*/
5666
int msWMSDispatch(mapObj *map, cgiRequestObj *req, owsRequestObj *ows_request,
737✔
5667
                  int force_wms_mode) {
5668
#ifdef USE_WMS_SVR
5669
  int nVersion = OWS_VERSION_NOTSET;
5670
  const char *version = NULL, *request = NULL, *service = NULL, *format = NULL,
5671
             *updatesequence = NULL, *language = NULL;
5672
  const char *wms_exception_format = NULL;
5673

5674
  /*
5675
  ** Process Params common to all requests
5676
  */
5677
  /* VERSION (WMTVER in 1.0.0) and REQUEST must be present in a valid request */
5678
  for (int i = 0; i < req->NumParams; i++) {
8,444✔
5679
    if (strcasecmp(req->ParamNames[i], "VERSION") == 0)
7,707✔
5680
      version = req->ParamValues[i];
726✔
5681
    else if (strcasecmp(req->ParamNames[i], "WMTVER") == 0 && version == NULL)
6,981✔
5682
      version = req->ParamValues[i];
×
5683
    else if (strcasecmp(req->ParamNames[i], "UPDATESEQUENCE") == 0)
6,981✔
5684
      updatesequence = req->ParamValues[i];
6✔
5685
    else if (strcasecmp(req->ParamNames[i], "REQUEST") == 0)
6,975✔
5686
      request = req->ParamValues[i];
728✔
5687
    else if (strcasecmp(req->ParamNames[i], "EXCEPTIONS") == 0)
6,247✔
5688
      wms_exception_format = req->ParamValues[i];
57✔
5689
    else if (strcasecmp(req->ParamNames[i], "SERVICE") == 0)
6,190✔
5690
      service = req->ParamValues[i];
735✔
5691
    else if (strcasecmp(req->ParamNames[i], "FORMAT") == 0)
5,455✔
5692
      format = req->ParamValues[i];
570✔
5693
    else if (strcasecmp(req->ParamNames[i], "LANGUAGE") == 0 &&
4,895✔
5694
             msOWSLookupMetadata(&(map->web.metadata), "MO",
10✔
5695
                                 "inspire_capabilities"))
5696
      language = req->ParamValues[i];
10✔
5697
  }
5698

5699
  /* If SERVICE is specified then it MUST be "WMS" */
5700
  if (service != NULL && strcasecmp(service, "WMS") != 0)
737✔
5701
    return MS_DONE; /* Not a WMS request */
5702

5703
  nVersion = msOWSParseVersionString(version);
737✔
5704
  if (nVersion == OWS_VERSION_BADFORMAT) {
737✔
5705
    /* Invalid version format. msSetError() has been called by
5706
     * msOWSParseVersionString() and we return the error as an exception
5707
     */
5708
    return msWMSException(map, OWS_VERSION_NOTSET, NULL, wms_exception_format);
8✔
5709
  }
5710

5711
  /*
5712
  ** GetCapbilities request needs the service parameter defined as WMS:
5713
  see section 7.1.3.2 wms 1.1.1 specs for description.
5714
  */
5715
  if (request && service == NULL &&
729✔
5716
      (strcasecmp(request, "capabilities") == 0 ||
3✔
5717
       strcasecmp(request, "GetCapabilities") == 0) &&
3✔
5718
      (nVersion >= OWS_1_1_0 || nVersion == OWS_VERSION_NOTSET)) {
×
5719
    if (force_wms_mode) {
×
5720
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5721
                           "Required SERVICE parameter missing.",
5722
                           "msWMSDispatch");
5723
      return msWMSException(map, nVersion, "ServiceNotDefined",
×
5724
                            wms_exception_format);
×
5725
    } else
5726
      return MS_DONE;
5727
  }
5728

5729
  /*
5730
  ** Dispatch request... we should probably do some validation on VERSION here
5731
  ** vs the versions we actually support.
5732
  */
5733
  if (request && (strcasecmp(request, "capabilities") == 0 ||
729✔
5734
                  strcasecmp(request, "GetCapabilities") == 0)) {
728✔
5735
    const char *enable_request;
5736
    int globally_enabled, disabled = MS_FALSE;
66✔
5737

5738
    if (nVersion == OWS_VERSION_NOTSET) {
66✔
5739
      version = msOWSLookupMetadata(&(map->web.metadata), "M",
10✔
5740
                                    "getcapabilities_version");
5741
      if (version)
10✔
5742
        nVersion = msOWSParseVersionString(version);
×
5743
      else
5744
        nVersion =
5745
            OWS_1_3_0; /* VERSION is optional with getCapabilities only */
5746
    }
5747

5748
    if (msOWSMakeAllLayersUnique(map) != MS_SUCCESS)
66✔
5749
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5750

5751
    msOWSRequestLayersEnabled(map, "M", "GetCapabilities", ows_request);
66✔
5752

5753
    enable_request =
5754
        msOWSLookupMetadata(&map->web.metadata, "OM", "enable_request");
66✔
5755
    globally_enabled =
5756
        msOWSParseRequestMetadata(enable_request, "GetCapabilities", &disabled);
66✔
5757

5758
    if (ows_request->numlayers == 0 && !globally_enabled) {
66✔
5759
      msSetErrorWithStatus(
2✔
5760
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5761
          "WMS request not enabled. Check wms/ows_enable_request settings.",
5762
          "msWMSGetCapabilities()");
5763
      return msWMSException(map, nVersion, NULL, wms_exception_format);
2✔
5764
    }
5765
    msAcquireLock(TLOCK_WxS);
64✔
5766
    const int status =
5767
        msWMSGetCapabilities(map, nVersion, req, ows_request, updatesequence,
64✔
5768
                             wms_exception_format, language);
5769
    msReleaseLock(TLOCK_WxS);
64✔
5770
    return status;
64✔
5771
  } else if (request && (strcasecmp(request, "context") == 0 ||
662✔
5772
                         strcasecmp(request, "GetContext") == 0)) {
662✔
5773
    /* Return a context document with all layers in this mapfile
5774
     * This is not a standard WMS request.
5775
     * __TODO__ The real implementation should actually return only context
5776
     * info for selected layers in the LAYERS parameter.
5777
     */
5778
    const char *getcontext_enabled;
5779
    getcontext_enabled =
5780
        msOWSLookupMetadata(&(map->web.metadata), "MO", "getcontext_enabled");
×
5781

5782
    if (nVersion != OWS_VERSION_NOTSET) {
×
5783
      /* VERSION, if specified, is Map Context version, not WMS version */
5784
      /* Pass it via wms_context_version metadata */
5785
      char szVersion[OWS_VERSION_MAXLEN];
5786
      msInsertHashTable(&(map->web.metadata), "wms_context_version",
×
5787
                        msOWSGetVersionString(nVersion, szVersion));
5788
    }
5789
    /* Now set version to 1.1.1 for error handling purposes */
5790
    nVersion = OWS_1_1_1;
5791

5792
    if (getcontext_enabled == NULL || atoi(getcontext_enabled) == 0) {
×
5793
      msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5794
                           "GetContext not enabled on this server.",
5795
                           "msWMSDispatch()");
5796
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5797
    }
5798

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

5802
    msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
×
5803
    msIO_sendHeaders();
×
5804

5805
    if (msWriteMapContext(map, stdout) != MS_SUCCESS)
×
5806
      return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5807
    /* Request completed */
5808
    return MS_SUCCESS;
5809
  } else if (request && strcasecmp(request, "GetMap") == 0 && format &&
662✔
5810
             strcasecmp(format, "image/txt") == 0) {
470✔
5811
    /* Until someone adds full support for ASCII graphics this should do. ;) */
5812
    msIO_setHeader("Content-Type", "text/plain; charset=UTF-8");
×
5813
    msIO_sendHeaders();
×
5814
    msIO_printf(".\n               ,,ggddY\"\"\"Ybbgg,,\n          ,agd888b,_ "
×
5815
                "\"Y8, ___'\"\"Ybga,\n       ,gdP\"\"88888888baa,.\"\"8b    \""
5816
                "888g,\n     ,dP\"     ]888888888P'  \"Y     '888Yb,\n   ,dP\""
5817
                "      ,88888888P\"  db,       \"8P\"\"Yb,\n  ,8\"       ,8888"
5818
                "88888b, d8888a           \"8,\n ,8'        d88888888888,88P\""
5819
                "' a,          '8,\n,8'         88888888888888PP\"  \"\"      "
5820
                "     '8,\nd'          I88888888888P\"                   'b\n8"
5821
                "           '8\"88P\"\"Y8P'                      8\n8         "
5822
                "   Y 8[  _ \"                        8\n8              \"Y8d8"
5823
                "b  \"Y a                   8\n8                 '\"\"8d,   __"
5824
                "                 8\nY,                    '\"8bd888b,        "
5825
                "     ,P\n'8,                     ,d8888888baaa       ,8'\n '8"
5826
                ",                    888888888888'      ,8'\n  '8a           "
5827
                "        \"8888888888I      a8'\n   'Yba                  'Y88"
5828
                "88888P'    adP'\n     \"Yba                 '888888P'   adY\""
5829
                "\n       '\"Yba,             d8888P\" ,adP\"' \n          '\""
5830
                "Y8baa,      ,d888P,ad8P\"' \n               ''\"\"YYba8888P\""
5831
                "\"''\n");
5832
    return MS_SUCCESS;
×
5833
  }
5834

5835
  /* If SERVICE, VERSION and REQUEST not included than this isn't a WMS req*/
5836
  if (service == NULL && nVersion == OWS_VERSION_NOTSET && request == NULL)
663✔
5837
    return MS_DONE; /* Not a WMS request */
5838

5839
  /* VERSION *and* REQUEST required by both getMap and getFeatureInfo */
5840
  if (nVersion == OWS_VERSION_NOTSET) {
663✔
5841
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
1✔
5842
                         "Incomplete WMS request: VERSION parameter missing",
5843
                         "msWMSDispatch()");
5844
    return msWMSException(map, OWS_VERSION_NOTSET, NULL, wms_exception_format);
1✔
5845
  }
5846

5847
  /*check if the version is one of the supported versions*/
5848
  if (nVersion != OWS_1_0_0 && nVersion != OWS_1_1_0 && nVersion != OWS_1_1_1 &&
662✔
5849
      nVersion != OWS_1_3_0) {
389✔
5850
    msSetErrorWithStatus(
×
5851
        MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5852
        "Invalid WMS version: VERSION %s is not supported. Supported "
5853
        "versions are 1.0.0, 1.1.0, 1.1.1, 1.3.0",
5854
        "msWMSDispatch()", version);
5855
    return msWMSException(map, OWS_VERSION_NOTSET, NULL, wms_exception_format);
×
5856
  }
5857

5858
  if (request == NULL) {
662✔
5859
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5860
                         "Incomplete WMS request: REQUEST parameter missing",
5861
                         "msWMSDispatch()");
5862
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5863
  }
5864

5865
  /* hack !? The function can return MS_DONE ... be sure it's a wms request
5866
   * before checking the enabled layers */
5867
  if ((strcasecmp(request, "GetStyles") == 0) ||
662✔
5868
      (strcasecmp(request, "GetLegendGraphic") == 0) ||
602✔
5869
      (strcasecmp(request, "GetSchemaExtension") == 0) ||
555✔
5870
      (strcasecmp(request, "map") == 0 || strcasecmp(request, "GetMap") == 0) ||
553✔
5871
      (strcasecmp(request, "feature_info") == 0 ||
82✔
5872
       strcasecmp(request, "GetFeatureInfo") == 0) ||
82✔
5873
      (strcasecmp(request, "DescribeLayer") == 0)) {
21✔
5874
    const char *request_tmp;
5875
    if (strcasecmp(request, "map") == 0)
662✔
5876
      request_tmp = "GetMap";
5877
    else if (strcasecmp(request, "feature_info") == 0)
662✔
5878
      request_tmp = "GetFeatureInfo";
5879
    else
5880
      request_tmp = request;
5881

5882
    msOWSRequestLayersEnabled(map, "M", request_tmp, ows_request);
662✔
5883
    if (ows_request->numlayers == 0) {
662✔
5884
      msSetErrorWithStatus(
1✔
5885
          MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5886
          "WMS request not enabled. Check wms/ows_enable_request settings.",
5887
          "msWMSDispatch()");
5888
      return msWMSException(map, nVersion, NULL, wms_exception_format);
1✔
5889
    }
5890
  }
5891

5892
  if (msOWSMakeAllLayersUnique(map) != MS_SUCCESS)
661✔
5893
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5894

5895
  bool isContentDependentLegend = false;
5896
  if (strcasecmp(request, "GetLegendGraphic") == 0) {
661✔
5897
    /*
5898
     * check for a BBOX in the request, in that case we have a content-dependant
5899
     * legend request, and should be following the GetMap path a bit more
5900
     */
5901
    bool found = false;
5902
    for (int i = 0; i < req->NumParams; i++) {
430✔
5903
      if (strcasecmp(req->ParamNames[i], "BBOX") == 0) {
396✔
5904
        if (req->ParamValues[i] && *req->ParamValues[i]) {
12✔
5905
          found = true;
5906
          break;
5907
        }
5908
      }
5909
    }
5910
    if (found) {
46✔
5911
      isContentDependentLegend = true;
5912
      /* getLegendGraphic uses LAYER= , we need to create a LAYERS= value that
5913
       * is identical we'll suppose that the client is conformat and hasn't
5914
       * included a LAYERS= parameter in its request */
5915
      for (int i = 0; i < req->NumParams; i++) {
182✔
5916
        if (strcasecmp(req->ParamNames[i], "LAYER") == 0) {
170✔
5917
          req->ParamNames[req->NumParams] = msStrdup("LAYERS");
9✔
5918
          req->ParamValues[req->NumParams] = msStrdup(req->ParamValues[i]);
9✔
5919
          req->NumParams++;
9✔
5920
        }
5921
      }
5922
    } else {
5923
      return msWMSLegendGraphic(map, nVersion, req->ParamNames,
34✔
5924
                                req->ParamValues, req->NumParams,
5925
                                wms_exception_format, ows_request, NULL);
34✔
5926
    }
5927
  }
5928

5929
  if (strcasecmp(request, "GetStyles") == 0)
627✔
5930
    return msWMSGetStyles(map, nVersion, req->ParamNames, req->ParamValues,
60✔
5931
                          req->NumParams, wms_exception_format, ows_request);
60✔
5932

5933
  else if (request && strcasecmp(request, "GetSchemaExtension") == 0)
567✔
5934
    return msWMSGetSchemaExtension(map);
2✔
5935

5936
  /* getMap parameters are used by both getMap, getFeatureInfo, and content
5937
   * dependent legendgraphics */
5938
  if (strcasecmp(request, "map") == 0 || strcasecmp(request, "GetMap") == 0 ||
565✔
5939
      strcasecmp(request, "feature_info") == 0 ||
94✔
5940
      strcasecmp(request, "GetFeatureInfo") == 0 ||
94✔
5941
      strcasecmp(request, "DescribeLayer") == 0 || isContentDependentLegend) {
33✔
5942

5943
    const int status = msWMSLoadGetMapParams(
565✔
5944
        map, nVersion, req->ParamNames, req->ParamValues, req->NumParams,
5945
        wms_exception_format, request, ows_request);
5946
    if (status != MS_SUCCESS)
565✔
5947
      return status;
5948
  }
5949

5950
  /* This function owns validated_language, so remember to free it later*/
5951
  char *validated_language = msOWSGetLanguageFromList(map, "MO", language);
504✔
5952
  if (validated_language != NULL) {
504✔
5953
    msMapSetLanguageSpecificConnection(map, validated_language);
47✔
5954
  }
5955
  msFree(validated_language);
504✔
5956

5957
  if (strcasecmp(request, "map") == 0 || strcasecmp(request, "GetMap") == 0)
504✔
5958
    return msWMSGetMap(map, nVersion, req->ParamNames, req->ParamValues,
415✔
5959
                       req->NumParams, wms_exception_format, ows_request);
415✔
5960
  else if (strcasecmp(request, "feature_info") == 0 ||
89✔
5961
           strcasecmp(request, "GetFeatureInfo") == 0)
89✔
5962
    return msWMSFeatureInfo(map, nVersion, req->ParamNames, req->ParamValues,
58✔
5963
                            req->NumParams, wms_exception_format, ows_request);
58✔
5964
  else if (strcasecmp(request, "DescribeLayer") == 0) {
31✔
5965
    return msWMSDescribeLayer(map, nVersion, req->ParamNames, req->ParamValues,
19✔
5966
                              req->NumParams, wms_exception_format,
5967
                              ows_request);
19✔
5968
  } else if (isContentDependentLegend) {
12✔
5969
    return msWMSGetContentDependentLegend(map, nVersion, req->ParamNames,
12✔
5970
                                          req->ParamValues, req->NumParams,
5971
                                          wms_exception_format, ows_request);
12✔
5972
  }
5973

5974
  /* Hummmm... incomplete or unsupported WMS request */
5975
  if (service != NULL && strcasecmp(service, "WMS") == 0) {
×
5976
    msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
×
5977
                         "Incomplete or unsupported WMS request",
5978
                         "msWMSDispatch()");
5979
    return msWMSException(map, nVersion, NULL, wms_exception_format);
×
5980
  } else
5981
    return MS_DONE; /* Not a WMS request */
5982
#else
5983
  msSetErrorWithStatus(MS_WMSERR, MS_HTTP_400_BAD_REQUEST,
5984
                       "WMS server support is not available.",
5985
                       "msWMSDispatch()");
5986
  return (MS_FAILURE);
5987
#endif
5988
}
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