/src/MapServer/src/maperror.c
Line | Count | Source (jump to first uncovered line) |
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 | 58.7k | errorObj *msGetErrorObj() { |
97 | 58.7k | static errorObj ms_error = {MS_NOERR, "", "", "", MS_FALSE, 0, NULL, 0}; |
98 | 58.7k | return &ms_error; |
99 | 58.7k | } |
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 | 20.7k | static errorObj *msInsertErrorObj(void) { |
180 | 20.7k | errorObj *ms_error; |
181 | 20.7k | ms_error = msGetErrorObj(); |
182 | | |
183 | 20.7k | 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 | 4.70k | errorObj *new_error; |
189 | 4.70k | 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 | 4.70k | if (new_error) { |
196 | 4.70k | new_error->next = ms_error->next; |
197 | 4.70k | new_error->code = ms_error->code; |
198 | 4.70k | new_error->isreported = ms_error->isreported; |
199 | 4.70k | strlcpy(new_error->routine, ms_error->routine, |
200 | 4.70k | sizeof(new_error->routine)); |
201 | 4.70k | strlcpy(new_error->message, ms_error->message, |
202 | 4.70k | sizeof(new_error->message)); |
203 | 4.70k | new_error->errorcount = ms_error->errorcount; |
204 | | |
205 | 4.70k | ms_error->next = new_error; |
206 | 4.70k | ms_error->code = MS_NOERR; |
207 | 4.70k | ms_error->isreported = MS_FALSE; |
208 | 4.70k | ms_error->routine[0] = '\0'; |
209 | 4.70k | ms_error->message[0] = '\0'; |
210 | 4.70k | ms_error->errorcount = 0; |
211 | 4.70k | } |
212 | 4.70k | ms_error->totalerrorcount++; |
213 | 4.70k | } |
214 | | |
215 | 20.7k | return ms_error; |
216 | 20.7k | } |
217 | | |
218 | | /* msResetErrorList() |
219 | | ** |
220 | | ** Clear the list of error objects. |
221 | | */ |
222 | 16.3k | void msResetErrorList() { |
223 | 16.3k | errorObj *ms_error, *this_error; |
224 | 16.3k | ms_error = msGetErrorObj(); |
225 | | |
226 | 16.3k | this_error = ms_error->next; |
227 | 21.0k | while (this_error != NULL) { |
228 | 4.70k | errorObj *next_error; |
229 | | |
230 | 4.70k | next_error = this_error->next; |
231 | 4.70k | msFree(this_error); |
232 | 4.70k | this_error = next_error; |
233 | 4.70k | } |
234 | | |
235 | 16.3k | ms_error->next = NULL; |
236 | 16.3k | ms_error->code = MS_NOERR; |
237 | 16.3k | ms_error->isreported = MS_FALSE; |
238 | 16.3k | ms_error->routine[0] = '\0'; |
239 | 16.3k | ms_error->message[0] = '\0'; |
240 | 16.3k | ms_error->errorcount = 0; |
241 | 16.3k | 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 | 16.3k | } |
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 | 0 | char *msAddErrorDisplayString(char *source, errorObj *error) { |
292 | 0 | if ((source = msStringConcatenate(source, error->routine)) == NULL) |
293 | 0 | return (NULL); |
294 | 0 | if ((source = msStringConcatenate(source, ": ")) == NULL) |
295 | 0 | return (NULL); |
296 | 0 | if ((source = msStringConcatenate(source, ms_errorCodes[error->code])) == |
297 | 0 | NULL) |
298 | 0 | return (NULL); |
299 | 0 | if ((source = msStringConcatenate(source, " ")) == NULL) |
300 | 0 | return (NULL); |
301 | 0 | if ((source = msStringConcatenate(source, error->message)) == NULL) |
302 | 0 | return (NULL); |
303 | 0 | 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 | 0 | return source; |
318 | 0 | } |
319 | | |
320 | 0 | char *msGetErrorString(const char *delimiter) { |
321 | 0 | char *errstr = NULL; |
322 | |
|
323 | 0 | errorObj *error = msGetErrorObj(); |
324 | |
|
325 | 0 | if (!delimiter || !error) |
326 | 0 | return (NULL); |
327 | | |
328 | 0 | while (error && error->code != MS_NOERR) { |
329 | 0 | if ((errstr = msAddErrorDisplayString(errstr, error)) == NULL) |
330 | 0 | return (NULL); |
331 | | |
332 | 0 | if (error->next && |
333 | 0 | error->next->code != |
334 | 0 | MS_NOERR) { /* (peek ahead) more errors, use delimiter */ |
335 | 0 | if ((errstr = msStringConcatenate(errstr, delimiter)) == NULL) |
336 | 0 | return (NULL); |
337 | 0 | } |
338 | 0 | error = error->next; |
339 | 0 | } |
340 | | |
341 | 0 | return (errstr); |
342 | 0 | } |
343 | | |
344 | 81.4k | static void msRedactString(char *str, const char *keyword) { |
345 | | |
346 | 81.4k | char *password = strstr(str, keyword); |
347 | 81.4k | if (password != NULL) { |
348 | 1.41k | const char chOptionDelimiter = password - str > 0 ? password[-1] : 0; |
349 | 1.41k | char *ptr = password + strlen(keyword); |
350 | 1.41k | char chStringSep = *ptr; |
351 | 1.41k | if (chStringSep == '\'' || chStringSep == '"') { |
352 | 473 | ++ptr; |
353 | 937 | } else if (chOptionDelimiter == ';' && chStringSep == '{' && |
354 | 937 | 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 | 5 | ++ptr; |
358 | 5 | chStringSep = '}'; |
359 | 932 | } else { |
360 | 932 | chStringSep = '\0'; |
361 | 932 | } |
362 | | /* Replace all characters from after equal sign to end of line, end of |
363 | | * string, or end of quoted string. |
364 | | */ |
365 | 1.41k | char *ptr_first_redacted_char = NULL; |
366 | 25.5k | while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') { |
367 | 24.8k | if (chStringSep == '\0') { |
368 | 15.2k | if (*ptr == chOptionDelimiter) |
369 | 696 | break; |
370 | 15.2k | } else { |
371 | 9.63k | if (*ptr == chStringSep) { |
372 | 66 | break; |
373 | 66 | } |
374 | 9.56k | if (*ptr == '\\' && ptr[1] == chStringSep) { |
375 | 4 | ptr++; |
376 | 4 | } |
377 | 9.56k | } |
378 | 24.0k | if (!ptr_first_redacted_char) { |
379 | 1.04k | ptr_first_redacted_char = ptr; |
380 | 1.04k | *ptr = '*'; |
381 | 1.04k | } |
382 | 24.0k | ptr++; |
383 | 24.0k | } |
384 | 1.41k | if (ptr_first_redacted_char) { |
385 | 1.04k | memmove(ptr_first_redacted_char + 1, ptr, strlen(ptr) + 1); |
386 | 1.04k | } |
387 | 1.41k | } |
388 | 81.4k | } |
389 | | |
390 | 40.7k | void msRedactCredentials(char *str) { |
391 | | |
392 | | // postgres or mssql formats |
393 | 40.7k | msRedactString(str, "password="); |
394 | | // ODBC connections can use pwd rather than password |
395 | 40.7k | msRedactString(str, "pwd="); |
396 | 40.7k | } |
397 | | |
398 | | static void msSetErrorInternal(int ms_errcode, const char *http_status, |
399 | 21.6k | const char *message, const char *routine) { |
400 | | |
401 | 21.6k | errorObj *ms_error = msGetErrorObj(); |
402 | | |
403 | | /* Insert the error to the list if it is not the same as the previous error*/ |
404 | 21.6k | if (ms_error->code != ms_errcode || !EQUAL(message, ms_error->message) || |
405 | 21.6k | !EQUAL(routine, ms_error->routine)) { |
406 | 20.7k | ms_error = msInsertErrorObj(); |
407 | 20.7k | if (!routine) |
408 | 0 | strcpy(ms_error->routine, ""); |
409 | 20.7k | else { |
410 | 20.7k | strlcpy(ms_error->routine, routine, sizeof(ms_error->routine)); |
411 | 20.7k | } |
412 | | |
413 | 20.7k | if (!http_status) |
414 | 20.7k | 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 | 20.7k | strlcpy(ms_error->message, message, sizeof(ms_error->message)); |
421 | 20.7k | ms_error->code = ms_errcode; |
422 | 20.7k | ms_error->errorcount = 0; |
423 | 20.7k | } else |
424 | 851 | ++ms_error->errorcount; |
425 | | |
426 | 21.6k | msRedactCredentials(ms_error->message); |
427 | | |
428 | | /* Log a copy of errors to MS_ERRORFILE if set (handled automatically inside |
429 | | * msDebug()) */ |
430 | 21.6k | msDebug("%s: %s %s\n", ms_error->routine, ms_errorCodes[ms_error->code], |
431 | 21.6k | ms_error->message); |
432 | 21.6k | } |
433 | | |
434 | 21.6k | void msSetError(int code, const char *message_fmt, const char *routine, ...) { |
435 | 21.6k | va_list args; |
436 | 21.6k | char message[MESSAGELENGTH]; |
437 | | |
438 | 21.6k | if (!message_fmt) |
439 | 1.48k | strcpy(message, ""); |
440 | 20.1k | else { |
441 | 20.1k | va_start(args, routine); |
442 | 20.1k | vsnprintf(message, MESSAGELENGTH, message_fmt, args); |
443 | 20.1k | va_end(args); |
444 | 20.1k | } |
445 | 21.6k | msSetErrorInternal(code, NULL, message, routine); |
446 | 21.6k | } |
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 | 0 | const char *message_fmt, const char *routine, ...) { |
458 | 0 | va_list args; |
459 | 0 | char message[MESSAGELENGTH]; |
460 | |
|
461 | 0 | if (!message_fmt) |
462 | 0 | strcpy(message, ""); |
463 | 0 | else { |
464 | 0 | va_start(args, routine); |
465 | 0 | vsnprintf(message, MESSAGELENGTH, message_fmt, args); |
466 | 0 | va_end(args); |
467 | 0 | } |
468 | 0 | if (http_status) { |
469 | 0 | if (gIsWMS) { |
470 | 0 | if (!CPLTestBoolean( |
471 | 0 | CPLGetConfigOption("MS_WMS_ERROR_STATUS_CODE", "OFF"))) |
472 | 0 | http_status = NULL; |
473 | 0 | } else { |
474 | 0 | http_status = NULL; |
475 | 0 | } |
476 | 0 | } |
477 | 0 | msSetErrorInternal(ms_errcode, http_status, message, routine); |
478 | 0 | } |
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 | 0 | void msWriteErrorXML(FILE *stream) { |
492 | 0 | char *message; |
493 | 0 | errorObj *ms_error = msGetErrorObj(); |
494 | |
|
495 | 0 | while (ms_error && ms_error->code != MS_NOERR) { |
496 | 0 | message = msEncodeHTMLEntities(ms_error->message); |
497 | |
|
498 | 0 | msIO_fprintf(stream, "%s: %s %s\n", ms_error->routine, |
499 | 0 | ms_errorCodes[ms_error->code], message); |
500 | 0 | ms_error->isreported = MS_TRUE; |
501 | 0 | ms_error = ms_error->next; |
502 | |
|
503 | 0 | msFree(message); |
504 | 0 | } |
505 | 0 | } |
506 | | |
507 | 0 | void msWriteErrorImage(mapObj *map, char *filename, int blank) { |
508 | 0 | imageObj *img; |
509 | 0 | int width = 400, height = 300; |
510 | 0 | const int nMargin = 5; |
511 | |
|
512 | 0 | char **papszLines = NULL; |
513 | 0 | pointObj pnt = {0}; |
514 | 0 | outputFormatObj *format = NULL; |
515 | 0 | char *errormsg = msGetErrorString("; "); |
516 | 0 | errorObj *error = msGetErrorObj(); |
517 | 0 | char *imagepath = NULL, *imageurl = NULL; |
518 | 0 | colorObj imagecolor, *imagecolorptr = NULL; |
519 | 0 | textSymbolObj ts; |
520 | 0 | labelObj label; |
521 | 0 | int charWidth = 5, |
522 | 0 | charHeight = 8; /* hardcoded, should be looked up from ft face */ |
523 | 0 | if (!errormsg) { |
524 | 0 | errormsg = msStrdup("No error found sorry. This is likely a bug"); |
525 | 0 | } |
526 | |
|
527 | 0 | if (map) { |
528 | 0 | if (map->width > 0 && map->height > 0) { |
529 | 0 | width = map->width; |
530 | 0 | height = map->height; |
531 | 0 | } |
532 | 0 | format = map->outputformat; |
533 | 0 | imagepath = map->web.imagepath; |
534 | 0 | imageurl = map->web.imageurl; |
535 | 0 | } |
536 | | |
537 | | /* Default to GIF if no suitable GD output format set */ |
538 | 0 | if (format == NULL || !MS_RENDERER_PLUGIN(format)) |
539 | 0 | format = msCreateDefaultOutputFormat(NULL, "AGG/PNG8", "png", NULL); |
540 | |
|
541 | 0 | if (!format->transparent) { |
542 | 0 | if (map && MS_VALID_COLOR(map->imagecolor)) { |
543 | 0 | imagecolorptr = &map->imagecolor; |
544 | 0 | } else { |
545 | 0 | MS_INIT_COLOR(imagecolor, 255, 255, 255, 255); |
546 | 0 | imagecolorptr = &imagecolor; |
547 | 0 | } |
548 | 0 | } |
549 | |
|
550 | 0 | img = msImageCreate(width, height, format, imagepath, imageurl, |
551 | 0 | MS_DEFAULT_RESOLUTION, MS_DEFAULT_RESOLUTION, |
552 | 0 | imagecolorptr); |
553 | |
|
554 | 0 | const int nTextLength = strlen(errormsg); |
555 | 0 | const int nWidthTxt = nTextLength * charWidth; |
556 | 0 | const int nUsableWidth = width - (nMargin * 2); |
557 | | |
558 | | /* Check to see if it all fits on one line. If not, split the text on several |
559 | | * lines. */ |
560 | 0 | if (!blank) { |
561 | 0 | int nLines; |
562 | 0 | if (nWidthTxt > nUsableWidth) { |
563 | 0 | const int nMaxCharsPerLine = nUsableWidth / charWidth; |
564 | 0 | nLines = (int)ceil((double)nTextLength / (double)nMaxCharsPerLine); |
565 | 0 | if (nLines > 0) { |
566 | 0 | papszLines = (char **)malloc(nLines * sizeof(char *)); |
567 | 0 | for (int i = 0; i < nLines; i++) { |
568 | 0 | papszLines[i] = (char *)malloc((nMaxCharsPerLine + 1) * sizeof(char)); |
569 | 0 | papszLines[i][0] = '\0'; |
570 | 0 | } |
571 | 0 | } |
572 | 0 | for (int i = 0; i < nLines; i++) { |
573 | 0 | const int nStart = i * nMaxCharsPerLine; |
574 | 0 | int nEnd = nStart + nMaxCharsPerLine; |
575 | 0 | if (nStart < nTextLength) { |
576 | 0 | if (nEnd > nTextLength) |
577 | 0 | nEnd = nTextLength; |
578 | 0 | const int nLength = nEnd - nStart; |
579 | |
|
580 | 0 | strncpy(papszLines[i], errormsg + nStart, nLength); |
581 | 0 | papszLines[i][nLength] = '\0'; |
582 | 0 | } |
583 | 0 | } |
584 | 0 | } else { |
585 | 0 | nLines = 1; |
586 | 0 | papszLines = (char **)malloc(nLines * sizeof(char *)); |
587 | 0 | papszLines[0] = msStrdup(errormsg); |
588 | 0 | } |
589 | 0 | initLabel(&label); |
590 | 0 | MS_INIT_COLOR(label.color, 0, 0, 0, 255); |
591 | 0 | MS_INIT_COLOR(label.outlinecolor, 255, 255, 255, 255); |
592 | 0 | label.outlinewidth = 1; |
593 | |
|
594 | 0 | label.size = MS_SMALL; |
595 | 0 | MS_REFCNT_INCR((&label)); |
596 | 0 | for (int i = 0; i < nLines; i++) { |
597 | 0 | pnt.y = charHeight * ((i * 2) + 1); |
598 | 0 | pnt.x = charWidth; |
599 | 0 | initTextSymbol(&ts); |
600 | 0 | msPopulateTextSymbolForLabelAndString(&ts, &label, papszLines[i], 1, 1, |
601 | 0 | 0); |
602 | 0 | if (MS_LIKELY(MS_SUCCESS == msComputeTextPath(map, &ts))) { |
603 | 0 | if (MS_SUCCESS != msDrawTextSymbol(NULL, img, pnt, &ts)) { |
604 | | /* an error occurred, but there's nothing much we can do about it here |
605 | | * as we are already handling an error condition */ |
606 | 0 | } |
607 | 0 | freeTextSymbol(&ts); |
608 | 0 | } |
609 | 0 | } |
610 | 0 | if (papszLines) { |
611 | 0 | free(papszLines); |
612 | 0 | } |
613 | 0 | } |
614 | | |
615 | | /* actually write the image */ |
616 | 0 | if (!filename) { |
617 | 0 | msIO_setHeader("Content-Type", "%s", MS_IMAGE_MIME_TYPE(format)); |
618 | 0 | msIO_sendHeaders(); |
619 | 0 | } |
620 | 0 | msSaveImage(NULL, img, filename); |
621 | 0 | msFreeImage(img); |
622 | | |
623 | | /* the errors are reported */ |
624 | 0 | while (error && error->code != MS_NOERR) { |
625 | 0 | error->isreported = MS_TRUE; |
626 | 0 | error = error->next; |
627 | 0 | } |
628 | |
|
629 | 0 | if (format->refcount == 0) |
630 | 0 | msFreeOutputFormat(format); |
631 | 0 | msFree(errormsg); |
632 | 0 | } |
633 | | |
634 | 0 | char *msGetVersion() { |
635 | 0 | static char version[2048]; |
636 | |
|
637 | 0 | sprintf(version, "MapServer version %s", MS_VERSION); |
638 | | |
639 | | // add versions of required dependencies |
640 | 0 | static char PROJVersion[20]; |
641 | 0 | sprintf(PROJVersion, " PROJ version %d.%d", PROJ_VERSION_MAJOR, |
642 | 0 | PROJ_VERSION_MINOR); |
643 | 0 | strcat(version, PROJVersion); |
644 | |
|
645 | 0 | static char GDALVersion[20]; |
646 | 0 | sprintf(GDALVersion, " GDAL version %d.%d", GDAL_VERSION_MAJOR, |
647 | 0 | GDAL_VERSION_MINOR); |
648 | 0 | strcat(version, GDALVersion); |
649 | |
|
650 | 0 | #if (defined USE_PNG) |
651 | 0 | strcat(version, " OUTPUT=PNG"); |
652 | 0 | #endif |
653 | 0 | #if (defined USE_JPEG) |
654 | 0 | strcat(version, " OUTPUT=JPEG"); |
655 | 0 | #endif |
656 | | #ifdef USE_KML |
657 | | strcat(version, " OUTPUT=KML"); |
658 | | #endif |
659 | 0 | strcat(version, " SUPPORTS=PROJ"); |
660 | 0 | strcat(version, " SUPPORTS=AGG"); |
661 | 0 | strcat(version, " SUPPORTS=FREETYPE"); |
662 | | #ifdef USE_CAIRO |
663 | | strcat(version, " SUPPORTS=CAIRO"); |
664 | | #endif |
665 | | #if defined(USE_SVG_CAIRO) || defined(USE_RSVG) |
666 | | strcat(version, " SUPPORTS=SVG_SYMBOLS"); |
667 | | #ifdef USE_SVG_CAIRO |
668 | | strcat(version, " SUPPORTS=SVGCAIRO"); |
669 | | #else |
670 | | strcat(version, " SUPPORTS=RSVG"); |
671 | | #endif |
672 | | #endif |
673 | | #ifdef USE_OGL |
674 | | strcat(version, " SUPPORTS=OPENGL"); |
675 | | #endif |
676 | 0 | #ifdef USE_ICONV |
677 | 0 | strcat(version, " SUPPORTS=ICONV"); |
678 | 0 | #endif |
679 | | #ifdef USE_EXEMPI |
680 | | strcat(version, " SUPPORTS=XMP"); |
681 | | #endif |
682 | | #ifdef USE_FRIBIDI |
683 | | strcat(version, " SUPPORTS=FRIBIDI"); |
684 | | #endif |
685 | 0 | #ifdef USE_WMS_SVR |
686 | 0 | strcat(version, " SUPPORTS=WMS_SERVER"); |
687 | 0 | #endif |
688 | | #ifdef USE_WMS_LYR |
689 | | strcat(version, " SUPPORTS=WMS_CLIENT"); |
690 | | #endif |
691 | 0 | #ifdef USE_WFS_SVR |
692 | 0 | strcat(version, " SUPPORTS=WFS_SERVER"); |
693 | 0 | #endif |
694 | | #ifdef USE_WFS_LYR |
695 | | strcat(version, " SUPPORTS=WFS_CLIENT"); |
696 | | #endif |
697 | 0 | #ifdef USE_WCS_SVR |
698 | 0 | strcat(version, " SUPPORTS=WCS_SERVER"); |
699 | 0 | #endif |
700 | | #ifdef USE_SOS_SVR |
701 | | strcat(version, " SUPPORTS=SOS_SERVER"); |
702 | | #endif |
703 | 0 | #ifdef USE_OGCAPI_SVR |
704 | 0 | strcat(version, " SUPPORTS=OGCAPI_SERVER"); |
705 | 0 | #endif |
706 | | #ifdef USE_FASTCGI |
707 | | strcat(version, " SUPPORTS=FASTCGI"); |
708 | | #endif |
709 | | #ifdef USE_THREAD |
710 | | strcat(version, " SUPPORTS=THREADS"); |
711 | | #endif |
712 | | #ifdef USE_GEOS |
713 | | strcat(version, " SUPPORTS=GEOS"); |
714 | | #endif |
715 | | #ifdef USE_V8_MAPSCRIPT |
716 | | strcat(version, " SUPPORTS=V8"); |
717 | | #endif |
718 | | #ifdef USE_PBF |
719 | | strcat(version, " SUPPORTS=PBF"); |
720 | | #endif |
721 | 0 | #ifdef USE_JPEG |
722 | 0 | strcat(version, " INPUT=JPEG"); |
723 | 0 | #endif |
724 | | #ifdef USE_SDE |
725 | | strcat(version, " INPUT=SDE"); |
726 | | #endif |
727 | | #ifdef USE_POSTGIS |
728 | | strcat(version, " INPUT=POSTGIS"); |
729 | | #endif |
730 | | #ifdef USE_ORACLESPATIAL |
731 | | strcat(version, " INPUT=ORACLESPATIAL"); |
732 | | #endif |
733 | 0 | strcat(version, " INPUT=OGR"); |
734 | 0 | strcat(version, " INPUT=GDAL"); |
735 | 0 | strcat(version, " INPUT=SHAPEFILE"); |
736 | 0 | strcat(version, " INPUT=FLATGEOBUF"); |
737 | 0 | return (version); |
738 | 0 | } |
739 | | |
740 | 0 | int msGetVersionInt() { return MS_VERSION_NUM; } |