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

geographika / mapserver / 28017742761

23 Jun 2026 09:51AM UTC coverage: 42.467% (+0.01%) from 42.453%
28017742761

push

github

geographika
Add FID tests

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

535 existing lines in 4 files now uncovered.

64718 of 152397 relevant lines covered (42.47%)

27423.8 hits per line

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

95.39
/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 <algorithm>
29
#include <string>
30
#include <vector>
31

32
#include "mapserver.h"
33
#include "mapogcapi.h"
34
#include "mapserv-index.h"
35
#include "maptemplate.h"
36
#include "mapows.h"
37
#include "mapserv-config.h"
38

39
#include "cpl_conv.h"
40

41
#include "third-party/include_nlohmann_json.hpp"
42
#include "third-party/include_pantor_inja.hpp"
43

44
using namespace inja;
45
using json = nlohmann::json;
46

47
/*
48
** HTML Templates
49
*/
50
#define TEMPLATE_HTML_INDEX "landing.html"
51
#define TEMPLATE_HTML_MAP_INDEX "map.html"
52

53
/**
54
 * Get an online resource using the supplied namespaces
55
 */
56
std::string getOnlineResource(mapObj *map, cgiRequestObj *request,
31✔
57
                              const std::string &namespaces) {
58
  std::string onlineResource;
59

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

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

82
  return onlineResource;
31✔
83
}
84

85
static json processCGI(mapObj *map, cgiRequestObj *request) {
15✔
86

87
  json response;
88

89
  // check if CGI modes have been disabled
90
  const char *enable_modes =
91
      msLookupHashTable(&(map->web.metadata), "ms_enable_modes");
15✔
92

93
  int disabled = MS_FALSE;
15✔
94
  msOWSParseRequestMetadata(enable_modes, "BROWSE", &disabled);
15✔
95

96
  if (disabled == MS_FALSE) {
15✔
97
    response["title"] = map->name;
28✔
98

99
    std::string onlineResource = getOnlineResource(map, request, "MCFGOA");
28✔
100

101
    json serviceDocs = json::array(
182✔
102
        {{{"href",
103
           onlineResource + "template=openlayers&mode=browse&layers=all"},
14✔
104
          {"title", "OpenLayers Viewer"},
105
          {"service", "CGI"},
106
          {"type", "text/html"}}});
28✔
107
    response["service-doc"] = serviceDocs;
28✔
108
  }
109

110
  return response;
15✔
111
}
266✔
112

113
static json processOGCAPI(mapObj *map, cgiRequestObj *request) {
15✔
114

115
  json response;
116

117
  const char *title = msOWSLookupMetadata(&(map->web.metadata), "AO", "title");
15✔
118
  if (!title) {
15✔
119
    title = map->name;
5✔
120
  }
121

122
  int status = msOWSRequestIsEnabled(map, NULL, "AO", "OGCAPI", MS_FALSE);
15✔
123
  if (status == MS_TRUE) {
15✔
124
    response["title"] = title;
4✔
125

126
    std::string onlineResource = msOGCAPIGetApiRootUrl(map, request);
4✔
127

128
    // strip any trailing slash as we add it back manually in the hrefs
129
    if (!onlineResource.empty() && onlineResource.back() == '/') {
4✔
130
      onlineResource.pop_back();
131
    }
132

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

148
static json processWMS(mapObj *map, cgiRequestObj *request) {
15✔
149

150
  json response;
151

152
  const char *title = msOWSLookupMetadata(&(map->web.metadata), "MO", "title");
15✔
153
  if (!title) {
15✔
154
    title = map->name;
6✔
155
  }
156

157
  int globally_enabled = MS_FALSE;
158
  int disabled = MS_FALSE;
15✔
159

160
  const char *enable_request =
161
      msOWSLookupMetadata(&map->web.metadata, "MO", "enable_request");
15✔
162

163
  globally_enabled =
164
      msOWSParseRequestMetadata(enable_request, "GetCapabilities", &disabled);
15✔
165

166
  if (globally_enabled == MS_TRUE) {
15✔
167
    response["title"] = title;
9✔
168

169
    std::string onlineResource = getOnlineResource(map, request, "MO");
9✔
170

171
    std::vector<std::string> versions = {"1.0.0", "1.1.0", "1.1.1", "1.3.0"};
9✔
172
    json serviceDescriptions = json::array();
9✔
173

174
    for (const auto &ver : versions) {
45✔
175
      serviceDescriptions.push_back(
468✔
176
          {{"href", std::string(onlineResource) + "version=" + ver +
36✔
177
                        "&request=GetCapabilities&service=WMS"},
178
           {"title", "WMS GetCapabilities URL (version " + ver + ")"},
36✔
179
           {"service", "WMS"},
180
           {"type", "text/xml"}});
181
    }
182
    response["service-desc"] = serviceDescriptions;
18✔
183
  }
9✔
184

185
  return response;
15✔
186
}
540✔
187

188
static json processWFS(mapObj *map, cgiRequestObj *request) {
15✔
189

190
  json response;
191

192
  const char *title = msOWSLookupMetadata(&(map->web.metadata), "FO", "title");
15✔
193
  if (!title) {
15✔
194
    title = map->name;
5✔
195
  }
196

197
  int globally_enabled = MS_FALSE;
198
  int disabled = MS_FALSE;
15✔
199

200
  const char *enable_request =
201
      msOWSLookupMetadata(&map->web.metadata, "FO", "enable_request");
15✔
202

203
  globally_enabled =
204
      msOWSParseRequestMetadata(enable_request, "GetCapabilities", &disabled);
15✔
205

206
  if (globally_enabled == MS_TRUE) {
15✔
207
    response["title"] = title;
4✔
208

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

211
    std::vector<std::string> versions = {"1.0.0", "1.1.0", "2.0.0"};
4✔
212
    json serviceDescriptions = json::array();
4✔
213

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

225
  return response;
15✔
226
}
180✔
227

228
static json processWCS(mapObj *map, cgiRequestObj *request) {
15✔
229

230
  json response;
231

232
  const char *title = msOWSLookupMetadata(&(map->web.metadata), "CO", "title");
15✔
233
  if (!title) {
15✔
234
    title = map->name;
6✔
235
  }
236

237
  int globally_enabled = MS_FALSE;
238
  int disabled = MS_FALSE;
15✔
239

240
  const char *enable_request =
241
      msOWSLookupMetadata(&map->web.metadata, "CO", "enable_request");
15✔
242

243
  globally_enabled =
244
      msOWSParseRequestMetadata(enable_request, "GetCapabilities", &disabled);
15✔
245

246
  if (globally_enabled == MS_TRUE) {
15✔
247
    response["title"] = title;
4✔
248

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

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

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

267
static json createMapDetails(mapObj *map, cgiRequestObj *request) {
15✔
268

269
  json links = json::array();
15✔
270
  json result;
271

272
  result = processCGI(map, request);
30✔
273
  if (!result.empty()) {
14✔
274
    links.push_back(result);
14✔
275
  }
276

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

302
  return {{"linkset", links}};
75✔
303
}
60✔
304

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

314
  mapJson["key"] = key;
62✔
315
  mapJson["has-error"] = false;
31✔
316

317
  if (value) {
31✔
318
    mapJson["title"] = value;
46✔
319
  } else {
320
    mapJson["title"] = map->name;
24✔
321
  }
322

323
  mapJson["layer-count"] = map->numlayers;
31✔
324
  mapJson["name"] = map->name;
93✔
325

326
  std::string onlineResource;
327

328
  if (char *res = msBuildOnlineResource(NULL, request)) {
31✔
329
    onlineResource = res;
330
    free(res);
3✔
331

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

341
  // make sure the root URL end with a '/'
342
  if (!onlineResource.empty() && onlineResource.back() != '/') {
31✔
343
    onlineResource += '/';
344
  }
345

346
  mapJson["service-desc"] =
31✔
347
      json::array({{{"href", onlineResource + std::string(key) + "/?f=json"},
403✔
348
                    {"title", key},
349
                    {"type", "application/vnd.oai.openapi+json"}}});
350

351
  mapJson["service-doc"] =
31✔
352
      json::array({{{"href", onlineResource + std::string(key) + "/"},
403✔
353
                    {"title", key},
354
                    {"type", "text/html"}}});
355

356
  return mapJson;
31✔
357
}
930✔
358

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

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

379
  return mapJson;
2✔
380
}
60✔
381

382
/**
383
 * Load the Map for the key, by getting its file path
384
 * from the CONFIG file
385
 */
386
static mapObj *getMapFromConfig(configObj *config, const char *key) {
33✔
387
  const char *mapfilePath = msLookupHashTable(&config->maps, key);
33✔
388

389
  if (!mapfilePath)
33✔
390
    return nullptr;
391

392
  char pathBuf[MS_MAXPATHLEN];
393
  mapfilePath = msConfigGetMap(config, key, pathBuf);
33✔
394

395
  if (!mapfilePath) {
33✔
396
    return nullptr;
397
  }
398
  char *mapfilePathCopy = msStrdup(mapfilePath);
33✔
399
  mapObj *map = msLoadMap(mapfilePathCopy, nullptr, config);
33✔
400
  msFree(mapfilePathCopy);
33✔
401

402
  return map;
33✔
403
}
404

405
/**
406
 * Generate HTML by populating a template with the dynamic JSON
407
 */
408
static int createHTMLOutput(json response, const char *templateName) {
3✔
409
  std::string path;
410
  char fullpath[MS_MAXPATHLEN];
411

412
  path = msOGCAPIGetTemplateDirectory(NULL, "html_template_directory",
6✔
413
                                      "MS_INDEX_TEMPLATE_DIRECTORY");
3✔
414
  if (path.empty()) {
3✔
UNCOV
415
    msOGCAPIOutputError(OGCAPI_CONFIG_ERROR, "Template directory not set.");
×
UNCOV
416
    return MS_FAILURE;
×
417
  }
418
  msBuildPath(fullpath, NULL, path.c_str());
3✔
419

420
  json j = {{"response", response}};
12✔
421
  msOGCAPIOutputTemplate(fullpath, templateName, j, OGCAPI_MIMETYPE_HTML);
3✔
422

423
  return MS_SUCCESS;
424
}
12✔
425

426
/**
427
 * Return an individual map landing page response
428
 */
429
int msOGCAPIDispatchMapIndexRequest(mapservObj *mapserv, configObj *config) {
15✔
430
#ifdef USE_OGCAPI_SVR
431
  if (!mapserv || !config)
15✔
432
    return MS_FAILURE; // Handle null pointers
433

434
  cgiRequestObj *request = mapserv->request;
15✔
435

436
  if (request->api_path_length != 1) {
15✔
UNCOV
437
    const char *pathInfo = getenv("PATH_INFO");
×
UNCOV
438
    msSetError(MS_OGCAPIERR, "Invalid PATH_INFO format: \"%s\"",
×
439
               "msOGCAPIDispatchMapIndexRequest()", pathInfo);
UNCOV
440
    return MS_FAILURE;
×
441
  }
442

443
  OGCAPIFormat format = msOGCAPIGetOutputFormat(request);
15✔
444

445
  if (format == OGCAPIFormat::Invalid) {
15✔
UNCOV
446
    msOGCAPIOutputError(OGCAPI_PARAM_ERROR, "Unsupported format requested.");
×
UNCOV
447
    return MS_FAILURE;
×
448
  }
449

450
  const char *key = request->api_path[0];
15✔
451

452
  mapObj *map = getMapFromConfig(config, key);
15✔
453
  if (!map) {
15✔
UNCOV
454
    msOGCAPIOutputError(OGCAPI_CONFIG_ERROR, "Mapfile not found in config.");
×
UNCOV
455
    return MS_FAILURE;
×
456
  }
457
  json response = createMapDetails(map, request);
15✔
458

459
  // add in summary map details
460
  json summary = createMapSummary(map, key, request);
15✔
461

462
  // remove unwanted properties, before merging objects
463
  summary.erase("service-desc");
15✔
464
  summary.erase("service-doc");
15✔
465

466
  response.update(summary);
15✔
467

468
  if (format == OGCAPIFormat::JSON) {
15✔
469
    msOGCAPIOutputJson(response, OGCAPI_MIMETYPE_JSON, {});
26✔
470
  } else if (format == OGCAPIFormat::HTML) {
2✔
471
    createHTMLOutput(response, TEMPLATE_HTML_MAP_INDEX);
4✔
472
  } else {
UNCOV
473
    msOGCAPIOutputError(OGCAPI_PARAM_ERROR, "Unsupported format requested.");
×
474
  }
475

476
  msFreeMap(map);
15✔
477
  return MS_SUCCESS;
478

479
#else
480
  msSetError(MS_OGCAPIERR, "OGC API server support is not enabled.",
481
             "msOGCAPIDispatchMapIndexRequest()");
482
  return MS_FAILURE;
483
#endif
484
}
485

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

494
  cgiRequestObj *request = mapserv->request;
3✔
495
  const OGCAPIFormat format = msOGCAPIGetOutputFormat(request);
3✔
496

497
  if (format == OGCAPIFormat::Invalid) {
3✔
498
    msOGCAPIOutputError(OGCAPI_PARAM_ERROR, "Unsupported format requested.");
1✔
499
    return MS_FAILURE;
1✔
500
  }
501

502
  // Collect keys and sort alphabetically (case-insensitive)
503
  std::vector<std::string> keys;
504
  const char *key = NULL;
505
  while ((key = msNextKeyFromHashTable(&config->maps, key)) != NULL) {
20✔
506
    keys.push_back(key);
36✔
507
  }
508
  std::sort(keys.begin(), keys.end(),
2✔
509
            [](const std::string &a, const std::string &b) {
510
              return strcasecmp(a.c_str(), b.c_str()) < 0;
64✔
511
            });
512

513
  json links = json::array();
2✔
514
  for (const auto &k : keys) {
20✔
515
    if (mapObj *map = getMapFromConfig(config, k.c_str())) {
18✔
516
      links.push_back(createMapSummary(map, k.c_str(), request));
16✔
517
      msFreeMap(map);
16✔
518
    } else {
519
      // there was a problem loading the map
520
      links.push_back(createMapError(k.c_str()));
4✔
521
    }
522
  }
523

524
  json response = {{"linkset", links}};
8✔
525

526
  if (format == OGCAPIFormat::JSON) {
2✔
527
    msOGCAPIOutputJson(response, OGCAPI_MIMETYPE_JSON, {});
2✔
528
  } else if (format == OGCAPIFormat::HTML) {
1✔
529
    createHTMLOutput(response, TEMPLATE_HTML_INDEX);
2✔
530
  } else {
531
    assert(false && "Unhandled OGCAPIFormat value");
532
  }
533

534
  return MS_SUCCESS;
535

536
#else
537
  msSetError(MS_OGCAPIERR, "OGC API server support is not enabled.",
538
             "msOGCAPIDispatchIndexRequest()");
539
  return MS_FAILURE;
540
#endif
541
}
10✔
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