PIYO - Tech & Life -

ブラウザのjavascriptのみを使ってipa展開からplist解析まで

App Javascript ipa plist

ここ数日取り組んでだことについてまとめます。iPhoneアプリのバイナリであるipaをごにょごにょしてplistファイルを取り出し中身を見てやろうっていうことをブラウザ単体でできるかという取り組みです。

関連記事というか、下準備の記事がこちらです。

ipaファイルを指定する

ipaファイルはHTMLのinput要素で指定させます。↓の例ではwebkitdirectory directoryをつけているのでフォルダ指定になっています。

<form action="#">
  <input type="file" id="file-input" webkitdirectory directory />
  <input type="button" id="button-start" value="plist解析" />
</form>

このHTMLによりこういうフォームができます。ipaファイルが入っているフォルダを指定して、plist解析ボタンにより処理をスタートさせる作りにしておきました。

ipaファイルを展開する

ipaファイルはzip形式で圧縮されています。zip.jsというJavascriptがライブラリがあり、これを使えばブラウザ上でファイルを展開して色々ごにょごにょできるようになっています。

デモ(Read a zip file demo)を参考に展開部分を書いてみました。

inputからファイルの情報を得る

var fileInput = document.getElementById("file-input");
var file=fileInput.files[0]; 
// fileはipaファイルの情報を持っている

展開する

model.getEntries(file, function(entries) {
  entries.forEach(function(entry) {
    // entry が展開後の中身
  });
});

modelっていうオブジェクトが急にでてきました。zip.jsのサンプルから持ってきたもので、zip.jsを使う関数を持っているオブジェクトです。単に使うだけならわざわざこんな別のオブジェクトを用意する必要はなく、普通に関数にでもしておけばいいかと思います。

var model = (function() {
  var URL = obj.webkitURL || obj.mozURL || obj.URL;

  return {
    getEntries : function(file, onend) {
      zip.createReader(new zip.BlobReader(file), function(zipReader) {
        zipReader.getEntries(onend);
      }, onerror);
    }
  };
})();

getEntries関数では、zip.jsのzip.createReaderによって渡したファイルを展開してコールバックに要素を渡してくれるという風になっています。

plistを解析する

plistを特定する

ipaファイルをgetEntriesによって展開すると中身のコレクションが得られるので、その中からplistを特定します。ここは単純に文字列比較を用いました。

model.getEntries(file, function(entries) {
  entries.forEach(function(entry) {
    if(!entry.filename.match("app/Info.plist")){
      return;
    }
    // entry は Info.plistファイル
  }
});

plistの中身を得る

これでplist以外のものを除外できました。次にやることはplistを内容を取得することです。ここでもzip.jsのインターフェースを使います。

サンプルコードにおけるzip.js呼び出し用のオブジェクトmodelgetEntryFileという関数がありました。それを少し改変し、与えたファイルの内容をバイナリで得られるようにしました。

得られたバイナリデータはonendコールバックに渡して処理します。

getEntryFile : function(entry, onend, onprogress) {
  var writer = new zip.BlobWriter();
  entry.getData(writer, function(data) {
    onend(data);
  }, onprogress);
}
model.getEntryFile(entry, function(data) {
  // data にplistの中身が入っている
}, null);

plistのバイナリデータを解析する

あとはbplistParserを使ってplistを解析しJSONにするだけです。これは昨日の記事で紹介したライブラリを使うことで実現できます。

binary plist parser for Javascript (non-Node) - ぴよログ

parseFileのコールバックにjsonが渡ってくるのであとはそれを好きなように使えばOK。

model.getEntryFile(entry, function(data) {
  var fileReader = new FileReader();
  fileReader.onload = function() {
    bplist.parseFile(this.result, function(e, json){
      if(e) throw e;
      onComplete(json);
    });
  };
  fileReader.readAsArrayBuffer(data);
}, null);

ブラウザ単体でipaを展開してplistを解析するという話でした。

動くサンプルはこちら。

pi-chan/extract-ipa-parse-plist-example