Yuito-app-android/app/src/main/java/com/keylesspalace/tusky/util/HttpHeaderLink.kt

138 lines
4.7 KiB
Kotlin

/* Copyright 2022 Tusky Contributors
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>.
*/
package com.keylesspalace.tusky.util
import android.net.Uri
import androidx.annotation.VisibleForTesting
import androidx.core.net.toUri
/**
* Represents one link and its parameters from the link header of an HTTP message.
*
* @see [RFC5988](https://tools.ietf.org/html/rfc5988)
*/
class HttpHeaderLink
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
constructor(
uri: String
) {
data class Parameter(val name: String, val value: String?)
private val parameters: MutableList<Parameter> = ArrayList()
val uri: Uri = uri.toUri()
private data class ValueResult(val value: String, val end: Int = -1)
companion object {
private fun findEndOfQuotedString(line: String, start: Int): Int {
var i = start
while (i < line.length) {
val c = line[i]
if (c == '\\') {
i += 1
} else if (c == '"') {
return i
}
i++
}
return -1
}
private fun parseValue(line: String, start: Int): ValueResult {
val foundIndex = line.indexOfAny(charArrayOf(';', ',', '"'), start, false)
if (foundIndex == -1) {
return ValueResult(line.substring(start).trim())
}
val c = line[foundIndex]
return if (c == ';' || c == ',') {
ValueResult(line.substring(start, foundIndex).trim(), foundIndex)
} else {
var quoteEnd = findEndOfQuotedString(line, foundIndex + 1)
if (quoteEnd == -1) {
quoteEnd = line.length
}
ValueResult(line.substring(foundIndex + 1, quoteEnd).trim(), quoteEnd)
}
}
private fun parseParameters(line: String, start: Int, link: HttpHeaderLink): Int {
var i = start
while (i < line.length) {
val foundIndex = line.indexOfAny(charArrayOf('=', ','), i, false)
if (foundIndex == -1) {
return -1
} else if (line[foundIndex] == ',') {
return foundIndex
}
val name = line.substring(line.indexOf(';', i) + 1, foundIndex).trim()
val result = parseValue(line, foundIndex)
val value = result.value
val parameter = Parameter(name, value)
link.parameters.add(parameter)
i = if (result.end == -1) {
return -1
} else {
result.end
}
}
return -1
}
/**
* @param line the entire link header, not including the initial "Link:"
* @return all links found in the header
*/
fun parse(line: String?): List<HttpHeaderLink> {
val links: MutableList<HttpHeaderLink> = mutableListOf()
line ?: return links
var i = 0
while (i < line.length) {
val uriEnd = line.indexOf('>', i)
val uri = line.substring(line.indexOf('<', i) + 1, uriEnd)
val link = HttpHeaderLink(uri)
links.add(link)
val parseEnd = parseParameters(line, uriEnd, link)
i = if (parseEnd == -1) {
break
} else {
parseEnd
}
i++
}
return links
}
/**
* @param links intended to be those returned by parse()
* @param relationType of the parameter "rel", commonly "next" or "prev"
* @return the link matching the given relation type
*/
fun findByRelationType(
links: List<HttpHeaderLink>,
relationType: String
): HttpHeaderLink? {
return links.find { link ->
link.parameters.any { parameter ->
parameter.name == "rel" && parameter.value == relationType
}
}
}
}
}