ひだまりソケットは壊れない

ソフトウェア開発に関する話を書きます。 最近は主に Android アプリ、Windows アプリ (UWP アプリ)、Java 関係です。

まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。

『Web API: The Good Parts』 を読んだ

Web API: The Good Parts

Web API: The Good Parts

同僚から借りて読みました。 全体としては Web API の設計に少しでも携わる人間ならとりあえず読んでおいたらいいんじゃないかなーという感じです。 薄いし。

本書を読んだからと言って最高の Web API の設計ができるようになるとは思わないですが、Web API の設計をする際に知っておくべきことが一通りまとまっていて良い感じだと思いました。

学びメモ

知らなかったことや、なんとなく知ってたけど改めて調べたことなどまとめておきます。

RFC 5861 での Cache-Control ヘッダの拡張

RFC 5861 にて、Cache-Control ヘッダの 2 つの拡張が定義されています。

  • stale-while-revalidate ディレクティブ : プロキシサーバー上で max-age を超えてキャッシュが切れた後も、(裏側で非同期にキャッシュの検証を行いつつ) キャッシュ済みの古いレスポンスデータを返しても良い時間を指定できる。
  • stale-if-error ディレクティブ : オリジンサーバーへのリクエストに失敗する場合に、キャッシュ済みの古いレスポンスデータを返してもよい時間を指定できる。

RFC 5861 の現在の state は “Informational” らしいのでどれぐらいサポートされてるのかわからないですが、「プロキシサーバー上でキャッシュが無効になったあとも、裏でキャッシュ更新しつつユーザーからのリクエストに対してはレスポンスを返してほしい」 って状況はあるので、こういうのが既存のプロキシサーバーなどでサポートされてるとアプリケーション開発時に便利に使えそうです。

本書の 4.3.6 節 「Cache-Control ヘッダ」 に記述されています。

独自のメディアタイプを作る

メディアタイプの登録プロセスの効率と柔軟性を増やすために、サブタイプのいくつかの登録 『木』 (registration “trees”) が RFC 6838 にて定義されています。 それぞれの木には facet *1 が定義されているので、新たに作る独自のメディアタイプの用途に応じて登録木を選択し、それに応じた facet を付けることになります。 (登録木によっては登録作業なども必要。)

  • Standards Tree : IETF 標準に関連付けられるなどした、標準のメディアタイプのための木。 Facet はない。
  • Vendor Tree : 公に使用可能な製品に結びつけられたメディアタイプのための木。 Facet は “vnd.”。 (例 : “application/vnd.ms-excel”)
  • Personal or Vanity Tree : 実験的に作られたり、商業的に流通しない製品の一部だったりするメディアタイプのための木。 Facet は “prs.”。
  • Unregistered x. Tree : 私的に、ローカルな環境でのみ使われるメディアタイプのための木。 Facet は “x.”。

Web API で使用されるメディアタイプを新たに独自に定義する場合は、vendor tree か personal or vanity tree のどちらかになるでしょう

本書の 4.4.3 節 「自分でメディアタイプを定義する場合」 に書かれています。

CORS 周り

XMLHttpRequest *2 には同一生成元ポリシー (Same Origin Policy) が適用されるため、通常は生成元の異なる URL に XMLHttpRequest でリクエストを投げることができません。 異なる生成元へのリクエストが可能になるように考えられた仕様として CORS (Cross-Origin Resource Sharing) があります。

それ自体は知ってたのですが、

  • CORS でプリフライトリクエストが投げられる条件 (POST メソッドでもプリフライトリクエストするとは限らない、みたいな話を聞いたことはあったけど、詳しくは知らなかった)、とか
  • CookieAuthentication などのヘッダで認証情報をやり取りする場合は、サーバー側は Access-Control-Allow-Credentials ヘッダを返す必要があり、クライアント側は XMLHttpRequest#withCredentials というプロパティを true にしないといけない

など知らなかったので、いろいろあるなーと思いました。

ちなみに最近は CORS (や他の仕様) を置き換えるものとして Fetch Standard が作られてるみたいですね。

本書の 4.5 節 「同一生成元ポリシーとクロスオリジンリソース共有」 に書かれています。

セキュリティ周りの話

X-Content-Type-Options ヘッダ

IE (どのバージョンまで? 11 も?) には Content-Type ヘッダの値を無視してレスポンス内容のデータ形式を推定する Content Sniffing という機能があり、JSON として解釈されるべきコンテンツが HTML として解釈されて XSS 脆弱性になる、という問題があります。

IE 8 以降の場合は、サーバーがレスポンスとして次のレスポンスヘッダを返すことで Content Sniffing を無効にできます。 さらに、FirefoxChromeIE 9 以降では、このヘッダを付けることで JSON ハイジャック *3 (後述) の危険性を減らすことができるそうです。 (JSON を返すときは常に付けましょう。)

X-Content-Type-Options: nosniff

詳細は次のページに書かれています。

本書では、6.3.1 節 「XSS」 に書かれています。

JSON ハイジャック

JSON 形式のレスポンスを JavaScript としてブラウザに解釈させることで、別オリジンのサーバーから配信される JSON の内容を悪意ある第三者の web ページが読みだすことができてしまう、という脆弱性。 例えば、悪意ある第三者の web ページが Array オブジェクトのコンストラクタを書き換えたうえで、配列を返す JSON ファイルを script 要素で読み込むと、JSON の中身が書き換えられた Array オブジェクトのコンストラクタに渡されてデータが読まれる、という感じです。 (これは Firefox 2 系で発生していた脆弱性で、最近のブラウザは対策済みらしいです。)

サーバー側の対策として、以下のものがあります。

  • script 要素では読み込めないようにする : script 要素で読み込まれた場合には送られないヘッダフィールド (X-Requested-With ヘッダなど) を必須にする。
  • JSON のレスポンスをブラウザが JS として認識しないようにする : Content-Type: application/json にすることはもちろん、X-Content-Type-Options: nosniff を付ける。
  • JSON のレスポンスを JS として解釈不可能にする (あるいは JS として実行されてもデータが読まれないようにする) : レスポンスの先頭に while (true); を付けるとか。

本書では、6.3;3 節 「JSON ハイジャック」 に書かれています。

セキュリティ周りの各種ヘッダ
  • X-XSS-Protection レスポンスヘッダ : XSS を発生させそうなパターンがレスポンスに含まれている場合に、ブラウザがレスポンスをブロックする機能を有効にしたり無効にしたりできるヘッダフィールドぽい。 この機能は ChromeSafariIE (8 以降) がサポートしてるぽい?
    • ちゃんとしたドキュメントはあんまり見当たらない。
    • XSS ブロックの機能はデフォルトで有効っぽい *4 から、サーバーサイドでは基本的には付けなくてもよさそう?
    • 参考 : IE8 Security Part IV: The XSS Filter | IEBlog
  • Strict-Transport-Security レスポンスヘッダ : ブラウザからのアクセスを HTTPS に限定させる。
  • Public-Key-Pins レスポンスヘッダ : ブラウザに暗号公開鍵とドメイン (?) を結び付けさせる → ブラウザは将来に証明書が偽造された場合に検知できるようになる。

本書の 6.5 節 「セキュリティ関係の HTTP ヘッダ」 に書かれています。

ブラウザがレスポンス内容を UTF-7 と誤認することによる XSS の成立

XSS 対策として 「<」 という文字列をエスケープするなどの方法がありますが、ブラウザにレスポンス内容の文字エンコーディングUTF-7 だと誤認させることで、「<」 のエスケープをしていても 「<script>」 という文字列をそのままブラウザに食わせることができる、という攻撃があるとのことです。

UTF-7 では、「<」 という文字は ASCII の 「+ADw-」 として表現されるためです。 「+Adw-script+AD4-」 という文字列を UTF-7 としてブラウザに解釈させれば 「<script>」 とみなされるので、「<」 という文字のエスケープをしていてもそれをすり抜けて XSS ができることがあるようです。 (古いブラウザの脆弱性。)

最近のブラウザだと問題ないようですが、念のため対策するのであれば 「+」 をエスケープすると良いようです。

本書の 6.3.1 節 「XSS」 に書かれています。

ステータスコード 429 Too Many Request

レートリミットに達した場合に返すためのステータスコードとして 429 Too Many Request が定義されているとのこと。 初めて知りました。

