Skip to content

Uncaught std::regex_error in createTokenMap aborts the app during offline pack download when style glyphs are hosted on demotiles.maplibre.org #4403

Description

@bonesyjr

Platform / version

  • iOS, MapLibre Native ios-v6.27.0 (via @maplibre/maplibre-react-native 11.3.4), observed in production on iOS 26.5.2
  • The affected code is shared (src/mbgl/util/mapbox.cpp), so Android should reproduce as well

Steps to reproduce

  1. Use any style whose glyphs points at the default MapLibre tile server host, e.g.
    https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf
    (tiles can come from anywhere — only the glyph URL matters; sources in our style are on a different host entirely)
  2. Create an offline pack from that style (OfflineDownload / MLNOfflineStorage addPack…)
  3. As soon as the style resource response is processed, the process aborts with SIGABRT

100% reproducible.

Root cause

OfflineDownload::activateDownload()'s style response callback calls
mbgl::util::mapbox::canonicalizeGlyphURL(tileServerOptions, url). Because the glyph URL matches
the default TileServerOptions::MapLibreConfiguration() baseURL (https://demotiles.maplibre.org),
isNormalizedSourceURL(...) returns true and createTokenMap(tileServerOptions.glyphsTemplate(), url) runs.

createTokenMap (src/mbgl/util/mapbox.cpp) builds a std::regex from the glyphs template after
substituting only the {domain|path|directory|filename|extension} tokens:

std::regex tokenPattern(R"(\{domain\}|\{path\}|\{directory\}|\{filename\}|\{extension\})");
std::string templatePattern = std::regex_replace(urlTemplate, tokenPattern, "(.+)", std::regex_constants::match_any);
std::regex r2(templatePattern);   // <-- throws

The default MapLibre glyphs template is /font/{fontstack}/{start}-{end}.pbf, so {fontstack},
{start} and {end} survive into templatePattern. {f etc. are invalid ECMAScript brace
quantifiers, so the std::regex constructor throws regex_error. Nothing on the
DatabaseFileSource thread catches it → std::terminate → SIGABRT.

(isNormalizedSourceURL happens to survive because its token pattern \{.+\} greedily swallows
{fontstack}/{start}-{end} in one replacement — which is why execution reaches createTokenMap
at all.)

Symbolicated crash (thread org.maplibre.mbgl.DatabaseFileSource)

libc++abi.dylib  __cxa_throw
MapLibre  std::__1::__throw_regex_error[abi:ue170006]<(std::__1::regex_constants::error_type)8>()  (regex:0)
MapLibre  std::__1::basic_regex<...>::__parse_ERE_dupl_symbol<...>(...)  (regex:0)
MapLibre  std::__1::basic_regex<...>::__parse_ecma_exp<...>(...)  (regex:4283)
MapLibre  std::__1::basic_regex<...>::basic_regex[abi:ue170006]<...>(std::string const&, syntax_option_type)  (regex:2719)
MapLibre  mbgl::util::mapbox::createTokenMap(std::string const&, std::string const&)  (mapbox.cpp:84)
MapLibre  mbgl::util::mapbox::canonicalizeGlyphURL(mbgl::TileServerOptions const&, std::string const&)  (mapbox.cpp:392)
MapLibre  mbgl::OfflineDownload::activateDownload()::$_6 response callback  (function.h:364)
MapLibre  mbgl::OfflineDownload::ensureResource(...) response lambda  (function.h:364)
MapLibre  mbgl::FileSourceRequest::setResponse(mbgl::Response const&)  (file_source_request.cpp:29)
MapLibre  mbgl::Mailbox::receive()  (mailbox.cpp:149)
MapLibre  mbgl::util::RunLoop::process()  (run_loop.hpp:133)
MapLibre  mbgl::util::Thread<mbgl::DatabaseFileSourceThread> thread body  (thread.hpp:70)

Suggested fix

Any (ideally both) of:

  1. In createTokenMap (and isNormalizedSourceURL), regex-escape the non-token parts of the
    template — or at least strip/escape leftover {...} tokens — before constructing std::regex,
    so arbitrary glyphsTemplate values can't produce an invalid pattern.
  2. Wrap the canonicalization calls in OfflineDownload in try/catch and fall back to the raw
    URL, so a canonicalization failure degrades gracefully instead of terminating the host app.

Related

  • Offline download crashes #177 reported offline downloads crashing with demotiles in 2022 and already suspected the regex
    validation in mapbox.cpp; it was closed as "demotiles doesn't support offline" after the
    maintainer hit a different symptom (304 handling). The uncaught-throw path reported here is
    still present in ios-v6.27.0, and it triggers even when only the glyphs live on
    demotiles.maplibre.org (a common configuration, since demotiles is MapLibre's public font
    endpoint) while all tile sources are elsewhere.
  • Uncaught std::domain_error when style URL is unreachable (offline → host app crash) #4295 is the same failure class on a different code path (uncaught exception on the offline
    path terminating the host app).

Workaround for anyone hitting this

Host your glyphs anywhere that is not demotiles.maplibre.org (we moved to OpenFreeMap's
font endpoint); the canonicalizer then leaves the URL untouched and the download completes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions