162 lines
10 KiB
Markdown
162 lines
10 KiB
Markdown
# 4.2 フォームに入力された内容の検証
|
||
|
||
Web開発にはユーザが入力したいかなる情報も信用してはならないという原則があります。そのためユーザの入力した情報を検証しフィルターすることは非常に重要になってきます。ブログやニュースの中でどこそこのホームページがハッキングされたりセキュリティホールが存在するといったことをよく聞くかもしれません。これらの大部分はユーザの入力した情報に対してホームページが厳格な検証を行わなかった事によるものです。そのため、安全なWebプログラムを書くために、フォームの入力を検証する意義は非常に大きいのです。
|
||
|
||
Webアプリケーションを書く時は主に2つの場所でデータ検証を行います。ひとつはページ上でのJavaScriptによる検証で(現在この方面では多くのプラグインがあります。例えばValidationJSプラグインなどがそうです)、もうひとつはサーバ側での検証です。この節ではどのようにサーバでの検証を行うか解説します。
|
||
|
||
## 必須フィールド
|
||
あるフォーム要素から一つの値を取り出したいとします。例えば前の節のユーザ名はどのように処理するのでしょうか?Goにはbuiltin関数`len`があり、文字列の長さを得ることができます。lenを使ってデータの長さを測ることができます。例えば:
|
||
|
||
if len(r.Form["username"][0])==0{
|
||
//空だった場合の処理
|
||
}
|
||
|
||
`r.Form`は異なる型のフォーム要素の空白に対して異なる処理を行います。空のテキストフィールド、テキストエリアおよびファイルアップロードに対して、その要素の値を空にします。また選択されていないコンボボックスやセレクトボックスr.Formの中にはそもそもその項目を作りません。上の例の中の方法でデータを取得した時プログラムはエラーを発生させます。そのため、`r.Form.Get()`を使って値を取る必要があります。なぜなら、もしフィールドが存在しなかった場合、この方法で取得すると空の値を得るからです。ですが、`r.Form.Get()`は単体の値しか得ることができません。もしmapの値であれば必ず上の方法で得る必要があります。
|
||
|
||
## 数
|
||
例えばフォームからある人の年齢が50歳や10歳といった具体的な値を必要としていて、"おっさん"とか"まだ若い"というようなものでなかったとします。このようにフォームの入力フィールドの中で数字のみを許容するようにさせたい場合、整数かどうかを判断するために、まずint型に変換を行ってから処理を行います。
|
||
|
||
正の整数を判断しようとする場合は、まずint型に変換してから処理を行います
|
||
|
||
getint,err:=strconv.Atoi(r.Form.Get("age"))
|
||
if err!=nil{
|
||
//数の変換でエラーが発生。つまり、数字ではありません。
|
||
}
|
||
|
||
//次にこの数の取りうる範囲を判断します。
|
||
if getint >100 {
|
||
//大きすぎる
|
||
}
|
||
|
||
もう一つの方法は正規表現による方法です。
|
||
|
||
if m, _ := regexp.MatchString("^[0-9]+$", r.Form.Get("age")); !m {
|
||
return false
|
||
}
|
||
|
||
性能の高さを必要とするユーザからすればこれはよく話題にのぼる問題です。彼らはなるべく正規表現を避けるべきだと考えています。なぜなら正規表現の速度は一般的に遅いからです。しかし現在のようにコンピュータの性能がこれほど発達した時代では、このように簡単な正規表現の効率と型変換関数の間ではそれほど大きな差はありません。もしあなたが正規表現に詳しく、他の言語でも使用されているのであれば、Goの中で正規表現を使うのは便利な方法の一つです。
|
||
|
||
>Goの正規表現の実装は[RE2](http://code.google.com/p/re2/wiki/Syntax)です。すべての文字はUTF-8エンコーディングです。
|
||
|
||
## 中国語
|
||
フォームの要素からユーザの中国語名を得たい場合で、なおかつ正しい中国語であることを保証したい場合、検証を行う必要があります。ユーザに自由に入力はさせません。中国語に対する有効な検証には、`unicode` パッケージが提供する `func Is(rangeTab *RangeTable, r rune) bool` と正規表現を使う方法があります。下のコードをご確認ください
|
||
|
||
if m, _ := regexp.MatchString("^\\p{Han}+$", r.Form.Get("realname")); !m {
|
||
return false
|
||
}
|
||
|
||
## 英語
|
||
あるユーザの英語名を知りたいときなど、フォームの要素から英語の値を取り出したい場合は、astaxieであってasta谢ではないはずです。(訳注:「谢」はピンインでxieと書く)
|
||
|
||
簡単な正規表現を使ってデータを検証することができます:
|
||
|
||
if m, _ := regexp.MatchString("^[a-zA-Z]+$", r.Form.Get("engname")); !m {
|
||
return false
|
||
}
|
||
|
||
|
||
## メールアドレス
|
||
ユーザが入力したEmailアドレスが正しいか確認したい場合は以下のような方法で検証できます:
|
||
|
||
if m, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,}).([a-z]{2,4})$`, r.Form.Get("email")); !m {
|
||
fmt.Println("no")
|
||
}else{
|
||
fmt.Println("yes")
|
||
}
|
||
|
||
|
||
## 携帯電話番号
|
||
ユーザが入力した携帯電話番号が正しいか判断したい場合は以下の正規表現で検証できます(訳注:中国の携帯電話番号):
|
||
|
||
if m, _ := regexp.MatchString(`^(1[3|4|5|8][0-9]\d{4,8})$`, r.Form.Get("mobile")); !m {
|
||
return false
|
||
}
|
||
|
||
## プルダウンメニュー
|
||
フォームの中の`<select>`要素が生成するプルダウンメニューにおいて、ハッカーは時々このプルダウンメニューに無い項目を偽造してあなたに送りつけるかもしれません。このような場合値がもともと設定されたものであることを判断するにはどうすればよいでしょうか?
|
||
|
||
selectには以下の要素があるとします:
|
||
|
||
<select name="fruit">
|
||
<option value="apple">apple</option>
|
||
<option value="pear">pear</option>
|
||
<option value="banane">banane</option>
|
||
</select>
|
||
|
||
この場合は以下のように検証することができます
|
||
|
||
slice:=[]string{"apple","pear","banane"}
|
||
|
||
for _, v := range slice {
|
||
if v == r.Form.Get("fruit") {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
|
||
## ラジオボタン
|
||
男と女という性別の選択肢を出力するようなラジオボタンでどれかが選択されているか判断するとします。15歳の退屈な少年がhttpプロトコルの本を片手にtelnetクライアントからあなたのプログラムに対してリクエストを送信したとしましょう。あなたは男に1を、女に2を設定していて、彼が3という値を送信した場合、あなたのプログラムは例外を出すでしょうか?プルダウンメニューの判断と同じように我々が得ようとしている値がそもそも設定されたものであるかを判断しなければなりません。
|
||
|
||
<input type="radio" name="gender" value="1">男
|
||
<input type="radio" name="gender" value="2">女
|
||
|
||
プルダウンメニューの方法と同じように行うことができます
|
||
|
||
slice:=[]int{1,2}
|
||
|
||
for _, v := range slice {
|
||
if v == r.Form.Get("gender") {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
|
||
## チェックボックス
|
||
趣味を選択するチェックボックスがあり、ユーザが選択したものとあなたがユーザに提供した選択が同じ型のデータであることを保証する場合
|
||
|
||
<input type="checkbox" name="interest" value="football">サッカー
|
||
<input type="checkbox" name="interest" value="basketball">バスケットボール
|
||
<input type="checkbox" name="interest" value="tennis">テニス
|
||
|
||
チェックボックスではラジオボタンの時とくらべ検証方法が少し異なります。受け取るデータはsliceだからです。
|
||
|
||
slice:=[]string{"football","basketball","tennis"}
|
||
a:=Slice_diff(r.Form["interest"],slice)
|
||
if a == nil{
|
||
return true
|
||
}
|
||
|
||
return false
|
||
|
||
上の`Slice_diff`という関数には私のオープンソースのライブラリが含まれます(sliceとmapを操作するライブラリ)[https://github.com/astaxie/beeku](https://github.com/astaxie/beeku)
|
||
|
||
## 日付と時間
|
||
ユーザが入力した日時が有効か確認したいとします。例えばユーザがスケジュールで8月45日にパーティを開く予定を入力したり、未来の時間を誕生日にしてみたりといった場合です。
|
||
|
||
Goではtimeの処理パッケージを提供しています。ユーザの入力した年月日を目的の時間に変換してから、判断を行います。
|
||
|
||
t := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
|
||
fmt.Printf("Go launched at %s\n", t.Local())
|
||
|
||
timeを取得した後、多くの時間関数の操作を行うことができます。具体的な判断は自身の要件に合わせて調整してください。
|
||
|
||
## 身分証明書番号
|
||
フォームに入力された身分証明書を検証する場合、正規表現を使って簡単に検証できます。しかし身分証明書番号は15桁と18桁があるので2つとも検証しなければなりません。(訳注:中国では身分証明書に個人を特定する身分証明番号(以前は15桁、現在は18桁)が記載されています。)
|
||
|
||
//15桁の身分証明書の検証。15桁はすべて数字です。
|
||
if m, _ := regexp.MatchString(`^(\d{15})$`, r.Form.Get("usercard")); !m {
|
||
return false
|
||
}
|
||
|
||
//18桁の身分証明書の検証。18桁の前17桁は数字で、最後の一桁はチェックデジットです。数字または文字Xを取り得ます。
|
||
if m, _ := regexp.MatchString(`^(\d{17})([0-9]|X)$`, r.Form.Get("usercard")); !m {
|
||
return false
|
||
}
|
||
|
||
以上、よく使用されるサーバ側でのフォーム要素の検証をいくつかご紹介しました。このイントロダクションを通してGoによるデータ検証、特に正規表現での処理に対する理解が深まるよう願っています。
|
||
|
||
## links
|
||
* [目次](<preface.md>)
|
||
* 前へ: [フォームの入力を処理する](<04.1.md>)
|
||
* 次へ: [クロスサイトスクリプティングの予防](<04.3.md>)
|