mirror of https://github.com/Corewala/Buran
parent
affa99e8f2
commit
467e3fc0b7
|
@ -56,14 +56,17 @@ class OmniTerm(private val listener: Listener) {
|
||||||
when {
|
when {
|
||||||
link.startsWith(GEM_SCHEME) -> uri.set(link)
|
link.startsWith(GEM_SCHEME) -> uri.set(link)
|
||||||
link.startsWith("//") -> uri.set("gemini:$link")
|
link.startsWith("//") -> uri.set("gemini:$link")
|
||||||
|
link.startsWith("http://") or link.startsWith("https://") -> {
|
||||||
|
uri.set(link)
|
||||||
|
}
|
||||||
link.contains(":") -> listener.openExternal(link)
|
link.contains(":") -> listener.openExternal(link)
|
||||||
else -> uri.resolve(link)
|
else -> uri.resolve(link)
|
||||||
}
|
}
|
||||||
|
|
||||||
val address = uri.toString().replace("//", "/").replace("gemini:/", "gemini://")
|
val address = uri.toString().replace("//", "/").replace(":/", "://")
|
||||||
println("OmniTerm resolved address: $address")
|
|
||||||
|
|
||||||
if(invokeListener) listener.request(address)
|
if(invokeListener) listener.request(address)
|
||||||
|
println("OmniTerm resolved address: $address")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reset(){
|
fun reset(){
|
||||||
|
|
|
@ -6,7 +6,7 @@ import corewala.buran.io.database.history.BuranHistory
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
interface Datasource {
|
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 isRequesting(): Boolean
|
||||||
fun cancel()
|
fun cancel()
|
||||||
fun canGoBack(): Boolean
|
fun canGoBack(): Boolean
|
||||||
|
|
|
@ -8,9 +8,9 @@ import corewala.buran.OppenURI
|
||||||
import corewala.buran.io.GemState
|
import corewala.buran.io.GemState
|
||||||
import corewala.buran.io.database.history.BuranHistory
|
import corewala.buran.io.database.history.BuranHistory
|
||||||
import corewala.buran.io.keymanager.BuranKeyManager
|
import corewala.buran.io.keymanager.BuranKeyManager
|
||||||
|
import corewala.toURI
|
||||||
import corewala.toUri
|
import corewala.toUri
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.lang.IllegalStateException
|
|
||||||
import java.net.ConnectException
|
import java.net.ConnectException
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.net.UnknownHostException
|
import java.net.UnknownHostException
|
||||||
|
@ -32,7 +32,7 @@ class GeminiDatasource(private val context: Context, val history: BuranHistory):
|
||||||
|
|
||||||
private var currentRequestAddress: String? = null
|
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.forceDownload = forceDownload
|
||||||
|
|
||||||
this.onUpdate = onUpdate
|
this.onUpdate = onUpdate
|
||||||
|
@ -41,10 +41,12 @@ class GeminiDatasource(private val context: Context, val history: BuranHistory):
|
||||||
|
|
||||||
onUpdate(GemState.Requesting(uri))
|
onUpdate(GemState.Requesting(uri))
|
||||||
|
|
||||||
|
if(address.startsWith("gemini://")){
|
||||||
currentRequestAddress = address
|
currentRequestAddress = address
|
||||||
|
}
|
||||||
|
|
||||||
GlobalScope.launch {
|
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
|
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"
|
val protocol = "TLS"
|
||||||
|
|
||||||
initSSLFactory(protocol, clientCertPassword)
|
initSSLFactory(protocol, clientCertPassword)
|
||||||
|
|
||||||
|
val port = if(uri.port != -1){
|
||||||
|
uri.port
|
||||||
|
}else{
|
||||||
|
1965
|
||||||
|
}
|
||||||
|
|
||||||
val socket: SSLSocket?
|
val socket: SSLSocket?
|
||||||
try {
|
try {
|
||||||
socket = socketFactory?.createSocket(uri.host, 1965) as SSLSocket
|
socket = socketFactory?.createSocket(uri.host, port) as SSLSocket
|
||||||
|
|
||||||
println("Buran socket handshake with ${uri.host}")
|
println("Buran socket handshake with ${uri.host}")
|
||||||
socket.startHandshake()
|
socket.startHandshake()
|
||||||
|
@ -102,7 +110,12 @@ class GeminiDatasource(private val context: Context, val history: BuranHistory):
|
||||||
val bufferedWriter = BufferedWriter(outputStreamWriter)
|
val bufferedWriter = BufferedWriter(outputStreamWriter)
|
||||||
val outWriter = PrintWriter(bufferedWriter)
|
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")
|
println("Buran socket requesting $requestEntity")
|
||||||
outWriter.print(requestEntity)
|
outWriter.print(requestEntity)
|
||||||
outWriter.flush()
|
outWriter.flush()
|
||||||
|
@ -134,10 +147,10 @@ class GeminiDatasource(private val context: Context, val history: BuranHistory):
|
||||||
when {
|
when {
|
||||||
currentRequestAddress != uri.toString() -> {}
|
currentRequestAddress != uri.toString() -> {}
|
||||||
header.code == GeminiResponse.INPUT -> onUpdate(GemState.ResponseInput(uri, header))
|
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.CLIENT_CERTIFICATE_REQUIRED -> onUpdate(GemState.ClientCertRequired(uri, header))
|
||||||
header.code != GeminiResponse.SUCCESS -> onUpdate(GemState.ResponseError(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("text/") -> getString(socket, uri, header, onUpdate)
|
||||||
header.meta.startsWith("image/") -> getBinary(socket, uri, header, onUpdate)
|
header.meta.startsWith("image/") -> getBinary(socket, uri, header, onUpdate)
|
||||||
else -> {
|
else -> {
|
||||||
|
@ -172,10 +185,6 @@ class GeminiDatasource(private val context: Context, val history: BuranHistory):
|
||||||
|
|
||||||
val processed = GemtextHelper.findCodeBlocks(lines)
|
val processed = GemtextHelper.findCodeBlocks(lines)
|
||||||
|
|
||||||
when {
|
|
||||||
!uri.toString().startsWith("gemini://") -> throw IllegalStateException("Not a Gemini Uri")
|
|
||||||
}
|
|
||||||
|
|
||||||
updateHistory(uri)
|
updateHistory(uri)
|
||||||
onUpdate(GemState.ResponseGemtext(uri, header, processed))
|
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()
|
val ouri = OppenURI()
|
||||||
ouri.set("gemini://$host")
|
ouri.set(uri.scheme + uri.host)
|
||||||
return ouri.resolve(address)
|
return ouri.resolve(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,6 +81,8 @@ class GemActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private var certPassword: String? = null
|
private var certPassword: String? = null
|
||||||
|
|
||||||
|
private var proxiedAddress: String? = null
|
||||||
|
|
||||||
private var previousPosition: Int = 0
|
private var previousPosition: Int = 0
|
||||||
|
|
||||||
private var initialised: Boolean = false
|
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 ->
|
private val onLink: (link: URI, longTap: Boolean, adapterPosition: Int) -> Unit = { uri, longTap, _: Int ->
|
||||||
if(longTap){
|
if(longTap){
|
||||||
val globalURI = if(!uri.toString().contains("//") and !uri.toString().contains(":")){
|
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 {
|
} else {
|
||||||
uri.toString()
|
uri.toString()
|
||||||
}
|
}
|
||||||
|
@ -112,6 +114,7 @@ class GemActivity : AppCompatActivity() {
|
||||||
binding.addressEdit.hint = getString(R.string.main_input_hint)
|
binding.addressEdit.hint = getString(R.string.main_input_hint)
|
||||||
inSearch = false
|
inSearch = false
|
||||||
}
|
}
|
||||||
|
|
||||||
omniTerm.navigation(uri.toString())
|
omniTerm.navigation(uri.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -496,7 +499,9 @@ class GemActivity : AppCompatActivity() {
|
||||||
is GemState.Requesting -> {
|
is GemState.Requesting -> {
|
||||||
loadingView(true)
|
loadingView(true)
|
||||||
}
|
}
|
||||||
is GemState.NotGeminiRequest -> externalProtocol(state)
|
is GemState.NotGeminiRequest -> {
|
||||||
|
externalProtocol(state.uri)
|
||||||
|
}
|
||||||
is GemState.ResponseError -> {
|
is GemState.ResponseError -> {
|
||||||
omniTerm.reset()
|
omniTerm.reset()
|
||||||
showAlert("${GeminiResponse.getCodeString(state.header.code)}:\n\n${state.header.meta}")
|
showAlert("${GeminiResponse.getCodeString(state.header.code)}:\n\n${state.header.meta}")
|
||||||
|
@ -506,7 +511,14 @@ class GemActivity : AppCompatActivity() {
|
||||||
updateClientCertIcon()
|
updateClientCertIcon()
|
||||||
showAlert("${GeminiResponse.getCodeString(state.header.code)}:\n\n${state.header.meta}")
|
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.ResponseText -> renderText(state)
|
||||||
is GemState.ResponseImage -> renderImage(state)
|
is GemState.ResponseImage -> renderImage(state)
|
||||||
is GemState.ResponseBinary -> renderBinary(state)
|
is GemState.ResponseBinary -> renderBinary(state)
|
||||||
|
@ -531,7 +543,7 @@ class GemActivity : AppCompatActivity() {
|
||||||
.setMessage("${state.uri}")
|
.setMessage("${state.uri}")
|
||||||
.setPositiveButton(getString(R.string.download).toUpperCase()) { _, _ ->
|
.setPositiveButton(getString(R.string.download).toUpperCase()) { _, _ ->
|
||||||
loadingView(true)
|
loadingView(true)
|
||||||
model.requestBinaryDownload(state.uri, clientCertPassword)
|
model.requestBinaryDownload(state.uri, clientCertPassword, null)
|
||||||
}
|
}
|
||||||
.setNegativeButton(getString(R.string.cancel).toUpperCase()) { _, _ -> }
|
.setNegativeButton(getString(R.string.cancel).toUpperCase()) { _, _ -> }
|
||||||
.show()
|
.show()
|
||||||
|
@ -638,15 +650,15 @@ class GemActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun externalProtocol(state: GemState.NotGeminiRequest) = runOnUiThread {
|
private fun externalProtocol(uri: URI) = runOnUiThread {
|
||||||
loadingView(false)
|
loadingView(false)
|
||||||
val uri = state.uri.toString()
|
val uri = uri.toString()
|
||||||
|
|
||||||
when {
|
when {
|
||||||
(uri.startsWith("http://") || uri.startsWith("https://")) -> openExternalLink(uri)
|
(uri.startsWith("http://") || uri.startsWith("https://")) -> openExternalLink(uri)
|
||||||
else -> {
|
else -> {
|
||||||
val viewIntent = Intent(Intent.ACTION_VIEW)
|
val viewIntent = Intent(Intent.ACTION_VIEW)
|
||||||
viewIntent.data = Uri.parse(state.uri.toString())
|
viewIntent.data = Uri.parse(uri.toString())
|
||||||
|
|
||||||
try {
|
try {
|
||||||
startActivity(viewIntent)
|
startActivity(viewIntent)
|
||||||
|
@ -654,7 +666,7 @@ class GemActivity : AppCompatActivity() {
|
||||||
showAlert(
|
showAlert(
|
||||||
String.format(
|
String.format(
|
||||||
getString(R.string.no_app_installed_that_can_open),
|
getString(R.string.no_app_installed_that_can_open),
|
||||||
state.uri
|
uri
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -683,7 +695,6 @@ class GemActivity : AppCompatActivity() {
|
||||||
}else{
|
}else{
|
||||||
val viewIntent = Intent(Intent.ACTION_VIEW)
|
val viewIntent = Intent(Intent.ACTION_VIEW)
|
||||||
viewIntent.data = Uri.parse(address)
|
viewIntent.data = Uri.parse(address)
|
||||||
|
|
||||||
startActivity(viewIntent)
|
startActivity(viewIntent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -691,7 +702,7 @@ class GemActivity : AppCompatActivity() {
|
||||||
private fun renderGemtext(state: GemState.ResponseGemtext) = runOnUiThread {
|
private fun renderGemtext(state: GemState.ResponseGemtext) = runOnUiThread {
|
||||||
loadingView(false)
|
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
|
//todo - colours didn't change when switching themes, so disabled for now
|
||||||
//val addressSpan = SpannableString(state.uri.toString())
|
//val addressSpan = SpannableString(state.uri.toString())
|
||||||
|
@ -911,12 +922,29 @@ class GemActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
updateClientCertIcon()
|
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(getInternetStatus()){
|
||||||
if(initialised){
|
if(initialised){
|
||||||
if(address.isEmpty()){
|
if(address.isEmpty()){
|
||||||
loadLocalHome()
|
loadLocalHome()
|
||||||
}else{
|
}else{
|
||||||
model.request(address, certPassword)
|
model.request(address, certPassword, null)
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
initialise()
|
initialise()
|
||||||
|
|
|
@ -20,12 +20,12 @@ class GemViewModel: ViewModel() {
|
||||||
this.onState = onState
|
this.onState = onState
|
||||||
|
|
||||||
if(home.startsWith("gemini://") and !home.contains(" ")){
|
if(home.startsWith("gemini://") and !home.contains(" ")){
|
||||||
request(home, null)
|
request(home, null, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun request(address: String, clientCertPassword: String?) {
|
fun request(address: String, clientCertPassword: String?, alternativeRequest: String?) {
|
||||||
gemini.request(address, false, clientCertPassword){ state ->
|
gemini.request(address, false, clientCertPassword, alternativeRequest){ state ->
|
||||||
onState(state)
|
onState(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,15 +38,15 @@ class GemViewModel: ViewModel() {
|
||||||
gemini.cancel()
|
gemini.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requestBinaryDownload(uri: URI, clientCertPassword: String?) {
|
fun requestBinaryDownload(uri: URI, clientCertPassword: String?, alternativeRequest: String?) {
|
||||||
gemini.request(uri.toString(), true, clientCertPassword){ state ->
|
gemini.request(uri.toString(), true, clientCertPassword, alternativeRequest){ state ->
|
||||||
onState(state)
|
onState(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//todo - same action as above... refactor
|
//todo - same action as above... refactor
|
||||||
fun requestInlineImage(uri: URI, clientCertPassword: String?, onImageReady: (cacheUri: Uri?) -> Unit){
|
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) {
|
when (state) {
|
||||||
is GemState.ResponseImage -> onImageReady(state.cacheUri)
|
is GemState.ResponseImage -> onImageReady(state.cacheUri)
|
||||||
else -> onState(state)
|
else -> onState(state)
|
||||||
|
|
|
@ -68,6 +68,8 @@
|
||||||
<string name="web_content_switch_label">Ouvrir en interne</string>
|
<string name="web_content_switch_label">Ouvrir en interne</string>
|
||||||
<string name="http_proxy">Mandataire HTTP</string>
|
<string name="http_proxy">Mandataire HTTP</string>
|
||||||
<string name="no_http_proxy_set">Pas de 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="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="pkcs_notice">Seuls les magasins de clés client PKCS12 sont actuellement supportés.</string>
|
||||||
<string name="client_certificate">Certificat Client</string>
|
<string name="client_certificate">Certificat Client</string>
|
||||||
|
|
|
@ -68,6 +68,8 @@
|
||||||
<string name="web_content_switch_label">Open internally</string>
|
<string name="web_content_switch_label">Open internally</string>
|
||||||
<string name="http_proxy">HTTP proxy</string>
|
<string name="http_proxy">HTTP proxy</string>
|
||||||
<string name="no_http_proxy_set">No HTTP proxy set</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="show_inline_images">Inline local images</string>
|
||||||
<string name="pkcs_notice">Only PKCS12 client keystores are currently supported.</string>
|
<string name="pkcs_notice">Only PKCS12 client keystores are currently supported.</string>
|
||||||
<string name="client_certificate">Client Certificate</string>
|
<string name="client_certificate">Client Certificate</string>
|
||||||
|
|
Loading…
Reference in New Issue