アカウント追加時のサーバ名入力補完リストの更新。apiHostではなくapDomainが入力された場合にWebFingerを使ってホストを推測する。

This commit is contained in:
tateisu 2023-03-19 04:52:17 +09:00
parent c9fcd8d5c1
commit 12f129a878
4 changed files with 11891 additions and 396 deletions

View File

@ -0,0 +1,44 @@
package jp.juggler.subwaytooter.api
import jp.juggler.subwaytooter.api.entity.Host
import jp.juggler.util.data.mayUri
import jp.juggler.util.data.notEmpty
import jp.juggler.util.log.LogCategory
import kotlinx.coroutines.CancellationException
import org.w3c.dom.NodeList
import java.io.ByteArrayInputStream
import javax.xml.parsers.DocumentBuilderFactory
private val log = LogCategory("WebFinger")
private fun NodeList.items() =
(0 until length).mapNotNull { item(it) }
suspend fun TootApiClient.getApiHostFromWebFinger(apDomain: Host): Host? {
val (result, bytes) = this.getHttpBytes("https://${apDomain.ascii}/.well-known/host-meta")
result ?: throw CancellationException()
result.error?.notEmpty()?.let { error(it) }
bytes ?: error("getApiHostFromWebFinger: missing response body.")
val document = DocumentBuilderFactory.newInstance().newDocumentBuilder()
.parse(ByteArrayInputStream(bytes))
val hostSet = document.getElementsByTagName("Link")
.items()
.filter { "lrdd" == it.attributes?.getNamedItem("rel")?.nodeValue }
.mapNotNull { it.attributes?.getNamedItem("template")?.nodeValue?.mayUri()?.authority?.notEmpty() }
.map { Host.parse(it) }
.toSet()
return when (hostSet.size) {
1 -> hostSet.first()
0 -> {
log.e("can't find api host for domain ${apDomain.pretty} .")
null
}
else -> {
log.e("multiple hosts found for domain ${apDomain.pretty} . ${hostSet.joinToString(", ") { it.pretty }}")
null
}
}
}

View File

@ -45,11 +45,7 @@ class Host private constructor(
val cached = hostSet[srcArg]
if (cached != null) return cached
val src = srcArg.removeUrlSchema()
val ascii = if( """[^A-Za-z0-9._-]""".toRegex().find(src)!=null){
IDN.toASCII(src, IDN.ALLOW_UNASSIGNED).lowercase()
}else{
IDN.toASCII(src, IDN.ALLOW_UNASSIGNED).lowercase()
}
val ascii = IDN.toASCII(src, IDN.ALLOW_UNASSIGNED).lowercase()
val pretty = IDN.toUnicode(src, IDN.ALLOW_UNASSIGNED)
val host = if (ascii == pretty) Host(ascii) else Host(ascii, pretty)
hostSet[src] = host

View File

@ -10,6 +10,7 @@ import androidx.core.widget.addTextChangedListener
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.entity.Host
import jp.juggler.subwaytooter.api.entity.TootInstance
import jp.juggler.subwaytooter.api.getApiHostFromWebFinger
import jp.juggler.subwaytooter.api.runApiTask2
import jp.juggler.subwaytooter.databinding.DlgAccountAddBinding
import jp.juggler.subwaytooter.databinding.LvAuthTypeBinding
@ -219,16 +220,25 @@ class LoginForm(
private fun nextPage() {
activity.run {
launchAndShowError {
val hostname = validateAndShow() ?: return@launchAndShowError
val host = Host.parse(hostname)
var host = Host.parse(validateAndShow() ?: return@launchAndShowError)
var error: String? = null
val tootInstance = try {
runApiTask2(host) {
TootInstance.getExOrThrow(it, forceUpdate =true)
val tootInstance = runApiTask2(host) { client ->
try {
// ユーザの入力がホスト名かドメイン名かは分からない。
// WebFingerでホストを調べる
client.getApiHostFromWebFinger(host)?.let {
if (it != host) {
host = it
client.apiHost = it
}
}
// サーバ情報を読む
TootInstance.getExOrThrow(client, forceUpdate = true)
} catch (ex: Throwable) {
error = ex.message
null
}
} catch (ex: Throwable) {
error = ex.message
null
}
if (isDestroyed || isFinishing) return@launchAndShowError
targetServer = host

File diff suppressed because it is too large Load Diff