1
0
mirror of https://github.com/square/okhttp.git synced 2025-07-31 05:04:26 +03:00

Refactor DOH classes to Kotlin (#4768)

This commit is contained in:
Yuri Schimke
2019-03-24 12:24:40 +00:00
committed by GitHub
parent df158ce90a
commit c2fa8ca193
7 changed files with 478 additions and 520 deletions

View File

@ -13,33 +13,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.dnsoverhttps;
package okhttp3.dnsoverhttps
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
import okhttp3.Dns;
import okhttp3.Dns
import java.net.InetAddress
import java.net.UnknownHostException
/**
* Internal Bootstrap DNS implementation for handling initial connection to DNS over HTTPS server.
*
* Returns hardcoded results for the known host.
*/
final class BootstrapDns implements Dns {
private final String dnsHostname;
private final List<InetAddress> dnsServers;
BootstrapDns(String dnsHostname, List<InetAddress> dnsServers) {
this.dnsHostname = dnsHostname;
this.dnsServers = dnsServers;
}
@Override public List<InetAddress> lookup(String hostname) throws UnknownHostException {
if (!this.dnsHostname.equals(hostname)) {
throw new UnknownHostException(
"BootstrapDns called for " + hostname + " instead of " + dnsHostname);
internal class BootstrapDns(
private val dnsHostname: String,
private val dnsServers: List<InetAddress>
) : Dns {
@Throws(UnknownHostException::class)
override fun lookup(hostname: String): List<InetAddress> {
if (this.dnsHostname != hostname) {
throw UnknownHostException(
"BootstrapDns called for $hostname instead of $dnsHostname"
)
}
return dnsServers;
return dnsServers
}
}

View File

@ -1,359 +0,0 @@
/*
* Copyright (C) 2018 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.dnsoverhttps;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import javax.annotation.Nullable;
import okhttp3.CacheControl;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Dns;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.internal.platform.Platform;
import okhttp3.internal.publicsuffix.PublicSuffixDatabase;
import okio.ByteString;
import static java.util.Arrays.asList;
/**
* DNS over HTTPS implementation.
*
* Implementation of https://tools.ietf.org/html/draft-ietf-doh-dns-over-https-13
*
* <blockquote>A DNS API client encodes a single DNS query into an HTTP request
* using either the HTTP GET or POST method and the other requirements
* of this section. The DNS API server defines the URI used by the
* request through the use of a URI Template.</blockquote>
*
* <h3>Warning: This is a non-final API.</h3>
*
* <p><strong>As of OkHttp 3.11, this feature is an unstable preview: the API is subject to change,
* and the implementation is incomplete. We expect that OkHttp 3.12 or 3.13 will finalize this API.
* Until then, expect API and behavior changes when you update your OkHttp dependency.</strong>
*/
public class DnsOverHttps implements Dns {
public static final MediaType DNS_MESSAGE = MediaType.get("application/dns-message");
public static final int MAX_RESPONSE_SIZE = 64 * 1024;
private final OkHttpClient client;
private final HttpUrl url;
private final boolean includeIPv6;
private final boolean post;
private final boolean resolvePrivateAddresses;
private final boolean resolvePublicAddresses;
DnsOverHttps(Builder builder) {
if (builder.client == null) {
throw new NullPointerException("client not set");
}
if (builder.url == null) {
throw new NullPointerException("url not set");
}
this.url = builder.url;
this.includeIPv6 = builder.includeIPv6;
this.post = builder.post;
this.resolvePrivateAddresses = builder.resolvePrivateAddresses;
this.resolvePublicAddresses = builder.resolvePublicAddresses;
this.client = builder.client.newBuilder().dns(buildBootstrapClient(builder)).build();
}
private static Dns buildBootstrapClient(Builder builder) {
List<InetAddress> hosts = builder.bootstrapDnsHosts;
if (hosts != null) {
return new BootstrapDns(builder.url.host(), hosts);
} else {
return builder.systemDns;
}
}
public HttpUrl url() {
return url;
}
public boolean post() {
return post;
}
public boolean includeIPv6() {
return includeIPv6;
}
public OkHttpClient client() {
return client;
}
public boolean resolvePrivateAddresses() {
return resolvePrivateAddresses;
}
public boolean resolvePublicAddresses() {
return resolvePublicAddresses;
}
@Override public List<InetAddress> lookup(String hostname) throws UnknownHostException {
if (!resolvePrivateAddresses || !resolvePublicAddresses) {
boolean privateHost = isPrivateHost(hostname);
if (privateHost && !resolvePrivateAddresses) {
throw new UnknownHostException("private hosts not resolved");
}
if (!privateHost && !resolvePublicAddresses) {
throw new UnknownHostException("public hosts not resolved");
}
}
return lookupHttps(hostname);
}
private List<InetAddress> lookupHttps(String hostname) throws UnknownHostException {
List<Call> networkRequests = new ArrayList<>(2);
List<Exception> failures = new ArrayList<>(2);
List<InetAddress> results = new ArrayList<>(5);
buildRequest(hostname, networkRequests, results, failures, DnsRecordCodec.TYPE_A);
if (includeIPv6) {
buildRequest(hostname, networkRequests, results, failures, DnsRecordCodec.TYPE_AAAA);
}
executeRequests(hostname, networkRequests, results, failures);
if (!results.isEmpty()) {
return results;
}
return throwBestFailure(hostname, failures);
}
private void buildRequest(String hostname, List<Call> networkRequests, List<InetAddress> results,
List<Exception> failures, int type) {
Request request = buildRequest(hostname, type);
Response response = getCacheOnlyResponse(request);
if (response != null) {
processResponse(response, hostname, results, failures);
} else {
networkRequests.add(client.newCall(request));
}
}
private void executeRequests(final String hostname, List<Call> networkRequests,
final List<InetAddress> responses, final List<Exception> failures) {
final CountDownLatch latch = new CountDownLatch(networkRequests.size());
for (Call call : networkRequests) {
call.enqueue(new Callback() {
@Override public void onFailure(Call call, IOException e) {
synchronized (failures) {
failures.add(e);
}
latch.countDown();
}
@Override public void onResponse(Call call, Response response) {
processResponse(response, hostname, responses, failures);
latch.countDown();
}
});
}
try {
latch.await();
} catch (InterruptedException e) {
failures.add(e);
}
}
private void processResponse(Response response, String hostname, List<InetAddress> results,
List<Exception> failures) {
try {
List<InetAddress> addresses = readResponse(hostname, response);
synchronized (results) {
results.addAll(addresses);
}
} catch (Exception e) {
synchronized (failures) {
failures.add(e);
}
}
}
private List<InetAddress> throwBestFailure(String hostname, List<Exception> failures)
throws UnknownHostException {
if (failures.size() == 0) {
throw new UnknownHostException(hostname);
}
Exception failure = failures.get(0);
if (failure instanceof UnknownHostException) {
throw (UnknownHostException) failure;
}
UnknownHostException unknownHostException = new UnknownHostException(hostname);
unknownHostException.initCause(failure);
for (int i = 1; i < failures.size(); i++) {
unknownHostException.addSuppressed(failures.get(i));
}
throw unknownHostException;
}
private @Nullable Response getCacheOnlyResponse(Request request) {
if (!post && client.cache() != null) {
try {
Request cacheRequest = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();
Response cacheResponse = client.newCall(cacheRequest).execute();
if (cacheResponse.code() != 504) {
return cacheResponse;
}
} catch (IOException ioe) {
// Failures are ignored as we can fallback to the network
// and hopefully repopulate the cache.
}
}
return null;
}
private List<InetAddress> readResponse(String hostname, Response response) throws Exception {
if (response.cacheResponse() == null && response.protocol() != Protocol.HTTP_2) {
Platform.get().log(Platform.WARN, "Incorrect protocol: " + response.protocol(), null);
}
try {
if (!response.isSuccessful()) {
throw new IOException("response: " + response.code() + " " + response.message());
}
ResponseBody body = response.body();
if (body.contentLength() > MAX_RESPONSE_SIZE) {
throw new IOException("response size exceeds limit ("
+ MAX_RESPONSE_SIZE
+ " bytes): "
+ body.contentLength()
+ " bytes");
}
ByteString responseBytes = body.source().readByteString();
return DnsRecordCodec.decodeAnswers(hostname, responseBytes);
} finally {
response.close();
}
}
private Request buildRequest(String hostname, int type) {
Request.Builder requestBuilder = new Request.Builder().header("Accept", DNS_MESSAGE.toString());
ByteString query = DnsRecordCodec.encodeQuery(hostname, type);
if (post) {
requestBuilder = requestBuilder.url(url).post(RequestBody.create(DNS_MESSAGE, query));
} else {
String encoded = query.base64Url().replace("=", "");
HttpUrl requestUrl = url.newBuilder().addQueryParameter("dns", encoded).build();
requestBuilder = requestBuilder.url(requestUrl);
}
return requestBuilder.build();
}
static boolean isPrivateHost(String host) {
return PublicSuffixDatabase.get().getEffectiveTldPlusOne(host) == null;
}
public static final class Builder {
@Nullable OkHttpClient client = null;
@Nullable HttpUrl url = null;
boolean includeIPv6 = true;
boolean post = false;
Dns systemDns = Dns.SYSTEM;
@Nullable List<InetAddress> bootstrapDnsHosts = null;
boolean resolvePrivateAddresses = false;
boolean resolvePublicAddresses = true;
public Builder() {
}
public DnsOverHttps build() {
return new DnsOverHttps(this);
}
public Builder client(OkHttpClient client) {
this.client = client;
return this;
}
public Builder url(HttpUrl url) {
this.url = url;
return this;
}
public Builder includeIPv6(boolean includeIPv6) {
this.includeIPv6 = includeIPv6;
return this;
}
public Builder post(boolean post) {
this.post = post;
return this;
}
public Builder resolvePrivateAddresses(boolean resolvePrivateAddresses) {
this.resolvePrivateAddresses = resolvePrivateAddresses;
return this;
}
public Builder resolvePublicAddresses(boolean resolvePublicAddresses) {
this.resolvePublicAddresses = resolvePublicAddresses;
return this;
}
public Builder bootstrapDnsHosts(@Nullable List<InetAddress> bootstrapDnsHosts) {
this.bootstrapDnsHosts = bootstrapDnsHosts;
return this;
}
public Builder bootstrapDnsHosts(InetAddress... bootstrapDnsHosts) {
return bootstrapDnsHosts(asList(bootstrapDnsHosts));
}
public Builder systemDns(Dns systemDns) {
this.systemDns = systemDns;
return this;
}
}
}

View File

@ -0,0 +1,327 @@
/*
* Copyright (C) 2018 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.dnsoverhttps
import okhttp3.CacheControl
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Dns
import okhttp3.HttpUrl
import okhttp3.MediaType
import okhttp3.OkHttpClient
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.Response
import okhttp3.internal.platform.Platform
import okhttp3.internal.publicsuffix.PublicSuffixDatabase
import java.io.IOException
import java.net.InetAddress
import java.net.UnknownHostException
import java.util.ArrayList
import java.util.Arrays.asList
import java.util.concurrent.CountDownLatch
/**
* DNS over HTTPS implementation.
*
* Implementation of https://tools.ietf.org/html/draft-ietf-doh-dns-over-https-13
*
* <blockquote>A DNS API client encodes a single DNS query into an HTTP request
* using either the HTTP GET or POST method and the other requirements
* of this section. The DNS API server defines the URI used by the
* request through the use of a URI Template.</blockquote>
*
* <h3>Warning: This is a non-final API.</h3>
*
*
* As of OkHttp 3.14, this feature is an unstable preview: the API is subject to change,
* and the implementation is incomplete. We expect that OkHttp 4.0 or 4.1 will finalize this API.
* Until then, expect API and behavior changes when you update your OkHttp dependency.**
*/
class DnsOverHttps internal constructor(builder: Builder) : Dns {
private val client: OkHttpClient =
builder.client?.newBuilder()?.dns(buildBootstrapClient(builder))?.build()
?: throw NullPointerException("client not set")
private val url: HttpUrl = builder.url ?: throw NullPointerException("url not set")
private val includeIPv6: Boolean = builder.includeIPv6
private val post: Boolean = builder.post
private val resolvePrivateAddresses: Boolean = builder.resolvePrivateAddresses
private val resolvePublicAddresses: Boolean = builder.resolvePublicAddresses
fun url(): HttpUrl = url
fun post(): Boolean = post
fun includeIPv6(): Boolean = includeIPv6
fun client(): OkHttpClient = client
fun resolvePrivateAddresses(): Boolean = resolvePrivateAddresses
fun resolvePublicAddresses(): Boolean = resolvePublicAddresses
@Throws(UnknownHostException::class)
override fun lookup(hostname: String): List<InetAddress> {
if (!resolvePrivateAddresses || !resolvePublicAddresses) {
val privateHost = isPrivateHost(hostname)
if (privateHost && !resolvePrivateAddresses) {
throw UnknownHostException("private hosts not resolved")
}
if (!privateHost && !resolvePublicAddresses) {
throw UnknownHostException("public hosts not resolved")
}
}
return lookupHttps(hostname)
}
@Throws(UnknownHostException::class)
private fun lookupHttps(hostname: String): List<InetAddress> {
val networkRequests = ArrayList<Call>(2)
val failures = ArrayList<Exception>(2)
val results = ArrayList<InetAddress>(5)
buildRequest(hostname, networkRequests, results, failures, DnsRecordCodec.TYPE_A)
if (includeIPv6) {
buildRequest(hostname, networkRequests, results, failures, DnsRecordCodec.TYPE_AAAA)
}
executeRequests(hostname, networkRequests, results, failures)
return if (!results.isEmpty()) {
results
} else {
throwBestFailure(hostname, failures)
}
}
private fun buildRequest(
hostname: String, networkRequests: MutableList<Call>, results: MutableList<InetAddress>,
failures: MutableList<Exception>, type: Int
) {
val request = buildRequest(hostname, type)
val response = getCacheOnlyResponse(request)
response?.let { processResponse(it, hostname, results, failures) } ?: networkRequests.add(
client.newCall(request))
}
private fun executeRequests(
hostname: String, networkRequests: List<Call>,
responses: MutableList<InetAddress>, failures: MutableList<Exception>
) {
val latch = CountDownLatch(networkRequests.size)
for (call in networkRequests) {
call.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
synchronized(failures) {
failures.add(e)
}
latch.countDown()
}
override fun onResponse(call: Call, response: Response) {
processResponse(response, hostname, responses, failures)
latch.countDown()
}
})
}
try {
latch.await()
} catch (e: InterruptedException) {
failures.add(e)
}
}
private fun processResponse(
response: Response, hostname: String, results: MutableList<InetAddress>,
failures: MutableList<Exception>
) {
try {
val addresses = readResponse(hostname, response)
synchronized(results) {
results.addAll(addresses)
}
} catch (e: Exception) {
synchronized(failures) {
failures.add(e)
}
}
}
@Throws(UnknownHostException::class)
private fun throwBestFailure(hostname: String, failures: List<Exception>): List<InetAddress> {
if (failures.isEmpty()) {
throw UnknownHostException(hostname)
}
val failure = failures[0]
if (failure is UnknownHostException) {
throw failure
}
val unknownHostException = UnknownHostException(hostname)
unknownHostException.initCause(failure)
for (i in 1 until failures.size) {
unknownHostException.addSuppressed(failures[i])
}
throw unknownHostException
}
private fun getCacheOnlyResponse(request: Request): Response? {
if (!post && client.cache() != null) {
try {
val cacheRequest = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build()
val cacheResponse = client.newCall(cacheRequest).execute()
if (cacheResponse.code() != 504) {
return cacheResponse
}
} catch (ioe: IOException) {
// Failures are ignored as we can fallback to the network
// and hopefully repopulate the cache.
}
}
return null
}
@Throws(Exception::class)
private fun readResponse(hostname: String, response: Response): List<InetAddress> {
if (response.cacheResponse() == null && response.protocol() !== Protocol.HTTP_2) {
Platform.get().log(Platform.WARN, "Incorrect protocol: ${response.protocol()}", null)
}
response.use {
if (!response.isSuccessful) {
throw IOException("response: " + response.code() + " " + response.message())
}
val body = response.body()
if (body!!.contentLength() > MAX_RESPONSE_SIZE) {
throw IOException(
"response size exceeds limit ($MAX_RESPONSE_SIZE bytes): ${body.contentLength()} bytes"
)
}
val responseBytes = body.source().readByteString()
return DnsRecordCodec.decodeAnswers(hostname, responseBytes)
}
}
private fun buildRequest(hostname: String, type: Int): Request =
Request.Builder().header("Accept", DNS_MESSAGE.toString()).apply {
val query = DnsRecordCodec.encodeQuery(hostname, type)
if (post) {
url(url).post(RequestBody.create(DNS_MESSAGE, query))
} else {
val encoded = query.base64Url().replace("=", "")
val requestUrl = url.newBuilder().addQueryParameter("dns", encoded).build()
url(requestUrl)
}
}.build()
class Builder {
internal var client: OkHttpClient? = null
internal var url: HttpUrl? = null
internal var includeIPv6 = true
internal var post = false
internal var systemDns = Dns.SYSTEM
internal var bootstrapDnsHosts: List<InetAddress>? = null
internal var resolvePrivateAddresses = false
internal var resolvePublicAddresses = true
fun build(): DnsOverHttps = DnsOverHttps(this)
fun client(client: OkHttpClient): Builder {
this.client = client
return this
}
fun url(url: HttpUrl): Builder {
this.url = url
return this
}
fun includeIPv6(includeIPv6: Boolean): Builder {
this.includeIPv6 = includeIPv6
return this
}
fun post(post: Boolean): Builder {
this.post = post
return this
}
fun resolvePrivateAddresses(resolvePrivateAddresses: Boolean): Builder {
this.resolvePrivateAddresses = resolvePrivateAddresses
return this
}
fun resolvePublicAddresses(resolvePublicAddresses: Boolean): Builder {
this.resolvePublicAddresses = resolvePublicAddresses
return this
}
fun bootstrapDnsHosts(bootstrapDnsHosts: List<InetAddress>?): Builder {
this.bootstrapDnsHosts = bootstrapDnsHosts
return this
}
fun bootstrapDnsHosts(vararg bootstrapDnsHosts: InetAddress): Builder {
return bootstrapDnsHosts(asList(*bootstrapDnsHosts))
}
fun systemDns(systemDns: Dns): Builder {
this.systemDns = systemDns
return this
}
}
companion object {
val DNS_MESSAGE: MediaType = MediaType.get("application/dns-message")
const val MAX_RESPONSE_SIZE = 64 * 1024
private fun buildBootstrapClient(builder: Builder): Dns {
val hosts = builder.bootstrapDnsHosts
return if (hosts != null) {
BootstrapDns(builder.url!!.host(), hosts)
} else {
builder.systemDns
}
}
internal fun isPrivateHost(host: String): Boolean {
return PublicSuffixDatabase.get().getEffectiveTldPlusOne(host) == null
}
}
}

View File

@ -1,139 +0,0 @@
/*
* Copyright 2016 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License, version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package okhttp3.dnsoverhttps;
import java.io.EOFException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import okio.Buffer;
import okio.ByteString;
import okio.Utf8;
/**
* Trivial Dns Encoder/Decoder, basically ripped from Netty full implementation.
*/
class DnsRecordCodec {
private static final byte SERVFAIL = 2;
private static final byte NXDOMAIN = 3;
public static final int TYPE_A = 0x0001;
public static final int TYPE_AAAA = 0x001c;
private static final int TYPE_PTR = 0x000c;
private static final Charset ASCII = Charset.forName("ASCII");
private DnsRecordCodec() {
}
public static ByteString encodeQuery(String host, int type) {
Buffer buf = new Buffer();
buf.writeShort(0); // query id
buf.writeShort(256); // flags with recursion
buf.writeShort(1); // question count
buf.writeShort(0); // answerCount
buf.writeShort(0); // authorityResourceCount
buf.writeShort(0); // additional
Buffer nameBuf = new Buffer();
final String[] labels = host.split("\\.");
for (String label : labels) {
long utf8ByteCount = Utf8.size(label);
if (utf8ByteCount != label.length()) {
throw new IllegalArgumentException("non-ascii hostname: " + host);
}
nameBuf.writeByte((byte) utf8ByteCount);
nameBuf.writeUtf8(label);
}
nameBuf.writeByte(0); // end
nameBuf.copyTo(buf, 0, nameBuf.size());
buf.writeShort(type);
buf.writeShort(1); // CLASS_IN
return buf.readByteString();
}
public static List<InetAddress> decodeAnswers(String hostname, ByteString byteString)
throws Exception {
List<InetAddress> result = new ArrayList<>();
Buffer buf = new Buffer();
buf.write(byteString);
buf.readShort(); // query id
final int flags = buf.readShort() & 0xffff;
if (flags >> 15 == 0) {
throw new IllegalArgumentException("not a response");
}
byte responseCode = (byte) (flags & 0xf);
if (responseCode == NXDOMAIN) {
throw new UnknownHostException(hostname + ": NXDOMAIN");
} else if (responseCode == SERVFAIL) {
throw new UnknownHostException(hostname + ": SERVFAIL");
}
final int questionCount = buf.readShort() & 0xffff;
final int answerCount = buf.readShort() & 0xffff;
buf.readShort(); // authority record count
buf.readShort(); // additional record count
for (int i = 0; i < questionCount; i++) {
skipName(buf); // name
buf.readShort(); // type
buf.readShort(); // class
}
for (int i = 0; i < answerCount; i++) {
skipName(buf); // name
int type = buf.readShort() & 0xffff;
buf.readShort(); // class
final long ttl = buf.readInt() & 0xffffffffL; // ttl
final int length = buf.readShort() & 0xffff;
if (type == TYPE_A || type == TYPE_AAAA) {
byte[] bytes = new byte[length];
buf.read(bytes);
result.add(InetAddress.getByAddress(bytes));
} else {
buf.skip(length);
}
}
return result;
}
private static void skipName(Buffer in) throws EOFException {
// 0 - 63 bytes
int length = in.readByte();
if (length < 0) {
// compressed name pointer, first two bits are 1
// drop second byte of compression offset
in.skip(1);
} else {
while (length > 0) {
// skip each part of the domain name
in.skip(length);
length = in.readByte();
}
}
}
}

View File

@ -0,0 +1,134 @@
/*
* Copyright 2016 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License, version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package okhttp3.dnsoverhttps
import okio.Buffer
import okio.ByteString
import okio.Utf8
import java.io.EOFException
import java.net.InetAddress
import java.net.UnknownHostException
import java.nio.charset.StandardCharsets
import java.util.ArrayList
/**
* Trivial Dns Encoder/Decoder, basically ripped from Netty full implementation.
*/
object DnsRecordCodec {
private const val SERVFAIL = 2
private const val NXDOMAIN = 3
const val TYPE_A = 0x0001
const val TYPE_AAAA = 0x001c
private const val TYPE_PTR = 0x000c
private val ASCII = StandardCharsets.US_ASCII
@JvmStatic
fun encodeQuery(host: String, type: Int): ByteString = Buffer().apply {
writeShort(0) // query id
writeShort(256) // flags with recursion
writeShort(1) // question count
writeShort(0) // answerCount
writeShort(0) // authorityResourceCount
writeShort(0) // additional
val nameBuf = Buffer()
val labels = host.split('.').dropLastWhile { it.isEmpty() }.toTypedArray()
for (label in labels) {
val utf8ByteCount = Utf8.size(label)
if (utf8ByteCount != label.length.toLong()) {
throw IllegalArgumentException("non-ascii hostname: $host")
}
nameBuf.writeByte(utf8ByteCount.toInt())
nameBuf.writeUtf8(label)
}
nameBuf.writeByte(0) // end
nameBuf.copyTo(this, 0, nameBuf.size())
writeShort(type)
writeShort(1) // CLASS_IN
}.readByteString()
@Throws(Exception::class)
@JvmStatic
fun decodeAnswers(hostname: String, byteString: ByteString): List<InetAddress> {
val result = ArrayList<InetAddress>()
val buf = Buffer()
buf.write(byteString)
buf.readShort() // query id
val flags = buf.readShort().toInt() and 0xffff
if (flags shr 15 == 0) {
throw IllegalArgumentException("not a response")
}
val responseCode = flags and 0xf
if (responseCode == NXDOMAIN) {
throw UnknownHostException("$hostname: NXDOMAIN")
} else if (responseCode == SERVFAIL) {
throw UnknownHostException("$hostname: SERVFAIL")
}
val questionCount = buf.readShort().toInt() and 0xffff
val answerCount = buf.readShort().toInt() and 0xffff
buf.readShort() // authority record count
buf.readShort() // additional record count
for (i in 0 until questionCount) {
skipName(buf) // name
buf.readShort() // type
buf.readShort() // class
}
for (i in 0 until answerCount) {
skipName(buf) // name
val type = buf.readShort().toInt() and 0xffff
buf.readShort() // class
val ttl = buf.readInt().toLong() and 0xffffffffL // ttl
val length = buf.readShort().toInt() and 0xffff
if (type == TYPE_A || type == TYPE_AAAA) {
val bytes = ByteArray(length)
buf.read(bytes)
result.add(InetAddress.getByAddress(bytes))
} else {
buf.skip(length.toLong())
}
}
return result
}
@Throws(EOFException::class)
private fun skipName(source: Buffer) {
// 0 - 63 bytes
var length = source.readByte().toInt()
if (length < 0) {
// compressed name pointer, first two bits are 1
// drop second byte of compression offset
source.skip(1)
} else {
while (length > 0) {
// skip each part of the domain name
source.skip(length.toLong())
length = source.readByte().toInt()
}
}
}
}

View File

@ -1,3 +0,0 @@
/** A DNS over HTTPS implementation for OkHttp. */
@okhttp3.internal.annotations.EverythingIsNonNull
package okhttp3.dnsoverhttps;

View File

@ -0,0 +1,2 @@
/** A DNS over HTTPS implementation for OkHttp. */
package okhttp3.dnsoverhttps