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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug 🪲Something isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions