Webサーバーで動く超シンプルコミックビューワを作ってみた

Webサーバーで動く超シンプルコミックビューワを作ってみた
===============

iPadからブラウザ経由でPCのzip画像を見るために，
[FBVVWB](https://github.com/GuiltyCat/fbvvwb)というCGIを作った．

個人的には満足して使っているが，
セキュリティ的にはとんでもないので，
胸を張っておすすめはできない．

(Google検索にひっかからなくて不便だから，
記事を書いて検索結果に表示させたい．)


注意
--------

このCGIは非常に危険で，利用する場合は，細心の注意が必要．

必ず個人利用のLAN内だけで運用し，
なおかつ厳重なアクセス制限を行うこと．

絶対に公開サーバーに置かないこと．
ほぼ間違いなく，全てのファイルを盗み出され，
ついでに全てのデータが消し飛ばさる．
運が悪ければそれ以上も有り得る．

また，ファイルやコンテンツによっては他者の著作物の違法アップロード認定されるかも．

はじめに
-------------

iPadから自分のPCのファイルにアクセスして，
画像とか動画，zipやrarを覗きたい．
せっかくのiPadだし，活用しないと損でしょ．

なんだけど，
iPadからPCにsambaとかでネットワークマウントしても，
ファイルの扱いがどうも思うようにいかない．
zipを望みのアプリで開けないのだ．
もちろん有料アプリだと，アプリ内から開けるらしいけど，一銭も払うつもりはない．

ということで，同様のことを何とかできないか考えた結果，
PCをWebサーバーにして，CGI経由で簡易コミックビューワ兼ファイルブラウザを作ることにした．
色々機能を追加した結果，検索とか削除もできる．

削除の機能は危険なので入れたくないが，
かき集めた画像を整理する時にどうしても必要だった．

ちなみにVPNとかを使えば，外部からもアクセスもできるが，
画像の圧縮なんかしないので，通信量がやばい．
そもそも，余計なリスクが増えるからやらない方がよいと思う．


環境
---

- ArchLinux
- Apache
- Bash
- lsar/unar, locate, pdfinfo, pdftoppm, ...

一応HTML5に準拠したつもり．(CSSなしの直書きだけど)


今回作ったもの
------------

作ったのは
[FBVVWB](https://github.com/GuiltyCat/fbvvwb)
というやつ．

File Browser and Viewer Via Web Browserの略だ．

できることは，こんな感じ．

- `/home/`と`/mnt/`以下のディレクトリを移動したり，
- 画像を見たり(jpg, png, zip, rar, pdf)，
	- 見開き表示にも対応
- 動画を見たり(.mp4, .avi)
- 音楽を聞いたり(.mp3)
- ファイルを検索したり
- ファイルを削除したり


動かし方
--------------

CGIとして動作させてもよいが，
所詮bashなので，普通に実行すればhtmlを吐く．

ただ，POSTがstdinで渡されることを想定しているので注意が必要．
何も渡さない場合は`CTRL-D`とかで入力を打ち切ってね．

```
bash index.bash
```

入力受け付けでクエリを直打ちしてもいいが，
GETクエリも使える．
こんな風にして渡せばよい．
stdinは打ち切ってね．

```
env QUERY_STRING="cp=/home/<user>" bash index.bash"
```



ポイント
-------------

### 1ファイルのbash scriptだけでできている

ごちゃごちゃさせるのは嫌いだし，
ドキュメントのメンテナンスも面倒ということで，
オールインワンのCGIだ．

ドキュメント(README.md)はbashのコメントから自動生成される．
本当は文芸的プログラミング言語みたいなことをしたかった．

### すべてクエリでパラメータを渡す

ワンファイルだから当然なのだが，
全てクエリを使って機能を切り換えている．

ファイルブラウザモードやファイルビューモードなど，
いくつかの基本画面があるが，
それらは全て`mode`とか，現在のパスを使って切り替えている．

なので，もりもりのクエリが付きリンクを張ってある．

そして，画像の拡大率など保存しておきたい値は，
とりあえず現在のクエリを全てコピペしておく戦略で対処している．

#### クエリの例

長大なので，詳しくはソースを見て欲しいが，少しだけ紹介する．

現在の場所，ディレクトリやフォルダを表すのに以下のようなクエリを使っている．

```
cp=/home/happy/hello.jpg
```

そして，`cp`がディレクトリであればファイルブラウザモードになる．
また，ファイルであればその拡張子に応じて，
それを開くためのページを用意している．

ちょっとした工夫
---------

### ディレクトリ遷移の履歴

単純に実装すると，ファイルを開いた後に戻るリンクを押すと，
常にページの一番上から始まってしまう．
このままだと，ファイル数が多い場合にちょこちょこ中身を除き難くなってしまう．

そこで，ファイルブラウザモードでは全てのリンクに上から順番に連番のidを付け，
一つ上のディレクトリで選択されたファイルのページ内リンクに飛ぶようにしてある．

このクリックした履歴を保存するために以下のようなクエリを用意した．

```
uplink=_0_4_3
```

これはスタックになっている．
ディレクトリの階層が下るにつれて，
`_<num>`がpush(追記)されていく．
逆に上に上るリンクには，最後から`_<num>`がpopされ，
リンクの一番最後にページ内ジャンプである`#<num>`が付け足されている．


### zipやrar，pdfの解凍

これは無駄に拘ってしまった．

unarを使って個別ファイルだけ解凍するんだけど，
日本語のファイル名を渡しても解凍できなかったの．

というわけでインデックスを使ってファイルを指定して解凍する．
だけど，このインデックスが曲者だ．
lsarの出力の順番がインデックスに対応しているんだけど，
これはソートされていなかったりする．

というわけで，grepに行番号を付与して，
行番号を除いてsort -Vすることで自然な並び順で解凍できるようにしていある．
しかも，grepの行番号とlsarのインデックスが微妙にずれている．

また，毎回lsarを走らせるのもあれなので，
画像一覧を保存して使い回せるようにしてある．

### 画像ビューワ時の画面遷移

画像を開く場合と，zipやrarを開く場合の見た目をほとんど同じにしたかった．
といっても，zipだとunarを使った解凍が入るので結構面倒．

そこで，クエリを使ってモードを導入したかったが，
`if`のオンパレードで読み難いことこの上ない．

そこで，少しでもましになるように関数を細かくわけ，
分散させることにした．
大幅な変更があると逆にきついが，見通しは良くなった．


### 検索機能と戻るボタン

ここでの戻るボタンというのはブラウザのものではなく，ページ内に作る戻るボタンのことね．
コミックビューワだと次のページに移動する毎に，新しいページが作られるから，
ブラウザの戻る機能だときつい．

ファイルブラウザモードからファイルを選択して
コミッキュビューワモードに入る．
だから，基本は一つ上のディレクトリに戻る(選択したファイルのページにフォーカスして)
でよい．

でも，検索してからファイルを選択していた場合は，
戻るボタンでは検索結果に戻りたい．
ということで，検索キーワードはクエリに残したままにし，
逆に検索キーワードが残っている場合は
上のディレクトリへボタンを再検索のボタンに切り替えることにした．

(再検索なので効率が悪い．
なので，今後は検索結果の保存してそれを読み込むとかにするかも．)

最初は別のフラグを立てようかとも思ったが，
キーワードクエリが上手く活用でき，
新たなものを追加せずにすんだ．

こういうやりくりやロジックを考えるのは中々面白い．
無理矢理でなく，自然とそうなるべきという解決法あ見つかると快感．


bashで嵌った所
------

今まで気付いてなかったんだけど，
bashって`$()`とか使うとサブプロセスが走って変数にアクセスできても更新はできない．
それだけじゃなくて，通常のパイプとかでも更新できない．

それに嵌った．
例えば次のコードだと最後の出力は$0$だ．

```
COUNTER=0
for i in $(seq 10); do
	sort "${i}.txt"  | while read -r j;do
		COUNTER=$((COUNTER+1))
	done
done
echo "${COUNTER}"
```

これを防ぐには，パイプをなくして次のようにする．

```
COUNTER=0
for i in $(seq 10); do
	while read -r j;do
		COUNTER=$((COUNTER+1))
	done < <(sort "${i}.txt")
done
echo "${COUNTER}"
```

これはまだいいの．
問題はこっち`$()`．
こんなコードで，`QUERY`を更新したいのにー．
ということがあった．

```
QUERY="a"
function Query(){
	QUERY="b"
	echo "http://localhost"
}
echo "<a href=\"$(Query)\">Hello</a>"
echo "${QUERY}"
```

これは少し汚くなるが，直接呼び出すしかないのだろうか．

```
QUERY="a"
function Query(){
	QUERY="b"
	echo -n "http://localhost"
}
echo -n "<a href=\""
Query
echo "\">Hello</a>"
echo "${QUERY}"
```


最後に
------------

意外と簡単にできたので面白かった．
bashの嵌り所も一つ知ったし十分使える．

ただし，とにかくセキュリティが恐い．


`<video>`タグで動画を再生すると，なんかすごくカクカクする時がある．
読み込み待ちで一字停止するというより，
どうもコンマ何秒ほど巻き戻して再生して，
というのが繰り返されているようだ．
三歩進んで一歩さがる! みたいな．

まあ，こんな使い方を想定していないだろうからしょうがないか．


(補足)Apacheの設定
---------------

この辺はあまり詳しくないので，本当に最低限以下だと思う．
なので，きちんと調べた上で設定して欲しい．
というか教えて欲しい．

まず，ApacheのCGIを有効にしておく，設定ファイルはこれだ．

```
/etc/httpd/conf/httpd.conf
```

以下のどっちか使う方をアンコメントする．

```
# LoadModule cgi_module modules/mod_cgi.so
# LoadModule cgid_module modules/mod_cgid.so
```

また今回のスクリプトの拡張子は`.bash`なのでこれを登録しておく．

```
AddHandler cgi-script .cgi .bash
```

また，`suexec`を使い，アクセスできるユーザーを特定しておく．
そして，CGIを置くディレクトリにアクセス制限や実行許可等の設定をしていく．
デフォルトの場所を使うと`Options`に`ExecCGI`を明示的に設定しなくてよいらしい．

```
SuexecUserGroup <user name> <user name>
<Directory "/srv/http/cgi-bin">
	AllowOverride None
	Options None
	AuthType Digest
	AuthName "<authentication name>"
	AuthUserFile "<file path created by htdigest>"
	Require valid-user
</Directory>
```

さすがにBasic認証は恐いので，気休めだがDigest認証を使う．
HTTPSも使いたかったが挫折．

また，今回はユーザーディレクトリをFBVVWBの一時ファイル置き場に使ったり，
画像のリンクに使うので，とりあえずhomeにアクセスできるようにしておく．
場合によっては`/mnt`とか設定してもよいが，基本はシンボリックリンクで辿れるるので，別にいらないと思う．
ただ，検索してアクセスしたい場合は必要．(シンボリックパスでなく，本当の場所へのリンクが貼られるので．)

この辺は画像をコピーして表示すれば解決はできるが，
今度は削除する時に面倒になるのでやっていない．

本当は画像の解凍とかもbashの`<()`みたいなプロセス置換が使えるといいんだけどね．


```
<IfModule userdir_module>
    UserDir disabled
    UserDir enabled <user name>
    UserDir ./
</IfModule>
...
<Directory "/home/*">
    AllowOverride None
    Options FollowSymLinks Indexes
    AuthType Digest
    AuthName "<authentication name"
    AuthUserFile "<file path created by htdigest"
    Require valid-user
</Directory>
```