Start translation to brazilian portuguese
This commit is contained in:
54
pt-br/code/src/apps/ch.4.4/main.go
Normal file
54
pt-br/code/src/apps/ch.4.4/main.go
Normal file
@@ -0,0 +1,54 @@
|
||||
// Example code for Chapter 3.2 from "Build Web Application with Golang"
|
||||
// Purpose: Shows how to prevent duplicate submissions by using tokens
|
||||
// Example code for Chapter 4.4 based off the code from Chapter 4.2
|
||||
// Run `go run main.go` then access http://localhost:9090
|
||||
package main
|
||||
|
||||
import (
|
||||
"apps/ch.4.4/nonce"
|
||||
"apps/ch.4.4/validator"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
PORT = "9090"
|
||||
HOST_URL = "http://localhost:" + PORT
|
||||
)
|
||||
|
||||
var submissions nonce.Nonces
|
||||
var t *template.Template
|
||||
|
||||
func index(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, HOST_URL+"/profile", http.StatusTemporaryRedirect)
|
||||
}
|
||||
func profileHandler(w http.ResponseWriter, r *http.Request) {
|
||||
t.ExecuteTemplate(w, "profile", submissions.NewNonce())
|
||||
}
|
||||
func checkProfile(w http.ResponseWriter, r *http.Request) {
|
||||
var errs validator.Errors
|
||||
r.ParseForm()
|
||||
token := r.Form.Get("token")
|
||||
if err := submissions.CheckThenMarkToken(token); err != nil {
|
||||
errs = validator.Errors{[]error{err}}
|
||||
} else {
|
||||
p := validator.ProfilePage{&r.Form}
|
||||
errs = p.GetErrors()
|
||||
}
|
||||
t.ExecuteTemplate(w, "submission", errs)
|
||||
}
|
||||
func init() {
|
||||
submissions = nonce.New()
|
||||
t = template.Must(template.ParseFiles("profile.gtpl", "submission.gtpl"))
|
||||
}
|
||||
func main() {
|
||||
http.HandleFunc("/", index)
|
||||
http.HandleFunc("/profile", profileHandler)
|
||||
http.HandleFunc("/checkprofile", checkProfile)
|
||||
|
||||
err := http.ListenAndServe(":"+PORT, nil) // setting listening port
|
||||
if err != nil {
|
||||
log.Fatal("ListenAndServe: ", err)
|
||||
}
|
||||
}
|
||||
70
pt-br/code/src/apps/ch.4.4/nonce/main.go
Normal file
70
pt-br/code/src/apps/ch.4.4/nonce/main.go
Normal file
@@ -0,0 +1,70 @@
|
||||
// A nonce is a number or string used only once.
|
||||
// This is useful for generating a unique token for login pages to prevent duplicate submissions.
|
||||
package nonce
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Contains a unique token
|
||||
type Nonce struct {
|
||||
Token string
|
||||
}
|
||||
|
||||
// Keeps track of marked/used tokens
|
||||
type Nonces struct {
|
||||
hashs map[string]bool
|
||||
}
|
||||
|
||||
func New() Nonces {
|
||||
return Nonces{make(map[string]bool)}
|
||||
}
|
||||
func (n *Nonces) NewNonce() Nonce {
|
||||
return Nonce{n.NewToken()}
|
||||
}
|
||||
|
||||
// Returns a new unique token
|
||||
func (n *Nonces) NewToken() string {
|
||||
t := createToken()
|
||||
for n.HasToken(t) {
|
||||
t = createToken()
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Checks if token has been marked.
|
||||
func (n *Nonces) HasToken(token string) bool {
|
||||
return n.hashs[token] == true
|
||||
}
|
||||
func (n *Nonces) MarkToken(token string) {
|
||||
n.hashs[token] = true
|
||||
}
|
||||
func (n *Nonces) CheckToken(token string) error {
|
||||
if token == "" {
|
||||
return errors.New("No token supplied")
|
||||
}
|
||||
if n.HasToken(token) {
|
||||
return errors.New("Duplicate submission.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (n *Nonces) CheckThenMarkToken(token string) error {
|
||||
defer n.MarkToken(token)
|
||||
if err := n.CheckToken(token); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func createToken() string {
|
||||
h := md5.New()
|
||||
now := time.Now().Unix()
|
||||
io.WriteString(h, strconv.FormatInt(now, 10))
|
||||
io.WriteString(h, strconv.FormatInt(rand.Int63(), 10))
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
85
pt-br/code/src/apps/ch.4.4/profile.gtpl
Normal file
85
pt-br/code/src/apps/ch.4.4/profile.gtpl
Normal file
@@ -0,0 +1,85 @@
|
||||
{{define "profile"}}<!DOCTYPE html>
|
||||
<html>
|
||||
<style>
|
||||
.row{
|
||||
display: table-row;
|
||||
}
|
||||
.cell{
|
||||
display: table-cell;
|
||||
}
|
||||
.required{
|
||||
color: red
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<h2>Profile Setup:</h2>
|
||||
<form action="/checkprofile" method="POST">
|
||||
<div class="row">
|
||||
<div class="cell"><span class="required">*</span>User Name:</div>
|
||||
<div class="cell"><input type="text" name="username" id="username" required/></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell"><span class="required">*</span>Age:</div>
|
||||
<div class="cell"><input type="number" min="13" max="130" name="age" id="age" size="3" required/></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell"><span class="required">*</span>Email:</div>
|
||||
<div class="cell"><input type="email" name="email" id="email" placeholder="john@example.com" required/></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell"><span class="required">*</span>Birth day:</div>
|
||||
<div class="cell">
|
||||
<input type="date" name="birthday" id="birthday" placeholder="MM/DD/YYYY" required/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Gender:</div>
|
||||
<div class="cell">
|
||||
<label for="gender_male">
|
||||
Male: <input type="radio" name="gender" value="m" id="gender_male"/>
|
||||
</label>
|
||||
<label for="gender_female">
|
||||
Female: <input type="radio" name="gender" value="f" id="gender_female"/>
|
||||
</label>
|
||||
<label for="gender_na">
|
||||
N/A: <input type="radio" name="gender" value="na" id="gender_na"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Siblings:</div>
|
||||
<div class="cell">
|
||||
<label for="sibling_male">
|
||||
Brother: <input type="checkbox" name="sibling" value="m" id="sibling_male"/>
|
||||
</label>
|
||||
<label for="sibling_female">
|
||||
Sister: <input type="checkbox" name="sibling" value="f" id="sibling_female"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Shirt Size:</div>
|
||||
<div class="cell">
|
||||
<select id="shirt_size" >
|
||||
<option></option>
|
||||
<option value="s">Small</option>
|
||||
<option value="m">Medium</option>
|
||||
<option value="l">Large</option>
|
||||
<option value="xl">X-Large</option>
|
||||
<option value="xxl">XX-Large</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Chinese Name:</div>
|
||||
<div class="cell"><input type="text" name="chineseName" id="chineseName"/></div>
|
||||
</div>
|
||||
<br/>
|
||||
<span class="required">*</span>Required
|
||||
<br/>
|
||||
<input type="hidden" name="token" value="{{.Token}}"/>
|
||||
<input type="submit" value="Submit" id="submitBtn"/>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
17
pt-br/code/src/apps/ch.4.4/submission.gtpl
Normal file
17
pt-br/code/src/apps/ch.4.4/submission.gtpl
Normal file
@@ -0,0 +1,17 @@
|
||||
{{define "submission"}}<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
{{if .Errors}}
|
||||
<h2>Errors:</h2>
|
||||
<ol>
|
||||
{{range .Errors}}
|
||||
<li>{{.}}</li>
|
||||
{{end}}
|
||||
</ol>
|
||||
{{else}}
|
||||
Profile successfully submitted.<br/>
|
||||
Note: Refreshing the page will produce a duplicate entry.
|
||||
{{end}}
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
175
pt-br/code/src/apps/ch.4.4/validator/main.go
Normal file
175
pt-br/code/src/apps/ch.4.4/validator/main.go
Normal file
@@ -0,0 +1,175 @@
|
||||
// This file contains all the validators to validate the profile page.
|
||||
package validator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ProfilePage struct {
|
||||
Form *url.Values
|
||||
}
|
||||
type Errors struct {
|
||||
Errors []error
|
||||
}
|
||||
|
||||
// Goes through the form object and validates each element.
|
||||
// Attachs an error to the output if validation fails.
|
||||
func (p *ProfilePage) GetErrors() Errors {
|
||||
errs := make([]error, 0, 10)
|
||||
if *p.Form == nil || len(*p.Form) < 1 {
|
||||
errs = append(errs, errors.New("No data was received. Please submit from the profile page."))
|
||||
}
|
||||
for name, val := range *p.Form {
|
||||
if fn, ok := stringValidator[name]; ok {
|
||||
if err := fn(strings.Join(val, "")); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
} else {
|
||||
if fn, ok := stringsValidator[name]; ok {
|
||||
if err := fn(val); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Errors{errs}
|
||||
}
|
||||
|
||||
const (
|
||||
// Used for parsing the time
|
||||
mmddyyyyForm = "01/02/2006" // we want the date sent in this format
|
||||
yyyymmddForm = "2006-01-02" // However, HTML5 pages send the date in this format
|
||||
)
|
||||
|
||||
var stringValidator map[string]func(string) error = map[string]func(string) error{
|
||||
// parameter name : validator reference
|
||||
"age": checkAge,
|
||||
"birthday": checkDate,
|
||||
"chineseName": checkChineseName,
|
||||
"email": checkEmail,
|
||||
"gender": checkGender,
|
||||
"shirtsize": checkShirtSize,
|
||||
"username": checkUsername,
|
||||
}
|
||||
var stringsValidator map[string]func([]string) error = map[string]func([]string) error{
|
||||
// parameter name : validator reference
|
||||
"sibling": checkSibling,
|
||||
}
|
||||
|
||||
// Returns true if slices have a common element
|
||||
func doSlicesIntersect(s1, s2 []string) bool {
|
||||
if s1 == nil || s2 == nil {
|
||||
return false
|
||||
}
|
||||
for _, str := range s1 {
|
||||
if isElementInSlice(str, s2) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
func isElementInSlice(str string, sl []string) bool {
|
||||
if sl == nil || str == "" {
|
||||
return false
|
||||
}
|
||||
for _, v := range sl {
|
||||
if v == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Checks if all the characters are chinese characters. Won't check if empty.'
|
||||
func checkChineseName(str string) error {
|
||||
if str != "" {
|
||||
if m, _ := regexp.MatchString("^[\\x{4e00}-\\x{9fa5}]+$", strings.Trim(str, " ")); !m {
|
||||
return errors.New("Please make sure that the chinese name only contains chinese characters.")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks if a user name exist.
|
||||
func checkUsername(str string) error {
|
||||
if strings.Trim(str, " ") == "" {
|
||||
return errors.New("Please enter a username.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if age is a number and between 13 and 130
|
||||
func checkAge(str string) error {
|
||||
age, err := strconv.Atoi(str)
|
||||
if str == "" || err != nil {
|
||||
return errors.New("Please enter a valid age.")
|
||||
}
|
||||
if age < 13 {
|
||||
return errors.New("You must be at least 13 years of age to submit.")
|
||||
}
|
||||
if age > 130 {
|
||||
return errors.New("You're too old to register, grandpa.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func checkEmail(str string) error {
|
||||
if m, err := regexp.MatchString(`^[^@]+@[^@]+$`, str); !m {
|
||||
fmt.Println("err = ", err)
|
||||
return errors.New("Please enter a valid email address.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks if a valid date was passed.
|
||||
func checkDate(str string) error {
|
||||
_, err := time.Parse(mmddyyyyForm, str)
|
||||
if err != nil {
|
||||
_, err = time.Parse(yyyymmddForm, str)
|
||||
}
|
||||
if str == "" || err != nil {
|
||||
return errors.New("Please enter a valid Date.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks if the passed input is a known gender option
|
||||
func checkGender(str string) error {
|
||||
if str == "" {
|
||||
return nil
|
||||
}
|
||||
siblings := []string{"m", "f", "na"}
|
||||
if !isElementInSlice(str, siblings) {
|
||||
return errors.New("Please select a valid gender.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if all the values are known options.
|
||||
func checkSibling(strs []string) error {
|
||||
if strs == nil || len(strs) < 1 {
|
||||
return nil
|
||||
}
|
||||
siblings := []string{"m", "f"}
|
||||
if siblings != nil && !doSlicesIntersect(siblings, strs) {
|
||||
return errors.New("Please select a valid sibling")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks if the shirt size is a known option.
|
||||
func checkShirtSize(str string) error {
|
||||
if str == "" {
|
||||
return nil
|
||||
}
|
||||
shirts := []string{"s", "m", "l", "xl", "xxl"}
|
||||
if !isElementInSlice(str, shirts) {
|
||||
return errors.New("Please select a valid shirt size")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user