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

MapServer / MapServer / 26328754667

23 May 2026 09:01AM UTC coverage: 42.44% (+0.001%) from 42.439%
26328754667

push

github

web-flow
Fix incorrect JSON error message output on Index Pages (#7512)

* Don't return user-entered input, and remove duplicated error

* EOL fix

4 of 7 new or added lines in 2 files covered. (57.14%)

19638 existing lines in 3 files now uncovered.

64644 of 152320 relevant lines covered (42.44%)

27393.65 hits per line

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

95.31
/src/mapserv-index.cpp
1
/**********************************************************************
2
 *
3
 * Project:  MapServer
4
 * Purpose:  MapServer Index Page Implementation
5
 * Author:   Seth Girvin and the MapServer team.
6
 *
7
 **********************************************************************
8
 * Copyright (c) 1996-2025 Regents of the University of Minnesota.
9
 *
10
 * Permission is hereby granted, free of charge, to any person obtaining a
11
 * copy of this software and associated documentation files (the "Software"),
12
 * to deal in the Software without restriction, including without limitation
13
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14
 * and/or sell copies of the Software, and to permit persons to whom the
15
 * Software is furnished to do so, subject to the following conditions:
16
 *
17
 * The above copyright notice and this permission notice shall be included in
18
 * all copies of this Software or works derived from this Software.
19
 *
20
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
23
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26
 ****************************************************************************/
27

28
#include "mapserver.h"
29
#include "mapogcapi.h"
30
#include "mapserv-index.h"
31
#include "maptemplate.h"
32
#include "mapows.h"
33
#include "mapserv-config.h"
34

35
#include "cpl_conv.h"
36

37
#include "third-party/include_nlohmann_json.hpp"
38
#include "third-party/include_pantor_inja.hpp"
39

40
using namespace inja;
41
using json = nlohmann::json;
42

43
/*
44
** HTML Templates
45
*/
46
#define TEMPLATE_HTML_INDEX "landing.html"
47
#define TEMPLATE_HTML_MAP_INDEX "map.html"
48

49
/**
50
 * Get an online resource using the supplied namespaces
51
 */
52
std::string getOnlineResource(mapObj *map, cgiRequestObj *request,
27✔
53
                              const std::string &namespaces) {
54
  std::string onlineResource;
55

56
  if (char *res = msOWSGetOnlineResource(map, namespaces.c_str(),
27✔
57
                                         "onlineresource", request)) {
58
    onlineResource = res;
59
    free(res);
12✔
60
  } else {
61
    // fallback: use a relative URL if no onlineresource can be found or created
62
    onlineResource = "./?";
63
  }
64

65
  if (!onlineResource.empty()) {
27✔
66
    if (onlineResource.find('?') != std::string::npos) {
27✔
67
      // URL already has a query string
68
      char last = onlineResource.back();
27✔
69
      if (last != '?' && last != '&') {
27✔
70
        onlineResource += '&';
71
      }
72
    } else {
73
      // no query string so append '?'
74
      onlineResource += '?';
75
    }
76
  }
77

78
  return onlineResource;
27✔
79
}
80

81
static json processCGI(mapObj *map, cgiRequestObj *request) {
13✔
82

83
  json response;
84

85
  // check if CGI modes have been disabled
86
  const char *enable_modes =
87
      msLookupHashTable(&(map->web.metadata), "ms_enable_modes");
13✔
88

89
  int disabled = MS_FALSE;
13✔
90
  msOWSParseRequestMetadata(enable_modes, "BROWSE", &disabled);
13✔
91

92
  if (disabled == MS_FALSE) {
13✔
93
    response["title"] = map->name;
24✔
94

95
    std::string onlineResource = getOnlineResource(map, request, "MCFGOA");
24✔
96

97
    json serviceDocs = json::array(
156✔
98
        {{{"href",
99
           onlineResource + "template=openlayers&mode=browse&layers=all"},
12✔
100
          {"title", "OpenLayers Viewer"},
101
          {"service", "CGI"},
102
          {"type", "text/html"}}});
24✔
103
    response["service-doc"] = serviceDocs;
24✔
104
  }
105

106
  return response;
13✔
107
}
228✔
108

109
static json processOGCAPI(mapObj *map, cgiRequestObj *request) {
13✔
110

111
  json response;
112

113
  const char *title = msOWSLookupMetadata(&(map->web.metadata), "AO", "title");
13✔
114
  if (!title) {
13✔
115
    title = map->name;
5✔
116
  }
117

118
  int status = msOWSRequestIsEnabled(map, NULL, "AO", "OGCAPI", MS_FALSE);
13✔
119
  if (status == MS_TRUE) {
13✔
120
    response["title"] = title;
4✔
121

122
    std::string onlineResource = msOGCAPIGetApiRootUrl(map, request);
4✔
123

124
    // strip any trailing slash as we add it back manually in the hrefs
125
    if (!onlineResource.empty() && onlineResource.back() == '/') {
4✔
126
      onlineResource.pop_back();
127
    }
128

129
    json serviceDescriptions =
130
        json::array({{{"href", onlineResource + "/?f=json"},
56✔
131
                      {"title", "OGC API Root Service"},
132
                      {"service", "OGC API"},
133
                      {"type", "application/json"}}});
8✔
134
    json serviceDocs = json::array({{{"href", onlineResource + "/?f=html"},
56✔
135
                                     {"title", "OGC API Landing Page"},
136
                                     {"service", "OGC API"},
137
                                     {"type", "text/html"}}});
8✔
138
    response["service-desc"] = serviceDescriptions;
4✔
139
    response["service-doc"] = serviceDocs;
8✔
140
  }
141
  return response;
13✔
142
}
152✔
143

144
static json processWMS(mapObj *map, cgiRequestObj *request) {
13✔
145

146
  json response;
147

148
  const char *title = msOWSLookupMetadata(&(map->web.metadata), "MO", "title");
13✔
149
  if (!title) {
13✔
150
    title = map->name;
6✔
151
  }
152

153
  int globally_enabled = MS_FALSE;
154
  int disabled = MS_FALSE;
13✔
155

156
  const char *enable_request =
157
      msOWSLookupMetadata(&map->web.metadata, "MO", "enable_request");
13✔
158

159
  globally_enabled =
160
      msOWSParseRequestMetadata(enable_request, "GetCapabilities", &disabled);
13✔
161

162
  if (globally_enabled == MS_TRUE) {
13✔
163
    response["title"] = title;
7✔
164

165
    std::string onlineResource = getOnlineResource(map, request, "MO");
7✔
166

167
    std::vector<std::string> versions = {"1.0.0", "1.1.0", "1.1.1", "1.3.0"};
7✔
168
    json serviceDescriptions = json::array();
7✔
169

170
    for (const auto &ver : versions) {
35✔
171
      serviceDescriptions.push_back(
364✔
172
          {{"href", std::string(onlineResource) + "version=" + ver +
28✔
173
                        "&request=GetCapabilities&service=WMS"},
174
           {"title", "WMS GetCapabilities URL (version " + ver + ")"},
28✔
175
           {"service", "WMS"},
176
           {"type", "text/xml"}});
177
    }
178
    response["service-desc"] = serviceDescriptions;
14✔
179
  }
7✔
180

181
  return response;
13✔
182
}
420✔
183

184
static json processWFS(mapObj *map, cgiRequestObj *request) {
13✔
185

186
  json response;
187

188
  const char *title = msOWSLookupMetadata(&(map->web.metadata), "FO", "title");
13✔
189
  if (!title) {
13✔
190
    title = map->name;
5✔
191
  }
192

193
  int globally_enabled = MS_FALSE;
194
  int disabled = MS_FALSE;
13✔
195

196
  const char *enable_request =
197
      msOWSLookupMetadata(&map->web.metadata, "FO", "enable_request");
13✔
198

199
  globally_enabled =
200
      msOWSParseRequestMetadata(enable_request, "GetCapabilities", &disabled);
13✔
201

202
  if (globally_enabled == MS_TRUE) {
13✔
203
    response["title"] = title;
4✔
204

205
    std::string onlineResource = getOnlineResource(map, request, "MO");
4✔
206

207
    std::vector<std::string> versions = {"1.0.0", "1.1.0", "2.0.0"};
4✔
208
    json serviceDescriptions = json::array();
4✔
209

210
    for (const auto &ver : versions) {
16✔
211
      serviceDescriptions.push_back(
156✔
212
          {{"href", onlineResource + "version=" + ver +
12✔
213
                        "&request=GetCapabilities&service=WFS"},
214
           {"title", "WFS GetCapabilities URL (version " + ver + ")"},
12✔
215
           {"service", "WFS"},
216
           {"type", "text/xml"}});
217
    }
218
    response["service-desc"] = serviceDescriptions;
8✔
219
  }
4✔
220

221
  return response;
13✔
222
}
180✔
223

224
static json processWCS(mapObj *map, cgiRequestObj *request) {
13✔
225

226
  json response;
227

228
  const char *title = msOWSLookupMetadata(&(map->web.metadata), "CO", "title");
13✔
229
  if (!title) {
13✔
230
    title = map->name;
6✔
231
  }
232

233
  int globally_enabled = MS_FALSE;
234
  int disabled = MS_FALSE;
13✔
235

236
  const char *enable_request =
237
      msOWSLookupMetadata(&map->web.metadata, "CO", "enable_request");
13✔
238

239
  globally_enabled =
240
      msOWSParseRequestMetadata(enable_request, "GetCapabilities", &disabled);
13✔
241

242
  if (globally_enabled == MS_TRUE) {
13✔
243
    response["title"] = title;
4✔
244

245
    std::string onlineResource = getOnlineResource(map, request, "CO");
4✔
246

247
    std::vector<std::string> versions = {"1.0.0", "1.1.0", "2.0.0", "2.0.1"};
4✔
248
    json serviceDescriptions = json::array();
4✔
249

250
    for (auto &ver : versions) {
20✔
251
      serviceDescriptions.push_back(
208✔
252
          {{"href", onlineResource + "version=" + ver +
16✔
253
                        "&request=GetCapabilities&service=WCS"},
254
           {"title", "WCS GetCapabilities URL (version " + ver + ")"},
16✔
255
           {"service", "WCS"},
256
           {"type", "text/xml"}});
257
    }
258
    response["service-desc"] = serviceDescriptions;
8✔
259
  }
4✔
260
  return response;
13✔
261
}
240✔
262

263
static json createMapDetails(mapObj *map, cgiRequestObj *request) {
13✔
264

265
  json links = json::array();
13✔
266
  json result;
267

268
  result = processCGI(map, request);
26✔
269
  if (!result.empty()) {
12✔
270
    links.push_back(result);
12✔
271
  }
272

273
#ifdef USE_OGCAPI_SVR
274
  result = processOGCAPI(map, request);
26✔
275
  if (!result.empty()) {
4✔
276
    links.push_back(result);
4✔
277
  }
278
#endif
279
#ifdef USE_WMS_SVR
280
  result = processWMS(map, request);
26✔
281
  if (!result.empty()) {
7✔
282
    links.push_back(result);
7✔
283
  }
284
#endif
285
#ifdef USE_WFS_SVR
286
  result = processWFS(map, request);
26✔
287
  if (!result.empty()) {
4✔
288
    links.push_back(result);
4✔
289
  }
290
#endif
291
#ifdef USE_WCS_SVR
292
  result = processWCS(map, request);
26✔
293
  if (!result.empty()) {
4✔
294
    links.push_back(result);
4✔
295
  }
296
#endif
297

298
  return {{"linkset", links}};
65✔
299
}
52✔
300

301
/**
302
 * Create a JSON object with a summary of the Mapfile
303
 **/
304
static json createMapSummary(mapObj *map, const char *key,
29✔
305
                             cgiRequestObj *request) {
306
  json mapJson;
307
  const char *value =
308
      msOWSLookupMetadata(&(map->web.metadata), "MCFGOA", "title");
29✔
309

310
  mapJson["key"] = key;
58✔
311
  mapJson["has-error"] = false;
29✔
312

313
  if (value) {
29✔
314
    mapJson["title"] = value;
42✔
315
  } else {
316
    mapJson["title"] = map->name;
24✔
317
  }
318

319
  mapJson["layer-count"] = map->numlayers;
29✔
320
  mapJson["name"] = map->name;
87✔
321

322
  std::string onlineResource;
323

324
  if (char *res = msBuildOnlineResource(NULL, request)) {
29✔
325
    onlineResource = res;
326
    free(res);
3✔
327

328
    // remove trailing '?' if present
329
    if (!onlineResource.empty() && onlineResource.back() == '?') {
3✔
330
      onlineResource.pop_back();
331
    }
332
  } else {
333
    // use a relative URL if no onlineresource cannot be created
334
    onlineResource = "./"; // fallback
335
  }
336

337
  // make sure the root URL end with a '/'
338
  if (!onlineResource.empty() && onlineResource.back() != '/') {
29✔
339
    onlineResource += '/';
340
  }
341

342
  mapJson["service-desc"] =
29✔
343
      json::array({{{"href", onlineResource + std::string(key) + "/?f=json"},
377✔
344
                    {"title", key},
345
                    {"type", "application/vnd.oai.openapi+json"}}});
346

347
  mapJson["service-doc"] =
29✔
348
      json::array({{{"href", onlineResource + std::string(key) + "/"},
377✔
349
                    {"title", key},
350
                    {"type", "text/html"}}});
351

352
  return mapJson;
29✔
353
}
870✔
354

355
/**
356
 * For invalid maps return a JSON object reporting the error
357
 **/
358
static json createMapError(const char *key) {
2✔
359
  json mapJson;
360
  mapJson["key"] = key;
4✔
361
  mapJson["has-error"] = true;
4✔
362
  mapJson["title"] = " <b>Error loading the map</b>";
4✔
363
  mapJson["layer-count"] = 0;
4✔
364
  mapJson["name"] = key;
4✔
365
  mapJson["service-desc"] =
2✔
366
      json::array({{{"href", "./" + std::string(key) + "/?f=json"},
26✔
367
                    {"title", key},
368
                    {"type", "application/vnd.oai.openapi+json"}}});
369

370
  mapJson["service-doc"] =
2✔
371
      json::array({{{"href", "./" + std::string(key) + "/"},
26✔
372
                    {"title", key},
373
                    {"type", "text/html"}}});
374

375
  return mapJson;
2✔
376
}
60✔
377

378
/**
379
 * Load the Map for the key, by getting its file path
380
 * from the CONFIG file
381
 */
382
static mapObj *getMapFromConfig(configObj *config, const char *key) {
31✔
383
  const char *mapfilePath = msLookupHashTable(&config->maps, key);
31✔
384

385
  if (!mapfilePath)
31✔
386
    return nullptr;
387

388
  char pathBuf[MS_MAXPATHLEN];
389
  mapfilePath = msConfigGetMap(config, key, pathBuf);
31✔
390

391
  if (!mapfilePath) {
31✔
392
    return nullptr;
393
  }
394
  char *mapfilePathCopy = msStrdup(mapfilePath);
31✔
395
  mapObj *map = msLoadMap(mapfilePathCopy, nullptr, config);
31✔
396
  msFree(mapfilePathCopy);
31✔
397

398
  return map;
31✔
399
}
400

401
/**
402
 * Generate HTML by populating a template with the dynamic JSON
403
 */
404
static int createHTMLOutput(json response, const char *templateName) {
2✔
405
  std::string path;
406
  char fullpath[MS_MAXPATHLEN];
407

408
  path = msOGCAPIGetTemplateDirectory(NULL, "html_template_directory",
4✔
409
                                      "MS_INDEX_TEMPLATE_DIRECTORY");
2✔
410
  if (path.empty()) {
2✔
411
    msOGCAPIOutputError(OGCAPI_CONFIG_ERROR, "Template directory not set.");
×
412
    return MS_FAILURE;
×
413
  }
414
  msBuildPath(fullpath, NULL, path.c_str());
2✔
415

416
  json j = {{"response", response}};
8✔
417
  msOGCAPIOutputTemplate(fullpath, templateName, j, OGCAPI_MIMETYPE_HTML);
2✔
418

419
  return MS_SUCCESS;
420
}
8✔
421

422
/**
423
 * Return an individual map landing page response
424
 */
425
int msOGCAPIDispatchMapIndexRequest(mapservObj *mapserv, configObj *config) {
13✔
426
#ifdef USE_OGCAPI_SVR
427
  if (!mapserv || !config)
13✔
428
    return MS_FAILURE; // Handle null pointers
429

430
  cgiRequestObj *request = mapserv->request;
13✔
431

432
  if (request->api_path_length != 1) {
13✔
433
    const char *pathInfo = getenv("PATH_INFO");
×
434
    msSetError(MS_OGCAPIERR, "Invalid PATH_INFO format: \"%s\"",
×
435
               "msOGCAPIDispatchMapIndexRequest()", pathInfo);
436
    return MS_FAILURE;
×
437
  }
438

439
  OGCAPIFormat format = msOGCAPIGetOutputFormat(request);
13✔
440

441
  if (format == OGCAPIFormat::Invalid) {
13✔
NEW
442
    msOGCAPIOutputError(OGCAPI_PARAM_ERROR, "Unsupported format requested.");
×
NEW
443
    return MS_FAILURE;
×
444
  }
445

446
  const char *key = request->api_path[0];
13✔
447

448
  mapObj *map = getMapFromConfig(config, key);
13✔
449
  if (!map) {
13✔
450
    msOGCAPIOutputError(OGCAPI_CONFIG_ERROR, "Mapfile not found in config.");
×
451
    return MS_FAILURE;
×
452
  }
453
  json response = createMapDetails(map, request);
13✔
454

455
  // add in summary map details
456
  json summary = createMapSummary(map, key, request);
13✔
457

458
  // remove unwanted properties, before merging objects
459
  summary.erase("service-desc");
13✔
460
  summary.erase("service-doc");
13✔
461

462
  response.update(summary);
13✔
463

464
  if (format == OGCAPIFormat::JSON) {
13✔
465
    msOGCAPIOutputJson(response, OGCAPI_MIMETYPE_JSON, {});
24✔
466
  } else if (format == OGCAPIFormat::HTML) {
1✔
467
    createHTMLOutput(response, TEMPLATE_HTML_MAP_INDEX);
2✔
468
  } else {
469
    msOGCAPIOutputError(OGCAPI_PARAM_ERROR, "Unsupported format requested.");
×
470
  }
471

472
  msFreeMap(map);
13✔
473
  return MS_SUCCESS;
474

475
#else
476
  msSetError(MS_OGCAPIERR, "OGC API server support is not enabled.",
477
             "msOGCAPIDispatchMapIndexRequest()");
478
  return MS_FAILURE;
479
#endif
480
}
481

482
/**
483
 * Return the MapServer landing page response
484
 */
485
int msOGCAPIDispatchIndexRequest(mapservObj *mapserv, configObj *config) {
3✔
486
#ifdef USE_OGCAPI_SVR
487
  if (!mapserv || !config)
3✔
488
    return MS_FAILURE; // Handle null pointers
489

490
  cgiRequestObj *request = mapserv->request;
3✔
491
  const OGCAPIFormat format = msOGCAPIGetOutputFormat(request);
3✔
492

493
  if (format == OGCAPIFormat::Invalid) {
3✔
494
    msOGCAPIOutputError(OGCAPI_PARAM_ERROR, "Unsupported format requested.");
1✔
495
    return MS_FAILURE;
1✔
496
  }
497

498
  const char *key = NULL;
499
  json links = json::array();
2✔
500

501
  while ((key = msNextKeyFromHashTable(&config->maps, key)) != NULL) {
20✔
502
    if (mapObj *map = getMapFromConfig(config, key)) {
18✔
503
      links.push_back(createMapSummary(map, key, request));
16✔
504
      msFreeMap(map);
16✔
505
    } else {
506
      // there was a problem loading the map
507
      links.push_back(createMapError(key));
4✔
508
    }
509
  }
510

511
  json response = {{"linkset", links}};
8✔
512

513
  if (format == OGCAPIFormat::JSON) {
2✔
514
    msOGCAPIOutputJson(response, OGCAPI_MIMETYPE_JSON, {});
2✔
515
  } else if (format == OGCAPIFormat::HTML) {
1✔
516
    createHTMLOutput(response, TEMPLATE_HTML_INDEX);
2✔
517
  } else {
518
    assert(false && "Unhandled OGCAPIFormat value");
519
  }
520

521
  return MS_SUCCESS;
522

523
#else
524
  msSetError(MS_OGCAPIERR, "OGC API server support is not enabled.",
525
             "msOGCAPIDispatchIndexRequest()");
526
  return MS_FAILURE;
527
#endif
528
}
8✔
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