Skip to content

Commit 7c18d1d

Browse files
committed
完成了第四章的写作
1 parent c0adec3 commit 7c18d1d

File tree

7 files changed

+161
-14
lines changed

7 files changed

+161
-14
lines changed

4.3.md

+7-6
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,24 @@
66

77
对XSS最佳的防护应该结合以下两种方法:验证所有输入数据,有效检测攻击(这个我们前面小节已经有过介绍);对所有输出数据进行适当的编码,以防止任何已成功注入的脚本在浏览器端运行。
88

9-
那么Go里面是怎么做这个有效防护的呢?Go的html/template里面带有下面几个函数可以帮你转移
9+
那么Go里面是怎么做这个有效防护的呢?Go的html/template里面带有下面几个函数可以帮你转义
10+
11+
- func HTMLEscape(w io.Writer, b []byte) //把b进行转义之后写到w
12+
- func HTMLEscapeString(s string) string //转义s之后返回结果字符串
13+
- func HTMLEscaper(args ...interface{}) string //支持多个参数一起转义,返回结果字符串
1014

11-
- func HTMLEscape(w io.Writer, b []byte) //把需要转移的b进行转义之后写到w
12-
- func HTMLEscapeString(s string) string //转移s之后返回转义的字符串
13-
- func HTMLEscaper(args ...interface{}) string //支持多个参数一起转义,然后返回一个字符串
1415

1516
我们看4.1小节的例子
1617

1718
fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) //输出到服务器端
1819
fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password")))
1920
template.HTMLEscape(w, []byte(r.Form.Get("username"))) //输出到客户端
2021

21-
我们看到最后输出到客户端的代码如下
22+
如果我们输入的username是`<script>alert()</script>`,那么我们可以在浏览器上面看到输出如下所示
2223

2324
![](images/4.3.escape.png?raw=true)
2425

25-
那么我们在输出我们的模板的时候怎么处理的呢?Go的html/template包现在默认就帮你过滤了html元素,但是有时候你又想输出这样的信息,text/template也支持。请看下面的例子所示
26+
那么我们在输出我们的模板的时候怎么处理的呢?Go的html/template包默认帮你过滤了html元素,但是有时候你又想输出这样的信息,请使用text/template。请看下面的例子所示
2627

2728
import "text/template"
2829
...

4.4.md

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

4.5.md

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#4.5处理文件上传
2+
你想处理一个由用户上传的文件。例如,你正在建设一个类似Instagram 的网站,所以需要处理和存储用户提供的照片。我们该如何处理呢?
3+
4+
文件要能够上传,首先第一步就是要修改form的`enctype`属性,`enctype`属性有如下三种情况:
5+
6+
application/x-www-form-urlencoded 表示在发送前编码所有字符(默认)
7+
multipart/form-data 不对字符编码。在使用包含文件上传控件的表单时,必须使用该值。
8+
text/plain 空格转换为 "+" 加号,但不对特殊字符编码。
9+
10+
我们如果要使得文件能够上传,那么我们的html应该如下所示
11+
12+
<html>
13+
<head>
14+
<title>上传文件</title>
15+
</head>
16+
<body>
17+
<form enctype="multipart/form-data" action="http://127.0.0.1:9090/upload" method="post">
18+
<input type="file" name="file" />
19+
<input type="hidden" name="token" value="{{.}}">
20+
<input type="submit" value="upload" />
21+
</form>
22+
</body>
23+
</html>
24+
25+
我们增加一个handlerFunc如下:
26+
27+
http.HandleFunc("/upload", upload)
28+
29+
// upload
30+
func upload(w http.ResponseWriter, r *http.Request) {
31+
fmt.Println("method:", r.Method) //获取请求的方法
32+
if r.Method == "GET" {
33+
crutime := time.Now().Unix()
34+
h := md5.New()
35+
io.WriteString(h, strconv.FormatInt(crutime, 10))
36+
token := fmt.Sprintf("%x", h.Sum(nil))
37+
38+
t, _ := template.ParseFiles("upload.gtpl")
39+
t.Execute(w, token)
40+
} else {
41+
r.ParseMultipartForm(32 << 20)
42+
file, handler, err := r.FormFile("file")
43+
if err != nil {
44+
fmt.Println(err)
45+
}
46+
defer file.Close()
47+
fmt.Fprintf(w, "%v", handler.Header)
48+
f, err := os.OpenFile("./test/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
49+
if err != nil {
50+
panic(err)
51+
}
52+
defer f.Close()
53+
io.Copy(f, file)
54+
}
55+
}
56+
57+
通过上面的代码我们可以看到,文件上传我们需要调用`r.ParseMultipartForm`,里面的参数表示maxMemory,调用`ParseMultipartForm`之后我们上传的文件存储在设置的那么大的内存里面,如果文件大小超过了这个内存,那么剩下的部分存储在系统的临时文件中。我们可以通过`r.FormFile`获取上面的文件句柄,然后实例中使用了`io.Copy`来存储文件。
58+
59+
>获取其他非文件字段信息的时候就不需要调用`r.ParseForm`,因为在需要的时候Go自动会去调用。而且`ParseMultipartForm`调用一次之后,后面再次调用不会再有效果。
60+
61+
通过上面的实例我们可以看到我们上传文件主要三步处理:
62+
63+
- 1、表单中增加enctype="multipart/form-data"
64+
- 2、服务端调用`r.ParseMultipartForm`,把上传的文件存储在内存和临时文件中
65+
- 3、使用`r.FormFile`获取文件句柄,然后对文件进行存储等处理。
66+
67+
文件handler是multipart.FileHeader,里面存储了如下结构信息
68+
69+
type FileHeader struct {
70+
Filename string
71+
Header textproto.MIMEHeader
72+
// contains filtered or unexported fields
73+
}
74+
75+
我们通过上面的实例代码打印出来上传文件的信息如下
76+
77+
![](images/4.5.upload2.png?raw=true)
78+
79+
80+
81+
## links
82+
* [目录](<preface.md>)
83+
* 上一节: [防止多次递交表单](<4.4.md>)
84+
* 下一节: [小结](<4.6.md>)
85+
86+
## LastModified
87+
* $Id$

4.6.md

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

images/4.4.token.png

14.3 KB
Loading

images/4.5.upload.png

12.7 KB
Loading

images/4.5.upload2.png

6.44 KB
Loading

0 commit comments

Comments
 (0)