« 2012年03月 | メイン | 2012年05月 »

2012年04月 アーカイブ

2012年04月04日

AndroidのWebView#addJavascriptInterfaceがどれだけ危険か検証してみた

Android SDKのJavaDocの説明はかなり簡素なんだけど、WebViewのaddJavascriptInterfaceの説明は珍しく説明が多い。
端的に意訳すると、「addJavascriptInterfaceを使うと、キミのアプリケーションをJavaScriptから操作できるようになるよ。とても便利な便利だけど、危険なセキュリティの問題があるよ。キミが書いたHTML以外では使わないでね。」という感じ。
「オブジェクトを公開する」行為が危険だというのは、技術者は直感的にわかると思うけど、じゃあどれくらい危険なのか実際に試してみた。
まず、次のようなAndroidアプリケーションを作った。
「android.permission.INTERNET」をパーミッションに指定して、WebViewを画面に設定し、後述するHTMLを開くようにした。
WebViewClientの説明は割愛。
検証を2種類行うため、ObjectのインスタンスとActivityのインスタンスをaddJavascriptInterfaceに設定。
Activityを公開することの危険性を示すために、「android.permission.READ_PHONE_STATE」をパーミッションに指定。
実際のコードは以下の通り。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.kanasansoft.android.JavascriptInterface"
    android:versionCode="1"
    android:versionName="0.0.1-SNAPSHOT"
>
    <uses-sdk android:minSdkVersion="4" />
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".JavascriptInterfaceActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
</manifest>
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
>
    <WebView
        android:id="@+id/webview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
    />
</LinearLayout>
strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">JavascriptInterface</string>
</resources>
JavascriptInterfaceActivity.java
package com.kanasansoft.android.JavascriptInterface;

import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;

public class JavascriptInterfaceActivity extends Activity {

    //HTMLファイルのURL
    private String url = "http://[address]/[path]/index.html";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        WebView webview = (WebView)findViewById(R.id.webview);
        webview.setWebViewClient(new WebViewClient(){});
        webview.getSettings().setJavaScriptEnabled(true);
        webview.loadUrl(url);
        MyObject myObject = new MyObject();
        webview.addJavascriptInterface(myObject, "object");
        webview.addJavascriptInterface(this, "activity");
    }

    static class MyObject {
    }

}
準備したHTMLの格子は以下の通り。
index.html
<html>
<head>
<style>
#logs{
width:100%;
height:90%;
font-size:75%;
</style>
<script>
function log(msg){
    var logs = document.getElementById("logs");
    logs.value+=">" + msg + "\n";
}

function initialize(){
    var reload = document.getElementById("reload");
    reload.addEventListener("click", function(){location.reload();}, false);
    getInfoFromObject();
    getInfoFromActivity();
}

function getInfoFromObject(){
    //検証コード
    //詳細は後述
}

function getInfoFromActivity(){
    //検証コード
    //詳細は後述
}

window.addEventListener("load", initialize, false);
</script>
</head>
<body>
<div>
<input type="button" value="reload" id="reload" />
</div>
<div>
<textarea id="logs"></textarea>
</div>
</body>
</html>
開いたら検証用のメソッドを2つ実行するだけ。
オブジェクトを公開することによる危険性
自分で書いたMyObjectを公開することの危険性を検証。
アプリケーションのコードをちょっと見ただけでは、MyObjectだけを公開しているように見える。
ところが、公開されているのはMyObjectのメソッドだけでなく、継承元のクラスのメソッドも含むので、リフレクションが使える。
ここではStringBufferを使ってみた。
function getInfoFromObject(){
    log("== Object ==");
    log("-- get object --");
    log(object);
    log(object.getClass().getName());
    log("-- get ClassLoader --");
    var classLoader = object.getClass().getClassLoader();
    log(classLoader);
    log("-- make StringBuffer --");
    var clazz = classLoader.loadClass("java.lang.StringBuffer");
    log(clazz);
    var sb = clazz.newInstance();
    sb.append(0x48); // "H"
    sb.append(0x65); // "e"
    sb.append(0x6c); // "l"
    sb.append(0x6c); // "l"
    sb.append(0x6f); // "o"
    log(sb.toString());
}
実行結果
>== Object ==
>-- get object --
>com.kanasansoft.android.JavascriptInterface.JavascriptInterfaceActivity$MyObject@2f8fdc00
>com.kanasansoft.android.JavascriptInterface.JavascriptInterfaceActivity$MyObject
>-- get ClassLoader --
>dalvik.system.PathClassLoader@2f8fd5d8
>-- make StringBuffer --
>class java.lang.StringBuffer
>Hello
今回はStringBufferを使ったけど、もちろん他のクラスも使える。
リフレクションを駆使すれば、かなり多くのことができるようになる。
コンテキストへの参照を取得できるオブジェクトを公開することによる危険性
Androidのコンテキストを取得できるようなオブジェクトを公開すると更に色んな情報が取得できるようになる。
ここでは、「android.permission.READ_PHONE_STATE」のパーミッションを持つアプリケーションを想定して、電話番号を取得してみた。
公開しているオブジェクトは、前述の通りActivity。
function getInfoFromActivity(){
    log("== Activity ==");
    log("-- get Activity --");
    log(activity);
    var context = activity.getApplicationContext();
    log(context);
    log("-- get TelephonyManager --");
    var classLoader = activity.getClass().getClassLoader();
    var clazz = classLoader.loadClass("android.telephony.TelephonyManager");
    log(clazz);
    var preCast = activity.getSystemService("phone");
    log(preCast);
    var telephonyManager = clazz.cast(preCast);
    log(telephonyManager);
    log("-- get Phone Number --");
    var phoneNumber = telephonyManager.getLine1Number();
    log(phoneNumber);
    log("-- no need cast --");
    log(preCast.getLine1Number());
}
実行結果
>== Activity ==
>-- get Activity --
>com.kanasansoft.android.JavascriptInterface.JavascriptInterfaceActivity@2f9032b0
>android.app.Application@2f8fdf70
>-- get TelephonyManager --
>class android.telephony.TelephonyManager
>android.telephony.TelephonyManager@2f9997a0
>android.telephony.TelephonyManager@2f9997a0
>-- get Phone Number --
>080xxxxxxxx
>-- no need cast --
>080xxxxxxxx
電話番号は(さすがに)ふせてる。
パーミッションがなくても色々できるし、許可されているパーミッションが増えればできることが更に増える。
ここでは電話番号を取得してみたけど、値の参照だけでなく色んな機能の実行も可能。
まとめ
WebViewでaddJavascriptInterfaceを使う場合は、開くHTMLは自分(達)で制御できる範囲内に留めるようにするべき。
Activityなどのアプリケーションのコンテキストを取得できるようなオブジェクトを公開するのは論外。
制御できないHTMLを開く場合は、removeJavascriptInterfaceを使うか、別のWebViewを使うしかないのかも。
Android SDKが、「インターフェイスを公開するURLを指定しないと動作しない」「公開するオブジェクトのメソッドのみ公開し、親クラスは呼び出せない」「公開に指定するのは、オブジェクトではなくメソッド」のようなAPIを持っていれば良いんだろうけど、現状はそうはなっていない。
addJavascriptInterfaceを使う場合は、上記のリスクをしっかり理解した上で使うようにするしかないのかも。

2012年04月29日

EclipseでAndroidプロジェクトをMavenで管理するための環境構築 + Emacsキーバインドの設定 (2012年版)

Eclipseを再インストールしようとしたら、去年書いた環境構築のEntryから、状況が色々変わっていたので書き直し。
環境はMac OS X Lion(10.7)。
手順が多いけど、できあがる開発環境は非常に魅力的なので是非試してもらいたいと思う。名前が似ているものが多いので注意。

Mavenは日本語の情報が少ないが、「Apache Maven 2.0入門」という良本がある。バージョンが古いけどお勧め。新品が売っていないので絶版しているのかもしれない。Maven3向けに書き換えたものを出し欲しい。
Eclipse
Eclipseは「Eclipse IDE for Java EE Developers」の3.7.2(Indigo)、Macなので「Mac OS X 64 Bit」を選択。
Eclipseの日本語化
Eclipseを日本語化したものもあるが、何か起きたときに原因追及がしにくくなるので後で簡単に英語版に戻せるように日本語化パックを使う。日本語化パックはblanco Frameworkの「Eclipse 日本語化言語パック (サードパーティ版)」、「3.7.0 Stream Build」の「Eclipse IDE for Java EE Developers」用を使用。
Eclipseディレクトリ直下の「dropins」に「nlpack」ディレクトリを作り、ダウンロードしたものを解凍。解凍すると「eclipse/dropins/nlpack/eclipse/features」と「eclipse/dropins/nlpack/eclipse/plugins」以下に日本語化パックが入る。プラグインを「eclipse/features」と「eclipse/plugins」に入れるようにと書いてある文書を見かけるが、何かあった時の問題の切り分けに苦労するので、「dropins」を活用する方法がお勧め。
Android SDK
以下のURLからr18をダウンロードして「~/Developer/」直下に解凍。
配置がHome配下なのは、パーミッションの問題に悩む必要がないため。入れたSDKのバージョンがわからなくなるのも面倒なのでディレクトリ名は「android-sdk-macosx」のまま。解凍後のディレクトリ名は、前は「android-sdk-mac_x86」だったんだけど、名前が変わっている。
Eclipseのプラグイン
以前は「新規ソフトウェアのインストール」しかなかったんだけど、「Eclipse マーケットプレイス」(以下、マーケットプレイス)が追加されている。

