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

geographika / mapserver / 19276394673

11 Nov 2025 07:28PM UTC coverage: 41.713% (+0.03%) from 41.681%
19276394673

push

github

geographika
Add null guard and missing map tests

2 of 4 new or added lines in 1 file covered. (50.0%)

10 existing lines in 2 files now uncovered.

62784 of 150515 relevant lines covered (41.71%)

25209.01 hits per line

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

94.09
/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,
15✔
53
                              const std::string &namespaces) {
54
  std::string onlineResource;
55

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

65
  if (!onlineResource.empty() && onlineResource.back() != '?') {
15✔
66
    onlineResource += '?';
67
  }
68

69
  return onlineResource;
15✔
70
}
71

72
static json processCGI(mapObj *map, cgiRequestObj *request) {
9✔
73

74
  json response;
75

76
  // check if CGI modes have been disabled
77
  const char *enable_modes =
78
      msLookupHashTable(&(map->web.metadata), "ms_enable_modes");
9✔
79

80
  int disabled = MS_FALSE;
9✔
81
  msOWSParseRequestMetadata(enable_modes, "BROWSE", &disabled);
9✔
82

83
  if (disabled == MS_FALSE) {
9✔
84
    response["title"] = map->name;
16✔
85

86
    std::string onlineResource = getOnlineResource(map, request, "MCFGOA");
16✔
87

88
    json serviceDocs = json::array(
104✔
89
        {{{"href",
90
           onlineResource + "template=openlayers&mode=browse&layers=all"},
8✔
91
          {"title", "OpenLayers Viewer"},
92
          {"service", "CGI"},
93
          {"type", "text/html"}}});
16✔
94
    response["service-doc"] = serviceDocs;
16✔
95
  }
96

97
  return response;
9✔
98
}
152✔
99

100
static json processOGCAPI(mapObj *map, cgiRequestObj *request) {
9✔
101

102
  json response;
103

104
  const char *title = msOWSLookupMetadata(&(map->web.metadata), "AO", "title");
9✔
105
  if (!title) {
9✔
106
    title = map->name;
5✔
107
  }
108

109
  int status = msOWSRequestIsEnabled(map, NULL, "AO", "OGCAPI", MS_FALSE);
9✔
110
  if (status == MS_TRUE) {
9✔
111
    response["title"] = title;
2✔
112

113
    std::string onlineResource = msOGCAPIGetApiRootUrl(map, request);
2✔
114

115
    // strip any trailing slash as we add it back manually in the hrefs
116
    if (!onlineResource.empty() && onlineResource.back() == '/') {
2✔
117
      onlineResource.pop_back();
118
    }
119

120
    json serviceDescriptions =
121
        json::array({{{"href", onlineResource + "/?f=json"},
28✔
122
                      {"title", "OGC API Root Service"},
123
                      {"service", "OGC API"},
124
                      {"type", "application/json"}}});
4✔
125
    json serviceDocs = json::array({{{"href", onlineResource + "/?f=html"},
28✔
126
                                     {"title", "OGC API Landing Page"},
127
                                     {"service", "OGC API"},
128
                                     {"type", "text/html"}}});
4✔
129
    response["service-desc"] = serviceDescriptions;
2✔
130
    response["service-doc"] = serviceDocs;
4✔
131
  }
132
  return response;
9✔
133
}
76✔
134

135
static json processWMS(mapObj *map, cgiRequestObj *request) {
9✔
136

137
  json response;
138

139
  const char *title = msOWSLookupMetadata(&(map->web.metadata), "MO", "title");
9✔
140
  if (!title) {
9✔
141
    title = map->name;
6✔
142
  }
143

144
  int globally_enabled = MS_FALSE;
145
  int disabled = MS_FALSE;
9✔
146

147
  const char *enable_request =
148
      msOWSLookupMetadata(&map->web.metadata, "MO", "enable_request");
9✔
149

150
  globally_enabled =
151
      msOWSParseRequestMetadata(enable_request, "GetCapabilities", &disabled);
9✔
152

153
  if (globally_enabled == MS_TRUE) {
9✔
154
    response["title"] = title;
3✔
155

156
    std::string onlineResource = getOnlineResource(map, request, "MO");
3✔
157

158
    std::vector<std::string> versions = {"1.0.0", "1.1.0", "1.1.1", "1.3.0"};
3✔
159
    json serviceDescriptions = json::array();
3✔
160

161
    for (const auto &ver : versions) {
15✔
162
      serviceDescriptions.push_back(
156✔
163
          {{"href", std::string(onlineResource) + "version=" + ver +
12✔
164
                        "&request=GetCapabilities&service=WMS"},
165
           {"title", "WMS GetCapabilities URL (version " + ver + ")"},
12✔
166
           {"service", "WMS"},
167
           {"type", "text/xml"}});
168
    }
169
    response["service-desc"] = serviceDescriptions;
6✔
170
  }
3✔
171

172
  return response;
9✔
173
}
180✔
174

175
static json processWFS(mapObj *map, cgiRequestObj *request) {
9✔
176

177
  json response;
178

179
  const char *title = msOWSLookupMetadata(&(map->web.metadata), "FO", "title");
9✔
180
  if (!title) {
9✔
181
    title = map->name;
5✔
182
  }
183

184
  int globally_enabled = MS_FALSE;
185
  int disabled = MS_FALSE;
9✔
186

187
  const char *enable_request =
188
      msOWSLookupMetadata(&map->web.metadata, "FO", "enable_request");
9✔
189

190
  globally_enabled =
191
      msOWSParseRequestMetadata(enable_request, "GetCapabilities", &disabled);
9✔
192

193
  if (globally_enabled == MS_TRUE) {
9✔
194
    response["title"] = title;
2✔
195

196
    std::string onlineResource = getOnlineResource(map, request, "MO");
2✔
197

198
    std::vector<std::string> versions = {"1.0.0", "1.1.0", "2.0.0"};
2✔
199
    json serviceDescriptions = json::array();
2✔
200

201
    for (const auto &ver : versions) {
8✔
202
      serviceDescriptions.push_back(
78✔
203
          {{"href", onlineResource + "version=" + ver +
6✔
204
                        "&request=GetCapabilities&service=WFS"},
205
           {"title", "WFS GetCapabilities URL (version " + ver + ")"},
6✔
206
           {"service", "WFS"},
207
           {"type", "text/xml"}});
208
    }
209
    response["service-desc"] = serviceDescriptions;
4✔
210
  }
2✔
211

212
  return response;
9✔
213
}
90✔
214

215
static json processWCS(mapObj *map, cgiRequestObj *request) {
9✔
216

217
  json response;
218

219
  const char *title = msOWSLookupMetadata(&(map->web.metadata), "CO", "title");
9✔
220
  if (!title) {
9✔
221
    title = map->name;
6✔
222
  }
223

224
  int globally_enabled = MS_FALSE;
225
  int disabled = MS_FALSE;
9✔
226

227
  const char *enable_request =
228
      msOWSLookupMetadata(&map->web.metadata, "CO", "enable_request");
9✔
229

230
  globally_enabled =
231
      msOWSParseRequestMetadata(enable_request, "GetCapabilities", &disabled);
9✔
232

233
  if (globally_enabled == MS_TRUE) {
9✔
234
    response["title"] = title;
2✔
235

236
    std::string onlineResource = getOnlineResource(map, request, "CO");
2✔
237

238
    std::vector<std::string> versions = {"1.0.0", "1.1.0", "2.0.0", "2.0.1"};
2✔
239
    json serviceDescriptions = json::array();
2✔
240

241
    for (auto &ver : versions) {
10✔
242
      serviceDescriptions.push_back(
104✔
243
          {{"href", onlineResource + "version=" + ver +
8✔
244
                        "&request=GetCapabilities&service=WCS"},
245
           {"title", "WCS GetCapabilities URL (version " + ver + ")"},
8✔
246
           {"service", "WCS"},
247
           {"type", "text/xml"}});
248
    }
249
    response["service-desc"] = serviceDescriptions;
4✔
250
  }
2✔
251
  return response;
9✔
252
}
120✔
253

254
static json createMapDetails(mapObj *map, cgiRequestObj *request) {
9✔
255

256
  json links = json::array();
9✔
257
  json result;
258

259
  result = processCGI(map, request);
18✔
260
  if (!result.empty()) {
8✔
261
    links.push_back(result);
8✔
262
  }
263

264
#ifdef USE_OGCAPI_SVR
265
  result = processOGCAPI(map, request);
18✔
266
  if (!result.empty()) {
2✔
267
    links.push_back(result);
2✔
268
  }
269
#endif
270
#ifdef USE_WMS_SVR
271
  result = processWMS(map, request);
18✔
272
  if (!result.empty()) {
3✔
273
    links.push_back(result);
3✔
274
  }
275
#endif
276
#ifdef USE_WFS_SVR
277
  result = processWFS(map, request);
18✔
278
  if (!result.empty()) {
2✔
279
    links.push_back(result);
2✔
280
  }
281
#endif
282
#ifdef USE_WCS_SVR
283
  result = processWCS(map, request);
18✔
284
  if (!result.empty()) {
2✔
285
    links.push_back(result);
2✔
286
  }
287
#endif
288

289
  return {{"linkset", links}};
45✔
290
}
36✔
291

292
/**
293
 * Create a JSON object with a summary of the Mapfile
294
 **/
295
static json createMapSummary(mapObj *map, const char *key,
23✔
296
                             cgiRequestObj *request) {
297
  json mapJson;
298
  const char *value =
299
      msOWSLookupMetadata(&(map->web.metadata), "MCFGOA", "title");
23✔
300

301
  mapJson["key"] = key;
46✔
302
  mapJson["has-error"] = false;
23✔
303

304
  if (value) {
23✔
305
    mapJson["title"] = value;
30✔
306
  } else {
307
    mapJson["title"] = map->name;
24✔
308
  }
309

310
  mapJson["layer-count"] = map->numlayers;
23✔
311
  mapJson["name"] = map->name;
69✔
312

313
  std::string onlineResource;
314

315
  if (char *res = msBuildOnlineResource(NULL, request)) {
23✔
316
    onlineResource = res;
317
    free(res);
×
318

319
    // remove trailing '?' if present
320
    if (!onlineResource.empty() && onlineResource.back() == '?') {
×
321
      onlineResource.pop_back();
322
    }
323
  } else {
324
    // use a relative URL if no onlineresource cannot be created
325
    onlineResource = "./"; // fallback
326
  }
327

328
  mapJson["service-desc"] =
23✔
329
      json::array({{{"href", onlineResource + std::string(key) + "/?f=json"},
299✔
330
                    {"title", key},
331
                    {"type", "application/vnd.oai.openapi+json"}}});
332

333
  mapJson["service-doc"] =
23✔
334
      json::array({{{"href", onlineResource + std::string(key) + "/"},
299✔
335
                    {"title", key},
336
                    {"type", "text/html"}}});
337

338
  return mapJson;
23✔
339
}
690✔
340

341
/**
342
 * For invalid maps return a JSON object reporting the error
343
 **/
344
static json createMapError(const char *key) {
2✔
345
  json mapJson;
346
  mapJson["key"] = key;
4✔
347
  mapJson["has-error"] = true;
4✔
348
  mapJson["title"] = " <b>Error loading the map</b>";
4✔
349
  mapJson["layer-count"] = 0;
4✔
350
  mapJson["name"] = key;
4✔
351
  mapJson["service-desc"] =
2✔
352
      json::array({{{"href", "./" + std::string(key) + "/?f=json"},
26✔
353
                    {"title", key},
354
                    {"type", "application/vnd.oai.openapi+json"}}});
355

356
  mapJson["service-doc"] =
2✔
357
      json::array({{{"href", "./" + std::string(key) + "/"},
26✔
358
                    {"title", key},
359
                    {"type", "text/html"}}});
360

361
  return mapJson;
2✔
362
}
60✔
363

364
/**
365
 * Load the Map for the key, by getting its file path
366
 * from the CONFIG file
367
 */
368
static mapObj *getMapFromConfig(configObj *config, const char *key) {
25✔
369
  const char *mapfilePath = msLookupHashTable(&config->maps, key);
25✔
370

371
  if (!mapfilePath)
25✔
372
    return nullptr;
373

374
  char pathBuf[MS_MAXPATHLEN];
375
  mapfilePath = msConfigGetMap(config, key, pathBuf);
25✔
376

377
  if (!mapfilePath) {
25✔
378
    return nullptr;
379
  }
380
  char *mapfilePathCopy = msStrdup(mapfilePath);
25✔
381
  mapObj *map = msLoadMap(mapfilePathCopy, nullptr, config);
25✔
382
  msFree(mapfilePathCopy);
25✔
383

384
  return map;
25✔
385
}
386

387
/**
388
 * Generate HTML by populating a template with the dynamic JSON
389
 */
390
static int createHTMLOutput(json response, const char *templateName) {
2✔
391
  std::string path;
392
  char fullpath[MS_MAXPATHLEN];
393

394
  path = msOGCAPIGetTemplateDirectory(NULL, "html_template_directory",
4✔
395
                                      "MS_INDEX_TEMPLATE_DIRECTORY");
2✔
396
  if (path.empty()) {
2✔
397
    msOGCAPIOutputError(OGCAPI_CONFIG_ERROR, "Template directory not set.");
×
398
    return MS_FAILURE;
×
399
  }
400
  msBuildPath(fullpath, NULL, path.c_str());
2✔
401

402
  json j = {{"response", response}};
8✔
403
  msOGCAPIOutputTemplate(fullpath, templateName, j, OGCAPI_MIMETYPE_HTML);
2✔
404

405
  return MS_SUCCESS;
406
}
8✔
407

408
/**
409
 * Return an individual map landing page response
410
 */
411
int msOGCAPIDispatchMapIndexRequest(mapservObj *mapserv, configObj *config) {
9✔
412
#ifdef USE_OGCAPI_SVR
413
  if (!mapserv || !config)
9✔
414
    return -1; // Handle null pointers
415

416
  cgiRequestObj *request = mapserv->request;
9✔
417

418
  if (request->api_path_length != 1) {
9✔
419
    const char *pathInfo = getenv("PATH_INFO");
×
420
    msSetError(MS_OGCAPIERR, "Invalid PATH_INFO format: \"%s\"",
×
421
               "msOGCAPIDispatchMapIndexRequest()", pathInfo);
422
    return MS_FAILURE;
×
423
  }
424

425
  OGCAPIFormat format = msOGCAPIGetOutputFormat(request);
9✔
426
  const char *key = request->api_path[0];
9✔
427

428
  mapObj *map = getMapFromConfig(config, key);
9✔
429
  if (!map) {
9✔
NEW
430
    msOGCAPIOutputError(OGCAPI_CONFIG_ERROR, "Mapfile not found in config.");
×
NEW
431
    return MS_FAILURE;
×
432
  }
433
  json response = createMapDetails(map, request);
9✔
434

435
  // add in summary map details
436
  json summary = createMapSummary(map, key, request);
9✔
437

438
  // remove unwanted properties, before merging objects
439
  summary.erase("service-desc");
9✔
440
  summary.erase("service-doc");
9✔
441

442
  response.update(summary);
9✔
443

444
  if (format == OGCAPIFormat::JSON) {
9✔
445
    msOGCAPIOutputJson(response, OGCAPI_MIMETYPE_JSON, {});
16✔
446
  } else if (format == OGCAPIFormat::HTML) {
1✔
447
    createHTMLOutput(response, TEMPLATE_HTML_MAP_INDEX);
2✔
448
  } else {
449
    msOGCAPIOutputError(OGCAPI_PARAM_ERROR, "Unsupported format requested.");
×
450
  }
451

452
  return MS_SUCCESS;
453

454
#else
455
  msSetError(MS_OGCAPIERR, "OGC API server support is not enabled.",
456
             "msOGCAPIDispatchMapIndexRequest()");
457
  return MS_FAILURE;
458
#endif
459
}
460

461
/**
462
 * Return the MapServer landing page response
463
 */
464
int msOGCAPIDispatchIndexRequest(mapservObj *mapserv, configObj *config) {
2✔
465
#ifdef USE_OGCAPI_SVR
466
  if (!mapserv || !config)
2✔
467
    return -1; // Handle null pointers
468

469
  cgiRequestObj *request = mapserv->request;
2✔
470
  const OGCAPIFormat format = msOGCAPIGetOutputFormat(request);
2✔
471

472
  const char *key = NULL;
473
  json links = json::array();
2✔
474

475
  while ((key = msNextKeyFromHashTable(&config->maps, key)) != NULL) {
18✔
476
    if (mapObj *map = getMapFromConfig(config, key)) {
16✔
477
      links.push_back(createMapSummary(map, key, request));
14✔
478
      msFreeMap(map);
14✔
479
    } else {
480
      // there was a problem loading the map
481
      links.push_back(createMapError(key));
4✔
482
    }
483
  }
484

485
  json response = {{"linkset", links}};
8✔
486

487
  if (format == OGCAPIFormat::JSON) {
2✔
488
    msOGCAPIOutputJson(response, OGCAPI_MIMETYPE_JSON, {});
2✔
489
  } else if (format == OGCAPIFormat::HTML) {
1✔
490
    createHTMLOutput(response, TEMPLATE_HTML_INDEX);
2✔
491
  } else {
492
    msOGCAPIOutputError(OGCAPI_PARAM_ERROR, "Unsupported format requested.");
×
493
  }
494

495
  return MS_SUCCESS;
496

497
#else
498
  msSetError(MS_OGCAPIERR, "OGC API server support is not enabled.",
499
             "msOGCAPIDispatchIndexRequest()");
500
  return MS_FAILURE;
501
#endif
502
}
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