to be continued ~とあるプログラマーの実験的開発日誌~ -11ページ目

to be continued ~とあるプログラマーの実験的開発日誌~

WEBデザイナー兼プログラマーである管理人が日々のトラブル解決に向けて奮闘する開発日誌。

少し前にクッキーが無効の訪問者を排除する方法を紹介したが、

実はあのような面倒な手段を用いたのには理由が2つある。


1つはCGIなどを利用しない通常のページでも実行できるということ。

これにはCGIを利用する場合でもフォーム入力などを行わない

受動的なページの時も含まれる。


もう1つは掲示板などの連続投稿を防止するため、

投稿時間をクッキーに書き込む方法と書き込んだクッキーを読み込む方法を紹介したので、

Perlにおけるクッキーの有効無効の判定のやり方はわざわざ言うまでもないと考えたからだ。

要は、環境変数のHTTP_COOKIEでクッキーが取得できることは紹介した。

その環境変数で受け取った値が空ならばクッキーは無効と判断しても良いだろうということだ。


ところが、これには大きな落とし穴があることに気づいた。

つまり、もともとクッキーが無効だったりした場合、クッキーを有効にしても

その時点では空のため、無効のままと判断してしまう可能性があるのだ。

空の値ではなく、無効の場合は何かのエラー値が変えるような方法はないかと調べてみたが、

どうやらなそさうである(JavaScriptならあるが、今回はCGIだけで考える)。


で、今回の核心だが、いろいろと調べているうちに、あるサイトで発見した方法が、

目から鱗の発想だったので(単に私がボンクラなだけかも知れないが)

それを紹介しようと思う。


簡単に言えば、有効無効の判定用のクッキーを書き込んでしまえということだ。

すなわち、今までのやり方はクッキーをGETして空か否かで判定していたのを

判定用のクッキーをSETするという手順を一つ増やして、

そのSETしたクッキーがGETできるかできないかで判定するという方法である。

これなら確かにもともと空であろうとなかろうと関係がなく、判定ができる。

プロセスはサーバーへの負荷や実行速度を考えてなるべく単純にしようと考えているから、

今回のような手順を増やすという発想はなかなか思いつかないわけだ。

まあ、何と言うか、面倒くさがってはいけないという教訓でもある。


前回の続き。


$contents =<<EOS;
<script type="text/javascript">
<!--
document.write('<a href="リンクアドレス" onclick="window.open(this.href, \'popwindow\', \'width=500, height=500, menubar=no, toolbar=no, scrollbars=yes\'); return false;">リンク先</a>');
// -->
</script>
EOS

エスケープ処理などでスクリプトを上記のように修正した結果、
出力されるページには、

<script type="text/javascript">
<!--
document.write('<a href="リンクアドレス" onclick="window.open(this.href, 'popwindow\', 'width=500, height=500, menubar=no, toolbar=no, scrollbars=yes'); return false;">リンク先</a>');
// -->
</script>

と、きちんと記述されているにも関わらず、上手く動作しないと書いた。
ん・・・? きちんと記述?


そう。結論を言ってしまえば、このきちんと記述されていることこそ問題だった。
つまり、変数$contentsに格納する際のエスケープ処理は行ったが、
出力されたdocument.write内の「'」をエスケープを忘れていたのである。
正しくはページのソースに次のように出力されなければいけない。

<script type="text/javascript">
<!--
document.write('<a href="リンクアドレス" onclick="window.open(this.href, \'popwindow\', \'width=500, height=500, menubar=no, toolbar=no, scrollbars=yes\'); return false;">リンク先</a>');
// -->
</script>

このためには$contentsに格納する際に二重にエスケープしてやる必要があったというわけだ。

$contents =<<EOS;
<script type="text/javascript">
<!--
document.write('<a href="リンクアドレス" onclick="window.open(this.href, \\'popwindow\\' , \\'width=500, height=500, menubar=no, toolbar=no, scrollbars=yes\\'); return false;">リンク先</a>');
// -->
</script>
EOS

これでようやく上手くいった。

ちなみに今回は$contentsへの格納をヒアドキュメントで行ったが、
通常の$contents = "...";で行ったとすれば、さらに複雑なエスケープが必要になる。
自信がある方は試してみてはいかがだろうか(私は御免だが)。
(ブログでは読みやすくするために記述内容に変数を使用していないが
実際のスクリプトではリンクタグに変数を使用しているので、「'」で囲うことはできない点も考慮すべし)

前回、JavaScriptが有効な場合のみポップアップのリンクを表示させる方法として
JavaScriptのdocument.writeでリンクタグを記述するやり方を紹介した。
記事では簡単に書いているが、実はどうしても上手くいかなくて半日ほど悩んだ。
もしかしたら同じようにはまる人がいるかも知れないので、
今回はその顛末について書いてみようと思う。


まず、何をしたかったかというと、CGIで動的なページを出力するのだが、
そこにJavaScriptが有効な場合のみ表示されるポップアップのリンクを表示させるのが目的である。
そこである変数(仮に「$contents」とする)にリンクタグを記述したJavaScriptのタグを格納する。
もう少しわかりやすく説明すると、

<script type="text/javascript">
<!--
document.write("ここにリンクタグが入る");
// -->
</script>

というJavaScriptの記述に

<a href="リンクアドレス" onclick="window.open(this.href, 'popwindow', 'width=500, height=500, menubar=no, toolbar=no, scrollbars=yes'); return false;">リンク先</a>

というリンクタグを記述して、
それを$contentsという変数に格納するのである。
ちなみにできるだけシンプルにするため、余分なエスケープ処理をする必要がない
ヒアドキュメントで行う。


何も考えずに記述すると、こんな感じ。

$contents =<<EOS;
<script type="text/javascript">
<!--
document.write("<a href="リンクアドレス" onclick="window.open(this.href, 'popwindow', 'width=500, height=500, menubar=no, toolbar=no, scrollbars=yes'); return false;">リンク先</a>");
// -->
</script>
EOS


当然ながらまったく動かない。理由は至極単純で、document.writeの表示部分は
「'」(シングルクォーテーション)もしくは「"」(ダブルクォーテーション)で囲まれた範囲であるのに
その内容にも「'」や「"」が含まれているため、範囲が特定できずにいるからだ。
まあ、スクリプトを書いていればこれはよく直面する問題なので、

この時点ではあまり気にしていなかった。

通常、クォーテーション内にクォーテーションがある場合の対処法は二つ。
一つは範囲を指定するクォーテーションと記述内容のクォーテーションをシングルとダブルに分けること。
つまり、範囲を「'」で囲っているなら内容の部分は「"」にしてやれば良い。
ただし、PerlやPHPの場合、「'」と「"」は同じようには使えないので注意が必要だ。
「'」の場合、記述内容はそのまま使われる。

つまり、変数などを使用していれば変数名がそのまま出力される。
「"」であれば置き換えられた値が出力される。
この点にだけ注意すればわかりやすい方法といえるだろう。
しかし、どちらも置き換えたい場合(当然その逆も)や

記述内容に「'」「"」が混在する時などはこの方法が使えない。
そこでもう一つの方法として、エスケープ処理を使う。
これは対象となる文字の前に「\」を付けることで、

本来特殊な意味を持つ記号をただの文字として扱うというものだ。
今回の例で言えば「範囲を指定する」という特殊な意味を持つ「"」を「\"」とすることで、
単なる文字とする(従って、最初と最後に付けるクォーテーションは範囲を指定させるので

エスケープさせてはいけない)。
というわけで、この二つの方法で修正してみる。
まず、リンクタグの「"」をそのまま使用できるようdocument.writeを囲うクォーテーションを「'」に変更。
これでリンクタグ内の「'」はそのまま使用できないので、エスケープ処理をしてやる。


修正した結果は次の通り。

$contents =<<EOS;
<script type="text/javascript">
<!--
document.write('<a href="リンクアドレス" onclick="window.open(this.href, \'popwindow\', \'width=500, height=500, menubar=no, toolbar=no, scrollbars=yes\'); return false;">リンク先</a>');
// -->
</script>
EOS


で、これで思い通りに動作するかというと、動かない・・・。
「'」と「"」を入れ替えたりしてみても結果は変わらず。
当然、これで解決したと思っていたのでここからはまる。
なぜ動かないか原因がわからん。
ちなみに出力したページのソースを見ると、

<script type="text/javascript">
<!--
document.write('<a href="リンクアドレス" onclick="window.open(this.href, 'popwindow\', 'width=500, height=500, menubar=no, toolbar=no, scrollbars=yes'); return false;">リンク先</a>');
// -->
</script>

となっていて問題はないように見える。


では、何が原因か?
勘のいい人ならお気づきだろう。
でも解答は次回に持ち越し。


前回辺りの記事でJavaScriptが無効な相手を排除する方法として
他のページに遷移させるメタ要素を<noscript>タグで囲うというやり方を紹介した。
<noscript>タグはスクリプトが無効な場合のみ有効になるから
スクリプトを有効にしてあれば実行されないというわけだ。


ただ、<noscript>タグではスクリプトが無効な場合は制御できるが、
スクリプトが有効な場合のみに実施したい行動は制御できない。
例えばJavaScriptが有効な場合はJSでポップアップさせるリンクを表示し、
無効な場合は通常のリンクを表示するといった場合だ。
<noscript>タグだけでは、無効な場合にポップアップリンクを隠すことができずに
通常のリンクだけを表示させることはできない。


そこで、スクリプトが有効な場合のみ表示させたいリンクを
JavaScriptのdocument.writeで記述してやる。


前述の例だと、

<noscript>ここにスクリプト無効時のリンク</noscript>
<script type="text/javascript">
<!--
document.write('ここにスクリプト有効時のリンク');
// -->
</script>

といった感じ。


この方法ではJavaScriptを利用して表示させるというやり方をしているが、

発想を転換してこんな方法も考えられる。
スクリプトが有効の時に表示させたいリンクをCSSのdisplay:noneで隠しておいて、
JavaScriptでそれを解除する。

これをソースにすると、


・HTML側
<noscript>ここにスクリプト無効時のリンク</noscript>
<p id="poplink">ここにスクリプト有効時のリンク</p>


・CSS側
#poplink {
display: none;
}


・JavaScript側
<script type="text/javascript">
document.getElementById('poplink').style.display='block';
</script>


ただ、この方法だと個別にIDをつけなければならないため、
複数のリンクを設置したい場合には工夫が必要だ。
個別の記事毎に折りたたんだり展開したりといった動作ならともかく
すべてのリンクを表示するかしないかだけの動作に
IDを振るのは無駄な気がする。
念のため、JavaScriptのターゲットにIDの代わりにCLASSを指定する方法がないか
調べてみたが、どうやら直接指定するのは無理なようで、
for文などを使って全要素を舐めてCLASS名を一致させるのが現実的なようだ。
(その方法ならIDを振り分けるのと大差ないので当然却下)


まあ、どちらの方法がスマートかの判断はお任せする。
ところで、今回の方法を研究していて
ある構文エラーにはまったので
次回はその件について書いてみようと思う。

前回、前々回とクッキーの利用を前提としたページ制作のために
クッキーを無効にしている訪問者の
JavaScriptによる排除方法について書いた。
しかし、この方法にはJavaScriptも無効になっている相手は
排除できないという落とし穴が存在する。


実をいうと、これに対応する方法は非常に簡単で、
<META>要素のRefreshを利用する。
サイトを移転した場合などに元のアドレスでよくお目にかかる
「○秒後に指定のアドレスにジャンプします」というやつだ。


当然だが、そのまま記述したのではJavaScriptの有効無効に関わらず
すべての訪問者が別ページに飛ばされてしまう。
これを防ぐには<noscript>~</noscript>で囲ってやれば良い。
noscriptタグは文字を記述する以外にこんな使い方もできるのだ。


具体的には以下の通り。

<noscript><META HTTP-EQUIV=Refresh CONTENT=0; URL=移動させたいページアドレス"></noscript>


ここまでの一連の流れをまとめると、
まずJavaScriptでクッキーが無効になっている訪問者を排除する
さらにJavaScriptが無効になっている場合を考慮して
メタ要素でJavaScriptを無効にしている訪問者を排除、ということになる。
ただし、ここまでやると非常に排他的なサイトになるので注意が必要だ。