マーケットプレイスは、「ヘルプ」=>「Eclipse マーケットプレイス...」を選択して開くダイアログで、インストールしたいプラグインを検索してインストールできる。

「新規ソフトウェアのインストール」は少しややこしい。「ヘルプ」=>「新規ソフトウェアのインストール...」を選択すると、「インストール」ダイアログが開くので「追加」をクリックし、任意の「名前」とダウンロード用の「ロケーション」のURLを入力し「OK」をクリック。「ロケーション」と自分がつけている「名前」は、各プラグイン別に後述。「名前」は自分がわかりやすいものに変更すると後で混乱しない。

「インストール」ダイアログの「作業対象」から追加したロケーションを選択すると、リストボックスにダウンロードできるプラグインの一覧が表示されるので、インストールしたいプラグインをチェックボックスで選択し「次へ」をクリック。後は指示に従ってインストール。途中、ライセンスの使用条件に同意する画面が出てくるので、そこだけ気をつける。
ADT Plugin for Eclipse
AndroidアプリケーションをEclipseで開発するためのプラグイン。ADTはAndroid Development Toolsの略。
「新規ソフトウェアのインストール」からインストールできる。「名前」と「ロケーション」は以下の通り。

名前 : Android Development Tools Plugin(ADT Plugin)
ロケーション : https://dl-ssl.google.com/android/eclipse/

インストール後、「Eclipse」=>「環境設定...」=>「Android」=>「SDK Location」にAndroid SDKディレクトリのパスを入力する必要がある。「~/Developer/android-sdk-macosx」と入力できれば良かったんだけど、絶対パスでないとだめらしい。ここでは、「/Users/[ログインユーザ名]/Developer/android-sdk-macosx」とした。

パスを設定後、「ウィンドウ」=>「Android SDK Manager」でダイアログを開き、SDKや「Platform-tools」のインストールができるようになる。また、「ウィンドウ」=>「AVD Manager」では、Android端末のエミュレータを作成することができる。
Maven Integration for Eclipse
MavenをEclipseから使うためのプラグイン。旧名は「M2Eclipse」。Mavenそのものが組み込まれているので、別途Mavenをインストールする必要がない。しかし、後述する理由で、現状は別のバージョンのMavenが必要になる。

マーケットプレイスからインストールできる。「M2Eclipse」ではMavenのバージョンは2系だったけど、「Maven Integration for Eclipse」では3.0.2になっている。

後述の「m2e-android」を使うには「Maven Integration for Eclipse」の3.0.3以上が必要なため、現状では以下の設定変更が必要。
Mavenの3.0.3以上がインストールされていない場合は、Mavenのインストールが必要になるので、後述の「Maven」の項目を参照のこと。
「Eclipse」=>「環境設定...」=>「Maven」=>「Installations」を開き、「Select the installation used to launch Maven:」の「Add」をクリック。ダイアログが開くので、Macの場合は既にインストールされている「/usr/share/java/maven-3.0.3」を選択する。
一覧の「External /usr/share/java/maven-3.0.3(3.0.3)」にチェックが入っているのを確認して「OK」をクリック。
m2e-android
Mavenに対応したAndroidのプロジェクトを作成するためのプラグイン。このプラグインをインストールすると、Mavenプロジェクトを作成するときに、「android-quickstart」というArchetypeが選択できるようになる。
正式名称は「Android Configurator for M2E Maven Integration for Eclipse」かも。これも名前が変更されていて、旧名は「Maven Integration for Android Development Tools」。ややこしい。公式サイトのURLも変わっている。
インストールは、Mavenプロジェクトを作成する画面で行える。スマートなやり方が他にありそうだけど見つけきれなかった。「ファイル」=>「新規」=>「その他...」=>「Maven」=>「Maven Project」を選択し「次へ >」をクリック、もう一度「次へ >」をクリックし、「Add Archetype...」をクリックする。ダイアログが開くので、次のように入力し「OK」をクリック。

Archetype Group Id: de.akquinet.android.archetypes
Archetype Artifact Id: android-quickstart
Archetype Version: 1.0.8
Repository URL:

これでArchetypeに「android-quickstart」が選択できるようになる。
「android-quickstart」をもとにプロジェクトを作成すると、「android-maven-plugin」の設定がされたpom.xmlが作られる。「android-maven-plugin」は、以前は「maven-android-plugin」という名前だったので何かを調べるときには注意。
「android-maven-plugin」は、前述の通りMaven3.0.3以上が必要となっている。

「android-quickstart」が出力したpom.xmlでコンパイルをしようとすると、「No Android SDK path could be found. You may configure it in the plugin configuration section in the pom file using <sdk><path>...</path></sdk> or <properties><android.sdk.path>...</android.sdk.path></properties> or on command-line using -Dandroid.sdk.path=... or by setting environment variable ANDROID_HOME」というエラーになる。これは、公式サイトにもある通り、Android SDKのパスが設定されていないため。pom.xmlの「project>build>plugins」配下の「android-maven-plugin」の「configuration>sdk」に、「<path>${user.home}/Developer/android-sdk-macosx</path>」を追加する。

また、このまま開発を行っていると「Duplicate files at the same path inside the APK」とエラーが出て正常なAPKファイルが作成できなくなることがある。原因はコンパイルを実行するときにtarget配下に追加するjarファイルが重複するため。この問題を解消するには、「android-maven-plugin」の「configuration」直下に「<extractDuplicates>true</extractDuplicates>」を追加する必要がある。

既存のAndroidプロジェクトをMavenに対応させる場合にも、「android-quickstart」で作成されるpom.xmlを参考にするとよい。
Android Connector
ここまでの状態で「android-quickstart」を使って新しいMavenプロジェクトを作成しても、「Plugin execution not covered by lifecycle configuration」というエラーが発生してしまう。MavenにAndroid関連の設定を認識させる必要があるっぽい。「m2e-android」の公式サイトに書かれている通り、「Android Connector」をインストールする必要がある。「Eclipse」=>「環境設定...」=>「Maven」=>「Discovery」を開き、「Open Catalog」をクリック。ダイアログが開くので、「Android Connector」を選択し「完了」をクリックする。後は画面の指示通り。
Maven
前述の「m2e-android」で使用している「android-maven-plugin」は、バージョン3.0.3以上のMavenが必要になる。しかし、「Maven Integration for Eclipse」(これも前述)に組み込まれているMavenのバージョンは3.0.2のため、別のMavenが用意しないといけない。
古いMavenしか認識されていない場合には、コンパイル時に「requires Maven version [3.0.3,)」というエラーが出力される。
「m2e-android」がバージョンアップすればこの手順は不要になるはず。

幸い、現行のMacでは3.0.3が入っているけど、ターミナルで「mvn -v」と実行して表示されるバージョンが「3.0.3」よりも古い場合は、やはりインストールが必要。
Lionを前提としているので詳細は書かないけど、例えば、Homebrewでインストールするには次のようにする。
% brew install maven
==> Downloading http://www.apache.org/dyn/closer.cgi/maven/binaries/apache-maven-3.0.4-bin.tar.gz
==> Best Mirror http://ftp.riken.jp/net/apache/maven/binaries/apache-maven-3.0.4-bin.tar.gz
######################################################################## 100.0%
Warning: Non-executables were installed to "bin".
Installing non-executables to "bin" is bad practice.
The offending files are:
/usr/local/Cellar/maven/3.0.4/bin/m2.conf
==> Summary
/usr/local/Cellar/maven/3.0.4: 40 files, 5.4M, built in 4 seconds
もしHomebrewでMavenをインストールした場合は、「Maven Integration for Eclipse」の設定で「/usr/local/Cellar/maven/[バージョン番号]/libexec」を選択すること。
Emacs+ Eclipse Plug-in
Eclipseは設定でEmacsに似たキーバインドに変更することができるが、中途半端感が否めない。「Emacs+ Eclipse Plug-in」は、よりEmacsのキーバインドに近づけることができるプラグイン。これも、マーケットプレイスからインストールできるようになっている。インストール後、「Eclipse」=>「環境設定...」=>「一般」=>「キー」の「スキーム」から「Emacs+ Scheme」を選択する。これを入れたら、イライラすることがなくなった。

2012年04月30日

WebRTCでリアルタイム画像処理してみた

WebRTCのVideo Captureで取得した画像を、リアルタイムで処理しても速度が落ちないのか試してみたら想像以上にうまくいった。
これなら、充分使用に耐えられる。
作ったのは、輪郭検出っぽいのやフレーム間データの差分を用いたものと、それを応用して輪郭が燃えるものや、動いた場所が光るもの。
輪郭検出は周囲のピクセルもみないといけないので、処理が重くなるんだけど、それでもリアルタイムで処理できた。

現行のGoogle Chromeなら、Dev版でなくても動作するが、MediaStreamを有効にする必要がある。
MediaStreamを有効にするには、アドレスバーに「about:flags」と入力しエンター、一覧の「MediaStream を有効にする」の「有効にする」をクリックして、Chromeを再起動する必要がある。
まだ試供的なもので、Flashにあるような確認ダイアログも表示されずに突然キャプチャされるので、後で必ず無効にすること。

Operaの仕様が追いついてくれると嬉しいんだけど...。
追記
GitHubにコードを置いた。
Google

タグ クラウド