200211

 

JavaScriptにおけるURLエンコードの処理

株式会社クレス

 

このメモは、JavaScriptでクッキーを処理する場合のポイントをお示しし、URLエンコードに関わるトラブルを回避していただくことを目的にしています。お急ぎの方は3章と4章を飛ばして読んで頂いて構いません。なおこのメモはなるべくIEでご覧下さい。

 

目次

1.  1 クッキーとURLエンコーディング

2.  2 URLエンコーディングとは

3.  3 JavaScriptにおけるescape()unescape()関数

4.  4 JavaScriptにおけるencodeURIdecodeURIencodeURIComponentdecodeURIComponent

5.  5 それでは一体どうすればよいか?

6.  6 UTF-8URLエンコード・デコード関数の例:

プログラム例

7.  7 JSPJavaScript間のクッキーによるデータ交換例

 

HTMLテキスト以外にウェブ・サーバがブラウザに情報を渡す手段は、HTTP応答メッセージのヘッダ部分にその情報をセットすることです(ボディ部分には通常HTMLテキストが入ります)。サーバ側からはクッキー設定のヘッダ行や自分で作った特別なヘッダ行に情報をセットできます。これらは(名前、値)のペアの形式をとります。

 

HTMLテキスト外で渡される情報は、ブラウザ画面に表示されないので、表示の目的以外のセッション管理などに使われます。サーブレットにおけるクッキーによるセッション維持のメカニズムは、まさしくこの特性を利用したものです。セッション以外にもクライアントとの各種管理情報(ブラウザのタイプやバージョン、ユーザ情報など)、暗号化のための情報(鍵や方式など)の交信といった応用も考えられます。また送ったクッキーはクライアントに蓄積されるので、繰り返し表示や画面に共通の情報を最初にクッキーで送ってしまうことも可能です。また、フォームに表示しないで必要な情報をサーバに返すことも出来ます。

 

たとえばTomcatなどのサーブレット・エンジンがセッション維持に使っている(“jsessionid”, ID)の類のメカニズム以外に、もっと詳細にそのセッションの情報(ユーザの名前など)をクライアントで維持し、必要に応じこれを表示するなどが可能になります。

 

JavaScriptにおいては、HTTP応答メッセージのヘッダ行を直接取り出す機能は残念ながらありません。しかしながらWindow.document.cookieオブジェクトを使って、クッキーを介した情報のやり取りが可能です。しかしながら、後述のようにHTTPメッセージのヘッダ行はURLエンコードされねばなりません。ASCII文字セットだけで済む欧米と違って我々のように2バイトの文字セットを標準的に使う場合は、URLエンコードに注意しなければなりません。皆さんが悩みまた問題を起こしやすいのはこの点でしょう。

 

このメモは、JavaScriptでクッキーを処理する場合のポイントをお示しし、URLエンコードに関わるトラブルを回避していただくことを目的にしています。

 


 

1.        クッキーとURLエンコーディング

 

クッキーとURLエンコーディングの基本的な知識が必要になりますので、最初にそのポイントを実例でお示しします。

 

通常cookieはアプリケーション・サーバがHTTP応答のパケットのヘッダ部分にセットしてブラウザに渡します。例えばIBMのサーブレット・エンジンは次のようなHTTPのヘッダ行をHTTP応答につけてセッション(サービスとクライアントとの対応の識別)の維持をとろうとしています。この例()ではsessionidという「名前」の変数とLV・・なる「値」の組を渡しています。

Set-Cookie: sessionid=LV140HYAAAABZQ....;Path=/

このようにcookieHTTPパケットのヘッダ行によって伝達されるので、2バイト文字や”;””=”などの危険な(プロトコル上意味を持つ)文字を含む「名前」や「値」を持つcookieをクライアントに渡すときは、危険なバイト文字を含まないようURLエンコードして、バイト列として伝達せねばなりません。

 

マルチバイト文字をサーブレット・エンジンは果たしてこれを認識して自動的にURLエンコードしてくれるのだろうか実験してみましょう。

 

次のサーブレットはネット上(http://ash.jp/java/hellocookie.htm)で公開されていたプログラムに一部手を加えたものです。このプログラムはサーブレットにおけるクッキー処理に関するヒントが入っていますので、ひととおり理解してください。

import java.io.*;

import java.net.*;

import javax.servlet.*;

import javax.servlet.http.*;

/** クッキー読み書きサーブレット **/

public class HelloCookie0 extends HttpServlet {

  public void doGet (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

    PrintWriter out;

    Cookie[] cookies;

    Cookie cookie;

    // Cookieの取得

    cookies = req.getCookies();

    cookie = null;

    if (cookies != null){

      for(int i=0; i < cookies.length; i++) {

        cookie = cookies[i];

        if (cookie.getName().equals("HelloCookie")) { break; }

      }

    }

    res.setContentType("text/html; charset=Shift_JIS");

    out = res.getWriter();

    HttpSession session = req.getSession();  // Sessionの取得と書き込み

    session.setMaxInactiveInterval(600);       // 10分間有効

    if (session.isNew()) {     // Cookieの書込み

      cookie = new Cookie("HelloCookie", "Hello World!");

      cookie.setMaxAge(60);   // 有効期間は1分で切れる

      res.addCookie(cookie);

      out.println("<html><body>");

      out.println("<h1>Write Cookie</h1>");

      out.println("<p>リロードしてください。</p>");

      out.println("</body></html>");

    } else {      // Cookieの表示

      out.println("<html><body>");

      out.println("<h1>");

      for(int i=0; i < cookies.length; i++) {

        cookie = cookies[i];

        out.println(cookie.getName()+" : "+ new String(cookie.getValue().getBytes("8859_1"), "Shift_JIS"));

        out.println("<BR>");

      }

      out.println("</h1>");

      out.println("<p>Cookieのサンプル(HelloCookie0.java)</p>");

      out.println("</body></html>");

    }

  }

}

このプログラムでcookie = new Cookie("HelloCookie", "Hello World!");という行の「値」の文字列に漢字や危険な文字を設定するとサーブレット・エンジンはどのような応答パケットを送信するか試してみましょう。

 

a)       ”Hello 日本!”と変えてtelnetのソフトウエアでアクセスすると次のような結果が得られます。最初の2行(空白行も含む)はtelnetが送ったHTTP要求メッセージです。それ以降がサーブレット・エンジン(ここではTomcat)が返したHTTP応答メッセージです。前半がヘッダ部分、最後の4行がボディ部分です。さてHTTP応答のヘッダ部分を見ると、Set-Cookieなるヘッダ行が2行存在することがお分かりでしょう。最初はサーブレットが作成したもので、後のはサーブレット・エンジンがセッション維持の為に作成したものです。従って、ブラウザにしてみると、2つのクッキーが渡されたということになります。ブラウザ側でこれらのクッキーに何も変更を加えなければ、サブミット・ボタンなどでこのURLを再度アクセスしたときには、これらのクッキーは有効期限が切れていなければそのままサーバに返されます。

GET /examples/servlet/HelloCookie0 HTTP/1.0

 

HTTP/1.1 200 OK

Content-Type: text/html; charset=Shift_JIS

Connection: close

Date: Wed, 30 Oct 2002 03:45:24 GMT

Server: Apache Tomcat/4.0.4-b3 (HTTP/1.1 Connector)

Set-Cookie: HelloCookie=Hello 日本!;Expires=Wed, 30-Oct-2002 03:50:25 GMT

Set-Cookie: JSESSIONID=BAFB93DD6C7848751C369747B316DB6C;Path=/examples

 

<html><body>

<h1>Write Cookie</h1>

<p>リロードしてください。</p>

</body></html>

 

b)       さてこのHTTP応答メッセージをみると、telnetの対応文字セットをShift_JISとしたので、“Hello 日本!”の部分は文字化けを起こさないで正しく読み出されています。また、IE(v6)http://localhost:8080/examples/servlet/HelloCookie0とアクセスし、皆さんのPCC:\WINDOWS\Cookiesを調べると、このクッキーが文字化けしないで受理されているのが確認できるでしょう。でも、実はこれは、たまたまうまくいったというだけなのです。日本という文字はShift_JISとしてヘッダに入っているのですが、このバイト列のどのバイトもASCIIの「危険な文字」に落ちていないからです。更に、この実験ではローカルのホストを使っており、ネットワークを介してはいません。ネットワークのノードによっては(古いシステムですが)7ビットしか伝送されず、最上位の1ビット(MSB)は誤り検出や同期などの目的に使われているものもあります。そのようなノードをこのHTTPメッセージが通過すると、当然文字化けを生じてしまいます。

 

c)        危険な文字を含む文字列がクッキーの「値」の場所にセットされたらどうなるでしょうか?クッキーの「名前」に危険な1バイト文字が含まれているとサーブレット・エンジンは例外を生じるよう規定されているのですが、「値」にはそのような制約がどういう訳だか規定されていません。試しに” AAA ;B%BB”とスペースとセミコロンを含む文字を出力してみよう。telnetでこのサーブレットを呼び出して見ると次のようなHTTP応答パケットを観察することができます。

HTTP/1.1 200 OK

Content-Type: text/html; charset=Shift_JIS

Connection: close

Date: Tue, 22 Oct 2002 04:39:25 GMT

Server: Apache Tomcat/4.0.4-b3 (HTTP/1.1 Connector)

Set-Cookie: HelloCookie=Hello AAA;B%BB;Expires=Tue, 22-Oct-2002 04:44:26 GMT

以下省略

これをブラウザ(IE6)はどのように取り込んだかをExplorerで見るとC:\WINDOWS\Cookiesのディレクトリに記録されているファイルは次のようなテキストになっています。つまりスペースは受け付けたがセミコロン以降はクッキーとしては受け付けてはいないのです。セミコロンはインターネットの世界では「区切り文字」なのです。

HelloCookie

AAA

localhost/examples/servlet/

1024

1088345728

29522332

2383813024

29522331

*

 

次の章で詳しく説明しますが、URLエンコーディングはMSB1のバイトや、インターネット上で特別の意味を持つ7ビットASCII文字を、7ビットASCII文字を使って混乱させないで送るための仕組みなのです。

 

これまでの実験からお分かりのように、クッキーの送信にあたっては名前、値ともURLエンコードして送ることが推奨されます。これはNetScape社の解説書でも推奨されていることであります。なおサーブレット・エンジンがセッション維持の為にcookieをセットするときはURLエンコードしてはいない、というかURLエンコードの必要のない文字しか使っていません(URL変換しても何の変化も生じません)。telnetなどで検査しやすい(バイトのままでも「名前」がそのまま読める)よう、「名前」はURLエンコードに引っかからない英数の文字列にすることがトラブル防止になるでしょう。

 

さて、次のように名前と値双方をJavaURLEncoderでエンコードして作ったクッキーはどのようにブラウザが処理するか調べてみましょう。

      String namestring = "内閣総理大臣";

      String valuestring = "小泉純一郎";

      namestring = URLEncoder.encode(namestring, "Shift_JIS");

      valuestring = URLEncoder.encode(valuestring, "Shift_JIS");

      cookie = new Cookie(namestring, valuestring);

そうすると、c:\windows\cookiesのファイルを調べてみると次のようにブラウザはURLエンコードされた文字列をそのまま受け取っているだけだと言うことがわかります。これを元に戻すのはプログラマの責任という訳です。

%93%E0%8A%74%91%8D%97%9D%91%E5%90%62

%8F%AC%90%F2%8F%83%88%EA%98%59

localhost/examples/servlet/

1024

3287822464

29522316

294622464

29522316

*

 

つまりNetscapeの考え方は、「クッキーは1バイト文字で、且つ安全な文字で構成された文字列が名前と値のペアとして存在していることを前提としている。そうでない文字列をそのような制約にしたがって変換して使うのはプログラマの責任である」というものでしょう。

 

先頭に戻る


 

 

2.        URLエンコーディングとは

 

どのようなバイト列でも7ビットだけのASCII文字を使ってインターネットの網を通過させるための仕組みとしてのURLエンコーディング、あるいはその逆のURLデコーディングについてもう少し詳細に理解することにします。

 

メール(SMTP)HTTPなどのパケットは、ヘッダ部に宛先やその他メッセージの制御に関わる情報が載せられます。このヘッダは途中の(ヘッダが解釈される)ゲートウエーを幾つか中継され、端から端のノードに伝達されるので、これらのノードに理解できるコードと文字で表現されなければなりません。ヘッダ部に日本語のような2バイトの文字が入ると、ノードはこれを1バイトずつ解釈しようとします。そのときにそのバイトのどれかがノードにとって特別の意味を持つバイトであったら、正しい結果が得られなくなってしまいます。更にネットワークによっては各バイトの一番上のビット(MSB)を欠落させる伝送ノードが存在します。従ってどのような文字であってもそのような制約のなかで安全に且つ透過的に伝送されることが必要になります。具体的には7ビットで且つ「安全な」ASCII(American Standard Code for Information Interchange)文字セットからなる文字列に変換してインターネットを通すということです。そのような仕組みとしてURLエンコードが考えられました。URLエンコードというのは、もともとヘッダ部のURL部分に2バイト文字や制御文字と紛らわしい文字が入るのを防止するために考えられたからそう呼ばれています。しかし送られる情報をすべて「見える」文字列に変換するのは都合が良いことが多く、メッセージのボディ部分の伝達にも使われます。ボディ部分の変換にはもうひとつMIME(Multi-Purpose Internet Mail Extensions)のエンコーディングがあります。これは2バイトのバイナリ・データを3バイトの7ビットASCII文字に変換するもので、マルチメディア情報の転送に使われます。

 

URLエンコードの手順は以下のようです。

 

@       日本語のように2バイトの文字は1バイト毎にとりだしてASCII文字とみなして以下の変換を行う。

