私が歌川です

@utgwkk が書いている

ラーメンをつまみ食いされる夢

ラーメン屋に来て、おばさん2人組の間の席に案内される。ラーメンを食べていたら両隣から箸をつつかれてつまみ食いされる!! やめてくださいって言っても止めてくれなかったので席を移動したらついて来るし止めてくれない。怒って、ラーメンを床にぶちまけたら2人とも床に箸をやっておいしそうに食べているので、怖くなって店から逃げ出した。

続・2ちゃんねるのトリップを Unicode の時代に実装する

blog.utgw.net

以前こういう記事を書きました。今回はその続きです。

トリップとは

トリップとは、2ちゃんねるなどの匿名掲示板において個人を簡単に証明するための合言葉、またその機能によって表示される文字列のことを指します*1

前回までのあらすじ

Python2までは文字列 str のエンコードは環境依存でUnicode文字列は unicode 型になる、Python3からは str がUnicode文字列になる、ということに困っていました。

どう困るのかというと、 crypt.crypt()str 型を受け付ける、つまりUnicode文字列しか受け付けなくなってしまいます。トリップを生成するのに使う文字列はShift-JISにエンコードされていないと正しい結果が得られません。

バイト列 bytes にしてしまえばShift-JISの文字列 (バイト列) を操作できますが、 crypt.crypt()bytes 型を受け付けません。Perlの場合は内部表現文字列とバイト列のどちらも crypt に渡すことができたので困りませんでした。

そうこうしているうちに、Python2はEOLを迎え、2ちゃんねるは 2ch.sc と 5ch.net に分かれてしまいました。


(2021/1/5 21:45 追記ここから)

id:nonylene さんに passlibpasslib.hash.des_crypt.hash にはバイト列も渡せる、というのを教えてもらいました。やはりC拡張を書く必要はなかった!!!

全ての詳細をすっ飛ばすと、これだけでやりたいことが実現できます。

from passlib.hash import des_crypt

# 中略

tripkey = tripstr[1:]
# treat as Shift-JIS bytes
tripkey = bytes(tripkey, encoding='shift-jis')
salt = (tripkey + b'H.')[1:3]
salt = re.sub(rb'[^\.-z]', b'.', salt)
salt = salt.translate(bytes.maketrans(b':;<=>?@[\\]^_`', b'ABCDEFGabcdef'))
trip = des_crypt.hash(tripkey, salt=salt.decode('shift-jis'))
trip = trip[-10:]

passlibのコードを見ると*2、Python3ではUnicode文字列しか受け付けなくなったのでpure Pythonな実装にフォールバックします、といったコメントが書いてあります。 crypt(3) には char * を渡せばよいなら文字コード関係なくバイト列でいいはず、と思っていたのですが、どうやら間違っていなかったようです。

ということで、以降に書いてあるC言語のコードは必要なくなりました。よかったですね。

(追記ここまで)


解法

Pythonの crypt.crypt() ならびにPerlの crypt は、crypt(3)を実装したものです。 そして、Python3でもバイト列は種々の文字コードでencodeできます。 ということは crypt(3) にバイト列をもとにした文字列 char * を渡せるインタフェースがあればよさそうですね。 軽く検索した感じでは bytes を渡せる crypt(3) のPython実装は見つかりませんでした*3

ということでPythonのC拡張を書きます。C拡張は書いたことがあるので、サクッとできるつもりでした。結局、C拡張内で関数の引数をパースするのがうまくいかなくてCPythonのcryptモジュールの実装をコピペしてきたのですが……。

blog.utgw.net

ともあれ crypt.crypt() の実装の本質はこのあたり*4*5なので、あとは bytes 型を受け付けて char * に変換するように書き換えてやればよさそうです。

バイト列を受け付けるように書き換えた crypt.crypt() の実装 bytecrypt.crypt() *6がこちらになります*7。エラーハンドリングが雑なので、このまま実用するのは危険です。

static PyObject* bytecrypt (PyObject *module, PyObject *const *args, Py_ssize_t nargs) {
    PyObject *return_value = NULL;
    const char *word, *salt, *crypted;
    struct crypt_data data;

    word = PyBytes_AsString(args[0]);
    if (word == NULL) {
        goto exit;
    }
    salt = PyBytes_AsString(args[1]);
    if (salt == NULL) {
        goto exit;
    }

    memset(&data, 0, sizeof(data));
    crypted = crypt_r(word, salt, &data);
    if (crypted == NULL) {
        return_value = PyErr_SetFromErrno(PyExc_OSError);
        goto exit;
    }

    return_value = PyBytes_FromString(crypted);

exit:
    return return_value;
}

あとはこの bytecrypt.crypt() を使ってやれば完成です*8

>>> generate_trip('#istrip')
'◆/WG5qp963c'
>>> generate_trip('#ニコニコ')
'◆pA8Bpf.Qvk'

2バイト文字*9や半角カナを含む入力に対しても正しく出力できていそうですね*10

>>> generate_trip('#t%{rシ)L,')
'◆HAckEr.Tac'
>>> generate_trip('#DRL諞Qq@')
'◆AAAAAAAc.s'
>>> generate_trip('#JM@/!詫8')
'◆GoGoGO/Bos'
>>> generate_trip('#s0ンX[aF-')
'◆1uzee/wmbQ'
>>> generate_trip('#ゥ.N避y承')
'◆4/9.......'
>>> generate_trip('#DRL諞Qq@')
'◆AAAAAAAc.s'

何度かリンクを貼っていましたがリポジトリはこちらです。

github.com

昔作った画像検索・閲覧システムをReactで書き直した

blog.utgw.net

2018年の中ごろまで動かしていて、Twitter Streaming APIの終了と同時に稼動停止した画像検索・閲覧システムだったけど、年末年始やることがなかったのでReactで書き直すことにした。

書き直して再稼動させるというわけでもなく、Reactに再入門するためぐらいのモチベーション。TODOアプリケーションや三目並べを書いてもReact入門はできるかもしれないけど、自分にとって馴染みがあるトピックのほうがとっつきやすそう、という感じ。 <script> タグでVue.jsを読み込む、コンポーネントの分割はなく全てがベタッと書いてある、jquery-lightboxを読み込んでいる、という状態だったのをもうちょっとましにできたらゴール。

とにかく高速に始めたいのでcreate-react-appで作業場所を用意した。 --template typescript 引数を渡すとTypeScriptで書き始められる。とにかく高速に、と言いつつESLintやprettierの設定はひと通り済ませた。

$ npx create-react-app sukui-front --template typescript

クラスコンポーネントを一切書かずにReact hooksだけで書いてみたけど、今のところは変なつまずきもなくてよさそう。画像一覧に関する情報を useImages() っていうcustom hookに集約させる、みたいな書き方ができている。

初回render時にだけAPIを叩くときは useEffect(() => { doAPI(); }, []) ってやればいいのだろうか。ミスったときはすごい勢いでAPIリクエストが行われていて、外部サーバーに向けていなくてよかったね~という感じになる。

ページ送りボタンとか、検索フォームとか、アプリケーションのいろいろなところから画像に関する情報を取得・操作することになる。Reduxとか使えばいいのかもしれないけど、いろいろな概念を一気に習得しようとすると道が長くなるのでとりあえず完成させることを目標にした。

リポジトリはこちら。

github.com