FirefoxOSのアプリには、通常のWebサイトと同等のホスト型アプリと、アプリのファイルを一つのzipにまとめて配布されるパッケージ型アプリがあります。
注意するべき点として、パッケージ型アプリの場合はオリジンがhttp://<hostname>
などではなく、app://<uuid>
になります。
AngularJSはFirefoxOSアプリの開発にも力を発揮する強力なフレームワークだと思います。
AngularJSを使ったFirefoxOSのパッケージ型アプリで、ngSrcやngHrefを使ってURLを動的にバインディングする際に起きた問題と対策をシェアします。
ngSrcとngHref
img要素のsrcやa要素のhrefで指定するURLまたはその一部をcontrollerで変更したりしたい場合、src/href内に直接{{}}でバインディングするのではなく、ngSrc/ngHrefを使うべきとされています。
理由としては、例えばsrc="{{url}}
と書いた時に、{{url}}
という文字列がURLとして解釈される瞬間が存在するため、ブラウザからリクエストが飛んだり、ユーザーが未構築のリンクを踏む可能性があるのを防ぐということです。
簡単な例を示しましょう。
<!DOCTYPE html>
<html ng-app="fos-test">
<head>
<meta charset="utf-8">
<title>FirefoxOS App with AngularJS</title>
</head>
<body>
<div ng-controller="MainController">
<img ng-src="{{imageUrl}}"/>
<a ng-href="{{linkUrl}}">link</a>
</div>
<script src="js/libs/angular.min.js"></script>
<script src="js/app.js"></script>
</body>
</html>
angular.module('fos-test', [])
.controller('MainController', ['$scope', function($scope) {
$scope.imageUrl = 'img/icons/icon60x60.png';
$scope.linkUrl = 'link.html';
}]);
通常のWebサイトであれば何の問題もなく画像とリンクが表示されるでしょう。
パッケージ型アプリの場合
上の例をFirefoxOSのパッケージ型アプリとして実行してみると、画像が表示されず、リンクのURLもおかしいことがわかります。
Webサイトとして表示した場合には、データバインディングの結果、以下のようなHTMLが構築されます。
<img ng-src="img/icons/icon60x60.png" src="img/icons/icon60x60.png"></img>
<a ng-href="link.html" href="link.html"></a>
これに対して、パッケージ型アプリで構築されるHTMLは以下のようになっていて、src/hrefに構築されたURLがおかしいことがわかります。
<img ng-src="img/icons/icon60x60.png" src="unsafe:app://5a6bea7e-1ae6-4045-965c-6b02ac71aa71/img/icons/icon60x60.png"></img>
<a ng-href="link.html" href="unsafe:app://5a6bea7e-1ae6-4045-965c-6b02ac71aa71/link.html"></a>
パッケージ型アプリの場合、オリジンがapp://<uuid>
になるという話でした。
src/hrefともに、オリジンからはじまるフルパスの前にunsafe:
がついた形になっているのがわかります。
imgSrcSanitizationWhitelistとaHrefSanitizationWhitelist
ということで、次はunsafe:
がどこから来ているのかという話になります。
AngularJSでは、XSSなどの攻撃を防ぐためにsrc/hrefを解決する際には、URLが一定の規則を守っているかチェックする仕組みが入っています。
許可するURLの規則は正規表現で指定されます。
このときの内部処理のおおまかな流れとしては以下のようになります。
1. {{}}の中身を解決してURLの文字列を組み立てる
2. URLをオリジンから始まる絶対URLに変換する
3. 変換された絶対URLが正規表現にマッチした場合はそのURLをDOMに書き込み、マッチしなかった場合はURLの前にunsafe:
をつけてDOMに書き込む
上の例のhrefの場合、データバインディングを行った後のURLがlink.html
で、絶対URLとしてapp://5a6bea7e-1ae6-4045-965c-6b02ac71aa71/link.html
が組み立てられます。
この絶対URLが正規表現にマッチしないため、HTML上ではhref="unsafe:app://5a6bea7e-1ae6-4045-965c-6b02ac71aa71/link.html"
となっていることがわかります。
その正規表現の取得、変更を行うのが、$compileProvider
のimgSrcSanitizationWhitelist
とaHrefSanitizationWhitelist
です。
imgSrcSanitizationWhitelist
のデフォルトは/^\s*((https?|ftp|file):|data:image\/)/
, aHrefSanitizationWhitelist
のデフォルトは/^\s*(https?|ftp|mailto|tel|file):/
のようになっています。
確かにapp://
からはじまるURLはマッチしないようになっています。
解決策
それでは、app://
からはじまるURLを許可するために正規表現を変更しましょう。
module.config
の中で$compileProvider
を依存性注入し、imgSrcSanitizationWhitelist
とaHrefSanitizationWhitelist
の引数にそれぞれ新しい正規表現を指定します。
angular.module('fos-test', [])
.config(function($compileProvider) {
$compileProvider.imgSrcSanitizationWhitelist(/^\s*((https?|ftp|file|app):|data:image\/)/);
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|file|app):/);
})
.controller('MainController', ['$scope', function($scope) {
$scope.imageUrl = 'img/icons/icon60x60.png';
$scope.linkUrl = 'link.html';
}]);
これで無事、パッケージ型アプリでも通常のWebサイトと同様の表示ができるようになりました。