⬆️ Upgrade to Kotlin 1.7
[Sone.git] / src / main / kotlin / net / pterodactylus / sone / text / SoneTextParser.kt
index 64125c1..6af0fde 100644 (file)
@@ -1,28 +1,20 @@
 package net.pterodactylus.sone.text
 
-import freenet.keys.FreenetURI
-import freenet.support.Base64
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.data.impl.IdOnlySone
-import net.pterodactylus.sone.database.PostProvider
-import net.pterodactylus.sone.database.SoneProvider
-import net.pterodactylus.sone.text.LinkType.CHK
-import net.pterodactylus.sone.text.LinkType.FREEMAIL
-import net.pterodactylus.sone.text.LinkType.HTTP
-import net.pterodactylus.sone.text.LinkType.HTTPS
-import net.pterodactylus.sone.text.LinkType.KSK
-import net.pterodactylus.sone.text.LinkType.POST
-import net.pterodactylus.sone.text.LinkType.SONE
-import net.pterodactylus.sone.text.LinkType.SSK
+import freenet.keys.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.data.impl.*
+import net.pterodactylus.sone.database.*
+import net.pterodactylus.sone.text.LinkType.*
 import net.pterodactylus.sone.text.LinkType.USK
-import net.pterodactylus.sone.utils.let
-import org.bitpedia.util.Base32
-import java.net.MalformedURLException
+import net.pterodactylus.sone.utils.*
+import org.bitpedia.util.*
+import java.net.*
+import javax.inject.*
 
 /**
  * [Parser] implementation that can recognize Freenet URIs.
  */
-class SoneTextParser(private val soneProvider: SoneProvider?, private val postProvider: PostProvider?) {
+class SoneTextParser @Inject constructor(private val soneProvider: SoneProvider?, private val postProvider: PostProvider?) {
 
        fun parse(source: String, context: SoneTextParserContext?) =
                        source.split("\n")
@@ -40,7 +32,7 @@ class SoneTextParser(private val soneProvider: SoneProvider?, private val postPr
                                else
                                        LinkType.values()
                                                        .mapNotNull { it.findNext(remainder.second) }
-                                                       .minBy { it.position }
+                                                       .minByOrNull { it.position }
                                                        .let {
                                                                when {
                                                                        it == null -> PlainTextPart(remainder.second) to ""
@@ -50,27 +42,41 @@ class SoneTextParser(private val soneProvider: SoneProvider?, private val postPr
                                                        }
                        }.map { it.first }.toList()
 
+       private val NextLink.linkWithoutBacklink: String
+               get() {
+                       val backlink = link.indexOf("/../")
+                       val query = link.indexOf("?")
+                       return if ((backlink > -1) && ((query == -1) || (query > -1) && (backlink < query)))
+                               link.substring(0, backlink)
+                       else
+                               link
+               }
+
        private fun NextLink.toPart(context: SoneTextParserContext?) = when (linkType) {
                KSK, CHK -> try {
-                       FreenetURI(link).let { freenetUri ->
+                       FreenetURI(linkWithoutBacklink).let { freenetUri ->
                                FreenetLinkPart(
-                                               link,
-                                               if (freenetUri.isKSK) {
-                                                       freenetUri.guessableKey
-                                               } else {
-                                                       freenetUri.metaString ?: freenetUri.docName ?: link.substring(0, 9)
-                                               },
-                                               link.split('?').first()
+                                               linkWithoutBacklink,
+                                               freenetUri.allMetaStrings?.lastOrNull { it != "" } ?: freenetUri.docName ?: linkWithoutBacklink.substring(0, 9),
+                                               linkWithoutBacklink.split('?').first()
                                )
                        }
                } catch (e: MalformedURLException) {
-                       PlainTextPart(link)
+                       PlainTextPart(linkWithoutBacklink)
                }
                SSK, USK ->
                        try {
-                                FreenetLinkPart(link, FreenetURI(link).docName, trusted = context?.routingKey?.contentEquals(FreenetURI(link).routingKey) == true)
+                               FreenetURI(linkWithoutBacklink)
+                                               .workaroundForFaultyConstructorInFred1485AndBelow()
+                                               .let { uri ->
+                                                       uri.allMetaStrings
+                                                                       ?.takeIf { (it.size > 1) || ((it.size == 1) && (it.single() != "")) }
+                                                                       ?.lastOrNull()
+                                                                       ?: uri.docName
+                                                                       ?: "${uri.keyType}@${uri.routingKey.asFreenetBase64}"
+                                               }.let { FreenetLinkPart(linkWithoutBacklink.removeSuffix("/"), it, trusted = context?.routingKey?.contentEquals(FreenetURI(linkWithoutBacklink).routingKey) == true) }
                        } catch (e: MalformedURLException) {
-                               PlainTextPart(link)
+                               PlainTextPart(linkWithoutBacklink)
                        }
                SONE -> link.substring(7).let { SonePart(soneProvider?.getSone(it) ?: IdOnlySone(it)) }
                POST -> postProvider?.getPost(link.substring(7))?.let { PostPart(it) } ?: PlainTextPart(link)
@@ -89,6 +95,9 @@ class SoneTextParser(private val soneProvider: SoneProvider?, private val postPr
 
 }
 
+private fun FreenetURI.workaroundForFaultyConstructorInFred1485AndBelow() =
+       also { if (it.routingKey == null) throw MalformedURLException("SSK/USK without routing key") }
+
 private fun List<String>.mergeMultipleEmptyLines() = fold(emptyList<String>()) { previous, current ->
        if (previous.isEmpty()) {
                previous + current
@@ -111,7 +120,7 @@ private fun List<Part>.mergeAdjacentPlainTextParts() = fold(emptyList<Part>()) {
 
 private fun List<Part>.removeEmptyPlainTextParts() = filterNot { it == PlainTextPart("") }
 
-private val String.decodedId: String get() = Base64.encode(Base32.decode(this))
+private val String.decodedId: String get() = Base32.decode(this).asFreenetBase64
 private val String.withoutProtocol get() = substring(indexOf("//") + 2)
 private val String.withoutUrlParameters get() = split('?').first()
 
@@ -134,7 +143,7 @@ private val String.withoutMiddlePathComponents
        }
 private val String.withoutTrailingSlash get() = if (endsWith("/")) substring(0, length - 1) else this
 private val SoneTextParserContext.routingKey: ByteArray? get() = postingSone?.routingKey
-private val Sone.routingKey: ByteArray get() = Base64.decode(id)
+private val Sone.routingKey: ByteArray get() = id.fromFreenetBase64
 
 private enum class LinkType(private val scheme: String, private val freenetLink: Boolean) {