A       名前と値にある「安全でない」文字は"%xx"というエスケープ文字列に変換する。"xx"はその文字のASCII値を16進表示したものである。「安全でない」文字には=, &, %, +やプリントできない文字,MSB(最上位ビット)が1の文字を含む。

B       全てのASCIIのスペース文字を+に変換する。

C       名前と値を=&でつないでひとつの文字列にする。例えばname1=value1&name2=value2&name3=value3

 

この文字列がPOST要求メッセージのボディ部分、あるいはGET要求のクエリ文字列、あるいはクッキーのヘッダ行としてはめ込まれるのです。

 

先頭に戻る

 


 

 

3.        JavaScriptにおけるescape()unescape()関数

 

いよいよクライアント(ブラウザ)のほうに話を移しましょう。JavaScriptでは当初はescape()unescape()のグローバル関数がそのような目的で使われることがありました。しかしこれらの関数は完全なURLエンコード対応でない上に、その関数の定義が途中で変わってしまい、お勧めできません。

 

そうするとJavaScriptでクッキーを読み出すとき、URLエンコードされたクッキーをどの文字セットだと理解してデコードするのでしょうか?JavaにおいてもURLEncoderURLDecoderの二つのクラスにおいて文字セット(しかもW3Cが勧告しているからといってUTF-8を推奨!)を指定するようになったのが最近のこと(j2sdk1.4から)なのです。

 

JavaScriptの言語仕様(escape/unescape)にはそのような機能が存在しないのが混乱のもとになっているようです。例えばescape()メソッドはNN(Netscape Navigator)Shift_JISのコードをURLエンコードもどきで、IE(Internet Explorer)はユニコード表記(エスケープ・シーケンスであってUTFではない!)を返してしまうのです。逆操作のescape()メソッドもこれに対応します。ところが問題を更に複雑にしているのが、IEescape()関数は通常のUTF-16URLエンコード(例えばj2sdk1.4URLEncoder.encode(str, “UTF-16”);)とは全く異なる単なるユニコード表記の文字列をつくりだす、ということなのです。例えば次のようなhtmファイルをIEで開くと、

<HTML>

<HEAD>

<TITLE>JavaScript escape()/unescape() functionarity test</TITLE>

<META HTTP-EQUIV="content-type" CONTENT="text/html; charset=SHIFT_JIS">

</TITLE>

<BODY>

<PRE>

JavaScriptにおけるescape関数のブラウザによる相違をチェックする

<SCRIPT LANGUAGE="JavaScript">

  s="内閣総理大臣=小泉純一郎";

  document.writeln("original string  : "+s);

  s=escape(s);

  document.writeln("escaped string   : "+s);

  s=unescape(s);

  document.writeln("unescaped string : "+s);

</SCRIPT>

</PRE>

</BODY>

</HTML>

次のような画面が表示されます。

JavaScriptにおけるescape関数のブラウザによる相違をチェックする

original string  : 内閣総理大臣=小泉純一郎

escaped string   : %u5185%u95A3%u7DCF%u7406%u5927%u81E3%3D%u5C0F%u6CC9%u7D14%u4E00%u90CE

unescaped string : 内閣総理大臣=小泉純一郎

つまり%u・・・・なるユニコードの16進表記を返していることがお分かりでしょう。

 

次のように、IEunescape()関数を使って読み出せないかとURLEncoder.encode()メソッドを使ってサーバ側で、cookieをセットしたとしましょう。

import java.io.*;

import java.net.*;

import javax.servlet.*;

import javax.servlet.http.*;

/** クッキー読み書きサーブレット **/

public class HelloCookie extends HttpServlet {

  public void doGet (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

    PrintWriter out;

    Cookie[] cookies;

    Cookie cookie;

    String urlencoding = "UTF-16"; //必要に応じ"Shift_JIS"”UTF-8”などを試す

    // Cookieの取得

    cookies = req.getCookies();

    cookie = null;

    if (cookies != null){

      for(int i=0; i < cookies.length; i++) {

        cookie = cookies[i];

        if (cookie.getName().equals("HelloCookie")) { break; }

      }

    }

    res.setContentType("text/html; charset=Shift_JIS");

    out = res.getWriter();

    if (cookie == null) {      // Cookieの書込み

      String namestring = "内閣総理大臣";

      String valuestring = "小泉純一郎";

      namestring = URLEncoder.encode(namestring, urlencoding);

      valuestring = URLEncoder.encode(valuestring, urlencoding);

      cookie = new Cookie(namestring, valuestring);

      cookie.setMaxAge(300);   // 有効期間は5分で切れる

      res.addCookie(cookie);

      out.println("<html><body>");

      out.println("<h1>Write Cookie</h1>");

      out.println("<P>");

      out.println("<SCRIPT LANGUAGE=\"JavaScript\">");

      out.println("s=unescape(document.cookie);");

      out.println("document.write(\"unescaped cookie : \"+s);");

      out.println("</SCRIPT>");

      out.println("</P>");

      out.println("<p>リロードしてください。</p>");

      out.println("</body></html>");

    } else {      // Cookieの表示

      out.println("<html><body>");

      out.println("<h1>");

      out.println(URLDecoder.decode(cookie.getValue(), urlencoding));

      out.println("</h1>");

      out.println("<p>Cookieのサンプル(HelloCookie.java</p>");

      out.println("</body></html>");

    }

  }

}

このときtelnetでアクセスしてTomcatが送信するHTTP応答パケットのSet-Cookie行を調べると次のようになっています。

Set-Cookie: %FE%FF%51%85%95%A3%7D%CF%74%06%59%27%81%E3=%FE%FF%5C%0F%6C%C9%7D

%14%4E%00%90%CE;Expires=Wed, 23-Oct-2002 04:33:23 GMT

つまり「内閣総理大臣」の文字列は%FE%FF%51%85%95%A3%7D%CF%74%06%59%27%81%E3に、「小泉純一郎」の文字列は%FE%FF%5C%0F%6C%C9%7D%14%4E%00%90%CEに変換されています。FEFFなる文字列はこれがBOM(バイト順マーク)で、ビッグエンディアンであることを意味します。これが正式のUTF-16コード表示なのです。「内閣総理大臣=小泉純一郎」という文字列の場合は次のように変換されます。

%FE%FF%51%85%95%A3%7D%CF%74%06%59%27%81%E3%00%3D%5C%0F%6C%C9%7D%14%4E%00%90%CE

当然のことながらこのサーブレットの出力をIE unescape()関数は正しく受け付けてはくれません。

 

IEが作り出す「内閣総理大臣=小泉純一郎」のエンコーディングはさきほど示したように単なるユニコード表記の

%u5185%u95A3%u7DCF%u7406%u5927%u81E3%3D%u5C0F%u6CC9%u7D14%u4E00%u90CE

であって、これとは全く異なっています。おまけに”=”という文字はユニコード表示されないで単なる%3DというASCII文字として処理されてしまっています。どうして%u003Dとしないのでしょうか。ECMA-262に準拠したもののようですが、困ったものです。したがって「IEの場合は怪しげなescape()unescape()関数は使わないほうが良い」というのが結論です。

 

