fix(sdk-webview): reject file picker result when MIME type doesn't match accepted types

Some devices show .doc files in the picker even when only .docx was
requested (accept=".docx"). Passing the .doc URI to the WebView renderer
could cause a native crash. Now we check the actual MIME type of the
selected file against the accepted list and cancel the upload if it does
not match, preventing the renderer from receiving an unexpected file type.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
XuqmGroup 2026-06-23 11:27:21 +08:00
父节点 a481da7bad
当前提交 265994042e

查看文件

@ -360,6 +360,7 @@ fun XWebViewView(
// <input type="file" capture> camera capture
val pendingFileCallback = remember { mutableStateOf<ValueCallback<Array<Uri>>?>(null) }
val pendingCameraUri = remember { mutableStateOf<Uri?>(null) }
val pendingAcceptMimes = remember { mutableStateOf<Array<String>?>(null) }
val takePictureLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.TakePicture()
@ -374,7 +375,28 @@ fun XWebViewView(
val pickContentLauncher = rememberLauncherForActivityResult(GetContentWithMimeTypes()) { uri ->
val cb = pendingFileCallback.value
pendingFileCallback.value = null
cb?.onReceiveValue(if (uri != null) arrayOf(uri) else null)
if (uri == null) {
cb?.onReceiveValue(null)
return@rememberLauncherForActivityResult
}
// Validate that the selected file's actual MIME type is within the accepted list.
// Some devices show extra file types in the picker (e.g. .doc when only .docx was
// requested). Passing an unsupported type to the WebView renderer can cause a native
// crash. We accept the file only when its MIME type matches, or fall back to passing
// it through when we can't determine the type (to avoid blocking legitimate uploads).
val acceptedMimes = pendingAcceptMimes.value
val actualMime = uri.let { context.contentResolver.getType(it) }
val allowed = acceptedMimes.isNullOrEmpty() ||
acceptedMimes.any { accepted ->
accepted == "*/*" ||
(actualMime != null && (actualMime == accepted || actualMime.startsWith(accepted.substringBefore("*"))))
}
if (!allowed) {
android.util.Log.w("XWV", "Rejected file: actualMime=$actualMime not in accepted=$acceptedMimes")
cb?.onReceiveValue(null)
} else {
cb?.onReceiveValue(arrayOf(uri))
}
}
val shouldLaunchCamera = remember { mutableStateOf(false) }
@ -605,6 +627,7 @@ fun XWebViewView(
pendingFileCallback.value = filePathCallback
val acceptMimes = resolvePickerMimeTypes(fileChooserParams.acceptTypes)
pendingAcceptMimes.value = acceptMimes
val isCameraCapture = fileChooserParams.isCaptureEnabled &&
fileChooserParams.acceptTypes.any { it.contains("image") }
val isImageOnly = acceptMimes.all { it.startsWith("image/") || it == "image/*" }