当日は参加枠でお話を聞きに行くつもりでしたが、いつの間にか資料を作って発表する流れになっていたので、急ごしらえですがMediaStore
がらみのことについて LT をしてきました。
最近MediaStore
の、特にAudio
周りを触っているのですが、以前にはImages
も触ったことがあって、相変わらず面倒くさい構造になっているな……と思っていました。データの取り出し方が分かってしまえばあとは決まり文句を打つだけで良いのですが、如何せんその取り出し方が分からないものも多く、都度調べ物をしていては時間が無限にあっても足りないと感じたので、Facade のようなまとまった API をライブラリとして提供しています、というのがおおまかな概要になります。
MediaStore
そのものは単にデータベースの構成やUri
を外部に公開するためのもので、その実態であるところのContentProvider
は別のところにあります。開発者は通常のContentProvider
へのアクセスと同じ手段でもってMediaStore
からデータを読みだすのですが、いくつかのややこしい話があります。
- 基本的に Javadoc が不足気味である。
- カラム情報を持つクラスに継承構造があり、テーブルごとのカラム情報を持つクラスは共通する親クラスのカラム情報を受け継いでいる。但し共通カラムが必ずしも各テーブルに存在するとは限らない。
- カラムの制約が明記されていないものがある(UNIQUE や NOT NULL など)。
- クエリのための
Uri
が正しくないと、欲しいデータが揃わない(おそらく内部的に結合を行っている)。 - 公開されていない
Uri
を使って得られるデータもある。 - テーブルの構造的問題と
ContentResolver
の API の問題で、SQL インジェクションをする場面がある
最後の SQL インジェクションに関しては、4.x の時代にバンドルされていたギャラリーアプリが実践しています。ギャラリーアプリでは写真をディレクトリ単位で管理していましたが、どの写真がどのディレクトリに属しているかという情報は正規化されておらず、写真データのカラムに結合したままになっています。このため、存在するディレクトリを引いてくるには、写真データの入っているテーブルに対し、GROUP BY を伴ったクエリを発行するひつようがありますが、ContentResolver
にはそのようなメソッドが生えていないため、WHERE
句に相当する部分で SQL インジェクションを余儀なくされる、といった具合です。
ContentResolver#query()
の引数であるselection
がWHERE
句を構成する部分にあたりますが、当然条件を書く上ではプレースホルダを使用します。これによって、selectionArgs
に対しての SQL インジェクションは成功しないのですが、selection
そのものに SQL インジェクションをする文を書くことはできてしまいます。内部的には、selection
をWHERE (%s)
という文字列のテンプレートに流し込んでいるだけなので、例えば1) GROUP BY ...
などとすれば、WHERE
句はつねに真となって SQL インジェクションができます。
まさか公式にそれを逆手に取った実装をしているとは思いませんでしたが、まあ、そういうことです。
ContentProvider での実装如何では SQL インジェクションが容易にできる場合もあれば困難を極める場合もありますが、このあたりは(本題からはそれていますが)気をつけておくべきと言えます。まあ、Webサービスを作る場合と同じ考え方で作っていれば、このような穴が空くようには思えませんが…
それはともかくとしても、バッドノウハウとボイラプレートの山をこれ以上積み上げないためにも、また API を整理するためにも、何かしら Facade のようなものを用意して置く必要性を感じた、ということですね。
一応 GitHub には既に公開していますが、まだ mavenCentral にはリリースしていません。もうすこしやりたいことがあるので、そのめどがついたタイミングでリリースしようと思います。
GitHub - Drivemode/MediaFacade: Facade modules for dealing with complicated MediaStore.