fix: sanitize mpv event channel strings for valid UTF-8
This commit is contained in:
@@ -35,6 +35,20 @@ class MpvPlayerCore(private val activity: Activity) :
|
|||||||
|
|
||||||
// Guards MPVLib.create/destroy which share global native state
|
// Guards MPVLib.create/destroy which share global native state
|
||||||
private val mpvLock = Object()
|
private val mpvLock = Object()
|
||||||
|
|
||||||
|
// JNI's NewStringUTF may produce Java Strings with lone surrogates when
|
||||||
|
// mpv passes invalid UTF-8 (log messages, system-encoded paths, etc.).
|
||||||
|
// Lone surrogates cause FormatException in Flutter's StandardMessageCodec
|
||||||
|
// when it encodes them as UTF-8. Re-encoding strips these invalid chars.
|
||||||
|
private fun safeString(s: String): String {
|
||||||
|
for (c in s) {
|
||||||
|
if (c.isSurrogate()) {
|
||||||
|
// Slow path: re-encode through UTF-8 to replace lone surrogates with U+FFFD
|
||||||
|
return s.encodeToByteArray().decodeToString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s // Fast path: no surrogates, string is safe
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var surfaceView: SurfaceView? = null
|
private var surfaceView: SurfaceView? = null
|
||||||
@@ -344,8 +358,9 @@ class MpvPlayerCore(private val activity: Activity) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun eventProperty(property: String, value: String) {
|
override fun eventProperty(property: String, value: String) {
|
||||||
|
val safe = safeString(value)
|
||||||
activity.runOnUiThread {
|
activity.runOnUiThread {
|
||||||
delegate?.onPropertyChange(property, value)
|
delegate?.onPropertyChange(property, safe)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,11 +391,13 @@ class MpvPlayerCore(private val activity: Activity) :
|
|||||||
MPVLib.MPV_LOG_LEVEL_TRACE -> "trace"
|
MPVLib.MPV_LOG_LEVEL_TRACE -> "trace"
|
||||||
else -> "info"
|
else -> "info"
|
||||||
}
|
}
|
||||||
|
val safePrefix = safeString(prefix)
|
||||||
|
val safeText = safeString(text)
|
||||||
activity.runOnUiThread {
|
activity.runOnUiThread {
|
||||||
delegate?.onEvent("log-message", mapOf(
|
delegate?.onEvent("log-message", mapOf(
|
||||||
"prefix" to prefix,
|
"prefix" to safePrefix,
|
||||||
"level" to levelStr,
|
"level" to levelStr,
|
||||||
"text" to text
|
"text" to safeText
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,21 @@ private class MetalLayer: CAMetalLayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Safely convert a C string to Swift String with UTF-8 validation.
|
||||||
|
/// Falls back to Latin-1 decoding if the bytes are not valid UTF-8.
|
||||||
|
/// mpv does not guarantee UTF-8 for log messages, error strings, or
|
||||||
|
/// system-encoded paths — sending invalid UTF-8 through Flutter's
|
||||||
|
/// StandardMessageCodec causes FormatException crashes.
|
||||||
|
private func safeString(_ cstr: UnsafePointer<CChar>) -> String {
|
||||||
|
if let s = String(validatingUTF8: cstr) {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
// Latin-1 fallback: interpret each byte as its Unicode scalar
|
||||||
|
let len = strlen(cstr)
|
||||||
|
let buf = UnsafeBufferPointer(start: UnsafeRawPointer(cstr).assumingMemoryBound(to: UInt8.self), count: len)
|
||||||
|
return String(buf.map { Character(Unicode.Scalar($0)) })
|
||||||
|
}
|
||||||
|
|
||||||
/// Core MPV player using Metal rendering for iOS
|
/// Core MPV player using Metal rendering for iOS
|
||||||
class MpvPlayerCore: NSObject {
|
class MpvPlayerCore: NSObject {
|
||||||
|
|
||||||
@@ -523,9 +538,9 @@ class MpvPlayerCore: NSObject {
|
|||||||
case MPV_EVENT_LOG_MESSAGE:
|
case MPV_EVENT_LOG_MESSAGE:
|
||||||
if let msgPtr = event.data?.assumingMemoryBound(to: mpv_event_log_message.self) {
|
if let msgPtr = event.data?.assumingMemoryBound(to: mpv_event_log_message.self) {
|
||||||
let msg = msgPtr.pointee
|
let msg = msgPtr.pointee
|
||||||
let prefix = msg.prefix.map { String(cString: $0) } ?? ""
|
let prefix = msg.prefix.map { safeString($0) } ?? ""
|
||||||
let level = msg.level.map { String(cString: $0) } ?? ""
|
let level = msg.level.map { safeString($0) } ?? ""
|
||||||
let text = msg.text.map { String(cString: $0) } ?? ""
|
let text = msg.text.map { safeString($0) } ?? ""
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.delegate?.onEvent(name: "log-message", data: [
|
self.delegate?.onEvent(name: "log-message", data: [
|
||||||
@@ -564,7 +579,7 @@ class MpvPlayerCore: NSObject {
|
|||||||
case MPV_FORMAT_STRING:
|
case MPV_FORMAT_STRING:
|
||||||
if let ptr = property.data {
|
if let ptr = property.data {
|
||||||
let cstr = ptr.assumingMemoryBound(to: UnsafePointer<CChar>?.self).pointee
|
let cstr = ptr.assumingMemoryBound(to: UnsafePointer<CChar>?.self).pointee
|
||||||
value = cstr.map { String(cString: $0) }
|
value = cstr.map { safeString($0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -611,7 +626,7 @@ class MpvPlayerCore: NSObject {
|
|||||||
private func convertNode(_ node: mpv_node) -> Any? {
|
private func convertNode(_ node: mpv_node) -> Any? {
|
||||||
switch node.format {
|
switch node.format {
|
||||||
case MPV_FORMAT_STRING:
|
case MPV_FORMAT_STRING:
|
||||||
return node.u.string.map { String(cString: $0) }
|
return node.u.string.map { safeString($0) }
|
||||||
|
|
||||||
case MPV_FORMAT_FLAG:
|
case MPV_FORMAT_FLAG:
|
||||||
return node.u.flag != 0
|
return node.u.flag != 0
|
||||||
@@ -636,7 +651,7 @@ class MpvPlayerCore: NSObject {
|
|||||||
guard let list = node.u.list?.pointee else { return nil }
|
guard let list = node.u.list?.pointee else { return nil }
|
||||||
var dict = [String: Any]()
|
var dict = [String: Any]()
|
||||||
for i in 0..<Int(list.num) {
|
for i in 0..<Int(list.num) {
|
||||||
if let key = list.keys?[i].map({ String(cString: $0) }),
|
if let key = list.keys?[i].map({ safeString($0) }),
|
||||||
let val = convertNode(list.values[i])
|
let val = convertNode(list.values[i])
|
||||||
{
|
{
|
||||||
dict[key] = val
|
dict[key] = val
|
||||||
|
|||||||
@@ -50,6 +50,17 @@ endfunction()
|
|||||||
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
|
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
|
||||||
add_subdirectory(${FLUTTER_MANAGED_DIR})
|
add_subdirectory(${FLUTTER_MANAGED_DIR})
|
||||||
|
|
||||||
|
# Fetch simdutf for SIMD-accelerated UTF-8 validation.
|
||||||
|
# mpv strings are not guaranteed to be valid UTF-8; invalid bytes sent through
|
||||||
|
# Flutter's StandardMessageCodec cause FormatException crashes.
|
||||||
|
include(FetchContent)
|
||||||
|
FetchContent_Declare(
|
||||||
|
simdutf
|
||||||
|
URL https://github.com/simdutf/simdutf/releases/download/v6.4.2/singleheader.zip
|
||||||
|
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||||
|
)
|
||||||
|
FetchContent_MakeAvailable(simdutf)
|
||||||
|
|
||||||
# System-level dependencies.
|
# System-level dependencies.
|
||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||||
|
|||||||
@@ -28,10 +28,18 @@ pkg_check_modules(MPV REQUIRED IMPORTED_TARGET mpv)
|
|||||||
# Find epoxy (OpenGL loader).
|
# Find epoxy (OpenGL loader).
|
||||||
pkg_check_modules(EPOXY REQUIRED IMPORTED_TARGET epoxy)
|
pkg_check_modules(EPOXY REQUIRED IMPORTED_TARGET epoxy)
|
||||||
|
|
||||||
|
# Build simdutf as a static library from the single-header amalgamation.
|
||||||
|
add_library(simdutf STATIC "${simdutf_SOURCE_DIR}/simdutf.cpp")
|
||||||
|
target_include_directories(simdutf PUBLIC "${simdutf_SOURCE_DIR}")
|
||||||
|
target_compile_features(simdutf PUBLIC cxx_std_17)
|
||||||
|
# Suppress warnings in third-party code
|
||||||
|
target_compile_options(simdutf PRIVATE -w)
|
||||||
|
|
||||||
# Add dependency libraries. Add any application-specific dependencies here.
|
# Add dependency libraries. Add any application-specific dependencies here.
|
||||||
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
|
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
|
||||||
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
|
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
|
||||||
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::MPV)
|
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::MPV)
|
||||||
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::EPOXY)
|
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::EPOXY)
|
||||||
|
target_link_libraries(${BINARY_NAME} PRIVATE simdutf)
|
||||||
|
|
||||||
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
|
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||||
|
|||||||
@@ -13,6 +13,47 @@
|
|||||||
#include <clocale>
|
#include <clocale>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <simdutf.h>
|
||||||
|
|
||||||
|
// Sanitize a C string that may contain invalid UTF-8 sequences.
|
||||||
|
// Uses simdutf for SIMD-accelerated validation (fast path for valid strings),
|
||||||
|
// then falls back to iterative replacement with U+FFFD on the rare invalid case.
|
||||||
|
// mpv does not guarantee UTF-8 for log messages, error strings, or
|
||||||
|
// system-encoded paths — sending these unsanitized through Flutter's
|
||||||
|
// StandardMessageCodec causes FormatException crashes.
|
||||||
|
static std::string SanitizeUtf8(const char* input) {
|
||||||
|
if (!input) return std::string();
|
||||||
|
size_t len = strlen(input);
|
||||||
|
if (len == 0) return std::string();
|
||||||
|
|
||||||
|
// Fast path: SIMD-accelerated validation — almost all strings pass this
|
||||||
|
if (simdutf::validate_utf8(input, len)) {
|
||||||
|
return std::string(input, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slow path: find each invalid position, copy valid prefix, insert U+FFFD,
|
||||||
|
// skip the bad byte, and repeat.
|
||||||
|
std::string result;
|
||||||
|
result.reserve(len);
|
||||||
|
size_t pos = 0;
|
||||||
|
|
||||||
|
while (pos < len) {
|
||||||
|
auto r = simdutf::validate_utf8_with_errors(input + pos, len - pos);
|
||||||
|
// Copy the valid prefix up to the error
|
||||||
|
if (r.count > 0) {
|
||||||
|
result.append(input + pos, r.count);
|
||||||
|
}
|
||||||
|
pos += r.count;
|
||||||
|
if (r.error == simdutf::error_code::SUCCESS) {
|
||||||
|
break; // remaining tail is valid
|
||||||
|
}
|
||||||
|
// Replace the invalid byte with U+FFFD and skip it
|
||||||
|
result.append("\xEF\xBF\xBD");
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// Flutter on Linux uses EGL (OpenGL ES) for both X11 and Wayland.
|
// Flutter on Linux uses EGL (OpenGL ES) for both X11 and Wayland.
|
||||||
static void* get_opengl_proc_address(void* ctx, const char* name) {
|
static void* get_opengl_proc_address(void* ctx, const char* name) {
|
||||||
@@ -483,11 +524,11 @@ void MpvPlayer::HandleMpvEvent(mpv_event* event) {
|
|||||||
|
|
||||||
FlValue* data = fl_value_new_map();
|
FlValue* data = fl_value_new_map();
|
||||||
fl_value_set_string_take(data, "prefix",
|
fl_value_set_string_take(data, "prefix",
|
||||||
fl_value_new_string(msg->prefix ? msg->prefix : ""));
|
fl_value_new_string(SanitizeUtf8(msg->prefix).c_str()));
|
||||||
fl_value_set_string_take(data, "level",
|
fl_value_set_string_take(data, "level",
|
||||||
fl_value_new_string(msg->level ? msg->level : ""));
|
fl_value_new_string(SanitizeUtf8(msg->level).c_str()));
|
||||||
fl_value_set_string_take(data, "text",
|
fl_value_set_string_take(data, "text",
|
||||||
fl_value_new_string(msg->text ? msg->text : ""));
|
fl_value_new_string(SanitizeUtf8(msg->text).c_str()));
|
||||||
SendEvent("log-message", data);
|
SendEvent("log-message", data);
|
||||||
fl_value_unref(data);
|
fl_value_unref(data);
|
||||||
break;
|
break;
|
||||||
@@ -559,7 +600,7 @@ FlValue* MpvPlayer::NodeToFlValue(mpv_node* node) {
|
|||||||
|
|
||||||
switch (node->format) {
|
switch (node->format) {
|
||||||
case MPV_FORMAT_STRING:
|
case MPV_FORMAT_STRING:
|
||||||
return fl_value_new_string(node->u.string ? node->u.string : "");
|
return fl_value_new_string(SanitizeUtf8(node->u.string).c_str());
|
||||||
case MPV_FORMAT_FLAG:
|
case MPV_FORMAT_FLAG:
|
||||||
return fl_value_new_bool(node->u.flag != 0);
|
return fl_value_new_bool(node->u.flag != 0);
|
||||||
case MPV_FORMAT_INT64:
|
case MPV_FORMAT_INT64:
|
||||||
|
|||||||
@@ -35,6 +35,21 @@ private class MetalLayer: CAMetalLayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Safely convert a C string to Swift String with UTF-8 validation.
|
||||||
|
/// Falls back to Latin-1 decoding if the bytes are not valid UTF-8.
|
||||||
|
/// mpv does not guarantee UTF-8 for log messages, error strings, or
|
||||||
|
/// system-encoded paths — sending invalid UTF-8 through Flutter's
|
||||||
|
/// StandardMessageCodec causes FormatException crashes.
|
||||||
|
private func safeString(_ cstr: UnsafePointer<CChar>) -> String {
|
||||||
|
if let s = String(validatingUTF8: cstr) {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
// Latin-1 fallback: interpret each byte as its Unicode scalar
|
||||||
|
let len = strlen(cstr)
|
||||||
|
let buf = UnsafeBufferPointer(start: UnsafeRawPointer(cstr).assumingMemoryBound(to: UInt8.self), count: len)
|
||||||
|
return String(buf.map { Character(Unicode.Scalar($0)) })
|
||||||
|
}
|
||||||
|
|
||||||
/// Core MPV player using Metal rendering
|
/// Core MPV player using Metal rendering
|
||||||
class MpvPlayerCore: NSObject {
|
class MpvPlayerCore: NSObject {
|
||||||
|
|
||||||
@@ -506,9 +521,9 @@ class MpvPlayerCore: NSObject {
|
|||||||
case MPV_EVENT_LOG_MESSAGE:
|
case MPV_EVENT_LOG_MESSAGE:
|
||||||
if let msgPtr = event.data?.assumingMemoryBound(to: mpv_event_log_message.self) {
|
if let msgPtr = event.data?.assumingMemoryBound(to: mpv_event_log_message.self) {
|
||||||
let msg = msgPtr.pointee
|
let msg = msgPtr.pointee
|
||||||
let prefix = msg.prefix.map { String(cString: $0) } ?? ""
|
let prefix = msg.prefix.map { safeString($0) } ?? ""
|
||||||
let level = msg.level.map { String(cString: $0) } ?? ""
|
let level = msg.level.map { safeString($0) } ?? ""
|
||||||
let text = msg.text.map { String(cString: $0) } ?? ""
|
let text = msg.text.map { safeString($0) } ?? ""
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.delegate?.onEvent(name: "log-message", data: [
|
self.delegate?.onEvent(name: "log-message", data: [
|
||||||
@@ -547,7 +562,7 @@ class MpvPlayerCore: NSObject {
|
|||||||
case MPV_FORMAT_STRING:
|
case MPV_FORMAT_STRING:
|
||||||
if let ptr = property.data {
|
if let ptr = property.data {
|
||||||
let cstr = ptr.assumingMemoryBound(to: UnsafePointer<CChar>?.self).pointee
|
let cstr = ptr.assumingMemoryBound(to: UnsafePointer<CChar>?.self).pointee
|
||||||
value = cstr.map { String(cString: $0) }
|
value = cstr.map { safeString($0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -592,7 +607,7 @@ class MpvPlayerCore: NSObject {
|
|||||||
private func convertNode(_ node: mpv_node) -> Any? {
|
private func convertNode(_ node: mpv_node) -> Any? {
|
||||||
switch node.format {
|
switch node.format {
|
||||||
case MPV_FORMAT_STRING:
|
case MPV_FORMAT_STRING:
|
||||||
return node.u.string.map { String(cString: $0) }
|
return node.u.string.map { safeString($0) }
|
||||||
|
|
||||||
case MPV_FORMAT_FLAG:
|
case MPV_FORMAT_FLAG:
|
||||||
return node.u.flag != 0
|
return node.u.flag != 0
|
||||||
@@ -617,7 +632,7 @@ class MpvPlayerCore: NSObject {
|
|||||||
guard let list = node.u.list?.pointee else { return nil }
|
guard let list = node.u.list?.pointee else { return nil }
|
||||||
var dict = [String: Any]()
|
var dict = [String: Any]()
|
||||||
for i in 0..<Int(list.num) {
|
for i in 0..<Int(list.num) {
|
||||||
if let key = list.keys?[i].map({ String(cString: $0) }),
|
if let key = list.keys?[i].map({ safeString($0) }),
|
||||||
let val = convertNode(list.values[i]) {
|
let val = convertNode(list.values[i]) {
|
||||||
dict[key] = val
|
dict[key] = val
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,17 @@ else()
|
|||||||
set(MPV_LIB_DIR "${mpv_dev_SOURCE_DIR}")
|
set(MPV_LIB_DIR "${mpv_dev_SOURCE_DIR}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Fetch simdutf for SIMD-accelerated UTF-8 validation.
|
||||||
|
# mpv strings are not guaranteed to be valid UTF-8; invalid bytes sent through
|
||||||
|
# Flutter's StandardMessageCodec cause FormatException crashes.
|
||||||
|
include(FetchContent)
|
||||||
|
FetchContent_Declare(
|
||||||
|
simdutf
|
||||||
|
URL https://github.com/simdutf/simdutf/releases/download/v6.4.2/singleheader.zip
|
||||||
|
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||||
|
)
|
||||||
|
FetchContent_MakeAvailable(simdutf)
|
||||||
|
|
||||||
# The name of the executable created for the application. Change this to change
|
# The name of the executable created for the application. Change this to change
|
||||||
# the on-disk name of your application.
|
# the on-disk name of your application.
|
||||||
set(BINARY_NAME "plezy")
|
set(BINARY_NAME "plezy")
|
||||||
|
|||||||
@@ -35,11 +35,19 @@ target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTT
|
|||||||
# Disable Windows macros that collide with C++ standard library functions.
|
# Disable Windows macros that collide with C++ standard library functions.
|
||||||
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
|
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
|
||||||
|
|
||||||
|
# Build simdutf as a static library from the single-header amalgamation.
|
||||||
|
add_library(simdutf STATIC "${simdutf_SOURCE_DIR}/simdutf.cpp")
|
||||||
|
target_include_directories(simdutf PUBLIC "${simdutf_SOURCE_DIR}")
|
||||||
|
target_compile_features(simdutf PUBLIC cxx_std_17)
|
||||||
|
# Suppress warnings in third-party code
|
||||||
|
target_compile_options(simdutf PRIVATE /W0)
|
||||||
|
|
||||||
# Add dependency libraries and include directories. Add any application-specific
|
# Add dependency libraries and include directories. Add any application-specific
|
||||||
# dependencies here.
|
# dependencies here.
|
||||||
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app flutter_wrapper_plugin)
|
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app flutter_wrapper_plugin)
|
||||||
target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib" "comctl32.lib")
|
target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib" "comctl32.lib")
|
||||||
target_link_libraries(${BINARY_NAME} PRIVATE "${MPV_LIB_DIR}/libmpv.dll.a")
|
target_link_libraries(${BINARY_NAME} PRIVATE "${MPV_LIB_DIR}/libmpv.dll.a")
|
||||||
|
target_link_libraries(${BINARY_NAME} PRIVATE simdutf)
|
||||||
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
|
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||||
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}")
|
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
target_include_directories(${BINARY_NAME} PRIVATE "${MPV_INCLUDE_DIR}")
|
target_include_directories(${BINARY_NAME} PRIVATE "${MPV_INCLUDE_DIR}")
|
||||||
|
|||||||
@@ -2,6 +2,47 @@
|
|||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <simdutf.h>
|
||||||
|
|
||||||
|
// Sanitize a C string that may contain invalid UTF-8 sequences.
|
||||||
|
// Uses simdutf for SIMD-accelerated validation (fast path for valid strings),
|
||||||
|
// then falls back to iterative replacement with U+FFFD on the rare invalid case.
|
||||||
|
// mpv does not guarantee UTF-8 for log messages, error strings, or
|
||||||
|
// system-encoded paths — sending these unsanitized through Flutter's
|
||||||
|
// StandardMessageCodec causes FormatException crashes.
|
||||||
|
static std::string SanitizeUtf8(const char* input) {
|
||||||
|
if (!input) return std::string();
|
||||||
|
size_t len = strlen(input);
|
||||||
|
if (len == 0) return std::string();
|
||||||
|
|
||||||
|
// Fast path: SIMD-accelerated validation — almost all strings pass this
|
||||||
|
if (simdutf::validate_utf8(input, len)) {
|
||||||
|
return std::string(input, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slow path: find each invalid position, copy valid prefix, insert U+FFFD,
|
||||||
|
// skip the bad byte, and repeat.
|
||||||
|
std::string result;
|
||||||
|
result.reserve(len);
|
||||||
|
size_t pos = 0;
|
||||||
|
|
||||||
|
while (pos < len) {
|
||||||
|
auto r = simdutf::validate_utf8_with_errors(input + pos, len - pos);
|
||||||
|
// Copy the valid prefix up to the error
|
||||||
|
if (r.count > 0) {
|
||||||
|
result.append(input + pos, r.count);
|
||||||
|
}
|
||||||
|
pos += r.count;
|
||||||
|
if (r.error == simdutf::error_code::SUCCESS) {
|
||||||
|
break; // remaining tail is valid
|
||||||
|
}
|
||||||
|
// Replace the invalid byte with U+FFFD and skip it
|
||||||
|
result.append("\xEF\xBF\xBD");
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
static void LogToFile(const char* message) {
|
static void LogToFile(const char* message) {
|
||||||
std::ofstream log("C:\\Users\\admin\\mpv_debug.log", std::ios::app);
|
std::ofstream log("C:\\Users\\admin\\mpv_debug.log", std::ios::app);
|
||||||
@@ -349,11 +390,11 @@ void MpvPlayer::HandleMpvEvent(mpv_event* event) {
|
|||||||
|
|
||||||
flutter::EncodableMap data;
|
flutter::EncodableMap data;
|
||||||
data[flutter::EncodableValue("prefix")] =
|
data[flutter::EncodableValue("prefix")] =
|
||||||
flutter::EncodableValue(msg->prefix ? msg->prefix : "");
|
flutter::EncodableValue(SanitizeUtf8(msg->prefix));
|
||||||
data[flutter::EncodableValue("level")] =
|
data[flutter::EncodableValue("level")] =
|
||||||
flutter::EncodableValue(msg->level ? msg->level : "");
|
flutter::EncodableValue(SanitizeUtf8(msg->level));
|
||||||
data[flutter::EncodableValue("text")] =
|
data[flutter::EncodableValue("text")] =
|
||||||
flutter::EncodableValue(msg->text ? msg->text : "");
|
flutter::EncodableValue(SanitizeUtf8(msg->text));
|
||||||
SendEvent("log-message", data);
|
SendEvent("log-message", data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -435,8 +476,7 @@ void MpvPlayer::SendPropertyChange(const char* name, mpv_node* data) {
|
|||||||
if (data) {
|
if (data) {
|
||||||
switch (data->format) {
|
switch (data->format) {
|
||||||
case MPV_FORMAT_STRING:
|
case MPV_FORMAT_STRING:
|
||||||
value = flutter::EncodableValue(
|
value = flutter::EncodableValue(SanitizeUtf8(data->u.string));
|
||||||
data->u.string ? std::string(data->u.string) : std::string());
|
|
||||||
break;
|
break;
|
||||||
case MPV_FORMAT_FLAG:
|
case MPV_FORMAT_FLAG:
|
||||||
value = flutter::EncodableValue(data->u.flag != 0);
|
value = flutter::EncodableValue(data->u.flag != 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user