Skip to content

[Bug]: addCustomHeader URL regex matching fails on Android for partial/substring matches #4257

Description

@DqPatrick

Mapbox Version

default

React Native Version

0.83.6

Platform

Android, iOS

@rnmapbox/maps version

10.3.0

Standalone component to reproduce

import React from 'react';
import { View } from 'react-native';
import Mapbox from '@rnmapbox/maps';

// Substring regex (works on iOS, fails on Android due to strict matching)
const tileUrlRegex = /https:\/\/tiles\.example\.com\/v1\/map/;

class BugReportExample extends React.Component {
  componentDidMount() {
    // Inject custom header using the partial-match regex
    Mapbox.addCustomHeader(
      'X-Test-Header',
      'test-value-123',
      { urlMatching: tileUrlRegex }
    );
  }

  render() {
    return (
      <View style={{ flex: 1 }}>
        <Mapbox.MapView style={{ flex: 1 }}>
          <Mapbox.Camera centerCoordinate={[-74.00597, 40.71427]} zoomLevel={14} />
          
          {/* A RasterSource pointing to the dummy tile server to trigger requests containing extra parameters */}
          <Mapbox.RasterSource
            id="exampleTileSource"
            tileUrlTemplates={[
              'https://tiles.example.com/v1/map?style=streets&zoom={z}&x={x}&y={y}&format=png'
            ]}
            tileSize={256}
          >
            <Mapbox.RasterLayer id="exampleTileLayer" />
          </Mapbox.RasterSource>
        </Mapbox.MapView>
      </View>
    );
  }
}

export default BugReportExample;

Observed behavior and steps to reproduce

When calling Mapbox.addCustomHeader with a regular expression in urlMatching, the behavior diverges between platforms:

  • iOS successfully matches the URL and injects the header if the regex matches any substring of the request URL.
  • Android silently fails to inject the header unless the regular expression matches the entire URL string from start to finish.

Steps to Reproduce:

  1. Call Mapbox.addCustomHeader("X-Test-Header", "test-value-123", { urlMatching: /https:\/\/tiles\.example\.com\/v1\/map/ }) using a basic substring regex.
  2. Load a map layer pointing to a target URL that contains trailing parameters, such as:
    https://tiles.example.com/v1/map?style=streets&zoom=14&x=4823&y=6122&format=png
  3. Run the app on an iOS device/simulator. Observe that the network request successfully includes X-Test-Header: test-value-123.
  4. Run the exact same code on an Android device/emulator. Observe the network traffic. The custom header is missing because the trailing query and coordinate parameters (?style=streets&zoom=14...) prevent the regex from achieving a full-string match on the native layer.

Expected behavior

The regular expression evaluation should be unified across both platforms. Both iOS and Android should evaluate urlMatching using a substring/partial match algorithm.

If a pattern matches a portion of the incoming request URL, the header should be successfully appended on both platforms without forcing developers to write cross-platform "hacks" like appending .*$ to their regular expressions.

Notes / preliminary analysis

The bug is caused by a fundamental difference in how the native platforms evaluate regular expressions within their custom network interceptors.

iOS Native Side (Substring Match)

iOS utilizes NSRegularExpression.firstMatch, which checks for the first occurrence of a matching substring in the URL:

if pattern.firstMatch(in: urlString, options: [], range: range) != nil {
    headers[key] = entry.headerValue
}
  • This acts like JavaScript's .test() or a substring search. It is highly flexible.

Android Native Side (Strict Full Match)

Android utilizes Kotlin's Regex.matches(), which evaluates whether the entire URL matches the pattern:

if (urlRegexp.matches(destination)) {
    headers[entry.key] = entry.value.headerValue
}
  • Kotlin's Regex.matches(input) compiles down to Java's Matcher.matches().
  • According to Java specifications, matches() checks if the entire region sequence conforms to the regex (essentially wrapping your pattern in implicit ^...$ anchors). If there are trailing query parameters (which there always are on tile/map servers), the check fails.

Suggested Fix

To align Android with the iOS implementation, the Android native network interceptor should be updated to use containsMatchIn instead of matches:

// Change this line in the Android interceptor to support substring matches:
if (urlRegexp.containsMatchIn(destination)) { 
    headers[entry.key] = entry.value.headerValue
}

Additional links and references

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug 🪲Something isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions