Skip to content

Conversation

@OtavioStasiak
Copy link
Contributor

@OtavioStasiak OtavioStasiak commented Jan 12, 2026

Proposed changes

In iOS 18.6, Apple introduced stricter validation for URL(fileURLWithPath:) that causes fatal crashes in production TestFlight builds when HTTP/HTTPS URLs are incorrectly passed to this API.

API MISUSE: URL(filePath:) called with an HTTP URL string. Using URL(string:) instead.
Fatal error: API MISUSE: URL(filePath:) called with an HTTP URL string. URL(string:) returned nil.

The isFileUrlPath() function in expo-modules-core was incorrectly identifying HTTP/HTTPS URLs as file paths. This happened because:

  1. The function checks if a URL has no scheme by parsing it with URL(string:)
  2. If the URL parsing fails or returns nil (e.g., malformed URLs), it assumes it's a file path
  3. The code then calls URL(fileURLWithPath:) on HTTP URLs, triggering iOS 18.6's strict validation
  4. iOS 18.6 detects the API misuse and crashes with a fatal error

Solution:

Added explicit HTTP/HTTPS URL detection before any file path checks to prevent HTTP URLs from being passed to URL(fileURLWithPath:).

  1. Early HTTP/HTTPS Detection: Check for http:// or https:// prefixes at the start of the conversion process
  2. Direct URL Parsing: Parse HTTP URLs using URL(string:) or URLComponents and return early
  3. Safety Check: Double-check inside file path handling to ensure HTTP URLs don't slip through
  4. Enhanced Validation: Added validation for empty paths, control characters, and path format requirements

Issue(s)

https://rocketchat.atlassian.net/browse/SUP-962

How to test or reproduce

Screenshots

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced URL parsing and validation with improved error handling for more reliable URL conversion across iOS platforms.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 12, 2026

Walkthrough

This patch enhances URL conversion logic in expo-modules-core's Convertibles.swift by adding HTTP(S) URL detection with fallback parsing, introducing iOS 18.6+ file path validation with comprehensive error handling for empty strings, invalid characters, and path format requirements.

Changes

Cohort / File(s) Summary
URL Conversion Enhancement
patches/expo-modules-core+2.3.12.patch
Added HTTP/HTTPS URL handling with fallback to URLComponents parsing; introduced iOS 18.6+ file path validation including empty string guards, whitespace trimming, absolute/file URL format requirements, invalid character checks, and dual constructor support (file:// URLs and absolute paths)

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Suggested reviewers

  • diegolmello

Poem

🐰 A patch hops through the URL fields,
HTTP caught, file paths now sealed,
iOS validation, errors revealed,
Robust parsing, the shift in how we build.
hippity-hop

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly addresses the main change: fixing a crash on iOS 18.6+ when loading HTTP/HTTPS image URLs, which aligns with the patch's focus on enhanced URL conversion logic and platform-specific validation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix.normalize-url-to-use-expo-image

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/containers/UIKit/Image.tsx (1)

37-41: Normalize the URL in Media for consistency and robustness.

Media passes imageUrl directly to ImageContainer without normalization, while Thumb normalizes its URL using normalizeImageUrl. The downstream formatAttachmentUrl and MessageImage do not perform the same validation. Since normalizeImageUrl specifically prevents iOS crashes with expo-image by rejecting invalid protocols (file://, data:) and validating HTTP/HTTPS URLs, this inconsistency could leave Media vulnerable to crashes if element?.imageUrl contains invalid or malformed URLs. Apply normalizeImageUrl to imageUrl in Media before passing it to ImageContainer.

🤖 Fix all issues with AI agents
In @app/containers/message/Components/Attachments/Reply.tsx:
- Around line 169-177: The protocol check using image.includes('http') is too
permissive and misclassifies relative paths; change it to a proper starts-with
or regex check (e.g., test for 'http://' or 'https://' via
image.startsWith('http://') || image.startsWith('https://') or
/^https?:\/\//.test(image)) when constructing fullImageUrl so that only true
absolute URLs are used, keeping the rest of the logic that builds fullImageUrl
and calls normalizeImageUrl(user.id, user.token) unchanged.
🧹 Nitpick comments (1)
app/lib/methods/helpers/normalizeImageUrl.ts (1)

24-32: Consider case-insensitive checks for URL scheme prefixes.

The checks use startsWith which is case-sensitive. While uncommon, URLs like FILE:// or Data: would bypass these guards and fail later at the URL parsing stage. The current behavior is safe (parsing would reject them), but explicit handling would be clearer.

Optional: Case-insensitive prefix checks
 	// Reject file:// URLs - these should be handled separately
-	if (trimmedUrl.startsWith('file://')) {
+	if (trimmedUrl.toLowerCase().startsWith('file://')) {
 		return null;
 	}

 	// Reject data: URLs - these are handled differently by expo-image
-	if (trimmedUrl.startsWith('data:')) {
+	if (trimmedUrl.toLowerCase().startsWith('data:')) {
 		return null;
 	}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ede2569 and a251a51.

⛔ Files ignored due to path filters (1)
  • app/containers/markdown/__snapshots__/Markdown.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (4)
  • app/containers/UIKit/Image.tsx
  • app/containers/message/Components/Attachments/Reply.tsx
  • app/containers/message/Urls.tsx
  • app/lib/methods/helpers/normalizeImageUrl.ts
🧰 Additional context used
🧬 Code graph analysis (3)
app/containers/message/Urls.tsx (3)
app/lib/methods/helpers/normalizeImageUrl.ts (1)
  • normalizeImageUrl (12-68)
app/containers/UIKit/index.tsx (2)
  • image (124-126)
  • image (185-187)
app/containers/UIKit/Image.tsx (1)
  • Image (54-54)
app/containers/message/Components/Attachments/Reply.tsx (1)
app/lib/methods/helpers/normalizeImageUrl.ts (1)
  • normalizeImageUrl (12-68)
app/containers/UIKit/Image.tsx (2)
app/containers/UIKit/interfaces.ts (1)
  • IThumb (210-213)
app/lib/methods/helpers/normalizeImageUrl.ts (1)
  • normalizeImageUrl (12-68)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: ESLint and Test / run-eslint-and-test
  • GitHub Check: format
🔇 Additional comments (5)
app/containers/message/Components/Attachments/Reply.tsx (1)

179-179: LGTM!

The normalized URL is correctly passed to the Image component, ensuring expo-image receives a validated HTTP/HTTPS URL.

app/containers/UIKit/Image.tsx (1)

27-35: LGTM!

The Thumb component correctly normalizes the URL and gracefully handles invalid/null URLs by returning null.

app/containers/message/Urls.tsx (2)

72-90: LGTM!

The URL normalization is correctly integrated:

  • The hook runs unconditionally (respecting React's rules of hooks)
  • The loadAsync call is properly guarded with normalizedUrl && maxSize
  • The early return after the hook prevents rendering invalid URLs

123-123: LGTM!

The Image source correctly uses the normalized URL.

app/lib/methods/helpers/normalizeImageUrl.ts (1)

12-67: Well-designed defensive helper.

The function handles edge cases thoroughly:

  • Null/undefined/empty inputs
  • Whitespace trimming
  • Unsupported schemes (file://, data:)
  • Protocol-relative URLs
  • Malformed URLs

The docstring clearly explains the iOS crash scenario this prevents.

@github-actions
Copy link

iOS Build Available

Rocket.Chat Experimental 4.69.0.108048

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @patches/expo-modules-core+2.3.12.patch:
- Around line 7-68: The patch accidentally adds two IDE-generated Eclipse
.project files (expo-modules-core/.project and
expo-modules-core/expo-module-gradle-plugin/.project); remove these files from
the patch and regenerate the patch so it contains only the intended runtime code
changes (the Swift/URL validation edits), ensuring no IDE/project descriptor
files are included.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a251a51 and 6566c7b.

📒 Files selected for processing (1)
  • patches/expo-modules-core+2.3.12.patch
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: ESLint and Test / run-eslint-and-test
  • GitHub Check: format
🔇 Additional comments (1)
patches/expo-modules-core+2.3.12.patch (1)

73-82: Good defensive fix to prevent crashes on empty file paths.

The guard correctly prevents URL(fileURLWithPath:) from receiving an empty string, which would create an invalid URL and could cause crashes downstream in expo-image. This aligns well with the documented intent in commit 6566c7b ("fix: expo url validation") and the project's pattern of handling edge cases in the expo ecosystem.

Minor note: UrlContainsInvalidCharactersException is semantically imprecise for an empty value (empty ≠ invalid characters), but reusing the existing exception type is a pragmatic choice that avoids adding new exception types for edge cases.

@github-actions
Copy link

iOS Build Available

Rocket.Chat Experimental 4.69.0.108105

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @app/views/ShareView/Preview.tsx:
- Around line 101-116: The IconPreview call passes item?.filename which can be
undefined; update the title prop in the IconPreview invocation (inside
Preview.tsx where IconPreview is rendered) to provide a guaranteed string
fallback (e.g., item?.filename ?? '' or 'Untitled') so the title prop always
receives a string; leave other props (description using prettyBytes(item?.size
?? 0), theme, width, height) unchanged.
🧹 Nitpick comments (1)
app/containers/message/Components/Attachments/Image/Image.tsx (1)

15-15: Dual URL validation is redundant but acceptable.

The component now uses both isValidUrl (line 24) and isValidImageUri (line 70). Since isValidImageUri is more comprehensive and specifically designed for expo-image safety, consider consolidating to use only isValidImageUri in showImage to avoid redundant checks.

♻️ Optional consolidation
-import { isValidUrl } from '../../../../../lib/methods/helpers/isValidUrl';
 // ...
-const showImage = isValidUrl(uri) && imageDimensions.width && status === 'downloaded';
+const showImage = isValidImageUri(uri) && imageDimensions.width && status === 'downloaded';
 // ...
-const isValidUri = isValidImageUri(uri);
 // ...
-{showImage && isValidUri ? (
+{showImage ? (

Also applies to: 24-24, 70-74

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6566c7b and 9fd30c0.

⛔ Files ignored due to path filters (1)
  • app/containers/markdown/__snapshots__/Markdown.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (4)
  • app/containers/ImageViewer/ImageViewer.tsx
  • app/containers/message/Components/Attachments/Image/Image.tsx
  • app/lib/methods/helpers/normalizeImageUrl.ts
  • app/views/ShareView/Preview.tsx
🧰 Additional context used
🧬 Code graph analysis (3)
app/containers/message/Components/Attachments/Image/Image.tsx (1)
app/lib/methods/helpers/normalizeImageUrl.ts (1)
  • isValidImageUri (10-56)
app/containers/ImageViewer/ImageViewer.tsx (1)
app/lib/methods/helpers/normalizeImageUrl.ts (1)
  • isValidImageUri (10-56)
app/views/ShareView/Preview.tsx (1)
app/lib/methods/helpers/normalizeImageUrl.ts (1)
  • isValidImageUri (10-56)
🔇 Additional comments (5)
app/containers/message/Components/Attachments/Image/Image.tsx (1)

27-37: LGTM: Proper guard added before async image loading.

The isValidImageUri check correctly prevents Image.loadAsync from being called with invalid URIs, which aligns with the PR's goal of preventing expo-image crashes.

app/containers/ImageViewer/ImageViewer.tsx (1)

130-137: LGTM: Early validation prevents expo-image crashes.

The early return pattern correctly guards against invalid URIs. The fallback maintains layout dimensions, which prevents UI shifts.

Consider whether a placeholder icon or error indicator would improve user experience, though the silent fallback may be intentional for this component's use case.

app/lib/methods/helpers/normalizeImageUrl.ts (3)

10-56: Well-structured URI validation with comprehensive coverage.

The function properly handles all common URI schemes (data:, file://, http/https, absolute paths) with appropriate fallbacks. The use of try-catch around new URL() prevents exceptions from propagating.


67-123: Solid HTTP/HTTPS URL normalization with edge case handling.

The function correctly:

  • Rejects non-HTTP schemes (data:, file://) as documented
  • Handles protocol-relative URLs (//example.com) by prefixing https:
  • Returns normalized URL strings via urlObj.toString()

Note: The behavioral difference between isValidImageUri (accepts data:) and normalizeImageUrl (rejects data:) is intentional per the documentation, but consumers should be aware of this distinction.


1-1: Import is properly configured.

The react-native-url-polyfill package is installed as a dependency (version 2.0.0), so the import statement will resolve correctly.

@OtavioStasiak OtavioStasiak had a problem deploying to experimental_android_build January 13, 2026 16:02 — with GitHub Actions Error
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @patches/expo-modules-core+2.3.12.patch:
- Around line 1-68: The patch accidentally includes IDE-generated
Eclipse/Buildship files—remove the added
node_modules/expo-modules-core/android/.project and
node_modules/expo-modules-core/expo-module-gradle-plugin/.project entries from
the patch (they contain machine-specific IDs like 1767968821913/1910) and
regenerate the patch so only the URL normalization fix remains; optionally add
node_modules/**/.project to .gitignore before regenerating to prevent
recurrence.
🧹 Nitpick comments (1)
patches/expo-modules-core+2.3.12.patch (1)

85-92: Verbose logging may impact performance in production.

The extensive os_log calls with emoji prefixes and multiple parameters will execute on every URL conversion in production builds. While OSLog is designed to be efficient, the frequency of URL conversions in an image-heavy app could accumulate overhead.

Consider:

  1. Using #if DEBUG preprocessor directives to limit logging to debug builds
  2. Using .debug log type instead of .default for non-error cases (system may optimize these away in production)
♻️ Suggested approach using debug type
-    os_log("🔍 [URL.convert] Attempting to convert URI: %{public}@ (length: %d)", log: logger, type: .default, value, value.count)
+    os_log("🔍 [URL.convert] Attempting to convert URI: %{public}@ (length: %d)", log: logger, type: .debug, value, value.count)
     
     // First we try to create a URL without extra encoding, as it came.
     if let url = convertToUrl(string: value) {
-      os_log("✅ [URL.convert] Successfully converted via convertToUrl: %{public}@", log: logger, type: .default, value)
+      os_log("✅ [URL.convert] Successfully converted via convertToUrl: %{public}@", log: logger, type: .debug, value)
       return url
     }

Keep .error type for actual error cases to ensure they're captured.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e559e08 and 7fb5bbb.

📒 Files selected for processing (1)
  • patches/expo-modules-core+2.3.12.patch
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: ESLint and Test / run-eslint-and-test
  • GitHub Check: format
🔇 Additional comments (2)
patches/expo-modules-core+2.3.12.patch (2)

151-159: Error handling and fallback path looks correct.

The percent-encoding fallback and final error handling maintain consistent behavior:

  • Failed conversions are logged with diagnostic details before throwing
  • UrlContainsInvalidCharactersException is used uniformly, which provides predictable error handling for callers

98-149: Crash prevention logic is well-structured.

The defensive validation for iOS 18.6+ stricter URL handling is comprehensive:

  • Empty path guard prevents assertion failures
  • Whitespace trimming handles edge cases
  • Format validation ensures absolute paths or file:// URLs
  • Control character detection catches invalid input

The inline format validation (distinguishing absolute paths from file:// URLs) is defensive and necessary even if isFileUrlPath already identified this as a file path, since it validates the trimmed path conforms to acceptable formats before URL creation.

@github-actions
Copy link

iOS Build Available

Rocket.Chat Experimental 4.69.0.108128

@github-actions
Copy link

iOS Build Available

Rocket.Chat Experimental 4.69.0.108131

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @patches/expo-modules-core+2.3.12.patch:
- Around line 54-66: The block adds a new isHttpUrl that shadows an earlier
variable and incorrectly uses value when constructing the URL; rename the new
boolean (e.g., isHttpUrlLowercased) to avoid shadowing the earlier isHttpUrl and
base it on trimmedLowercased, and when attempting to parse an HTTP URL in the
guard branch use URL(string: trimmedPath) (not URL(string: value)); update the
guard condition to use the renamed boolean and ensure
UrlContainsInvalidCharactersException is thrown only after attempting
URL(string: trimmedPath).
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 87968c3 and 4d0d0d2.

📒 Files selected for processing (1)
  • patches/expo-modules-core+2.3.12.patch
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: ESLint and Test / run-eslint-and-test
  • GitHub Check: format
🔇 Additional comments (3)
patches/expo-modules-core+2.3.12.patch (3)

9-27: LGTM! Early HTTP URL detection prevents iOS 18.6 crash.

The guard against calling fileURLWithPath on HTTP URLs is critical for iOS 18.6+ compatibility. The fallback to URLComponents for RFC 3986 compliance is a sensible addition.


1-4: Monitor expo-modules-core for upstream iOS 18 URL handling fixes.

This patch targets expo-modules-core@2.3.12, which is outdated (current version is 3.0.29 as of January 2026). GitHub issues confirm that iOS 18/Xcode 16 URL fileURLWithPath crashes remain unresolved in the package, with users currently downgrading or applying similar patches as workarounds. Monitor the Expo GitHub repository for an upstream fix before upgrading to newer versions. If upgrading becomes necessary, verify whether the patch still applies or if the issue has been addressed.


38-49: Consider the consistency between isFileUrlPath check and trimmed path usage.

The isFileUrlPath(value) check on line 38 uses the original untrimmed value, but subsequent code trims whitespace (line 46) and uses trimmedPath for validation and URL creation (line 88). While the patch includes defensive re-validation of the trimmed path (checking isAbsolutePath and isFileURL against trimmedPath), this guards against the inconsistency. However, if isFileUrlPath doesn't handle leading/trailing whitespace correctly, the initial branch decision might rely on characteristics that disappear after trimming. The current defensive checks mitigate this, but consider documenting whether isFileUrlPath handles whitespace or trimming the value earlier in the flow.

@github-actions
Copy link

iOS Build Available

Rocket.Chat Experimental 4.69.0.108133

@OtavioStasiak OtavioStasiak changed the title fix: normalize url fix(ios): prevent crash on iOS 18.6+ when loading HTTP/HTTPS image URLs Jan 13, 2026
Copy link
Member

@diegolmello diegolmello left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing PR to expo (so we can remove this patch in the future) and an example URL to understand the issue/solution better

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants