diff --git a/4.3.md b/4.3.md
index cb705646..783e889f 100644
--- a/4.3.md
+++ b/4.3.md
@@ -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是``,那么我们可以在浏览器上面看到输出如下所示:

-那么我们在输出我们的模板的时候怎么处理的呢?Go的html/template包现在默认就帮你过滤了html元素,但是有时候你又想输出这样的信息,text/template也支持。请看下面的例子所示
+那么我们在输出我们的模板的时候怎么处理的呢?Go的html/template包默认帮你过滤了html元素,但是有时候你又想输出这样的信息,请使用text/template。请看下面的例子所示
import "text/template"
...
diff --git a/4.4.md b/4.4.md
index f947d8cf..8de992b8 100644
--- a/4.4.md
+++ b/4.4.md
@@ -1,9 +1,56 @@
-#4.4防止多次递交表单
-
-## links
- * [目录]()
- * 上一节: [预防跨站脚本](<4.3.md>)
- * 下一节: [处理文件上传](<4.5.md>)
-
-## LastModified
+#4.4防止多次递交表单
+
+不知道你是否曾经看到过一个论坛或者博客,在一个帖子或者文章后面出现多条重复的记录,这些大多数是因为用户重复递交了留言的表单引起的。由于种种原因,用户经常会重复递交表单。通常这只是鼠标的误操作,如双击了递交按钮,也可能是为了编辑或者再次核对填写过的信息,点击了浏览器的后退按钮,然后又再次点击了递交按钮而不是浏览器的前进按钮。当然,也可能是故意的——比如,在某项在线调查或者博彩活动中重复投票。那我们如何有效的防止用户多次递交相同的表单呢?
+
+解决方案是在表单中添加一个带有唯一值得隐藏字段。在验证表单时,先检查带有该惟一值的表单是否已经递交过了。如果是,拒绝再次递交;如果不是,则处理表单进行逻辑处理。另外,如果是采用了Ajax模式递交表单的话,当表单递交后,通过javascript来禁用表单的递交按钮。
+
+我继续拿4.2小节的例子优化:
+
+ 用户名:
+ 密码:
+
+
+
+我们看到在模版里面我们增加了一个隐藏字段`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"))) //输出到客户端
+ }
+ }
+
+上面的代码输出到页面的源码如下:
+
+
+
+我们看到token已经有输出值,你可以不断的刷新,可以看到这个值在不断的变化。这样就保证了每次显示form表单的时候都是唯一的,用户递交的表单保持了唯一性。
+
+我们的解决方案可以防止非恶意的攻击,并能使恶意用户暂时不知所措,然后,它却不能排除所有的欺骗性的动机,对此类情况还需要更复杂的工作。
+
+## links
+ * [目录]()
+ * 上一节: [预防跨站脚本](<4.3.md>)
+ * 下一节: [处理文件上传](<4.5.md>)
+
+## LastModified
* $Id$
\ No newline at end of file
diff --git a/4.5.md b/4.5.md
new file mode 100644
index 00000000..888eec77
--- /dev/null
+++ b/4.5.md
@@ -0,0 +1,87 @@
+#4.5处理文件上传
+你想处理一个由用户上传的文件。例如,你正在建设一个类似Instagram 的网站,所以需要处理和存储用户提供的照片。我们该如何处理呢?
+
+文件要能够上传,首先第一步就是要修改form的`enctype`属性,`enctype`属性有如下三种情况:
+
+ application/x-www-form-urlencoded 表示在发送前编码所有字符(默认)
+ multipart/form-data 不对字符编码。在使用包含文件上传控件的表单时,必须使用该值。
+ text/plain 空格转换为 "+" 加号,但不对特殊字符编码。
+
+我们如果要使得文件能够上传,那么我们的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
+ }
+
+我们通过上面的实例代码打印出来上传文件的信息如下
+
+
+
+
+
+## links
+ * [目录]()
+ * 上一节: [防止多次递交表单](<4.4.md>)
+ * 下一节: [小结](<4.6.md>)
+
+## LastModified
+ * $Id$
\ No newline at end of file
diff --git a/4.6.md b/4.6.md
new file mode 100644
index 00000000..4c562cd3
--- /dev/null
+++ b/4.6.md
@@ -0,0 +1,12 @@
+#4.6 小结
+这一章里面我们学习了Go里面如何处理表单信息,我们通过一个登陆、一个上传例子展示了Go处理form表单信息,处理文件上传的能力。但是在处理表单过程中我们需要验证用户输入的信息,考虑到网站安全、数据过滤就变得相当重要了,因此专门一个小节讲解了各方面的数据过滤,顺带讲了一下Go里面对正则的处理。
+
+通过这一章能够让你了解客户端和服务器端如何进行数据的交互,让客户端的数据进入我们的系服务器统,让我们系统处理之后的数据展现给客户端。
+
+## links
+ * [目录]()
+ * 上一节: [处理文件上传](<4.4.md>)
+ * 下一章: [访问数据库](<5.md>)
+
+## LastModified
+ * $Id$
\ No newline at end of file
diff --git a/images/4.4.token.png b/images/4.4.token.png
new file mode 100644
index 00000000..b52cc1d1
Binary files /dev/null and b/images/4.4.token.png differ
diff --git a/images/4.5.upload.png b/images/4.5.upload.png
new file mode 100644
index 00000000..e5766e38
Binary files /dev/null and b/images/4.5.upload.png differ
diff --git a/images/4.5.upload2.png b/images/4.5.upload2.png
new file mode 100644
index 00000000..06460446
Binary files /dev/null and b/images/4.5.upload2.png differ