Coverage Report

Created: 2026-09-14 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/MapServer/src/maperror.c
Line
Count
Source
1
/******************************************************************************
2
 * $Id$
3
 *
4
 * Project:  MapServer
5
 * Purpose:  Implementation of msSetError(), msDebug() and related functions.
6
 * Author:   Steve Lime and the MapServer team.
7
 *
8
 ******************************************************************************
9
 * Copyright (c) 1996-2005 Regents of the University of Minnesota.
10
 *
11
 * Permission is hereby granted, free of charge, to any person obtaining a
12
 * copy of this software and associated documentation files (the "Software"),
13
 * to deal in the Software without restriction, including without limitation
14
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15
 * and/or sell copies of the Software, and to permit persons to whom the
16
 * Software is furnished to do so, subject to the following conditions:
17
 *
18
 * The above copyright notice and this permission notice shall be included in
19
 * all copies of this Software or works derived from this Software.
20
 *
21
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27
 * DEALINGS IN THE SOFTWARE.
28
 ****************************************************************************/
29
30
#include "mapserver.h"
31
#include "maperror.h"
32
#include "mapthread.h"
33
#include "maptime.h"
34
35
#include <time.h>
36
#ifndef _WIN32
37
#include <sys/time.h>
38
#include <unistd.h>
39
#endif
40
#include <stdarg.h>
41
42
#include "cpl_conv.h"
43
#include "cpl_string.h"
44
45
static char *const ms_errorCodes[MS_NUMERRORCODES] = {
46
    "",
47
    "Unable to access file.",
48
    "Memory allocation error.",
49
    "Incorrect data type.",
50
    "Symbol definition error.",
51
    "Regular expression error.",
52
    "TrueType Font error.",
53
    "DBASE file error.",
54
    "GD library error.",
55
    "Unknown identifier.",
56
    "Premature End-of-File.",
57
    "Projection library error.",
58
    "General error message.",
59
    "CGI error.",
60
    "Web application error.",
61
    "Image handling error.",
62
    "Hash table error.",
63
    "Join error.",
64
    "Search returned no results.",
65
    "Shapefile error.",
66
    "Expression parser error.",
67
    "SDE error.",
68
    "OGR error.",
69
    "Query error.",
70
    "WMS server error.",
71
    "WMS connection error.",
72
    "OracleSpatial error.",
73
    "WFS server error.",
74
    "WFS connection error.",
75
    "WMS Map Context error.",
76
    "HTTP request error.",
77
    "Child array error.",
78
    "WCS server error.",
79
    "GEOS library error.",
80
    "Invalid rectangle.",
81
    "Date/time error.",
82
    "GML encoding error.",
83
    "SOS server error.",
84
    "NULL parent pointer error.",
85
    "AGG library error.",
86
    "OWS error.",
87
    "OpenGL renderer error.",
88
    "Renderer error.",
89
    "V8 engine error.",
90
    "OCG API error.",
91
    "Flatgeobuf error."};
92
93
#ifndef USE_THREAD
94
95
// Get the MapServer error object
96
51.5k
errorObj *msGetErrorObj() {
97
51.5k
  static errorObj ms_error = {MS_NOERR, "", "", "", MS_FALSE, 0, NULL, 0};
98
51.5k
  return &ms_error;
99
51.5k
}
100
#endif
101
102
#ifdef USE_THREAD
103
104
typedef struct te_info {
105
  struct te_info *next;
106
  void *thread_id;
107
  errorObj ms_error;
108
} te_info_t;
109
110
static te_info_t *error_list = NULL;
111
112
errorObj *msGetErrorObj() {
113
  te_info_t *link;
114
  void *thread_id;
115
  errorObj *ret_obj;
116
117
  msAcquireLock(TLOCK_ERROROBJ);
118
119
  thread_id = msGetThreadId();
120
121
  /* find link for this thread */
122
123
  for (link = error_list;
124
       link != NULL && link->thread_id != thread_id && link->next != NULL &&
125
       link->next->thread_id != thread_id;
126
       link = link->next) {
127
  }
128
129
  /* If the target thread link is already at the head of the list were ok */
130
  if (error_list != NULL && error_list->thread_id == thread_id) {
131
  }
132
133
  /* We don't have one ... initialize one. */
134
  else if (link == NULL || link->next == NULL) {
135
    te_info_t *new_link;
136
    errorObj error_obj = {MS_NOERR, "", "", "", 0, 0, NULL, 0};
137
138
    new_link = (te_info_t *)malloc(sizeof(te_info_t));
139
    new_link->next = error_list;
140
    new_link->thread_id = thread_id;
141
    new_link->ms_error = error_obj;
142
143
    error_list = new_link;
144
  }
145
146
  /* If the link is not already at the head of the list, promote it */
147
  else {
148
    te_info_t *target = link->next;
149
150
    link->next = link->next->next;
151
    target->next = error_list;
152
    error_list = target;
153
  }
154
155
  ret_obj = &(error_list->ms_error);
156
157
  msReleaseLock(TLOCK_ERROROBJ);
158
159
  return ret_obj;
160
}
161
#endif
162
163
/* msInsertErrorObj()
164
**
165
** We maintain a chained list of errorObj in which the first errorObj is
166
** the most recent (i.e. a stack).  msErrorReset() should be used to clear
167
** the list.
168
**
169
** Note that since some code in MapServer will fetch the head of the list and
170
** keep a handle on it for a while, the head of the chained list is static
171
** and never changes.
172
** A new errorObj is always inserted after the head, and only if the
173
** head of the list already contains some information.  i.e. If the static
174
** errorObj at the head of the list is empty then it is returned directly,
175
** otherwise a new object is inserted after the head and the data that was in
176
** the head is moved to the new errorObj, freeing the head errorObj to receive
177
** the new error information.
178
*/
179
9.02k
static errorObj *msInsertErrorObj(void) {
180
9.02k
  errorObj *ms_error;
181
9.02k
  ms_error = msGetErrorObj();
182
183
9.02k
  if (ms_error->code != MS_NOERR && ms_error->totalerrorcount < 100) {
184
    /* Head of the list already in use, insert a new errorObj after the head
185
     * and move head contents to this new errorObj, freeing the errorObj
186
     * for reuse.
187
     */
188
1.44k
    errorObj *new_error;
189
1.44k
    new_error = (errorObj *)malloc(sizeof(errorObj));
190
191
    /* Note: if malloc() failed then we simply do nothing and the head will
192
     * be overwritten by the caller... we cannot produce an error here
193
     * since we are already inside a msSetError() call.
194
     */
195
1.44k
    if (new_error) {
196
1.44k
      new_error->next = ms_error->next;
197
1.44k
      new_error->code = ms_error->code;
198
1.44k
      new_error->isreported = ms_error->isreported;
199
1.44k
      strlcpy(new_error->routine, ms_error->routine,
200
1.44k
              sizeof(new_error->routine));
201
1.44k
      strlcpy(new_error->message, ms_error->message,
202
1.44k
              sizeof(new_error->message));
203
1.44k
      new_error->errorcount = ms_error->errorcount;
204
205
1.44k
      ms_error->next = new_error;
206
1.44k
      ms_error->code = MS_NOERR;
207
1.44k
      ms_error->isreported = MS_FALSE;
208
1.44k
      ms_error->routine[0] = '\0';
209
1.44k
      ms_error->message[0] = '\0';
210
1.44k
      ms_error->errorcount = 0;
211
1.44k
    }
212
1.44k
    ms_error->totalerrorcount++;
213
1.44k
  }
214
215
9.02k
  return ms_error;
216
9.02k
}
217
218
/* msResetErrorList()
219
**
220
** Clear the list of error objects.
221
*/
222
15.7k
void msResetErrorList() {
223
15.7k
  errorObj *ms_error, *this_error;
224
15.7k
  ms_error = msGetErrorObj();
225
226
15.7k
  this_error = ms_error->next;
227
17.1k
  while (this_error != NULL) {
228
1.44k
    errorObj *next_error;
229
230
1.44k
    next_error = this_error->next;
231
1.44k
    msFree(this_error);
232
1.44k
    this_error = next_error;
233
1.44k
  }
234
235
15.7k
  ms_error->next = NULL;
236
15.7k
  ms_error->code = MS_NOERR;
237
15.7k
  ms_error->isreported = MS_FALSE;
238
15.7k
  ms_error->routine[0] = '\0';
239
15.7k
  ms_error->message[0] = '\0';
240
15.7k
  ms_error->errorcount = 0;
241
15.7k
  ms_error->totalerrorcount = 0;
242
243
  /* -------------------------------------------------------------------- */
244
  /*      Cleanup our entry in the thread list.  This is mainly           */
245
  /*      imprortant when msCleanup() calls msResetErrorList().           */
246
  /* -------------------------------------------------------------------- */
247
#ifdef USE_THREAD
248
  {
249
    void *thread_id = msGetThreadId();
250
    te_info_t *link;
251
252
    msAcquireLock(TLOCK_ERROROBJ);
253
254
    /* find link for this thread */
255
256
    for (link = error_list;
257
         link != NULL && link->thread_id != thread_id && link->next != NULL &&
258
         link->next->thread_id != thread_id;
259
         link = link->next) {
260
    }
261
262
    if (link->thread_id == thread_id) {
263
      /* presumably link is at head of list.  */
264
      if (error_list == link)
265
        error_list = link->next;
266
267
      free(link);
268
    } else if (link->next != NULL && link->next->thread_id == thread_id) {
269
      te_info_t *next_link = link->next;
270
      link->next = link->next->next;
271
      free(next_link);
272
    }
273
    msReleaseLock(TLOCK_ERROROBJ);
274
  }
275
#endif
276
15.7k
}
277
278
0
char *msGetErrorCodeString(int code) {
279
280
0
  if (code < 0 || code > MS_NUMERRORCODES - 1)
281
0
    return ("Invalid error code.");
282
283
0
  return (ms_errorCodes[code]);
284
0
}
285
286
/* -------------------------------------------------------------------- */
287
/*      Adding the displayable error string to a given string           */
288
/*      and reallocates the memory enough to hold the characters.       */
289
/*      If source is null returns a newly allocated string              */
290
/* -------------------------------------------------------------------- */
291
2.79k
char *msAddErrorDisplayString(char *source, errorObj *error) {
292
2.79k
  if ((source = msStringConcatenate(source, error->routine)) == NULL)
293
0
    return (NULL);
294
2.79k
  if ((source = msStringConcatenate(source, ": ")) == NULL)
295
0
    return (NULL);
296
2.79k
  if ((source = msStringConcatenate(source, ms_errorCodes[error->code])) ==
297
2.79k
      NULL)
298
0
    return (NULL);
299
2.79k
  if ((source = msStringConcatenate(source, " ")) == NULL)
300
0
    return (NULL);
301
2.79k
  if ((source = msStringConcatenate(source, error->message)) == NULL)
302
0
    return (NULL);
303
2.79k
  if (error->errorcount > 0) {
304
0
    char *pszTmp;
305
0
    if ((source = msStringConcatenate(source, " (message repeated ")) == NULL)
306
0
      return (NULL);
307
0
    pszTmp = msIntToString(error->errorcount);
308
0
    if ((source = msStringConcatenate(source, pszTmp)) == NULL) {
309
0
      msFree(pszTmp);
310
0
      return (NULL);
311
0
    }
312
0
    msFree(pszTmp);
313
0
    if ((source = msStringConcatenate(source, " times)")) == NULL)
314
0
      return (NULL);
315
0
  }
316
317
2.79k
  return source;
318
2.79k
}
319
320
2.78k
char *msGetErrorString(const char *delimiter) {
321
2.78k
  char *errstr = NULL;
322
323
2.78k
  errorObj *error = msGetErrorObj();
324
325
2.78k
  if (!delimiter || !error)
326
0
    return (NULL);
327
328
5.57k
  while (error && error->code != MS_NOERR) {
329
2.79k
    if ((errstr = msAddErrorDisplayString(errstr, error)) == NULL)
330
0
      return (NULL);
331
332
2.79k
    if (error->next &&
333
28
        error->next->code !=
334
28
            MS_NOERR) { /* (peek ahead) more errors, use delimiter */
335
28
      if ((errstr = msStringConcatenate(errstr, delimiter)) == NULL)
336
0
        return (NULL);
337
28
    }
338
2.79k
    error = error->next;
339
2.79k
  }
340
341
2.78k
  return (errstr);
342
2.78k
}
343
344
32.4k
static void msRedactString(char *str, const char *keyword) {
345
346
32.4k
  char *password = strstr(str, keyword);
347
32.4k
  if (password != NULL) {
348
657
    const char chOptionDelimiter = password - str > 0 ? password[-1] : 0;
349
657
    char *ptr = password + strlen(keyword);
350
657
    char chStringSep = *ptr;
351
657
    if (chStringSep == '\'' || chStringSep == '"') {
352
200
      ++ptr;
353
457
    } else if (chOptionDelimiter == ';' && chStringSep == '{' &&
354
21
               strcmp(keyword, "pwd=") == 0) {
355
      // Handle cases like "\\SQL2019;DATABASE=msautotest;Driver={ODBC Driver 17
356
      // for SQL Server};pwd={Password;12!};uid=sa;"
357
8
      ++ptr;
358
8
      chStringSep = '}';
359
449
    } else {
360
449
      chStringSep = '\0';
361
449
    }
362
    /* Replace all characters from after equal sign to end of line, end of
363
     * string, or end of quoted string.
364
     */
365
657
    char *ptr_first_redacted_char = NULL;
366
26.2k
    while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') {
367
25.8k
      if (chStringSep == '\0') {
368
20.2k
        if (*ptr == chOptionDelimiter)
369
122
          break;
370
20.2k
      } else {
371
5.63k
        if (*ptr == chStringSep) {
372
122
          break;
373
122
        }
374
5.51k
        if (*ptr == '\\' && ptr[1] == chStringSep) {
375
345
          ptr++;
376
345
        }
377
5.51k
      }
378
25.6k
      if (!ptr_first_redacted_char) {
379
602
        ptr_first_redacted_char = ptr;
380
602
        *ptr = '*';
381
602
      }
382
25.6k
      ptr++;
383
25.6k
    }
384
657
    if (ptr_first_redacted_char) {
385
602
      memmove(ptr_first_redacted_char + 1, ptr, strlen(ptr) + 1);
386
602
    }
387
657
  }
388
32.4k
}
389
390
16.2k
void msRedactCredentials(char *str) {
391
392
  // postgres or mssql formats
393
16.2k
  msRedactString(str, "password=");
394
  // ODBC connections can use pwd rather than password
395
16.2k
  msRedactString(str, "pwd=");
396
16.2k
}
397
398
static void msSetErrorInternal(int ms_errcode, const char *http_status,
399
16.2k
                               const char *message, const char *routine) {
400
401
16.2k
  errorObj *ms_error = msGetErrorObj();
402
403
  /* Insert the error to the list if it is not the same as the previous error*/
404
16.2k
  if (ms_error->code != ms_errcode || !EQUAL(message, ms_error->message) ||
405
9.02k
      !EQUAL(routine, ms_error->routine)) {
406
9.02k
    ms_error = msInsertErrorObj();
407
9.02k
    if (!routine)
408
0
      strcpy(ms_error->routine, "");
409
9.02k
    else {
410
9.02k
      strlcpy(ms_error->routine, routine, sizeof(ms_error->routine));
411
9.02k
    }
412
413
9.02k
    if (!http_status)
414
9.02k
      strcpy(ms_error->http_status, "");
415
0
    else {
416
0
      strlcpy(ms_error->http_status, http_status,
417
0
              sizeof(ms_error->http_status));
418
0
    }
419
420
9.02k
    strlcpy(ms_error->message, message, sizeof(ms_error->message));
421
9.02k
    ms_error->code = ms_errcode;
422
9.02k
    ms_error->errorcount = 0;
423
9.02k
  } else
424
7.18k
    ++ms_error->errorcount;
425
426
16.2k
  msRedactCredentials(ms_error->message);
427
428
  /* Log a copy of errors to MS_ERRORFILE if set (handled automatically inside
429
   * msDebug()) */
430
16.2k
  msDebug("%s: %s %s\n", ms_error->routine, ms_errorCodes[ms_error->code],
431
16.2k
          ms_error->message);
432
16.2k
}
433
434
13.9k
void msSetError(int code, const char *message_fmt, const char *routine, ...) {
435
13.9k
  va_list args;
436
13.9k
  char message[MESSAGELENGTH];
437
438
13.9k
  if (!message_fmt)
439
72
    strcpy(message, "");
440
13.8k
  else {
441
13.8k
    va_start(args, routine);
442
13.8k
    vsnprintf(message, MESSAGELENGTH, message_fmt, args);
443
13.8k
    va_end(args);
444
13.8k
  }
445
13.9k
  msSetErrorInternal(code, NULL, message, routine);
446
13.9k
}
447
448
#ifdef _MSC_VER
449
__declspec(thread) int gIsWMS = MS_FALSE;
450
#else
451
static _Thread_local int gIsWMS = MS_FALSE;
452
#endif
453
454
0
void msSetErrorSetIsWMS(int is_wms) { gIsWMS = is_wms; }
455
456
void msSetErrorWithStatus(int ms_errcode, const char *http_status,
457
2.28k
                          const char *message_fmt, const char *routine, ...) {
458
2.28k
  va_list args;
459
2.28k
  char message[MESSAGELENGTH];
460
461
2.28k
  if (!message_fmt)
462
0
    strcpy(message, "");
463
2.28k
  else {
464
2.28k
    va_start(args, routine);
465
2.28k
    vsnprintf(message, MESSAGELENGTH, message_fmt, args);
466
2.28k
    va_end(args);
467
2.28k
  }
468
2.28k
  if (http_status) {
469
2.28k
    if (gIsWMS) {
470
0
      if (!CPLTestBoolean(
471
0
              CPLGetConfigOption("MS_WMS_ERROR_STATUS_CODE", "OFF")))
472
0
        http_status = NULL;
473
2.28k
    } else {
474
2.28k
      http_status = NULL;
475
2.28k
    }
476
2.28k
  }
477
2.28k
  msSetErrorInternal(ms_errcode, http_status, message, routine);
478
2.28k
}
479
480
0
void msWriteError(FILE *stream) {
481
0
  errorObj *ms_error = msGetErrorObj();
482
483
0
  while (ms_error && ms_error->code != MS_NOERR) {
484
0
    msIO_fprintf(stream, "%s: %s %s <br>\n", ms_error->routine,
485
0
                 ms_errorCodes[ms_error->code], ms_error->message);
486
0
    ms_error->isreported = MS_TRUE;
487
0
    ms_error = ms_error->next;
488
0
  }
489
0
}
490
491
2.76k
void msWriteErrorXML(FILE *stream) {
492
2.76k
  char *message;
493
2.76k
  errorObj *ms_error = msGetErrorObj();
494
495
6.04k
  while (ms_error && ms_error->code != MS_NOERR) {
496
3.28k
    message = msEncodeHTMLEntities(ms_error->message);
497
498
3.28k
    msIO_fprintf(stream, "%s: %s %s\n", ms_error->routine,
499
3.28k
                 ms_errorCodes[ms_error->code], message);
500
3.28k
    ms_error->isreported = MS_TRUE;
501
3.28k
    ms_error = ms_error->next;
502
503
3.28k
    msFree(message);
504
3.28k
  }
505
2.76k
}
506
507
804
void msWriteErrorImage(mapObj *map, char *filename, int blank) {
508
804
  imageObj *img;
509
804
  int width = 400, height = 300;
510
804
  const int nMargin = 5;
511
512
804
  char **papszLines = NULL;
513
804
  pointObj pnt = {0};
514
804
  outputFormatObj *format = NULL;
515
804
  char *errormsg = msGetErrorString("; ");
516
804
  errorObj *error = msGetErrorObj();
517
804
  char *imagepath = NULL, *imageurl = NULL;
518
804
  colorObj imagecolor, *imagecolorptr = NULL;
519
804
  textSymbolObj ts;
520
804
  labelObj label;
521
804
  int charWidth = 5,
522
804
      charHeight = 8; /* hardcoded, should be looked up from ft face */
523
804
  if (!errormsg) {
524
0
    errormsg = msStrdup("No error found sorry. This is likely a bug");
525
0
  }
526
527
804
  if (map) {
528
    /* Error paths that run before the requested image size has been validated
529
       (e.g. a WMS GetMap exception raised while the request is still being
530
       parsed) leave map->width/map->height holding raw client input, so only
531
       use them when they are within the MAXSIZE limit of the map. The width
532
       must also leave room for at least one character between the margins,
533
       otherwise the line splitting below has no usable width to divide by. */
534
804
    if (map->width >= (nMargin * 2) + charWidth && map->width <= map->maxsize &&
535
0
        map->height > 0 && map->height <= map->maxsize) {
536
0
      width = map->width;
537
0
      height = map->height;
538
0
    }
539
804
    format = map->outputformat;
540
804
    imagepath = map->web.imagepath;
541
804
    imageurl = map->web.imageurl;
542
804
  }
543
544
  /* Default to GIF if no suitable GD output format set */
545
804
  if (format == NULL || !MS_RENDERER_PLUGIN(format))
546
0
    format = msCreateDefaultOutputFormat(NULL, "AGG/PNG8", "png", NULL);
547
548
804
  if (!format->transparent) {
549
804
    if (map && MS_VALID_COLOR(map->imagecolor)) {
550
804
      imagecolorptr = &map->imagecolor;
551
804
    } else {
552
0
      MS_INIT_COLOR(imagecolor, 255, 255, 255, 255);
553
0
      imagecolorptr = &imagecolor;
554
0
    }
555
804
  }
556
557
804
  img = msImageCreate(width, height, format, imagepath, imageurl,
558
804
                      MS_DEFAULT_RESOLUTION, MS_DEFAULT_RESOLUTION,
559
804
                      imagecolorptr);
560
804
  if (img == NULL) {
561
    /* Nothing to draw the message on. Return with the errors still unreported
562
       and no headers sent, so that msCGIWriteError() emits its own error page
563
       instead; marking them reported here would make it skip that and leave
564
       the client with an empty response. */
565
0
    if (format->refcount == 0)
566
0
      msFreeOutputFormat(format);
567
0
    msFree(errormsg);
568
0
    return;
569
0
  }
570
571
804
  const int nTextLength = strlen(errormsg);
572
804
  const int nWidthTxt = nTextLength * charWidth;
573
804
  const int nUsableWidth = width - (nMargin * 2);
574
575
  /* Check to see if it all fits on one line. If not, split the text on several
576
   * lines. */
577
804
  if (!blank) {
578
802
    int nLines;
579
802
    if (nWidthTxt > nUsableWidth) {
580
252
      const int nMaxCharsPerLine = nUsableWidth / charWidth;
581
252
      nLines = (int)ceil((double)nTextLength / (double)nMaxCharsPerLine);
582
252
      if (nLines > 0) {
583
252
        papszLines = (char **)malloc(nLines * sizeof(char *));
584
1.21k
        for (int i = 0; i < nLines; i++) {
585
958
          papszLines[i] = (char *)malloc((nMaxCharsPerLine + 1) * sizeof(char));
586
958
          papszLines[i][0] = '\0';
587
958
        }
588
252
      }
589
1.21k
      for (int i = 0; i < nLines; i++) {
590
958
        const int nStart = i * nMaxCharsPerLine;
591
958
        int nEnd = nStart + nMaxCharsPerLine;
592
958
        if (nStart < nTextLength) {
593
958
          if (nEnd > nTextLength)
594
245
            nEnd = nTextLength;
595
958
          const int nLength = nEnd - nStart;
596
597
958
          strncpy(papszLines[i], errormsg + nStart, nLength);
598
958
          papszLines[i][nLength] = '\0';
599
958
        }
600
958
      }
601
550
    } else {
602
550
      nLines = 1;
603
550
      papszLines = (char **)malloc(nLines * sizeof(char *));
604
550
      papszLines[0] = msStrdup(errormsg);
605
550
    }
606
802
    initLabel(&label);
607
802
    MS_INIT_COLOR(label.color, 0, 0, 0, 255);
608
802
    MS_INIT_COLOR(label.outlinecolor, 255, 255, 255, 255);
609
802
    label.outlinewidth = 1;
610
611
802
    label.size = MS_SMALL;
612
802
    MS_REFCNT_INCR((&label));
613
2.31k
    for (int i = 0; i < nLines; i++) {
614
1.50k
      pnt.y = charHeight * ((i * 2) + 1);
615
1.50k
      pnt.x = charWidth;
616
1.50k
      initTextSymbol(&ts);
617
1.50k
      msPopulateTextSymbolForLabelAndString(&ts, &label, papszLines[i], 1, 1,
618
1.50k
                                            0);
619
1.50k
      if (MS_LIKELY(MS_SUCCESS == msComputeTextPath(map, &ts))) {
620
1.50k
        if (MS_SUCCESS != msDrawTextSymbol(NULL, img, pnt, &ts)) {
621
          /* an error occurred, but there's nothing much we can do about it here
622
           * as we are already handling an error condition */
623
0
        }
624
1.50k
        freeTextSymbol(&ts);
625
1.50k
      }
626
1.50k
    }
627
802
    if (papszLines) {
628
802
      free(papszLines);
629
802
    }
630
802
  }
631
632
  /* actually write the image */
633
804
  if (!filename) {
634
804
    msIO_setHeader("Content-Type", "%s", MS_IMAGE_MIME_TYPE(format));
635
804
    msIO_sendHeaders();
636
804
  }
637
804
  msSaveImage(NULL, img, filename);
638
804
  msFreeImage(img);
639
640
  /* the errors are reported */
641
2.41k
  while (error && error->code != MS_NOERR) {
642
1.60k
    error->isreported = MS_TRUE;
643
1.60k
    error = error->next;
644
1.60k
  }
645
646
804
  if (format->refcount == 0)
647
0
    msFreeOutputFormat(format);
648
804
  msFree(errormsg);
649
804
}
650
651
0
char *msGetVersion() {
652
0
  static char version[2048];
653
654
0
  sprintf(version, "MapServer version %s", MS_VERSION);
655
656
  // add versions of required dependencies
657
0
  static char PROJVersion[20];
658
0
  sprintf(PROJVersion, " PROJ version %d.%d", PROJ_VERSION_MAJOR,
659
0
          PROJ_VERSION_MINOR);
660
0
  strcat(version, PROJVersion);
661
662
0
  static char GDALVersion[20];
663
0
  sprintf(GDALVersion, " GDAL version %d.%d", GDAL_VERSION_MAJOR,
664
0
          GDAL_VERSION_MINOR);
665
0
  strcat(version, GDALVersion);
666
667
0
#if (defined USE_PNG)
668
0
  strcat(version, " OUTPUT=PNG");
669
0
#endif
670
0
#if (defined USE_JPEG)
671
0
  strcat(version, " OUTPUT=JPEG");
672
0
#endif
673
#ifdef USE_KML
674
  strcat(version, " OUTPUT=KML");
675
#endif
676
0
  strcat(version, " SUPPORTS=PROJ");
677
0
  strcat(version, " SUPPORTS=AGG");
678
0
  strcat(version, " SUPPORTS=FREETYPE");
679
#ifdef USE_CAIRO
680
  strcat(version, " SUPPORTS=CAIRO");
681
#endif
682
#if defined(USE_SVG_CAIRO) || defined(USE_RSVG)
683
  strcat(version, " SUPPORTS=SVG_SYMBOLS");
684
#ifdef USE_SVG_CAIRO
685
  strcat(version, " SUPPORTS=SVGCAIRO");
686
#else
687
  strcat(version, " SUPPORTS=RSVG");
688
#endif
689
#endif
690
#ifdef USE_OGL
691
  strcat(version, " SUPPORTS=OPENGL");
692
#endif
693
0
#ifdef USE_ICONV
694
0
  strcat(version, " SUPPORTS=ICONV");
695
0
#endif
696
#ifdef USE_EXEMPI
697
  strcat(version, " SUPPORTS=XMP");
698
#endif
699
#ifdef USE_FRIBIDI
700
  strcat(version, " SUPPORTS=FRIBIDI");
701
#endif
702
0
#ifdef USE_WMS_SVR
703
0
  strcat(version, " SUPPORTS=WMS_SERVER");
704
0
#endif
705
#ifdef USE_WMS_LYR
706
  strcat(version, " SUPPORTS=WMS_CLIENT");
707
#endif
708
0
#ifdef USE_WFS_SVR
709
0
  strcat(version, " SUPPORTS=WFS_SERVER");
710
0
#endif
711
#ifdef USE_WFS_LYR
712
  strcat(version, " SUPPORTS=WFS_CLIENT");
713
#endif
714
0
#ifdef USE_WCS_SVR
715
0
  strcat(version, " SUPPORTS=WCS_SERVER");
716
0
#endif
717
#ifdef USE_SOS_SVR
718
  strcat(version, " SUPPORTS=SOS_SERVER");
719
#endif
720
0
#ifdef USE_OGCAPI_SVR
721
0
  strcat(version, " SUPPORTS=OGCAPI_SERVER");
722
0
#endif
723
#ifdef USE_FASTCGI
724
  strcat(version, " SUPPORTS=FASTCGI");
725
#endif
726
#ifdef USE_THREAD
727
  strcat(version, " SUPPORTS=THREADS");
728
#endif
729
#ifdef USE_GEOS
730
  strcat(version, " SUPPORTS=GEOS");
731
#endif
732
#ifdef USE_V8_MAPSCRIPT
733
  strcat(version, " SUPPORTS=V8");
734
#endif
735
#ifdef USE_PBF
736
  strcat(version, " SUPPORTS=PBF");
737
#endif
738
0
#ifdef USE_JPEG
739
0
  strcat(version, " INPUT=JPEG");
740
0
#endif
741
#ifdef USE_SDE
742
  strcat(version, " INPUT=SDE");
743
#endif
744
#ifdef USE_POSTGIS
745
  strcat(version, " INPUT=POSTGIS");
746
#endif
747
#ifdef USE_ORACLESPATIAL
748
  strcat(version, " INPUT=ORACLESPATIAL");
749
#endif
750
0
  strcat(version, " INPUT=OGR");
751
0
  strcat(version, " INPUT=GDAL");
752
0
  strcat(version, " INPUT=SHAPEFILE");
753
0
  strcat(version, " INPUT=FLATGEOBUF");
754
0
  return (version);
755
0
}
756
757
0
int msGetVersionInt() { return MS_VERSION_NUM; }