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

geographika / mapserver / 19379829740

14 Nov 2025 10:54PM UTC coverage: 41.713%. First build
19379829740

push

github

geographika
Always ensure a trailing slash

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

62786 of 150518 relevant lines covered (41.71%)

25208.64 hits per line

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

93.69
/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
  // make sure that URLs end with '/'
329
  if (!onlineResource.empty() && onlineResource != "./" &&
23✔
NEW
330
      onlineResource.back() != '/') {
×
331
    onlineResource += '/';
332
  }
333

334
  mapJson["service-desc"] =
23✔
335
      json::array({{{"href", onlineResource + std::string(key) + "/?f=json"},
299✔
336
                    {"title", key},
337
                    {"type", "application/vnd.oai.openapi+json"}}});
338

339
  mapJson["service-doc"] =
23✔
340
      json::array({{{"href", onlineResource + std::string(key) + "/"},
299✔
341
                    {"title", key},
342
                    {"type", "text/html"}}});
343

344
  return mapJson;
23✔
345
}
644✔
346

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

362
  mapJson["service-doc"] =
2✔
363
      json::array({{{"href", "./" + std::string(key) + "/"},
26✔
364
                    {"title", key},
365
                    {"type", "text/html"}}});
366

367
  return mapJson;
2✔
368
}
60✔
369

370
/**
371
 * Load the Map for the key, by getting its file path
372
 * from the CONFIG file
373
 */
374
static mapObj *getMapFromConfig(configObj *config, const char *key) {
25✔
375
  const char *mapfilePath = msLookupHashTable(&config->maps, key);
25✔
376

377
  if (!mapfilePath)
25✔
378
    return nullptr;
379

380
  char pathBuf[MS_MAXPATHLEN];
381
  mapfilePath = msConfigGetMap(config, key, pathBuf);
25✔
382

383
  if (!mapfilePath) {
25✔
384
    return nullptr;
385
  }
386
  char *mapfilePathCopy = msStrdup(mapfilePath);
25✔
387
  mapObj *map = msLoadMap(mapfilePathCopy, nullptr, config);
25✔
388
  msFree(mapfilePathCopy);
25✔
389

390
  return map;
25✔
391
}
392

393
/**
394
 * Generate HTML by populating a template with the dynamic JSON
395
 */
396
static int createHTMLOutput(json response, const char *templateName) {
2✔
397
  std::string path;
398
  char fullpath[MS_MAXPATHLEN];
399

400
  path = msOGCAPIGetTemplateDirectory(NULL, "html_template_directory",
4✔
401
                                      "MS_INDEX_TEMPLATE_DIRECTORY");
2✔
402
  if (path.empty()) {
2✔
403
    msOGCAPIOutputError(OGCAPI_CONFIG_ERROR, "Template directory not set.");
×
404
    return MS_FAILURE;
×
405
  }
406
  msBuildPath(fullpath, NULL, path.c_str());
2✔
407

408
  json j = {{"response", response}};
8✔
409
  msOGCAPIOutputTemplate(fullpath, templateName, j, OGCAPI_MIMETYPE_HTML);
2✔
410

411
  return MS_SUCCESS;
412
}
8✔
413

414
/**
415
 * Return an individual map landing page response
416
 */
417
int msOGCAPIDispatchMapIndexRequest(mapservObj *mapserv, configObj *config) {
9✔
418
#ifdef USE_OGCAPI_SVR
419
  if (!mapserv || !config)
9✔
420
    return -1; // Handle null pointers
421

422
  cgiRequestObj *request = mapserv->request;
9✔
423

424
  if (request->api_path_length != 1) {
9✔
425
    const char *pathInfo = getenv("PATH_INFO");
×
426
    msSetError(MS_OGCAPIERR, "Invalid PATH_INFO format: \"%s\"",
×
427
               "msOGCAPIDispatchMapIndexRequest()", pathInfo);
428
    return MS_FAILURE;
×
429
  }
430

431
  OGCAPIFormat format = msOGCAPIGetOutputFormat(request);
9✔
432
  const char *key = request->api_path[0];
9✔
433

434
  mapObj *map = getMapFromConfig(config, key);
9✔
435
  if (!map) {
9✔
436
    msOGCAPIOutputError(OGCAPI_CONFIG_ERROR, "Mapfile not found in config.");
×
437
    return MS_FAILURE;
×
438
  }
439
  json response = createMapDetails(map, request);
9✔
440

441
  // add in summary map details
442
  json summary = createMapSummary(map, key, request);
9✔
443

444
  // remove unwanted properties, before merging objects
445
  summary.erase("service-desc");
9✔
446
  summary.erase("service-doc");
9✔
447

448
  response.update(summary);
9✔
449

450
  if (format == OGCAPIFormat::JSON) {
9✔
451
    msOGCAPIOutputJson(response, OGCAPI_MIMETYPE_JSON, {});
16✔
452
  } else if (format == OGCAPIFormat::HTML) {
1✔
453
    createHTMLOutput(response, TEMPLATE_HTML_MAP_INDEX);
2✔
454
  } else {
455
    msOGCAPIOutputError(OGCAPI_PARAM_ERROR, "Unsupported format requested.");
×
456
  }
457

458
  msFreeMap(map);
9✔
459
  return MS_SUCCESS;
460

461
#else
462
  msSetError(MS_OGCAPIERR, "OGC API server support is not enabled.",
463
             "msOGCAPIDispatchMapIndexRequest()");
464
  return MS_FAILURE;
465
#endif
466
}
467

468
/**
469
 * Return the MapServer landing page response
470
 */
471
int msOGCAPIDispatchIndexRequest(mapservObj *mapserv, configObj *config) {
2✔
472
#ifdef USE_OGCAPI_SVR
473
  if (!mapserv || !config)
2✔
474
    return -1; // Handle null pointers
475

476
  cgiRequestObj *request = mapserv->request;
2✔
477
  const OGCAPIFormat format = msOGCAPIGetOutputFormat(request);
2✔
478

479
  const char *key = NULL;
480
  json links = json::array();
2✔
481

482
  while ((key = msNextKeyFromHashTable(&config->maps, key)) != NULL) {
18✔
483
    if (mapObj *map = getMapFromConfig(config, key)) {
16✔
484
      links.push_back(createMapSummary(map, key, request));
14✔
485
      msFreeMap(map);
14✔
486
    } else {
487
      // there was a problem loading the map
488
      links.push_back(createMapError(key));
4✔
489
    }
490
  }
491

492
  json response = {{"linkset", links}};
8✔
493

494
  if (format == OGCAPIFormat::JSON) {
2✔
495
    msOGCAPIOutputJson(response, OGCAPI_MIMETYPE_JSON, {});
2✔
496
  } else if (format == OGCAPIFormat::HTML) {
1✔
497
    createHTMLOutput(response, TEMPLATE_HTML_INDEX);
2✔
498
  } else {
499
    msOGCAPIOutputError(OGCAPI_PARAM_ERROR, "Unsupported format requested.");
×
500
  }
501

502
  return MS_SUCCESS;
503

504
#else
505
  msSetError(MS_OGCAPIERR, "OGC API server support is not enabled.",
506
             "msOGCAPIDispatchIndexRequest()");
507
  return MS_FAILURE;
508
#endif
509
}
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