サンプル・プログラム
パラメータの入力
input1.html
44: <!-- 入力と表示 -->
45: <input type="text" id="from" size="4" value="" />から
46: <input type="text" id="to" size="4" value="" />の和は
47: <span id="sum"></span>です.
48: <input type="button" id="exec" value="計算" onClick="execute()" />
49:
計算をさせるためのボタン <input type="button" id="exec"> を用意した。このボタンをクリックすると、onClick属性で指定しているJavaScriptの execute()関数(後述)を呼び出す。
input1.html
10: /**
11: * 計算と画面表示
12: */
13: function execute() {
14: //変数宣言
15: let a = document.getElementById('from').value; //開始値
16: a = parseInt(a, 10); //10進整数化
17: let b = document.getElementById('to').value; //終了値
18: b = parseInt(b, 10); //10進整数化
19: let n = 0; //合計値
20:
21: //aからbまでの整数の合計を求める
22: for (let i = a; i <= b; i++) {
23: n += i;
24: }
25:
26: //結果を表示する
27: document.getElementById('sum').innerHTML = n;
28: }
HTMLフォームから取り出した値は文字列なので、計算できるように parseInt関数を使って整数に変換する。第2引数の 10 は、10進数として解釈するという指定である。
また、このブロック全体が execute()関数 として定義されている。関数については、4.2 ユーザー定義関数で追い追い解説する。ここでは、ボタンがクリックされたら呼び出されるプログラムと考えておいてほしい。
入力値のバリデーション
文字列を入力すると parseInt 関数が NaN(数値ではないことを表す値)を返し、forループ の繰り返し条件が成立しないため、合計は0のままになる。一方、小数を入力すると parseInt 関数が小数点以下を切り捨てた整数を返すため、たとえば1.5と入力しても1として計算してしまう。
そこで、小数や文字列が入力されたらエラーメッセージを表示して計算しないようにしてみよう。
まず、画面から入力されたデータ(文字列)を変数a, bに代入する。これは "input1.html" と同じである。
次に、入力されたa, bの先頭や末尾の空白を除去する。trim メソッドを使う。これはWebアプリに限らず、入力の前処理作法の1つだ。
それから、入力されたa, bをエスケープする。ここでいうエスケープとは、HTMLの特殊文字 &, ", ', <, > をエンティティ参照に変換する処理のことである。これらの文字をそのまま画面に出力すると、HTMLのタグなどとして解釈され、表示が崩れたり、意図しないプログラムが動いたりするからだ。Webアプリでは、入力ミスもそうだが、悪意のある第三者が入力する可能性もあるので、エスケープ処理を行うなどして、プログラムの誤動作を防ぐようにしなければならない。
このあとがバリデーション(validation)である。ここでいうバリデーションとは、入力されたデータが期待通りの値であることを検証する処理を指す。
ここでは、(1) a, bが空欄でないこと、(2) a, bが数字(0,1,2‥‥9)だけでできていること、(3) a<b であることの3つを検証する。3つすべてをクリアしたらforループに入り、1つでも満たさなければエラーメッセージを表示して計算しない。なお、(2) では符号や小数点を認めていないので、"input2.html" には負の整数や小数を入力できない。
input2.html
26: /**
27: * 計算と画面表示
28: */
29: function execute() {
30: //変数宣言
31: let a = document.getElementById('from').value; //開始値
32: let b = document.getElementById('to').value; //終了値
33: let n = ''; //合計値
34: let errmsg = ''; //エラーメッセージ
35:
36: //空白除去
37: a = a.trim();
38: b = b.trim();
39:
40: //入力文字のエスケープ
41: a = specialCharacters2HTMLentities(a);
42: b = specialCharacters2HTMLentities(b);
43:
44: //バリデーション
45: if (a.length === 0) {
46: errmsg = '開始値に整数を入力してください';
47: } else if (b.length === 0) {
48: errmsg = '終了値に整数を入力してください';
49: } else if (! a.match(/^[0-9]+$/gi)) {
50: errmsg = '"' + a + '" は整数ではありません';
51: } else if (! b.match(/^[0-9]+$/gi)) {
52: errmsg = '"' + b + '" は整数ではありません';
53: } else {
54: a = parseInt(a, 10); //10進整数化
55: b = parseInt(b, 10); //10進整数化
56: if (a >= b) {
57: errmsg = b + 'は' + a + 'より大きな整数にしてください';
58: } else {
59: //aからbまでの整数の合計を求める
60: n = 0;
61: for (let i = a; i <= b; i++) {
62: n += i;
63: }
64: }
65: }
66: //エラー・メッセージがあれば整形
67: if (errmsg !== '') {
68: errmsg = 'エラー:' + errmsg + '.';
69: }
70:
71: //結果を表示する
72: document.getElementById('sum').innerHTML = n;
73: document.getElementById('error').innerHTML = errmsg;
74: }
また、エスケープを行うユーザー関数 specialCharacters2HTMLentities を用意した。
"if (a.length === 0) {...}" は、変数aに入っている文字列の長さが0、つまり何も文字が入っていないとき(空文字)にブロック内を実行するという if文である。
そうでないとき、"else if (b.length === 0) {...}" で、変数bが空文字かどうかを同じように調べる。
そうでないとき、"else if (! a.match(/^[0-9]+$/gi)) {...}" で、変数aが数字(0,1,2‥‥9)だけでできているかどうかを調べる。先頭の ! は否定を表すので、数字以外の文字が混じっているときにブロック内を実行し、エラーメッセージをセットする。
そうでないとき、"else if (! b.match(/^[0-9]+$/gi)) {...}" で、変数bについて同じ検証を行う。
上記いずれでもない場合は "else {...}" のブロックを実行する。
さらに elseブロックの中で、a<b を検証するif文が入れ子になっている。"if (a >= b) {...}" が成り立つときはエラーメッセージをセットし、そうでないとき――3つの検証をすべてクリアしたとき――に、else のブロックにある計算のためのforループを実行する。
このようにif~else文やforループは、ブロックの中に入れ子にすることができる。詳しいことは 3.1 if~else文 で説明する。
matchメソッドは正規表現によるマッチングの結果を返すもので、これも 5.4 正規表現 で追い追い解説する。
input2.html
10: /**
11: * 特殊文字をHTMLエンティティに変換する
12: * @param string str 文字列
13: * @return string 変換後文字列
14: */
15: function specialCharacters2HTMLentities(str) {
16: let ss = str + '';
17: ss = ss.replace(/&/g, '&');
18: ss = ss.replace(/"/g, '"');
19: ss = ss.replace(/'/g, ''');
20: ss = ss.replace(/</g, '<');
21: ss = ss.replace(/>/g, '>');
22:
23: return ss;
24: }
コラム:エスケープ処理
<script>window.open("/");</script>――という文字列を入力すると、エラーを表示した途端、ぱふぅ家のホームページを別ウィンドウで開かせることができてしまう場合がある。
今回はHTMLの特殊文字をエンティティ参照に変換するだけだが、データベースにアクセスするようなときにはSQLインジェクションにも配慮する必要がある。
人間が入力したデータや他システムから送られてくるデータは、かならずしも予期したとおりのものでないことがある。入力に対しては、かならず、バリデーション(エスケープ処理を含む)を組み込む習慣を身につけよう。
参考サイト
- parseInt():mdn web docs_
- String.prototype.trim():mdn web docs_
- String.prototype.match():mdn web docs_
- if...else:mdn web docs_
- PHPセキュリティ対策:数値入力とバリデーション:ぱふぅ家のホームページ
- 【初心者向け】SQLインジェクションの概要と対策方法:KAGOYA
