diff --git a/buildSrc/src/main/kotlin/ProjectConfig.kt b/buildSrc/src/main/kotlin/ProjectConfig.kt index 8386cec82..0c5a8b5b1 100644 --- a/buildSrc/src/main/kotlin/ProjectConfig.kt +++ b/buildSrc/src/main/kotlin/ProjectConfig.kt @@ -55,6 +55,6 @@ const val NEWPIPE_APPLICATION_ID_NEW = "net.newpipe.app" // vc=19 / 0.1.0-AE — rust pipeline cutover. Extraction via // strawcore-core (Sulkta-Coop/strawcore) via the UniFFI wrapper; no // NewPipeExtractor in the runtime path. -const val STRAW_VERSION_CODE = 41 -const val STRAW_VERSION_NAME = "0.1.0-BA" +const val STRAW_VERSION_CODE = 42 +const val STRAW_VERSION_NAME = "0.1.0-BB" const val STRAW_APPLICATION_ID = "com.sulkta.straw" diff --git a/rust/strawcore/Cargo.toml b/rust/strawcore/Cargo.toml index f693973ee..1cd5ce697 100644 --- a/rust/strawcore/Cargo.toml +++ b/rust/strawcore/Cargo.toml @@ -34,8 +34,6 @@ strawcore-core = { path = "../../../strawcore" } rquickjs-sys = { version = "0.11", default-features = false, features = ["bindgen"] } # Error glue. thiserror = "1" -# Single-threaded init for the runtime + extractor singletons. -once_cell = "1" # Android log integration — `log::info!()` ends up in `adb logcat -s strawcore`. log = "0.4" android_logger = { version = "0.14", default-features = false } diff --git a/strawApp/src/main/kotlin/com/sulkta/straw/StrawActivity.kt b/strawApp/src/main/kotlin/com/sulkta/straw/StrawActivity.kt index ead83af3f..9bcb0664e 100644 --- a/strawApp/src/main/kotlin/com/sulkta/straw/StrawActivity.kt +++ b/strawApp/src/main/kotlin/com/sulkta/straw/StrawActivity.kt @@ -42,11 +42,9 @@ import com.sulkta.straw.feature.search.SearchScreen import com.sulkta.straw.feature.settings.SettingsScreen import kotlinx.coroutines.flow.MutableStateFlow -private val YT_HOSTS = setOf( - "youtube.com", "www.youtube.com", "m.youtube.com", - "music.youtube.com", "youtube-nocookie.com", "www.youtube-nocookie.com", - "youtu.be", -) +// Allowlist now lives in util/YtUrl.kt with extra hardening (scheme +// requirement, trailing-dot strip). Round-7 audit MED-4: prior shape +// duplicated the host set here and would drift away from the util. private val YT_URL_RE = Regex( "https?://(?:www\\.|m\\.|music\\.)?(?:youtube(?:-nocookie)?\\.com/[A-Za-z0-9_/?=&\\-.%]+|youtu\\.be/[A-Za-z0-9_\\-]+)", ) @@ -231,8 +229,6 @@ class StrawActivity : ComponentActivity() { } } - private fun looksLikeYouTube(url: String): Boolean { - val host = runCatching { java.net.URI(url).host }.getOrNull() ?: return false - return host.lowercase() in YT_HOSTS - } + private fun looksLikeYouTube(url: String): Boolean = + com.sulkta.straw.util.isAllowedYtUrl(url) } diff --git a/strawApp/src/main/kotlin/com/sulkta/straw/feature/search/SearchViewModel.kt b/strawApp/src/main/kotlin/com/sulkta/straw/feature/search/SearchViewModel.kt index 2e64950fe..249327e12 100644 --- a/strawApp/src/main/kotlin/com/sulkta/straw/feature/search/SearchViewModel.kt +++ b/strawApp/src/main/kotlin/com/sulkta/straw/feature/search/SearchViewModel.kt @@ -144,6 +144,14 @@ class SearchViewModel : ViewModel() { .firstOrNull { it.query.equals(q, ignoreCase = true) } ?.items } else null + // Cancel any prior in-flight submit BEFORE writing the cached + // preview to the UI — round-7 audit MED-1: previously a fresh + // submit that hit the cache could be clobbered seconds later + // by the prior submit's late terminal write, because the + // prior coroutine had already advanced past its `ensureActive` + // gate by the time the new submit got around to cancelling. + inFlight?.cancel() + if (cached != null && cached.isNotEmpty()) { _ui.update { it.copy( @@ -164,7 +172,6 @@ class SearchViewModel : ViewModel() { } } - inFlight?.cancel() inFlight = viewModelScope.launch { try { // strawcore.search() is suspend on the tokio runtime baked