ちなみに先ほどのサーブレットで String urlencoding = "Shift_JIS";と変更して、これをNNで呼び出すと次のように正常に表示されます。


但しNNではShift_JISURLエンコードを使っていれば問題がないかというと残念ながらそうではありません。例えば「内閣総理大臣 小泉純一郎」と間に半角スペース文字が入っているとNNは「内閣総理大臣+小泉純一郎」とプラス記号に変えてしままいます。これはURLエンコードの解釈の相違によるもので、j2sdk1.4ではスペースは”+”文字に変換するのに、NNescape()関数の場合はスペースを%20に変換します。細かいことですがj2sdk1.4では総て統一して%nnの形式で変換するのにNNescape()関数の場合はバイトに直したときに危険な文字でなければそのまま1バイト文字として変換します。具体的に"内閣総理大臣 小泉純一郎"なる文字列の変換の相違を示すと:

J2sdk1.4URLEncoder.encode()

%93%E0%8A%74%91%8D%97%9D%91%E5%90%62

+%8F%AC%90%F2%8F%83%88%EA%98%59

NNJavaScriptescape()

%93%E0%8At%91%8D%97%9D%91%E5%90b

%20%8F%AC%90%F2%8F%83%88%EA%98Y

となっています。この相違は双方で吸収されるのですが、残念ながらNNunescape()関数はプラス文字をスペースに戻さないので結局互換性がとれないことになります。

 

以上のことからJavaScriptescape()unescape()IENN双方に問題があり、Webアプリのようにサーバ/IENNともに問題なく情報交換させようとするとこれらの関数は使うべきでない、と結論されます。

 

先頭に戻る

 

 

 


 

4.        JavaScriptにおけるencodeURIdecodeURIencodeURIComponentdecodeURIComponent

 

このような問題を解決しようと、新しく4つのグローバル関数が最近定義されています。この章ではこれらの関数を調べてはいますが、最近のバージョンのブラウザにしか実装されていないこと、JavaURLエンコーディングとの完全な互換性がないことから、やはりお勧めはしかねます。

 

最近encodeURIdecodeURIencodeURIComponentdecodeURIComponent4つの関数がECMA-262 3rd Editionに設定されました。実装はJavaScript 1.5, NES 6.0からとなっています。http://developer.netscape.com/docs/manuals/js/core/jsref15/toplev.html#1118346にそれらの関数の定義がなされています。それによればこれらの関数はUTF-8で変換しています。ただしこの表のようにIE5NN4ではこれらの関数が実装されていないので使えません。NN4IE5を使っている人は未だ相当存在しているのが現状であり、これらの関数を使うのには問題があります。

バージョン

代表的なブラウザ

備考

1.0

NN2.0 の途中〜

IE3.0

JavaScript のオリジナルの仕様

1.1

NN3

IE3.02+JScript1.3パッチ

Array objectやイベントなどの追加

1.2

NN4.0 4.05

IE4

Layer/DIV 機能, CSSの追加

1.3

NN4.06

IE5.0

unicodeなど主に ECMA-262対応

1.4

NN5( 開発中止 )

catch/try 等の例外処理など( ECMA-262対応 )

1.5

Mozilla5( NN6 )

IE6

ECMA-262 3rd Edition

2.0

?

ECMA-262 4th Edition

 

いずれにしても先ずこれらの関数を調べてみましょう。次の二つの表はIE6NN7でどのようなURL変換がされるか実験したものです。

encodeURI

元の文字列

IE6の出力

NN7の出力

内閣総理大臣 小泉純一郎

%E5%86%85%E9%96%A3%E7%B7

%8F%E7%90%86%E5%A4%A7%E8%87%A3

%20%E5%B0%8F%E6%B3%89%E7

%B4%94%E4%B8%80%E9%83%8E

%E5%86%85%E9%96%A3%E7%B7

%8F%E7%90%86%E5%A4%A7%E8%87%A3

%20%E5%B0%8F%E6%B3%89%E7

%B4%94%E4%B8%80%E9%83%8E

内閣総理大臣X小泉純一郎

%E5%86%85%E9%96%A3%E7%B7

%8F%E7%90%86%E5%A4%A7%E8%87%A3

X%E5%B0%8F%E6%B3%89%E7

%B4%94%E4%B8%80%E9%83%8E

%E5%86%85%E9%96%A3%E7%B7

%8F%E7%90%86%E5%A4%A7%E8%87%A3

X%E5%B0%8F%E6%B3%89%E7

%B4%94%E4%B8%80%E9%83%8E

 

encodeURIComponent

元の文字列

IE6の出力

NN7の出力

内閣総理大臣 小泉純一郎

%E5%86%85%E9%96%A3%E7%B7

%8F%E7%90%86%E5%A4%A7%E8%87%A3

%20%E5%B0%8F%E6%B3%89%E7

%B4%94%E4%B8%80%E9%83%8E

%E5%86%85%E9%96%A3%E7%B7

%8F%E7%90%86%E5%A4%A7%E8%87%A3

%20%E5%B0%8F%E6%B3%89%E7

%B4%94%E4%B8%80%E9%83%8E

内閣総理大臣X小泉純一郎

%E5%86%85%E9%96%A3%E7%B7

%8F%E7%90%86%E5%A4%A7%E8%87%A3

X%E5%B0%8F%E6%B3%89%E7

%B4%94%E4%B8%80%E9%83%8E

%E5%86%85%E9%96%A3%E7%B7

%8F%E7%90%86%E5%A4%A7%E8%87%A3

X%E5%B0%8F%E6%B3%89%E7

%B4%94%E4%B8%80%E9%83%8E

 

これから判るようにNNIEでは全く同じ結果が得られています。アルゴリズムは簡単で1バイトの文字は危険な文字(この例では” ”)は%XX形式に変換するがそうでなければ(この例では”X”)その文字をそのまま出力するものです。共にスペースは”+”ではなくて”%20”と変換されます。

 

ところで参考までに、URIとしての変換とURIComponentとしての変換の相違は何なんでしょうか?仕様によればURIとして変換する場合は、次の文字は変換しないが、URIComponentの場合は予約文字も変換の対象となるとしています。

カテゴリ

文字

予約文字

, / ? : @ & = + $ ,

エスケープしない文字

アルファベット, 10進数字, - _ . ! ~ * ' ( )

スコア

#

したがってdocument.cookieでとりだした文字列をそのまま戻すときはdecodeURI()を使うことになります。クッキーの「値」や「名前」を処理するときはdecodeURIComponent()などを使うとしています。これはJavaScriptがクッキーを(名前、値)のオブジェクトの集合ではなく、単なる文字列として取り扱かったため生じた問題です。これもまたプログラマに混乱を与える要因になります。いずれはそのように改定されることになるでしょう。

 

ちなみに:

 ” %E5%86%85%E9%96%A3%E7%B7%8F%E7%90%86%E5%A4%A7%E8%87%A3%20%E5%B0%8F%E6%B3%89%E7%B4%94%E4%B8%80%E9%83%8E”

なる文字列は、decodeURI()decodeURIComponent()も同じ内閣総理大臣=小泉純一郎をかえします。

 

しかしながら、互換性の問題があるのです。つまりj2sdk1.4URLencoder.encode(String,”UTF-8”)がエスケープする文字セットと、encodeURIのエスケープする文字セットが異なるのです。更に、j2sdk1.4URLエンコードした文字列がdecodeURI関数では正しく読み取れないことになるのです。

 

encodeURI関数がエスケープしない文字は下表のようでした。

カテゴリ

文字

予約文字

, / ? : @ & = + $ ,

エスケープしない文字

アルファベット, 10進数字, - _ . ! ~ * ' ( )

スコア

#

EncodeURIComponent関数のほうはエスケープしない文字列には予約文字が含まれないのが違うところです。

 

しかしながら、j2sdk1.4URLencoder.encode(String,”UTF-8”)がエスケープしない文字は下表のようになっています。赤字且つ斜め文字の部分がエスケープしない文字、但しスペース(SP)”+”に置き換え且つそれはエスケープしない文字です。

Char   Decimal    Hex             Char     Decimal    Hex

                                                    

NUL       0        0                  SOH        1        1

STX       2        2                  ETX        3        3

EOT       4        4                  ENQ        5        5

ACK       6        6                  BEL        7        7

BS        8        8                   HT        9        9

NL       10        a                   VT       11        b

NP       12        c                   CR       13        d

SO       14        e                   SI       15        f

DLE      16       10                  DC1       17       11

DC2      18       12                  DC3       19       13

DC4      20       14                  NAK       21       15

SYN      22       16                  ETB       23       17

CAN      24       18                   EM       25       19

SUB      26       1a                  ESC       27       1b

FS       28       1c                   GS       29       1d

RS       30       1e                   US       31       1f

SP       32       20                    !       33       21

"        34       22                    #       35       23

$        36       24                    %       37       25

&        38       26                    '       39       27

(        40       28                    )       41       29

*        42       2a                    +       43       2b

,        44       2c                    -       45       2d

.        46       2e                    /       47       2f

0        48       30                    1       49       31

2        50       32                    3       51       33

4        52       34                    5       53       35

6        54       36                    7       55       37

8        56       38                    9       57       39

:        58       3a                    ;       59       3b

<        60       3c                    =       61       3d

>        62       3e                    ?       63       3f

@        64       40                    A       65       41

B        66       42                    C       67       43

D        68       44                    E       69       45

F        70       46                    G       71       47

H        72       48                    I       73       49

J        74       4a                    K       75       4b

L        76       4c                    M       77       4d

N        78       4e                    O       79       4f

P        80       50                    Q       81       51

R        82       52                    S       83       53

T        84       54                    U       85       55

V        86       56                    W       87       57

X        88       58                    Y       89       59

Z        90       5a                    [       91       5b

\        92       5c                    ]       93       5d

^        94       5e                    _       95       5f

`        96       60                    a       97       61

b        98       62                    c       99       63

d       100       64                    e       101      65

f       102       66                    g       103      67

h       104       68                    i       105      69

j       106       6a                    k       107      6b

l       108       6c                    m       109      6d

n       110       6e                    o       111      6f

p       112       70                    q       113      71

r       114       72                    s       115      73

t       116       74                    u       117      75

v       118       76                    w       119      77

x       120       78                    y       121      79

z       122       7a                    {       123      7b

|       124       7c                    }       125      7d

~       126       7e                  DEL       127      7f

 

従って実験サーブレットのプログラムで、

      String namestring = "URLエンコードの実験";

      String valuestring = " !\"#$%&'()*+"+'\u002c'+"-./01289:;<=>?@ABCXYZ[\\]"+'\u005e'+'\u005f'+'\u0060'+"abcxyz{|}"+'\u007e';

      namestring = URLEncoder.encode(namestring, urlencoding);

      valuestring = URLEncoder.encode(valuestring, urlencoding);

として、’\u0020’から’\u007e’までの文字がどのようにURLエンコードされ、且つJavaScriptdecodeURI関数がどのようにデコードするかを試してみると次のような結果が得られます。

undecoded cookie : URL%E3%82%A8%E3%83%B3%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E5%AE%9F%E9%A8%93

=+%21%22%23%24%25%26%27%28%29*%2B%2C-.%2F01289%3A%3B%3C%3D%3E%3F%40ABCXYZ%5B%5C%5D%5E_%60

abcxyz%7B%7C%7D%7E

decoded cookie : URLエンコードの実験=+!"%23%24%%26'()*%2B%2C-.%2F01289%3A%3B<%3D>%3F%40ABCXYZ[\]^_`abcxyz{|}~

つまりdecodeURI関数は予約文字とスコア文字へのデコードはせず、そのままエスケープ表示を返してしまっているのです。実際はencodeURIでエスケープしない文字へのデコード総てが非デコードの対象となります。

 

「これらの文字を使わない」というのもひとつの解決法かも知れないが、あまり良い手段とはいえません。

 

それではdecodeURI関数を使わないでdecodeURIComponent関数を使えばよいのではないかとおっしゃる方もいるでしょう。確かにこちらの関数のほうが問題を起こしにくいのですが、+をスペースに戻してくれない問題は解決されていないのです。

 

また、クッキーを一括エンコードする場合はencodeURIComponent関数を使ってはいけないことも忘れないで下さい。

 

先頭に戻る

 

 

 

 

5.        それでは一体どうすればよいか?

 

いままで散々長ったらしい説明をしていながら、否定的な結果ばかりではないか、とのご不満もおありでしょう。結論としては「Javaと互換性あるURLエンコーディングのJavaScriptの関数を用意する」ということになります。

 

現状では残念ながらencodeURIdecodeURIencodeURIComponentdecodeURIComponentといった関数が使えないブラウザが未だ多く利用されている現状であること、これらの関数がJava 2プラットホームのURLエンコードとの互換性がないことを勘案して、cookieJavaScriptを使ってクライアント/サーバ間で情報交換する場合にはどうしたらよいのでしょうか?結局つぎのような結論となります。

 

結論

 

JavaScriptがサーバがセットしたクッキーを処理するようなアプリケーションにおいては:

1)       クッキーの名前と値ともにURLエンコードすることが推奨されるが、名前は「危険」でない(URLエンコードに影響されない)文字からなる英数文字列に限定すべきですtelnetなどのツールによる検証やデバッグの便を配慮したものです。

2)       NN及びIEescape()unescape()関数は問題があるので使ってはなりません。EncodeURI()decodeURI()encodeURIComponent()decodeURIComponent()も、古いバージョンのブラウザでは対応できていないし、j2sdk1.4との互換性の問題があります。結局J2sdk1.4java.netのパッケージにあるURLEncoder.encode()及びURLDecoder.decode()と同じ機能を持ったJavaScriptの関数を用意するのがベストです。そうすれば自分がどのブラウザ上のどのバージョンで走っているかをJavaScriptは識別する必要もなくなります。

URLエンコードはUTF-8に統一する。これはEncodeURI()decodeURI()encodeURIComponent()decodeURIComponent()UTF-8を使っているからという理由です。またJavaの仕様においてもUTF-8を推奨しています。日本語では極めて効率が悪いのですが、この世界ではUTF-8を標準的に使ったほうが無難でしょう。

 

先頭に戻る

 

 

 

 

 

6.        UTF-8URLエンコード・デコード関数の例:

 

ユニコード(1バイト、2バイト長、4バイト長がある)を外部とバイト列としてやり取りする形式(Unicode Transfer Format: UTF)にはUTF-8UTF-16が良く使われます。今回はUTF-8によるURLエンコードを検討するので、UTF-8へのURL変換法を紹介します。このまま使えとは申しません。このプログラムを参考にしていただければ幸いです。

 

 

UCSからUTF-8への変換法は次のテーブルに示したようになります。

UCSからUTF-8への変換法

UCS-2 (UCS-4)

ビットパターン

第1バイト

第2バイト

第3バイト

第4バイト

U+0000 ..

 U+007F

00000000-0xxxxxxx

0xxxxxxx

 

 

 

U+0080 ..

U+07FF

00000xxx-xxyyyyyy

110xxxxx

10yyyyyy

 

 

U+0800 ..

U+FFFF

xxxxyyyy-yyzzzzzz

1110xxxx

10yyyyyy

10zzzzzz

 

U+10000..

U+1FFFFF

00000000-000wwwxx-

xxxxyyyy-yyzzzzzzz

11110www

10xxxxxx

10yyyyyy

10zzzzzz

この形式の特徴は1バイト文字以外は一番上のビット(MSB)がゼロにならないことで、一番上のビットがゼロの文字は7ビットASCII文字セットそのものだということです。したがって変換のアルゴリズムは極めて簡単です。URL変換に際しては1バイト形式以外の形式では必ず各バイトは%hhとエスケープ形式となります。

 

java.net.URLencoder.encode(String,”UTF-8”)に相当した関数をJavaScriptで実現する際は、次のようなアルゴリズムになります。

もしその文字が'\u0020’なら、それを'\u002b’に置き換える

  そうでなければ、

    もしその文字が'\u002a’、'\u002d’、'\u002e’、'\u0030’・・'\u0039’、 '\u0042’・・

'\u005a’、'\u005f’、'\u0061’・・'\u007a’でなければ

      その文字をUTF-8変換し、各バイトを%XXのエスケープ文字列に変換する

 

java.net.URLdecoder.decode(String,”UTF-8”)に相当した関数のアルゴリズムは;

もしその文字が'+’なら、それを' ’に置き換える

  それ以外の文字で、

もしその文字がエスケープ文字列だったらこれをUTF-8変換だとしてユニコード文字に戻す

    (それ以外の文字はそのまま)

と簡単なものです。

 

以下にその関数と、実験用HTMLを示します。これらの関数は将来の拡張4バイト長ユニコード(UCS-4)にも対応しています。このHTMLファイルをブラウザで開くと次のような結果が得られるでしょう。

JavaScriptによるJ2プラットフォーム互換URLエンコード関数とそのテスト

original string    : 内閣総理大臣 小泉純一郎 !"#$%&'()*+,-./01289:;<=>?@ABCXYZ[\]^_`abcxyz{|}~

URL encoded string : %e5%86%85%e9%96%a3%e7%b7%8f%e7%90%86%e5%a4%a7%e8%87%a3+%e5%b0%8f%e6%b3%89%e7%b4%94%e4%b8%80%e9

%83%8e+%21%22%23%24%25%26%27%28%29*%2b%2c-.%2f01289%3a%3b%3c%3d%3e%3f%40ABCXYZ%5b%5c%5d%5e_%60

abcxyz%7b%7c%7d%7e

URL decoded string : 内閣総理大臣 小泉純一郎 !"#$%&'()*+,-./01289:;<=>?@ABCXYZ[\]^_`abcxyz{|}~

 

実際のアプリケーションにおいては:

ア)                ウェブ・サーバ側は「値」の文字列をjava.net.URLEncoder.encode(String, “UTF-8”)を使ってURLエンコードしてクッキーの「値」にセットする

イ)                ブラウザはWindow.document.cookieで取り出した文字列を

(ア)                      「名前」と「値」に危険な文字が含まれていなければそのまま新しいdecodeURL()にかけるか、

(イ)                      「値」の文字列をdecodeURL()にかけるか

               して正しい文字列に戻すことになります。

ウ)                Window.document.cookieを変更する場合は、新しいencodeURL()関数をつかって逆の操作をします。但しWindow.document.cookieに何か新しい「名前」の文字列を代入するということは、今までのクッキーに新しいクッキーが追加される(「名前」が同じならその「値」が書き換えられます。)ことになることに注意しましょう。

 

参考:

クッキーの中から所定の名前の値を抽出する関数の例です。

  function loadCookie(name) {

        var allcookies = document.cookie;

        if (allcookies == "") return "";

        var start = allcookies.indexOf(name + "=");

        if (start == -1) return "";

        start += name.length + 1;

        var end = allcookies.indexOf(';',start);

        if (end == -1) end = allcookies.length;

        return allcookies.substring(start,end);

  }

クッキーの「値」がURLエンコードされている場合は、

var decodedValue = decodeURL(loadCookie(name));

のようにデコードします。また「名前」や「値」に予約語が含まれていないことがはっきりしている場合は

var allcookies = decodeURL(document.cookie);

という一括処理の使い方も可能です。


プログラム例

 

<HTML>

<HEAD>

<TITLE>j2 platform equivalent URL encode/decode functions</TITLE>

<META HTTP-EQUIV="content-type" CONTENT="text/html; charset=SHIFT_JIS">

</TITLE>

<SCRIPT LANGUAGE="JavaScript">

 

/*  Function Equivalent to java.net.URLEncoder.encode(String, "UTF-8")

    Copyright (C) 2002, Cresc Corp.

    Version: 1.0

*/

function encodeURL(str){

    var s0, i, s, u;

    s0 = "";                // encoded str

    for (i = 0; i < str.length; i++){   // scan the source

        s = str.charAt(i);

        u = str.charCodeAt(i);          // get unicode of the char

        if (s == " "){s0 += "+";}       // SP should be converted to "+"

        else {

            if ( u == 0x2a || u == 0x2d || u == 0x2e || u == 0x5f || ((u >= 0x30) && (u <= 0x39)) || ((u >= 0x41) && (u <= 0x5a)) || ((u >= 0x61) && (u <= 0x7a))){       // check for escape

                s0 = s0 + s;            // don't escape

            }

            else {                  // escape

                if ((u >= 0x0) && (u <= 0x7f)){     // single byte format

                    s = "0"+u.toString(16);

                    s0 += "%"+ s.substr(s.length-2);

                }

                else if (u > 0x1fffff){     // quaternary byte format (extended)

                    s0 += "%" + (oxf0 + ((u & 0x1c0000) >> 18)).toString(16);

                    s0 += "%" + (0x80 + ((u & 0x3f000) >> 12)).toString(16);

                    s0 += "%" + (0x80 + ((u & 0xfc0) >> 6)).toString(16);

                    s0 += "%" + (0x80 + (u & 0x3f)).toString(16);

                }

                else if (u > 0x7ff){        // triple byte format

                    s0 += "%" + (0xe0 + ((u & 0xf000) >> 12)).toString(16);

                    s0 += "%" + (0x80 + ((u & 0xfc0) >> 6)).toString(16);

                    s0 += "%" + (0x80 + (u & 0x3f)).toString(16);

                }

                else {                      // double byte format

                    s0 += "%" + (0xc0 + ((u & 0x7c0) >> 6)).toString(16);

                    s0 += "%" + (0x80 + (u & 0x3f)).toString(16);

                }

            }

        }

    }

    return s0;

}

 

/*  Function Equivalent to java.net.URLDecoder.decode(String, "UTF-8")

    Copyright (C) 2002, Cresc Corp.

    Version: 1.0

*/

function decodeURL(str){

    var s0, i, j, s, ss, u, n, f;

    s0 = "";                // decoded str

    for (i = 0; i < str.length; i++){   // scan the source str

        s = str.charAt(i);

        if (s == "+"){s0 += " ";}       // "+" should be changed to SP

        else {

            if (s != "%"){s0 += s;}     // add an unescaped char

            else{               // escape sequence decoding

                u = 0;          // unicode of the character

                f = 1;          // escape flag, zero means end of this sequence

                while (true) {

                    ss = "";        // local str to parse as int

                        for (j = 0; j < 2; j++ ) {  // get two maximum hex characters for parse

                            sss = str.charAt(++i);

                            if (((sss >= "0") && (sss <= "9")) || ((sss >= "a") && (sss <= "f"))  || ((sss >= "A") && (sss <= "F"))) {

                                ss += sss;      // if hex, add the hex character

                            } else {--i; break;}    // not a hex char., exit the loop

                        }

                    n = parseInt(ss, 16);           // parse the hex str as byte

                    if (n <= 0x7f){u = n; f = 1;}   // single byte format

                    if ((n >= 0xc0) && (n <= 0xdf)){u = n & 0x1f; f = 2;}   // double byte format

                    if ((n >= 0xe0) && (n <= 0xef)){u = n & 0x0f; f = 3;}   // triple byte format

                    if ((n >= 0xf0) && (n <= 0xf7)){u = n & 0x07; f = 4;}   // quaternary byte format (extended)

                    if ((n >= 0x80) && (n <= 0xbf)){u = (u << 6) + (n & 0x3f); --f;}         // not a first, shift and add 6 lower bits

                    if (f <= 1){break;}         // end of the utf byte sequence

                    if (str.charAt(i + 1) == "%"){ i++ ;}                   // test for the next shift byte

                    else {break;}                   // abnormal, format error

                }

            s0 += String.fromCharCode(u);           // add the escaped character

            }

        }

    }

    return s0;

}

</SCRIPT>

</HEAD>

<BODY>

<PRE>

JavaScriptによるJ2プラットフォーム互換URLエンコード関数とそのテスト

<SCRIPT LANGUAGE="JavaScript">

    s = "内閣総理大臣 小泉純一郎" + " !\"#$%&'()*+" + '\u002c'+ "-./01289:;<=>?@ABCXYZ[\\]" + '\u005e' + '\u005f' + '\u0060' + "abcxyz{|}" + '\u007e';

    document.writeln("original string    : "+s);

    s = encodeURL(s);

    document.writeln("URL encoded string : "+s);

    s = decodeURL(s);

    document.writeln("URL decoded string : "+s);

</SCRIPT>

</PRE>

</BODY>

</HTML>

 

 

先頭に戻る

 

 


 

7.        JSPJavaScript間のクッキーによるデータ交換例

 

最後に皆さんが実際にサーバのプログラムを作成される際の参考として、JSPJavaScript間でテキストをクッキーを介してやり取りするサンプルをお示しします。

 

 

このJSPをブラウザがアクセスすると次のような画面が得られるはずです。


 


動作は次のようです:

1.  最初にユーザはテキストエリアに任意のテキストを入力し、「クッキー書込みと送信」のボタンを押すと、そのテキストはURLエンコードされたのち”userdata”という「名前」の「値」としてクッキーにセットされサーバに送られます。

2.  サーバはクライアントから送られてきたクッキーの内容のすべてを先ず「名前」/「値」のセットとしてクライアントへのHTMLテキストに書き込みます。その際「値」のほうはURLデコードしたものを括弧でくくって一緒に書き込みます。

3.  サーバは”userdata”という「名前」の「値」の部分をURLデコードしたユーザからのテキストを再度URLエンコードしてクッキーにセットします。

4.  クライアントのほうは、サーバからのHTMLテキストを表示すると共に、一緒に送られてきたJavaScriptにより受信したクッキーの内容を生で表示し、次に”userdata”の「値」の部分をURLデコードして表示します。

5.  送信したテキストと送り返されてきたテキストが一致すれば、正しく交信が出来たことになります。

 

本当にクッキーがサーバとクライアント双方で書き込んでいるか心配ですか?URLエンコードされたクッキーの「値」を見てください。エスケープされた文字がJavaScriptのほうは小文字の16進表示、JSPのほうは大文字の16進表示になっています。これはJavaScriptNumber.toString(16)メソッドが小文字で出力するのに対し、Java 2プラットホームのURLEncoder.encode(String,”UTF-8”)なるメソッドは大文字で出力するからです。これで双方のURL処理関数が機能し、かつ互換性が取れていることが確認されます。

 

なお、復帰や改行も入力できますので試してください。HTMLではこれらの文字は表示されませんが、正しくエンコード処理されていることがわかります。ブラウザの設定によっては復帰(CR: %0d)と改行(NL: %0a)双方がクッキーに入る場合もありますし、改行のみの場合もありますので改行処理には注意が必要です。

 

以下にJSPページを紹介します。JSPのなかにJavaScriptも入っているので読みづらいところは我慢してください。JSPのプログラム部分を赤で、JavaScriptの部分を青で色分けしてあります。このプログラムを実際に体験したのち、読んで頂くと理解が早いと思います。

<%@ page contentType="text/html; charset=Shift_JIS" session="true"

    import="java.net.*" %>

<%  Cookie[] cookies;

    Cookie cookie;

    cookies = request.getCookies();

    cookie = null;

    if (cookies != null){

        for (int i = 0; i < cookies.length; i++){

            cookie = cookies[i];

            if (cookie.getName().equals("userdata")){break;}

        }

        String encodedUserData = cookie.getValue();

        String decodedUserData = URLDecoder.decode(encodedUserData, "UTF-8");

        cookie = new Cookie("userdata", URLEncoder.encode(decodedUserData, "UTF-8"));

        cookie.setMaxAge(300);  // give 5 minute to survive for the cookie

        response.addCookie(cookie);

    }

%>

 

<HTML>

<HEAD>

<TITLE>JavaScript j2 platform equivalent URL encode/decode functions and their test</TITLE>

<META HTTP-EQUIV="content-type" CONTENT="text/html; charset=SHIFT_JIS">

</TITLE>

<SCRIPT LANGUAGE="JavaScript">

 

/*  Function Equivalent to URLEncoder.encode(String, "UTF-8")

    Copyright (C) 2002 Cresc Corp.

    Version: 1.0

*/

function encodeURL(str){

    var s0, i, s, u;

    s0 = "";                // encoded str

    for (i = 0; i < str.length; i++){   // scan the source

        s = str.charAt(i);

        u = str.charCodeAt(i);          // get unicode of the char

        if (s == " "){s0 += "+";}       // SP should be converted to "+"

        else {

            if ( u == 0x2a || u == 0x2d || u == 0x2e || u == 0x5f || ((u >= 0x30) && (u <= 0x39)) || ((u >= 0x41) && (u <= 0x5a)) || ((u >= 0x61) && (u <= 0x7a))){     // check for escape

                s0 = s0 + s;           // don't escape

            }

            else {                      // escape

                if ((u >= 0x0) && (u <= 0x7f)){     // single byte format

                    s = "0"+u.toString(16);

                    s0 += "%"+ s.substr(s.length-2);

                }

                else if (u > 0x1fffff){     // quaternary byte format (extended)

                    s0 += "%" + (oxf0 + ((u & 0x1c0000) >> 18)).toString(16);

                    s0 += "%" + (0x80 + ((u & 0x3f000) >> 12)).toString(16);

                    s0 += "%" + (0x80 + ((u & 0xfc0) >> 6)).toString(16);

                    s0 += "%" + (0x80 + (u & 0x3f)).toString(16);

                }

                else if (u > 0x7ff){        // triple byte format

                    s0 += "%" + (0xe0 + ((u & 0xf000) >> 12)).toString(16);

                    s0 += "%" + (0x80 + ((u & 0xfc0) >> 6)).toString(16);

                    s0 += "%" + (0x80 + (u & 0x3f)).toString(16);

                }

                else {                      // double byte format

                    s0 += "%" + (0xc0 + ((u & 0x7c0) >> 6)).toString(16);

                    s0 += "%" + (0x80 + (u & 0x3f)).toString(16);

                }

            }

        }

    }

    return s0;

}

 

/*  Function Equivalent to URLDecoder.decode(String, "UTF-8")

    Copyright (C) 2002 Cresc Corp.

    Version: 1.0

*/

function decodeURL(str){

    var s0, i, j, s, ss, u, n, f;

    s0 = "";                // decoded str

    for (i = 0; i < str.length; i++){   // scan the source str

        s = str.charAt(i);

        if (s == "+"){s0 += " ";}       // "+" should be changed to SP

        else {

            if (s != "%"){s0 += s;}     // add an unescaped char

            else{               // escape sequence decoding

                u = 0;          // unicode of the character

                f = 1;          // escape flag, zero means end of this sequence

                while (true) {

                    ss = "";    // local str to parse as int

                        for (j = 0; j < 2; j++ ) {  // get two maximum hex characters to parse

                            sss = str.charAt(++i);

                            if (((sss >= "0") && (sss <= "9")) || ((sss >= "a") && (sss <= "f"))  || ((sss >= "A") && (sss <= "F"))) {

                                ss += sss;          // if hex, add the hex character

                            } else {--i; break;}    // not a hex char., exit the loop

                        }

                    n = parseInt(ss, 16);           // parse the hex str as byte

                    if (n <= 0x7f){u = n; f = 1;}   // single byte format

                    if ((n >= 0xc0) && (n <= 0xdf)){u = n & 0x1f; f = 2;}   // double byte format

                    if ((n >= 0xe0) && (n <= 0xef)){u = n & 0x0f; f = 3;}   // triple byte format

                    if ((n >= 0xf0) && (n <= 0xf7)){u = n & 0x07; f = 4;}   // quaternary byte format (extended)

                    if ((n >= 0x80) && (n <= 0xbf)){u = (u << 6) + (n & 0x3f); --f;}    // not a first, shift and add 6 lower bits

                    if (f <= 1){break;}             // end of the utf byte sequence

                    if (str.charAt(i + 1) == "%"){ i++ ;}                   // test for the next shift byte

                    else {break;}                   // abnormal, format error

                }

            s0 += String.fromCharCode(u);           // add the escaped character

            }

        }

    }

    return s0;

}

 

/*  Function to get cookie parameter value string with specified name

    Copyright (C) 2002 Cresc Corp.

    Version: 1.0

*/

function loadCookie(name) {

    var allcookies = document.cookie;

    if (allcookies == "") return "";

    var start = allcookies.indexOf(name + "=");

    if (start == -1) return "";

    start += name.length + 1;

    var end = allcookies.indexOf(';',start);

    if (end == -1) end = allcookies.length;

    return decodeURL(allcookies.substring(start,end));

}

 

/*  Function to send the textarea data throuth cookie

    Copyright (C) 2002 Cresc Corp.

    Version: 1.0

*/

function sendThis(){

    document.cookie="userdata="+encodeURL(document.inForm.text.value);  //set data

    window.location.reload();   // and reload this page

}

</SCRIPT>

 

 

</HEAD>

<BODY>

JavaScriptによるJ2プラットフォーム互換URLエンコード関数と、JSPによるそのテスト<BR>

URLエンコードされたクッキーのパラメタを介したサーブレットとJavaScript間のデータの交換)<BR><BR>

 

<% if (cookies != null){ %>

<DIV STYLE="width: 50% word-break:break-all">

サーバーが受け取ったクッキー(URLデコード後):<BR><%

        for (int i = 0; i < cookies.length; i++){ %>

名前=<%=    cookies[i].getName() %><BR>

=<%=      cookies[i].getValue() %><BR>

  (<%=     URLDecoder.decode(cookies[i].getValue(), "UTF-8") %>)<BR>

<%      }

    } %>

</DIV>

<P STYLE="width: 50%">

クライアントが受け取ったクッキー(URLエンコードされている):<BR>

<SCRIPT LANGUAGE="JavaScript">

    document.writeln(document.cookie);

</SCRIPT>

</P>

<P STYLE="width: 50%">

クライアント受信したクッキーの中のデータ部分:<BR>

<SCRIPT LANGUAGE="JavaScript">

    document.writeln(loadCookie("userdata"));

</SCRIPT>

</P>

サーバに送信するテキストを書き込んで下さい:<BR>

<FORM NAME="inForm">

<TEXTAREA ROWS="3" COLS="60" WRAP="soft" NAME="text"></TEXTAREA><BR>

<INPUT TYPE="button" VALUE="クッキー書込みと送信" ONCLICK="sendThis()">

</FORM>

 

</BODY>

</HTML>

 

以上

 

先頭に戻る