ヘッダフィールドの値として使用できる時刻の形式は決められてる?

本書には以下のように書かれていて、X-Rate-Limit-Reset のようなヘッダフィールドの値として Unix タイムスタンプを使うことは問題がある、といってるんですけど実際どうなんでしょうね。 (本書 195 ページ。)

HTTP ヘッダに Unix タイムスタンプを入れるのは、実は問題があるのです。 というのは、RFC 7231 の HTTP 1.1 仕様によればヘッダに入れてよい時間の形式は以下の 3 種類に限定されているからです

多分根拠は RFC 7231 の以下の箇所だと思うんですけど、これって別にあらゆるヘッダフィールドの値として使われる時刻の形式について言っているのではなくて、HTTP-date について言ってるだけな気もします。 わかんないですけど。

A recipient that parses a timestamp value in an HTTP header field MUST accept all three HTTP-date formats. When a sender generates a header field that contains one or more timestamps defined as HTTP-date, the sender MUST generate those timestamps in the IMF-fixdate format.

RFC 7231 (section 7.1.1.1 Date/Time Formats)

*1:サブタイプの最初のドットの前の部分。 本書では 「接頭辞」 と呼ばれている。

*2:本書には 『XHTTPRequest』 って書かれてる……。

*3:本書では 『JSON インジェクション』 って書かれてたけど、「JSON ハイジャック」 が正しいはず。

*4:IE ではユーザーが設定で無効にできるぽいけど。

VirtualBox 5 上の Debian 8 に Guest Additions をインストールする

まあドキュメントを読めって話ではあります。 次のページにまとまっています。

背景

わりと長く VirtualBox を使ってきて、何度も Guest Additions をインストールしてきましたが、これまで Guest Additions のインストール手順をちゃんと見たことがありませんでした。

これまでは雑にインストールメディアからインストールできてたのですが、今回 VirtualBox 上の仮想マシンDebian 8 をセットアップして Guest Additions をインストールしたらちゃんとインストールできてなかった *1 ので、改めて調べてみました。

インストール方法

最初に言った通りドキュメントに全部書かれてますので、詳細はドキュメントを読んでください。

事前準備

まず、事前準備として、dkms パッケージをインストールします。 (Debian/Ubuntu 系の場合。)

apt-get update
apt-get install dkms

(root ユーザー以外で上記コマンドを実行する場合は sudo してください。)

んでもってシステムを再起動します。

インストール

再起動後、ゲスト OS が起動したら、まずは Guest Additions のインストールメディア (VBoxGuestAdditions.iso) を挿入します。 「デバイス」 メニューの 「Guest Additions CD イメージの挿入」 を選択すると、ゲスト OS にインストールメディアが (仮想的に) 挿入されます。

挿入された CD-ROM は /media/cdrom にマウントされます。 端末を開いて /media/cdrom に移動して、次のコマンドを実行することでインストールが開始されます。

sh ./VBoxLinuxAdditions.run

(root ユーザー以外で実行する場合は sudo しましょう。)

関連エントリ

VirtualBox で共有フォルダを使う

VirtualBox 上の Debian に Guest Additions をインストールする

dkms パッケージをインストールする以外の方法が紹介されてますが、まあ gccmakelinux-headers の正しいバージョンが入ればなんでも良さそうです。

*1:事前準備ができてなかった

UWP アプリ 「みお×ぽん」 をリリースしました

2016 年 1 月 19 日に、Universal Windows Platform (UWP) アプリ 「みお×ぽん」 をリリースしました。 下記ページからダウンロードできます。

www.microsoft.com

どんなアプリ?

株式会社インターネットイニシアティブが提供するインターネット通信サービス 「IIJmio 高速モバイル / D サービス」 の利用状況の確認と、高速通信のためのクーポンの有効・無効の切り替えを行うことができます。 Windows 10 を搭載した PC や、Windows 10 mobile 端末などで利用できます。

IIJ 公式アプリ 「みおぽん」 (Android 用 / iOS 用) の機能を Windows プラットフォームで提供するために開発しました。

内部的には、IIJ の提供する 「みおぽん API」 を使用しています。

既知の問題点

  • Windows 10 mobile で IIJmio アカウントに接続しようとしても、ログイン画面が崩れた状態で表示され、ログインできない問題があります。 (後述のとおり、バージョン 1.0.1 で解決しました。)
    • アカウント情報は Windows 10 プラットフォームと mobile プラットフォームで同期されますので、mobile で使用したい場合は先に PC 側でアカウントに接続することでこの問題を回避できます。
    • 早めに解消できるように頑張ります……!

2016-01-23 追記

  • Windows 10 mobile で IIJmio アカウントに接続できない問題は、2016 年 1 月 23 日にリリースしたバージョン 1.0.1 で解消しました。

Chrome からの共有で onNewIntent が呼ばれない問題 (Android アプリの documentLaunchMode の話)

AndroidAPI level 21 で導入された documentLaunchMode に関する Activity の挙動にバグっぽいところがあって、結構扱いに困るのでまとめておきます。 「documentLaunchMode? 関係ないや」 って思ってる人でも、外部アプリからの Intent を扱うアプリを書くときに影響されるかもしれません。

まとめ

  • Activity の documentLaunchMode として intoExisting が指定されており、既存の Activity が再利用される場合は、launchModestandard であっても Activity の onNewIntent メソッドが呼ばれるべき *1 だが、実際には呼ばれない。
    • バグっぽい。
  • AndroidManifext.xmldocumentLaunchMode の値を指定していなくても、外部アプリが投げる Intent に intoExisting 相当のフラグ (FLAG_ACTIVITY_NEW_DOCUMENT フラグ) が設定されていることがあるので、外部からの Intent を受け取る Activity を書いている場合には否が応でもこの問題に悩まされる。
    • 例えば Chrome アプリの 「共有」 が投げるインテントには FLAG_ACTIVITY_NEW_DOCUMENT フラグが設定されている。
  • とりあえずの対処法は、AndroidManifest.xml で、Activity に launchMode="singleTop" を指定すること。
    • アプリ内部で使用することを考えて singleTop にしづらい場合は、内部で使用する Activity と外部からの Intent を受け取るための Activity を別に定義して (単純にサブクラスを作れば良いだろう)、外部からの Intent を受け取るための Activity の launchModesingleTop にする、などの対応が必要。

関連する発表資料

この記事の内容に関連した話を 2015 年 9 月 30 日の 「関西モバイル研究会 #6」 で発表しました。

背景

この問題に行きついた背景です。

  • 外部アプリからテキスト (Intent.EXTRA_TEXT) を含む Intent を受け取って処理する Activity を含むアプリを開発していた。
    • Chrome アプリの 「共有」 で投げられる Intent も受け取れる。
  • 次のようなユーザー操作を行うと、Activity が新しい Intent を扱えなくて困った。
    1. Chrome でとあるページ (例として 「http://example.com/1」) で 「共有」 し、Activity を起動。 → Activity では Intent から 「http://example.com/1」 というテキストを取りだせる。
    2. Activity を終了せずに、アプリを切り替えて Chrome に戻る。
    3. 別のページ (例として 「http://example.com/2」) で再度 「共有」 し、同じ Activity を起動。 → Activity が破棄されずに残っていた場合、onStart メソッドonResume メソッドが呼ばれるが、onNewIntent メソッドが呼ばれず、getIntent メソッドで取得できる Intent も前に開いたときの Intent になっている。 → 新しい Intent の情報が得られない!

documentLaunchMode の話

documentLaunchMode とはなんぞや、という話。

  • 公式ドキュメントとしては以下のページを読むとわかりやすいです。
  • API level 21 で導入。
  • Activity 起動時の挙動を制御するためのもの。
    • Activity 起動時に毎回新しいタスクを生成するようにしたり、コンポーネント情報と data URI 情報が同じ場合は同じタスクを使うようにしたり、みたいな指定ができる。
  • これを指定することで、「最近のタスク一覧」 に同じアプリケーションの複数ドキュメントを表示できる。
  • AndroidManifest.xml で指定することもできるし、startActivity にフラグとして渡すこともできる。

intoExisting

  • documentLaunchMode の値として intoExisting を指定すると、既存のすべてのタスクの中から Intent のコンポーネント情報と dataURI が同じものが探される。
    • あれば、そのタスクの中身がリセットされて、Activity が再利用されて Activity の onNewIntent メソッドが呼ばれる。
    • なければ、新しいタスクが生成される。
  • と、ドキュメントには書かれているが、onNewIntent メソッドが呼ばれるのはタスクの Activity の launchMode として singleTop などの値が指定されている場合で、standard では呼ばれなかった。 (まじかよ……)
  • DocumentCentricApps というサンプルコードがあるが、サンプルコードのコメントなどはドキュメント通りの挙動を期待しているが、実際の挙動はドキュメントどおりにはならなかった。 (Nexus 5; Android 5.1.1 で確認)

バグっぽい

Chrome アプリの 「共有」 で onNewIntent が呼ばれない問題

原因

Chrome アプリの 「共有」 (メニューの 「共有」 の右側に表示されている、最後に起動したアプリアイコンをタップ) で投げられる Intentを見てみたところ、以下のフラグが設定されていました。

  • FLAG_ACTIVITY_PREVIOUS_IS_TOP
  • FLAG_ACTIVITY_FORWARD_RESULT
  • FLAG_GRANT_READ_URI_PERMISSION
  • FLAG_ACTIVITY_NEW_DOCUMENT (API level 21; documentLaunchMode="intoExisting" 相当のフラグ)

上述のとおり、documentLaunchModeintoExisting で、launchModestandard だと、古い Activity が再利用されるもの onNewIntent メソッドが呼ばれないのです。 つらいですね。

回避策

一つの解決策としては、Activity に launchMode="singleTop" を設定する方法があります。 これだと onNewIntent メソッドが呼ばれるようになります。

しかし、外部からの Intent を受け取るだけじゃなくてアプリ内部からも起動される Activity の場合は launchMode="singleTop" を設定することができないことも多いでしょう。 そういう場合は、アプリ内部からしか起動されない Activity と外部からの Intent を受け取る Activity を別のクラスにしてしまって (片方をもう一方のサブクラスとするなど)、外部からの Intent を受け取る Activity にだけ launchMode="singleTop" を設定するなどの方法を採ることになるでしょうか。 もうちょっといい方法があれば嬉しいですが、それぐらいしか思いつきませんでした。

終わり

この問題、とにかく辛いのですがあんまり困ってる人を見かけないので、もしいい感じの回避策があるのでしたら教えてください!!!!

*1:ドキュメントを読む感じだとそうだと思われる

実機の Android 端末に対して Hierarchy Viewer を使って View の階層構造を調べる

Android アプリを開発する際に便利な Hierarchy Viewer ですが、日本語のブログ記事だと実機の Android 端末に対して Hierarchy Viewer を使う際に ViewServer を使わない方法を紹介してるものがあまり見当たらない *1 ので、ViewServer を使わない方法を紹介しておきます。

Hierarchy Viewer とは

Hierarchy Viewer は、Android アプリの UI のデバッグや最適化を行う際に便利なツールです。レイアウトの View の階層構造を表示する機能 (Web 開発でいうところの DOM インスペクタみたいな感じ) や、ディスプレイの表示内容を拡大して表示する機能 (Pixel Perfect というツール) があります。

Hierarchy Viewer を使うための準備

上記ページに書いてあります。 Android 4.1 以降の端末であれば、次の 2 つのことをすれば良いようです。

  1. Android 端末の開発者向けオプションを有効にする
  2. 開発に使用している PC の環境変数 ANDROID_HVPROTO の値を ddm にする

ロックされている Android 4.1 未満の端末の場合は、ViewServer を使用することになります。 エミュレータのような非ロックの Android 4.1 未満の端末なら何もする必要はなさそうです。 (エミュレータは非ロックです。)

使い方

公式のドキュメントを読むのが一番良いと思うのでリンクをはっておきます。

下記ページにて、Hierarchy Viewer の起動 (Android Studio のメニューから起動する方法) から一通りの使用方法が説明されています。

また、Hierarchy Viewer のより細かな使い方や、Pixel Perfect の使い方などが以下のページで説明されています。

関連ページ

*1:「最近は ViewServer を使わなくてもできるらしいけど方法がわからなかった」 というような記述がみられる

*2:このエントリを書いた後に存在に気づきました。