Share: confirm before opening non-http(s) URIs from scanned content#646
Share: confirm before opening non-http(s) URIs from scanned content#646dsremo wants to merge 2 commits into
Conversation
Currently any URI a QR code resolves to is handed straight to ACTION_VIEW
the moment the user taps Open. For http(s)/content/file that's the
expected behaviour, but for tel:/sms:/mailto:/mms:/intent:/etc. it means
a malicious QR can auto-dial a premium-rate number, pre-compose an SMS
to a short-code, or hand off to an arbitrary app via a deep-link
intent:// before the user can react.
This adds a single AlertDialog before openUri() launches an intent for
any scheme other than http/https/content/file. The dialog shows the
full URI and a human-readable description of which app it would open
('phone dialer', 'SMS composer', etc.), with Cancel as the default
action. http(s) URLs continue to open immediately as before — the new
path adds zero friction for the common case.
Silent callers (silent=true) bypass the confirmation since they're
internal openers (eg. user has already approved via the action sheet
in another flow).
8 new strings + 41 lines in Share.kt. No new permissions, no new
dependencies, no UI changes outside the new dialog.
|
Thanks for the contribution! An additional dialog really makes sense, but…
if I scan a
Also please always add all translations since |
4099dab to
ba61e40
Compare
The Mail/SMS/Phone FABs on the action sheet go through IntentAction.execute() -> execShareIntent(), which bypassed the consent dialog added in the previous commit (that path only guarded the generic Open icon's openUri() flow). Extract confirmSensitiveAction() and schemeNeedsConsent() helpers, then add execShareIntentWithConsent(intent) which inspects the intent's data URI and gates on the same scheme list. IntentAction now calls the new function so mailto/tel/sms/MATMSG barcodes prompt the user whether they tap the Mail/SMS/Phone FAB or the generic Open icon. Intents with no data URI (calendar add, vcard import) and intents to safe schemes (http/https/content/file) pass through. Translate the 8 new scheme_* strings into all 25 existing locales so ./gradlew :app:lintDebug stays clean.
|
Fixed both points. The Mail FAB goes through IntentAction.execute() → execShareIntent(), not openUri(), so my dialog never fired on that path. Refactored Share.kt to extract the dialog logic into confirmSensitiveAction() + schemeNeedsConsent(), and added a new Context.execShareIntentWithConsent(intent) that inspects intent.data?.scheme and reuses the same dialog. IntentAction.execute() now calls that helper, so all four sensitive-scheme subclasses (MailAction, MatMsgAction, SmsAction, TelAction) are guarded. VCardAction and VEventAction have no data URI and pass through unchanged. Found one more path but since it is meant for the automated opening and is already user-authored so probably the person would not want the dialog there so letting it be independent. Translations added for all 25 existing locales; ./gradlew :app:lintDebug is clean. : Yes AI is really helpfull in monotonous tasks 😁 |

Problem
Right now any URI a QR code resolves to is handed straight to
Intent.ACTION_VIEWthe moment the user taps the open icon in the action sheet. Forhttp/https/content/filethat's the expected behaviour, but fortel:/sms:/mailto:/intent:/ arbitrary custom schemes it means a malicious QR can:tel:sms:/smsto:intent://com.target.app/...#Intent;...;end…before the user has any chance to read the actual destination. The action sheet shows the parsed content, but the act of tapping the open icon commits the launch.
Fix
A single
AlertDialogbeforeopenUri()launches the intent — but only for schemes other thanhttp,https,content,file. http(s) URLs continue to open immediately as before — zero added friction for the common case.The dialog shows:
"phone dialer","SMS composer","MMS composer","email composer", or"external app (\<scheme\>)"for anything elseDefault action is Cancel; positive button is "Open".
What changes
app/src/main/kotlin/de/markusfisch/android/binaryeye/content/Share.kt(+41):openUri()now branches by scheme; non-standard schemes route through a newAlertDialog. Standard schemes (andsilent=truecalls) go to a small extractedlaunchUri()helper that preserves the original behaviour (FLAG_GRANT_READ_URI_PERMISSIONforcontent://,execShareIntentvsstartIntentbased onsilent).app/src/main/res/values/strings.xml(+8): five scheme-label strings, dialog title, body template, "Open" button label.No new permissions, no new dependencies, no UI changes outside the new dialog.
Tested
tel:+12025551234→ dialog shows "phone dialer" + URI → Cancel does nothing, Open launches the dialer ✓https://example.com/...URL → opens immediately, no dialog (same as today) ✓mailto:test@example.com?subject=...→ dialog shows "email composer" ✓intent://...#Intent;...;end→ dialog shows "external app (intent)" ✓silent=truepath) → bypasses dialog as expected ✓Notes
silentparameter onopenUri()is preserved untouched — bypasses the confirmation since silent callers are internal openers (the user has already approved through another flow).tel:codes legitimately), happy to add a toggle.