0
0
mirror of https://github.com/ankidroid/Anki-Android.git synced 2024-09-20 03:52:15 +02:00

fix: CORS issue with JS loading

This commit is contained in:
Brayan Oliveira 2024-05-04 13:11:09 -03:00 committed by Mike Hardy
parent 84dea7a48f
commit bf1f7fbba7
3 changed files with 68 additions and 1 deletions

View File

@ -993,6 +993,7 @@ abstract class AbstractFlashcardViewer :
}
protected open fun createWebView(): WebView {
val resourceHandler = ViewerResourceHandler(this)
val webView: WebView = MyWebView(this).apply {
scrollBarStyle = View.SCROLLBARS_OUTSIDE_OVERLAY
with(settings) {
@ -1010,7 +1011,7 @@ abstract class AbstractFlashcardViewer :
isScrollbarFadingEnabled = true
// Set transparent color to prevent flashing white when night mode enabled
setBackgroundColor(Color.argb(1, 0, 0, 0))
CardViewerWebClient(this@AbstractFlashcardViewer).apply {
CardViewerWebClient(resourceHandler, this@AbstractFlashcardViewer).apply {
webViewClient = this
this@AbstractFlashcardViewer.webViewClient = this
}
@ -2233,6 +2234,7 @@ abstract class AbstractFlashcardViewer :
}
inner class CardViewerWebClient internal constructor(
private val resourceHandler: ViewerResourceHandler,
private val onPageFinishedCallback: OnPageFinishedCallback? = null
) : WebViewClient(), JavascriptEvaluator {
private var pageFinishedFired = true
@ -2266,6 +2268,7 @@ abstract class AbstractFlashcardViewer :
if (url.toString().startsWith("file://")) {
url.path?.let { path -> migrationService?.migrateFileImmediately(File(path)) }
}
resourceHandler.shouldInterceptRequest(request)?.let { return it }
return null
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) 2024 Brayan Oliveira <brayandso.dev@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.ichi2.anki
import android.content.Context
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import androidx.core.net.toFile
import com.ichi2.utils.AssetHelper.guessMimeType
import timber.log.Timber
import java.io.FileInputStream
class ViewerResourceHandler(context: Context) {
private val mediaDir = CollectionHelper.getMediaDirectory(context).path
/**
* Loads resources from `collection.media` when requested by JS scripts.
*
* Differently from common media requests, scripts' requests have an `Origin` header
* and are susceptible to CORS policy, so `Access-Control-Allow-Origin` is necessary.
*/
fun shouldInterceptRequest(request: WebResourceRequest): WebResourceResponse? {
val url = request.url
if (request.method != "GET" || url.scheme != "file" || "Origin" !in request.requestHeaders) {
return null
}
try {
val file = url.toFile()
if (file.parent != mediaDir || !file.exists()) {
return null
}
val inputStream = FileInputStream(file)
return WebResourceResponse(guessMimeType(file.path), null, inputStream).apply {
responseHeaders = mapOf("Access-Control-Allow-Origin" to "*")
}
} catch (e: Exception) {
Timber.d("File couldn't be loaded")
return null
}
}
}

View File

@ -23,6 +23,7 @@ import android.webkit.CookieManager
import android.webkit.WebChromeClient
import android.webkit.WebResourceError
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.FrameLayout
@ -37,6 +38,7 @@ import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.ichi2.anki.CollectionHelper
import com.ichi2.anki.R
import com.ichi2.anki.ViewerResourceHandler
import com.ichi2.anki.dialogs.TtsVoicesDialogFragment
import com.ichi2.anki.localizedErrorMessage
import com.ichi2.anki.snackbar.showSnackbar
@ -121,7 +123,15 @@ abstract class CardViewerFragment(@LayoutRes layout: Int) : Fragment(layout) {
}
private fun onCreateWebViewClient(savedInstanceState: Bundle?): WebViewClient {
val resourceHandler = ViewerResourceHandler(requireContext())
return object : WebViewClient() {
override fun shouldInterceptRequest(
view: WebView?,
request: WebResourceRequest
): WebResourceResponse? {
return resourceHandler.shouldInterceptRequest(request)
}
override fun onPageFinished(view: WebView?, url: String?) {
viewModel.onPageFinished(isAfterRecreation = savedInstanceState != null)
}