2014年 07月 02日

FuzzyFinder (fuf) で git ls-files を対象に fuf る

nmap gb :call fuf#givenfile#launch('', 0, 'x ', split(system('git ls-files'), '\n'))<CR>

こうすると対象ファイルを git ls-files にして fuf を呼べる

ユーザ由来の構造化データによるSQLインジェクション

Kazuho's Weblog: The JSON SQL Injection Vulnerability について。元記事をはっちゃめっちゃに要約すると

  • SQL::Maker にユーザから受けとったデコード済み JSON をそのまま突っ込むと SQL インジェクションになる場合がある
  • SQL::Maker 側でそういったことが起こらないように strict オプションをつけたから、できればそっち使え
  • 別に SQL::Maker に限らないから気をつけろ

という話っぽい。本来であればユーザ入力をタイプチェックをすべきだけど、クエリビルダレベルでも、脆弱性にならないようにもうちょっと考慮してもいいよねという趣旨かな…

strict モードは非互換なので、既存のコードが動かなくなる可能性があるようです。

Teng での対応

Teng を使っているとデフォルトで SQL::Maker がクエリビルダとして使われるので、同じように危険な場合があります。Teng 0.24 から、クエリビルダのコンストラクタにオプションを渡せるようになったので、以下のように書くと strict モードになります。

my $teng = My::DB->new({
    ...
    sql_builder_args => { strict => 1 }
});

Teng 0.23 以前の場合、このオプションがないので、以下のように自分で sql_builder に SQL::Maker のインスタンスを作って渡す必要があります。

my $teng = My::DB->new({
    ...
    sql_builder => SQL::Maker->new(driver => 'SQLite', strict => 1);
});
既に使っている場合

strict モードつきにしてみて問題なく動くならそのほうがいいですね。非常に単純なクエリは strict モードにしても変わりませんから、

$teng->search('foo', { id => $id })

みたいなクエリしか書いたことねーぞ! って場合、何も考えず strict を有効にして試してみると今後比較的穏かな気分でコードを書けることになります。

そうではない場合でも、あまりに大量にクエリビルダで複雑なことをしているというわけではない限り、 strict を有効にしてコードを書きかえ、検証しなおすべきでしょう。

さらに、どうせコード書きかえるなら根本的に入力のタイプチェックを行うのも考えたほうがいいと思います。


どうしても strict モードを有効にしたくないぐらい複雑にクエリビルダを使いこなしている場合かつ、脆弱性になりそうなコードを書いた覚えがあってヤバいぞという場合、ユーザから受けとる JSON などの構造化データを復号化するところで、一括して再帰的に全ての HashRef, ArrayRef, ScalarRef などを適当に bless することで、ある種の「汚染された」フラグとし、少なくとも SQL インジェクションについては防ぐことができると期待できます。

これは、SQL::Maker が strict オプションが入っていなくても、元から bless された構造化データについては文字列化が走るような挙動になっているためです。

ただ、これはこれでやはり全体的に影響する変更になるので、検証がstrict を入れる場合と同じように大変であり、コードを書きかえる手間の問題ないし、SQL インジェクションのリスクとほかのところで問題がでるリスクを比べた場合に、どうしてもとる1つの手段であって、基本は strict を有効にするように頑張ったほうが生産的かつ安全だと考えられます。

ユーザが構造化データを作れること

ウェブアプリケーション開発者は、普通のHTMLフォームが文字列しか送信できないという、歴史的経緯により無意識に、危険なデータは文字列でしかこないとなんとなく思っている。なのでユーザーが構造化データをつくって送れること自体がうっかり脅威になりえることがある。この手の問題は Perl に限らず Rails でも発生してる。

昨今では JSON をリクエストボディーにしたり、クエリ文字列にルールを与えて (例えば foo[bar]=1&foo[baz]=2 みたいな) 構造化データを受けとれるようにしたりといったことが行われる。これにより、信頼できない構造化データというのが生まれている。

単純なデータ構造でクエリを組み立てるというのは、それ自体が危険なAPI設計といえ、また、入力のタイプチェックをすれば防げる問題なので、それを強制するフレームワークになっているほうが良い、という学びを得られた。

2014年 06月 30日

Perl (PSGI) で zip ファイルを動的に作りつつ順次クライアントに送りつける

滅多にないことだと思うが、非常に大きな zip ファイルを動的に生成してダウンロードさせるみたいなことをしたいことがあるかもしれない。

Archive::Zip だとストリーム生成できないので、Archive::Zip::SimpleZip を使う。Archive::Zip::SimpleZip だとストリーム出力で file handle などに書き出せる。

これで一度ファイルに書いてから、そのファイルを sendfile 的にレスポンスしてもいいのだけれど、書いている間はクライアントからしてみれば完全に無反応になるので、場合によってはタイムアウトになってしまうことがある。そこでストリーム出力をそのままクライアントにストリームしたくなる。

簡単なコードにすると以下のようになった。Archive::Zip::SimpleZip にコールバックかオブジェクトを渡せたらいいが、渡せないようなので、文字列リファレンスをいちいちクリアしながら出力するキモいコードになっている。

#!plackup
use strict;
use warnings;

use Archive::Zip::SimpleZip qw($SimpleZipError :zip_method);

sub {
    my $env = shift;

    # Return streaming response
    sub {
        my $respond = shift;
        my $writer = $respond->([
            200,
            [
                'Content-Type' => 'application/zip',
                'Content-Disposition' => 'attachment; filename="dekai.zip"',
            ],
        ]);

        my $_buf = "";
        my $buf = \$_buf;
        my $zip = Archive::Zip::SimpleZip->new($buf, Stream => 1) or die "Failed to create zip file";

        for my $n (1..1000) {
            warn $n;
            $zip->addString("$n" x 100, Name => sprintf('%04d.txt', $n), Method => ZIP_CM_STORE) or die "SimpleZipError : $SimpleZipError";
            $writer->write($$buf); $$buf = "";
            select(undef, undef, undef, 0.01);
        }

        $zip->close or die "SimpleZipError : $SimpleZipError";

        $writer->write($$buf);
        $writer->close();
    };
}
2014年 06月 28日

ブラウザでモールス練習

WebAudio 使ったモールス練習機を自分で作って使っているので公開する。

ほんとに全く聴きとれないときから使っているので、コッホ法というスタンダードなモールスの覚えかたに従って練習してくように作ってある。

見た目的に想像つくと思いますがスマートフォンでも動きます (Chrome for Android でだけ確認)。


コッホ法とは以下のような特徴のトレーニング方法で、無線電信の巧みと技にも書いてあるが、心理学者のルドウィグ・コッホさんが効率的にモールスを習得するための研究した結果を反映したもので、知られているトレーニング方法の中では唯一根拠があるといえそう。

  • 送信スピードは最低でも15wpm (word per minutes) 以上にする
    • 1文字の「符号」をまとまって認識するため
  • 最初は2文字だけから始まり、90%以上聴きとれるようになったら1文字足していく

最初は文字間の無音を長くして、だんだん狭くしていくといいらしい。文字間の無音は長点と同じ長さしかないので、聴いてみると案外短い。1文字ずつシーケンシャルに聴きとっていると間に合わないので、脳内でバッファリングしながら解読する必要がある。

覚える順番は LCWO.net準拠にしてある。LCWO.net もブラウザでモールス練習できるサイトで便利。グラフ化もしてくれるので、学習成果をテストするのに使ってた。ただ、mp3 をいちいちダウンロードしてくる感じなので、そのへんがちょっと使いにくい。コッホ法一通り終わって (20wpm 90%) からは Code Group のほうで 24wpm ぐらいまでちょくちょくテストしてたけど、最近はランダムじゃなくて意味のある単語の練習をしているので、やってない。

もう1年ぐらい前に作ったやつだけど日記にしていなかった。

2014年 06月 26日

モールスでニュースを聴く

というのを作った。404 Not Found というのがあって、フィードをとってきてモールスにして再生するというもので、面白いのだけれど Java だし Mac だと音がおかしく試すのも辛い感じだった。簡単そうなので似たようなのを作った。

WebAudio + JavaScript のみで実装しているので、殆ど面倒なところはなく、Chrome for Android でも動くので外でも使える。

ただ、フィード取得に Yahoo! Pipes を使っていて、ときどき内容がとれないことがある。よくわからない。

しかし作ってちょっと聴いてみたけどさっぱり聴きとれない。多少は聴けるようになったかな〜と思ったけど全然だった。聴きとり全く上達しない。

2014年 06月 10日

アナログ回路でよさそうな本

よくわからなくて買えてない。なんかセオリー本みたいのないんだろうか。

原理的な知識より、現実的な回路を例にして、どんなときにどんなパターンで設計するか?というのが一通り書いてある本がほしい。定数の決めかた (数式) とか、その定数が結果にどれぐらい影響を及ぼして何とトレードオフにあるかとか。この回路のときは能動素子のこのパラメータに気をつけろとか。

複数の選択肢がある場合、何をどう計って設計を検討するかとか、そういうノウハウって絶対あると思うんだけどなあ。回路同士の選択肢もそうだけど、オペアンプとか死ぬほど種類があるわけで、選ぶのがつらい。

git で直前にいたブランチをマージ

最近まで知らなくていちいちコピペして git merge foobarbranch とかやっていたが、

git checkout foobarbranch
.... なんか作業
git checkout master
git merge @{-1}

みたいに、@{-1} を使うと直前に触っていたブランチを示すことができ、これですぐに merge が実行できることを知った。

単にハイフンでいいらしい……


2014年 04月 25日

ngResource を使って未保存の情報を明確にする

ngResource は $get してオブジェクトを変更して \$save を呼んだらサーバサイドに反映とかいうことができます。

  1. データを $get などで取得した直後の状態、(サーバサイドで) 保存されている状態
  2. 取得したリソースを変更した状態、(サーバサイドで) 未保存の状態
  3. $save などで保存完了した状態、(サーバサイドで) 保存されている状態

とリソースの状態が変化します。

しかし、実際のアプリケーションでは、変更されたが更新されていない状態というのを表示したくなることが多々あります (変更があって未保存なら項目の色を変えるとか)。

なので、保存前の状態をどこかに保存して、保存されたらそれを更新するとかいう作業が必要になります。ただ、コントローラ側の ngResource のコールバックでいちいちこんなことしようとするとバグるので、ngResource の定義側でなんとかしたいところです。

transformResponse を使う

transformResponse は ngResource で何かしたときに呼ばれてくれるので、ここで angular.copy() で resource.saved みたいなプロパティに変更前のデータを全部つっこんでやると大変楽です。だいたい以下のようなコードです (実際はコードをまとめるけど)。

var Entry = $resource('/api/entries', { id : '@id' }, {
    'query':  {
        method:'GET',
        isArray: true,
        transformResponse : function (data, headers) {
            data = angular.fromJson(data);
            data.saved = angular.copy(data);
            return data;
        }
    },
    'save':  {
        method:'POST',
        transformResponse : function (data, headers) {
            data = angular.fromJson(data);
            data.saved = angular.copy(data);
            return data;
        }
    }
});
// controller
$scope.entry = Entry.get({ id : 1 });
// $scope.entry.saved is original data
// template
<div ng-class="{ changed: entry.body != entry.saved.body }">
    <textarea ng-model="entry.body"></textarea>
</div>

このように、取得したリソースを $scope に突っ込み、リソースを対象に ng-model を設定してやると、あとはいい感じになります。save などを呼ぶと変更された内容でリクエストが飛び (unsaved も送信されるのが余計ですが)、サーバ側で適切に保存されたエントリのレスポンスを返せば、transformResponse でリソースの更新がかかるので、全状態が自動でリセットされます。

2014年 04月 22日

WebAudio のデバッグ用のオシロスコープ

WebAudio で ScriptProcessorNode とかを使ってちょっと変なことしようと思うと、波形が実際どうなっているのか見たくなったりするわけですが、うまいこと取得できなくてイライラするわけです。なのでトリガをかけて一定期間の波形を表示するのを作りました。

デバッグ用なので、UI 上には殆ど機能はなく、以下のようにコード上で設定を引数で渡す感じです。トリガはSimple, RaisingEdge, FallingEdge, DualEdge だけとりあえず実装してあります。実装すればチャンネル間の and トリガとかもできる感じの設計です。

WebAudioDebug.prove(context, merger, {
    bufferSize : 64e3,
    windowTime : 100e-3,
    highResolution : false,
    trigger : WebAudioDebug.OscilloscopeNode.Trigger.RaisingEdge({ triggerChannel: 0, width : 10, threshold : 0.5 }),
    continuous : false
});

作ってる途中でやる気が失せたので、特に highResolution: true (Retina 対応) にしたときパフォーマンスがめっちゃ劣化するけどそのままです。あと、繋げる段数が違ったノードを merge して渡すとズレたりするので、ちょっとめんどうくさい感じです。なんとかしたいけど、その前にやる気がなくなったのでそのうちやる気がでたらやるかもしれないです。

2014年 04月 11日

Perl の Locale::Maketext::Lexicon::Gettext フォーマットのメッセージをJSでフォーマットする

Perl の Locale::Maketext::Lexicon::Gettext は以下のような Gettext ライクなフォーマットを扱うが、同じようなことを JS でしたいとき

%1 さん、こんにちは
%quant(%1,user,users)
  • %function() は任意の関数が呼べる感じなので、JS でもそのようにしておく。
  • numf は実装してない。
  • Locale.data= { ... } みたいなのが言語別に完全にわかれているのを想定してる
var Locale = {
    loc : function (msgid, arg) {
        var args = Array.prototype.slice.call(arguments, 0);
        var text = Locale.data[msgid];
        if (text) {
            if (/%/.test(text)) {
                text = text.replace(/%(\d)/g, function (_, n) {
                    return args[n];
                });
                text = text.replace(/%([a-z*#]+)\(([^\)]+)\)/g, function (_, func, args) {
                    if (func == '#') func = 'numf';
                    if (func == '*') func = 'quant';
                    args = args.split(/,/);
                    return Locale[func].apply(null, args);
                });
                return text;
            } else {
                return text;
            }
        } else {
            return msgid;
        }
    },

    quant : function (number, singular, plural, negative) {
        if (number == 0 && negative) { // no warnings
            return negative;
        } else
        if (number == 1) {
            return singular;
        } else
        if (plural) {
            return plural;
        } else {
            return singular + 's';
        }
    },

    numf : function (number) {
        return number; // XXX
    }
};