Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package mapserver for openSUSE:Factory checked in at 2026-07-13 14:26:03 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/mapserver (Old) and /work/SRC/openSUSE:Factory/.mapserver.new.1991 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "mapserver" Mon Jul 13 14:26:03 2026 rev:14 rq:1365155 version:8.6.5 Changes: -------- --- /work/SRC/openSUSE:Factory/mapserver/mapserver.changes 2026-05-29 18:08:47.822569209 +0200 +++ /work/SRC/openSUSE:Factory/.mapserver.new.1991/mapserver.changes 2026-07-13 14:26:20.135865301 +0200 @@ -1,0 +2,19 @@ +Sun Jul 12 14:55:27 UTC 2026 - Jan Engelhardt <[email protected]> + +- Update to release 8.6.5 + * Properly escape mapserv_onlineresource in OpenLayers viewer + with WMS + * PostGIS: make sure identifier value is numeric when the + declared type is numeric too + * OGC API Features: validate offset before rendering to fix + reflected XSS + * WCS POST: NULL-Dereference Denial of Service When `BoundingBox` + Omits `crs` + * Reflected XSS in WMS OpenLayers output via the + `X-Forwarded-Host` header + * Heap buffer overflow in WMS GetLegendGraphic via repeated LAYER + parameters + * msMySQLJoinNext(): prevent SQL injection + * msPOSTGRESQLJoinNext(): avoid potential SQL injection + +------------------------------------------------------------------- Old: ---- mapserver-8.6.3.tar.gz New: ---- mapserver-8.6.5.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ mapserver.spec ++++++ --- /var/tmp/diff_new_pack.GyZ5RB/_old 2026-07-13 14:26:22.855958304 +0200 +++ /var/tmp/diff_new_pack.GyZ5RB/_new 2026-07-13 14:26:22.855958304 +0200 @@ -33,7 +33,7 @@ %{?sle15_python_module_pythons} Name: mapserver -Version: 8.6.3 +Version: 8.6.5 Release: 0 Summary: Environment for building spatially-enabled internet applications License: MIT ++++++ _scmsync.obsinfo ++++++ --- /var/tmp/diff_new_pack.GyZ5RB/_old 2026-07-13 14:26:22.883959261 +0200 +++ /var/tmp/diff_new_pack.GyZ5RB/_new 2026-07-13 14:26:22.903959945 +0200 @@ -1,5 +1,5 @@ -mtime: 1780027572 -commit: 1b845d6d9e38b8c7107ede08ca144e61f7d83be50fe01edc2e4db97a7020d2f1 +mtime: 1783869242 +commit: 27ee9a28a81ad7795d28578db26017fac0a6f1373ec9553259edb1294f0ada42 url: https://src.opensuse.org/jengelh/mapserver revision: master ++++++ build.specials.obscpio ++++++ ++++++ build.specials.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/.gitignore new/.gitignore --- old/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/.gitignore 2026-07-12 17:14:02.000000000 +0200 @@ -0,0 +1 @@ +.osc ++++++ mapserver-8.6.3.tar.gz -> mapserver-8.6.5.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/.github/workflows/start.sh new/mapserver-8.6.5/.github/workflows/start.sh --- old/mapserver-8.6.3/.github/workflows/start.sh 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/.github/workflows/start.sh 2026-07-10 19:20:43.000000000 +0200 @@ -190,4 +190,11 @@ MAPSERVER_CONFIG_FILE=/tmp/mapserver.conf mapserv QUERY_STRING="MAP=wfs_simple.map&SERVICE=WFS&REQUEST=GetCapabilities" > /tmp/res.txt cat /tmp/res.txt | grep "Web application error" >/dev/null || (cat /tmp/res.txt && /bin/false) +# Check https://github.com/MapServer/MapServer/security/advisories/GHSA-xqj6-vjqr-33vv +MAPSERVER_CONFIG_FILE=${WORK_DIR}/msautotest/etc/mapserv.conf SCRIPT_NAME=mapserv SERVER_PORT=80 HTTP_X_FORWARDED_HOST="with'single'quote" mapserv QUERY_STRING="MAP=wms_simple_no_onlineresource.map&SERVICE=WMS&VERSION=1.1.0&REQUEST=GetMap&SRS=EPSG:4326&BBOX=-67.5725,42.3683,-58.9275,48.13&FORMAT=application/openlayers&WIDTH=300&HEIGHT=200&STYLES=&LAYERS=road" > /tmp/res.txt +# We need to escape backslash once for the grep regexp, and once for the shell... +# Hence 4 backslashes for one in the actual output +cat /tmp/res.txt | grep "with\\\\'single\\\\'quote" >/dev/null || (cat /tmp/res.txt && /bin/false) + + echo "Done !" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/CITATION.cff new/mapserver-8.6.5/CITATION.cff --- old/mapserver-8.6.3/CITATION.cff 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/CITATION.cff 2026-07-10 19:20:43.000000000 +0200 @@ -1,8 +1,8 @@ cff-version: 1.2.0 title: MapServer message: If you use this software, please cite it using the metadata from this file. -version: 8.6.3 -date-released: 2026-05-07 +version: 8.6.5 +date-released: 2026-07-10 abstract: MapServer is an Open Source platform for publishing spatial data and interactive mapping applications to the web. type: software authors: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/CMakeLists.txt new/mapserver-8.6.5/CMakeLists.txt --- old/mapserver-8.6.3/CMakeLists.txt 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/CMakeLists.txt 2026-07-10 19:20:43.000000000 +0200 @@ -17,7 +17,7 @@ set (MapServer_VERSION_MAJOR 8) set (MapServer_VERSION_MINOR 6) -set (MapServer_VERSION_REVISION 3) +set (MapServer_VERSION_REVISION 5) set (MapServer_VERSION_SUFFIX "") # Set C++ version diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/HISTORY.md new/mapserver-8.6.5/HISTORY.md --- old/mapserver-8.6.3/HISTORY.md 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/HISTORY.md 2026-07-10 19:20:43.000000000 +0200 @@ -13,6 +13,28 @@ The online Migration Guide can be found at https://mapserver.org/MIGRATION_GUIDE.html +8.6.5 release (2026-07-10) +-------------------------- + +- security: OGC API Features: validate offset before rendering to fix reflected XSS (#7573) + +- security: WCS POST: NULL-Dereference Denial of Service When `BoundingBox` Omits `crs` (#7556) + +- security: Reflected XSS in WMS OpenLayers output via the `X-Forwarded-Host` header (#7544) + +- security: Heap buffer overflow in WMS GetLegendGraphic via repeated LAYER parameters (#7555) + +- security: msMySQLJoinNext(): prevent SQL injection (#7547) + +- security: msPOSTGRESQLJoinNext(): avoid potential SQL injection (#7546) + +8.6.4 release (2026-06-01) +-------------------------- + +- security: properly escape mapserv_onlineresource in OpenLayers viewer with WMS (#7517) + +- security: PostGIS: make sure identifier value is numeric when the declared type is numeric too (#7516) + 8.6.3 release (2026-05-07) -------------------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/SECURITY.md new/mapserver-8.6.5/SECURITY.md --- old/mapserver-8.6.3/SECURITY.md 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/SECURITY.md 2026-07-10 19:20:43.000000000 +0200 @@ -10,6 +10,13 @@ Please follow the general guidelines for bug submissions, when describing the vulnerability (see https://mapserver.org/development/bugs.html). +## Note about use of AI/LLM/automated scanners + +While reporters are free to use any tool of their choice to detect issues, they +must *manually* confirm the findings of those tools with an actual reproducing +test case, not just a theoretical analysis. +The report must also be humanly checked and be kept as succint as possible. + ## Supported Versions The MapServer PSC (Project Steering Committee) will release patches for security vulnerabilities diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/src/apps/mapserv.c new/mapserver-8.6.5/src/apps/mapserv.c --- old/mapserver-8.6.3/src/apps/mapserv.c 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/src/apps/mapserv.c 2026-07-10 19:20:43.000000000 +0200 @@ -60,6 +60,14 @@ static void msCleanupOnSignal(int nInData) { (void)nInData; finish_process = 1; +#ifdef USE_FASTCGI + /* Ask libfcgi to stop accepting new requests. This causes a blocked + * FCGI_Accept() to return -1, so the main loop can exit promptly + * instead of waiting for the next request to arrive. Safe to call + * from a signal handler: it just sets an internal flag (declared in + * fcgiapp.h, which is pulled in via fcgi_stdio.h above). */ + FCGX_ShutdownPending(); +#endif } #endif @@ -242,8 +250,22 @@ /* Setup cleanup magic, mainly for FastCGI case. */ /* -------------------------------------------------------------------- */ #ifndef _WIN32 - signal(SIGUSR1, msCleanupOnSignal); - signal(SIGTERM, msCleanupOnSignal); + { + /* Install via sigaction without SA_RESTART so that a blocking + * accept()/select() inside FCGI_Accept() returns EINTR when the + * signal arrives while the process is idle. Combined with + * FCGX_ShutdownPending() in the handler, this lets the FastCGI + * loop exit on SIGTERM instead of waiting for the next request. */ + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = msCleanupOnSignal; + sigemptyset(&sa.sa_mask); + sigaddset(&sa.sa_mask, SIGUSR1); + sigaddset(&sa.sa_mask, SIGTERM); + sa.sa_flags = 0; /* deliberately no SA_RESTART */ + sigaction(SIGUSR1, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + } #endif #ifdef USE_FASTCGI diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/src/cgiutil.c new/mapserver-8.6.5/src/cgiutil.c --- old/mapserver-8.6.3/src/cgiutil.c 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/src/cgiutil.c 2026-07-10 19:20:43.000000000 +0200 @@ -396,22 +396,37 @@ } } -char x2c(char *what) { - register char digit; - - digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A') + 10 : (what[0] - '0')); - digit *= 16; - digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A') + 10 : (what[1] - '0')); - return (digit); +static int hex_to_integer(char c) { + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + return -1; } void unescape_url(char *url) { - register int x, y; + register int x, y, hi, lo; for (x = 0, y = 0; url[y]; ++x, ++y) { - if ((url[x] = url[y]) == '%') { - url[x] = x2c(&url[y + 1]); + if (url[y] == '%') { + hi = hex_to_integer(url[y + 1]); + if (hi < 0) { + /* %-encoding is malformed, leave as-is */ + url[x] = '%'; + continue; + } + lo = hex_to_integer(url[y + 2]); + if (lo < 0) { + /* %-encoding is malformed, leave as-is */ + url[x] = '%'; + continue; + } + url[x] = (char)(hi * 16 + lo); y += 2; + } else { + url[x] = url[y]; } } url[x] = '\0'; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/src/cgiutil.h new/mapserver-8.6.5/src/cgiutil.h --- old/mapserver-8.6.3/src/cgiutil.h 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/src/cgiutil.h 2026-07-10 19:20:43.000000000 +0200 @@ -93,7 +93,6 @@ MS_DLL_EXPORT char *makeword_skip(char *, char, char); MS_DLL_EXPORT char *makeword(char *, char); MS_DLL_EXPORT char *fmakeword(FILE *, char, int *); -MS_DLL_EXPORT char x2c(char *); MS_DLL_EXPORT void unescape_url(char *); MS_DLL_EXPORT void plustospace(char *); MS_DLL_EXPORT int rind(char *, char); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/src/flatgeobuf/flatgeobuf_c.cpp new/mapserver-8.6.5/src/flatgeobuf/flatgeobuf_c.cpp --- old/mapserver-8.6.3/src/flatgeobuf/flatgeobuf_c.cpp 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/src/flatgeobuf/flatgeobuf_c.cpp 2026-07-10 19:20:43.000000000 +0200 @@ -15,15 +15,21 @@ uint32_t INIT_BUFFER_SIZE = 1024 * 4; template <typename T> -void parse_value(uint8_t *data, char **values, uint16_t i, uint32_t &offset, bool found) +bool parse_value(uint8_t *data, uint32_t size, char **values, uint16_t i, uint32_t &offset, bool found) { using std::to_string; + if (sizeof(T) > size - offset) + { + msSetError(MS_FGBERR, "Invalid size for property value", "flatgeobuf_decode_properties"); + return false; + } if (found) { msFree(values[i]); values[i] = msStrdup(to_string(*((T*) (data + offset))).c_str()); } offset += sizeof(T); + return true; } ctx *flatgeobuf_init_ctx() @@ -139,7 +145,8 @@ if (properties && properties->size() != 0) { ctx->properties = (uint8_t *) properties->data(); ctx->properties_size = properties->size(); - flatgeobuf_decode_properties(ctx, layer, shape); + if (flatgeobuf_decode_properties(ctx, layer, shape) == -1) + return -1; } else { ctx->properties_size = 0; } @@ -183,47 +190,51 @@ type = column.type; switch (type) { case flatgeobuf_column_type_bool: - parse_value<uint8_t>(data, values, ii, offset, found); + if (!parse_value<uint8_t>(data, size, values, ii, offset, found)) return -1; break; case flatgeobuf_column_type_byte: - parse_value<int8_t>(data, values, ii, offset, found); + if (!parse_value<int8_t>(data, size, values, ii, offset, found)) return -1; break; case flatgeobuf_column_type_ubyte: - parse_value<uint8_t>(data, values, ii, offset, found); + if (!parse_value<uint8_t>(data, size, values, ii, offset, found)) return -1; break; case flatgeobuf_column_type_short: - parse_value<int16_t>(data, values, ii, offset, found); + if (!parse_value<int16_t>(data, size, values, ii, offset, found)) return -1; break; case flatgeobuf_column_type_ushort: - parse_value<uint16_t>(data, values, ii, offset, found); + if (!parse_value<uint16_t>(data, size, values, ii, offset, found)) return -1; break; case flatgeobuf_column_type_int: - parse_value<int32_t>(data, values, ii, offset, found); + if (!parse_value<int32_t>(data, size, values, ii, offset, found)) return -1; break; case flatgeobuf_column_type_uint: - parse_value<uint32_t>(data, values, ii, offset, found); + if (!parse_value<uint32_t>(data, size, values, ii, offset, found)) return -1; break; case flatgeobuf_column_type_long: - parse_value<int64_t>(data, values, ii, offset, found); + if (!parse_value<int64_t>(data, size, values, ii, offset, found)) return -1; break; case flatgeobuf_column_type_ulong: - parse_value<uint64_t>(data, values, ii, offset, found); + if (!parse_value<uint64_t>(data, size, values, ii, offset, found)) return -1; break; case flatgeobuf_column_type_float: - parse_value<float>(data, values, ii, offset, found); + if (!parse_value<float>(data, size, values, ii, offset, found)) return -1; break; case flatgeobuf_column_type_double: - parse_value<double>(data, values, ii, offset, found); + if (!parse_value<double>(data, size, values, ii, offset, found)) return -1; break; case flatgeobuf_column_type_datetime: case flatgeobuf_column_type_string: { uint32_t len; - if (offset + sizeof(len) > size){ + if (sizeof(len) > size - offset){ msSetError(MS_FGBERR, "Invalid size for string value", "flatgeobuf_decode_properties"); return -1; } memcpy(&len, data + offset, sizeof(uint32_t)); offset += sizeof(len); + if (len > size - offset){ + msSetError(MS_FGBERR, "Invalid size for string value", "flatgeobuf_decode_properties"); + return -1; + } if (found) { char *str = (char *) msSmallMalloc(len + 1); memcpy(str, data + offset, len); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/src/mapimageio.c new/mapserver-8.6.5/src/mapimageio.c --- old/mapserver-8.6.3/src/mapimageio.c 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/src/mapimageio.c 2026-07-10 19:20:43.000000000 +0200 @@ -860,6 +860,17 @@ } } +/* release the giflib handle, ignoring the close status */ +static void closeGIF(GifFileType *image) { +#if defined GIFLIB_MAJOR && GIFLIB_MINOR && \ + ((GIFLIB_MAJOR == 5 && GIFLIB_MINOR >= 1) || (GIFLIB_MAJOR > 5)) + int errcode; + DGifCloseFile(image, &errcode); +#else + DGifCloseFile(image); +#endif +} + /* not fully implemented and tested */ /* missing: set the first pointer to a,r,g,b */ int readGIF(char *path, rasterBufferObj *rb) { @@ -905,11 +916,24 @@ a = rb->data.rgba.a = &rb->data.rgba.pixels[3]; cmap = (image->Image.ColorMap) ? image->Image.ColorMap : image->SColorMap; + if (cmap == NULL) { + msSetError(MS_MISCERR, "gif image has no color map", "readGIF()"); + free(rb->data.rgba.pixels); + rb->data.rgba.pixels = NULL; + closeGIF(image); + return MS_FAILURE; + } + /* the background index is read straight from the file and may point past + the end of the color table */ + const int bgIndex = (image->SBackGroundColor >= 0 && + image->SBackGroundColor < cmap->ColorCount) + ? image->SBackGroundColor + : 0; for (unsigned i = 0; i < rb->width * rb->height; i++) { *a = 255; - *r = cmap->Colors[image->SBackGroundColor].Red; - *g = cmap->Colors[image->SBackGroundColor].Green; - *b = cmap->Colors[image->SBackGroundColor].Blue; + *r = cmap->Colors[bgIndex].Red; + *g = cmap->Colors[bgIndex].Green; + *b = cmap->Colors[bgIndex].Blue; a += rb->data.rgba.pixel_step; r += rb->data.rgba.pixel_step; g += rb->data.rgba.pixel_step; @@ -955,6 +979,9 @@ msSetError(MS_MISCERR, "corrupted gif image, img size exceeds screen size", "readGIF()"); + free(rb->data.rgba.pixels); + rb->data.rgba.pixels = NULL; + closeGIF(image); return MS_FAILURE; } @@ -983,7 +1010,8 @@ for (unsigned j = 0; j < width; j++) { GifPixelType pix = line[j]; - if (transIdx == -1 || pix != transIdx) { + if ((transIdx == -1 || pix != transIdx) && + pix < cmap->ColorCount) { *a = 255; *r = cmap->Colors[pix].Red; *g = cmap->Colors[pix].Green; @@ -1018,7 +1046,8 @@ } for (unsigned j = 0; j < width; j++) { GifPixelType pix = line[j]; - if (transIdx == -1 || pix != transIdx) { + if ((transIdx == -1 || pix != transIdx) && + pix < cmap->ColorCount) { *a = 255; *r = cmap->Colors[pix].Red; *g = cmap->Colors[pix].Green; @@ -1102,25 +1131,7 @@ } while (recordType != TERMINATE_RECORD_TYPE); -#if defined GIFLIB_MAJOR && GIFLIB_MINOR && \ - ((GIFLIB_MAJOR == 5 && GIFLIB_MINOR >= 1) || (GIFLIB_MAJOR > 5)) - if (DGifCloseFile(image, &errcode) == GIF_ERROR) { - msSetError(MS_MISCERR, "failed to close gif after loading: %s", "readGIF()", - gif_error_msg(errcode)); - return MS_FAILURE; - } -#else - if (DGifCloseFile(image) == GIF_ERROR) { -#if defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5 - msSetError(MS_MISCERR, "failed to close gif after loading: %s", "readGIF()", - gif_error_msg(image->Error)); -#else - msSetError(MS_MISCERR, "failed to close gif after loading: %s", "readGIF()", - gif_error_msg()); -#endif - return MS_FAILURE; - } -#endif + closeGIF(image); return MS_SUCCESS; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/src/mapjoin.c new/mapserver-8.6.5/src/mapjoin.c --- old/mapserver-8.6.3/src/mapjoin.c 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/src/mapjoin.c 2026-07-10 19:20:43.000000000 +0200 @@ -808,8 +808,22 @@ /* if(strcmp(joininfo->target, msMySQLReadStringAttribute(joininfo->conn, i, * joininfo->toindex)) == 0) break; */ /* } */ - snprintf(qbuf, sizeof(qbuf), "SELECT * FROM %s WHERE %s = %s", join->table, - joininfo->tocolumn, joininfo->target); + + char *endptr = NULL; + (void)strtoll(joininfo->target, &endptr, 10); + if (endptr != joininfo->target + strlen(joininfo->target)) { + msSetError(MS_JOINERR, + "Non-numeric value in MySQL JOIN colunn not supported.", + "msMySQLJoinNext()"); + return (MS_FAILURE); + } + + char *escapedTableName = msReplaceSubstring(join->table, "`", "``"); + char *escapedToColumn = msReplaceSubstring(joininfo->tocolumn, "`", "``"); + snprintf(qbuf, sizeof(qbuf), "SELECT * FROM `%s` WHERE `%s` = %s", + escapedTableName, escapedToColumn, joininfo->target); + msFree(escapedTableName); + msFree(escapedToColumn); MYDEBUG printf("%s<BR>\n", qbuf); if ((joininfo->qresult = msMySQLQuery(qbuf, joininfo->conn))) { /* There were some rows found, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/src/maplayer.c new/mapserver-8.6.5/src/maplayer.c --- old/mapserver-8.6.3/src/maplayer.c 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/src/maplayer.c 2026-07-10 19:20:43.000000000 +0200 @@ -2254,33 +2254,41 @@ /* */ /* Return the property name in a properly escaped and quoted form. */ /************************************************************************/ -char *LayerDefaultEscapePropertyName(layerObj *layer, const char *pszString) { + +char *msDefaultEscapePropertyName(const char *pszString) { char *pszEscapedStr = NULL; int i, j = 0; - if (layer && pszString && strlen(pszString) > 0) { - int nLength = strlen(pszString); + int nLength = strlen(pszString); - pszEscapedStr = (char *)msSmallMalloc(1 + 2 * nLength + 1 + 1); - pszEscapedStr[j++] = '"'; + pszEscapedStr = (char *)msSmallMalloc(1 + 2 * nLength + 1 + 1); + pszEscapedStr[j++] = '"'; - for (i = 0; i < nLength; i++) { - char c = pszString[i]; - if (c == '"') { - pszEscapedStr[j++] = '"'; - pszEscapedStr[j++] = '"'; - } else if (c == '\\') { - pszEscapedStr[j++] = '\\'; - pszEscapedStr[j++] = '\\'; - } else - pszEscapedStr[j++] = c; - } - pszEscapedStr[j++] = '"'; - pszEscapedStr[j++] = 0; + for (i = 0; i < nLength; i++) { + char c = pszString[i]; + if (c == '"') { + pszEscapedStr[j++] = '"'; + pszEscapedStr[j++] = '"'; + } else if (c == '\\') { + pszEscapedStr[j++] = '\\'; + pszEscapedStr[j++] = '\\'; + } else + pszEscapedStr[j++] = c; } + pszEscapedStr[j++] = '"'; + pszEscapedStr[j++] = 0; + return pszEscapedStr; } +static char *LayerDefaultEscapePropertyName(layerObj *layer, + const char *pszString) { + if (layer && pszString && strlen(pszString) > 0) { + return msDefaultEscapePropertyName(pszString); + } + return NULL; +} + /* * msConnectLayer * diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/src/mapogcapi.cpp new/mapserver-8.6.5/src/mapogcapi.cpp --- old/mapserver-8.6.3/src/mapogcapi.cpp 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/src/mapogcapi.cpp 2026-07-10 19:20:43.000000000 +0200 @@ -1351,6 +1351,18 @@ return MS_SUCCESS; } } else { // bbox query + const char *offsetStr = getRequestParameter(request, "offset"); + if (offsetStr) { + if (msStringToInt(offsetStr, &offset, 10) != MS_SUCCESS) { + msOGCAPIOutputError(OGCAPI_PARAM_ERROR, "Bad value for offset."); + return MS_SUCCESS; + } + if (offset < 0) { + msOGCAPIOutputError(OGCAPI_PARAM_ERROR, "Offset out of range."); + return MS_SUCCESS; + } + } + map->query.type = MS_QUERY_BY_RECT; map->query.mode = MS_QUERY_MULTIPLE; map->query.layer = i; @@ -1376,14 +1388,8 @@ map->query.only_cache_result_count = MS_FALSE; map->query.maxfeatures = limit; - const char *offsetStr = getRequestParameter(request, "offset"); if (offsetStr) { - if (msStringToInt(offsetStr, &offset, 10) != MS_SUCCESS) { - msOGCAPIOutputError(OGCAPI_PARAM_ERROR, "Bad value for offset."); - return MS_SUCCESS; - } - - if (offset < 0 || offset >= numberMatched) { + if (offset >= numberMatched) { msOGCAPIOutputError(OGCAPI_PARAM_ERROR, "Offset out of range."); return MS_SUCCESS; } @@ -2023,9 +2029,6 @@ strstr(p, OGCAPI_MIMETYPE_HTML) != nullptr)) { format = OGCAPIFormat::HTML; } else if (p) { - std::string errorMsg("Unsupported format requested: "); - errorMsg += p; - msOGCAPIOutputError(OGCAPI_PARAM_ERROR, errorMsg.c_str()); format = OGCAPIFormat::Invalid; } else { format = OGCAPIFormat::HTML; // default for now @@ -2060,6 +2063,7 @@ const OGCAPIFormat format = msOGCAPIGetOutputFormat(request); if (format == OGCAPIFormat::Invalid) { + msOGCAPIOutputError(OGCAPI_PARAM_ERROR, "Unsupported format requested."); return MS_SUCCESS; // avoid any downstream MapServer processing } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/src/mapogcfilter.cpp new/mapserver-8.6.5/src/mapogcfilter.cpp --- old/mapserver-8.6.3/src/mapogcfilter.cpp 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/src/mapogcfilter.cpp 2026-07-10 19:20:43.000000000 +0200 @@ -806,6 +806,26 @@ return status; } +static bool msCheckDepthLessThanInternal(const CPLXMLNode *psNode, + int nMaxDepth) { + if (nMaxDepth <= 0) + return false; + for (const CPLXMLNode *psIter = psNode->psChild; psIter; + psIter = psIter->psNext) { + if (!msCheckDepthLessThanInternal(psIter, nMaxDepth - 1)) + return false; + } + return true; +} + +bool msCheckDepthLessThan(const CPLXMLNode *psNode, int nMaxDepth) { + for (const CPLXMLNode *psIter = psNode; psIter; psIter = psIter->psNext) { + if (!msCheckDepthLessThanInternal(psIter, nMaxDepth)) + return false; + } + return true; +} + /************************************************************************/ /* FilterNode *FLTPaserFilterEncoding(char *szXMLString) */ /* */ @@ -828,6 +848,11 @@ if (psRoot == NULL) return NULL; + if (!msCheckDepthLessThan(psRoot, 256)) { + msDebug("FLTParseFilterEncoding(): %s", "Too deep nesting in filter"); + CPLDestroyXMLNode(psRoot); + return NULL; + } /* strip namespaces. We srtip all name spaces (#1350)*/ CPLStripXMLNamespace(psRoot, NULL, 1); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/src/mapogcfilter.h new/mapserver-8.6.5/src/mapogcfilter.h --- old/mapserver-8.6.3/src/mapogcfilter.h 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/src/mapogcfilter.h 2026-07-10 19:20:43.000000000 +0200 @@ -54,6 +54,9 @@ /* -------------------------------------------------------------------- */ /* prototypes. */ /* -------------------------------------------------------------------- */ + +bool msCheckDepthLessThan(const CPLXMLNode *psNode, int nMaxDepth); + MS_DLL_EXPORT int FLTIsNumeric(const char *pszValue); MS_DLL_EXPORT int FLTApplyExpressionToLayer(layerObj *lp, const char *pszExpression); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/src/mapogcsld.cpp new/mapserver-8.6.5/src/mapogcsld.cpp --- old/mapserver-8.6.3/src/mapogcsld.cpp 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/src/mapogcsld.cpp 2026-07-10 19:20:43.000000000 +0200 @@ -644,6 +644,11 @@ msSetError(MS_WMSERR, "Invalid SLD document : %s", "", psSLDXML); return NULL; } + if (!msCheckDepthLessThan(psRoot, 256)) { + msSetError(MS_WMSERR, "Invalid SLD document : too deep nesting", ""); + CPLDestroyXMLNode(psRoot); + return NULL; + } /* strip namespaces ogc and sld and gml */ CPLStripXMLNamespace(psRoot, "ogc", 1); @@ -4981,9 +4986,11 @@ iValue = 0; for (int i = 0; i < nLength; i++) { // Check for comparison operator - if ((i + 1 < nLength && EQUALN(&pszExpression[i], szCompare, 2)) || - (i + 1 < nLength && EQUALN(&pszExpression[i], szCompare2, 2)) || - (i + 1 < nLength && EQUALN(&pszExpression[i], szCompare3, 2))) { + if ((i + 1 < nLength && EQUALN(&pszExpression[i], szCompare2, 2)) || + (i + 1 < nLength && EQUALN(&pszExpression[i], szCompare3, 2)) || + (i + 1 < nLength && EQUALN(&pszExpression[i], szCompare, 2) && + (i == 0 || pszExpression[i - 1] == ' ') && + (i + 2 >= nLength || pszExpression[i + 2] == ' '))) { // Extract value after the operator int offset = 2; // All operators are 2 characters long pszAttributeValue = msStrdup(&pszExpression[i + offset]); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/src/mapows.c new/mapserver-8.6.5/src/mapows.c --- old/mapserver-8.6.3/src/mapows.c 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/src/mapows.c 2026-07-10 19:20:43.000000000 +0200 @@ -1468,7 +1468,7 @@ schemas_location = msLookupHashTable(&(map->web.metadata), "inspire_schemas_location"); if (schemas_location == NULL) - schemas_location = "http://inspire.ec.europa.eu/schemas"; + schemas_location = INSPIRE_DEFAULT_SCHEMAS_LOCATION; return schemas_location; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/src/mapows.h new/mapserver-8.6.5/src/mapows.h --- old/mapserver-8.6.3/src/mapows.h 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/src/mapows.h 2026-07-10 19:20:43.000000000 +0200 @@ -40,6 +40,13 @@ */ #define OWS_DEFAULT_SCHEMAS_LOCATION "http://schemas.opengis.net" +/* Likewise, this is the URL to the official INSPIRE Schema Repository. Used by + * default for INSPIRE services (i.e. services that define + * ows_inspire_capabilities) unless the inspire_schemas_location web + * metadata is set in the mapfile. + */ +#define INSPIRE_DEFAULT_SCHEMAS_LOCATION "https://inspire.ec.europa.eu/schemas" + #if defined USE_LIBXML2 && defined USE_WFS_SVR #include <libxml/tree.h> #endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/src/mappostgis.cpp new/mapserver-8.6.5/src/mappostgis.cpp --- old/mapserver-8.6.3/src/mappostgis.cpp 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/src/mappostgis.cpp 2026-07-10 19:20:43.000000000 +0200 @@ -2024,18 +2024,16 @@ } } } - bool insert_and = false; + std::string strWhere; if (bIsValidRect && !strRect.empty()) { strWhere += strRect; - insert_and = true; } /* Handle a translated filter (RFC91). */ if (layer->filter.native_string) { - if (insert_and) { + if (!strWhere.empty()) { strWhere += " AND "; - insert_and = true; } strWhere += '('; strWhere += layer->filter.native_string; @@ -2045,17 +2043,17 @@ /* Handle a native filter set as a PROCESSING option (#5001). */ const char *native_filter = msLayerGetProcessingKey(layer, "NATIVE_FILTER"); if (native_filter) { - if (insert_and) { + if (!strWhere.empty()) { strWhere += " AND "; - insert_and = true; } + strWhere += '('; strWhere += native_filter; strWhere += ')'; } if (uid) { - if (insert_and) { + if (!strWhere.empty()) { strWhere += " AND "; } @@ -3736,6 +3734,17 @@ native_string += stresc; native_string += '\''; } else { + char *endptr = nullptr; + std::strtod(stresc, &endptr); + if (endptr != stresc + strlen(stresc)) { + if (layer->debug >= 2) { + msDebug( + "msPostGISLayerTranslateFilter. '%s' is not a numeric value.\n", + filter->string); + } + msFree(stresc); + return MS_FAILURE; + } native_string += stresc; } msFree(stresc); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/src/mappostgresql.c new/mapserver-8.6.5/src/mappostgresql.c --- old/mapserver-8.6.3/src/mappostgresql.c 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/src/mappostgresql.c 2026-07-10 19:20:43.000000000 +0200 @@ -266,6 +266,19 @@ return MS_SUCCESS; } +static char *msPOSTGRESQLEscapeSQLParam(PGconn *conn, const char *pszString) { + + size_t nSrcLen = strlen(pszString); + char *pszEscapedStr = (char *)msSmallMalloc(2 * nSrcLen + 1); + int nError = 0; + PQescapeStringConn(conn, pszEscapedStr, pszString, nSrcLen, &nError); + if (nError != 0) { + free(pszEscapedStr); + pszEscapedStr = NULL; + } + return pszEscapedStr; +} + /************************************************************************/ /* msPOSTGRESQLJoinNext() */ /* */ @@ -306,7 +319,8 @@ /* Write the list of column names. */ length = 0; for (i = 0; i < join->numitems; i++) { - length += 8 + strlen(join->items[i]) + 2; + length += strlen("::text") + strlen("\"\"") + strlen(join->items[i]) * 2 + + strlen(", "); } if (length > 1024 * 1024) { msSetError(MS_MEMERR, "Too many joins.\n", "msPOSTGRESQLJoinNext()"); @@ -321,24 +335,33 @@ columns[0] = 0; for (i = 0; i < join->numitems; i++) { - strcat(columns, "\""); - strcat(columns, join->items[i]); - strcat(columns, "\"::text"); + char *stresc = msDefaultEscapePropertyName(join->items[i]); + strcat(columns, stresc); + msFree(stresc); + strcat(columns, "::text"); if (i != join->numitems - 1) { strcat(columns, ", "); } } /* Create the query string. */ - const size_t nSize = 26 + strlen(columns) + strlen(join->table) + - strlen(join->to) + strlen(joininfo->from_value); - sql = (char *)malloc(nSize); - if (!sql) { - msSetError(MS_MEMERR, "Failure to malloc.\n", "msPOSTGRESQLJoinNext()"); + char *pszEscapedFrom = + msPOSTGRESQLEscapeSQLParam(joininfo->conn, joininfo->from_value); + if (!pszEscapedFrom) { + free(columns); return MS_FAILURE; } + char *pszEscapedTable = msDefaultEscapePropertyName(join->table); + char *pszEscapedTo = msDefaultEscapePropertyName(join->to); + const size_t nSize = strlen("SELECT FROM WHERE = ''") + strlen(columns) + + strlen(pszEscapedTable) + strlen(pszEscapedTo) + + strlen(pszEscapedFrom) + 1; + sql = (char *)msSmallMalloc(nSize); snprintf(sql, nSize, "SELECT %s FROM %s WHERE %s = '%s'", columns, - join->table, join->to, joininfo->from_value); + pszEscapedTable, pszEscapedTo, pszEscapedFrom); + msFree(pszEscapedTable); + msFree(pszEscapedTo); + msFree(pszEscapedFrom); if (joininfo->layer_debug) { msDebug("msPOSTGRESQLJoinNext(): executing %s.\n", sql); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/src/maprasterquery.c new/mapserver-8.6.5/src/maprasterquery.c --- old/mapserver-8.6.3/src/maprasterquery.c 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/src/maprasterquery.c 2026-07-10 19:20:43.000000000 +0200 @@ -1205,7 +1205,10 @@ switch (rlinfo->eDataType) { case GDT_Byte: +#if GDAL_VERSION_MAJOR > 3 || \ + (GDAL_VERSION_MAJOR == 3 && GDAL_VERSION_MINOR >= 7) case GDT_Int8: +#endif case GDT_UInt16: case GDT_Int16: case GDT_Int32: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/src/mapscale.c new/mapserver-8.6.5/src/mapscale.c --- old/mapserver-8.6.3/src/mapscale.c 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/src/mapscale.c 2026-07-10 19:20:43.000000000 +0200 @@ -309,7 +309,7 @@ } } - sprintf(label, "%g", j * i); + snprintf(label, sizeof(label), "%g", j * i); map->scalebar.label.position = MS_CC; p.x = ox + j * isx; /* + MS_NINT(fontPtr->w/2); */ p.y = oy + map->scalebar.height + MS_NINT(VSPACING * fontHeight); @@ -320,9 +320,10 @@ } state = -state; } - sprintf(label, "%g", j * i); + snprintf(label, sizeof(label), "%g", j * i); ox = ox + j * isx - MS_NINT((strlen(label) * fontWidth) / 2.0); - sprintf(label, "%g %s", j * i, unitText[map->scalebar.units]); + snprintf(label, sizeof(label), "%g %s", j * i, + unitText[map->scalebar.units]); map->scalebar.label.position = MS_CR; p.x = ox; /* + MS_NINT(fontPtr->w/2); */ p.y = oy + map->scalebar.height + MS_NINT(VSPACING * fontHeight); @@ -361,12 +362,13 @@ goto scale_cleanup; } - sprintf(label, "%g", j * i); + snprintf(label, sizeof(label), "%g", j * i); if (j != map->scalebar.intervals) { map->scalebar.label.position = MS_CC; p.x = ox + j * isx; /* + MS_NINT(fontPtr->w/2); */ } else { - sprintf(label, "%g %s", j * i, unitText[map->scalebar.units]); + snprintf(label, sizeof(label), "%g %s", j * i, + unitText[map->scalebar.units]); map->scalebar.label.position = MS_CR; p.x = ox + j * isx - MS_NINT((strlen(label) * fontWidth) / 2.0); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/src/mapscript/python/pyextend.i new/mapserver-8.6.5/src/mapscript/python/pyextend.i --- old/mapserver-8.6.3/src/mapscript/python/pyextend.i 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/src/mapscript/python/pyextend.i 2026-07-10 19:20:43.000000000 +0200 @@ -257,7 +257,7 @@ PyObject *order; order = PyTuple_New(self->numlayers); for (i = 0; i < self->numlayers; i++) { - PyTuple_SetItem(order,i,PyInt_FromLong((long)self->layerorder[i])); + PyTuple_SetItem(order,i,PyLong_FromLong((long)self->layerorder[i])); } return order; } @@ -269,7 +269,7 @@ int i; Py_ssize_t size = PyTuple_Size(order); for (i = 0; i < size; i++) { - self->layerorder[i] = (int)PyInt_AsLong(PyTuple_GetItem(order, i)); + self->layerorder[i] = (int)PyLong_AsLong(PyTuple_GetItem(order, i)); } return MS_SUCCESS; } @@ -281,8 +281,8 @@ { PyObject* output ; output = PyTuple_New(2); - PyTuple_SetItem(output,0,PyInt_FromLong((long)self->width)); - PyTuple_SetItem(output,1,PyInt_FromLong((long)self->height)); + PyTuple_SetItem(output,0,PyLong_FromLong((long)self->width)); + PyTuple_SetItem(output,1,PyLong_FromLong((long)self->height)); return output; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/src/mapserv-index.cpp new/mapserver-8.6.5/src/mapserv-index.cpp --- old/mapserver-8.6.3/src/mapserv-index.cpp 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/src/mapserv-index.cpp 2026-07-10 19:20:43.000000000 +0200 @@ -425,7 +425,7 @@ int msOGCAPIDispatchMapIndexRequest(mapservObj *mapserv, configObj *config) { #ifdef USE_OGCAPI_SVR if (!mapserv || !config) - return -1; // Handle null pointers + return MS_FAILURE; // Handle null pointers cgiRequestObj *request = mapserv->request; @@ -437,6 +437,12 @@ } OGCAPIFormat format = msOGCAPIGetOutputFormat(request); + + if (format == OGCAPIFormat::Invalid) { + msOGCAPIOutputError(OGCAPI_PARAM_ERROR, "Unsupported format requested."); + return MS_FAILURE; + } + const char *key = request->api_path[0]; mapObj *map = getMapFromConfig(config, key); @@ -479,11 +485,16 @@ int msOGCAPIDispatchIndexRequest(mapservObj *mapserv, configObj *config) { #ifdef USE_OGCAPI_SVR if (!mapserv || !config) - return -1; // Handle null pointers + return MS_FAILURE; // Handle null pointers cgiRequestObj *request = mapserv->request; const OGCAPIFormat format = msOGCAPIGetOutputFormat(request); + if (format == OGCAPIFormat::Invalid) { + msOGCAPIOutputError(OGCAPI_PARAM_ERROR, "Unsupported format requested."); + return MS_FAILURE; + } + const char *key = NULL; json links = json::array(); @@ -504,7 +515,7 @@ } else if (format == OGCAPIFormat::HTML) { createHTMLOutput(response, TEMPLATE_HTML_INDEX); } else { - msOGCAPIOutputError(OGCAPI_PARAM_ERROR, "Unsupported format requested."); + assert(false && "Unhandled OGCAPIFormat value"); } return MS_SUCCESS; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/src/mapserver.h new/mapserver-8.6.5/src/mapserver.h --- old/mapserver-8.6.3/src/mapserver.h 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/src/mapserver.h 2026-07-10 19:20:43.000000000 +0200 @@ -2889,7 +2889,9 @@ MS_DLL_EXPORT int msEncodeChar(const char); MS_DLL_EXPORT char *msEncodeUrlExcept(const char *, const char); MS_DLL_EXPORT char *msEncodeUrl(const char *); -MS_DLL_EXPORT char *msEscapeJSonString(const char *pszJSonString); +char *msDefaultEscapePropertyName(const char *pszString); +MS_DLL_EXPORT char *msEscapeJSonString(const char *pszString); +MS_DLL_EXPORT char *msEscapeJSonLikeString(const char *pszString, char chQuote); MS_DLL_EXPORT char *msEncodeHTMLEntities(const char *string); MS_DLL_EXPORT void msDecodeHTMLEntities(const char *string); MS_DLL_EXPORT int msIsXMLTagValid(const char *string); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/src/mapstring.cpp new/mapserver-8.6.5/src/mapstring.cpp --- old/mapserver-8.6.3/src/mapstring.cpp 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/src/mapstring.cpp 2026-07-10 19:20:43.000000000 +0200 @@ -1205,18 +1205,18 @@ /* msEscapeJSonString() */ /************************************************************************/ -/* The input (and output) string are not supposed to start/end with double */ -/* quote characters. It is the responsibility of the caller to do that. */ -char *msEscapeJSonString(const char *pszJSonString) { +/* The input (and output) string are not supposed to start/end with chQuote + * characters. It is the responsibility of the caller to do that. */ +char *msEscapeJSonLikeString(const char *pszString, char chQuote) { /* Worst case is one character to become \uABCD so 6 characters */ char *pszRet; int i = 0, j = 0; static const char *pszHex = "0123456789ABCDEF"; - pszRet = (char *)msSmallMalloc(strlen(pszJSonString) * 6 + 1); + pszRet = (char *)msSmallMalloc(strlen(pszString) * 6 + 1); /* From http://www.json.org/ */ - for (i = 0; pszJSonString[i] != '\0'; i++) { - unsigned char ch = pszJSonString[i]; + for (i = 0; pszString[i] != '\0'; i++) { + unsigned char ch = pszString[i]; if (ch == '\b') { pszRet[j++] = '\\'; pszRet[j++] = 'b'; @@ -1239,9 +1239,9 @@ pszRet[j++] = '0'; pszRet[j++] = pszHex[ch / 16]; pszRet[j++] = pszHex[ch % 16]; - } else if (ch == '"') { + } else if (ch == chQuote) { pszRet[j++] = '\\'; - pszRet[j++] = '"'; + pszRet[j++] = chQuote; } else if (ch == '\\') { pszRet[j++] = '\\'; pszRet[j++] = '\\'; @@ -1253,6 +1253,12 @@ return pszRet; } +/* The input (and output) string are not supposed to start/end with double + * quote characters. It is the responsibility of the caller to do that. */ +char *msEscapeJSonString(const char *pszString) { + return msEscapeJSonLikeString(pszString, '"'); +} + /* msEncodeHTMLEntities() ** ** Return a copy of string after replacing some problematic chars with their diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/src/maptemplate.c new/mapserver-8.6.5/src/maptemplate.c --- old/mapserver-8.6.3/src/maptemplate.c 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/src/maptemplate.c 2026-07-10 19:20:43.000000000 +0200 @@ -43,6 +43,7 @@ #include <assert.h> #include <ctype.h> +#include <math.h> static inline void IGUR_sizet(size_t ignored) { (void)ignored; @@ -82,11 +83,19 @@ " <script " "src=\"[openlayers_js_url]\"></script>\n" " <script>\n" + " if (!ol.proj.get('[openlayers_projection]')) {\n" + " ol.proj.addProjection(new ol.proj.Projection({ code: " + "'[openlayers_projection]' }));\n" + " }\n" " [openlayers_layer]\n" " const map = new ol.Map({\n" " layers: [mslayer],\n" " target: 'map',\n" - " view: new ol.View()\n" + " view: new ol.View({\n" + " projection: '[openlayers_projection]',\n" + " minResolution: [ol_minresolution],\n" + " maxResolution: [ol_maxresolution]\n" + " })\n" " });\n" " map.getView().fit([[minx], [miny], [maxx], [maxy]], { size: " "map.getSize() });\n" @@ -108,11 +117,7 @@ " });"; static const char *const olLayerWMSTag = - "if (!ol.proj.get('[openlayers_projection]')) {\n" - " ol.proj.addProjection(new ol.proj.Projection({ code : " - "'[openlayers_projection]' }));\n" - " }\n" - " const mslayer = new ol.layer.Image({\n" + "const mslayer = new ol.layer.Image({\n" " source: new ol.source.Image({\n" " loader: ol.source.wms.createLoader({\n" " url: '[mapserv_onlineresource]',\n" @@ -4097,7 +4102,27 @@ #else ol = msBuildOnlineResource(mapserv->map, mapserv->request); #endif - outstr = msReplaceSubstring(outstr, "[mapserv_onlineresource]", ol); + + if (strstr(outstr, "'[mapserv_onlineresource]'")) { + // Fix + // https://github.com/MapServer/MapServer/security/advisories/GHSA-xqj6-vjqr-33vv + // The value is inside a single-quoted JS string in a <script> element. + // Escaping the quote is not enough: a literal "</script>" in the value + // still closes the element. Encode '<' and '>' as JS unicode escapes so + // they cannot break out of the script block. + char *pszEscaped = msEscapeJSonLikeString(ol, '\''); + pszEscaped = msReplaceSubstring(pszEscaped, "<", "\\u003c"); + pszEscaped = msReplaceSubstring(pszEscaped, ">", "\\u003e"); + const size_t nLen = 1 + strlen(pszEscaped) + 1 + 1; + char *pszEscapedWithSingleQuotes = (char *)msSmallMalloc(nLen); + snprintf(pszEscapedWithSingleQuotes, nLen, "'%s'", pszEscaped); + outstr = msReplaceSubstring(outstr, "'[mapserv_onlineresource]'", + pszEscapedWithSingleQuotes); + msFree(pszEscapedWithSingleQuotes); + msFree(pszEscaped); + } else { + outstr = msReplaceSubstring(outstr, "[mapserv_onlineresource]", ol); + } msFree(ol); } @@ -5031,6 +5056,16 @@ } } + if (mapserv->Mode == BROWSE) { + char *epsgCode = NULL; + msOWSGetEPSGProj(&(mapserv->map->projection), NULL, NULL, MS_TRUE, + &epsgCode); + if (epsgCode) { + // Transfers ownership of epsgCode to projection + projection = epsgCode; + } + } + if (mapserv->map->outputformat->mimetype && *mapserv->map->outputformat->mimetype) { format = mapserv->map->outputformat->mimetype; @@ -5061,12 +5096,36 @@ buffer = msReplaceSubstring(buffer, "[openlayers_js_url]", openlayersUrl); buffer = msReplaceSubstring(buffer, "[openlayers_css_url]", openlayersCssUrl); buffer = msReplaceSubstring(buffer, "[openlayers_layer]", layer); - if (projection) - buffer = msReplaceSubstring(buffer, "[openlayers_projection]", projection); - if (format) + if (!projection) { + // if no projection can be found then use the default OL projection + projection = msStrdup("EPSG:3857"); + } + buffer = msReplaceSubstring(buffer, "[openlayers_projection]", projection); + if (format) { buffer = msReplaceSubstring(buffer, "[openlayers_format]", format); - else + } else { buffer = msReplaceSubstring(buffer, "[openlayers_format]", "image/png"); + } + + double extentWidth = mapserv->map->extent.maxx - mapserv->map->extent.minx; + double extentHeight = mapserv->map->extent.maxy - mapserv->map->extent.miny; + // assume default tile size and zoom-levels + int zoomLevels = 20; + double maxResolution = MS_MAX(extentWidth, extentHeight) / 256.0; + + if (maxResolution <= 0) { + maxResolution = 1; // fallback to avoid any divide by zero + } + + double minResolution = maxResolution / pow(2.0, zoomLevels); + char olMinRes[64], olMaxRes[64]; + + snprintf(olMaxRes, sizeof(olMaxRes), "%g", maxResolution); + snprintf(olMinRes, sizeof(olMinRes), "%g", minResolution); + + buffer = msReplaceSubstring(buffer, "[ol_maxresolution]", olMaxRes); + buffer = msReplaceSubstring(buffer, "[ol_minresolution]", olMinRes); + msIO_fwrite(buffer, strlen(buffer), 1, stdout); free(layer); free(buffer); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/src/maptree.c new/mapserver-8.6.5/src/maptree.c --- old/mapserver-8.6.3/src/maptree.c 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/src/maptree.c 2026-07-10 19:20:43.000000000 +0200 @@ -380,7 +380,7 @@ return (treeNodeAddShapeId(tree->root, id, rect, tree->maxdepth)); } -static void treeCollectShapeIds(treeNodeObj *node, rectObj aoi, +static void treeCollectShapeIds(treeNodeObj *node, rectObj aoi, int numshapes, ms_bitarray status) { int i; @@ -395,14 +395,15 @@ /* Add the local nodes shapeids to the list. */ /* -------------------------------------------------------------------- */ for (i = 0; i < node->numshapes; i++) - msSetBit(status, node->ids[i], 1); + if (node->ids[i] >= 0 && node->ids[i] < numshapes) + msSetBit(status, node->ids[i], 1); /* -------------------------------------------------------------------- */ /* Recurse to subnodes if they exist. */ /* -------------------------------------------------------------------- */ for (i = 0; i < node->numsubnodes; i++) { if (node->subnode[i]) - treeCollectShapeIds(node->subnode[i], aoi, status); + treeCollectShapeIds(node->subnode[i], aoi, numshapes, status); } } @@ -415,7 +416,7 @@ return (NULL); } - treeCollectShapeIds(tree->root, aoi, status); + treeCollectShapeIds(tree->root, aoi, tree->numshapes, status); return (status); } @@ -507,11 +508,13 @@ if (disktree->needswap) { for (i = 0; i < numshapes; i++) { SwapWord(4, &ids[i]); - msSetBit(status, ids[i], 1); + if (ids[i] >= 0 && ids[i] < disktree->nShapes) + msSetBit(status, ids[i], 1); } } else { for (i = 0; i < numshapes; i++) - msSetBit(status, ids[i], 1); + if (ids[i] >= 0 && ids[i] < disktree->nShapes) + msSetBit(status, ids[i], 1); } free(ids); ids = NULL; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/src/mapwcs.cpp new/mapserver-8.6.5/src/mapwcs.cpp --- old/mapserver-8.6.3/src/mapwcs.cpp 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/src/mapwcs.cpp 2026-07-10 19:20:43.000000000 +0200 @@ -357,6 +357,23 @@ } /************************************************************************/ +/* msWCSSetParam */ +/************************************************************************/ + +static int msWCSSetParam(char **ppszOut, cgiRequestObj *request, int i, + const char *pszExpectedParamName) { + if (strcasecmp(request->ParamNames[i], pszExpectedParamName) == 0) { + /* Keep the first value supplied and ignore later duplicates. This avoids + leaking the previously allocated value when the same parameter appears + multiple times in a request (consistent with msWFSSetParam()). */ + if (*ppszOut == NULL) + *ppszOut = msStrdup(request->ParamValues[i]); + return 1; + } + return 0; +} + +/************************************************************************/ /* msWCSParseRequest() */ /************************************************************************/ @@ -434,6 +451,15 @@ if (EQUAL((char *)tmpNode->name, "BoundingBox")) { xmlNodePtr cornerNode = NULL; params->crs = (char *)xmlGetProp(tmpNode, BAD_CAST "crs"); + if (!params->crs) { + msSetError(MS_WCSERR, "Required parameter CRS was not supplied.", + "msWCSGetCoverage()"); + msWCSException(map, "MissingParameterValue", "crs", + params->version); + xmlFreeDoc(doc); + xmlCleanupParser(); + return MS_DONE; + } if (strncasecmp(params->crs, "urn:ogc:def:crs:", 16) == 0 && strncasecmp(params->crs + strlen(params->crs) - 8, "imageCRS", 8) == 0) @@ -447,8 +473,13 @@ msSetError(MS_WCSERR, "Wrong number of arguments for LowerCorner", "msWCSParseRequest()"); - return msWCSException(map, "InvalidParameterValue", - "LowerCorner", params->version); + msWCSException(map, "InvalidParameterValue", "LowerCorner", + params->version); + msFreeCharArray(tokens, n); + xmlFree(value); + xmlFreeDoc(doc); + xmlCleanupParser(); + return MS_DONE; } params->bbox.minx = atof(tokens[0]); params->bbox.miny = atof(tokens[1]); @@ -462,8 +493,13 @@ msSetError(MS_WCSERR, "Wrong number of arguments for UpperCorner", "msWCSParseRequest()"); - return msWCSException(map, "InvalidParameterValue", - "UpperCorner", params->version); + msWCSException(map, "InvalidParameterValue", "UpperCorner", + params->version); + msFreeCharArray(tokens, n); + xmlFree(value); + xmlFreeDoc(doc); + xmlCleanupParser(); + return MS_DONE; } params->bbox.maxx = atof(tokens[0]); params->bbox.maxy = atof(tokens[1]); @@ -493,8 +529,13 @@ msSetError(MS_WCSERR, "Wrong number of arguments for GridOrigin", "msWCSParseRequest()"); - return msWCSException(map, "InvalidParameterValue", - "GridOffsets", params->version); + msWCSException(map, "InvalidParameterValue", "GridOffsets", + params->version); + msFreeCharArray(tokens, n); + xmlFree(value); + xmlFreeDoc(doc); + xmlCleanupParser(); + return MS_DONE; } params->originx = atof(tokens[0]); params->originy = atof(tokens[1]); @@ -507,8 +548,13 @@ msSetError(MS_WCSERR, "Wrong number of arguments for GridOffsets", "msWCSParseRequest()"); - return msWCSException(map, "InvalidParameterValue", - "GridOffsets", params->version); + msWCSException(map, "InvalidParameterValue", "GridOffsets", + params->version); + msFreeCharArray(tokens, n); + xmlFree(value); + xmlFreeDoc(doc); + xmlCleanupParser(); + return MS_DONE; } /* take absolute values to convert to positive RESX/RESY style WCS 1.0 behavior. *but* this does break some possibilities! */ @@ -540,22 +586,20 @@ if (request->NumParams > 0) { for (i = 0; i < request->NumParams; i++) { - if (strcasecmp(request->ParamNames[i], "VERSION") == 0) - params->version = msStrdup(request->ParamValues[i]); - if (strcasecmp(request->ParamNames[i], "UPDATESEQUENCE") == 0) - params->updatesequence = msStrdup(request->ParamValues[i]); - else if (strcasecmp(request->ParamNames[i], "REQUEST") == 0) - params->request = msStrdup(request->ParamValues[i]); - else if (strcasecmp(request->ParamNames[i], "INTERPOLATION") == 0) - params->interpolation = msStrdup(request->ParamValues[i]); - else if (strcasecmp(request->ParamNames[i], "SERVICE") == 0) - params->service = msStrdup(request->ParamValues[i]); - else if (strcasecmp(request->ParamNames[i], "SECTION") == 0) /* 1.0 */ - params->section = - msStrdup(request->ParamValues[i]); /* TODO: validate value here */ - else if (strcasecmp(request->ParamNames[i], "SECTIONS") == 0) /* 1.1 */ - params->section = - msStrdup(request->ParamValues[i]); /* TODO: validate value here */ + if (msWCSSetParam(&(params->version), request, i, "VERSION")) { + } else if (msWCSSetParam(&(params->updatesequence), request, i, + "UPDATESEQUENCE")) { + } else if (msWCSSetParam(&(params->request), request, i, "REQUEST")) { + } else if (msWCSSetParam(&(params->interpolation), request, i, + "INTERPOLATION")) { + } else if (msWCSSetParam(&(params->service), request, i, "SERVICE")) { + } else if (msWCSSetParam(&(params->section), request, i, + "SECTION")) { /* 1.0 */ + /* TODO: validate value here */ + } else if (msWCSSetParam(&(params->section), request, i, + "SECTIONS")) { /* 1.1 */ + /* TODO: validate value here */ + } /* GetCoverage parameters. */ else if (strcasecmp(request->ParamNames[i], "BBOX") == 0) { @@ -583,14 +627,12 @@ else if (strcasecmp(request->ParamNames[i], "COVERAGE") == 0) params->coverages = CSLAddString(params->coverages, request->ParamValues[i]); - else if (strcasecmp(request->ParamNames[i], "TIME") == 0) - params->time = msStrdup(request->ParamValues[i]); - else if (strcasecmp(request->ParamNames[i], "FORMAT") == 0) - params->format = msStrdup(request->ParamValues[i]); - else if (strcasecmp(request->ParamNames[i], "CRS") == 0) - params->crs = msStrdup(request->ParamValues[i]); - else if (strcasecmp(request->ParamNames[i], "RESPONSE_CRS") == 0) - params->response_crs = msStrdup(request->ParamValues[i]); + else if (msWCSSetParam(&(params->time), request, i, "TIME")) { + } else if (msWCSSetParam(&(params->format), request, i, "FORMAT")) { + } else if (msWCSSetParam(&(params->crs), request, i, "CRS")) { + } else if (msWCSSetParam(&(params->response_crs), request, i, + "RESPONSE_CRS")) { + } /* WCS 1.1 DescribeCoverage and GetCoverage ... */ else if (strcasecmp(request->ParamNames[i], "IDENTIFIER") == 0 || @@ -606,8 +648,10 @@ if (tokens == NULL || n < 5) { msSetError(MS_WCSERR, "Wrong number of arguments for BOUNDINGBOX.", "msWCSParseRequest()"); - return msWCSException(map, "InvalidParameterValue", "boundingbox", - params->version); + msWCSException(map, "InvalidParameterValue", "boundingbox", + params->version); + msFreeCharArray(tokens, n); + return MS_DONE; } /* NOTE: WCS 1.1 boundingbox is center of pixel oriented, not edge @@ -618,6 +662,7 @@ params->bbox.maxx = atof(tokens[2]); params->bbox.maxy = atof(tokens[3]); + msFree(params->crs); params->crs = msStrdup(tokens[4]); msFreeCharArray(tokens, n); /* normalize imageCRS urns to simply "imageCRS" */ @@ -630,8 +675,10 @@ if (tokens == NULL || n < 2) { msSetError(MS_WCSERR, "Wrong number of arguments for GridOffsets", "msWCSParseRequest()"); - return msWCSException(map, "InvalidParameterValue", "GridOffsets", - params->version); + msWCSException(map, "InvalidParameterValue", "GridOffsets", + params->version); + msFreeCharArray(tokens, n); + return MS_DONE; } /* take absolute values to convert to positive RESX/RESY style WCS 1.0 behavior. *but* this does break some possibilities! */ @@ -643,8 +690,10 @@ if (tokens == NULL || n < 2) { msSetError(MS_WCSERR, "Wrong number of arguments for GridOrigin", "msWCSParseRequest()"); - return msWCSException(map, "InvalidParameterValue", "GridOffsets", - params->version); + msWCSException(map, "InvalidParameterValue", "GridOffsets", + params->version); + msFreeCharArray(tokens, n); + return MS_DONE; } params->originx = atof(tokens[0]); params->originy = atof(tokens[1]); @@ -3163,6 +3212,11 @@ strcmp(ows_request->version, "1.1.2") == 0) { auto paramsTmp = msWCSCreateParams(); status = msWCSParseRequest(request, paramsTmp, map); + if (status == MS_DONE) { + msWCSFreeParams(paramsTmp); + free(paramsTmp); + return MS_FAILURE; + } if (status == MS_FAILURE) { msWCSFreeParams(paramsTmp); free(paramsTmp); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mapserver-8.6.3/src/mapwms.cpp new/mapserver-8.6.5/src/mapwms.cpp --- old/mapserver-8.6.3/src/mapwms.cpp 2026-05-07 14:17:41.000000000 +0200 +++ new/mapserver-8.6.5/src/mapwms.cpp 2026-07-10 19:20:43.000000000 +0200 @@ -991,9 +991,12 @@ non mandatory */ for (int i = 0; i < numentries; i++) { + if (strcasecmp(names[i], "REQUEST") == 0) { + request = values[i]; + } /* check if SLD is passed. If yes, check for OGR support */ - if (strcasecmp(names[i], "SLD") == 0 || - strcasecmp(names[i], "SLD_BODY") == 0) { + else if (strcasecmp(names[i], "SLD") == 0 || + strcasecmp(names[i], "SLD_BODY") == 0) { sldenabled = msOWSLookupMetadata(&(map->web.metadata), "MO", "sld_enabled"); @@ -1012,15 +1015,15 @@ } } + const char *layerKey = request && strcasecmp(request, "GetLegendGraphic") == 0 + ? "LAYER" + : "LAYERS"; + std::vector<std::string> wmslayers; for (int i = 0; i < numentries; i++) { /* getMap parameters */ - if (strcasecmp(names[i], "REQUEST") == 0) { - request = values[i]; - } - - if (strcasecmp(names[i], "LAYERS") == 0) { + if (strcasecmp(names[i], layerKey) == 0) { std::vector<int> layerOrder(map->numlayers); wmslayers = msStringSplit(values[i], ','); @@ -5983,16 +5986,6 @@ } if (found) { isContentDependentLegend = true; - /* getLegendGraphic uses LAYER= , we need to create a LAYERS= value that - * is identical we'll suppose that the client is conformat and hasn't - * included a LAYERS= parameter in its request */ - for (int i = 0; i < req->NumParams; i++) { - if (strcasecmp(req->ParamNames[i], "LAYER") == 0) { - req->ParamNames[req->NumParams] = msStrdup("LAYERS"); - req->ParamValues[req->NumParams] = msStrdup(req->ParamValues[i]); - req->NumParams++; - } - } } else { return msWMSLegendGraphic(map, nVersion, req->ParamNames, req->ParamValues, req->NumParams,
