Fix XML code
authorLanius Trolling <lanius@laniustrolling.dev>
Sun, 14 Apr 2024 16:14:25 +0000 (12:14 -0400)
committerLanius Trolling <lanius@laniustrolling.dev>
Sun, 14 Apr 2024 16:14:25 +0000 (12:14 -0400)
13 files changed:
src/jvmMain/kotlin/info/mechyrdia/Configuration.kt
src/jvmMain/kotlin/info/mechyrdia/data/DataFiles.kt
src/jvmMain/kotlin/info/mechyrdia/data/Nations.kt
src/jvmMain/kotlin/info/mechyrdia/data/ViewComments.kt
src/jvmMain/kotlin/info/mechyrdia/data/ViewsFiles.kt
src/jvmMain/kotlin/info/mechyrdia/data/Xml.kt
src/jvmMain/kotlin/info/mechyrdia/lore/Fonts.kt
src/jvmMain/kotlin/info/mechyrdia/lore/ParserBuilder.kt
src/jvmMain/kotlin/info/mechyrdia/lore/ViewOg.kt
src/jvmMain/kotlin/info/mechyrdia/lore/ViewsQuote.kt
src/jvmMain/kotlin/info/mechyrdia/lore/ViewsRobots.kt
src/jvmMain/kotlin/info/mechyrdia/lore/ViewsRss.kt
src/jvmMain/kotlin/info/mechyrdia/route/ResourceTypes.kt

index 17ff8fd54c786b66aba47f14f6121508af06639e..eba5a89129b2d88947820ed1f826ed69464b7b0d 100644 (file)
@@ -48,3 +48,5 @@ data class Configuration(
 
 val OwnerNationId: Id<NationData>
        get() = Id(Configuration.Current.ownerNation)
+
+const val MainDomainName = "https://mechyrdia.info"
index d5b5a02ce631f0f58e5c4c817836325e81978ddd..c6a9e80bf5af48b0b5e2b1d3b8b5768f49729fb7 100644 (file)
@@ -29,17 +29,16 @@ import java.time.Instant
 import kotlin.String
 import kotlin.time.Duration.Companion.hours
 
-fun StoragePath.getContentType(): ContentType {
-       val extension = elements.last().substringAfter('.', "")
-       return if (extension.isEmpty()) ContentType.Text.Plain else ContentType.defaultForFileExtension(extension)
-}
+val StoragePath.contentType: ContentType
+       get() {
+               val extension = elements.last().substringAfter('.', "")
+               return if (extension.isEmpty()) ContentType.Text.Plain else ContentType.defaultForFileExtension(extension)
+       }
 
 suspend fun ApplicationCall.respondStoredFile(path: StoragePath) {
        val content = FileStorage.instance.readFile(path) ?: return respond(HttpStatusCode.NotFound)
-       val type = path.getContentType()
-       
        attributes.put(StoragePathAttributeKey, path)
-       respondBytes(content, type)
+       respondBytes(content, path.contentType)
 }
 
 @JvmInline
index 54d9fadcb7e8e204c8f51861234ddb427cc988c6..1cac61c94f7359184cbc607e9c8c51027e9c5262 100644 (file)
@@ -49,7 +49,7 @@ data class NationData(
        }
 }
 
-val CallNationCacheAttribute = AttributeKey<MutableMap<Id<NationData>, NationData>>("NationCache")
+val CallNationCacheAttribute = AttributeKey<MutableMap<Id<NationData>, NationData>>("Mechyrdia.NationCache")
 
 val ApplicationCall.nationCache: MutableMap<Id<NationData>, NationData>
        get() = attributes.getOrNull(CallNationCacheAttribute)
@@ -63,7 +63,7 @@ suspend fun MutableMap<Id<NationData>, NationData>.getNation(id: Id<NationData>)
        }
 }
 
-private val callCurrentNationAttribute = AttributeKey<NationSession>("CurrentNation")
+private val CallCurrentNationAttribute = AttributeKey<NationSession>("Mechyrdia.CurrentNation")
 
 fun ApplicationCall.ownerNationOnly() {
        if (sessions.get<UserSession>()?.nationId != OwnerNationId)
@@ -71,7 +71,7 @@ fun ApplicationCall.ownerNationOnly() {
 }
 
 suspend fun ApplicationCall.currentNation(): NationData? {
-       attributes.getOrNull(callCurrentNationAttribute)?.let { sess ->
+       attributes.getOrNull(CallCurrentNationAttribute)?.let { sess ->
                return when (sess) {
                        NationSession.Anonymous -> null
                        is NationSession.LoggedIn -> sess.nation
@@ -80,10 +80,10 @@ suspend fun ApplicationCall.currentNation(): NationData? {
        
        val nationId = sessions.get<UserSession>()?.nationId
        return if (nationId == null) {
-               attributes.put(callCurrentNationAttribute, NationSession.Anonymous)
+               attributes.put(CallCurrentNationAttribute, NationSession.Anonymous)
                null
        } else nationCache.getNation(nationId).also { data ->
-               attributes.put(callCurrentNationAttribute, NationSession.LoggedIn(data))
+               attributes.put(CallCurrentNationAttribute, NationSession.LoggedIn(data))
        }
 }
 
index e76dfd1bcb8b0f5b63769cc04a1f306c916c5497..930f876b869abf8536ae862f5e51a7ccb2517240 100644 (file)
@@ -1,5 +1,6 @@
 package info.mechyrdia.data
 
+import info.mechyrdia.MainDomainName
 import info.mechyrdia.OwnerNationId
 import info.mechyrdia.lore.*
 import info.mechyrdia.route.Root
@@ -102,7 +103,7 @@ fun FlowContent.commentBox(comment: CommentRenderData, loggedInAs: Id<NationData
                                }
                                +Entities.nbsp
                                a(href = "#", classes = "copy-text") {
-                                       attributes["data-text"] = "https://mechyrdia.info${href(Root.Comments.ViewPage(comment.id))}"
+                                       attributes["data-text"] = "$MainDomainName${href(Root.Comments.ViewPage(comment.id))}"
                                        +"(Copy)"
                                }
                                
index 6d2b5418248ddd430f3f35d0c49fd893bcfd8e7b..1df19d8afcd216d252237e78b2d23b4de422fb64 100644 (file)
@@ -16,7 +16,7 @@ import io.ktor.server.response.*
 import kotlinx.coroutines.*
 import kotlinx.html.*
 
-private fun Map<String, StoredFileType>.sortedAsFiles() = toList()
+fun Map<String, StoredFileType>.sortedAsFiles() = toList()
        .sortedBy { (name, _) -> name }
        .sortedBy { (_, type) -> type }
 
index 071f1a74eea5a95704105f0c49a2033b8f19723c..7dc5a91723b1b8395aac9b5cb1ce308dd5cfa234 100644 (file)
@@ -17,10 +17,6 @@ import kotlinx.html.stream.createHTML
 import kotlinx.html.visit
 import kotlinx.html.visitAndFinalize
 import org.w3c.dom.Document
-import kotlin.collections.component1
-import kotlin.collections.component2
-import kotlin.collections.contains
-import kotlin.collections.set
 
 @DslMarker
 annotation class XmlTagMarker
@@ -41,8 +37,6 @@ interface XmlTagConsumer<out R> : TagConsumer<R> {
        fun onTagDeclaration(version: String, standalone: Boolean?)
        fun onTagDeclaration(standalone: Boolean?) = onTagDeclaration("1.0", standalone)
        
-       fun onTagNamespace(prefix: String?, namespace: String)
-       
        override fun onTagEvent(tag: Tag, event: String, value: (Event) -> Unit) {
                tagEventsNotSupported()
        }
@@ -90,14 +84,6 @@ private fun TagConsumer<*>.getDeclarationConsumer(): XmlDeclarationConsumer =
                else -> throw IllegalArgumentException("Unsupported TagConsumer subtype ${this::class.qualifiedName}")
        }
 
-private data class XmlTagImpl(val tag: Tag, val prefix: String?) : Tag by tag {
-       override val namespace: String?
-               get() = null
-       
-       override val tagName: String
-               get() = prefix?.let { "$it:" }.orEmpty() + tag.tagName
-}
-
 private class XmlTagConsumerImpl<out R>(val downstream: TagConsumer<R>) : XmlTagConsumer<R>, TagConsumer<R> by downstream {
        private var isDeclared = false
        
@@ -109,53 +95,6 @@ private class XmlTagConsumerImpl<out R>(val downstream: TagConsumer<R>) : XmlTag
                isDeclared = true
        }
        
-       private var defaultNamespace: String? = null
-       private var namespaces: MutableMap<String, String>? = mutableMapOf()
-       private val namespaceLookup = mutableMapOf<String, String>()
-       
-       override fun onTagNamespace(prefix: String?, namespace: String) {
-               if (prefix == null) {
-                       if (namespaces == null)
-                               error("Unable to add xmlns attribute after document has already started")
-                       defaultNamespace = namespace
-               } else
-                       (namespaces
-                               ?: error("Unable to add xmlns attribute after document has already started"))[prefix] = namespace
-       }
-       
-       override fun onTagStart(tag: Tag) {
-               namespaces?.let { namespaceList ->
-                       defaultNamespace?.let { namespaceDefault ->
-                               tag.attributes["xmlns"] = namespaceDefault
-                       }
-                       
-                       for ((prefix, namespace) in namespaceList) {
-                               tag.attributes["xmlns:$prefix"] = namespace
-                               namespaceLookup[namespace] = prefix
-                       }
-                       
-                       namespaces = null
-               }
-               
-               val prefix = when (tag.namespace) {
-                       null, defaultNamespace -> null
-                       in namespaceLookup -> namespaceLookup[tag.namespace]
-                       else -> throw IllegalArgumentException("Unrecognized namespace ${tag.namespace}")
-               }
-               
-               downstream.onTagStart(XmlTagImpl(tag, prefix))
-       }
-       
-       override fun onTagEnd(tag: Tag) {
-               val prefix = when (tag.namespace) {
-                       null, defaultNamespace -> null
-                       in namespaceLookup -> namespaceLookup[tag.namespace]
-                       else -> throw IllegalArgumentException("Unrecognized namespace ${tag.namespace}")
-               }
-               
-               downstream.onTagEnd(XmlTagImpl(tag, prefix))
-       }
-       
        override fun onTagEvent(tag: Tag, event: String, value: (Event) -> Unit) {
                tagEventsNotSupported()
        }
@@ -166,28 +105,6 @@ fun <T, C : XmlTagConsumer<T>> C.declaration(version: String = "1.0", standalone
        onTagDeclaration(version, standalone)
 }
 
-@XmlTagMarker
-fun <T, C : XmlTagConsumer<T>> C.defaultXmlns(namespace: String) = apply {
-       onTagNamespace(null, namespace)
-}
-
-@XmlTagMarker
-fun <T, C : XmlTagConsumer<T>> C.prefixedXmlns(prefix: String, namespace: String) = apply {
-       onTagNamespace(prefix, namespace)
-}
-
-@XmlTagMarker
-fun <T, C : XmlTagConsumer<T>> C.prefixedXmlns(map: Map<String, String>) = apply {
-       for ((prefix, namespace) in map)
-               onTagNamespace(prefix, namespace)
-}
-
-@XmlTagMarker
-fun <T, C : XmlTagConsumer<T>> C.prefixedXmlns(map: Iterable<Pair<String, String>>) = apply {
-       for ((prefix, namespace) in map)
-               onTagNamespace(prefix, namespace)
-}
-
 @XmlTagMarker
 class XmlTag(
        override val tagName: String,
index 9c36df5ddc2b6afe4f4133e5b7fc021741c389a0..17c0294a4ce196f89d3242d0c4a0c79b9ad79663 100644 (file)
@@ -61,9 +61,9 @@ data class SvgPath(
 )
 
 fun <T, C : XmlTagConsumer<T>> C.svg(svgDoc: SvgDoc) = declaration(standalone = false)
-       .defaultXmlns("http://www.w3.org/2000/svg")
        .root(
                "svg",
+               namespace = "http://www.w3.org/2000/svg",
                attributes = mapOf(
                        "width" to svgDoc.width.xmlValue,
                        "height" to svgDoc.height.xmlValue,
index f5b90d43fa3597c72039de30ed039db4a440fb24..34d468126bb57add6fc9c3bf34740224b5620bdd 100644 (file)
@@ -1,5 +1,6 @@
 package info.mechyrdia.lore
 
+import info.mechyrdia.MainDomainName
 import info.mechyrdia.data.Comment
 import info.mechyrdia.data.Id
 
@@ -92,7 +93,7 @@ private class ToCPropertyBuilderTag(val converter: (String) -> String, val sette
        }
 }
 
-fun String.imagePathToOpenGraphValue() = "https://mechyrdia.info/assets/images/${sanitizeLink()}"
+fun String.imagePathToOpenGraphValue() = "$MainDomainName/assets/images/${sanitizeLink()}"
 
 enum class ToCBuilderTag(val type: BuilderTag<TableOfContentsBuilder>) {
        H1(ToCHeaderBuilderTag(0)),
index 94374d7acacb1a75639283523c8c2a11bc9e29df..796ca73f9962da06dd9aa0f54a0384ea520cb623 100644 (file)
@@ -1,5 +1,6 @@
 package info.mechyrdia.lore
 
+import info.mechyrdia.MainDomainName
 import io.ktor.server.application.*
 import io.ktor.server.request.*
 import kotlinx.html.HEAD
@@ -27,5 +28,5 @@ fun HEAD.renderOgData(title: String, data: OpenGraphData) {
        ogProperty("type", "website")
        ogProperty("description", data.desc)
        ogProperty("image", data.image)
-       ogProperty("url", "https://mechyrdia.info/${request.path().removePrefix("/")}")
+       ogProperty("url", "$MainDomainName/${request.path().removePrefix("/")}")
 }
index 358951bad5c43812a0aa67c893ba1921d2518436..52aacacadcc12df1dd3c3cb6d41347ed4538e527 100644 (file)
@@ -1,6 +1,7 @@
 package info.mechyrdia.lore
 
 import info.mechyrdia.JsonFileCodec
+import info.mechyrdia.MainDomainName
 import info.mechyrdia.data.*
 import info.mechyrdia.route.KeyedEnumSerializer
 import io.ktor.http.*
@@ -24,13 +25,13 @@ data class Quote(
                get() = if (portrait.startsWith("http://") || portrait.startsWith("https://"))
                        portrait
                else
-                       "https://mechyrdia.info/assets/images/$portrait"
+                       "$MainDomainName/assets/images/$portrait"
        
        val fullLink: String
                get() = if (link.startsWith("http://") || link.startsWith("https://"))
                        link
                else
-                       "https://mechyrdia.info/lore/$link"
+                       "$MainDomainName/lore/$link"
 }
 
 private val quotesListGetter by storedData(StoragePath("quotes.json")) { jsonPath ->
index cc8654521ac778a997f4139699bcfba735e285eb..76a2cb234b46d1f9b0bfccea1b14735837593b02 100644 (file)
@@ -1,5 +1,6 @@
 package info.mechyrdia.lore
 
+import info.mechyrdia.MainDomainName
 import info.mechyrdia.data.*
 import java.time.Instant
 import java.time.format.DateTimeFormatter
@@ -50,7 +51,7 @@ private suspend fun buildIntroSitemap(): SitemapEntry? {
        val introLastModified = maxOf(introFile.updated, introMetaFile.updated)
        
        return SitemapEntry(
-               loc = "https://mechyrdia.info/",
+               loc = "$MainDomainName/",
                lastModified = introLastModified,
                changeFrequency = AVERAGE_FACTBOOK_INTRO_CHANGEFREQ,
                priority = FACTBOOK_INTRO_PRIORITY
@@ -61,7 +62,7 @@ private suspend fun buildLoreSitemap(): List<SitemapEntry> {
        return allPages().mapNotNull { page ->
                if (!page.path.isViewable) null
                else SitemapEntry(
-                       loc = "https://mechyrdia.info/lore/${page.path}",
+                       loc = "$MainDomainName/lore/${page.path}",
                        lastModified = page.stat.updated,
                        changeFrequency = AVERAGE_FACTBOOK_PAGE_CHANGEFREQ,
                        priority = FACTBOOK_PAGE_PRIORITY
@@ -72,8 +73,7 @@ private suspend fun buildLoreSitemap(): List<SitemapEntry> {
 suspend fun buildSitemap() = listOfNotNull(buildIntroSitemap()) + buildLoreSitemap()
 
 fun <T, C : XmlTagConsumer<T>> C.sitemap(entries: List<SitemapEntry>) = declaration()
-       .defaultXmlns("http://www.sitemaps.org/schemas/sitemap/0.9")
-       .root("urlset") {
+       .root("urlset", namespace = "http://www.sitemaps.org/schemas/sitemap/0.9") {
                for (entry in entries)
                        +entry
        }
index ef674d9c274fbcc37a0c6e75edf698457b5ad6b3..deb5566031aaabff521deca3864eaa56837824d9 100644 (file)
@@ -1,6 +1,7 @@
 package info.mechyrdia.lore
 
 import com.mongodb.client.model.Sorts
+import info.mechyrdia.MainDomainName
 import info.mechyrdia.OwnerNationId
 import info.mechyrdia.data.*
 import io.ktor.http.*
@@ -54,7 +55,7 @@ suspend fun generateRecentPageEdits(): RssChannel {
        
        return RssChannel(
                title = "Recently Edited Factbooks | The Hour of Decision",
-               link = "https://mechyrdia.info",
+               link = MainDomainName,
                description = "An RSS feed containing all factbooks in The Hour of Decision, in order of most recently edited.",
                pubDate = mostRecentChange,
                lastBuildDate = mostRecentChange,
@@ -73,7 +74,7 @@ suspend fun generateRecentPageEdits(): RssChannel {
                                        val pageOg = pageToC.toOpenGraph()
                                        
                                        val imageEnclosure = pageOg?.image?.let { url ->
-                                               val assetPath = url.removePrefix("https://mechyrdia.info/assets/")
+                                               val assetPath = url.removePrefix("$MainDomainName/assets/")
                                                val file = StoragePath.assetDir / assetPath
                                                RssItemEnclosure(
                                                        url = url,
@@ -85,9 +86,9 @@ suspend fun generateRecentPageEdits(): RssChannel {
                                        RssItem(
                                                title = pageToC.toPageTitle(),
                                                description = pageOg?.desc,
-                                               link = "https://mechyrdia.info/lore${pageLink.joinToString { "/$it" }}",
+                                               link = "$MainDomainName/lore${pageLink.joinToString { "/$it" }}",
                                                author = null,
-                                               comments = "https://mechyrdia.info/lore${pageLink.joinToString { "/$it" }}#comments",
+                                               comments = "$MainDomainName/lore${pageLink.joinToString { "/$it" }}#comments",
                                                enclosure = imageEnclosure,
                                                pubDate = page.stat.updated
                                        )
@@ -107,7 +108,7 @@ suspend fun ApplicationCall.recentCommentsRssFeedGenerator(limit: Int): RssChann
                
                return RssChannel(
                        title = "Recent Comments - Error | The Hour of Decision",
-                       link = "https://mechyrdia.info/comment/recent",
+                       link = "$MainDomainName/comment/recent",
                        description = "Comment limit must be between ${validLimits.first} and ${validLimits.last}, got $limit",
                        pubDate = null,
                        lastBuildDate = Instant.now(),
@@ -134,7 +135,7 @@ suspend fun ApplicationCall.recentCommentsRssFeedGenerator(limit: Int): RssChann
        
        return RssChannel(
                title = "Recent Comments | The Hour of Decision",
-               link = "https://mechyrdia.info/comment/recent",
+               link = "$MainDomainName/comment/recent",
                description = "An RSS feed containing the $limit most recently-submitted comments",
                pubDate = mostRecentComment,
                lastBuildDate = mostRecentComment,
@@ -144,11 +145,11 @@ suspend fun ApplicationCall.recentCommentsRssFeedGenerator(limit: Int): RssChann
                ),
                items = comments.map { comment ->
                        RssItem(
-                               title = "Comment by ${comment.submittedBy.name} on https://mechyrdia.info/lore/${comment.submittedIn}",
+                               title = "Comment by ${comment.submittedBy.name} on $MainDomainName/${comment.submittedIn}",
                                description = comment.contentsRaw.parseAs(ParserTree::toCommentPlainText),
-                               link = "https://mechyrdia.info/comment/view/${comment.id}",
+                               link = "$MainDomainName/comment/view/${comment.id}",
                                author = null,
-                               comments = "https://mechyrdia.info/lore/${comment.submittedIn}#comment-${comment.id}",
+                               comments = "$MainDomainName/lore/${comment.submittedIn}#comment-${comment.id}",
                                pubDate = comment.lastEdit ?: comment.submittedAt,
                                categories = listOf(
                                        RssCategory(domain = "https://nationstates.net", category = comment.submittedBy.name)
index 5ac391cc9abad08dc2a5317bb615cc50b61b7955..3238b3b7ce7145871fd898994788eba559a7c79f 100644 (file)
@@ -1,8 +1,6 @@
 package info.mechyrdia.route
 
-import info.mechyrdia.auth.loginPage
-import info.mechyrdia.auth.loginRoute
-import info.mechyrdia.auth.logoutRoute
+import info.mechyrdia.auth.*
 import info.mechyrdia.data.*
 import info.mechyrdia.lore.*
 import io.ktor.http.*
@@ -16,7 +14,7 @@ import io.ktor.util.*
 import io.ktor.util.pipeline.*
 import kotlinx.coroutines.delay
 
-val ErrorMessageAttributeKey = AttributeKey<String>("ErrorMessage")
+val ErrorMessageAttributeKey = AttributeKey<String>("Mechyrdia.ErrorMessage")
 
 @Resource("/")
 class Root(val error: String? = null) : ResourceHandler, ResourceFilter {
@@ -318,6 +316,7 @@ class Root(val error: String? = null) : ResourceHandler, ResourceFilter {
                        class CopyPost(val path: List<String>, val vfs: Vfs = Vfs()) : ResourceReceiver<AdminVfsCopyFilePayload> {
                                override suspend fun PipelineContext<Unit, ApplicationCall>.handleCall(payload: AdminVfsCopyFilePayload) {
                                        with(vfs) { filterCall() }
+                                       with(payload) { call.verifyCsrfToken() }
                                        
                                        call.adminDoCopyFile(StoragePath(payload.from), StoragePath(path))
                                }