181 lines
7.8 KiB
Markdown
181 lines
7.8 KiB
Markdown
# 4.2 驗證表單的輸入
|
||
|
||
開發 Web 的一個原則就是,不能信任使用者輸入的任何資訊,所以驗證和過濾使用者的輸入資訊就變得非常重要,我們經常會在微博、新聞中聽到某某網站被入侵了,存在什麼漏洞,這些大多是因為網站對於使用者輸入的資訊沒有做嚴格的驗證引起的,所以為了編寫出安全可靠的 Web 程式,驗證表單輸入的意義重大。
|
||
|
||
我們平常編寫 Web 應用主要有兩方面的資料驗證,一個是在頁面端的 js 驗證(目前在這方面有很多的外掛函式庫,比如 ValidationJS 外掛),一個是在伺服器端的驗證,我們這小節講解的是如何在伺服器端驗證。
|
||
|
||
## 必填欄位
|
||
你想要確保從一個表單元素中得到一個值,例如前面小節裡面的使用者名稱,我們如何處理呢?Go 有一個內建函式 `len` 可以取得字串的長度,這樣我們就可以透過 len 來取得資料的長度,例如:
|
||
|
||
```Go
|
||
if len(r.Form["username"][0])==0{
|
||
//為空的處理
|
||
}
|
||
```
|
||
`r.Form`對不同型別的表單元素的留空有不同的處理, 對於空文字框、空文字區域以及檔案上傳,元素的值為空值,而如果是未選中的複選框和單選按鈕,則根本不會在 r.Form 中產生相應條目,如果我們用上面例子中的方式去取得資料時程式就會報錯。所以我們需要透過`r.Form.Get()`來取得值,因為如果欄位不存在,透過該方式取得的是空值。但是透過`r.Form.Get()`只能取得單個的值,如果是 map 的值,必須透過上面的方式來取得。
|
||
|
||
## 數字
|
||
你想要確保一個表單輸入框中取得的只能是數字,例如,你想透過表單取得某個人的具體年齡是 50 歲還是 10 歲,而不是像“一把年紀了”或“年輕著呢”這種描述
|
||
|
||
如果我們是判斷正整數,那麼我們先轉化成 int 型別,然後進行處理
|
||
|
||
```Go
|
||
getint,err:=strconv.Atoi(r.Form.Get("age"))
|
||
if err!=nil{
|
||
//數字轉化出錯了,那麼可能就不是數字
|
||
}
|
||
|
||
//接下來就可以判斷這個數字的大小範圍了
|
||
if getint >100 {
|
||
//太大了
|
||
}
|
||
```
|
||
還有一種方式就是正則匹配的方式
|
||
|
||
```Go
|
||
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` 來驗證,也可以使用正則方式來驗證,這裡使用最簡單的正則方式,如下程式碼所示
|
||
|
||
```Go
|
||
if m, _ := regexp.MatchString("^\\p{Han}+$", r.Form.Get("realname")); !m {
|
||
return false
|
||
}
|
||
```
|
||
## 英文
|
||
我們期望透過表單元素取得一個英文值,例如我們想知道一個使用者的英文名,應該是 astaxie,而不是 asta 謝。
|
||
|
||
我們可以很簡單的透過正則驗證資料:
|
||
|
||
```Go
|
||
if m, _ := regexp.MatchString("^[a-zA-Z]+$", r.Form.Get("engname")); !m {
|
||
return false
|
||
}
|
||
```
|
||
|
||
## 電子郵件地址
|
||
你想知道使用者輸入的一個 Email 地址是否正確,透過如下這個方式可以驗證:
|
||
|
||
```Go
|
||
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")
|
||
}
|
||
```
|
||
|
||
## 手機號碼
|
||
你想要判斷使用者輸入的手機號碼是否正確,透過正則也可以驗證:
|
||
|
||
```Go
|
||
if m, _ := regexp.MatchString(`^(1[3|4|5|8][0-9]\d{4,8})$`, r.Form.Get("mobile")); !m {
|
||
return false
|
||
}
|
||
```
|
||
## 下拉選單
|
||
如果我們想要判斷表單裡面 `<select>` 元素產生的下拉選單中是否有被選中的專案。有些時候黑客可能會偽造這個下拉選單不存在的值傳送給你,那麼如何判斷這個值是否是我們預設的值呢?
|
||
|
||
我們的 select 可能是這樣的一些元素
|
||
```html
|
||
|
||
<select name="fruit">
|
||
<option value="apple">apple</option>
|
||
<option value="pear">pear</option>
|
||
<option value="banana">banana</option>
|
||
</select>
|
||
```
|
||
那麼我們可以這樣來驗證
|
||
|
||
```Go
|
||
slice:=[]string{"apple","pear","banana"}
|
||
|
||
v := r.Form.Get("fruit")
|
||
for _, item := range slice {
|
||
if item == v {
|
||
return true
|
||
}
|
||
}
|
||
|
||
return false
|
||
```
|
||
## 單選按鈕
|
||
如果我們想要判斷 radio 按鈕是否有一個被選中了,我們頁面的輸出可能就是一個男、女性別的選擇,但是也可能一個 15 歲大的無聊小孩,一手拿著 http 協議的書,另一隻手透過 telnet 客戶端向你的程式在傳送請求呢,你設定的性別男值是 1,女是 2,他給你傳送一個 3,你的程式會出現異常嗎?因此我們也需要像下拉選單的判斷方式類似,判斷我們取得的值是我們預設的值,而不是額外的值。
|
||
```html
|
||
|
||
<input type="radio" name="gender" value="1">男
|
||
<input type="radio" name="gender" value="2">女
|
||
```
|
||
那我們也可以類似下拉選單的做法一樣
|
||
|
||
```Go
|
||
slice:=[]string{"1","2"}
|
||
|
||
for _, v := range slice {
|
||
if v == r.Form.Get("gender") {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
```
|
||
## 複選框
|
||
有一項選擇興趣的複選框,你想確定使用者選中的和你提供給使用者選擇的是同一個型別的資料。
|
||
```html
|
||
|
||
<input type="checkbox" name="interest" value="football">足球
|
||
<input type="checkbox" name="interest" value="basketball">籃球
|
||
<input type="checkbox" name="interest" value="tennis">網球
|
||
```
|
||
對於複選框我們的驗證和單選有點不一樣,因為接收到的資料是一個 slice
|
||
|
||
```Go
|
||
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 的處理套件,我們可以把使用者的輸入年月日轉化成相應的時間,然後進行邏輯判斷
|
||
|
||
```Go
|
||
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 位,我們兩個都需要驗證
|
||
|
||
```Go
|
||
//驗證 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 的資料驗證有所了解,特別是 Go 裡面的正則處理。
|
||
|
||
## links
|
||
* [目錄](<preface.md>)
|
||
* 上一節:[處理表單的輸入](<04.1.md>)
|
||
* 下一節:[預防跨站指令碼](<04.3.md>)
|