Convert util/{HttpHeaderLink,PairedList,TimestampUtils,ThemeUtils} to Kotlin (#3046)
* Fix off-by-one error in HttpHeaderLink Link headers with multiple URLs with multiple parameters were being parsed incorrectly. Detected by adding unit tests ahead of converting to Kotlin. * Convert util/HttpHeaderLink from Java to Kotlin * Convert util/ThemeUtils from Java to Kotlin * Convert util/TimestampUtils from Java to Kotlin * Add tests for PairedList * Convert util/PairedList from Java to Kotlin * Implement feedback from PR * Relicense as GPL
This commit is contained in:
parent
0def7e7230
commit
22834431ca
30 changed files with 624 additions and 510 deletions
135
app/src/main/java/com/keylesspalace/tusky/util/HttpHeaderLink.kt
Normal file
135
app/src/main/java/com/keylesspalace/tusky/util/HttpHeaderLink.kt
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
/* 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue