From: Lanius Trolling Date: Sun, 22 Dec 2024 20:46:24 +0000 (-0500) Subject: Improve dummy-lock handling X-Git-Url: https://gitweb.starshipfights.net/?a=commitdiff_plain;h=addb9066729fdda5be277dd72b99859973226c5b;p=factbooks Improve dummy-lock handling --- diff --git a/src/main/kotlin/info/mechyrdia/route/ResourceWebDav.kt b/src/main/kotlin/info/mechyrdia/route/ResourceWebDav.kt index c33a6df..46c5795 100644 --- a/src/main/kotlin/info/mechyrdia/route/ResourceWebDav.kt +++ b/src/main/kotlin/info/mechyrdia/route/ResourceWebDav.kt @@ -34,7 +34,13 @@ import io.ktor.server.routing.Route import io.ktor.server.routing.method import io.ktor.server.routing.route import io.ktor.util.AttributeKey -import kotlinx.html.* +import kotlinx.html.a +import kotlinx.html.body +import kotlinx.html.h1 +import kotlinx.html.head +import kotlinx.html.li +import kotlinx.html.title +import kotlinx.html.ul import java.net.URI import java.time.Instant import java.time.ZoneOffset @@ -69,7 +75,10 @@ sealed class WebDavProperties : XmlInsertable { resourceProps() "supportedlock" { "lockentry" { - "lockscope" { "shared"() } + "lockscope" { + "shared"() + "exclusive"() + } "locktype" { "write"() } } } @@ -356,28 +365,139 @@ suspend fun ApplicationCall.webDavDelete(path: StoragePath) { respond(HttpStatusCode.NotFound) } -suspend fun ApplicationCall.webDavLock(path: StoragePath) { - beforeWebDav() +private sealed interface WebDavLockInfo { + @JvmInline + value class Scalar(val value: String?) : WebDavLockInfo - if (request.header(HttpHeaders.ContentType) != null) - receiveText() + @JvmInline + value class Complex(val subTags: Map) : WebDavLockInfo +} + +private fun XmlTag.webDavLockInfo(info: WebDavLockInfo, tag: String) { + when (info) { + is WebDavLockInfo.Scalar -> { + info.value?.let { + tag { +it } + } ?: tag() + } + + is WebDavLockInfo.Complex -> { + tag { + for ((k, v) in info.subTags) + webDavLockInfo(v, k) + } + } + } +} + +private fun getXmlTagPrefix(line: String): String { + val xmlnsIndex = line.indexOf("xmlns:") + if (xmlnsIndex < 0) + return "" + + val namespace = line.substring(xmlnsIndex + 6).substringBefore('=') + return "$namespace:" +} + +private enum class XmlTagType { + SCALAR, OPEN, CLOSE +} + +private fun getXmlTagType(line: String, prefix: String): Triple? { + val (isClose, lineMinusAngleBracket) = if (line.startsWith("') + if (isClose) + return Triple(tag, XmlTagType.CLOSE, null) + + if (tag.endsWith("/")) + return Triple(tag.substring(0, tag.length - 1), XmlTagType.SCALAR, null) + + if (lineMinusPrefix.endsWith("")) + return Triple(tag, XmlTagType.SCALAR, lineMinusPrefix.substring(tag.length + 1, tag.length + prefix.length + 3)) + return Triple(tag, XmlTagType.OPEN, null) +} + +private fun Iterator.nextOrNull(): T? = if (hasNext()) next() else null + +private fun Iterator.buildLockInfoTree(xmlTagPrefix: String? = null): Pair?, Boolean> { + val line = nextOrNull()?.trim() ?: return Pair(null, false) + if (line.isBlank() || line.startsWith(" return Pair(Pair(tagName, WebDavLockInfo.Scalar(tagValue)), true) + XmlTagType.OPEN -> { + val subTrees = buildMap { + do { + val (subTree, hasNext) = buildLockInfoTree(prefix) + subTree?.let { (k, v) -> put(k, v) } + } while (hasNext) + } + + return Pair(Pair(tagName, WebDavLockInfo.Complex(subTrees)), true) + } + + XmlTagType.CLOSE -> return Pair(null, false) + } +} + +private val defaultLockInfo: WebDavLockInfo.Complex = WebDavLockInfo.Complex( + mapOf( + "lockscope" to WebDavLockInfo.Complex( + mapOf( + "shared" to WebDavLockInfo.Scalar(null) + ) + ), + "locktype" to WebDavLockInfo.Complex( + mapOf( + "write" to WebDavLockInfo.Scalar(null) + ) + ), + "owner" to WebDavLockInfo.Scalar(null), + ) +) + +private const val InfiniteTimeout: String = "Second-31556925000" + +private suspend fun ApplicationCall.parseRequestLockInfo(): WebDavLockInfo.Complex { val depth = request.header(HttpHeaders.Depth) ?: "Infinity" + val (requestLockInfoTag, _) = receiveText().lineSequence().iterator().buildLockInfoTree() + val requestLockInfo = requestLockInfoTag?.takeIf { it.first == "lockinfo" }?.second as? WebDavLockInfo.Complex ?: defaultLockInfo + + return WebDavLockInfo.Complex( + requestLockInfo.subTags + mapOf( + "depth" to WebDavLockInfo.Scalar(depth), + "timeout" to WebDavLockInfo.Scalar(InfiniteTimeout), + "locktoken" to WebDavLockInfo.Complex( + mapOf( + "href" to WebDavLockInfo.Scalar("opaquelocktoken:${UUID.randomUUID()}") + ) + ) + ) + ) +} + +suspend fun ApplicationCall.webDavLock(path: StoragePath) { + beforeWebDav() + + val info = parseRequestLockInfo() respondXml { declaration() .root("prop", namespace = "DAV:") { "lockdiscovery" { - "activelock" { - "lockscope" { "shared"() } - "locktype" { "write"() } - "depth" { +depth } - "owner"() - "timeout" { +"Second-86400" } - "locktoken" { - "href" { +"opaquelocktoken:${UUID.randomUUID()}" } - } - } + webDavLockInfo(info, "activeLock") } } }