Files
build-web-application-with-…/zh/04.2.md
2019-05-18 12:52:23 +08:00

181 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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>)