パーミションのチェックを強制する(適切な例外を強制する)

UnknownHostExceptionってのが凄く分かり難い。どこにもセキュリティ違反の情報が無い。
せめてSecurityExceptionかその継承クラスにできなかったんだろうか。

:
無論対策はあるのだが、長くなりそうなので明日にでも

ということでソースコードを追ってみた。

static InetAddress getAllByNameImpl(String host, boolean returnUnshared) throws UnknownHostException {
    :
    略
    :
    if (isHostName(host)) {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkConnect(host, -1);
        }
        if (returnUnshared) {
            return lookupHostByName(host).clone();
        } else {
            return lookupHostByName(host);
        }
    }
    :
private static InetAddress lookupHostByName(String host) throws UnknownHostException {
    :
    略
    :
    try {
        InetAddress addresses = bytesToInetAddresses(getaddrinfo(host), host);
        addressCache.put(host, addresses);
        return addresses;
    } catch (UnknownHostException e) {
        addressCache.putUnknownHost(host);
        throw new UnknownHostException(host);
    }
    :
private static native byte[] getaddrinfo(String name) throws UnknownHostException;

以降JNIで書かれたC++コードに制御が移っている。

    • java_net_InetAddress.cpp.
static jobjectArray InetAddress_getaddrinfoImpl(JNIEnv* env, const char* name) {
    struct addrinfo hints, *addressList = NULL, *addrInfo;
    jobjectArray addressArray = NULL;
    :
    :
    int result = getaddrinfo(name, NULL, &hints, &addressList);
    if (result == 0 && addressList) {
       :
       :
    } else if (result == EAI_SYSTEM && errno == EACCES) {
        /* No permission to use network */
        jniThrowException(env, "java/lang/SecurityException",
            "Permission denied (maybe missing INTERNET permission)");
    } else {
        jniThrowException(env, "java/net/UnknownHostException",
                gai_strerror(result));
    }
    :
}

getaddrinfoのエラーが正しく戻れば(特にEACCESは"Permission denied"だ)SecurityExceptionを投げてくれるはずなのだが、実際にはUnknownHostExceptionが投げられている。 ..... ということで今回もnativeメソッドで探索は終了。


普通ならここで諦めるのだが、Contextクラスにはパーミションのチェックは強制することができるメソッドが用意されており、これを使えば前もって任意のパーミションが付加されているか否かを検査できる。

    • 例) "android.permission.INTERNET" が付加されているか否かを検査する
//Context中で書くことを前提に
this.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET, "適切なパーミションがマニフェストに記述されていません");

このメソッドは自身と自身を呼び出したプロセスが引数で指定されたパーミションを検査し、許可されていない場合はSecurityExceptionをスローしてくれる。

    • 実行結果
ERROR/AndroidRuntime(503): java.lang.SecurityException: 適切なパーミションがマニフェストに記述されていません: Neither user 10041 nor current process has android.permission.INTERNET.
ERROR/AndroidRuntime(503):     at android.app.ContextImpl.enforce(ContextImpl.java:1238)
ERROR/AndroidRuntime(503):     at android.app.ContextImpl.enforceCallingOrSelfPermission(ContextImpl.java:1267)
ERROR/AndroidRuntime(503):     at android.content.ContextWrapper.enforceCallingOrSelfPermission(ContextWrapper.java:395)

このように期待した例外とメッセージをログに出力することができるため、余計な遠回りや検討違いの原因探しをしなくても済む。

UnknownHostExceptionという分かり難い例外が修正されるまでは上記メソッドを適宜使うのが良いだろう。