From 467e3fc0b7a3a5d795893aeb616267fd8f5b46a1 Mon Sep 17 00:00:00 2001 From: Corewala Date: Mon, 25 Jul 2022 23:07:55 -0400 Subject: [PATCH] Added HTTP proxy It's a beautiful half-broken mess --- app/src/main/java/corewala/buran/OmniTerm.kt | 7 ++- .../corewala/buran/io/gemini/Datasource.kt | 2 +- .../buran/io/gemini/GeminiDatasource.kt | 39 +++++++++------ .../java/corewala/buran/ui/GemActivity.kt | 50 +++++++++++++++---- .../java/corewala/buran/ui/GemViewModel.kt | 12 ++--- app/src/main/res/values-fr/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + 7 files changed, 79 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/corewala/buran/OmniTerm.kt b/app/src/main/java/corewala/buran/OmniTerm.kt index 4f705d3..4ebf4a2 100644 --- a/app/src/main/java/corewala/buran/OmniTerm.kt +++ b/app/src/main/java/corewala/buran/OmniTerm.kt @@ -56,14 +56,17 @@ class OmniTerm(private val listener: Listener) { when { link.startsWith(GEM_SCHEME) -> uri.set(link) link.startsWith("//") -> uri.set("gemini:$link") + link.startsWith("http://") or link.startsWith("https://") -> { + uri.set(link) + } link.contains(":") -> listener.openExternal(link) else -> uri.resolve(link) } - val address = uri.toString().replace("//", "/").replace("gemini:/", "gemini://") - println("OmniTerm resolved address: $address") + val address = uri.toString().replace("//", "/").replace(":/", "://") if(invokeListener) listener.request(address) + println("OmniTerm resolved address: $address") } fun reset(){ diff --git a/app/src/main/java/corewala/buran/io/gemini/Datasource.kt b/app/src/main/java/corewala/buran/io/gemini/Datasource.kt index a69c04f..e4c608e 100644 --- a/app/src/main/java/corewala/buran/io/gemini/Datasource.kt +++ b/app/src/main/java/corewala/buran/io/gemini/Datasource.kt @@ -6,7 +6,7 @@ import corewala.buran.io.database.history.BuranHistory import java.net.URI interface Datasource { - fun request(address: String, forceDownload: Boolean, clientCertPassword: String?, onUpdate: (state: GemState) -> Unit) + fun request(address: String, forceDownload: Boolean, clientCertPassword: String?, alternativeRequest: String?, onUpdate: (state: GemState) -> Unit) fun isRequesting(): Boolean fun cancel() fun canGoBack(): Boolean diff --git a/app/src/main/java/corewala/buran/io/gemini/GeminiDatasource.kt b/app/src/main/java/corewala/buran/io/gemini/GeminiDatasource.kt index 8bd29f6..a42dd38 100644 --- a/app/src/main/java/corewala/buran/io/gemini/GeminiDatasource.kt +++ b/app/src/main/java/corewala/buran/io/gemini/GeminiDatasource.kt @@ -8,9 +8,9 @@ import corewala.buran.OppenURI import corewala.buran.io.GemState import corewala.buran.io.database.history.BuranHistory import corewala.buran.io.keymanager.BuranKeyManager +import corewala.toURI import corewala.toUri import java.io.* -import java.lang.IllegalStateException import java.net.ConnectException import java.net.URI import java.net.UnknownHostException @@ -32,7 +32,7 @@ class GeminiDatasource(private val context: Context, val history: BuranHistory): private var currentRequestAddress: String? = null - override fun request(address: String, forceDownload: Boolean, clientCertPassword: String?, onUpdate: (state: GemState) -> Unit) { + override fun request(address: String, forceDownload: Boolean, clientCertPassword: String?, alternativeRequest: String?, onUpdate: (state: GemState) -> Unit){ this.forceDownload = forceDownload this.onUpdate = onUpdate @@ -41,10 +41,12 @@ class GeminiDatasource(private val context: Context, val history: BuranHistory): onUpdate(GemState.Requesting(uri)) - currentRequestAddress = address + if(address.startsWith("gemini://")){ + currentRequestAddress = address + } GlobalScope.launch { - geminiRequest(uri, onUpdate, clientCertPassword) + geminiRequest(uri, onUpdate, clientCertPassword, alternativeRequest) } } @@ -66,14 +68,20 @@ class GeminiDatasource(private val context: Context, val history: BuranHistory): socketFactory = sslContext.socketFactory } - private fun geminiRequest(uri: URI, onUpdate: (state: GemState) -> Unit, clientCertPassword: String?){ + private fun geminiRequest(uri: URI, onUpdate: (state: GemState) -> Unit, clientCertPassword: String?, alternativeRequest: String?){ val protocol = "TLS" initSSLFactory(protocol, clientCertPassword) + val port = if(uri.port != -1){ + uri.port + }else{ + 1965 + } + val socket: SSLSocket? try { - socket = socketFactory?.createSocket(uri.host, 1965) as SSLSocket + socket = socketFactory?.createSocket(uri.host, port) as SSLSocket println("Buran socket handshake with ${uri.host}") socket.startHandshake() @@ -102,7 +110,12 @@ class GeminiDatasource(private val context: Context, val history: BuranHistory): val bufferedWriter = BufferedWriter(outputStreamWriter) val outWriter = PrintWriter(bufferedWriter) - val requestEntity = uri.toString() + "\r\n" + val requestEntity = if(alternativeRequest.isNullOrEmpty()){ + uri.toString() + }else{ + alternativeRequest + } + "\r\n" + println("Buran socket requesting $requestEntity") outWriter.print(requestEntity) outWriter.flush() @@ -134,10 +147,10 @@ class GeminiDatasource(private val context: Context, val history: BuranHistory): when { currentRequestAddress != uri.toString() -> {} header.code == GeminiResponse.INPUT -> onUpdate(GemState.ResponseInput(uri, header)) - header.code == GeminiResponse.REDIRECT -> onUpdate(GemState.Redirect(resolve(uri.host, header.meta))) + header.code == GeminiResponse.REDIRECT -> onUpdate(GemState.Redirect(resolve(uri, header.meta))) header.code == GeminiResponse.CLIENT_CERTIFICATE_REQUIRED -> onUpdate(GemState.ClientCertRequired(uri, header)) header.code != GeminiResponse.SUCCESS -> onUpdate(GemState.ResponseError(header)) - header.meta.startsWith("text/gemini") -> getGemtext(bufferedReader, uri, header, onUpdate) + header.meta.startsWith("text/gemini") -> getGemtext(bufferedReader, requestEntity.trim().toURI(), header, onUpdate) header.meta.startsWith("text/") -> getString(socket, uri, header, onUpdate) header.meta.startsWith("image/") -> getBinary(socket, uri, header, onUpdate) else -> { @@ -172,10 +185,6 @@ class GeminiDatasource(private val context: Context, val history: BuranHistory): val processed = GemtextHelper.findCodeBlocks(lines) - when { - !uri.toString().startsWith("gemini://") -> throw IllegalStateException("Not a Gemini Uri") - } - updateHistory(uri) onUpdate(GemState.ResponseGemtext(uri, header, processed)) } @@ -232,9 +241,9 @@ class GeminiDatasource(private val context: Context, val history: BuranHistory): } } - private fun resolve(host: String, address: String): String{ + private fun resolve(uri: URI, address: String): String{ val ouri = OppenURI() - ouri.set("gemini://$host") + ouri.set(uri.scheme + uri.host) return ouri.resolve(address) } diff --git a/app/src/main/java/corewala/buran/ui/GemActivity.kt b/app/src/main/java/corewala/buran/ui/GemActivity.kt index c369452..926ddb4 100644 --- a/app/src/main/java/corewala/buran/ui/GemActivity.kt +++ b/app/src/main/java/corewala/buran/ui/GemActivity.kt @@ -81,6 +81,8 @@ class GemActivity : AppCompatActivity() { private var certPassword: String? = null + private var proxiedAddress: String? = null + private var previousPosition: Int = 0 private var initialised: Boolean = false @@ -96,7 +98,7 @@ class GemActivity : AppCompatActivity() { private val onLink: (link: URI, longTap: Boolean, adapterPosition: Int) -> Unit = { uri, longTap, _: Int -> if(longTap){ val globalURI = if(!uri.toString().contains("//") and !uri.toString().contains(":")){ - (omniTerm.getCurrent() + uri.toString()).replace("//", "/").replace("gemini:/", "gemini://") + (omniTerm.getCurrent() + uri.toString()).replace("//", "/").replace(":/", "://") } else { uri.toString() } @@ -112,6 +114,7 @@ class GemActivity : AppCompatActivity() { binding.addressEdit.hint = getString(R.string.main_input_hint) inSearch = false } + omniTerm.navigation(uri.toString()) } } @@ -496,7 +499,9 @@ class GemActivity : AppCompatActivity() { is GemState.Requesting -> { loadingView(true) } - is GemState.NotGeminiRequest -> externalProtocol(state) + is GemState.NotGeminiRequest -> { + externalProtocol(state.uri) + } is GemState.ResponseError -> { omniTerm.reset() showAlert("${GeminiResponse.getCodeString(state.header.code)}:\n\n${state.header.meta}") @@ -506,7 +511,14 @@ class GemActivity : AppCompatActivity() { updateClientCertIcon() showAlert("${GeminiResponse.getCodeString(state.header.code)}:\n\n${state.header.meta}") } - is GemState.ResponseGemtext -> renderGemtext(state) + is GemState.ResponseGemtext -> { + if(state.uri.scheme != "gemini://"){ + Snackbar.make(binding.root, getString(R.string.proxied_content), Snackbar.LENGTH_LONG).setAction(getString(R.string.open_original)) { + externalProtocol(state.uri) + }.show() + } + renderGemtext(state) + } is GemState.ResponseText -> renderText(state) is GemState.ResponseImage -> renderImage(state) is GemState.ResponseBinary -> renderBinary(state) @@ -531,7 +543,7 @@ class GemActivity : AppCompatActivity() { .setMessage("${state.uri}") .setPositiveButton(getString(R.string.download).toUpperCase()) { _, _ -> loadingView(true) - model.requestBinaryDownload(state.uri, clientCertPassword) + model.requestBinaryDownload(state.uri, clientCertPassword, null) } .setNegativeButton(getString(R.string.cancel).toUpperCase()) { _, _ -> } .show() @@ -638,15 +650,15 @@ class GemActivity : AppCompatActivity() { } } - private fun externalProtocol(state: GemState.NotGeminiRequest) = runOnUiThread { + private fun externalProtocol(uri: URI) = runOnUiThread { loadingView(false) - val uri = state.uri.toString() + val uri = uri.toString() when { (uri.startsWith("http://") || uri.startsWith("https://")) -> openExternalLink(uri) else -> { val viewIntent = Intent(Intent.ACTION_VIEW) - viewIntent.data = Uri.parse(state.uri.toString()) + viewIntent.data = Uri.parse(uri.toString()) try { startActivity(viewIntent) @@ -654,7 +666,7 @@ class GemActivity : AppCompatActivity() { showAlert( String.format( getString(R.string.no_app_installed_that_can_open), - state.uri + uri ) ) } @@ -683,7 +695,6 @@ class GemActivity : AppCompatActivity() { }else{ val viewIntent = Intent(Intent.ACTION_VIEW) viewIntent.data = Uri.parse(address) - startActivity(viewIntent) } } @@ -691,7 +702,7 @@ class GemActivity : AppCompatActivity() { private fun renderGemtext(state: GemState.ResponseGemtext) = runOnUiThread { loadingView(false) - omniTerm.set(state.uri.toString()) + omniTerm.set(proxiedAddress ?: state.uri.toString()) //todo - colours didn't change when switching themes, so disabled for now //val addressSpan = SpannableString(state.uri.toString()) @@ -911,12 +922,29 @@ class GemActivity : AppCompatActivity() { } updateClientCertIcon() + if(address.startsWith("http://") or address.startsWith("https://")){ + val httpProxy = prefs.getString("http_proxy", null) + + if (httpProxy != null) { + if( + httpProxy.isNullOrEmpty() + or !httpProxy.startsWith("gemini://") + or httpProxy.contains(" ") + or !httpProxy.contains(".") + ){ + openExternalLink(address) + }else{ + model.request(httpProxy, certPassword, address) + } + } + } + if(getInternetStatus()){ if(initialised){ if(address.isEmpty()){ loadLocalHome() }else{ - model.request(address, certPassword) + model.request(address, certPassword, null) } }else{ initialise() diff --git a/app/src/main/java/corewala/buran/ui/GemViewModel.kt b/app/src/main/java/corewala/buran/ui/GemViewModel.kt index 8d04b83..572d76d 100644 --- a/app/src/main/java/corewala/buran/ui/GemViewModel.kt +++ b/app/src/main/java/corewala/buran/ui/GemViewModel.kt @@ -20,12 +20,12 @@ class GemViewModel: ViewModel() { this.onState = onState if(home.startsWith("gemini://") and !home.contains(" ")){ - request(home, null) + request(home, null, null) } } - fun request(address: String, clientCertPassword: String?) { - gemini.request(address, false, clientCertPassword){ state -> + fun request(address: String, clientCertPassword: String?, alternativeRequest: String?) { + gemini.request(address, false, clientCertPassword, alternativeRequest){ state -> onState(state) } } @@ -38,15 +38,15 @@ class GemViewModel: ViewModel() { gemini.cancel() } - fun requestBinaryDownload(uri: URI, clientCertPassword: String?) { - gemini.request(uri.toString(), true, clientCertPassword){ state -> + fun requestBinaryDownload(uri: URI, clientCertPassword: String?, alternativeRequest: String?) { + gemini.request(uri.toString(), true, clientCertPassword, alternativeRequest){ state -> onState(state) } } //todo - same action as above... refactor fun requestInlineImage(uri: URI, clientCertPassword: String?, onImageReady: (cacheUri: Uri?) -> Unit){ - gemini.request(uri.toString(), false, clientCertPassword){ state -> + gemini.request(uri.toString(), false, clientCertPassword, null){ state -> when (state) { is GemState.ResponseImage -> onImageReady(state.cacheUri) else -> onState(state) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 9235283..a5c3a1f 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -68,6 +68,8 @@ Ouvrir en interne Mandataire HTTP Pas de mandataire HTTP + Ce contenu est visualisé via un mandataire + Ouvrir l\'original Images locales en ligne Seuls les magasins de clés client PKCS12 sont actuellement supportés. Certificat Client diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 24b2fb3..6ea9044 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -68,6 +68,8 @@ Open internally HTTP proxy No HTTP proxy set + This content is rendered through a proxy + Open original Inline local images Only PKCS12 client keystores are currently supported. Client Certificate