新建一個 testform2.gtpl 文件,內容如下:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>修改資料</title>
</head>
<body>
<form action="/info" method="post" enctype="multipart/form-data">
頭像:<input type="file" name="userheader">
<br>
用戶名:<input type="text" name="username">
<br>
密碼:<input type="password" name="password">
<br>
年齡:<input type="text" name="age">
<br>
郵箱:<input type="text" name="email">
<br>
手機號碼:<input type="text" name="mobile">
<br>
身份證號碼:<input type="text" name="usercard">
<br>
測試xss:<input type="text" name="xss">
<br>
性別:
<select name="sex" >
<option value="0">無</option>
<option value="1">男</option>
<option value="2">女</option>
</select>
<br>
興趣:
<br>
<input type="checkbox" name="interest" value="no">無
<input type="checkbox" name="interest" value="football">足球
<input type="checkbox" name="interest" value="basketball">籃球
<input type="checkbox" name="interest" value="tennis">網球
<br>
<input type="hidden" name="token" value="{{.}}">
<input type="submit" value="提交">
</form>
</body>
</html>
新建一個 testform2.go 文件,內容如下:
package main
import (
"fmt"
"crypto/md5"
"html/template"
texttemplate "text/template"
"io"
"os"
"log"
"time"
"net/http"
"strconv"
"regexp"
"unicode"
)
//判斷是否在切片中
func In_slice(val string, slice []string) bool {
for _, v := range slice {
if v == val {
return true
}
}
return false
}
//判斷兩個切片的差異
//返回差異的數組
func Slice_diff(slice1, slice2 []string) (diffslice []string) {
for _, v := range slice1 {
if !In_slice(v, slice2) {
diffslice = append(diffslice, v)
}
}
return
}
func sayhelloName(w http.ResponseWriter,r *http.Request) {
//下面這個寫入到w 的是輸出到客戶端的
fmt.Fprintf(w,"hello testform2.go")
}
//驗證表單輸入
func info(w http.ResponseWriter,r *http.Request) {
fmt.Println("method:",r.Method) //獲取請求的方法
if r.Method == "GET"{
curtime := time.Now().Unix()
h := md5.New()
io.WriteString(h,strconv.FormatInt(curtime,10))
token := fmt.Sprintf("%x",h.Sum(nil)) //生成token
//設置cookie
expiration := time.Now()
expiration = expiration.AddDate(1, 0, 0)
cookie := http.Cookie{Name: "username", Value: "go-cookie", Expires: expiration}
http.SetCookie(w, &cookie)
t,_ := template.ParseFiles("testform2.gtpl")
t.Execute(w,token) //把token 寫入到模板中
}else{
//讀取cookie
cookie, _ := r.Cookie("username")
fmt.Println("cookie:",cookie)
//防止多次重複提交表單
//解決方案是在表單中添加一個帶有唯一值的隱藏字段。
// 在驗證表單時,先檢查帶有該唯一值的表單是否已經遞交過了。
// 如果是,拒絕再次遞交;如果不是,則處理表單進行邏輯處理。
res1 := verifyToken(w,r)
if !res1 {
return
}
//表單驗證
res2 := formValidate(w,r)
if !res2 {
return
}
//預防跨站腳本
res3 := xss(w,r)
if !res3 {
return
}
//獲取上傳文件
res4 := uploadHandler(w,r)
if !res4{
return
}
}
}
//防止多次重複提交表單
func verifyToken(w http.ResponseWriter,r *http.Request) bool{
//r.ParseForm() //普通表單
r.ParseMultipartForm(32<<20) //加入了 enctype="multipart/form-data" 的表單
token := r.Form.Get("token")
fmt.Println("token:",token)
if token != ""{
// 驗證 token 的合法性
if len(token) <10{
fmt.Fprintf(w,"token驗證失敗")
return false
}
fmt.Fprintf(w,"token驗證通過")
}else{
//不存在token 報錯
fmt.Fprintf(w,"token驗證失敗")
return false
}
fmt.Fprintf(w,"\n")
return true
}
//表單驗證
func formValidate(w http.ResponseWriter,r *http.Request) bool{
//r.ParseForm() //解析url傳遞的參數,對於POST則解析響應包的主體(request body)
r.ParseMultipartForm(32<<20) //加入了 enctype="multipart/form-data" 的表單
//請求的是登錄數據,name執行登錄的邏輯判斷
fmt.Println("username:",r.Form["username"])
fmt.Println("password:",r.Form["password"])
fmt.Println("age:",r.Form["age"])
fmt.Println("email:",r.Form["email"])
fmt.Println("mobile:",r.Form["mobile"])
fmt.Println("sex:",r.Form["sex"])
fmt.Println("interest:",r.Form["interest"])
fmt.Println("usercard:",r.Form["usercard"])
//驗證表單數據--------------------------------------------------------
//必填字段
if len(r.Form["username"][0]) == 0{
//返回到客戶端
fmt.Fprintf(w,"用戶名不能爲空")
return false
}
//unicode判斷中文
for _, val := range r.Form.Get("username"){
if unicode.Is(unicode.Scripts["Han"], val) {
//有中文
fmt.Fprintf(w,"unicode姓名不能有中文\n")
}
}
//正則判斷中文
chiReg := regexp.MustCompile("[\u4e00-\u9fa5]+")
for _, val := range r.Form.Get("username") {
if chiReg.MatchString(string(val)){
//有中文
fmt.Fprintf(w,"regexp姓名不能有中文\n")
return false
}
}
//判斷英文
match2,_ := regexp.MatchString("^[a-zA-Z]+$",r.Form.Get("username"))
if !match2{
fmt.Fprintf(w,"姓名只能是英文字母")
return false
}
//使用轉化判斷數字
getint,err := strconv.Atoi(r.Form.Get("age"))
if err != nil{
//數字轉化出錯了,那麼可能就不是數字了
fmt.Fprintf(w,"strconv年齡只能是數字\n")
}
//使用正則判斷數字 ,應該儘量避免使用正則表達式,因爲使用正則表達式的速度會比較慢
m,_ := regexp.MatchString("^[0-9]+$",r.Form.Get("age"));
if !m {
fmt.Fprintf(w,"regexp年齡只能是數字\n")
return false
}
//判斷數字的大小範圍
if getint > 200{
fmt.Fprintf(w,"年齡不能大於200")
return false
}
//判斷電子郵件
match3,_ := regexp.MatchString(`^([\w\.\_]{2,})@(\w{1,})\.([a-z]{2,4})$`,r.Form.Get("email"))
if !match3{
fmt.Fprintf(w,"電子郵箱格式不正確")
return false
}
//判斷手機號碼
match4,_ := regexp.MatchString(`^1[0-9][0-9]\d{8}$`,r.Form.Get("mobile"))
if !match4{
fmt.Fprintf(w,"手機號碼不正確")
return false
}
//判斷身份證號碼
//15位 或18位
usercard := r.Form.Get("usercard")
match5,_ := regexp.MatchString(`^(\d{15})$`,usercard)
match6,_ := regexp.MatchString(`^(\d{17})([0-9]|X)$`,usercard)
fmt.Println(usercard)
fmt.Println(len(usercard))
fmt.Println(!match6)
if len(usercard) <= 15 && !match5{
fmt.Fprintf(w,"身份證不正確")
return false
}else if len(usercard) > 15 && !match6{
fmt.Fprintf(w,"身份證不正確")
return false
}
//判斷下拉菜單的值是否符合預設值
sex_slice := []string{"1","2"}
sex_value := r.Form.Get("sex")
is_set := 0 //是否符合預設值 ,1 是,0否
for _,val := range sex_slice{
if sex_value == val{
is_set = 1
}
}
if is_set == 0{
fmt.Fprintf(w,"性別不正確")
return false
}
//判斷複選框
interest_slice := []string{"football","basketball","tennis"}
interest := r.Form["interest"]
array := Slice_diff(interest,interest_slice)
fmt.Println("interest:",array)
if array != nil{
//有差異
fmt.Fprintf(w,"興趣選擇不正確")
return false
}
return true
}
//預防跨站腳本
func xss(w http.ResponseWriter,r *http.Request) bool{
//template.HTMLEscape(w io.Writer, b []byte) //把b進行轉義之後寫到w
//template.HTMLEscapeString(s string) string //轉義s之後返回結果字符串
//template.HTMLEscaper(args ...interface{}) string //支持多個參數一起轉義,返回結果字符串
//輸出到服務器端
fmt.Println("xss:", template.HTMLEscapeString(r.Form.Get("xss")))
//轉義後輸出到客戶端
template.HTMLEscape(w, []byte(r.Form.Get("xss")))
fmt.Fprintf(w, "\n") //換行
//轉義後輸出到客戶端
t,err := template.New("foo").Parse(`{{define "T"}}hello,{{.}}!{{end}}`)
err = t.ExecuteTemplate(w,"T","<script>alert('you have been pwned')</script>")
if err != nil{
fmt.Println(err)
}
fmt.Fprintf(w, "\n") //換行
//使用 text/template 完全輸出到客戶端
t1,err1 := texttemplate.New("foo").Parse(`{{define "T"}}hello,{{.}}!{{end}}`)
err1 = t1.ExecuteTemplate(w,"T","<script>alert('you have been pwned')</script>")
if err1 != nil{
fmt.Println(err1)
}
fmt.Fprintf(w, "\n") //換行
//使用 html/template template.HTML 完全輸出到客戶端
t2, err2 := template.New("foo").Parse(`{{define "T"}}hello,{{.}}!{{end}}`)
err2 = t2.ExecuteTemplate(w,"T",template.HTML("<script>alert('you have been HTML')</script>"))
if err2 != nil{
fmt.Println(err2)
}
return true
}
//獲取上傳文件
func uploadHandler(w http.ResponseWriter,r *http.Request) bool{
//通過上面的代碼可以看到,處理文件上傳我們需要調用r.ParseMultipartForm,
// 裏面的參數表示maxMemory,調用ParseMultipartForm之後,
// 上傳的文件存儲在maxMemory大小的內存裏面,
// 如果文件大小超過了maxMemory,那麼剩下的部分將存儲在系統的臨時文件中。
// 我們可以通過r.FormFile獲取上面的文件句柄,然後實例中使用了io.Copy來存儲文件。
r.ParseMultipartForm(32<<20)
file,handler,err := r.FormFile("userheader")
//文件的handler 是multipart.FileHeader ,裏面存儲瞭如下結構信息
/*
type FileHeader struct{
Filename string
Header textproto.MIMEHeader
//contains filtered or unexported fields
}
*/
if err != nil{
fmt.Println(err)
return false
}
defer file.Close()
fmt.Fprintf(w,"%v",handler.Header)
f,err := os.OpenFile("./"+handler.Filename,os.O_WRONLY|os.O_CREATE,0777)
if err != nil{
fmt.Println(err)
return false
}
defer f.Close()
io.Copy(f,file)
return true
}
func main() {
http.HandleFunc("/",sayhelloName) //設置路由
http.HandleFunc("/info",info) //設置路由
err := http.ListenAndServe(":6665",nil) //設置監聽的端口
if err != nil{
log.Fatal("ListenAndServe:",err)
}
}
運行 testform2.go ,啓動服務器 :
[root@izj6c4jirdug8kh3uo6rdez goweb]# go run testform2.go
token驗證通過
<script>alert(123);</script>
hello,<script>alert('you have been pwned')</script>!
hello,<script>alert('you have been pwned')</script>!
hello,<script>alert('you have been HTML')</script>!map[Content-Disposition:[form-data; name="userheader"; filename="a435c26724359a53730a52d919f106a4.png"] Content-Type:[image/png]]
服務器端即可看到表單參數:
method: POST
token: 11112bd3ffe4ba8ddaea713ef49f9806
username: [awef]
password: []
age: [3]
email: [[email protected]]
mobile: [12312345678]
sex: [1]
interest: [football]
usercard: [123456789123456123]
123456789123456123
18
false
interest: []
xss: <script>alert(123);</script>
參考:https://www.golang123.com/book/9?chapterID=158