parent
04408dab4b
commit
bd4778063b
@ -1,190 +0,0 @@ |
||||
package com.github.tvbox.osc.util; |
||||
|
||||
import android.app.AlarmManager; |
||||
import android.app.PendingIntent; |
||||
import android.content.Context; |
||||
import android.content.pm.PackageInfo; |
||||
import android.content.pm.PackageManager; |
||||
import android.os.Build; |
||||
import android.os.Environment; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
import java.io.File; |
||||
import java.io.FileOutputStream; |
||||
import java.io.PrintWriter; |
||||
import java.io.StringWriter; |
||||
import java.io.Writer; |
||||
import java.lang.reflect.Field; |
||||
import java.text.DateFormat; |
||||
import java.text.SimpleDateFormat; |
||||
import java.util.Date; |
||||
import java.util.Map; |
||||
import java.util.concurrent.ConcurrentHashMap; |
||||
|
||||
|
||||
/** |
||||
* @author acer |
||||
* @date 2018/9/10 |
||||
*/ |
||||
|
||||
public class CrashHandler implements Thread.UncaughtExceptionHandler { |
||||
private static volatile CrashHandler instance; |
||||
private Context mContext; |
||||
private PendingIntent restartIntent; |
||||
private Thread.UncaughtExceptionHandler mDefaultHandler; |
||||
private String mExceptionInfo; |
||||
private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); |
||||
private DateFormat formatterFodder = new SimpleDateFormat("yyyyMMdd"); |
||||
private ConcurrentHashMap<String, String> mCrashInfo = new ConcurrentHashMap<>(); |
||||
|
||||
private CrashHandler() { |
||||
|
||||
} |
||||
|
||||
public static CrashHandler getInstance() { |
||||
if (instance == null) { |
||||
synchronized (CrashHandler.class) { |
||||
if (instance == null) { |
||||
instance = new CrashHandler(); |
||||
} |
||||
} |
||||
} |
||||
return instance; |
||||
} |
||||
|
||||
public void init(Context context, PendingIntent pendingIntent) { |
||||
mContext = context; |
||||
restartIntent = pendingIntent; |
||||
//保存一份系统默认的CrashHandler
|
||||
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); |
||||
//使用我们自定义的异常处理器替换程序默认的
|
||||
Thread.setDefaultUncaughtExceptionHandler(this); |
||||
} |
||||
|
||||
@Override |
||||
public void uncaughtException(Thread t, Throwable e) { |
||||
if (!catchCrashException(e) && mDefaultHandler != null) { |
||||
//没有自定义的CrashHandler的时候就调用系统默认的异常处理方式
|
||||
mDefaultHandler.uncaughtException(t, e); |
||||
} else { |
||||
//退出应用
|
||||
try { |
||||
Thread.sleep(2000); |
||||
} catch (InterruptedException e1) { |
||||
e1.printStackTrace(); |
||||
} |
||||
// 退出程序并在2s后重启
|
||||
AlarmManager mgr = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); |
||||
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 2000, restartIntent); |
||||
AppManager.getInstance().appExit(1); |
||||
} |
||||
} |
||||
|
||||
private boolean catchCrashException(Throwable ex) { |
||||
if (ex == null) { |
||||
return false; |
||||
} |
||||
collectInfo(ex); |
||||
//上传崩溃信息
|
||||
uploadCrashInfo(); |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* 获取异常信息和设备参数信息 |
||||
*/ |
||||
private void collectInfo(Throwable ex) { |
||||
mExceptionInfo = collectExceptionInfo(ex); |
||||
try { |
||||
// 获得包管理器
|
||||
PackageManager mPackageManager = mContext.getPackageManager(); |
||||
// 得到该应用的信息,即主Activity
|
||||
PackageInfo mPackageInfo = mPackageManager.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES); |
||||
this.mCrashInfo.put("PackageName", mContext.getPackageName()); |
||||
if (mPackageInfo != null) { |
||||
String versionName = mPackageInfo.versionName == null ? "null" : mPackageInfo.versionName; |
||||
String versionCode = mPackageInfo.versionCode + ""; |
||||
this.mCrashInfo.put("VersionName", versionName); |
||||
this.mCrashInfo.put("VersionCode", versionCode); |
||||
} |
||||
} catch (PackageManager.NameNotFoundException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
// 反射机制
|
||||
Field[] mFields = Build.class.getDeclaredFields(); |
||||
// 迭代Build的字段key-value 此处的信息主要是为了在服务器端手机各种版本手机报错的原因
|
||||
for (Field field : mFields) { |
||||
try { |
||||
field.setAccessible(true); |
||||
mCrashInfo.put(field.getName(), field.get("").toString()); |
||||
} catch (IllegalArgumentException e) { |
||||
e.printStackTrace(); |
||||
} catch (IllegalAccessException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private String saveCrashInfo2File(StringBuilder sb) { |
||||
try { |
||||
long timestamp = System.currentTimeMillis(); |
||||
String time = formatter.format(new Date()); |
||||
String fileName = "crash-" + time + "-" + timestamp + ".log"; |
||||
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { |
||||
String path = "/sdcard/movie/crash/" + formatterFodder.format(new Date()) + "/"; |
||||
File dir = new File(path); |
||||
if (!dir.exists()) { |
||||
dir.mkdirs(); |
||||
} |
||||
FileOutputStream fos = new FileOutputStream(path + fileName); |
||||
fos.write(sb.toString().getBytes()); |
||||
fos.close(); |
||||
} |
||||
return fileName; |
||||
} catch (Exception e) { |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* 获取捕获异常的信息 |
||||
*/ |
||||
private String collectExceptionInfo(Throwable ex) { |
||||
Writer mWriter = new StringWriter(); |
||||
PrintWriter mPrintWriter = new PrintWriter(mWriter); |
||||
ex.printStackTrace(mPrintWriter); |
||||
ex.printStackTrace(); |
||||
Throwable mThrowable = ex.getCause(); |
||||
// 迭代栈队列把所有的异常信息写入writer中
|
||||
while (mThrowable != null) { |
||||
mThrowable.printStackTrace(mPrintWriter); |
||||
// 换行 每个个异常栈之间换行
|
||||
mPrintWriter.append("\r\n"); |
||||
mThrowable = mThrowable.getCause(); |
||||
} |
||||
// 记得关闭
|
||||
mPrintWriter.close(); |
||||
return mWriter.toString(); |
||||
} |
||||
|
||||
/** |
||||
* 将HashMap遍历转换成StringBuffer |
||||
*/ |
||||
@NonNull |
||||
private static StringBuilder getInfoStr(ConcurrentHashMap<String, String> info) { |
||||
StringBuilder mStringBuilder = new StringBuilder(); |
||||
for (Map.Entry<String, String> entry : info.entrySet()) { |
||||
String key = entry.getKey(); |
||||
String value = entry.getValue(); |
||||
mStringBuilder.append(key + "=" + value + "\r\n"); |
||||
} |
||||
return mStringBuilder; |
||||
} |
||||
|
||||
private void uploadCrashInfo() { |
||||
StringBuilder mStringBuilder = getInfoStr(mCrashInfo); |
||||
mStringBuilder.append(mExceptionInfo); |
||||
saveCrashInfo2File(mStringBuilder); |
||||
} |
||||
} |
||||
@ -0,0 +1,45 @@ |
||||
/* |
||||
* 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.net.InetAddress; |
||||
import java.net.UnknownHostException; |
||||
import java.util.List; |
||||
import okhttp3.Dns; |
||||
|
||||
/** |
||||
* 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); |
||||
} |
||||
|
||||
return dnsServers; |
||||
} |
||||
} |
||||
@ -0,0 +1,399 @@ |
||||
/* |
||||
* 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 androidx.annotation.Nullable; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.net.InetAddress; |
||||
import java.net.UnknownHostException; |
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
import java.util.concurrent.CountDownLatch; |
||||
|
||||
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.Util; |
||||
import okhttp3.internal.platform.Platform; |
||||
import okhttp3.internal.publicsuffix.PublicSuffixDatabase; |
||||
import okio.ByteString; |
||||
|
||||
/** |
||||
* DNS over HTTPS implementation. |
||||
* <p> |
||||
* 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 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(); |
||||
} |
||||
|
||||
public void setUrl(HttpUrl newUrl) { |
||||
this.url = newUrl; |
||||
} |
||||
|
||||
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 (this.url == null) |
||||
return Dns.SYSTEM.lookup(hostname); |
||||
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); |
||||
} |
||||
|
||||
public byte[] lookupHttpsForwardSync(String hostname) throws Throwable { |
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); |
||||
try { |
||||
byteArrayOutputStream.write(executeRequestsSync(hostname, DnsRecordCodec.TYPE_A)); |
||||
} finally { |
||||
|
||||
} |
||||
try { |
||||
byteArrayOutputStream.write(executeRequestsSync(hostname, DnsRecordCodec.TYPE_AAAA)); |
||||
} finally { |
||||
|
||||
} |
||||
return byteArrayOutputStream.toByteArray(); |
||||
} |
||||
|
||||
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 Dns.SYSTEM.lookup(hostname); |
||||
// 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 byte[] executeRequestsSync(String hostname, int type) throws IOException { |
||||
Request request = buildRequest(hostname, type); |
||||
Response response = getCacheOnlyResponse(request); |
||||
|
||||
if (response == null) { |
||||
response = client.newCall(request).execute(); |
||||
} |
||||
return response.body().bytes(); |
||||
} |
||||
|
||||
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++) { |
||||
Util.addSuppressedIfPossible(unknownHostException, 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(Arrays.asList(bootstrapDnsHosts)); |
||||
} |
||||
|
||||
public Builder systemDns(Dns systemDns) { |
||||
this.systemDns = systemDns; |
||||
return this; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,139 @@ |
||||
/* |
||||
* 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(); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue