サンプル・プログラム
output3.html | HTMLへの出力 |
input3.html | HTMLからの入力 |
input4.html | 入力バリデーション |
バージョン | 更新日 | 内容 |
---|---|---|
1.0.1 | 2023/08/14 | コメント追加 |
1.0 | 2021/08/17 | 初版 |
バージョン | 更新日 | 内容 |
---|---|---|
1.1.0 | 2023/08/14 | specialCharacters2HTMLentities()見直し |
1.0 | 2021/08/21 | 初版 |
バージョン | 更新日 | 内容 |
---|---|---|
1.1.0 | 2023/08/14 | specialCharacters2HTMLentities()見直し |
1.0 | 2021/08/21 | 初版 |
HTMLへの出力
55: //データをセット
56: document.getElementById('output1').innerHTML = OUTPUT1;
57: document.getElementById('output2').value = OUTPUT2;
58: document.getElementById('output3').value = OUTPUT3;
59: }
pタグやdivタグのように、直接HTMLとして出力するタイプのオブジェクトのときは、innerHTML プロパティに代入する。ここではプレーン・テキストを代入しているが、HTML文を代入することもできる。
さらに、JavaScriptのプログラムそのものを代入することができるのだが、後述する脆弱性に繋がるので注意しておきたい。
inputタグやtextareaタグのように、属性valueに表示データを記述するタイプのオブジェクトのときは、value プロパティに代入する。
49: //スタイルシートをセット
50: const IDs = [ 'input1', 'input2', 'output1', 'output2', 'help' ];
51: IDs.forEach(function (id) {
52: document.getElementById(id).style.width = (WIDTH - 30).toString() + 'px';
53: });
54:
ここでは、複数の要素に同じスタイルシート(width)を指定したいので、指定するIDを配列に持たせ、forEachメソッドを使って一気に処理している。
HTMLからの入力
75: /**
76: * 実行ボタン押下処理
77: * @param なし
78: * @return なし
79: */
80: function execute() {
81: let str1 = document.getElementById('input1').value;
82: let str2 = document.getElementById('input2').value;
83:
84: //入力文字のエスケープ
85: str1 = specialCharacters2HTMLentities(str1);
86: str2 = specialCharacters2HTMLentities(str2);
87:
88: //結果を表示
89: document.getElementById('output1').innerHTML = 'input1:' + str1;
90: document.getElementById('output2').innerHTML = 'input2:' + str2;
91: }
入力元として利用可能なオブジェクトは、inputタグやtextareaタグである。value プロパティの値を取得する。
入力バリデーション
40: /**
41: * 特殊文字をHTMLエンティティに変換する
42: * @param String str 文字列
43: * @return String 変換後文字列
44: */
45: function specialCharacters2HTMLentities(str) {
46: //文字列置換テーブル
47: //regx: 置換パターン
48: //regs: 置換文字列
49: const CONVERT_TABLE = [
50: {
51: regx: /&/g,
52: reps: '&',
53: }, {
54: regx: /"/g,
55: reps: '"',
56: }, {
57: regx: /'/g,
58: reps: ''',
59: }, {
60: regx: /</g,
61: reps: '<',
62: }, {
63: regx: />/g,
64: reps: '>'
65: }];
66:
67: let ss = str + '';
68: CONVERT_TABLE.forEach(function (item) {
69: ss = ss.replace(item['regx'], item['reps']);
70: });
71:
72: return ss;
73: }
XSS攻撃を回避するため、正規表現を用い、これらの文字をエンティティ参照に変換する specialCharacters2HTMLentities 関数を用意した。
置換パターン(regx)と置換文字列(regs)のセットをオブジェクト配列 CONVERT_TABLE として用意した。この配列を編集すれば、容易に他のパターンを置換することができるようになる。
76: /**
77: * 文字列のバリデーション
78: * @param String data チェックする文字列
79: * @param Number min, max 文字列長の最短/最長(NaNなら無視)
80: * @param Array pattern 適合パターン(正規表現)配列(OR条件)
81: * @return String '':成功/その他はエラーメッセージ
82: */
83: function validString(data, min, max, patterns) {
84: //引数チェック
85: if (! Array.isArray(patterns)) {
86: return '引数patternsが間違っています';
87: }
88: if (! isNaN(min) && ! Number.isInteger(min)) {
89: return '引数minが間違っています';
90: }
91: if (! isNaN(max) && ! Number.isInteger(max)) {
92: return '引数maxが間違っています';
93: }
94: if (! isNaN(min) && ! isNaN(max) && (min > max)) {
95: return '引数minまたはmaxが間違っています';
96: }
97: if (data == '') {
98: return '引数dataが空です';
99: }
100:
101: //長さチェック
102: if (! isNaN(min) && (data.length < min)) {
103: return min.toString() + '文字より短い';
104: }
105: if (! isNaN(max) && (data.length > max)) {
106: return max.toString() + '文字より長い';
107: }
108:
109: //パターンマッチング
110: let flag = false;
111: patterns.forEach(function (regx) {
112: //1つでもマッチすればフラグを立てて脱出する.
113: if (data.match(regx)) {
114: flag = true;
115: return;
116: }
117: });
118:
119: return flag ? '' : '規定外の文字が含まれている';
120: }
前半は、この関数に渡される引数そのもののバリデーションを行う。
続いて、文字列の長さをチェックする。
最後に、パターンに一致するかどうかをチェックする。配列の要素の1つでも一致していればOKとする。
251: //英数字
252: let pat3 = [/^[a-z0-9]+$/i];
259: //日本語
260: let pat4 = [/^[\x00-\x7F\u3041-\u3096\u30A1-\u30FA々〇〻\u3400-\u9FFF\uF900-\uFAFF\uD840-\uD87F\uDC00-\uDFFF]+$/];
122: /**
123: * 整数のバリデーション
124: * @param String data チェックする数字
125: * @param Number min, max 最小値,最大値(NaNなら無視)
126: * @param Boolean comma true:区切りカンマを認める/false:認めない
127: * @return String '':成功/その他はエラーメッセージ
128: */
129: function validInteger(data, min, max, comma) {
130: //パターン
131: const pat1 = [/^[0-9\+\-]+$/];
132: const pat2 = [/^[0-9\+\-(\,)]+$/];
133:
134: //引数チェック
135: if (! isNaN(min) && ! Number.isInteger(min)) {
136: return '引数minが間違っています';
137: }
138: if (! isNaN(max) && ! Number.isInteger(max)) {
139: return '引数maxが間違っています';
140: }
141: if (! isNaN(min) && ! isNaN(max) && (min > max)) {
142: return '引数minまたはmaxが間違っています';
143: }
144: if (typeof(comma) != 'boolean') {
145: return '引数commaが間違っています';
146: }
147:
148: //パターンマッチング
149: let res, str, n;
150: if (comma) {
151: res = validString(data, NaN, NaN, pat2);
152: if (res != '') return res;
153: //コンマ除去
154: str = data.replace(pat2, function(match, p1) {
155: return '';
156: });
157: n = parseInt(str, 10);
158: } else {
159: res = validString(data, NaN, NaN, pat1);
160: if (res != '') return res;
161: n = parseInt(data, 10);
162: }
163:
164: //値の範囲チェック
165: if (!isNaN(min) && (n < min)) return min.toString() + 'より小さい';
166: if (!isNaN(max) && (n > max)) return max.toString() + 'より大きい';
167:
168: return '';
169: }
桁区切りとしてカンマを認めているときは、カンマを除去してから整数化して値域チェックを行う。
171: /**
172: * 小数のバリデーション
173: * @param String data チェックする数字
174: * @param Number min, max 最小値,最大値(NaNなら無視)
175: * @param Boolean exp true:指数表記を認める/false:認めない
176: * @return String '':成功/その他はエラーメッセージ
177: */
178: function validFloat(data, min, max, exp) {
179: //パターン
180: const pat1 = [/^[0-9\+\-\.]+$/];
181: const pat2 = [/^[0-9\+\-\.eE]+$/];
182:
183: //引数チェック
184: if (! isNaN(min) && (typeof(min) != 'number')) {
185: return '引数minが間違っています';
186: }
187: if (! isNaN(max) && (typeof(max) != 'number')) {
188: return '引数maxが間違っています';
189: }
190: if (! isNaN(min) && isNaN(max) && (min > max)) {
191: return '引数minまたはmaxが間違っています';
192: }
193: if (typeof(exp) != 'boolean') {
194: return '引数commaが間違っています';
195: }
196:
197: //パターンマッチング
198: let res, str, n;
199: res = validString(data, NaN, NaN, exp ? pat2 : pat1);
200: if (res != '') return res;
201: x = parseFloat(data);
202:
203: //値の範囲チェック
204: if (!isNaN(min) && (x < min)) return min.toString() + 'より小さい';
205: if (!isNaN(max) && (x > max)) return max.toString() + 'より大きい';
206:
207: return '';
208: }
コラム:セキュリティ対策
ぱふぅ家ホームページでも、ログ解析してみたところ、入力を許しているページへの無意味なアクセスが非常に多い。サイトの脆弱性を突いて、そこを踏み台にして他者への攻撃を行ったり、サイト改ざんしてマルウェアをばら撒くなどの目的でアクセスしているのだろう。
このシリーズは入門者向けということで、サイバーセキュリティ対策については詳しく解説しないが、自作プログラムをインターネット上で公開するときは、悪意のある第三者に狙われていることを常に意識していただきたい。
つまり、画面出力やキーボード入力は、そのスクリプトが動作しているHTMLファイル(documentオブジェクト)のプロパティにデータを出力したり入力することで実現する。