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

geographika / mapserver / 18178597241

01 Oct 2025 11:29PM UTC coverage: 41.556% (+0.001%) from 41.555%
18178597241

push

github

geographika
Fix scope

1 of 1 new or added line in 1 file covered. (100.0%)

166 existing lines in 4 files now uncovered.

62299 of 149917 relevant lines covered (41.56%)

25033.5 hits per line

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

94.9
/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
  return msLoadMap(mapfilePath, nullptr, config);
25✔
378
}
379

380
/**
381
 * Generate HTML by populating a template with the dynamic JSON
382
 */
383
static int createHTMLOutput(json response, const char *templateName) {
2✔
384
  std::string path;
385
  char fullpath[MS_MAXPATHLEN];
386

387
  path = msOGCAPIGetTemplateDirectory(NULL, "html_template_directory",
4✔
388
                                      "MS_INDEX_TEMPLATE_DIRECTORY");
2✔
389
  if (path.empty()) {
2✔
390
    msOGCAPIOutputError(OGCAPI_CONFIG_ERROR, "Template directory not set.");
×
391
    return MS_FAILURE;
×
392
  }
393
  msBuildPath(fullpath, NULL, path.c_str());
2✔
394

395
  json j = {{"response", response}};
8✔
396
  msOGCAPIOutputTemplate(fullpath, templateName, j, OGCAPI_MIMETYPE_HTML);
2✔
397

398
  return MS_SUCCESS;
399
}
8✔
400

401
/**
402
 * Return an individual map landing page response
403
 */
404
int msOGCAPIDispatchMapIndexRequest(mapservObj *mapserv, configObj *config) {
9✔
405
#ifdef USE_OGCAPI_SVR
406
  if (!mapserv || !config)
9✔
407
    return -1; // Handle null pointers
408

409
  cgiRequestObj *request = mapserv->request;
9✔
410

411
  if (request->api_path_length != 1) {
9✔
412
    const char *pathInfo = getenv("PATH_INFO");
×
413
    msSetError(MS_OGCAPIERR, "Invalid PATH_INFO format: \"%s\"",
×
414
               "msOGCAPIDispatchMapIndexRequest()", pathInfo);
415
    return MS_FAILURE;
×
416
  }
417

418
  OGCAPIFormat format = msOGCAPIGetOutputFormat(request);
9✔
419
  const char *key = request->api_path[0];
9✔
420

421
  mapObj *map = getMapFromConfig(config, key);
9✔
422
  json response = createMapDetails(map, request);
9✔
423

424
  // add in summary map details
425
  json summary = createMapSummary(map, key, request);
9✔
426

427
  // remove unwanted properties, before merging objects
428
  summary.erase("service-desc");
9✔
429
  summary.erase("service-doc");
9✔
430

431
  response.update(summary);
9✔
432

433
  if (format == OGCAPIFormat::JSON) {
9✔
434
    msOGCAPIOutputJson(response, OGCAPI_MIMETYPE_JSON, {});
16✔
435
  } else if (format == OGCAPIFormat::HTML) {
1✔
436
    createHTMLOutput(response, TEMPLATE_HTML_MAP_INDEX);
2✔
437
  } else {
438
    msOGCAPIOutputError(OGCAPI_PARAM_ERROR, "Unsupported format requested.");
×
439
  }
440

441
  return MS_SUCCESS;
442

443
#else
444
  msSetError(MS_OGCAPIERR, "OGC API server support is not enabled.",
445
             "msOGCAPIDispatchMapIndexRequest()");
446
  return MS_FAILURE;
447
#endif
448
}
449

450
/**
451
 * Return the MapServer landing page response
452
 */
453
int msOGCAPIDispatchIndexRequest(mapservObj *mapserv, configObj *config) {
2✔
454
#ifdef USE_OGCAPI_SVR
455
  if (!mapserv || !config)
2✔
456
    return -1; // Handle null pointers
457

458
  cgiRequestObj *request = mapserv->request;
2✔
459
  const OGCAPIFormat format = msOGCAPIGetOutputFormat(request);
2✔
460

461
  const char *key = NULL;
462
  json links = json::array();
2✔
463

464
  while ((key = msNextKeyFromHashTable(&config->maps, key)) != NULL) {
18✔
465
    if (mapObj *map = getMapFromConfig(config, key)) {
16✔
466
      links.push_back(createMapSummary(map, key, request));
14✔
467
      msFreeMap(map);
14✔
468
    } else {
469
      // there was a problem loading the map
470
      links.push_back(createMapError(key));
4✔
471
    }
472
  }
473

474
  json response = {{"linkset", links}};
8✔
475

476
  if (format == OGCAPIFormat::JSON) {
2✔
477
    msOGCAPIOutputJson(response, OGCAPI_MIMETYPE_JSON, {});
2✔
478
  } else if (format == OGCAPIFormat::HTML) {
1✔
479
    createHTMLOutput(response, TEMPLATE_HTML_INDEX);
2✔
480
  } else {
UNCOV
481
    msOGCAPIOutputError(OGCAPI_PARAM_ERROR, "Unsupported format requested.");
×
482
  }
483

484
  return MS_SUCCESS;
485

486
#else
487
  msSetError(MS_OGCAPIERR, "OGC API server support is not enabled.",
488
             "msOGCAPIDispatchIndexRequest()");
489
  return MS_FAILURE;
490
#endif
491
}
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