import io.ktor.server.routing.method
import io.ktor.server.routing.route
import io.ktor.util.AttributeKey
-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 kotlinx.html.*
import java.net.URI
import java.time.Instant
import java.time.ZoneOffset
resourceProps()
"supportedlock" {
"lockentry" {
- "lockscope" {
- "shared"()
- "exclusive"()
- }
+ "lockscope" { "shared"() }
"locktype" { "write"() }
}
}
respond(HttpStatusCode.NotFound)
}
-private sealed interface WebDavLockInfo {
- @JvmInline
- value class Scalar(val value: String?) : WebDavLockInfo
-
- @JvmInline
- value class Complex(val subTags: Map<String, WebDavLockInfo>) : 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<String, XmlTagType, String?>? {
- val (isClose, lineMinusAngleBracket) = if (line.startsWith("</"))
- Pair(true, line.substring(2))
- else if (line.startsWith("<"))
- Pair(false, line.substring(1))
- else
- return null
-
- val lineMinusPrefix = lineMinusAngleBracket.removePrefix(prefix)
- val tag = lineMinusPrefix.substringBefore('>')
- 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("</$prefix$tag>"))
- return Triple(tag, XmlTagType.SCALAR, lineMinusPrefix.substring(tag.length + 1, tag.length + prefix.length + 3))
-
- return Triple(tag, XmlTagType.OPEN, null)
-}
-
-private fun <T : Any> Iterator<T>.nextOrNull(): T? = if (hasNext()) next() else null
-
-private fun Iterator<String>.buildLockInfoTree(xmlTagPrefix: String? = null): Pair<Pair<String, WebDavLockInfo>?, Boolean> {
- val line = nextOrNull()?.trim() ?: return Pair(null, false)
- if (line.isBlank() || line.startsWith("<?"))
- return Pair(null, true)
-
- val prefix = xmlTagPrefix ?: getXmlTagPrefix(line)
- val (tagName, tagType, tagValue) = getXmlTagType(line, prefix) ?: return Pair(null, true)
-
- when (tagType) {
- XmlTagType.SCALAR -> 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()
+ if (request.header(HttpHeaders.ContentType) != null)
+ receiveText()
+
+ val depth = request.header(HttpHeaders.Depth) ?: "Infinity"
respondXml {
declaration()
.root("prop", namespace = "DAV:") {
"lockdiscovery" {
- webDavLockInfo(info, "activeLock")
+ "activelock" {
+ "lockscope" { "shared"() }
+ "locktype" { "write"() }
+ "depth" { +depth }
+ "owner"()
+ "timeout" { +"Second-86400" }
+ "locktoken" {
+ "href" { +"opaquelocktoken:${UUID.randomUUID()}" }
+ }
+ }
}
}
}