2008/9/28 日曜日

Javascript AJAX 生テキストデータ送信について

Filed under: 開発メモ — admin @ 17:42:39

PHPとJavaScriptのURIエンコードを比較
追記 : 2005.7.16 PHPのurlencode()が空白文字を「+」に変換する件

Ajaxでは、サーバーからデータを受信する際、生のテキスト文字列を使用すると、 responseText,responceXMLそれぞれに文字化けするブラウザがあります。[ 参考:->文字化け調査 ]

これは、Ajaxでの送受信時には、Formの時のようにブラウザが自動でURIエンコード/デコードをしてくれるわけではない、ということに起因すると言えるかもしれません。

一般的には、Formの動作に見られるように、URIエンコードして送受信することで、安全でない文字などが引き起こす、いろいろな問題を回避しようとするわけです。(AjaxでGETやPOSTでの送出時は、ブラウザが自動ではエンコードしてくれない以上、プログラマーの義務ではないかという気がしています。POSTならsetRequestHeader()で、GETなら自前で、、、。)この時、使えるメソッドとして、JavaScriptには、 escape()、encodeURI()、そして、encodeURIComponent() があります。

このうち、escape()は、古いメソッドで、ブラウザにより実装が異なるため使わない方が安全です。残りの、encodeURI()とencodeURIComponent()は、ECMAScriptの仕様に従ったメソッドで、現在流通しているブラウザなら共通に動作するはずです。

ありがたいことに、この2つのメソッドは、ページのcharset(Shift_JIS,EUC,UTF-8,,,)が何であっても、UTF-8 としてエンコードしてくれます。つまり、「Shift_JISからEUCへ」など異なったcharset環境間でデータを受け渡す場合にも必ず「UTF- 8」として明示的にエンコードされたデータとして受け渡せるので安心です。

したがって、もし、サーバーから静的ファイルを取ってくるだけなら、JavaScriptのURIエンコードメソッドで変換した文字列をサーバーにおいておけば、JavaScriptによるAjaxでの受信は、どのブラウザでも同じ種類のデコードメソッドを使えるので文字化けしなくなります。

ところが、サーバーから動的に受信する場合は、あらかじめJavaScriptによるエンコードができません。つまり、サーバー側のPHPなど他の言語でURIエンコードされたものをJavaScriptでデコードしないといけないのです。でも、URIエンコードの実装は、実は、言語によって異なっています。。。そこで、他の言語でのエンコードとJavaScriptエンコード、そしてデコードの違いを実際に確認してみます。今回はPHPです。

もし、問題なくAjaxで受信してデコードされるならそれを使えば良いし、もし、多少の違いがあるなら、そこを修正すれば良いと、いうわけで、、。

比較する文字列
;/?:@&=+$%-_!~*{}[].,()”あa^# ‘
(#の後ろに空白文字があります)
PHP

$data  = ‘;/?:@&=+$%-_!~*{}[].,()”あa^# ‘.”‘”;

mb_http_output ( ‘UTF-8′ );
$data = mb_convert_encoding($data,”UTF-8″,mb_internal_encoding());

$html  = “<br><b>urlencode() : </b><br>”.urlencode($data);
$html .= “<br><b>rawurlencode() : </b><br>”.rawurlencode($data);

echo($html);

urlencode() :
%3B%2F%3F%3A%40%26%3D%2B%24%25-_%21%7E%2A%7B%7D%5B%5D.%2C%28%29%22%E3%81%82a%5E%23+%27
rawurlencode() :
%3B%2F%3F%3A%40%26%3D%2B%24%25-_%21%7E%2A%7B%7D%5B%5D.%2C%28%29%22%E3%81%82a%5E%23%20%27

–>PHPがエンコードしない文字列

-_.a

注意:urlencode()は、空白文字を「+」に変換し、rawurlencode()は「%20」に変換するという違いがあります。
JavaScript

var data = ‘;/?:@&=+$%-_!~*{}[].,()”あa^# ‘+”‘”;

var enchtml = ‘<br><b>encodeURI() : </b><br>’+encodeURI(data)
enchtml + ‘<br><b>encodeURIComponent() : </b><br>’+encodeURIComponent(data)

document.write(enchtml)

encodeURI() :
;/?:@&=+$%25-_!~*%7B%7D%5B%5D.,()%22%E3%81%82a%5E#%20′

–>encodeURI()がエンコードしない文字列

;/?:@&=+$-_!~*.,()a#’

encodeURIComponent() :
%3B%2F%3F%3A%40%26%3D%2B%24%25-_!~*%7B%7D%5B%5D.%2C()%22%E3%81%82a%5E%23%20′

–>encodeURIComponent()がエンコードしない文字列

-_!~*.()a’

結論1
つまり、PHPとJavaScript(ECMAScript)のURIエンコードは、違うのです。
では、デコードするとどうなるでしょう?

デコード : PHP urlencode() → JS decodeURIComponent()

<?php

$data  = ‘;/?:@&=+$%-_!~*{}[].,()”あa^# ‘.”‘”;

mb_http_output ( ‘UTF-8′ );
$data = mb_convert_encoding($data,”UTF-8″,mb_internal_encoding());

?>

<script>

//PHPでエンコード
datad = “<?php echo(urlencode($data)); ?>”

//JavaScriptでデコード
document.write(”<br>”+decodeURIComponent(datad))

</script>

PHP urlencode()でエンコード
%3B%2F%3F%3A%40%26%3D%2B%24%25-_%21%7E%2A%7B%7D%5B%5D.%2C%28%29%22%E3%81%82a%5E%23+%27

JS decodeURIComponent()でデコード
;/?:@&=+$%-_!~*{}[].,()”あa^#+’

–>encodeURIComponent()はエンコードしないが、decodeURIComponent()がデコードした文字列

!~*()’

(-_.aは、PHP がエンコードしません。)

おお、encodeURIComponent()でエンコード出来なかった文字までデコード出来てる!

てことは、PHP–>JS なら、「utf-8化してurlencode()」–>「decodeURIComponent()」で、使えるのでしょうか?
うーん、、、でも、これって、ECMAの仕様的にOKなんでしょうか?よくわかりません、、、。

この組み合わせの場合、最大の問題は、urlencode()によって「+」に変った空白文字がそのままなため普通の「+」と区別がつかなくなること。 ということで、次は、空白文字をencodeURIComponentと同じ「%20」に変換する、rawurlencode()で試してみましょう。

デコード : PHP rawurlencode() → JS decodeURIComponent()

<?php

$data  = ‘;/?:@&=+$%-_!~*{}[].,()”あa^# ‘.”‘”;

mb_http_output ( ‘UTF-8′ );
$data = mb_convert_encoding($data,”UTF-8″,mb_internal_encoding());

?>

<script>

//PHPでエンコード
datad = “<?php echo(rawurlencode($data)); ?>”

//JavaScriptでデコード
document.write(”<br>”+decodeURIComponent(datad))

</script>

PHP rawurlencode()でエンコード
%3B%2F%3F%3A%40%26%3D%2B%24%25-_%21%7E%2A%7B%7D%5B%5D.%2C%28%29%22%E3%81%82a%5E%23%20%27

JS decodeURIComponent()でデコード
;/?:@&=+$%-_!~*{}[].,()”あa^# ‘

–>encodeURIComponent()はエンコードしないが、decodeURIComponent()がデコードした文字列

!~*()’

(-_.aは、PHP がエンコードしません。)

おお、こんどこそデコード出来てる!
、、、他に問題ないかなぁ、、、(^^;。
結論2
PHPは、 utf-8化 + rawurlencode() → JS decodeURIComponent() この組み合わせで、行けそうかも?

実験してみました
Allabout Ajax/動的なテーブル書き換え2 (+PHP)
クライアント側のスクリプト
サーバー側のスクリプト

参考 :
ECMA-262 3rd
15.1.3 URI 処理関数のプロパティ (URI Handling Function Properties)

HTML 4.0( 日本語訳 )
17.13.3 フォーム・データの処理
methodが”get”で、 actionがHTTP URIである場合は actionの値を取り、それに`?’を付け、次いで “application/x-www-form-urlencoded” content typeを使ってコード化されたフォーム・データ・セットを追加します。そしてリンクをURIにわたします。この過程でフォーム・データはASCIIコードに限定されます。
methodが”post”で actionがHTTP URIの場合は、 action属性を使ってHTTP “post”処理と enctype属性によって特定された 内容タイプに従って作成されたメッセージを伝えます。

application/x-www-form-urlencoded urlencoded
これは、初期内容タイプです。この内容タイプで転送されたフォームは、以下の様にコード化されなければなりません:

制御名と値は、入れ替え(エスケープ)られます。空白文字符号は `+’に置き代えられてから、貯えられた文字符号は [RFC1738]、セクション2.2に記載されている様に置き代えられます:非英数文字符号は`%HH’で、パーセント記号や十六進法数値はASCII コードに。強制改行は “CR LF”の組(例、 `%0D%0A’)で代表されます。 制御名前/値は文書に現われる順番にリストされます。名前は `=’で分けられ、名前/値は `&’でお互いに分けられます。

:
:
enctype = content-type [CI] この属性は、サーバーにフォームを転送するために使われる content typeを特定します( methodの値が”post”の場合)。この属性の初期値は、 “application/x-www-form-urlencoded”です。 “multipart/form-data”値は、 type=”file”のある INPUT要素と組で使わなければなりません。

HTML 4.01( 日本語訳 )
17.13.3 フォーム・データの処理

RFC1738 Uniform Resource Locators (URL) ( 日本語訳 )
2.2. URL 文字エンコーディングの問題
*これはRFC 1738とRFC 1808で定められた書き方を改めるものです。
2.2. 予約文字 Reserved Characters

URI 構成要素のデータが予約された目的と衝突するならば、対立するデータは、 URI を形成する前に回避されなければなりません。
reserved = “;” | “/” | “?” | “:” | “@” | “&” | “=” | “+” | “$” | “,”

2.3. 予約されていない文字 Unreserved Characters

URI で許されるが予約された目的がないデータ文字は、非予約文字と呼ばれます。これは大文字・小文字(のアルファベット)、十進法の数字、そして句読点や記号の限られた集合を含みます。
unreserved = alphanum | mark
mark = “-” | “_” | “.” | “!” | “~” | “*” | “‘” | “(” | “)”
非予約文字は、 URI の意味論を変えることなく回避することができます。しかし URI が現れるために、 unescape された文字を許さない文脈で使用されているのでなければ、これは行なわれるべきではありません。

2.4.1. 回避符号化 Escaped Encoding

略..パーセント文字”%”と、その後に続く..略..16進法による数字2桁から構成されています。例えば、”%20″はUS-ASCII空白文字の回避された符号化です。

2.4.2. 回避するときとしないとき When to Escape and Unescape

(たとえば) ”%25″ を URI のデータとして使用する際は、これを回避しなければなりません。
reserved = “;” | “/” | “?” | “:” | “@” | “&” | “=” | “+” | “$” | “,”

2.4.3. 排除される US-ASCII 文字 Excluded US-ASCII Characters

空白を表す文字は排除されます。
space = <US-ASCII coded character 20 hexadecimal>

山形括弧 “<” と “>” そして二重引用符 (”) は排除されます。なぜなら、これらはしばしばURI周辺の区切り子として文書や作法の分野で使われるからです。 “#” 記号は排除されます。なぜなら、これはURIを、URI参照中のフラグメント識別子(第4項)から区切るのに使われるからです。百分率記号は排除されます。なぜなら、これは回避された文字の符号化に使われるからです。
delims = “<” | “>” | “#” | “%” | <”>

その他の文字は排除されます。なぜならそのような文字は、gatewaysなどの転送手先が往々にして書き変えたり、区切り子として使われたりすることがわかっているからです。
unwise = “{” | “}” | “|” | “\” | “^” | “[” | “]” | “`”
排除された文字に対応するデータは、URI内で適切に表示されるように、必ず回避されなくてはなりません。

RFC1738 Uniform Resource Locators (URL) ( 日本語訳 )
2.2. URL 文字エンコーディングの問題
8ビット文字は文字 “%” に続く 8ビット文字の 16進数値となる 2 つの 16進数 (”0123456789ABCDEF”) の 3文字により符号化される。(文字 “abcdef” も 16進数符号化で使われるだろう。)

8ビット文字は、もしそれらが US-ASCII コード文字セット内の印字可能な文 字でない場合、その文字の使用が安全でない場合、もしくはそれが特殊な URL スキーム内で何か別の処理のために予約されている場合に符号化されなければ ならない。

HTML convert time: 0.185 sec. Powered by WordPress ME