136 lines
4.7 KiB
Kotlin
136 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
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|