forked from openrs2/openrs2
parent
86e9963a75
commit
5da59135ea
@ -0,0 +1,30 @@ |
|||||||
|
plugins { |
||||||
|
`maven-publish` |
||||||
|
kotlin("jvm") |
||||||
|
} |
||||||
|
|
||||||
|
dependencies { |
||||||
|
api("com.google.inject:guice:${Versions.guice}") |
||||||
|
|
||||||
|
implementation("com.google.guava:guava:${Versions.guava}") |
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.kotlinCoroutines}") |
||||||
|
} |
||||||
|
|
||||||
|
publishing { |
||||||
|
publications.create<MavenPublication>("maven") { |
||||||
|
from(components["java"]) |
||||||
|
|
||||||
|
pom { |
||||||
|
packaging = "jar" |
||||||
|
name.set("OpenRS2 HTTP") |
||||||
|
description.set( |
||||||
|
""" |
||||||
|
Guava module for creating a HTTP client with .netrc and |
||||||
|
redirection support. The I/O dispatcher is used to run |
||||||
|
asynchronous requests, as code using coroutines will likely use |
||||||
|
the I/O dispatcher to read the response body. |
||||||
|
""".trimIndent() |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
package org.openrs2.http |
||||||
|
|
||||||
|
import kotlinx.coroutines.Dispatchers |
||||||
|
import kotlinx.coroutines.asExecutor |
||||||
|
import java.net.http.HttpClient |
||||||
|
import javax.inject.Provider |
||||||
|
|
||||||
|
public class HttpClientProvider : Provider<HttpClient> { |
||||||
|
override fun get(): HttpClient { |
||||||
|
return HttpClient.newBuilder() |
||||||
|
.authenticator(NetrcAuthenticator.read()) |
||||||
|
.executor(Dispatchers.IO.asExecutor()) |
||||||
|
.followRedirects(HttpClient.Redirect.NORMAL) |
||||||
|
.build() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
package org.openrs2.http |
||||||
|
|
||||||
|
import com.google.inject.AbstractModule |
||||||
|
import com.google.inject.Scopes |
||||||
|
import java.net.http.HttpClient |
||||||
|
|
||||||
|
public object HttpModule : AbstractModule() { |
||||||
|
override fun configure() { |
||||||
|
bind(HttpClient::class.java) |
||||||
|
.toProvider(HttpClientProvider::class.java) |
||||||
|
.`in`(Scopes.SINGLETON) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
package org.openrs2.http |
||||||
|
|
||||||
|
import java.io.IOException |
||||||
|
import java.net.http.HttpResponse |
||||||
|
|
||||||
|
private val DEFAULT_EXPECTED_STATUS_CODES = setOf(200) |
||||||
|
|
||||||
|
public fun <T> HttpResponse<T>.checkStatusCode(expectedStatusCodes: Set<Int> = DEFAULT_EXPECTED_STATUS_CODES) { |
||||||
|
val status = statusCode() |
||||||
|
if (status !in expectedStatusCodes) { |
||||||
|
throw IOException("Unexpected status code: $status") |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
package org.openrs2.http |
||||||
|
|
||||||
|
import com.google.common.base.StandardSystemProperty |
||||||
|
import java.io.IOException |
||||||
|
import java.net.Authenticator |
||||||
|
import java.net.PasswordAuthentication |
||||||
|
import java.nio.file.Files |
||||||
|
import java.nio.file.Paths |
||||||
|
import java.util.Scanner |
||||||
|
|
||||||
|
public class NetrcAuthenticator( |
||||||
|
private val hosts: Map<String, PasswordAuthentication>, |
||||||
|
private val default: PasswordAuthentication? |
||||||
|
) : Authenticator() { |
||||||
|
override fun getPasswordAuthentication(): PasswordAuthentication? { |
||||||
|
return hosts.getOrDefault(requestingHost, default) |
||||||
|
} |
||||||
|
|
||||||
|
public companion object { |
||||||
|
private val EMPTY_AUTHENTICATOR = NetrcAuthenticator(emptyMap(), null) |
||||||
|
|
||||||
|
public fun read(): NetrcAuthenticator { |
||||||
|
val home = StandardSystemProperty.USER_HOME.value() ?: throw IOException("user.home not defined") |
||||||
|
val path = Paths.get(home, ".netrc") |
||||||
|
|
||||||
|
return if (Files.exists(path)) { |
||||||
|
Scanner(path, Charsets.UTF_8).use { scanner -> |
||||||
|
read(scanner) |
||||||
|
} |
||||||
|
} else { |
||||||
|
EMPTY_AUTHENTICATOR |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public fun read(scanner: Scanner): NetrcAuthenticator { |
||||||
|
return NetrcReader(scanner).read() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,95 @@ |
|||||||
|
package org.openrs2.http |
||||||
|
|
||||||
|
import java.io.IOException |
||||||
|
import java.net.PasswordAuthentication |
||||||
|
import java.util.Scanner |
||||||
|
import java.util.regex.Pattern |
||||||
|
|
||||||
|
public class NetrcReader(private val scanner: Scanner) { |
||||||
|
private enum class State { |
||||||
|
DEFAULT, |
||||||
|
READ_MACHINE |
||||||
|
} |
||||||
|
|
||||||
|
private val hosts = mutableMapOf<String, PasswordAuthentication>() |
||||||
|
private var default: PasswordAuthentication? = null |
||||||
|
|
||||||
|
private var host: String? = null |
||||||
|
private var username: String? = null |
||||||
|
private var password: String? = null |
||||||
|
|
||||||
|
private var state = State.DEFAULT |
||||||
|
set(value) { |
||||||
|
if (field != State.DEFAULT) { |
||||||
|
val auth = PasswordAuthentication(username ?: "", password?.toCharArray() ?: EMPTY_CHAR_ARRAY) |
||||||
|
|
||||||
|
val host = host |
||||||
|
if (host != null) { |
||||||
|
hosts[host] = auth |
||||||
|
} else if (default == null) { |
||||||
|
default = auth |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
field = value |
||||||
|
} |
||||||
|
|
||||||
|
init { |
||||||
|
scanner.useDelimiter(WHITESPACE) |
||||||
|
} |
||||||
|
|
||||||
|
public fun read(): NetrcAuthenticator { |
||||||
|
while (scanner.hasNext()) { |
||||||
|
when (val token = scanner.next()) { |
||||||
|
"machine" -> { |
||||||
|
if (!scanner.hasNext()) { |
||||||
|
throw IOException("Expected hostname") |
||||||
|
} |
||||||
|
|
||||||
|
state = State.READ_MACHINE |
||||||
|
host = scanner.next() |
||||||
|
} |
||||||
|
"default" -> { |
||||||
|
state = State.READ_MACHINE |
||||||
|
host = null |
||||||
|
} |
||||||
|
"login", "password", "account" -> { |
||||||
|
if (state != State.READ_MACHINE) { |
||||||
|
throw IOException("Unexpected token '$token'") |
||||||
|
} else if (!scanner.hasNext()) { |
||||||
|
throw IOException("Expected $token") |
||||||
|
} |
||||||
|
|
||||||
|
if (token == "login") { |
||||||
|
username = scanner.next() |
||||||
|
} else if (token == "password") { |
||||||
|
password = scanner.next() |
||||||
|
} |
||||||
|
} |
||||||
|
"macdef" -> skipMacro() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// trigger the logic to add the final machine to the map |
||||||
|
state = State.DEFAULT |
||||||
|
|
||||||
|
return NetrcAuthenticator(hosts, default) |
||||||
|
} |
||||||
|
|
||||||
|
private fun skipMacro() { |
||||||
|
if (!scanner.hasNext()) { |
||||||
|
throw IOException("Expected macro name") |
||||||
|
} |
||||||
|
|
||||||
|
while (scanner.hasNextLine() && scanner.nextLine().isNotEmpty()) { |
||||||
|
// empty |
||||||
|
} |
||||||
|
|
||||||
|
state = State.DEFAULT |
||||||
|
} |
||||||
|
|
||||||
|
private companion object { |
||||||
|
private val WHITESPACE = Pattern.compile("\\s+") |
||||||
|
private val EMPTY_CHAR_ARRAY = charArrayOf() |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue