完成了第四章的写作

This commit is contained in:
astaxie
2012-09-07 16:27:15 +08:00
parent c0adec3389
commit 7c18d1dfa7
7 changed files with 161 additions and 14 deletions

13
4.3.md
View File

@@ -6,11 +6,12 @@
对XSS最佳的防护应该结合以下两种方法验证所有输入数据有效检测攻击(这个我们前面小节已经有过介绍);对所有输出数据进行适当的编码,以防止任何已成功注入的脚本在浏览器端运行。
那么Go里面是怎么做这个有效防护的呢Go的html/template里面带有下面几个函数可以帮你转
那么Go里面是怎么做这个有效防护的呢Go的html/template里面带有下面几个函数可以帮你转
- func HTMLEscape(w io.Writer, b []byte) //把b进行转义之后写到w
- func HTMLEscapeString(s string) string //转义s之后返回结果字符串
- func HTMLEscaper(args ...interface{}) string //支持多个参数一起转义,返回结果字符串
- func HTMLEscape(w io.Writer, b []byte) //把需要转移的b进行转义之后写到w
- func HTMLEscapeString(s string) string //转移s之后返回转义的字符串
- func HTMLEscaper(args ...interface{}) string //支持多个参数一起转义,然后返回一个字符串
我们看4.1小节的例子
@@ -18,11 +19,11 @@
fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password")))
template.HTMLEscape(w, []byte(r.Form.Get("username"))) //输出到客户端
我们看到最后输出到客户端的代码如下
如果我们输入的username是`<script>alert()</script>`,那么我们可以在浏览器上面看到输出如下所示
![](images/4.3.escape.png?raw=true)
那么我们在输出我们的模板的时候怎么处理的呢Go的html/template包现在默认帮你过滤了html元素但是有时候你又想输出这样的信息text/template也支持。请看下面的例子所示
那么我们在输出我们的模板的时候怎么处理的呢Go的html/template包默认帮你过滤了html元素但是有时候你又想输出这样的信息请使用text/template。请看下面的例子所示
import "text/template"
...

63
4.4.md
View File

@@ -1,9 +1,56 @@
#4.4防止多次递交表单
## links
* [目录](<preface.md>)
* 上一节: [预防跨站脚本](<4.3.md>)
* 下一节: [处理文件上传](<4.5.md>)
## LastModified
#4.4防止多次递交表单
不知道你是否曾经看到过一个论坛或者博客,在一个帖子或者文章后面出现多条重复的记录,这些大多数是因为用户重复递交了留言的表单引起的。由于种种原因,用户经常会重复递交表单。通常这只是鼠标的误操作,如双击了递交按钮,也可能是为了编辑或者再次核对填写过的信息,点击了浏览器的后退按钮,然后又再次点击了递交按钮而不是浏览器的前进按钮。当然,也可能是故意的——比如,在某项在线调查或者博彩活动中重复投票。那我们如何有效的防止用户多次递交相同的表单呢?
解决方案是在表单中添加一个带有唯一值得隐藏字段。在验证表单时先检查带有该惟一值的表单是否已经递交过了。如果是拒绝再次递交如果不是则处理表单进行逻辑处理。另外如果是采用了Ajax模式递交表单的话当表单递交后通过javascript来禁用表单的递交按钮。
我继续拿4.2小节的例子优化:
用户名:<input type="text" name="username">
密码:<input type="password" name="password">
<input type="hidden" name="token" value="{{.}}">
<input type="submit" value="登陆">
我们看到在模版里面我们增加了一个隐藏字段`token`这个值我们通过MD5(时间戳)来获取惟一值,然后我们把这个值存储到服务器端(session来控制我们将在第六章讲解如何保存)
func login(w http.ResponseWriter, r *http.Request) {
fmt.Println("method:", r.Method) //获取请求的方法
if r.Method == "GET" {
crutime := time.Now().Unix()
h := md5.New()
io.WriteString(h, strconv.FormatInt(crutime, 10))
token := fmt.Sprintf("%x", h.Sum(nil))
t, _ := template.ParseFiles("login.gtpl")
t.Execute(w, token)
} else {
//请求的是登陆数据,那么执行登陆的逻辑判断
r.ParseForm()
token := r.Form.Get("token")
if token != "" {
//验证token的合法性
} else {
//不存在token报错
}
fmt.Println("username length:", len(r.Form["username"][0]))
fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) //输出到服务器端
fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password")))
template.HTMLEscape(w, []byte(r.Form.Get("username"))) //输出到客户端
}
}
上面的代码输出到页面的源码如下:
![](images/4.4.token.png?raw=true)
我们看到token已经有输出值你可以不断的刷新可以看到这个值在不断的变化。这样就保证了每次显示form表单的时候都是唯一的用户递交的表单保持了唯一性。
我们的解决方案可以防止非恶意的攻击,并能使恶意用户暂时不知所措,然后,它却不能排除所有的欺骗性的动机,对此类情况还需要更复杂的工作。
## links
* [目录](<preface.md>)
* 上一节: [预防跨站脚本](<4.3.md>)
* 下一节: [处理文件上传](<4.5.md>)
## LastModified
* $Id$

87
4.5.md Normal file
View File

@@ -0,0 +1,87 @@
#4.5处理文件上传
你想处理一个由用户上传的文件。例如你正在建设一个类似Instagram 的网站,所以需要处理和存储用户提供的照片。我们该如何处理呢?
文件要能够上传首先第一步就是要修改form的`enctype`属性,`enctype`属性有如下三种情况:
application/x-www-form-urlencoded 表示在发送前编码所有字符(默认)
multipart/form-data 不对字符编码。在使用包含文件上传控件的表单时,必须使用该值。
text/plain 空格转换为 "+" 加号,但不对特殊字符编码。
我们如果要使得文件能够上传那么我们的html应该如下所示
<html>
<head>
<title>上传文件</title>
</head>
<body>
<form enctype="multipart/form-data" action="http://127.0.0.1:9090/upload" method="post">
<input type="file" name="file" />
<input type="hidden" name="token" value="{{.}}">
<input type="submit" value="upload" />
</form>
</body>
</html>
我们增加一个handlerFunc如下
http.HandleFunc("/upload", upload)
// upload
func upload(w http.ResponseWriter, r *http.Request) {
fmt.Println("method:", r.Method) //获取请求的方法
if r.Method == "GET" {
crutime := time.Now().Unix()
h := md5.New()
io.WriteString(h, strconv.FormatInt(crutime, 10))
token := fmt.Sprintf("%x", h.Sum(nil))
t, _ := template.ParseFiles("upload.gtpl")
t.Execute(w, token)
} else {
r.ParseMultipartForm(32 << 20)
file, handler, err := r.FormFile("file")
if err != nil {
fmt.Println(err)
}
defer file.Close()
fmt.Fprintf(w, "%v", handler.Header)
f, err := os.OpenFile("./test/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
panic(err)
}
defer f.Close()
io.Copy(f, file)
}
}
通过上面的代码我们可以看到,文件上传我们需要调用`r.ParseMultipartForm`,里面的参数表示maxMemory调用`ParseMultipartForm`之后我们上传的文件存储在设置的那么大的内存里面,如果文件大小超过了这个内存,那么剩下的部分存储在系统的临时文件中。我们可以通过`r.FormFile`获取上面的文件句柄,然后实例中使用了`io.Copy`来存储文件。
>获取其他非文件字段信息的时候就不需要调用`r.ParseForm`,因为在需要的时候Go自动会去调用。而且`ParseMultipartForm`调用一次之后,后面再次调用不会再有效果。
通过上面的实例我们可以看到我们上传文件主要三步处理:
- 1、表单中增加enctype="multipart/form-data"
- 2、服务端调用`r.ParseMultipartForm`,把上传的文件存储在内存和临时文件中
- 3、使用`r.FormFile`获取文件句柄,然后对文件进行存储等处理。
文件handler是multipart.FileHeader,里面存储了如下结构信息
type FileHeader struct {
Filename string
Header textproto.MIMEHeader
// contains filtered or unexported fields
}
我们通过上面的实例代码打印出来上传文件的信息如下
![](images/4.5.upload2.png?raw=true)
## links
* [目录](<preface.md>)
* 上一节: [防止多次递交表单](<4.4.md>)
* 下一节: [小结](<4.6.md>)
## LastModified
* $Id$

12
4.6.md Normal file
View File

@@ -0,0 +1,12 @@
#4.6 小结
这一章里面我们学习了Go里面如何处理表单信息我们通过一个登陆、一个上传例子展示了Go处理form表单信息处理文件上传的能力。但是在处理表单过程中我们需要验证用户输入的信息考虑到网站安全、数据过滤就变得相当重要了因此专门一个小节讲解了各方面的数据过滤顺带讲了一下Go里面对正则的处理。
通过这一章能够让你了解客户端和服务器端如何进行数据的交互,让客户端的数据进入我们的系服务器统,让我们系统处理之后的数据展现给客户端。
## links
* [目录](<preface.md>)
* 上一节: [处理文件上传](<4.4.md>)
* 下一章: [访问数据库](<5.md>)
## LastModified
* $Id$

BIN
images/4.4.token.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images/4.5.upload.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
images/4.5.upload2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB