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