Added HTTP proxy

It's a beautiful half-broken mess
This commit is contained in:
Corewala 2022-07-25 23:07:55 -04:00
parent affa99e8f2
commit 467e3fc0b7
7 changed files with 79 additions and 35 deletions

View File

@ -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(){

View File

@ -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

View File

@ -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)
}

View File

@ -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()

View File

@ -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)

View File

@ -68,6 +68,8 @@
<string name="web_content_switch_label">Ouvrir en interne</string>
<string name="http_proxy">Mandataire HTTP</string>
<string name="no_http_proxy_set">Pas de mandataire HTTP</string>
<string name="proxied_content">Ce contenu est visualisé via un mandataire</string>
<string name="open_original">Ouvrir l\'original</string>
<string name="show_inline_images">Images locales en ligne</string>
<string name="pkcs_notice">Seuls les magasins de clés client PKCS12 sont actuellement supportés.</string>
<string name="client_certificate">Certificat Client</string>

View File

@ -68,6 +68,8 @@
<string name="web_content_switch_label">Open internally</string>
<string name="http_proxy">HTTP proxy</string>
<string name="no_http_proxy_set">No HTTP proxy set</string>
<string name="proxied_content">This content is rendered through a proxy</string>
<string name="open_original">Open original</string>
<string name="show_inline_images">Inline local images</string>
<string name="pkcs_notice">Only PKCS12 client keystores are currently supported.</string>
<string name="client_certificate">Client Certificate</string>